Repository navigation
chore(deps): update dependency postgres to v4 - #1089
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
renovate
Bot
force-pushed
the
renovate/postgres-4.x
branch
from
November 5, 2026 20:42
9080f46 to
4d88870
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
^3.4.7→^4.0.0Release Notes
porsager/postgres (postgres)
v4.1.0Compare Source
New
query_timeout(#1278). Seconds a query may take, waiting for a connection and running together, before it is canceled and rejects withQUERY_TIMEOUT. Off by default. Unlikestatement_timeoutit also ends a query whose server or network has gone silent, which TCP alone takes many minutes to notice. Transaction control queries andforEachare limited too; cursors, streams andsql.listenare not.cancel_timeout, 5 seconds by default: how long a canceled query may take to end before its connection is closed.sql.batch([queries])sends queries in one round trip with a single Sync: outside a transaction they commit or roll back together, and the batch rejects with the failing query's position inerror.index.jsonon a transform (#1169). The built-in transforms rename the keys of JSON values in results but send JSON values as they are.json: falseleaves JSON values as they are both ways, andjson: truerenames their keys when sending too, with the transform'scolumn.to. Without it nothing changes.eager_types:truereads array types and domains when the pool starts instead of when a query first meets one.sql.identifier(...parts)(#1292). An identifier from its parts, each quoted as it is and joined with., for a name that contains a dot, whichsql(name)reads as a separator:sql.identifier('acme.inc', 'users')gives"acme.inc"."users". Thetransformis not applied. With no parts it throwsNO_PARTS.enginesis>=18and CI runs it. Nothing in Postgres.js needs 20;using,await usingand tracing are used where the runtime has them. Loading it withrequirestill needs Node.js 20.19 or 22.12.sql`...`.parsers(parsers)(#1294). A query's own parsers, for its rows only: by type,{ [oid]: parse }, on top of the instance'stypes, or a function that gets each column and returns the parser for it,nullfor the server's text. A library that knows how to decode some queries' columns can decode them once, inside the row reader, while every other query on the instance and its pool keeps the instance's parsers. Such a query receives every column as text, sobyteaand the integer arrays otherwise read in binary reach its parsers as text. A query without.parsers()reads its rows as before.Fixed
sql.listenkeeps its connection after the lastunlisten()(#1298). The connection was closed so the process could exit (#1022), which made every followinglisten()connect again: about 3.5 ms instead of 0.1 ms perlisten. It now stays open without keeping the process alive once nothing is listened.PGSSLMODEis read (#1307). It was ignored, soPGSSLMODE=verify-fullconnected in plain text without an error. This may change how existing setups connect:requirenow uses TLS and fails against a server without it, andverify-caandverify-fullcheck the certificate against Node's CAs, not~/.postgresql/root.crtorPGSSLROOTCERT. A url or anssloption still comes first.insert into ${ sql('s') }.t ${ sql(row) }orinsert into "s".t, the quoted schema and the table were read as two words, so the row was taken for a list of identifiers and threwx.replace is not a function.sql.listenkeeps reconnecting when its first reconnect fails (#1285, #1286). After the listen connection closed, it was reopened once, and if that attempt failed, as it does while a restarting server still refuses connections, nothing tried again, soonlistenwas never called and notifications were lost. A failed reconnect is now retried afterbackoff, until it succeeds, the channel is unlistened or the pool ends..cancel(),.signal()and nowquery_timeoutsend a cancel request and waited for the server to end the query, so a server or network gone silent left both the query and the promise.cancel()returns pending until TCP gave up. This applies to every cancel, also withoutquery_timeout: when the server doesn't answer withincancel_timeout, the connection is closed and the pool connects again. Whether the queries on it ran is unknown, so they reject withCONNECTION_DESTROYED(a timed out one withQUERY_TIMEOUT) whosecauseisCANCEL_UNANSWERED, not with57014. The cancel request's own connection is limited bycancel_timeouttoo, and a cancel is no longer sent for a query whose result has already arrived, where it could only hit the query after it. A cancel request whose own connection fails, as over TLS to a server gone silent, no longer crashes the process with an unhandled rejection. A query that times out while it waits behind another one on its connection is canceled only after it has had its ownquery_timeoutonce its turn comes, instead of the moment its turn comes, when it had usually finished and the cancel stopped the next query; if the server sends nothing forcancel_timeoutwhile it waits, the connection is closed. A query waiting for types to be read rejects withQUERY_TIMEOUTwithout closing its connection, and the read of array types and domains is limited byquery_timeouttoo.sslrootcert,sslcertandsslkeyin a url are used (#1308). They were sent to the server as startup parameters, so?sslrootcert=systemfailed withunrecognized configuration parameter. Files are read intoca,certandkey. As in libpq, a root certificate withrequireorverify-cachecks the CA but not the host name, andverify-fullchecks both, as doesssl: true. A file without ansslmodemeansrequire.transaction_timeout(25P04), anidle_in_transaction_session_timeout(25P03) orpg_terminate_backend(57P01) ended the session,sql.beginrejected withCONNECTION_CLOSED, so a timeout or a termination looked the same as a network drop; it now rejects with the FATAL error the server sent before closing, as a query in flight already did, and a later query on that connection rejects withCONNECTION_CLOSEDthat has it ascause. A server restart or failover ends sessions with57P01, so code that retries a transaction onCONNECTION_CLOSEDshould retry on that code too. A FATAL in answer toCOMMITrejects withCONNECTION_CLOSEDthat has it ascause, as the outcome is unknown, as it is when the connection is lost duringCOMMIT.ssl: 'verify-full'orssl: trueand an IP as the host, a connection upgraded to TLS checked the certificate againstlocalhost, so a certificate forlocalhostfrom a trusted CA passed for any IP and a correct certificate for the IP failed. Direct TLS was not affected.beginrejects when its transaction's connection is lost (#1281, #1282). One whose callback was still running when the connection went resolved with the callback's result after the transaction had already rejected; it now rejects with the transaction's error.backoffand waiting queries reject onceconnect_timeouthas passed, instead of reconnecting as fast as the server accepts for as long as it keeps closing. Withconnect_timeoutset to 0, null or false the pool now keeps trying for as long as it takes, as the option says, also after a refused or closed startup, which used to reject the waiting queries at once.FATALerror gives that error to the queries pipelined behind it as well, instead ofCONNECTION_CLOSED.Sfield is recognised as one. The non-localizedVfield is optional in the protocol: PostgreSQL sends it since 9.6, and a pooler that builds its own errors, such as PgBouncer, may send only the localizedS. Such an error was not seen as FATAL, so the query, asql.beginand the queries pipelined behind it rejected withCONNECTION_CLOSEDwithout acauseinstead of with the error.error.severitynow falls back toseverity_localwhenVis missing.queryandparameters.prepare: falseavoids it.copy ... to stdoutstream frees the connection (#1306). Afterdestroy(), breaking out offor awaitor a failedpipeline, the connection stayed paused for good and the queries behind it never ran. The rest of the copy is now read and dropped, so the connection is free once the server has sent it. The query is not canceled, so in a transaction the next query still succeeds.Performance
sslobject with acabuilds its TLS context once (#1280). The connections made with it share one context instead of each parsing the wholecalist again, so with a provider's CA bundle a new connection takes about half the time and CPU, and a burst of them no longer blocks the event loop. Replacing one of the object's values, as with a renewedcert, builds a new context for the connections after it.nullis prepared, described and run in one round trip, as one whose parameter types are all known is, since those values are written the same for any type the server picks; one with other values only the server can type is still described first, once per connection.backoff, and onceconnect_timeouthas passed the connections waiting for it are closed and their queries reject, as for a failed startup.eager_types: truereads them when the pool starts, as before.timestamptz, andtimestampanddateread as UTC, are decoded straight from the bytes when they use the built-in parsers, with the text parser for anything else (BC,infinity, years before 1000); about 20% more rows per second for rows with timestamps.byteaand arrays ofint2,int4,int8andboolare fetched in binary once a prepared statement's columns are known on the connection, when they use the built-in parsers: the values are the same as from the text, about 4.5 times faster to read forbyteaand 3 times for anint4[]. A custom parser for the type or its elements, andraw(), still get the text.v4.0.0Compare Source
v4 rewrites everything below the public API: the pool, the connection, the protocol, startup and the socket layer. Queries, helpers, transactions, cursors, COPY, listen, subscribe, large objects and the options work as in 3.4.9, apart from the changes below.
Security
database,userorconnectionvalue such as'db\0user\0postgres'was sent as further startup parameters, so an app that put untrusted input there, as with a database per tenant, could be made to log in as another user where that user needs no password, or withreplicationoroptionsset. A NUL in any protocol string, also in query text and identifiers, now rejects withNUL_CHARACTER.__proto__is an own property (also in v3). It became the prototype of the row, so a JSON value in it, for instance from a column alias built from user input, made the row inherit its properties (row.isAdmin), in query results and insql.subscriberows alike.Upgrading from v3
The README has an upgrade guide, with a query that shows whether the change in parameter types concerns you.
infer: falseby default) (#392). v3 typed aDateastimestamptz, a boolean, aBuffer, abigintand arrays of these from JavaScript; v4 sends them without a type and writes each value for the type the server asks for, as libpq does.timestamptzcolumns, values written to a column of their own type, strings, numbers and anything cast work as before. Outside UTC, aDatewritten to or compared with atimestampordatecolumn is taken as UTC instead of the session's local time, and atimestampis read as UTC, so it comes back as the instant that was written whatever the time zone. ADatewhere the query doesn't decide its type, such as indate_trunc('day', ${ date }), needs a cast (${ date }::timestamptz).infer: truekeeps v3's behaviour.requirecan load on Node.js 20.19 and 22.12 and later (require('postgres').default).sql.array()is removed. Pass the array as it is, with a cast where the query doesn't decide its type:${ ids }::int[]. Callingsql.array()throwsARRAY_REMOVED.sql.beginrejects withTRANSACTION_ROLLED_BACK; v3 took it as a commit.sqlafterrelease(), or on a transaction'ssqlafter it has committed or rolled back, rejects; v3 ran it on whatever connection it then had. Releasing a reserved connection while its ownBEGINis still open closes it instead of returning an open transaction to the pool.beginoutsidesql.begin, a reserved connection ormax: 1rejects withUNSAFE_TRANSACTIONand is rolled back. A query that looks like it may open a transaction runs alone on its connection, so nothing else lands in it, and any query that leaves its connection in a transaction is rolled back and rejected. v3 rejectedBEGINbut left the transaction open. A script that opens and closes its own transaction in one query, such as a migration file withBEGINandCOMMIT, runs; v3 ran it and still rejected it.sql.begin(#1236, #1238) or one opened by hand on a reserved connection, just as a server error would: the queries after it fail with25P02and a laterCOMMITanswersROLLBACK. v3 skipped the query and let the rest commit.int[]comes back as an array of numbers; v3 returned the text.53300) is retried withbackoffuntilconnect_timeouthas passed, and the waiting queries reject with that error only then. Such a refusal is often momentary: connections opened at the same time against a connection limit can all be refused while the server counts them. v3 rejected the queries of the refused connection at once.target_session_attrsrejects withCONNECTION_DESTROYEDinstead of retrying forever.AUTH_TYPE_NOT_IMPLEMENTEDinstead of waiting for the connect timeout.onerroris logged. When thesql.subscribestream fails and none of its subscriptions has anonerror, the error goes toconsole.errorbefore the stream reconnects; v3 dropped it. Passonerrorto handle or silence it.infinitytimestamps areInfinityand-Infinity, both ways, instead of an invalidDate.import postgres, { type Sql } from 'postgres'). A reserved connection is no longer typed as havingbeginandend, which it never had.New
scheduler: falsepipelines on the busy connection with the fewest queries in flight instead; v3 went round-robin over the busy connections. Withmax: 1it is off, as there is only one connection.queue_timeout: seconds a query, transaction or reserve may wait for a connection before it rejects withQUEUE_TIMEOUT, unsent (#1036, #824)..signal(abortSignal)on a query: aborting rejects it if it is still waiting, or cancels it if it is running..cancel()or an aborted.signal()while the cursor's callback runs, or while an async iterator over it is between batches, fetches no more rows, closes the portal and rejects with57014. v3 sent a cancel request that the idle server ignored, so the cursor ran on to the end (or, arriving late, could cancel the next query).sql` and `.join(items)joins fragments and values with a fragment as separator, leaving out falsy items and adding spaces where missing (#807, #813).sql.begininside a transaction runs in a savepoint (#985).sql.rollback()rolls back a transaction, or the savepoint it is called in, without throwing;sql.beginresolves with the callback's result (#500).Promise.allbetween awaited ones are sent together and run in the order they were made (#951).toandfromcan be type names, looked up when the pool starts, with array types, for extension types such as pgvector (#1115, #719).usingandawait using:sqlends the pool and a reserved connection is released when it goes out of scope (#886, #1094).unref_sockets: truelets the process exit while connections are idle, likeallowExitOnIdlein node-postgres (#869, #918).min: connections kept open once the pool is used;idle_timeoutcloses only those above it (#672).useras a function, called each time a connection opens, likepassword, for credentials that rotate (#881).PGPASSFILEor~/.pgpassis read as libpq reads it (#964).name => valuearguments,make_interval(${ sql({ days: 1 }) })(#1040).copy ... from stdinwritable hascountandcommandonce it has finished, with the number of rows the server copied (#465).slot: 'name'keeps the slot ofsql.subscribeacross restarts and streams from where it was last confirmed. A transaction is confirmed only once its handlers have finished, a handler that fails gets its transaction delivered again, and handlers may be async: they run one after the other in commit order while the stream waits.sql.subscribe:truncateevents, and thelsn,timeandxidof the transaction on every change. Reconnecting retries with backoff.tls-server-end-point) when the server offers it, binding the login to the server certificate.channel_bindingis'prefer'by default,'require'or'disable'as in libpq, also from the url orPGCHANNELBINDING;'require'rejects an unbound connection withCHANNEL_BINDING_REQUIRED(#1008).sslnegotiation: 'direct'(PostgreSQL 17+) is opened as TLS from the first byte and is the fastest encrypted connection.workerdexport condition, oncloudflare:sockets, with TLS bystartTls.query.textandquery.parametersshow the text and values a query will be sent with, before it is sent.sql.stats: connections, opening, idle, leased and waiting, right now.postgres:query,postgres:connectandpostgres:transactionas diagnostics tracing channels, andpostgres:poolandpostgres:noticeas plain channels, for OpenTelemetry and other tracers without an option or a wrapper. Nothing is published, and nothing costs, without a subscriber. See Tracing.max: 1only for a query that is alone on the connection, so queries keep their order. v3 kept failing until the connection was replaced. Each connection reads rows and writes values only by what its own server described for the statement. When one connection sees a statement describe differently, the others prepare it again before their next use instead of trusting what they learned before.sql.close()closes the connections once their queries are done and leaves the pool usable.options.socketis also called with the host and port, and a socket can say it has done TLS itself (negotiated: true).onread: falsereads through the socket stream instead of a shared buffer, should a runtime ever misbehave with it.Fixed
The open issues and pull requests were triaged into 33 correctness bugs, each with a regression test in the v4 suite. Among them:
data_type_name; v3 set it as'data type_name', with a space, and typed it astype_name.JSON.stringifyof aPostgresErrorincludes its message (#767, #1132).readable({ start, end })stops atend; v3 ignored it and read to the end of the object.file://urls.TLS_HANDSHAKE_FAILEDinstead ofCONNECTION_CLOSEDorCONNECT_TIMEOUT(#1213), andperf_hooksis no longer imported (#990). Writes are split into 16 KiB pieces, as Workers were reported to drop a plain connection on a larger write (#1268).42P05(#960).ISqlis exported for functions that take the pool, a transaction or a reserved connection (#1163, #1150);PostgresErroris exported by name as a value as well as a type (#862, #703); nested arrays withnulland other values in helpers (#743); a typed query with a helper inside (#958); column lists that arestring[], while unknown literal columns are still caught (#916); class instances andobjectas JSON (#664, #625);nullandfalsefor the timer options (#1178).update t as x set ${ sql(o) }(#974),insert into t as x ${ sql(o) }(#517, #774),grant update on ${ sql(tables) }(#1101), rows inin (${ sql(rows) })(#820), a column list forinsert into t ${ sql(columns) } select ...(#769), objects in avalueshelper with picked columns, also inside a fragment (#1073, #1042), and fragments among the columns ofselect ${ sql([...]) }(#777). An insert or update helper without columns throwsNO_COLUMNSinstead of sending invalid SQL (#644). Several names outside a clause,order by ${ sql('a', 'b') }, become a list of identifiers; v3 threw aTypeError.valueslist cast afterwards, is written as an array literal or as JSON instead of1,2or[object Object](#741, #872, #765).?host=and?port=as libpq reads them (#984), and passwords with characters that are not percent-encoded, which are used as written (#735, #890).sql.beginand not awaited finishes before the transaction commits.max: 1, a query made beforesql.beginruns before the transaction.connect_timeoutdestroys a TCP connection that is still being opened, so the process can exit.AUTHENTICATION_INCOMPLETE, so SCRAM cannot be skipped.onnoticethat throws during startup no longer fails the connection.sql.subscribeparses custom array types and domains like queries do.Bufferrejects its query, also in a cursor, instead of hanging the connection.DEALLOCATE ALL, a statement prepared again is not forgotten because of an older query's error.search_pathper tenant, say) gets the rows of its own schema on every connection.debuggets the values as they are sent and the parameter types, also on the first use of a statement.sql.listen()({ state, unlisten }) andsql.prepare()(returns the name) match what they return.notifyinside a transaction uses the transaction (#966).nullin arrays (#1258),Infinity(#728), years below 100 and offsets in seconds (#1185).PostgresErrorfrom a plain message (#703) and writable stacks (#1227).end()stops connection attempts under way and settles every query; a reserved connection is never stranded.Performance
int2,int4,oidandboolcolumns are decoded straight from the bytes when they use the built-in parsers; a custom parser still gets the text.onread) instead of a buffer per chunk, on Node, Bun and Deno.Testing
node tests/unit).node tests/simulate.js).sh tests/cf/run.sh) and a TypeScript test for the types (cd types && npm test).npm run coverage(node tests/coverage.js,--linesfor the lines not run) runs the tests with Node's own V8 coverage and reports it per file insrc, with no dependency; CI fails belowCOVERAGE_MIN.Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.