@prisma/orm-family-sql 8.0.0-rc.4-dev.13 → 8.0.0-rc.4-dev.15
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/orm-client.d.mts +37 -4
- package/dist/orm-client.d.mts.map +1 -1
- package/dist/orm-client.mjs +317 -245
- package/dist/orm-client.mjs.map +1 -1
- package/package.json +17 -17
package/dist/orm-client.mjs
CHANGED
|
@@ -910,6 +910,104 @@ function buildAggregateInput(contract, namespaceId, tableName, state, modelName,
|
|
|
910
910
|
if (state.offset !== void 0) inner = inner.withOffset(state.offset);
|
|
911
911
|
return { source: DerivedTableSource.as(tableName, inner) };
|
|
912
912
|
}
|
|
913
|
+
function emptyState() {
|
|
914
|
+
return {
|
|
915
|
+
filters: [],
|
|
916
|
+
includes: [],
|
|
917
|
+
orderBy: void 0,
|
|
918
|
+
cursor: void 0,
|
|
919
|
+
distinct: void 0,
|
|
920
|
+
distinctOn: void 0,
|
|
921
|
+
selectedFields: void 0,
|
|
922
|
+
limit: void 0,
|
|
923
|
+
offset: void 0,
|
|
924
|
+
variantName: void 0,
|
|
925
|
+
annotations: /* @__PURE__ */ new Map()
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
function emptyGroupPagingState() {
|
|
929
|
+
return {
|
|
930
|
+
orderBy: [],
|
|
931
|
+
limit: void 0,
|
|
932
|
+
offset: void 0
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function param(codec, value) {
|
|
936
|
+
if (codec === void 0) return ParamRef.of(value);
|
|
937
|
+
return ParamRef.of(value, { codec });
|
|
938
|
+
}
|
|
939
|
+
function paramList(codec, values) {
|
|
940
|
+
return ListExpression.of(values.map((value) => param(codec, value)));
|
|
941
|
+
}
|
|
942
|
+
function scalarComparisonMethod(op) {
|
|
943
|
+
return ((left, codec) => (value) => {
|
|
944
|
+
if (value === null && (op === "eq" || op === "neq")) return op === "eq" ? NullCheckExpr.isNull(left) : NullCheckExpr.isNotNull(left);
|
|
945
|
+
return new BinaryExpr(op, left, param(codec, value));
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
function listComparisonMethod(op) {
|
|
949
|
+
return ((left, codec) => (values) => new BinaryExpr(op, left, paramList(codec, values)));
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Declares trait requirements and runtime factory for each comparison method.
|
|
953
|
+
*
|
|
954
|
+
* - `traits: []` means "no trait required" — always available
|
|
955
|
+
* - Multi-trait: `traits: ['equality', 'order']` means BOTH traits are required
|
|
956
|
+
*/
|
|
957
|
+
const COMPARISON_METHODS_META = {
|
|
958
|
+
eq: {
|
|
959
|
+
traits: ["equality"],
|
|
960
|
+
create: scalarComparisonMethod("eq")
|
|
961
|
+
},
|
|
962
|
+
neq: {
|
|
963
|
+
traits: ["equality"],
|
|
964
|
+
create: scalarComparisonMethod("neq")
|
|
965
|
+
},
|
|
966
|
+
in: {
|
|
967
|
+
traits: ["equality"],
|
|
968
|
+
create: listComparisonMethod("in")
|
|
969
|
+
},
|
|
970
|
+
notIn: {
|
|
971
|
+
traits: ["equality"],
|
|
972
|
+
create: listComparisonMethod("notIn")
|
|
973
|
+
},
|
|
974
|
+
gt: {
|
|
975
|
+
traits: ["order"],
|
|
976
|
+
create: scalarComparisonMethod("gt")
|
|
977
|
+
},
|
|
978
|
+
lt: {
|
|
979
|
+
traits: ["order"],
|
|
980
|
+
create: scalarComparisonMethod("lt")
|
|
981
|
+
},
|
|
982
|
+
gte: {
|
|
983
|
+
traits: ["order"],
|
|
984
|
+
create: scalarComparisonMethod("gte")
|
|
985
|
+
},
|
|
986
|
+
lte: {
|
|
987
|
+
traits: ["order"],
|
|
988
|
+
create: scalarComparisonMethod("lte")
|
|
989
|
+
},
|
|
990
|
+
like: {
|
|
991
|
+
traits: ["textual"],
|
|
992
|
+
create: scalarComparisonMethod("like")
|
|
993
|
+
},
|
|
994
|
+
asc: {
|
|
995
|
+
traits: ["order"],
|
|
996
|
+
create: (left) => () => OrderByItem.asc(left)
|
|
997
|
+
},
|
|
998
|
+
desc: {
|
|
999
|
+
traits: ["order"],
|
|
1000
|
+
create: (left) => () => OrderByItem.desc(left)
|
|
1001
|
+
},
|
|
1002
|
+
isNull: {
|
|
1003
|
+
traits: [],
|
|
1004
|
+
create: (left) => () => NullCheckExpr.isNull(left)
|
|
1005
|
+
},
|
|
1006
|
+
isNotNull: {
|
|
1007
|
+
traits: [],
|
|
1008
|
+
create: (left) => () => NullCheckExpr.isNotNull(left)
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
913
1011
|
function toAggregateProjection(contract, aggregates, namespaceId, tableName, selector) {
|
|
914
1012
|
const { codec, input: inputCodec, lower } = resolveAggregate({
|
|
915
1013
|
aggregates,
|
|
@@ -992,8 +1090,8 @@ function validateGroupedHavingExpr(expr) {
|
|
|
992
1090
|
rawExpr: rejectHavingExpr
|
|
993
1091
|
});
|
|
994
1092
|
}
|
|
995
|
-
function aggregateInputColumns(tableName, entries, orderBy) {
|
|
996
|
-
const columns =
|
|
1093
|
+
function aggregateInputColumns(tableName, entries, orderBy, groupByColumns = []) {
|
|
1094
|
+
const columns = new Set(groupByColumns);
|
|
997
1095
|
for (const [, selector] of entries) if (selector.column !== void 0) columns.add(selector.column);
|
|
998
1096
|
for (const item of orderBy ?? []) if (item.expr.kind === "column-ref") columns.add(item.expr.column);
|
|
999
1097
|
if (columns.size === 0) return [ProjectionItem.of("__row", LiteralExpr.of(1))];
|
|
@@ -1032,7 +1130,7 @@ function compileAggregate(contract, aggregates, namespaceId, tableName, state, a
|
|
|
1032
1130
|
const { params } = deriveParamsFromAst(ast);
|
|
1033
1131
|
return buildOrmQueryPlan(contract, ast, params);
|
|
1034
1132
|
}
|
|
1035
|
-
function compileGroupedAggregate(contract, aggregates, namespaceId, tableName,
|
|
1133
|
+
function compileGroupedAggregate(contract, aggregates, namespaceId, tableName, preGroupState, groupByColumns, aggregateSpec, havingExpr, modelName, postGroup = emptyGroupPagingState()) {
|
|
1036
1134
|
if (groupByColumns.length === 0) throw ormError("ORM.GROUP_BY_FIELD_MISSING", "groupBy() requires at least one field", { meta: {
|
|
1037
1135
|
namespaceId,
|
|
1038
1136
|
tableName
|
|
@@ -1043,14 +1141,30 @@ function compileGroupedAggregate(contract, aggregates, namespaceId, tableName, f
|
|
|
1043
1141
|
namespaceId,
|
|
1044
1142
|
tableName
|
|
1045
1143
|
} });
|
|
1144
|
+
if (preGroupState.distinctOn !== void 0 && preGroupState.distinctOn.length > 0) assertDistinctOnCapability(contract, "distinctOn");
|
|
1046
1145
|
const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), codecRefForStorageColumn(contract.storage, namespaceId, tableName, column))), ...entries.map(([alias, selector]) => {
|
|
1047
1146
|
const { expr, codec } = toAggregateProjection(contract, aggregates, namespaceId, tableName, selector);
|
|
1048
1147
|
return ProjectionItem.of(alias, expr, codec);
|
|
1049
1148
|
})];
|
|
1050
|
-
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1149
|
+
const hasPagination = preGroupState.limit !== void 0 || preGroupState.offset !== void 0;
|
|
1150
|
+
const hasDistinct = preGroupState.distinct !== void 0 && preGroupState.distinct.length > 0 || preGroupState.distinctOn !== void 0 && preGroupState.distinctOn.length > 0;
|
|
1151
|
+
const needsInputSelect = hasPagination || hasDistinct;
|
|
1152
|
+
let ast;
|
|
1153
|
+
if (needsInputSelect) {
|
|
1154
|
+
const { source } = buildAggregateInput(contract, namespaceId, tableName, preGroupState, modelName, aggregateInputColumns(tableName, entries, preGroupState.orderBy, groupByColumns));
|
|
1155
|
+
ast = SelectAst.from(source).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
|
|
1156
|
+
} else {
|
|
1157
|
+
const polyInfo = modelName ? resolvePolymorphismInfo(contract, namespaceId, modelName) : void 0;
|
|
1158
|
+
const variantJoins = polyInfo && polyInfo.mtiVariants.length > 0 ? buildMtiJoins(contract, namespaceId, polyInfo, preGroupState.variantName, void 0).joins : [];
|
|
1159
|
+
ast = SelectAst.from(tableSourceForContract(contract, namespaceId, tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
|
|
1160
|
+
if (variantJoins.length > 0) ast = ast.withJoins(variantJoins);
|
|
1161
|
+
const where = buildStateWhere(contract, tableName, preGroupState, { namespaceId });
|
|
1162
|
+
if (where) ast = ast.withWhere(where);
|
|
1163
|
+
}
|
|
1053
1164
|
if (havingExpr) ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
|
|
1165
|
+
if (postGroup.orderBy.length > 0) ast = ast.withOrderBy(postGroup.orderBy);
|
|
1166
|
+
if (postGroup.limit !== void 0) ast = ast.withLimit(postGroup.limit);
|
|
1167
|
+
if (postGroup.offset !== void 0) ast = ast.withOffset(postGroup.offset);
|
|
1054
1168
|
const { params } = deriveParamsFromAst(ast);
|
|
1055
1169
|
return buildOrmQueryPlan(contract, ast, params);
|
|
1056
1170
|
}
|
|
@@ -1851,97 +1965,6 @@ function compileSelectWithIncludes(contract, aggregates, namespaceId, tableName,
|
|
|
1851
1965
|
function queryPlanRows(scope, plan) {
|
|
1852
1966
|
return scope.query(plan);
|
|
1853
1967
|
}
|
|
1854
|
-
function emptyState() {
|
|
1855
|
-
return {
|
|
1856
|
-
filters: [],
|
|
1857
|
-
includes: [],
|
|
1858
|
-
orderBy: void 0,
|
|
1859
|
-
cursor: void 0,
|
|
1860
|
-
distinct: void 0,
|
|
1861
|
-
distinctOn: void 0,
|
|
1862
|
-
selectedFields: void 0,
|
|
1863
|
-
limit: void 0,
|
|
1864
|
-
offset: void 0,
|
|
1865
|
-
variantName: void 0,
|
|
1866
|
-
annotations: /* @__PURE__ */ new Map()
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
function param(codec, value) {
|
|
1870
|
-
if (codec === void 0) return ParamRef.of(value);
|
|
1871
|
-
return ParamRef.of(value, { codec });
|
|
1872
|
-
}
|
|
1873
|
-
function paramList(codec, values) {
|
|
1874
|
-
return ListExpression.of(values.map((value) => param(codec, value)));
|
|
1875
|
-
}
|
|
1876
|
-
function scalarComparisonMethod(op) {
|
|
1877
|
-
return ((left, codec) => (value) => {
|
|
1878
|
-
if (value === null && (op === "eq" || op === "neq")) return op === "eq" ? NullCheckExpr.isNull(left) : NullCheckExpr.isNotNull(left);
|
|
1879
|
-
return new BinaryExpr(op, left, param(codec, value));
|
|
1880
|
-
});
|
|
1881
|
-
}
|
|
1882
|
-
function listComparisonMethod(op) {
|
|
1883
|
-
return ((left, codec) => (values) => new BinaryExpr(op, left, paramList(codec, values)));
|
|
1884
|
-
}
|
|
1885
|
-
/**
|
|
1886
|
-
* Declares trait requirements and runtime factory for each comparison method.
|
|
1887
|
-
*
|
|
1888
|
-
* - `traits: []` means "no trait required" — always available
|
|
1889
|
-
* - Multi-trait: `traits: ['equality', 'order']` means BOTH traits are required
|
|
1890
|
-
*/
|
|
1891
|
-
const COMPARISON_METHODS_META = {
|
|
1892
|
-
eq: {
|
|
1893
|
-
traits: ["equality"],
|
|
1894
|
-
create: scalarComparisonMethod("eq")
|
|
1895
|
-
},
|
|
1896
|
-
neq: {
|
|
1897
|
-
traits: ["equality"],
|
|
1898
|
-
create: scalarComparisonMethod("neq")
|
|
1899
|
-
},
|
|
1900
|
-
in: {
|
|
1901
|
-
traits: ["equality"],
|
|
1902
|
-
create: listComparisonMethod("in")
|
|
1903
|
-
},
|
|
1904
|
-
notIn: {
|
|
1905
|
-
traits: ["equality"],
|
|
1906
|
-
create: listComparisonMethod("notIn")
|
|
1907
|
-
},
|
|
1908
|
-
gt: {
|
|
1909
|
-
traits: ["order"],
|
|
1910
|
-
create: scalarComparisonMethod("gt")
|
|
1911
|
-
},
|
|
1912
|
-
lt: {
|
|
1913
|
-
traits: ["order"],
|
|
1914
|
-
create: scalarComparisonMethod("lt")
|
|
1915
|
-
},
|
|
1916
|
-
gte: {
|
|
1917
|
-
traits: ["order"],
|
|
1918
|
-
create: scalarComparisonMethod("gte")
|
|
1919
|
-
},
|
|
1920
|
-
lte: {
|
|
1921
|
-
traits: ["order"],
|
|
1922
|
-
create: scalarComparisonMethod("lte")
|
|
1923
|
-
},
|
|
1924
|
-
like: {
|
|
1925
|
-
traits: ["textual"],
|
|
1926
|
-
create: scalarComparisonMethod("like")
|
|
1927
|
-
},
|
|
1928
|
-
asc: {
|
|
1929
|
-
traits: ["order"],
|
|
1930
|
-
create: (left) => () => OrderByItem.asc(left)
|
|
1931
|
-
},
|
|
1932
|
-
desc: {
|
|
1933
|
-
traits: ["order"],
|
|
1934
|
-
create: (left) => () => OrderByItem.desc(left)
|
|
1935
|
-
},
|
|
1936
|
-
isNull: {
|
|
1937
|
-
traits: [],
|
|
1938
|
-
create: (left) => () => NullCheckExpr.isNull(left)
|
|
1939
|
-
},
|
|
1940
|
-
isNotNull: {
|
|
1941
|
-
traits: [],
|
|
1942
|
-
create: (left) => () => NullCheckExpr.isNotNull(left)
|
|
1943
|
-
}
|
|
1944
|
-
};
|
|
1945
1968
|
function dispatchCollectionRows(options) {
|
|
1946
1969
|
const { context, runtime, state, tableName, modelName, namespaceId } = options;
|
|
1947
1970
|
const { contract } = context;
|
|
@@ -2389,152 +2412,6 @@ function assertFieldHasEqualityTrait(context, namespaceId, modelName, fieldName)
|
|
|
2389
2412
|
trait: "equality"
|
|
2390
2413
|
} });
|
|
2391
2414
|
}
|
|
2392
|
-
var GroupedCollection = class GroupedCollection {
|
|
2393
|
-
ctx;
|
|
2394
|
-
contract;
|
|
2395
|
-
modelName;
|
|
2396
|
-
tableName;
|
|
2397
|
-
namespaceId;
|
|
2398
|
-
baseFilters;
|
|
2399
|
-
groupByFields;
|
|
2400
|
-
groupByColumns;
|
|
2401
|
-
havingFilters;
|
|
2402
|
-
constructor(ctx, modelName, options) {
|
|
2403
|
-
this.ctx = ctx;
|
|
2404
|
-
this.contract = ctx.context.contract;
|
|
2405
|
-
this.modelName = modelName;
|
|
2406
|
-
this.tableName = options.tableName;
|
|
2407
|
-
this.namespaceId = options.namespaceId;
|
|
2408
|
-
this.baseFilters = options.baseFilters;
|
|
2409
|
-
this.groupByFields = options.groupByFields;
|
|
2410
|
-
this.groupByColumns = options.groupByColumns;
|
|
2411
|
-
this.havingFilters = options.havingFilters;
|
|
2412
|
-
}
|
|
2413
|
-
having(predicate) {
|
|
2414
|
-
const havingExpr = predicate(createHavingBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName, this.tableName));
|
|
2415
|
-
return new GroupedCollection(this.ctx, this.modelName, {
|
|
2416
|
-
tableName: this.tableName,
|
|
2417
|
-
namespaceId: this.namespaceId,
|
|
2418
|
-
baseFilters: this.baseFilters,
|
|
2419
|
-
groupByFields: this.groupByFields,
|
|
2420
|
-
groupByColumns: this.groupByColumns,
|
|
2421
|
-
havingFilters: [...this.havingFilters, havingExpr]
|
|
2422
|
-
});
|
|
2423
|
-
}
|
|
2424
|
-
/**
|
|
2425
|
-
* Read terminal: run a grouped aggregate query.
|
|
2426
|
-
*
|
|
2427
|
-
* Accepts an optional `configure` callback that receives a
|
|
2428
|
-
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
2429
|
-
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
2430
|
-
*/
|
|
2431
|
-
async aggregate(fn, configure) {
|
|
2432
|
-
const aggregateSpec = fn(createAggregateBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName));
|
|
2433
|
-
const aggregateEntries = Object.entries(aggregateSpec);
|
|
2434
|
-
if (aggregateEntries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "groupBy().aggregate() requires at least one aggregation selector", { meta: {
|
|
2435
|
-
method: "groupBy.aggregate",
|
|
2436
|
-
model: this.modelName
|
|
2437
|
-
} });
|
|
2438
|
-
for (const [alias, selector] of aggregateEntries) if (!isAggregateSelector(selector)) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `groupBy().aggregate() selector "${alias}" is invalid`, { meta: {
|
|
2439
|
-
method: "groupBy.aggregate",
|
|
2440
|
-
model: this.modelName,
|
|
2441
|
-
alias
|
|
2442
|
-
} });
|
|
2443
|
-
let annotationsMap;
|
|
2444
|
-
if (configure !== void 0) {
|
|
2445
|
-
const meta = createMetaBuilder("read", "groupBy.aggregate");
|
|
2446
|
-
configure(meta);
|
|
2447
|
-
if (meta.annotations.size > 0) annotationsMap = meta.annotations;
|
|
2448
|
-
}
|
|
2449
|
-
const compiled = mergeAnnotations(compileGroupedAggregate(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.tableName, this.baseFilters, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters)), annotationsMap);
|
|
2450
|
-
return (await queryPlanRows(this.ctx.runtime, compiled).toArray()).map((row) => {
|
|
2451
|
-
const mapped = mapStorageRowToModelFields(this.contract, this.namespaceId, this.modelName, row);
|
|
2452
|
-
for (const [alias] of aggregateEntries) mapped[alias] = row[alias];
|
|
2453
|
-
return mapped;
|
|
2454
|
-
});
|
|
2455
|
-
}
|
|
2456
|
-
};
|
|
2457
|
-
/**
|
|
2458
|
-
* The having metric methods, one per operation the registry contributes —
|
|
2459
|
-
* the runtime mirror of the contract's emitted aggregate map, which is what
|
|
2460
|
-
* types the surface as {@link HavingBuilder}. HAVING compares the value
|
|
2461
|
-
* inside the database, so only an operation's plain `AggregateExpr` form is
|
|
2462
|
-
* sound here: an operation outside the SQL aggregate alphabet exists only in
|
|
2463
|
-
* its descriptor-lowered form — a rendering for the driver boundary — and is
|
|
2464
|
-
* refused. The typed surface already excludes it; the runtime refusal covers
|
|
2465
|
-
* dynamic invocation.
|
|
2466
|
-
*/
|
|
2467
|
-
function createHavingBuilder(contract, aggregates, namespaceId, modelName, tableName) {
|
|
2468
|
-
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
2469
|
-
const builder = {};
|
|
2470
|
-
for (const operation of aggregateOperationNames(aggregates)) builder[operation] = (field) => {
|
|
2471
|
-
if (!isAggregateFn(operation)) throw ormError("ORM.AGGREGATE_PROJECTION_ONLY", `Aggregate operation '${operation}' is projection-only: it has no plain SQL form for HAVING, ORDER BY, or comparison positions.`, {
|
|
2472
|
-
why: "An operation outside the SQL aggregate alphabet reaches SQL only through its descriptor's lowering hook — a rendering for the driver boundary. HAVING and ORDER BY compare the value inside the database, where that rendering would change SQL semantics.",
|
|
2473
|
-
fix: `Project '${operation}' in a select and filter or order on the projected value, or use an operation from the SQL aggregate alphabet.`,
|
|
2474
|
-
meta: { operation }
|
|
2475
|
-
});
|
|
2476
|
-
return createHavingComparisonMethods(new AggregateExpr(operation, field === void 0 ? void 0 : ColumnRef.of(tableName, fieldToColumn[field] ?? field)));
|
|
2477
|
-
};
|
|
2478
|
-
return blindCast(builder);
|
|
2479
|
-
}
|
|
2480
|
-
function createHavingComparisonMethods(metric) {
|
|
2481
|
-
const buildBinaryExpr = (op, value) => new BinaryExpr(op, metric, LiteralExpr.of(value));
|
|
2482
|
-
return {
|
|
2483
|
-
eq(value) {
|
|
2484
|
-
return buildBinaryExpr("eq", value);
|
|
2485
|
-
},
|
|
2486
|
-
neq(value) {
|
|
2487
|
-
return buildBinaryExpr("neq", value);
|
|
2488
|
-
},
|
|
2489
|
-
gt(value) {
|
|
2490
|
-
return buildBinaryExpr("gt", value);
|
|
2491
|
-
},
|
|
2492
|
-
lt(value) {
|
|
2493
|
-
return buildBinaryExpr("lt", value);
|
|
2494
|
-
},
|
|
2495
|
-
gte(value) {
|
|
2496
|
-
return buildBinaryExpr("gte", value);
|
|
2497
|
-
},
|
|
2498
|
-
lte(value) {
|
|
2499
|
-
return buildBinaryExpr("lte", value);
|
|
2500
|
-
}
|
|
2501
|
-
};
|
|
2502
|
-
}
|
|
2503
|
-
function createIncludeScalar(fn, state, column) {
|
|
2504
|
-
return {
|
|
2505
|
-
kind: "includeScalar",
|
|
2506
|
-
fn,
|
|
2507
|
-
state,
|
|
2508
|
-
...column !== void 0 ? { column } : {}
|
|
2509
|
-
};
|
|
2510
|
-
}
|
|
2511
|
-
function createIncludeCombine(branches) {
|
|
2512
|
-
return {
|
|
2513
|
-
kind: "includeCombine",
|
|
2514
|
-
branches
|
|
2515
|
-
};
|
|
2516
|
-
}
|
|
2517
|
-
function isIncludeScalar(value) {
|
|
2518
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2519
|
-
const candidate = value;
|
|
2520
|
-
return candidate.kind === "includeScalar" && typeof candidate.fn === "string" && isCollectionState(candidate.state);
|
|
2521
|
-
}
|
|
2522
|
-
function isIncludeCombine(value) {
|
|
2523
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2524
|
-
const candidate = value;
|
|
2525
|
-
if (candidate.kind !== "includeCombine") return false;
|
|
2526
|
-
if (typeof candidate.branches !== "object" || candidate.branches === null) return false;
|
|
2527
|
-
return true;
|
|
2528
|
-
}
|
|
2529
|
-
function isCollectionStateCarrier(value) {
|
|
2530
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2531
|
-
return isCollectionState(value.state);
|
|
2532
|
-
}
|
|
2533
|
-
function isCollectionState(value) {
|
|
2534
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2535
|
-
const candidate = value;
|
|
2536
|
-
return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
|
|
2537
|
-
}
|
|
2538
2415
|
function hasThrough$1(relation) {
|
|
2539
2416
|
return relation.through !== void 0;
|
|
2540
2417
|
}
|
|
@@ -2847,6 +2724,200 @@ function firstTargetColumn(contract, relation) {
|
|
|
2847
2724
|
if (!firstField) return;
|
|
2848
2725
|
return resolveFieldToColumn(contract, relation.toNamespace, relation.to, firstField);
|
|
2849
2726
|
}
|
|
2727
|
+
var GroupedCollection = class GroupedCollection {
|
|
2728
|
+
ctx;
|
|
2729
|
+
contract;
|
|
2730
|
+
modelName;
|
|
2731
|
+
tableName;
|
|
2732
|
+
namespaceId;
|
|
2733
|
+
preGroupState;
|
|
2734
|
+
groupByFields;
|
|
2735
|
+
groupByColumns;
|
|
2736
|
+
havingFilters;
|
|
2737
|
+
postGroup;
|
|
2738
|
+
constructor(ctx, modelName, options) {
|
|
2739
|
+
this.ctx = ctx;
|
|
2740
|
+
this.contract = ctx.context.contract;
|
|
2741
|
+
this.modelName = modelName;
|
|
2742
|
+
this.tableName = options.tableName;
|
|
2743
|
+
this.namespaceId = options.namespaceId;
|
|
2744
|
+
this.preGroupState = options.preGroupState;
|
|
2745
|
+
this.groupByFields = options.groupByFields;
|
|
2746
|
+
this.groupByColumns = options.groupByColumns;
|
|
2747
|
+
this.havingFilters = options.havingFilters;
|
|
2748
|
+
this.postGroup = options.postGroup;
|
|
2749
|
+
}
|
|
2750
|
+
#clone(overrides) {
|
|
2751
|
+
return blindCast(new GroupedCollection(this.ctx, this.modelName, {
|
|
2752
|
+
tableName: this.tableName,
|
|
2753
|
+
namespaceId: this.namespaceId,
|
|
2754
|
+
preGroupState: this.preGroupState,
|
|
2755
|
+
groupByFields: this.groupByFields,
|
|
2756
|
+
groupByColumns: this.groupByColumns,
|
|
2757
|
+
havingFilters: this.havingFilters,
|
|
2758
|
+
postGroup: this.postGroup,
|
|
2759
|
+
...overrides
|
|
2760
|
+
}));
|
|
2761
|
+
}
|
|
2762
|
+
having(predicate) {
|
|
2763
|
+
const havingExpr = predicate(createHavingBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName, this.tableName));
|
|
2764
|
+
return this.#clone({ havingFilters: [...this.havingFilters, havingExpr] });
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* Append an `ORDER BY` clause on the grouped rows themselves — orders by
|
|
2768
|
+
* group key. Ordering by an aggregate alias needs a builder surface over
|
|
2769
|
+
* the aliases and isn't supported here. Unlocks post-group `take(...)` /
|
|
2770
|
+
* `skip(...)`, which page a group order that would otherwise be undefined.
|
|
2771
|
+
*/
|
|
2772
|
+
orderBy(selection) {
|
|
2773
|
+
const selectors = Array.isArray(selection) ? selection : [selection];
|
|
2774
|
+
if (selectors.length === 0) throw ormError("ORM.ARGUMENT_INVALID", `orderBy() for model "${this.modelName}" requires at least one selector`, { meta: {
|
|
2775
|
+
method: "orderBy",
|
|
2776
|
+
model: this.modelName
|
|
2777
|
+
} });
|
|
2778
|
+
const accessor = createModelAccessor(this.ctx.context, this.namespaceId, this.modelName);
|
|
2779
|
+
const nextOrders = selectors.map((selector) => selector(accessor));
|
|
2780
|
+
return this.#clone({ postGroup: {
|
|
2781
|
+
...this.postGroup,
|
|
2782
|
+
orderBy: [...this.postGroup.orderBy, ...nextOrders]
|
|
2783
|
+
} });
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Apply `LIMIT n` to the grouped rows. Replaces any previous post-group
|
|
2787
|
+
* limit. Requires a prior `orderBy(...)` — a database may return groups in
|
|
2788
|
+
* any order, so "the first n groups" is undefined without one.
|
|
2789
|
+
*/
|
|
2790
|
+
take(n) {
|
|
2791
|
+
return this.#clone({ postGroup: {
|
|
2792
|
+
...this.postGroup,
|
|
2793
|
+
limit: n
|
|
2794
|
+
} });
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Apply `OFFSET n` to the grouped rows. Replaces any previous post-group
|
|
2798
|
+
* offset. Requires a prior `orderBy(...)`, same as `take(...)` — Prisma
|
|
2799
|
+
* pairs `skip`/`take` with `orderBy` on `groupBy` for the same reason.
|
|
2800
|
+
*/
|
|
2801
|
+
skip(n) {
|
|
2802
|
+
return this.#clone({ postGroup: {
|
|
2803
|
+
...this.postGroup,
|
|
2804
|
+
offset: n
|
|
2805
|
+
} });
|
|
2806
|
+
}
|
|
2807
|
+
/**
|
|
2808
|
+
* Read terminal: run a grouped aggregate query.
|
|
2809
|
+
*
|
|
2810
|
+
* Accepts an optional `configure` callback that receives a
|
|
2811
|
+
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
2812
|
+
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
2813
|
+
*/
|
|
2814
|
+
async aggregate(fn, configure) {
|
|
2815
|
+
const aggregateSpec = fn(createAggregateBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName));
|
|
2816
|
+
const aggregateEntries = Object.entries(aggregateSpec);
|
|
2817
|
+
if (aggregateEntries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "groupBy().aggregate() requires at least one aggregation selector", { meta: {
|
|
2818
|
+
method: "groupBy.aggregate",
|
|
2819
|
+
model: this.modelName
|
|
2820
|
+
} });
|
|
2821
|
+
for (const [alias, selector] of aggregateEntries) if (!isAggregateSelector(selector)) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `groupBy().aggregate() selector "${alias}" is invalid`, { meta: {
|
|
2822
|
+
method: "groupBy.aggregate",
|
|
2823
|
+
model: this.modelName,
|
|
2824
|
+
alias
|
|
2825
|
+
} });
|
|
2826
|
+
let annotationsMap;
|
|
2827
|
+
if (configure !== void 0) {
|
|
2828
|
+
const meta = createMetaBuilder("read", "groupBy.aggregate");
|
|
2829
|
+
configure(meta);
|
|
2830
|
+
if (meta.annotations.size > 0) annotationsMap = meta.annotations;
|
|
2831
|
+
}
|
|
2832
|
+
const compiled = mergeAnnotations(compileGroupedAggregate(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.tableName, this.preGroupState, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters), this.modelName, this.postGroup), annotationsMap);
|
|
2833
|
+
return (await queryPlanRows(this.ctx.runtime, compiled).toArray()).map((row) => {
|
|
2834
|
+
const mapped = mapStorageRowToModelFields(this.contract, this.namespaceId, this.modelName, row);
|
|
2835
|
+
for (const [alias] of aggregateEntries) mapped[alias] = row[alias];
|
|
2836
|
+
return mapped;
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
};
|
|
2840
|
+
/**
|
|
2841
|
+
* The having metric methods, one per operation the registry contributes —
|
|
2842
|
+
* the runtime mirror of the contract's emitted aggregate map, which is what
|
|
2843
|
+
* types the surface as {@link HavingBuilder}. HAVING compares the value
|
|
2844
|
+
* inside the database, so only an operation's plain `AggregateExpr` form is
|
|
2845
|
+
* sound here: an operation outside the SQL aggregate alphabet exists only in
|
|
2846
|
+
* its descriptor-lowered form — a rendering for the driver boundary — and is
|
|
2847
|
+
* refused. The typed surface already excludes it; the runtime refusal covers
|
|
2848
|
+
* dynamic invocation.
|
|
2849
|
+
*/
|
|
2850
|
+
function createHavingBuilder(contract, aggregates, namespaceId, modelName, tableName) {
|
|
2851
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
2852
|
+
const builder = {};
|
|
2853
|
+
for (const operation of aggregateOperationNames(aggregates)) builder[operation] = (field) => {
|
|
2854
|
+
if (!isAggregateFn(operation)) throw ormError("ORM.AGGREGATE_PROJECTION_ONLY", `Aggregate operation '${operation}' is projection-only: it has no plain SQL form for HAVING, ORDER BY, or comparison positions.`, {
|
|
2855
|
+
why: "An operation outside the SQL aggregate alphabet reaches SQL only through its descriptor's lowering hook — a rendering for the driver boundary. HAVING and ORDER BY compare the value inside the database, where that rendering would change SQL semantics.",
|
|
2856
|
+
fix: `Project '${operation}' in a select and filter or order on the projected value, or use an operation from the SQL aggregate alphabet.`,
|
|
2857
|
+
meta: { operation }
|
|
2858
|
+
});
|
|
2859
|
+
return createHavingComparisonMethods(new AggregateExpr(operation, field === void 0 ? void 0 : ColumnRef.of(tableName, fieldToColumn[field] ?? field)));
|
|
2860
|
+
};
|
|
2861
|
+
return blindCast(builder);
|
|
2862
|
+
}
|
|
2863
|
+
function createHavingComparisonMethods(metric) {
|
|
2864
|
+
const buildBinaryExpr = (op, value) => new BinaryExpr(op, metric, LiteralExpr.of(value));
|
|
2865
|
+
return {
|
|
2866
|
+
eq(value) {
|
|
2867
|
+
return buildBinaryExpr("eq", value);
|
|
2868
|
+
},
|
|
2869
|
+
neq(value) {
|
|
2870
|
+
return buildBinaryExpr("neq", value);
|
|
2871
|
+
},
|
|
2872
|
+
gt(value) {
|
|
2873
|
+
return buildBinaryExpr("gt", value);
|
|
2874
|
+
},
|
|
2875
|
+
lt(value) {
|
|
2876
|
+
return buildBinaryExpr("lt", value);
|
|
2877
|
+
},
|
|
2878
|
+
gte(value) {
|
|
2879
|
+
return buildBinaryExpr("gte", value);
|
|
2880
|
+
},
|
|
2881
|
+
lte(value) {
|
|
2882
|
+
return buildBinaryExpr("lte", value);
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
function createIncludeScalar(fn, state, column) {
|
|
2887
|
+
return {
|
|
2888
|
+
kind: "includeScalar",
|
|
2889
|
+
fn,
|
|
2890
|
+
state,
|
|
2891
|
+
...column !== void 0 ? { column } : {}
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
function createIncludeCombine(branches) {
|
|
2895
|
+
return {
|
|
2896
|
+
kind: "includeCombine",
|
|
2897
|
+
branches
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
function isIncludeScalar(value) {
|
|
2901
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2902
|
+
const candidate = value;
|
|
2903
|
+
return candidate.kind === "includeScalar" && typeof candidate.fn === "string" && isCollectionState(candidate.state);
|
|
2904
|
+
}
|
|
2905
|
+
function isIncludeCombine(value) {
|
|
2906
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2907
|
+
const candidate = value;
|
|
2908
|
+
if (candidate.kind !== "includeCombine") return false;
|
|
2909
|
+
if (typeof candidate.branches !== "object" || candidate.branches === null) return false;
|
|
2910
|
+
return true;
|
|
2911
|
+
}
|
|
2912
|
+
function isCollectionStateCarrier(value) {
|
|
2913
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2914
|
+
return isCollectionState(value.state);
|
|
2915
|
+
}
|
|
2916
|
+
function isCollectionState(value) {
|
|
2917
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2918
|
+
const candidate = value;
|
|
2919
|
+
return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
|
|
2920
|
+
}
|
|
2850
2921
|
function createRelationMutator() {
|
|
2851
2922
|
return {
|
|
2852
2923
|
create(data) {
|
|
@@ -3655,10 +3726,11 @@ var CollectionImpl = class CollectionImpl {
|
|
|
3655
3726
|
return new GroupedCollection(this.ctx, this.modelName, {
|
|
3656
3727
|
tableName: this.tableName,
|
|
3657
3728
|
namespaceId: this.namespaceId,
|
|
3658
|
-
|
|
3729
|
+
preGroupState: this.state,
|
|
3659
3730
|
groupByFields: [...fields],
|
|
3660
3731
|
groupByColumns,
|
|
3661
|
-
havingFilters: []
|
|
3732
|
+
havingFilters: [],
|
|
3733
|
+
postGroup: emptyGroupPagingState()
|
|
3662
3734
|
});
|
|
3663
3735
|
}
|
|
3664
3736
|
/**
|