Uh oh!
There was an error while loading. Please reload this page.
fix(driver-sql): give redshift its connect-timeout row so the 10s dialect bound applies - #11836
Conversation
…ialect bound applies `SqlDriver` answers three questions about a knex `client` name from three tables. `redshift` was in `POSTGRES_WIRE_CLIENTS` (#11389 put it there for the calendar-day parser pin) but absent from `DIALECT_CONNECT_TIMEOUT`, so `withConnectBound` injected no `connectionTimeoutMillis` and the attempt fell through to the strictly looser 15s `pool.createTimeoutMillis` backstop — while the method's own docblock calls 10s "the effective bound", reached past only by a dialect with no such knob (SQLite) or one that ignores it. Redshift is neither: knex's `Client_Redshift extends Client_PG`, so `pg` honours the knob. Nothing errored and nothing was logged; the bound was just 50% looser. The row is added as a literal extension of `POSTGRES_EMIT_CLIENTS`, exactly as `cockroachdb` already was — no table is merged, converged or restructured, and `POSTGRES_WIRE_CLIENTS` and the dialect getters are untouched. No SQL-emission identity is granted. `redshift`'s absence from that table was load-bearing as documentation: the note inside `withConnectBound` cited it as the measured reason its early return must not skip the session pins. Adding the row retires that example, so the note is rewritten to record the retired measurement and to carry the reasoning directly, and the fixtures that pinned the absence are re-judged rather than respelled — including a tripwire that goes red the moment the two memberships diverge again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VK8rFDtg8eREaxBGX99Csn
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f105dd6e3e11084695100b8cbb506fa65a26a065 && git checkout f105dd6e3e11084695100b8cbb506fa65a26a065
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bb418686547de42c8feae103fa2e41dd419aa171 e2db65e58e842cd41a1ffb5a9f7ccca4935c31fc && git checkout -B drift-repro bb418686547de42c8feae103fa2e41dd419aa171 && git merge --no-ff e2db65e58e842cd41a1ffb5a9f7ccca4935c31fc
node scripts/docs-audit/affected-docs.mjs --json bb418686547de42c8feae103fa2e41dd419aa171 |
Uh oh!
There was an error while loading. Please reload this page.
Closes#11784
What was measured
SqlDriveranswers three separate questions about a knexclientname from three separate tables.redshiftwas a member of the wire table and absent from the connect-timeout table:redshiftbeforeisPostgres/isSqlite/isMysqlPOSTGRES_WIRE_CLIENTSDIALECT_CONNECT_TIMEOUTSo
withConnectBoundinjected noconnectionTimeoutMillisfor aredshiftdatasource, and the attempt fell through to the strictly looser 15spool.createTimeoutMillisbackstop — whileDEFAULT_CONNECT_TIMEOUT_MS's own docblock calls 10s "the effective bound", with the pool value "a strictly looser backstop, reached only by a dialect that has no connect-timeout knob (SQLite) or ignores the one we set".A redshift host is neither of those, and this was verified rather than inherited from the card: knex's
Client_Redshiftliterallyextends Client_PG(knex/lib/dialects/redshift/index.js), inheritingacquireRawConnection, so the settings object reachespg.Client, which honoursconnectionTimeoutMillis. It had the knob and would have obeyed it; it just never received it.The before-state is silence — nothing errors, nothing is logged, the bound is simply 50% looser for one client name. That is why no test here asserts "it works": the assertions pin the injected knex config.
What changed
One row, added as a literal extension of
POSTGRES_EMIT_CLIENTSin exactly the shapecockroachdbalready had:What was deliberately NOT changed
redshiftgains no emission identity;flagsstay{sqlite:false, postgres:false, mysql:false}and it keeps itsCURRENT_TIMESTAMPdefault. That is the open support-scope decision [Decision] 会说 Postgres 方言的另外两种数据库(Redshift、CockroachDB),平台要不要当 Postgres 来建表?(#11550 的宽半边) #11756, which this branch does not answer and does not wait on: the connect-timeout knob is a property of the npm driver doing the connecting, not of which DDL dialect gets compiled. The independence is now pinned mechanically (timeout row present and emission identity still false, asserted in the same case) instead of argued in prose.POSTGRES_WIRE_CLIENTS) — untouched;redshiftwas already in it.client: 'postgres'silently loses every dialect-specific behaviour #11550's entire subject and is gated on [Decision] 会说 Postgres 方言的另外两种数据库(Redshift、CockroachDB),平台要不要当 Postgres 来建表?(#11550 的宽半边) #11756. That card ispm:blockedand unassigned, so there was no live occupant — but it declares this same table, so this branch keeps the diff to one row that a restructure can carry forward trivially.DIALECT_CONNECT_TIMEOUTnow holds the same five names asPOSTGRES_WIRE_CLIENTS. That is a coincidence of today's membership, not an invariant, and spelling it as...POSTGRES_WIRE_CLIENTSwould handredshiftandcockroachdbSQL-emission identity as a silent refactor side effect. Both the table comment and the #11550 membership test now say so explicitly.Surface check, per the dispatch: this moves a connect-timeout bound, not a contract's accept/reject surface and not a public API surface.
DIALECT_CONNECT_TIMEOUTis aprivate static readonly; no exported type, error code or request verdict moves. The behaviour delta is that a redshift connection attempt which would previously have completed between 10s and 15s now fails at 10s with pg's accuratetimeout expiredinstead of knex's misleading "the pool is probably full".The load-bearing comment — repaired, not deleted
redshift's absence from this table was load-bearing as documentation in two places, and a third pinned it as a test fixture:DIALECT_CONNECT_TIMEOUT's own comment said "redshiftstill carries no entry; see the note inwithConnectBoundfor why that absence is load-bearing." — rewritten.withConnectBoundcited it by name as the measured reason its early return must not skip the session pins: "areturnplaced at this point silently opted it out of a fix it needs" — rewritten.sql-driver-11550-…test.ts's membership pin repeated the claim in its comment — rewritten.The note is repaired, not dropped, and it keeps both halves. The retired measurement is recorded as retired (redshift really was in
POSTGRES_WIRE_CLIENTSand really was absent here, so an early return really did opt it out — that happened, and deleting it would lose it). Then the hazard is restated without depending on the example: every client that needs a session pin happens to have a timeout row today, which is a fact about current membership and not a property either table promises, so the next pg-wire client added without a connect-timeout knob restores the exact bug — silently, with every test green.Because there is no longer a client that demonstrates the hazard by observation, the guard is a tripwire rather than a behavioural assertion: it computes
(POSTGRES_WIRE_CLIENTS ∪ MYSQL_EMIT_CLIENTS) \ keys(DIALECT_CONNECT_TIMEOUT)and goes red the moment that set is non-empty, with a failure message telling the author the divergence is allowed but that they must confirm the fall-through is intact and refresh the note with the live example they just created.Fixture triage
Three fixtures pinned the old state; each was re-judged individually rather than respelled:
sql-driver-connect-bound.test.ts— new subject case pinning the injected config (URL form and object form), with the two controls the dispatch asked for:pgas the positive control (a client that already had a row, so a broken injection path fails them together rather than looking redshift-specific) andbetter-sqlite3as the negative control (legitimately has no such knob, so a row-blind injector that timed everything would be caught rather than read as a pass). Plus the "must not be equal" invariant: 10s dialect bound with 15s pool still strictly looser behind it.sql-driver-11389-date-tz-skew.test.ts— this case read redshift's absence as its demonstration (connectionTimeoutMillistoBeUndefined()). Its assertion was inverted, not deleted, because the fact it exists to pin is driver-sql: every Field.date read from PostgreSQL is one day early when the process TZ is east of UTC — toDateOnly() reads UTC components off a local-midnight Date #11389's — a pg-wire client reaches the calendar-day parser hook — and that is unchanged.sql-driver-11550-dialect-client-spellings.test.ts— membership pin updated to includeredshift(a real, deliberate membership change, the only one since driver-sql: the isSqlite/isPostgres/isMysql getters recognise fewer knex client spellings than knex accepts, so a validclient: 'postgres'silently loses every dialect-specific behaviour #11550's no-op refactor), and its comment repaired.Ablation
Predicted direction stated in advance: RED, naming the 5 cases expected to fail. Mutation proven on disk before any test result was read, by anchored grep counts on the text actually being changed, both directions:
Restore ran under
trap … EXIT INT TERM; verified on disk afterwards (with-redshift 1 / without-redshift 0) withgit status --porcelainempty.Result — exactly the 5 predicted cases, no more, no fewer:
No rebuild was performed, and that is a measurement rather than an assumption: these tests import
./sql-driver.js, a relative specifier vitest resolves tosrc/, with no alias inpackages/drivers/driver-sql/vitest.config.tsredirecting it. Adist/-resolved subject would have stayed green — the dangerous direction — so an observed RED is itself the proof that the tests read source. A green ablation would have been rebuilt and re-run rather than believed.Honest note on what the ablation does not discriminate:
leaves a redshift host's own explicit connect timeout alonestays green under the mutation, because with no row nothing is injected and the host's 60s survives either way. It is a companion assertion, not a pin on this change.Verification
All at the pushed commit
e2db65e58e; the tree was clean and unchanged from that commit through every run below. Exit codes captured before any pipe (direct redirect +set -o pipefail), and each gate's own printed verdict line is quoted rather than a bare$?.pnpm --filter @objectstack/driver-sql test→TEST_EXIT=0—Test Files 126 passed | 8 skipped (134),Tests 1970 passed | 110 skipped (2080)pnpm --filter @objectstack/driver-sql typecheck→TYPECHECK_EXIT=0pnpm lint(full repo,eslint . --no-inline-config) →LINT_FULL_EXIT=0— run whole, so no narrowing is claimednode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack— 14 path-derived + 6 convention-triggered families, all 21 run, all exit 0 (withcheck:nul-bytes), including:check-driver-conformance: OK — 45 covered cell(s), 0 in the DEBT ledger, 0 exempt.check-engine-double-contract: OK — 401 pinned, 133 in the DEBT ledger, 2 exempt.check-nul-bytes: OK (scanned 6592 text file(s) … no raw ASCII control bytes)check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured … none above its recorded number.where-matcher conformance holds: 295 matcher(s) discovered … 0 silently-wrongquery-options-erasure ratchet holds … none newDriver-conformance ledger, before and after (lane standing promise):
45 covered / 0 DEBT / 0 exempt→45 covered / 0 DEBT / 0 exempt. Unchanged; nothing added to the ledger.No reverse-verification of a cross-package type change is reported, because there is none: the change is a private static table inside one package and adds no exported type.
Generated by Claude Code