From 089214e85c23c90cf142d47fb30bd03f42874984 Mon Sep 17 00:00:00 2001 From: Louis Orleans Date: Tue, 21 Jan 2025 17:16:15 -0800 Subject: [PATCH 01/15] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20fix=20CONNECT=5FTIME?= =?UTF-8?q?OUT=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `CONNECT_TIMEOUT` error's name is `CONNECT_TIMEOUT`, not `CONNECTION_CONNECT_TIMEOUT`. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1dcdd668..b79c207f 100644 --- a/README.md +++ b/README.md @@ -1303,8 +1303,8 @@ This error is thrown if the user has called [`sql.end()`](#teardown--cleanup) an This error is thrown for any queries that were pending when the timeout to [`sql.end({ timeout: X })`](#teardown--cleanup) was reached. -##### CONNECTION_CONNECT_TIMEOUT -> write CONNECTION_CONNECT_TIMEOUT host:port +##### CONNECT_TIMEOUT +> write CONNECT_TIMEOUT host:port This error is thrown if the startup phase of the connection (tcp, protocol negotiation, and auth) took more than the default 30 seconds or what was specified using `connect_timeout` or `PGCONNECT_TIMEOUT`. From ad0ed4476e09f41f147859cb5a42971d2b99e9c7 Mon Sep 17 00:00:00 2001 From: adrtivv Date: Fri, 11 Apr 2025 00:37:07 +0530 Subject: [PATCH 02/15] fixed typings for generic error code variants --- types/index.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index 8989ff47..eb604918 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -456,7 +456,8 @@ declare namespace postgres { | 'NOT_TAGGED_CALL' | 'UNDEFINED_VALUE' | 'MAX_PARAMETERS_EXCEEDED' - | 'SASL_SIGNATURE_MISMATCH'; + | 'SASL_SIGNATURE_MISMATCH' + | 'UNSAFE_TRANSACTION'; message: string; } From b0d8c8f363e006a74472d76f859da60c52a80368 Mon Sep 17 00:00:00 2001 From: Stephen Haberman Date: Sat, 3 May 2025 21:23:22 -0500 Subject: [PATCH 03/15] docs: Add prepare: true to sql.unsafe docs. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b79c207f..c135cd17 100644 --- a/README.md +++ b/README.md @@ -568,6 +568,8 @@ If you know what you're doing, you can use `unsafe` to pass any string you'd lik sql.unsafe('select ' + danger + ' from users where id = ' + dragons) ``` +By default, `sql.unsafe` assumes the `query` string is sufficiently dynamic that prepared statements do not make sense, and so defaults them to off. If you'd like to re-enable prepared statements, you can pass `{ prepare: true }`. + You can also nest `sql.unsafe` within a safe `sql` expression. This is useful if only part of your fraction has unsafe elements. ```js From 26ee4782b456207d48571c3469c50ada013b06cc Mon Sep 17 00:00:00 2001 From: madflow Date: Sun, 23 Feb 2025 17:46:44 +0100 Subject: [PATCH 04/15] docs: dynamic ordering --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index c135cd17..b04ac21c 100644 --- a/README.md +++ b/README.md @@ -342,6 +342,27 @@ select * from users select * from users where user_id = $1 ``` +### Dynamic ordering + +```js +const id = 1 +const order = { + username: 'asc' + created_at: 'desc' +} +await sql` + select + * + from ticket + where account = ${ id } + order by ${ + Object.entries(order).flatMap(([column, order], i) => + [i ? sql`,` : sql``, sql`${ sql(column) } ${ order === 'desc' ? sql`desc` : sql`asc` }`] + ) + } +` +``` + ### SQL functions Using keywords or calling functions dynamically is also possible by using ``` sql`` ``` fragments. ```js From a39dfefd507c3ea5568377faef531a8f6e0d90b6 Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Tue, 20 May 2025 16:08:13 -0600 Subject: [PATCH 05/15] Ensure non object errors thrown are handled properly --- src/connection.js | 3 +++ tests/index.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/connection.js b/src/connection.js index 97cc97e1..44af2bee 100644 --- a/src/connection.js +++ b/src/connection.js @@ -385,6 +385,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose } function queryError(query, err) { + if (!err || typeof err !== 'object') + err = new Error(err) + 'query' in err || 'parameters' in err || Object.defineProperties(err, { stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug }, query: { value: query.string, enumerable: options.debug }, diff --git a/tests/index.js b/tests/index.js index bf81b036..65635399 100644 --- a/tests/index.js +++ b/tests/index.js @@ -429,6 +429,22 @@ t('Reconnect using SSL', { timeout: 2 }, async() => { return [1, (await sql`select 1 as x`)[0].x] }) +t('Proper handling of non object Errors', async() => { + const sql = postgres({ socket: () => { throw 'wat' } }) + + return [ + 'wat', await sql`select 1 as x`.catch(e => e.message) + ] +}) + +t('Proper handling of null Errors', async() => { + const sql = postgres({ socket: () => { throw null } }) + + return [ + 'null', await sql`select 1 as x`.catch(e => e.message) + ] +}) + t('Login without password', async() => { return [true, (await postgres({ ...options, ...login })`select true as x`)[0].x] }) From 76c13f2f2f5a57f52bf927c9942912b70ce8a65c Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Tue, 20 May 2025 16:58:09 -0600 Subject: [PATCH 06/15] Fix stuck queries on non busy connections --- src/connection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/connection.js b/src/connection.js index 44af2bee..8e71751a 100644 --- a/src/connection.js +++ b/src/connection.js @@ -293,7 +293,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (incomings) { incomings.push(x) remaining -= x.length - if (remaining >= 0) + if (remaining > 0) return } From e8bb3b829ae2058d70694b397de434d046091a34 Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Tue, 20 May 2025 16:59:07 -0600 Subject: [PATCH 07/15] Fix failures in sql.reserve() connect shadowing real errors fixes #778 #923 #944 #1028 --- src/connection.js | 2 +- src/index.js | 7 ++++--- tests/index.js | 8 ++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/connection.js b/src/connection.js index 8e71751a..b1ff1244 100644 --- a/src/connection.js +++ b/src/connection.js @@ -381,7 +381,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (queryError(initial, err), initial = null) + initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) } function queryError(query, err) { diff --git a/src/index.js b/src/index.js index 2dfd24e8..944d50cf 100644 --- a/src/index.js +++ b/src/index.js @@ -204,9 +204,10 @@ function Postgres(a, b) { const queue = Queue() const c = open.length ? open.shift() - : await new Promise(r => { - queries.push({ reserve: r }) - closed.length && connect(closed.shift()) + : await new Promise((resolve, reject) => { + const query = { reserve: resolve, reject } + queries.push(query) + closed.length && connect(closed.shift(), query) }) move(c, reserved) diff --git a/tests/index.js b/tests/index.js index 65635399..5b5e6f87 100644 --- a/tests/index.js +++ b/tests/index.js @@ -445,6 +445,14 @@ t('Proper handling of null Errors', async() => { ] }) +t('Ensure reserve throws proper error', async() => { + const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) + + return [ + 'wat', await sql.reserve().catch(e => e) + ] +}) + t('Login without password', async() => { return [true, (await postgres({ ...options, ...login })`select true as x`)[0].x] }) From 36dbe2fbf500dac8cea979b620ccc1e2e10d0de5 Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Tue, 20 May 2025 17:09:46 -0600 Subject: [PATCH 08/15] --unstable not needed for deno, but use --no-lock to not include deno.lock --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d53fe2ca..f556cd28 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test": "npm run test:esm && npm run test:cjs && npm run test:deno", "test:esm": "node tests/index.js", "test:cjs": "npm run build:cjs && cd cjs/tests && node index.js && cd ../../", - "test:deno": "npm run build:deno && cd deno/tests && deno run --unstable --allow-all --unsafely-ignore-certificate-errors index.js && cd ../../", + "test:deno": "npm run build:deno && cd deno/tests && deno run --no-lock --allow-all --unsafely-ignore-certificate-errors index.js && cd ../../", "lint": "eslint src && eslint tests", "prepare": "npm run build", "prepublishOnly": "npm run lint" From a92f4705968cebc0451d6ab3270b26a48a112318 Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Tue, 20 May 2025 17:20:56 -0600 Subject: [PATCH 09/15] build --- cf/src/connection.js | 7 ++++-- cf/src/index.js | 9 ++++---- cjs/src/connection.js | 7 ++++-- cjs/src/index.js | 9 ++++---- cjs/tests/index.js | 24 ++++++++++++++++++++ deno/README.md | 50 ++++++++++++++++++++++++++++++++---------- deno/src/connection.js | 7 ++++-- deno/src/index.js | 9 ++++---- deno/tests/index.js | 24 ++++++++++++++++++++ deno/types/index.d.ts | 3 ++- tests/index.js | 6 ++--- 11 files changed, 122 insertions(+), 33 deletions(-) diff --git a/cf/src/connection.js b/cf/src/connection.js index ee8b1e69..d918b136 100644 --- a/cf/src/connection.js +++ b/cf/src/connection.js @@ -295,7 +295,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (incomings) { incomings.push(x) remaining -= x.length - if (remaining >= 0) + if (remaining > 0) return } @@ -383,10 +383,13 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (queryError(initial, err), initial = null) + initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) } function queryError(query, err) { + if (!err || typeof err !== 'object') + err = new Error(err) + 'query' in err || 'parameters' in err || Object.defineProperties(err, { stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug }, query: { value: query.string, enumerable: options.debug }, diff --git a/cf/src/index.js b/cf/src/index.js index d24e9f9c..3ffb7e65 100644 --- a/cf/src/index.js +++ b/cf/src/index.js @@ -205,9 +205,10 @@ function Postgres(a, b) { const queue = Queue() const c = open.length ? open.shift() - : await new Promise(r => { - queries.push({ reserve: r }) - closed.length && connect(closed.shift()) + : await new Promise((resolve, reject) => { + const query = { reserve: resolve, reject } + queries.push(query) + closed.length && connect(closed.shift(), query) }) move(c, reserved) @@ -481,7 +482,7 @@ function parseOptions(a, b) { {} ), connection : { - application_name: 'postgres.js', + application_name: env.PGAPPNAME || 'postgres.js', ...o.connection, ...Object.entries(query).reduce((acc, [k, v]) => (k in defaults || (acc[k] = v), acc), {}) }, diff --git a/cjs/src/connection.js b/cjs/src/connection.js index f7f58d14..db427247 100644 --- a/cjs/src/connection.js +++ b/cjs/src/connection.js @@ -293,7 +293,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (incomings) { incomings.push(x) remaining -= x.length - if (remaining >= 0) + if (remaining > 0) return } @@ -381,10 +381,13 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (queryError(initial, err), initial = null) + initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) } function queryError(query, err) { + if (!err || typeof err !== 'object') + err = new Error(err) + 'query' in err || 'parameters' in err || Object.defineProperties(err, { stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug }, query: { value: query.string, enumerable: options.debug }, diff --git a/cjs/src/index.js b/cjs/src/index.js index 40ac2c18..baf7e60a 100644 --- a/cjs/src/index.js +++ b/cjs/src/index.js @@ -204,9 +204,10 @@ function Postgres(a, b) { const queue = Queue() const c = open.length ? open.shift() - : await new Promise(r => { - queries.push({ reserve: r }) - closed.length && connect(closed.shift()) + : await new Promise((resolve, reject) => { + const query = { reserve: resolve, reject } + queries.push(query) + closed.length && connect(closed.shift(), query) }) move(c, reserved) @@ -480,7 +481,7 @@ function parseOptions(a, b) { {} ), connection : { - application_name: 'postgres.js', + application_name: env.PGAPPNAME || 'postgres.js', ...o.connection, ...Object.entries(query).reduce((acc, [k, v]) => (k in defaults || (acc[k] = v), acc), {}) }, diff --git a/cjs/tests/index.js b/cjs/tests/index.js index 7d84ac67..901edb54 100644 --- a/cjs/tests/index.js +++ b/cjs/tests/index.js @@ -429,6 +429,30 @@ t('Reconnect using SSL', { timeout: 2 }, async() => { return [1, (await sql`select 1 as x`)[0].x] }) +t('Proper handling of non object Errors', async() => { + const sql = postgres({ socket: () => { throw 'wat' } }) // eslint-disable-line + + return [ + 'wat', await sql`select 1 as x`.catch(e => e.message) + ] +}) + +t('Proper handling of null Errors', async() => { + const sql = postgres({ socket: () => { throw null } }) // eslint-disable-line + + return [ + 'null', await sql`select 1 as x`.catch(e => e.message) + ] +}) + +t('Ensure reserve throws proper error', async() => { + const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) // eslint-disable-line + + return [ + 'wat', await sql.reserve().catch(e => e) + ] +}) + t('Login without password', async() => { return [true, (await postgres({ ...options, ...login })`select true as x`)[0].x] }) diff --git a/deno/README.md b/deno/README.md index 6f8085cf..b6ec85b7 100644 --- a/deno/README.md +++ b/deno/README.md @@ -338,6 +338,27 @@ select * from users select * from users where user_id = $1 ``` +### Dynamic ordering + +```js +const id = 1 +const order = { + username: 'asc' + created_at: 'desc' +} +await sql` + select + * + from ticket + where account = ${ id } + order by ${ + Object.entries(order).flatMap(([column, order], i) => + [i ? sql`,` : sql``, sql`${ sql(column) } ${ order === 'desc' ? sql`desc` : sql`asc` }`] + ) + } +` +``` + ### SQL functions Using keywords or calling functions dynamically is also possible by using ``` sql`` ``` fragments. ```js @@ -564,6 +585,8 @@ If you know what you're doing, you can use `unsafe` to pass any string you'd lik sql.unsafe('select ' + danger + ' from users where id = ' + dragons) ``` +By default, `sql.unsafe` assumes the `query` string is sufficiently dynamic that prepared statements do not make sense, and so defaults them to off. If you'd like to re-enable prepared statements, you can pass `{ prepare: true }`. + You can also nest `sql.unsafe` within a safe `sql` expression. This is useful if only part of your fraction has unsafe elements. ```js @@ -1121,20 +1144,25 @@ It is also possible to connect to the database without a connection string or an const sql = postgres() ``` -| Option | Environment Variables | -| ----------------- | ------------------------ | -| `host` | `PGHOST` | -| `port` | `PGPORT` | -| `database` | `PGDATABASE` | -| `username` | `PGUSERNAME` or `PGUSER` | -| `password` | `PGPASSWORD` | -| `idle_timeout` | `PGIDLE_TIMEOUT` | -| `connect_timeout` | `PGCONNECT_TIMEOUT` | +| Option | Environment Variables | +| ------------------ | ------------------------ | +| `host` | `PGHOST` | +| `port` | `PGPORT` | +| `database` | `PGDATABASE` | +| `username` | `PGUSERNAME` or `PGUSER` | +| `password` | `PGPASSWORD` | +| `application_name` | `PGAPPNAME` | +| `idle_timeout` | `PGIDLE_TIMEOUT` | +| `connect_timeout` | `PGCONNECT_TIMEOUT` | ### Prepared statements Prepared statements will automatically be created for any queries where it can be inferred that the query is static. This can be disabled by using the `prepare: false` option. For instance — this is useful when [using PGBouncer in `transaction mode`](https://github.com/porsager/postgres/issues/93#issuecomment-656290493). +**update**: [since 1.21.0](https://www.pgbouncer.org/2023/10/pgbouncer-1-21-0) +PGBouncer supports protocol-level named prepared statements when [configured +properly](https://www.pgbouncer.org/config.html#max_prepared_statements) + ## Custom Types You can add ergonomic support for custom types, or simply use `sql.typed(value, type)` inline, where type is the PostgreSQL `oid` for the type and the correctly serialized string. _(`oid` values for types can be found in the `pg_catalog.pg_type` table.)_ @@ -1294,8 +1322,8 @@ This error is thrown if the user has called [`sql.end()`](#teardown--cleanup) an This error is thrown for any queries that were pending when the timeout to [`sql.end({ timeout: X })`](#teardown--cleanup) was reached. -##### CONNECTION_CONNECT_TIMEOUT -> write CONNECTION_CONNECT_TIMEOUT host:port +##### CONNECT_TIMEOUT +> write CONNECT_TIMEOUT host:port This error is thrown if the startup phase of the connection (tcp, protocol negotiation, and auth) took more than the default 30 seconds or what was specified using `connect_timeout` or `PGCONNECT_TIMEOUT`. diff --git a/deno/src/connection.js b/deno/src/connection.js index 1726a9aa..79c64be5 100644 --- a/deno/src/connection.js +++ b/deno/src/connection.js @@ -296,7 +296,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (incomings) { incomings.push(x) remaining -= x.length - if (remaining >= 0) + if (remaining > 0) return } @@ -384,10 +384,13 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (queryError(initial, err), initial = null) + initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) } function queryError(query, err) { + if (!err || typeof err !== 'object') + err = new Error(err) + 'query' in err || 'parameters' in err || Object.defineProperties(err, { stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug }, query: { value: query.string, enumerable: options.debug }, diff --git a/deno/src/index.js b/deno/src/index.js index 3bbdf2ba..98a82345 100644 --- a/deno/src/index.js +++ b/deno/src/index.js @@ -205,9 +205,10 @@ function Postgres(a, b) { const queue = Queue() const c = open.length ? open.shift() - : await new Promise(r => { - queries.push({ reserve: r }) - closed.length && connect(closed.shift()) + : await new Promise((resolve, reject) => { + const query = { reserve: resolve, reject } + queries.push(query) + closed.length && connect(closed.shift(), query) }) move(c, reserved) @@ -481,7 +482,7 @@ function parseOptions(a, b) { {} ), connection : { - application_name: 'postgres.js', + application_name: env.PGAPPNAME || 'postgres.js', ...o.connection, ...Object.entries(query).reduce((acc, [k, v]) => (k in defaults || (acc[k] = v), acc), {}) }, diff --git a/deno/tests/index.js b/deno/tests/index.js index 5b5d6e57..45c3421b 100644 --- a/deno/tests/index.js +++ b/deno/tests/index.js @@ -431,6 +431,30 @@ t('Reconnect using SSL', { timeout: 2 }, async() => { return [1, (await sql`select 1 as x`)[0].x] }) +t('Proper handling of non object Errors', async() => { + const sql = postgres({ socket: () => { throw 'wat' } }) // eslint-disable-line + + return [ + 'wat', await sql`select 1 as x`.catch(e => e.message) + ] +}) + +t('Proper handling of null Errors', async() => { + const sql = postgres({ socket: () => { throw null } }) // eslint-disable-line + + return [ + 'null', await sql`select 1 as x`.catch(e => e.message) + ] +}) + +t('Ensure reserve throws proper error', async() => { + const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) // eslint-disable-line + + return [ + 'wat', await sql.reserve().catch(e => e) + ] +}) + t('Login without password', async() => { return [true, (await postgres({ ...options, ...login })`select true as x`)[0].x] }) diff --git a/deno/types/index.d.ts b/deno/types/index.d.ts index 2088662d..44a07af0 100644 --- a/deno/types/index.d.ts +++ b/deno/types/index.d.ts @@ -458,7 +458,8 @@ declare namespace postgres { | 'NOT_TAGGED_CALL' | 'UNDEFINED_VALUE' | 'MAX_PARAMETERS_EXCEEDED' - | 'SASL_SIGNATURE_MISMATCH'; + | 'SASL_SIGNATURE_MISMATCH' + | 'UNSAFE_TRANSACTION'; message: string; } diff --git a/tests/index.js b/tests/index.js index 5b5e6f87..7a8afbf4 100644 --- a/tests/index.js +++ b/tests/index.js @@ -430,7 +430,7 @@ t('Reconnect using SSL', { timeout: 2 }, async() => { }) t('Proper handling of non object Errors', async() => { - const sql = postgres({ socket: () => { throw 'wat' } }) + const sql = postgres({ socket: () => { throw 'wat' } }) // eslint-disable-line return [ 'wat', await sql`select 1 as x`.catch(e => e.message) @@ -438,7 +438,7 @@ t('Proper handling of non object Errors', async() => { }) t('Proper handling of null Errors', async() => { - const sql = postgres({ socket: () => { throw null } }) + const sql = postgres({ socket: () => { throw null } }) // eslint-disable-line return [ 'null', await sql`select 1 as x`.catch(e => e.message) @@ -446,7 +446,7 @@ t('Proper handling of null Errors', async() => { }) t('Ensure reserve throws proper error', async() => { - const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) + const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) // eslint-disable-line return [ 'wat', await sql.reserve().catch(e => e) From ca4da7ca56a5fad7a4fd495c9e640c66f325bc45 Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Tue, 20 May 2025 17:40:43 -0600 Subject: [PATCH 10/15] 3.4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f556cd28..0748f3b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres", - "version": "3.4.5", + "version": "3.4.6", "description": "Fastest full featured PostgreSQL client for Node.js", "type": "module", "module": "src/index.js", From e34826d43036bda349a18d0354389ec6d737aec4 Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Wed, 21 May 2025 09:59:53 -0600 Subject: [PATCH 11/15] Fix reserved queries failing on connect --- src/connection.js | 11 +++++++---- tests/index.js | 12 +++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/connection.js b/src/connection.js index b1ff1244..c3f554aa 100644 --- a/src/connection.js +++ b/src/connection.js @@ -109,7 +109,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose queue: queues.closed, idleTimer, connect(query) { - initial = query || true + initial = query reconnect() }, terminate, @@ -381,10 +381,13 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) + initial && (queryError(initial, err), initial = null) } function queryError(query, err) { + if (query.reserve) + return query.reject(err) + if (!err || typeof err !== 'object') err = new Error(err) @@ -535,11 +538,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose } if (needsTypes) { - initial === true && (initial = null) + initial.reserve && (initial = null) return fetchArrayTypes() } - initial !== true && execute(initial) + initial && !initial.reserve && execute(initial) options.shared.retries = retries = 0 initial = null return diff --git a/tests/index.js b/tests/index.js index 7a8afbf4..07ff98ed 100644 --- a/tests/index.js +++ b/tests/index.js @@ -445,7 +445,7 @@ t('Proper handling of null Errors', async() => { ] }) -t('Ensure reserve throws proper error', async() => { +t('Ensure reserve on connection throws proper error', async() => { const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) // eslint-disable-line return [ @@ -2604,3 +2604,13 @@ t('arrays in reserved connection', async() => { x.join('') ] }) + +t('Ensure reserve on query throws proper error', async() => { + const sql = postgres({ idle_timeout }) // eslint-disable-line + const reserved = await sql.reserve() + const [{ x }] = await reserved`select 'wat' as x` + + return [ + 'wat', x, reserved.release() + ] +}) From 657fea0959ed0ffc228f851bbff13b13e6179eef Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Wed, 21 May 2025 10:00:41 -0600 Subject: [PATCH 12/15] build --- cf/src/connection.js | 11 +++++++---- cjs/src/connection.js | 11 +++++++---- cjs/tests/index.js | 12 +++++++++++- deno/src/connection.js | 11 +++++++---- deno/tests/index.js | 12 +++++++++++- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/cf/src/connection.js b/cf/src/connection.js index d918b136..203af80d 100644 --- a/cf/src/connection.js +++ b/cf/src/connection.js @@ -111,7 +111,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose queue: queues.closed, idleTimer, connect(query) { - initial = query || true + initial = query reconnect() }, terminate, @@ -383,10 +383,13 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) + initial && (queryError(initial, err), initial = null) } function queryError(query, err) { + if (query.reserve) + return query.reject(err) + if (!err || typeof err !== 'object') err = new Error(err) @@ -537,11 +540,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose } if (needsTypes) { - initial === true && (initial = null) + initial.reserve && (initial = null) return fetchArrayTypes() } - initial !== true && execute(initial) + initial && !initial.reserve && execute(initial) options.shared.retries = retries = 0 initial = null return diff --git a/cjs/src/connection.js b/cjs/src/connection.js index db427247..589d3638 100644 --- a/cjs/src/connection.js +++ b/cjs/src/connection.js @@ -109,7 +109,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose queue: queues.closed, idleTimer, connect(query) { - initial = query || true + initial = query reconnect() }, terminate, @@ -381,10 +381,13 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) + initial && (queryError(initial, err), initial = null) } function queryError(query, err) { + if (query.reserve) + return query.reject(err) + if (!err || typeof err !== 'object') err = new Error(err) @@ -535,11 +538,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose } if (needsTypes) { - initial === true && (initial = null) + initial.reserve && (initial = null) return fetchArrayTypes() } - initial !== true && execute(initial) + initial && !initial.reserve && execute(initial) options.shared.retries = retries = 0 initial = null return diff --git a/cjs/tests/index.js b/cjs/tests/index.js index 901edb54..ec5222f7 100644 --- a/cjs/tests/index.js +++ b/cjs/tests/index.js @@ -445,7 +445,7 @@ t('Proper handling of null Errors', async() => { ] }) -t('Ensure reserve throws proper error', async() => { +t('Ensure reserve on connection throws proper error', async() => { const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) // eslint-disable-line return [ @@ -2604,3 +2604,13 @@ t('arrays in reserved connection', async() => { x.join('') ] }) + +t('Ensure reserve on query throws proper error', async() => { + const sql = postgres({ idle_timeout }) // eslint-disable-line + const reserved = await sql.reserve() + const [{ x }] = await reserved`select 'wat' as x` + + return [ + 'wat', x, reserved.release() + ] +}) diff --git a/deno/src/connection.js b/deno/src/connection.js index 79c64be5..a3f43c48 100644 --- a/deno/src/connection.js +++ b/deno/src/connection.js @@ -112,7 +112,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose queue: queues.closed, idleTimer, connect(query) { - initial = query || true + initial = query reconnect() }, terminate, @@ -384,10 +384,13 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function errored(err) { stream && (stream.destroy(err), stream = null) query && queryError(query, err) - initial && (initial.reserve ? initial.reject(err) : queryError(initial, err), initial = null) + initial && (queryError(initial, err), initial = null) } function queryError(query, err) { + if (query.reserve) + return query.reject(err) + if (!err || typeof err !== 'object') err = new Error(err) @@ -538,11 +541,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose } if (needsTypes) { - initial === true && (initial = null) + initial.reserve && (initial = null) return fetchArrayTypes() } - initial !== true && execute(initial) + initial && !initial.reserve && execute(initial) options.shared.retries = retries = 0 initial = null return diff --git a/deno/tests/index.js b/deno/tests/index.js index 45c3421b..adedf1e0 100644 --- a/deno/tests/index.js +++ b/deno/tests/index.js @@ -447,7 +447,7 @@ t('Proper handling of null Errors', async() => { ] }) -t('Ensure reserve throws proper error', async() => { +t('Ensure reserve on connection throws proper error', async() => { const sql = postgres({ socket: () => { throw 'wat' }, idle_timeout }) // eslint-disable-line return [ @@ -2607,4 +2607,14 @@ t('arrays in reserved connection', async() => { ] }) +t('Ensure reserve on query throws proper error', async() => { + const sql = postgres({ idle_timeout }) // eslint-disable-line + const reserved = await sql.reserve() + const [{ x }] = await reserved`select 'wat' as x` + + return [ + 'wat', x, reserved.release() + ] +}) + ;globalThis.addEventListener("unload", () => Deno.exit(process.exitCode)) \ No newline at end of file From 0068aa4825e1087cdad16f37142b447b3f0eab0c Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Wed, 21 May 2025 10:03:50 -0600 Subject: [PATCH 13/15] Test node 23 and 24 too --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af00f7e0..970d2771 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - node: ['12', '14', '16', '18', '20', '21', '22'] + node: ['12', '14', '16', '18', '20', '21', '22', '23', '24'] postgres: ['12', '13', '14', '15', '16', '17'] runs-on: ubuntu-latest services: From 9b92b65da6a5121545581a6dd5de859c2a70177f Mon Sep 17 00:00:00 2001 From: Rasmus Porsager Date: Wed, 21 May 2025 10:13:48 -0600 Subject: [PATCH 14/15] 3.4.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0748f3b8..65157609 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres", - "version": "3.4.6", + "version": "3.4.7", "description": "Fastest full featured PostgreSQL client for Node.js", "type": "module", "module": "src/index.js", From 32feb259a3c9abffab761bd1758b3168d9e0cebc Mon Sep 17 00:00:00 2001 From: Jobians Techie <88005779+Jobians@users.noreply.github.com> Date: Thu, 12 Jun 2025 23:18:42 +0100 Subject: [PATCH 15/15] Fix PGAPPNAME env access by prioritizing connection.application_name Use connection.application_name if provided before falling back to PGAPPNAME env var to avoid unnecessary env access errors. --- deno/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno/src/index.js b/deno/src/index.js index 98a82345..aa7a920f 100644 --- a/deno/src/index.js +++ b/deno/src/index.js @@ -482,8 +482,8 @@ function parseOptions(a, b) { {} ), connection : { - application_name: env.PGAPPNAME || 'postgres.js', ...o.connection, + application_name: o.connection?.application_name ?? env.PGAPPNAME ?? 'postgres.js', ...Object.entries(query).reduce((acc, [k, v]) => (k in defaults || (acc[k] = v), acc), {}) }, types : o.types || {},