turbine-orm 0.53.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/cjs/cli/mcp.js +6 -0
- package/dist/cjs/cli/studio.js +10 -0
- package/dist/cjs/client.d.ts +182 -25
- package/dist/cjs/client.js +210 -29
- package/dist/cjs/dialect.d.ts +24 -0
- package/dist/cjs/dialect.js +1 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/mssql.js +1 -0
- package/dist/cjs/mysql.js +1 -0
- package/dist/cjs/powdb.js +1 -0
- package/dist/cjs/query/builder.js +11 -2
- package/dist/cjs/query/utils.d.ts +85 -1
- package/dist/cjs/query/utils.js +151 -1
- package/dist/cjs/query/warn-registry.d.ts +6 -0
- package/dist/cjs/query/warn-registry.js +6 -0
- package/dist/cjs/sqlite.js +1 -0
- package/dist/cli/mcp.js +6 -0
- package/dist/cli/studio.js +11 -1
- package/dist/client.d.ts +182 -25
- package/dist/client.js +211 -30
- package/dist/dialect.d.ts +24 -0
- package/dist/dialect.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/mssql.js +1 -0
- package/dist/mysql.js +1 -0
- package/dist/powdb.js +1 -0
- package/dist/query/builder.js +11 -2
- package/dist/query/utils.d.ts +85 -1
- package/dist/query/utils.js +146 -1
- package/dist/query/warn-registry.d.ts +6 -0
- package/dist/query/warn-registry.js +6 -0
- package/dist/sqlite.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -429,7 +429,7 @@ const db = turbine({
|
|
|
429
429
|
});
|
|
430
430
|
```
|
|
431
431
|
|
|
432
|
-
> **`utcTimestamps` is a process-wide decision, not a per-client one.** The two halves settle differently. The WRITE half is per client: a `Date` bound to a zone-less `date` / `timestamp` column is rewritten to a UTC literal unless that client opted out. The READ half is a pg type
|
|
432
|
+
> **`utcTimestamps` is a process-wide decision, not a per-client one.** The two halves settle differently. The WRITE half is per client: a `Date` bound to a zone-less `date` / `timestamp` column is rewritten to a UTC literal unless that client opted out. The READ half is a set of pg type parsers on OIDs 1114 (`timestamp`), 1082 (`date`) and their array forms 1115 / 1182, and `pg.types.setTypeParser` installs one parser per OID for the whole pg module, shared by every pool, every raw query, and any other library on the same `pg`. The first client in the process settles it for all the rest. Constructing a second client with the **opposite** value therefore throws `ValidationError` (`TURBINE_E003`) at construction, rather than handing back a client that writes UTC and reads local (or the reverse) and so does not round-trip its own values. The message names both values and the two ways out: give every client in the process the same `utcTimestamps`, or run the odd one out in its own process. Clients built on an external pool (`pool: ...`, `turbineHttp()`) never REGISTER a parser and settle nothing on their own, so a process holding only those keeps whatever parser configuration the caller set up. They are not exempt from the check, though: registration is process-global, so once a Turbine-owned client has installed the parsers an external-pool client reads through them too, and a disagreeing one would write local `date` literals while reading UTC.
|
|
433
433
|
|
|
434
434
|
> **Upgrading with `utcTimestamps: false`.** If you already set `false`, this release changes the **stored text** of your writes: the write path now honors the flag where it previously ignored it, so zone-less columns receive local-calendar literals instead of UTC ones. Rows written before the upgrade and rows written after carry two conventions in the same column until you backfill.
|
|
435
435
|
|
|
@@ -446,11 +446,16 @@ const db = turbine({
|
|
|
446
446
|
preparedStatements: true, // see the warning below
|
|
447
447
|
sqlCache: true, // SQL template cache (default true)
|
|
448
448
|
sqlCacheSize: 1000, // distinct query SHAPES retained per table (default 1000)
|
|
449
|
+
// Postgres only, opt-in, unset by default (Turbine then sends nothing).
|
|
450
|
+
// Pins how the backend picks between a custom and a generic plan.
|
|
451
|
+
// planCacheMode: 'force_custom_plan',
|
|
449
452
|
});
|
|
450
453
|
```
|
|
451
454
|
|
|
452
455
|
Where a pg-style alias exists (`max`, `idleTimeoutMillis`, `connectionTimeoutMillis`), the explicit Turbine field wins when both are set.
|
|
453
456
|
|
|
457
|
+
> **`planCacheMode` (Postgres only, opt-in).** PostgreSQL promotes a **named** prepared statement to a generic plan on its sixth execution, and a generic plan is costed blind to the bound values. On a predicate whose selectivity swings per value (a `tenant_id` equality on a shared table, where one value matches a handful of rows and another matches most of them), the statement can be locked onto a plan chosen for the average value, and it never reverts. `planCacheMode: 'auto' | 'force_custom_plan' | 'force_generic_plan'` pins the backend's choice; `'force_custom_plan'` re-plans every execution and removes the cliff. It is applied as a connection parameter (`options=-c plan_cache_mode=...`) when Turbine opens a connection, so it is in force for that connection's first statement and for every checkout, `$transaction`, stream and pipeline on it, and it cannot race your first query. Leave it unset (the default) and Turbine sends nothing at all. Reach for it when you have measured a statement getting slower after its fifth execution: `count()` on a skewed predicate is the shape that promotes through Turbine, while `findMany` / `findFirst` bind `LIMIT $n` and are much less exposed. Three scope limits: it does nothing on an **external pool** (Turbine never opens those connections, so set the GUC in the driver's own setup; Turbine-owned string `replicas` on that same client DO get it); a Postgres wire-compatible engine without the setting (CockroachDB, YugabyteDB, pre-12 PostgreSQL) refuses the connection parameter itself; and a **connection pooler** may filter startup parameters (PgBouncer's `ignore_startup_parameters`), where `ALTER ROLE ... SET plan_cache_mode = ...` is the way in. Any value outside the three throws `ValidationError` at construction, and a non-Postgres engine throws `UnsupportedFeatureError` (`TURBINE_E017`).
|
|
458
|
+
|
|
454
459
|
> **`preparedStatements` and connection poolers.** With prepared statements on, Turbine submits queries as `{ name, text, values }` so Postgres caches the parse and plan **per backend connection**. That is a real win against a database you connect to directly, and a hazard behind a transaction-pooling proxy (PgBouncer in `transaction` mode, Supabase's pooler port, some serverless poolers): the named statement is prepared on one backend and your next query may land on another, which fails with `prepared statement "..." does not exist`. Turbine defaults it to `true` only for pools it creates itself and `false` for external pools passed via `pool` / `turbineHttp()`, because serverless drivers are the common case there. If you are pointing a Turbine-owned pool at a transaction pooler, set `preparedStatements: false`. The environment variable `TURBINE_DISABLE_PREPARED=1` turns it off globally without a code change.
|
|
455
460
|
|
|
456
461
|
### Client escape hatches
|
|
@@ -1092,7 +1097,7 @@ Turbine maps Postgres types to TypeScript:
|
|
|
1092
1097
|
| `int8` / `bigint` | `number` | Values > `Number.MAX_SAFE_INTEGER` (2^53 - 1) are returned as `string` at runtime to avoid precision loss. This affects < 0.01% of use cases (auto-increment IDs, counts, etc. are all safe). |
|
|
1093
1098
|
| `numeric`, `money` | `string` | Arbitrary precision, kept as string to avoid JS float issues |
|
|
1094
1099
|
| `text`, `varchar`, `uuid`, `citext` | `string` | |
|
|
1095
|
-
| `timestamptz`, `timestamp`, `date` | `Date` | `timestamp` (without time zone)
|
|
1100
|
+
| `timestamptz`, `timestamp`, `date` | `Date` | `timestamp` (without time zone) **and `date`** are parsed as UTC by default (Prisma/Rails/Django convention), so the same row yields the same instant in every region. Opt out with `utcTimestamps: false`. Since v0.52 the flag also reaches the WRITE side on Postgres: it governs the `Date` values that `create` / `update` / `upsert` and `where` clauses bind to zone-less `date` / `timestamp` columns. Before v0.52 it reached the read path only, so a client that had set `false` was still binding UTC, and those statements (and the text they store) change on upgrade. The two halves settle at different scopes: the write half is per client, the read half is a process-global pg type parser, so every client in one process must agree or construction throws `ValidationError`. Since v0.54 `date` reads at UTC midnight rather than the process's local midnight (its array form too), which moves the **epoch value** of a `date` by your process's UTC offset even though the calendar day it denotes is unchanged: format with `toISOString().slice(0, 10)`, not with local-component helpers like `toLocaleDateString()`. See [Relation loading and wire encoding](#relation-loading-and-wire-encoding). |
|
|
1096
1101
|
| `boolean` | `boolean` | |
|
|
1097
1102
|
| `json`, `jsonb` | `unknown` | |
|
|
1098
1103
|
| `bytea` | `Buffer` | |
|
package/dist/cjs/cli/mcp.js
CHANGED
|
@@ -13,6 +13,7 @@ const pg_1 = __importDefault(require("pg"));
|
|
|
13
13
|
const index_advisor_js_1 = require("../index-advisor.js");
|
|
14
14
|
const introspect_js_1 = require("../introspect.js");
|
|
15
15
|
const index_js_1 = require("../query/index.js");
|
|
16
|
+
const utils_js_1 = require("../query/utils.js");
|
|
16
17
|
const schema_js_1 = require("../schema.js");
|
|
17
18
|
const migrate_js_1 = require("./migrate.js");
|
|
18
19
|
const pii_tags_js_1 = require("./pii-tags.js");
|
|
@@ -117,6 +118,11 @@ const TOOLS = [
|
|
|
117
118
|
function startMcpServer(options, transport = {}) {
|
|
118
119
|
const input = transport.input ?? process.stdin;
|
|
119
120
|
const output = transport.output ?? process.stdout;
|
|
121
|
+
// Read zone-less `date` / `timestamp` values as UTC, as TurbineClient does on
|
|
122
|
+
// a pool it owns. This server builds its own raw pool, so without it a
|
|
123
|
+
// `date` sampled here is serialized at the CLI process's local midnight while
|
|
124
|
+
// the application reading the same row through Turbine sees UTC midnight.
|
|
125
|
+
(0, utils_js_1.registerUtcTemporalParsers)();
|
|
120
126
|
const ctx = {
|
|
121
127
|
options,
|
|
122
128
|
pool: new pg_1.default.Pool({ connectionString: options.url, max: 2, idleTimeoutMillis: 10_000 }),
|
package/dist/cjs/cli/studio.js
CHANGED
|
@@ -103,6 +103,16 @@ async function startStudio(options) {
|
|
|
103
103
|
statementTimeout = { sql: 'SELECT 1', params: [] };
|
|
104
104
|
}
|
|
105
105
|
else {
|
|
106
|
+
// Read zone-less `date` / `timestamp` values as UTC, exactly as
|
|
107
|
+
// TurbineClient does on a pool it owns. Studio builds a raw pg.Pool and
|
|
108
|
+
// never constructs a TurbineClient, so without this the viewer renders a
|
|
109
|
+
// `date` cell at the CLI process's local midnight while the application
|
|
110
|
+
// reading the same row through Turbine sees UTC midnight: the previous
|
|
111
|
+
// evening east of UTC. It also matters for `--write`, where the cell the
|
|
112
|
+
// UI echoes back is what gets stored. The CLI process is Turbine's own, so
|
|
113
|
+
// there is no foreign pg consumer to disturb, and the parsers are
|
|
114
|
+
// registered before the first query runs.
|
|
115
|
+
(0, utils_js_1.registerUtcTemporalParsers)();
|
|
106
116
|
// pg.Pool satisfies the PgCompatPool contract (same as the external-pool
|
|
107
117
|
// seam in client.ts); the cast keeps one typed pool field for both modes.
|
|
108
118
|
pool = new pg_1.default.Pool({
|
package/dist/cjs/client.d.ts
CHANGED
|
@@ -125,6 +125,11 @@ export interface TurbineDriver {
|
|
|
125
125
|
/** SQL dialect: placeholders, transaction keywords, session-config, capability flags. */
|
|
126
126
|
readonly dialect: Dialect;
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* The values PostgreSQL's `plan_cache_mode` accepts. See
|
|
130
|
+
* {@link TurbineConfig.planCacheMode}.
|
|
131
|
+
*/
|
|
132
|
+
export type PlanCacheMode = 'auto' | 'force_custom_plan' | 'force_generic_plan';
|
|
128
133
|
export interface TurbineConfig {
|
|
129
134
|
/**
|
|
130
135
|
* An external pg-compatible pool. Use this to plug in serverless drivers
|
|
@@ -176,8 +181,11 @@ export interface TurbineConfig {
|
|
|
176
181
|
warnOnUnlimited?: boolean | Record<string, boolean>;
|
|
177
182
|
/**
|
|
178
183
|
* Interpret Postgres `timestamp` (without time zone) values as UTC, both
|
|
179
|
-
* at the driver level (
|
|
180
|
-
*
|
|
184
|
+
* at the driver level (type parsers for OIDs 1114 `timestamp`, 1082 `date`,
|
|
185
|
+
* and their array forms 1115 / 1182, registered only when Turbine owns the
|
|
186
|
+
* pool) and when coercing nested-relation JSON dates. A `date` column is
|
|
187
|
+
* zone-less too, so it reads back as UTC midnight rather than the process's
|
|
188
|
+
* local midnight. This is the
|
|
181
189
|
* Prisma/Rails/Django convention and makes results independent of the
|
|
182
190
|
* server's local time zone. Default: `true`. Set `false` for the legacy
|
|
183
191
|
* local-time interpretation, which also turns off the matching WRITE-side
|
|
@@ -186,15 +194,76 @@ export interface TurbineConfig {
|
|
|
186
194
|
*
|
|
187
195
|
* PER PROCESS, NOT PER CLIENT. The read half is a pg type parser, and
|
|
188
196
|
* `pg.types.setTypeParser` installs one parser per OID for the whole
|
|
189
|
-
* process. The first
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
197
|
+
* process. The first client settles it for every later one, so constructing
|
|
198
|
+
* a second client with the OPPOSITE value throws a `ValidationError` rather
|
|
199
|
+
* than handing back a client whose writes and reads disagree. Give every
|
|
200
|
+
* client in the process the same value, or isolate the odd one in its own
|
|
201
|
+
* process. A client on an EXTERNAL pool never REGISTERS a parser (it inherits
|
|
202
|
+
* whatever configuration the caller's driver has, so a process containing
|
|
203
|
+
* only external-pool clients is untouched), but it does take part in the
|
|
204
|
+
* agreement check: registration is process-global, so once a Turbine-owned
|
|
205
|
+
* client has installed the parsers an external-pool client reads through them
|
|
206
|
+
* too, and a disagreeing one would write local `date` literals while reading
|
|
207
|
+
* UTC.
|
|
196
208
|
*/
|
|
197
209
|
utcTimestamps?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Pin `plan_cache_mode` on every connection this client opens, fixing how the
|
|
212
|
+
* PostgreSQL backend chooses between a custom plan (re-planned per parameter
|
|
213
|
+
* set) and a generic plan (planned once, blind to the values).
|
|
214
|
+
*
|
|
215
|
+
* Why it exists: Turbine sends NAMED prepared statements by default on a pool
|
|
216
|
+
* it owns, and PostgreSQL promotes a named statement to a generic plan after
|
|
217
|
+
* five executions. A generic plan is planned for the AVERAGE parameter, so a
|
|
218
|
+
* predicate whose selectivity varies wildly per value (the canonical case is a
|
|
219
|
+
* `tenant_id` / `user_id` equality on a shared table, where one value matches
|
|
220
|
+
* a handful of rows and another matches most of them) is planned blind to the
|
|
221
|
+
* value it will actually get, and it never reverts. `'force_custom_plan'`
|
|
222
|
+
* removes that, at the cost of re-planning each execution.
|
|
223
|
+
*
|
|
224
|
+
* Scope, measured rather than assumed: it applies to the statements Turbine
|
|
225
|
+
* itself promotes. `count()` on such a predicate is promoted after five
|
|
226
|
+
* executions and is controlled by this option; `findMany` / `findFirst` bind
|
|
227
|
+
* `LIMIT $n`, and a parameterized limit denies the planner the limit fraction
|
|
228
|
+
* that makes a skewed plan look cheap, so those are much less exposed. Treat
|
|
229
|
+
* the option as a targeted remedy for a plan that is measurably worse after
|
|
230
|
+
* its fifth execution, not a general speed-up.
|
|
231
|
+
*
|
|
232
|
+
* Default `undefined`: Turbine issues NOTHING and the backend keeps its own
|
|
233
|
+
* default (`auto`), byte-identical to not setting the option.
|
|
234
|
+
*
|
|
235
|
+
* SESSION-LEVEL, NOT PER QUERY. It is applied as a connection parameter
|
|
236
|
+
* (`options=-c plan_cache_mode=...`) when the pool opens a connection, so it
|
|
237
|
+
* is in force for that connection's very first statement and persists for its
|
|
238
|
+
* whole life: every pooled checkout, `$transaction`, stream and pipeline on
|
|
239
|
+
* that connection inherits it, and it is NOT reset between checkouts. A
|
|
240
|
+
* caller's own `SET` / `SET LOCAL` still overrides it for that session or
|
|
241
|
+
* transaction, exactly as it would over any other session default.
|
|
242
|
+
*
|
|
243
|
+
* POSTGRES-ONLY. Engines whose dialect does not report
|
|
244
|
+
* `supportsPlanCacheMode` throw {@link UnsupportedFeatureError} (E017) at
|
|
245
|
+
* construction. The capability flag can only speak for the DIALECT, though,
|
|
246
|
+
* and `plan_cache_mode` is PostgreSQL 12+: a Postgres wire-compatible engine
|
|
247
|
+
* driven through the default `postgresDialect` (CockroachDB, YugabyteDB, an
|
|
248
|
+
* older server) has no such setting, and rejects the connection parameter
|
|
249
|
+
* itself with `unrecognized configuration parameter` at the first checkout
|
|
250
|
+
* rather than with E017. Leave the option unset on those.
|
|
251
|
+
*
|
|
252
|
+
* EXTERNAL POOLS ARE NOT TOUCHED. When the caller supplies `pool`, they own
|
|
253
|
+
* connection lifecycle, and Turbine has no hook that runs on their
|
|
254
|
+
* connections without also mutating a pool it does not own. Same rule as the
|
|
255
|
+
* type parsers above. The option is then a no-op on that pool, with a
|
|
256
|
+
* dev-mode warning; a serverless/HTTP driver should set the GUC in its own
|
|
257
|
+
* connection setup. Turbine-OWNED string `replicas` are still Turbine's own
|
|
258
|
+
* connections and do get it, even next to an external primary, so setting it
|
|
259
|
+
* in that shape splits the policy between reads and writes.
|
|
260
|
+
*
|
|
261
|
+
* BEHIND A CONNECTION POOLER, the GUC travels as a connection-time `options`
|
|
262
|
+
* startup parameter, which a pooler may refuse to pass through (PgBouncer's
|
|
263
|
+
* `ignore_startup_parameters`). Set it on the role or server there
|
|
264
|
+
* (`ALTER ROLE ... SET plan_cache_mode = ...`) instead.
|
|
265
|
+
*/
|
|
266
|
+
planCacheMode?: PlanCacheMode;
|
|
198
267
|
/**
|
|
199
268
|
* Refuse a nested `connect` / `connectOrCreate` that would re-parent a
|
|
200
269
|
* to-many child already owned by a different parent. Off by default,
|
|
@@ -545,21 +614,33 @@ export declare class TurbineClient {
|
|
|
545
614
|
readonly schema: SchemaMetadata;
|
|
546
615
|
private static int8ParserRegistered;
|
|
547
616
|
/**
|
|
548
|
-
* The `utcTimestamps` value the FIRST
|
|
549
|
-
*
|
|
550
|
-
* client has been constructed yet.
|
|
617
|
+
* The `utcTimestamps` value the FIRST TurbineClient in this process settled
|
|
618
|
+
* on, or `undefined` while none has been constructed yet.
|
|
551
619
|
*
|
|
552
620
|
* `pg.types.setTypeParser` is process-global by nature: there is one parser
|
|
553
621
|
* per OID for the whole pg module, so the READ side of `utcTimestamps` cannot
|
|
554
622
|
* be per client the way the WRITE side is. Recording the settled value (not
|
|
555
623
|
* just "registered yes/no") is what lets the constructor detect a second
|
|
556
624
|
* client asking for the opposite and refuse it, instead of handing back a
|
|
557
|
-
* client whose reads and writes disagree.
|
|
625
|
+
* client whose reads and writes disagree. Every client records it, including
|
|
626
|
+
* one on an external pool, which registers nothing but still READS through
|
|
627
|
+
* whatever an owned client in the same process installed. See
|
|
628
|
+
* {@link assertUtcTimestampsAgree}.
|
|
558
629
|
*/
|
|
559
630
|
private static utcTimestampParserMode;
|
|
631
|
+
/**
|
|
632
|
+
* Whether the zone-less temporal read parsers (OIDs 1114, 1082, 1115, 1182)
|
|
633
|
+
* have actually been installed. Separate from
|
|
634
|
+
* {@link utcTimestampParserMode}, which every client settles: only an OWNED
|
|
635
|
+
* pool registers, so a client on an external pool must not make a later
|
|
636
|
+
* owned client skip registration.
|
|
637
|
+
*/
|
|
638
|
+
private static utcTimestampParsersRegistered;
|
|
560
639
|
private readonly logging;
|
|
561
640
|
/** Active SQL dialect, owns transaction keywords, set_config, raw-SQL placeholders, capability flags. */
|
|
562
641
|
private readonly dialect;
|
|
642
|
+
/** Validated `plan_cache_mode` to pin on every owned connection, or undefined to issue nothing. */
|
|
643
|
+
private readonly planCacheMode;
|
|
563
644
|
private readonly tableCache;
|
|
564
645
|
private readonly middlewares;
|
|
565
646
|
private readonly queryListeners;
|
|
@@ -592,19 +673,89 @@ export declare class TurbineClient {
|
|
|
592
673
|
private primaryView?;
|
|
593
674
|
constructor(config: TurbineConfig | undefined, schema: SchemaMetadata);
|
|
594
675
|
/**
|
|
595
|
-
*
|
|
596
|
-
*
|
|
676
|
+
* Validate a caller-supplied `planCacheMode` and refuse it on an engine that
|
|
677
|
+
* has no plan cache to pin.
|
|
678
|
+
*
|
|
679
|
+
* Two refusals, both at construction rather than at first query, so a
|
|
680
|
+
* misconfigured client never opens a connection:
|
|
681
|
+
*
|
|
682
|
+
* - a value outside {@link PLAN_CACHE_MODES} throws `ValidationError`
|
|
683
|
+
* (E003). This is the security boundary as well as the usability one: a
|
|
684
|
+
* GUC value cannot be a bind parameter in either place it can be set (a
|
|
685
|
+
* `SET` statement or the connection `options` string), so the returned
|
|
686
|
+
* value is a MEMBER OF THE FROZEN LIST, never the caller's string, and
|
|
687
|
+
* there is no path by which caller text reaches the connection or the SQL.
|
|
688
|
+
* - a dialect that does not report `supportsPlanCacheMode` throws
|
|
689
|
+
* `UnsupportedFeatureError` (E017), in the same style as the other
|
|
690
|
+
* capability refusals.
|
|
691
|
+
*/
|
|
692
|
+
private static resolvePlanCacheMode;
|
|
693
|
+
/**
|
|
694
|
+
* Pin `plan_cache_mode` on every connection an OWNED pool opens, by putting
|
|
695
|
+
* it in the pool's **connection parameters** rather than issuing a `SET`.
|
|
696
|
+
*
|
|
697
|
+
* PostgreSQL's `options` startup parameter (`-c plan_cache_mode=...`) is
|
|
698
|
+
* applied by the backend as it starts the session, so the setting is in force
|
|
699
|
+
* for the connection's very first statement and for its whole life: every
|
|
700
|
+
* pooled checkout, `$transaction`, stream and pipeline on it inherits it.
|
|
701
|
+
* There is no per-checkout reset, and none is wanted, that IS the intent.
|
|
702
|
+
*
|
|
703
|
+
* Why not `pool.on('connect', c => c.query('SET ...'))`, the obvious
|
|
704
|
+
* alternative: pg hands the fresh client to the waiting caller in the same
|
|
705
|
+
* tick it emits `connect`, so the caller's first query is issued while the
|
|
706
|
+
* `SET` is still the active query. That path works today only through pg's
|
|
707
|
+
* deprecated same-client query queueing (it logs a DeprecationWarning per new
|
|
708
|
+
* connection and is slated for removal in pg 9), and it costs an extra round
|
|
709
|
+
* trip on every connection. The startup parameter costs nothing and cannot
|
|
710
|
+
* race.
|
|
711
|
+
*
|
|
712
|
+
* Nothing the caller already set is discarded, in either of the two places
|
|
713
|
+
* pg reads `options` from. pg's `ConnectionParameters` lets a value parsed
|
|
714
|
+
* out of a `connectionString` OVERRIDE the explicit `options` field, so when
|
|
715
|
+
* the URL already carries `?options=...` the GUC is appended THERE; and the
|
|
716
|
+
* explicit field itself falls back to `process.env.PGOPTIONS` only while it
|
|
717
|
+
* is unset, so setting it blind would silently drop a deployment's
|
|
718
|
+
* `PGOPTIONS` (its `search_path` or `statement_timeout`, not merely a slower
|
|
719
|
+
* plan). Both are read first and the GUC is appended to whichever applies.
|
|
720
|
+
*
|
|
721
|
+
* One deployment caveat: an `options` startup parameter is a connection-time
|
|
722
|
+
* parameter, and a connection pooler in front of Postgres may reject
|
|
723
|
+
* parameters it is not configured to pass through (PgBouncer's
|
|
724
|
+
* `ignore_startup_parameters`). A `SET` on checkout would survive that, at
|
|
725
|
+
* the cost of the race and the round trip above. Callers behind such a pooler
|
|
726
|
+
* should set the GUC on the server or role instead
|
|
727
|
+
* (`ALTER ROLE ... SET plan_cache_mode = ...`).
|
|
728
|
+
*/
|
|
729
|
+
private static withPlanCacheMode;
|
|
730
|
+
/**
|
|
731
|
+
* `connectionString` with `setting` appended to its existing `options` query
|
|
732
|
+
* parameter, or `null` when it carries no `options` (in which case the caller
|
|
733
|
+
* should use the `options` pool field, which is not overridden).
|
|
734
|
+
*
|
|
735
|
+
* Only the query string is rewritten, never the userinfo or host, so a
|
|
736
|
+
* percent-encoded password cannot be mangled by a round trip through `URL`.
|
|
737
|
+
* The split is on the first `?`, which is also where pg's own parser puts the
|
|
738
|
+
* query-string boundary: a connection string with an unencoded `?` inside the
|
|
739
|
+
* password is not parseable by pg either, so there is no shape this handles
|
|
740
|
+
* differently from the driver.
|
|
741
|
+
*/
|
|
742
|
+
private static mergeConnectionStringOptions;
|
|
743
|
+
/**
|
|
744
|
+
* Refuse a `utcTimestamps` value that contradicts the one an earlier client
|
|
745
|
+
* in this process settled the zone-less temporal read parsers (OIDs 1114,
|
|
746
|
+
* 1082, 1115, 1182) on.
|
|
597
747
|
*
|
|
598
748
|
* The flag has two halves. The WRITE half is per client: a bound `Date` on a
|
|
599
749
|
* zone-less `date` / `timestamp` column is rewritten to a UTC literal unless
|
|
600
750
|
* the owning client opted out (`coerceWriteValue` in query/writes.ts). The
|
|
601
|
-
* READ half is the pg type
|
|
602
|
-
* installs ONE parser per OID for the whole process,
|
|
603
|
-
* every raw query, and any other library using the same
|
|
604
|
-
* no per-pool parser hook to bind it to, and moving the
|
|
605
|
-
* `parseRow` instead would leave every non-ORM read (raw SQL,
|
|
606
|
-
* a caller's own `pool.query`) on the driver's value while
|
|
607
|
-
* default path's output type, so the read half stays
|
|
751
|
+
* READ half is the pg type parsers for OIDs 1114 / 1082 / 1115 / 1182, and
|
|
752
|
+
* `pg.types.setTypeParser` installs ONE parser per OID for the whole process,
|
|
753
|
+
* shared by every pool, every raw query, and any other library using the same
|
|
754
|
+
* pg module. There is no per-pool parser hook to bind it to, and moving the
|
|
755
|
+
* coercion into `parseRow` instead would leave every non-ORM read (raw SQL,
|
|
756
|
+
* `client.sql`, a caller's own `pool.query`) on the driver's value while
|
|
757
|
+
* changing the default path's output type, so the read half stays
|
|
758
|
+
* process-wide.
|
|
608
759
|
*
|
|
609
760
|
* That makes the mixed shape unserveable rather than merely awkward: the
|
|
610
761
|
* second client would write local calendar fields and read them back as UTC
|
|
@@ -612,9 +763,15 @@ export declare class TurbineClient {
|
|
|
612
763
|
* A client that silently does not round-trip is the worst of the three
|
|
613
764
|
* outcomes, so construction fails with the two ways out.
|
|
614
765
|
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
766
|
+
* EVERY client takes part, not only the ones on a Turbine-owned pool. Only an
|
|
767
|
+
* owned pool REGISTERS the parsers, but registration is process-global, so a
|
|
768
|
+
* client on an external pool (Neon, Vercel Postgres, Hyperdrive) constructed
|
|
769
|
+
* alongside an owned one reads through them too. It is exactly the pairing
|
|
770
|
+
* that produced a silent read/write disagreement: an external-pool client
|
|
771
|
+
* with `utcTimestamps: false` writing local `date` literals while reading UTC
|
|
772
|
+
* ones, which walks the stored calendar day back a day per read-modify-write
|
|
773
|
+
* cycle. An external-pool client ALONE in a process is unaffected: it settles
|
|
774
|
+
* the value, registers nothing, and keeps the caller's parser configuration.
|
|
618
775
|
*/
|
|
619
776
|
private static assertUtcTimestampsAgree;
|
|
620
777
|
/**
|