@prisma-next/sql-lane 0.3.0-dev.11 → 0.3.0-dev.114

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 (66) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +18 -1
  3. package/dist/builder-DpxuiFO4.d.mts +118 -0
  4. package/dist/builder-DpxuiFO4.d.mts.map +1 -0
  5. package/dist/builder-MYJh5-Gn.mjs +1153 -0
  6. package/dist/builder-MYJh5-Gn.mjs.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 +29 -24
  12. package/src/raw.ts +9 -2
  13. package/src/sql/builder.ts +12 -9
  14. package/src/sql/context.ts +3 -3
  15. package/src/sql/include-builder.ts +109 -70
  16. package/src/sql/join-builder.ts +21 -11
  17. package/src/sql/mutation-builder.ts +94 -107
  18. package/src/sql/plan.ts +88 -128
  19. package/src/sql/predicate-builder.ts +74 -65
  20. package/src/sql/projection.ts +17 -12
  21. package/src/sql/select-builder.ts +64 -102
  22. package/src/types/internal.ts +6 -5
  23. package/src/utils/errors.ts +2 -2
  24. package/src/utils/state.ts +5 -6
  25. package/dist/chunk-AWSKRSFP.js +0 -1569
  26. package/dist/chunk-AWSKRSFP.js.map +0 -1
  27. package/dist/exports/sql.d.ts +0 -5
  28. package/dist/exports/sql.d.ts.map +0 -1
  29. package/dist/exports/sql.js +0 -11
  30. package/dist/exports/sql.js.map +0 -1
  31. package/dist/index.d.ts +0 -5
  32. package/dist/index.d.ts.map +0 -1
  33. package/dist/index.js +0 -11
  34. package/dist/index.js.map +0 -1
  35. package/dist/raw.d.ts +0 -11
  36. package/dist/raw.d.ts.map +0 -1
  37. package/dist/sql/builder.d.ts +0 -11
  38. package/dist/sql/builder.d.ts.map +0 -1
  39. package/dist/sql/context.d.ts +0 -5
  40. package/dist/sql/context.d.ts.map +0 -1
  41. package/dist/sql/include-builder.d.ts +0 -35
  42. package/dist/sql/include-builder.d.ts.map +0 -1
  43. package/dist/sql/join-builder.d.ts +0 -4
  44. package/dist/sql/join-builder.d.ts.map +0 -1
  45. package/dist/sql/mutation-builder.d.ts +0 -64
  46. package/dist/sql/mutation-builder.d.ts.map +0 -1
  47. package/dist/sql/plan.d.ts +0 -4
  48. package/dist/sql/plan.d.ts.map +0 -1
  49. package/dist/sql/predicate-builder.d.ts +0 -11
  50. package/dist/sql/predicate-builder.d.ts.map +0 -1
  51. package/dist/sql/projection.d.ts +0 -18
  52. package/dist/sql/projection.d.ts.map +0 -1
  53. package/dist/sql/select-builder.d.ts +0 -35
  54. package/dist/sql/select-builder.d.ts.map +0 -1
  55. package/dist/types/internal.d.ts +0 -35
  56. package/dist/types/internal.d.ts.map +0 -1
  57. package/dist/types/public.d.ts +0 -18
  58. package/dist/types/public.d.ts.map +0 -1
  59. package/dist/utils/assertions.d.ts +0 -28
  60. package/dist/utils/assertions.d.ts.map +0 -1
  61. package/dist/utils/capabilities.d.ts +0 -4
  62. package/dist/utils/capabilities.d.ts.map +0 -1
  63. package/dist/utils/errors.d.ts +0 -30
  64. package/dist/utils/errors.d.ts.map +0 -1
  65. package/dist/utils/state.d.ts +0 -30
  66. package/dist/utils/state.d.ts.map +0 -1
package/src/sql/plan.ts CHANGED
@@ -1,42 +1,60 @@
1
1
  import type { PlanMeta } from '@prisma-next/contract/types';
2
- import type { OperationExpr } from '@prisma-next/sql-relational-core/ast';
3
- import { compact } from '@prisma-next/sql-relational-core/ast';
4
- import type { AnyColumnBuilder } from '@prisma-next/sql-relational-core/types';
2
+ import { type AnyExpression, compact } from '@prisma-next/sql-relational-core/ast';
3
+ import type { AnyExpressionSource } from '@prisma-next/sql-relational-core/types';
5
4
  import {
6
- collectColumnRefs,
7
- getColumnInfo,
8
- getOperationExpr,
9
5
  isColumnBuilder,
10
- isOperationExpr,
6
+ isExpressionBuilder,
11
7
  } from '@prisma-next/sql-relational-core/utils/guards';
12
8
  import type { MetaBuildArgs } from '../types/internal';
13
9
  import { assertColumnBuilder } from '../utils/assertions';
14
10
  import { errorMissingColumnForAlias } from '../utils/errors';
15
11
 
12
+ /**
13
+ * Extracts column references from an ExpressionSource (ColumnBuilder or ExpressionBuilder).
14
+ */
15
+ function collectRefsFromExpressionSource(
16
+ source: AnyExpressionSource,
17
+ refsColumns: Map<string, { table: string; column: string }>,
18
+ ): void {
19
+ if (isExpressionBuilder(source)) {
20
+ const allRefs = source.expr.collectColumnRefs();
21
+ for (const ref of allRefs) {
22
+ refsColumns.set(`${ref.table}.${ref.column}`, {
23
+ table: ref.table,
24
+ column: ref.column,
25
+ });
26
+ }
27
+ } else if (isColumnBuilder(source)) {
28
+ // ColumnBuilder - use table and column directly
29
+ const col = source as unknown as { table: string; column: string };
30
+ refsColumns.set(`${col.table}.${col.column}`, {
31
+ table: col.table,
32
+ column: col.column,
33
+ });
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Extracts column references from an Expression (AST node).
39
+ */
40
+ function collectRefsFromExpression(
41
+ expr: AnyExpression,
42
+ refsColumns: Map<string, { table: string; column: string }>,
43
+ ): void {
44
+ for (const ref of expr.collectColumnRefs()) {
45
+ refsColumns.set(`${ref.table}.${ref.column}`, {
46
+ table: ref.table,
47
+ column: ref.column,
48
+ });
49
+ }
50
+ }
51
+
16
52
  export function buildMeta(args: MetaBuildArgs): PlanMeta {
17
53
  const refsColumns = new Map<string, { table: string; column: string }>();
18
54
  const refsTables = new Set<string>([args.table.name]);
19
55
 
20
56
  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
- }
57
+ collectRefsFromExpressionSource(column, refsColumns);
40
58
  }
41
59
 
42
60
  if (args.joins) {
@@ -81,100 +99,38 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
81
99
  }
82
100
  // Add child WHERE columns if present
83
101
  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
- });
102
+ if (include.childWhere.kind === 'nullCheck') {
103
+ collectRefsFromExpression(include.childWhere.expr, refsColumns);
104
+ } else {
105
+ collectRefsFromExpression(include.childWhere.left, refsColumns);
106
+ const childWhereRight = include.childWhere.right;
107
+ if (isColumnBuilder(childWhereRight) || isExpressionBuilder(childWhereRight)) {
108
+ collectRefsFromExpressionSource(childWhereRight, refsColumns);
109
+ }
97
110
  }
98
111
  }
99
112
  // Add child ORDER BY columns if present
100
113
  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
- }
114
+ // childOrderBy.expr is Expression (already converted at builder creation time)
115
+ collectRefsFromExpression(include.childOrderBy.expr, refsColumns);
111
116
  }
112
117
  }
113
118
  }
114
119
 
115
120
  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
- }
121
+ if (args.where.kind === 'nullCheck') {
122
+ collectRefsFromExpression(args.where.expr, refsColumns);
126
123
  } 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);
131
- for (const ref of allRefs) {
132
- refsColumns.set(`${ref.table}.${ref.column}`, {
133
- table: ref.table,
134
- column: ref.column,
135
- });
136
- }
137
- } 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,
142
- });
124
+ collectRefsFromExpression(args.where.left, refsColumns);
125
+ const whereRight = args.where.right;
126
+ if (isColumnBuilder(whereRight) || isExpressionBuilder(whereRight)) {
127
+ collectRefsFromExpressionSource(whereRight, refsColumns);
143
128
  }
144
129
  }
145
-
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
- });
154
- }
155
130
  }
156
131
 
157
132
  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,
175
- });
176
- }
177
- }
133
+ collectRefsFromExpression(args.orderBy.expr, refsColumns);
178
134
  }
179
135
 
180
136
  // Build projection map - mark include aliases with special marker
@@ -191,13 +147,18 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
191
147
  // This shouldn't happen if projection building is correct, but handle gracefully
192
148
  errorMissingColumnForAlias(alias, index);
193
149
  }
194
-
195
- const operationExpr = getOperationExpr(column);
196
- if (operationExpr) {
197
- return [alias, `operation:${operationExpr.method}`];
150
+ // Check if column is an ExpressionBuilder (operation result)
151
+ if (isExpressionBuilder(column)) {
152
+ return [alias, `operation:${column.expr.method}`];
153
+ }
154
+ // column is ColumnBuilder
155
+ const col = column as unknown as { table?: string; column?: string };
156
+ if (!col.table || !col.column) {
157
+ // This is a placeholder column for an include - skip it
158
+ return [alias, `include:${alias}`];
198
159
  }
199
160
 
200
- return [alias, `${column.table}.${column.column}`];
161
+ return [alias, `${col.table}.${col.column}`];
201
162
  }),
202
163
  );
203
164
 
@@ -213,15 +174,15 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
213
174
  if (!column) {
214
175
  continue;
215
176
  }
216
- const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
217
- if (operationExpr) {
177
+ if (isExpressionBuilder(column)) {
178
+ const operationExpr = column.expr;
218
179
  if (operationExpr.returns.kind === 'typeId') {
219
180
  projectionTypes[alias] = operationExpr.returns.type;
220
181
  } else if (operationExpr.returns.kind === 'builtin') {
221
182
  projectionTypes[alias] = operationExpr.returns.type;
222
183
  }
223
184
  } else {
224
- // TypeScript can't narrow ColumnBuilder properly
185
+ // column is ColumnBuilder
225
186
  const col = column as unknown as { columnMeta?: { codecId: string } };
226
187
  const columnMeta = col.columnMeta;
227
188
  const codecId = columnMeta?.codecId;
@@ -233,7 +194,7 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
233
194
 
234
195
  // Build codec assignments from column types
235
196
  // Skip include aliases - they don't need codec entries
236
- const projectionCodecs: Record<string, string> = {};
197
+ const codecs: Record<string, string> = {};
237
198
  for (let i = 0; i < args.projection.aliases.length; i++) {
238
199
  const alias = args.projection.aliases[i];
239
200
  if (!alias || includeAliases.has(alias)) {
@@ -243,34 +204,28 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
243
204
  if (!column) {
244
205
  continue;
245
206
  }
246
- const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
247
- if (operationExpr) {
207
+ if (isExpressionBuilder(column)) {
208
+ const operationExpr = column.expr;
248
209
  if (operationExpr.returns.kind === 'typeId') {
249
- projectionCodecs[alias] = operationExpr.returns.type;
210
+ codecs[alias] = operationExpr.returns.type;
250
211
  }
251
212
  } else {
252
213
  // Use columnMeta.codecId directly as typeId (already canonicalized)
253
- // TypeScript can't narrow ColumnBuilder properly
214
+ // column is ColumnBuilder
254
215
  const col = column as unknown as { columnMeta?: { codecId: string } };
255
216
  const columnMeta = col.columnMeta;
256
217
  const codecId = columnMeta?.codecId;
257
218
  if (codecId) {
258
- projectionCodecs[alias] = codecId;
219
+ codecs[alias] = codecId;
259
220
  }
260
221
  }
261
222
  }
262
223
 
263
- // Merge projection and parameter codecs
264
- const allCodecs: Record<string, string> = {
265
- ...projectionCodecs,
266
- ...(args.paramCodecs ? args.paramCodecs : {}),
267
- };
268
-
269
224
  return Object.freeze(
270
225
  compact({
271
226
  target: args.contract.target,
272
227
  targetFamily: args.contract.targetFamily,
273
- coreHash: args.contract.coreHash,
228
+ storageHash: args.contract.storageHash,
274
229
  lane: 'dsl',
275
230
  refs: {
276
231
  tables: Array.from(refsTables),
@@ -279,8 +234,13 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
279
234
  projection: projectionMap,
280
235
  projectionTypes: Object.keys(projectionTypes).length > 0 ? projectionTypes : undefined,
281
236
  annotations:
282
- Object.keys(allCodecs).length > 0
283
- ? Object.freeze({ codecs: Object.freeze(allCodecs) })
237
+ Object.keys(codecs).length > 0 || args.limit !== undefined
238
+ ? Object.freeze(
239
+ compact({
240
+ codecs: Object.keys(codecs).length > 0 ? Object.freeze(codecs) : undefined,
241
+ limit: args.limit,
242
+ }),
243
+ )
284
244
  : undefined,
285
245
  paramDescriptors: args.paramDescriptors,
286
246
  profileHash: args.contract.profileHash,
@@ -1,21 +1,20 @@
1
- import type { ParamDescriptor } from '@prisma-next/contract/types';
1
+ import { planInvalid } from '@prisma-next/plan';
2
2
  import type { SqlContract, SqlStorage, StorageColumn } from '@prisma-next/sql-contract/types';
3
- import type {
4
- BinaryExpr,
5
- ColumnRef,
6
- OperationExpr,
7
- ParamRef,
8
- } from '@prisma-next/sql-relational-core/ast';
3
+ import type { AnyExpression, NullCheckExpr } from '@prisma-next/sql-relational-core/ast';
9
4
  import {
10
- createBinaryExpr,
11
- createColumnRef,
12
- createParamRef,
5
+ BinaryExpr,
6
+ NullCheckExpr as NullCheckExprNode,
7
+ ParamRef as ParamRefNode,
13
8
  } from '@prisma-next/sql-relational-core/ast';
14
- import type { BinaryBuilder, ParamPlaceholder } from '@prisma-next/sql-relational-core/types';
9
+ import type {
10
+ BinaryBuilder,
11
+ NullCheckBuilder,
12
+ ParamPlaceholder,
13
+ UnaryBuilder,
14
+ } from '@prisma-next/sql-relational-core/types';
15
15
  import {
16
- getColumnInfo,
17
- getOperationExpr,
18
16
  isColumnBuilder,
17
+ isExpressionBuilder,
19
18
  isParamPlaceholder,
20
19
  } from '@prisma-next/sql-relational-core/utils/guards';
21
20
  import {
@@ -26,32 +25,59 @@ import {
26
25
  } from '../utils/errors';
27
26
 
28
27
  export interface BuildWhereExprResult {
29
- expr: BinaryExpr;
28
+ expr: AnyExpression;
30
29
  codecId: string | undefined;
31
30
  paramName: string;
32
31
  }
33
32
 
33
+ function isNullCheckBuilder(builder: BinaryBuilder | UnaryBuilder): builder is NullCheckBuilder {
34
+ return builder.kind === 'nullCheck';
35
+ }
36
+
37
+ function buildNullCheckExpr(
38
+ contract: SqlContract<SqlStorage>,
39
+ where: NullCheckBuilder,
40
+ ): NullCheckExpr {
41
+ const expr = where.expr;
42
+
43
+ if (expr.kind === 'column-ref') {
44
+ const { table, column } = expr;
45
+ const contractTable = contract.storage.tables[table];
46
+ if (!contractTable) {
47
+ errorUnknownTable(table);
48
+ }
49
+
50
+ const columnMeta: StorageColumn | undefined = contractTable.columns[column];
51
+ if (!columnMeta) {
52
+ errorUnknownColumn(column, table);
53
+ }
54
+ }
55
+
56
+ return where.isNull ? NullCheckExprNode.isNull(expr) : NullCheckExprNode.isNotNull(expr);
57
+ }
58
+
34
59
  export function buildWhereExpr(
35
60
  contract: SqlContract<SqlStorage>,
36
- where: BinaryBuilder,
61
+ where: BinaryBuilder | UnaryBuilder,
37
62
  paramsMap: Record<string, unknown>,
38
- descriptors: ParamDescriptor[],
39
- values: unknown[],
40
63
  ): BuildWhereExprResult {
41
- let leftExpr: ColumnRef | OperationExpr;
64
+ if (isNullCheckBuilder(where)) {
65
+ return {
66
+ expr: buildNullCheckExpr(contract, where),
67
+ codecId: undefined,
68
+ paramName: '',
69
+ };
70
+ }
71
+
72
+ let leftExpr: AnyExpression;
42
73
  let codecId: string | undefined;
43
- let rightExpr: ColumnRef | ParamRef;
74
+ let rightExpr: AnyExpression;
44
75
  let paramName: string;
45
76
 
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);
77
+ leftExpr = where.left;
54
78
 
79
+ if (leftExpr.kind === 'column-ref') {
80
+ const { table, column } = leftExpr;
55
81
  const contractTable = contract.storage.tables[table];
56
82
  if (!contractTable) {
57
83
  errorUnknownTable(table);
@@ -63,15 +89,11 @@ export function buildWhereExpr(
63
89
  }
64
90
 
65
91
  codecId = columnMeta.codecId;
66
- leftExpr = createColumnRef(table, column);
67
- } else {
68
- // where.left is neither OperationExpr nor ColumnBuilder - invalid state
69
- errorFailedToBuildWhereClause();
92
+ } else if (leftExpr.kind === 'operation') {
93
+ codecId = leftExpr.returns.kind === 'typeId' ? leftExpr.returns.type : leftExpr.forTypeId;
70
94
  }
71
95
 
72
- // Handle where.right - can be ParamPlaceholder or AnyColumnBuilder
73
96
  if (isParamPlaceholder(where.right)) {
74
- // Handle param placeholder (existing logic)
75
97
  const placeholder: ParamPlaceholder = where.right;
76
98
  paramName = placeholder.name;
77
99
 
@@ -80,50 +102,37 @@ export function buildWhereExpr(
80
102
  }
81
103
 
82
104
  const value = paramsMap[paramName];
83
- const index = values.push(value);
84
105
 
85
- // Construct descriptor directly from validated StorageColumn
86
- if (isColumnBuilder(where.left)) {
87
- const { table, column } = getColumnInfo(where.left);
88
- const contractTable = contract.storage.tables[table];
89
- const columnMeta = contractTable?.columns[column];
90
- if (columnMeta) {
91
- descriptors.push({
92
- name: paramName,
93
- source: 'dsl',
94
- refs: { table, column },
95
- nullable: columnMeta.nullable,
96
- codecId: columnMeta.codecId,
97
- nativeType: columnMeta.nativeType,
98
- });
99
- }
106
+ if (!codecId) {
107
+ throw planInvalid(`Cannot determine codecId for parameter '${paramName}'`);
100
108
  }
109
+ rightExpr = ParamRefNode.of(value, {
110
+ name: paramName,
111
+ codecId,
112
+ });
113
+ } else if (isColumnBuilder(where.right) || isExpressionBuilder(where.right)) {
114
+ rightExpr = where.right.toExpr();
101
115
 
102
- rightExpr = createParamRef(index, paramName);
103
- } else if (isColumnBuilder(where.right)) {
104
- // Handle column builder on the right
105
- const { table, column } = getColumnInfo(where.right);
106
-
107
- const contractTable = contract.storage.tables[table];
108
- if (!contractTable) {
109
- errorUnknownTable(table);
110
- }
116
+ if (rightExpr.kind === 'column-ref') {
117
+ const { table, column } = rightExpr;
118
+ const contractTable = contract.storage.tables[table];
119
+ if (!contractTable) {
120
+ errorUnknownTable(table);
121
+ }
111
122
 
112
- const columnMeta: StorageColumn | undefined = contractTable.columns[column];
113
- if (!columnMeta) {
114
- errorUnknownColumn(column, table);
123
+ const columnMeta: StorageColumn | undefined = contractTable.columns[column];
124
+ if (!columnMeta) {
125
+ errorUnknownColumn(column, table);
126
+ }
115
127
  }
116
128
 
117
- rightExpr = createColumnRef(table, column);
118
- // Use a placeholder paramName for column references (not used for params)
119
129
  paramName = '';
120
130
  } else {
121
- // where.right is neither ParamPlaceholder nor ColumnBuilder - invalid state
122
131
  errorFailedToBuildWhereClause();
123
132
  }
124
133
 
125
134
  return {
126
- expr: createBinaryExpr(where.op, leftExpr, rightExpr),
135
+ expr: new BinaryExpr(where.op, leftExpr, rightExpr),
127
136
  codecId,
128
137
  paramName,
129
138
  };
@@ -1,6 +1,7 @@
1
1
  import type { TableRef } from '@prisma-next/sql-relational-core/ast';
2
- import type { AnyColumnBuilder, NestedProjection } from '@prisma-next/sql-relational-core/types';
3
- import { isColumnBuilder } from '@prisma-next/sql-relational-core/utils/guards';
2
+ import { ColumnRef } from '@prisma-next/sql-relational-core/ast';
3
+ import type { AnyExpressionSource, NestedProjection } from '@prisma-next/sql-relational-core/types';
4
+ import { isExpressionSource } from '@prisma-next/sql-relational-core/utils/guards';
4
5
  import type { ProjectionInput } from '../types/internal';
5
6
  import {
6
7
  errorAliasCollision,
@@ -47,14 +48,14 @@ export function flattenProjection(
47
48
  projection: NestedProjection,
48
49
  tracker: AliasTracker,
49
50
  currentPath: string[] = [],
50
- ): { aliases: string[]; columns: AnyColumnBuilder[] } {
51
+ ): { aliases: string[]; columns: AnyExpressionSource[] } {
51
52
  const aliases: string[] = [];
52
- const columns: AnyColumnBuilder[] = [];
53
+ const columns: AnyExpressionSource[] = [];
53
54
 
54
55
  for (const [key, value] of Object.entries(projection)) {
55
56
  const path = [...currentPath, key];
56
57
 
57
- if (isColumnBuilder(value)) {
58
+ if (isExpressionSource(value)) {
58
59
  const alias = tracker.register(path);
59
60
  aliases.push(alias);
60
61
  columns.push(value);
@@ -77,21 +78,25 @@ export function buildProjectionState(
77
78
  ): ProjectionState {
78
79
  const tracker = new AliasTracker();
79
80
  const aliases: string[] = [];
80
- const columns: (AnyColumnBuilder | null)[] = [];
81
+ const columns: AnyExpressionSource[] = [];
81
82
 
82
83
  for (const [key, value] of Object.entries(projection)) {
83
84
  if (value === true) {
84
- // Boolean true means this is an include reference
85
+ // Boolean true marks an include projection alias. The concrete expression
86
+ // is stitched during include AST construction.
85
87
  const matchingInclude = includes?.find((inc) => inc.alias === key);
86
88
  if (!matchingInclude) {
87
89
  errorIncludeAliasNotFound(key);
88
90
  }
89
- // For include references, we track the alias but use null as placeholder
90
- // The actual handling happens in AST building where we create includeRef
91
- // Using null instead of invalid ColumnBuilder avoids code smell
92
91
  aliases.push(key);
93
- columns.push(null);
94
- } else if (isColumnBuilder(value)) {
92
+ columns.push({
93
+ kind: 'column',
94
+ table: matchingInclude.table.name,
95
+ column: '',
96
+ columnMeta: { nativeType: 'jsonb', codecId: 'core/json@1', nullable: true },
97
+ toExpr: () => ColumnRef.of(matchingInclude.table.name, ''),
98
+ } as AnyExpressionSource);
99
+ } else if (isExpressionSource(value)) {
95
100
  const alias = tracker.register([key]);
96
101
  aliases.push(alias);
97
102
  columns.push(value);