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,
},
])
},