@prisma-next/sql-relational-core 0.5.0-dev.4 → 0.5.0-dev.40

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 (62) hide show
  1. package/README.md +67 -1
  2. package/dist/codec-types-DJEaWT36.d.mts +313 -0
  3. package/dist/codec-types-DJEaWT36.d.mts.map +1 -0
  4. package/dist/{errors-ChY_dHam.d.mts → errors-BRt5yHo9.d.mts} +2 -2
  5. package/dist/errors-BRt5yHo9.d.mts.map +1 -0
  6. package/dist/{errors-D3xmG4h-.mjs → errors-D6kqqjHM.mjs} +1 -1
  7. package/dist/{errors-D3xmG4h-.mjs.map → errors-D6kqqjHM.mjs.map} +1 -1
  8. package/dist/exports/ast.d.mts +27 -12
  9. package/dist/exports/ast.d.mts.map +1 -1
  10. package/dist/exports/ast.mjs +63 -1089
  11. package/dist/exports/ast.mjs.map +1 -1
  12. package/dist/exports/errors.d.mts +4 -4
  13. package/dist/exports/errors.mjs +1 -1
  14. package/dist/exports/expression.d.mts +79 -0
  15. package/dist/exports/expression.d.mts.map +1 -0
  16. package/dist/exports/expression.mjs +41 -0
  17. package/dist/exports/expression.mjs.map +1 -0
  18. package/dist/exports/plan.d.mts +3 -2
  19. package/dist/exports/plan.mjs +1 -17
  20. package/dist/exports/query-lane-context.d.mts +3 -3
  21. package/dist/exports/types.d.mts +5 -4
  22. package/dist/index.d.mts +11 -9
  23. package/dist/index.mjs +6 -4
  24. package/dist/plan-C7SiEWkN.d.mts +25 -0
  25. package/dist/plan-C7SiEWkN.d.mts.map +1 -0
  26. package/dist/{query-lane-context-UlR8vOkd.d.mts → query-lane-context-BF-wuc0r.d.mts} +53 -3
  27. package/dist/query-lane-context-BF-wuc0r.d.mts.map +1 -0
  28. package/dist/sql-execution-plan-Dgx7BGin.d.mts +33 -0
  29. package/dist/sql-execution-plan-Dgx7BGin.d.mts.map +1 -0
  30. package/dist/{types-C3Hg-CVz.d.mts → types-B4dL4lc3.d.mts} +17 -22
  31. package/dist/types-B4dL4lc3.d.mts.map +1 -0
  32. package/dist/types-BUlUvdIU.d.mts +24 -0
  33. package/dist/types-BUlUvdIU.d.mts.map +1 -0
  34. package/dist/{types-k9pir8XY.d.mts → types-BWOCTYd8.d.mts} +12 -19
  35. package/dist/types-BWOCTYd8.d.mts.map +1 -0
  36. package/dist/types-DUL-3vy6.mjs +1064 -0
  37. package/dist/types-DUL-3vy6.mjs.map +1 -0
  38. package/package.json +9 -8
  39. package/src/ast/adapter-types.ts +8 -0
  40. package/src/ast/codec-types.ts +251 -45
  41. package/src/ast/sql-codecs.ts +20 -3
  42. package/src/ast/types.ts +142 -172
  43. package/src/ast/util.ts +23 -0
  44. package/src/exports/expression.ts +1 -0
  45. package/src/exports/plan.ts +1 -0
  46. package/src/exports/types.ts +1 -0
  47. package/src/expression.ts +117 -0
  48. package/src/index.ts +1 -0
  49. package/src/plan.ts +11 -30
  50. package/src/query-lane-context.ts +52 -1
  51. package/src/runtime-scope.ts +20 -0
  52. package/src/sql-execution-plan.ts +28 -0
  53. package/src/types.ts +9 -22
  54. package/dist/codec-types-DcEITed4.d.mts +0 -144
  55. package/dist/codec-types-DcEITed4.d.mts.map +0 -1
  56. package/dist/errors-ChY_dHam.d.mts.map +0 -1
  57. package/dist/exports/plan.mjs.map +0 -1
  58. package/dist/plan-Cs65hb-E.d.mts +0 -28
  59. package/dist/plan-Cs65hb-E.d.mts.map +0 -1
  60. package/dist/query-lane-context-UlR8vOkd.d.mts.map +0 -1
  61. package/dist/types-C3Hg-CVz.d.mts.map +0 -1
  62. package/dist/types-k9pir8XY.d.mts.map +0 -1
@@ -1,3 +1,4 @@
1
+ import type { JsonValue } from '@prisma-next/contract/types';
1
2
  import { type as arktype } from 'arktype';
2
3
  import { codec, defineCodecs } from './codec-types';
3
4
 
@@ -104,12 +105,28 @@ const sqlTextCodec = codec({
104
105
  decode: (wire: string): string => wire,
105
106
  });
106
107
 
107
- const sqlTimestampCodec = codec({
108
+ const sqlTimestampCodec = codec<
109
+ typeof SQL_TIMESTAMP_CODEC_ID,
110
+ readonly ['equality', 'order'],
111
+ Date,
112
+ Date
113
+ >({
108
114
  typeId: SQL_TIMESTAMP_CODEC_ID,
109
115
  targetTypes: ['timestamp'],
110
116
  traits: ['equality', 'order'],
111
- encode: (value: string | Date): string => (value instanceof Date ? value.toISOString() : value),
112
- decode: (wire: string | Date): string => (wire instanceof Date ? wire.toISOString() : wire),
117
+ encode: (value: Date): Date => value,
118
+ decode: (wire: Date): Date => wire,
119
+ encodeJson: (value: Date): JsonValue => value.toISOString(),
120
+ decodeJson: (json: JsonValue): Date => {
121
+ if (typeof json !== 'string') {
122
+ throw new Error(`Expected ISO date string for sql/timestamp@1, got ${typeof json}`);
123
+ }
124
+ const date = new Date(json);
125
+ if (Number.isNaN(date.getTime())) {
126
+ throw new Error(`Invalid ISO date string for sql/timestamp@1: ${json}`);
127
+ }
128
+ return date;
129
+ },
113
130
  paramsSchema: precisionParamsSchema,
114
131
  renderOutputType: (typeParams) => {
115
132
  const precision = typeParams['precision'];
package/src/ast/types.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { PlanRefs } from '@prisma-next/contract/types';
2
1
  import type { ParamSpec } from '@prisma-next/operations';
3
2
  import type { SqlLoweringSpec } from '@prisma-next/sql-operations';
4
3
 
@@ -146,54 +145,89 @@ function collectParamRefsWith<TNode extends Expression>(node: TNode): ParamRef[]
146
145
  });
147
146
  }
148
147
 
149
- function sortRefs(
150
- tables: ReadonlySet<string>,
151
- columns: ReadonlyMap<string, { table: string; column: string }>,
152
- ): PlanRefs {
153
- const sortedTables = [...tables].sort((a, b) => a.localeCompare(b));
154
- const sortedColumns = [...columns.values()].sort((a, b) => {
155
- const tableCompare = a.table.localeCompare(b.table);
156
- if (tableCompare !== 0) {
157
- return tableCompare;
158
- }
159
- return a.column.localeCompare(b.column);
160
- });
148
+ function rewriteTableSource(table: TableSource, rewriter: AstRewriter): TableSource {
149
+ return rewriter.tableSource ? rewriter.tableSource(table) : table;
150
+ }
161
151
 
162
- return {
163
- tables: sortedTables,
164
- columns: sortedColumns,
165
- };
166
- }
167
-
168
- function addColumnRefToRefSets(
169
- columnRef: ColumnRef,
170
- tables: Set<string>,
171
- columns: Map<string, { table: string; column: string }>,
172
- ): void {
173
- if (columnRef.table === 'excluded') {
174
- return;
175
- }
176
- tables.add(columnRef.table);
177
- const key = `${columnRef.table}.${columnRef.column}`;
178
- if (!columns.has(key)) {
179
- columns.set(key, {
180
- table: columnRef.table,
181
- column: columnRef.column,
182
- });
152
+ function rewriteProjectionItem(item: ProjectionItem, rewriter: AstRewriter): ProjectionItem {
153
+ const rewrittenExpr =
154
+ item.expr.kind === 'literal'
155
+ ? rewriter.literal
156
+ ? rewriter.literal(item.expr)
157
+ : item.expr
158
+ : item.expr.rewrite(rewriter);
159
+ return new ProjectionItem(item.alias, rewrittenExpr, item.codecId);
160
+ }
161
+
162
+ function rewriteInsertValue(value: InsertValue, rewriter: AstRewriter): InsertValue {
163
+ switch (value.kind) {
164
+ case 'param-ref':
165
+ return rewriter.paramRef ? rewriteParamRefForInsert(value, rewriter) : value;
166
+ case 'column-ref':
167
+ return rewriter.columnRef ? rewriteColumnRefForInsert(value, rewriter) : value;
168
+ case 'default-value':
169
+ return value;
183
170
  }
184
171
  }
185
172
 
186
- function mergeRefsInto(
187
- refs: PlanRefs,
188
- tables: Set<string>,
189
- columns: Map<string, { table: string; column: string }>,
190
- ): void {
191
- for (const table of refs.tables ?? []) {
192
- tables.add(table);
173
+ function rewriteParamRefForInsert(value: ParamRef, rewriter: AstRewriter): InsertValue {
174
+ const rewritten = rewriter.paramRef ? rewriter.paramRef(value) : value;
175
+ return rewritten.kind === 'param-ref' ? rewritten : value;
176
+ }
177
+
178
+ function rewriteColumnRefForInsert(value: ColumnRef, rewriter: AstRewriter): InsertValue {
179
+ const rewritten = rewriter.columnRef ? rewriter.columnRef(value) : value;
180
+ return rewritten.kind === 'column-ref' ? rewritten : value;
181
+ }
182
+
183
+ function rewriteInsertRow(
184
+ row: Readonly<Record<string, InsertValue>>,
185
+ rewriter: AstRewriter,
186
+ ): Record<string, InsertValue> {
187
+ const result: Record<string, InsertValue> = {};
188
+ for (const [key, value] of Object.entries(row)) {
189
+ result[key] = rewriteInsertValue(value, rewriter);
193
190
  }
194
- for (const column of refs.columns ?? []) {
195
- addColumnRefToRefSets(new ColumnRef(column.table, column.column), tables, columns);
191
+ return result;
192
+ }
193
+
194
+ function rewriteUpdateSetValue(
195
+ value: ColumnRef | ParamRef,
196
+ rewriter: AstRewriter,
197
+ ): ColumnRef | ParamRef {
198
+ if (value.kind === 'column-ref') {
199
+ const rewritten = rewriter.columnRef ? rewriter.columnRef(value) : value;
200
+ return rewritten.kind === 'column-ref' ? rewritten : value;
201
+ }
202
+ const rewritten = rewriter.paramRef ? rewriter.paramRef(value) : value;
203
+ return rewritten.kind === 'param-ref' ? rewritten : value;
204
+ }
205
+
206
+ function rewriteUpdateSet(
207
+ set: Readonly<Record<string, ColumnRef | ParamRef>>,
208
+ rewriter: AstRewriter,
209
+ ): Record<string, ColumnRef | ParamRef> {
210
+ const result: Record<string, ColumnRef | ParamRef> = {};
211
+ for (const [key, value] of Object.entries(set)) {
212
+ result[key] = rewriteUpdateSetValue(value, rewriter);
196
213
  }
214
+ return result;
215
+ }
216
+
217
+ function rewriteOnConflict(onConflict: InsertOnConflict, rewriter: AstRewriter): InsertOnConflict {
218
+ const columns = onConflict.columns.map((columnRef) => {
219
+ const rewritten = rewriter.columnRef ? rewriter.columnRef(columnRef) : columnRef;
220
+ return rewritten.kind === 'column-ref' ? rewritten : columnRef;
221
+ });
222
+
223
+ if (onConflict.action.kind === 'do-nothing') {
224
+ return new InsertOnConflict(columns, new DoNothingConflictAction());
225
+ }
226
+
227
+ return new InsertOnConflict(
228
+ columns,
229
+ new DoUpdateSetConflictAction(rewriteUpdateSet(onConflict.action.set, rewriter)),
230
+ );
197
231
  }
198
232
 
199
233
  abstract class AstNode {
@@ -205,18 +239,11 @@ abstract class AstNode {
205
239
  }
206
240
 
207
241
  abstract class QueryAst extends AstNode {
208
- abstract collectRefs(): PlanRefs;
209
242
  abstract collectParamRefs(): ParamRef[];
210
243
  abstract toQueryAst(): AnyQueryAst;
211
-
212
- collectColumnRefs(): ColumnRef[] {
213
- const refs = this.collectRefs().columns ?? [];
214
- return refs.map((ref) => new ColumnRef(ref.table, ref.column));
215
- }
216
244
  }
217
245
 
218
246
  abstract class FromSource extends AstNode {
219
- abstract collectRefs(): PlanRefs;
220
247
  abstract rewrite(rewriter: AstRewriter): AnyFromSource;
221
248
  abstract toFromSource(): AnyFromSource;
222
249
  }
@@ -270,13 +297,6 @@ export class TableSource extends FromSource {
270
297
  override toFromSource(): AnyFromSource {
271
298
  return this;
272
299
  }
273
-
274
- override collectRefs(): PlanRefs {
275
- return {
276
- tables: [this.name],
277
- columns: [],
278
- };
279
- }
280
300
  }
281
301
 
282
302
  export interface TableRef {
@@ -310,10 +330,6 @@ export class DerivedTableSource extends FromSource {
310
330
  override toFromSource(): AnyFromSource {
311
331
  return this;
312
332
  }
313
-
314
- override collectRefs(): PlanRefs {
315
- return this.query.collectRefs();
316
- }
317
333
  }
318
334
 
319
335
  export class ColumnRef extends Expression {
@@ -1052,16 +1068,22 @@ export class ProjectionItem extends AstNode {
1052
1068
  readonly kind = 'projection-item' as const;
1053
1069
  readonly alias: string;
1054
1070
  readonly expr: ProjectionExpr;
1071
+ readonly codecId: string | undefined;
1055
1072
 
1056
- constructor(alias: string, expr: ProjectionExpr) {
1073
+ constructor(alias: string, expr: ProjectionExpr, codecId?: string) {
1057
1074
  super();
1058
1075
  this.alias = alias;
1059
1076
  this.expr = expr;
1077
+ this.codecId = codecId;
1060
1078
  this.freeze();
1061
1079
  }
1062
1080
 
1063
- static of(alias: string, expr: ProjectionExpr): ProjectionItem {
1064
- return new ProjectionItem(alias, expr);
1081
+ static of(alias: string, expr: ProjectionExpr, codecId?: string): ProjectionItem {
1082
+ return new ProjectionItem(alias, expr, codecId);
1083
+ }
1084
+
1085
+ withCodecId(codecId: string | undefined): ProjectionItem {
1086
+ return new ProjectionItem(this.alias, this.expr, codecId);
1065
1087
  }
1066
1088
  }
1067
1089
 
@@ -1218,6 +1240,7 @@ export class SelectAst extends QueryAst {
1218
1240
  ? rewriter.literal(projection.expr)
1219
1241
  : projection.expr
1220
1242
  : projection.expr.rewrite(rewriter),
1243
+ projection.codecId,
1221
1244
  ),
1222
1245
  ),
1223
1246
  where: this.where?.rewrite(rewriter),
@@ -1234,7 +1257,7 @@ export class SelectAst extends QueryAst {
1234
1257
  return rewriter.select ? rewriter.select(rewritten) : rewritten;
1235
1258
  }
1236
1259
 
1237
- override collectColumnRefs(): ColumnRef[] {
1260
+ collectColumnRefs(): ColumnRef[] {
1238
1261
  const refs: ColumnRef[] = [];
1239
1262
  const pushRefs = (columns: ReadonlyArray<ColumnRef>) => {
1240
1263
  refs.push(...columns);
@@ -1322,35 +1345,6 @@ export class SelectAst extends QueryAst {
1322
1345
  return refs;
1323
1346
  }
1324
1347
 
1325
- override collectRefs(): PlanRefs {
1326
- const tables = new Set<string>();
1327
- const columns = new Map<string, { table: string; column: string }>();
1328
-
1329
- const addSource = (source: AnyFromSource) => {
1330
- mergeRefsInto(source.collectRefs(), tables, columns);
1331
- };
1332
-
1333
- addSource(this.from);
1334
-
1335
- for (const join of this.joins ?? []) {
1336
- addSource(join.source);
1337
- if (join.on.kind === 'eq-col-join-on') {
1338
- addColumnRefToRefSets(join.on.left, tables, columns);
1339
- addColumnRefToRefSets(join.on.right, tables, columns);
1340
- } else {
1341
- for (const columnRef of join.on.collectColumnRefs()) {
1342
- addColumnRefToRefSets(columnRef, tables, columns);
1343
- }
1344
- }
1345
- }
1346
-
1347
- for (const columnRef of this.collectColumnRefs()) {
1348
- addColumnRefToRefSets(columnRef, tables, columns);
1349
- }
1350
-
1351
- return sortRefs(tables, columns);
1352
- }
1353
-
1354
1348
  override toQueryAst(): AnyQueryAst {
1355
1349
  return this;
1356
1350
  }
@@ -1418,13 +1412,13 @@ export class InsertAst extends QueryAst {
1418
1412
  readonly table: TableSource;
1419
1413
  readonly rows: ReadonlyArray<Readonly<Record<string, InsertValue>>>;
1420
1414
  readonly onConflict: InsertOnConflict | undefined;
1421
- readonly returning: ReadonlyArray<ColumnRef> | undefined;
1415
+ readonly returning: ReadonlyArray<ProjectionItem> | undefined;
1422
1416
 
1423
1417
  constructor(
1424
1418
  table: TableSource,
1425
1419
  rows: ReadonlyArray<Record<string, InsertValue>> = [{}],
1426
1420
  onConflict?: InsertOnConflict,
1427
- returning?: ReadonlyArray<ColumnRef>,
1421
+ returning?: ReadonlyArray<ProjectionItem>,
1428
1422
  ) {
1429
1423
  super();
1430
1424
  this.table = table;
@@ -1451,7 +1445,7 @@ export class InsertAst extends QueryAst {
1451
1445
  );
1452
1446
  }
1453
1447
 
1454
- withReturning(returning: ReadonlyArray<ColumnRef> | undefined): InsertAst {
1448
+ withReturning(returning: ReadonlyArray<ProjectionItem> | undefined): InsertAst {
1455
1449
  return new InsertAst(
1456
1450
  this.table,
1457
1451
  this.rows.map((row) => ({ ...row })),
@@ -1469,6 +1463,15 @@ export class InsertAst extends QueryAst {
1469
1463
  );
1470
1464
  }
1471
1465
 
1466
+ rewrite(rewriter: AstRewriter): InsertAst {
1467
+ return new InsertAst(
1468
+ rewriteTableSource(this.table, rewriter),
1469
+ this.rows.map((row) => rewriteInsertRow(row, rewriter)),
1470
+ this.onConflict ? rewriteOnConflict(this.onConflict, rewriter) : undefined,
1471
+ this.returning?.map((item) => rewriteProjectionItem(item, rewriter)),
1472
+ );
1473
+ }
1474
+
1472
1475
  override collectParamRefs(): ParamRef[] {
1473
1476
  const refs: ParamRef[] = [];
1474
1477
  for (const row of this.rows) {
@@ -1485,44 +1488,12 @@ export class InsertAst extends QueryAst {
1485
1488
  }
1486
1489
  }
1487
1490
  }
1488
- return refs;
1489
- }
1490
-
1491
- override collectRefs(): PlanRefs {
1492
- const tables = new Set<string>([this.table.name]);
1493
- const columns = new Map<string, { table: string; column: string }>();
1494
-
1495
- const addColumn = (columnRef: ColumnRef) => addColumnRefToRefSets(columnRef, tables, columns);
1496
- const addValue = (value: InsertValue) => {
1497
- if (value.kind === 'column-ref') {
1498
- addColumn(value);
1491
+ for (const item of this.returning ?? []) {
1492
+ if (item.expr.kind !== 'literal') {
1493
+ refs.push(...item.expr.collectParamRefs());
1499
1494
  }
1500
- };
1501
-
1502
- for (const row of this.rows) {
1503
- for (const value of Object.values(row)) {
1504
- addValue(value);
1505
- }
1506
- }
1507
-
1508
- for (const columnRef of this.returning ?? []) {
1509
- addColumn(columnRef);
1510
1495
  }
1511
-
1512
- if (this.onConflict) {
1513
- for (const columnRef of this.onConflict.columns) {
1514
- addColumn(columnRef);
1515
- }
1516
- if (this.onConflict.action.kind === 'do-update-set') {
1517
- for (const value of Object.values(this.onConflict.action.set)) {
1518
- if (value.kind === 'column-ref') {
1519
- addColumn(value);
1520
- }
1521
- }
1522
- }
1523
- }
1524
-
1525
- return sortRefs(tables, columns);
1496
+ return refs;
1526
1497
  }
1527
1498
 
1528
1499
  override toQueryAst(): AnyQueryAst {
@@ -1535,13 +1506,13 @@ export class UpdateAst extends QueryAst {
1535
1506
  readonly table: TableSource;
1536
1507
  readonly set: Readonly<Record<string, ColumnRef | ParamRef>>;
1537
1508
  readonly where: AnyExpression | undefined;
1538
- readonly returning: ReadonlyArray<ColumnRef> | undefined;
1509
+ readonly returning: ReadonlyArray<ProjectionItem> | undefined;
1539
1510
 
1540
1511
  constructor(
1541
1512
  table: TableSource,
1542
1513
  set: Readonly<Record<string, ColumnRef | ParamRef>> = {},
1543
1514
  where?: AnyExpression,
1544
- returning?: ReadonlyArray<ColumnRef>,
1515
+ returning?: ReadonlyArray<ProjectionItem>,
1545
1516
  ) {
1546
1517
  super();
1547
1518
  this.table = table;
@@ -1563,10 +1534,19 @@ export class UpdateAst extends QueryAst {
1563
1534
  return new UpdateAst(this.table, this.set, where, this.returning);
1564
1535
  }
1565
1536
 
1566
- withReturning(returning: ReadonlyArray<ColumnRef> | undefined): UpdateAst {
1537
+ withReturning(returning: ReadonlyArray<ProjectionItem> | undefined): UpdateAst {
1567
1538
  return new UpdateAst(this.table, this.set, this.where, returning);
1568
1539
  }
1569
1540
 
1541
+ rewrite(rewriter: AstRewriter): UpdateAst {
1542
+ return new UpdateAst(
1543
+ rewriteTableSource(this.table, rewriter),
1544
+ rewriteUpdateSet(this.set, rewriter),
1545
+ this.where?.rewrite(rewriter),
1546
+ this.returning?.map((item) => rewriteProjectionItem(item, rewriter)),
1547
+ );
1548
+ }
1549
+
1570
1550
  override collectParamRefs(): ParamRef[] {
1571
1551
  const refs: ParamRef[] = [];
1572
1552
  for (const value of Object.values(this.set)) {
@@ -1577,28 +1557,12 @@ export class UpdateAst extends QueryAst {
1577
1557
  if (this.where) {
1578
1558
  refs.push(...this.where.collectParamRefs());
1579
1559
  }
1580
- return refs;
1581
- }
1582
-
1583
- override collectRefs(): PlanRefs {
1584
- const tables = new Set<string>([this.table.name]);
1585
- const columns = new Map<string, { table: string; column: string }>();
1586
-
1587
- for (const value of Object.values(this.set)) {
1588
- if (value.kind === 'column-ref') {
1589
- addColumnRefToRefSets(value, tables, columns);
1560
+ for (const item of this.returning ?? []) {
1561
+ if (item.expr.kind !== 'literal') {
1562
+ refs.push(...item.expr.collectParamRefs());
1590
1563
  }
1591
1564
  }
1592
-
1593
- for (const columnRef of this.where?.collectColumnRefs() ?? []) {
1594
- addColumnRefToRefSets(columnRef, tables, columns);
1595
- }
1596
-
1597
- for (const columnRef of this.returning ?? []) {
1598
- addColumnRefToRefSets(columnRef, tables, columns);
1599
- }
1600
-
1601
- return sortRefs(tables, columns);
1565
+ return refs;
1602
1566
  }
1603
1567
 
1604
1568
  override toQueryAst(): AnyQueryAst {
@@ -1610,9 +1574,13 @@ export class DeleteAst extends QueryAst {
1610
1574
  readonly kind = 'delete' as const;
1611
1575
  readonly table: TableSource;
1612
1576
  readonly where: AnyExpression | undefined;
1613
- readonly returning: ReadonlyArray<ColumnRef> | undefined;
1577
+ readonly returning: ReadonlyArray<ProjectionItem> | undefined;
1614
1578
 
1615
- constructor(table: TableSource, where?: AnyExpression, returning?: ReadonlyArray<ColumnRef>) {
1579
+ constructor(
1580
+ table: TableSource,
1581
+ where?: AnyExpression,
1582
+ returning?: ReadonlyArray<ProjectionItem>,
1583
+ ) {
1616
1584
  super();
1617
1585
  this.table = table;
1618
1586
  this.where = where;
@@ -1628,27 +1596,29 @@ export class DeleteAst extends QueryAst {
1628
1596
  return new DeleteAst(this.table, where, this.returning);
1629
1597
  }
1630
1598
 
1631
- withReturning(returning: ReadonlyArray<ColumnRef> | undefined): DeleteAst {
1599
+ withReturning(returning: ReadonlyArray<ProjectionItem> | undefined): DeleteAst {
1632
1600
  return new DeleteAst(this.table, this.where, returning);
1633
1601
  }
1634
1602
 
1635
- override collectParamRefs(): ParamRef[] {
1636
- return this.where?.collectParamRefs() ?? [];
1603
+ rewrite(rewriter: AstRewriter): DeleteAst {
1604
+ return new DeleteAst(
1605
+ rewriteTableSource(this.table, rewriter),
1606
+ this.where?.rewrite(rewriter),
1607
+ this.returning?.map((item) => rewriteProjectionItem(item, rewriter)),
1608
+ );
1637
1609
  }
1638
1610
 
1639
- override collectRefs(): PlanRefs {
1640
- const tables = new Set<string>([this.table.name]);
1641
- const columns = new Map<string, { table: string; column: string }>();
1642
-
1643
- for (const columnRef of this.where?.collectColumnRefs() ?? []) {
1644
- addColumnRefToRefSets(columnRef, tables, columns);
1611
+ override collectParamRefs(): ParamRef[] {
1612
+ const refs: ParamRef[] = [];
1613
+ if (this.where) {
1614
+ refs.push(...this.where.collectParamRefs());
1645
1615
  }
1646
-
1647
- for (const columnRef of this.returning ?? []) {
1648
- addColumnRefToRefSets(columnRef, tables, columns);
1616
+ for (const item of this.returning ?? []) {
1617
+ if (item.expr.kind !== 'literal') {
1618
+ refs.push(...item.expr.collectParamRefs());
1619
+ }
1649
1620
  }
1650
-
1651
- return sortRefs(tables, columns);
1621
+ return refs;
1652
1622
  }
1653
1623
 
1654
1624
  override toQueryAst(): AnyQueryAst {
package/src/ast/util.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { AnyQueryAst, ParamRef } from './types';
2
+
1
3
  export function compact<T extends Record<string, unknown>>(o: T): T {
2
4
  const out: Record<string, unknown> = {};
3
5
  for (const [k, v] of Object.entries(o)) {
@@ -7,3 +9,24 @@ export function compact<T extends Record<string, unknown>>(o: T): T {
7
9
  }
8
10
  return out as T;
9
11
  }
12
+
13
+ /**
14
+ * Walks an AST's parameter references in first-encounter order and dedupes
15
+ * by ParamRef identity. The single canonical helper used by every consumer
16
+ * that aligns `plan.params` with metadata-by-index — the SQL builder lane,
17
+ * the SQL ORM client, the SQL runtime encoder, and the Postgres renderer's
18
+ * `$N` index map — so the four walks cannot drift in dedupe semantics.
19
+ *
20
+ * SQLite's `?`-placeholder renderer intentionally does NOT use this helper
21
+ * because it needs one params entry per occurrence in the SQL.
22
+ */
23
+ export function collectOrderedParamRefs(ast: AnyQueryAst): ReadonlyArray<ParamRef> {
24
+ const seen = new Set<ParamRef>();
25
+ const ordered: ParamRef[] = [];
26
+ for (const ref of ast.collectParamRefs()) {
27
+ if (seen.has(ref)) continue;
28
+ seen.add(ref);
29
+ ordered.push(ref);
30
+ }
31
+ return Object.freeze(ordered);
32
+ }
@@ -0,0 +1 @@
1
+ export * from '../expression';
@@ -1 +1,2 @@
1
1
  export * from '../plan';
2
+ export type { SqlExecutionPlan } from '../sql-execution-plan';
@@ -1 +1,2 @@
1
+ export type { RuntimeScope, SqlOrmPlan } from '../runtime-scope';
1
2
  export * from '../types';
@@ -0,0 +1,117 @@
1
+ import type { ParamSpec } from '@prisma-next/operations';
2
+ import type { QueryOperationReturn } from '@prisma-next/sql-contract/types';
3
+ import type { SqlLoweringSpec } from '@prisma-next/sql-operations';
4
+ import type { AnyExpression as AstExpression } from './ast/types';
5
+ import { OperationExpr, ParamRef } from './ast/types';
6
+
7
+ export type ScopeField = { codecId: string; nullable: boolean };
8
+
9
+ /**
10
+ * A typed SQL expression. Identity is carried by the `returnType` descriptor
11
+ * (inherited from `QueryOperationReturn` and narrowed to `T`) — distinct `T`
12
+ * makes distinct Expression types structurally. `buildAst()` materialises the
13
+ * underlying AST node.
14
+ */
15
+ export type Expression<T extends ScopeField> = QueryOperationReturn & {
16
+ readonly returnType: T;
17
+ buildAst(): AstExpression;
18
+ };
19
+
20
+ type CodecIdsWithTrait<
21
+ CT extends Record<string, { readonly input: unknown }>,
22
+ RequiredTraits extends readonly string[],
23
+ > = {
24
+ [K in keyof CT & string]: CT[K] extends { readonly traits: infer T }
25
+ ? [RequiredTraits[number]] extends [T]
26
+ ? K
27
+ : never
28
+ : never;
29
+ }[keyof CT & string];
30
+
31
+ type NullSuffix<N> = N extends true ? null : never;
32
+
33
+ /**
34
+ * An expression or literal value targeting a specific codec.
35
+ *
36
+ * Accepts any of:
37
+ * - An `Expression` whose codec matches exactly
38
+ * - A raw JS value of the codec's `input` type
39
+ * - `null` when `Nullable` is true
40
+ */
41
+ export type CodecExpression<
42
+ CodecId extends string,
43
+ Nullable extends boolean,
44
+ CT extends Record<string, { readonly input: unknown }>,
45
+ > =
46
+ | Expression<{ codecId: CodecId; nullable: Nullable }>
47
+ | (CodecId extends keyof CT ? CT[CodecId]['input'] : never)
48
+ | NullSuffix<Nullable>;
49
+
50
+ /**
51
+ * An expression or literal value targeting any codec whose trait set contains
52
+ * all the required traits.
53
+ *
54
+ * Resolves the trait set to the union of matching codec identities via
55
+ * `CodecIdsWithTrait`, then reuses `CodecExpression` for the codec-id form.
56
+ */
57
+ export type TraitExpression<
58
+ Traits extends readonly string[],
59
+ Nullable extends boolean,
60
+ CT extends Record<string, { readonly input: unknown }>,
61
+ > = CodecExpression<CodecIdsWithTrait<CT, Traits>, Nullable, CT>;
62
+
63
+ /**
64
+ * Resolve a raw value or an Expression into an AST expression node.
65
+ *
66
+ * When `value` is an Expression (duck-typed by its `buildAst` method), the AST
67
+ * it wraps is returned. Otherwise the value is embedded as a ParamRef tagged
68
+ * with `codecId` (if given). Pass `codecId` to encode the literal with a
69
+ * specific codec — most operations do.
70
+ */
71
+ export function toExpr(value: unknown, codecId?: string): AstExpression {
72
+ if (isExpressionLike(value)) {
73
+ return value.buildAst();
74
+ }
75
+ return ParamRef.of(value, codecId ? { codecId } : undefined);
76
+ }
77
+
78
+ function isExpressionLike(value: unknown): value is Expression<ScopeField> {
79
+ return (
80
+ typeof value === 'object' &&
81
+ value !== null &&
82
+ 'buildAst' in value &&
83
+ typeof (value as { buildAst: unknown }).buildAst === 'function'
84
+ );
85
+ }
86
+
87
+ export interface BuildOperationSpec<R extends ScopeField> {
88
+ readonly method: string;
89
+ /**
90
+ * The operation's arguments. The first element is the self argument (the
91
+ * value the operation is being applied to); the rest are the remaining
92
+ * user-supplied arguments.
93
+ */
94
+ readonly args: readonly [AstExpression, ...AstExpression[]];
95
+ readonly returns: R & ParamSpec;
96
+ readonly lowering: SqlLoweringSpec;
97
+ }
98
+
99
+ /**
100
+ * Construct an OperationExpr AST node and wrap it as a typed Expression.
101
+ * Operation implementations use this to turn their user-facing arguments into
102
+ * the AST node the compilation pipeline eventually lowers to SQL.
103
+ */
104
+ export function buildOperation<R extends ScopeField>(spec: BuildOperationSpec<R>): Expression<R> {
105
+ const [self, ...rest] = spec.args;
106
+ const op = new OperationExpr({
107
+ method: spec.method,
108
+ self,
109
+ args: rest.length > 0 ? rest : undefined,
110
+ returns: spec.returns,
111
+ lowering: spec.lowering,
112
+ });
113
+ return {
114
+ returnType: spec.returns,
115
+ buildAst: () => op,
116
+ };
117
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './exports/ast';
2
2
  export * from './exports/errors';
3
+ export * from './exports/expression';
3
4
  export * from './exports/plan';
4
5
  export * from './exports/query-lane-context';
5
6
  export * from './exports/types';