@prisma-next/sql-relational-core 0.5.0-dev.9 → 0.6.0-dev.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/README.md +59 -25
- package/dist/codec-types-BUfBna4G.d.mts +107 -0
- package/dist/codec-types-BUfBna4G.d.mts.map +1 -0
- package/dist/{errors-D3xmG4h-.mjs → errors-BF7W5uUd.mjs} +2 -2
- package/dist/{errors-D3xmG4h-.mjs.map → errors-BF7W5uUd.mjs.map} +1 -1
- package/dist/{errors-CDvhnH7P.d.mts → errors-BgNpVAT8.d.mts} +2 -2
- package/dist/errors-BgNpVAT8.d.mts.map +1 -0
- package/dist/exports/ast.d.mts +189 -112
- package/dist/exports/ast.d.mts.map +1 -1
- package/dist/exports/ast.mjs +207 -1322
- package/dist/exports/ast.mjs.map +1 -1
- package/dist/exports/codec-descriptor-registry.d.mts +18 -0
- package/dist/exports/codec-descriptor-registry.d.mts.map +1 -0
- package/dist/exports/codec-descriptor-registry.mjs +40 -0
- package/dist/exports/codec-descriptor-registry.mjs.map +1 -0
- package/dist/exports/errors.d.mts +1 -4
- package/dist/exports/errors.mjs +2 -3
- package/dist/exports/expression.d.mts +84 -0
- package/dist/exports/expression.d.mts.map +1 -0
- package/dist/exports/expression.mjs +62 -0
- package/dist/exports/expression.mjs.map +1 -0
- package/dist/exports/middleware.d.mts +2 -0
- package/dist/exports/middleware.mjs +2 -0
- package/dist/exports/plan.d.mts +3 -3
- package/dist/exports/plan.mjs +27 -10
- package/dist/exports/plan.mjs.map +1 -1
- package/dist/exports/query-lane-context.d.mts +2 -3
- package/dist/exports/query-lane-context.mjs +1 -1
- package/dist/exports/types.d.mts +2 -4
- package/dist/exports/types.mjs +1 -1
- package/dist/index.d.mts +12 -12
- package/dist/index.mjs +8 -5
- package/dist/middleware-D2Wv9QIf.mjs +82 -0
- package/dist/middleware-D2Wv9QIf.mjs.map +1 -0
- package/dist/middleware-USDeR8p1.d.mts +121 -0
- package/dist/middleware-USDeR8p1.d.mts.map +1 -0
- package/dist/plan-DFpOIsKB.d.mts +44 -0
- package/dist/plan-DFpOIsKB.d.mts.map +1 -0
- package/dist/query-lane-context-C2tLZGp4.d.mts +72 -0
- package/dist/query-lane-context-C2tLZGp4.d.mts.map +1 -0
- package/dist/{sql-execution-plan-DY0WvJOW.d.mts → sql-execution-plan-ffz8TSfr.d.mts} +2 -3
- package/dist/sql-execution-plan-ffz8TSfr.d.mts.map +1 -0
- package/dist/{types-C3Hg-CVz.d.mts → types-BZcKgaYA.d.mts} +61 -24
- package/dist/types-BZcKgaYA.d.mts.map +1 -0
- package/dist/types-CK9ZJ6OU.mjs +1112 -0
- package/dist/types-CK9ZJ6OU.mjs.map +1 -0
- package/dist/{types-B5XsBeaT.d.mts → types-CL18G37a.d.mts} +3 -4
- package/dist/types-CL18G37a.d.mts.map +1 -0
- package/dist/{types-CG3u534i.d.mts → types-D6Pk1DTr.d.mts} +11 -13
- package/dist/types-D6Pk1DTr.d.mts.map +1 -0
- package/dist/util-DWmhUCEO.mjs +34 -0
- package/dist/util-DWmhUCEO.mjs.map +1 -0
- package/package.json +16 -13
- package/src/ast/adapter-types.ts +13 -16
- package/src/ast/codec-types.ts +87 -419
- package/src/ast/sql-codec-helpers.ts +79 -0
- package/src/ast/sql-codecs.ts +285 -125
- package/src/ast/types.ts +222 -173
- package/src/ast/util.ts +23 -0
- package/src/ast/validate-param-refs.ts +39 -0
- package/src/codec-descriptor-registry.ts +52 -0
- package/src/exports/ast.ts +2 -0
- package/src/exports/codec-descriptor-registry.ts +1 -0
- package/src/exports/expression.ts +1 -0
- package/src/exports/middleware.ts +8 -0
- package/src/expression.ts +134 -0
- package/src/index.ts +2 -0
- package/src/middleware/param-ref-mutator.ts +230 -0
- package/src/plan.ts +32 -16
- package/src/query-lane-context.ts +35 -55
- package/src/types.ts +1 -3
- package/dist/codec-types-B2Xdq0Wr.d.mts +0 -167
- package/dist/codec-types-B2Xdq0Wr.d.mts.map +0 -1
- package/dist/errors-CDvhnH7P.d.mts.map +0 -1
- package/dist/plan-CRPxW2Jj.d.mts +0 -32
- package/dist/plan-CRPxW2Jj.d.mts.map +0 -1
- package/dist/query-lane-context-DDRW5NBG.d.mts +0 -89
- package/dist/query-lane-context-DDRW5NBG.d.mts.map +0 -1
- package/dist/sql-execution-plan-DY0WvJOW.d.mts.map +0 -1
- package/dist/types-B5XsBeaT.d.mts.map +0 -1
- package/dist/types-C3Hg-CVz.d.mts.map +0 -1
- package/dist/types-CG3u534i.d.mts.map +0 -1
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
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
):
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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, item.refs);
|
|
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;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
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);
|
|
190
|
+
}
|
|
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;
|
|
183
201
|
}
|
|
202
|
+
const rewritten = rewriter.paramRef ? rewriter.paramRef(value) : value;
|
|
203
|
+
return rewritten.kind === 'param-ref' ? rewritten : value;
|
|
184
204
|
}
|
|
185
205
|
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
for (const
|
|
192
|
-
|
|
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);
|
|
193
213
|
}
|
|
194
|
-
|
|
195
|
-
|
|
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());
|
|
196
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 {
|
|
@@ -300,9 +320,7 @@ export class DerivedTableSource extends FromSource {
|
|
|
300
320
|
return new DerivedTableSource(alias, query);
|
|
301
321
|
}
|
|
302
322
|
|
|
303
|
-
// Intentionally does not call rewriter.tableSource — derived tables are rewritten
|
|
304
|
-
// via their inner query, not intercepted at the FromSource level. A future
|
|
305
|
-
// fromSource?(source: AnyFromSource) callback would be needed for that.
|
|
323
|
+
// Intentionally does not call rewriter.tableSource — derived tables are rewritten via their inner query, not intercepted at the FromSource level. A future fromSource?(source: AnyFromSource) callback would be needed for that.
|
|
306
324
|
override rewrite(rewriter: AstRewriter): AnyFromSource {
|
|
307
325
|
return new DerivedTableSource(this.alias, this.query.rewrite(rewriter));
|
|
308
326
|
}
|
|
@@ -310,10 +328,6 @@ export class DerivedTableSource extends FromSource {
|
|
|
310
328
|
override toFromSource(): AnyFromSource {
|
|
311
329
|
return this;
|
|
312
330
|
}
|
|
313
|
-
|
|
314
|
-
override collectRefs(): PlanRefs {
|
|
315
|
-
return this.query.collectRefs();
|
|
316
|
-
}
|
|
317
331
|
}
|
|
318
332
|
|
|
319
333
|
export class ColumnRef extends Expression {
|
|
@@ -376,23 +390,37 @@ export class IdentifierRef extends Expression {
|
|
|
376
390
|
}
|
|
377
391
|
}
|
|
378
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Column ref carried by a {@link ParamRef} that was constructed at a column-bound site (e.g. an INSERT/UPDATE column assignment, a `WHERE column = $param` comparison). The encode-side dispatch path uses `refs` to call `contractCodecs.forColumn(refs.table, refs.column)`, which selects the per-instance parameterized codec for the column — required when a parameterized codec id is shared by multiple columns with distinct
|
|
395
|
+
* typeParams (e.g. `vector(1024)` vs. `vector(1536)`).
|
|
396
|
+
*
|
|
397
|
+
* `refs` may legitimately be `undefined` for `ParamRef`s constructed without a column context — the validator pass (`validateParamRefRefs`) treats refs-less ParamRefs as a hard error only when their codec id is parameterized.
|
|
398
|
+
*/
|
|
399
|
+
export interface ParamRefBindingRefs {
|
|
400
|
+
readonly table: string;
|
|
401
|
+
readonly column: string;
|
|
402
|
+
}
|
|
403
|
+
|
|
379
404
|
export class ParamRef extends Expression {
|
|
380
405
|
readonly kind = 'param-ref' as const;
|
|
381
406
|
readonly value: unknown;
|
|
382
407
|
readonly name: string | undefined;
|
|
383
408
|
readonly codecId: string | undefined;
|
|
409
|
+
readonly refs: ParamRefBindingRefs | undefined;
|
|
384
410
|
|
|
385
411
|
constructor(
|
|
386
412
|
value: unknown,
|
|
387
413
|
options?: {
|
|
388
414
|
name?: string;
|
|
389
415
|
codecId?: string;
|
|
416
|
+
refs?: ParamRefBindingRefs;
|
|
390
417
|
},
|
|
391
418
|
) {
|
|
392
419
|
super();
|
|
393
420
|
this.value = value;
|
|
394
421
|
this.name = options?.name;
|
|
395
422
|
this.codecId = options?.codecId;
|
|
423
|
+
this.refs = options?.refs ? Object.freeze({ ...options.refs }) : undefined;
|
|
396
424
|
this.freeze();
|
|
397
425
|
}
|
|
398
426
|
|
|
@@ -401,6 +429,7 @@ export class ParamRef extends Expression {
|
|
|
401
429
|
options?: {
|
|
402
430
|
name?: string;
|
|
403
431
|
codecId?: string;
|
|
432
|
+
refs?: ParamRefBindingRefs;
|
|
404
433
|
},
|
|
405
434
|
): ParamRef {
|
|
406
435
|
return new ParamRef(value, options);
|
|
@@ -1052,16 +1081,33 @@ export class ProjectionItem extends AstNode {
|
|
|
1052
1081
|
readonly kind = 'projection-item' as const;
|
|
1053
1082
|
readonly alias: string;
|
|
1054
1083
|
readonly expr: ProjectionExpr;
|
|
1084
|
+
readonly codecId: string | undefined;
|
|
1085
|
+
/**
|
|
1086
|
+
* Column-bound metadata for the projection. Populated by builder paths that promote a top-level field shortcut (`select('email', ...)`) into a bare `IdentifierRef` AST while still knowing the originating `(table, column)`. Decode-side dispatch consults `refs` to call `forColumn(table, column)` so parameterized codec ids resolve to the per-instance codec — required when multiple columns share a codec id with distinct
|
|
1087
|
+
* typeParams (e.g. `varchar(36)` vs. `varchar(255)`). Stays `undefined` for `column-ref` projections (the AST already carries the binding) and for non-column-bound projections (computed expressions, subqueries, raw aliases).
|
|
1088
|
+
*/
|
|
1089
|
+
readonly refs: ParamRefBindingRefs | undefined;
|
|
1055
1090
|
|
|
1056
|
-
constructor(alias: string, expr: ProjectionExpr) {
|
|
1091
|
+
constructor(alias: string, expr: ProjectionExpr, codecId?: string, refs?: ParamRefBindingRefs) {
|
|
1057
1092
|
super();
|
|
1058
1093
|
this.alias = alias;
|
|
1059
1094
|
this.expr = expr;
|
|
1095
|
+
this.codecId = codecId;
|
|
1096
|
+
this.refs = refs ? Object.freeze({ ...refs }) : undefined;
|
|
1060
1097
|
this.freeze();
|
|
1061
1098
|
}
|
|
1062
1099
|
|
|
1063
|
-
static of(
|
|
1064
|
-
|
|
1100
|
+
static of(
|
|
1101
|
+
alias: string,
|
|
1102
|
+
expr: ProjectionExpr,
|
|
1103
|
+
codecId?: string,
|
|
1104
|
+
refs?: ParamRefBindingRefs,
|
|
1105
|
+
): ProjectionItem {
|
|
1106
|
+
return new ProjectionItem(alias, expr, codecId, refs);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
withCodecId(codecId: string | undefined): ProjectionItem {
|
|
1110
|
+
return new ProjectionItem(this.alias, this.expr, codecId, this.refs);
|
|
1065
1111
|
}
|
|
1066
1112
|
}
|
|
1067
1113
|
|
|
@@ -1218,6 +1264,8 @@ export class SelectAst extends QueryAst {
|
|
|
1218
1264
|
? rewriter.literal(projection.expr)
|
|
1219
1265
|
: projection.expr
|
|
1220
1266
|
: projection.expr.rewrite(rewriter),
|
|
1267
|
+
projection.codecId,
|
|
1268
|
+
projection.refs,
|
|
1221
1269
|
),
|
|
1222
1270
|
),
|
|
1223
1271
|
where: this.where?.rewrite(rewriter),
|
|
@@ -1234,7 +1282,7 @@ export class SelectAst extends QueryAst {
|
|
|
1234
1282
|
return rewriter.select ? rewriter.select(rewritten) : rewritten;
|
|
1235
1283
|
}
|
|
1236
1284
|
|
|
1237
|
-
|
|
1285
|
+
collectColumnRefs(): ColumnRef[] {
|
|
1238
1286
|
const refs: ColumnRef[] = [];
|
|
1239
1287
|
const pushRefs = (columns: ReadonlyArray<ColumnRef>) => {
|
|
1240
1288
|
refs.push(...columns);
|
|
@@ -1322,35 +1370,6 @@ export class SelectAst extends QueryAst {
|
|
|
1322
1370
|
return refs;
|
|
1323
1371
|
}
|
|
1324
1372
|
|
|
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
1373
|
override toQueryAst(): AnyQueryAst {
|
|
1355
1374
|
return this;
|
|
1356
1375
|
}
|
|
@@ -1418,13 +1437,13 @@ export class InsertAst extends QueryAst {
|
|
|
1418
1437
|
readonly table: TableSource;
|
|
1419
1438
|
readonly rows: ReadonlyArray<Readonly<Record<string, InsertValue>>>;
|
|
1420
1439
|
readonly onConflict: InsertOnConflict | undefined;
|
|
1421
|
-
readonly returning: ReadonlyArray<
|
|
1440
|
+
readonly returning: ReadonlyArray<ProjectionItem> | undefined;
|
|
1422
1441
|
|
|
1423
1442
|
constructor(
|
|
1424
1443
|
table: TableSource,
|
|
1425
1444
|
rows: ReadonlyArray<Record<string, InsertValue>> = [{}],
|
|
1426
1445
|
onConflict?: InsertOnConflict,
|
|
1427
|
-
returning?: ReadonlyArray<
|
|
1446
|
+
returning?: ReadonlyArray<ProjectionItem>,
|
|
1428
1447
|
) {
|
|
1429
1448
|
super();
|
|
1430
1449
|
this.table = table;
|
|
@@ -1451,7 +1470,7 @@ export class InsertAst extends QueryAst {
|
|
|
1451
1470
|
);
|
|
1452
1471
|
}
|
|
1453
1472
|
|
|
1454
|
-
withReturning(returning: ReadonlyArray<
|
|
1473
|
+
withReturning(returning: ReadonlyArray<ProjectionItem> | undefined): InsertAst {
|
|
1455
1474
|
return new InsertAst(
|
|
1456
1475
|
this.table,
|
|
1457
1476
|
this.rows.map((row) => ({ ...row })),
|
|
@@ -1469,6 +1488,15 @@ export class InsertAst extends QueryAst {
|
|
|
1469
1488
|
);
|
|
1470
1489
|
}
|
|
1471
1490
|
|
|
1491
|
+
rewrite(rewriter: AstRewriter): InsertAst {
|
|
1492
|
+
return new InsertAst(
|
|
1493
|
+
rewriteTableSource(this.table, rewriter),
|
|
1494
|
+
this.rows.map((row) => rewriteInsertRow(row, rewriter)),
|
|
1495
|
+
this.onConflict ? rewriteOnConflict(this.onConflict, rewriter) : undefined,
|
|
1496
|
+
this.returning?.map((item) => rewriteProjectionItem(item, rewriter)),
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1472
1500
|
override collectParamRefs(): ParamRef[] {
|
|
1473
1501
|
const refs: ParamRef[] = [];
|
|
1474
1502
|
for (const row of this.rows) {
|
|
@@ -1485,44 +1513,12 @@ export class InsertAst extends QueryAst {
|
|
|
1485
1513
|
}
|
|
1486
1514
|
}
|
|
1487
1515
|
}
|
|
1488
|
-
|
|
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);
|
|
1499
|
-
}
|
|
1500
|
-
};
|
|
1501
|
-
|
|
1502
|
-
for (const row of this.rows) {
|
|
1503
|
-
for (const value of Object.values(row)) {
|
|
1504
|
-
addValue(value);
|
|
1516
|
+
for (const item of this.returning ?? []) {
|
|
1517
|
+
if (item.expr.kind !== 'literal') {
|
|
1518
|
+
refs.push(...item.expr.collectParamRefs());
|
|
1505
1519
|
}
|
|
1506
1520
|
}
|
|
1507
|
-
|
|
1508
|
-
for (const columnRef of this.returning ?? []) {
|
|
1509
|
-
addColumn(columnRef);
|
|
1510
|
-
}
|
|
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);
|
|
1521
|
+
return refs;
|
|
1526
1522
|
}
|
|
1527
1523
|
|
|
1528
1524
|
override toQueryAst(): AnyQueryAst {
|
|
@@ -1535,13 +1531,13 @@ export class UpdateAst extends QueryAst {
|
|
|
1535
1531
|
readonly table: TableSource;
|
|
1536
1532
|
readonly set: Readonly<Record<string, ColumnRef | ParamRef>>;
|
|
1537
1533
|
readonly where: AnyExpression | undefined;
|
|
1538
|
-
readonly returning: ReadonlyArray<
|
|
1534
|
+
readonly returning: ReadonlyArray<ProjectionItem> | undefined;
|
|
1539
1535
|
|
|
1540
1536
|
constructor(
|
|
1541
1537
|
table: TableSource,
|
|
1542
1538
|
set: Readonly<Record<string, ColumnRef | ParamRef>> = {},
|
|
1543
1539
|
where?: AnyExpression,
|
|
1544
|
-
returning?: ReadonlyArray<
|
|
1540
|
+
returning?: ReadonlyArray<ProjectionItem>,
|
|
1545
1541
|
) {
|
|
1546
1542
|
super();
|
|
1547
1543
|
this.table = table;
|
|
@@ -1563,10 +1559,19 @@ export class UpdateAst extends QueryAst {
|
|
|
1563
1559
|
return new UpdateAst(this.table, this.set, where, this.returning);
|
|
1564
1560
|
}
|
|
1565
1561
|
|
|
1566
|
-
withReturning(returning: ReadonlyArray<
|
|
1562
|
+
withReturning(returning: ReadonlyArray<ProjectionItem> | undefined): UpdateAst {
|
|
1567
1563
|
return new UpdateAst(this.table, this.set, this.where, returning);
|
|
1568
1564
|
}
|
|
1569
1565
|
|
|
1566
|
+
rewrite(rewriter: AstRewriter): UpdateAst {
|
|
1567
|
+
return new UpdateAst(
|
|
1568
|
+
rewriteTableSource(this.table, rewriter),
|
|
1569
|
+
rewriteUpdateSet(this.set, rewriter),
|
|
1570
|
+
this.where?.rewrite(rewriter),
|
|
1571
|
+
this.returning?.map((item) => rewriteProjectionItem(item, rewriter)),
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1570
1575
|
override collectParamRefs(): ParamRef[] {
|
|
1571
1576
|
const refs: ParamRef[] = [];
|
|
1572
1577
|
for (const value of Object.values(this.set)) {
|
|
@@ -1577,28 +1582,12 @@ export class UpdateAst extends QueryAst {
|
|
|
1577
1582
|
if (this.where) {
|
|
1578
1583
|
refs.push(...this.where.collectParamRefs());
|
|
1579
1584
|
}
|
|
1580
|
-
|
|
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);
|
|
1585
|
+
for (const item of this.returning ?? []) {
|
|
1586
|
+
if (item.expr.kind !== 'literal') {
|
|
1587
|
+
refs.push(...item.expr.collectParamRefs());
|
|
1590
1588
|
}
|
|
1591
1589
|
}
|
|
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);
|
|
1590
|
+
return refs;
|
|
1602
1591
|
}
|
|
1603
1592
|
|
|
1604
1593
|
override toQueryAst(): AnyQueryAst {
|
|
@@ -1610,9 +1599,13 @@ export class DeleteAst extends QueryAst {
|
|
|
1610
1599
|
readonly kind = 'delete' as const;
|
|
1611
1600
|
readonly table: TableSource;
|
|
1612
1601
|
readonly where: AnyExpression | undefined;
|
|
1613
|
-
readonly returning: ReadonlyArray<
|
|
1602
|
+
readonly returning: ReadonlyArray<ProjectionItem> | undefined;
|
|
1614
1603
|
|
|
1615
|
-
constructor(
|
|
1604
|
+
constructor(
|
|
1605
|
+
table: TableSource,
|
|
1606
|
+
where?: AnyExpression,
|
|
1607
|
+
returning?: ReadonlyArray<ProjectionItem>,
|
|
1608
|
+
) {
|
|
1616
1609
|
super();
|
|
1617
1610
|
this.table = table;
|
|
1618
1611
|
this.where = where;
|
|
@@ -1628,27 +1621,82 @@ export class DeleteAst extends QueryAst {
|
|
|
1628
1621
|
return new DeleteAst(this.table, where, this.returning);
|
|
1629
1622
|
}
|
|
1630
1623
|
|
|
1631
|
-
withReturning(returning: ReadonlyArray<
|
|
1624
|
+
withReturning(returning: ReadonlyArray<ProjectionItem> | undefined): DeleteAst {
|
|
1632
1625
|
return new DeleteAst(this.table, this.where, returning);
|
|
1633
1626
|
}
|
|
1634
1627
|
|
|
1628
|
+
rewrite(rewriter: AstRewriter): DeleteAst {
|
|
1629
|
+
return new DeleteAst(
|
|
1630
|
+
rewriteTableSource(this.table, rewriter),
|
|
1631
|
+
this.where?.rewrite(rewriter),
|
|
1632
|
+
this.returning?.map((item) => rewriteProjectionItem(item, rewriter)),
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1635
1636
|
override collectParamRefs(): ParamRef[] {
|
|
1636
|
-
|
|
1637
|
+
const refs: ParamRef[] = [];
|
|
1638
|
+
if (this.where) {
|
|
1639
|
+
refs.push(...this.where.collectParamRefs());
|
|
1640
|
+
}
|
|
1641
|
+
for (const item of this.returning ?? []) {
|
|
1642
|
+
if (item.expr.kind !== 'literal') {
|
|
1643
|
+
refs.push(...item.expr.collectParamRefs());
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return refs;
|
|
1637
1647
|
}
|
|
1638
1648
|
|
|
1639
|
-
override
|
|
1640
|
-
|
|
1641
|
-
|
|
1649
|
+
override toQueryAst(): AnyQueryAst {
|
|
1650
|
+
return this;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1642
1653
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1654
|
+
/**
|
|
1655
|
+
* Raw-SQL query AST node carrying interpolated parameter / expression nodes
|
|
1656
|
+
* embedded inside literal SQL fragments.
|
|
1657
|
+
*
|
|
1658
|
+
* `fragments` and `args` are interleaved during lowering:
|
|
1659
|
+
* `fragments[0] + lower(args[0]) + fragments[1] + ... + fragments[n]`.
|
|
1660
|
+
* Construction enforces `fragments.length === args.length + 1`.
|
|
1661
|
+
*
|
|
1662
|
+
* Extends {@link QueryAst} (whole-query AST, not a sub-expression).
|
|
1663
|
+
* Construction does not validate that each arg is a `ParamRef` /
|
|
1664
|
+
* `AnyExpression`: the type system already rejects bare values because
|
|
1665
|
+
* `args` is typed `readonly AnyExpression[]`. The user-facing `raw\`...\``
|
|
1666
|
+
* factory (separate `sql-raw-factory` component) layers stricter
|
|
1667
|
+
* type-level rejection on top of this AST node.
|
|
1668
|
+
*/
|
|
1669
|
+
export class RawSqlExpr extends QueryAst {
|
|
1670
|
+
readonly kind = 'raw-sql' as const;
|
|
1671
|
+
readonly fragments: readonly string[];
|
|
1672
|
+
readonly args: readonly AnyExpression[];
|
|
1673
|
+
|
|
1674
|
+
constructor(fragments: readonly string[], args: readonly AnyExpression[]) {
|
|
1675
|
+
super();
|
|
1676
|
+
if (fragments.length !== args.length + 1) {
|
|
1677
|
+
throw new Error(
|
|
1678
|
+
`RawSqlExpr: fragments.length must equal args.length + 1 (got fragments=${fragments.length}, args=${args.length})`,
|
|
1679
|
+
);
|
|
1645
1680
|
}
|
|
1681
|
+
this.fragments = Object.freeze([...fragments]);
|
|
1682
|
+
this.args = Object.freeze([...args]);
|
|
1683
|
+
this.freeze();
|
|
1684
|
+
}
|
|
1646
1685
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1686
|
+
static of(fragments: readonly string[], args: readonly AnyExpression[]): RawSqlExpr {
|
|
1687
|
+
return new RawSqlExpr(fragments, args);
|
|
1688
|
+
}
|
|
1650
1689
|
|
|
1651
|
-
|
|
1690
|
+
override collectParamRefs(): ParamRef[] {
|
|
1691
|
+
const refs: ParamRef[] = [];
|
|
1692
|
+
for (const arg of this.args) {
|
|
1693
|
+
if (arg.kind === 'param-ref') {
|
|
1694
|
+
refs.push(arg);
|
|
1695
|
+
} else {
|
|
1696
|
+
refs.push(...arg.collectParamRefs());
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return refs;
|
|
1652
1700
|
}
|
|
1653
1701
|
|
|
1654
1702
|
override toQueryAst(): AnyQueryAst {
|
|
@@ -1656,7 +1704,7 @@ export class DeleteAst extends QueryAst {
|
|
|
1656
1704
|
}
|
|
1657
1705
|
}
|
|
1658
1706
|
|
|
1659
|
-
export type AnyQueryAst = SelectAst | InsertAst | UpdateAst | DeleteAst;
|
|
1707
|
+
export type AnyQueryAst = SelectAst | InsertAst | UpdateAst | DeleteAst | RawSqlExpr;
|
|
1660
1708
|
export type AnyFromSource = TableSource | DerivedTableSource;
|
|
1661
1709
|
export type AnyExpression =
|
|
1662
1710
|
| ColumnRef
|
|
@@ -1684,6 +1732,7 @@ export const queryAstKinds: ReadonlySet<string> = new Set<AnyQueryAst['kind']>([
|
|
|
1684
1732
|
'insert',
|
|
1685
1733
|
'update',
|
|
1686
1734
|
'delete',
|
|
1735
|
+
'raw-sql',
|
|
1687
1736
|
]);
|
|
1688
1737
|
export const whereExprKinds: ReadonlySet<string> = new Set<AnyExpression['kind']>([
|
|
1689
1738
|
'binary',
|
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,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder-pipeline validator pass: every {@link ParamRef} whose `codecId` resolves to a *parameterized* descriptor must carry `refs: { table, column }` so encode-side dispatch can call `contractCodecs.forColumn(table, column)`. Refs-less parameterized `ParamRef`s are a hard error — the codec-id-keyed `forCodecId` fallback cannot disambiguate per-instance codecs (e.g. `vector(1024)` vs. `vector(1536)`), so the dispatch
|
|
3
|
+
* path must reject them at validation time rather than silently bind to the wrong instance.
|
|
4
|
+
*
|
|
5
|
+
* Non-parameterized codec ids (the `voidParamsSchema` case) are always dispatch-safe via codec id alone, so refs-less ParamRefs for those ids are accepted unchanged.
|
|
6
|
+
*
|
|
7
|
+
* The pass runs post-build / pre-execute — the natural location is the SQL runtime's `lower()` step, between any `beforeCompile` rewrites and `encodeParams`. See AC-5 in the codec-registry-unification spec.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { runtimeError } from '@prisma-next/framework-components/runtime';
|
|
11
|
+
import type { CodecDescriptorRegistry } from '../query-lane-context';
|
|
12
|
+
import type { AnyQueryAst, ParamRef } from './types';
|
|
13
|
+
import { collectOrderedParamRefs } from './util';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate that every parameterized-codec `ParamRef` in `plan` carries `refs`. Throws `RUNTIME.PARAM_REF_REFS_REQUIRED` (a runtime envelope) naming the codec id and the binding label when the invariant is violated. Returns the plan unchanged on success — callers that prefer a side-effecting assertion can ignore the return value.
|
|
17
|
+
*
|
|
18
|
+
* The `registry` is consulted via `descriptorFor(codecId).isParameterized` — `true` whenever the descriptor's `paramsSchema` is not the singleton `voidParamsSchema`.
|
|
19
|
+
*/
|
|
20
|
+
export function validateParamRefRefs(plan: AnyQueryAst, registry: CodecDescriptorRegistry): void {
|
|
21
|
+
for (const ref of collectOrderedParamRefs(plan)) {
|
|
22
|
+
diagnoseRef(ref, registry);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function diagnoseRef(ref: ParamRef, registry: CodecDescriptorRegistry): void {
|
|
27
|
+
if (!ref.codecId) return;
|
|
28
|
+
const descriptor = registry.descriptorFor(ref.codecId);
|
|
29
|
+
if (descriptor === undefined) return;
|
|
30
|
+
if (!descriptor.isParameterized) return;
|
|
31
|
+
if (ref.refs) return;
|
|
32
|
+
|
|
33
|
+
const label = ref.name ?? '<anonymous>';
|
|
34
|
+
throw runtimeError(
|
|
35
|
+
'RUNTIME.PARAM_REF_REFS_REQUIRED',
|
|
36
|
+
`ParamRef '${label}' for parameterized codec '${ref.codecId}' is missing column refs; column-aware dispatch requires { table, column } at the binding site.`,
|
|
37
|
+
{ codecId: ref.codecId, paramName: ref.name },
|
|
38
|
+
);
|
|
39
|
+
}
|