turbine-orm 0.65.0 → 0.66.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 +34 -32
- package/dist/adapters/cockroachdb.js +21 -3
- package/dist/adapters/index.d.ts +15 -0
- package/dist/adapters/yugabytedb.js +20 -3
- package/dist/cjs/adapters/cockroachdb.js +21 -3
- package/dist/cjs/adapters/index.d.ts +15 -0
- package/dist/cjs/adapters/yugabytedb.js +20 -3
- package/dist/cjs/cli/destructive.d.ts +18 -4
- package/dist/cjs/cli/destructive.js +230 -122
- package/dist/cjs/cli/index.d.ts +21 -4
- package/dist/cjs/cli/index.js +119 -22
- package/dist/cjs/cli/mcp.d.ts +28 -8
- package/dist/cjs/cli/mcp.js +170 -127
- package/dist/cjs/cli/migrate.d.ts +134 -13
- package/dist/cjs/cli/migrate.js +349 -241
- package/dist/cjs/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cjs/cli/pii-predicate-guard.js +390 -0
- package/dist/cjs/cli/prisma-resolve.js +75 -4
- package/dist/cjs/cli/prisma-schema.d.ts +17 -1
- package/dist/cjs/cli/prisma-schema.js +83 -17
- package/dist/cjs/cli/sql-statements.d.ts +125 -0
- package/dist/cjs/cli/sql-statements.js +378 -0
- package/dist/cjs/cli/studio.js +49 -118
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +43 -0
- package/dist/cjs/client.js +125 -6
- package/dist/cjs/dialect.d.ts +123 -0
- package/dist/cjs/dialect.js +33 -0
- package/dist/cjs/errors.d.ts +74 -1
- package/dist/cjs/errors.js +239 -25
- package/dist/cjs/index-advisor.d.ts +33 -1
- package/dist/cjs/index-advisor.js +32 -1
- package/dist/cjs/introspect.d.ts +48 -0
- package/dist/cjs/introspect.js +222 -91
- package/dist/cjs/mssql.js +43 -1
- package/dist/cjs/mysql.d.ts +5 -2
- package/dist/cjs/mysql.js +202 -17
- package/dist/cjs/nested-write.js +6 -1
- package/dist/cjs/pipeline-submittable.js +17 -3
- package/dist/cjs/pipeline.js +75 -9
- package/dist/cjs/powdb.d.ts +23 -0
- package/dist/cjs/powdb.js +33 -1
- package/dist/cjs/powql.d.ts +61 -9
- package/dist/cjs/powql.js +186 -49
- package/dist/cjs/prisma-compat.js +160 -41
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +80 -18
- package/dist/cjs/query/batched-loader.d.ts +10 -0
- package/dist/cjs/query/batched-loader.js +268 -7
- package/dist/cjs/query/builder.d.ts +73 -0
- package/dist/cjs/query/builder.js +225 -28
- package/dist/cjs/query/filters.d.ts +162 -0
- package/dist/cjs/query/filters.js +250 -1
- package/dist/cjs/query/relations.d.ts +10 -10
- package/dist/cjs/query/relations.js +93 -12
- package/dist/cjs/query/types.d.ts +14 -1
- package/dist/cjs/query/utils.d.ts +146 -2
- package/dist/cjs/query/utils.js +210 -4
- package/dist/cjs/query/warn-registry.d.ts +10 -0
- package/dist/cjs/query/warn-registry.js +10 -0
- package/dist/cjs/query/where-compile.d.ts +30 -0
- package/dist/cjs/query/where-compile.js +41 -0
- package/dist/cjs/query/where.d.ts +128 -13
- package/dist/cjs/query/where.js +215 -77
- package/dist/cjs/query/writes.d.ts +1 -1
- package/dist/cjs/query/writes.js +39 -15
- package/dist/cjs/schema-builder.d.ts +2 -1
- package/dist/cjs/schema-sql.d.ts +94 -4
- package/dist/cjs/schema-sql.js +506 -30
- package/dist/cjs/schema.d.ts +3 -1
- package/dist/cjs/sqlite.d.ts +6 -0
- package/dist/cjs/sqlite.js +151 -10
- package/dist/cjs/typed-sql.d.ts +29 -1
- package/dist/cjs/typed-sql.js +30 -12
- package/dist/cli/destructive.d.ts +18 -4
- package/dist/cli/destructive.js +229 -121
- package/dist/cli/index.d.ts +21 -4
- package/dist/cli/index.js +120 -24
- package/dist/cli/mcp.d.ts +28 -8
- package/dist/cli/mcp.js +172 -129
- package/dist/cli/migrate.d.ts +134 -13
- package/dist/cli/migrate.js +347 -238
- package/dist/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cli/pii-predicate-guard.js +386 -0
- package/dist/cli/prisma-resolve.js +75 -4
- package/dist/cli/prisma-schema.d.ts +17 -1
- package/dist/cli/prisma-schema.js +83 -17
- package/dist/cli/sql-statements.d.ts +125 -0
- package/dist/cli/sql-statements.js +373 -0
- package/dist/cli/studio.js +49 -118
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +43 -0
- package/dist/client.js +126 -7
- package/dist/dialect.d.ts +123 -0
- package/dist/dialect.js +33 -0
- package/dist/errors.d.ts +74 -1
- package/dist/errors.js +228 -19
- package/dist/index-advisor.d.ts +33 -1
- package/dist/index-advisor.js +31 -1
- package/dist/introspect.d.ts +48 -0
- package/dist/introspect.js +221 -91
- package/dist/mssql.js +44 -2
- package/dist/mysql.d.ts +5 -2
- package/dist/mysql.js +203 -18
- package/dist/nested-write.js +7 -2
- package/dist/pipeline-submittable.js +18 -4
- package/dist/pipeline.js +76 -10
- package/dist/powdb.d.ts +23 -0
- package/dist/powdb.js +33 -2
- package/dist/powql.d.ts +61 -9
- package/dist/powql.js +187 -50
- package/dist/prisma-compat.js +160 -41
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +82 -20
- package/dist/query/batched-loader.d.ts +10 -0
- package/dist/query/batched-loader.js +270 -9
- package/dist/query/builder.d.ts +73 -0
- package/dist/query/builder.js +226 -30
- package/dist/query/filters.d.ts +162 -0
- package/dist/query/filters.js +246 -1
- package/dist/query/relations.d.ts +10 -10
- package/dist/query/relations.js +94 -14
- package/dist/query/types.d.ts +14 -1
- package/dist/query/utils.d.ts +146 -2
- package/dist/query/utils.js +204 -3
- package/dist/query/warn-registry.d.ts +10 -0
- package/dist/query/warn-registry.js +10 -0
- package/dist/query/where-compile.d.ts +30 -0
- package/dist/query/where-compile.js +40 -1
- package/dist/query/where.d.ts +128 -13
- package/dist/query/where.js +216 -80
- package/dist/query/writes.d.ts +1 -1
- package/dist/query/writes.js +40 -16
- package/dist/schema-builder.d.ts +2 -1
- package/dist/schema-sql.d.ts +94 -4
- package/dist/schema-sql.js +505 -30
- package/dist/schema.d.ts +3 -1
- package/dist/sqlite.d.ts +6 -0
- package/dist/sqlite.js +151 -10
- package/dist/typed-sql.d.ts +29 -1
- package/dist/typed-sql.js +30 -12
- package/package.json +6 -4
package/dist/cjs/schema.d.ts
CHANGED
|
@@ -115,7 +115,9 @@ export interface ColumnMetadata {
|
|
|
115
115
|
* Tagged in `defineSchema` (`pii: true`) and carried through generated
|
|
116
116
|
* metadata. A PII column is EXCLUDED from default projections: it comes back
|
|
117
117
|
* only when explicitly named in `select` or when the query passes
|
|
118
|
-
* `includePii:
|
|
118
|
+
* `includePii: UNSAFE` (full opt-in, the imported sentinel symbol, since a
|
|
119
|
+
* literal `true` is what a stray JSON body deserializes to). Studio redacts
|
|
120
|
+
* PII cells by default.
|
|
119
121
|
* Optional / defaults to `false`; untagged schemas behave exactly as before.
|
|
120
122
|
* Introspection never auto-tags PII (it is a code-first declaration).
|
|
121
123
|
*/
|
package/dist/cjs/sqlite.d.ts
CHANGED
|
@@ -67,6 +67,12 @@ export declare class SqlitePool implements PgCompatPool {
|
|
|
67
67
|
/** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
|
|
68
68
|
readonly db: DatabaseSync;
|
|
69
69
|
private closed;
|
|
70
|
+
/**
|
|
71
|
+
* `sql -> declared-boolean result columns` (or `null` for none). Bounded like
|
|
72
|
+
* the query builder's SQL-template cache, since a caller can generate
|
|
73
|
+
* unbounded distinct statements (raw SQL, an IN-list that inlines).
|
|
74
|
+
*/
|
|
75
|
+
private readonly booleanCache;
|
|
70
76
|
constructor(db: DatabaseSync);
|
|
71
77
|
query(text: QueryArg, values?: unknown[]): Promise<any>;
|
|
72
78
|
connect(): Promise<PgCompatPoolClient>;
|
package/dist/cjs/sqlite.js
CHANGED
|
@@ -57,6 +57,7 @@ const client_js_1 = require("./client.js");
|
|
|
57
57
|
const dialect_js_1 = require("./dialect.js");
|
|
58
58
|
const errors_js_1 = require("./errors.js");
|
|
59
59
|
const introspect_js_1 = require("./introspect.js");
|
|
60
|
+
const utils_js_1 = require("./query/utils.js");
|
|
60
61
|
const schema_js_1 = require("./schema.js");
|
|
61
62
|
let cachedDatabaseSync;
|
|
62
63
|
/**
|
|
@@ -130,14 +131,101 @@ function normalizeValue(value) {
|
|
|
130
131
|
}
|
|
131
132
|
return value;
|
|
132
133
|
}
|
|
133
|
-
/**
|
|
134
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Convert a `node:sqlite` null-prototype row into a normalized plain object.
|
|
136
|
+
*
|
|
137
|
+
* `booleanKeys`, when present, names the result columns whose ORIGIN column is
|
|
138
|
+
* declared with a boolean affinity; their 1/0 storage is turned back into
|
|
139
|
+
* `true`/`false` (see {@link booleanResultColumns}). Anything else in such a
|
|
140
|
+
* column, and every column not listed, passes through untouched.
|
|
141
|
+
*/
|
|
142
|
+
function normalizeRow(row, booleanKeys) {
|
|
135
143
|
const out = {};
|
|
136
144
|
for (const key of Object.keys(row)) {
|
|
137
|
-
|
|
145
|
+
const value = normalizeValue(row[key]);
|
|
146
|
+
out[key] = booleanKeys?.has(key) && (value === 1 || value === 0) ? value === 1 : value;
|
|
138
147
|
}
|
|
139
148
|
return out;
|
|
140
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* The result columns of a prepared statement whose ORIGIN column is declared
|
|
152
|
+
* boolean, or `null` when there are none (the overwhelmingly common case).
|
|
153
|
+
*
|
|
154
|
+
* WHY THIS EXISTS. SQLite has no boolean storage class: a `BOOLEAN` column
|
|
155
|
+
* holds 1/0 integers. Turbine still generates `ok: boolean` for it
|
|
156
|
+
* ({@link sqliteTypeToTs} maps `/bool/` to `boolean`), so `row.ok === true` was
|
|
157
|
+
* false, `JSON.stringify(row)` emitted `1`, and a value the ORM itself wrote as
|
|
158
|
+
* `true` came back as a number. The declared type is the ONLY thing that says
|
|
159
|
+
* "this integer is a boolean", and it lives in the schema, not in the value.
|
|
160
|
+
*
|
|
161
|
+
* The decltype comes from the driver rather than from Turbine's metadata
|
|
162
|
+
* because the driver knows which TABLE each result column came from.
|
|
163
|
+
* `StatementSync.columns()` reports `type` as `sqlite3_column_decltype`, which
|
|
164
|
+
* is non-null only for a direct table-column reference and null for any
|
|
165
|
+
* expression, so a computed column can never be coerced by accident.
|
|
166
|
+
*
|
|
167
|
+
* Node gained `columns()` in 22.13 / 23.4 while this engine's floor is 22.5, so
|
|
168
|
+
* it is feature-detected: on the narrow band without it, booleans keep reading
|
|
169
|
+
* back as 1/0 exactly as before rather than the engine refusing to run.
|
|
170
|
+
*/
|
|
171
|
+
function booleanResultColumns(stmt) {
|
|
172
|
+
const columnsFn = stmt.columns;
|
|
173
|
+
if (typeof columnsFn !== 'function')
|
|
174
|
+
return null;
|
|
175
|
+
let described;
|
|
176
|
+
try {
|
|
177
|
+
described = columnsFn.call(stmt);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// A statement the driver cannot describe (some PRAGMA forms) is not worth
|
|
181
|
+
// failing a query over; skip the coercion for it.
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
let out = null;
|
|
185
|
+
for (const c of described) {
|
|
186
|
+
if (typeof c.name !== 'string' || typeof c.type !== 'string')
|
|
187
|
+
continue;
|
|
188
|
+
if (!/bool/i.test(c.type))
|
|
189
|
+
continue;
|
|
190
|
+
if (out === null)
|
|
191
|
+
out = new Set();
|
|
192
|
+
out.add(c.name);
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The `node:sqlite` namespace, looked up once, or `null` when this Node build
|
|
198
|
+
* has no such builtin. Deliberately separate from {@link loadDatabaseSync},
|
|
199
|
+
* which THROWS: this is a capability question asked from the pure dialect,
|
|
200
|
+
* which must stay importable on Node 20.
|
|
201
|
+
*/
|
|
202
|
+
let cachedSqliteNamespace;
|
|
203
|
+
/**
|
|
204
|
+
* Can the driver report a result column's DECLARED type?
|
|
205
|
+
*
|
|
206
|
+
* This is the same capability {@link booleanResultColumns} feature-detects, and
|
|
207
|
+
* the two have to be asked the SAME question or they split: that helper is what
|
|
208
|
+
* turns 1/0 into `true`/`false` on every direct read, and the boolean
|
|
209
|
+
* `jsonWireRule` is what does it on the join path. On the Node band with
|
|
210
|
+
* `node:sqlite` but without `StatementSync.columns()` (>= 22.5, < 22.13 / 23.4)
|
|
211
|
+
* the helper returns `null` and every direct read keeps 1/0, so a join that
|
|
212
|
+
* still converted would produce exactly the strategy-dependent type flip the
|
|
213
|
+
* rule exists to prevent, on the one runtime where nothing else can catch it.
|
|
214
|
+
*
|
|
215
|
+
* The prototype property is re-read on each call rather than memoized as a
|
|
216
|
+
* boolean, so the answer cannot outlive the capability.
|
|
217
|
+
*/
|
|
218
|
+
function driverReportsDeclaredTypes() {
|
|
219
|
+
if (cachedSqliteNamespace === undefined) {
|
|
220
|
+
try {
|
|
221
|
+
cachedSqliteNamespace = (0, node_module_1.createRequire)(process.cwd())('node:sqlite');
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
cachedSqliteNamespace = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return typeof cachedSqliteNamespace?.StatementSync?.prototype?.columns === 'function';
|
|
228
|
+
}
|
|
141
229
|
// ---------------------------------------------------------------------------
|
|
142
230
|
// Statement classification + error translation
|
|
143
231
|
// ---------------------------------------------------------------------------
|
|
@@ -241,7 +329,7 @@ function toNamedBinding(values) {
|
|
|
241
329
|
}
|
|
242
330
|
return named;
|
|
243
331
|
}
|
|
244
|
-
function runStatement(db, sql, values) {
|
|
332
|
+
function runStatement(db, sql, values, booleanCache) {
|
|
245
333
|
const binding = toNamedBinding(values);
|
|
246
334
|
let stmt;
|
|
247
335
|
try {
|
|
@@ -253,7 +341,15 @@ function runStatement(db, sql, values) {
|
|
|
253
341
|
stmt.setReadBigInts(true);
|
|
254
342
|
try {
|
|
255
343
|
if (statementReturnsRows(sql)) {
|
|
256
|
-
|
|
344
|
+
// Describe each distinct statement ONCE. `null` (no boolean column) is a
|
|
345
|
+
// cached answer too, so the ordinary query pays a single Map hit per call
|
|
346
|
+
// and nothing per row.
|
|
347
|
+
let booleanKeys = booleanCache?.get(sql);
|
|
348
|
+
if (booleanKeys === undefined) {
|
|
349
|
+
booleanKeys = booleanResultColumns(stmt);
|
|
350
|
+
booleanCache?.set(sql, booleanKeys);
|
|
351
|
+
}
|
|
352
|
+
const rows = (binding ? stmt.all(binding) : stmt.all()).map((r) => normalizeRow(r, booleanKeys ?? undefined));
|
|
257
353
|
return { rows, rowCount: rows.length };
|
|
258
354
|
}
|
|
259
355
|
const info = binding ? stmt.run(binding) : stmt.run();
|
|
@@ -276,21 +372,28 @@ class SqlitePool {
|
|
|
276
372
|
/** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
|
|
277
373
|
db;
|
|
278
374
|
closed = false;
|
|
375
|
+
/**
|
|
376
|
+
* `sql -> declared-boolean result columns` (or `null` for none). Bounded like
|
|
377
|
+
* the query builder's SQL-template cache, since a caller can generate
|
|
378
|
+
* unbounded distinct statements (raw SQL, an IN-list that inlines).
|
|
379
|
+
*/
|
|
380
|
+
booleanCache = new utils_js_1.LRUCache(1000);
|
|
279
381
|
constructor(db) {
|
|
280
382
|
this.db = db;
|
|
281
383
|
}
|
|
282
384
|
// biome-ignore lint/suspicious/noExplicitAny: pg-compat query is generic over the row shape; runStatement returns plain objects.
|
|
283
385
|
async query(text, values) {
|
|
284
386
|
const { text: sql, params } = normalizeQueryArgs(text, values);
|
|
285
|
-
return runStatement(this.db, sql, params);
|
|
387
|
+
return runStatement(this.db, sql, params, this.booleanCache);
|
|
286
388
|
}
|
|
287
389
|
async connect() {
|
|
288
390
|
const db = this.db;
|
|
391
|
+
const booleanCache = this.booleanCache;
|
|
289
392
|
return {
|
|
290
393
|
// biome-ignore lint/suspicious/noExplicitAny: see query() above.
|
|
291
394
|
query: async (text, values) => {
|
|
292
395
|
const { text: sql, params } = normalizeQueryArgs(text, values);
|
|
293
|
-
return runStatement(db, sql, params);
|
|
396
|
+
return runStatement(db, sql, params, booleanCache);
|
|
294
397
|
},
|
|
295
398
|
release: () => {
|
|
296
399
|
// Single shared connection, nothing to return to a pool.
|
|
@@ -375,6 +478,7 @@ exports.sqliteDialect = {
|
|
|
375
478
|
supportsReturning: true,
|
|
376
479
|
supportsILike: false,
|
|
377
480
|
supportsVector: false,
|
|
481
|
+
supportsJsonContains: false,
|
|
378
482
|
// FTS5 is a virtual-table feature with its own MATCH syntax, not the
|
|
379
483
|
// `to_tsvector @@ to_tsquery` shape Turbine's `search` filter emits.
|
|
380
484
|
supportsFullTextSearch: false,
|
|
@@ -447,6 +551,33 @@ exports.sqliteDialect = {
|
|
|
447
551
|
},
|
|
448
552
|
};
|
|
449
553
|
}
|
|
554
|
+
// A declared-boolean column is 1/0 in storage, and the driver shim turns
|
|
555
|
+
// that back into true/false on every direct read (see
|
|
556
|
+
// booleanResultColumns). `json_object` sees the raw integer and has no
|
|
557
|
+
// decltype to consult, so the join strategy alone would have kept handing
|
|
558
|
+
// back 1/0 while the top-level read, the batched loader and the flatten
|
|
559
|
+
// plan returned booleans, which is the same strategy-dependent type flip
|
|
560
|
+
// the bigint rule above exists to prevent. No cast is needed, only the
|
|
561
|
+
// decode: the JSON number IS the stored value, it just has to be read the
|
|
562
|
+
// same way.
|
|
563
|
+
//
|
|
564
|
+
// Gated on the SAME capability the direct-read half feature-detects (see
|
|
565
|
+
// driverReportsDeclaredTypes). Without it the shim cannot name a boolean
|
|
566
|
+
// result column, so 1/0 is the answer everywhere; converting here alone
|
|
567
|
+
// would put the flip back, pointing the other way.
|
|
568
|
+
if (t.includes('BOOL') && driverReportsDeclaredTypes()) {
|
|
569
|
+
return {
|
|
570
|
+
// Carried as TEXT because that is how the decode half is reached at
|
|
571
|
+
// all: the parser only runs `decode` on a string cell, so a rule that
|
|
572
|
+
// left the JSON number alone would never fire. The CASE narrows the
|
|
573
|
+
// carrier to EXACTLY the two values that mean true/false, so anything
|
|
574
|
+
// else a declared-boolean column happens to hold (SQLite does not
|
|
575
|
+
// enforce the type) stays in its own JSON form and reads back as the
|
|
576
|
+
// driver would have returned it, rather than being stringified.
|
|
577
|
+
sql: (ref) => `CASE WHEN ${ref} IN (0, 1) THEN CAST(${ref} AS TEXT) ELSE ${ref} END`,
|
|
578
|
+
decode: (value) => (value === '1' ? true : value === '0' ? false : value),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
450
581
|
// A BLOB cannot go into JSON at all: SQLite raises "JSON cannot hold BLOB
|
|
451
582
|
// values" and the whole query fails with a raw SQL logic error, where the
|
|
452
583
|
// batched loader returns the row fine. Carry hex and rebuild the bytes.
|
|
@@ -531,9 +662,14 @@ exports.sqliteDialect = {
|
|
|
531
662
|
// COLLATE NOCASE is ASCII-only (no Unicode case folding), documented limit.
|
|
532
663
|
return `${column} LIKE ${paramRef} COLLATE NOCASE`;
|
|
533
664
|
},
|
|
665
|
+
// UNREACHABLE while `supportsJsonContains` is false, and kept deliberately:
|
|
666
|
+
// it is the emulation this dialect would use if the encoding mismatch were
|
|
667
|
+
// repaired, and deleting it would delete the record of what was tried. The
|
|
668
|
+
// param is bound as JSON text (`'"gold"'`) while `json_each.value` yields the
|
|
669
|
+
// decoded SQL value (`gold`), so this predicate never holds for ANY operand.
|
|
670
|
+
// See `supportsJsonContains` in dialect.ts for the measurements and the shape
|
|
671
|
+
// a faithful version needs (`json_each.type` alongside the value).
|
|
534
672
|
buildJsonContains(column, paramRef) {
|
|
535
|
-
// Emulated containment: true when any top-level JSON value equals the param.
|
|
536
|
-
// Limited vs Postgres `@>` (no deep/object containment), jsonPathSupport='function'.
|
|
537
673
|
return `EXISTS (SELECT 1 FROM json_each(${column}) WHERE json_each.value = ${paramRef})`;
|
|
538
674
|
},
|
|
539
675
|
buildJsonPathExtract(column, pathParamRef) {
|
|
@@ -616,7 +752,12 @@ exports.sqliteDialect = {
|
|
|
616
752
|
function pragma(db, sql) {
|
|
617
753
|
// PRAGMA / SELECT against sqlite_master, read-only, identifiers are SQLite
|
|
618
754
|
// catalog names (never user input here), values normalized for safe ints.
|
|
619
|
-
|
|
755
|
+
// No boolean-key set: PRAGMA output columns are catalog metadata (`notnull`,
|
|
756
|
+
// `pk`, `unique`), which are 0/1 integers the introspector reads AS integers.
|
|
757
|
+
return db
|
|
758
|
+
.prepare(sql)
|
|
759
|
+
.all()
|
|
760
|
+
.map((r) => normalizeRow(r));
|
|
620
761
|
}
|
|
621
762
|
/**
|
|
622
763
|
* Read a live SQLite database (an open `DatabaseSync` handle) into the same
|
package/dist/cjs/typed-sql.d.ts
CHANGED
|
@@ -79,7 +79,35 @@ export declare class TypedSqlQuery<T extends Record<string, unknown>> implements
|
|
|
79
79
|
private readonly sql;
|
|
80
80
|
private readonly params;
|
|
81
81
|
private readonly logging;
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
84
|
+
* in rather than read here because this module must not import the client.
|
|
85
|
+
*
|
|
86
|
+
* It has to reach the EXECUTION, not the construction, and that is the whole
|
|
87
|
+
* reason it is a constructor parameter instead of a wrapper around
|
|
88
|
+
* `client.sql\`...\``: this builder is lazy, so a scope established around
|
|
89
|
+
* the tagged-template call is torn down before a single row is fetched.
|
|
90
|
+
* Wrapping the template call compiled, ran, and protected nothing.
|
|
91
|
+
*
|
|
92
|
+
* Defaults to calling through, so a `TypedSqlQuery` built outside a client
|
|
93
|
+
* behaves exactly as before.
|
|
94
|
+
*/
|
|
95
|
+
private readonly runScoped;
|
|
96
|
+
constructor(pool: PgCompatPool, sql: string, params: unknown[], logging: boolean,
|
|
97
|
+
/**
|
|
98
|
+
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
99
|
+
* in rather than read here because this module must not import the client.
|
|
100
|
+
*
|
|
101
|
+
* It has to reach the EXECUTION, not the construction, and that is the whole
|
|
102
|
+
* reason it is a constructor parameter instead of a wrapper around
|
|
103
|
+
* `client.sql\`...\``: this builder is lazy, so a scope established around
|
|
104
|
+
* the tagged-template call is torn down before a single row is fetched.
|
|
105
|
+
* Wrapping the template call compiled, ran, and protected nothing.
|
|
106
|
+
*
|
|
107
|
+
* Defaults to calling through, so a `TypedSqlQuery` built outside a client
|
|
108
|
+
* behaves exactly as before.
|
|
109
|
+
*/
|
|
110
|
+
runScoped?: <R>(fn: () => R) => R);
|
|
83
111
|
/** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
|
|
84
112
|
private run;
|
|
85
113
|
/**
|
package/dist/cjs/typed-sql.js
CHANGED
|
@@ -96,24 +96,42 @@ class TypedSqlQuery {
|
|
|
96
96
|
sql;
|
|
97
97
|
params;
|
|
98
98
|
logging;
|
|
99
|
-
|
|
99
|
+
runScoped;
|
|
100
|
+
constructor(pool, sql, params, logging,
|
|
101
|
+
/**
|
|
102
|
+
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
103
|
+
* in rather than read here because this module must not import the client.
|
|
104
|
+
*
|
|
105
|
+
* It has to reach the EXECUTION, not the construction, and that is the whole
|
|
106
|
+
* reason it is a constructor parameter instead of a wrapper around
|
|
107
|
+
* `client.sql\`...\``: this builder is lazy, so a scope established around
|
|
108
|
+
* the tagged-template call is torn down before a single row is fetched.
|
|
109
|
+
* Wrapping the template call compiled, ran, and protected nothing.
|
|
110
|
+
*
|
|
111
|
+
* Defaults to calling through, so a `TypedSqlQuery` built outside a client
|
|
112
|
+
* behaves exactly as before.
|
|
113
|
+
*/
|
|
114
|
+
runScoped = (fn) => fn()) {
|
|
100
115
|
this.pool = pool;
|
|
101
116
|
this.sql = sql;
|
|
102
117
|
this.params = params;
|
|
103
118
|
this.logging = logging;
|
|
119
|
+
this.runScoped = runScoped;
|
|
104
120
|
}
|
|
105
121
|
/** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
run() {
|
|
123
|
+
return this.runScoped(async () => {
|
|
124
|
+
if (this.logging) {
|
|
125
|
+
console.log(`[turbine] Typed SQL: ${this.sql.trim().substring(0, 120)}...`);
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const result = await this.pool.query(this.sql, this.params);
|
|
129
|
+
return result.rows;
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
throw (0, errors_js_1.wrapPgError)(err);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
117
135
|
}
|
|
118
136
|
/**
|
|
119
137
|
* PromiseLike implementation: `await db.sql<T>\`...\`` resolves to `T[]`.
|
|
@@ -18,15 +18,27 @@
|
|
|
18
18
|
* Row removal hides in more than a leading `DELETE`, so the scan also covers:
|
|
19
19
|
* the optional-`COLUMN` shorthand (`ALTER TABLE t DROP email`), data-modifying
|
|
20
20
|
* CTEs (`WITH d AS (DELETE ...) SELECT ...`), `MERGE ... THEN DELETE`, dynamic
|
|
21
|
-
* SQL inside a `DO`/function body,
|
|
22
|
-
*
|
|
21
|
+
* SQL inside a `DO`/function body, an `UPDATE` whose only WHERE sits inside a
|
|
22
|
+
* subquery (which restricts nothing), a `DROP ... CASCADE` of a non-table
|
|
23
|
+
* object (which takes dependent COLUMNS with it), a detached partition, and an
|
|
24
|
+
* `EXPLAIN ANALYZE` of any of the above (which really executes it).
|
|
25
|
+
*
|
|
26
|
+
* The lexing, "where does a statement end" and "which characters are code",
|
|
27
|
+
* is NOT done here: it lives in `sql-statements.ts` and is shared with the
|
|
28
|
+
* migration runner. Two lexers is how this guard came to disagree with the
|
|
29
|
+
* executor about what a file contained (see that module's header).
|
|
23
30
|
*/
|
|
24
|
-
export type DestructiveKind = 'drop-table' | 'drop-schema' | 'drop-database' | 'drop-owned' | 'drop-matview' | 'drop-column' | 'truncate' | 'delete' | 'update-without-where' | 'alter-column-type' | 'merge-delete';
|
|
31
|
+
export type DestructiveKind = 'drop-table' | 'drop-schema' | 'drop-database' | 'drop-owned' | 'drop-matview' | 'drop-column' | 'drop-cascade' | 'detach-partition' | 'truncate' | 'delete' | 'update-without-where' | 'alter-column-type' | 'merge-delete' | 'rename' | 'dynamic-destructive';
|
|
25
32
|
export interface DestructiveStatement {
|
|
26
33
|
/** The offending SQL statement (trimmed, possibly long, display truncated) */
|
|
27
34
|
statement: string;
|
|
28
35
|
kind: DestructiveKind;
|
|
29
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Best-effort extracted object name (table, schema, or table.column). For
|
|
38
|
+
* `drop-cascade` it is prefixed with the object kind (`TYPE order_status`),
|
|
39
|
+
* because the same rule covers eight different kinds of object and the bare
|
|
40
|
+
* name would not tell the operator what they are about to lose.
|
|
41
|
+
*/
|
|
30
42
|
target: string;
|
|
31
43
|
}
|
|
32
44
|
/** Human explanation per kind, used in CLI output. */
|
|
@@ -36,3 +48,5 @@ export declare const DESTRUCTIVE_KIND_LABEL: Record<DestructiveKind, string>;
|
|
|
36
48
|
* return every statement that can destroy data.
|
|
37
49
|
*/
|
|
38
50
|
export declare function scanDestructiveSql(sql: string): DestructiveStatement[];
|
|
51
|
+
/** Shown in place of an object name that does not exist until the block runs. */
|
|
52
|
+
export declare const DYNAMIC_TARGET = "<name assembled at run time>";
|