diff --git a/connection/connection.ts b/connection/connection.ts index e7278c6c..c062553c 100644 --- a/connection/connection.ts +++ b/connection/connection.ts @@ -685,7 +685,7 @@ export class Connection { case INCOMING_QUERY_MESSAGES.DATA_ROW: { const row_data = parseRowDataMessage(current_message); try { - result.insertRow(row_data); + result.insertRow(row_data, this.#connection_params.controls); } catch (e) { error = e; } @@ -862,7 +862,7 @@ export class Connection { case INCOMING_QUERY_MESSAGES.DATA_ROW: { const row_data = parseRowDataMessage(current_message); try { - result.insertRow(row_data); + result.insertRow(row_data, this.#connection_params.controls); } catch (e) { error = e; } diff --git a/connection/connection_params.ts b/connection/connection_params.ts index c3037736..bf006a21 100644 --- a/connection/connection_params.ts +++ b/connection/connection_params.ts @@ -91,19 +91,43 @@ export interface TLSOptions { caCertificates: string[]; } +/** + * Control the behavior for the client instance + */ +export type ClientControls = { + /** + * The strategy to use when decoding binary fields + * + * `string` : all values are returned as string, and the user has to take care of parsing + * `auto` : deno-postgres parses the data into JS objects (as many as possible implemented, non-implemented parsers would still return strings) + * + * Default: `auto` + * + * Future strategies might include: + * - `strict` : deno-postgres parses the data into JS objects, and if a parser is not implemented, it throws an error + * - `raw` : the data is returned as Uint8Array + */ + decodeStrategy?: "string" | "auto"; +}; + /** The Client database connection options */ export type ClientOptions = { /** Name of the application connecing to the database */ applicationName?: string; /** Additional connection options */ connection?: Partial; + /** Control the client behavior */ + controls?: ClientControls; /** The database name */ database?: string; /** The name of the host */ hostname?: string; /** The type of host connection */ host_type?: "tcp" | "socket"; - /** Additional client options */ + /** + * Additional connection URI options + * https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS + */ options?: string | Record; /** The database user password */ password?: string; @@ -118,14 +142,18 @@ export type ClientOptions = { /** The configuration options required to set up a Client instance */ export type ClientConfiguration = & Required< - Omit + Omit< + ClientOptions, + "password" | "port" | "tls" | "connection" | "options" | "controls" + > > & { + connection: ConnectionOptions; + controls?: ClientControls; + options: Record; password?: string; port: number; tls: TLSOptions; - connection: ConnectionOptions; - options: Record; }; function formatMissingParams(missingParams: string[]) { @@ -168,7 +196,7 @@ function assertRequiredOptions( // TODO // Support more options from the spec -/** options from URI per https://www.postgresql.org/docs/14/libpq-connect.html#LIBPQ-CONNSTRING */ +/** options from URI per https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING */ interface PostgresUri { application_name?: string; dbname?: string; @@ -447,6 +475,7 @@ export function createParams( caCertificates: params?.tls?.caCertificates ?? [], }, user: params.user ?? pgEnv.user, + controls: params.controls, }; assertRequiredOptions( diff --git a/query/decode.ts b/query/decode.ts index b09940d6..1afe82a0 100644 --- a/query/decode.ts +++ b/query/decode.ts @@ -35,6 +35,7 @@ import { decodeTid, decodeTidArray, } from "./decoders.ts"; +import { ClientControls } from "../connection/connection_params.ts"; export class Column { constructor( @@ -58,7 +59,7 @@ const decoder = new TextDecoder(); // TODO // Decode binary fields function decodeBinary() { - throw new Error("Not implemented!"); + throw new Error("Decoding binary data is not implemented!"); } function decodeText(value: Uint8Array, typeOid: number) { @@ -208,10 +209,19 @@ function decodeText(value: Uint8Array, typeOid: number) { } } -export function decode(value: Uint8Array, column: Column) { +export function decode( + value: Uint8Array, + column: Column, + controls?: ClientControls, +) { if (column.format === Format.BINARY) { return decodeBinary(); } else if (column.format === Format.TEXT) { + // If the user has specified a decode strategy, use that + if (controls?.decodeStrategy === "string") { + return decoder.decode(value); + } + // default to 'auto' mode, which uses the typeOid to determine the decoding strategy return decodeText(value, column.typeOid); } else { throw new Error(`Unknown column format: ${column.format}`); diff --git a/query/query.ts b/query/query.ts index 46f9b3c5..0bb39d7b 100644 --- a/query/query.ts +++ b/query/query.ts @@ -1,6 +1,7 @@ import { encodeArgument, type EncodedArg } from "./encode.ts"; import { type Column, decode } from "./decode.ts"; import { type Notice } from "../connection/message.ts"; +import { type ClientControls } from "../connection/connection_params.ts"; // TODO // Limit the type of parameters that can be passed @@ -242,7 +243,7 @@ export class QueryArrayResult< /** * Insert a row into the result */ - insertRow(row_data: Uint8Array[]) { + insertRow(row_data: Uint8Array[], controls?: ClientControls) { if (!this.rowDescription) { throw new Error( "The row descriptions required to parse the result data weren't initialized", @@ -256,7 +257,7 @@ export class QueryArrayResult< if (raw_value === null) { return null; } - return decode(raw_value, column); + return decode(raw_value, column, controls); }) as T; this.rows.push(row); @@ -303,7 +304,7 @@ export class QueryObjectResult< /** * Insert a row into the result */ - insertRow(row_data: Uint8Array[]) { + insertRow(row_data: Uint8Array[], controls?: ClientControls) { if (!this.rowDescription) { throw new Error( "The row description required to parse the result data wasn't initialized", @@ -364,7 +365,7 @@ export class QueryObjectResult< if (raw_value === null) { row[columns[index]] = null; } else { - row[columns[index]] = decode(raw_value, current_column); + row[columns[index]] = decode(raw_value, current_column, controls); } return row; diff --git a/tests/config.ts b/tests/config.ts index fbd2b45f..17bf701c 100644 --- a/tests/config.ts +++ b/tests/config.ts @@ -1,4 +1,7 @@ -import { ClientConfiguration } from "../connection/connection_params.ts"; +import { + ClientConfiguration, + ClientOptions, +} from "../connection/connection_params.ts"; import config_file1 from "./config.json" with { type: "json" }; type TcpConfiguration = Omit & { @@ -67,17 +70,20 @@ export const getClearSocketConfiguration = (): SocketConfiguration => { }; /** MD5 authenticated user with privileged access to the database */ -export const getMainConfiguration = (): TcpConfiguration => { +export const getMainConfiguration = ( + _config?: ClientOptions, +): TcpConfiguration => { return { applicationName: config.postgres_md5.applicationName, database: config.postgres_md5.database, hostname: config.postgres_md5.hostname, - host_type: "tcp", - options: {}, password: config.postgres_md5.password, + user: config.postgres_md5.users.main, + ..._config, + options: {}, port: config.postgres_md5.port, tls: enabled_tls, - user: config.postgres_md5.users.main, + host_type: "tcp", }; }; diff --git a/tests/decode_test.ts b/tests/decode_test.ts index 000cbab4..06512911 100644 --- a/tests/decode_test.ts +++ b/tests/decode_test.ts @@ -1,3 +1,4 @@ +import { Column, decode } from "../query/decode.ts"; import { decodeBigint, decodeBigintArray, @@ -17,6 +18,7 @@ import { decodeTid, } from "../query/decoders.ts"; import { assertEquals, assertThrows } from "./test_deps.ts"; +import { Oid } from "../query/oid.ts"; Deno.test("decodeBigint", function () { assertEquals(decodeBigint("18014398509481984"), 18014398509481984n); @@ -248,3 +250,78 @@ Deno.test("decodeTid", function () { 29383838509481984n, ]); }); + +Deno.test("decode strategy", function () { + const testValues = [ + { + value: "40", + column: new Column("test", 0, 0, Oid.int4, 0, 0, 0), + parsed: 40, + }, + { + value: "my_value", + column: new Column("test", 0, 0, Oid.text, 0, 0, 0), + parsed: "my_value", + }, + { + value: "[(100,50),(350,350)]", + column: new Column("test", 0, 0, Oid.path, 0, 0, 0), + parsed: [ + { x: "100", y: "50" }, + { x: "350", y: "350" }, + ], + }, + { + value: '{"value_1","value_2","value_3"}', + column: new Column("test", 0, 0, Oid.text_array, 0, 0, 0), + parsed: ["value_1", "value_2", "value_3"], + }, + { + value: "1997-12-17 07:37:16-08", + column: new Column("test", 0, 0, Oid.timestamp, 0, 0, 0), + parsed: new Date("1997-12-17 07:37:16-08"), + }, + { + value: "Yes", + column: new Column("test", 0, 0, Oid.bool, 0, 0, 0), + parsed: true, + }, + { + value: "<(12.4,2),3.5>", + column: new Column("test", 0, 0, Oid.circle, 0, 0, 0), + parsed: { point: { x: "12.4", y: "2" }, radius: "3.5" }, + }, + { + value: '{"test":1,"val":"foo","example":[1,2,false]}', + column: new Column("test", 0, 0, Oid.jsonb, 0, 0, 0), + parsed: { test: 1, val: "foo", example: [1, 2, false] }, + }, + { + value: "18014398509481984", + column: new Column("test", 0, 0, Oid.int8, 0, 0, 0), + parsed: 18014398509481984n, + }, + { + value: "{3.14,1.11,0.43,200}", + column: new Column("test", 0, 0, Oid.float4_array, 0, 0, 0), + parsed: [3.14, 1.11, 0.43, 200], + }, + ]; + + for (const testValue of testValues) { + const encodedValue = new TextEncoder().encode(testValue.value); + + // check default behavior + assertEquals(decode(encodedValue, testValue.column), testValue.parsed); + // check 'auto' behavior + assertEquals( + decode(encodedValue, testValue.column, { decodeStrategy: "auto" }), + testValue.parsed, + ); + // check 'string' behavior + assertEquals( + decode(encodedValue, testValue.column, { decodeStrategy: "string" }), + testValue.value, + ); + } +}); diff --git a/tests/query_client_test.ts b/tests/query_client_test.ts index bd6c5014..4c4217bf 100644 --- a/tests/query_client_test.ts +++ b/tests/query_client_test.ts @@ -14,12 +14,14 @@ import { } from "./test_deps.ts"; import { getMainConfiguration } from "./config.ts"; import { PoolClient, QueryClient } from "../client.ts"; +import { ClientOptions } from "../connection/connection_params.ts"; function withClient( t: (client: QueryClient) => void | Promise, + config?: ClientOptions, ) { async function clientWrapper() { - const client = new Client(getMainConfiguration()); + const client = new Client(getMainConfiguration(config)); try { await client.connect(); await t(client); @@ -29,7 +31,7 @@ function withClient( } async function poolWrapper() { - const pool = new Pool(getMainConfiguration(), 1); + const pool = new Pool(getMainConfiguration(config), 1); let client; try { client = await pool.connect(); @@ -112,15 +114,56 @@ Deno.test( }), ); +Deno.test( + "Decode strategy - auto", + withClient( + async (client) => { + const result = await client.queryObject( + `SELECT ARRAY[1, 2, 3] AS _int_array, 3.14::REAL AS _float, 'DATA' AS _text, '{"test": "foo", "arr": [1,2,3]}'::JSONB AS _json, 'Y'::BOOLEAN AS _bool`, + ); + + assertEquals(result.rows, [ + { + _bool: true, + _float: 3.14, + _int_array: [1, 2, 3], + _json: { test: "foo", arr: [1, 2, 3] }, + _text: "DATA", + }, + ]); + }, + { controls: { decodeStrategy: "auto" } }, + ), +); + +Deno.test( + "Decode strategy - string", + withClient( + async (client) => { + const result = await client.queryObject( + `SELECT ARRAY[1, 2, 3] AS _int_array, 3.14::REAL AS _float, 'DATA' AS _text, '{"test": "foo", "arr": [1,2,3]}'::JSONB AS _json, 'Y'::BOOLEAN AS _bool`, + ); + + assertEquals(result.rows, [ + { + _bool: "t", + _float: "3.14", + _int_array: "{1,2,3}", + _json: '{"arr": [1, 2, 3], "test": "foo"}', + _text: "DATA", + }, + ]); + }, + { controls: { decodeStrategy: "string" } }, + ), +); + Deno.test( "Array arguments", withClient(async (client) => { { const value = "1"; - const result = await client.queryArray( - "SELECT $1", - [value], - ); + const result = await client.queryArray("SELECT $1", [value]); assertEquals(result.rows, [[value]]); } @@ -135,10 +178,7 @@ Deno.test( { const value = "3"; - const result = await client.queryObject( - "SELECT $1 AS ID", - [value], - ); + const result = await client.queryObject("SELECT $1 AS ID", [value]); assertEquals(result.rows, [{ id: value }]); } @@ -158,10 +198,7 @@ Deno.test( withClient(async (client) => { { const value = "1"; - const result = await client.queryArray( - "SELECT $id", - { id: value }, - ); + const result = await client.queryArray("SELECT $id", { id: value }); assertEquals(result.rows, [[value]]); } @@ -176,10 +213,9 @@ Deno.test( { const value = "3"; - const result = await client.queryObject( - "SELECT $id as ID", - { id: value }, - ); + const result = await client.queryObject("SELECT $id as ID", { + id: value, + }); assertEquals(result.rows, [{ id: value }]); } @@ -218,10 +254,9 @@ Deno.test( await client.queryArray`CREATE TEMP TABLE PREPARED_STATEMENT_ERROR (X INT)`; await assertRejects(() => - client.queryArray( - "INSERT INTO PREPARED_STATEMENT_ERROR VALUES ($1)", - ["TEXT"], - ) + client.queryArray("INSERT INTO PREPARED_STATEMENT_ERROR VALUES ($1)", [ + "TEXT", + ]) ); const { rows } = await client.queryObject<{ result: number }>({ @@ -237,10 +272,7 @@ Deno.test( "Array query can handle multiple query failures at once", withClient(async (client) => { await assertRejects( - () => - client.queryArray( - "SELECT 1; SELECT '2'::INT; SELECT 'A'::INT", - ), + () => client.queryArray("SELECT 1; SELECT '2'::INT; SELECT 'A'::INT"), PostgresError, "invalid input syntax for type integer", ); @@ -257,9 +289,7 @@ Deno.test( Deno.test( "Array query handles error during data processing", withClient(async (client) => { - await assertRejects( - () => client.queryObject`SELECT 'A' AS X, 'B' AS X`, - ); + await assertRejects(() => client.queryObject`SELECT 'A' AS X, 'B' AS X`); const value = "193"; const { rows: result_2 } = await client.queryObject`SELECT ${value} AS B`; @@ -292,11 +322,13 @@ Deno.test( withClient(async (client) => { await client.queryArray`CREATE TEMP TABLE PREPARED_STATEMENT_ERROR (X INT)`; - await assertRejects(() => - client.queryArray( - "INSERT INTO PREPARED_STATEMENT_ERROR VALUES ($1)", - ["TEXT"], - ), PostgresError); + await assertRejects( + () => + client.queryArray("INSERT INTO PREPARED_STATEMENT_ERROR VALUES ($1)", [ + "TEXT", + ]), + PostgresError, + ); const result = "handled"; @@ -313,9 +345,7 @@ Deno.test( Deno.test( "Prepared query handles error during data processing", withClient(async (client) => { - await assertRejects( - () => client.queryObject`SELECT ${1} AS A, ${2} AS A`, - ); + await assertRejects(() => client.queryObject`SELECT ${1} AS A, ${2} AS A`); const value = "z"; const { rows: result_2 } = await client.queryObject`SELECT ${value} AS B`; @@ -329,10 +359,10 @@ Deno.test( const item_1 = "Test;Azer"; const item_2 = "123;456"; - const { rows: result_1 } = await client.queryArray( - `SELECT ARRAY[$1, $2]`, - [item_1, item_2], - ); + const { rows: result_1 } = await client.queryArray(`SELECT ARRAY[$1, $2]`, [ + item_1, + item_2, + ]); assertEquals(result_1[0], [[item_1, item_2]]); }), ); @@ -441,10 +471,7 @@ Deno.test( text: `SELECT 1`, fields: ["res"], }); - assertEquals( - result[0].res, - 1, - ); + assertEquals(result[0].res, 1); assertEquals(client.connected, true); }), @@ -470,9 +497,7 @@ Deno.test( Deno.test( "Handling of query notices", withClient(async (client) => { - await client.queryArray( - "CREATE TEMP TABLE NOTICE_TEST (ABC INT);", - ); + await client.queryArray("CREATE TEMP TABLE NOTICE_TEST (ABC INT);"); const { warnings } = await client.queryArray( "CREATE TEMP TABLE IF NOT EXISTS NOTICE_TEST (ABC INT);", ); @@ -544,10 +569,9 @@ Deno.test( assertEquals(result_1[0][0], expectedBytes); - const { rows: result_2 } = await client.queryArray( - "SELECT $1::BYTEA", - [expectedBytes], - ); + const { rows: result_2 } = await client.queryArray("SELECT $1::BYTEA", [ + expectedBytes, + ]); assertEquals(result_2[0][0], expectedBytes); }), ); @@ -584,10 +608,9 @@ Deno.test( assertEquals(result.rowCount, 2); // parameterized delete - result = await client.queryArray( - "DELETE FROM METADATA WHERE VALUE = $1", - [300], - ); + result = await client.queryArray("DELETE FROM METADATA WHERE VALUE = $1", [ + 300, + ]); assertEquals(result.command, "DELETE"); assertEquals(result.rowCount, 1); @@ -626,7 +649,7 @@ Deno.test( `); assertEquals(result, [ - { "very_very_very_very_very_very_very_very_very_very_very_long_nam": 1 }, + { very_very_very_very_very_very_very_very_very_very_very_long_nam: 1 }, ]); assert(warnings[0].message.includes("will be truncated")); @@ -756,10 +779,7 @@ Deno.test( fields: ["a"], }); - assertEquals( - result_1[0].a, - 1, - ); + assertEquals(result_1[0].a, 1); await assertRejects( async () => { @@ -848,9 +868,10 @@ Deno.test( withClient(async (client) => { const value = { x: "A", y: "B" }; - const { rows } = await client.queryObject< - { x: string; y: string } - >`SELECT ${value.x} AS x, ${value.y} AS y`; + const { rows } = await client.queryObject<{ + x: string; + y: string; + }>`SELECT ${value.x} AS x, ${value.y} AS y`; assertEquals(rows[0], value); }), @@ -883,18 +904,18 @@ Deno.test( await transaction.queryArray`CREATE TEMP TABLE TEST (X INTEGER)`; const savepoint = await transaction.savepoint("table_creation"); await transaction.queryArray`INSERT INTO TEST (X) VALUES (1)`; - const query_1 = await transaction.queryObject< - { x: number } - >`SELECT X FROM TEST`; + const query_1 = await transaction.queryObject<{ + x: number; + }>`SELECT X FROM TEST`; assertEquals( query_1.rows[0].x, 1, "Operation was not executed inside transaction", ); await transaction.rollback(savepoint); - const query_2 = await transaction.queryObject< - { x: number } - >`SELECT X FROM TEST`; + const query_2 = await transaction.queryObject<{ + x: number; + }>`SELECT X FROM TEST`; assertEquals( query_2.rowCount, 0, @@ -953,21 +974,21 @@ Deno.test( await transaction_rr.begin(); // This locks the current value of the test table - await transaction_rr.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + await transaction_rr.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; // Modify data outside the transaction await client_2.queryArray`UPDATE FOR_TRANSACTION_TEST SET X = 2`; - const { rows: query_1 } = await client_2.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + const { rows: query_1 } = await client_2.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; assertEquals(query_1, [{ x: 2 }]); - const { rows: query_2 } = await transaction_rr.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + const { rows: query_2 } = await transaction_rr.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; assertEquals( query_2, [{ x: 1 }], @@ -976,9 +997,9 @@ Deno.test( await transaction_rr.commit(); - const { rows: query_3 } = await client_1.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + const { rows: query_3 } = await client_1.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; assertEquals( query_3, [{ x: 2 }], @@ -1007,9 +1028,9 @@ Deno.test( await transaction_rr.begin(); // This locks the current value of the test table - await transaction_rr.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + await transaction_rr.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; // Modify data outside the transaction await client_2.queryArray`UPDATE FOR_TRANSACTION_TEST SET X = 2`; @@ -1021,9 +1042,9 @@ Deno.test( "A serializable transaction should throw if the data read in the transaction has been modified externally", ); - const { rows: query_3 } = await client_1.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + const { rows: query_3 } = await client_1.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; assertEquals( query_3, [{ x: 2 }], @@ -1064,23 +1085,22 @@ Deno.test( await client_1.queryArray`DROP TABLE IF EXISTS FOR_TRANSACTION_TEST`; await client_1.queryArray`CREATE TABLE FOR_TRANSACTION_TEST (X INTEGER)`; await client_1.queryArray`INSERT INTO FOR_TRANSACTION_TEST (X) VALUES (1)`; - const transaction_1 = client_1.createTransaction( - "transactionSnapshot1", - { isolation_level: "repeatable_read" }, - ); + const transaction_1 = client_1.createTransaction("transactionSnapshot1", { + isolation_level: "repeatable_read", + }); await transaction_1.begin(); // This locks the current value of the test table - await transaction_1.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + await transaction_1.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; // Modify data outside the transaction await client_2.queryArray`UPDATE FOR_TRANSACTION_TEST SET X = 2`; - const { rows: query_1 } = await transaction_1.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + const { rows: query_1 } = await transaction_1.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; assertEquals( query_1, [{ x: 1 }], @@ -1089,15 +1109,15 @@ Deno.test( const snapshot = await transaction_1.getSnapshot(); - const transaction_2 = client_2.createTransaction( - "transactionSnapshot2", - { isolation_level: "repeatable_read", snapshot }, - ); + const transaction_2 = client_2.createTransaction("transactionSnapshot2", { + isolation_level: "repeatable_read", + snapshot, + }); await transaction_2.begin(); - const { rows: query_2 } = await transaction_2.queryObject< - { x: number } - >`SELECT X FROM FOR_TRANSACTION_TEST`; + const { rows: query_2 } = await transaction_2.queryObject<{ + x: number; + }>`SELECT X FROM FOR_TRANSACTION_TEST`; assertEquals( query_2, [{ x: 1 }], @@ -1170,9 +1190,9 @@ Deno.test( await transaction.begin(); await transaction.queryArray`INSERT INTO MY_TEST (X) VALUES (1)`; - const { rows: query_1 } = await transaction.queryObject< - { x: number } - >`SELECT X FROM MY_TEST`; + const { rows: query_1 } = await transaction.queryObject<{ + x: number; + }>`SELECT X FROM MY_TEST`; assertEquals(query_1, [{ x: 1 }]); await transaction.rollback({ chain: true }); @@ -1185,9 +1205,9 @@ Deno.test( await transaction.rollback(); - const { rowCount: query_2 } = await client.queryObject< - { x: number } - >`SELECT X FROM MY_TEST`; + const { rowCount: query_2 } = await client.queryObject<{ + x: number; + }>`SELECT X FROM MY_TEST`; assertEquals(query_2, 0); assertEquals( @@ -1250,31 +1270,31 @@ Deno.test( await transaction.begin(); await transaction.queryArray`CREATE TEMP TABLE X (Y INT)`; await transaction.queryArray`INSERT INTO X VALUES (1)`; - const { rows: query_1 } = await transaction.queryObject< - { y: number } - >`SELECT Y FROM X`; + const { rows: query_1 } = await transaction.queryObject<{ + y: number; + }>`SELECT Y FROM X`; assertEquals(query_1, [{ y: 1 }]); const savepoint = await transaction.savepoint(savepoint_name); await transaction.queryArray`DELETE FROM X`; - const { rowCount: query_2 } = await transaction.queryObject< - { y: number } - >`SELECT Y FROM X`; + const { rowCount: query_2 } = await transaction.queryObject<{ + y: number; + }>`SELECT Y FROM X`; assertEquals(query_2, 0); await savepoint.update(); await transaction.queryArray`INSERT INTO X VALUES (2)`; - const { rows: query_3 } = await transaction.queryObject< - { y: number } - >`SELECT Y FROM X`; + const { rows: query_3 } = await transaction.queryObject<{ + y: number; + }>`SELECT Y FROM X`; assertEquals(query_3, [{ y: 2 }]); await transaction.rollback(savepoint); - const { rowCount: query_4 } = await transaction.queryObject< - { y: number } - >`SELECT Y FROM X`; + const { rowCount: query_4 } = await transaction.queryObject<{ + y: number; + }>`SELECT Y FROM X`; assertEquals(query_4, 0); assertEquals( @@ -1291,9 +1311,9 @@ Deno.test( // This checks that the savepoint can be called by name as well await transaction.rollback(savepoint_name); - const { rows: query_5 } = await transaction.queryObject< - { y: number } - >`SELECT Y FROM X`; + const { rows: query_5 } = await transaction.queryObject<{ + y: number; + }>`SELECT Y FROM X`; assertEquals(query_5, [{ y: 1 }]); await transaction.commit();