@dbsp/core 1.4.0 → 1.6.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/dist/index.d.ts +43 -11
- package/dist/index.js +815 -141
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -645,7 +645,12 @@ function schemaToModelIR(definition, constraints, extras, options) {
|
|
|
645
645
|
options
|
|
646
646
|
);
|
|
647
647
|
validateFkTargets(tables);
|
|
648
|
-
const relations = buildRelations(
|
|
648
|
+
const relations = buildRelations(
|
|
649
|
+
definition,
|
|
650
|
+
refsByTable,
|
|
651
|
+
tableNames,
|
|
652
|
+
constraints
|
|
653
|
+
);
|
|
649
654
|
const tableMap = /* @__PURE__ */ new Map();
|
|
650
655
|
for (const table of tables) {
|
|
651
656
|
tableMap.set(table.name, table);
|
|
@@ -1057,6 +1062,7 @@ function buildTableConstraints(tableName, constraints) {
|
|
|
1057
1062
|
}
|
|
1058
1063
|
};
|
|
1059
1064
|
if (fkRef.options.onDelete) fk.onDelete = fkRef.options.onDelete;
|
|
1065
|
+
if (fkRef.options.onUpdate) fk.onUpdate = fkRef.options.onUpdate;
|
|
1060
1066
|
extraForeignKeys.push(fk);
|
|
1061
1067
|
}
|
|
1062
1068
|
}
|
|
@@ -1095,7 +1101,7 @@ function normalizeColumnDef(def) {
|
|
|
1095
1101
|
}
|
|
1096
1102
|
return def;
|
|
1097
1103
|
}
|
|
1098
|
-
function buildRelations(_definition, refsByTable, tableNames) {
|
|
1104
|
+
function buildRelations(_definition, refsByTable, tableNames, constraints) {
|
|
1099
1105
|
const relations = [];
|
|
1100
1106
|
for (const tableName of tableNames) {
|
|
1101
1107
|
const refs = refsByTable.get(tableName) || [];
|
|
@@ -1195,8 +1201,75 @@ function buildRelations(_definition, refsByTable, tableNames) {
|
|
|
1195
1201
|
}
|
|
1196
1202
|
}
|
|
1197
1203
|
}
|
|
1204
|
+
addCompositeConstraintRelations(relations, tableNames, constraints);
|
|
1198
1205
|
return relations;
|
|
1199
1206
|
}
|
|
1207
|
+
function relationKey(source, name) {
|
|
1208
|
+
return `${source}.${name}`;
|
|
1209
|
+
}
|
|
1210
|
+
function pushRelationIfAbsent(relations, relationKeys, relation) {
|
|
1211
|
+
const key = relationKey(relation.source, relation.name);
|
|
1212
|
+
if (relationKeys.has(key)) return;
|
|
1213
|
+
relationKeys.add(key);
|
|
1214
|
+
relations.push(relation);
|
|
1215
|
+
}
|
|
1216
|
+
function deriveCompositeConstraintRelationName(fkColumn, options) {
|
|
1217
|
+
return deriveLocalRelation(fkColumn, options);
|
|
1218
|
+
}
|
|
1219
|
+
function addCompositeConstraintRelations(relations, tableNames, constraints) {
|
|
1220
|
+
if (!constraints) return;
|
|
1221
|
+
const tableSet = new Set(tableNames);
|
|
1222
|
+
const relationKeys = new Set(
|
|
1223
|
+
relations.map((relation) => relationKey(relation.source, relation.name))
|
|
1224
|
+
);
|
|
1225
|
+
for (const tableName of tableNames) {
|
|
1226
|
+
const foreignKeys = constraints[tableName]?.foreignKeys;
|
|
1227
|
+
if (!foreignKeys) continue;
|
|
1228
|
+
for (const fkRef of foreignKeys) {
|
|
1229
|
+
if (!tableSet.has(fkRef.target)) continue;
|
|
1230
|
+
const columns = fkRef.options.columns;
|
|
1231
|
+
if (!columns?.length) continue;
|
|
1232
|
+
const references = fkRef.options.references ?? ["id"];
|
|
1233
|
+
if (columns.length === 1 && references.length === 1) continue;
|
|
1234
|
+
const foreignKey = [...columns];
|
|
1235
|
+
const referencedKey = [...references];
|
|
1236
|
+
const belongsToName = deriveCompositeConstraintRelationName(
|
|
1237
|
+
foreignKey[0],
|
|
1238
|
+
fkRef.options
|
|
1239
|
+
);
|
|
1240
|
+
const inverseName = fkRef.options.inverse ?? tableName;
|
|
1241
|
+
const isUnique = fkRef.options.unique ?? false;
|
|
1242
|
+
const inverseType = isUnique ? "hasOne" : "hasMany";
|
|
1243
|
+
const inverseCardinality = isUnique ? "one" : "many";
|
|
1244
|
+
pushRelationIfAbsent(relations, relationKeys, {
|
|
1245
|
+
name: belongsToName,
|
|
1246
|
+
type: "belongsTo",
|
|
1247
|
+
source: tableName,
|
|
1248
|
+
target: fkRef.target,
|
|
1249
|
+
foreignKey,
|
|
1250
|
+
targetKey: referencedKey,
|
|
1251
|
+
cardinality: "one",
|
|
1252
|
+
optionality: fkRef.options.nullable ? "optional" : "required",
|
|
1253
|
+
includeStrategy: "auto",
|
|
1254
|
+
filterStrategy: "auto",
|
|
1255
|
+
joinDefault: "auto"
|
|
1256
|
+
});
|
|
1257
|
+
pushRelationIfAbsent(relations, relationKeys, {
|
|
1258
|
+
name: inverseName,
|
|
1259
|
+
type: inverseType,
|
|
1260
|
+
source: fkRef.target,
|
|
1261
|
+
target: tableName,
|
|
1262
|
+
foreignKey,
|
|
1263
|
+
sourceKey: referencedKey,
|
|
1264
|
+
cardinality: inverseCardinality,
|
|
1265
|
+
optionality: "optional",
|
|
1266
|
+
includeStrategy: "auto",
|
|
1267
|
+
filterStrategy: "auto",
|
|
1268
|
+
joinDefault: "auto"
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1200
1273
|
function columnTypeToJsType(type) {
|
|
1201
1274
|
switch (type) {
|
|
1202
1275
|
// Numeric types → 'number' (includes integer, bigint, decimal)
|
|
@@ -1591,6 +1664,9 @@ function isManyToMany(rel) {
|
|
|
1591
1664
|
return rel.kind === "manyToMany";
|
|
1592
1665
|
}
|
|
1593
1666
|
|
|
1667
|
+
// src/planner.ts
|
|
1668
|
+
import { resolveJsonAggOrderKey, toColumnList } from "@dbsp/types";
|
|
1669
|
+
|
|
1594
1670
|
// src/dx/errors.ts
|
|
1595
1671
|
var ExecutionError = class _ExecutionError extends Error {
|
|
1596
1672
|
name = "ExecutionError";
|
|
@@ -2325,8 +2401,9 @@ function generateBidirectionalReasoning(storageHint, strategy) {
|
|
|
2325
2401
|
function isSubquerySelectedColumnNonNullable(existsIntent, sourceTable, model) {
|
|
2326
2402
|
const rel = model.getRelation(`${sourceTable}.${existsIntent.relation}`);
|
|
2327
2403
|
if (!rel) return false;
|
|
2328
|
-
const
|
|
2329
|
-
if (
|
|
2404
|
+
const fkColumns = toColumnList(rel.foreignKey);
|
|
2405
|
+
if (fkColumns.length !== 1) return false;
|
|
2406
|
+
const fk = fkColumns[0];
|
|
2330
2407
|
const targetTableIR = model.getTable(rel.target);
|
|
2331
2408
|
if (!targetTableIR) return false;
|
|
2332
2409
|
const column = targetTableIR.columns.find((c) => c.name === fk);
|
|
@@ -2343,18 +2420,22 @@ function optimizeInToExists(where, sourceTable, model, negated = false) {
|
|
|
2343
2420
|
return where;
|
|
2344
2421
|
}
|
|
2345
2422
|
const subSelect = inWhere.subquery.select;
|
|
2346
|
-
if (
|
|
2423
|
+
if (subSelect?.type !== "fields") return where;
|
|
2347
2424
|
const fields = "fields" in subSelect ? subSelect.fields : void 0;
|
|
2348
|
-
if (
|
|
2425
|
+
if (fields?.length !== 1) return where;
|
|
2349
2426
|
const subColumn = fields[0];
|
|
2350
2427
|
if (!subColumn) return where;
|
|
2351
2428
|
const relationsFrom = model.getRelationsFrom(sourceTable);
|
|
2352
2429
|
const sourceTableIR = model.getTable(sourceTable);
|
|
2353
|
-
const
|
|
2430
|
+
const sourcePkColumns = toColumnList(sourceTableIR?.primaryKey);
|
|
2431
|
+
if (sourcePkColumns.length > 1) return where;
|
|
2432
|
+
const sourcePk = sourcePkColumns[0] ?? "id";
|
|
2354
2433
|
let matchedRelation;
|
|
2355
2434
|
for (const rel of relationsFrom) {
|
|
2356
2435
|
if (rel.target !== inWhere.subquery.from) continue;
|
|
2357
|
-
const
|
|
2436
|
+
const fkColumns = toColumnList(rel.foreignKey);
|
|
2437
|
+
if (fkColumns.length !== 1) continue;
|
|
2438
|
+
const fk = fkColumns[0];
|
|
2358
2439
|
if (rel.type === "hasMany" && fk === subColumn && inWhere.field === sourcePk) {
|
|
2359
2440
|
matchedRelation = rel.name;
|
|
2360
2441
|
break;
|
|
@@ -2656,6 +2737,9 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
|
|
|
2656
2737
|
const cascadedJoinType = ancestorIsLeftJoin && include.join === void 0 ? "left" : autoJoinType;
|
|
2657
2738
|
const explicitJoinType = includeStrategy === "join" ? include.join ?? cascadedJoinType : void 0;
|
|
2658
2739
|
const includeDecisionId = generateDecisionId(state, "include-strategy");
|
|
2740
|
+
const parentKey = relation.type === "belongsTo" ? relation.targetKey : relation.sourceKey;
|
|
2741
|
+
const targetTable = model.getTable(relation.target);
|
|
2742
|
+
const targetOrder = targetTable ? resolveJsonAggOrderKey(targetTable) : void 0;
|
|
2659
2743
|
state.decisions.push({
|
|
2660
2744
|
id: includeDecisionId,
|
|
2661
2745
|
type: "include-strategy",
|
|
@@ -2669,7 +2753,12 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
|
|
|
2669
2753
|
// Foreign key info for json_agg compilation (Phase 3)
|
|
2670
2754
|
...relation.foreignKey !== void 0 && {
|
|
2671
2755
|
foreignKey: relation.foreignKey
|
|
2672
|
-
}
|
|
2756
|
+
},
|
|
2757
|
+
...parentKey !== void 0 && { parentKey },
|
|
2758
|
+
...targetOrder && targetOrder.columns.length > 0 && {
|
|
2759
|
+
targetOrderKey: targetOrder.columns
|
|
2760
|
+
},
|
|
2761
|
+
...targetOrder?.fallback && { orderByFallback: true }
|
|
2673
2762
|
},
|
|
2674
2763
|
choice: includeStrategy,
|
|
2675
2764
|
// Embed joinType so the adapter's join handler can use it directly
|
|
@@ -6163,10 +6252,98 @@ import {
|
|
|
6163
6252
|
NqlLexer,
|
|
6164
6253
|
compile as nqlCompile
|
|
6165
6254
|
} from "@dbsp/nql";
|
|
6255
|
+
import { resolveJsonAggOrderKey as resolveJsonAggOrderKey2 } from "@dbsp/types";
|
|
6166
6256
|
import {
|
|
6257
|
+
explainUnsupportedNqlBindingIncludeHop,
|
|
6167
6258
|
getTrustedNqlRelationFilterFields,
|
|
6168
6259
|
NQL_INTERNAL_COMPILER_OPTIONS
|
|
6169
6260
|
} from "@dbsp/types/internal";
|
|
6261
|
+
|
|
6262
|
+
// src/dx/hydration-utils.ts
|
|
6263
|
+
function isRootIncludeDecision(decision) {
|
|
6264
|
+
const intentPath = decision.context.intentPath;
|
|
6265
|
+
return typeof intentPath !== "string" || !intentPath.includes(".include[");
|
|
6266
|
+
}
|
|
6267
|
+
function hydrateJsonAggIncludes(results, planReport) {
|
|
6268
|
+
const jsonAggDecisions = planReport.decisions.filter(
|
|
6269
|
+
(d) => d.type === "include-strategy" && d.choice === "json_agg" && isRootIncludeDecision(d)
|
|
6270
|
+
);
|
|
6271
|
+
if (jsonAggDecisions.length === 0) {
|
|
6272
|
+
return;
|
|
6273
|
+
}
|
|
6274
|
+
const relationInfo = /* @__PURE__ */ new Map();
|
|
6275
|
+
for (const decision of jsonAggDecisions) {
|
|
6276
|
+
const canonicalName = decision.context?.relation;
|
|
6277
|
+
const includeAlias = decision.context?.includeAlias;
|
|
6278
|
+
const relationType = decision.context?.relationType;
|
|
6279
|
+
const isToOne = relationType === "belongsTo" || relationType === "hasOne";
|
|
6280
|
+
if (typeof canonicalName === "string") {
|
|
6281
|
+
relationInfo.set(canonicalName, {
|
|
6282
|
+
isToOne,
|
|
6283
|
+
includeAlias: typeof includeAlias === "string" ? includeAlias : canonicalName,
|
|
6284
|
+
canonicalName
|
|
6285
|
+
});
|
|
6286
|
+
} else if (typeof includeAlias === "string") {
|
|
6287
|
+
relationInfo.set(includeAlias, { isToOne, includeAlias });
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
if (relationInfo.size === 0) {
|
|
6291
|
+
return;
|
|
6292
|
+
}
|
|
6293
|
+
for (const row of results) {
|
|
6294
|
+
if (typeof row !== "object" || row === null) {
|
|
6295
|
+
continue;
|
|
6296
|
+
}
|
|
6297
|
+
const record2 = row;
|
|
6298
|
+
for (const [relationName, info] of relationInfo) {
|
|
6299
|
+
const candidates = [relationName];
|
|
6300
|
+
if (info.includeAlias && info.includeAlias !== relationName) {
|
|
6301
|
+
candidates.push(info.includeAlias);
|
|
6302
|
+
}
|
|
6303
|
+
let actualColumnName = null;
|
|
6304
|
+
for (const baseName of candidates) {
|
|
6305
|
+
const snakeJson = `${baseName}_json`;
|
|
6306
|
+
const camelJson = snakeJson.replace(
|
|
6307
|
+
/_([a-z])/g,
|
|
6308
|
+
(_, c) => c.toUpperCase()
|
|
6309
|
+
);
|
|
6310
|
+
if (snakeJson in record2) {
|
|
6311
|
+
actualColumnName = snakeJson;
|
|
6312
|
+
break;
|
|
6313
|
+
}
|
|
6314
|
+
if (camelJson in record2) {
|
|
6315
|
+
actualColumnName = camelJson;
|
|
6316
|
+
break;
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
if (actualColumnName) {
|
|
6320
|
+
const jsonValue = record2[actualColumnName];
|
|
6321
|
+
let parsed;
|
|
6322
|
+
if (typeof jsonValue === "string") {
|
|
6323
|
+
try {
|
|
6324
|
+
parsed = JSON.parse(jsonValue);
|
|
6325
|
+
} catch {
|
|
6326
|
+
parsed = info.isToOne ? null : [];
|
|
6327
|
+
}
|
|
6328
|
+
} else if (Array.isArray(jsonValue)) {
|
|
6329
|
+
parsed = jsonValue;
|
|
6330
|
+
} else if (jsonValue === null || jsonValue === void 0) {
|
|
6331
|
+
parsed = info.isToOne ? null : [];
|
|
6332
|
+
} else {
|
|
6333
|
+
parsed = jsonValue;
|
|
6334
|
+
}
|
|
6335
|
+
if (info.isToOne && Array.isArray(parsed)) {
|
|
6336
|
+
parsed = parsed.length > 0 ? parsed[0] : null;
|
|
6337
|
+
}
|
|
6338
|
+
const outputKey = info.includeAlias ?? relationName;
|
|
6339
|
+
record2[outputKey] = parsed;
|
|
6340
|
+
delete record2[actualColumnName];
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
|
|
6346
|
+
// src/dx/nql.ts
|
|
6170
6347
|
var NQL_RAW_FRAGMENT = /* @__PURE__ */ Symbol("NqlRawFragment");
|
|
6171
6348
|
function hasNqlBindings(bundle) {
|
|
6172
6349
|
return (bundle.bindings?.size ?? 0) > 0;
|
|
@@ -6203,28 +6380,348 @@ function findBindingFinalRelationFilter(where) {
|
|
|
6203
6380
|
return void 0;
|
|
6204
6381
|
}
|
|
6205
6382
|
}
|
|
6206
|
-
function
|
|
6383
|
+
function bindingFinalIncludeError(bindingName, relation, reason) {
|
|
6384
|
+
return new Error(
|
|
6385
|
+
`NQL binding-final query '${bindingName}' cannot use relation include '${relation}' (ref-#192): ${reason}.`
|
|
6386
|
+
);
|
|
6387
|
+
}
|
|
6388
|
+
function getBindingRelationMetadata(bundle, bindingName) {
|
|
6389
|
+
return bundle.bindingOutputSchemas?.get(bindingName)?.relationFilters;
|
|
6390
|
+
}
|
|
6391
|
+
function getScalarBindingRelationsByName(bundle, bindingName) {
|
|
6392
|
+
const metadata = getBindingRelationMetadata(bundle, bindingName);
|
|
6393
|
+
const relations = /* @__PURE__ */ new Map();
|
|
6394
|
+
for (const relation of metadata?.scalarRelations ?? []) {
|
|
6395
|
+
relations.set(relation.relation, relation);
|
|
6396
|
+
}
|
|
6397
|
+
return relations;
|
|
6398
|
+
}
|
|
6399
|
+
function bindingIncludeNodeShape(include) {
|
|
6400
|
+
return include;
|
|
6401
|
+
}
|
|
6402
|
+
function virtualRelationIncludeShape(relation) {
|
|
6403
|
+
const relationType = relation.relationType;
|
|
6404
|
+
const foreignKey = relationType === "belongsTo" ? relation.sourceColumn : relationType === "hasOne" || relationType === "hasMany" ? relation.targetColumn : void 0;
|
|
6405
|
+
return {
|
|
6406
|
+
type: relationType,
|
|
6407
|
+
foreignKey,
|
|
6408
|
+
source: relation.sourceTable,
|
|
6409
|
+
target: relation.targetTable,
|
|
6410
|
+
...relation.through !== void 0 && { through: relation.through },
|
|
6411
|
+
...relation.throughTargetColumn !== void 0 && {
|
|
6412
|
+
otherKey: relation.throughTargetColumn
|
|
6413
|
+
}
|
|
6414
|
+
};
|
|
6415
|
+
}
|
|
6416
|
+
var BINDING_INCLUDE_NODE_ONLY_RELATION = {
|
|
6417
|
+
type: "hasMany",
|
|
6418
|
+
foreignKey: "__dbsp_binding_include_node__",
|
|
6419
|
+
source: "__dbsp_binding_include_source__",
|
|
6420
|
+
target: "__dbsp_binding_include_target__"
|
|
6421
|
+
};
|
|
6422
|
+
function assertSupportedBindingIncludeNodeShape(bindingName, rootIncludeRelation, include, reasonPrefix = "") {
|
|
6423
|
+
const unsupportedReason = explainUnsupportedNqlBindingIncludeHop(
|
|
6424
|
+
include.relation,
|
|
6425
|
+
BINDING_INCLUDE_NODE_ONLY_RELATION,
|
|
6426
|
+
bindingIncludeNodeShape(include)
|
|
6427
|
+
);
|
|
6428
|
+
if (unsupportedReason) {
|
|
6429
|
+
throw bindingFinalIncludeError(
|
|
6430
|
+
bindingName,
|
|
6431
|
+
rootIncludeRelation,
|
|
6432
|
+
`${reasonPrefix}${unsupportedReason}`
|
|
6433
|
+
);
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
function bindingIncludeCoversRelationPath(include, segments) {
|
|
6437
|
+
const [head, ...tail] = segments;
|
|
6438
|
+
if (!head || include.relation !== head) return false;
|
|
6439
|
+
if (tail.length === 0) return true;
|
|
6440
|
+
return (include.include ?? []).some(
|
|
6441
|
+
(child) => bindingIncludeCoversRelationPath(child, tail)
|
|
6442
|
+
);
|
|
6443
|
+
}
|
|
6444
|
+
function bindingIncludesCoverRelationPath(includes, relationPath) {
|
|
6445
|
+
const segments = relationPath.split(".");
|
|
6446
|
+
if (segments.some((segment) => segment.length === 0)) return false;
|
|
6447
|
+
return (includes ?? []).some(
|
|
6448
|
+
(include) => bindingIncludeCoversRelationPath(include, segments)
|
|
6449
|
+
);
|
|
6450
|
+
}
|
|
6451
|
+
function trustedBindingRelationColumnIsAdmitted(column) {
|
|
6452
|
+
if (column.kind !== "relationColumn") return false;
|
|
6453
|
+
const trusted = getTrustedNqlRelationFilterFields(column);
|
|
6454
|
+
if (trusted?.selectedColumn === void 0) return false;
|
|
6455
|
+
const isDotted = column.relation.split(".").length > 1;
|
|
6456
|
+
if (trusted.cardinality === "one") {
|
|
6457
|
+
return !isDotted || trusted.hops.length > 0;
|
|
6458
|
+
}
|
|
6459
|
+
const hasCompleteManyToManyProof = (trusted.relationType === "manyToMany" || trusted.relationType === "belongsToMany") && trusted.through !== void 0 && trusted.throughSourceColumn !== void 0 && trusted.throughTargetColumn !== void 0;
|
|
6460
|
+
return trusted.cardinality === "many" && (trusted.relationType === "hasMany" || hasCompleteManyToManyProof) && !isDotted && trusted.hops.length === 0;
|
|
6461
|
+
}
|
|
6462
|
+
function assertProvenBindingInclude(bindingName, include, provenRelations) {
|
|
6463
|
+
const proven = provenRelations.get(include.relation);
|
|
6464
|
+
if (!proven) {
|
|
6465
|
+
throw bindingFinalIncludeError(
|
|
6466
|
+
bindingName,
|
|
6467
|
+
include.relation,
|
|
6468
|
+
"the relation is not in the binding proven virtual-relation set"
|
|
6469
|
+
);
|
|
6470
|
+
}
|
|
6471
|
+
const unsupportedReason = explainUnsupportedNqlBindingIncludeHop(
|
|
6472
|
+
include.relation,
|
|
6473
|
+
virtualRelationIncludeShape(proven),
|
|
6474
|
+
bindingIncludeNodeShape(include)
|
|
6475
|
+
);
|
|
6476
|
+
if (unsupportedReason) {
|
|
6477
|
+
throw bindingFinalIncludeError(
|
|
6478
|
+
bindingName,
|
|
6479
|
+
include.relation,
|
|
6480
|
+
unsupportedReason
|
|
6481
|
+
);
|
|
6482
|
+
}
|
|
6483
|
+
return proven;
|
|
6484
|
+
}
|
|
6485
|
+
function getProvenBindingIncludes(intent, bundle) {
|
|
6486
|
+
const provenRelations = getScalarBindingRelationsByName(bundle, intent.from);
|
|
6487
|
+
const provenIncludes = /* @__PURE__ */ new Map();
|
|
6488
|
+
for (const include of intent.include ?? []) {
|
|
6489
|
+
const proven = assertProvenBindingInclude(
|
|
6490
|
+
intent.from,
|
|
6491
|
+
include,
|
|
6492
|
+
provenRelations
|
|
6493
|
+
);
|
|
6494
|
+
provenIncludes.set(include.relation, proven);
|
|
6495
|
+
}
|
|
6496
|
+
return provenIncludes;
|
|
6497
|
+
}
|
|
6498
|
+
function assertBindingFinalQueryCanUseSyntheticPlan(intent, bundle) {
|
|
6499
|
+
getProvenBindingIncludes(intent, bundle);
|
|
6207
6500
|
const relationColumns = intent.select?.type === "expressions" ? intent.select.columns.flatMap((column) => {
|
|
6208
6501
|
if (column.kind !== "relationColumn") return [];
|
|
6209
|
-
|
|
6210
|
-
|
|
6502
|
+
if (column.column === "*" && bindingIncludesCoverRelationPath(intent.include, column.relation)) {
|
|
6503
|
+
return [];
|
|
6504
|
+
}
|
|
6505
|
+
if (trustedBindingRelationColumnIsAdmitted(column)) {
|
|
6211
6506
|
return [];
|
|
6212
6507
|
}
|
|
6213
6508
|
return [column.relation];
|
|
6214
6509
|
}) : [];
|
|
6215
6510
|
const relationFilter = intent.where ? findBindingFinalRelationFilter(intent.where) : void 0;
|
|
6216
6511
|
const havingRelationFilter = intent.having ? findBindingFinalRelationFilter(intent.having) : void 0;
|
|
6217
|
-
if (
|
|
6512
|
+
if (relationColumns.length > 0 || (intent.joins?.length ?? 0) > 0 || relationFilter !== void 0 || havingRelationFilter !== void 0) {
|
|
6218
6513
|
throw new Error(
|
|
6219
6514
|
`NQL binding-final query '${intent.from}' cannot select relation columns, use includes, joins, or relation filters. Relation planning requires a physical model table, not a CTE binding.`
|
|
6220
6515
|
);
|
|
6221
6516
|
}
|
|
6222
6517
|
}
|
|
6223
|
-
function
|
|
6224
|
-
|
|
6518
|
+
function createBindingIncludeDecision(intent, include, relation, index, model) {
|
|
6519
|
+
const relationType = relation.relationType;
|
|
6520
|
+
const foreignKey = relationType === "belongsTo" ? relation.sourceColumn : relation.targetColumn;
|
|
6521
|
+
const parentKey = relationType === "belongsTo" ? relation.targetColumn : relation.sourceColumn;
|
|
6522
|
+
const targetTable = model.getTable(relation.targetTable);
|
|
6523
|
+
const targetOrder = targetTable ? resolveJsonAggOrderKey2(targetTable) : void 0;
|
|
6524
|
+
return {
|
|
6525
|
+
id: `binding-include-${index}`,
|
|
6526
|
+
type: "include-strategy",
|
|
6527
|
+
context: {
|
|
6528
|
+
sourceTable: intent.from,
|
|
6529
|
+
target: relation.targetTable,
|
|
6530
|
+
relation: relation.relation,
|
|
6531
|
+
relationType,
|
|
6532
|
+
foreignKey,
|
|
6533
|
+
parentKey,
|
|
6534
|
+
...targetOrder && targetOrder.columns.length > 0 && {
|
|
6535
|
+
targetOrderKey: targetOrder.columns
|
|
6536
|
+
},
|
|
6537
|
+
...targetOrder?.fallback && { orderByFallback: true },
|
|
6538
|
+
includeAlias: include.relation,
|
|
6539
|
+
intentPath: `include[${index}]`
|
|
6540
|
+
},
|
|
6541
|
+
choice: "json_agg",
|
|
6542
|
+
reasoning: "NQL binding-final include uses proven binding virtual relation metadata and the json_agg include pipeline.",
|
|
6543
|
+
alternatives: []
|
|
6544
|
+
};
|
|
6545
|
+
}
|
|
6546
|
+
function findModelRelation(model, sourceTable, relationName) {
|
|
6547
|
+
return model.getRelation(`${sourceTable}.${relationName}`) ?? model.getRelationsFrom(sourceTable).find((relation) => relation.name === relationName);
|
|
6548
|
+
}
|
|
6549
|
+
function parentKeyForRelation(relation) {
|
|
6550
|
+
if (relation.type === "belongsTo") return relation.targetKey;
|
|
6551
|
+
return relation.sourceKey;
|
|
6552
|
+
}
|
|
6553
|
+
function assertSupportedBindingIncludeHop(bindingName, rootIncludeRelation, relationName, relation, include, reasonPrefix = "") {
|
|
6554
|
+
const unsupportedReason = explainUnsupportedNqlBindingIncludeHop(
|
|
6555
|
+
relationName,
|
|
6556
|
+
relation,
|
|
6557
|
+
bindingIncludeNodeShape(include)
|
|
6558
|
+
);
|
|
6559
|
+
if (unsupportedReason) {
|
|
6560
|
+
throw bindingFinalIncludeError(
|
|
6561
|
+
bindingName,
|
|
6562
|
+
rootIncludeRelation,
|
|
6563
|
+
`${reasonPrefix}${unsupportedReason}`
|
|
6564
|
+
);
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
function assertSupportedBindingTailIncludeTree(bindingName, rootInclude, sourceTable, includes, model) {
|
|
6568
|
+
for (const include of includes) {
|
|
6569
|
+
assertSupportedBindingIncludeNodeShape(
|
|
6570
|
+
bindingName,
|
|
6571
|
+
rootInclude.relation,
|
|
6572
|
+
include,
|
|
6573
|
+
"tail "
|
|
6574
|
+
);
|
|
6575
|
+
const relation = findModelRelation(model, sourceTable, include.relation);
|
|
6576
|
+
if (!relation) {
|
|
6577
|
+
throw bindingFinalIncludeError(
|
|
6578
|
+
bindingName,
|
|
6579
|
+
rootInclude.relation,
|
|
6580
|
+
`tail relation '${include.relation}' is not declared on table '${sourceTable}'`
|
|
6581
|
+
);
|
|
6582
|
+
}
|
|
6583
|
+
assertSupportedBindingIncludeHop(
|
|
6584
|
+
bindingName,
|
|
6585
|
+
rootInclude.relation,
|
|
6586
|
+
include.relation,
|
|
6587
|
+
relation,
|
|
6588
|
+
include,
|
|
6589
|
+
"tail "
|
|
6590
|
+
);
|
|
6591
|
+
assertSupportedBindingTailIncludeTree(
|
|
6592
|
+
bindingName,
|
|
6593
|
+
rootInclude,
|
|
6594
|
+
relation.target,
|
|
6595
|
+
include.include ?? [],
|
|
6596
|
+
model
|
|
6597
|
+
);
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
function rebaseBindingTailIntentPath(intentPath, includeIndex) {
|
|
6601
|
+
return `include[${includeIndex}]${intentPath ? `.${intentPath}` : ""}`;
|
|
6602
|
+
}
|
|
6603
|
+
function createBindingTailIncludeDecisions(bindingName, include, firstHopRelation, includeIndex, model) {
|
|
6604
|
+
const nestedIncludes = include.include ?? [];
|
|
6605
|
+
if (nestedIncludes.length === 0) return [];
|
|
6606
|
+
assertSupportedBindingTailIncludeTree(
|
|
6607
|
+
bindingName,
|
|
6608
|
+
include,
|
|
6609
|
+
firstHopRelation.targetTable,
|
|
6610
|
+
nestedIncludes,
|
|
6611
|
+
model
|
|
6612
|
+
);
|
|
6613
|
+
const tailPlan = plan(
|
|
6614
|
+
{
|
|
6615
|
+
type: "select",
|
|
6616
|
+
from: firstHopRelation.targetTable,
|
|
6617
|
+
include: nestedIncludes
|
|
6618
|
+
},
|
|
6619
|
+
model,
|
|
6620
|
+
{ defaultIncludeStrategy: "json_agg" }
|
|
6621
|
+
);
|
|
6622
|
+
return tailPlan.decisions.filter((decision) => decision.type === "include-strategy").map((decision, tailIndex) => {
|
|
6623
|
+
const sourceTable = decision.context.sourceTable;
|
|
6624
|
+
const relationName = decision.context.relation;
|
|
6625
|
+
if (!relationName) {
|
|
6626
|
+
throw bindingFinalIncludeError(
|
|
6627
|
+
bindingName,
|
|
6628
|
+
include.relation,
|
|
6629
|
+
"tail include planning produced a decision without a relation name"
|
|
6630
|
+
);
|
|
6631
|
+
}
|
|
6632
|
+
const relation = findModelRelation(model, sourceTable, relationName);
|
|
6633
|
+
if (!relation) {
|
|
6634
|
+
throw bindingFinalIncludeError(
|
|
6635
|
+
bindingName,
|
|
6636
|
+
include.relation,
|
|
6637
|
+
`tail relation '${relationName}' is not declared on table '${sourceTable}'`
|
|
6638
|
+
);
|
|
6639
|
+
}
|
|
6640
|
+
assertSupportedBindingIncludeHop(
|
|
6641
|
+
bindingName,
|
|
6642
|
+
include.relation,
|
|
6643
|
+
relationName,
|
|
6644
|
+
relation,
|
|
6645
|
+
{ relation: relationName },
|
|
6646
|
+
"tail "
|
|
6647
|
+
);
|
|
6648
|
+
const baseContext = { ...decision.context };
|
|
6649
|
+
delete baseContext.foreignKey;
|
|
6650
|
+
delete baseContext.parentKey;
|
|
6651
|
+
delete baseContext.targetOrderKey;
|
|
6652
|
+
delete baseContext.orderByFallback;
|
|
6653
|
+
const parentKey = parentKeyForRelation(relation);
|
|
6654
|
+
const targetTable = model.getTable(relation.target);
|
|
6655
|
+
const targetOrder = targetTable ? resolveJsonAggOrderKey2(targetTable) : void 0;
|
|
6656
|
+
return {
|
|
6657
|
+
...decision,
|
|
6658
|
+
id: `binding-include-${includeIndex}-tail-${tailIndex}`,
|
|
6659
|
+
choice: "json_agg",
|
|
6660
|
+
reasoning: "NQL binding-final tail include uses real-table relation metadata and is forced through the json_agg include pipeline.",
|
|
6661
|
+
context: {
|
|
6662
|
+
...baseContext,
|
|
6663
|
+
sourceTable: relation.source,
|
|
6664
|
+
target: relation.target,
|
|
6665
|
+
relation: relation.name,
|
|
6666
|
+
relationType: relation.type,
|
|
6667
|
+
...relation.foreignKey !== void 0 && {
|
|
6668
|
+
foreignKey: relation.foreignKey
|
|
6669
|
+
},
|
|
6670
|
+
// Leave parentKey absent when RelationIR does not specify it so the
|
|
6671
|
+
// adapter applies the same defaultPkColumnName fallback as real-table includes.
|
|
6672
|
+
...parentKey !== void 0 && { parentKey },
|
|
6673
|
+
...targetOrder && targetOrder.columns.length > 0 && {
|
|
6674
|
+
targetOrderKey: targetOrder.columns
|
|
6675
|
+
},
|
|
6676
|
+
...targetOrder?.fallback && { orderByFallback: true },
|
|
6677
|
+
intentPath: rebaseBindingTailIntentPath(
|
|
6678
|
+
decision.context.intentPath,
|
|
6679
|
+
includeIndex
|
|
6680
|
+
)
|
|
6681
|
+
}
|
|
6682
|
+
};
|
|
6683
|
+
});
|
|
6684
|
+
}
|
|
6685
|
+
function createBindingFinalPlan(intent, bundle, model, dialectCapabilities) {
|
|
6686
|
+
assertBindingFinalQueryCanUseSyntheticPlan(intent, bundle);
|
|
6687
|
+
const provenIncludes = getProvenBindingIncludes(intent, bundle);
|
|
6688
|
+
const decisions = (intent.include ?? []).flatMap((include, index) => {
|
|
6689
|
+
if (dialectCapabilities?.supportsJsonAgg === false) {
|
|
6690
|
+
throw bindingFinalIncludeError(
|
|
6691
|
+
intent.from,
|
|
6692
|
+
include.relation,
|
|
6693
|
+
"JSON aggregation for binding includes is not supported by this adapter"
|
|
6694
|
+
);
|
|
6695
|
+
}
|
|
6696
|
+
const proven = provenIncludes.get(include.relation);
|
|
6697
|
+
if (!proven) {
|
|
6698
|
+
throw bindingFinalIncludeError(
|
|
6699
|
+
intent.from,
|
|
6700
|
+
include.relation,
|
|
6701
|
+
"include relation proof was not emitted by the binding compiler"
|
|
6702
|
+
);
|
|
6703
|
+
}
|
|
6704
|
+
const firstHopDecision = createBindingIncludeDecision(
|
|
6705
|
+
intent,
|
|
6706
|
+
include,
|
|
6707
|
+
proven,
|
|
6708
|
+
index,
|
|
6709
|
+
model
|
|
6710
|
+
);
|
|
6711
|
+
return [
|
|
6712
|
+
firstHopDecision,
|
|
6713
|
+
...createBindingTailIncludeDecisions(
|
|
6714
|
+
intent.from,
|
|
6715
|
+
include,
|
|
6716
|
+
proven,
|
|
6717
|
+
index,
|
|
6718
|
+
model
|
|
6719
|
+
)
|
|
6720
|
+
];
|
|
6721
|
+
});
|
|
6225
6722
|
return {
|
|
6226
6723
|
rootTable: intent.from,
|
|
6227
|
-
decisions
|
|
6724
|
+
decisions,
|
|
6228
6725
|
warnings: [],
|
|
6229
6726
|
ctes: [],
|
|
6230
6727
|
intent,
|
|
@@ -6235,6 +6732,11 @@ function createBindingFinalPlan(intent) {
|
|
|
6235
6732
|
}
|
|
6236
6733
|
};
|
|
6237
6734
|
}
|
|
6735
|
+
function bindingFinalPlanHasJsonAggIncludes(planReport) {
|
|
6736
|
+
return planReport.decisions.some(
|
|
6737
|
+
(decision) => decision.type === "include-strategy" && decision.choice === "json_agg"
|
|
6738
|
+
);
|
|
6739
|
+
}
|
|
6238
6740
|
function nqlRaw(fragment) {
|
|
6239
6741
|
if (typeof fragment !== "string") {
|
|
6240
6742
|
throw new TypeError("nqlRaw() expects a string fragment");
|
|
@@ -6349,6 +6851,14 @@ function assembleNqlTemplate(strings, values) {
|
|
|
6349
6851
|
function hasMutationBindingBodies(bundle) {
|
|
6350
6852
|
return (bundle.mutationBindings?.size ?? 0) > 0;
|
|
6351
6853
|
}
|
|
6854
|
+
function hasSnapshotReadBindingSteps(bundle) {
|
|
6855
|
+
return bundle.nqlProgramSequence?.some(
|
|
6856
|
+
(step) => step.kind === "query" && step.snapshot === true
|
|
6857
|
+
) ?? false;
|
|
6858
|
+
}
|
|
6859
|
+
function hasExecutableNqlProgramSequence(bundle) {
|
|
6860
|
+
return hasMutationBindingBodies(bundle) || hasSnapshotReadBindingSteps(bundle);
|
|
6861
|
+
}
|
|
6352
6862
|
function requireMutationBindingColumns(bundle, bindName) {
|
|
6353
6863
|
const columns = bundle.bindingOutputSchemas?.get(bindName)?.columns;
|
|
6354
6864
|
if (columns === void 0 || columns.length === 0) {
|
|
@@ -6412,6 +6922,7 @@ function orderedSequenceStepToProgramStep(step) {
|
|
|
6412
6922
|
intent: step.query,
|
|
6413
6923
|
...step.bindName !== void 0 && { bindName: step.bindName },
|
|
6414
6924
|
final: step.final,
|
|
6925
|
+
...step.snapshot === true && { snapshot: true },
|
|
6415
6926
|
...dependencyStep.bindingDependencies !== void 0 && {
|
|
6416
6927
|
bindingDependencies: dependencyStep.bindingDependencies
|
|
6417
6928
|
}
|
|
@@ -6427,15 +6938,32 @@ function orderedSequenceStepToProgramStep(step) {
|
|
|
6427
6938
|
}
|
|
6428
6939
|
};
|
|
6429
6940
|
}
|
|
6941
|
+
var structuredCloneFn = globalThis.structuredClone;
|
|
6942
|
+
function hasCustomSnapshotPrototype(value) {
|
|
6943
|
+
if (value === null || typeof value !== "object") return false;
|
|
6944
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6945
|
+
return prototype !== Object.prototype && prototype !== null && prototype !== Array.prototype && prototype !== Date.prototype && prototype !== Map.prototype && prototype !== Set.prototype;
|
|
6946
|
+
}
|
|
6947
|
+
function cloneMutationSnapshotColumnValue(value) {
|
|
6948
|
+
if (hasCustomSnapshotPrototype(value) || structuredCloneFn === void 0) {
|
|
6949
|
+
return value;
|
|
6950
|
+
}
|
|
6951
|
+
try {
|
|
6952
|
+
return structuredCloneFn(value);
|
|
6953
|
+
} catch {
|
|
6954
|
+
return value;
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6430
6957
|
function snapshotMutationRows(rows) {
|
|
6431
6958
|
return rows.map((row) => {
|
|
6432
|
-
if (
|
|
6433
|
-
return
|
|
6959
|
+
if (row === null || typeof row !== "object") {
|
|
6960
|
+
return cloneMutationSnapshotColumnValue(row);
|
|
6434
6961
|
}
|
|
6435
|
-
|
|
6436
|
-
|
|
6962
|
+
const snapshot = { ...row };
|
|
6963
|
+
for (const key of Reflect.ownKeys(snapshot)) {
|
|
6964
|
+
snapshot[key] = cloneMutationSnapshotColumnValue(snapshot[key]);
|
|
6437
6965
|
}
|
|
6438
|
-
return
|
|
6966
|
+
return snapshot;
|
|
6439
6967
|
});
|
|
6440
6968
|
}
|
|
6441
6969
|
function assertNqlProgramSteps(compiledIntent, steps) {
|
|
@@ -6459,6 +6987,13 @@ function assertNqlProgramSteps(compiledIntent, steps) {
|
|
|
6459
6987
|
}
|
|
6460
6988
|
const seenBindNames = /* @__PURE__ */ new Set();
|
|
6461
6989
|
for (const step of steps) {
|
|
6990
|
+
if (step.kind === "query" && step.snapshot === true) {
|
|
6991
|
+
if (step.bindName === void 0 || step.final) {
|
|
6992
|
+
throw new Error(
|
|
6993
|
+
"NQL program sequence query snapshots must be named non-final statements."
|
|
6994
|
+
);
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6462
6997
|
if (step.bindName === void 0) continue;
|
|
6463
6998
|
if (seenBindNames.has(step.bindName)) {
|
|
6464
6999
|
throw new Error(
|
|
@@ -6564,6 +7099,18 @@ function filterOptionalMapByBindingDependencies(source, bindingDependencies) {
|
|
|
6564
7099
|
const filtered = filterMapByBindingDependencies(source, bindingDependencies);
|
|
6565
7100
|
return filtered.size > 0 ? filtered : void 0;
|
|
6566
7101
|
}
|
|
7102
|
+
function runtimeSourceBindingNames(sourceBundle) {
|
|
7103
|
+
const names = /* @__PURE__ */ new Set();
|
|
7104
|
+
for (const name of sourceBundle.mutationBindings?.keys() ?? []) {
|
|
7105
|
+
names.add(name);
|
|
7106
|
+
}
|
|
7107
|
+
for (const step of sourceBundle.nqlProgramSequence ?? []) {
|
|
7108
|
+
if (step.kind === "query" && step.snapshot === true && step.bindName !== void 0) {
|
|
7109
|
+
names.add(step.bindName);
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
7112
|
+
return names.size > 0 ? names : void 0;
|
|
7113
|
+
}
|
|
6567
7114
|
function filterMapByRuntimeBindingDependencies(source, runtimeSourceBindings, bindingDependencies) {
|
|
6568
7115
|
if (source === void 0) return /* @__PURE__ */ new Map();
|
|
6569
7116
|
if (bindingDependencies === void 0 || runtimeSourceBindings === void 0 || runtimeSourceBindings.size === 0) {
|
|
@@ -6694,11 +7241,16 @@ var NqlBuilderImpl = class {
|
|
|
6694
7241
|
"NQL mutations do not have execution plans; use dump() for SQL and parameters."
|
|
6695
7242
|
);
|
|
6696
7243
|
}
|
|
6697
|
-
return isBindingFinalQuery(compiled.bundle) ? createBindingFinalPlan(
|
|
7244
|
+
return isBindingFinalQuery(compiled.bundle) ? createBindingFinalPlan(
|
|
7245
|
+
compiled.intent,
|
|
7246
|
+
compiled.bundle,
|
|
7247
|
+
this.model,
|
|
7248
|
+
this.adapter?.dialectCapabilities
|
|
7249
|
+
) : this.planInternal();
|
|
6698
7250
|
}
|
|
6699
7251
|
dump(meta) {
|
|
6700
7252
|
const compiledIntent = this.compile();
|
|
6701
|
-
if (
|
|
7253
|
+
if (hasExecutableNqlProgramSequence(compiledIntent.bundle)) {
|
|
6702
7254
|
return this.dumpNqlProgramSequence(compiledIntent, meta);
|
|
6703
7255
|
}
|
|
6704
7256
|
if (compiledIntent.kind === "mutation") {
|
|
@@ -6709,7 +7261,12 @@ var NqlBuilderImpl = class {
|
|
|
6709
7261
|
);
|
|
6710
7262
|
}
|
|
6711
7263
|
const bindingFinalQuery = isBindingFinalQuery(compiledIntent.bundle);
|
|
6712
|
-
const planReport = bindingFinalQuery ? createBindingFinalPlan(
|
|
7264
|
+
const planReport = bindingFinalQuery ? createBindingFinalPlan(
|
|
7265
|
+
compiledIntent.intent,
|
|
7266
|
+
compiledIntent.bundle,
|
|
7267
|
+
this.model,
|
|
7268
|
+
this.adapter?.dialectCapabilities
|
|
7269
|
+
) : this.planInternal();
|
|
6713
7270
|
if (!this.adapter) {
|
|
6714
7271
|
return {
|
|
6715
7272
|
plan: planReport,
|
|
@@ -6718,7 +7275,10 @@ var NqlBuilderImpl = class {
|
|
|
6718
7275
|
...meta !== void 0 && { meta }
|
|
6719
7276
|
};
|
|
6720
7277
|
}
|
|
6721
|
-
const finalBundle = this.createFinalNqlStatementBundle(
|
|
7278
|
+
const finalBundle = this.createFinalNqlStatementBundle(
|
|
7279
|
+
compiledIntent,
|
|
7280
|
+
bindingFinalPlanHasJsonAggIncludes(planReport) ? planReport : void 0
|
|
7281
|
+
);
|
|
6722
7282
|
const compiled = bindingFinalQuery || hasNqlBindings(finalBundle) ? this.adapter.compile(finalBundle, this.nqlBundleCompileOptions()) : this.adapter.compile(planReport);
|
|
6723
7283
|
try {
|
|
6724
7284
|
return this.adapter.createDump(planReport, compiled, meta);
|
|
@@ -6770,10 +7330,10 @@ var NqlBuilderImpl = class {
|
|
|
6770
7330
|
...this._schemaName !== void 0 && { schemaName: this._schemaName }
|
|
6771
7331
|
};
|
|
6772
7332
|
}
|
|
6773
|
-
createNqlStatementBundle(statement, bindings, runtimeBindings, sourceBundle, bindingDependencies) {
|
|
7333
|
+
createNqlStatementBundle(statement, bindings, runtimeBindings, sourceBundle, bindingDependencies, planReport) {
|
|
6774
7334
|
const statementBindings = filterMapByRuntimeBindingDependencies(
|
|
6775
7335
|
bindings,
|
|
6776
|
-
sourceBundle
|
|
7336
|
+
runtimeSourceBindingNames(sourceBundle),
|
|
6777
7337
|
bindingDependencies
|
|
6778
7338
|
);
|
|
6779
7339
|
const statementRuntimeBindings = filterMapByBindingDependencies(
|
|
@@ -6794,6 +7354,7 @@ var NqlBuilderImpl = class {
|
|
|
6794
7354
|
);
|
|
6795
7355
|
return {
|
|
6796
7356
|
...statement,
|
|
7357
|
+
...planReport !== void 0 && { plan: planReport },
|
|
6797
7358
|
...statementBindings.size > 0 && { bindings: statementBindings },
|
|
6798
7359
|
...statementBindingOutputSchemas !== void 0 && {
|
|
6799
7360
|
bindingOutputSchemas: statementBindingOutputSchemas
|
|
@@ -6806,7 +7367,7 @@ var NqlBuilderImpl = class {
|
|
|
6806
7367
|
}
|
|
6807
7368
|
};
|
|
6808
7369
|
}
|
|
6809
|
-
createFinalNqlStatementBundle(compiledIntent) {
|
|
7370
|
+
createFinalNqlStatementBundle(compiledIntent, planReport) {
|
|
6810
7371
|
const finalStep = createNqlProgramSteps(compiledIntent).at(-1);
|
|
6811
7372
|
if (compiledIntent.kind === "query") {
|
|
6812
7373
|
return this.createNqlStatementBundle(
|
|
@@ -6814,7 +7375,8 @@ var NqlBuilderImpl = class {
|
|
|
6814
7375
|
compiledIntent.bundle.bindings ?? /* @__PURE__ */ new Map(),
|
|
6815
7376
|
compiledIntent.bundle.runtimeBindings ?? /* @__PURE__ */ new Map(),
|
|
6816
7377
|
compiledIntent.bundle,
|
|
6817
|
-
finalStep?.bindingDependencies
|
|
7378
|
+
finalStep?.bindingDependencies,
|
|
7379
|
+
planReport
|
|
6818
7380
|
);
|
|
6819
7381
|
}
|
|
6820
7382
|
return this.createNqlStatementBundle(
|
|
@@ -6822,7 +7384,8 @@ var NqlBuilderImpl = class {
|
|
|
6822
7384
|
compiledIntent.bundle.bindings ?? /* @__PURE__ */ new Map(),
|
|
6823
7385
|
compiledIntent.bundle.runtimeBindings ?? /* @__PURE__ */ new Map(),
|
|
6824
7386
|
compiledIntent.bundle,
|
|
6825
|
-
finalStep?.bindingDependencies
|
|
7387
|
+
finalStep?.bindingDependencies,
|
|
7388
|
+
planReport
|
|
6826
7389
|
);
|
|
6827
7390
|
}
|
|
6828
7391
|
runMutationStatement(adapter, bundle, intent, inTransaction) {
|
|
@@ -6910,7 +7473,7 @@ var NqlBuilderImpl = class {
|
|
|
6910
7473
|
if (step.final) {
|
|
6911
7474
|
finalRows = rows.transformedRows;
|
|
6912
7475
|
}
|
|
6913
|
-
} else if (step.
|
|
7476
|
+
} else if (step.snapshot === true && step.bindName !== void 0) {
|
|
6914
7477
|
const statementBundle = this.createNqlStatementBundle(
|
|
6915
7478
|
{ query: step.intent },
|
|
6916
7479
|
priorBindings,
|
|
@@ -6922,7 +7485,39 @@ var NqlBuilderImpl = class {
|
|
|
6922
7485
|
statementBundle,
|
|
6923
7486
|
this.nqlBundleCompileOptions()
|
|
6924
7487
|
);
|
|
7488
|
+
const rows = await txAdapter.execute(compiled);
|
|
7489
|
+
runtimeBindings.set(
|
|
7490
|
+
step.bindName,
|
|
7491
|
+
createRuntimeBinding(
|
|
7492
|
+
sourceBundle,
|
|
7493
|
+
step.bindName,
|
|
7494
|
+
snapshotMutationRows(rows),
|
|
7495
|
+
txAdapter.dbCasing
|
|
7496
|
+
)
|
|
7497
|
+
);
|
|
7498
|
+
} else if (step.final) {
|
|
7499
|
+
const planReport = isBindingFinalQuery(compiledIntent.bundle) && compiledIntent.kind === "query" ? createBindingFinalPlan(
|
|
7500
|
+
step.intent,
|
|
7501
|
+
sourceBundle,
|
|
7502
|
+
this.model,
|
|
7503
|
+
txAdapter.dialectCapabilities
|
|
7504
|
+
) : void 0;
|
|
7505
|
+
const statementBundle = this.createNqlStatementBundle(
|
|
7506
|
+
{ query: step.intent },
|
|
7507
|
+
priorBindings,
|
|
7508
|
+
runtimeBindings,
|
|
7509
|
+
sourceBundle,
|
|
7510
|
+
step.bindingDependencies,
|
|
7511
|
+
planReport !== void 0 && bindingFinalPlanHasJsonAggIncludes(planReport) ? planReport : void 0
|
|
7512
|
+
);
|
|
7513
|
+
const compiled = txAdapter.compile(
|
|
7514
|
+
statementBundle,
|
|
7515
|
+
this.nqlBundleCompileOptions()
|
|
7516
|
+
);
|
|
6925
7517
|
finalRows = await txAdapter.execute(compiled);
|
|
7518
|
+
if (planReport !== void 0 && bindingFinalPlanHasJsonAggIncludes(planReport)) {
|
|
7519
|
+
hydrateJsonAggIncludes(finalRows, planReport);
|
|
7520
|
+
}
|
|
6926
7521
|
}
|
|
6927
7522
|
if (step.bindName) {
|
|
6928
7523
|
const boundQuery = sourceBundle.bindings?.get(step.bindName);
|
|
@@ -6967,12 +7562,19 @@ var NqlBuilderImpl = class {
|
|
|
6967
7562
|
});
|
|
6968
7563
|
}
|
|
6969
7564
|
} else {
|
|
7565
|
+
const planReport = step.final && isBindingFinalQuery(compiledIntent.bundle) && compiledIntent.kind === "query" ? createBindingFinalPlan(
|
|
7566
|
+
step.intent,
|
|
7567
|
+
sourceBundle,
|
|
7568
|
+
this.model,
|
|
7569
|
+
adapter.dialectCapabilities
|
|
7570
|
+
) : void 0;
|
|
6970
7571
|
const statementBundle = this.createNqlStatementBundle(
|
|
6971
7572
|
{ query: step.intent },
|
|
6972
7573
|
priorBindings,
|
|
6973
7574
|
runtimeBindings,
|
|
6974
7575
|
sourceBundle,
|
|
6975
|
-
step.bindingDependencies
|
|
7576
|
+
step.bindingDependencies,
|
|
7577
|
+
planReport !== void 0 && bindingFinalPlanHasJsonAggIncludes(planReport) ? planReport : void 0
|
|
6976
7578
|
);
|
|
6977
7579
|
const compiled = adapter.compile(
|
|
6978
7580
|
statementBundle,
|
|
@@ -6984,6 +7586,12 @@ var NqlBuilderImpl = class {
|
|
|
6984
7586
|
sql: compiled.sql,
|
|
6985
7587
|
params: compiled.parameters
|
|
6986
7588
|
});
|
|
7589
|
+
if (step.snapshot === true && step.bindName !== void 0) {
|
|
7590
|
+
runtimeBindings.set(step.bindName, {
|
|
7591
|
+
columns: requireMutationBindingColumns(sourceBundle, step.bindName),
|
|
7592
|
+
rows: []
|
|
7593
|
+
});
|
|
7594
|
+
}
|
|
6987
7595
|
}
|
|
6988
7596
|
if (step.bindName !== void 0) {
|
|
6989
7597
|
const boundQuery = sourceBundle.bindings?.get(step.bindName);
|
|
@@ -6994,7 +7602,12 @@ var NqlBuilderImpl = class {
|
|
|
6994
7602
|
}
|
|
6995
7603
|
const finalStep = steps.at(-1);
|
|
6996
7604
|
if (finalStep?.kind === "query") {
|
|
6997
|
-
const planReport = isBindingFinalQuery(compiledIntent.bundle) ? createBindingFinalPlan(
|
|
7605
|
+
const planReport = isBindingFinalQuery(compiledIntent.bundle) ? createBindingFinalPlan(
|
|
7606
|
+
finalStep.intent,
|
|
7607
|
+
compiledIntent.bundle,
|
|
7608
|
+
this.model,
|
|
7609
|
+
this.adapter?.dialectCapabilities
|
|
7610
|
+
) : this.planInternal();
|
|
6998
7611
|
const dumpMeta2 = {
|
|
6999
7612
|
compiledAt: /* @__PURE__ */ new Date(),
|
|
7000
7613
|
...this._schemaName !== void 0 && { schema: this._schemaName },
|
|
@@ -7058,7 +7671,7 @@ var NqlBuilderImpl = class {
|
|
|
7058
7671
|
);
|
|
7059
7672
|
}
|
|
7060
7673
|
const compiledIntent = this.compile();
|
|
7061
|
-
if (
|
|
7674
|
+
if (hasExecutableNqlProgramSequence(compiledIntent.bundle)) {
|
|
7062
7675
|
return this.executeNqlProgramSequence(compiledIntent, adapter);
|
|
7063
7676
|
}
|
|
7064
7677
|
if (compiledIntent.kind === "mutation") {
|
|
@@ -7070,11 +7683,24 @@ var NqlBuilderImpl = class {
|
|
|
7070
7683
|
)).transformedRows;
|
|
7071
7684
|
}
|
|
7072
7685
|
if (isBindingFinalQuery(compiledIntent.bundle)) {
|
|
7686
|
+
const planReport2 = createBindingFinalPlan(
|
|
7687
|
+
compiledIntent.intent,
|
|
7688
|
+
compiledIntent.bundle,
|
|
7689
|
+
this.model,
|
|
7690
|
+
adapter.dialectCapabilities
|
|
7691
|
+
);
|
|
7073
7692
|
const compiled2 = adapter.compile(
|
|
7074
|
-
this.createFinalNqlStatementBundle(
|
|
7693
|
+
this.createFinalNqlStatementBundle(
|
|
7694
|
+
compiledIntent,
|
|
7695
|
+
bindingFinalPlanHasJsonAggIncludes(planReport2) ? planReport2 : void 0
|
|
7696
|
+
),
|
|
7075
7697
|
this.nqlBundleCompileOptions()
|
|
7076
7698
|
);
|
|
7077
|
-
|
|
7699
|
+
const rows = await adapter.execute(compiled2);
|
|
7700
|
+
if (bindingFinalPlanHasJsonAggIncludes(planReport2)) {
|
|
7701
|
+
hydrateJsonAggIncludes(rows, planReport2);
|
|
7702
|
+
}
|
|
7703
|
+
return rows;
|
|
7078
7704
|
}
|
|
7079
7705
|
const planReport = this.planInternal();
|
|
7080
7706
|
const finalBundle = this.createFinalNqlStatementBundle(compiledIntent);
|
|
@@ -7389,85 +8015,8 @@ async function cursorPaginate(builder, options) {
|
|
|
7389
8015
|
};
|
|
7390
8016
|
}
|
|
7391
8017
|
|
|
7392
|
-
// src/dx/
|
|
7393
|
-
|
|
7394
|
-
const jsonAggDecisions = planReport.decisions.filter(
|
|
7395
|
-
(d) => d.type === "include-strategy" && d.choice === "json_agg"
|
|
7396
|
-
);
|
|
7397
|
-
if (jsonAggDecisions.length === 0) {
|
|
7398
|
-
return;
|
|
7399
|
-
}
|
|
7400
|
-
const relationInfo = /* @__PURE__ */ new Map();
|
|
7401
|
-
for (const decision of jsonAggDecisions) {
|
|
7402
|
-
const canonicalName = decision.context?.relation;
|
|
7403
|
-
const includeAlias = decision.context?.includeAlias;
|
|
7404
|
-
const relationType = decision.context?.relationType;
|
|
7405
|
-
const isToOne = relationType === "belongsTo" || relationType === "hasOne";
|
|
7406
|
-
if (typeof canonicalName === "string") {
|
|
7407
|
-
relationInfo.set(canonicalName, {
|
|
7408
|
-
isToOne,
|
|
7409
|
-
includeAlias: typeof includeAlias === "string" ? includeAlias : canonicalName,
|
|
7410
|
-
canonicalName
|
|
7411
|
-
});
|
|
7412
|
-
} else if (typeof includeAlias === "string") {
|
|
7413
|
-
relationInfo.set(includeAlias, { isToOne, includeAlias });
|
|
7414
|
-
}
|
|
7415
|
-
}
|
|
7416
|
-
if (relationInfo.size === 0) {
|
|
7417
|
-
return;
|
|
7418
|
-
}
|
|
7419
|
-
for (const row of results) {
|
|
7420
|
-
if (typeof row !== "object" || row === null) {
|
|
7421
|
-
continue;
|
|
7422
|
-
}
|
|
7423
|
-
const record2 = row;
|
|
7424
|
-
for (const [relationName, info] of relationInfo) {
|
|
7425
|
-
const candidates = [relationName];
|
|
7426
|
-
if (info.includeAlias && info.includeAlias !== relationName) {
|
|
7427
|
-
candidates.push(info.includeAlias);
|
|
7428
|
-
}
|
|
7429
|
-
let actualColumnName = null;
|
|
7430
|
-
for (const baseName of candidates) {
|
|
7431
|
-
const snakeJson = `${baseName}_json`;
|
|
7432
|
-
const camelJson = snakeJson.replace(
|
|
7433
|
-
/_([a-z])/g,
|
|
7434
|
-
(_, c) => c.toUpperCase()
|
|
7435
|
-
);
|
|
7436
|
-
if (snakeJson in record2) {
|
|
7437
|
-
actualColumnName = snakeJson;
|
|
7438
|
-
break;
|
|
7439
|
-
}
|
|
7440
|
-
if (camelJson in record2) {
|
|
7441
|
-
actualColumnName = camelJson;
|
|
7442
|
-
break;
|
|
7443
|
-
}
|
|
7444
|
-
}
|
|
7445
|
-
if (actualColumnName) {
|
|
7446
|
-
const jsonValue = record2[actualColumnName];
|
|
7447
|
-
let parsed;
|
|
7448
|
-
if (typeof jsonValue === "string") {
|
|
7449
|
-
try {
|
|
7450
|
-
parsed = JSON.parse(jsonValue);
|
|
7451
|
-
} catch {
|
|
7452
|
-
parsed = info.isToOne ? null : [];
|
|
7453
|
-
}
|
|
7454
|
-
} else if (Array.isArray(jsonValue)) {
|
|
7455
|
-
parsed = jsonValue;
|
|
7456
|
-
} else if (jsonValue === null || jsonValue === void 0) {
|
|
7457
|
-
parsed = info.isToOne ? null : [];
|
|
7458
|
-
} else {
|
|
7459
|
-
parsed = jsonValue;
|
|
7460
|
-
}
|
|
7461
|
-
if (info.isToOne && Array.isArray(parsed)) {
|
|
7462
|
-
parsed = parsed.length > 0 ? parsed[0] : null;
|
|
7463
|
-
}
|
|
7464
|
-
const outputKey = info.includeAlias ?? relationName;
|
|
7465
|
-
record2[outputKey] = parsed;
|
|
7466
|
-
delete record2[actualColumnName];
|
|
7467
|
-
}
|
|
7468
|
-
}
|
|
7469
|
-
}
|
|
7470
|
-
}
|
|
8018
|
+
// src/dx/result-hydrator.ts
|
|
8019
|
+
import { toColumnList as toColumnList2 } from "@dbsp/types";
|
|
7471
8020
|
|
|
7472
8021
|
// src/dx/relation-paths.ts
|
|
7473
8022
|
function nonEmptyString(value) {
|
|
@@ -7632,7 +8181,7 @@ var ResultHydrator = class {
|
|
|
7632
8181
|
for (const includeInfo of subqueryIncludes) {
|
|
7633
8182
|
const parentIds = [];
|
|
7634
8183
|
for (const r of results) {
|
|
7635
|
-
const id = this.
|
|
8184
|
+
const id = this.extractQueryKeyValue(
|
|
7636
8185
|
r,
|
|
7637
8186
|
includeInfo.sourceKey
|
|
7638
8187
|
);
|
|
@@ -7844,14 +8393,16 @@ var ResultHydrator = class {
|
|
|
7844
8393
|
* Get the foreign key column name from relation metadata.
|
|
7845
8394
|
*/
|
|
7846
8395
|
getForeignKeyColumn(foreignKey) {
|
|
7847
|
-
|
|
8396
|
+
const columns = toColumnList2(foreignKey);
|
|
8397
|
+
if (columns.length === 0) {
|
|
7848
8398
|
return "parent_id";
|
|
7849
8399
|
}
|
|
7850
|
-
if (
|
|
7851
|
-
|
|
8400
|
+
if (columns.length !== 1) {
|
|
8401
|
+
throw new Error(
|
|
8402
|
+
`Recursive include hydration requires a single-column self-referential foreign key; got ${JSON.stringify(columns)}.`
|
|
8403
|
+
);
|
|
7852
8404
|
}
|
|
7853
|
-
|
|
7854
|
-
return first ?? "parent_id";
|
|
8405
|
+
return columns[0];
|
|
7855
8406
|
}
|
|
7856
8407
|
/**
|
|
7857
8408
|
* Build traversal config for recursive CTE.
|
|
@@ -7943,10 +8494,14 @@ var ResultHydrator = class {
|
|
|
7943
8494
|
* case (slower but collision-safe for any value content).
|
|
7944
8495
|
*/
|
|
7945
8496
|
extractKeyValue(obj, key) {
|
|
7946
|
-
|
|
7947
|
-
|
|
8497
|
+
const columns = toColumnList2(key);
|
|
8498
|
+
if (columns.length === 0) {
|
|
8499
|
+
return void 0;
|
|
8500
|
+
}
|
|
8501
|
+
if (columns.length === 1) {
|
|
8502
|
+
return obj[columns[0]];
|
|
7948
8503
|
}
|
|
7949
|
-
const values =
|
|
8504
|
+
const values = columns.map((k) => obj[k]);
|
|
7950
8505
|
if (values.some((v2) => v2 === void 0 || v2 === null)) {
|
|
7951
8506
|
return void 0;
|
|
7952
8507
|
}
|
|
@@ -7956,6 +8511,17 @@ var ResultHydrator = class {
|
|
|
7956
8511
|
}
|
|
7957
8512
|
return parts.join(COMPOSITE_KEY_SEP);
|
|
7958
8513
|
}
|
|
8514
|
+
extractQueryKeyValue(obj, key) {
|
|
8515
|
+
const columns = toColumnList2(key);
|
|
8516
|
+
if (columns.length === 0) {
|
|
8517
|
+
return void 0;
|
|
8518
|
+
}
|
|
8519
|
+
if (columns.length === 1) {
|
|
8520
|
+
return obj[columns[0]];
|
|
8521
|
+
}
|
|
8522
|
+
const values = columns.map((k) => obj[k]);
|
|
8523
|
+
return values.some((v2) => v2 === void 0 || v2 === null) ? void 0 : values;
|
|
8524
|
+
}
|
|
7959
8525
|
};
|
|
7960
8526
|
|
|
7961
8527
|
// src/dx/set-operation-builder.ts
|
|
@@ -10377,6 +10943,7 @@ function rangeContainedBy(fieldOrColumn, valueOrRange, rangeType = "daterange")
|
|
|
10377
10943
|
}
|
|
10378
10944
|
|
|
10379
10945
|
// src/dx/schema-bridge.ts
|
|
10946
|
+
import { buildRelationKeyFields, toColumnList as toColumnList3 } from "@dbsp/types";
|
|
10380
10947
|
import * as v from "valibot";
|
|
10381
10948
|
function generatedTypeToColumnType(genType) {
|
|
10382
10949
|
switch (genType) {
|
|
@@ -10429,13 +10996,49 @@ function mapRelationType2(kind) {
|
|
|
10429
10996
|
return "belongsToMany";
|
|
10430
10997
|
}
|
|
10431
10998
|
}
|
|
10999
|
+
function sourceTableFromQualifiedName(qualifiedName) {
|
|
11000
|
+
const dotIndex = qualifiedName.indexOf(".");
|
|
11001
|
+
return dotIndex > 0 ? qualifiedName.slice(0, dotIndex) : qualifiedName;
|
|
11002
|
+
}
|
|
11003
|
+
function relationNameFromQualifiedName(qualifiedName) {
|
|
11004
|
+
const dotIndex = qualifiedName.indexOf(".");
|
|
11005
|
+
return dotIndex > 0 ? qualifiedName.slice(dotIndex + 1) : qualifiedName;
|
|
11006
|
+
}
|
|
11007
|
+
function columnListsEqual(left, right) {
|
|
11008
|
+
const leftColumns = toColumnList3(left);
|
|
11009
|
+
const rightColumns = toColumnList3(right);
|
|
11010
|
+
return leftColumns.length === rightColumns.length && leftColumns.every((column, index) => column === rightColumns[index]);
|
|
11011
|
+
}
|
|
11012
|
+
function generatedReferencedColumns(key) {
|
|
11013
|
+
const columns = toColumnList3(key);
|
|
11014
|
+
return columns.length > 0 ? columns : ["id"];
|
|
11015
|
+
}
|
|
11016
|
+
function findRelationForeignKey(sourceTable, genRelation, tables) {
|
|
11017
|
+
switch (genRelation.kind) {
|
|
11018
|
+
case "belongsTo":
|
|
11019
|
+
return tables.get(sourceTable)?.foreignKeys.find(
|
|
11020
|
+
(fk) => fk.references.table === genRelation.target && columnListsEqual(fk.columns, genRelation.foreignKey)
|
|
11021
|
+
);
|
|
11022
|
+
case "hasMany":
|
|
11023
|
+
return tables.get(genRelation.target)?.foreignKeys.find(
|
|
11024
|
+
(fk) => fk.references.table === sourceTable && columnListsEqual(fk.columns, genRelation.foreignKey)
|
|
11025
|
+
);
|
|
11026
|
+
case "manyToMany":
|
|
11027
|
+
return void 0;
|
|
11028
|
+
}
|
|
11029
|
+
}
|
|
11030
|
+
function isGeneratedTableWithConfig(table) {
|
|
11031
|
+
const columns = table.columns;
|
|
11032
|
+
return typeof columns === "object" && columns !== null && typeof columns.type !== "string";
|
|
11033
|
+
}
|
|
10432
11034
|
function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
10433
11035
|
const columns = [];
|
|
10434
11036
|
const foreignKeys = [];
|
|
10435
11037
|
const indexes = [];
|
|
10436
11038
|
const primaryKeys = [];
|
|
11039
|
+
const tableColumns = isGeneratedTableWithConfig(genTable) ? genTable.columns : genTable;
|
|
10437
11040
|
const indexedColumns = /* @__PURE__ */ new Set();
|
|
10438
|
-
for (const [colName, colDef] of Object.entries(
|
|
11041
|
+
for (const [colName, colDef] of Object.entries(tableColumns)) {
|
|
10439
11042
|
const col2 = {
|
|
10440
11043
|
name: colName,
|
|
10441
11044
|
type: generatedTypeToColumnType(colDef.type),
|
|
@@ -10488,7 +11091,7 @@ function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
|
10488
11091
|
const fkColumn = fk.columns[0];
|
|
10489
11092
|
const targetColumn = fk.references.columns[0];
|
|
10490
11093
|
if (fk.references.table === tableName && fkColumn !== void 0 && targetColumn !== void 0) {
|
|
10491
|
-
const colDef =
|
|
11094
|
+
const colDef = tableColumns[fkColumn];
|
|
10492
11095
|
const inferredName = fkColumn.endsWith("Id") ? fkColumn.slice(0, -2) : "parent";
|
|
10493
11096
|
const parentRole = colDef?.references?.parentRole ?? inferredName;
|
|
10494
11097
|
const childRole = colDef?.references?.childRole ?? (parentRole === "parent" ? "children" : `${parentRole}s`);
|
|
@@ -10504,7 +11107,10 @@ function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
|
10504
11107
|
}
|
|
10505
11108
|
}
|
|
10506
11109
|
let primaryKey;
|
|
10507
|
-
|
|
11110
|
+
const configuredPrimaryKey = isGeneratedTableWithConfig(genTable) ? genTable.primaryKey : void 0;
|
|
11111
|
+
if (configuredPrimaryKey !== void 0) {
|
|
11112
|
+
primaryKey = configuredPrimaryKey;
|
|
11113
|
+
} else if (primaryKeys.length > 0) {
|
|
10508
11114
|
primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : primaryKeys;
|
|
10509
11115
|
} else {
|
|
10510
11116
|
const fkColumns = foreignKeys.flatMap((fk) => fk.columns);
|
|
@@ -10515,6 +11121,27 @@ function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
|
10515
11121
|
primaryKey = hasId ? "id" : void 0;
|
|
10516
11122
|
}
|
|
10517
11123
|
}
|
|
11124
|
+
if (isGeneratedTableWithConfig(genTable)) {
|
|
11125
|
+
for (const fk of genTable.foreignKeys ?? []) {
|
|
11126
|
+
const foreignKey = {
|
|
11127
|
+
columns: [...fk.columns],
|
|
11128
|
+
references: {
|
|
11129
|
+
table: fk.references.table,
|
|
11130
|
+
columns: [...fk.references.columns]
|
|
11131
|
+
}
|
|
11132
|
+
};
|
|
11133
|
+
if (fk.onDelete) foreignKey.onDelete = fk.onDelete;
|
|
11134
|
+
if (fk.onUpdate) foreignKey.onUpdate = fk.onUpdate;
|
|
11135
|
+
foreignKeys.push(foreignKey);
|
|
11136
|
+
}
|
|
11137
|
+
for (const index of genTable.indexes ?? []) {
|
|
11138
|
+
indexes.push({
|
|
11139
|
+
columns: [...index.columns],
|
|
11140
|
+
unique: index.unique ?? false,
|
|
11141
|
+
...index.name !== void 0 && { name: index.name }
|
|
11142
|
+
});
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
10518
11145
|
const frozenColumns = Object.freeze(columns);
|
|
10519
11146
|
return Object.freeze({
|
|
10520
11147
|
name: tableName,
|
|
@@ -10527,10 +11154,9 @@ function buildTableIRFromDefinition(tableName, genTable, fkAutoIndex) {
|
|
|
10527
11154
|
}
|
|
10528
11155
|
});
|
|
10529
11156
|
}
|
|
10530
|
-
function buildRelationIR(qualifiedName, genRelation, hints) {
|
|
10531
|
-
const
|
|
10532
|
-
const
|
|
10533
|
-
const relationName = dotIndex > 0 ? qualifiedName.slice(dotIndex + 1) : qualifiedName;
|
|
11157
|
+
function buildRelationIR(qualifiedName, genRelation, hints, relationFk) {
|
|
11158
|
+
const sourceTable = sourceTableFromQualifiedName(qualifiedName);
|
|
11159
|
+
const relationName = relationNameFromQualifiedName(qualifiedName);
|
|
10534
11160
|
const hint = hints[qualifiedName];
|
|
10535
11161
|
const relCardinality = genRelation.kind === "hasMany" ? genRelation.cardinality : void 0;
|
|
10536
11162
|
const cardinality = hint?.cardinality === "one" ? "one" : relCardinality === "one" ? "one" : genRelation.kind === "belongsTo" ? "one" : "many";
|
|
@@ -10554,14 +11180,38 @@ function buildRelationIR(qualifiedName, genRelation, hints) {
|
|
|
10554
11180
|
case "belongsTo":
|
|
10555
11181
|
return {
|
|
10556
11182
|
...baseRelation,
|
|
10557
|
-
|
|
10558
|
-
|
|
11183
|
+
...buildRelationKeyFields(
|
|
11184
|
+
relationFk ?? {
|
|
11185
|
+
columns: genRelation.foreignKey,
|
|
11186
|
+
references: {
|
|
11187
|
+
table: genRelation.target,
|
|
11188
|
+
columns: generatedReferencedColumns(genRelation.targetKey)
|
|
11189
|
+
}
|
|
11190
|
+
},
|
|
11191
|
+
"belongsTo",
|
|
11192
|
+
{
|
|
11193
|
+
foreignKeyShape: genRelation.foreignKey,
|
|
11194
|
+
...genRelation.targetKey !== void 0 ? { referencedKeyShape: genRelation.targetKey } : {}
|
|
11195
|
+
}
|
|
11196
|
+
)
|
|
10559
11197
|
};
|
|
10560
11198
|
case "hasMany":
|
|
10561
11199
|
return {
|
|
10562
11200
|
...baseRelation,
|
|
10563
|
-
|
|
10564
|
-
|
|
11201
|
+
...buildRelationKeyFields(
|
|
11202
|
+
relationFk ?? {
|
|
11203
|
+
columns: genRelation.foreignKey,
|
|
11204
|
+
references: {
|
|
11205
|
+
table: sourceTable,
|
|
11206
|
+
columns: generatedReferencedColumns(genRelation.sourceKey)
|
|
11207
|
+
}
|
|
11208
|
+
},
|
|
11209
|
+
"inverse",
|
|
11210
|
+
{
|
|
11211
|
+
foreignKeyShape: genRelation.foreignKey,
|
|
11212
|
+
...genRelation.sourceKey !== void 0 ? { referencedKeyShape: genRelation.sourceKey } : {}
|
|
11213
|
+
}
|
|
11214
|
+
)
|
|
10565
11215
|
};
|
|
10566
11216
|
case "manyToMany":
|
|
10567
11217
|
return {
|
|
@@ -10583,9 +11233,15 @@ function buildModelFromSchema(schema2) {
|
|
|
10583
11233
|
);
|
|
10584
11234
|
}
|
|
10585
11235
|
for (const [qualifiedName, genRelation] of Object.entries(schema2.relations)) {
|
|
11236
|
+
const sourceTable = sourceTableFromQualifiedName(qualifiedName);
|
|
10586
11237
|
relations.set(
|
|
10587
11238
|
qualifiedName,
|
|
10588
|
-
buildRelationIR(
|
|
11239
|
+
buildRelationIR(
|
|
11240
|
+
qualifiedName,
|
|
11241
|
+
genRelation,
|
|
11242
|
+
schema2.hints,
|
|
11243
|
+
findRelationForeignKey(sourceTable, genRelation, tables)
|
|
11244
|
+
)
|
|
10589
11245
|
);
|
|
10590
11246
|
}
|
|
10591
11247
|
return new ModelIRImpl(tables, relations);
|
|
@@ -10698,6 +11354,7 @@ var ForeignKeyReferenceSchema = v.object({
|
|
|
10698
11354
|
parentRole: v.optional(v.string()),
|
|
10699
11355
|
childRole: v.optional(v.string())
|
|
10700
11356
|
});
|
|
11357
|
+
var StringOrStringArraySchema = v.union([v.string(), v.array(v.string())]);
|
|
10701
11358
|
var ColumnDefinitionSchema = v.object({
|
|
10702
11359
|
type: SchemaColumnTypeSchema,
|
|
10703
11360
|
primaryKey: v.optional(v.boolean()),
|
|
@@ -10711,7 +11368,24 @@ var ColumnDefinitionSchema = v.object({
|
|
|
10711
11368
|
var FlatTableDefinitionSchema = safeRecord(ColumnDefinitionSchema);
|
|
10712
11369
|
var TableDefWithConfigSchema = v.object({
|
|
10713
11370
|
columns: FlatTableDefinitionSchema,
|
|
10714
|
-
primaryKey: v.
|
|
11371
|
+
primaryKey: v.optional(StringOrStringArraySchema),
|
|
11372
|
+
foreignKeys: v.optional(
|
|
11373
|
+
v.array(
|
|
11374
|
+
v.object({
|
|
11375
|
+
columns: v.array(v.string()),
|
|
11376
|
+
references: v.object({
|
|
11377
|
+
table: v.string(),
|
|
11378
|
+
columns: v.array(v.string())
|
|
11379
|
+
}),
|
|
11380
|
+
onDelete: v.optional(
|
|
11381
|
+
v.picklist(["CASCADE", "SET NULL", "RESTRICT", "NO ACTION"])
|
|
11382
|
+
),
|
|
11383
|
+
onUpdate: v.optional(
|
|
11384
|
+
v.picklist(["CASCADE", "SET NULL", "RESTRICT", "NO ACTION"])
|
|
11385
|
+
)
|
|
11386
|
+
})
|
|
11387
|
+
)
|
|
11388
|
+
),
|
|
10715
11389
|
indexes: v.optional(
|
|
10716
11390
|
v.array(
|
|
10717
11391
|
v.object({
|
|
@@ -10733,15 +11407,15 @@ var IncludeStrategySchema = v.optional(
|
|
|
10733
11407
|
var BelongsToRelationSchema = v.object({
|
|
10734
11408
|
kind: v.literal("belongsTo"),
|
|
10735
11409
|
target: v.string(),
|
|
10736
|
-
foreignKey:
|
|
10737
|
-
targetKey: v.optional(
|
|
11410
|
+
foreignKey: StringOrStringArraySchema,
|
|
11411
|
+
targetKey: v.optional(StringOrStringArraySchema),
|
|
10738
11412
|
includeStrategy: IncludeStrategySchema
|
|
10739
11413
|
});
|
|
10740
11414
|
var HasManyRelationSchema = v.object({
|
|
10741
11415
|
kind: v.literal("hasMany"),
|
|
10742
11416
|
target: v.string(),
|
|
10743
|
-
foreignKey:
|
|
10744
|
-
sourceKey: v.optional(
|
|
11417
|
+
foreignKey: StringOrStringArraySchema,
|
|
11418
|
+
sourceKey: v.optional(StringOrStringArraySchema),
|
|
10745
11419
|
includeStrategy: IncludeStrategySchema
|
|
10746
11420
|
});
|
|
10747
11421
|
var ManyToManyRelationSchema = v.object({
|