From 08320fa6fa724f176ee53aa355f9fdd529ecfd8f Mon Sep 17 00:00:00 2001 From: Hector Ayala Date: Fri, 16 Feb 2024 10:45:30 -0400 Subject: [PATCH 1/4] fix: use camel case for camelCase option (#466) --- docs/README.md | 8 ++++---- query/query.ts | 14 +++++++------- tests/query_client_test.ts | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/README.md b/docs/README.md index 528c2d25..f9c75599 100644 --- a/docs/README.md +++ b/docs/README.md @@ -856,14 +856,14 @@ for that such as aliasing every query field that is done to the database, one easy built-in solution allows developers to transform the incoming query names into the casing of their preference without any extra steps -##### Camelcase +##### Camel case -To transform a query result into camelcase, you only need to provide the -`camelcase` option on your query call +To transform a query result into camel case, you only need to provide the +`camelCase` option on your query call ```ts const { rows: result } = await client.queryObject({ - camelcase: true, + camelCase: true, text: "SELECT FIELD_X, FIELD_Y FROM MY_TABLE", }); diff --git a/query/query.ts b/query/query.ts index 0bb39d7b..58977459 100644 --- a/query/query.ts +++ b/query/query.ts @@ -132,19 +132,19 @@ export interface QueryObjectOptions extends QueryOptions { // TODO // Support multiple case options /** - * Enabling camelcase will transform any snake case field names coming from the database into camel case ones + * Enabling camel case will transform any snake case field names coming from the database into camel case ones * * Ex: `SELECT 1 AS my_field` will return `{ myField: 1 }` * * This won't have any effect if you explicitly set the field names with the `fields` parameter */ - camelcase?: boolean; + camelCase?: boolean; /** * This parameter supersedes query column names coming from the databases in the order they were provided. * Fields must be unique and be in the range of (a-zA-Z0-9_), otherwise the query will throw before execution. * A field can not start with a number, just like JavaScript variables * - * This setting overrides the camelcase option + * This setting overrides the camel case option * * Ex: `SELECT 'A', 'B' AS my_field` with fields `["field_1", "field_2"]` will return `{ field_1: "A", field_2: "B" }` */ @@ -324,7 +324,7 @@ export class QueryObjectResult< this.columns = this.query.fields; } else { let column_names: string[]; - if (this.query.camelcase) { + if (this.query.camelCase) { column_names = this.rowDescription.columns.map((column) => snakecaseToCamelcase(column.name) ); @@ -380,7 +380,7 @@ export class QueryObjectResult< */ export class Query { public args: EncodedArg[]; - public camelcase?: boolean; + public camelCase?: boolean; /** * The explicitly set fields for the query result, they have been validated beforehand * for duplicates and invalid names @@ -408,7 +408,7 @@ export class Query { this.text = config_or_text; this.args = args.map(encodeArgument); } else { - const { camelcase, encoder = encodeArgument, fields } = config_or_text; + const { camelCase, encoder = encodeArgument, fields } = config_or_text; let { args = [], text } = config_or_text; // Check that the fields passed are valid and can be used to map @@ -432,7 +432,7 @@ export class Query { this.fields = fields; } - this.camelcase = camelcase; + this.camelCase = camelCase; if (!Array.isArray(args)) { [text, args] = objectQueryToQueryArgs(text, args); diff --git a/tests/query_client_test.ts b/tests/query_client_test.ts index 84e05f94..9def424b 100644 --- a/tests/query_client_test.ts +++ b/tests/query_client_test.ts @@ -796,7 +796,7 @@ Deno.test( ); Deno.test( - "Object query field names aren't transformed when camelcase is disabled", + "Object query field names aren't transformed when camel case is disabled", withClient(async (client) => { const record = { pos_x: "100", @@ -806,7 +806,7 @@ Deno.test( const { rows: result } = await client.queryObject({ args: [record.pos_x, record.pos_y, record.prefix_name_suffix], - camelcase: false, + camelCase: false, text: "SELECT $1 AS POS_X, $2 AS POS_Y, $3 AS PREFIX_NAME_SUFFIX", }); @@ -815,7 +815,7 @@ Deno.test( ); Deno.test( - "Object query field names are transformed when camelcase is enabled", + "Object query field names are transformed when camel case is enabled", withClient(async (client) => { const record = { posX: "100", @@ -825,7 +825,7 @@ Deno.test( const { rows: result } = await client.queryObject({ args: [record.posX, record.posY, record.prefixNameSuffix], - camelcase: true, + camelCase: true, text: "SELECT $1 AS POS_X, $2 AS POS_Y, $3 AS PREFIX_NAME_SUFFIX", }); @@ -846,13 +846,13 @@ Deno.test( ); Deno.test( - "Object query explicit fields override camelcase", + "Object query explicit fields override camel case", withClient(async (client) => { const record = { field_1: "A", field_2: "B", field_3: "C" }; const { rows: result } = await client.queryObject({ args: [record.field_1, record.field_2, record.field_3], - camelcase: true, + camelCase: true, fields: ["field_1", "field_2", "field_3"], text: "SELECT $1 AS POS_X, $2 AS POS_Y, $3 AS PREFIX_NAME_SUFFIX", }); @@ -888,7 +888,7 @@ Deno.test( await assertRejects( () => client.queryObject({ - camelcase: true, + camelCase: true, text: `SELECT 1 AS "fieldX", 2 AS field_x`, }), Error, From 698360d6555995c775e3b7fcff0b5b466c8c303a Mon Sep 17 00:00:00 2001 From: Hector Ayala Date: Sat, 17 Feb 2024 23:39:28 -0400 Subject: [PATCH 2/4] Feat: Add debugging logs control (#467) * feat: add debugging controls for logs * docs: format Readme file * chore: make debug control optional * chore: update docs * chore: format files * chore: update docs * chore: add color formatting and severity level notice logging. * chore: update debug docs with example * chore: update readme doc --- README.md | 37 ++++++++++++------- connection/connection.ts | 62 +++++++++++++++++++++++++++++--- connection/connection_params.ts | 5 +++ debug.ts | 28 +++++++++++++++ deps.ts | 6 +++- docs/README.md | 57 +++++++++++++++++++++++++++++ docs/debug-output.png | Bin 0 -> 28627 bytes 7 files changed, 177 insertions(+), 18 deletions(-) create mode 100644 debug.ts create mode 100644 docs/debug-output.png diff --git a/README.md b/README.md index 17859ea7..e480c2e1 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 c062553c..6cc0e037 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 82016253..7b68ea9c 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 00000000..b824b809 --- /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 1dcd6cea..3d10e31c 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 f9c75599..f66b5385 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 0000000000000000000000000000000000000000..02277a8d09a9719ebc024e8ee40503f2f453566f GIT binary patch literal 28627 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 literal 0 HcmV?d00001 From cfcaa5aeb7eb7a7b0abb8e05893b884db68d3660 Mon Sep 17 00:00:00 2001 From: Hector Ayala Date: Sun, 18 Feb 2024 01:00:06 -0400 Subject: [PATCH 3/4] chore: add query to error if enabled in debug (#469) --- client/error.ts | 8 +++++++- connection/connection.ts | 20 ++++++++++++++++++-- debug.ts | 8 +++++--- docs/README.md | 9 +++++---- tests/query_client_test.ts | 38 ++++++++++++++++++++++++++++++++++++++ tests/test_deps.ts | 1 + 6 files changed, 74 insertions(+), 10 deletions(-) diff --git a/client/error.ts b/client/error.ts index a7b97566..7fc4cccd 100644 --- a/client/error.ts +++ b/client/error.ts @@ -35,12 +35,18 @@ export class PostgresError extends Error { */ public fields: Notice; + /** + * The query that caused the error + */ + public query: string | undefined; + /** * Create a new PostgresError */ - constructor(fields: Notice) { + constructor(fields: Notice, query?: string) { super(fields.message); this.fields = fields; + this.query = query; this.name = "PostgresError"; } } diff --git a/connection/connection.ts b/connection/connection.ts index 6cc0e037..7ce3d38d 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -694,7 +694,15 @@ export class Connection { while (current_message.type !== INCOMING_QUERY_MESSAGES.READY) { switch (current_message.type) { case ERROR_MESSAGE: - error = new PostgresError(parseNoticeMessage(current_message)); + error = new PostgresError( + parseNoticeMessage(current_message), + isDebugOptionEnabled( + "queryInError", + this.#connection_params.controls?.debug, + ) + ? query.text + : undefined, + ); break; case INCOMING_QUERY_MESSAGES.COMMAND_COMPLETE: { result.handleCommandComplete( @@ -881,7 +889,15 @@ export class Connection { while (current_message.type !== INCOMING_QUERY_MESSAGES.READY) { switch (current_message.type) { case ERROR_MESSAGE: { - error = new PostgresError(parseNoticeMessage(current_message)); + error = new PostgresError( + parseNoticeMessage(current_message), + isDebugOptionEnabled( + "queryInError", + this.#connection_params.controls?.debug, + ) + ? query.text + : undefined, + ); break; } case INCOMING_QUERY_MESSAGES.BIND_COMPLETE: diff --git a/debug.ts b/debug.ts index b824b809..1b477888 100644 --- a/debug.ts +++ b/debug.ts @@ -8,12 +8,14 @@ export type DebugControls = DebugOptions | boolean; type DebugOptions = { - /** Log queries */ + /** Log all queries */ queries?: boolean; - /** Log INFO, NOTICE, and WARNING raised database messages */ + /** Log all INFO, NOTICE, and WARNING raised database messages */ notices?: boolean; - /** Log results */ + /** Log all results */ results?: boolean; + /** Include the SQL query that caused an error in the PostgresError object */ + queryInError?: boolean; }; export const isDebugOptionEnabled = ( diff --git a/docs/README.md b/docs/README.md index f66b5385..477b86f4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1403,8 +1403,10 @@ enabled by using the `debug` option in the Client `controls` parameter. Pass options: - `queries` : Logs all SQL queries executed by the client -- `notices` : Logs database messages (INFO, NOTICE, WARNING)) -- `results` : Logs the result of the queries +- `notices` : Logs all database messages (INFO, NOTICE, WARNING)) +- `results` : Logs all the result of the queries +- `queryInError` : Includes the SQL query that caused an error in the + PostgresError object ### Example @@ -1419,7 +1421,6 @@ const client = new Client({ port: 5432, password: "postgres", controls: { - // the same as `debug: true` debug: { queries: true, notices: true, @@ -1430,7 +1431,7 @@ const client = new Client({ await client.connect(); -const result = await client.queryObject`SELECT public.get_some_user()`; +await client.queryObject`SELECT public.get_uuid()`; await client.end(); ``` diff --git a/tests/query_client_test.ts b/tests/query_client_test.ts index 9def424b..0e71da69 100644 --- a/tests/query_client_test.ts +++ b/tests/query_client_test.ts @@ -8,6 +8,7 @@ import { import { assert, assertEquals, + assertInstanceOf, assertObjectMatch, assertRejects, assertThrows, @@ -284,6 +285,43 @@ Deno.test( ), ); +Deno.test( + "Debug query not in error", + withClient(async (client) => { + const invalid_query = "SELECT this_has $ 'syntax_error';"; + try { + await client.queryObject(invalid_query); + } catch (error) { + assertInstanceOf(error, PostgresError); + assertEquals(error.message, 'syntax error at or near "$"'); + assertEquals(error.query, undefined); + } + }), +); + +Deno.test( + "Debug query in error", + withClient( + async (client) => { + const invalid_query = "SELECT this_has $ 'syntax_error';"; + try { + await client.queryObject(invalid_query); + } catch (error) { + assertInstanceOf(error, PostgresError); + assertEquals(error.message, 'syntax error at or near "$"'); + assertEquals(error.query, invalid_query); + } + }, + { + controls: { + debug: { + queryInError: true, + }, + }, + }, + ), +); + Deno.test( "Array arguments", withClient(async (client) => { diff --git a/tests/test_deps.ts b/tests/test_deps.ts index 1fce7027..3ec05aaa 100644 --- a/tests/test_deps.ts +++ b/tests/test_deps.ts @@ -2,6 +2,7 @@ export * from "../deps.ts"; export { assert, assertEquals, + assertInstanceOf, assertNotEquals, assertObjectMatch, assertRejects, From df6a6490cf9e2f4e6c3fd82ba5320d44b0d34741 Mon Sep 17 00:00:00 2001 From: Hector Ayala Date: Sun, 18 Feb 2024 01:01:21 -0400 Subject: [PATCH 4/4] chore: bump version (#468) --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 10162a4f..51a2bcf8 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "lock": false, "name": "@bartlomieju/postgres", - "version": "0.18.1", + "version": "0.19.0", "exports": "./mod.ts" }