diff --git a/README.md b/README.md index 17859ea..e480c2e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,15 @@ A lightweight PostgreSQL driver for Deno focused on developer experience. [node-postgres](https://github.com/brianc/node-postgres) and [pq](https://github.com/lib/pq). -## Example +## Documentation + +The documentation is available on the `deno-postgres` website +[https://deno-postgres.com/](https://deno-postgres.com/) + +Join the [Discord](https://discord.gg/HEdTCvZUSf) as well! It's a good place to +discuss bugs and features before opening issues. + +## Examples ```ts // deno run --allow-net --allow-read mod.ts @@ -51,17 +59,6 @@ await client.connect(); await client.end(); ``` -For more examples, visit the documentation available at -[https://deno-postgres.com/](https://deno-postgres.com/) - -## Documentation - -The documentation is available on the deno-postgres website -[https://deno-postgres.com/](https://deno-postgres.com/) - -Join the [Discord](https://discord.gg/HEdTCvZUSf) as well! It's a good place to -discuss bugs and features before opening issues. - ## Contributing ### Prerequisites @@ -156,6 +153,22 @@ This situation will stabilize as `std` and `deno-postgres` approach version 1.0. | 1.17.0 | 0.15.0 | 0.17.1 | | | 1.40.0 | 0.17.2 | | Now available on JSR | +## Breaking changes + +Although `deno-postgres` is reasonably stable and robust, it is a WIP, and we're +still exploring the design. Expect some breaking changes as we reach version 1.0 +and enhance the feature set. Please check the Releases for more info on breaking +changes. Please reach out if there are any undocumented breaking changes. + +## Found issues? + +Please +[file an issue](https://github.com/denodrivers/postgres/issues/new/choose) with +any problems with the driver in this repository's issue section. If you would +like to help, please look at the +[issues](https://github.com/denodrivers/postgres/issues) as well. You can pick +up one of them and try to implement it. + ## Contributing guidelines When contributing to the repository, make sure to: diff --git a/connection/connection.ts b/connection/connection.ts index c062553..6cc0e03 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -32,6 +32,7 @@ import { BufWriter, delay, joinPath, + rgb24, yellow, } from "../deps.ts"; import { DeferredStack } from "../utils/deferred.ts"; @@ -68,6 +69,7 @@ import { INCOMING_TLS_MESSAGES, } from "./message_code.ts"; import { hashMd5Password } from "./auth.ts"; +import { isDebugOptionEnabled } from "../debug.ts"; // Work around unstable limitation type ConnectOptions = @@ -97,7 +99,25 @@ function assertSuccessfulAuthentication(auth_message: Message) { } function logNotice(notice: Notice) { - console.error(`${bold(yellow(notice.severity))}: ${notice.message}`); + if (notice.severity === "INFO") { + console.info( + `[ ${bold(rgb24(notice.severity, 0xff99ff))} ] : ${notice.message}`, + ); + } else if (notice.severity === "NOTICE") { + console.info(`[ ${bold(yellow(notice.severity))} ] : ${notice.message}`); + } else if (notice.severity === "WARNING") { + console.warn( + `[ ${bold(rgb24(notice.severity, 0xff9900))} ] : ${notice.message}`, + ); + } +} + +function logQuery(query: string) { + console.info(`[ ${bold(rgb24("QUERY", 0x00ccff))} ] : ${query}`); +} + +function logResults(rows: unknown[]) { + console.info(`[ ${bold(rgb24("RESULTS", 0x00cc00))} ] :`, rows); } const decoder = new TextDecoder(); @@ -695,7 +715,14 @@ export class Connection { break; case INCOMING_QUERY_MESSAGES.NOTICE_WARNING: { const notice = parseNoticeMessage(current_message); - logNotice(notice); + if ( + isDebugOptionEnabled( + "notices", + this.#connection_params.controls?.debug, + ) + ) { + logNotice(notice); + } result.warnings.push(notice); break; } @@ -819,6 +846,12 @@ export class Connection { /** * https://www.postgresql.org/docs/14/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY */ + async #preparedQuery( + query: Query, + ): Promise; + async #preparedQuery( + query: Query, + ): Promise; async #preparedQuery( query: Query, ): Promise { @@ -872,7 +905,14 @@ export class Connection { break; case INCOMING_QUERY_MESSAGES.NOTICE_WARNING: { const notice = parseNoticeMessage(current_message); - logNotice(notice); + if ( + isDebugOptionEnabled( + "notices", + this.#connection_params.controls?.debug, + ) + ) { + logNotice(notice); + } result.warnings.push(notice); break; } @@ -911,11 +951,23 @@ export class Connection { await this.#queryLock.pop(); try { + if ( + isDebugOptionEnabled("queries", this.#connection_params.controls?.debug) + ) { + logQuery(query.text); + } + let result: QueryArrayResult | QueryObjectResult; if (query.args.length === 0) { - return await this.#simpleQuery(query); + result = await this.#simpleQuery(query); } else { - return await this.#preparedQuery(query); + result = await this.#preparedQuery(query); + } + if ( + isDebugOptionEnabled("results", this.#connection_params.controls?.debug) + ) { + logResults(result.rows); } + return result; } catch (e) { if (e instanceof ConnectionError) { await this.end(); diff --git a/connection/connection_params.ts b/connection/connection_params.ts index 8201625..7b68ea9 100644 --- a/connection/connection_params.ts +++ b/connection/connection_params.ts @@ -2,6 +2,7 @@ import { parseConnectionUri } from "../utils/utils.ts"; import { ConnectionParamsError } from "../client/error.ts"; import { fromFileUrl, isAbsolute } from "../deps.ts"; import { OidType } from "../query/oid.ts"; +import { DebugControls } from "../debug.ts"; /** * The connection string must match the following URI structure. All parameters but database and user are optional @@ -115,6 +116,10 @@ export type DecoderFunction = (value: string, oid: number) => unknown; * Control the behavior for the client instance */ export type ClientControls = { + /** + * Debugging options + */ + debug?: DebugControls; /** * The strategy to use when decoding results data * diff --git a/debug.ts b/debug.ts new file mode 100644 index 0000000..b824b80 --- /dev/null +++ b/debug.ts @@ -0,0 +1,28 @@ +/** + * Controls debugging behavior. If set to `true`, all debug options are enabled. + * If set to `false`, all debug options are disabled. Can also be an object with + * specific debug options to enable. + * + * {@default false} + */ +export type DebugControls = DebugOptions | boolean; + +type DebugOptions = { + /** Log queries */ + queries?: boolean; + /** Log INFO, NOTICE, and WARNING raised database messages */ + notices?: boolean; + /** Log results */ + results?: boolean; +}; + +export const isDebugOptionEnabled = ( + option: keyof DebugOptions, + options?: DebugControls, +): boolean => { + if (typeof options === "boolean") { + return options; + } + + return !!options?.[option]; +}; diff --git a/deps.ts b/deps.ts index 1dcd6ce..3d10e31 100644 --- a/deps.ts +++ b/deps.ts @@ -6,7 +6,11 @@ export { BufWriter } from "https://deno.land/std@0.214.0/io/buf_writer.ts"; export { copy } from "https://deno.land/std@0.214.0/bytes/copy.ts"; export { crypto } from "https://deno.land/std@0.214.0/crypto/crypto.ts"; export { delay } from "https://deno.land/std@0.214.0/async/delay.ts"; -export { bold, yellow } from "https://deno.land/std@0.214.0/fmt/colors.ts"; +export { + bold, + rgb24, + yellow, +} from "https://deno.land/std@0.214.0/fmt/colors.ts"; export { fromFileUrl, isAbsolute, diff --git a/docs/README.md b/docs/README.md index f9c7559..f66b538 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1393,3 +1393,60 @@ await transaction.queryArray`INSERT INTO DONT_DELETE_ME VALUES (2)`; // Still in await transaction.commit(); // Transaction ends, client gets unlocked ``` + +## Debugging + +The driver can provide different types of logs if as needed. By default, logs +are disabled to keep your environment as uncluttered as possible. Logging can be +enabled by using the `debug` option in the Client `controls` parameter. Pass +`true` to enable all logs, or turn on logs granulary by enabling the following +options: + +- `queries` : Logs all SQL queries executed by the client +- `notices` : Logs database messages (INFO, NOTICE, WARNING)) +- `results` : Logs the result of the queries + +### Example + +```ts +// debug_test.ts +import { Client } from "./mod.ts"; + +const client = new Client({ + user: "postgres", + database: "postgres", + hostname: "localhost", + port: 5432, + password: "postgres", + controls: { + // the same as `debug: true` + debug: { + queries: true, + notices: true, + results: true, + }, + }, +}); + +await client.connect(); + +const result = await client.queryObject`SELECT public.get_some_user()`; + +await client.end(); +``` + +```sql +-- example database function that raises messages +CREATE OR REPLACE FUNCTION public.get_uuid() + RETURNS uuid LANGUAGE plpgsql +AS $function$ + BEGIN + RAISE INFO 'This function generates a random UUID :)'; + RAISE NOTICE 'A UUID takes up 128 bits in memory.'; + RAISE WARNING 'UUIDs must follow a specific format and lenght in order to be valid!'; + RETURN gen_random_uuid(); + END; +$function$;; +``` + +![debug-output](debug-output.png) diff --git a/docs/debug-output.png b/docs/debug-output.png new file mode 100644 index 0000000..02277a8 Binary files /dev/null and b/docs/debug-output.png differ