qubu 0.6.0 → 0.6.2
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 +112 -0
- package/dist/{complete-DP7pliuY.mjs → canonical-B5_ouh-c.mjs} +466 -241
- package/dist/codegen.d.mts +2 -2
- package/dist/codegen.mjs +180 -90
- package/dist/column-Cyc2CMnG.mjs +116 -0
- package/dist/column-DDRvD7SF.mjs +721 -0
- package/dist/{constraints-YGyNPQ_z.mjs → constraints-CAmi18Uk.mjs} +8 -3
- package/dist/core.d.mts +3 -3
- package/dist/core.mjs +4 -5
- package/dist/diagnostics-I9vVtXkc.mjs +40 -0
- package/dist/diff.d.mts +9 -9
- package/dist/diff.mjs +152 -102
- package/dist/{explain-CkIK13L_.mjs → explain-BIqEmZha.mjs} +1 -1
- package/dist/expressions-_6JF_J77.mjs +130 -0
- package/dist/index-CaxrMD1A.d.mts +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +414 -77
- package/dist/introspection/mysql.d.mts +26 -0
- package/dist/introspection/mysql.mjs +1278 -0
- package/dist/introspection/postgres.d.mts +43 -0
- package/dist/introspection/postgres.mjs +1889 -0
- package/dist/introspection/sqlite.d.mts +15 -0
- package/dist/introspection/sqlite.mjs +1172 -0
- package/dist/introspection.d.mts +26 -87
- package/dist/introspection.mjs +3 -4351
- package/dist/mysql.d.mts +3 -3
- package/dist/mysql.mjs +8 -7
- package/dist/{errors-Dxv73YJu.mjs → omit-VEr9Ydux.mjs} +5 -1
- package/dist/{on-conflict-DZQ85f1t.mjs → on-conflict-B2rFyHGF.mjs} +78 -8
- package/dist/on-duplicate-key-update-Czsw1Yq-.mjs +95 -0
- package/dist/postgres-hFhd0I9n.mjs +68 -0
- package/dist/postgres.d.mts +3 -3
- package/dist/postgres.mjs +3 -52
- package/dist/registry-BXE_4M9P.mjs +127 -0
- package/dist/{relational-CxnLCqZQ.mjs → relational-CoPBETjI.mjs} +3 -3
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +8 -7
- package/dist/{serialize-BN07IK0v.mjs → serialize-CyobNEx-.mjs} +174 -30
- package/dist/serialize-Du2UPZMt.d.mts +92 -0
- package/dist/snapshot/mysql.d.mts +5 -5
- package/dist/snapshot/mysql.mjs +32 -32
- package/dist/snapshot/postgres.d.mts +5 -5
- package/dist/snapshot/postgres.mjs +25 -25
- package/dist/snapshot/sqlite.d.mts +5 -5
- package/dist/snapshot/sqlite.mjs +30 -30
- package/dist/snapshot-mIb-Zzb5.mjs +1489 -0
- package/dist/snapshot.d.mts +4 -5
- package/dist/snapshot.mjs +3 -4
- package/dist/{source-SqrKWjFJ.mjs → source-DYSUqzvb.mjs} +2 -2
- package/dist/sqlite.d.mts +2 -2
- package/dist/sqlite.mjs +10 -10
- package/dist/{table-BwflqeAj.mjs → table-B8zEq0az.mjs} +4 -4
- package/dist/{types-BIJsj2fJ.mjs → types-CYHpSPwj.mjs} +10 -7
- package/dist/{types-CTCqtFlS.d.mts → types-CiMvKi5V.d.mts} +14 -4
- package/dist/{types-C0VkiwpR.d.mts → types-Dqr4o2I1.d.mts} +640 -189
- package/dist/value-CpaUFtjw.mjs +45 -0
- package/dist/vite/ambient.d.ts +2 -0
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +2 -0
- package/docs/dialects-and-execution.md +61 -11
- package/docs/getting-started.md +1 -1
- package/docs/guides/compose-queries.md +22 -0
- package/docs/guides/drizzle.md +11 -11
- package/docs/guides/extensions/typed-expressions.md +5 -3
- package/docs/guides/json.md +95 -2
- package/docs/guides/mutations.md +125 -0
- package/docs/guides/valtio-sync.md +113 -0
- package/docs/index.md +1 -1
- package/docs/migrations/adapters.md +58 -21
- package/docs/migrations/index.md +50 -12
- package/docs/migrations/operations.md +20 -6
- package/docs/migrations/recovery.md +7 -1
- package/docs/query-model/fragments.md +21 -1
- package/docs/reference/introspection-support.md +16 -18
- package/docs/reference/supported-surface.md +23 -11
- package/docs/schema/catalog-model.md +20 -7
- package/docs/schema/code-generation.md +4 -3
- package/docs/schema/diff.md +2 -2
- package/docs/schema/introspection.md +24 -16
- package/docs/schema/snapshots.md +18 -8
- package/docs/schema/storage-and-schema-sql.md +7 -2
- package/docs/sql-semantic-types.md +8 -0
- package/package.json +14 -1
- package/dist/canonical-DMvR9yBe.mjs +0 -972
- package/dist/column-Da37jYSD.mjs +0 -309
- package/dist/column-r1Y4ivwt.mjs +0 -327
- package/dist/complete-types-CY0KbzNw.d.mts +0 -371
- package/dist/index-B2rZf3-2.d.mts +0 -32
- package/dist/json-Db7XRD91.mjs +0 -169
- package/dist/omit-OxV58AwX.mjs +0 -5
- package/dist/registry-BRcUuazJ.mjs +0 -256
- package/dist/serialize-CEIIlWhC.d.mts +0 -66
- package/dist/snapshot-Xam8-q0j.mjs +0 -392
- package/dist/standard-DfcZEVOj.mjs +0 -12
- package/dist/value-D14I_XgL.mjs +0 -29
package/dist/index.mjs
CHANGED
|
@@ -1,58 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { i as
|
|
7
|
-
import {
|
|
8
|
-
import { t as
|
|
9
|
-
import { r as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { i as
|
|
1
|
+
import { A as fragment, B as decodeResultRow, C as isExpression, D as markExpressionCategory, E as makeSchemaExpression, F as attachResultValue, G as timestampResultDecoder, H as resultShapeValue, I as bigintResultDecoder, L as booleanResultDecoder, M as parenthesize, O as markSchemaExpression, P as ResultDecodingError, R as createResultShape, S as snakeCaseIdentifier, T as makeExpression, U as resultValue, V as jsonTextResultDecoder, W as resultValueOf, X as assertDialectCapability, c as externalDefault, d as identityColumn, i as identifier, j as isFragment, l as externalGeneratedColumn, t as createColumnReference, u as generatedColumn, w as isSchemaExpression, x as resolveSqlNames, z as dateResultDecoder } from "./column-DDRvD7SF.mjs";
|
|
2
|
+
import { i as value, t as asValue } from "./value-CpaUFtjw.mjs";
|
|
3
|
+
import { c as render, n as call, o as cast, r as schemaCall, t as createClause } from "./types-CYHpSPwj.mjs";
|
|
4
|
+
import { n as QueryValidationError, r as queryValidationError, t as omit } from "./omit-VEr9Ydux.mjs";
|
|
5
|
+
import { _ as timestamp, c as encodeColumnParameter, d as nativeColumn, f as nativeStorage, g as text, h as portableStorage, i as column, l as integer, m as numeric, n as binary, o as columnSqlType, p as nullable, r as boolean, s as date, t as bigint, u as json, v as uuid } from "./column-Cyc2CMnG.mjs";
|
|
6
|
+
import { a as isDistinctFrom, c as lt, d as notLike, f as expressionOperand, i as gte, l as lte, m as renderOperands, n as eq, o as isNotDistinctFrom, p as isNullOperand, r as gt, s as like, u as ne } from "./relational-CoPBETjI.mjs";
|
|
7
|
+
import { r as exposeColumns, t as createSource } from "./source-DYSUqzvb.mjs";
|
|
8
|
+
import { n as alias, r as lateral, t as table } from "./table-B8zEq0az.mjs";
|
|
9
|
+
import { a as primaryKey, c as uniqueConstraint, i as foreignKey, n as catalogForeignKey, o as references, r as check, s as unique, t as catalogCheck, u as index } from "./constraints-CAmi18Uk.mjs";
|
|
10
|
+
import { c as unsafeSchemaSql } from "./expressions-_6JF_J77.mjs";
|
|
11
|
+
import { i as schema } from "./registry-BXE_4M9P.mjs";
|
|
12
|
+
import { i as renderDuplicateKeyUpdate, n as incomingAlias } from "./on-duplicate-key-update-Czsw1Yq-.mjs";
|
|
13
13
|
//#region src/execution.ts
|
|
14
14
|
function qubu(adapter, options = {}) {
|
|
15
|
-
|
|
16
|
-
const client = createClient(adapter, observation);
|
|
17
|
-
if (!isTransactionalQueryAdapter(adapter)) return client;
|
|
18
|
-
return Object.freeze({
|
|
19
|
-
...client,
|
|
20
|
-
async transaction(callback, transactionOptions) {
|
|
21
|
-
const active = startTransactionOperation(observation, transactionOptions?.hookMetadata);
|
|
22
|
-
try {
|
|
23
|
-
const result = await adapter.transaction(async (transactionAdapter) => callback(createClient(transactionAdapter, observation, active?.operation.id)), adapterTransactionOptions(transactionOptions));
|
|
24
|
-
active?.finish({ status: "success" });
|
|
25
|
-
return result;
|
|
26
|
-
} catch (error) {
|
|
27
|
-
active?.finish({
|
|
28
|
-
status: "error",
|
|
29
|
-
error
|
|
30
|
-
});
|
|
31
|
-
throw error;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
});
|
|
15
|
+
return createClient(adapter, createObservation(options.hooks));
|
|
35
16
|
}
|
|
36
17
|
function createClient(adapter, observation, parentId) {
|
|
37
|
-
|
|
18
|
+
return Object.freeze({
|
|
38
19
|
adapter,
|
|
39
20
|
execute(query, options) {
|
|
40
21
|
return executeInternal(query, adapter, options ?? {}, observation, parentId);
|
|
41
22
|
},
|
|
42
23
|
rows(query, options) {
|
|
43
24
|
return executeRowsInternal(query, adapter, options ?? {}, observation, parentId);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const streaming = isStreamingQueryAdapter(adapter);
|
|
47
|
-
const explainable = isExplainableQueryAdapter(adapter);
|
|
48
|
-
if (!streaming && !explainable) return Object.freeze(client);
|
|
49
|
-
return Object.freeze({
|
|
50
|
-
...client,
|
|
51
|
-
...streaming ? { stream(query, options) {
|
|
25
|
+
},
|
|
26
|
+
...isStreamingQueryAdapter(adapter) ? { stream(query, options) {
|
|
52
27
|
return streamInternal(query, adapter, options ?? {}, observation, parentId);
|
|
53
28
|
} } : {},
|
|
54
|
-
...
|
|
29
|
+
...isExplainableQueryAdapter(adapter) ? { explain(query, options) {
|
|
55
30
|
return explainInternal(query, adapter, options ?? {}, observation, parentId);
|
|
31
|
+
} } : {},
|
|
32
|
+
...isTransactionalQueryAdapter(adapter) ? { async transaction(callback, options) {
|
|
33
|
+
const active = startTransactionOperation(observation, options?.hookMetadata, parentId);
|
|
34
|
+
try {
|
|
35
|
+
const result = await adapter.transaction(async (scoped) => callback(createClient(scoped, observation, active?.operation.id)), adapterTransactionOptions(options));
|
|
36
|
+
active?.finish({ status: "success" });
|
|
37
|
+
return result;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
active?.finish({
|
|
40
|
+
status: "error",
|
|
41
|
+
error
|
|
42
|
+
});
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
56
45
|
} } : {}
|
|
57
46
|
});
|
|
58
47
|
}
|
|
@@ -83,11 +72,11 @@ function startQueryOperation(observation, kind, request, dialect, metadata, pare
|
|
|
83
72
|
parentId
|
|
84
73
|
});
|
|
85
74
|
}
|
|
86
|
-
function startTransactionOperation(observation, metadata) {
|
|
75
|
+
function startTransactionOperation(observation, metadata, parentId) {
|
|
87
76
|
return startOperation(observation, {
|
|
88
77
|
kind: "transaction",
|
|
89
78
|
metadata,
|
|
90
|
-
parentId
|
|
79
|
+
parentId
|
|
91
80
|
});
|
|
92
81
|
}
|
|
93
82
|
function startOperation(observation, input) {
|
|
@@ -278,7 +267,8 @@ function createExplainRequest(query, adapter, options) {
|
|
|
278
267
|
});
|
|
279
268
|
const statement = Object.freeze({
|
|
280
269
|
text: dialect.explain.render(request.statement.text, query.queryKind, explainRenderOptions(options)),
|
|
281
|
-
parameters: request.statement.parameters
|
|
270
|
+
parameters: request.statement.parameters,
|
|
271
|
+
...request.statement.parameterSqlTypes === void 0 ? {} : { parameterSqlTypes: request.statement.parameterSqlTypes }
|
|
282
272
|
});
|
|
283
273
|
return Object.freeze({
|
|
284
274
|
...request,
|
|
@@ -307,7 +297,8 @@ function isQuery(value) {
|
|
|
307
297
|
//#region src/expressions/map-result.ts
|
|
308
298
|
/** Attach an application decoder to an expression without changing its SQL. */
|
|
309
299
|
function mapResult(expression, decoder) {
|
|
310
|
-
const
|
|
300
|
+
const metadata = resultValueOf(expression);
|
|
301
|
+
const mapped = makeExpression(expression.expressionKind, (context) => context.render(expression), expression.expressionCategory, resultValue(metadata?.type, decoder, metadata?.sqlType));
|
|
311
302
|
return isSchemaExpression(expression) ? markSchemaExpression(mapped) : mapped;
|
|
312
303
|
}
|
|
313
304
|
//#endregion
|
|
@@ -315,6 +306,7 @@ function mapResult(expression, decoder) {
|
|
|
315
306
|
function caseWhen(condition, thenValue, elseValue) {
|
|
316
307
|
const thenExpression = asValue(thenValue);
|
|
317
308
|
const elseExpression = asValue(elseValue);
|
|
309
|
+
const domain = resultValueOf(thenExpression)?.sqlType ?? resultValueOf(elseExpression)?.sqlType;
|
|
318
310
|
return makeSchemaExpression("operator", (context) => {
|
|
319
311
|
context.append("CASE WHEN ");
|
|
320
312
|
context.render(condition);
|
|
@@ -323,7 +315,7 @@ function caseWhen(condition, thenValue, elseValue) {
|
|
|
323
315
|
context.append(" ELSE ");
|
|
324
316
|
context.render(elseExpression);
|
|
325
317
|
context.append(" END");
|
|
326
|
-
});
|
|
318
|
+
}, resultValue(void 0, void 0, domain === "integer" ? "decimal" : domain));
|
|
327
319
|
}
|
|
328
320
|
//#endregion
|
|
329
321
|
//#region src/expressions/sql.ts
|
|
@@ -368,40 +360,40 @@ function count(expression) {
|
|
|
368
360
|
if (expression) context.render(expression);
|
|
369
361
|
else context.append("*");
|
|
370
362
|
context.append(")");
|
|
371
|
-
}, "aggregate");
|
|
363
|
+
}, "aggregate", resultValue(void 0, void 0, "integer"));
|
|
372
364
|
}
|
|
373
365
|
function countDistinct(expression) {
|
|
374
366
|
return makeExpression("function", (context) => {
|
|
375
367
|
context.append("COUNT(DISTINCT ");
|
|
376
368
|
context.render(expression);
|
|
377
369
|
context.append(")");
|
|
378
|
-
}, "aggregate");
|
|
370
|
+
}, "aggregate", resultValue(void 0, void 0, "integer"));
|
|
379
371
|
}
|
|
380
372
|
function sum(expression) {
|
|
381
|
-
return markExpressionCategory(call("SUM", expression), "aggregate");
|
|
373
|
+
return markExpressionCategory(attachResultValue(call("SUM", expression), resultValueOf(expression)), "aggregate");
|
|
382
374
|
}
|
|
383
375
|
function avg(expression) {
|
|
384
|
-
return markExpressionCategory(call("AVG", expression), "aggregate");
|
|
376
|
+
return markExpressionCategory(attachResultValue(call("AVG", expression), resultValue(void 0, void 0, "decimal")), "aggregate");
|
|
385
377
|
}
|
|
386
378
|
function min(expression) {
|
|
387
|
-
return markExpressionCategory(call("MIN", expression), "aggregate");
|
|
379
|
+
return markExpressionCategory(attachResultValue(call("MIN", expression), resultValueOf(expression)), "aggregate");
|
|
388
380
|
}
|
|
389
381
|
function max(expression) {
|
|
390
|
-
return markExpressionCategory(call("MAX", expression), "aggregate");
|
|
382
|
+
return markExpressionCategory(attachResultValue(call("MAX", expression), resultValueOf(expression)), "aggregate");
|
|
391
383
|
}
|
|
392
384
|
//#endregion
|
|
393
385
|
//#region src/expressions/functions/string.ts
|
|
394
|
-
function lower(
|
|
395
|
-
return schemaCall("LOWER",
|
|
386
|
+
function lower(input) {
|
|
387
|
+
return attachResultValue(schemaCall("LOWER", input), resultValue(void 0, void 0, "text"));
|
|
396
388
|
}
|
|
397
|
-
function upper(
|
|
398
|
-
return schemaCall("UPPER",
|
|
389
|
+
function upper(input) {
|
|
390
|
+
return attachResultValue(schemaCall("UPPER", input), resultValue(void 0, void 0, "text"));
|
|
399
391
|
}
|
|
400
|
-
function coalesce(...
|
|
401
|
-
return schemaCall("COALESCE", ...
|
|
392
|
+
function coalesce(first, ...rest) {
|
|
393
|
+
return attachResultValue(schemaCall("COALESCE", first, ...rest), resultValueOf(first));
|
|
402
394
|
}
|
|
403
395
|
function concat(...argumentsToConcat) {
|
|
404
|
-
return schemaCall("CONCAT", ...argumentsToConcat);
|
|
396
|
+
return attachResultValue(schemaCall("CONCAT", ...argumentsToConcat), resultValue(void 0, void 0, "text"));
|
|
405
397
|
}
|
|
406
398
|
//#endregion
|
|
407
399
|
//#region src/expressions/functions/window.ts
|
|
@@ -427,16 +419,16 @@ function over(expression, window) {
|
|
|
427
419
|
});
|
|
428
420
|
}
|
|
429
421
|
context.append(")");
|
|
430
|
-
}, "window");
|
|
422
|
+
}, "window", resultValueOf(expression));
|
|
431
423
|
}
|
|
432
424
|
function rowNumber() {
|
|
433
|
-
return markExpressionCategory(call("ROW_NUMBER"), "window");
|
|
425
|
+
return markExpressionCategory(attachResultValue(call("ROW_NUMBER"), resultValue(void 0, void 0, "integer")), "window");
|
|
434
426
|
}
|
|
435
427
|
function rank() {
|
|
436
|
-
return markExpressionCategory(call("RANK"), "window");
|
|
428
|
+
return markExpressionCategory(attachResultValue(call("RANK"), resultValue(void 0, void 0, "integer")), "window");
|
|
437
429
|
}
|
|
438
430
|
function denseRank() {
|
|
439
|
-
return markExpressionCategory(call("DENSE_RANK"), "window");
|
|
431
|
+
return markExpressionCategory(attachResultValue(call("DENSE_RANK"), resultValue(void 0, void 0, "integer")), "window");
|
|
440
432
|
}
|
|
441
433
|
//#endregion
|
|
442
434
|
//#region src/expressions/subquery.ts
|
|
@@ -481,7 +473,7 @@ function jsonScalar(document, path, kind) {
|
|
|
481
473
|
assertDialectCapability(context.dialect, "json");
|
|
482
474
|
if (!context.dialect.json) throw new Error(`Dialect "${context.dialect.name}" advertises JSON support without a JSON renderer`);
|
|
483
475
|
context.dialect.json.renderScalar(context, document, path.segments, kind);
|
|
484
|
-
}, kind === "boolean" ? resultValue("boolean") : void 0);
|
|
476
|
+
}, kind === "boolean" ? resultValue("boolean") : resultValue(void 0, void 0, kind === "number" ? "decimal" : "text"));
|
|
485
477
|
}
|
|
486
478
|
/** Extract a JSON string, returning SQL NULL for missing, null, or non-string values. */
|
|
487
479
|
function jsonText(document, path) {
|
|
@@ -513,7 +505,7 @@ function arithmetic(operator, left, right) {
|
|
|
513
505
|
context.append(` ${operator} `);
|
|
514
506
|
context.render(rightExpression);
|
|
515
507
|
context.append(")");
|
|
516
|
-
});
|
|
508
|
+
}, resultValue(void 0, void 0, resultValueOf(left)?.sqlType === "integer" ? "decimal" : resultValueOf(left)?.sqlType));
|
|
517
509
|
}
|
|
518
510
|
const add = (left, right) => arithmetic("+", left, right);
|
|
519
511
|
const subtract = (left, right) => arithmetic("-", left, right);
|
|
@@ -686,11 +678,11 @@ function renderPagination(context, clauses) {
|
|
|
686
678
|
if (index > 0) context.append(" ");
|
|
687
679
|
if (part.kind === "offset") {
|
|
688
680
|
context.append("OFFSET ");
|
|
689
|
-
context.parameter(part.rows);
|
|
681
|
+
context.parameter(part.rows, "integer");
|
|
690
682
|
context.append(" ROWS");
|
|
691
683
|
} else {
|
|
692
684
|
context.append(`FETCH ${part.direction} `);
|
|
693
|
-
context.parameter(part.rows);
|
|
685
|
+
context.parameter(part.rows, "integer");
|
|
694
686
|
context.append(" ROWS ONLY");
|
|
695
687
|
}
|
|
696
688
|
});
|
|
@@ -824,7 +816,9 @@ function select(selection, ...parts) {
|
|
|
824
816
|
clause,
|
|
825
817
|
index
|
|
826
818
|
})).sort((left, right) => left.clause.order - right.clause.order || left.index - right.index).map(({ clause }) => clause);
|
|
827
|
-
|
|
819
|
+
const row = selectionRow(selection);
|
|
820
|
+
const query = createQuery("select", row, selectionResultShape(selection), renderBody);
|
|
821
|
+
function renderBody(context, extraOrdering) {
|
|
828
822
|
const beforeSelect = orderedClauses.filter((clause) => clause.placement === "before-select");
|
|
829
823
|
const afterSelect = orderedClauses.filter((clause) => clause.placement === "after-select");
|
|
830
824
|
const paginationClauses = afterSelect.filter((clause) => clause.clauseKind === "offset" || clause.clauseKind === "fetch");
|
|
@@ -841,6 +835,12 @@ function select(selection, ...parts) {
|
|
|
841
835
|
context.append(" ");
|
|
842
836
|
}
|
|
843
837
|
renderSelection(selection, context);
|
|
838
|
+
extraOrdering?.terms.forEach((term, index) => {
|
|
839
|
+
if (!extraOrdering.hidden[index]) return;
|
|
840
|
+
context.append(", ");
|
|
841
|
+
context.render(term.expression);
|
|
842
|
+
context.append(" AS " + context.dialect.quoteIdentifier(extraOrdering.names[index]));
|
|
843
|
+
});
|
|
844
844
|
for (const clause of afterSelect) {
|
|
845
845
|
if (clause.clauseKind === "correlate") continue;
|
|
846
846
|
if (clause === distinctClause) continue;
|
|
@@ -852,7 +852,56 @@ function select(selection, ...parts) {
|
|
|
852
852
|
continue;
|
|
853
853
|
}
|
|
854
854
|
context.append(" ");
|
|
855
|
-
|
|
855
|
+
if (clause.clauseKind === "order-by" && extraOrdering) {
|
|
856
|
+
context.append("ORDER BY ");
|
|
857
|
+
extraOrdering.terms.forEach((term, index) => {
|
|
858
|
+
if (index) context.append(", ");
|
|
859
|
+
context.append(context.dialect.quoteIdentifier(extraOrdering.names[index]));
|
|
860
|
+
if (term.direction) context.append(` ${term.direction}`);
|
|
861
|
+
if (term.nulls) context.append(` NULLS ${term.nulls}`);
|
|
862
|
+
});
|
|
863
|
+
} else context.render(clause);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return Object.freeze({
|
|
867
|
+
...query,
|
|
868
|
+
cardinality: normalizedClauses.some((clause) => clause.clauseKind === "fetch" && clause.rows <= 1) ? "zero-or-one" : normalizedClauses.every((clause) => [
|
|
869
|
+
"distinct",
|
|
870
|
+
"order-by",
|
|
871
|
+
"row-lock",
|
|
872
|
+
"with"
|
|
873
|
+
].includes(clause.clauseKind)) ? "exactly-one" : "many",
|
|
874
|
+
renderJsonRows(context, ordinal) {
|
|
875
|
+
const terms = orderedClauses.find((clause) => clause.clauseKind === "order-by")?.terms ?? [];
|
|
876
|
+
if (orderedClauses.some((clause) => clause.clauseKind === "distinct") && terms.some((term) => !Object.values(selection).includes(term.expression))) throw new TypeError("Nested JSON DISTINCT ordering must use selected expressions");
|
|
877
|
+
const occupied = new Set(Object.keys(row).map(snakeCaseIdentifier));
|
|
878
|
+
occupied.add(ordinal);
|
|
879
|
+
const hidden = [];
|
|
880
|
+
const names = terms.map((term, index) => {
|
|
881
|
+
const selected = Object.entries(selection).find(([, expression]) => expression === term.expression);
|
|
882
|
+
hidden.push(selected === void 0);
|
|
883
|
+
if (selected) return snakeCaseIdentifier(selected[0]);
|
|
884
|
+
let name = `__qubu_json_sort_${index}`;
|
|
885
|
+
while (occupied.has(name)) name += "_";
|
|
886
|
+
occupied.add(name);
|
|
887
|
+
return name;
|
|
888
|
+
});
|
|
889
|
+
const quote = context.dialect.quoteIdentifier;
|
|
890
|
+
context.append(`SELECT ${quote("__qubu_ordered")}.*, DENSE_RANK() OVER (`);
|
|
891
|
+
if (terms.length) context.append("ORDER BY ");
|
|
892
|
+
terms.forEach((term, index) => {
|
|
893
|
+
if (index) context.append(", ");
|
|
894
|
+
context.append(`${quote("__qubu_ordered")}.${quote(names[index])}`);
|
|
895
|
+
if (term.direction) context.append(` ${term.direction}`);
|
|
896
|
+
if (term.nulls) context.append(` NULLS ${term.nulls}`);
|
|
897
|
+
});
|
|
898
|
+
context.append(`) AS ${quote(ordinal)} FROM (`);
|
|
899
|
+
renderBody(context, {
|
|
900
|
+
terms,
|
|
901
|
+
names,
|
|
902
|
+
hidden
|
|
903
|
+
});
|
|
904
|
+
context.append(`) AS ${quote("__qubu_ordered")}`);
|
|
856
905
|
}
|
|
857
906
|
});
|
|
858
907
|
}
|
|
@@ -864,8 +913,10 @@ function setOperation(operator, left, right) {
|
|
|
864
913
|
row: left.row,
|
|
865
914
|
resultShape: left.resultShape,
|
|
866
915
|
render: (context) => {
|
|
916
|
+
if (context.dialect.name === "sqlite") context.append("SELECT * FROM ");
|
|
867
917
|
context.render(parenthesize(left));
|
|
868
918
|
context.append(` ${operator} `);
|
|
919
|
+
if (context.dialect.name === "sqlite") context.append("SELECT * FROM ");
|
|
869
920
|
context.render(parenthesize(right));
|
|
870
921
|
}
|
|
871
922
|
};
|
|
@@ -1165,6 +1216,7 @@ function validateMutationClauses(kind, clauses) {
|
|
|
1165
1216
|
let whereSeen = false;
|
|
1166
1217
|
let returningSeen = false;
|
|
1167
1218
|
let allowAllSeen = false;
|
|
1219
|
+
let updateFromSeen = false;
|
|
1168
1220
|
for (const clause of clauses) if (clause.clauseKind === "where") {
|
|
1169
1221
|
if (whereSeen) throw queryValidationError({
|
|
1170
1222
|
code: "duplicate-clause",
|
|
@@ -1192,6 +1244,15 @@ function validateMutationClauses(kind, clauses) {
|
|
|
1192
1244
|
hint: "Keep one allowAll() marker when the mutation is intentionally unrestricted."
|
|
1193
1245
|
});
|
|
1194
1246
|
allowAllSeen = true;
|
|
1247
|
+
} else if (clause.clauseKind === "update-from") {
|
|
1248
|
+
if (updateFromSeen) throw queryValidationError({
|
|
1249
|
+
code: "duplicate-clause",
|
|
1250
|
+
context: "mutation.update.clauses",
|
|
1251
|
+
path: ["clauses", "updateFrom"],
|
|
1252
|
+
message: "UPDATE accepts only one FROM clause",
|
|
1253
|
+
hint: "Pass every source to one updateFrom() call."
|
|
1254
|
+
});
|
|
1255
|
+
updateFromSeen = true;
|
|
1195
1256
|
}
|
|
1196
1257
|
if (!whereSeen && !allowAllSeen) throw queryValidationError({
|
|
1197
1258
|
code: "unsafe-mutation",
|
|
@@ -1201,6 +1262,15 @@ function validateMutationClauses(kind, clauses) {
|
|
|
1201
1262
|
hint: "Add where(...) to target rows, or add allowAll() to make the unrestricted intent explicit."
|
|
1202
1263
|
});
|
|
1203
1264
|
}
|
|
1265
|
+
function validateMutationWithClauses(kind, clauses) {
|
|
1266
|
+
if (clauses.filter((clause) => clause.clauseKind === "with").length > 1) throw queryValidationError({
|
|
1267
|
+
code: "duplicate-clause",
|
|
1268
|
+
context: `mutation.${kind.toLowerCase()}.clauses`,
|
|
1269
|
+
path: ["clauses", "with"],
|
|
1270
|
+
message: `${kind} accepts only one WITH clause`,
|
|
1271
|
+
hint: "Pass every CTE to one withCte() clause."
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1204
1274
|
//#endregion
|
|
1205
1275
|
//#region src/query/mutation/insert.ts
|
|
1206
1276
|
function values(...rows) {
|
|
@@ -1222,7 +1292,37 @@ function insertSelect(query, columns) {
|
|
|
1222
1292
|
function insertInto(table, source, ...clauses) {
|
|
1223
1293
|
validateInsert(table, source);
|
|
1224
1294
|
const insertClauses = clauses;
|
|
1295
|
+
const duplicate = insertClauses.find((clause) => clause.clauseKind === "on-duplicate-key-update");
|
|
1296
|
+
if (duplicate) {
|
|
1297
|
+
if (insertClauses.some((clause) => clause.clauseKind === "returning")) throw queryValidationError({
|
|
1298
|
+
code: "invalid-mutation",
|
|
1299
|
+
context: "insert.duplicate-key",
|
|
1300
|
+
path: ["returning"],
|
|
1301
|
+
message: "MySQL duplicate-key updates do not support RETURNING",
|
|
1302
|
+
hint: "Read the row in a separate query."
|
|
1303
|
+
});
|
|
1304
|
+
if (duplicate.table !== table) throw queryValidationError({
|
|
1305
|
+
code: "invalid-mutation",
|
|
1306
|
+
context: "insert.duplicate-key",
|
|
1307
|
+
path: ["onDuplicateKeyUpdate"],
|
|
1308
|
+
message: "Duplicate-key updates require the same INSERT target table",
|
|
1309
|
+
hint: "Pass the INSERT table to onDuplicateKeyUpdate()."
|
|
1310
|
+
});
|
|
1311
|
+
if (insertClauses.filter((clause) => clause.clauseKind === "on-duplicate-key-update" || clause.clauseKind === "on-conflict").length !== 1) throw queryValidationError({
|
|
1312
|
+
code: "duplicate-clause",
|
|
1313
|
+
context: "insert.duplicate-key",
|
|
1314
|
+
path: ["clauses"],
|
|
1315
|
+
message: "INSERT accepts one conflict clause",
|
|
1316
|
+
hint: "Use one onDuplicateKeyUpdate() clause."
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
validateMutationWithClauses("INSERT", insertClauses);
|
|
1320
|
+
const withClause = insertClauses.find((clause) => clause.clauseKind === "with");
|
|
1225
1321
|
return createMutation("insert", insertClauses.find((clause) => clause.clauseKind === "returning")?.row ?? {}, insertClauses.find((clause) => clause.clauseKind === "returning")?.resultShape ?? { fields: [] }, (context) => {
|
|
1322
|
+
if (withClause) {
|
|
1323
|
+
context.render(withClause);
|
|
1324
|
+
context.append(" ");
|
|
1325
|
+
}
|
|
1226
1326
|
context.append("INSERT INTO ");
|
|
1227
1327
|
context.render(table.reference);
|
|
1228
1328
|
if (source.insertKind === "values") {
|
|
@@ -1230,7 +1330,7 @@ function insertInto(table, source, ...clauses) {
|
|
|
1230
1330
|
const explicitColumns = Object.keys(rows[0] ?? {});
|
|
1231
1331
|
const runtimeDefaultColumns = Object.entries(table.definitions).filter(([columnName, definition]) => !explicitColumns.includes(columnName) && definition.defaultFn !== void 0).map(([columnName]) => columnName);
|
|
1232
1332
|
const columns = [...explicitColumns, ...runtimeDefaultColumns];
|
|
1233
|
-
if (columns.length === 0) context.append(" DEFAULT VALUES");
|
|
1333
|
+
if (columns.length === 0) context.append(context.dialect.name === "mysql" ? ` () VALUES ${rows.map(() => "()").join(", ")}` : " DEFAULT VALUES");
|
|
1234
1334
|
else {
|
|
1235
1335
|
renderTargetColumns(context, table, columns);
|
|
1236
1336
|
context.append(" VALUES ");
|
|
@@ -1247,7 +1347,7 @@ function insertInto(table, source, ...clauses) {
|
|
|
1247
1347
|
}
|
|
1248
1348
|
} else if (source.insertKind === "default-values") {
|
|
1249
1349
|
const runtimeDefaults = Object.entries(table.definitions).filter(([, definition]) => definition.defaultFn !== void 0);
|
|
1250
|
-
if (runtimeDefaults.length === 0) context.append(" DEFAULT VALUES");
|
|
1350
|
+
if (runtimeDefaults.length === 0) context.append(context.dialect.name === "mysql" ? " () VALUES ()" : " DEFAULT VALUES");
|
|
1251
1351
|
else {
|
|
1252
1352
|
renderTargetColumns(context, table, runtimeDefaults.map(([columnName]) => columnName));
|
|
1253
1353
|
context.append(" VALUES (");
|
|
@@ -1260,11 +1360,41 @@ function insertInto(table, source, ...clauses) {
|
|
|
1260
1360
|
} else {
|
|
1261
1361
|
renderTargetColumns(context, table, source.columns);
|
|
1262
1362
|
context.append(" ");
|
|
1263
|
-
|
|
1363
|
+
if (duplicate) {
|
|
1364
|
+
const fields = Object.keys(source.query.row);
|
|
1365
|
+
if (fields.length !== source.columns.length) throw queryValidationError({
|
|
1366
|
+
code: "invalid-insert",
|
|
1367
|
+
context: "insert.select.columns",
|
|
1368
|
+
path: ["insertSelect", "columns"],
|
|
1369
|
+
message: "INSERT SELECT target and projection lengths must match",
|
|
1370
|
+
hint: "Map every projected field to one insert column."
|
|
1371
|
+
});
|
|
1372
|
+
context.append("SELECT * FROM (SELECT ");
|
|
1373
|
+
fields.forEach((field, index) => {
|
|
1374
|
+
if (index) context.append(", ");
|
|
1375
|
+
context.render(identifier("__qubu_insert_source"));
|
|
1376
|
+
context.append(".");
|
|
1377
|
+
context.render(identifier(snakeCaseIdentifier(field)));
|
|
1378
|
+
context.append(" AS ");
|
|
1379
|
+
context.render(identifier(table.sqlNames[source.columns[index]] ?? source.columns[index]));
|
|
1380
|
+
});
|
|
1381
|
+
context.append(" FROM (");
|
|
1382
|
+
context.renderRelation(source.query);
|
|
1383
|
+
context.append(") AS ");
|
|
1384
|
+
context.render(identifier("__qubu_insert_source"));
|
|
1385
|
+
context.append(") AS ");
|
|
1386
|
+
context.render(identifier(incomingAlias(table)));
|
|
1387
|
+
} else context.renderRelation(source.query);
|
|
1388
|
+
}
|
|
1389
|
+
if (duplicate && source.insertKind !== "select") {
|
|
1390
|
+
context.append(" AS ");
|
|
1391
|
+
context.render(identifier(incomingAlias(table)));
|
|
1264
1392
|
}
|
|
1265
1393
|
for (const clause of insertClauses) {
|
|
1394
|
+
if (clause.clauseKind === "with") continue;
|
|
1266
1395
|
context.append(" ");
|
|
1267
|
-
|
|
1396
|
+
if (clause.clauseKind === "on-duplicate-key-update") renderDuplicateKeyUpdate(context, clause, source.insertKind === "select" ? source.columns : void 0);
|
|
1397
|
+
else context.render(clause);
|
|
1268
1398
|
}
|
|
1269
1399
|
});
|
|
1270
1400
|
}
|
|
@@ -1278,7 +1408,7 @@ function renderTargetColumns(context, table, columns) {
|
|
|
1278
1408
|
}
|
|
1279
1409
|
function renderInsertValue(context, input, definition) {
|
|
1280
1410
|
if (isExpression(input)) context.render(input);
|
|
1281
|
-
else context.parameter(encodeColumnParameter(definition, input));
|
|
1411
|
+
else context.parameter(encodeColumnParameter(definition, input), columnSqlType(definition));
|
|
1282
1412
|
}
|
|
1283
1413
|
function validateInsert(table, source) {
|
|
1284
1414
|
const definitions = table.definitions;
|
|
@@ -1395,10 +1525,17 @@ function validateInsert(table, source) {
|
|
|
1395
1525
|
function update(table, assignments, ...clauses) {
|
|
1396
1526
|
const normalizedClauses = clauses;
|
|
1397
1527
|
validateMutationClauses("UPDATE", normalizedClauses);
|
|
1528
|
+
validateMutationWithClauses("UPDATE", normalizedClauses);
|
|
1398
1529
|
const entries = validateUpdate(table, assignments);
|
|
1399
1530
|
const whereClause = normalizedClauses.find((clause) => clause.clauseKind === "where");
|
|
1531
|
+
const withClause = normalizedClauses.find((clause) => clause.clauseKind === "with");
|
|
1532
|
+
const fromClause = normalizedClauses.find((clause) => clause.clauseKind === "update-from");
|
|
1400
1533
|
const returningClause = normalizedClauses.find((clause) => clause.clauseKind === "returning");
|
|
1401
1534
|
return createMutation("update", returningClause?.row ?? {}, returningClause?.resultShape ?? { fields: [] }, (context) => {
|
|
1535
|
+
if (withClause) {
|
|
1536
|
+
context.render(withClause);
|
|
1537
|
+
context.append(" ");
|
|
1538
|
+
}
|
|
1402
1539
|
context.append("UPDATE ");
|
|
1403
1540
|
context.render(table.reference);
|
|
1404
1541
|
context.append(" SET ");
|
|
@@ -1408,6 +1545,10 @@ function update(table, assignments, ...clauses) {
|
|
|
1408
1545
|
context.append(" = ");
|
|
1409
1546
|
renderAssignmentValue(context, value, table.definitions[columnName]);
|
|
1410
1547
|
});
|
|
1548
|
+
if (fromClause) {
|
|
1549
|
+
context.append(" ");
|
|
1550
|
+
context.render(fromClause);
|
|
1551
|
+
}
|
|
1411
1552
|
if (whereClause) {
|
|
1412
1553
|
context.append(" ");
|
|
1413
1554
|
context.render(whereClause);
|
|
@@ -1420,7 +1561,7 @@ function update(table, assignments, ...clauses) {
|
|
|
1420
1561
|
}
|
|
1421
1562
|
function renderAssignmentValue(context, value, definition) {
|
|
1422
1563
|
if (isExpression(value)) context.render(value);
|
|
1423
|
-
else context.parameter(encodeColumnParameter(definition, value));
|
|
1564
|
+
else context.parameter(encodeColumnParameter(definition, value), columnSqlType(definition));
|
|
1424
1565
|
}
|
|
1425
1566
|
function validateUpdate(table, assignments) {
|
|
1426
1567
|
const definitions = table.definitions;
|
|
@@ -1456,9 +1597,15 @@ function validateUpdate(table, assignments) {
|
|
|
1456
1597
|
function deleteFrom(table, ...clauses) {
|
|
1457
1598
|
const normalizedClauses = clauses;
|
|
1458
1599
|
validateMutationClauses("DELETE", normalizedClauses);
|
|
1600
|
+
validateMutationWithClauses("DELETE", normalizedClauses);
|
|
1459
1601
|
const whereClause = normalizedClauses.find((clause) => clause.clauseKind === "where");
|
|
1602
|
+
const withClause = normalizedClauses.find((clause) => clause.clauseKind === "with");
|
|
1460
1603
|
const returningClause = normalizedClauses.find((clause) => clause.clauseKind === "returning");
|
|
1461
1604
|
return createMutation("delete", returningClause?.row ?? {}, returningClause?.resultShape ?? { fields: [] }, (context) => {
|
|
1605
|
+
if (withClause) {
|
|
1606
|
+
context.render(withClause);
|
|
1607
|
+
context.append(" ");
|
|
1608
|
+
}
|
|
1462
1609
|
context.append("DELETE FROM ");
|
|
1463
1610
|
context.render(table.reference);
|
|
1464
1611
|
if (whereClause) {
|
|
@@ -1486,4 +1633,194 @@ function returning(selection) {
|
|
|
1486
1633
|
});
|
|
1487
1634
|
}
|
|
1488
1635
|
//#endregion
|
|
1489
|
-
|
|
1636
|
+
//#region src/expressions/nested-json.ts
|
|
1637
|
+
/**
|
|
1638
|
+
* Turn a SELECT or set query into an inferred array expression; empty queries return [].
|
|
1639
|
+
*
|
|
1640
|
+
* Preserves correlation, explicit ordering and pagination on PostgreSQL, MySQL 8.0.21+, and SQLite
|
|
1641
|
+
* 3.45+. Without ORDER BY, element order is unspecified. DISTINCT ordering must use expressions
|
|
1642
|
+
* present in the projection.
|
|
1643
|
+
*
|
|
1644
|
+
* Nested fields require supported runtime SQL domains. Bigints, binary values and serialized JSON
|
|
1645
|
+
* use exact text transport; dates and booleans are normalized without adapter-specific decoders.
|
|
1646
|
+
* Unsupported domains or lossy numeric representations throw. Custom result decoders receive the
|
|
1647
|
+
* transport value (including strings for bigint, decimal, binary hex and JSON), consistent with
|
|
1648
|
+
* their unknown-input contract.
|
|
1649
|
+
*/
|
|
1650
|
+
function jsonArrayFrom(query) {
|
|
1651
|
+
return nestedJson(query, true);
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Turn a query proven to return at most one row into an inferred object.
|
|
1655
|
+
*
|
|
1656
|
+
* Empty queries return null; exactly-one queries retain a non-null result type. Use an
|
|
1657
|
+
* unconditional fetchFirst(1) to prove the row bound for a table query. Shares dialect, projection
|
|
1658
|
+
* and exact-decoding rules with {@link jsonArrayFrom}.
|
|
1659
|
+
*
|
|
1660
|
+
* @throws {TypeError} When the query lacks a runtime at-most-one-row proof.
|
|
1661
|
+
*/
|
|
1662
|
+
function jsonObjectFrom(query) {
|
|
1663
|
+
if (query.cardinality !== "exactly-one" && query.cardinality !== "zero-or-one") throw new TypeError("jsonObjectFrom() requires a query proven to return at most one row");
|
|
1664
|
+
return nestedJson(query, false);
|
|
1665
|
+
}
|
|
1666
|
+
function nestedJson(query, array) {
|
|
1667
|
+
if (query.queryKind !== "select" && query.queryKind !== "set") throw new TypeError("Nested JSON requires a SELECT or set query");
|
|
1668
|
+
return makeExpression("subquery", (context) => renderNestedJson(context, query, array), "subquery", resultValue("json", (value, context) => {
|
|
1669
|
+
const parsed = typeof value === "string" ? parseExactJson(value) : value;
|
|
1670
|
+
if (array) {
|
|
1671
|
+
if (!Array.isArray(parsed)) throw new TypeError("Expected a nested JSON array");
|
|
1672
|
+
return parsed.map((row) => decodeNestedRow(row, query.resultShape, context));
|
|
1673
|
+
}
|
|
1674
|
+
return parsed === null ? null : decodeNestedRow(parsed, query.resultShape, context);
|
|
1675
|
+
}, "json"));
|
|
1676
|
+
}
|
|
1677
|
+
function renderNestedJson(context, query, array) {
|
|
1678
|
+
const dialect = context.dialect.name;
|
|
1679
|
+
if (dialect !== "postgresql" && dialect !== "sqlite" && dialect !== "mysql") throw new TypeError(`Nested JSON queries are not supported by dialect "${dialect}"`);
|
|
1680
|
+
const q = context.dialect.quoteIdentifier;
|
|
1681
|
+
const names = new Set(query.resultShape.fields.map((field) => snakeCaseIdentifier(field.name)));
|
|
1682
|
+
let ordinal = "__qubu_json_order";
|
|
1683
|
+
while (names.has(ordinal)) ordinal += "_";
|
|
1684
|
+
const ordered = array && query.renderJsonRows !== void 0;
|
|
1685
|
+
const reference = (name) => context.append(`${q("__qubu_json")}.${q(name)}`);
|
|
1686
|
+
const renderObject = () => {
|
|
1687
|
+
context.append(dialect === "postgresql" ? "json_build_object(" : "json_object(");
|
|
1688
|
+
query.resultShape.fields.forEach((field, index) => {
|
|
1689
|
+
if (index) context.append(", ");
|
|
1690
|
+
context.parameter(field.name, "text");
|
|
1691
|
+
if (dialect === "postgresql") context.append("::text");
|
|
1692
|
+
context.append(", ");
|
|
1693
|
+
const type = field.sqlType ?? field.type;
|
|
1694
|
+
const ref = () => reference(snakeCaseIdentifier(field.name));
|
|
1695
|
+
if (type === "decimal" && dialect === "sqlite") {
|
|
1696
|
+
context.append("CASE typeof(");
|
|
1697
|
+
ref();
|
|
1698
|
+
context.append(") WHEN 'real' THEN printf('%!.17g', ");
|
|
1699
|
+
ref();
|
|
1700
|
+
context.append(") WHEN 'integer' THEN CAST(");
|
|
1701
|
+
ref();
|
|
1702
|
+
context.append(" AS TEXT) WHEN 'null' THEN NULL ELSE json('invalid numeric storage') END");
|
|
1703
|
+
} else if (type === "bigint" || type === "decimal" || type === "json") {
|
|
1704
|
+
context.append("CAST(");
|
|
1705
|
+
ref();
|
|
1706
|
+
context.append(dialect === "mysql" ? " AS CHAR)" : " AS TEXT)");
|
|
1707
|
+
} else if (type === "binary") {
|
|
1708
|
+
if (dialect === "sqlite") {
|
|
1709
|
+
context.append("CASE WHEN ");
|
|
1710
|
+
ref();
|
|
1711
|
+
context.append(" IS NULL THEN NULL ELSE ");
|
|
1712
|
+
}
|
|
1713
|
+
context.append(dialect === "postgresql" ? "encode(" : "hex(");
|
|
1714
|
+
ref();
|
|
1715
|
+
context.append(dialect === "postgresql" ? ", 'hex')" : ")");
|
|
1716
|
+
if (dialect === "sqlite") context.append(" END");
|
|
1717
|
+
} else if (type === void 0 || type === "unknown" || ![
|
|
1718
|
+
"text",
|
|
1719
|
+
"uuid",
|
|
1720
|
+
"integer",
|
|
1721
|
+
"decimal",
|
|
1722
|
+
"boolean",
|
|
1723
|
+
"date",
|
|
1724
|
+
"timestamp",
|
|
1725
|
+
"json"
|
|
1726
|
+
].includes(type)) throw new TypeError(`Nested JSON field "${field.name}" requires a supported declared SQL domain`);
|
|
1727
|
+
else ref();
|
|
1728
|
+
});
|
|
1729
|
+
context.append(")");
|
|
1730
|
+
};
|
|
1731
|
+
context.append(array ? "COALESCE((SELECT " : "(SELECT ");
|
|
1732
|
+
if (array) context.append(dialect === "postgresql" ? "json_agg(" : dialect === "sqlite" ? "json_group_array(" : "JSON_ARRAYAGG(");
|
|
1733
|
+
renderObject();
|
|
1734
|
+
if (array) {
|
|
1735
|
+
if (ordered && dialect !== "mysql") {
|
|
1736
|
+
context.append(" ORDER BY ");
|
|
1737
|
+
reference(ordinal);
|
|
1738
|
+
}
|
|
1739
|
+
context.append(")");
|
|
1740
|
+
if (dialect === "mysql") {
|
|
1741
|
+
context.append(" OVER (");
|
|
1742
|
+
if (ordered) {
|
|
1743
|
+
context.append("ORDER BY ");
|
|
1744
|
+
reference(ordinal);
|
|
1745
|
+
context.append(" ");
|
|
1746
|
+
}
|
|
1747
|
+
context.append("ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)");
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
context.append(" FROM (");
|
|
1751
|
+
context.renderRelation(fragment((inner) => {
|
|
1752
|
+
if (ordered) query.renderJsonRows(inner, ordinal);
|
|
1753
|
+
else inner.render(query);
|
|
1754
|
+
}));
|
|
1755
|
+
context.append(`) AS ${q("__qubu_json")}`);
|
|
1756
|
+
if (array && dialect === "mysql") context.append(" LIMIT 1");
|
|
1757
|
+
context.append(")");
|
|
1758
|
+
if (array) context.append(dialect === "postgresql" ? ", '[]'::json)" : dialect === "mysql" ? ", JSON_ARRAY())" : ", json('[]'))");
|
|
1759
|
+
}
|
|
1760
|
+
function decodeNestedRow(value, shape, context) {
|
|
1761
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Expected a nested JSON object");
|
|
1762
|
+
const row = value;
|
|
1763
|
+
const entries = shape.fields.map((field) => {
|
|
1764
|
+
if (!Object.hasOwn(row, field.name)) throw new TypeError(`Missing nested JSON field "${field.name}"`);
|
|
1765
|
+
return [field.name, decodeNestedField(row[field.name], field, context)];
|
|
1766
|
+
});
|
|
1767
|
+
return Object.fromEntries(entries);
|
|
1768
|
+
}
|
|
1769
|
+
function decodeNestedField(value, field, context) {
|
|
1770
|
+
if (value === null) return null;
|
|
1771
|
+
if (field.decoder) return field.decoder(value, {
|
|
1772
|
+
...context,
|
|
1773
|
+
field: `${context.field}.${field.name}`
|
|
1774
|
+
});
|
|
1775
|
+
switch (field.type ?? field.sqlType) {
|
|
1776
|
+
case "bigint": return bigintResultDecoder(value, context);
|
|
1777
|
+
case "boolean": return booleanResultDecoder(value, context);
|
|
1778
|
+
case "date": return dateResultDecoder(value, context);
|
|
1779
|
+
case "timestamp": return timestampResultDecoder(value, context);
|
|
1780
|
+
case "json": return typeof value === "string" ? parseExactJson(value) : value;
|
|
1781
|
+
case "binary":
|
|
1782
|
+
if (typeof value !== "string" || !/^(?:[\da-f]{2})*$/iu.test(value)) throw new TypeError("Expected hexadecimal binary result");
|
|
1783
|
+
return Uint8Array.from(value.match(/../gu) ?? [], (byte) => parseInt(byte, 16));
|
|
1784
|
+
case "integer":
|
|
1785
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) throw new TypeError("Expected a safe integer result");
|
|
1786
|
+
return value;
|
|
1787
|
+
case "decimal":
|
|
1788
|
+
if (typeof value !== "string" && typeof value !== "number") throw new TypeError("Expected a numeric result");
|
|
1789
|
+
if (context.dialect.name === "sqlite") {
|
|
1790
|
+
canonicalDecimal(String(value));
|
|
1791
|
+
const number = Number(value);
|
|
1792
|
+
if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number)) throw new TypeError("Expected a finite, safely represented SQLite number");
|
|
1793
|
+
return number;
|
|
1794
|
+
}
|
|
1795
|
+
return exactNumber(String(value));
|
|
1796
|
+
case "text":
|
|
1797
|
+
case "uuid":
|
|
1798
|
+
if (typeof value !== "string") throw new TypeError("Expected a text result");
|
|
1799
|
+
return value;
|
|
1800
|
+
default: throw new TypeError(`Unsupported nested JSON field "${field.name}"`);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
function exactNumber(value) {
|
|
1804
|
+
const number = Number(value);
|
|
1805
|
+
if (!Number.isFinite(number) || canonicalDecimal(value) !== canonicalDecimal(String(number))) throw new TypeError("Nested JSON numeric value cannot be represented exactly as a JavaScript number");
|
|
1806
|
+
if (Number.isInteger(number) && !Number.isSafeInteger(number)) throw new TypeError("Nested JSON integer exceeds JavaScript's safe integer range");
|
|
1807
|
+
return number;
|
|
1808
|
+
}
|
|
1809
|
+
function canonicalDecimal(value) {
|
|
1810
|
+
const match = /^([+-]?)(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/u.exec(value);
|
|
1811
|
+
if (!match) throw new TypeError("Expected a decimal number");
|
|
1812
|
+
let digits = (match[2] + (match[3] ?? "")).replace(/^0+/u, "");
|
|
1813
|
+
if (!digits) return "0";
|
|
1814
|
+
let exponent = Number(match[4] ?? 0) - (match[3]?.length ?? 0);
|
|
1815
|
+
if (!Number.isSafeInteger(exponent)) throw new TypeError("Unsupported decimal exponent");
|
|
1816
|
+
const zeros = /0+$/u.exec(digits)?.[0].length ?? 0;
|
|
1817
|
+
digits = digits.slice(0, digits.length - zeros);
|
|
1818
|
+
exponent += zeros;
|
|
1819
|
+
return `${match[1] === "-" ? "-" : ""}${digits}e${exponent}`;
|
|
1820
|
+
}
|
|
1821
|
+
function parseExactJson(value) {
|
|
1822
|
+
for (const token of value.matchAll(/"(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/gu)) if (token[0][0] !== "\"") exactNumber(token[0]);
|
|
1823
|
+
return JSON.parse(value);
|
|
1824
|
+
}
|
|
1825
|
+
//#endregion
|
|
1826
|
+
export { QueryValidationError, ResultDecodingError, add, alias, all, allowAll, and, asValue, asc, avg, between, bigint, bigintResultDecoder, binary, boolean, booleanResultDecoder, call, caseWhen, cast, catalogCheck, catalogForeignKey, check, coalesce, column, concat, correlate, count, countDistinct, crossJoin, cte, date, dateResultDecoder, defaultValues, deleteFrom, denseRank, desc, distinct, divide, eq, except, execute, executeRows, exists, explain, externalDefault, externalGeneratedColumn, fetchFirst, fetchNext, foreignKey, from, fullJoin, generatedColumn, groupBy, gt, gte, having, identityColumn, inList, inQuery, index, innerJoin, insertInto, insertSelect, integer, intersect, isDistinctFrom, isNotDistinctFrom, isNotNull, isNull, isTrue, json, jsonArrayFrom, jsonBoolean, jsonExists, jsonNumber, jsonObjectFrom, jsonPath, jsonText, jsonTextResultDecoder, lateral, leftJoin, like, lower, lt, lte, mapResult, max, min, modulo, multiply, nativeColumn, nativeStorage, naturalJoin, ne, not, notExists, notIn, notLike, nullable, nullsFirst, nullsLast, numeric, offset, omit, or, order, orderBy, over, portableStorage, primaryKey, qubu, rank, recursiveCte, references, render, returning, rightJoin, rowLock, rowNumber, scalar, schema, schemaCall, select, sql, stream, subtract, sum, table, text, timestamp, timestampResultDecoder, union, unionAll, unique, uniqueConstraint, unsafeSchemaSql, update, upper, uuid, value, values, where, withCte };
|