@prisma-next/sql-orm-client 0.5.0-dev.6 → 0.5.0-dev.60
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 +31 -1
- package/dist/index.d.mts +38 -19
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +157 -124
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -19
- package/src/collection-dispatch.ts +18 -2
- package/src/collection-mutation-dispatch.ts +1 -1
- package/src/collection-runtime.ts +3 -2
- package/src/collection.ts +29 -1
- 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 +20 -16
- package/src/query-plan-select.ts +16 -6
- package/src/types.ts +58 -45
- package/src/where-binding.ts +5 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
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,
|
|
3
|
-
import { ifDefined } from "@prisma-next/utils/defined";
|
|
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";
|
|
4
3
|
|
|
5
4
|
//#region src/collection-contract.ts
|
|
6
5
|
function modelsOf(contract) {
|
|
@@ -398,83 +397,70 @@ function executeQueryPlan(scope, plan) {
|
|
|
398
397
|
|
|
399
398
|
//#endregion
|
|
400
399
|
//#region src/include-strategy.ts
|
|
400
|
+
/**
|
|
401
|
+
* Choose the SQL emission strategy for nested includes based on the
|
|
402
|
+
* contract's declared capabilities.
|
|
403
|
+
*
|
|
404
|
+
* - `'lateral'`: outer SELECT with one LATERAL JOIN per relation,
|
|
405
|
+
* aggregating to JSON. Requires both `lateral` and `jsonAgg`.
|
|
406
|
+
* Postgres has both.
|
|
407
|
+
* - `'correlated'`: outer SELECT with one correlated subquery per
|
|
408
|
+
* relation, aggregating to JSON. Requires `jsonAgg` only.
|
|
409
|
+
* SQLite has `jsonAgg` (via `json_group_array`) but no LATERAL.
|
|
410
|
+
* - `'multiQuery'`: fallback. One SELECT per relation, stitched
|
|
411
|
+
* together in JS via `WHERE pk IN (parent-pk-values)`. Always
|
|
412
|
+
* correct; just N+1 round-trips.
|
|
413
|
+
*
|
|
414
|
+
* The capability flags are looked up under the contract's
|
|
415
|
+
* `targetFamily` and `target` namespaces — the two layers the contract
|
|
416
|
+
* emitter actually populates. Cross-namespace ("`postgres.lateral`
|
|
417
|
+
* found while running SQLite") false positives are impossible because
|
|
418
|
+
* we only inspect the running target's namespaces.
|
|
419
|
+
*/
|
|
401
420
|
function selectIncludeStrategy(contract) {
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
const hasJsonAgg = hasCapability(capabilities?.["jsonAgg"]);
|
|
421
|
+
const hasLateral = capabilityFlag(contract, "lateral");
|
|
422
|
+
const hasJsonAgg = capabilityFlag(contract, "jsonAgg");
|
|
405
423
|
if (hasLateral && hasJsonAgg) return "lateral";
|
|
406
424
|
if (hasJsonAgg) return "correlated";
|
|
407
425
|
return "multiQuery";
|
|
408
426
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Read a capability flag from the contract's target/family namespaces.
|
|
429
|
+
*
|
|
430
|
+
* The contract emitter populates `capabilities[targetFamily]` (universal
|
|
431
|
+
* SQL flags like `jsonAgg`, `returning`) and `capabilities[target]`
|
|
432
|
+
* (target-specific flags like `lateral` on Postgres). Either may
|
|
433
|
+
* declare a given flag; the family namespace declares the floor and the
|
|
434
|
+
* target namespace can extend on top.
|
|
435
|
+
*/
|
|
436
|
+
function capabilityFlag(contract, flag) {
|
|
437
|
+
return contract.capabilities[contract.targetFamily]?.[flag] === true || contract.capabilities[contract.target]?.[flag] === true;
|
|
414
438
|
}
|
|
415
439
|
|
|
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
466
|
|
|
@@ -488,10 +474,21 @@ function combineWhereExprs(filters) {
|
|
|
488
474
|
|
|
489
475
|
//#endregion
|
|
490
476
|
//#region src/query-plan-aggregate.ts
|
|
491
|
-
function
|
|
492
|
-
if (selector.fn === "count") return
|
|
477
|
+
function toAggregateProjection(contract, tableName, selector) {
|
|
478
|
+
if (selector.fn === "count") return {
|
|
479
|
+
expr: AggregateExpr.count(),
|
|
480
|
+
codecId: void 0
|
|
481
|
+
};
|
|
493
482
|
if (!selector.column) throw new Error(`Aggregate selector "${selector.fn}" requires a field`);
|
|
494
|
-
|
|
483
|
+
const expr = new AggregateExpr(selector.fn, ColumnRef.of(tableName, selector.column));
|
|
484
|
+
if (selector.fn === "min" || selector.fn === "max") return {
|
|
485
|
+
expr,
|
|
486
|
+
codecId: contract.storage.tables[tableName]?.columns[selector.column]?.codecId
|
|
487
|
+
};
|
|
488
|
+
return {
|
|
489
|
+
expr,
|
|
490
|
+
codecId: void 0
|
|
491
|
+
};
|
|
495
492
|
}
|
|
496
493
|
function validateGroupedComparable(value) {
|
|
497
494
|
switch (value.kind) {
|
|
@@ -551,30 +548,39 @@ function validateGroupedHavingExpr(expr) {
|
|
|
551
548
|
function compileAggregate(contract, tableName, filters, aggregateSpec) {
|
|
552
549
|
const entries = Object.entries(aggregateSpec);
|
|
553
550
|
if (entries.length === 0) throw new Error("aggregate() requires at least one aggregation selector");
|
|
554
|
-
const projection = entries.map(([alias, selector]) =>
|
|
551
|
+
const projection = entries.map(([alias, selector]) => {
|
|
552
|
+
const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
|
|
553
|
+
return ProjectionItem.of(alias, expr, codecId);
|
|
554
|
+
});
|
|
555
555
|
let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection);
|
|
556
556
|
const where = combineWhereExprs(filters);
|
|
557
557
|
if (where) ast = ast.withWhere(where);
|
|
558
|
-
const { params
|
|
559
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
558
|
+
const { params } = deriveParamsFromAst(ast);
|
|
559
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
560
560
|
}
|
|
561
561
|
function compileGroupedAggregate(contract, tableName, filters, groupByColumns, aggregateSpec, havingExpr) {
|
|
562
562
|
if (groupByColumns.length === 0) throw new Error("groupBy() requires at least one field");
|
|
563
563
|
const entries = Object.entries(aggregateSpec);
|
|
564
564
|
if (entries.length === 0) throw new Error("groupBy().aggregate() requires at least one aggregation selector");
|
|
565
|
-
const
|
|
565
|
+
const table = contract.storage.tables[tableName];
|
|
566
|
+
const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId)), ...entries.map(([alias, selector]) => {
|
|
567
|
+
const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
|
|
568
|
+
return ProjectionItem.of(alias, expr, codecId);
|
|
569
|
+
})];
|
|
566
570
|
let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
|
|
567
571
|
const where = combineWhereExprs(filters);
|
|
568
572
|
if (where) ast = ast.withWhere(where);
|
|
569
573
|
if (havingExpr) ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
|
|
570
|
-
const { params
|
|
571
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
574
|
+
const { params } = deriveParamsFromAst(ast);
|
|
575
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
572
576
|
}
|
|
573
577
|
|
|
574
578
|
//#endregion
|
|
575
579
|
//#region src/query-plan-mutations.ts
|
|
576
580
|
function buildReturningColumns(contract, tableName, returningColumns) {
|
|
577
|
-
|
|
581
|
+
const columns = returningColumns && returningColumns.length > 0 ? [...returningColumns] : resolveTableColumns(contract, tableName);
|
|
582
|
+
const table = contract.storage.tables[tableName];
|
|
583
|
+
return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId));
|
|
578
584
|
}
|
|
579
585
|
function toParamAssignments(contract, tableName, values) {
|
|
580
586
|
const assignments = {};
|
|
@@ -622,14 +628,14 @@ function normalizeInsertRows(contract, tableName, rows) {
|
|
|
622
628
|
function compileInsertReturning(contract, tableName, rows, returningColumns) {
|
|
623
629
|
const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
|
|
624
630
|
const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows).withReturning(buildReturningColumns(contract, tableName, returningColumns));
|
|
625
|
-
const { params
|
|
626
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
631
|
+
const { params } = deriveParamsFromAst(ast);
|
|
632
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
627
633
|
}
|
|
628
634
|
function compileInsertCount(contract, tableName, rows) {
|
|
629
635
|
const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
|
|
630
636
|
const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows);
|
|
631
|
-
const { params
|
|
632
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
637
|
+
const { params } = deriveParamsFromAst(ast);
|
|
638
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
633
639
|
}
|
|
634
640
|
function stripUndefinedValues(row) {
|
|
635
641
|
const result = {};
|
|
@@ -665,38 +671,38 @@ function compileUpsertReturning(contract, tableName, createValues, updateValues,
|
|
|
665
671
|
const updateAssignments = Object.keys(updateValues).length > 0 ? toParamAssignments(contract, tableName, updateValues) : void 0;
|
|
666
672
|
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
673
|
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
|
|
674
|
+
const { params } = deriveParamsFromAst(ast);
|
|
675
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
670
676
|
}
|
|
671
677
|
function compileUpdateReturning(contract, tableName, setValues, filters, returningColumns) {
|
|
672
678
|
const where = combineWhereExprs(filters);
|
|
673
679
|
const { assignments } = toParamAssignments(contract, tableName, setValues);
|
|
674
680
|
let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments).withReturning(buildReturningColumns(contract, tableName, returningColumns));
|
|
675
681
|
if (where) ast = ast.withWhere(where);
|
|
676
|
-
const { params
|
|
677
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
682
|
+
const { params } = deriveParamsFromAst(ast);
|
|
683
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
678
684
|
}
|
|
679
685
|
function compileUpdateCount(contract, tableName, setValues, filters) {
|
|
680
686
|
const where = combineWhereExprs(filters);
|
|
681
687
|
const { assignments } = toParamAssignments(contract, tableName, setValues);
|
|
682
688
|
let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments);
|
|
683
689
|
if (where) ast = ast.withWhere(where);
|
|
684
|
-
const { params
|
|
685
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
690
|
+
const { params } = deriveParamsFromAst(ast);
|
|
691
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
686
692
|
}
|
|
687
693
|
function compileDeleteReturning(contract, tableName, filters, returningColumns) {
|
|
688
694
|
const where = combineWhereExprs(filters);
|
|
689
695
|
let ast = DeleteAst.from(TableSource.named(tableName)).withReturning(buildReturningColumns(contract, tableName, returningColumns));
|
|
690
696
|
if (where) ast = ast.withWhere(where);
|
|
691
|
-
const { params
|
|
692
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
697
|
+
const { params } = deriveParamsFromAst(ast);
|
|
698
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
693
699
|
}
|
|
694
700
|
function compileDeleteCount(contract, tableName, filters) {
|
|
695
701
|
const where = combineWhereExprs(filters);
|
|
696
702
|
let ast = DeleteAst.from(TableSource.named(tableName));
|
|
697
703
|
if (where) ast = ast.withWhere(where);
|
|
698
|
-
const { params
|
|
699
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
704
|
+
const { params } = deriveParamsFromAst(ast);
|
|
705
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
700
706
|
}
|
|
701
707
|
|
|
702
708
|
//#endregion
|
|
@@ -796,7 +802,7 @@ function bindSelectAst(contract, ast) {
|
|
|
796
802
|
return new SelectAst({
|
|
797
803
|
from: bindFromSource(contract, ast.from),
|
|
798
804
|
joins: ast.joins?.map((join) => bindJoin(contract, join)),
|
|
799
|
-
projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr))),
|
|
805
|
+
projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr), projection.codecId)),
|
|
800
806
|
where: ast.where ? bindWhereExprNode(contract, ast.where) : void 0,
|
|
801
807
|
orderBy: ast.orderBy?.map((orderItem) => bindOrderByItem(contract, orderItem)),
|
|
802
808
|
distinct: ast.distinct,
|
|
@@ -812,7 +818,9 @@ function bindSelectAst(contract, ast) {
|
|
|
812
818
|
//#endregion
|
|
813
819
|
//#region src/query-plan-select.ts
|
|
814
820
|
function buildProjection(contract, tableName, selectedFields, tableRef = tableName) {
|
|
815
|
-
|
|
821
|
+
const columns = selectedFields && selectedFields.length > 0 ? [...selectedFields] : resolveTableColumns(contract, tableName);
|
|
822
|
+
const table = contract.storage.tables[tableName];
|
|
823
|
+
return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableRef, column), table?.columns[column]?.codecId));
|
|
816
824
|
}
|
|
817
825
|
function createBoundaryExpr(tableName, entry) {
|
|
818
826
|
return new BinaryExpr(entry.direction === "asc" ? "gt" : "lt", ColumnRef.of(tableName, entry.column), LiteralExpr.of(entry.value));
|
|
@@ -946,10 +954,11 @@ function buildMtiJoins(contract, polyInfo, variantName) {
|
|
|
946
954
|
const join = joinType === "inner" ? JoinAst.inner(TableSource.named(variant.table), joinOn) : JoinAst.left(TableSource.named(variant.table), joinOn);
|
|
947
955
|
joins.push(join);
|
|
948
956
|
const variantColumns = resolveTableColumns(contract, variant.table);
|
|
957
|
+
const variantTable = contract.storage.tables[variant.table];
|
|
949
958
|
for (const col of variantColumns) {
|
|
950
959
|
if (col === pkColumn) continue;
|
|
951
960
|
const alias = `${variant.table}__${col}`;
|
|
952
|
-
projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col)));
|
|
961
|
+
projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col), variantTable?.columns[col]?.codecId));
|
|
953
962
|
}
|
|
954
963
|
}
|
|
955
964
|
return {
|
|
@@ -967,8 +976,8 @@ function compileSelect(contract, tableName, state, modelName) {
|
|
|
967
976
|
joins: mtiArtifacts.joins,
|
|
968
977
|
includeProjection: mtiArtifacts.projection
|
|
969
978
|
} : void 0);
|
|
970
|
-
const { params
|
|
971
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
979
|
+
const { params } = deriveParamsFromAst(ast);
|
|
980
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
972
981
|
}
|
|
973
982
|
function compileRelationSelect(contract, relatedTableName, targetColumn, parentPks, nestedState) {
|
|
974
983
|
const inFilter = BinaryExpr.in(ColumnRef.of(relatedTableName, targetColumn), ListExpression.fromValues(parentPks));
|
|
@@ -1009,8 +1018,8 @@ function compileSelectWithIncludeStrategy(contract, tableName, state, strategy,
|
|
|
1009
1018
|
includeProjection,
|
|
1010
1019
|
...topLevelWhere ? { where: topLevelWhere } : {}
|
|
1011
1020
|
});
|
|
1012
|
-
const { params
|
|
1013
|
-
return buildOrmQueryPlan(contract, ast, params
|
|
1021
|
+
const { params } = deriveParamsFromAst(ast);
|
|
1022
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
1014
1023
|
}
|
|
1015
1024
|
|
|
1016
1025
|
//#endregion
|
|
@@ -1401,7 +1410,7 @@ function shorthandToWhereExpr(context, modelName, filters) {
|
|
|
1401
1410
|
function assertFieldHasEqualityTrait(context, modelName, fieldName) {
|
|
1402
1411
|
const fieldType = modelOf(context.contract, modelName)?.fields?.[fieldName]?.type;
|
|
1403
1412
|
const codecId = fieldType?.kind === "scalar" ? fieldType.codecId : void 0;
|
|
1404
|
-
if (!(codecId ? context.
|
|
1413
|
+
if (!(codecId ? context.codecDescriptors.descriptorFor(codecId)?.traits ?? [] : []).includes("equality")) throw new Error(`Shorthand filter on "${modelName}.${fieldName}": field does not support equality comparisons`);
|
|
1405
1414
|
}
|
|
1406
1415
|
|
|
1407
1416
|
//#endregion
|
|
@@ -1655,12 +1664,13 @@ function createModelAccessor(context, modelName) {
|
|
|
1655
1664
|
existing.push(op);
|
|
1656
1665
|
}
|
|
1657
1666
|
for (const [name, entry] of Object.entries(context.queryOperations.entries())) {
|
|
1658
|
-
const self = entry.args[0];
|
|
1659
1667
|
const op = [name, entry];
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1668
|
+
const self = entry.self;
|
|
1669
|
+
if (!self) continue;
|
|
1670
|
+
if (self.codecId !== void 0) registerOp(self.codecId, op);
|
|
1671
|
+
else if (self.traits !== void 0) for (const descriptor of context.codecDescriptors.values()) {
|
|
1672
|
+
const descriptorTraits = descriptor.traits;
|
|
1673
|
+
if (self.traits.every((t) => descriptorTraits.includes(t))) registerOp(descriptor.codecId, op);
|
|
1664
1674
|
}
|
|
1665
1675
|
}
|
|
1666
1676
|
return new Proxy({}, { get(_target, prop) {
|
|
@@ -1668,48 +1678,51 @@ function createModelAccessor(context, modelName) {
|
|
|
1668
1678
|
const relation = modelRelations[prop];
|
|
1669
1679
|
if (relation) return createRelationFilterAccessor(context, modelName, tableName, relation);
|
|
1670
1680
|
const columnName = fieldToColumn[prop] ?? prop;
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1681
|
+
const column = resolveColumn(contract, tableName, columnName);
|
|
1682
|
+
if (!column) return;
|
|
1683
|
+
const traits = context.codecDescriptors.descriptorFor(column.codecId)?.traits ?? [];
|
|
1684
|
+
const operations = opsByCodecId.get(column.codecId) ?? [];
|
|
1685
|
+
return createScalarFieldAccessor(tableName, columnName, column.codecId, column.nullable, traits, operations, context);
|
|
1674
1686
|
} });
|
|
1675
1687
|
}
|
|
1676
|
-
function
|
|
1677
|
-
const
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
return (contract.storage.tables?.[tableName])?.columns?.[columnName]?.codecId;
|
|
1688
|
+
function resolveColumn(contract, tableName, columnName) {
|
|
1689
|
+
const column = contract.storage.tables?.[tableName]?.columns?.[columnName];
|
|
1690
|
+
if (!column) return void 0;
|
|
1691
|
+
return {
|
|
1692
|
+
codecId: column.codecId,
|
|
1693
|
+
nullable: column.nullable
|
|
1694
|
+
};
|
|
1684
1695
|
}
|
|
1685
|
-
function createScalarFieldAccessor(tableName, columnName, codecId, traits, operations, context) {
|
|
1696
|
+
function createScalarFieldAccessor(tableName, columnName, codecId, nullable, traits, operations, context) {
|
|
1686
1697
|
const column = ColumnRef.of(tableName, columnName);
|
|
1687
|
-
const
|
|
1698
|
+
const comparisonEntries = [];
|
|
1688
1699
|
for (const [name, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
1689
1700
|
if (meta.traits.some((t) => !traits.includes(t))) continue;
|
|
1690
|
-
|
|
1701
|
+
comparisonEntries.push([name, meta.create(column, codecId)]);
|
|
1691
1702
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1703
|
+
const accessor = {
|
|
1704
|
+
returnType: {
|
|
1705
|
+
codecId,
|
|
1706
|
+
nullable
|
|
1707
|
+
},
|
|
1708
|
+
buildAst: () => column,
|
|
1709
|
+
...Object.fromEntries(comparisonEntries)
|
|
1710
|
+
};
|
|
1711
|
+
for (const [name, entry] of operations) accessor[name] = createExtensionMethodFactory(accessor, entry, context);
|
|
1712
|
+
return accessor;
|
|
1694
1713
|
}
|
|
1695
|
-
function createExtensionMethodFactory(
|
|
1696
|
-
const returnTraits = context.codecs.traitsOf(entry.returns.codecId);
|
|
1697
|
-
const isPredicate = returnTraits.includes("boolean");
|
|
1714
|
+
function createExtensionMethodFactory(selfExpr, entry, context) {
|
|
1698
1715
|
return (...args) => {
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
returns: entry.returns,
|
|
1706
|
-
lowering: entry.lowering
|
|
1707
|
-
});
|
|
1708
|
-
if (isPredicate) return opExpr;
|
|
1716
|
+
const impl = entry.impl;
|
|
1717
|
+
const result = impl(selfExpr, ...args);
|
|
1718
|
+
const returnCodecId = result.returnType.codecId;
|
|
1719
|
+
const returnTraits = context.codecDescriptors.descriptorFor(returnCodecId)?.traits ?? [];
|
|
1720
|
+
if (returnTraits.includes("boolean")) return result.buildAst();
|
|
1721
|
+
const resultAst = result.buildAst();
|
|
1709
1722
|
const methods = {};
|
|
1710
1723
|
for (const [resultMethodName, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
1711
1724
|
if (meta.traits.some((t) => !returnTraits.includes(t))) continue;
|
|
1712
|
-
methods[resultMethodName] = meta.create(
|
|
1725
|
+
methods[resultMethodName] = meta.create(resultAst, returnCodecId);
|
|
1713
1726
|
}
|
|
1714
1727
|
return methods;
|
|
1715
1728
|
};
|
|
@@ -1756,7 +1769,7 @@ function toRelationWhereExpr(context, relatedModelName, predicate) {
|
|
|
1756
1769
|
for (const [fieldName, value] of Object.entries(predicate)) {
|
|
1757
1770
|
if (value === void 0) continue;
|
|
1758
1771
|
const fieldAccessor = accessor[fieldName];
|
|
1759
|
-
if (!fieldAccessor)
|
|
1772
|
+
if (!fieldAccessor) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": field is not defined on the model`);
|
|
1760
1773
|
if (value === null) {
|
|
1761
1774
|
if (!fieldAccessor.isNull) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": isNull is unexpectedly missing — this is a bug in trait gating`);
|
|
1762
1775
|
exprs.push(fieldAccessor.isNull());
|
|
@@ -1895,9 +1908,16 @@ async function updateFirstGraph(scope, context, modelName, filters, input) {
|
|
|
1895
1908
|
let parentRow = existingRow;
|
|
1896
1909
|
const mappedUpdateData = mapModelDataToStorageRow(contract, modelName, scalarData);
|
|
1897
1910
|
if (Object.keys(mappedUpdateData).length > 0) {
|
|
1911
|
+
const tableName = resolveModelTableName(contract, modelName);
|
|
1912
|
+
const appliedUpdateDefaults = context.applyMutationDefaults({
|
|
1913
|
+
op: "update",
|
|
1914
|
+
table: tableName,
|
|
1915
|
+
values: mappedUpdateData
|
|
1916
|
+
});
|
|
1917
|
+
for (const def of appliedUpdateDefaults) mappedUpdateData[def.column] = def.value;
|
|
1898
1918
|
const pkWhere = shorthandToWhereExpr(context, modelName, buildPrimaryKeyFilterFromRow(contract, modelName, existingRow));
|
|
1899
1919
|
if (!pkWhere) throw new Error(`Failed to build primary key filter for model "${modelName}"`);
|
|
1900
|
-
const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract,
|
|
1920
|
+
const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract, tableName, mappedUpdateData, [pkWhere], void 0)).toArray())[0];
|
|
1901
1921
|
if (updatedRaw) parentRow = mapStorageRowToModelFields(contract, modelName, updatedRaw);
|
|
1902
1922
|
}
|
|
1903
1923
|
for (const relationMutation of childOwned) await applyChildOwnedMutation(scope, context, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
@@ -2109,15 +2129,25 @@ function isToWhereExpr(arg) {
|
|
|
2109
2129
|
//#endregion
|
|
2110
2130
|
//#region src/collection.ts
|
|
2111
2131
|
function applyCreateDefaults(ctx, tableName, rows) {
|
|
2132
|
+
const defaultValueCache = rows.length > 1 ? /* @__PURE__ */ new Map() : void 0;
|
|
2112
2133
|
for (const row of rows) {
|
|
2113
2134
|
const applied = ctx.context.applyMutationDefaults({
|
|
2114
2135
|
op: "create",
|
|
2115
2136
|
table: tableName,
|
|
2116
|
-
values: row
|
|
2137
|
+
values: row,
|
|
2138
|
+
...defaultValueCache ? { defaultValueCache } : {}
|
|
2117
2139
|
});
|
|
2118
2140
|
for (const def of applied) row[def.column] = def.value;
|
|
2119
2141
|
}
|
|
2120
2142
|
}
|
|
2143
|
+
function applyUpdateDefaults(ctx, tableName, values) {
|
|
2144
|
+
const applied = ctx.context.applyMutationDefaults({
|
|
2145
|
+
op: "update",
|
|
2146
|
+
table: tableName,
|
|
2147
|
+
values
|
|
2148
|
+
});
|
|
2149
|
+
for (const def of applied) values[def.column] = def.value;
|
|
2150
|
+
}
|
|
2121
2151
|
function isToWhereExprInput(value) {
|
|
2122
2152
|
return typeof value === "object" && value !== null && "toWhereExpr" in value && typeof value.toWhereExpr === "function";
|
|
2123
2153
|
}
|
|
@@ -2480,6 +2510,7 @@ var Collection = class Collection {
|
|
|
2480
2510
|
applyCreateDefaults(this.ctx, this.tableName, [createValues]);
|
|
2481
2511
|
const updateValues = mapModelDataToStorageRow(this.contract, this.modelName, input.update);
|
|
2482
2512
|
const hasUpdateValues = Object.keys(updateValues).length > 0;
|
|
2513
|
+
if (hasUpdateValues) applyUpdateDefaults(this.ctx, this.tableName, updateValues);
|
|
2483
2514
|
const conflictColumns = resolveUpsertConflictColumns(this.contract, this.modelName, input.conflictOn);
|
|
2484
2515
|
if (conflictColumns.length === 0) throw new Error(`upsert() for model "${this.modelName}" requires conflict columns`);
|
|
2485
2516
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
@@ -2526,6 +2557,7 @@ var Collection = class Collection {
|
|
|
2526
2557
|
const generator = async function* () {};
|
|
2527
2558
|
return new AsyncIterableResult(generator());
|
|
2528
2559
|
}
|
|
2560
|
+
applyUpdateDefaults(this.ctx, this.tableName, mappedData);
|
|
2529
2561
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
2530
2562
|
const { selectedForQuery: selectedForUpdate, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
|
|
2531
2563
|
const compiled = compileUpdateReturning(this.contract, this.tableName, mappedData, this.state.filters, selectedForUpdate);
|
|
@@ -2542,6 +2574,7 @@ var Collection = class Collection {
|
|
|
2542
2574
|
async updateCount(data) {
|
|
2543
2575
|
const mappedData = mapModelDataToStorageRow(this.contract, this.modelName, data);
|
|
2544
2576
|
if (Object.keys(mappedData).length === 0) return 0;
|
|
2577
|
+
applyUpdateDefaults(this.ctx, this.tableName, mappedData);
|
|
2545
2578
|
const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.tableName);
|
|
2546
2579
|
const countState = {
|
|
2547
2580
|
...emptyState(),
|