@typed-sql/sqlite 2.2.0 → 2.3.0-rc.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/CHANGELOG.md +39 -0
- package/dist/packages/sqlite/src/index.d.ts.map +1 -1
- package/dist/packages/sqlite/src/index.js +14 -3
- package/dist/packages/sqlite/src/index.js.map +1 -1
- package/dist/packages/sqlite/src/node-sqlite.d.ts.map +1 -1
- package/dist/packages/sqlite/src/node-sqlite.js +11 -1
- package/dist/packages/sqlite/src/node-sqlite.js.map +1 -1
- package/dist/packages/sqlite/src/query.d.ts +14 -0
- package/dist/packages/sqlite/src/query.d.ts.map +1 -0
- package/dist/packages/sqlite/src/query.js +27 -0
- package/dist/packages/sqlite/src/query.js.map +1 -0
- package/dist/packages/sqlite/src/resolver.d.ts +9 -1
- package/dist/packages/sqlite/src/resolver.d.ts.map +1 -1
- package/dist/packages/sqlite/src/resolver.js +502 -77
- package/dist/packages/sqlite/src/resolver.js.map +1 -1
- package/dist/packages/sqlite/src/runtime.d.ts +4 -2
- package/dist/packages/sqlite/src/runtime.d.ts.map +1 -1
- package/dist/packages/sqlite/src/runtime.js +44 -21
- package/dist/packages/sqlite/src/runtime.js.map +1 -1
- package/dist/packages/sqlite/src/type-policy.d.ts +2 -0
- package/dist/packages/sqlite/src/type-policy.d.ts.map +1 -1
- package/dist/packages/sqlite/src/type-policy.js.map +1 -1
- package/package.json +9 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var _a;
|
|
2
|
-
import { ParameterCollector, ResolverSchemaIndex, unionTypeLiterals, } from "@typed-sql/core";
|
|
2
|
+
import { jsonArrayShape, jsonObjectShape, jsonShapeAtPath, jsonShapeFromValue, jsonShapeType, jsonTupleShape, nullJsonShape, ParameterCollector, ResolverSchemaIndex, unionJsonShapes, unionTypeLiterals, unknownJsonShape, updateJsonShapePath, } from "@typed-sql/core";
|
|
3
3
|
import { isSqliteAggregateFunction, isSqliteWindowFunction, normalizeSqliteDatabaseType, resolveSqliteBuiltin, SQLITE_CURRENT_TIME_KEYWORDS, SQLITE_EXTENSION_TABLE_FUNCTIONS, SQLITE_JSON_TABLE_FUNCTIONS, SQLITE_MATH_FUNCTIONS, SQLITE_UNSUPPORTED_OPERATORS, sqliteNumericOperands, sqliteOperator, } from "./catalog/index.js";
|
|
4
4
|
import { walkStatement } from "./parser/index.js";
|
|
5
5
|
import { compareSqliteVersions, parseSqliteVersion, sqliteVersionSupport } from "./support.js";
|
|
@@ -72,6 +72,9 @@ function isConstantFrameExpression(expression) {
|
|
|
72
72
|
return false;
|
|
73
73
|
}
|
|
74
74
|
class Resolver {
|
|
75
|
+
#runtimeValidation;
|
|
76
|
+
#requiresJsonValidation = false;
|
|
77
|
+
#projections;
|
|
75
78
|
#schema;
|
|
76
79
|
#policy;
|
|
77
80
|
#strict;
|
|
@@ -79,7 +82,9 @@ class Resolver {
|
|
|
79
82
|
#parameters = new ParameterCollector();
|
|
80
83
|
#index;
|
|
81
84
|
#activeWindows = new Map();
|
|
82
|
-
constructor(schema, options) {
|
|
85
|
+
constructor(schema, options, projections = new WeakMap()) {
|
|
86
|
+
this.#projections = projections;
|
|
87
|
+
this.#runtimeValidation = options.runtimeValidation === true;
|
|
83
88
|
this.#schema = schema;
|
|
84
89
|
this.#index = ResolverSchemaIndex.for(schema);
|
|
85
90
|
this.#policy = options.typePolicy ?? defaultSqliteTypePolicy;
|
|
@@ -306,12 +311,7 @@ class Resolver {
|
|
|
306
311
|
const columns = {};
|
|
307
312
|
result.columns.forEach((column, index) => {
|
|
308
313
|
const columnName = query.columns[index] === undefined ? column.name : name(query.columns[index]);
|
|
309
|
-
columns[columnName] =
|
|
310
|
-
name: columnName,
|
|
311
|
-
databaseType: column.databaseType ?? "unknown",
|
|
312
|
-
tsType: column.tsType,
|
|
313
|
-
nullable: column.nullable,
|
|
314
|
-
};
|
|
314
|
+
columns[columnName] = this.#project(columnName, column);
|
|
315
315
|
});
|
|
316
316
|
ctes.set(key, { name: key, columns });
|
|
317
317
|
}
|
|
@@ -371,12 +371,13 @@ class Resolver {
|
|
|
371
371
|
const columnName = query.columns[index] === undefined ? seed?.name : name(query.columns[index]);
|
|
372
372
|
if (columnName === undefined)
|
|
373
373
|
continue;
|
|
374
|
-
provisionalColumns[columnName] =
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
374
|
+
provisionalColumns[columnName] = this.#project(columnName, seed?.json === undefined
|
|
375
|
+
? {
|
|
376
|
+
tsType: seed?.tsType ?? "unknown",
|
|
377
|
+
nullable: seed?.nullable ?? true,
|
|
378
|
+
...(seed?.databaseType === undefined ? {} : { databaseType: seed.databaseType }),
|
|
379
|
+
}
|
|
380
|
+
: this.#jsonResult(unknownJsonShape, seed.nullable, seed.json.representation === "binary"));
|
|
380
381
|
}
|
|
381
382
|
const recursiveScope = new Map(inherited);
|
|
382
383
|
recursiveScope.set(name(query.name), { name: name(query.name), columns: provisionalColumns });
|
|
@@ -444,7 +445,7 @@ class Resolver {
|
|
|
444
445
|
#mergeCteSeedColumns(statements, outer, inherited) {
|
|
445
446
|
const merged = [];
|
|
446
447
|
for (const statement of statements) {
|
|
447
|
-
const probe = new _a(this.#schema, { typePolicy: this.#policy, strictExpressions: false });
|
|
448
|
+
const probe = new _a(this.#schema, { typePolicy: this.#policy, strictExpressions: false, runtimeValidation: this.#runtimeValidation }, this.#projections);
|
|
448
449
|
const columns = probe.#select(statement, outer, inherited, false);
|
|
449
450
|
if (merged.length === 0) {
|
|
450
451
|
merged.push(...columns);
|
|
@@ -453,12 +454,9 @@ class Resolver {
|
|
|
453
454
|
for (let index = 0; index < Math.min(merged.length, columns.length); index += 1) {
|
|
454
455
|
const current = merged[index];
|
|
455
456
|
const candidate = columns[index];
|
|
456
|
-
const sameDatabaseType = current.databaseType === candidate.databaseType && current.databaseType !== undefined;
|
|
457
457
|
merged[index] = {
|
|
458
|
+
...this.#mergeTypes([current, candidate], current.nullable || candidate.nullable),
|
|
458
459
|
name: current.name,
|
|
459
|
-
tsType: unionSqliteTypes([current.tsType, candidate.tsType]),
|
|
460
|
-
nullable: current.nullable || candidate.nullable,
|
|
461
|
-
...(sameDatabaseType ? { databaseType: current.databaseType } : {}),
|
|
462
460
|
range: current.range,
|
|
463
461
|
};
|
|
464
462
|
}
|
|
@@ -529,15 +527,9 @@ class Resolver {
|
|
|
529
527
|
for (let index = 0; index < columns.length; index += 1) {
|
|
530
528
|
const left = columns[index];
|
|
531
529
|
const candidate = right[index];
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
tsType: unionSqliteTypes([left.tsType, candidate.tsType]),
|
|
536
|
-
nullable: left.nullable || candidate.nullable,
|
|
537
|
-
...(left.databaseType === candidate.databaseType && left.databaseType !== undefined
|
|
538
|
-
? { databaseType: left.databaseType }
|
|
539
|
-
: {}),
|
|
540
|
-
};
|
|
530
|
+
const merged = this.#mergeTypes([left, candidate], left.nullable || candidate.nullable);
|
|
531
|
+
const { jsonSubtype: _subtype, jsonEmbedding: _embedding, ...projected } = merged;
|
|
532
|
+
columns[index] = { ...projected, name: left.name, range: left.range };
|
|
541
533
|
}
|
|
542
534
|
}
|
|
543
535
|
this.#activeWindows = previousWindows;
|
|
@@ -681,11 +673,7 @@ class Resolver {
|
|
|
681
673
|
this.#expression(reference.arguments[2], scope, ctes, this.#databaseType("text", false));
|
|
682
674
|
}
|
|
683
675
|
else {
|
|
684
|
-
const jsonInput =
|
|
685
|
-
tsType: sqliteFlexibleType(this.#policy),
|
|
686
|
-
nullable: true,
|
|
687
|
-
databaseType: "json",
|
|
688
|
-
};
|
|
676
|
+
const jsonInput = this.#jsonInput();
|
|
689
677
|
const rootPath = { tsType: "string", nullable: true, databaseType: "text" };
|
|
690
678
|
if (reference.arguments[0]?.kind === "parameter")
|
|
691
679
|
this.#expression(reference.arguments[0], scope, ctes, jsonInput);
|
|
@@ -741,12 +729,7 @@ class Resolver {
|
|
|
741
729
|
const result = this.#statement(reference.query, reference.lateral ? scope : scope.outer, ctes);
|
|
742
730
|
const columns = {};
|
|
743
731
|
for (const column of result.columns)
|
|
744
|
-
columns[column.name] =
|
|
745
|
-
name: column.name,
|
|
746
|
-
databaseType: column.databaseType ?? "unknown",
|
|
747
|
-
tsType: column.tsType,
|
|
748
|
-
nullable: column.nullable,
|
|
749
|
-
};
|
|
732
|
+
columns[column.name] = this.#project(column.name, column);
|
|
750
733
|
table = { name: name(reference.alias), columns };
|
|
751
734
|
alias = name(reference.alias);
|
|
752
735
|
}
|
|
@@ -867,6 +850,21 @@ class Resolver {
|
|
|
867
850
|
const source = this.#expression(expression.expression, scope, ctes, this.#databaseType(expression.databaseType.name, true));
|
|
868
851
|
if (!isKnownSqliteType(expression.databaseType.name))
|
|
869
852
|
this.#diagnostic("TSQ106", `Invalid or unknown SQLite cast type ${expression.databaseType.name}`, expression.databaseType.range);
|
|
853
|
+
if (mapSqliteCastType(expression.databaseType.name, this.#policy) === "string" &&
|
|
854
|
+
source.json?.representation !== "binary" &&
|
|
855
|
+
source.jsonSubtype)
|
|
856
|
+
return source;
|
|
857
|
+
if (mapSqliteCastType(expression.databaseType.name, this.#policy) === "string" &&
|
|
858
|
+
source.jsonEmbedding !== undefined &&
|
|
859
|
+
source.json?.representation !== "binary") {
|
|
860
|
+
const variants = source.jsonEmbedding.kind === "union" ? source.jsonEmbedding.variants : [source.jsonEmbedding];
|
|
861
|
+
return {
|
|
862
|
+
tsType: "string",
|
|
863
|
+
nullable: source.nullable,
|
|
864
|
+
databaseType: "text",
|
|
865
|
+
jsonEmbedding: unionJsonShapes(variants.map((item) => ["object", "array", "tuple", "null", "unknown"].includes(item.kind) ? item : { kind: "string" })),
|
|
866
|
+
};
|
|
867
|
+
}
|
|
870
868
|
return {
|
|
871
869
|
tsType: mapSqliteCastType(expression.databaseType.name, this.#policy),
|
|
872
870
|
nullable: source.nullable,
|
|
@@ -875,6 +873,15 @@ class Resolver {
|
|
|
875
873
|
}
|
|
876
874
|
if (expression.kind === "unary") {
|
|
877
875
|
const operand = this.#expression(expression.expression, scope, ctes);
|
|
876
|
+
if (expression.operator !== "+" &&
|
|
877
|
+
expression.operator !== "NOT" &&
|
|
878
|
+
(operand.json !== undefined || operand.scalarJsonShape !== undefined || operand.jsonEmbedding !== undefined))
|
|
879
|
+
return {
|
|
880
|
+
tsType: unionSqliteTypes([this.#policy.integer, "number"]),
|
|
881
|
+
nullable: operand.nullable,
|
|
882
|
+
databaseType: "numeric",
|
|
883
|
+
runtimeType: { union: [this.#policy.integer, "number"] },
|
|
884
|
+
};
|
|
878
885
|
return expression.operator === "NOT"
|
|
879
886
|
? { tsType: this.#policy.integer, nullable: operand.nullable, databaseType: "integer" }
|
|
880
887
|
: operand;
|
|
@@ -891,10 +898,7 @@ class Resolver {
|
|
|
891
898
|
const values = expression.branches.map((branch) => this.#expression(branch.then, scope, ctes));
|
|
892
899
|
if (expression.elseExpression !== undefined)
|
|
893
900
|
values.push(this.#expression(expression.elseExpression, scope, ctes));
|
|
894
|
-
return
|
|
895
|
-
tsType: unionSqliteTypes(values.map((value) => value.tsType)),
|
|
896
|
-
nullable: expression.elseExpression === undefined || values.some((value) => value.nullable),
|
|
897
|
-
};
|
|
901
|
+
return this.#mergeTypes(values, expression.elseExpression === undefined || values.some((value) => value.nullable));
|
|
898
902
|
}
|
|
899
903
|
if (expression.kind === "subquery") {
|
|
900
904
|
const result = this.#statement(expression.query, scope, ctes);
|
|
@@ -903,11 +907,8 @@ class Resolver {
|
|
|
903
907
|
return { tsType: "unknown", nullable: true };
|
|
904
908
|
}
|
|
905
909
|
const column = result.columns[0];
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
nullable: true,
|
|
909
|
-
...(column.databaseType === undefined ? {} : { databaseType: column.databaseType }),
|
|
910
|
-
};
|
|
910
|
+
const { name: _name, range: _range, ...type } = column;
|
|
911
|
+
return { ...type, nullable: true };
|
|
911
912
|
}
|
|
912
913
|
if (expression.kind === "exists") {
|
|
913
914
|
this.#statement(expression.query, scope, ctes);
|
|
@@ -955,9 +956,18 @@ class Resolver {
|
|
|
955
956
|
if (SQLITE_UNSUPPORTED_OPERATORS.has(expression.operator)) {
|
|
956
957
|
this.#unsupported(`SQLite does not support operator ${expression.operator}`, expression.range);
|
|
957
958
|
}
|
|
959
|
+
const operator = sqliteOperator(expression.operator);
|
|
958
960
|
let left;
|
|
959
961
|
let right;
|
|
960
|
-
if (
|
|
962
|
+
if (operator?.result === "json-text" || operator?.result === "json-flexible") {
|
|
963
|
+
left = this.#expression(expression.left, scope, ctes, this.#jsonInput());
|
|
964
|
+
right = this.#expression(expression.right, scope, ctes, {
|
|
965
|
+
tsType: "string",
|
|
966
|
+
nullable: true,
|
|
967
|
+
databaseType: "text",
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
else if (expression.left.kind === "parameter" && expression.right.kind !== "parameter") {
|
|
961
971
|
right = this.#expression(expression.right, scope, ctes);
|
|
962
972
|
left = this.#expression(expression.left, scope, ctes, right);
|
|
963
973
|
}
|
|
@@ -965,7 +975,6 @@ class Resolver {
|
|
|
965
975
|
left = this.#expression(expression.left, scope, ctes);
|
|
966
976
|
right = this.#expression(expression.right, scope, ctes, expression.right.kind === "parameter" ? left : undefined);
|
|
967
977
|
}
|
|
968
|
-
const operator = sqliteOperator(expression.operator);
|
|
969
978
|
if (operator?.result === "json-text" || operator?.result === "json-flexible") {
|
|
970
979
|
const availability = resolveSqliteBuiltin("JSON", 1, this.#schema.server?.versionKey ?? this.#schema.version, this.#schema.server?.features);
|
|
971
980
|
if (!this.#catalogAvailable(availability, `operator ${expression.operator}`, expression.range))
|
|
@@ -980,13 +989,15 @@ class Resolver {
|
|
|
980
989
|
if (operator?.result === "text") {
|
|
981
990
|
return { tsType: "string", nullable: left.nullable || right.nullable, databaseType: "text" };
|
|
982
991
|
}
|
|
983
|
-
if (operator?.result === "json-text")
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
992
|
+
if (operator?.result === "json-text" || operator?.result === "json-flexible") {
|
|
993
|
+
const argument = expression.right;
|
|
994
|
+
const path = argument.kind === "literal" && typeof argument.value === "string"
|
|
995
|
+
? this.#jsonPath(argument.value.startsWith("$") ? argument.value : `$.${JSON.stringify(argument.value)}`)
|
|
996
|
+
: argument.kind === "literal" && typeof argument.value === "number"
|
|
997
|
+
? [argument.value]
|
|
998
|
+
: undefined;
|
|
999
|
+
return this.#jsonExtract(this.#jsonDocument(left, expression.left), path, operator.result === "json-text", false, left.nullable || right.nullable);
|
|
1000
|
+
}
|
|
990
1001
|
if (operator?.result === "numeric" && sqliteNumericOperands(left.databaseType, right.databaseType)) {
|
|
991
1002
|
return {
|
|
992
1003
|
tsType: unionSqliteTypes([this.#policy.integer, "number"]),
|
|
@@ -1064,7 +1075,13 @@ class Resolver {
|
|
|
1064
1075
|
}
|
|
1065
1076
|
this.#builtinParameters(functionName, expression, scope, ctes);
|
|
1066
1077
|
this.#dateTimeArguments(functionName, expression);
|
|
1067
|
-
|
|
1078
|
+
const result = this.#jsonCall(functionName, values, expression.arguments) ??
|
|
1079
|
+
this.#builtinResult(builtin.definition, values, expression.arguments);
|
|
1080
|
+
if (builtinWindow) {
|
|
1081
|
+
const { jsonSubtype: _subtype, jsonEmbedding: _embedding, ...stored } = result;
|
|
1082
|
+
return stored;
|
|
1083
|
+
}
|
|
1084
|
+
return result;
|
|
1068
1085
|
}
|
|
1069
1086
|
if (builtin.status === "arity") {
|
|
1070
1087
|
const ranges = [...new Set(builtin.definitions.map(({ arguments: range }) => this.#arity(range)))].join(" or ");
|
|
@@ -1198,7 +1215,7 @@ class Resolver {
|
|
|
1198
1215
|
databaseType: "numeric",
|
|
1199
1216
|
};
|
|
1200
1217
|
const text = { tsType: "string", nullable: true, databaseType: "text" };
|
|
1201
|
-
const json =
|
|
1218
|
+
const json = this.#jsonInput();
|
|
1202
1219
|
const jsonValue = { tsType: sqliteFlexibleType(this.#policy), nullable: true };
|
|
1203
1220
|
const timeValue = {
|
|
1204
1221
|
tsType: unionSqliteTypes([this.#policy.integer, "number", "string"]),
|
|
@@ -1313,10 +1330,7 @@ class Resolver {
|
|
|
1313
1330
|
case "blob-nullable":
|
|
1314
1331
|
return { tsType: "Uint8Array", nullable: true, databaseType: "blob" };
|
|
1315
1332
|
case "coalesce":
|
|
1316
|
-
return
|
|
1317
|
-
tsType: unionSqliteTypes(values.map((value) => value.tsType)),
|
|
1318
|
-
nullable: values.every((value) => value.nullable),
|
|
1319
|
-
};
|
|
1333
|
+
return this.#mergeTypes(values, values.every((value) => value.nullable));
|
|
1320
1334
|
case "concat":
|
|
1321
1335
|
return { tsType: "string", nullable: false, databaseType: "text" };
|
|
1322
1336
|
case "concat-ws":
|
|
@@ -1326,16 +1340,10 @@ class Resolver {
|
|
|
1326
1340
|
case "first-argument-nullable":
|
|
1327
1341
|
return { ...first, nullable: true };
|
|
1328
1342
|
case "flexible-nullable":
|
|
1329
|
-
return
|
|
1330
|
-
tsType: unionSqliteTypes(values.map((value) => value.tsType)),
|
|
1331
|
-
nullable: values.some((value) => value.nullable),
|
|
1332
|
-
};
|
|
1343
|
+
return this.#mergeTypes(values, values.some((value) => value.nullable));
|
|
1333
1344
|
case "iif": {
|
|
1334
1345
|
const branches = values.filter((_, index) => index % 2 === 1 || (values.length % 2 === 1 && index === values.length - 1));
|
|
1335
|
-
return
|
|
1336
|
-
tsType: unionSqliteTypes(branches.map((value) => value.tsType)),
|
|
1337
|
-
nullable: values.length % 2 === 0 || branches.some((value) => value.nullable),
|
|
1338
|
-
};
|
|
1346
|
+
return this.#mergeTypes(branches, values.length % 2 === 0 || branches.some((value) => value.nullable));
|
|
1339
1347
|
}
|
|
1340
1348
|
case "integer":
|
|
1341
1349
|
return { tsType: this.#policy.integer, nullable: false, databaseType: "integer" };
|
|
@@ -1357,11 +1365,7 @@ class Resolver {
|
|
|
1357
1365
|
: { tsType: "Uint8Array", nullable: true, databaseType: "blob" };
|
|
1358
1366
|
case "lag-lead": {
|
|
1359
1367
|
const fallback = values[2];
|
|
1360
|
-
return
|
|
1361
|
-
...first,
|
|
1362
|
-
tsType: fallback === undefined ? first.tsType : unionSqliteTypes([first.tsType, fallback.tsType]),
|
|
1363
|
-
nullable: first.nullable || fallback === undefined || fallback.nullable,
|
|
1364
|
-
};
|
|
1368
|
+
return this.#mergeTypes(fallback === undefined ? [first] : [first, fallback], first.nullable || fallback === undefined || fallback.nullable);
|
|
1365
1369
|
}
|
|
1366
1370
|
case "nullif":
|
|
1367
1371
|
return { ...first, nullable: true };
|
|
@@ -1457,8 +1461,414 @@ class Resolver {
|
|
|
1457
1461
|
this.#diagnostic("TSQ101", `Unknown column ${identifier.name}`, identifier.range);
|
|
1458
1462
|
return column;
|
|
1459
1463
|
}
|
|
1464
|
+
#project(name, value) {
|
|
1465
|
+
const column = {
|
|
1466
|
+
name,
|
|
1467
|
+
databaseType: value.databaseType ?? "unknown",
|
|
1468
|
+
tsType: value.tsType,
|
|
1469
|
+
nullable: value.nullable,
|
|
1470
|
+
};
|
|
1471
|
+
const { name: _name, range: _range, jsonSubtype: _subtype, jsonEmbedding: _embedding, ...stored } = value;
|
|
1472
|
+
this.#projections.set(column, stored);
|
|
1473
|
+
return column;
|
|
1474
|
+
}
|
|
1475
|
+
#mergeTypes(values, nullable) {
|
|
1476
|
+
const first = values[0];
|
|
1477
|
+
if (first?.json !== undefined &&
|
|
1478
|
+
values.every((value) => value.json?.representation === first.json.representation)) {
|
|
1479
|
+
const result = this.#jsonResult(unionJsonShapes(values.map((value) => value.json.shape)), nullable, first.json.representation === "binary");
|
|
1480
|
+
return {
|
|
1481
|
+
...result,
|
|
1482
|
+
jsonEmbedding: unionJsonShapes(values.map((value) => this.#jsonValue(value))),
|
|
1483
|
+
json: {
|
|
1484
|
+
...result.json,
|
|
1485
|
+
...(values.some((value) => value.json?.requiresRuntimeValidation) ? { requiresRuntimeValidation: true } : {}),
|
|
1486
|
+
},
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
// Mixed parsed JSON/native SQL branches have no single physical decoding contract.
|
|
1490
|
+
if (values.some((value) => value.json?.representation === "parsed"))
|
|
1491
|
+
return { tsType: "unknown", nullable };
|
|
1492
|
+
const hasScalar = values.some((value) => value.scalarJsonShape !== undefined);
|
|
1493
|
+
const scalarShapes = values.map((value) => value.scalarJsonShape ??
|
|
1494
|
+
(value.tsType === "string" || value.json?.representation === "text"
|
|
1495
|
+
? { kind: "string" }
|
|
1496
|
+
: value.tsType === "bigint" || value.tsType === "number"
|
|
1497
|
+
? { kind: "number" }
|
|
1498
|
+
: undefined));
|
|
1499
|
+
if (hasScalar && scalarShapes.some((shape) => shape === undefined))
|
|
1500
|
+
return { tsType: "unknown", nullable };
|
|
1501
|
+
return {
|
|
1502
|
+
tsType: unionSqliteTypes(values.map((value) => value.tsType)),
|
|
1503
|
+
nullable,
|
|
1504
|
+
...(hasScalar
|
|
1505
|
+
? {
|
|
1506
|
+
scalarJsonShape: unionJsonShapes(scalarShapes),
|
|
1507
|
+
runtimeType: {
|
|
1508
|
+
union: values.map((value) => value.runtimeType ??
|
|
1509
|
+
(value.tsType === "string" || value.tsType === "number" || value.tsType === "bigint"
|
|
1510
|
+
? value.tsType
|
|
1511
|
+
: "unknown")),
|
|
1512
|
+
},
|
|
1513
|
+
}
|
|
1514
|
+
: {}),
|
|
1515
|
+
...(first?.databaseType !== undefined && values.every((value) => value.databaseType === first.databaseType)
|
|
1516
|
+
? { databaseType: first.databaseType }
|
|
1517
|
+
: {}),
|
|
1518
|
+
...(values.some((value) => value.json !== undefined || value.jsonEmbedding !== undefined)
|
|
1519
|
+
? { jsonEmbedding: unionJsonShapes(values.map((value) => this.#jsonValue(value))) }
|
|
1520
|
+
: {}),
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
#jsonInput() {
|
|
1524
|
+
return {
|
|
1525
|
+
tsType: sqliteFlexibleType(this.#policy),
|
|
1526
|
+
nullable: true,
|
|
1527
|
+
databaseType: "json",
|
|
1528
|
+
acceptsJsonInput: true,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
#jsonResult(shape, nullable, binary = false) {
|
|
1532
|
+
const representation = binary
|
|
1533
|
+
? "binary"
|
|
1534
|
+
: this.#policy.json === "parsed" && this.#runtimeValidation
|
|
1535
|
+
? "parsed"
|
|
1536
|
+
: "text";
|
|
1537
|
+
return {
|
|
1538
|
+
tsType: binary ? "Uint8Array" : representation === "parsed" ? jsonShapeType(shape) : "string",
|
|
1539
|
+
nullable,
|
|
1540
|
+
databaseType: binary ? "blob" : "text",
|
|
1541
|
+
jsonSubtype: true,
|
|
1542
|
+
json: {
|
|
1543
|
+
shape,
|
|
1544
|
+
representation,
|
|
1545
|
+
...(representation === "parsed" || this.#requiresJsonValidation ? { requiresRuntimeValidation: true } : {}),
|
|
1546
|
+
},
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
#jsonValue(value, expression) {
|
|
1550
|
+
if (expression?.kind === "literal" && expression.value === null)
|
|
1551
|
+
return nullJsonShape;
|
|
1552
|
+
let shape = value.jsonEmbedding ??
|
|
1553
|
+
(value.jsonSubtype || value.json?.representation === "binary" ? value.json?.shape : undefined) ??
|
|
1554
|
+
value.scalarJsonShape;
|
|
1555
|
+
if (shape === undefined) {
|
|
1556
|
+
const type = value.databaseType?.toLowerCase();
|
|
1557
|
+
if (["integer", "int", "real", "numeric"].includes(type ?? ""))
|
|
1558
|
+
shape = { kind: "number" };
|
|
1559
|
+
else if (["text", "varchar"].includes(type ?? ""))
|
|
1560
|
+
shape = { kind: "string" };
|
|
1561
|
+
else if (value.tsType === "string")
|
|
1562
|
+
shape = { kind: "string" };
|
|
1563
|
+
}
|
|
1564
|
+
shape ??= unknownJsonShape;
|
|
1565
|
+
return value.nullable ? unionJsonShapes([shape, nullJsonShape]) : shape;
|
|
1566
|
+
}
|
|
1567
|
+
#jsonDocument(value, expression) {
|
|
1568
|
+
if (value.json !== undefined)
|
|
1569
|
+
return value.json.shape;
|
|
1570
|
+
if (expression?.kind === "literal" && typeof expression.value === "string") {
|
|
1571
|
+
try {
|
|
1572
|
+
const parsed = JSON.parse(expression.value);
|
|
1573
|
+
// JSON.parse discards duplicate object labels; SQLite path lookup keeps the first.
|
|
1574
|
+
// Only canonical containers establish literal structure without another JSON parser.
|
|
1575
|
+
const trimmed = expression.value.trim();
|
|
1576
|
+
if ((trimmed.startsWith("{") || trimmed.startsWith("[")) && JSON.stringify(parsed) !== trimmed)
|
|
1577
|
+
return unknownJsonShape;
|
|
1578
|
+
return jsonShapeFromValue(parsed);
|
|
1579
|
+
}
|
|
1580
|
+
catch {
|
|
1581
|
+
/* JSON5 and malformed/dynamic input stay unknown. */
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return unknownJsonShape;
|
|
1585
|
+
}
|
|
1586
|
+
#jsonPath(text) {
|
|
1587
|
+
if (!text.startsWith("$"))
|
|
1588
|
+
return undefined;
|
|
1589
|
+
let rest = text.slice(1);
|
|
1590
|
+
const path = [];
|
|
1591
|
+
while (rest.length > 0 && path.length < 32) {
|
|
1592
|
+
const quoted = /^\.("(?:[^"\\]|\\.)*")/u.exec(rest);
|
|
1593
|
+
const member = /^\.([^.[\]"]+)/u.exec(rest);
|
|
1594
|
+
const index = /^\[(\d+|#(?:-\d+)?)\]/u.exec(rest);
|
|
1595
|
+
if (quoted !== null) {
|
|
1596
|
+
try {
|
|
1597
|
+
path.push(JSON.parse(quoted[1]));
|
|
1598
|
+
}
|
|
1599
|
+
catch {
|
|
1600
|
+
return undefined;
|
|
1601
|
+
}
|
|
1602
|
+
rest = rest.slice(quoted[0].length);
|
|
1603
|
+
}
|
|
1604
|
+
else if (member !== null) {
|
|
1605
|
+
path.push(member[1]);
|
|
1606
|
+
rest = rest.slice(member[0].length);
|
|
1607
|
+
}
|
|
1608
|
+
else if (index !== null) {
|
|
1609
|
+
path.push(index[1] === "#" || index[1] === "#-0"
|
|
1610
|
+
? Number.POSITIVE_INFINITY
|
|
1611
|
+
: index[1].startsWith("#-")
|
|
1612
|
+
? -Number(index[1].slice(2))
|
|
1613
|
+
: Number(index[1]));
|
|
1614
|
+
rest = rest.slice(index[0].length);
|
|
1615
|
+
}
|
|
1616
|
+
else
|
|
1617
|
+
return undefined;
|
|
1618
|
+
}
|
|
1619
|
+
return rest.length === 0 ? path : undefined;
|
|
1620
|
+
}
|
|
1621
|
+
#jsonExtract(shape, path, serialized, binary, nullable) {
|
|
1622
|
+
const selected = path === undefined ? { shape: unknownJsonShape, presence: "maybe" } : jsonShapeAtPath(shape, path);
|
|
1623
|
+
nullable ||= selected.presence !== "always";
|
|
1624
|
+
if (selected.presence === "never")
|
|
1625
|
+
return { tsType: "never", nullable: true, scalarJsonShape: nullJsonShape };
|
|
1626
|
+
if (serialized)
|
|
1627
|
+
return this.#jsonResult(selected.shape, nullable, binary);
|
|
1628
|
+
const variants = selected.shape.kind === "union" ? selected.shape.variants : [selected.shape];
|
|
1629
|
+
const structured = (item) => ["object", "array", "tuple"].includes(item.kind);
|
|
1630
|
+
if (variants.every(structured))
|
|
1631
|
+
return this.#jsonResult(selected.shape, nullable, binary);
|
|
1632
|
+
const types = [];
|
|
1633
|
+
const runtime = [];
|
|
1634
|
+
const scalars = [];
|
|
1635
|
+
for (const item of variants) {
|
|
1636
|
+
if (item.kind === "null") {
|
|
1637
|
+
nullable = true;
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
if (structured(item)) {
|
|
1641
|
+
types.push(binary ? "Uint8Array" : "string");
|
|
1642
|
+
runtime.push(binary ? "bytes" : "string");
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
const kind = item.kind === "literal" ? typeof item.value : item.kind;
|
|
1646
|
+
if (kind === "boolean") {
|
|
1647
|
+
types.push(this.#policy.integer === "bigint" ? "0n | 1n" : "0 | 1");
|
|
1648
|
+
runtime.push(this.#policy.integer);
|
|
1649
|
+
scalars.push(unionJsonShapes([
|
|
1650
|
+
{ kind: "literal", value: 0 },
|
|
1651
|
+
{ kind: "literal", value: 1 },
|
|
1652
|
+
]));
|
|
1653
|
+
}
|
|
1654
|
+
else if (kind === "number") {
|
|
1655
|
+
types.push(this.#policy.integer, "number");
|
|
1656
|
+
runtime.push(this.#policy.integer, "number");
|
|
1657
|
+
scalars.push({ kind: "number" });
|
|
1658
|
+
}
|
|
1659
|
+
else if (kind === "string") {
|
|
1660
|
+
types.push("string");
|
|
1661
|
+
runtime.push("string");
|
|
1662
|
+
scalars.push(item);
|
|
1663
|
+
}
|
|
1664
|
+
else {
|
|
1665
|
+
types.push(this.#policy.integer, "number", "string", ...(binary ? ["Uint8Array"] : []));
|
|
1666
|
+
runtime.push(this.#policy.integer, "number", "string", ...(binary ? ["bytes"] : []));
|
|
1667
|
+
nullable = true;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
const members = [...new Set(runtime)];
|
|
1671
|
+
return {
|
|
1672
|
+
tsType: types.length === 0 ? "never" : unionSqliteTypes(types),
|
|
1673
|
+
nullable,
|
|
1674
|
+
runtimeType: members.length === 0 ? "unknown" : members.length === 1 ? members[0] : { union: members },
|
|
1675
|
+
jsonEmbedding: unionJsonShapes(variants.map((item) => {
|
|
1676
|
+
const kind = item.kind === "literal" ? typeof item.value : item.kind;
|
|
1677
|
+
return kind === "boolean"
|
|
1678
|
+
? unionJsonShapes([
|
|
1679
|
+
{ kind: "literal", value: 0 },
|
|
1680
|
+
{ kind: "literal", value: 1 },
|
|
1681
|
+
])
|
|
1682
|
+
: kind === "number"
|
|
1683
|
+
? { kind: "number" }
|
|
1684
|
+
: item;
|
|
1685
|
+
})),
|
|
1686
|
+
...(scalars.length === variants.length ? { scalarJsonShape: unionJsonShapes(scalars) } : {}),
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
#jsonUpdate(shape, path, value, mode, arrayInsert = false, depth = 0, budget = { nodes: 0 }) {
|
|
1690
|
+
if (++budget.nodes > 1_024 || depth > 32)
|
|
1691
|
+
return unknownJsonShape;
|
|
1692
|
+
if (path.length === 0)
|
|
1693
|
+
return mode === "insert" ? shape : (value ?? nullJsonShape);
|
|
1694
|
+
if (shape.kind === "unknown")
|
|
1695
|
+
return shape;
|
|
1696
|
+
if (shape.kind === "union")
|
|
1697
|
+
return unionJsonShapes(shape.variants.map((item) => this.#jsonUpdate(item, path, value, mode, arrayInsert, depth + 1, budget)));
|
|
1698
|
+
let key = path[0];
|
|
1699
|
+
if (key === Number.POSITIVE_INFINITY && shape.kind === "tuple")
|
|
1700
|
+
key = shape.elements.length;
|
|
1701
|
+
if (typeof key === "string" && shape.kind === "object") {
|
|
1702
|
+
const selected = jsonShapeAtPath(shape, [key]);
|
|
1703
|
+
if (path.length === 1)
|
|
1704
|
+
return updateJsonShapePath(shape, [key], value, mode);
|
|
1705
|
+
if (selected.presence === "never" && mode === "replace")
|
|
1706
|
+
return shape;
|
|
1707
|
+
const child = selected.presence === "never"
|
|
1708
|
+
? typeof path[1] === "number"
|
|
1709
|
+
? jsonTupleShape([])
|
|
1710
|
+
: jsonObjectShape({})
|
|
1711
|
+
: selected.shape;
|
|
1712
|
+
let next = this.#jsonUpdate(child, path.slice(1), value, mode, arrayInsert, depth + 1, budget);
|
|
1713
|
+
if (selected.presence === "never" && next === child)
|
|
1714
|
+
return shape;
|
|
1715
|
+
if (selected.presence === "maybe" && mode !== "replace" && value !== undefined) {
|
|
1716
|
+
const missing = typeof path[1] === "number" ? jsonTupleShape([]) : jsonObjectShape({});
|
|
1717
|
+
next = unionJsonShapes([
|
|
1718
|
+
next,
|
|
1719
|
+
this.#jsonUpdate(missing, path.slice(1), value, mode, arrayInsert, depth + 1, budget),
|
|
1720
|
+
]);
|
|
1721
|
+
}
|
|
1722
|
+
return updateJsonShapePath(shape, [key], next, selected.presence === "never" ? "set" : "replace");
|
|
1723
|
+
}
|
|
1724
|
+
if (typeof key === "number" && shape.kind === "tuple") {
|
|
1725
|
+
const index = key < 0 ? shape.elements.length + key : key;
|
|
1726
|
+
if (!Number.isSafeInteger(index) || index < 0 || index > shape.elements.length)
|
|
1727
|
+
return shape;
|
|
1728
|
+
if (path.length === 1) {
|
|
1729
|
+
if (arrayInsert && value !== undefined) {
|
|
1730
|
+
const elements = [...shape.elements];
|
|
1731
|
+
elements.splice(index, 0, value);
|
|
1732
|
+
return jsonTupleShape(elements);
|
|
1733
|
+
}
|
|
1734
|
+
return updateJsonShapePath(shape, [index], value, mode);
|
|
1735
|
+
}
|
|
1736
|
+
if (index === shape.elements.length && mode === "replace")
|
|
1737
|
+
return shape;
|
|
1738
|
+
const child = shape.elements[index] ?? (typeof path[1] === "number" ? jsonTupleShape([]) : jsonObjectShape({}));
|
|
1739
|
+
return updateJsonShapePath(shape, [index], this.#jsonUpdate(child, path.slice(1), value, mode, arrayInsert, depth + 1, budget), index === shape.elements.length ? "set" : "replace");
|
|
1740
|
+
}
|
|
1741
|
+
if (typeof key === "number" && shape.kind === "array") {
|
|
1742
|
+
if (key === Number.POSITIVE_INFINITY && path.length > 1)
|
|
1743
|
+
return unknownJsonShape;
|
|
1744
|
+
return value === undefined
|
|
1745
|
+
? shape
|
|
1746
|
+
: jsonArrayShape(unionJsonShapes([
|
|
1747
|
+
shape.element,
|
|
1748
|
+
path.length === 1
|
|
1749
|
+
? value
|
|
1750
|
+
: this.#jsonUpdate(shape.element, path.slice(1), value, mode, arrayInsert, depth + 1, budget),
|
|
1751
|
+
...(path.length > 1 && mode !== "replace"
|
|
1752
|
+
? [
|
|
1753
|
+
this.#jsonUpdate(typeof path[1] === "number" ? jsonTupleShape([]) : jsonObjectShape({}), path.slice(1), value, mode, arrayInsert, depth + 1, budget),
|
|
1754
|
+
]
|
|
1755
|
+
: []),
|
|
1756
|
+
]));
|
|
1757
|
+
}
|
|
1758
|
+
return shape;
|
|
1759
|
+
}
|
|
1760
|
+
#jsonPatch(left, right, budget = { nodes: 0 }) {
|
|
1761
|
+
if (++budget.nodes > 1_024)
|
|
1762
|
+
return unknownJsonShape;
|
|
1763
|
+
if (left.kind === "unknown" || right.kind === "unknown")
|
|
1764
|
+
return unknownJsonShape;
|
|
1765
|
+
if (left.kind === "union")
|
|
1766
|
+
return unionJsonShapes(left.variants.map((item) => this.#jsonPatch(item, right, budget)));
|
|
1767
|
+
if (right.kind === "union")
|
|
1768
|
+
return unionJsonShapes(right.variants.map((item) => this.#jsonPatch(left, item, budget)));
|
|
1769
|
+
if (right.kind !== "object")
|
|
1770
|
+
return right;
|
|
1771
|
+
if (right.additionalProperties !== undefined ||
|
|
1772
|
+
Object.values(right.properties).some((property) => property.optional))
|
|
1773
|
+
return unknownJsonShape;
|
|
1774
|
+
let result = left.kind === "object" ? left : jsonObjectShape({});
|
|
1775
|
+
for (const [key, property] of Object.entries(right.properties)) {
|
|
1776
|
+
const previous = jsonShapeAtPath(result, [key]);
|
|
1777
|
+
const apply = (patch) => {
|
|
1778
|
+
if (++budget.nodes > 1_024 || patch.kind === "unknown")
|
|
1779
|
+
return unknownJsonShape;
|
|
1780
|
+
if (patch.kind === "union")
|
|
1781
|
+
return unionJsonShapes(patch.variants.map(apply));
|
|
1782
|
+
return updateJsonShapePath(result, [key], patch.kind === "null"
|
|
1783
|
+
? undefined
|
|
1784
|
+
: this.#jsonPatch(previous.presence === "never" ? jsonObjectShape({}) : previous.shape, patch, budget));
|
|
1785
|
+
};
|
|
1786
|
+
result = apply(property.shape);
|
|
1787
|
+
}
|
|
1788
|
+
return result;
|
|
1789
|
+
}
|
|
1790
|
+
#jsonCall(name, values, args) {
|
|
1791
|
+
if (!name.startsWith("JSON"))
|
|
1792
|
+
return undefined;
|
|
1793
|
+
const binary = name.startsWith("JSONB");
|
|
1794
|
+
const plain = name.replace(/^JSONB/u, "JSON");
|
|
1795
|
+
const result = (shape, nullable = false) => this.#jsonResult(shape, nullable, binary);
|
|
1796
|
+
if (plain === "JSON" || plain === "JSON_PRETTY")
|
|
1797
|
+
return result(this.#jsonDocument(values[0], args[0]), values[0].nullable);
|
|
1798
|
+
if (plain === "JSON_ARRAY")
|
|
1799
|
+
return result(jsonTupleShape(values.map((value, index) => this.#jsonValue(value, args[index]))));
|
|
1800
|
+
if (plain === "JSON_OBJECT") {
|
|
1801
|
+
const properties = {};
|
|
1802
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
1803
|
+
const key = args[index];
|
|
1804
|
+
if (key.kind !== "literal")
|
|
1805
|
+
return result(unknownJsonShape);
|
|
1806
|
+
if (typeof key.value !== "string") {
|
|
1807
|
+
this.#diagnostic("TSQ202", "SQLite JSON object labels must be text", key.range);
|
|
1808
|
+
return { tsType: "unknown", nullable: true };
|
|
1809
|
+
}
|
|
1810
|
+
Object.defineProperty(properties, key.value, {
|
|
1811
|
+
value: {
|
|
1812
|
+
shape: Object.hasOwn(properties, key.value)
|
|
1813
|
+
? unionJsonShapes([properties[key.value].shape, this.#jsonValue(values[index + 1], args[index + 1])])
|
|
1814
|
+
: this.#jsonValue(values[index + 1], args[index + 1]),
|
|
1815
|
+
},
|
|
1816
|
+
enumerable: true,
|
|
1817
|
+
configurable: true,
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
return result(jsonObjectShape(properties));
|
|
1821
|
+
}
|
|
1822
|
+
if (plain === "JSON_GROUP_ARRAY")
|
|
1823
|
+
return result(jsonArrayShape(this.#jsonValue(values[0], args[0])));
|
|
1824
|
+
if (plain === "JSON_GROUP_OBJECT")
|
|
1825
|
+
return result(jsonObjectShape({}, this.#jsonValue(values[1], args[1])));
|
|
1826
|
+
if (plain === "JSON_QUOTE")
|
|
1827
|
+
return result(this.#jsonValue(values[0], args[0]));
|
|
1828
|
+
if (plain === "JSON_EXTRACT") {
|
|
1829
|
+
const shape = this.#jsonDocument(values[0], args[0]);
|
|
1830
|
+
const paths = args
|
|
1831
|
+
.slice(1)
|
|
1832
|
+
.map((argument) => argument.kind === "literal" && typeof argument.value === "string"
|
|
1833
|
+
? this.#jsonPath(argument.value)
|
|
1834
|
+
: undefined);
|
|
1835
|
+
if (paths.length === 1)
|
|
1836
|
+
return this.#jsonExtract(shape, paths[0], false, binary, values[0].nullable || values[1].nullable);
|
|
1837
|
+
const selected = paths.map((path) => path === undefined
|
|
1838
|
+
? unknownJsonShape
|
|
1839
|
+
: (() => {
|
|
1840
|
+
const item = jsonShapeAtPath(shape, path);
|
|
1841
|
+
return item.presence === "never"
|
|
1842
|
+
? nullJsonShape
|
|
1843
|
+
: item.presence === "maybe"
|
|
1844
|
+
? unionJsonShapes([item.shape, nullJsonShape])
|
|
1845
|
+
: item.shape;
|
|
1846
|
+
})());
|
|
1847
|
+
return result(jsonTupleShape(selected), values.some((value) => value.nullable));
|
|
1848
|
+
}
|
|
1849
|
+
if (["JSON_SET", "JSON_INSERT", "JSON_REPLACE", "JSON_ARRAY_INSERT", "JSON_REMOVE"].includes(plain)) {
|
|
1850
|
+
let shape = this.#jsonDocument(values[0], args[0]);
|
|
1851
|
+
for (let index = 1; index < args.length; index += plain === "JSON_REMOVE" ? 1 : 2) {
|
|
1852
|
+
const argument = args[index];
|
|
1853
|
+
const path = argument.kind === "literal" && typeof argument.value === "string"
|
|
1854
|
+
? this.#jsonPath(argument.value)
|
|
1855
|
+
: undefined;
|
|
1856
|
+
if (path === undefined)
|
|
1857
|
+
return result(unknownJsonShape, true);
|
|
1858
|
+
if (plain === "JSON_REMOVE" && path.length === 0)
|
|
1859
|
+
return { tsType: "never", nullable: true, scalarJsonShape: nullJsonShape };
|
|
1860
|
+
shape = this.#jsonUpdate(shape, path, plain === "JSON_REMOVE" ? undefined : this.#jsonValue(values[index + 1], args[index + 1]), plain === "JSON_INSERT" ? "insert" : plain === "JSON_REPLACE" ? "replace" : "set", plain === "JSON_ARRAY_INSERT");
|
|
1861
|
+
}
|
|
1862
|
+
return result(shape, plain === "JSON_REMOVE"
|
|
1863
|
+
? values.some((value) => value.nullable)
|
|
1864
|
+
: values[0].nullable || values.some((value, index) => index % 2 === 1 && value.nullable));
|
|
1865
|
+
}
|
|
1866
|
+
if (plain === "JSON_PATCH")
|
|
1867
|
+
return result(this.#jsonPatch(this.#jsonDocument(values[0], args[0]), this.#jsonDocument(values[1], args[1])), values.some((value) => value.nullable));
|
|
1868
|
+
return undefined;
|
|
1869
|
+
}
|
|
1460
1870
|
#columnType(relation, column) {
|
|
1461
|
-
return {
|
|
1871
|
+
return { ...this.#snapshotType(column), nullable: column.nullable || relation.nullable };
|
|
1462
1872
|
}
|
|
1463
1873
|
#hasImplicitRowid(table, columnName) {
|
|
1464
1874
|
if (!["rowid", "oid", "_rowid_"].includes(columnName.toLowerCase()))
|
|
@@ -1489,6 +1899,21 @@ class Resolver {
|
|
|
1489
1899
|
#snapshotType(column) {
|
|
1490
1900
|
if (column === undefined)
|
|
1491
1901
|
return undefined;
|
|
1902
|
+
const projected = this.#projections.get(column);
|
|
1903
|
+
if (projected !== undefined)
|
|
1904
|
+
return projected;
|
|
1905
|
+
const declaration = column.jsonDeclaration;
|
|
1906
|
+
if (declaration !== undefined && (declaration.source === "database" || this.#runtimeValidation)) {
|
|
1907
|
+
if (declaration.source === "application")
|
|
1908
|
+
this.#requiresJsonValidation = true;
|
|
1909
|
+
const binary = column.databaseType.toLowerCase() === "blob";
|
|
1910
|
+
const result = this.#jsonResult(declaration.shape, column.nullable, binary);
|
|
1911
|
+
const { jsonSubtype: _subtype, ...stored } = result;
|
|
1912
|
+
return {
|
|
1913
|
+
...stored,
|
|
1914
|
+
...(declaration.source === "application" ? { json: { ...result.json, requiresRuntimeValidation: true } } : {}),
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1492
1917
|
return { tsType: column.tsType, nullable: column.nullable, databaseType: column.databaseType };
|
|
1493
1918
|
}
|
|
1494
1919
|
#generated(column) {
|