From cf7e29f9b1a5ed1b5d80525fa434be35bf44ee95 Mon Sep 17 00:00:00 2001 From: Stephen Haberman Date: Sun, 23 Mar 2025 08:07:31 -0500 Subject: [PATCH 1/3] fix: Remove array support from inferType. I believe this was always broken, because the oid of a boolean[] is not the same as the oid of a boolean, and AFAICT inferType was resolving "boolean[] --> the oid of a boolean" without any attempt to translate over to the `boolean[]` oid. There's talk of removing `inferType` entirely, which would have to wait until v4, but I think if this array-based support has always been broken (afaiu?), we can just remove it immediately in a v3 bug fix release. Fixes #471 --- src/types.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types.js b/src/types.js index 7c7c2b93..e9fabdba 100644 --- a/src/types.js +++ b/src/types.js @@ -224,7 +224,6 @@ export const inferType = function inferType(x) { x instanceof Uint8Array ? 17 : (x === true || x === false) ? 16 : typeof x === 'bigint' ? 20 : - Array.isArray(x) ? inferType(x[0]) : 0 ) } From d8d2dafd4cdd06a4cee2d6f80aa87034a27844be Mon Sep 17 00:00:00 2001 From: Stephen Haberman Date: Mon, 24 Mar 2025 19:25:46 -0500 Subject: [PATCH 2/3] feat: Add onquery callback. --- cf/src/connection.js | 1 + cf/src/index.js | 4 +++- cf/src/query.js | 27 +++++++++++++++++++++++---- cjs/src/connection.js | 1 + cjs/src/index.js | 4 +++- cjs/src/query.js | 27 +++++++++++++++++++++++---- deno/README.md | 27 ++++++++++++++++----------- deno/src/connection.js | 1 + deno/src/index.js | 4 +++- deno/src/query.js | 27 +++++++++++++++++++++++---- src/connection.js | 1 + src/index.js | 2 ++ src/query.js | 27 +++++++++++++++++++++++---- 13 files changed, 123 insertions(+), 30 deletions(-) diff --git a/cf/src/connection.js b/cf/src/connection.js index ee8b1e69..7d7a7158 100644 --- a/cf/src/connection.js +++ b/cf/src/connection.js @@ -167,6 +167,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose : (query = q, query.active = true) build(q) + q.onquery && (q.onquery = q.onquery(q)) return write(toBuffer(q)) && !q.describeFirst && !q.cursorFn diff --git a/cf/src/index.js b/cf/src/index.js index d24e9f9c..eb94550a 100644 --- a/cf/src/index.js +++ b/cf/src/index.js @@ -86,6 +86,7 @@ function Postgres(a, b) { function Sql(handler) { handler.debug = options.debug + handler.onquery = options.onquery Object.entries(options.types).reduce((acc, [name, type]) => { acc[name] = (x) => new Parameter(x, type.to) @@ -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), {}) }, @@ -491,6 +492,7 @@ function parseOptions(a, b) { onnotify : o.onnotify, onclose : o.onclose, onparameter : o.onparameter, + onquery : o.onquery, socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, diff --git a/cf/src/query.js b/cf/src/query.js index 0d44a15c..877927c7 100644 --- a/cf/src/query.js +++ b/cf/src/query.js @@ -13,6 +13,9 @@ export class Query extends Promise { reject = b }) + this.resolver = resolve + this.rejecter = reject + this.tagged = Array.isArray(strings.raw) this.strings = strings this.args = args @@ -23,19 +26,29 @@ export class Query extends Promise { this.state = null this.statement = null - this.resolve = x => (this.active = false, resolve(x)) - this.reject = x => (this.active = false, reject(x)) - this.active = false this.cancelled = null this.executed = false this.signature = '' + this.onquery = this.handler.onquery this[originError] = this.handler.debug ? new Error() : this.tagged && cachedError(this.strings) } + resolve(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.resolver(x) + } + + reject(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.rejecter(x) + } + get origin() { return (this.handler.debug ? this[originError].stack @@ -137,7 +150,13 @@ export class Query extends Promise { } async handle() { - !this.executed && (this.executed = true) && await 1 && this.handler(this) + if (this.executed) + return + + this.executed = true + await 1 + this.onquery && (this.onquery = this.onquery(this)) + this.handler(this) } execute() { diff --git a/cjs/src/connection.js b/cjs/src/connection.js index f7f58d14..85e5e319 100644 --- a/cjs/src/connection.js +++ b/cjs/src/connection.js @@ -165,6 +165,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose : (query = q, query.active = true) build(q) + q.onquery && (q.onquery = q.onquery(q)) return write(toBuffer(q)) && !q.describeFirst && !q.cursorFn diff --git a/cjs/src/index.js b/cjs/src/index.js index 40ac2c18..3708db87 100644 --- a/cjs/src/index.js +++ b/cjs/src/index.js @@ -85,6 +85,7 @@ function Postgres(a, b) { function Sql(handler) { handler.debug = options.debug + handler.onquery = options.onquery Object.entries(options.types).reduce((acc, [name, type]) => { acc[name] = (x) => new Parameter(x, type.to) @@ -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), {}) }, @@ -490,6 +491,7 @@ function parseOptions(a, b) { onnotify : o.onnotify, onclose : o.onclose, onparameter : o.onparameter, + onquery : o.onquery, socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, diff --git a/cjs/src/query.js b/cjs/src/query.js index 45327f2f..f6c0fc22 100644 --- a/cjs/src/query.js +++ b/cjs/src/query.js @@ -13,6 +13,9 @@ const Query = module.exports.Query = class Query extends Promise { reject = b }) + this.resolver = resolve + this.rejecter = reject + this.tagged = Array.isArray(strings.raw) this.strings = strings this.args = args @@ -23,19 +26,29 @@ const Query = module.exports.Query = class Query extends Promise { this.state = null this.statement = null - this.resolve = x => (this.active = false, resolve(x)) - this.reject = x => (this.active = false, reject(x)) - this.active = false this.cancelled = null this.executed = false this.signature = '' + this.onquery = this.handler.onquery this[originError] = this.handler.debug ? new Error() : this.tagged && cachedError(this.strings) } + resolve(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.resolver(x) + } + + reject(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.rejecter(x) + } + get origin() { return (this.handler.debug ? this[originError].stack @@ -137,7 +150,13 @@ const Query = module.exports.Query = class Query extends Promise { } async handle() { - !this.executed && (this.executed = true) && await 1 && this.handler(this) + if (this.executed) + return + + this.executed = true + await 1 + this.onquery && (this.onquery = this.onquery(this)) + this.handler(this) } execute() { diff --git a/deno/README.md b/deno/README.md index 6f8085cf..354fee26 100644 --- a/deno/README.md +++ b/deno/README.md @@ -1121,20 +1121,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 +1299,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..bdfc9639 100644 --- a/deno/src/connection.js +++ b/deno/src/connection.js @@ -168,6 +168,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose : (query = q, query.active = true) build(q) + q.onquery && (q.onquery = q.onquery(q)) return write(toBuffer(q)) && !q.describeFirst && !q.cursorFn diff --git a/deno/src/index.js b/deno/src/index.js index 3bbdf2ba..82bd5a7f 100644 --- a/deno/src/index.js +++ b/deno/src/index.js @@ -86,6 +86,7 @@ function Postgres(a, b) { function Sql(handler) { handler.debug = options.debug + handler.onquery = options.onquery Object.entries(options.types).reduce((acc, [name, type]) => { acc[name] = (x) => new Parameter(x, type.to) @@ -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), {}) }, @@ -491,6 +492,7 @@ function parseOptions(a, b) { onnotify : o.onnotify, onclose : o.onclose, onparameter : o.onparameter, + onquery : o.onquery, socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, diff --git a/deno/src/query.js b/deno/src/query.js index 0d44a15c..877927c7 100644 --- a/deno/src/query.js +++ b/deno/src/query.js @@ -13,6 +13,9 @@ export class Query extends Promise { reject = b }) + this.resolver = resolve + this.rejecter = reject + this.tagged = Array.isArray(strings.raw) this.strings = strings this.args = args @@ -23,19 +26,29 @@ export class Query extends Promise { this.state = null this.statement = null - this.resolve = x => (this.active = false, resolve(x)) - this.reject = x => (this.active = false, reject(x)) - this.active = false this.cancelled = null this.executed = false this.signature = '' + this.onquery = this.handler.onquery this[originError] = this.handler.debug ? new Error() : this.tagged && cachedError(this.strings) } + resolve(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.resolver(x) + } + + reject(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.rejecter(x) + } + get origin() { return (this.handler.debug ? this[originError].stack @@ -137,7 +150,13 @@ export class Query extends Promise { } async handle() { - !this.executed && (this.executed = true) && await 1 && this.handler(this) + if (this.executed) + return + + this.executed = true + await 1 + this.onquery && (this.onquery = this.onquery(this)) + this.handler(this) } execute() { diff --git a/src/connection.js b/src/connection.js index 97cc97e1..60c18b6f 100644 --- a/src/connection.js +++ b/src/connection.js @@ -165,6 +165,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose : (query = q, query.active = true) build(q) + q.onquery && (q.onquery = q.onquery(q)) return write(toBuffer(q)) && !q.describeFirst && !q.cursorFn diff --git a/src/index.js b/src/index.js index 2dfd24e8..9a70f63a 100644 --- a/src/index.js +++ b/src/index.js @@ -85,6 +85,7 @@ function Postgres(a, b) { function Sql(handler) { handler.debug = options.debug + handler.onquery = options.onquery Object.entries(options.types).reduce((acc, [name, type]) => { acc[name] = (x) => new Parameter(x, type.to) @@ -490,6 +491,7 @@ function parseOptions(a, b) { onnotify : o.onnotify, onclose : o.onclose, onparameter : o.onparameter, + onquery : o.onquery, socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, diff --git a/src/query.js b/src/query.js index 0d44a15c..877927c7 100644 --- a/src/query.js +++ b/src/query.js @@ -13,6 +13,9 @@ export class Query extends Promise { reject = b }) + this.resolver = resolve + this.rejecter = reject + this.tagged = Array.isArray(strings.raw) this.strings = strings this.args = args @@ -23,19 +26,29 @@ export class Query extends Promise { this.state = null this.statement = null - this.resolve = x => (this.active = false, resolve(x)) - this.reject = x => (this.active = false, reject(x)) - this.active = false this.cancelled = null this.executed = false this.signature = '' + this.onquery = this.handler.onquery this[originError] = this.handler.debug ? new Error() : this.tagged && cachedError(this.strings) } + resolve(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.resolver(x) + } + + reject(x) { + this.active = false + this.onquery && (this.onquery = this.onquery(x)) + this.rejecter(x) + } + get origin() { return (this.handler.debug ? this[originError].stack @@ -137,7 +150,13 @@ export class Query extends Promise { } async handle() { - !this.executed && (this.executed = true) && await 1 && this.handler(this) + if (this.executed) + return + + this.executed = true + await 1 + this.onquery && (this.onquery = this.onquery(this)) + this.handler(this) } execute() { From ede9db4a5337797dad83f81b0404529513430414 Mon Sep 17 00:00:00 2001 From: Stephen Haberman Date: Tue, 25 Mar 2025 15:57:10 -0500 Subject: [PATCH 3/3] chore: Update files for github protocol to work. --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d53fe2ca..82e60c56 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,12 @@ "prepublishOnly": "npm run lint" }, "files": [ - "/cf/src", - "/cf/polyfills.js", - "/cjs/src", - "/cjs/package.json", - "/src", - "/types" + "cf/src/*", + "cf/polyfills.js", + "cjs/src/*", + "cjs/package.json", + "src", + "types" ], "author": "Rasmus Porsager (https://www.porsager.com)", "funding": {