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/mssql.js
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
* the row selection).
|
|
54
54
|
* - **Result strategy `'output'`:** create/update/delete/upsert return their rows
|
|
55
55
|
* from the same statement. `createMany` returns the inserted rows via
|
|
56
|
-
* `OUTPUT INSERTED.*` on the multi-row VALUES insert (≤ 1000 rows /
|
|
56
|
+
* `OUTPUT INSERTED.*` on the multi-row VALUES insert (≤ 1000 rows / 2098 params
|
|
57
57
|
* per statement, exceeding either throws a clear `ValidationError`; chunk
|
|
58
58
|
* yourself or use single `create`s).
|
|
59
59
|
* - **MERGE concurrency caveat:** `MERGE` is the upsert primitive; under high
|
|
@@ -104,15 +104,33 @@ const dialect_js_1 = require("./dialect.js");
|
|
|
104
104
|
const errors_js_1 = require("./errors.js");
|
|
105
105
|
const introspect_js_1 = require("./introspect.js");
|
|
106
106
|
const optional_peer_import_cjs_1 = __importDefault(require("./optional-peer-import.cjs"));
|
|
107
|
+
const filters_js_1 = require("./query/filters.js");
|
|
107
108
|
const utils_js_1 = require("./query/utils.js");
|
|
108
109
|
const schema_js_1 = require("./schema.js");
|
|
110
|
+
const sql_placeholders_js_1 = require("./sql-placeholders.js");
|
|
109
111
|
// ---------------------------------------------------------------------------
|
|
110
112
|
// SQL Server / connection limits
|
|
111
113
|
// ---------------------------------------------------------------------------
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Max bound parameters per SQL Server statement, as the driver sends them.
|
|
116
|
+
* The server's limit is 2100 per RPC call, and `mssql` runs a parameterized
|
|
117
|
+
* statement through `sp_executesql`, whose own `@stmt` and `@params` take two
|
|
118
|
+
* of them: measured, 2098 runs and 2099 is refused with error 8003.
|
|
119
|
+
*/
|
|
120
|
+
const MSSQL_MAX_PARAMS = 2098;
|
|
114
121
|
/** Max rows per multi-row INSERT … VALUES statement. */
|
|
115
122
|
const MSSQL_MAX_INSERT_ROWS = 1000;
|
|
123
|
+
/** Integer column types narrower than 64 bits, in SQL Server and code-first (PostgreSQL) spellings. */
|
|
124
|
+
const SMALL_INTEGER_TYPES = new Set([
|
|
125
|
+
'tinyint',
|
|
126
|
+
'smallint',
|
|
127
|
+
'int',
|
|
128
|
+
'integer',
|
|
129
|
+
'int2',
|
|
130
|
+
'int4',
|
|
131
|
+
'serial',
|
|
132
|
+
'smallserial',
|
|
133
|
+
]);
|
|
116
134
|
/** Canonical key sort matching the query builder's `sortedEntries`/collect order. */
|
|
117
135
|
function sortedRelEntries(obj) {
|
|
118
136
|
return Object.entries(obj).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
@@ -124,8 +142,9 @@ function sortedRelEntries(obj) {
|
|
|
124
142
|
* Coerce an arbitrary JS value into something `mssql` can bind. `BIT` accepts JS
|
|
125
143
|
* booleans directly; `undefined`/`null` → NULL; `bigint` follows the safe-int
|
|
126
144
|
* policy (number when it fits in 2^53, else a decimal string, no precision
|
|
127
|
-
* loss); `Date
|
|
128
|
-
*
|
|
145
|
+
* loss); a `Date` passes through, a `Uint8Array` binds as a Buffer view (VARBINARY);
|
|
146
|
+
* any remaining object/array → JSON text
|
|
147
|
+
* (matches how Turbine already pre-serializes JSON filter params).
|
|
129
148
|
*/
|
|
130
149
|
function toMssqlParam(value) {
|
|
131
150
|
if (value === undefined || value === null)
|
|
@@ -142,10 +161,45 @@ function toMssqlParam(value) {
|
|
|
142
161
|
}
|
|
143
162
|
if (value instanceof Date)
|
|
144
163
|
return value;
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
// The driver infers VARBINARY only for a Buffer; any other Uint8Array was
|
|
165
|
+
// bound as NVARCHAR and refused ("Invalid string"). A Buffer view shares the
|
|
166
|
+
// bytes, so nothing is copied.
|
|
167
|
+
if (value instanceof Uint8Array) {
|
|
168
|
+
return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
169
|
+
}
|
|
147
170
|
return JSON.stringify(value);
|
|
148
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* The JSON text an `in` / `notIn` list binds as. OPENJSON hands each element
|
|
174
|
+
* back as NVARCHAR text, and SQL Server converts that text to the compared
|
|
175
|
+
* column's type, so each element is written as the text that converts to the
|
|
176
|
+
* value `equals` would bind: a bigint as its digits (`JSON.stringify` refuses
|
|
177
|
+
* one) and a Date as its UTC date and time with no zone (the driver binds a
|
|
178
|
+
* Date as DATETIME2 in UTC; `JSON.stringify` adds a `Z`). No text converts to
|
|
179
|
+
* bytes, so a binary element is refused with the spelling that works.
|
|
180
|
+
*/
|
|
181
|
+
function mssqlInListJson(values) {
|
|
182
|
+
const parts = values.map((value) => {
|
|
183
|
+
if (typeof value === 'bigint')
|
|
184
|
+
return value.toString();
|
|
185
|
+
if (value instanceof Date) {
|
|
186
|
+
if (Number.isNaN(value.getTime())) {
|
|
187
|
+
throw new errors_js_1.ValidationError('SQL Server cannot bind an Invalid Date in an `in` list. Pass a valid Date, or null.');
|
|
188
|
+
}
|
|
189
|
+
return JSON.stringify(value.toISOString().slice(0, 23));
|
|
190
|
+
}
|
|
191
|
+
if (value instanceof Uint8Array) {
|
|
192
|
+
throw new errors_js_1.UnsupportedFeatureError('a binary value in an `in` / `notIn` list', 'mssql', 'SQL Server receives the list as JSON text, which cannot carry bytes. Write the list as branches: ' +
|
|
193
|
+
'`{ OR: [{ field: value1 }, { field: value2 }] }`, or use the default `join` relation strategy for ' +
|
|
194
|
+
'a relation keyed on a binary column.');
|
|
195
|
+
}
|
|
196
|
+
if (value === undefined)
|
|
197
|
+
return 'null';
|
|
198
|
+
const bound = toMssqlParam(value);
|
|
199
|
+
return JSON.stringify(bound);
|
|
200
|
+
});
|
|
201
|
+
return `[${parts.join(',')}]`;
|
|
202
|
+
}
|
|
149
203
|
// ---------------------------------------------------------------------------
|
|
150
204
|
// Result shaping + error translation
|
|
151
205
|
// ---------------------------------------------------------------------------
|
|
@@ -194,6 +248,17 @@ function coerceBigIntColumns(rows, columns) {
|
|
|
194
248
|
}
|
|
195
249
|
}
|
|
196
250
|
const EMPTY_RESULT = { rows: [], rowCount: 0 };
|
|
251
|
+
/** A JSON column's stored text as its value; text that is not JSON is returned as stored. */
|
|
252
|
+
function parseJsonText(value) {
|
|
253
|
+
if (typeof value !== 'string')
|
|
254
|
+
return value;
|
|
255
|
+
try {
|
|
256
|
+
return JSON.parse(value);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
197
262
|
/**
|
|
198
263
|
* Augment a `mssql` driver error with the Postgres-shaped `.code` (SQLSTATE) and
|
|
199
264
|
* detail fields that `wrapPgError` understands, so SQL Server constraint failures
|
|
@@ -260,13 +325,48 @@ function normalizeQueryArgs(arg, values) {
|
|
|
260
325
|
return { text: arg, params: values ?? [] };
|
|
261
326
|
return { text: arg.text, params: arg.values ?? values ?? [] };
|
|
262
327
|
}
|
|
263
|
-
/**
|
|
264
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Bind one parameter. Two value kinds get an explicit SQL type, because the
|
|
330
|
+
* driver's inference picks the wrong one for them:
|
|
331
|
+
*
|
|
332
|
+
* - A JS `bigint` is declared BIGINT. Inferred, a small one became INT and one
|
|
333
|
+
* past 2^53 an NVARCHAR string, and a multi-row `VALUES` list types each
|
|
334
|
+
* column by the highest-precedence type across its rows, so a `createMany`
|
|
335
|
+
* mixing the two converted the string to INT and failed ("overflowed an int
|
|
336
|
+
* column").
|
|
337
|
+
* - A `Date` is declared DATETIME2(7). Inferred, it is the legacy DATETIME,
|
|
338
|
+
* whose 1/300 s ticks round milliseconds on the way in, so 23:59:59.999 was
|
|
339
|
+
* stored as the next day's 00:00:00.000. DATETIME2 holds the exact instant
|
|
340
|
+
* and converts to every temporal column type.
|
|
341
|
+
*/
|
|
342
|
+
function bindParam(request, name, value, types) {
|
|
343
|
+
if (typeof value === 'bigint' && types?.BigInt !== undefined) {
|
|
344
|
+
request.input(name, types.BigInt, value);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (value instanceof Date && !Number.isNaN(value.getTime()) && typeof types?.DateTime2 === 'function') {
|
|
348
|
+
request.input(name, types.DateTime2(7), value);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
request.input(name, toMssqlParam(value));
|
|
352
|
+
}
|
|
353
|
+
/** The refusal for a placeholder the shim cannot bind (see rewritePositionalPlaceholders). */
|
|
354
|
+
function refusePlaceholder(message) {
|
|
355
|
+
throw new errors_js_1.ValidationError(message);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Bind positional `params[]` to `@p1`, `@p2`, … inputs and run the statement.
|
|
359
|
+
* A `$n` (the spelling `rawUnsafe` documents for every engine) is rewritten to
|
|
360
|
+
* `@pN` first: T-SQL reads `$1` as a MONEY literal, so it would run, compare
|
|
361
|
+
* against the number 1, and bind nothing.
|
|
362
|
+
*/
|
|
363
|
+
async function runRequest(request, text, params, types) {
|
|
364
|
+
const sql = (0, sql_placeholders_js_1.rewritePositionalPlaceholders)(text, 'mssql', params.length, (i) => `@p${i}`, refusePlaceholder);
|
|
265
365
|
for (let i = 0; i < params.length; i++) {
|
|
266
|
-
request
|
|
366
|
+
bindParam(request, `p${i + 1}`, params[i], types);
|
|
267
367
|
}
|
|
268
368
|
try {
|
|
269
|
-
const result = await request.query(
|
|
369
|
+
const result = await request.query(sql);
|
|
270
370
|
return shapeResult(result);
|
|
271
371
|
}
|
|
272
372
|
catch (err) {
|
|
@@ -352,7 +452,7 @@ class MssqlTxClient {
|
|
|
352
452
|
return EMPTY_RESULT;
|
|
353
453
|
}
|
|
354
454
|
const request = new this.sqlNS.Request(this.tx ?? this.pool);
|
|
355
|
-
return runRequest(request, rawSql, params);
|
|
455
|
+
return runRequest(request, rawSql, params, this.sqlNS);
|
|
356
456
|
}
|
|
357
457
|
release() {
|
|
358
458
|
// The transaction's connection is owned by the mssql Transaction object and is
|
|
@@ -396,7 +496,7 @@ class MssqlPool {
|
|
|
396
496
|
// biome-ignore lint/suspicious/noExplicitAny: pg-compat query is generic over the row shape.
|
|
397
497
|
async query(text, values) {
|
|
398
498
|
const { text: sql, params } = normalizeQueryArgs(text, values);
|
|
399
|
-
return runRequest(this.pool.request(), sql, params);
|
|
499
|
+
return runRequest(this.pool.request(), sql, params, this.sqlNS);
|
|
400
500
|
}
|
|
401
501
|
async connect() {
|
|
402
502
|
return new MssqlTxClient(this.pool, this.sqlNS);
|
|
@@ -478,8 +578,11 @@ function mssqlColumnType(type, maxLength) {
|
|
|
478
578
|
return 'DATE';
|
|
479
579
|
if (/\bTIME\b/.test(t))
|
|
480
580
|
return 'TIME';
|
|
581
|
+
// A bounded binary column is VARBINARY(n): VARBINARY(MAX) cannot be a key.
|
|
582
|
+
if (/VARBINARY/.test(t))
|
|
583
|
+
return `VARBINARY(${maxLength ?? 255})`;
|
|
481
584
|
if (/BYTEA|BLOB/.test(t))
|
|
482
|
-
return 'VARBINARY(MAX)';
|
|
585
|
+
return maxLength != null ? `VARBINARY(${maxLength})` : 'VARBINARY(MAX)';
|
|
483
586
|
if (/VARCHAR/.test(t))
|
|
484
587
|
return maxLength != null ? `NVARCHAR(${maxLength})` : 'NVARCHAR(255)';
|
|
485
588
|
if (/CHAR/.test(t))
|
|
@@ -521,6 +624,45 @@ function mssqlOutput(returning, alias) {
|
|
|
521
624
|
exports.mssqlDialect = {
|
|
522
625
|
...dialect_js_1.postgresDialect,
|
|
523
626
|
name: 'mssql',
|
|
627
|
+
capabilities: Object.freeze({
|
|
628
|
+
distinctOn: false,
|
|
629
|
+
positionalJson: false,
|
|
630
|
+
nullsOrderingClause: false,
|
|
631
|
+
nullsSortHigh: false,
|
|
632
|
+
insensitiveInList: false,
|
|
633
|
+
jsonDocumentEquality: 'text',
|
|
634
|
+
jsonExtractTyped: false,
|
|
635
|
+
jsonExactNumericCast: false,
|
|
636
|
+
numericTextComparison: false,
|
|
637
|
+
enforcesForeignKeys: true,
|
|
638
|
+
partitionLimitPushdown: false,
|
|
639
|
+
utcTimestampRewrite: false,
|
|
640
|
+
paramTypeCasts: false,
|
|
641
|
+
namedEnumTypes: false,
|
|
642
|
+
// No JSON column type: json is NVARCHAR(MAX), so a text column holds JSON.
|
|
643
|
+
structuredValueColumns: 'scalars',
|
|
644
|
+
// The limit the driver can actually send (see MSSQL_MAX_PARAMS), not 2100.
|
|
645
|
+
maxBindParameters: MSSQL_MAX_PARAMS,
|
|
646
|
+
maxInsertRows: MSSQL_MAX_INSERT_ROWS,
|
|
647
|
+
upsertConflictTarget: 'explicit',
|
|
648
|
+
setClauseOrder: 'snapshot',
|
|
649
|
+
reselectLocksRows: false,
|
|
650
|
+
integerDivision: '/',
|
|
651
|
+
updateExpressions: true,
|
|
652
|
+
// MERGE refuses a target row matched twice (error 8672).
|
|
653
|
+
bulkUpsertAppliesRepeatedKeys: false,
|
|
654
|
+
readOnlyTransactions: false,
|
|
655
|
+
connectionParameters: false,
|
|
656
|
+
backendCancel: false,
|
|
657
|
+
idleTransactionTimeout: false,
|
|
658
|
+
abortsTransactionOnError: false,
|
|
659
|
+
connectionDriver: 'engine-driver',
|
|
660
|
+
preparesEveryStatement: false,
|
|
661
|
+
statementSyntax: 'mssql',
|
|
662
|
+
inlineForeignKeys: true,
|
|
663
|
+
// NVARCHAR(MAX) / VARBINARY(MAX) cannot be an index key column.
|
|
664
|
+
unboundedTextKeys: false,
|
|
665
|
+
}),
|
|
524
666
|
// No RETURNING → the statement emits its rows via OUTPUT INSERTED.* in ONE
|
|
525
667
|
// round-trip (Phase-0 'output' strategy, executed like 'returning').
|
|
526
668
|
resultStrategy: 'output',
|
|
@@ -544,6 +686,17 @@ exports.mssqlDialect = {
|
|
|
544
686
|
// is Postgres-only (out of scope here).
|
|
545
687
|
supportsLateralJoin: false,
|
|
546
688
|
supportsPlanCacheMode: false,
|
|
689
|
+
// SQL Server locks through table hints, not a trailing clause: not in this
|
|
690
|
+
// release.
|
|
691
|
+
rowLockModes: [],
|
|
692
|
+
// PERCENTILE_CONT is a window function here (OVER required), so it does not
|
|
693
|
+
// combine with other aggregates in one statement.
|
|
694
|
+
supportsPercentileAggregates: false,
|
|
695
|
+
// MERGE ... WITH (HOLDLOCK).
|
|
696
|
+
supportsBulkUpsert: true,
|
|
697
|
+
// Both MERGE forms alias the target `T` (and the incoming rows `S`), so the
|
|
698
|
+
// conflict-UPDATE reads the existing row through `T.`, never the table name.
|
|
699
|
+
upsertTargetAlias: 'T',
|
|
547
700
|
// sp_getapplock / sp_releaseapplock exist (used by a future migrate adapter).
|
|
548
701
|
supportsAdvisoryLock: true,
|
|
549
702
|
// No in-band EXPLAIN: SQL Server's SHOWPLAN is a session toggle
|
|
@@ -602,7 +755,9 @@ exports.mssqlDialect = {
|
|
|
602
755
|
// returned `1`, i.e. the strategy silently changed the caller's value
|
|
603
756
|
// types. Above 2^53 all three keep the string, which is the point of
|
|
604
757
|
// carrying text at all.
|
|
605
|
-
|
|
758
|
+
// `int8` is the spelling a code-first (`defineSchema`) schema records for
|
|
759
|
+
// the same BIGINT column, so it needs the same rule.
|
|
760
|
+
if (t === 'bigint' || t === 'int8') {
|
|
606
761
|
return {
|
|
607
762
|
sql: (ref) => `CAST(${ref} AS NVARCHAR(50))`,
|
|
608
763
|
decode: (value) => {
|
|
@@ -620,7 +775,8 @@ exports.mssqlDialect = {
|
|
|
620
775
|
// nvarchar cell as a JSON *string* (only `buildRelationSubquery`'s nested
|
|
621
776
|
// relations are JSON_QUERY-wrapped, never a scalar column), so parsing the
|
|
622
777
|
// row hands back exactly the string the driver returns for a top-level
|
|
623
|
-
// read. Both routes already agree, and casting would only add noise
|
|
778
|
+
// read. Both routes already agree, and casting would only add noise; the
|
|
779
|
+
// text is parsed for both by `decodeColumnValue` in the row parser. This
|
|
624
780
|
// is the divergence MySQL DOES have, where JSON_OBJECT embeds a real JSON
|
|
625
781
|
// column as a nested value while the driver hands back text.
|
|
626
782
|
//
|
|
@@ -634,19 +790,41 @@ exports.mssqlDialect = {
|
|
|
634
790
|
}
|
|
635
791
|
return undefined;
|
|
636
792
|
},
|
|
793
|
+
// A json/jsonb column is NVARCHAR(MAX) here, so the driver hands back the
|
|
794
|
+
// document's text, and FOR JSON PATH embeds that text as a JSON string, which
|
|
795
|
+
// the relation parser turns back into the same text. Parsing it once in the
|
|
796
|
+
// row parser gives both paths the value PostgreSQL returns.
|
|
797
|
+
decodeColumnValue(columnType) {
|
|
798
|
+
const t = columnType.toLowerCase();
|
|
799
|
+
return t === 'json' || t === 'jsonb' ? parseJsonText : undefined;
|
|
800
|
+
},
|
|
637
801
|
castAggregate(expr, target) {
|
|
638
802
|
return `CAST(${expr} AS ${target === 'int' ? 'INT' : 'FLOAT'})`;
|
|
639
803
|
},
|
|
804
|
+
// AVG over an integer column is an integer here (56.4 came back 56), and
|
|
805
|
+
// casting the RESULT cannot recover the fraction. Widen the input instead:
|
|
806
|
+
// FLOAT for the narrow integers, the PostgreSQL `avg` value in double
|
|
807
|
+
// precision; DECIMAL for BIGINT, which keeps an exact average (AVG of
|
|
808
|
+
// DECIMAL(38,0) is DECIMAL(38,6)) where FLOAT would round past 2^53.
|
|
809
|
+
averageArgument(columnRef, columnType) {
|
|
810
|
+
const t = columnType.toLowerCase();
|
|
811
|
+
if (t === 'bigint' || t === 'int8' || t === 'bigserial')
|
|
812
|
+
return `CAST(${columnRef} AS DECIMAL(38, 0))`;
|
|
813
|
+
if (SMALL_INTEGER_TYPES.has(t))
|
|
814
|
+
return `CAST(${columnRef} AS FLOAT)`;
|
|
815
|
+
return undefined;
|
|
816
|
+
},
|
|
640
817
|
// No array params. OPENJSON expands a single JSON-array param into a row set,
|
|
641
818
|
// keeping ONE placeholder (so the SQL cache stays valid regardless of list
|
|
642
819
|
// length) and handling the empty list (OPENJSON('[]') → zero rows). SQL Server
|
|
643
|
-
// implicitly converts the nvarchar `value` to the column's type,
|
|
644
|
-
//
|
|
820
|
+
// implicitly converts the nvarchar `value` to the column's type, and
|
|
821
|
+
// mssqlInListJson writes each element as the text that converts to the value
|
|
822
|
+
// `equals` binds. OPENJSON requires SQL Server 2016+.
|
|
645
823
|
buildInClause(expr, paramRef, negated) {
|
|
646
824
|
return `${expr} ${negated ? 'NOT IN' : 'IN'} (SELECT [value] FROM OPENJSON(${paramRef}))`;
|
|
647
825
|
},
|
|
648
826
|
inClauseParam(values) {
|
|
649
|
-
return
|
|
827
|
+
return mssqlInListJson(Array.isArray(values) ? values : []);
|
|
650
828
|
},
|
|
651
829
|
// OUTPUT replaces RETURNING, injected mid-statement by the statement builders,
|
|
652
830
|
// never as a trailing clause.
|
|
@@ -659,7 +837,7 @@ exports.mssqlDialect = {
|
|
|
659
837
|
},
|
|
660
838
|
buildBulkInsertStatement(input) {
|
|
661
839
|
// No UNNEST in SQL Server, emit multi-row VALUES with flattened, named `@pN`
|
|
662
|
-
// placeholders. Enforce the engine's 1000-row /
|
|
840
|
+
// placeholders. Enforce the engine's 1000-row / 2098-param statement limits.
|
|
663
841
|
const rowCount = input.rowValues.length;
|
|
664
842
|
const paramCount = input.rowValues.reduce((n, row) => n + row.length, 0);
|
|
665
843
|
if (rowCount > MSSQL_MAX_INSERT_ROWS) {
|
|
@@ -718,6 +896,35 @@ exports.mssqlDialect = {
|
|
|
718
896
|
`WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${sourceVals})` +
|
|
719
897
|
`${out};`);
|
|
720
898
|
},
|
|
899
|
+
buildBulkUpsertStatement(input) {
|
|
900
|
+
// MERGE, with HOLDLOCK: without it two concurrent MERGEs can both see "not
|
|
901
|
+
// matched" for one key and the second insert fails on the unique key. A
|
|
902
|
+
// NULL in a key column never matches (T.c = S.c is not true), so the row
|
|
903
|
+
// is inserted, as PostgreSQL's ON CONFLICT treats it. `ignore` drops the
|
|
904
|
+
// WHEN MATCHED arm, so only the inserted rows come back through OUTPUT.
|
|
905
|
+
// The bind-parameter limit (MSSQL_MAX_PARAMS) is enforced by the caller's chunking.
|
|
906
|
+
let n = 0;
|
|
907
|
+
const tuples = input.rowValues.map((row) => `(${row.map(() => this.paramPlaceholder(++n)).join(', ')})`).join(', ');
|
|
908
|
+
const params = input.rowValues.flat();
|
|
909
|
+
const alias = this.upsertTargetAlias ?? 'T';
|
|
910
|
+
const on = input.conflictColumns.map((c) => `${alias}.${c} = S.${c}`).join(' AND ');
|
|
911
|
+
const cols = input.columns.join(', ');
|
|
912
|
+
let matched = '';
|
|
913
|
+
if (input.update) {
|
|
914
|
+
const update = input.update(params, { target: alias, incoming: 'S' });
|
|
915
|
+
matched =
|
|
916
|
+
`WHEN MATCHED${update.where ? ` AND (${update.where})` : ''} ` +
|
|
917
|
+
`THEN UPDATE SET ${update.setClauses.join(', ')} `;
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
sql: `MERGE INTO ${input.table} WITH (HOLDLOCK) AS ${alias} ` +
|
|
921
|
+
`USING (VALUES ${tuples}) AS S (${cols}) ON (${on}) ` +
|
|
922
|
+
matched +
|
|
923
|
+
`WHEN NOT MATCHED THEN INSERT (${cols}) VALUES (${input.columns.map((c) => `S.${c}`).join(', ')})` +
|
|
924
|
+
`${mssqlOutput(input.returning, 'INSERTED')};`,
|
|
925
|
+
params,
|
|
926
|
+
};
|
|
927
|
+
},
|
|
721
928
|
// UPDATE/DELETE inject OUTPUT mid-statement (between SET and WHERE / FROM and
|
|
722
929
|
// WHERE), a trailing clause would be invalid T-SQL.
|
|
723
930
|
buildUpdateStatement(input) {
|
|
@@ -761,6 +968,35 @@ exports.mssqlDialect = {
|
|
|
761
968
|
buildJsonPathExtract(column, pathParamRef) {
|
|
762
969
|
return `JSON_VALUE(${column}, ${pathParamRef})`;
|
|
763
970
|
},
|
|
971
|
+
// `JSON_VALUE` returns the same text for 5 and "5", so `{ path, equals }`
|
|
972
|
+
// reads the value's JSON TYPE from OPENJSON over the parent of the path
|
|
973
|
+
// (type 0 null, 1 string, 2 number, 3 boolean) and compares within it.
|
|
974
|
+
// Every part is bound, so the SQL text is the same for every operand type.
|
|
975
|
+
// Numbers compare as float (5 equals 5.0; beyond 2^53 precision is lost);
|
|
976
|
+
// strings compare with a binary collation, i.e. case-sensitively, whatever
|
|
977
|
+
// the column's collation. Arrays and objects are refused: OPENJSON returns
|
|
978
|
+
// their stored text, so whitespace and key order would decide the match.
|
|
979
|
+
buildJsonPathEquals(input) {
|
|
980
|
+
const { value, path } = input;
|
|
981
|
+
if (path.length === 0) {
|
|
982
|
+
throw new errors_js_1.UnsupportedFeatureError(`JSON \`equals\` on the whole document (${input.label})`, 'mssql', 'SQL Server stores the document as text, so whitespace and key order would decide the match. Compare a ' +
|
|
983
|
+
'scalar with `{ path: [...], equals }`, or run the query on PostgreSQL or MySQL.');
|
|
984
|
+
}
|
|
985
|
+
if (value !== null && typeof value === 'object') {
|
|
986
|
+
throw new errors_js_1.UnsupportedFeatureError(`JSON path \`equals\` with ${Array.isArray(value) ? 'an array' : 'an object'} (${input.label})`, 'mssql', 'SQL Server returns an array or object at a path as its stored text, so whitespace and key order would ' +
|
|
987
|
+
'decide the match. Compare a scalar at a deeper path, or run the query on PostgreSQL or MySQL.');
|
|
988
|
+
}
|
|
989
|
+
const type = value === null ? 0 : typeof value === 'string' ? 1 : typeof value === 'boolean' ? 3 : 2;
|
|
990
|
+
const text = value === null ? null : String(value);
|
|
991
|
+
const parent = input.bind(input.encodePath(path.slice(0, -1)));
|
|
992
|
+
const key = input.bind(String(path[path.length - 1]));
|
|
993
|
+
const typeRef = input.bind(type);
|
|
994
|
+
const v = input.bind(text);
|
|
995
|
+
return (`EXISTS (SELECT 1 FROM OPENJSON(${input.column}, ${parent}) AS [jpe] ` +
|
|
996
|
+
`WHERE [jpe].[key] = ${key} AND [jpe].[type] = ${typeRef} AND ([jpe].[type] = 0 ` +
|
|
997
|
+
`OR ([jpe].[type] = 2 AND TRY_CONVERT(float, [jpe].[value]) = TRY_CONVERT(float, ${v})) ` +
|
|
998
|
+
`OR ([jpe].[type] <> 2 AND [jpe].[value] = ${v} COLLATE Latin1_General_BIN2)))`);
|
|
999
|
+
},
|
|
764
1000
|
// ---- Type mapping -------------------------------------------------------
|
|
765
1001
|
typeToTypeScript(dialectType, nullable) {
|
|
766
1002
|
return mssqlTypeToTs(dialectType, nullable);
|
|
@@ -771,6 +1007,18 @@ exports.mssqlDialect = {
|
|
|
771
1007
|
buildColumnType(input) {
|
|
772
1008
|
return mssqlColumnType(input.type, input.maxLength);
|
|
773
1009
|
},
|
|
1010
|
+
/**
|
|
1011
|
+
* SQL Server has no enum type: an NVARCHAR column long enough for the
|
|
1012
|
+
* longest label (at least 255), and a CHECK that admits only the labels.
|
|
1013
|
+
* The CHECK compares under the column's collation, so on a case-insensitive
|
|
1014
|
+
* default collation `'ACTIVE'` passes for a label `'active'`, as it does in
|
|
1015
|
+
* every other text comparison there.
|
|
1016
|
+
*/
|
|
1017
|
+
buildEnumColumn(column, labels) {
|
|
1018
|
+
const literals = labels.map((label) => `N'${label.replace(/'/g, "''")}'`);
|
|
1019
|
+
const longest = Math.max(255, ...labels.map((label) => label.length));
|
|
1020
|
+
return { type: 'VARCHAR', maxLength: longest, check: `${column} IN (${literals.join(', ')})` };
|
|
1021
|
+
},
|
|
774
1022
|
buildColumnDefinition(input) {
|
|
775
1023
|
const isSerial = /serial/i.test(input.type);
|
|
776
1024
|
const parts = [input.name, this.buildColumnType(input)];
|
|
@@ -880,7 +1128,7 @@ exports.mssqlDialect = {
|
|
|
880
1128
|
* as a string.
|
|
881
1129
|
*
|
|
882
1130
|
* **Param-push order** strictly mirrors `collectRelationSubqueryParams`:
|
|
883
|
-
* - manyToMany / to-many with limit|orderBy: `where` → `limit` → nested;
|
|
1131
|
+
* - manyToMany / to-many with limit|offset|orderBy: `where` → `limit` → `offset` → nested;
|
|
884
1132
|
* - to-one / unordered-unlimited to-many: nested → `where` (no limit).
|
|
885
1133
|
*/
|
|
886
1134
|
function buildForJsonSubquery(dialect, ctx) {
|
|
@@ -888,9 +1136,27 @@ function buildForJsonSubquery(dialect, ctx) {
|
|
|
888
1136
|
const q = (name) => dialect.quoteIdentifier(name);
|
|
889
1137
|
const qTarget = q(targetTable);
|
|
890
1138
|
const qParent = q(parentRef);
|
|
891
|
-
|
|
1139
|
+
// `orderByEntries` flattens the array form (`[{ views: 'desc' }, { id: 'asc' }]`)
|
|
1140
|
+
// into the same entry list as the object form, as the core relation builder
|
|
1141
|
+
// does; `Object.entries` read an array's indexes as field names.
|
|
1142
|
+
const orderEntries = spec !== true && spec.orderBy ? (0, filters_js_1.orderByEntries)(spec.orderBy).filter(([, dir]) => dir !== undefined) : [];
|
|
892
1143
|
const hasOrder = orderEntries.length > 0;
|
|
893
1144
|
const hasLimit = spec !== true && spec.limit !== undefined;
|
|
1145
|
+
// `skip` arrives folded into `offset` (the args seam does that, and refuses
|
|
1146
|
+
// it on a to-one relation).
|
|
1147
|
+
const offset = spec === true ? undefined : spec.offset;
|
|
1148
|
+
const hasOffset = offset !== undefined;
|
|
1149
|
+
/** Bind the relation offset after the limit, the order the param-collect mirror pushes them. */
|
|
1150
|
+
const bindOffset = () => {
|
|
1151
|
+
if (!hasOffset)
|
|
1152
|
+
return undefined;
|
|
1153
|
+
const n = Number(offset);
|
|
1154
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
1155
|
+
throw new errors_js_1.ValidationError(`relation offset on "${targetTable}" must be a non-negative integer, received: ${String(offset)}`);
|
|
1156
|
+
}
|
|
1157
|
+
params.push(n);
|
|
1158
|
+
return dialect.paramPlaceholder(params.length);
|
|
1159
|
+
};
|
|
894
1160
|
/**
|
|
895
1161
|
* `<alias>.<col> AS [<field>]` selection for the FOR JSON object keys.
|
|
896
1162
|
*
|
|
@@ -924,7 +1190,10 @@ function buildForJsonSubquery(dialect, ctx) {
|
|
|
924
1190
|
return cols;
|
|
925
1191
|
};
|
|
926
1192
|
/** ORDER BY + OFFSET/FETCH paging clause for a to-many FOR JSON subquery. */
|
|
927
|
-
const buildPaging = (a, limitPlaceholder) => {
|
|
1193
|
+
const buildPaging = (a, limitPlaceholder, offsetPlaceholder) => {
|
|
1194
|
+
const offsetRows = `OFFSET ${offsetPlaceholder ?? '0'} ROWS`;
|
|
1195
|
+
const fetch = limitPlaceholder !== undefined ? ` FETCH NEXT ${limitPlaceholder} ROWS ONLY` : '';
|
|
1196
|
+
const paged = limitPlaceholder !== undefined || offsetPlaceholder !== undefined;
|
|
928
1197
|
if (hasOrder) {
|
|
929
1198
|
const orderBy = orderEntries
|
|
930
1199
|
.map(([k, dir]) => {
|
|
@@ -957,19 +1226,15 @@ function buildForJsonSubquery(dialect, ctx) {
|
|
|
957
1226
|
return `${a}.${q(col)} ${safeDir}`;
|
|
958
1227
|
})
|
|
959
1228
|
.join(', ');
|
|
960
|
-
// FOR JSON permits ORDER BY; OFFSET/FETCH is only needed to
|
|
961
|
-
return
|
|
962
|
-
? ` ORDER BY ${orderBy} OFFSET 0 ROWS FETCH NEXT ${limitPlaceholder} ROWS ONLY`
|
|
963
|
-
: ` ORDER BY ${orderBy}`;
|
|
964
|
-
}
|
|
965
|
-
if (limitPlaceholder !== undefined) {
|
|
966
|
-
return ` ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT ${limitPlaceholder} ROWS ONLY`;
|
|
1229
|
+
// FOR JSON permits ORDER BY; OFFSET/FETCH is only needed to page.
|
|
1230
|
+
return paged ? ` ORDER BY ${orderBy} ${offsetRows}${fetch}` : ` ORDER BY ${orderBy}`;
|
|
967
1231
|
}
|
|
968
|
-
|
|
1232
|
+
// OFFSET/FETCH requires an ORDER BY.
|
|
1233
|
+
return paged ? ` ORDER BY (SELECT NULL) ${offsetRows}${fetch}` : '';
|
|
969
1234
|
};
|
|
970
1235
|
// ----- manyToMany: JOIN target through the junction table -----------------
|
|
971
1236
|
if (relDef.type === 'manyToMany') {
|
|
972
|
-
return buildForJsonManyToMany(dialect, ctx, { colSelect, buildNested, buildPaging, hasLimit });
|
|
1237
|
+
return buildForJsonManyToMany(dialect, ctx, { colSelect, buildNested, buildPaging, bindOffset, hasLimit });
|
|
973
1238
|
}
|
|
974
1239
|
const isToOne = relDef.type === 'belongsTo' || relDef.type === 'hasOne';
|
|
975
1240
|
// Correlation direction is about WHERE THE FK LIVES, not cardinality:
|
|
@@ -988,17 +1253,19 @@ function buildForJsonSubquery(dialect, ctx) {
|
|
|
988
1253
|
return `SELECT TOP 1 ${cols} FROM ${qTarget} ${alias} WHERE ${where} FOR JSON PATH, WITHOUT_ARRAY_WRAPPER, INCLUDE_NULL_VALUES`;
|
|
989
1254
|
}
|
|
990
1255
|
// ----- hasMany ------------------------------------------------------------
|
|
991
|
-
const willWrap = hasLimit || hasOrder;
|
|
1256
|
+
const willWrap = hasLimit || hasOrder || hasOffset;
|
|
992
1257
|
let nestedCols;
|
|
993
1258
|
let extra;
|
|
994
1259
|
let limitPlaceholder;
|
|
1260
|
+
let offsetPlaceholder;
|
|
995
1261
|
if (willWrap) {
|
|
996
|
-
// Wrap order: where → limit → nested.
|
|
1262
|
+
// Wrap order: where → limit → offset → nested.
|
|
997
1263
|
extra = ctx.buildWhere(alias);
|
|
998
1264
|
if (hasLimit) {
|
|
999
1265
|
params.push(Number(spec.limit));
|
|
1000
1266
|
limitPlaceholder = dialect.paramPlaceholder(params.length);
|
|
1001
1267
|
}
|
|
1268
|
+
offsetPlaceholder = bindOffset();
|
|
1002
1269
|
nestedCols = buildNested(alias);
|
|
1003
1270
|
}
|
|
1004
1271
|
else {
|
|
@@ -1008,7 +1275,7 @@ function buildForJsonSubquery(dialect, ctx) {
|
|
|
1008
1275
|
}
|
|
1009
1276
|
const where = extra ? `${correlation} AND ${extra}` : correlation;
|
|
1010
1277
|
const cols = [...colSelect(alias), ...nestedCols].join(', ');
|
|
1011
|
-
const paging = buildPaging(alias, limitPlaceholder);
|
|
1278
|
+
const paging = buildPaging(alias, limitPlaceholder, offsetPlaceholder);
|
|
1012
1279
|
return `SELECT ISNULL((SELECT ${cols} FROM ${qTarget} ${alias} WHERE ${where}${paging} FOR JSON PATH, INCLUDE_NULL_VALUES), '[]')`;
|
|
1013
1280
|
}
|
|
1014
1281
|
/** `FOR JSON PATH` subquery for a manyToMany relation (JOIN target through junction). */
|
|
@@ -1042,17 +1309,18 @@ function buildForJsonManyToMany(dialect, ctx, h) {
|
|
|
1042
1309
|
`but referenceKey has ${refKeys.length}.`);
|
|
1043
1310
|
}
|
|
1044
1311
|
const correlation = sourceKeys.map((jcol, i) => `${jalias}.${q(jcol)} = ${qParent}.${q(refKeys[i])}`).join(' AND ');
|
|
1045
|
-
// Param order mirrors collectRelationSubqueryParams: where → limit → nested.
|
|
1312
|
+
// Param order mirrors collectRelationSubqueryParams: where → limit → offset → nested.
|
|
1046
1313
|
const extra = ctx.buildWhere(alias);
|
|
1047
1314
|
let limitPlaceholder;
|
|
1048
1315
|
if (h.hasLimit) {
|
|
1049
1316
|
params.push(Number(spec.limit));
|
|
1050
1317
|
limitPlaceholder = dialect.paramPlaceholder(params.length);
|
|
1051
1318
|
}
|
|
1319
|
+
const offsetPlaceholder = h.bindOffset();
|
|
1052
1320
|
const nestedCols = h.buildNested(alias);
|
|
1053
1321
|
const where = extra ? `${correlation} AND ${extra}` : correlation;
|
|
1054
1322
|
const cols = [...h.colSelect(alias), ...nestedCols].join(', ');
|
|
1055
|
-
const paging = h.buildPaging(alias, limitPlaceholder);
|
|
1323
|
+
const paging = h.buildPaging(alias, limitPlaceholder, offsetPlaceholder);
|
|
1056
1324
|
return (`SELECT ISNULL((SELECT ${cols} FROM ${qTarget} ${alias} ` +
|
|
1057
1325
|
`JOIN ${qJunction} ${jalias} ON ${joinOn} WHERE ${where}${paging} ` +
|
|
1058
1326
|
`FOR JSON PATH, INCLUDE_NULL_VALUES), '[]')`);
|
|
@@ -1086,8 +1354,8 @@ const bitIsTrue = (v) => v === true || v === 1 || v === '1' || v === 'true';
|
|
|
1086
1354
|
* same logical schema, legacy-first naming, per-column disambiguation, and
|
|
1087
1355
|
* collision resolution against scalar column fields included.
|
|
1088
1356
|
*/
|
|
1089
|
-
function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, columnsByTable) {
|
|
1090
|
-
return (0, introspect_js_1.deriveEngineRelations)(tableNames, foreignKeys, pkByTable, columnsByTable);
|
|
1357
|
+
function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, columnsByTable, logger) {
|
|
1358
|
+
return (0, introspect_js_1.deriveEngineRelations)(tableNames, foreignKeys, pkByTable, columnsByTable, logger);
|
|
1091
1359
|
}
|
|
1092
1360
|
/**
|
|
1093
1361
|
* Introspect a SQL Server database into the same {@link SchemaMetadata} shape the
|
|
@@ -1098,6 +1366,8 @@ function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, colum
|
|
|
1098
1366
|
*
|
|
1099
1367
|
* @param exec Runs a parameterized (`@p1`, `@p2`, …) query and returns rows.
|
|
1100
1368
|
* @param schema The SQL Server schema to introspect (default `dbo`).
|
|
1369
|
+
* @param options Optional include / exclude table-name filters, and the logger
|
|
1370
|
+
* that receives relation-naming notes (console when absent).
|
|
1101
1371
|
*/
|
|
1102
1372
|
async function introspectMssqlWith(exec, schema = 'dbo', options = {}) {
|
|
1103
1373
|
// ----- Tables -----
|
|
@@ -1236,16 +1506,19 @@ async function introspectMssqlWith(exec, schema = 'dbo', options = {}) {
|
|
|
1236
1506
|
}
|
|
1237
1507
|
}
|
|
1238
1508
|
// ----- Relations -----
|
|
1239
|
-
|
|
1509
|
+
// One field per column, as the PostgreSQL introspector requires.
|
|
1510
|
+
for (const [tableName, cols] of columnsByTable)
|
|
1511
|
+
(0, introspect_js_1.assertDistinctColumnFields)(tableName, cols);
|
|
1512
|
+
const relationsByTable = buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, columnsByTable, options.logger);
|
|
1240
1513
|
// ----- Assemble TableMetadata -----
|
|
1241
|
-
const tables =
|
|
1514
|
+
const tables = (0, introspect_js_1.nullRecord)();
|
|
1242
1515
|
for (const tableName of tableNames) {
|
|
1243
1516
|
const columns = columnsByTable.get(tableName) ?? [];
|
|
1244
|
-
const columnMap =
|
|
1245
|
-
const reverseColumnMap =
|
|
1517
|
+
const columnMap = (0, introspect_js_1.nullRecord)();
|
|
1518
|
+
const reverseColumnMap = (0, introspect_js_1.nullRecord)();
|
|
1246
1519
|
const dateColumns = new Set();
|
|
1247
|
-
const dialectTypes =
|
|
1248
|
-
const pgTypes =
|
|
1520
|
+
const dialectTypes = (0, introspect_js_1.nullRecord)();
|
|
1521
|
+
const pgTypes = (0, introspect_js_1.nullRecord)();
|
|
1249
1522
|
const allColumns = [];
|
|
1250
1523
|
for (const col of columns) {
|
|
1251
1524
|
columnMap[col.field] = col.name;
|
|
@@ -1267,7 +1540,7 @@ async function introspectMssqlWith(exec, schema = 'dbo', options = {}) {
|
|
|
1267
1540
|
allColumns,
|
|
1268
1541
|
primaryKey: pkByTable.get(tableName) ?? [],
|
|
1269
1542
|
uniqueColumns: uniqueByTable.get(tableName) ?? [],
|
|
1270
|
-
relations: relationsByTable.get(tableName) ??
|
|
1543
|
+
relations: relationsByTable.get(tableName) ?? (0, introspect_js_1.nullRecord)(),
|
|
1271
1544
|
indexes: indexesByTable.get(tableName) ?? [],
|
|
1272
1545
|
};
|
|
1273
1546
|
}
|
|
@@ -1293,6 +1566,7 @@ async function introspectMssql(options) {
|
|
|
1293
1566
|
return await introspectMssqlWith(exec, options.schema ?? 'dbo', {
|
|
1294
1567
|
include: options.include,
|
|
1295
1568
|
exclude: options.exclude,
|
|
1569
|
+
logger: options.logger,
|
|
1296
1570
|
});
|
|
1297
1571
|
}
|
|
1298
1572
|
finally {
|
|
@@ -1401,35 +1675,48 @@ async function turbineMssql(target, schema, options = {}) {
|
|
|
1401
1675
|
owns = true;
|
|
1402
1676
|
}
|
|
1403
1677
|
}
|
|
1404
|
-
|
|
1678
|
+
let client;
|
|
1405
1679
|
try {
|
|
1406
|
-
|
|
1407
|
-
|
|
1680
|
+
// Probe the server version (fail fast on SQL Server < 2016).
|
|
1681
|
+
try {
|
|
1682
|
+
const rows = (await pool.query("SELECT CAST(SERVERPROPERTY('ProductMajorVersion') AS INT) AS v")).rows;
|
|
1683
|
+
assertSupportedVersion(Number(rows[0]?.v ?? 0));
|
|
1684
|
+
}
|
|
1685
|
+
catch (err) {
|
|
1686
|
+
if (err instanceof errors_js_1.ConnectionError)
|
|
1687
|
+
throw err;
|
|
1688
|
+
// A non-version probe failure (permissions, etc.) should not block startup.
|
|
1689
|
+
}
|
|
1690
|
+
// Everything that is not a SQL Server introspection option is client config
|
|
1691
|
+
// and rides through untouched, so a new TurbineConfig option works here the
|
|
1692
|
+
// day it lands. The engine-owned keys come last, so no caller can unbind the
|
|
1693
|
+
// dialect.
|
|
1694
|
+
const { schema: _dbSchema, ...clientConfig } = options;
|
|
1695
|
+
client = new client_js_1.TurbineClient({
|
|
1696
|
+
...clientConfig,
|
|
1697
|
+
pool,
|
|
1698
|
+
dialect: exports.mssqlDialect,
|
|
1699
|
+
preparedStatements: false,
|
|
1700
|
+
}, schema);
|
|
1408
1701
|
}
|
|
1409
1702
|
catch (err) {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
//
|
|
1703
|
+
// A failed factory hands the caller nothing to close, so a pool it BUILT
|
|
1704
|
+
// (already connected) is closed here rather than left holding the process
|
|
1705
|
+
// open; the client constructor refuses a bad config, a static global filter
|
|
1706
|
+
// naming no table or column for one. A pool the caller supplied stays open.
|
|
1707
|
+
if (owns)
|
|
1708
|
+
await pool.end().catch(() => { });
|
|
1709
|
+
throw err;
|
|
1413
1710
|
}
|
|
1414
|
-
// Everything that is not a SQL Server introspection option is client config
|
|
1415
|
-
// and rides through untouched, so a new TurbineConfig option works here the
|
|
1416
|
-
// day it lands. The engine-owned keys come last, so no caller can unbind the
|
|
1417
|
-
// dialect.
|
|
1418
|
-
const { schema: _dbSchema, ...clientConfig } = options;
|
|
1419
|
-
const client = new client_js_1.TurbineClient({
|
|
1420
|
-
...clientConfig,
|
|
1421
|
-
pool,
|
|
1422
|
-
dialect: exports.mssqlDialect,
|
|
1423
|
-
preparedStatements: false,
|
|
1424
|
-
}, schema);
|
|
1425
1711
|
if (owns) {
|
|
1426
1712
|
// Turbine built this pool, so disconnect()/end() must close it. External pools
|
|
1427
1713
|
// (injection) stay the caller's responsibility (disconnect() no-op), consistent
|
|
1428
1714
|
// with turbineHttp / turbineMysql / turbineSqlite.
|
|
1429
1715
|
const baseDisconnect = client.disconnect.bind(client);
|
|
1430
|
-
const close = async () => {
|
|
1431
|
-
await baseDisconnect();
|
|
1716
|
+
const close = async (options) => {
|
|
1717
|
+
const result = options === undefined ? await baseDisconnect() : await baseDisconnect(options);
|
|
1432
1718
|
await pool.end();
|
|
1719
|
+
return result ?? undefined;
|
|
1433
1720
|
};
|
|
1434
1721
|
const patch = client;
|
|
1435
1722
|
patch.disconnect = close;
|