@prisma-next/sql-lane 0.3.0-pr.93.5 → 0.3.0-pr.95.1

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/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,42 +111,27 @@ 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
114
+ // childWhere.left is Expression (already converted at builder creation time)
115
+ collectRefsFromExpression(include.childWhere.left, refsColumns);
116
+ // Handle right side of child WHERE clause - can be ParamPlaceholder or ExpressionSource
90
117
  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
- });
118
+ if (isColumnBuilder(childWhereRight) || isExpressionBuilder(childWhereRight)) {
119
+ collectRefsFromExpressionSource(childWhereRight, refsColumns);
97
120
  }
98
121
  }
99
122
  // Add child ORDER BY columns if present
100
123
  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
- }
124
+ // childOrderBy.expr is Expression (already converted at builder creation time)
125
+ collectRefsFromExpression(include.childOrderBy.expr, refsColumns);
111
126
  }
112
127
  }
113
128
  }
114
129
 
115
130
  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);
131
+ // args.where.left is Expression (already converted at builder creation time)
132
+ const leftExpr: Expression = args.where.left;
133
+ if (isOperationExpr(leftExpr)) {
134
+ const allRefs = collectColumnRefs(leftExpr);
120
135
  for (const ref of allRefs) {
121
136
  refsColumns.set(`${ref.table}.${ref.column}`, {
122
137
  table: ref.table,
@@ -124,56 +139,37 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
124
139
  });
125
140
  }
126
141
  } 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
- });
143
- }
142
+ // leftExpr is ColumnRef
143
+ refsColumns.set(`${leftExpr.table}.${leftExpr.column}`, {
144
+ table: leftExpr.table,
145
+ column: leftExpr.column,
146
+ });
144
147
  }
145
148
 
146
- // Handle right side of WHERE clause - can be ParamPlaceholder or AnyColumnBuilder
149
+ // Handle right side of WHERE clause - can be ParamPlaceholder or AnyExpressionSource
147
150
  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
- });
151
+ if (isColumnBuilder(whereRight) || isExpressionBuilder(whereRight)) {
152
+ collectRefsFromExpressionSource(whereRight, refsColumns);
154
153
  }
155
154
  }
156
155
 
157
156
  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,
157
+ // args.orderBy.expr is Expression (already converted at builder creation time)
158
+ const orderByExpr: Expression = args.orderBy.expr;
159
+ if (isOperationExpr(orderByExpr)) {
160
+ const allRefs = collectColumnRefs(orderByExpr);
161
+ for (const ref of allRefs) {
162
+ refsColumns.set(`${ref.table}.${ref.column}`, {
163
+ table: ref.table,
164
+ column: ref.column,
175
165
  });
176
166
  }
167
+ } else {
168
+ // orderByExpr is ColumnRef
169
+ refsColumns.set(`${orderByExpr.table}.${orderByExpr.column}`, {
170
+ table: orderByExpr.table,
171
+ column: orderByExpr.column,
172
+ });
177
173
  }
178
174
  }
179
175
 
@@ -191,13 +187,18 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
191
187
  // This shouldn't happen if projection building is correct, but handle gracefully
192
188
  errorMissingColumnForAlias(alias, index);
193
189
  }
194
-
195
- const operationExpr = getOperationExpr(column);
196
- if (operationExpr) {
197
- return [alias, `operation:${operationExpr.method}`];
190
+ // Check if column is an ExpressionBuilder (operation result)
191
+ if (isExpressionBuilder(column)) {
192
+ return [alias, `operation:${column.expr.method}`];
193
+ }
194
+ // column is ColumnBuilder
195
+ const col = column as unknown as { table?: string; column?: string };
196
+ if (!col.table || !col.column) {
197
+ // This is a placeholder column for an include - skip it
198
+ return [alias, `include:${alias}`];
198
199
  }
199
200
 
200
- return [alias, `${column.table}.${column.column}`];
201
+ return [alias, `${col.table}.${col.column}`];
201
202
  }),
202
203
  );
203
204
 
@@ -213,15 +214,15 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
213
214
  if (!column) {
214
215
  continue;
215
216
  }
216
- const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
217
- if (operationExpr) {
217
+ if (isExpressionBuilder(column)) {
218
+ const operationExpr = column.expr;
218
219
  if (operationExpr.returns.kind === 'typeId') {
219
220
  projectionTypes[alias] = operationExpr.returns.type;
220
221
  } else if (operationExpr.returns.kind === 'builtin') {
221
222
  projectionTypes[alias] = operationExpr.returns.type;
222
223
  }
223
224
  } else {
224
- // TypeScript can't narrow ColumnBuilder properly
225
+ // column is ColumnBuilder
225
226
  const col = column as unknown as { columnMeta?: { codecId: string } };
226
227
  const columnMeta = col.columnMeta;
227
228
  const codecId = columnMeta?.codecId;
@@ -243,14 +244,14 @@ export function buildMeta(args: MetaBuildArgs): PlanMeta {
243
244
  if (!column) {
244
245
  continue;
245
246
  }
246
- const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
247
- if (operationExpr) {
247
+ if (isExpressionBuilder(column)) {
248
+ const operationExpr = column.expr;
248
249
  if (operationExpr.returns.kind === 'typeId') {
249
250
  projectionCodecs[alias] = operationExpr.returns.type;
250
251
  }
251
252
  } else {
252
253
  // Use columnMeta.codecId directly as typeId (already canonicalized)
253
- // TypeScript can't narrow ColumnBuilder properly
254
+ // column is ColumnBuilder
254
255
  const col = column as unknown as { columnMeta?: { codecId: string } };
255
256
  const columnMeta = col.columnMeta;
256
257
  const codecId = columnMeta?.codecId;
@@ -1,21 +1,11 @@
1
1
  import type { ParamDescriptor } from '@prisma-next/contract/types';
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';
9
- import {
10
- createBinaryExpr,
11
- createColumnRef,
12
- createParamRef,
13
- } from '@prisma-next/sql-relational-core/ast';
3
+ import type { BinaryExpr, Expression, ParamRef } from '@prisma-next/sql-relational-core/ast';
4
+ import { createBinaryExpr, createParamRef } from '@prisma-next/sql-relational-core/ast';
14
5
  import type { BinaryBuilder, ParamPlaceholder } from '@prisma-next/sql-relational-core/types';
15
6
  import {
16
- getColumnInfo,
17
- getOperationExpr,
18
7
  isColumnBuilder,
8
+ isExpressionBuilder,
19
9
  isParamPlaceholder,
20
10
  } from '@prisma-next/sql-relational-core/utils/guards';
21
11
  import {
@@ -38,20 +28,28 @@ export function buildWhereExpr(
38
28
  descriptors: ParamDescriptor[],
39
29
  values: unknown[],
40
30
  ): BuildWhereExprResult {
41
- let leftExpr: ColumnRef | OperationExpr;
31
+ let leftExpr: Expression;
42
32
  let codecId: string | undefined;
43
- let rightExpr: ColumnRef | ParamRef;
33
+ let rightExpr: Expression | ParamRef;
44
34
  let paramName: string;
45
35
 
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);
36
+ // Validate where.left is a valid Expression (col or operation)
37
+ const validExpressionKinds = ['col', 'operation'];
38
+ if (
39
+ !where.left ||
40
+ typeof where.left !== 'object' ||
41
+ !validExpressionKinds.includes((where.left as { kind?: string }).kind ?? '')
42
+ ) {
43
+ errorFailedToBuildWhereClause();
44
+ }
45
+
46
+ // where.left is an Expression (already converted at builder creation time)
47
+ // It could be a ColumnRef or OperationExpr
48
+ leftExpr = where.left;
54
49
 
50
+ // If the left expression is a column reference, extract codecId for param descriptors
51
+ if (leftExpr.kind === 'col') {
52
+ const { table, column } = leftExpr;
55
53
  const contractTable = contract.storage.tables[table];
56
54
  if (!contractTable) {
57
55
  errorUnknownTable(table);
@@ -63,13 +61,9 @@ export function buildWhereExpr(
63
61
  }
64
62
 
65
63
  codecId = columnMeta.codecId;
66
- leftExpr = createColumnRef(table, column);
67
- } else {
68
- // where.left is neither OperationExpr nor ColumnBuilder - invalid state
69
- errorFailedToBuildWhereClause();
70
64
  }
71
65
 
72
- // Handle where.right - can be ParamPlaceholder or AnyColumnBuilder
66
+ // Handle where.right - can be ParamPlaceholder or ExpressionSource
73
67
  if (isParamPlaceholder(where.right)) {
74
68
  // Handle param placeholder (existing logic)
75
69
  const placeholder: ParamPlaceholder = where.right;
@@ -82,9 +76,9 @@ export function buildWhereExpr(
82
76
  const value = paramsMap[paramName];
83
77
  const index = values.push(value);
84
78
 
85
- // Construct descriptor directly from validated StorageColumn
86
- if (isColumnBuilder(where.left)) {
87
- const { table, column } = getColumnInfo(where.left);
79
+ // Construct descriptor directly from validated StorageColumn if left is a column
80
+ if (leftExpr.kind === 'col') {
81
+ const { table, column } = leftExpr;
88
82
  const contractTable = contract.storage.tables[table];
89
83
  const columnMeta = contractTable?.columns[column];
90
84
  if (columnMeta) {
@@ -100,25 +94,28 @@ export function buildWhereExpr(
100
94
  }
101
95
 
102
96
  rightExpr = createParamRef(index, paramName);
103
- } else if (isColumnBuilder(where.right)) {
104
- // Handle column builder on the right
105
- const { table, column } = getColumnInfo(where.right);
97
+ } else if (isColumnBuilder(where.right) || isExpressionBuilder(where.right)) {
98
+ // Handle ExpressionSource (ColumnBuilder or ExpressionBuilder) on the right
99
+ rightExpr = where.right.toExpr();
106
100
 
107
- const contractTable = contract.storage.tables[table];
108
- if (!contractTable) {
109
- errorUnknownTable(table);
110
- }
101
+ // Validate column exists in contract if it's a ColumnRef
102
+ if (rightExpr.kind === 'col') {
103
+ const { table, column } = rightExpr;
104
+ const contractTable = contract.storage.tables[table];
105
+ if (!contractTable) {
106
+ errorUnknownTable(table);
107
+ }
111
108
 
112
- const columnMeta: StorageColumn | undefined = contractTable.columns[column];
113
- if (!columnMeta) {
114
- errorUnknownColumn(column, table);
109
+ const columnMeta: StorageColumn | undefined = contractTable.columns[column];
110
+ if (!columnMeta) {
111
+ errorUnknownColumn(column, table);
112
+ }
115
113
  }
116
114
 
117
- rightExpr = createColumnRef(table, column);
118
- // Use a placeholder paramName for column references (not used for params)
115
+ // Use a placeholder paramName for expression references (not used for params)
119
116
  paramName = '';
120
117
  } else {
121
- // where.right is neither ParamPlaceholder nor ColumnBuilder - invalid state
118
+ // where.right is neither ParamPlaceholder nor ExpressionSource - invalid state
122
119
  errorFailedToBuildWhereClause();
123
120
  }
124
121
 
@@ -1,6 +1,6 @@
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 type { AnyExpressionSource, NestedProjection } from '@prisma-next/sql-relational-core/types';
3
+ import { isExpressionSource } from '@prisma-next/sql-relational-core/utils/guards';
4
4
  import type { ProjectionInput } from '../types/internal';
5
5
  import {
6
6
  errorAliasCollision,
@@ -47,14 +47,14 @@ export function flattenProjection(
47
47
  projection: NestedProjection,
48
48
  tracker: AliasTracker,
49
49
  currentPath: string[] = [],
50
- ): { aliases: string[]; columns: AnyColumnBuilder[] } {
50
+ ): { aliases: string[]; columns: AnyExpressionSource[] } {
51
51
  const aliases: string[] = [];
52
- const columns: AnyColumnBuilder[] = [];
52
+ const columns: AnyExpressionSource[] = [];
53
53
 
54
54
  for (const [key, value] of Object.entries(projection)) {
55
55
  const path = [...currentPath, key];
56
56
 
57
- if (isColumnBuilder(value)) {
57
+ if (isExpressionSource(value)) {
58
58
  const alias = tracker.register(path);
59
59
  aliases.push(alias);
60
60
  columns.push(value);
@@ -77,7 +77,7 @@ export function buildProjectionState(
77
77
  ): ProjectionState {
78
78
  const tracker = new AliasTracker();
79
79
  const aliases: string[] = [];
80
- const columns: (AnyColumnBuilder | null)[] = [];
80
+ const columns: AnyExpressionSource[] = [];
81
81
 
82
82
  for (const [key, value] of Object.entries(projection)) {
83
83
  if (value === true) {
@@ -86,12 +86,17 @@ export function buildProjectionState(
86
86
  if (!matchingInclude) {
87
87
  errorIncludeAliasNotFound(key);
88
88
  }
89
- // For include references, we track the alias but use null as placeholder
89
+ // For include references, we track the alias but use a placeholder object
90
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: () => ({ kind: 'col', table: matchingInclude.table.name, column: '' }),
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);
@@ -11,7 +11,6 @@ import type {
11
11
  TableRef,
12
12
  } from '@prisma-next/sql-relational-core/ast';
13
13
  import {
14
- createColumnRef,
15
14
  createJoinOnBuilder,
16
15
  createOrderByItem,
17
16
  createSelectAst,
@@ -31,9 +30,8 @@ import type {
31
30
  OrderBuilder,
32
31
  SqlBuilderOptions,
33
32
  } from '@prisma-next/sql-relational-core/types';
34
- import { getOperationExpr } from '@prisma-next/sql-relational-core/utils/guards';
33
+ import { isExpressionBuilder } from '@prisma-next/sql-relational-core/utils/guards';
35
34
  import type { ProjectionInput } from '../types/internal';
36
- import { assertColumnBuilder } from '../utils/assertions';
37
35
  import { checkIncludeCapabilities } from '../utils/capabilities';
38
36
  import {
39
37
  errorChildProjectionEmpty,
@@ -41,7 +39,6 @@ import {
41
39
  errorIncludeAliasCollision,
42
40
  errorLimitMustBeNonNegativeInteger,
43
41
  errorMissingAlias,
44
- errorMissingColumnForAlias,
45
42
  errorSelectMustBeCalled,
46
43
  errorSelfJoinNotSupported,
47
44
  errorUnknownTable,
@@ -320,15 +317,8 @@ export class SelectBuilderImpl<
320
317
  const orderByClause = this.state.orderBy
321
318
  ? (() => {
322
319
  const orderBy = this.state.orderBy as OrderBuilder<string, StorageColumn, unknown>;
323
- const orderExpr = orderBy.expr;
324
- const operationExpr = getOperationExpr(orderExpr);
325
- const expr: ColumnRef | OperationExpr = operationExpr
326
- ? operationExpr
327
- : (() => {
328
- const colBuilder = orderExpr as { table: string; column: string };
329
- return createColumnRef(colBuilder.table, colBuilder.column);
330
- })();
331
- return [createOrderByItem(expr, orderBy.dir)];
320
+ // orderBy.expr is already an Expression (ColumnRef or OperationExpr)
321
+ return [createOrderByItem(orderBy.expr, orderBy.dir)];
332
322
  })()
333
323
  : undefined;
334
324
 
@@ -357,28 +347,19 @@ export class SelectBuilderImpl<
357
347
  alias,
358
348
  expr: { kind: 'includeRef', alias },
359
349
  });
360
- } else {
361
- // Not an include - column must not be null
362
- if (!column) {
363
- errorMissingColumnForAlias(alias, i);
364
- }
365
- // Check if this column has an operation expression
366
- const operationExpr = (column as { _operationExpr?: OperationExpr })._operationExpr;
367
- if (operationExpr) {
368
- projectEntries.push({
369
- alias,
370
- expr: operationExpr,
371
- });
372
- } else {
373
- // This is a regular column
374
- // TypeScript can't narrow ColumnBuilder properly
375
- const col = column as { table: string; column: string };
376
- assertColumnBuilder(col, 'projection column');
377
- projectEntries.push({
378
- alias,
379
- expr: createColumnRef(col.table, col.column),
380
- });
381
- }
350
+ } else if (column && isExpressionBuilder(column)) {
351
+ // This is an ExpressionBuilder (operation result) - use its expr
352
+ projectEntries.push({
353
+ alias,
354
+ expr: column.expr,
355
+ });
356
+ } else if (column) {
357
+ // This is a regular ColumnBuilder - use toExpr() to get ColumnRef
358
+ const columnRef = column.toExpr();
359
+ projectEntries.push({
360
+ alias,
361
+ expr: columnRef,
362
+ });
382
363
  }
383
364
  }
384
365
 
@@ -3,13 +3,13 @@ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
3
  import type { TableRef } from '@prisma-next/sql-relational-core/ast';
4
4
  import type {
5
5
  AnyBinaryBuilder,
6
- AnyColumnBuilder,
6
+ AnyExpressionSource,
7
7
  AnyOrderBuilder,
8
8
  NestedProjection,
9
9
  } from '@prisma-next/sql-relational-core/types';
10
10
  import type { ProjectionState } from '../utils/state';
11
11
 
12
- export type ProjectionInput = Record<string, AnyColumnBuilder | boolean | NestedProjection>;
12
+ export type ProjectionInput = Record<string, AnyExpressionSource | boolean | NestedProjection>;
13
13
 
14
14
  export interface MetaBuildArgs {
15
15
  readonly contract: SqlContract<SqlStorage>;
@@ -98,7 +98,7 @@ export function errorMissingParameter(paramName: string): never {
98
98
 
99
99
  export function errorInvalidProjectionValue(path: string[]): never {
100
100
  throw planInvalid(
101
- `Invalid projection value at path ${path.join('.')}: expected ColumnBuilder or nested object`,
101
+ `Invalid projection value at path ${path.join('.')}: expected ExpressionSource (ColumnBuilder or ExpressionBuilder) or nested object`,
102
102
  );
103
103
  }
104
104
 
@@ -1,16 +1,14 @@
1
1
  import type { TableRef } from '@prisma-next/sql-relational-core/ast';
2
2
  import type {
3
3
  AnyBinaryBuilder,
4
- AnyColumnBuilder,
4
+ AnyExpressionSource,
5
5
  AnyOrderBuilder,
6
6
  JoinOnPredicate,
7
7
  } from '@prisma-next/sql-relational-core/types';
8
8
 
9
9
  export interface ProjectionState {
10
10
  readonly aliases: string[];
11
- // columns can be null for include placeholders (when alias matches an include)
12
- // This maintains array alignment but avoids creating invalid ColumnBuilders
13
- readonly columns: (AnyColumnBuilder | null)[];
11
+ readonly columns: AnyExpressionSource[];
14
12
  }
15
13
 
16
14
  export interface JoinState {