@prisma-next/sql-builder 0.5.0-dev.6 → 0.5.0-dev.61

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.
@@ -1,16 +1,20 @@
1
- import { AggregateExpr, AndExpr, BinaryExpr, ColumnRef, DeleteAst, DerivedTableSource, ExistsExpr, IdentifierRef, InsertAst, JoinAst, ListExpression, LiteralExpr, NullCheckExpr, OperationExpr, OrExpr, OrderByItem, ParamRef, ProjectionItem, SelectAst, SubqueryExpr, TableSource, UpdateAst } from "@prisma-next/sql-relational-core/ast";
1
+ import { AggregateExpr, AndExpr, BinaryExpr, ColumnRef, DeleteAst, DerivedTableSource, ExistsExpr, IdentifierRef, InsertAst, JoinAst, ListExpression, LiteralExpr, NullCheckExpr, OrExpr, OrderByItem, ParamRef, ProjectionItem, SelectAst, SubqueryExpr, TableSource, UpdateAst, collectOrderedParamRefs } from "@prisma-next/sql-relational-core/ast";
2
+ import { refsOf, toExpr } from "@prisma-next/sql-relational-core/expression";
2
3
 
3
4
  //#region src/runtime/expression-impl.ts
4
5
  /**
5
- * Runtime wrapper around a relational-core AST expression node.
6
- * Carries ScopeField metadata (codecId, nullable) for plan generation.
6
+ * Runtime wrapper around a relational-core AST expression node. Carries ScopeField metadata (codecId, nullable) so aggregate-like combinators can propagate the input codec onto their result.
7
+ *
8
+ * `refs` records the column-bound binding (`{ table, column }`) when known — the field-proxy populates it for both the namespaced form (`f.user.email` → `ColumnRef`) and the top-level shortcut (`f.email` → `IdentifierRef` + refs metadata). Encode-side dispatch and the `validateParamRefRefs` pass read it via `refsOf(expression)`.
7
9
  */
8
10
  var ExpressionImpl = class {
9
11
  ast;
10
- field;
11
- constructor(ast, field) {
12
+ returnType;
13
+ refs;
14
+ constructor(ast, returnType, refs) {
12
15
  this.ast = ast;
13
- this.field = field;
16
+ this.returnType = returnType;
17
+ this.refs = refs;
14
18
  }
15
19
  buildAst() {
16
20
  return this.ast;
@@ -19,11 +23,30 @@ var ExpressionImpl = class {
19
23
 
20
24
  //#endregion
21
25
  //#region src/runtime/field-proxy.ts
26
+ /**
27
+ * For a top-level field name, find the namespace (table alias) that contributed it. When exactly one namespace owns the field, the top-level binding is unambiguously column-bound and we record that `(table, column)` pair on the `ExpressionImpl` so encode-side dispatch (`forColumn`) and the `validateParamRefRefs` pass can find it. The AST stays as `IdentifierRef` to preserve SQL rendering — adapters render top-level
28
+ * identifiers without an explicit table qualifier — so this change is metadata-only and produces no SQL drift.
29
+ */
30
+ function findUniqueNamespaceFor$1(scope, fieldName) {
31
+ let found;
32
+ for (const [namespace, fields] of Object.entries(scope.namespaces)) if (Object.hasOwn(fields, fieldName)) {
33
+ if (found !== void 0) return void 0;
34
+ found = namespace;
35
+ }
36
+ return found;
37
+ }
22
38
  function createFieldProxy(scope) {
23
39
  return new Proxy({}, { get(_target, prop) {
24
40
  if (Object.hasOwn(scope.topLevel, prop)) {
25
41
  const topField = scope.topLevel[prop];
26
- if (topField) return new ExpressionImpl(IdentifierRef.of(prop), topField);
42
+ if (topField) {
43
+ const namespace = findUniqueNamespaceFor$1(scope, prop);
44
+ const refs = namespace ? {
45
+ table: namespace,
46
+ column: prop
47
+ } : void 0;
48
+ return new ExpressionImpl(IdentifierRef.of(prop), topField, refs);
49
+ }
27
50
  }
28
51
  if (Object.hasOwn(scope.namespaces, prop)) {
29
52
  const nsFields = scope.namespaces[prop];
@@ -46,42 +69,72 @@ const BOOL_FIELD = {
46
69
  codecId: "pg/bool@1",
47
70
  nullable: false
48
71
  };
49
- function resolve(value) {
50
- if (value instanceof ExpressionImpl) return value.buildAst();
51
- return ParamRef.of(value);
72
+ const resolve = toExpr;
73
+ /**
74
+ * Resolve a binary-comparison operand into an AST expression, threading the column-bound side's `codecId` + `refs` to the raw-value side.
75
+ *
76
+ * For `fns.eq(f.email, 'alice@example.com')`, `f.email` is the column-bound expression carrying a `ColumnRef` AST and a `returnType.codecId` (`pg/varchar@1`); the raw string operand has no codec context. By deriving the codec context from the column-bound side and forwarding it via `toExpr(value, codecId, refs)`, the resulting `ParamRef` carries the column refs that encode-side `forColumn` dispatch needs (and that the
77
+ * validator pass requires for parameterized codec ids like `pg/varchar@1` with a length parameter).
78
+ */
79
+ function resolveOperand(operand, otherCodecId, otherRefs) {
80
+ if (isExpressionLike(operand)) return operand.buildAst();
81
+ return toExpr(operand, otherCodecId, otherRefs);
82
+ }
83
+ function isExpressionLike(value) {
84
+ return typeof value === "object" && value !== null && "buildAst" in value && typeof value.buildAst === "function";
85
+ }
86
+ function operandCodecId(operand) {
87
+ if (!isExpressionLike(operand)) return void 0;
88
+ return operand.returnType?.codecId;
52
89
  }
53
- function resolveToAst(value) {
54
- if (value instanceof ExpressionImpl) return value.buildAst();
90
+ function operandRefs(operand) {
91
+ return refsOf(operand);
92
+ }
93
+ /**
94
+ * Resolves an Expression via `buildAst()`, or wraps a raw value as a `LiteralExpr` — an SQL literal inlined into the query text, not a bound parameter.
95
+ *
96
+ * Used for `and` / `or` operands. The usual operand is an `Expression<bool>` (e.g. the result of `fns.eq`), which this function passes through by calling `buildAst()`. The only time the raw-value branch fires is when the caller writes `fns.and(true, x)` or similar — inlining `TRUE`/`FALSE` literals lets the SQL planner statically simplify `TRUE AND x` to `x`, which it cannot do for an opaque `ParamRef`.
97
+ */
98
+ function toLiteralExpr(value) {
99
+ if (typeof value === "object" && value !== null && "buildAst" in value && typeof value.buildAst === "function") return value.buildAst();
55
100
  return new LiteralExpr(value);
56
101
  }
57
102
  function boolExpr(astNode) {
58
103
  return new ExpressionImpl(astNode, BOOL_FIELD);
59
104
  }
105
+ function binaryWithSharedCodec(a, b, build) {
106
+ const aCodecId = operandCodecId(a);
107
+ const bCodecId = operandCodecId(b);
108
+ const aRefs = operandRefs(a);
109
+ return build(resolveOperand(a, bCodecId, operandRefs(b)), resolveOperand(b, aCodecId, aRefs));
110
+ }
60
111
  function eq(a, b) {
61
112
  if (b === null) return boolExpr(NullCheckExpr.isNull(resolve(a)));
62
113
  if (a === null) return boolExpr(NullCheckExpr.isNull(resolve(b)));
63
- return boolExpr(new BinaryExpr("eq", resolve(a), resolve(b)));
114
+ return boolExpr(binaryWithSharedCodec(a, b, (l, r) => new BinaryExpr("eq", l, r)));
64
115
  }
65
116
  function ne(a, b) {
66
117
  if (b === null) return boolExpr(NullCheckExpr.isNotNull(resolve(a)));
67
118
  if (a === null) return boolExpr(NullCheckExpr.isNotNull(resolve(b)));
68
- return boolExpr(new BinaryExpr("neq", resolve(a), resolve(b)));
119
+ return boolExpr(binaryWithSharedCodec(a, b, (l, r) => new BinaryExpr("neq", l, r)));
69
120
  }
70
121
  function comparison(a, b, op) {
71
- return boolExpr(new BinaryExpr(op, resolve(a), resolve(b)));
122
+ return boolExpr(binaryWithSharedCodec(a, b, (l, r) => new BinaryExpr(op, l, r)));
72
123
  }
73
124
  function inOrNotIn(expr, valuesOrSubquery, op) {
74
125
  const left = expr.buildAst();
126
+ const leftCodecId = expr.returnType.codecId;
127
+ const leftRefs = refsOf(expr);
75
128
  const binaryFn = op === "in" ? BinaryExpr.in : BinaryExpr.notIn;
76
129
  if (Array.isArray(valuesOrSubquery)) {
77
- const refs = valuesOrSubquery.map((v) => resolve(v));
130
+ const refs = valuesOrSubquery.map((v) => resolveOperand(v, leftCodecId, leftRefs));
78
131
  return boolExpr(binaryFn(left, ListExpression.of(refs)));
79
132
  }
80
133
  return boolExpr(binaryFn(left, SubqueryExpr.of(valuesOrSubquery.buildAst())));
81
134
  }
82
135
  function numericAgg(fn, expr) {
83
136
  return new ExpressionImpl(AggregateExpr[fn](expr.buildAst()), {
84
- codecId: expr.field.codecId,
137
+ codecId: expr.returnType.codecId,
85
138
  nullable: true
86
139
  });
87
140
  }
@@ -93,8 +146,8 @@ function createBuiltinFunctions() {
93
146
  gte: (a, b) => comparison(a, b, "gte"),
94
147
  lt: (a, b) => comparison(a, b, "lt"),
95
148
  lte: (a, b) => comparison(a, b, "lte"),
96
- and: (...exprs) => boolExpr(AndExpr.of(exprs.map(resolveToAst))),
97
- or: (...exprs) => boolExpr(OrExpr.of(exprs.map(resolveToAst))),
149
+ and: (...exprs) => boolExpr(AndExpr.of(exprs.map(toLiteralExpr))),
150
+ or: (...exprs) => boolExpr(OrExpr.of(exprs.map(toLiteralExpr))),
98
151
  exists: (subquery) => boolExpr(ExistsExpr.exists(subquery.buildAst())),
99
152
  notExists: (subquery) => boolExpr(ExistsExpr.notExists(subquery.buildAst())),
100
153
  in: (expr, valuesOrSubquery) => inOrNotIn(expr, valuesOrSubquery, "in"),
@@ -116,35 +169,17 @@ function createAggregateOnlyFunctions() {
116
169
  max: (expr) => numericAgg("max", expr)
117
170
  };
118
171
  }
119
- function createExtensionFunction(name, entry) {
120
- return (...args) => {
121
- const resolvedArgs = args.map((arg, i) => {
122
- if (arg instanceof ExpressionImpl) return arg.buildAst();
123
- const codecId = entry.args[i]?.codecId;
124
- return ParamRef.of(arg, codecId ? { codecId } : void 0);
125
- });
126
- const self = resolvedArgs[0];
127
- const restArgs = resolvedArgs.slice(1);
128
- return new ExpressionImpl(new OperationExpr({
129
- method: name,
130
- self,
131
- args: restArgs.length > 0 ? restArgs : void 0,
132
- returns: entry.returns,
133
- lowering: entry.lowering
134
- }), entry.returns);
135
- };
136
- }
137
- function createFunctions(queryOperationTypes) {
172
+ function createFunctions(operations) {
138
173
  const builtins = createBuiltinFunctions();
139
174
  return new Proxy({}, { get(_target, prop) {
140
175
  const builtin = builtins[prop];
141
176
  if (builtin) return builtin;
142
- const extOp = queryOperationTypes[prop];
143
- if (extOp) return createExtensionFunction(prop, extOp);
177
+ const op = operations[prop];
178
+ if (op) return op.impl;
144
179
  } });
145
180
  }
146
- function createAggregateFunctions(queryOperationTypes) {
147
- const baseFns = createFunctions(queryOperationTypes);
181
+ function createAggregateFunctions(operations) {
182
+ const baseFns = createFunctions(operations);
148
183
  const aggregates = createAggregateOnlyFunctions();
149
184
  return new Proxy({}, { get(_target, prop) {
150
185
  const agg = aggregates[prop];
@@ -195,6 +230,17 @@ function combineWhereExprs(exprs) {
195
230
  if (exprs.length === 1) return exprs[0];
196
231
  return AndExpr.of(exprs);
197
232
  }
233
+ /**
234
+ * Same uniqueness rule as the field-proxy's `findUniqueNamespaceFor`: when exactly one namespace owns a top-level field, the binding is unambiguous. Used by `select('col', ...)` to attach `refs` metadata to the resulting `ProjectionItem` while keeping the AST as `IdentifierRef` (so SQL renders unchanged).
235
+ */
236
+ function findUniqueNamespaceFor(scope, fieldName) {
237
+ let found;
238
+ for (const [namespace, fields] of Object.entries(scope.namespaces)) if (Object.hasOwn(fields, fieldName)) {
239
+ if (found !== void 0) return void 0;
240
+ found = namespace;
241
+ }
242
+ return found;
243
+ }
198
244
  function buildSelectAst(state) {
199
245
  const where = combineWhereExprs(state.where);
200
246
  return new SelectAst({
@@ -212,36 +258,12 @@ function buildSelectAst(state) {
212
258
  selectAllIntent: void 0
213
259
  });
214
260
  }
215
- function buildQueryPlan(ast, rowFields, ctx) {
216
- const projectionTypes = {};
217
- const codecs = {};
218
- for (const [alias, field] of Object.entries(rowFields)) {
219
- projectionTypes[alias] = field.codecId;
220
- codecs[alias] = field.codecId;
221
- }
222
- const paramRefs = ast.collectParamRefs();
223
- const seen = /* @__PURE__ */ new Set();
224
- const uniqueRefs = [];
225
- for (const ref of paramRefs) if (!seen.has(ref)) {
226
- seen.add(ref);
227
- uniqueRefs.push(ref);
228
- }
229
- const paramValues = uniqueRefs.map((r) => r.value);
230
- const paramDescriptors = uniqueRefs.map((ref, i) => ({
231
- index: i + 1,
232
- source: "dsl",
233
- ...ref.codecId ? { codecId: ref.codecId } : {}
234
- }));
235
- for (const [i, ref] of uniqueRefs.entries()) if (ref.codecId) codecs[`$${i + 1}`] = ref.codecId;
236
- const hasProjectionTypes = Object.keys(projectionTypes).length > 0;
237
- const hasCodecs = Object.keys(codecs).length > 0;
261
+ function buildQueryPlan(ast, ctx) {
262
+ const paramValues = collectOrderedParamRefs(ast).map((r) => r.value);
238
263
  const meta = Object.freeze({
239
264
  target: ctx.target,
240
265
  storageHash: ctx.storageHash,
241
- lane: "dsl",
242
- paramDescriptors,
243
- ...hasProjectionTypes ? { projectionTypes } : {},
244
- ...hasCodecs ? { annotations: Object.freeze({ codecs: Object.freeze(codecs) }) } : {}
266
+ lane: "dsl"
245
267
  });
246
268
  return Object.freeze({
247
269
  ast,
@@ -250,7 +272,7 @@ function buildQueryPlan(ast, rowFields, ctx) {
250
272
  });
251
273
  }
252
274
  function buildPlan(state, ctx) {
253
- return buildQueryPlan(buildSelectAst(state), state.rowFields, ctx);
275
+ return buildQueryPlan(buildSelectAst(state), ctx);
254
276
  }
255
277
  function tableToScope(name, table) {
256
278
  const fields = {};
@@ -314,7 +336,12 @@ function resolveSelectArgs(args, scope, ctx) {
314
336
  for (const colName of args) {
315
337
  const field = scope.topLevel[colName];
316
338
  if (!field) throw new Error(`Column "${colName}" not found in scope`);
317
- projections.push(ProjectionItem.of(colName, IdentifierRef.of(colName)));
339
+ const namespace = findUniqueNamespaceFor(scope, colName);
340
+ const refs = namespace ? {
341
+ table: namespace,
342
+ column: colName
343
+ } : void 0;
344
+ projections.push(ProjectionItem.of(colName, IdentifierRef.of(colName), field.codecId, refs));
318
345
  newRowFields[colName] = field;
319
346
  }
320
347
  return {
@@ -327,8 +354,9 @@ function resolveSelectArgs(args, scope, ctx) {
327
354
  const exprFn = args[1];
328
355
  const fns = createAggregateFunctions(ctx.queryOperationTypes);
329
356
  const result = exprFn(createFieldProxy(scope), fns);
330
- projections.push(ProjectionItem.of(alias, result.buildAst()));
331
- newRowFields[alias] = result.field;
357
+ const field = result.returnType;
358
+ projections.push(ProjectionItem.of(alias, result.buildAst(), field.codecId));
359
+ newRowFields[alias] = field;
332
360
  return {
333
361
  projections,
334
362
  newRowFields
@@ -339,8 +367,9 @@ function resolveSelectArgs(args, scope, ctx) {
339
367
  const fns = createAggregateFunctions(ctx.queryOperationTypes);
340
368
  const record = callbackFn(createFieldProxy(scope), fns);
341
369
  for (const [key, expr] of Object.entries(record)) {
342
- projections.push(ProjectionItem.of(key, expr.buildAst()));
343
- newRowFields[key] = expr.field;
370
+ const field = expr.returnType;
371
+ projections.push(ProjectionItem.of(key, expr.buildAst(), field.codecId));
372
+ newRowFields[key] = field;
344
373
  }
345
374
  return {
346
375
  projections,
@@ -562,7 +591,13 @@ function buildParamValues(values, table, tableName, op, ctx) {
562
591
  const params = {};
563
592
  for (const [col, value] of Object.entries(values)) {
564
593
  const column = table.columns[col];
565
- params[col] = ParamRef.of(value, column ? { codecId: column.codecId } : void 0);
594
+ params[col] = ParamRef.of(value, column ? {
595
+ codecId: column.codecId,
596
+ refs: {
597
+ table: tableName,
598
+ column: col
599
+ }
600
+ } : void 0);
566
601
  }
567
602
  for (const def of ctx.applyMutationDefaults({
568
603
  op,
@@ -570,12 +605,18 @@ function buildParamValues(values, table, tableName, op, ctx) {
570
605
  values
571
606
  })) {
572
607
  const column = table.columns[def.column];
573
- params[def.column] = ParamRef.of(def.value, column ? { codecId: column.codecId } : void 0);
608
+ params[def.column] = ParamRef.of(def.value, column ? {
609
+ codecId: column.codecId,
610
+ refs: {
611
+ table: tableName,
612
+ column: def.column
613
+ }
614
+ } : void 0);
574
615
  }
575
616
  return params;
576
617
  }
577
- function buildReturningColumnRefs(tableName, columns) {
578
- return columns.map((col) => ColumnRef.of(tableName, col));
618
+ function buildReturningProjections(tableName, columns, rowFields) {
619
+ return columns.map((col) => ProjectionItem.of(col, ColumnRef.of(tableName, col), rowFields[col]?.codecId));
579
620
  }
580
621
  function evaluateWhere(whereCallback, scope, queryOperationTypes) {
581
622
  return whereCallback(createFieldProxy(scope), createFunctions(queryOperationTypes)).buildAst();
@@ -608,8 +649,8 @@ var InsertQueryImpl = class InsertQueryImpl extends BuilderBase {
608
649
  build() {
609
650
  const paramValues = buildParamValues(this.#values, this.#table, this.#tableName, "create", this.ctx);
610
651
  let ast = InsertAst.into(TableSource.named(this.#tableName)).withValues(paramValues);
611
- if (this.#returningColumns.length > 0) ast = ast.withReturning(buildReturningColumnRefs(this.#tableName, this.#returningColumns));
612
- return buildQueryPlan(ast, this.#rowFields, this.ctx);
652
+ if (this.#returningColumns.length > 0) ast = ast.withReturning(buildReturningProjections(this.#tableName, this.#returningColumns, this.#rowFields));
653
+ return buildQueryPlan(ast, this.ctx);
613
654
  }
614
655
  };
615
656
  var UpdateQueryImpl = class UpdateQueryImpl extends BuilderBase {
@@ -646,8 +687,8 @@ var UpdateQueryImpl = class UpdateQueryImpl extends BuilderBase {
646
687
  const setParams = buildParamValues(this.#setValues, this.#table, this.#tableName, "update", this.ctx);
647
688
  const whereExpr = combineWhereExprs(this.#whereCallbacks.map((cb) => evaluateWhere(cb, this.#scope, this.ctx.queryOperationTypes)));
648
689
  let ast = UpdateAst.table(TableSource.named(this.#tableName)).withSet(setParams).withWhere(whereExpr);
649
- if (this.#returningColumns.length > 0) ast = ast.withReturning(buildReturningColumnRefs(this.#tableName, this.#returningColumns));
650
- return buildQueryPlan(ast, this.#rowFields, this.ctx);
690
+ if (this.#returningColumns.length > 0) ast = ast.withReturning(buildReturningProjections(this.#tableName, this.#returningColumns, this.#rowFields));
691
+ return buildQueryPlan(ast, this.ctx);
651
692
  }
652
693
  };
653
694
  var DeleteQueryImpl = class DeleteQueryImpl extends BuilderBase {
@@ -679,8 +720,8 @@ var DeleteQueryImpl = class DeleteQueryImpl extends BuilderBase {
679
720
  build() {
680
721
  const whereExpr = combineWhereExprs(this.#whereCallbacks.map((cb) => evaluateWhere(cb, this.#scope, this.ctx.queryOperationTypes)));
681
722
  let ast = DeleteAst.from(TableSource.named(this.#tableName)).withWhere(whereExpr);
682
- if (this.#returningColumns.length > 0) ast = ast.withReturning(buildReturningColumnRefs(this.#tableName, this.#returningColumns));
683
- return buildQueryPlan(ast, this.#rowFields, this.ctx);
723
+ if (this.#returningColumns.length > 0) ast = ast.withReturning(buildReturningProjections(this.#tableName, this.#returningColumns, this.#rowFields));
724
+ return buildQueryPlan(ast, this.ctx);
684
725
  }
685
726
  };
686
727