@prisma-next/sql-lane 0.3.0-dev.7 → 0.3.0-dev.71

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.
Files changed (64) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +20 -3
  3. package/dist/builder-DLGrrQ5F.mjs +1254 -0
  4. package/dist/builder-DLGrrQ5F.mjs.map +1 -0
  5. package/dist/builder-DizPddCD.d.mts +121 -0
  6. package/dist/builder-DizPddCD.d.mts.map +1 -0
  7. package/dist/exports/sql.d.mts +3 -0
  8. package/dist/exports/sql.mjs +3 -0
  9. package/dist/index.d.mts +3 -0
  10. package/dist/index.mjs +3 -0
  11. package/package.json +28 -24
  12. package/src/raw.ts +9 -2
  13. package/src/sql/context.ts +3 -3
  14. package/src/sql/include-builder.ts +3 -3
  15. package/src/sql/mutation-builder.ts +61 -10
  16. package/src/sql/plan.ts +117 -102
  17. package/src/sql/predicate-builder.ts +94 -40
  18. package/src/sql/projection.ts +15 -10
  19. package/src/sql/select-builder.ts +26 -41
  20. package/src/types/internal.ts +5 -4
  21. package/src/utils/errors.ts +1 -1
  22. package/src/utils/state.ts +5 -6
  23. package/dist/chunk-AWSKRSFP.js +0 -1569
  24. package/dist/chunk-AWSKRSFP.js.map +0 -1
  25. package/dist/exports/sql.d.ts +0 -5
  26. package/dist/exports/sql.d.ts.map +0 -1
  27. package/dist/exports/sql.js +0 -11
  28. package/dist/exports/sql.js.map +0 -1
  29. package/dist/index.d.ts +0 -5
  30. package/dist/index.d.ts.map +0 -1
  31. package/dist/index.js +0 -11
  32. package/dist/index.js.map +0 -1
  33. package/dist/raw.d.ts +0 -11
  34. package/dist/raw.d.ts.map +0 -1
  35. package/dist/sql/builder.d.ts +0 -11
  36. package/dist/sql/builder.d.ts.map +0 -1
  37. package/dist/sql/context.d.ts +0 -5
  38. package/dist/sql/context.d.ts.map +0 -1
  39. package/dist/sql/include-builder.d.ts +0 -35
  40. package/dist/sql/include-builder.d.ts.map +0 -1
  41. package/dist/sql/join-builder.d.ts +0 -4
  42. package/dist/sql/join-builder.d.ts.map +0 -1
  43. package/dist/sql/mutation-builder.d.ts +0 -64
  44. package/dist/sql/mutation-builder.d.ts.map +0 -1
  45. package/dist/sql/plan.d.ts +0 -4
  46. package/dist/sql/plan.d.ts.map +0 -1
  47. package/dist/sql/predicate-builder.d.ts +0 -11
  48. package/dist/sql/predicate-builder.d.ts.map +0 -1
  49. package/dist/sql/projection.d.ts +0 -18
  50. package/dist/sql/projection.d.ts.map +0 -1
  51. package/dist/sql/select-builder.d.ts +0 -35
  52. package/dist/sql/select-builder.d.ts.map +0 -1
  53. package/dist/types/internal.d.ts +0 -35
  54. package/dist/types/internal.d.ts.map +0 -1
  55. package/dist/types/public.d.ts +0 -18
  56. package/dist/types/public.d.ts.map +0 -1
  57. package/dist/utils/assertions.d.ts +0 -28
  58. package/dist/utils/assertions.d.ts.map +0 -1
  59. package/dist/utils/capabilities.d.ts +0 -4
  60. package/dist/utils/capabilities.d.ts.map +0 -1
  61. package/dist/utils/errors.d.ts +0 -30
  62. package/dist/utils/errors.d.ts.map +0 -1
  63. package/dist/utils/state.d.ts +0 -30
  64. package/dist/utils/state.d.ts.map +0 -1
@@ -10,7 +10,7 @@ import {
10
10
  createUpdateAst,
11
11
  } from '@prisma-next/sql-relational-core/ast';
12
12
  import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
13
- import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
13
+ import type { ExecutionContext } from '@prisma-next/sql-relational-core/query-lane-context';
14
14
  import type {
15
15
  AnyColumnBuilder,
16
16
  BinaryBuilder,
@@ -18,6 +18,7 @@ import type {
18
18
  InferReturningRow,
19
19
  ParamPlaceholder,
20
20
  SqlBuilderOptions,
21
+ UnaryBuilder,
21
22
  } from '@prisma-next/sql-relational-core/types';
22
23
  import { checkReturningCapability } from '../utils/capabilities';
23
24
  import {
@@ -48,7 +49,7 @@ export interface UpdateBuilder<
48
49
  CodecTypes extends Record<string, { readonly output: unknown }> = Record<string, never>,
49
50
  Row = unknown,
50
51
  > {
51
- where(predicate: BinaryBuilder): UpdateBuilder<TContract, CodecTypes, Row>;
52
+ where(predicate: BinaryBuilder | UnaryBuilder): UpdateBuilder<TContract, CodecTypes, Row>;
52
53
  returning<const Columns extends readonly AnyColumnBuilder[]>(
53
54
  ...columns: Columns
54
55
  ): UpdateBuilder<TContract, CodecTypes, InferReturningRow<Columns>>;
@@ -60,7 +61,7 @@ export interface DeleteBuilder<
60
61
  CodecTypes extends Record<string, { readonly output: unknown }> = Record<string, never>,
61
62
  Row = unknown,
62
63
  > {
63
- where(predicate: BinaryBuilder): DeleteBuilder<TContract, CodecTypes, Row>;
64
+ where(predicate: BinaryBuilder | UnaryBuilder): DeleteBuilder<TContract, CodecTypes, Row>;
64
65
  returning<const Columns extends readonly AnyColumnBuilder[]>(
65
66
  ...columns: Columns
66
67
  ): DeleteBuilder<TContract, CodecTypes, InferReturningRow<Columns>>;
@@ -74,7 +75,7 @@ export class InsertBuilderImpl<
74
75
  > implements InsertBuilder<TContract, CodecTypes, Row>
75
76
  {
76
77
  private readonly contract: TContract;
77
- private readonly context: QueryLaneContext<TContract>;
78
+ private readonly context: ExecutionContext<TContract>;
78
79
  private readonly table: TableRef;
79
80
  private readonly values: Record<string, ParamPlaceholder>;
80
81
  private returningColumns: AnyColumnBuilder[] = [];
@@ -149,6 +150,31 @@ export class InsertBuilderImpl<
149
150
  values[columnName] = createParamRef(index, paramName);
150
151
  }
151
152
 
153
+ const appliedDefaults = this.context.applyMutationDefaults({
154
+ op: 'create',
155
+ table: this.table.name,
156
+ values,
157
+ });
158
+
159
+ for (const defaultValue of appliedDefaults) {
160
+ const columnMeta = contractTable.columns[defaultValue.column];
161
+ if (!columnMeta) {
162
+ errorUnknownColumn(defaultValue.column, this.table.name);
163
+ }
164
+
165
+ const index = paramValues.push(defaultValue.value);
166
+ paramCodecs[defaultValue.column] = columnMeta.codecId;
167
+ paramDescriptors.push({
168
+ name: defaultValue.column,
169
+ source: 'dsl',
170
+ refs: { table: this.table.name, column: defaultValue.column },
171
+ codecId: columnMeta.codecId,
172
+ nativeType: columnMeta.nativeType,
173
+ nullable: columnMeta.nullable,
174
+ });
175
+ values[defaultValue.column] = createParamRef(index, defaultValue.column);
176
+ }
177
+
152
178
  const returning: ColumnRef[] = this.returningColumns.map((col) => {
153
179
  // TypeScript can't narrow ColumnBuilder properly
154
180
  const c = col as unknown as { table: string; column: string };
@@ -203,10 +229,10 @@ export class UpdateBuilderImpl<
203
229
  > implements UpdateBuilder<TContract, CodecTypes, Row>
204
230
  {
205
231
  private readonly contract: TContract;
206
- private readonly context: QueryLaneContext<TContract>;
232
+ private readonly context: ExecutionContext<TContract>;
207
233
  private readonly table: TableRef;
208
234
  private readonly set: Record<string, ParamPlaceholder>;
209
- private wherePredicate?: BinaryBuilder;
235
+ private wherePredicate?: BinaryBuilder | UnaryBuilder;
210
236
  private returningColumns: AnyColumnBuilder[] = [];
211
237
 
212
238
  constructor(
@@ -220,7 +246,7 @@ export class UpdateBuilderImpl<
220
246
  this.set = set;
221
247
  }
222
248
 
223
- where(predicate: BinaryBuilder): UpdateBuilder<TContract, CodecTypes, Row> {
249
+ where(predicate: BinaryBuilder | UnaryBuilder): UpdateBuilder<TContract, CodecTypes, Row> {
224
250
  const builder = new UpdateBuilderImpl<TContract, CodecTypes, Row>(
225
251
  {
226
252
  context: this.context,
@@ -299,6 +325,31 @@ export class UpdateBuilderImpl<
299
325
  set[columnName] = createParamRef(index, paramName);
300
326
  }
301
327
 
328
+ const appliedDefaults = this.context.applyMutationDefaults({
329
+ op: 'update',
330
+ table: this.table.name,
331
+ values: set,
332
+ });
333
+
334
+ for (const defaultValue of appliedDefaults) {
335
+ const columnMeta = contractTable.columns[defaultValue.column];
336
+ if (!columnMeta) {
337
+ errorUnknownColumn(defaultValue.column, this.table.name);
338
+ }
339
+
340
+ const index = paramValues.push(defaultValue.value);
341
+ paramCodecs[defaultValue.column] = columnMeta.codecId;
342
+ paramDescriptors.push({
343
+ name: defaultValue.column,
344
+ source: 'dsl',
345
+ refs: { table: this.table.name, column: defaultValue.column },
346
+ codecId: columnMeta.codecId,
347
+ nativeType: columnMeta.nativeType,
348
+ nullable: columnMeta.nullable,
349
+ });
350
+ set[defaultValue.column] = createParamRef(index, defaultValue.column);
351
+ }
352
+
302
353
  const whereResult = buildWhereExpr(
303
354
  this.contract,
304
355
  this.wherePredicate,
@@ -372,9 +423,9 @@ export class DeleteBuilderImpl<
372
423
  > implements DeleteBuilder<TContract, CodecTypes, Row>
373
424
  {
374
425
  private readonly contract: TContract;
375
- private readonly context: QueryLaneContext<TContract>;
426
+ private readonly context: ExecutionContext<TContract>;
376
427
  private readonly table: TableRef;
377
- private wherePredicate?: BinaryBuilder;
428
+ private wherePredicate?: BinaryBuilder | UnaryBuilder;
378
429
  private returningColumns: AnyColumnBuilder[] = [];
379
430
 
380
431
  constructor(options: SqlBuilderOptions<TContract>, table: TableRef) {
@@ -383,7 +434,7 @@ export class DeleteBuilderImpl<
383
434
  this.table = table;
384
435
  }
385
436
 
386
- where(predicate: BinaryBuilder): DeleteBuilder<TContract, CodecTypes, Row> {
437
+ where(predicate: BinaryBuilder | UnaryBuilder): DeleteBuilder<TContract, CodecTypes, Row> {
387
438
  const builder = new DeleteBuilderImpl<TContract, CodecTypes, Row>(
388
439
  {
389
440
  context: this.context,
package/src/sql/plan.ts CHANGED
@@ -1,42 +1,72 @@
1
1
  import type { PlanMeta } from '@prisma-next/contract/types';
2
- import type { OperationExpr } from '@prisma-next/sql-relational-core/ast';
2
+ import type { Expression } from '@prisma-next/sql-relational-core/ast';
3
3
  import { compact } from '@prisma-next/sql-relational-core/ast';
4
- import type { AnyColumnBuilder } from '@prisma-next/sql-relational-core/types';
4
+ import type { AnyExpressionSource } from '@prisma-next/sql-relational-core/types';
5
5
  import {
6
6
  collectColumnRefs,
7
- getColumnInfo,
8
- getOperationExpr,
9
7
  isColumnBuilder,
8
+ isExpressionBuilder,
10
9
  isOperationExpr,
11
10
  } from '@prisma-next/sql-relational-core/utils/guards';
12
11
  import type { MetaBuildArgs } from '../types/internal';
13
12
  import { assertColumnBuilder } from '../utils/assertions';
14
13
  import { errorMissingColumnForAlias } from '../utils/errors';
15
14
 
15
+ /**
16
+ * Extracts column references from an ExpressionSource (ColumnBuilder or ExpressionBuilder).
17
+ */
18
+ function collectRefsFromExpressionSource(
19
+ source: AnyExpressionSource,
20
+ refsColumns: Map<string, { table: string; column: string }>,
21
+ ): void {
22
+ if (isExpressionBuilder(source)) {
23
+ // ExpressionBuilder has an OperationExpr - collect all column refs
24
+ const allRefs = collectColumnRefs(source.expr);
25
+ for (const ref of allRefs) {
26
+ refsColumns.set(`${ref.table}.${ref.column}`, {
27
+ table: ref.table,
28
+ column: ref.column,
29
+ });
30
+ }
31
+ } else if (isColumnBuilder(source)) {
32
+ // ColumnBuilder - use table and column directly
33
+ const col = source as unknown as { table: string; column: string };
34
+ refsColumns.set(`${col.table}.${col.column}`, {
35
+ table: col.table,
36
+ column: col.column,
37
+ });
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Extracts column references from an Expression (AST node).
43
+ */
44
+ function collectRefsFromExpression(
45
+ expr: Expression,
46
+ refsColumns: Map<string, { table: string; column: string }>,
47
+ ): void {
48
+ if (isOperationExpr(expr)) {
49
+ const allRefs = collectColumnRefs(expr);
50
+ for (const ref of allRefs) {
51
+ refsColumns.set(`${ref.table}.${ref.column}`, {
52
+ table: ref.table,
53
+ column: ref.column,
54
+ });
55
+ }
56
+ } else if (expr.kind === 'col') {
57
+ refsColumns.set(`${expr.table}.${expr.column}`, {
58
+ table: expr.table,
59
+ column: expr.column,
60
+ });
61
+ }
62
+ }
63
+
16
64
  export function buildMeta(args: MetaBuildArgs): PlanMeta {
17
65
  const refsColumns = new Map<string, { table: string; column: string }>();
18
66
  const refsTables = new Set<string>([args.table.name]);
19
67
 
20
68
  for (const column of args.projection.columns) {
21
- // Skip null columns (include placeholders)
22
- if (!column) {
23
- continue;
24
- }
25
- const operationExpr = getOperationExpr(column);
26
- if (operationExpr) {
27
- const allRefs = collectColumnRefs(operationExpr);
28
- for (const ref of allRefs) {
29
- refsColumns.set(`${ref.table}.${ref.column}`, {
30
- table: ref.table,
31
- column: ref.column,
32
- });
33
- }
34
- } else {
35
- refsColumns.set(`${column.table}.${column.column}`, {
36
- table: column.table,
37
- column: column.column,
38
- });
39
- }
69
+ collectRefsFromExpressionSource(column, refsColumns);
40
70
  }
41
71
 
42
72
  if (args.joins) {
@@ -81,53 +111,40 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
81
111
  }
82
112
  // Add child WHERE columns if present
83
113
  if (include.childWhere) {
84
- const colInfo = getColumnInfo(include.childWhere.left);
85
- refsColumns.set(`${colInfo.table}.${colInfo.column}`, {
86
- table: colInfo.table,
87
- column: colInfo.column,
88
- });
89
- // Handle right side of child WHERE clause
90
- const childWhereRight = include.childWhere.right;
91
- if (isColumnBuilder(childWhereRight)) {
92
- const rightColInfo = getColumnInfo(childWhereRight);
93
- refsColumns.set(`${rightColInfo.table}.${rightColInfo.column}`, {
94
- table: rightColInfo.table,
95
- column: rightColInfo.column,
96
- });
114
+ // Handle UnaryBuilder (e.g., NullCheckBuilder) - it only has 'expr' property
115
+ if (include.childWhere.kind === 'nullCheck') {
116
+ const expr: Expression = include.childWhere.expr;
117
+ collectRefsFromExpression(expr, refsColumns);
118
+ } else {
119
+ // BinaryBuilder - has 'left' and 'right' properties
120
+ // childWhere.left is Expression (already converted at builder creation time)
121
+ collectRefsFromExpression(include.childWhere.left, refsColumns);
122
+ // Handle right side of child WHERE clause - can be ParamPlaceholder or ExpressionSource
123
+ const childWhereRight = include.childWhere.right;
124
+ if (isColumnBuilder(childWhereRight) || isExpressionBuilder(childWhereRight)) {
125
+ collectRefsFromExpressionSource(childWhereRight, refsColumns);
126
+ }
97
127
  }
98
128
  }
99
129
  // Add child ORDER BY columns if present
100
130
  if (include.childOrderBy) {
101
- const orderBy = include.childOrderBy as unknown as {
102
- expr?: AnyColumnBuilder | OperationExpr;
103
- };
104
- if (orderBy.expr) {
105
- const colInfo = getColumnInfo(orderBy.expr);
106
- refsColumns.set(`${colInfo.table}.${colInfo.column}`, {
107
- table: colInfo.table,
108
- column: colInfo.column,
109
- });
110
- }
131
+ // childOrderBy.expr is Expression (already converted at builder creation time)
132
+ collectRefsFromExpression(include.childOrderBy.expr, refsColumns);
111
133
  }
112
134
  }
113
135
  }
114
136
 
115
137
  if (args.where) {
116
- const whereLeft = args.where.left;
117
- // Check if whereLeft is an OperationExpr directly (not wrapped in ColumnBuilder)
118
- if (isOperationExpr(whereLeft)) {
119
- const allRefs = collectColumnRefs(whereLeft);
120
- for (const ref of allRefs) {
121
- refsColumns.set(`${ref.table}.${ref.column}`, {
122
- table: ref.table,
123
- column: ref.column,
124
- });
125
- }
138
+ // Handle UnaryBuilder (e.g., NullCheckBuilder) - it only has 'expr' property
139
+ if (args.where.kind === 'nullCheck') {
140
+ const expr: Expression = args.where.expr;
141
+ collectRefsFromExpression(expr, refsColumns);
126
142
  } else {
127
- // Check if whereLeft is a ColumnBuilder with an _operationExpr property
128
- const operationExpr = (whereLeft as { _operationExpr?: OperationExpr })._operationExpr;
129
- if (operationExpr) {
130
- const allRefs = collectColumnRefs(operationExpr);
143
+ // BinaryBuilder - has 'left' and 'right' properties
144
+ // args.where.left is Expression (already converted at builder creation time)
145
+ const leftExpr: Expression = args.where.left;
146
+ if (isOperationExpr(leftExpr)) {
147
+ const allRefs = collectColumnRefs(leftExpr);
131
148
  for (const ref of allRefs) {
132
149
  refsColumns.set(`${ref.table}.${ref.column}`, {
133
150
  table: ref.table,
@@ -135,45 +152,38 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
135
152
  });
136
153
  }
137
154
  } else {
138
- const colBuilder = assertColumnBuilder(whereLeft, 'where clause must be a ColumnBuilder');
139
- refsColumns.set(`${colBuilder.table}.${colBuilder.column}`, {
140
- table: colBuilder.table,
141
- column: colBuilder.column,
155
+ // leftExpr is ColumnRef
156
+ refsColumns.set(`${leftExpr.table}.${leftExpr.column}`, {
157
+ table: leftExpr.table,
158
+ column: leftExpr.column,
142
159
  });
143
160
  }
144
- }
145
161
 
146
- // Handle right side of WHERE clause - can be ParamPlaceholder or AnyColumnBuilder
147
- const whereRight = args.where.right;
148
- if (isColumnBuilder(whereRight)) {
149
- const colInfo = getColumnInfo(whereRight);
150
- refsColumns.set(`${colInfo.table}.${colInfo.column}`, {
151
- table: colInfo.table,
152
- column: colInfo.column,
153
- });
162
+ // Handle right side of WHERE clause - can be ParamPlaceholder or AnyExpressionSource
163
+ const whereRight = args.where.right;
164
+ if (isColumnBuilder(whereRight) || isExpressionBuilder(whereRight)) {
165
+ collectRefsFromExpressionSource(whereRight, refsColumns);
166
+ }
154
167
  }
155
168
  }
156
169
 
157
170
  if (args.orderBy) {
158
- const orderBy = args.orderBy as unknown as {
159
- expr?: AnyColumnBuilder | OperationExpr;
160
- };
161
- const orderByExpr = orderBy.expr;
162
- if (orderByExpr) {
163
- if (isOperationExpr(orderByExpr)) {
164
- const allRefs = collectColumnRefs(orderByExpr);
165
- for (const ref of allRefs) {
166
- refsColumns.set(`${ref.table}.${ref.column}`, {
167
- table: ref.table,
168
- column: ref.column,
169
- });
170
- }
171
- } else {
172
- refsColumns.set(`${orderByExpr.table}.${orderByExpr.column}`, {
173
- table: orderByExpr.table,
174
- column: orderByExpr.column,
171
+ // args.orderBy.expr is Expression (already converted at builder creation time)
172
+ const orderByExpr: Expression = args.orderBy.expr;
173
+ if (isOperationExpr(orderByExpr)) {
174
+ const allRefs = collectColumnRefs(orderByExpr);
175
+ for (const ref of allRefs) {
176
+ refsColumns.set(`${ref.table}.${ref.column}`, {
177
+ table: ref.table,
178
+ column: ref.column,
175
179
  });
176
180
  }
181
+ } else {
182
+ // orderByExpr is ColumnRef
183
+ refsColumns.set(`${orderByExpr.table}.${orderByExpr.column}`, {
184
+ table: orderByExpr.table,
185
+ column: orderByExpr.column,
186
+ });
177
187
  }
178
188
  }
179
189
 
@@ -191,13 +201,18 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
191
201
  // This shouldn't happen if projection building is correct, but handle gracefully
192
202
  errorMissingColumnForAlias(alias, index);
193
203
  }
194
-
195
- const operationExpr = getOperationExpr(column);
196
- if (operationExpr) {
197
- return [alias, `operation:${operationExpr.method}`];
204
+ // Check if column is an ExpressionBuilder (operation result)
205
+ if (isExpressionBuilder(column)) {
206
+ return [alias, `operation:${column.expr.method}`];
207
+ }
208
+ // column is ColumnBuilder
209
+ const col = column as unknown as { table?: string; column?: string };
210
+ if (!col.table || !col.column) {
211
+ // This is a placeholder column for an include - skip it
212
+ return [alias, `include:${alias}`];
198
213
  }
199
214
 
200
- return [alias, `${column.table}.${column.column}`];
215
+ return [alias, `${col.table}.${col.column}`];
201
216
  }),
202
217
  );
203
218
 
@@ -213,15 +228,15 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
213
228
  if (!column) {
214
229
  continue;
215
230
  }
216
- const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
217
- if (operationExpr) {
231
+ if (isExpressionBuilder(column)) {
232
+ const operationExpr = column.expr;
218
233
  if (operationExpr.returns.kind === 'typeId') {
219
234
  projectionTypes[alias] = operationExpr.returns.type;
220
235
  } else if (operationExpr.returns.kind === 'builtin') {
221
236
  projectionTypes[alias] = operationExpr.returns.type;
222
237
  }
223
238
  } else {
224
- // TypeScript can't narrow ColumnBuilder properly
239
+ // column is ColumnBuilder
225
240
  const col = column as unknown as { columnMeta?: { codecId: string } };
226
241
  const columnMeta = col.columnMeta;
227
242
  const codecId = columnMeta?.codecId;
@@ -243,14 +258,14 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
243
258
  if (!column) {
244
259
  continue;
245
260
  }
246
- const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
247
- if (operationExpr) {
261
+ if (isExpressionBuilder(column)) {
262
+ const operationExpr = column.expr;
248
263
  if (operationExpr.returns.kind === 'typeId') {
249
264
  projectionCodecs[alias] = operationExpr.returns.type;
250
265
  }
251
266
  } else {
252
267
  // Use columnMeta.codecId directly as typeId (already canonicalized)
253
- // TypeScript can't narrow ColumnBuilder properly
268
+ // column is ColumnBuilder
254
269
  const col = column as unknown as { columnMeta?: { codecId: string } };
255
270
  const columnMeta = col.columnMeta;
256
271
  const codecId = columnMeta?.codecId;
@@ -270,7 +285,7 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
270
285
  compact({
271
286
  target: args.contract.target,
272
287
  targetFamily: args.contract.targetFamily,
273
- coreHash: args.contract.coreHash,
288
+ storageHash: args.contract.storageHash,
274
289
  lane: 'dsl',
275
290
  refs: {
276
291
  tables: Array.from(refsTables),
@@ -1,21 +1,25 @@
1
1
  import type { ParamDescriptor } from '@prisma-next/contract/types';
2
2
  import type { SqlContract, SqlStorage, StorageColumn } from '@prisma-next/sql-contract/types';
3
3
  import type {
4
- BinaryExpr,
5
- ColumnRef,
6
- OperationExpr,
4
+ Expression,
5
+ NullCheckExpr,
7
6
  ParamRef,
7
+ WhereExpr,
8
8
  } from '@prisma-next/sql-relational-core/ast';
9
9
  import {
10
10
  createBinaryExpr,
11
- createColumnRef,
11
+ createNullCheckExpr,
12
12
  createParamRef,
13
13
  } from '@prisma-next/sql-relational-core/ast';
14
- import type { BinaryBuilder, ParamPlaceholder } from '@prisma-next/sql-relational-core/types';
14
+ import type {
15
+ BinaryBuilder,
16
+ NullCheckBuilder,
17
+ ParamPlaceholder,
18
+ UnaryBuilder,
19
+ } from '@prisma-next/sql-relational-core/types';
15
20
  import {
16
- getColumnInfo,
17
- getOperationExpr,
18
21
  isColumnBuilder,
22
+ isExpressionBuilder,
19
23
  isParamPlaceholder,
20
24
  } from '@prisma-next/sql-relational-core/utils/guards';
21
25
  import {
@@ -26,32 +30,83 @@ import {
26
30
  } from '../utils/errors';
27
31
 
28
32
  export interface BuildWhereExprResult {
29
- expr: BinaryExpr;
33
+ expr: WhereExpr;
30
34
  codecId: string | undefined;
31
35
  paramName: string;
32
36
  }
33
37
 
38
+ /**
39
+ * Type guard to check if a builder is a NullCheckBuilder (unary).
40
+ */
41
+ function isNullCheckBuilder(builder: BinaryBuilder | UnaryBuilder): builder is NullCheckBuilder {
42
+ return builder.kind === 'nullCheck';
43
+ }
44
+
45
+ /**
46
+ * Builds a NullCheckExpr from a NullCheckBuilder.
47
+ */
48
+ function buildNullCheckExpr(
49
+ contract: SqlContract<SqlStorage>,
50
+ where: NullCheckBuilder,
51
+ ): NullCheckExpr {
52
+ const expr = where.expr;
53
+
54
+ // Validate column exists in contract if it's a ColumnRef
55
+ if (expr.kind === 'col') {
56
+ const { table, column } = expr;
57
+ const contractTable = contract.storage.tables[table];
58
+ if (!contractTable) {
59
+ errorUnknownTable(table);
60
+ }
61
+
62
+ const columnMeta: StorageColumn | undefined = contractTable.columns[column];
63
+ if (!columnMeta) {
64
+ errorUnknownColumn(column, table);
65
+ }
66
+ }
67
+
68
+ return createNullCheckExpr(expr, where.isNull);
69
+ }
70
+
34
71
  export function buildWhereExpr(
35
72
  contract: SqlContract<SqlStorage>,
36
- where: BinaryBuilder,
73
+ where: BinaryBuilder | UnaryBuilder,
37
74
  paramsMap: Record<string, unknown>,
38
75
  descriptors: ParamDescriptor[],
39
76
  values: unknown[],
40
77
  ): BuildWhereExprResult {
41
- let leftExpr: ColumnRef | OperationExpr;
78
+ // Handle NullCheckBuilder (unary expression)
79
+ if (isNullCheckBuilder(where)) {
80
+ return {
81
+ expr: buildNullCheckExpr(contract, where),
82
+ codecId: undefined,
83
+ paramName: '',
84
+ };
85
+ }
86
+
87
+ // Handle BinaryBuilder (binary expression)
88
+ let leftExpr: Expression;
42
89
  let codecId: string | undefined;
43
- let rightExpr: ColumnRef | ParamRef;
90
+ let rightExpr: Expression | ParamRef;
44
91
  let paramName: string;
45
92
 
46
- // Check if where.left is an OperationExpr directly (from operation.eq())
47
- // or a ColumnBuilder with _operationExpr property
48
- const operationExpr = getOperationExpr(where.left);
49
- if (operationExpr) {
50
- leftExpr = operationExpr;
51
- } else if (isColumnBuilder(where.left)) {
52
- // where.left is a ColumnBuilder - use proper type narrowing
53
- const { table, column } = getColumnInfo(where.left);
93
+ // Validate where.left is a valid Expression (col or operation)
94
+ const validExpressionKinds = ['col', 'operation'];
95
+ if (
96
+ !where.left ||
97
+ typeof where.left !== 'object' ||
98
+ !validExpressionKinds.includes((where.left as { kind?: string }).kind ?? '')
99
+ ) {
100
+ errorFailedToBuildWhereClause();
101
+ }
102
+
103
+ // where.left is an Expression (already converted at builder creation time)
104
+ // It could be a ColumnRef or OperationExpr
105
+ leftExpr = where.left;
54
106
 
107
+ // If the left expression is a column reference, extract codecId for param descriptors
108
+ if (leftExpr.kind === 'col') {
109
+ const { table, column } = leftExpr;
55
110
  const contractTable = contract.storage.tables[table];
56
111
  if (!contractTable) {
57
112
  errorUnknownTable(table);
@@ -63,13 +118,9 @@ export function buildWhereExpr(
63
118
  }
64
119
 
65
120
  codecId = columnMeta.codecId;
66
- leftExpr = createColumnRef(table, column);
67
- } else {
68
- // where.left is neither OperationExpr nor ColumnBuilder - invalid state
69
- errorFailedToBuildWhereClause();
70
121
  }
71
122
 
72
- // Handle where.right - can be ParamPlaceholder or AnyColumnBuilder
123
+ // Handle where.right - can be ParamPlaceholder or ExpressionSource
73
124
  if (isParamPlaceholder(where.right)) {
74
125
  // Handle param placeholder (existing logic)
75
126
  const placeholder: ParamPlaceholder = where.right;
@@ -82,9 +133,9 @@ export function buildWhereExpr(
82
133
  const value = paramsMap[paramName];
83
134
  const index = values.push(value);
84
135
 
85
- // Construct descriptor directly from validated StorageColumn
86
- if (isColumnBuilder(where.left)) {
87
- const { table, column } = getColumnInfo(where.left);
136
+ // Construct descriptor directly from validated StorageColumn if left is a column
137
+ if (leftExpr.kind === 'col') {
138
+ const { table, column } = leftExpr;
88
139
  const contractTable = contract.storage.tables[table];
89
140
  const columnMeta = contractTable?.columns[column];
90
141
  if (columnMeta) {
@@ -100,25 +151,28 @@ export function buildWhereExpr(
100
151
  }
101
152
 
102
153
  rightExpr = createParamRef(index, paramName);
103
- } else if (isColumnBuilder(where.right)) {
104
- // Handle column builder on the right
105
- const { table, column } = getColumnInfo(where.right);
154
+ } else if (isColumnBuilder(where.right) || isExpressionBuilder(where.right)) {
155
+ // Handle ExpressionSource (ColumnBuilder or ExpressionBuilder) on the right
156
+ rightExpr = where.right.toExpr();
106
157
 
107
- const contractTable = contract.storage.tables[table];
108
- if (!contractTable) {
109
- errorUnknownTable(table);
110
- }
158
+ // Validate column exists in contract if it's a ColumnRef
159
+ if (rightExpr.kind === 'col') {
160
+ const { table, column } = rightExpr;
161
+ const contractTable = contract.storage.tables[table];
162
+ if (!contractTable) {
163
+ errorUnknownTable(table);
164
+ }
111
165
 
112
- const columnMeta: StorageColumn | undefined = contractTable.columns[column];
113
- if (!columnMeta) {
114
- errorUnknownColumn(column, table);
166
+ const columnMeta: StorageColumn | undefined = contractTable.columns[column];
167
+ if (!columnMeta) {
168
+ errorUnknownColumn(column, table);
169
+ }
115
170
  }
116
171
 
117
- rightExpr = createColumnRef(table, column);
118
- // Use a placeholder paramName for column references (not used for params)
172
+ // Use a placeholder paramName for expression references (not used for params)
119
173
  paramName = '';
120
174
  } else {
121
- // where.right is neither ParamPlaceholder nor ColumnBuilder - invalid state
175
+ // where.right is neither ParamPlaceholder nor ExpressionSource - invalid state
122
176
  errorFailedToBuildWhereClause();
123
177
  }
124
178