diff --git a/.changeset/hungry-donkeys-tell.md b/.changeset/hungry-donkeys-tell.md new file mode 100644 index 000000000..2e7b749b5 --- /dev/null +++ b/.changeset/hungry-donkeys-tell.md @@ -0,0 +1,5 @@ +--- +'@electric-sql/pglite': patch +--- + +Expose the fields node-postgres derives from the CommandComplete command tag on `Results`: `command` (e.g. `SELECT`, `INSERT`, `CREATE`), and `rowCount` (the per-statement count from the tag). The tag was already received and parsed internally to compute `affectedRows`, but these values were not surfaced. Matching node-postgres' `command`/`rowCount` result fields lets pg-compatible adapters report them without re-parsing SQL. diff --git a/docs/docs/api.md b/docs/docs/api.md index c2e7d2785..4a4c889c4 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -456,6 +456,12 @@ Result objects have the following properties: - `affectedRows?: number`
Count of the rows affected by the query. Note, this is _not_ the count of rows returned, it is the number or rows in the database changed by the query. +- `command?: string`
+ The command of the query that was run, taken from the command tag reported by Postgres, e.g. `SELECT`, `INSERT` or `CREATE`. Matches the `command` field of node-postgres query results. + +- `rowCount?: number`
+ The number of rows reported by the command tag, e.g. the rows returned by a `SELECT` or changed by an `UPDATE`. Not set when the tag carries no count (e.g. `CREATE TABLE`). Unlike `affectedRows` this is per statement and not cumulative; it matches the `rowCount` field of node-postgres query results. + - `fields: { name: string; dataTypeID: number }[]`
Field name and Postgres data type ID for each field returned. diff --git a/packages/pglite-pgmq/tests/pgmq.test.ts b/packages/pglite-pgmq/tests/pgmq.test.ts index 11fce2192..838a760c3 100644 --- a/packages/pglite-pgmq/tests/pgmq.test.ts +++ b/packages/pglite-pgmq/tests/pgmq.test.ts @@ -56,6 +56,8 @@ describe(`pgmq`, () => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }, ]) @@ -79,6 +81,8 @@ SELECT * from pgmq.send( }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }, ]) diff --git a/packages/pglite-pgvector/tests/pgvector.test.ts b/packages/pglite-pgvector/tests/pgvector.test.ts index 8f6cc5215..5256a4b6f 100644 --- a/packages/pglite-pgvector/tests/pgvector.test.ts +++ b/packages/pglite-pgvector/tests/pgvector.test.ts @@ -64,6 +64,8 @@ describe(`pgvector`, () => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 3, }, ]) }) diff --git a/packages/pglite-postgis/tests/postgis.test.ts b/packages/pglite-postgis/tests/postgis.test.ts index c813b484f..7e0a61511 100644 --- a/packages/pglite-postgis/tests/postgis.test.ts +++ b/packages/pglite-postgis/tests/postgis.test.ts @@ -98,6 +98,8 @@ WHERE ST_Within(c.location, s.geom);`) }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }, ]) @@ -133,6 +135,8 @@ WHERE ST_Within(c.location, s.geom);`) }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }, ]) @@ -172,6 +176,8 @@ WHERE ST_Within(c.location, s.geom);`) }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }, ]) }) @@ -208,6 +214,8 @@ WHERE ST_Within(c.location, s.geom);`) }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }, ]) }) diff --git a/packages/pglite/src/interface.ts b/packages/pglite/src/interface.ts index 07c88b343..840009d58 100644 --- a/packages/pglite/src/interface.ts +++ b/packages/pglite/src/interface.ts @@ -173,6 +173,16 @@ export type Row = T export type Results = { rows: Row[] affectedRows?: number + /** + * The command of the query that was run, e.g. "SELECT", "INSERT", "CREATE". + */ + command?: string + /** + * The number of rows reported by the command tag, e.g. the rows returned + * by a SELECT or changed by an UPDATE. Unlike `affectedRows` this is per statement + * and not cumulative across a multi-statement query. + */ + rowCount?: number fields: { name: string; dataTypeID: number }[] blob?: Blob // Only set when a file is returned, such as from a COPY command } diff --git a/packages/pglite/src/parse.ts b/packages/pglite/src/parse.ts index 06e4490d7..107a225e2 100644 --- a/packages/pglite/src/parse.ts +++ b/packages/pglite/src/parse.ts @@ -61,13 +61,39 @@ export function parseResults( } case 'commandComplete': { const msg = message as CommandCompleteMessage - affectedRows += retrieveRowCount(msg) - resultSets.push({ + // A command tag that carries a row count ends in it ("SELECT 2", + // "UPDATE 3", "INSERT 0 5"); all other tags end in a word + // ("CREATE TABLE"). + const parts = msg.text.split(' ') + const command = parts[0] + const rowCount = parseInt(parts[parts.length - 1], 10) + + switch (command) { + case 'INSERT': + case 'UPDATE': + case 'DELETE': + case 'COPY': + case 'MERGE': + affectedRows += rowCount + break + } + + const result = { ...currentResultSet, + command, affectedRows, - ...(blob ? { blob } : {}), - }) + } + + if (!Number.isNaN(rowCount)) { + result.rowCount = rowCount + } + + if (blob) { + result.blob = blob + } + + resultSets.push(result) currentResultSet = { rows: [], fields: [] } break @@ -86,21 +112,6 @@ export function parseResults( return resultSets } -function retrieveRowCount(msg: CommandCompleteMessage): number { - const parts = msg.text.split(' ') - switch (parts[0]) { - case 'INSERT': - return parseInt(parts[2], 10) - case 'UPDATE': - case 'DELETE': - case 'COPY': - case 'MERGE': - return parseInt(parts[1], 10) - default: - return 0 - } -} - /** Get the dataTypeIDs from a list of messages, if it's available. */ export function parseDescribeStatementResults( messages: Array, diff --git a/packages/pglite/tests/array-types.test.ts b/packages/pglite/tests/array-types.test.ts index 3b9ec84d8..ba6ce739b 100644 --- a/packages/pglite/tests/array-types.test.ts +++ b/packages/pglite/tests/array-types.test.ts @@ -77,6 +77,8 @@ describe('array types', () => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }) diff --git a/packages/pglite/tests/basic.test.ts b/packages/pglite/tests/basic.test.ts index ed35dfb60..f28a88940 100644 --- a/packages/pglite/tests/basic.test.ts +++ b/packages/pglite/tests/basic.test.ts @@ -49,11 +49,15 @@ await testEsmCjsAndDTC(async (importType) => { expect(multiStatementResult).toEqual([ { affectedRows: 1, + command: 'INSERT', + rowCount: 1, rows: [], fields: [], }, { affectedRows: 2, + command: 'UPDATE', + rowCount: 1, rows: [], fields: [], }, @@ -64,6 +68,8 @@ await testEsmCjsAndDTC(async (importType) => { { name: 'name', dataTypeID: 25 }, ], affectedRows: 2, + command: 'SELECT', + rowCount: 1, }, ]) }) @@ -98,6 +104,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) const updateResult = await db.query("UPDATE test SET name = 'test2';") @@ -105,6 +113,8 @@ await testEsmCjsAndDTC(async (importType) => { rows: [], fields: [], affectedRows: 1, + command: 'UPDATE', + rowCount: 1, }) }) @@ -137,6 +147,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) const updateResult = @@ -145,6 +157,8 @@ await testEsmCjsAndDTC(async (importType) => { rows: [], fields: [], affectedRows: 1, + command: 'UPDATE', + rowCount: 1, }) }) @@ -343,6 +357,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }) @@ -376,6 +392,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }) @@ -428,6 +446,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }) @@ -472,6 +492,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 2, }) await tx.rollback() }) @@ -496,6 +518,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }) it('merge delete', async () => { @@ -594,6 +618,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 2, }) }) @@ -634,6 +660,8 @@ await testEsmCjsAndDTC(async (importType) => { { name: 'last_name', dataTypeID: 25 }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }) it('timezone', async () => { @@ -821,6 +849,8 @@ await testEsmCjsAndDTC(async (importType) => { }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }) }) diff --git a/packages/pglite/tests/targets/deno/basic.test.deno.js b/packages/pglite/tests/targets/deno/basic.test.deno.js index 6a7a611ba..3c7d67e9d 100644 --- a/packages/pglite/tests/targets/deno/basic.test.deno.js +++ b/packages/pglite/tests/targets/deno/basic.test.deno.js @@ -26,11 +26,15 @@ Deno.test({ assertEquals(multiStatementResult, [ { affectedRows: 1, + command: 'INSERT', + rowCount: 1, rows: [], fields: [], }, { affectedRows: 2, + command: 'UPDATE', + rowCount: 1, rows: [], fields: [], }, @@ -41,6 +45,8 @@ Deno.test({ { name: 'name', dataTypeID: 25 }, ], affectedRows: 2, + command: 'SELECT', + rowCount: 1, }, ]) }, @@ -80,6 +86,8 @@ Deno.test({ }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) const updateResult = await db.query("UPDATE test SET name = 'test2';") @@ -87,6 +95,8 @@ Deno.test({ rows: [], fields: [], affectedRows: 1, + command: 'UPDATE', + rowCount: 1, }) }, }) @@ -232,6 +242,8 @@ Deno.test({ }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) // standardize timestamp comparison to UTC milliseconds to ensure predictable test runs on machines in different timezones. @@ -276,6 +288,8 @@ Deno.test({ }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }, }) @@ -334,6 +348,8 @@ Deno.test({ }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 2, }) await tx.rollback() }) @@ -358,6 +374,8 @@ Deno.test({ }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }) }, }) @@ -416,6 +434,8 @@ Deno.test({ }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 2, }) }, }) diff --git a/packages/pglite/tests/targets/deno/fs.test.deno.js b/packages/pglite/tests/targets/deno/fs.test.deno.js index 6a4e9fa19..b4d4ad8d8 100644 --- a/packages/pglite/tests/targets/deno/fs.test.deno.js +++ b/packages/pglite/tests/targets/deno/fs.test.deno.js @@ -23,11 +23,15 @@ Deno.test({ assertEquals(multiStatementResult, [ { affectedRows: 1, + command: 'INSERT', + rowCount: 1, rows: [], fields: [], }, { affectedRows: 2, + command: 'UPDATE', + rowCount: 1, rows: [], fields: [], }, @@ -38,6 +42,8 @@ Deno.test({ { name: 'name', dataTypeID: 25 }, ], affectedRows: 2, + command: 'SELECT', + rowCount: 1, }, ]) @@ -61,6 +67,8 @@ Deno.test({ { name: 'name', dataTypeID: 25 }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 1, }, ]) diff --git a/packages/pglite/tests/targets/deno/pgvector.test.deno.js b/packages/pglite/tests/targets/deno/pgvector.test.deno.js index e70634572..65b4e7640 100644 --- a/packages/pglite/tests/targets/deno/pgvector.test.deno.js +++ b/packages/pglite/tests/targets/deno/pgvector.test.deno.js @@ -68,6 +68,8 @@ Deno.test({ }, ], affectedRows: 0, + command: 'SELECT', + rowCount: 3, }, ]) },