@prisma-next/sql-orm-client 0.5.0-dev.8 → 0.5.0-dev.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/index.d.mts +53 -45
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +310 -214
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -22
- package/src/collection-contract.ts +19 -0
- package/src/collection-dispatch.ts +2 -2
- package/src/collection-mutation-dispatch.ts +1 -1
- package/src/collection-runtime.ts +3 -2
- package/src/collection.ts +104 -9
- package/src/execute-query-plan.ts +4 -5
- package/src/filters.ts +1 -1
- package/src/include-strategy.ts +36 -14
- package/src/model-accessor.ts +69 -63
- package/src/mutation-executor.ts +10 -2
- package/src/query-plan-aggregate.ts +32 -13
- package/src/query-plan-meta.ts +6 -66
- package/src/query-plan-mutations.ts +31 -23
- package/src/query-plan-select.ts +16 -6
- package/src/types.ts +88 -103
- package/src/where-binding.ts +10 -2
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { AsyncIterableResult } from "@prisma-next/runtime
|
|
2
|
-
import { AggregateExpr, AndExpr, BinaryExpr, ColumnRef, DefaultValueExpr, DeleteAst, DerivedTableSource, EqColJoinOn, ExistsExpr, InsertAst, InsertOnConflict, JoinAst, JsonArrayAggExpr, JsonObjectExpr, ListExpression, LiteralExpr, NotExpr, NullCheckExpr,
|
|
1
|
+
import { AsyncIterableResult } from "@prisma-next/framework-components/runtime";
|
|
2
|
+
import { AggregateExpr, AndExpr, BinaryExpr, ColumnRef, DefaultValueExpr, DeleteAst, DerivedTableSource, EqColJoinOn, ExistsExpr, InsertAst, InsertOnConflict, JoinAst, JsonArrayAggExpr, JsonObjectExpr, ListExpression, LiteralExpr, NotExpr, NullCheckExpr, OrExpr, OrderByItem, ParamRef, ProjectionItem, SelectAst, SubqueryExpr, TableSource, UpdateAst, collectOrderedParamRefs, isWhereExpr } from "@prisma-next/sql-relational-core/ast";
|
|
3
3
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
4
|
-
|
|
5
4
|
//#region src/collection-contract.ts
|
|
6
5
|
function modelsOf(contract) {
|
|
7
6
|
return contract.models;
|
|
@@ -177,6 +176,13 @@ function resolveModelTableName(contract, modelName) {
|
|
|
177
176
|
function resolvePrimaryKeyColumn(contract, tableName) {
|
|
178
177
|
return contract.storage.tables[tableName]?.primaryKey?.columns[0] ?? "id";
|
|
179
178
|
}
|
|
179
|
+
function resolveRowIdentityColumns(contract, tableName) {
|
|
180
|
+
const table = contract.storage.tables[tableName];
|
|
181
|
+
if (!table) return [];
|
|
182
|
+
if (table.primaryKey && table.primaryKey.columns.length > 0) return table.primaryKey.columns;
|
|
183
|
+
for (const unique of table.uniques) if (unique.columns.length > 0) return unique.columns;
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
180
186
|
function assertReturningCapability(contract, action) {
|
|
181
187
|
if (hasContractCapability(contract, "returning")) return;
|
|
182
188
|
throw new Error(`${action} requires contract capability "returning"`);
|
|
@@ -198,7 +204,6 @@ function capabilityEnabled(value) {
|
|
|
198
204
|
function isToOneCardinality(cardinality) {
|
|
199
205
|
return cardinality === "1:1" || cardinality === "N:1";
|
|
200
206
|
}
|
|
201
|
-
|
|
202
207
|
//#endregion
|
|
203
208
|
//#region src/aggregate-builder.ts
|
|
204
209
|
function createAggregateBuilder(contract, modelName) {
|
|
@@ -238,7 +243,6 @@ function createFieldAggregateSelector(fieldToColumn, field, fn) {
|
|
|
238
243
|
column: fieldToColumn[fieldName] ?? fieldName
|
|
239
244
|
};
|
|
240
245
|
}
|
|
241
|
-
|
|
242
246
|
//#endregion
|
|
243
247
|
//#region src/collection-aggregate-result.ts
|
|
244
248
|
function normalizeAggregateResult(aggregateSpec, row) {
|
|
@@ -270,7 +274,6 @@ function normalizeAggregateResult(aggregateSpec, row) {
|
|
|
270
274
|
}
|
|
271
275
|
return result;
|
|
272
276
|
}
|
|
273
|
-
|
|
274
277
|
//#endregion
|
|
275
278
|
//#region src/collection-column-mapping.ts
|
|
276
279
|
function mapFieldsToColumns(contract, modelName, fieldNames) {
|
|
@@ -287,7 +290,6 @@ function mapCursorValuesToColumns(contract, modelName, cursorValues) {
|
|
|
287
290
|
}
|
|
288
291
|
return mappedCursor;
|
|
289
292
|
}
|
|
290
|
-
|
|
291
293
|
//#endregion
|
|
292
294
|
//#region src/collection-runtime.ts
|
|
293
295
|
function augmentSelectionForJoinColumns(selectedFields, requiredColumns) {
|
|
@@ -348,12 +350,12 @@ function mapPolymorphicRow(contract, baseModelName, polyInfo, row, variantName)
|
|
|
348
350
|
const variant = variantName ? polyInfo.variants.get(variantName) : polyInfo.variantsByValue.get(row[polyInfo.discriminatorColumn]);
|
|
349
351
|
if (!variant) {
|
|
350
352
|
const baseMap = getCompleteColumnToFieldMap(contract, baseModelName);
|
|
351
|
-
const mapped
|
|
353
|
+
const mapped = {};
|
|
352
354
|
for (const [col, val] of Object.entries(row)) {
|
|
353
355
|
const field = baseMap[col];
|
|
354
|
-
if (field !== void 0) mapped
|
|
356
|
+
if (field !== void 0) mapped[field] = val;
|
|
355
357
|
}
|
|
356
|
-
return mapped
|
|
358
|
+
return mapped;
|
|
357
359
|
}
|
|
358
360
|
const mtiTable = variant.strategy === "mti" ? variant.table : void 0;
|
|
359
361
|
const mergedMap = getMergedColumnToFieldMap(contract, baseModelName, variant.modelName, mtiTable);
|
|
@@ -389,95 +391,78 @@ async function acquireRuntimeScope(runtime) {
|
|
|
389
391
|
};
|
|
390
392
|
return { scope: connection };
|
|
391
393
|
}
|
|
392
|
-
|
|
393
394
|
//#endregion
|
|
394
395
|
//#region src/execute-query-plan.ts
|
|
395
396
|
function executeQueryPlan(scope, plan) {
|
|
396
397
|
return scope.execute(plan);
|
|
397
398
|
}
|
|
398
|
-
|
|
399
399
|
//#endregion
|
|
400
400
|
//#region src/include-strategy.ts
|
|
401
|
+
/**
|
|
402
|
+
* Choose the SQL emission strategy for nested includes based on the
|
|
403
|
+
* contract's declared capabilities.
|
|
404
|
+
*
|
|
405
|
+
* - `'lateral'`: outer SELECT with one LATERAL JOIN per relation,
|
|
406
|
+
* aggregating to JSON. Requires both `lateral` and `jsonAgg`.
|
|
407
|
+
* Postgres has both.
|
|
408
|
+
* - `'correlated'`: outer SELECT with one correlated subquery per
|
|
409
|
+
* relation, aggregating to JSON. Requires `jsonAgg` only.
|
|
410
|
+
* SQLite has `jsonAgg` (via `json_group_array`) but no LATERAL.
|
|
411
|
+
* - `'multiQuery'`: fallback. One SELECT per relation, stitched
|
|
412
|
+
* together in JS via `WHERE pk IN (parent-pk-values)`. Always
|
|
413
|
+
* correct; just N+1 round-trips.
|
|
414
|
+
*
|
|
415
|
+
* The capability flags are looked up under the contract's
|
|
416
|
+
* `targetFamily` and `target` namespaces — the two layers the contract
|
|
417
|
+
* emitter actually populates. Cross-namespace ("`postgres.lateral`
|
|
418
|
+
* found while running SQLite") false positives are impossible because
|
|
419
|
+
* we only inspect the running target's namespaces.
|
|
420
|
+
*/
|
|
401
421
|
function selectIncludeStrategy(contract) {
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
const hasJsonAgg = hasCapability(capabilities?.["jsonAgg"]);
|
|
422
|
+
const hasLateral = capabilityFlag(contract, "lateral");
|
|
423
|
+
const hasJsonAgg = capabilityFlag(contract, "jsonAgg");
|
|
405
424
|
if (hasLateral && hasJsonAgg) return "lateral";
|
|
406
425
|
if (hasJsonAgg) return "correlated";
|
|
407
426
|
return "multiQuery";
|
|
408
427
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
428
|
+
/**
|
|
429
|
+
* Read a capability flag from the contract's target/family namespaces.
|
|
430
|
+
*
|
|
431
|
+
* The contract emitter populates `capabilities[targetFamily]` (universal
|
|
432
|
+
* SQL flags like `jsonAgg`, `returning`) and `capabilities[target]`
|
|
433
|
+
* (target-specific flags like `lateral` on Postgres). Either may
|
|
434
|
+
* declare a given flag; the family namespace declares the floor and the
|
|
435
|
+
* target namespace can extend on top.
|
|
436
|
+
*/
|
|
437
|
+
function capabilityFlag(contract, flag) {
|
|
438
|
+
return contract.capabilities[contract.targetFamily]?.[flag] === true || contract.capabilities[contract.target]?.[flag] === true;
|
|
414
439
|
}
|
|
415
|
-
|
|
416
440
|
//#endregion
|
|
417
441
|
//#region src/query-plan-meta.ts
|
|
418
|
-
function resolveProjectionCodecs(contract, ast) {
|
|
419
|
-
const codecs = {};
|
|
420
|
-
if (ast.kind === "select") {
|
|
421
|
-
for (const item of ast.projection) if (item.expr.kind === "column-ref") {
|
|
422
|
-
const col = contract.storage.tables[item.expr.table]?.columns[item.expr.column];
|
|
423
|
-
if (col?.codecId) codecs[item.alias] = col.codecId;
|
|
424
|
-
}
|
|
425
|
-
} else if (ast.returning) {
|
|
426
|
-
const tableName = ast.table.name;
|
|
427
|
-
const table = contract.storage.tables[tableName];
|
|
428
|
-
if (!table) return void 0;
|
|
429
|
-
for (const colRef of ast.returning) {
|
|
430
|
-
const col = table.columns[colRef.column];
|
|
431
|
-
if (col?.codecId) codecs[colRef.column] = col.codecId;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
return Object.keys(codecs).length > 0 ? codecs : void 0;
|
|
435
|
-
}
|
|
436
442
|
function deriveParamsFromAst(ast) {
|
|
437
|
-
|
|
438
|
-
return {
|
|
439
|
-
params: collectedParams.map((p) => p.value),
|
|
440
|
-
paramDescriptors: collectedParams.map((p) => ({
|
|
441
|
-
...ifDefined("name", p.name),
|
|
442
|
-
...ifDefined("codecId", p.codecId),
|
|
443
|
-
source: "dsl"
|
|
444
|
-
}))
|
|
445
|
-
};
|
|
443
|
+
return { params: collectOrderedParamRefs(ast).map((p) => p.value) };
|
|
446
444
|
}
|
|
447
445
|
function resolveTableColumns(contract, tableName) {
|
|
448
446
|
const table = contract.storage.tables[tableName];
|
|
449
447
|
if (!table) throw new Error(`Unknown table "${tableName}" in SQL ORM query planner`);
|
|
450
448
|
return Object.keys(table.columns);
|
|
451
449
|
}
|
|
452
|
-
function buildOrmPlanMeta(contract
|
|
450
|
+
function buildOrmPlanMeta(contract) {
|
|
453
451
|
return {
|
|
454
452
|
target: contract.target,
|
|
455
453
|
targetFamily: contract.targetFamily,
|
|
456
454
|
storageHash: contract.storage.storageHash,
|
|
457
455
|
...contract.profileHash !== void 0 ? { profileHash: contract.profileHash } : {},
|
|
458
|
-
lane: "orm-client"
|
|
459
|
-
paramDescriptors: [...paramDescriptors]
|
|
456
|
+
lane: "orm-client"
|
|
460
457
|
};
|
|
461
458
|
}
|
|
462
|
-
function buildOrmQueryPlan(contract, ast, params
|
|
463
|
-
const projectionTypes = resolveProjectionCodecs(contract, ast);
|
|
464
|
-
const codecAnnotations = projectionTypes ? { codecs: Object.freeze({ ...projectionTypes }) } : void 0;
|
|
465
|
-
const limitAnnotation = ast.kind === "select" && ast.limit !== void 0 ? { limit: ast.limit } : void 0;
|
|
466
|
-
const annotations = codecAnnotations || limitAnnotation ? Object.freeze({
|
|
467
|
-
...codecAnnotations,
|
|
468
|
-
...limitAnnotation
|
|
469
|
-
}) : void 0;
|
|
459
|
+
function buildOrmQueryPlan(contract, ast, params) {
|
|
470
460
|
return Object.freeze({
|
|
471
461
|
ast,
|
|
472
462
|
params: [...params],
|
|
473
|
-
meta:
|
|
474
|
-
...buildOrmPlanMeta(contract, paramDescriptors),
|
|
475
|
-
...ifDefined("projectionTypes", projectionTypes),
|
|
476
|
-
...ifDefined("annotations", annotations)
|
|
477
|
-
}
|
|
463
|
+
meta: buildOrmPlanMeta(contract)
|
|
478
464
|
});
|
|
479
465
|
}
|
|
480
|
-
|
|
481
466
|
//#endregion
|
|
482
467
|
//#region src/where-utils.ts
|
|
483
468
|
function combineWhereExprs(filters) {
|
|
@@ -485,13 +470,23 @@ function combineWhereExprs(filters) {
|
|
|
485
470
|
if (filters.length === 1) return filters[0];
|
|
486
471
|
return AndExpr.of(filters);
|
|
487
472
|
}
|
|
488
|
-
|
|
489
473
|
//#endregion
|
|
490
474
|
//#region src/query-plan-aggregate.ts
|
|
491
|
-
function
|
|
492
|
-
if (selector.fn === "count") return
|
|
475
|
+
function toAggregateProjection(contract, tableName, selector) {
|
|
476
|
+
if (selector.fn === "count") return {
|
|
477
|
+
expr: AggregateExpr.count(),
|
|
478
|
+
codecId: void 0
|
|
479
|
+
};
|
|
493
480
|
if (!selector.column) throw new Error(`Aggregate selector "${selector.fn}" requires a field`);
|
|
494
|
-
|
|
481
|
+
const expr = new AggregateExpr(selector.fn, ColumnRef.of(tableName, selector.column));
|
|
482
|
+
if (selector.fn === "min" || selector.fn === "max") return {
|
|
483
|
+
expr,
|
|
484
|
+
codecId: contract.storage.tables[tableName]?.columns[selector.column]?.codecId
|
|
485
|
+
};
|
|
486
|
+
return {
|
|
487
|
+
expr,
|
|
488
|
+
codecId: void 0
|
|
489
|
+
};
|
|
495
490
|
}
|
|
496
491
|
function validateGroupedComparable(value) {
|
|
497
492
|
switch (value.kind) {
|
|
@@ -528,53 +523,61 @@ function validateGroupedHavingExpr(expr) {
|
|
|
528
523
|
throw new Error("ParamRef is not supported in grouped having expressions");
|
|
529
524
|
},
|
|
530
525
|
list: rejectHavingExpr,
|
|
531
|
-
and(expr
|
|
532
|
-
return AndExpr.of(expr
|
|
526
|
+
and(expr) {
|
|
527
|
+
return AndExpr.of(expr.exprs.map((child) => validateGroupedHavingExpr(child)));
|
|
533
528
|
},
|
|
534
|
-
or(expr
|
|
535
|
-
return OrExpr.of(expr
|
|
529
|
+
or(expr) {
|
|
530
|
+
return OrExpr.of(expr.exprs.map((child) => validateGroupedHavingExpr(child)));
|
|
536
531
|
},
|
|
537
|
-
exists(expr
|
|
538
|
-
throw new Error(`Unsupported grouped having expression kind "${expr
|
|
532
|
+
exists(expr) {
|
|
533
|
+
throw new Error(`Unsupported grouped having expression kind "${expr.kind}"`);
|
|
539
534
|
},
|
|
540
|
-
nullCheck(expr
|
|
541
|
-
return new NullCheckExpr(validateGroupedMetricExpr(expr
|
|
535
|
+
nullCheck(expr) {
|
|
536
|
+
return new NullCheckExpr(validateGroupedMetricExpr(expr.expr), expr.isNull);
|
|
542
537
|
},
|
|
543
|
-
not(expr
|
|
544
|
-
return new NotExpr(validateGroupedHavingExpr(expr
|
|
538
|
+
not(expr) {
|
|
539
|
+
return new NotExpr(validateGroupedHavingExpr(expr.expr));
|
|
545
540
|
},
|
|
546
|
-
binary(expr
|
|
547
|
-
return new BinaryExpr(expr
|
|
541
|
+
binary(expr) {
|
|
542
|
+
return new BinaryExpr(expr.op, validateGroupedMetricExpr(expr.left), validateGroupedComparable(expr.right));
|
|
548
543
|
}
|
|
549
544
|
});
|
|
550
545
|
}
|
|
551
546
|
function compileAggregate(contract, tableName, filters, aggregateSpec) {
|
|
552
547
|
const entries = Object.entries(aggregateSpec);
|
|
553
548
|
if (entries.length === 0) throw new Error("aggregate() requires at least one aggregation selector");
|
|
554
|
-
const projection = entries.map(([alias, selector]) =>
|
|
549
|
+
const projection = entries.map(([alias, selector]) => {
|
|
550
|
+
const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
|
|
551
|
+
return ProjectionItem.of(alias, expr, codecId);
|
|
552
|
+
});
|
|
555
553
|
let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection);
|
|
556
554
|
const where = combineWhereExprs(filters);
|
|
557
555
|
if (where) ast = ast.withWhere(where);
|
|
558
|
-
const { params
|
|
559
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
556
|
+
const { params } = deriveParamsFromAst(ast);
|
|
557
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
560
558
|
}
|
|
561
559
|
function compileGroupedAggregate(contract, tableName, filters, groupByColumns, aggregateSpec, havingExpr) {
|
|
562
560
|
if (groupByColumns.length === 0) throw new Error("groupBy() requires at least one field");
|
|
563
561
|
const entries = Object.entries(aggregateSpec);
|
|
564
562
|
if (entries.length === 0) throw new Error("groupBy().aggregate() requires at least one aggregation selector");
|
|
565
|
-
const
|
|
563
|
+
const table = contract.storage.tables[tableName];
|
|
564
|
+
const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId)), ...entries.map(([alias, selector]) => {
|
|
565
|
+
const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
|
|
566
|
+
return ProjectionItem.of(alias, expr, codecId);
|
|
567
|
+
})];
|
|
566
568
|
let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
|
|
567
569
|
const where = combineWhereExprs(filters);
|
|
568
570
|
if (where) ast = ast.withWhere(where);
|
|
569
571
|
if (havingExpr) ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
|
|
570
|
-
const { params
|
|
571
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
572
|
+
const { params } = deriveParamsFromAst(ast);
|
|
573
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
572
574
|
}
|
|
573
|
-
|
|
574
575
|
//#endregion
|
|
575
576
|
//#region src/query-plan-mutations.ts
|
|
576
577
|
function buildReturningColumns(contract, tableName, returningColumns) {
|
|
577
|
-
|
|
578
|
+
const columns = returningColumns && returningColumns.length > 0 ? [...returningColumns] : resolveTableColumns(contract, tableName);
|
|
579
|
+
const table = contract.storage.tables[tableName];
|
|
580
|
+
return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId));
|
|
578
581
|
}
|
|
579
582
|
function toParamAssignments(contract, tableName, values) {
|
|
580
583
|
const assignments = {};
|
|
@@ -585,7 +588,11 @@ function toParamAssignments(contract, tableName, values) {
|
|
|
585
588
|
if (!codecId) throw new Error(`Unknown column "${column}" in table "${tableName}"`);
|
|
586
589
|
assignments[column] = ParamRef.of(value, {
|
|
587
590
|
name: column,
|
|
588
|
-
codecId
|
|
591
|
+
codecId,
|
|
592
|
+
refs: {
|
|
593
|
+
table: tableName,
|
|
594
|
+
column
|
|
595
|
+
}
|
|
589
596
|
});
|
|
590
597
|
}
|
|
591
598
|
return { assignments };
|
|
@@ -610,7 +617,11 @@ function normalizeInsertRows(contract, tableName, rows) {
|
|
|
610
617
|
if (!codecId) throw new Error(`Unknown column "${column}" in table "${tableName}"`);
|
|
611
618
|
normalizedRow[column] = ParamRef.of(row[column], {
|
|
612
619
|
name: column,
|
|
613
|
-
codecId
|
|
620
|
+
codecId,
|
|
621
|
+
refs: {
|
|
622
|
+
table: tableName,
|
|
623
|
+
column
|
|
624
|
+
}
|
|
614
625
|
});
|
|
615
626
|
continue;
|
|
616
627
|
}
|
|
@@ -622,14 +633,14 @@ function normalizeInsertRows(contract, tableName, rows) {
|
|
|
622
633
|
function compileInsertReturning(contract, tableName, rows, returningColumns) {
|
|
623
634
|
const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
|
|
624
635
|
const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows).withReturning(buildReturningColumns(contract, tableName, returningColumns));
|
|
625
|
-
const { params
|
|
626
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
636
|
+
const { params } = deriveParamsFromAst(ast);
|
|
637
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
627
638
|
}
|
|
628
639
|
function compileInsertCount(contract, tableName, rows) {
|
|
629
640
|
const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
|
|
630
641
|
const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows);
|
|
631
|
-
const { params
|
|
632
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
642
|
+
const { params } = deriveParamsFromAst(ast);
|
|
643
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
633
644
|
}
|
|
634
645
|
function stripUndefinedValues(row) {
|
|
635
646
|
const result = {};
|
|
@@ -665,40 +676,39 @@ function compileUpsertReturning(contract, tableName, createValues, updateValues,
|
|
|
665
676
|
const updateAssignments = Object.keys(updateValues).length > 0 ? toParamAssignments(contract, tableName, updateValues) : void 0;
|
|
666
677
|
const onConflict = updateAssignments ? InsertOnConflict.on(conflictColumns.map((column) => ColumnRef.of(tableName, column))).doUpdateSet(updateAssignments.assignments) : InsertOnConflict.on(conflictColumns.map((column) => ColumnRef.of(tableName, column))).doNothing();
|
|
667
678
|
const ast = InsertAst.into(TableSource.named(tableName)).withValues(createAssignments.assignments).withOnConflict(onConflict).withReturning(buildReturningColumns(contract, tableName, returningColumns));
|
|
668
|
-
const { params
|
|
669
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
679
|
+
const { params } = deriveParamsFromAst(ast);
|
|
680
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
670
681
|
}
|
|
671
682
|
function compileUpdateReturning(contract, tableName, setValues, filters, returningColumns) {
|
|
672
683
|
const where = combineWhereExprs(filters);
|
|
673
684
|
const { assignments } = toParamAssignments(contract, tableName, setValues);
|
|
674
685
|
let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments).withReturning(buildReturningColumns(contract, tableName, returningColumns));
|
|
675
686
|
if (where) ast = ast.withWhere(where);
|
|
676
|
-
const { params
|
|
677
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
687
|
+
const { params } = deriveParamsFromAst(ast);
|
|
688
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
678
689
|
}
|
|
679
690
|
function compileUpdateCount(contract, tableName, setValues, filters) {
|
|
680
691
|
const where = combineWhereExprs(filters);
|
|
681
692
|
const { assignments } = toParamAssignments(contract, tableName, setValues);
|
|
682
693
|
let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments);
|
|
683
694
|
if (where) ast = ast.withWhere(where);
|
|
684
|
-
const { params
|
|
685
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
695
|
+
const { params } = deriveParamsFromAst(ast);
|
|
696
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
686
697
|
}
|
|
687
698
|
function compileDeleteReturning(contract, tableName, filters, returningColumns) {
|
|
688
699
|
const where = combineWhereExprs(filters);
|
|
689
700
|
let ast = DeleteAst.from(TableSource.named(tableName)).withReturning(buildReturningColumns(contract, tableName, returningColumns));
|
|
690
701
|
if (where) ast = ast.withWhere(where);
|
|
691
|
-
const { params
|
|
692
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
702
|
+
const { params } = deriveParamsFromAst(ast);
|
|
703
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
693
704
|
}
|
|
694
705
|
function compileDeleteCount(contract, tableName, filters) {
|
|
695
706
|
const where = combineWhereExprs(filters);
|
|
696
707
|
let ast = DeleteAst.from(TableSource.named(tableName));
|
|
697
708
|
if (where) ast = ast.withWhere(where);
|
|
698
|
-
const { params
|
|
699
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
709
|
+
const { params } = deriveParamsFromAst(ast);
|
|
710
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
700
711
|
}
|
|
701
|
-
|
|
702
712
|
//#endregion
|
|
703
713
|
//#region src/where-binding.ts
|
|
704
714
|
function bindWhereExpr(contract, expr) {
|
|
@@ -706,55 +716,55 @@ function bindWhereExpr(contract, expr) {
|
|
|
706
716
|
}
|
|
707
717
|
function bindWhereExprNode(contract, expr) {
|
|
708
718
|
return expr.accept({
|
|
709
|
-
columnRef(expr
|
|
710
|
-
return bindExpression(contract, expr
|
|
719
|
+
columnRef(expr) {
|
|
720
|
+
return bindExpression(contract, expr);
|
|
711
721
|
},
|
|
712
|
-
identifierRef(expr
|
|
713
|
-
return expr
|
|
722
|
+
identifierRef(expr) {
|
|
723
|
+
return expr;
|
|
714
724
|
},
|
|
715
|
-
subquery(expr
|
|
716
|
-
return bindExpression(contract, expr
|
|
725
|
+
subquery(expr) {
|
|
726
|
+
return bindExpression(contract, expr);
|
|
717
727
|
},
|
|
718
|
-
operation(expr
|
|
719
|
-
return bindExpression(contract, expr
|
|
728
|
+
operation(expr) {
|
|
729
|
+
return bindExpression(contract, expr);
|
|
720
730
|
},
|
|
721
|
-
aggregate(expr
|
|
722
|
-
return bindExpression(contract, expr
|
|
731
|
+
aggregate(expr) {
|
|
732
|
+
return bindExpression(contract, expr);
|
|
723
733
|
},
|
|
724
|
-
jsonObject(expr
|
|
725
|
-
return bindExpression(contract, expr
|
|
734
|
+
jsonObject(expr) {
|
|
735
|
+
return bindExpression(contract, expr);
|
|
726
736
|
},
|
|
727
|
-
jsonArrayAgg(expr
|
|
728
|
-
return bindExpression(contract, expr
|
|
737
|
+
jsonArrayAgg(expr) {
|
|
738
|
+
return bindExpression(contract, expr);
|
|
729
739
|
},
|
|
730
|
-
literal(expr
|
|
731
|
-
return expr
|
|
740
|
+
literal(expr) {
|
|
741
|
+
return expr;
|
|
732
742
|
},
|
|
733
|
-
param(expr
|
|
734
|
-
return expr
|
|
743
|
+
param(expr) {
|
|
744
|
+
return expr;
|
|
735
745
|
},
|
|
736
|
-
list(expr
|
|
737
|
-
return bindExpression(contract, expr
|
|
746
|
+
list(expr) {
|
|
747
|
+
return bindExpression(contract, expr);
|
|
738
748
|
},
|
|
739
|
-
binary(expr
|
|
740
|
-
const left = bindExpression(contract, expr
|
|
749
|
+
binary(expr) {
|
|
750
|
+
const left = bindExpression(contract, expr.left);
|
|
741
751
|
const bindingColumn = left.kind === "column-ref" ? left : void 0;
|
|
742
|
-
return new BinaryExpr(expr
|
|
752
|
+
return new BinaryExpr(expr.op, left, bindComparable(contract, expr.right, bindingColumn));
|
|
743
753
|
},
|
|
744
|
-
and(expr
|
|
745
|
-
return AndExpr.of(expr
|
|
754
|
+
and(expr) {
|
|
755
|
+
return AndExpr.of(expr.exprs.map((part) => bindWhereExprNode(contract, part)));
|
|
746
756
|
},
|
|
747
|
-
or(expr
|
|
748
|
-
return OrExpr.of(expr
|
|
757
|
+
or(expr) {
|
|
758
|
+
return OrExpr.of(expr.exprs.map((part) => bindWhereExprNode(contract, part)));
|
|
749
759
|
},
|
|
750
|
-
exists(expr
|
|
751
|
-
return expr
|
|
760
|
+
exists(expr) {
|
|
761
|
+
return expr.notExists ? ExistsExpr.notExists(bindSelectAst(contract, expr.subquery)) : ExistsExpr.exists(bindSelectAst(contract, expr.subquery));
|
|
752
762
|
},
|
|
753
|
-
nullCheck(expr
|
|
754
|
-
return expr
|
|
763
|
+
nullCheck(expr) {
|
|
764
|
+
return expr.isNull ? NullCheckExpr.isNull(bindExpression(contract, expr.expr)) : NullCheckExpr.isNotNull(bindExpression(contract, expr.expr));
|
|
755
765
|
},
|
|
756
|
-
not(expr
|
|
757
|
-
return new NotExpr(bindWhereExprNode(contract, expr
|
|
766
|
+
not(expr) {
|
|
767
|
+
return new NotExpr(bindWhereExprNode(contract, expr.expr));
|
|
758
768
|
}
|
|
759
769
|
});
|
|
760
770
|
}
|
|
@@ -767,7 +777,13 @@ function bindComparable(contract, comparable, bindingColumn) {
|
|
|
767
777
|
function createParamRef(contract, columnRef, value) {
|
|
768
778
|
const codecId = contract.storage.tables[columnRef.table]?.columns[columnRef.column]?.codecId;
|
|
769
779
|
if (!codecId) throw new Error(`Unknown column "${columnRef.column}" in table "${columnRef.table}"`);
|
|
770
|
-
return ParamRef.of(value, {
|
|
780
|
+
return ParamRef.of(value, {
|
|
781
|
+
codecId,
|
|
782
|
+
refs: {
|
|
783
|
+
table: columnRef.table,
|
|
784
|
+
column: columnRef.column
|
|
785
|
+
}
|
|
786
|
+
});
|
|
771
787
|
}
|
|
772
788
|
function createExpressionBinder(contract) {
|
|
773
789
|
return { select: (ast) => bindSelectAst(contract, ast) };
|
|
@@ -796,7 +812,7 @@ function bindSelectAst(contract, ast) {
|
|
|
796
812
|
return new SelectAst({
|
|
797
813
|
from: bindFromSource(contract, ast.from),
|
|
798
814
|
joins: ast.joins?.map((join) => bindJoin(contract, join)),
|
|
799
|
-
projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr))),
|
|
815
|
+
projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr), projection.codecId, projection.refs)),
|
|
800
816
|
where: ast.where ? bindWhereExprNode(contract, ast.where) : void 0,
|
|
801
817
|
orderBy: ast.orderBy?.map((orderItem) => bindOrderByItem(contract, orderItem)),
|
|
802
818
|
distinct: ast.distinct,
|
|
@@ -808,11 +824,12 @@ function bindSelectAst(contract, ast) {
|
|
|
808
824
|
selectAllIntent: ast.selectAllIntent
|
|
809
825
|
});
|
|
810
826
|
}
|
|
811
|
-
|
|
812
827
|
//#endregion
|
|
813
828
|
//#region src/query-plan-select.ts
|
|
814
829
|
function buildProjection(contract, tableName, selectedFields, tableRef = tableName) {
|
|
815
|
-
|
|
830
|
+
const columns = selectedFields && selectedFields.length > 0 ? [...selectedFields] : resolveTableColumns(contract, tableName);
|
|
831
|
+
const table = contract.storage.tables[tableName];
|
|
832
|
+
return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableRef, column), table?.columns[column]?.codecId));
|
|
816
833
|
}
|
|
817
834
|
function createBoundaryExpr(tableName, entry) {
|
|
818
835
|
return new BinaryExpr(entry.direction === "asc" ? "gt" : "lt", ColumnRef.of(tableName, entry.column), LiteralExpr.of(entry.value));
|
|
@@ -946,10 +963,11 @@ function buildMtiJoins(contract, polyInfo, variantName) {
|
|
|
946
963
|
const join = joinType === "inner" ? JoinAst.inner(TableSource.named(variant.table), joinOn) : JoinAst.left(TableSource.named(variant.table), joinOn);
|
|
947
964
|
joins.push(join);
|
|
948
965
|
const variantColumns = resolveTableColumns(contract, variant.table);
|
|
966
|
+
const variantTable = contract.storage.tables[variant.table];
|
|
949
967
|
for (const col of variantColumns) {
|
|
950
968
|
if (col === pkColumn) continue;
|
|
951
969
|
const alias = `${variant.table}__${col}`;
|
|
952
|
-
projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col)));
|
|
970
|
+
projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col), variantTable?.columns[col]?.codecId));
|
|
953
971
|
}
|
|
954
972
|
}
|
|
955
973
|
return {
|
|
@@ -967,8 +985,8 @@ function compileSelect(contract, tableName, state, modelName) {
|
|
|
967
985
|
joins: mtiArtifacts.joins,
|
|
968
986
|
includeProjection: mtiArtifacts.projection
|
|
969
987
|
} : void 0);
|
|
970
|
-
const { params
|
|
971
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
988
|
+
const { params } = deriveParamsFromAst(ast);
|
|
989
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
972
990
|
}
|
|
973
991
|
function compileRelationSelect(contract, relatedTableName, targetColumn, parentPks, nestedState) {
|
|
974
992
|
const inFilter = BinaryExpr.in(ColumnRef.of(relatedTableName, targetColumn), ListExpression.fromValues(parentPks));
|
|
@@ -993,9 +1011,9 @@ function compileSelectWithIncludeStrategy(contract, tableName, state, strategy,
|
|
|
993
1011
|
}
|
|
994
1012
|
for (const include of state.includes) {
|
|
995
1013
|
if (strategy === "lateral") {
|
|
996
|
-
const artifact
|
|
997
|
-
includeJoins.push(artifact
|
|
998
|
-
includeProjection.push(artifact
|
|
1014
|
+
const artifact = buildLateralIncludeArtifacts(contract, tableName, include);
|
|
1015
|
+
includeJoins.push(artifact.join);
|
|
1016
|
+
includeProjection.push(artifact.projection);
|
|
999
1017
|
continue;
|
|
1000
1018
|
}
|
|
1001
1019
|
const artifact = buildCorrelatedIncludeProjection(contract, tableName, include);
|
|
@@ -1009,10 +1027,9 @@ function compileSelectWithIncludeStrategy(contract, tableName, state, strategy,
|
|
|
1009
1027
|
includeProjection,
|
|
1010
1028
|
...topLevelWhere ? { where: topLevelWhere } : {}
|
|
1011
1029
|
});
|
|
1012
|
-
const { params
|
|
1013
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
1030
|
+
const { params } = deriveParamsFromAst(ast);
|
|
1031
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
1014
1032
|
}
|
|
1015
|
-
|
|
1016
1033
|
//#endregion
|
|
1017
1034
|
//#region src/collection-dispatch.ts
|
|
1018
1035
|
function dispatchCollectionRows(options) {
|
|
@@ -1282,7 +1299,6 @@ function coerceNumericValue(value) {
|
|
|
1282
1299
|
}
|
|
1283
1300
|
return null;
|
|
1284
1301
|
}
|
|
1285
|
-
|
|
1286
1302
|
//#endregion
|
|
1287
1303
|
//#region src/collection-mutation-dispatch.ts
|
|
1288
1304
|
function dispatchMutationRows(options) {
|
|
@@ -1364,7 +1380,6 @@ async function executeMutationReturningSingleRow(options) {
|
|
|
1364
1380
|
if (release) await release();
|
|
1365
1381
|
}
|
|
1366
1382
|
}
|
|
1367
|
-
|
|
1368
1383
|
//#endregion
|
|
1369
1384
|
//#region src/filters.ts
|
|
1370
1385
|
function and(...exprs) {
|
|
@@ -1401,9 +1416,8 @@ function shorthandToWhereExpr(context, modelName, filters) {
|
|
|
1401
1416
|
function assertFieldHasEqualityTrait(context, modelName, fieldName) {
|
|
1402
1417
|
const fieldType = modelOf(context.contract, modelName)?.fields?.[fieldName]?.type;
|
|
1403
1418
|
const codecId = fieldType?.kind === "scalar" ? fieldType.codecId : void 0;
|
|
1404
|
-
if (!(codecId ? context.
|
|
1419
|
+
if (!(codecId ? context.codecDescriptors.descriptorFor(codecId)?.traits ?? [] : []).includes("equality")) throw new Error(`Shorthand filter on "${modelName}.${fieldName}": field does not support equality comparisons`);
|
|
1405
1420
|
}
|
|
1406
|
-
|
|
1407
1421
|
//#endregion
|
|
1408
1422
|
//#region src/grouped-collection.ts
|
|
1409
1423
|
var GroupedCollection = class GroupedCollection {
|
|
@@ -1503,7 +1517,6 @@ function coerceAggregateValue(fn, value) {
|
|
|
1503
1517
|
}
|
|
1504
1518
|
return value;
|
|
1505
1519
|
}
|
|
1506
|
-
|
|
1507
1520
|
//#endregion
|
|
1508
1521
|
//#region src/include-descriptors.ts
|
|
1509
1522
|
const aggregateFns = new Set([
|
|
@@ -1548,7 +1561,6 @@ function isCollectionState(value) {
|
|
|
1548
1561
|
const candidate = value;
|
|
1549
1562
|
return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
|
|
1550
1563
|
}
|
|
1551
|
-
|
|
1552
1564
|
//#endregion
|
|
1553
1565
|
//#region src/types.ts
|
|
1554
1566
|
function emptyState() {
|
|
@@ -1565,17 +1577,40 @@ function emptyState() {
|
|
|
1565
1577
|
variantName: void 0
|
|
1566
1578
|
};
|
|
1567
1579
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1580
|
+
/**
|
|
1581
|
+
* Resolve the unique column ref carried by `left` so its surrounding `ParamRef` can dispatch through `forColumn`. The previous implementation only matched bare `column-ref` expressions, which lost refs the moment the expression got wrapped (`upper(f.id)`, `BinaryExpr`, function-call expressions, etc.) — and column-aware dispatch (AC-5) silently degraded for any predicate that touched a computed column.
|
|
1582
|
+
*
|
|
1583
|
+
* Walking via `collectColumnRefs()` and accepting only a single unambiguous ref gives `eq(upper(f.id), value)` and friends correct dispatch without inventing refs for ambiguous shapes (e.g. `eq(concat(f.firstName, f.lastName), value)` returns `undefined` and falls through to the codec-id path, which is correct for non-parameterized comparisons).
|
|
1584
|
+
*/
|
|
1585
|
+
function refsFromLeft(left) {
|
|
1586
|
+
if (left.kind === "column-ref") return {
|
|
1587
|
+
table: left.table,
|
|
1588
|
+
column: left.column
|
|
1589
|
+
};
|
|
1590
|
+
const columnRefs = left.collectColumnRefs();
|
|
1591
|
+
if (columnRefs.length !== 1) return void 0;
|
|
1592
|
+
const single = columnRefs[0];
|
|
1593
|
+
if (!single) return void 0;
|
|
1594
|
+
return {
|
|
1595
|
+
table: single.table,
|
|
1596
|
+
column: single.column
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
function param(codecId, value, refs) {
|
|
1600
|
+
if (codecId === void 0 && refs === void 0) return ParamRef.of(value);
|
|
1601
|
+
return ParamRef.of(value, {
|
|
1602
|
+
...ifDefined("codecId", codecId),
|
|
1603
|
+
...ifDefined("refs", refs)
|
|
1604
|
+
});
|
|
1570
1605
|
}
|
|
1571
|
-
function paramList(codecId, values) {
|
|
1572
|
-
return ListExpression.of(values.map((value) => param(codecId, value)));
|
|
1606
|
+
function paramList(codecId, values, refs) {
|
|
1607
|
+
return ListExpression.of(values.map((value) => param(codecId, value, refs)));
|
|
1573
1608
|
}
|
|
1574
1609
|
function scalarComparisonMethod(op) {
|
|
1575
|
-
return ((left, codecId) => (value) => new BinaryExpr(op, left, param(codecId, value)));
|
|
1610
|
+
return ((left, codecId) => (value) => new BinaryExpr(op, left, param(codecId, value, refsFromLeft(left))));
|
|
1576
1611
|
}
|
|
1577
1612
|
function listComparisonMethod(op) {
|
|
1578
|
-
return ((left, codecId) => (values) => new BinaryExpr(op, left, paramList(codecId, values)));
|
|
1613
|
+
return ((left, codecId) => (values) => new BinaryExpr(op, left, paramList(codecId, values, refsFromLeft(left))));
|
|
1579
1614
|
}
|
|
1580
1615
|
/**
|
|
1581
1616
|
* Declares trait requirements and runtime factory for each comparison method.
|
|
@@ -1637,7 +1672,6 @@ const COMPARISON_METHODS_META = {
|
|
|
1637
1672
|
create: (left) => () => NullCheckExpr.isNotNull(left)
|
|
1638
1673
|
}
|
|
1639
1674
|
};
|
|
1640
|
-
|
|
1641
1675
|
//#endregion
|
|
1642
1676
|
//#region src/model-accessor.ts
|
|
1643
1677
|
function createModelAccessor(context, modelName) {
|
|
@@ -1655,12 +1689,13 @@ function createModelAccessor(context, modelName) {
|
|
|
1655
1689
|
existing.push(op);
|
|
1656
1690
|
}
|
|
1657
1691
|
for (const [name, entry] of Object.entries(context.queryOperations.entries())) {
|
|
1658
|
-
const self = entry.args[0];
|
|
1659
1692
|
const op = [name, entry];
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1693
|
+
const self = entry.self;
|
|
1694
|
+
if (!self) continue;
|
|
1695
|
+
if (self.codecId !== void 0) registerOp(self.codecId, op);
|
|
1696
|
+
else if (self.traits !== void 0) for (const descriptor of context.codecDescriptors.values()) {
|
|
1697
|
+
const descriptorTraits = descriptor.traits;
|
|
1698
|
+
if (self.traits.every((t) => descriptorTraits.includes(t))) registerOp(descriptor.codecId, op);
|
|
1664
1699
|
}
|
|
1665
1700
|
}
|
|
1666
1701
|
return new Proxy({}, { get(_target, prop) {
|
|
@@ -1668,48 +1703,51 @@ function createModelAccessor(context, modelName) {
|
|
|
1668
1703
|
const relation = modelRelations[prop];
|
|
1669
1704
|
if (relation) return createRelationFilterAccessor(context, modelName, tableName, relation);
|
|
1670
1705
|
const columnName = fieldToColumn[prop] ?? prop;
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1706
|
+
const column = resolveColumn(contract, tableName, columnName);
|
|
1707
|
+
if (!column) return;
|
|
1708
|
+
const traits = context.codecDescriptors.descriptorFor(column.codecId)?.traits ?? [];
|
|
1709
|
+
const operations = opsByCodecId.get(column.codecId) ?? [];
|
|
1710
|
+
return createScalarFieldAccessor(tableName, columnName, column.codecId, column.nullable, traits, operations, context);
|
|
1674
1711
|
} });
|
|
1675
1712
|
}
|
|
1676
|
-
function
|
|
1677
|
-
const
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
return (contract.storage.tables?.[tableName])?.columns?.[columnName]?.codecId;
|
|
1713
|
+
function resolveColumn(contract, tableName, columnName) {
|
|
1714
|
+
const column = contract.storage.tables?.[tableName]?.columns?.[columnName];
|
|
1715
|
+
if (!column) return void 0;
|
|
1716
|
+
return {
|
|
1717
|
+
codecId: column.codecId,
|
|
1718
|
+
nullable: column.nullable
|
|
1719
|
+
};
|
|
1684
1720
|
}
|
|
1685
|
-
function createScalarFieldAccessor(tableName, columnName, codecId, traits, operations, context) {
|
|
1721
|
+
function createScalarFieldAccessor(tableName, columnName, codecId, nullable, traits, operations, context) {
|
|
1686
1722
|
const column = ColumnRef.of(tableName, columnName);
|
|
1687
|
-
const
|
|
1723
|
+
const comparisonEntries = [];
|
|
1688
1724
|
for (const [name, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
1689
1725
|
if (meta.traits.some((t) => !traits.includes(t))) continue;
|
|
1690
|
-
|
|
1726
|
+
comparisonEntries.push([name, meta.create(column, codecId)]);
|
|
1691
1727
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1728
|
+
const accessor = {
|
|
1729
|
+
returnType: {
|
|
1730
|
+
codecId,
|
|
1731
|
+
nullable
|
|
1732
|
+
},
|
|
1733
|
+
buildAst: () => column,
|
|
1734
|
+
...Object.fromEntries(comparisonEntries)
|
|
1735
|
+
};
|
|
1736
|
+
for (const [name, entry] of operations) accessor[name] = createExtensionMethodFactory(accessor, entry, context);
|
|
1737
|
+
return accessor;
|
|
1694
1738
|
}
|
|
1695
|
-
function createExtensionMethodFactory(
|
|
1696
|
-
const returnTraits = context.codecs.traitsOf(entry.returns.codecId);
|
|
1697
|
-
const isPredicate = returnTraits.includes("boolean");
|
|
1739
|
+
function createExtensionMethodFactory(selfExpr, entry, context) {
|
|
1698
1740
|
return (...args) => {
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
returns: entry.returns,
|
|
1706
|
-
lowering: entry.lowering
|
|
1707
|
-
});
|
|
1708
|
-
if (isPredicate) return opExpr;
|
|
1741
|
+
const impl = entry.impl;
|
|
1742
|
+
const result = impl(selfExpr, ...args);
|
|
1743
|
+
const returnCodecId = result.returnType.codecId;
|
|
1744
|
+
const returnTraits = context.codecDescriptors.descriptorFor(returnCodecId)?.traits ?? [];
|
|
1745
|
+
if (returnTraits.includes("boolean")) return result.buildAst();
|
|
1746
|
+
const resultAst = result.buildAst();
|
|
1709
1747
|
const methods = {};
|
|
1710
1748
|
for (const [resultMethodName, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
1711
1749
|
if (meta.traits.some((t) => !returnTraits.includes(t))) continue;
|
|
1712
|
-
methods[resultMethodName] = meta.create(
|
|
1750
|
+
methods[resultMethodName] = meta.create(resultAst, returnCodecId);
|
|
1713
1751
|
}
|
|
1714
1752
|
return methods;
|
|
1715
1753
|
};
|
|
@@ -1756,7 +1794,7 @@ function toRelationWhereExpr(context, relatedModelName, predicate) {
|
|
|
1756
1794
|
for (const [fieldName, value] of Object.entries(predicate)) {
|
|
1757
1795
|
if (value === void 0) continue;
|
|
1758
1796
|
const fieldAccessor = accessor[fieldName];
|
|
1759
|
-
if (!fieldAccessor)
|
|
1797
|
+
if (!fieldAccessor) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": field is not defined on the model`);
|
|
1760
1798
|
if (value === null) {
|
|
1761
1799
|
if (!fieldAccessor.isNull) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": isNull is unexpectedly missing — this is a bug in trait gating`);
|
|
1762
1800
|
exprs.push(fieldAccessor.isNull());
|
|
@@ -1791,7 +1829,6 @@ function firstTargetColumn(contract, relation) {
|
|
|
1791
1829
|
if (!firstField) return;
|
|
1792
1830
|
return resolveFieldToColumn(contract, relation.to, firstField);
|
|
1793
1831
|
}
|
|
1794
|
-
|
|
1795
1832
|
//#endregion
|
|
1796
1833
|
//#region src/relation-mutator.ts
|
|
1797
1834
|
function createRelationMutator() {
|
|
@@ -1826,7 +1863,6 @@ function isRelationMutationDescriptor(value) {
|
|
|
1826
1863
|
function isRelationMutationCallback(value) {
|
|
1827
1864
|
return typeof value === "function";
|
|
1828
1865
|
}
|
|
1829
|
-
|
|
1830
1866
|
//#endregion
|
|
1831
1867
|
//#region src/mutation-executor.ts
|
|
1832
1868
|
function hasNestedMutationCallbacks(contract, modelName, data) {
|
|
@@ -1895,9 +1931,16 @@ async function updateFirstGraph(scope, context, modelName, filters, input) {
|
|
|
1895
1931
|
let parentRow = existingRow;
|
|
1896
1932
|
const mappedUpdateData = mapModelDataToStorageRow(contract, modelName, scalarData);
|
|
1897
1933
|
if (Object.keys(mappedUpdateData).length > 0) {
|
|
1934
|
+
const tableName = resolveModelTableName(contract, modelName);
|
|
1935
|
+
const appliedUpdateDefaults = context.applyMutationDefaults({
|
|
1936
|
+
op: "update",
|
|
1937
|
+
table: tableName,
|
|
1938
|
+
values: mappedUpdateData
|
|
1939
|
+
});
|
|
1940
|
+
for (const def of appliedUpdateDefaults) mappedUpdateData[def.column] = def.value;
|
|
1898
1941
|
const pkWhere = shorthandToWhereExpr(context, modelName, buildPrimaryKeyFilterFromRow(contract, modelName, existingRow));
|
|
1899
1942
|
if (!pkWhere) throw new Error(`Failed to build primary key filter for model "${modelName}"`);
|
|
1900
|
-
const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract,
|
|
1943
|
+
const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract, tableName, mappedUpdateData, [pkWhere], void 0)).toArray())[0];
|
|
1901
1944
|
if (updatedRaw) parentRow = mapStorageRowToModelFields(contract, modelName, updatedRaw);
|
|
1902
1945
|
}
|
|
1903
1946
|
for (const relationMutation of childOwned) await applyChildOwnedMutation(scope, context, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
@@ -1990,9 +2033,9 @@ async function applyChildOwnedMutation(scope, context, parentModelName, parentRo
|
|
|
1990
2033
|
for (const criterion of mutation.criteria) {
|
|
1991
2034
|
const criterionWhere = shorthandToWhereExpr(context, relation.relatedModelName, criterion);
|
|
1992
2035
|
if (!criterionWhere) throw new Error(`connect() nested mutation for relation "${relation.relationName}" requires non-empty criterion`);
|
|
1993
|
-
const setValues
|
|
1994
|
-
for (const [childColumn, parentValue] of parentValues.entries()) setValues
|
|
1995
|
-
await executeUpdateCount(scope, contract, relation.relatedTableName, setValues
|
|
2036
|
+
const setValues = {};
|
|
2037
|
+
for (const [childColumn, parentValue] of parentValues.entries()) setValues[childColumn] = parentValue;
|
|
2038
|
+
await executeUpdateCount(scope, contract, relation.relatedTableName, setValues, [criterionWhere]);
|
|
1996
2039
|
}
|
|
1997
2040
|
return;
|
|
1998
2041
|
}
|
|
@@ -2092,7 +2135,6 @@ function getRelationDefinitions(contract, modelName) {
|
|
|
2092
2135
|
function toFieldName(contract, modelName, columnName) {
|
|
2093
2136
|
return getColumnToFieldMap(contract, modelName)[columnName] ?? columnName;
|
|
2094
2137
|
}
|
|
2095
|
-
|
|
2096
2138
|
//#endregion
|
|
2097
2139
|
//#region src/where-interop.ts
|
|
2098
2140
|
function normalizeWhereArg(arg, options) {
|
|
@@ -2105,19 +2147,28 @@ function normalizeWhereArg(arg, options) {
|
|
|
2105
2147
|
function isToWhereExpr(arg) {
|
|
2106
2148
|
return typeof arg === "object" && arg !== null && "toWhereExpr" in arg && !isWhereExpr(arg);
|
|
2107
2149
|
}
|
|
2108
|
-
|
|
2109
2150
|
//#endregion
|
|
2110
2151
|
//#region src/collection.ts
|
|
2111
2152
|
function applyCreateDefaults(ctx, tableName, rows) {
|
|
2153
|
+
const defaultValueCache = rows.length > 1 ? /* @__PURE__ */ new Map() : void 0;
|
|
2112
2154
|
for (const row of rows) {
|
|
2113
2155
|
const applied = ctx.context.applyMutationDefaults({
|
|
2114
2156
|
op: "create",
|
|
2115
2157
|
table: tableName,
|
|
2116
|
-
values: row
|
|
2158
|
+
values: row,
|
|
2159
|
+
...defaultValueCache ? { defaultValueCache } : {}
|
|
2117
2160
|
});
|
|
2118
2161
|
for (const def of applied) row[def.column] = def.value;
|
|
2119
2162
|
}
|
|
2120
2163
|
}
|
|
2164
|
+
function applyUpdateDefaults(ctx, tableName, values) {
|
|
2165
|
+
const applied = ctx.context.applyMutationDefaults({
|
|
2166
|
+
op: "update",
|
|
2167
|
+
table: tableName,
|
|
2168
|
+
values
|
|
2169
|
+
});
|
|
2170
|
+
for (const def of applied) values[def.column] = def.value;
|
|
2171
|
+
}
|
|
2121
2172
|
function isToWhereExprInput(value) {
|
|
2122
2173
|
return typeof value === "object" && value !== null && "toWhereExpr" in value && typeof value.toWhereExpr === "function";
|
|
2123
2174
|
}
|
|
@@ -2480,6 +2531,7 @@ var Collection = class Collection {
|
|
|
2480
2531
|
applyCreateDefaults(this.ctx, this.tableName, [createValues]);
|
|
2481
2532
|
const updateValues = mapModelDataToStorageRow(this.contract, this.modelName, input.update);
|
|
2482
2533
|
const hasUpdateValues = Object.keys(updateValues).length > 0;
|
|
2534
|
+
if (hasUpdateValues) applyUpdateDefaults(this.ctx, this.tableName, updateValues);
|
|
2483
2535
|
const conflictColumns = resolveUpsertConflictColumns(this.contract, this.modelName, input.conflictOn);
|
|
2484
2536
|
if (conflictColumns.length === 0) throw new Error(`upsert() for model "${this.modelName}" requires conflict columns`);
|
|
2485
2537
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
@@ -2517,7 +2569,12 @@ var Collection = class Collection {
|
|
|
2517
2569
|
const pkCriterion = buildPrimaryKeyFilterFromRow(this.contract, this.modelName, updatedRow);
|
|
2518
2570
|
return this.#reloadMutationRowByPrimaryKey(pkCriterion);
|
|
2519
2571
|
}
|
|
2520
|
-
return (
|
|
2572
|
+
return withMutationScope(this.ctx.runtime, async (scope) => {
|
|
2573
|
+
const scoped = this.#withRuntime(scope);
|
|
2574
|
+
const identityWhere = await scoped.#findFirstMatchingRowIdentityWhere();
|
|
2575
|
+
if (!identityWhere) return null;
|
|
2576
|
+
return (await scoped.#clone({ filters: [identityWhere] }).updateAll(data))[0] ?? null;
|
|
2577
|
+
});
|
|
2521
2578
|
}
|
|
2522
2579
|
updateAll(data) {
|
|
2523
2580
|
assertReturningCapability(this.contract, "updateAll()");
|
|
@@ -2526,6 +2583,7 @@ var Collection = class Collection {
|
|
|
2526
2583
|
const generator = async function* () {};
|
|
2527
2584
|
return new AsyncIterableResult(generator());
|
|
2528
2585
|
}
|
|
2586
|
+
applyUpdateDefaults(this.ctx, this.tableName, mappedData);
|
|
2529
2587
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
2530
2588
|
const { selectedForQuery: selectedForUpdate, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
|
|
2531
2589
|
const compiled = compileUpdateReturning(this.contract, this.tableName, mappedData, this.state.filters, selectedForUpdate);
|
|
@@ -2542,6 +2600,7 @@ var Collection = class Collection {
|
|
|
2542
2600
|
async updateCount(data) {
|
|
2543
2601
|
const mappedData = mapModelDataToStorageRow(this.contract, this.modelName, data);
|
|
2544
2602
|
if (Object.keys(mappedData).length === 0) return 0;
|
|
2603
|
+
applyUpdateDefaults(this.ctx, this.tableName, mappedData);
|
|
2545
2604
|
const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.tableName);
|
|
2546
2605
|
const countState = {
|
|
2547
2606
|
...emptyState(),
|
|
@@ -2556,10 +2615,18 @@ var Collection = class Collection {
|
|
|
2556
2615
|
}
|
|
2557
2616
|
async delete() {
|
|
2558
2617
|
assertReturningCapability(this.contract, "delete()");
|
|
2559
|
-
return (
|
|
2618
|
+
return withMutationScope(this.ctx.runtime, async (scope) => {
|
|
2619
|
+
const scoped = this.#withRuntime(scope);
|
|
2620
|
+
const identityWhere = await scoped.#findFirstMatchingRowIdentityWhere();
|
|
2621
|
+
if (!identityWhere) return null;
|
|
2622
|
+
return (await scoped.#clone({ filters: [identityWhere] }).#executeDeleteReturning().toArray())[0] ?? null;
|
|
2623
|
+
});
|
|
2560
2624
|
}
|
|
2561
2625
|
deleteAll() {
|
|
2562
2626
|
assertReturningCapability(this.contract, "deleteAll()");
|
|
2627
|
+
return this.#executeDeleteReturning();
|
|
2628
|
+
}
|
|
2629
|
+
#executeDeleteReturning() {
|
|
2563
2630
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
2564
2631
|
const { selectedForQuery: selectedForDelete, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
|
|
2565
2632
|
const compiled = compileDeleteReturning(this.contract, this.tableName, this.state.filters, selectedForDelete);
|
|
@@ -2596,6 +2663,24 @@ var Collection = class Collection {
|
|
|
2596
2663
|
}
|
|
2597
2664
|
return criterion;
|
|
2598
2665
|
}
|
|
2666
|
+
async #findFirstMatchingRowIdentityWhere() {
|
|
2667
|
+
const identityColumns = resolveRowIdentityColumns(this.contract, this.tableName);
|
|
2668
|
+
if (identityColumns.length === 0) throw new Error(`update()/delete() on model "${this.modelName}" requires the table to have a primary key or unique constraint`);
|
|
2669
|
+
const firstRow = await this.#clone({
|
|
2670
|
+
selectedFields: [...identityColumns],
|
|
2671
|
+
includes: []
|
|
2672
|
+
}).first();
|
|
2673
|
+
if (!firstRow) return null;
|
|
2674
|
+
const columnToField = getColumnToFieldMap(this.contract, this.modelName);
|
|
2675
|
+
const criterion = {};
|
|
2676
|
+
for (const column of identityColumns) {
|
|
2677
|
+
const fieldName = columnToField[column] ?? column;
|
|
2678
|
+
const value = firstRow[fieldName];
|
|
2679
|
+
if (value === void 0) throw new Error(`Missing identity field "${fieldName}" while resolving single-row scope for model "${this.modelName}"`);
|
|
2680
|
+
criterion[fieldName] = value;
|
|
2681
|
+
}
|
|
2682
|
+
return shorthandToWhereExpr(this.ctx.context, this.modelName, criterion) ?? null;
|
|
2683
|
+
}
|
|
2599
2684
|
async #reloadMutationRowByPrimaryKey(criterion) {
|
|
2600
2685
|
return this.#reloadMutationRowByCriterion(criterion, "primary key");
|
|
2601
2686
|
}
|
|
@@ -2627,6 +2712,18 @@ var Collection = class Collection {
|
|
|
2627
2712
|
...overrides
|
|
2628
2713
|
});
|
|
2629
2714
|
}
|
|
2715
|
+
#withRuntime(runtime) {
|
|
2716
|
+
const Ctor = this.constructor;
|
|
2717
|
+
return new Ctor({
|
|
2718
|
+
...this.ctx,
|
|
2719
|
+
runtime
|
|
2720
|
+
}, this.modelName, {
|
|
2721
|
+
tableName: this.tableName,
|
|
2722
|
+
state: this.state,
|
|
2723
|
+
registry: this.registry,
|
|
2724
|
+
includeRefinementMode: this.includeRefinementMode
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2630
2727
|
#cloneWithRow(overrides) {
|
|
2631
2728
|
return this.#createSelf({
|
|
2632
2729
|
...this.state,
|
|
@@ -2660,7 +2757,6 @@ var Collection = class Collection {
|
|
|
2660
2757
|
});
|
|
2661
2758
|
}
|
|
2662
2759
|
};
|
|
2663
|
-
|
|
2664
2760
|
//#endregion
|
|
2665
2761
|
//#region src/orm.ts
|
|
2666
2762
|
function orm(options) {
|
|
@@ -2702,7 +2798,7 @@ function isCollectionClass(value) {
|
|
|
2702
2798
|
if (!candidate.prototype || typeof candidate.prototype !== "object") return false;
|
|
2703
2799
|
return candidate.prototype instanceof Collection;
|
|
2704
2800
|
}
|
|
2705
|
-
|
|
2706
2801
|
//#endregion
|
|
2707
2802
|
export { Collection, GroupedCollection, all, and, emptyState, not, or, orm };
|
|
2803
|
+
|
|
2708
2804
|
//# sourceMappingURL=index.mjs.map
|