@prisma-next/sql-lane 0.3.0-dev.3 → 0.3.0-dev.31
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 +2 -2
- package/dist/{chunk-WA646VY6.js → chunk-72PNERR5.js} +133 -161
- package/dist/chunk-72PNERR5.js.map +1 -0
- package/dist/exports/sql.d.ts +5 -5
- package/dist/exports/sql.d.ts.map +1 -0
- package/dist/exports/sql.js +1 -1
- package/dist/index.d.ts +5 -116
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/raw.d.ts +11 -0
- package/dist/raw.d.ts.map +1 -0
- package/dist/sql/builder.d.ts +11 -0
- package/dist/sql/builder.d.ts.map +1 -0
- package/dist/sql/context.d.ts +5 -0
- package/dist/sql/context.d.ts.map +1 -0
- package/dist/sql/include-builder.d.ts +35 -0
- package/dist/sql/include-builder.d.ts.map +1 -0
- package/dist/sql/join-builder.d.ts +4 -0
- package/dist/sql/join-builder.d.ts.map +1 -0
- package/dist/sql/mutation-builder.d.ts +64 -0
- package/dist/sql/mutation-builder.d.ts.map +1 -0
- package/dist/sql/plan.d.ts +4 -0
- package/dist/sql/plan.d.ts.map +1 -0
- package/dist/sql/predicate-builder.d.ts +11 -0
- package/dist/sql/predicate-builder.d.ts.map +1 -0
- package/dist/sql/projection.d.ts +18 -0
- package/dist/sql/projection.d.ts.map +1 -0
- package/dist/sql/select-builder.d.ts +35 -0
- package/dist/sql/select-builder.d.ts.map +1 -0
- package/dist/types/internal.d.ts +35 -0
- package/dist/types/internal.d.ts.map +1 -0
- package/dist/types/public.d.ts +18 -0
- package/dist/types/public.d.ts.map +1 -0
- package/dist/utils/assertions.d.ts +28 -0
- package/dist/utils/assertions.d.ts.map +1 -0
- package/dist/utils/capabilities.d.ts +4 -0
- package/dist/utils/capabilities.d.ts.map +1 -0
- package/dist/utils/errors.d.ts +30 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/state.d.ts +30 -0
- package/dist/utils/state.d.ts.map +1 -0
- package/package.json +20 -20
- package/src/exports/sql.ts +12 -0
- package/src/index.ts +13 -0
- package/src/raw.ts +230 -0
- package/src/sql/builder.ts +66 -0
- package/src/sql/context.ts +10 -0
- package/src/sql/include-builder.ts +248 -0
- package/src/sql/join-builder.ts +18 -0
- package/src/sql/mutation-builder.ts +494 -0
- package/src/sql/plan.ts +290 -0
- package/src/sql/predicate-builder.ts +127 -0
- package/src/sql/projection.ts +117 -0
- package/src/sql/select-builder.ts +430 -0
- package/src/types/internal.ts +41 -0
- package/src/types/public.ts +36 -0
- package/src/utils/assertions.ts +34 -0
- package/src/utils/capabilities.ts +39 -0
- package/src/utils/errors.ts +168 -0
- package/src/utils/state.ts +38 -0
- package/dist/chunk-WA646VY6.js.map +0 -1
package/src/sql/plan.ts
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { PlanMeta } from '@prisma-next/contract/types';
|
|
2
|
+
import type { Expression } from '@prisma-next/sql-relational-core/ast';
|
|
3
|
+
import { compact } from '@prisma-next/sql-relational-core/ast';
|
|
4
|
+
import type { AnyExpressionSource } from '@prisma-next/sql-relational-core/types';
|
|
5
|
+
import {
|
|
6
|
+
collectColumnRefs,
|
|
7
|
+
isColumnBuilder,
|
|
8
|
+
isExpressionBuilder,
|
|
9
|
+
isOperationExpr,
|
|
10
|
+
} from '@prisma-next/sql-relational-core/utils/guards';
|
|
11
|
+
import type { MetaBuildArgs } from '../types/internal';
|
|
12
|
+
import { assertColumnBuilder } from '../utils/assertions';
|
|
13
|
+
import { errorMissingColumnForAlias } from '../utils/errors';
|
|
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
|
+
|
|
64
|
+
export function buildMeta(args: MetaBuildArgs): PlanMeta {
|
|
65
|
+
const refsColumns = new Map<string, { table: string; column: string }>();
|
|
66
|
+
const refsTables = new Set<string>([args.table.name]);
|
|
67
|
+
|
|
68
|
+
for (const column of args.projection.columns) {
|
|
69
|
+
collectRefsFromExpressionSource(column, refsColumns);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (args.joins) {
|
|
73
|
+
for (const join of args.joins) {
|
|
74
|
+
refsTables.add(join.table.name);
|
|
75
|
+
const onLeft = assertColumnBuilder(join.on.left, 'join ON left');
|
|
76
|
+
const onRight = assertColumnBuilder(join.on.right, 'join ON right');
|
|
77
|
+
refsColumns.set(`${onLeft.table}.${onLeft.column}`, {
|
|
78
|
+
table: onLeft.table,
|
|
79
|
+
column: onLeft.column,
|
|
80
|
+
});
|
|
81
|
+
refsColumns.set(`${onRight.table}.${onRight.column}`, {
|
|
82
|
+
table: onRight.table,
|
|
83
|
+
column: onRight.column,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (args.includes) {
|
|
89
|
+
for (const include of args.includes) {
|
|
90
|
+
refsTables.add(include.table.name);
|
|
91
|
+
// Add ON condition columns
|
|
92
|
+
// JoinOnPredicate.left and .right are always ColumnBuilder
|
|
93
|
+
const leftCol = assertColumnBuilder(include.on.left, 'include ON left');
|
|
94
|
+
const rightCol = assertColumnBuilder(include.on.right, 'include ON right');
|
|
95
|
+
refsColumns.set(`${leftCol.table}.${leftCol.column}`, {
|
|
96
|
+
table: leftCol.table,
|
|
97
|
+
column: leftCol.column,
|
|
98
|
+
});
|
|
99
|
+
refsColumns.set(`${rightCol.table}.${rightCol.column}`, {
|
|
100
|
+
table: rightCol.table,
|
|
101
|
+
column: rightCol.column,
|
|
102
|
+
});
|
|
103
|
+
// Add child projection columns
|
|
104
|
+
for (const column of include.childProjection.columns) {
|
|
105
|
+
const col = assertColumnBuilder(column, 'include child projection column');
|
|
106
|
+
|
|
107
|
+
refsColumns.set(`${col.table}.${col.column}`, {
|
|
108
|
+
table: col.table,
|
|
109
|
+
column: col.column,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Add child WHERE columns if present
|
|
113
|
+
if (include.childWhere) {
|
|
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
|
|
117
|
+
const childWhereRight = include.childWhere.right;
|
|
118
|
+
if (isColumnBuilder(childWhereRight) || isExpressionBuilder(childWhereRight)) {
|
|
119
|
+
collectRefsFromExpressionSource(childWhereRight, refsColumns);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Add child ORDER BY columns if present
|
|
123
|
+
if (include.childOrderBy) {
|
|
124
|
+
// childOrderBy.expr is Expression (already converted at builder creation time)
|
|
125
|
+
collectRefsFromExpression(include.childOrderBy.expr, refsColumns);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (args.where) {
|
|
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);
|
|
135
|
+
for (const ref of allRefs) {
|
|
136
|
+
refsColumns.set(`${ref.table}.${ref.column}`, {
|
|
137
|
+
table: ref.table,
|
|
138
|
+
column: ref.column,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
// leftExpr is ColumnRef
|
|
143
|
+
refsColumns.set(`${leftExpr.table}.${leftExpr.column}`, {
|
|
144
|
+
table: leftExpr.table,
|
|
145
|
+
column: leftExpr.column,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Handle right side of WHERE clause - can be ParamPlaceholder or AnyExpressionSource
|
|
150
|
+
const whereRight = args.where.right;
|
|
151
|
+
if (isColumnBuilder(whereRight) || isExpressionBuilder(whereRight)) {
|
|
152
|
+
collectRefsFromExpressionSource(whereRight, refsColumns);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (args.orderBy) {
|
|
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,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// orderByExpr is ColumnRef
|
|
169
|
+
refsColumns.set(`${orderByExpr.table}.${orderByExpr.column}`, {
|
|
170
|
+
table: orderByExpr.table,
|
|
171
|
+
column: orderByExpr.column,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Build projection map - mark include aliases with special marker
|
|
177
|
+
const includeAliases = new Set(args.includes?.map((inc) => inc.alias) ?? []);
|
|
178
|
+
const projectionMap = Object.fromEntries(
|
|
179
|
+
args.projection.aliases.map((alias, index) => {
|
|
180
|
+
if (includeAliases.has(alias)) {
|
|
181
|
+
// Mark include alias with special marker
|
|
182
|
+
return [alias, `include:${alias}`];
|
|
183
|
+
}
|
|
184
|
+
const column = args.projection.columns[index];
|
|
185
|
+
if (!column) {
|
|
186
|
+
// Null column means this is an include placeholder, but alias doesn't match includes
|
|
187
|
+
// This shouldn't happen if projection building is correct, but handle gracefully
|
|
188
|
+
errorMissingColumnForAlias(alias, index);
|
|
189
|
+
}
|
|
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}`];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return [alias, `${col.table}.${col.column}`];
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Build projectionTypes mapping: alias → column type ID
|
|
206
|
+
// Skip include aliases - they don't have column types
|
|
207
|
+
const projectionTypes: Record<string, string> = {};
|
|
208
|
+
for (let i = 0; i < args.projection.aliases.length; i++) {
|
|
209
|
+
const alias = args.projection.aliases[i];
|
|
210
|
+
if (!alias || includeAliases.has(alias)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const column = args.projection.columns[i];
|
|
214
|
+
if (!column) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (isExpressionBuilder(column)) {
|
|
218
|
+
const operationExpr = column.expr;
|
|
219
|
+
if (operationExpr.returns.kind === 'typeId') {
|
|
220
|
+
projectionTypes[alias] = operationExpr.returns.type;
|
|
221
|
+
} else if (operationExpr.returns.kind === 'builtin') {
|
|
222
|
+
projectionTypes[alias] = operationExpr.returns.type;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
// column is ColumnBuilder
|
|
226
|
+
const col = column as unknown as { columnMeta?: { codecId: string } };
|
|
227
|
+
const columnMeta = col.columnMeta;
|
|
228
|
+
const codecId = columnMeta?.codecId;
|
|
229
|
+
if (codecId) {
|
|
230
|
+
projectionTypes[alias] = codecId;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Build codec assignments from column types
|
|
236
|
+
// Skip include aliases - they don't need codec entries
|
|
237
|
+
const projectionCodecs: Record<string, string> = {};
|
|
238
|
+
for (let i = 0; i < args.projection.aliases.length; i++) {
|
|
239
|
+
const alias = args.projection.aliases[i];
|
|
240
|
+
if (!alias || includeAliases.has(alias)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const column = args.projection.columns[i];
|
|
244
|
+
if (!column) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (isExpressionBuilder(column)) {
|
|
248
|
+
const operationExpr = column.expr;
|
|
249
|
+
if (operationExpr.returns.kind === 'typeId') {
|
|
250
|
+
projectionCodecs[alias] = operationExpr.returns.type;
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
// Use columnMeta.codecId directly as typeId (already canonicalized)
|
|
254
|
+
// column is ColumnBuilder
|
|
255
|
+
const col = column as unknown as { columnMeta?: { codecId: string } };
|
|
256
|
+
const columnMeta = col.columnMeta;
|
|
257
|
+
const codecId = columnMeta?.codecId;
|
|
258
|
+
if (codecId) {
|
|
259
|
+
projectionCodecs[alias] = codecId;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Merge projection and parameter codecs
|
|
265
|
+
const allCodecs: Record<string, string> = {
|
|
266
|
+
...projectionCodecs,
|
|
267
|
+
...(args.paramCodecs ? args.paramCodecs : {}),
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return Object.freeze(
|
|
271
|
+
compact({
|
|
272
|
+
target: args.contract.target,
|
|
273
|
+
targetFamily: args.contract.targetFamily,
|
|
274
|
+
coreHash: args.contract.coreHash,
|
|
275
|
+
lane: 'dsl',
|
|
276
|
+
refs: {
|
|
277
|
+
tables: Array.from(refsTables),
|
|
278
|
+
columns: Array.from(refsColumns.values()),
|
|
279
|
+
},
|
|
280
|
+
projection: projectionMap,
|
|
281
|
+
projectionTypes: Object.keys(projectionTypes).length > 0 ? projectionTypes : undefined,
|
|
282
|
+
annotations:
|
|
283
|
+
Object.keys(allCodecs).length > 0
|
|
284
|
+
? Object.freeze({ codecs: Object.freeze(allCodecs) })
|
|
285
|
+
: undefined,
|
|
286
|
+
paramDescriptors: args.paramDescriptors,
|
|
287
|
+
profileHash: args.contract.profileHash,
|
|
288
|
+
}) as PlanMeta,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { ParamDescriptor } from '@prisma-next/contract/types';
|
|
2
|
+
import type { SqlContract, SqlStorage, StorageColumn } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type { BinaryExpr, Expression, ParamRef } from '@prisma-next/sql-relational-core/ast';
|
|
4
|
+
import { createBinaryExpr, createParamRef } from '@prisma-next/sql-relational-core/ast';
|
|
5
|
+
import type { BinaryBuilder, ParamPlaceholder } from '@prisma-next/sql-relational-core/types';
|
|
6
|
+
import {
|
|
7
|
+
isColumnBuilder,
|
|
8
|
+
isExpressionBuilder,
|
|
9
|
+
isParamPlaceholder,
|
|
10
|
+
} from '@prisma-next/sql-relational-core/utils/guards';
|
|
11
|
+
import {
|
|
12
|
+
errorFailedToBuildWhereClause,
|
|
13
|
+
errorMissingParameter,
|
|
14
|
+
errorUnknownColumn,
|
|
15
|
+
errorUnknownTable,
|
|
16
|
+
} from '../utils/errors';
|
|
17
|
+
|
|
18
|
+
export interface BuildWhereExprResult {
|
|
19
|
+
expr: BinaryExpr;
|
|
20
|
+
codecId: string | undefined;
|
|
21
|
+
paramName: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildWhereExpr(
|
|
25
|
+
contract: SqlContract<SqlStorage>,
|
|
26
|
+
where: BinaryBuilder,
|
|
27
|
+
paramsMap: Record<string, unknown>,
|
|
28
|
+
descriptors: ParamDescriptor[],
|
|
29
|
+
values: unknown[],
|
|
30
|
+
): BuildWhereExprResult {
|
|
31
|
+
let leftExpr: Expression;
|
|
32
|
+
let codecId: string | undefined;
|
|
33
|
+
let rightExpr: Expression | ParamRef;
|
|
34
|
+
let paramName: string;
|
|
35
|
+
|
|
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;
|
|
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;
|
|
53
|
+
const contractTable = contract.storage.tables[table];
|
|
54
|
+
if (!contractTable) {
|
|
55
|
+
errorUnknownTable(table);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const columnMeta: StorageColumn | undefined = contractTable.columns[column];
|
|
59
|
+
if (!columnMeta) {
|
|
60
|
+
errorUnknownColumn(column, table);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
codecId = columnMeta.codecId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Handle where.right - can be ParamPlaceholder or ExpressionSource
|
|
67
|
+
if (isParamPlaceholder(where.right)) {
|
|
68
|
+
// Handle param placeholder (existing logic)
|
|
69
|
+
const placeholder: ParamPlaceholder = where.right;
|
|
70
|
+
paramName = placeholder.name;
|
|
71
|
+
|
|
72
|
+
if (!Object.hasOwn(paramsMap, paramName)) {
|
|
73
|
+
errorMissingParameter(paramName);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const value = paramsMap[paramName];
|
|
77
|
+
const index = values.push(value);
|
|
78
|
+
|
|
79
|
+
// Construct descriptor directly from validated StorageColumn if left is a column
|
|
80
|
+
if (leftExpr.kind === 'col') {
|
|
81
|
+
const { table, column } = leftExpr;
|
|
82
|
+
const contractTable = contract.storage.tables[table];
|
|
83
|
+
const columnMeta = contractTable?.columns[column];
|
|
84
|
+
if (columnMeta) {
|
|
85
|
+
descriptors.push({
|
|
86
|
+
name: paramName,
|
|
87
|
+
source: 'dsl',
|
|
88
|
+
refs: { table, column },
|
|
89
|
+
nullable: columnMeta.nullable,
|
|
90
|
+
codecId: columnMeta.codecId,
|
|
91
|
+
nativeType: columnMeta.nativeType,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
rightExpr = createParamRef(index, paramName);
|
|
97
|
+
} else if (isColumnBuilder(where.right) || isExpressionBuilder(where.right)) {
|
|
98
|
+
// Handle ExpressionSource (ColumnBuilder or ExpressionBuilder) on the right
|
|
99
|
+
rightExpr = where.right.toExpr();
|
|
100
|
+
|
|
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
|
+
}
|
|
108
|
+
|
|
109
|
+
const columnMeta: StorageColumn | undefined = contractTable.columns[column];
|
|
110
|
+
if (!columnMeta) {
|
|
111
|
+
errorUnknownColumn(column, table);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Use a placeholder paramName for expression references (not used for params)
|
|
116
|
+
paramName = '';
|
|
117
|
+
} else {
|
|
118
|
+
// where.right is neither ParamPlaceholder nor ExpressionSource - invalid state
|
|
119
|
+
errorFailedToBuildWhereClause();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
expr: createBinaryExpr(where.op, leftExpr, rightExpr),
|
|
124
|
+
codecId,
|
|
125
|
+
paramName,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { TableRef } from '@prisma-next/sql-relational-core/ast';
|
|
2
|
+
import type { AnyExpressionSource, NestedProjection } from '@prisma-next/sql-relational-core/types';
|
|
3
|
+
import { isExpressionSource } from '@prisma-next/sql-relational-core/utils/guards';
|
|
4
|
+
import type { ProjectionInput } from '../types/internal';
|
|
5
|
+
import {
|
|
6
|
+
errorAliasCollision,
|
|
7
|
+
errorAliasPathEmpty,
|
|
8
|
+
errorIncludeAliasNotFound,
|
|
9
|
+
errorInvalidProjectionKey,
|
|
10
|
+
errorInvalidProjectionValue,
|
|
11
|
+
errorProjectionEmpty,
|
|
12
|
+
} from '../utils/errors';
|
|
13
|
+
import type { IncludeState, ProjectionState } from '../utils/state';
|
|
14
|
+
|
|
15
|
+
export function generateAlias(path: string[]): string {
|
|
16
|
+
if (path.length === 0) {
|
|
17
|
+
errorAliasPathEmpty();
|
|
18
|
+
}
|
|
19
|
+
return path.join('_');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class AliasTracker {
|
|
23
|
+
private readonly aliases = new Set<string>();
|
|
24
|
+
private readonly aliasToPath = new Map<string, string[]>();
|
|
25
|
+
|
|
26
|
+
register(path: string[]): string {
|
|
27
|
+
const alias = generateAlias(path);
|
|
28
|
+
if (this.aliases.has(alias)) {
|
|
29
|
+
const existingPath = this.aliasToPath.get(alias);
|
|
30
|
+
errorAliasCollision(path, alias, existingPath);
|
|
31
|
+
}
|
|
32
|
+
this.aliases.add(alias);
|
|
33
|
+
this.aliasToPath.set(alias, path);
|
|
34
|
+
return alias;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getPath(alias: string): string[] | undefined {
|
|
38
|
+
return this.aliasToPath.get(alias);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
has(alias: string): boolean {
|
|
42
|
+
return this.aliases.has(alias);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function flattenProjection(
|
|
47
|
+
projection: NestedProjection,
|
|
48
|
+
tracker: AliasTracker,
|
|
49
|
+
currentPath: string[] = [],
|
|
50
|
+
): { aliases: string[]; columns: AnyExpressionSource[] } {
|
|
51
|
+
const aliases: string[] = [];
|
|
52
|
+
const columns: AnyExpressionSource[] = [];
|
|
53
|
+
|
|
54
|
+
for (const [key, value] of Object.entries(projection)) {
|
|
55
|
+
const path = [...currentPath, key];
|
|
56
|
+
|
|
57
|
+
if (isExpressionSource(value)) {
|
|
58
|
+
const alias = tracker.register(path);
|
|
59
|
+
aliases.push(alias);
|
|
60
|
+
columns.push(value);
|
|
61
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
62
|
+
const nested = flattenProjection(value, tracker, path);
|
|
63
|
+
aliases.push(...nested.aliases);
|
|
64
|
+
columns.push(...nested.columns);
|
|
65
|
+
} else {
|
|
66
|
+
errorInvalidProjectionValue(path);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { aliases, columns };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function buildProjectionState(
|
|
74
|
+
_table: TableRef,
|
|
75
|
+
projection: ProjectionInput,
|
|
76
|
+
includes?: ReadonlyArray<IncludeState>,
|
|
77
|
+
): ProjectionState {
|
|
78
|
+
const tracker = new AliasTracker();
|
|
79
|
+
const aliases: string[] = [];
|
|
80
|
+
const columns: AnyExpressionSource[] = [];
|
|
81
|
+
|
|
82
|
+
for (const [key, value] of Object.entries(projection)) {
|
|
83
|
+
if (value === true) {
|
|
84
|
+
// Boolean true means this is an include reference
|
|
85
|
+
const matchingInclude = includes?.find((inc) => inc.alias === key);
|
|
86
|
+
if (!matchingInclude) {
|
|
87
|
+
errorIncludeAliasNotFound(key);
|
|
88
|
+
}
|
|
89
|
+
// For include references, we track the alias but use a placeholder object
|
|
90
|
+
// The actual handling happens in AST building where we create includeRef
|
|
91
|
+
aliases.push(key);
|
|
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)) {
|
|
100
|
+
const alias = tracker.register([key]);
|
|
101
|
+
aliases.push(alias);
|
|
102
|
+
columns.push(value);
|
|
103
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
104
|
+
const nested = flattenProjection(value as NestedProjection, tracker, [key]);
|
|
105
|
+
aliases.push(...nested.aliases);
|
|
106
|
+
columns.push(...nested.columns);
|
|
107
|
+
} else {
|
|
108
|
+
errorInvalidProjectionKey(key);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (aliases.length === 0) {
|
|
113
|
+
errorProjectionEmpty();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { aliases, columns };
|
|
117
|
+
}
|