turbine-orm 0.81.0 → 0.90.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 +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
package/dist/cjs/sqlite.js
CHANGED
|
@@ -20,18 +20,24 @@
|
|
|
20
20
|
* - `RETURNING` + `ON CONFLICT … DO UPDATE` (SQLite ≥ 3.35) → create / upsert /
|
|
21
21
|
* update / delete return real rows in a single statement (`resultStrategy =
|
|
22
22
|
* 'returning'`, same as Postgres).
|
|
23
|
-
* - **Single-writer:** one connection / one
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* - **Single-writer:** one connection / one transaction at a time. On one
|
|
24
|
+
* client, statements from other callers wait (FIFO, up to `busyTimeoutMs`)
|
|
25
|
+
* while a transaction is open instead of running inside it; another
|
|
26
|
+
* process's writer gets `SQLITE_BUSY`. Both surface as the retryable
|
|
27
|
+
* `SerializationFailureError`. `journal_mode = WAL` is enabled for file
|
|
28
|
+
* databases to allow concurrent readers across connections.
|
|
26
29
|
* - **Unsupported (throw `UnsupportedFeatureError`):** pgvector distance ops,
|
|
27
30
|
* LISTEN/NOTIFY (`$listen` / `$notify`), RLS `sessionContext`. Advisory-lock
|
|
28
31
|
* migration locking is unavailable, SQLite is single-writer, so migrations
|
|
29
32
|
* serialize naturally.
|
|
30
|
-
* - **Type affinity caveats:** SQLite has no native `BOOLEAN` (0/1 integers)
|
|
31
|
-
* `DATE` (TEXT/INTEGER). Booleans bind as 1/0; `Date` values
|
|
32
|
-
* text;
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* - **Type affinity caveats:** SQLite has no native `BOOLEAN` (0/1 integers),
|
|
34
|
+
* `JSON` (text) or `DATE` (TEXT/INTEGER). Booleans bind as 1/0; `Date` values
|
|
35
|
+
* bind as ISO-8601 text; an integral JS number binds as INTEGER. A column the
|
|
36
|
+
* schema declares boolean or json/jsonb reads back as a boolean or a parsed
|
|
37
|
+
* value on every path; date/time columns are coerced back to `Date`.
|
|
38
|
+
* Integers wider than `Number.MAX_SAFE_INTEGER` come back as strings (the
|
|
39
|
+
* same safe-int policy Turbine uses for Postgres `int8`). `numeric` has no
|
|
40
|
+
* exact storage: it reads back as a JS number.
|
|
35
41
|
* - **Case-insensitive matching** uses `COLLATE NOCASE`, which is **ASCII-only**
|
|
36
42
|
* (no Unicode case folding).
|
|
37
43
|
*
|
|
@@ -69,7 +75,7 @@
|
|
|
69
75
|
*
|
|
70
76
|
* const db = turbineSqlite(':memory:', schemaDefToMetadata(schema));
|
|
71
77
|
* for (const stmt of schemaToSQL(schema, { dialect: sqliteDialect })) {
|
|
72
|
-
* await db.
|
|
78
|
+
* await db.rawUnsafe(stmt);
|
|
73
79
|
* }
|
|
74
80
|
*
|
|
75
81
|
* // `db.table(...)`, not `db.users`: the typed property accessors are emitted
|
|
@@ -84,13 +90,16 @@ exports.sqliteTypeToTs = sqliteTypeToTs;
|
|
|
84
90
|
exports.introspectSqliteDatabase = introspectSqliteDatabase;
|
|
85
91
|
exports.introspectSqlite = introspectSqlite;
|
|
86
92
|
exports.turbineSqlite = turbineSqlite;
|
|
93
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
87
94
|
const node_module_1 = require("node:module");
|
|
88
95
|
const client_js_1 = require("./client.js");
|
|
89
96
|
const dialect_js_1 = require("./dialect.js");
|
|
90
97
|
const errors_js_1 = require("./errors.js");
|
|
91
98
|
const introspect_js_1 = require("./introspect.js");
|
|
99
|
+
const sql_comment_js_1 = require("./query/sql-comment.js");
|
|
92
100
|
const utils_js_1 = require("./query/utils.js");
|
|
93
101
|
const schema_js_1 = require("./schema.js");
|
|
102
|
+
const sql_placeholders_js_1 = require("./sql-placeholders.js");
|
|
94
103
|
let cachedDatabaseSync;
|
|
95
104
|
/**
|
|
96
105
|
* Lazily load `node:sqlite`'s `DatabaseSync` constructor.
|
|
@@ -128,7 +137,8 @@ function loadDatabaseSync() {
|
|
|
128
137
|
* Coerce an arbitrary JS value into something `node:sqlite` can bind.
|
|
129
138
|
*
|
|
130
139
|
* `node:sqlite` throws on `boolean`, `undefined`, `Date`, and plain objects, so
|
|
131
|
-
* we normalize: booleans → 1/0,
|
|
140
|
+
* we normalize: booleans → 1/0, a safe integer → INTEGER (see below),
|
|
141
|
+
* `undefined`/`null` → NULL, `Date` → ISO text,
|
|
132
142
|
* `Uint8Array`/`Buffer` → BLOB, and any remaining object/array → JSON text
|
|
133
143
|
* (matches how Turbine already pre-serializes JSON filter params).
|
|
134
144
|
*/
|
|
@@ -139,6 +149,12 @@ function toSqliteParam(value) {
|
|
|
139
149
|
case 'boolean':
|
|
140
150
|
return value ? 1 : 0;
|
|
141
151
|
case 'number':
|
|
152
|
+
// `node:sqlite` binds every JS number as REAL, so `views / 2` divided as
|
|
153
|
+
// floats (75 / 2 = 37.5, stored in an INTEGER column) and an integral
|
|
154
|
+
// number written to a TEXT column was stored as '3.0'. Binding a safe
|
|
155
|
+
// integer as an INTEGER gives the arithmetic and text PostgreSQL gives.
|
|
156
|
+
// -0 stays REAL: as an integer it would lose its sign.
|
|
157
|
+
return Number.isSafeInteger(value) && !Object.is(value, -0) ? BigInt(value) : value;
|
|
142
158
|
case 'bigint':
|
|
143
159
|
case 'string':
|
|
144
160
|
return value;
|
|
@@ -149,6 +165,44 @@ function toSqliteParam(value) {
|
|
|
149
165
|
return value; // also covers Buffer
|
|
150
166
|
return JSON.stringify(value);
|
|
151
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* The JSON text an `in` / `notIn` list binds as, element by element in the
|
|
170
|
+
* form {@link toSqliteParam} would bind it, so a list element compares exactly
|
|
171
|
+
* as the same value given to `equals` does. A bigint is written as a JSON
|
|
172
|
+
* integer (exact to 64 bits; `JSON.stringify` refuses one), an infinity as the
|
|
173
|
+
* overflowing literal SQLite's JSON parser reads as one, and a binary value as
|
|
174
|
+
* `{"b": "<hex>"}`, which {@link sqliteDialect.buildInClause} turns back into a
|
|
175
|
+
* BLOB. Any other object binds as its JSON text, as it would on its own.
|
|
176
|
+
*/
|
|
177
|
+
function sqliteInListJson(values) {
|
|
178
|
+
const parts = values.map((value) => {
|
|
179
|
+
if (value === undefined || value === null)
|
|
180
|
+
return 'null';
|
|
181
|
+
switch (typeof value) {
|
|
182
|
+
case 'boolean':
|
|
183
|
+
return value ? '1' : '0';
|
|
184
|
+
case 'number':
|
|
185
|
+
if (Number.isFinite(value))
|
|
186
|
+
return JSON.stringify(value);
|
|
187
|
+
if (Number.isNaN(value))
|
|
188
|
+
return 'null';
|
|
189
|
+
return value > 0 ? '9e999' : '-9e999';
|
|
190
|
+
case 'bigint':
|
|
191
|
+
return value.toString();
|
|
192
|
+
case 'string':
|
|
193
|
+
return JSON.stringify(value);
|
|
194
|
+
}
|
|
195
|
+
if (value instanceof Uint8Array) {
|
|
196
|
+
let hex = '';
|
|
197
|
+
for (const byte of value)
|
|
198
|
+
hex += byte.toString(16).padStart(2, '0');
|
|
199
|
+
return `{"b":"${hex}"}`;
|
|
200
|
+
}
|
|
201
|
+
const bound = toSqliteParam(value);
|
|
202
|
+
return JSON.stringify(bound);
|
|
203
|
+
});
|
|
204
|
+
return `[${parts.join(',')}]`;
|
|
205
|
+
}
|
|
152
206
|
/**
|
|
153
207
|
* Normalize a single column value read from SQLite. With `setReadBigInts(true)`
|
|
154
208
|
* every integer column comes back as a `bigint`; apply the same safe-integer
|
|
@@ -163,44 +217,142 @@ function normalizeValue(value) {
|
|
|
163
217
|
}
|
|
164
218
|
return value;
|
|
165
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* The decode a column of `type` needs on SQLite, or `undefined`. `type` is a
|
|
222
|
+
* declared SQLite type or a schema type (`bool`, `jsonb`). The ONE answer both
|
|
223
|
+
* read paths use: the driver shim for direct reads and {@link sqliteDialect}'s
|
|
224
|
+
* `jsonWireRule` for `with` rows, so the two cannot disagree about a column.
|
|
225
|
+
*/
|
|
226
|
+
function sqliteValueDecode(type) {
|
|
227
|
+
const t = type.toUpperCase();
|
|
228
|
+
if (t.includes('BOOL'))
|
|
229
|
+
return 'boolean';
|
|
230
|
+
if (t === 'JSON' || t === 'JSONB')
|
|
231
|
+
return 'json';
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
function declaredDecoders(schema) {
|
|
235
|
+
if (!schema)
|
|
236
|
+
return null;
|
|
237
|
+
let out = null;
|
|
238
|
+
for (const [table, meta] of Object.entries(schema.tables)) {
|
|
239
|
+
for (const column of meta.columns) {
|
|
240
|
+
const type = column.pgType ?? meta.pgTypes?.[column.name];
|
|
241
|
+
const decode = typeof type === 'string' ? sqliteValueDecode(type) : undefined;
|
|
242
|
+
if (decode === undefined)
|
|
243
|
+
continue;
|
|
244
|
+
if (out === null)
|
|
245
|
+
out = new Map();
|
|
246
|
+
out.set(`${table}\u0000${column.name}`, decode);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* A decimal integer or float as SQLite prints it (`printf('%!.17g')` / `CAST(... AS TEXT)`).
|
|
253
|
+
*
|
|
254
|
+
* Linear by construction: the fraction's digits can only follow a literal `.`,
|
|
255
|
+
* so a run of digits has exactly one way to match. The earlier `\d+\.?\d*`
|
|
256
|
+
* could split a dot-less run between its two quantifiers in every possible
|
|
257
|
+
* place, which made a long non-numeric cell take seconds to reject (quadratic:
|
|
258
|
+
* 40,000 digits and an `x` took 0.8 s, a million take 1.5 ms now).
|
|
259
|
+
*/
|
|
260
|
+
const SQLITE_NUMBER_TEXT = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
261
|
+
/**
|
|
262
|
+
* The text SQLite's INTEGER, REAL and NUMERIC affinities convert to a number,
|
|
263
|
+
* measured against SQLite 3.51: an optional sign, a decimal integer or float,
|
|
264
|
+
* an optional exponent, and ASCII whitespace around it. Anything else
|
|
265
|
+
* (`'abc'`, `''`, `'Infinity'`, `'0x10'`, `'1_000'`) is stored as TEXT in the
|
|
266
|
+
* numeric column. Linear for the same reason as {@link SQLITE_NUMBER_TEXT}.
|
|
267
|
+
*/
|
|
268
|
+
const SQLITE_NUMERIC_LITERAL = /^[ \t\n\v\f\r]*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?[ \t\n\v\f\r]*$/i;
|
|
269
|
+
/**
|
|
270
|
+
* Declared column types whose SQLite affinity is numeric (INTEGER, REAL or
|
|
271
|
+
* NUMERIC), boolean included (stored 1/0). Matched whole, not by SQLite's own
|
|
272
|
+
* "contains INT" rule: the type a schema records for a column can be an enum's
|
|
273
|
+
* name (`point_kind`), which that rule reads as INTEGER while the column is
|
|
274
|
+
* TEXT. A type this misses is written as SQLite always wrote it, so a miss
|
|
275
|
+
* costs a refusal, never a wrong one.
|
|
276
|
+
*/
|
|
277
|
+
const SQLITE_NUMERIC_COLUMN_TYPE = /^(?:(?:tiny|small|medium|big)?int(?:eger)?|int[248]|(?:small|big)?serial|unsigned big int|real|float[48]?|double(?: precision)?|numeric|decimal|money|bool(?:ean)?)(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\))?$/i;
|
|
278
|
+
/**
|
|
279
|
+
* Decode a REAL / NUMERIC cell carried as text through a `with` join back to
|
|
280
|
+
* the value a direct read returns: an integer by the safe-integer policy, a
|
|
281
|
+
* double as the number its 17 digits name, `Inf` / `-Inf` as infinities.
|
|
282
|
+
*/
|
|
283
|
+
function decodeRealText(value) {
|
|
284
|
+
if (typeof value !== 'string')
|
|
285
|
+
return value;
|
|
286
|
+
if (/^-?\d+$/.test(value)) {
|
|
287
|
+
const asNumber = Number(value);
|
|
288
|
+
return Number.isSafeInteger(asNumber) ? asNumber : value;
|
|
289
|
+
}
|
|
290
|
+
if (value === 'Inf')
|
|
291
|
+
return Number.POSITIVE_INFINITY;
|
|
292
|
+
if (value === '-Inf')
|
|
293
|
+
return Number.NEGATIVE_INFINITY;
|
|
294
|
+
return SQLITE_NUMBER_TEXT.test(value) ? Number(value) : value;
|
|
295
|
+
}
|
|
296
|
+
/** Read a JSON column's stored text as its value; text that is not JSON is returned as stored. */
|
|
297
|
+
function parseJsonCell(value) {
|
|
298
|
+
if (typeof value !== 'string')
|
|
299
|
+
return value;
|
|
300
|
+
try {
|
|
301
|
+
return JSON.parse(value);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
166
307
|
/**
|
|
167
308
|
* Convert a `node:sqlite` null-prototype row into a normalized plain object.
|
|
168
309
|
*
|
|
169
|
-
* `
|
|
170
|
-
*
|
|
171
|
-
* `true`/`false
|
|
172
|
-
*
|
|
310
|
+
* `decoders`, when present, names the result columns whose ORIGIN column is a
|
|
311
|
+
* boolean or a JSON column (see {@link resultColumnDecoders}): 1/0 becomes
|
|
312
|
+
* `true`/`false`, JSON text becomes its value. Anything else in such a column,
|
|
313
|
+
* and every column not listed, passes through untouched.
|
|
173
314
|
*/
|
|
174
|
-
function normalizeRow(row,
|
|
315
|
+
function normalizeRow(row, decoders) {
|
|
175
316
|
const out = {};
|
|
176
317
|
for (const key of Object.keys(row)) {
|
|
177
318
|
const value = normalizeValue(row[key]);
|
|
178
|
-
|
|
319
|
+
const decode = decoders?.get(key);
|
|
320
|
+
out[key] =
|
|
321
|
+
decode === undefined
|
|
322
|
+
? value
|
|
323
|
+
: decode === 'json'
|
|
324
|
+
? parseJsonCell(value)
|
|
325
|
+
: value === 1 || value === 0
|
|
326
|
+
? value === 1
|
|
327
|
+
: value;
|
|
179
328
|
}
|
|
180
329
|
return out;
|
|
181
330
|
}
|
|
182
331
|
/**
|
|
183
|
-
* The result columns of a prepared statement
|
|
184
|
-
*
|
|
332
|
+
* The result columns of a prepared statement that need decoding, or `null`
|
|
333
|
+
* when there are none (the overwhelmingly common case).
|
|
185
334
|
*
|
|
186
|
-
* WHY THIS EXISTS. SQLite has no boolean storage class: a
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* "this integer is a boolean", and it lives in the schema, not in the value.
|
|
335
|
+
* WHY THIS EXISTS. SQLite has no boolean or JSON storage class: a boolean is
|
|
336
|
+
* stored as 1/0 and a JSON document as text. Turbine still generates
|
|
337
|
+
* `ok: boolean` and a JSON value for those columns, and PostgreSQL returns
|
|
338
|
+
* exactly that, so a value the ORM itself wrote as `true` or `{ a: 1 }` came
|
|
339
|
+
* back as `1` or `'{"a":1}'`.
|
|
192
340
|
*
|
|
193
|
-
*
|
|
194
|
-
* because the driver knows which TABLE
|
|
195
|
-
* `StatementSync.columns()` reports
|
|
196
|
-
*
|
|
197
|
-
* expression, so a computed column
|
|
341
|
+
* Two sources name such a column, both read from the driver's per-column
|
|
342
|
+
* description, because only the driver knows which TABLE a result column came
|
|
343
|
+
* from. `StatementSync.columns()` reports the declared type
|
|
344
|
+
* (`sqlite3_column_decltype`) and the origin table and column, all of which are
|
|
345
|
+
* null for an expression, so a computed column is never decoded by accident.
|
|
346
|
+
* - A declared type naming a boolean (`BOOLEAN`), as before.
|
|
347
|
+
* - The client's schema: an origin column declared boolean or json/jsonb there.
|
|
348
|
+
* Turbine's own DDL stores those as INTEGER and TEXT, so the declared type
|
|
349
|
+
* alone missed every table Turbine created.
|
|
198
350
|
*
|
|
199
|
-
* Node gained `columns()` in 22.
|
|
200
|
-
* it is feature-detected: on the narrow band without it,
|
|
201
|
-
* back as
|
|
351
|
+
* Node gained `columns()` in 22.16 / 23.11 while this engine's floor is 22.5, so
|
|
352
|
+
* it is feature-detected: on the narrow band without it, these columns keep
|
|
353
|
+
* reading back as stored rather than the engine refusing to run.
|
|
202
354
|
*/
|
|
203
|
-
function
|
|
355
|
+
function resultColumnDecoders(stmt, declared) {
|
|
204
356
|
const columnsFn = stmt.columns;
|
|
205
357
|
if (typeof columnsFn !== 'function')
|
|
206
358
|
return null;
|
|
@@ -210,18 +362,24 @@ function booleanResultColumns(stmt) {
|
|
|
210
362
|
}
|
|
211
363
|
catch {
|
|
212
364
|
// A statement the driver cannot describe (some PRAGMA forms) is not worth
|
|
213
|
-
// failing a query over; skip the
|
|
365
|
+
// failing a query over; skip the decoding for it.
|
|
214
366
|
return null;
|
|
215
367
|
}
|
|
216
368
|
let out = null;
|
|
217
369
|
for (const c of described) {
|
|
218
|
-
if (typeof c.name !== 'string'
|
|
370
|
+
if (typeof c.name !== 'string')
|
|
219
371
|
continue;
|
|
220
|
-
|
|
372
|
+
let decode;
|
|
373
|
+
if (typeof c.type === 'string' && sqliteValueDecode(c.type) === 'boolean')
|
|
374
|
+
decode = 'boolean';
|
|
375
|
+
else if (declared !== null && typeof c.table === 'string' && typeof c.column === 'string') {
|
|
376
|
+
decode = declared.get(`${c.table}\u0000${c.column}`);
|
|
377
|
+
}
|
|
378
|
+
if (decode === undefined)
|
|
221
379
|
continue;
|
|
222
380
|
if (out === null)
|
|
223
|
-
out = new
|
|
224
|
-
out.
|
|
381
|
+
out = new Map();
|
|
382
|
+
out.set(c.name, decode);
|
|
225
383
|
}
|
|
226
384
|
return out;
|
|
227
385
|
}
|
|
@@ -235,11 +393,11 @@ let cachedSqliteNamespace;
|
|
|
235
393
|
/**
|
|
236
394
|
* Can the driver report a result column's DECLARED type?
|
|
237
395
|
*
|
|
238
|
-
* This is the same capability {@link
|
|
396
|
+
* This is the same capability {@link resultColumnDecoders} feature-detects, and
|
|
239
397
|
* the two have to be asked the SAME question or they split: that helper is what
|
|
240
398
|
* turns 1/0 into `true`/`false` on every direct read, and the boolean
|
|
241
399
|
* `jsonWireRule` is what does it on the join path. On the Node band with
|
|
242
|
-
* `node:sqlite` but without `StatementSync.columns()` (>= 22.5, < 22.
|
|
400
|
+
* `node:sqlite` but without `StatementSync.columns()` (>= 22.5, < 22.16 / 23.11)
|
|
243
401
|
* the helper returns `null` and every direct read keeps 1/0, so a join that
|
|
244
402
|
* still converted would produce exactly the strategy-dependent type flip the
|
|
245
403
|
* rule exists to prevent, on the one runtime where nothing else can catch it.
|
|
@@ -269,7 +427,12 @@ const ROW_RETURNING_HEAD = /^\s*(?:select|with|pragma|explain|values)\b/i;
|
|
|
269
427
|
* A `RETURNING` clause turns any write into a row-producing statement.
|
|
270
428
|
*/
|
|
271
429
|
function statementReturnsRows(sql) {
|
|
272
|
-
|
|
430
|
+
// A `sqlComment` (query/sql-comment.ts) sits after the statement and its
|
|
431
|
+
// values are data, so the RETURNING test reads the statement without it: a
|
|
432
|
+
// value such as `orders-returning` must not turn a write into a row read
|
|
433
|
+
// and its change count into 0.
|
|
434
|
+
const code = (0, sql_comment_js_1.withoutTrailingComment)(sql);
|
|
435
|
+
return ROW_RETURNING_HEAD.test(code) || /\breturning\b/i.test(code);
|
|
273
436
|
}
|
|
274
437
|
/**
|
|
275
438
|
* Augment a `node:sqlite` error with the Postgres-shaped fields that
|
|
@@ -341,18 +504,34 @@ function augmentSqliteError(err) {
|
|
|
341
504
|
target.code = '23514';
|
|
342
505
|
return err;
|
|
343
506
|
default:
|
|
344
|
-
// SQLITE_BUSY (5) / SQLITE_LOCKED (6)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
507
|
+
// SQLITE_BUSY (5) / SQLITE_LOCKED (6) and their extended codes
|
|
508
|
+
// (SQLITE_BUSY_SNAPSHOT 517, SQLITE_LOCKED_SHAREDCACHE 262, ...). Built as
|
|
509
|
+
// the typed retryable error HERE rather than annotated, because the raw
|
|
510
|
+
// paths (`db.transaction(client => ...)`, `db.pool.query`) never pass
|
|
511
|
+
// through `wrapPgError` and would otherwise hand back the driver error.
|
|
512
|
+
if (primary === 5 || primary === 6)
|
|
513
|
+
return sqliteBusyError(e.errcode, message, err);
|
|
350
514
|
// SQLITE_ERROR (1): the statement itself is wrong. See the note above.
|
|
351
515
|
if (primary === 1)
|
|
352
516
|
return sqliteLogicError(message, err);
|
|
353
517
|
return err;
|
|
354
518
|
}
|
|
355
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* A `SQLITE_BUSY` / `SQLITE_LOCKED` failure as the retryable typed error, so
|
|
522
|
+
* `withRetry` / `$retry` and `err.isRetryable` handle it on every path.
|
|
523
|
+
* `busy_timeout` does not apply to a deferred transaction that read first and
|
|
524
|
+
* then tried to write (SQLITE_BUSY_SNAPSHOT): only retrying the WHOLE
|
|
525
|
+
* transaction can succeed, which is what the hint says.
|
|
526
|
+
*/
|
|
527
|
+
function sqliteBusyError(errcode, message, cause) {
|
|
528
|
+
const kind = (errcode & 0xff) === 5 ? 'SQLITE_BUSY' : 'SQLITE_LOCKED';
|
|
529
|
+
return new errors_js_1.SerializationFailureError({
|
|
530
|
+
message: `SQLite database is busy (${kind}, code ${errcode}): ${message}. Another connection holds the lock it needs. ` +
|
|
531
|
+
'Retry the whole transaction (withRetry does), or raise busyTimeoutMs.',
|
|
532
|
+
cause,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
356
535
|
/**
|
|
357
536
|
* The `ValidationError` for a `SQLITE_ERROR`, with a hint for the two spellings
|
|
358
537
|
* that account for nearly all of them.
|
|
@@ -370,17 +549,17 @@ function augmentSqliteError(err) {
|
|
|
370
549
|
function sqliteLogicError(message, cause) {
|
|
371
550
|
const missingTable = /^no such table:\s*(\S+)/i.exec(message);
|
|
372
551
|
if (missingTable) {
|
|
373
|
-
return new errors_js_1.ValidationError(`SQLite has no table "${missingTable[1]}": ${message}. ` +
|
|
552
|
+
return new errors_js_1.ValidationError(`SQLite has no table "${(0, introspect_js_1.printableCatalogName)(missingTable[1])}": ${(0, introspect_js_1.printableCatalogName)(message)}. ` +
|
|
374
553
|
'Create it first (run your migrations, or execute the CREATE TABLE statements for this schema); ' +
|
|
375
554
|
'an in-memory database starts empty on every connection.', { cause });
|
|
376
555
|
}
|
|
377
556
|
const missingColumn = /^no such column:\s*(\S+)/i.exec(message);
|
|
378
557
|
if (missingColumn) {
|
|
379
|
-
return new errors_js_1.ValidationError(`SQLite has no column "${missingColumn[1]}": ${message}. ` +
|
|
558
|
+
return new errors_js_1.ValidationError(`SQLite has no column "${(0, introspect_js_1.printableCatalogName)(missingColumn[1])}": ${(0, introspect_js_1.printableCatalogName)(message)}. ` +
|
|
380
559
|
'Check the spelling against the table as it exists in this database, ' +
|
|
381
560
|
'and re-run `turbine generate` if the schema has changed.', { cause });
|
|
382
561
|
}
|
|
383
|
-
return new errors_js_1.ValidationError(`SQLite rejected the statement: ${message}`, { cause });
|
|
562
|
+
return new errors_js_1.ValidationError(`SQLite rejected the statement: ${(0, introspect_js_1.printableCatalogName)(message)}`, { cause });
|
|
384
563
|
}
|
|
385
564
|
function normalizeQueryArgs(arg, values) {
|
|
386
565
|
if (typeof arg === 'string')
|
|
@@ -410,8 +589,15 @@ function toNamedBinding(values) {
|
|
|
410
589
|
}
|
|
411
590
|
return named;
|
|
412
591
|
}
|
|
413
|
-
|
|
592
|
+
/** The refusal for a placeholder the shim cannot bind (see rewritePositionalPlaceholders). */
|
|
593
|
+
function refusePlaceholder(message) {
|
|
594
|
+
throw new errors_js_1.ValidationError(message);
|
|
595
|
+
}
|
|
596
|
+
function runStatement(db, text, values, decoderCache, declared = null) {
|
|
414
597
|
const binding = toNamedBinding(values);
|
|
598
|
+
// `$n` is the spelling `rawUnsafe` documents for every engine; SQLite would
|
|
599
|
+
// read `$1` as a parameter NAMED "1", which the `{ p1, ... }` binding lacks.
|
|
600
|
+
const sql = (0, sql_placeholders_js_1.rewritePositionalPlaceholders)(text, 'sqlite', values.length, (i) => `:p${i}`, refusePlaceholder);
|
|
415
601
|
let stmt;
|
|
416
602
|
try {
|
|
417
603
|
stmt = db.prepare(sql);
|
|
@@ -422,15 +608,15 @@ function runStatement(db, sql, values, booleanCache) {
|
|
|
422
608
|
stmt.setReadBigInts(true);
|
|
423
609
|
try {
|
|
424
610
|
if (statementReturnsRows(sql)) {
|
|
425
|
-
// Describe each distinct statement ONCE. `null` (
|
|
611
|
+
// Describe each distinct statement ONCE. `null` (nothing to decode) is a
|
|
426
612
|
// cached answer too, so the ordinary query pays a single Map hit per call
|
|
427
613
|
// and nothing per row.
|
|
428
|
-
let
|
|
429
|
-
if (
|
|
430
|
-
|
|
431
|
-
|
|
614
|
+
let decoders = decoderCache?.get(sql);
|
|
615
|
+
if (decoders === undefined) {
|
|
616
|
+
decoders = resultColumnDecoders(stmt, declared);
|
|
617
|
+
decoderCache?.set(sql, decoders);
|
|
432
618
|
}
|
|
433
|
-
const rows = (binding ? stmt.all(binding) : stmt.all()).map((r) => normalizeRow(r,
|
|
619
|
+
const rows = (binding ? stmt.all(binding) : stmt.all()).map((r) => normalizeRow(r, decoders ?? undefined));
|
|
434
620
|
return { rows, rowCount: rows.length };
|
|
435
621
|
}
|
|
436
622
|
const info = binding ? stmt.run(binding) : stmt.run();
|
|
@@ -442,42 +628,96 @@ function runStatement(db, sql, values, booleanCache) {
|
|
|
442
628
|
throw augmentSqliteError(err);
|
|
443
629
|
}
|
|
444
630
|
}
|
|
631
|
+
/** Default wait, in ms, for a transaction another caller holds open on the handle. */
|
|
632
|
+
const DEFAULT_BUSY_TIMEOUT_MS = 5000;
|
|
633
|
+
const sqliteTxStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
634
|
+
/** Statement heads that leave the handle inside a transaction when they succeed. */
|
|
635
|
+
const TX_OPEN_HEAD = /^\s*(?:begin|savepoint)\b/i;
|
|
636
|
+
/** Statement heads that end the transaction (not `ROLLBACK TO`, which keeps it open). */
|
|
637
|
+
const TX_CLOSE_HEAD = /^\s*(?:commit|end|rollback(?!\s+(?:transaction\s+)?to\b))\b/i;
|
|
638
|
+
/**
|
|
639
|
+
* The one statement the pool can never run: something on the handle while the
|
|
640
|
+
* transaction that owns it is waiting for that very statement.
|
|
641
|
+
*/
|
|
642
|
+
function statementInsideTransactionError() {
|
|
643
|
+
return new errors_js_1.UnsupportedFeatureError('A statement on the client while its own transaction is open', 'sqlite', 'This call was made through the client, not the `tx` client, from inside an open $transaction callback. ' +
|
|
644
|
+
'SQLite has one connection here, so the call would either join that transaction (and be rolled back with ' +
|
|
645
|
+
'it) or wait for it forever. Use the `tx` client the callback receives, or make the call after the ' +
|
|
646
|
+
'transaction completes.');
|
|
647
|
+
}
|
|
648
|
+
/** The error a caller gets when an open transaction holds the handle past `busyTimeoutMs`. */
|
|
649
|
+
function handleBusyError(timeoutMs) {
|
|
650
|
+
return new errors_js_1.SerializationFailureError({
|
|
651
|
+
message: `SQLite database is busy: a transaction on this client held it for longer than busyTimeoutMs (${timeoutMs} ms). ` +
|
|
652
|
+
'Retry the operation (withRetry does), keep transactions short, or raise busyTimeoutMs.',
|
|
653
|
+
});
|
|
654
|
+
}
|
|
445
655
|
/**
|
|
446
656
|
* A `PgCompatPool` backed by a single `node:sqlite` `DatabaseSync` connection.
|
|
447
657
|
* SQLite is single-connection by nature (a `:memory:` database is per-handle),
|
|
448
|
-
* so `connect()` hands back a client over the **same** handle
|
|
449
|
-
*
|
|
450
|
-
*
|
|
658
|
+
* so `connect()` hands back a client over the **same** handle.
|
|
659
|
+
*
|
|
660
|
+
* One handle carries one transaction, so the pool decides who may use it. A
|
|
661
|
+
* caller whose statement opens a transaction OWNS the handle until that
|
|
662
|
+
* transaction ends; every other caller's statements, plain queries included,
|
|
663
|
+
* wait their turn in FIFO order (bounded by `busyTimeoutMs`) instead of running
|
|
664
|
+
* inside it. Without that, a write issued outside an open transaction joined
|
|
665
|
+
* it, reported success, and was rolled back with it, and reads saw its
|
|
666
|
+
* uncommitted rows. A checkout that does not open a transaction (a stream, a
|
|
667
|
+
* connectivity check) holds nothing, so other callers keep running beside it.
|
|
451
668
|
*/
|
|
452
669
|
class SqlitePool {
|
|
453
670
|
/** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
|
|
454
671
|
db;
|
|
455
672
|
closed = false;
|
|
456
673
|
/**
|
|
457
|
-
* `sql ->
|
|
674
|
+
* `sql -> result columns to decode` (or `null` for none). Bounded like
|
|
458
675
|
* the query builder's SQL-template cache, since a caller can generate
|
|
459
676
|
* unbounded distinct statements (raw SQL, an IN-list that inlines).
|
|
460
677
|
*/
|
|
461
|
-
|
|
462
|
-
|
|
678
|
+
decoderCache = new utils_js_1.LRUCache(1000);
|
|
679
|
+
/** Schema columns whose stored form is not their declared JS type (see {@link declaredDecoders}). */
|
|
680
|
+
declared;
|
|
681
|
+
busyTimeoutMs;
|
|
682
|
+
/** Who may run statements on the handle right now; `null` when nobody holds it. */
|
|
683
|
+
owner = null;
|
|
684
|
+
waiters = [];
|
|
685
|
+
/** The owner for statements sent through `pool.query()` rather than a checkout. */
|
|
686
|
+
poolOwner = {};
|
|
687
|
+
/** Transaction state when the driver cannot report it (`isTransaction` arrived in Node 22.16). */
|
|
688
|
+
trackedTransaction = false;
|
|
689
|
+
constructor(db, options = {}) {
|
|
463
690
|
this.db = db;
|
|
691
|
+
const t = Number(options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS);
|
|
692
|
+
this.busyTimeoutMs = Number.isFinite(t) && t >= 0 ? t : DEFAULT_BUSY_TIMEOUT_MS;
|
|
693
|
+
this.declared = declaredDecoders(options.schema);
|
|
464
694
|
}
|
|
465
695
|
// biome-ignore lint/suspicious/noExplicitAny: pg-compat query is generic over the row shape; runStatement returns plain objects.
|
|
466
696
|
async query(text, values) {
|
|
467
697
|
const { text: sql, params } = normalizeQueryArgs(text, values);
|
|
468
|
-
return
|
|
698
|
+
return this.runAs(this.poolOwner, sql, params);
|
|
469
699
|
}
|
|
470
700
|
async connect() {
|
|
471
|
-
|
|
472
|
-
|
|
701
|
+
if (this.closed)
|
|
702
|
+
throw new errors_js_1.ConnectionError('This SQLite client has been disconnected.');
|
|
703
|
+
const owner = {};
|
|
704
|
+
let released = false;
|
|
473
705
|
return {
|
|
474
706
|
// biome-ignore lint/suspicious/noExplicitAny: see query() above.
|
|
475
707
|
query: async (text, values) => {
|
|
708
|
+
if (released) {
|
|
709
|
+
throw new errors_js_1.ConnectionError('This SQLite connection was released (its transaction ended or timed out); ' +
|
|
710
|
+
'run the statement through the client or a new transaction.');
|
|
711
|
+
}
|
|
476
712
|
const { text: sql, params } = normalizeQueryArgs(text, values);
|
|
477
|
-
return
|
|
713
|
+
return this.runAs(owner, sql, params);
|
|
478
714
|
},
|
|
715
|
+
wrapTransactionCallback: (fn) => sqliteTxStorage.run({ pool: this, owner, parent: sqliteTxStorage.getStore() }, fn),
|
|
479
716
|
release: () => {
|
|
480
|
-
|
|
717
|
+
if (released)
|
|
718
|
+
return;
|
|
719
|
+
released = true;
|
|
720
|
+
this.releaseOwner(owner);
|
|
481
721
|
},
|
|
482
722
|
};
|
|
483
723
|
}
|
|
@@ -487,6 +727,103 @@ class SqlitePool {
|
|
|
487
727
|
this.closed = true;
|
|
488
728
|
this.db.close();
|
|
489
729
|
}
|
|
730
|
+
/** Is the handle inside a transaction right now? */
|
|
731
|
+
inTransaction() {
|
|
732
|
+
const reported = this.db.isTransaction;
|
|
733
|
+
return typeof reported === 'boolean' ? reported : this.trackedTransaction;
|
|
734
|
+
}
|
|
735
|
+
/** Run one statement for `owner`, waiting first if another caller's transaction holds the handle. */
|
|
736
|
+
async runAs(owner, sql, params) {
|
|
737
|
+
if (this.closed)
|
|
738
|
+
throw new errors_js_1.ConnectionError('This SQLite client has been disconnected.');
|
|
739
|
+
if (this.owner !== owner) {
|
|
740
|
+
if (this.owner !== null || this.waiters.length > 0) {
|
|
741
|
+
if (this.calledFromOwnersCallback())
|
|
742
|
+
throw statementInsideTransactionError();
|
|
743
|
+
await this.waitForHandle(owner);
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
this.owner = owner;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
const result = runStatement(this.db, sql, params, this.decoderCache, this.declared);
|
|
751
|
+
if (TX_OPEN_HEAD.test(sql))
|
|
752
|
+
this.trackedTransaction = true;
|
|
753
|
+
else if (TX_CLOSE_HEAD.test(sql))
|
|
754
|
+
this.trackedTransaction = false;
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
finally {
|
|
758
|
+
// A statement that leaves no transaction open hands the handle on.
|
|
759
|
+
if (this.owner === owner && !this.inTransaction())
|
|
760
|
+
this.handOff();
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/** True when the current async context is the callback of the transaction that owns the handle. */
|
|
764
|
+
calledFromOwnersCallback() {
|
|
765
|
+
for (let m = sqliteTxStorage.getStore(); m; m = m.parent) {
|
|
766
|
+
if (m.pool === this && m.owner === this.owner)
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
waitForHandle(owner) {
|
|
772
|
+
return new Promise((resolve, reject) => {
|
|
773
|
+
let timer;
|
|
774
|
+
const waiter = {
|
|
775
|
+
owner,
|
|
776
|
+
settled: false,
|
|
777
|
+
grant: () => {
|
|
778
|
+
if (timer)
|
|
779
|
+
clearTimeout(timer);
|
|
780
|
+
resolve();
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
this.waiters.push(waiter);
|
|
784
|
+
timer = setTimeout(() => {
|
|
785
|
+
if (waiter.settled)
|
|
786
|
+
return;
|
|
787
|
+
waiter.settled = true;
|
|
788
|
+
const at = this.waiters.indexOf(waiter);
|
|
789
|
+
if (at >= 0)
|
|
790
|
+
this.waiters.splice(at, 1);
|
|
791
|
+
reject(handleBusyError(this.busyTimeoutMs));
|
|
792
|
+
}, this.busyTimeoutMs);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
/** Give the handle to the next waiter, which runs its statement as soon as it resumes. */
|
|
796
|
+
handOff() {
|
|
797
|
+
this.owner = null;
|
|
798
|
+
while (this.waiters.length > 0) {
|
|
799
|
+
const next = this.waiters.shift();
|
|
800
|
+
if (next.settled)
|
|
801
|
+
continue;
|
|
802
|
+
next.settled = true;
|
|
803
|
+
this.owner = next.owner;
|
|
804
|
+
next.grant();
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* A checkout ended. If it still holds an open transaction (a timed-out or
|
|
810
|
+
* abandoned callback), roll it back first, so the next caller does not
|
|
811
|
+
* start inside it.
|
|
812
|
+
*/
|
|
813
|
+
releaseOwner(owner) {
|
|
814
|
+
if (this.owner !== owner)
|
|
815
|
+
return;
|
|
816
|
+
if (this.inTransaction() && !this.closed) {
|
|
817
|
+
try {
|
|
818
|
+
this.db.exec('ROLLBACK');
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
// Nothing left to roll back.
|
|
822
|
+
}
|
|
823
|
+
this.trackedTransaction = false;
|
|
824
|
+
}
|
|
825
|
+
this.handOff();
|
|
826
|
+
}
|
|
490
827
|
}
|
|
491
828
|
exports.SqlitePool = SqlitePool;
|
|
492
829
|
// ---------------------------------------------------------------------------
|
|
@@ -555,6 +892,44 @@ function sqliteColumnAffinity(type) {
|
|
|
555
892
|
exports.sqliteDialect = {
|
|
556
893
|
...dialect_js_1.postgresDialect,
|
|
557
894
|
name: 'sqlite',
|
|
895
|
+
capabilities: Object.freeze({
|
|
896
|
+
distinctOn: false,
|
|
897
|
+
positionalJson: false,
|
|
898
|
+
// NULLS FIRST / LAST since 3.30; NULL is the smallest value.
|
|
899
|
+
nullsOrderingClause: true,
|
|
900
|
+
nullsSortHigh: false,
|
|
901
|
+
insensitiveInList: false,
|
|
902
|
+
jsonDocumentEquality: 'text',
|
|
903
|
+
jsonExtractTyped: true,
|
|
904
|
+
jsonExactNumericCast: false,
|
|
905
|
+
numericTextComparison: false,
|
|
906
|
+
// Enforced only on a connection that turned `foreign_keys` on.
|
|
907
|
+
enforcesForeignKeys: false,
|
|
908
|
+
partitionLimitPushdown: false,
|
|
909
|
+
utcTimestampRewrite: false,
|
|
910
|
+
paramTypeCasts: false,
|
|
911
|
+
namedEnumTypes: false,
|
|
912
|
+
structuredValueColumns: 'scalars',
|
|
913
|
+
// The default SQLITE_MAX_VARIABLE_NUMBER.
|
|
914
|
+
maxBindParameters: 32766,
|
|
915
|
+
maxInsertRows: Number.POSITIVE_INFINITY,
|
|
916
|
+
upsertConflictTarget: 'explicit',
|
|
917
|
+
setClauseOrder: 'snapshot',
|
|
918
|
+
reselectLocksRows: false,
|
|
919
|
+
integerDivision: '/',
|
|
920
|
+
updateExpressions: true,
|
|
921
|
+
bulkUpsertAppliesRepeatedKeys: true,
|
|
922
|
+
readOnlyTransactions: false,
|
|
923
|
+
connectionParameters: false,
|
|
924
|
+
backendCancel: false,
|
|
925
|
+
idleTransactionTimeout: false,
|
|
926
|
+
abortsTransactionOnError: false,
|
|
927
|
+
connectionDriver: 'engine-driver',
|
|
928
|
+
preparesEveryStatement: false,
|
|
929
|
+
statementSyntax: 'sqlite',
|
|
930
|
+
inlineForeignKeys: true,
|
|
931
|
+
unboundedTextKeys: true,
|
|
932
|
+
}),
|
|
558
933
|
resultStrategy: 'returning', // SQLite ≥ 3.35 has RETURNING
|
|
559
934
|
supportsReturning: true,
|
|
560
935
|
supportsILike: false,
|
|
@@ -571,6 +946,13 @@ exports.sqliteDialect = {
|
|
|
571
946
|
// No FROM-clause LATERAL: the opt-in lateral pick plan is Postgres-only.
|
|
572
947
|
supportsLateralJoin: false,
|
|
573
948
|
supportsPlanCacheMode: false,
|
|
949
|
+
// No row locks: a write transaction locks the whole database.
|
|
950
|
+
rowLockModes: [],
|
|
951
|
+
// percentile_cont / percentile_disc exist only in builds compiled with
|
|
952
|
+
// SQLITE_ENABLE_PERCENTILE, which node:sqlite cannot be assumed to have.
|
|
953
|
+
supportsPercentileAggregates: false,
|
|
954
|
+
// INSERT ... ON CONFLICT (cols) DO UPDATE ... RETURNING (3.24 + 3.35).
|
|
955
|
+
supportsBulkUpsert: true,
|
|
574
956
|
// SQLite explains a compiled query with `EXPLAIN QUERY PLAN` (four columns:
|
|
575
957
|
// id, parent, notused, detail), overriding the inherited Postgres `EXPLAIN`.
|
|
576
958
|
explainQuery: { prefix: 'EXPLAIN QUERY PLAN' },
|
|
@@ -634,7 +1016,7 @@ exports.sqliteDialect = {
|
|
|
634
1016
|
}
|
|
635
1017
|
// A declared-boolean column is 1/0 in storage, and the driver shim turns
|
|
636
1018
|
// that back into true/false on every direct read (see
|
|
637
|
-
//
|
|
1019
|
+
// resultColumnDecoders). `json_object` sees the raw integer and has no
|
|
638
1020
|
// decltype to consult, so the join strategy alone would have kept handing
|
|
639
1021
|
// back 1/0 while the top-level read, the batched loader and the flatten
|
|
640
1022
|
// plan returned booleans, which is the same strategy-dependent type flip
|
|
@@ -646,7 +1028,8 @@ exports.sqliteDialect = {
|
|
|
646
1028
|
// driverReportsDeclaredTypes). Without it the shim cannot name a boolean
|
|
647
1029
|
// result column, so 1/0 is the answer everywhere; converting here alone
|
|
648
1030
|
// would put the flip back, pointing the other way.
|
|
649
|
-
|
|
1031
|
+
const decode = sqliteValueDecode(columnType);
|
|
1032
|
+
if (decode === 'boolean' && driverReportsDeclaredTypes()) {
|
|
650
1033
|
return {
|
|
651
1034
|
// Carried as TEXT because that is how the decode half is reached at
|
|
652
1035
|
// all: the parser only runs `decode` on a string cell, so a rule that
|
|
@@ -659,6 +1042,29 @@ exports.sqliteDialect = {
|
|
|
659
1042
|
decode: (value) => (value === '1' ? true : value === '0' ? false : value),
|
|
660
1043
|
};
|
|
661
1044
|
}
|
|
1045
|
+
// A json/jsonb column is JSON text in storage, which the shim parses on
|
|
1046
|
+
// every direct read (see resultColumnDecoders). `json_object` embeds the
|
|
1047
|
+
// same text as a JSON string, so the decode is the same parse. No cast: the
|
|
1048
|
+
// column reference already yields the text. Gated like the boolean rule,
|
|
1049
|
+
// since the direct-read half needs the same driver capability.
|
|
1050
|
+
if (decode === 'json' && driverReportsDeclaredTypes()) {
|
|
1051
|
+
return { sql: (ref) => ref, decode: parseJsonCell };
|
|
1052
|
+
}
|
|
1053
|
+
// `json_object` renders a REAL with 15 significant digits on the SQLite
|
|
1054
|
+
// that Node 22 and 24 bundle (3.47 to 3.51; 3.53 renders 17), so 0.1 + 0.2
|
|
1055
|
+
// came back 0.3 through a join and a NUMERIC past 15 digits was rounded
|
|
1056
|
+
// again on top of its REAL storage, while the top-level read and the
|
|
1057
|
+
// batched loader return the stored double. `printf('%!.17g')` prints every
|
|
1058
|
+
// double with enough digits to rebuild it, on every version; an INTEGER in
|
|
1059
|
+
// the same column is carried as its text and decoded by the safe-integer
|
|
1060
|
+
// policy above; text and NULL pass through as stored.
|
|
1061
|
+
if (/REAL|FLOA|DOUB|NUMERIC|DECIMAL|MONEY/.test(t)) {
|
|
1062
|
+
return {
|
|
1063
|
+
sql: (ref) => `CASE typeof(${ref}) WHEN 'real' THEN printf('%!.17g', ${ref}) ` +
|
|
1064
|
+
`WHEN 'integer' THEN CAST(${ref} AS TEXT) ELSE ${ref} END`,
|
|
1065
|
+
decode: decodeRealText,
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
662
1068
|
// A BLOB cannot go into JSON at all: SQLite raises "JSON cannot hold BLOB
|
|
663
1069
|
// values" and the whole query fails with a raw SQL logic error, where the
|
|
664
1070
|
// batched loader returns the row fine. Carry hex and rebuild the bytes.
|
|
@@ -692,11 +1098,35 @@ exports.sqliteDialect = {
|
|
|
692
1098
|
// No `= ANY(array)` in SQLite. `json_each` expands a single JSON-array param
|
|
693
1099
|
// into a row set, keeping ONE placeholder (so the SQL cache stays valid
|
|
694
1100
|
// regardless of list length) and handling the empty-list case correctly.
|
|
1101
|
+
// `x IN (SELECT ...)` compares like `x = ?`: the column's affinity and
|
|
1102
|
+
// collation apply to each element, as they do to a bound operand. JSON has no
|
|
1103
|
+
// bytes, so a binary element travels as `{"b": "<hex>"}` and is decoded back
|
|
1104
|
+
// to a BLOB here (see sqliteInListJson).
|
|
695
1105
|
buildInClause(expr, paramRef, negated) {
|
|
696
|
-
return `${expr} ${negated ? 'NOT IN' : 'IN'}
|
|
1106
|
+
return (`${expr} ${negated ? 'NOT IN' : 'IN'} ` +
|
|
1107
|
+
`(SELECT CASE WHEN type = 'object' THEN unhex(json_extract(value, '$.b')) ELSE value END ` +
|
|
1108
|
+
`FROM json_each(${paramRef}))`);
|
|
697
1109
|
},
|
|
698
1110
|
inClauseParam(values) {
|
|
699
|
-
return
|
|
1111
|
+
return sqliteInListJson(Array.isArray(values) ? values : []);
|
|
1112
|
+
},
|
|
1113
|
+
/**
|
|
1114
|
+
* A string written to a numeric or boolean column. SQLite's type affinity
|
|
1115
|
+
* keeps text it cannot convert as TEXT in that column, where PostgreSQL
|
|
1116
|
+
* refuses it at the server: `'abc'` in an INTEGER column, or `'true'` in a
|
|
1117
|
+
* boolean one (stored 1/0), which `{ flag: true }` then never matches. A
|
|
1118
|
+
* string that is a number (`'12.5'`, `'9007199254740993'`, `' 7 '`) is
|
|
1119
|
+
* accepted and converted by SQLite, as before.
|
|
1120
|
+
*/
|
|
1121
|
+
checkTextWrite(table, column, columnType, value) {
|
|
1122
|
+
if (!SQLITE_NUMERIC_COLUMN_TYPE.test(columnType.trim()))
|
|
1123
|
+
return undefined;
|
|
1124
|
+
if (SQLITE_NUMERIC_LITERAL.test(value))
|
|
1125
|
+
return undefined;
|
|
1126
|
+
const boolean = /BOOL/i.test(columnType);
|
|
1127
|
+
return (`The string written to "${column}" on "${table}", a column of type ${columnType}, is not a number, so SQLite ` +
|
|
1128
|
+
`would store it as text in that column; PostgreSQL refuses it. ` +
|
|
1129
|
+
(boolean ? 'Write true or false.' : 'Write a number, or a string holding one, such as "12.5".'));
|
|
700
1130
|
},
|
|
701
1131
|
buildReturningClause(selection = '*') {
|
|
702
1132
|
return ` RETURNING ${selection === '*' ? '*' : selection.join(', ')}`;
|
|
@@ -719,6 +1149,21 @@ exports.sqliteDialect = {
|
|
|
719
1149
|
params: input.rowValues.flat(),
|
|
720
1150
|
};
|
|
721
1151
|
},
|
|
1152
|
+
buildBulkUpsertStatement(input) {
|
|
1153
|
+
// VALUES, not `INSERT ... SELECT`: after a SELECT, SQLite cannot tell a
|
|
1154
|
+
// trailing `ON CONFLICT` from the SELECT's own clauses. The rest is the
|
|
1155
|
+
// PostgreSQL grammar (3.24 upsert, 3.35 RETURNING), `excluded` included.
|
|
1156
|
+
let n = 0;
|
|
1157
|
+
const tuples = input.rowValues.map((row) => `(${row.map(() => this.paramPlaceholder(++n)).join(', ')})`).join(', ');
|
|
1158
|
+
const params = input.rowValues.flat();
|
|
1159
|
+
return {
|
|
1160
|
+
sql: `INSERT INTO ${input.table} (${input.columns.join(', ')}) VALUES ${tuples} ` +
|
|
1161
|
+
`ON CONFLICT (${input.conflictColumns.join(', ')}) ` +
|
|
1162
|
+
(0, dialect_js_1.onConflictAction)(input, params, { target: input.table, incoming: 'excluded' }) +
|
|
1163
|
+
this.buildReturningClause(input.returning),
|
|
1164
|
+
params,
|
|
1165
|
+
};
|
|
1166
|
+
},
|
|
722
1167
|
buildDefaultValuesInsertStatement(input) {
|
|
723
1168
|
// SQLite's `DEFAULT VALUES` inserts exactly one row and the engine accepts
|
|
724
1169
|
// neither `VALUES (DEFAULT, …)` nor MySQL's empty `VALUES ()` tuple, so
|
|
@@ -739,6 +1184,12 @@ exports.sqliteDialect = {
|
|
|
739
1184
|
(input.updateWhere ? ` WHERE ${input.updateWhere}` : '') +
|
|
740
1185
|
this.buildReturningClause(input.returning));
|
|
741
1186
|
},
|
|
1187
|
+
// A SQLite transaction is always SERIALIZABLE, which satisfies every level
|
|
1188
|
+
// `$transaction({ isolationLevel })` can ask for, and the inherited
|
|
1189
|
+
// `BEGIN ISOLATION LEVEL ...` is a syntax error here.
|
|
1190
|
+
beginStatement() {
|
|
1191
|
+
return 'BEGIN';
|
|
1192
|
+
},
|
|
742
1193
|
buildInsensitiveLike(column, paramRef) {
|
|
743
1194
|
// COLLATE NOCASE is ASCII-only (no Unicode case folding), documented limit.
|
|
744
1195
|
return `${column} LIKE ${paramRef} COLLATE NOCASE`;
|
|
@@ -767,6 +1218,11 @@ exports.sqliteDialect = {
|
|
|
767
1218
|
buildColumnType(input) {
|
|
768
1219
|
return sqliteColumnAffinity(input.type);
|
|
769
1220
|
},
|
|
1221
|
+
/** SQLite has no enum type: a TEXT column, and a CHECK that admits only the labels (compared exactly, BINARY collation). */
|
|
1222
|
+
buildEnumColumn(column, labels) {
|
|
1223
|
+
const literals = labels.map((label) => `'${label.replace(/'/g, "''")}'`);
|
|
1224
|
+
return { type: 'TEXT', check: `${column} IN (${literals.join(', ')})` };
|
|
1225
|
+
},
|
|
770
1226
|
buildColumnDefinition(input) {
|
|
771
1227
|
const isSerial = /serial/i.test(input.type);
|
|
772
1228
|
const parts = [input.name];
|
|
@@ -942,16 +1398,19 @@ function introspectSqliteDatabase(db, options = {}) {
|
|
|
942
1398
|
// same logical schema, legacy-first naming, per-column disambiguation,
|
|
943
1399
|
// collision resolution against scalar column fields, and the conservative
|
|
944
1400
|
// pure-junction manyToMany auto-detection included.
|
|
945
|
-
|
|
1401
|
+
// One field per column, as the PostgreSQL introspector requires.
|
|
1402
|
+
for (const [tableName, cols] of columnsByTable)
|
|
1403
|
+
(0, introspect_js_1.assertDistinctColumnFields)(tableName, cols);
|
|
1404
|
+
const relationsByTable = (0, introspect_js_1.deriveEngineRelations)(tableNames, foreignKeys, pkByTable, columnsByTable, options.logger);
|
|
946
1405
|
// ----- Assemble TableMetadata -----
|
|
947
|
-
const tables =
|
|
1406
|
+
const tables = (0, introspect_js_1.nullRecord)();
|
|
948
1407
|
for (const tableName of tableNames) {
|
|
949
1408
|
const columns = columnsByTable.get(tableName) ?? [];
|
|
950
|
-
const columnMap =
|
|
951
|
-
const reverseColumnMap =
|
|
1409
|
+
const columnMap = (0, introspect_js_1.nullRecord)();
|
|
1410
|
+
const reverseColumnMap = (0, introspect_js_1.nullRecord)();
|
|
952
1411
|
const dateColumns = new Set();
|
|
953
|
-
const dialectTypes =
|
|
954
|
-
const pgTypes =
|
|
1412
|
+
const dialectTypes = (0, introspect_js_1.nullRecord)();
|
|
1413
|
+
const pgTypes = (0, introspect_js_1.nullRecord)();
|
|
955
1414
|
const allColumns = [];
|
|
956
1415
|
for (const col of columns) {
|
|
957
1416
|
columnMap[col.field] = col.name;
|
|
@@ -973,7 +1432,7 @@ function introspectSqliteDatabase(db, options = {}) {
|
|
|
973
1432
|
allColumns,
|
|
974
1433
|
primaryKey: pkByTable.get(tableName) ?? [],
|
|
975
1434
|
uniqueColumns: uniqueByTable.get(tableName) ?? [],
|
|
976
|
-
relations: relationsByTable.get(tableName) ??
|
|
1435
|
+
relations: relationsByTable.get(tableName) ?? (0, introspect_js_1.nullRecord)(),
|
|
977
1436
|
indexes: indexesByTable.get(tableName) ?? [],
|
|
978
1437
|
};
|
|
979
1438
|
}
|
|
@@ -998,7 +1457,7 @@ async function introspectSqlite(options) {
|
|
|
998
1457
|
throw new errors_js_1.ConnectionError(`Failed to open SQLite database "${options.connectionString}": ${err.message}`);
|
|
999
1458
|
}
|
|
1000
1459
|
try {
|
|
1001
|
-
return introspectSqliteDatabase(db, { include: options.include, exclude: options.exclude });
|
|
1460
|
+
return introspectSqliteDatabase(db, { include: options.include, exclude: options.exclude, logger: options.logger });
|
|
1002
1461
|
}
|
|
1003
1462
|
finally {
|
|
1004
1463
|
db.close();
|
|
@@ -1013,9 +1472,17 @@ function openSqliteDatabase(target, options) {
|
|
|
1013
1472
|
catch (err) {
|
|
1014
1473
|
throw new errors_js_1.ConnectionError(`Failed to open SQLite database "${target}": ${err.message}`);
|
|
1015
1474
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1475
|
+
try {
|
|
1476
|
+
db.exec(`PRAGMA busy_timeout = ${Number(options.busyTimeoutMs ?? 5000)}`);
|
|
1477
|
+
if (options.foreignKeys !== false)
|
|
1478
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
1479
|
+
}
|
|
1480
|
+
catch (err) {
|
|
1481
|
+
// Opened here and about to be unreachable: close it rather than leave the
|
|
1482
|
+
// file handle to the garbage collector.
|
|
1483
|
+
db.close();
|
|
1484
|
+
throw err;
|
|
1485
|
+
}
|
|
1019
1486
|
if (target !== ':memory:' && options.wal !== false) {
|
|
1020
1487
|
try {
|
|
1021
1488
|
db.exec('PRAGMA journal_mode = WAL');
|
|
@@ -1046,16 +1513,47 @@ function openSqliteDatabase(target, options) {
|
|
|
1046
1513
|
* ```
|
|
1047
1514
|
*/
|
|
1048
1515
|
function turbineSqlite(target, schema, options = {}) {
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1516
|
+
const owns = typeof target === 'string';
|
|
1517
|
+
const db = owns ? openSqliteDatabase(target, options) : target;
|
|
1518
|
+
let pool;
|
|
1519
|
+
let client;
|
|
1520
|
+
try {
|
|
1521
|
+
pool = new SqlitePool(db, { busyTimeoutMs: options.busyTimeoutMs, schema });
|
|
1522
|
+
// Everything that is not a SQLite pragma is client config and rides through
|
|
1523
|
+
// untouched, so a new TurbineConfig option works here the day it lands. The
|
|
1524
|
+
// engine-owned keys come last, so no caller can unbind the dialect.
|
|
1525
|
+
const { wal: _wal, busyTimeoutMs: _busyTimeoutMs, foreignKeys: _foreignKeys, ...clientConfig } = options;
|
|
1526
|
+
client = new client_js_1.TurbineClient({
|
|
1527
|
+
...clientConfig,
|
|
1528
|
+
pool,
|
|
1529
|
+
dialect: exports.sqliteDialect,
|
|
1530
|
+
preparedStatements: false,
|
|
1531
|
+
}, schema);
|
|
1532
|
+
}
|
|
1533
|
+
catch (err) {
|
|
1534
|
+
// A failed factory hands the caller nothing to close, so a handle it
|
|
1535
|
+
// OPENED is closed here (the client constructor refuses a bad config, a
|
|
1536
|
+
// static global filter naming no table or column for one). A handle the
|
|
1537
|
+
// caller passed in is the caller's and stays open.
|
|
1538
|
+
if (owns)
|
|
1539
|
+
db.close();
|
|
1540
|
+
throw err;
|
|
1541
|
+
}
|
|
1542
|
+
if (owns) {
|
|
1543
|
+
// Turbine opened this handle, so disconnect()/end() must close it, as the
|
|
1544
|
+
// other engine factories close a pool they built: the client sees
|
|
1545
|
+
// `TurbineConfig.pool` as external and would otherwise leave it open, still
|
|
1546
|
+
// answering queries after disconnect(). A handle the caller passed in stays
|
|
1547
|
+
// the caller's (disconnect() is a no-op for it).
|
|
1548
|
+
const baseDisconnect = client.disconnect.bind(client);
|
|
1549
|
+
const close = async (options) => {
|
|
1550
|
+
const result = options === undefined ? await baseDisconnect() : await baseDisconnect(options);
|
|
1551
|
+
await pool.end();
|
|
1552
|
+
return result ?? undefined;
|
|
1553
|
+
};
|
|
1554
|
+
const patch = client;
|
|
1555
|
+
patch.disconnect = close;
|
|
1556
|
+
patch.end = close;
|
|
1557
|
+
}
|
|
1558
|
+
return client;
|
|
1061
1559
|
}
|