@prisma-next/sql-lane 0.3.0-dev.4 → 0.3.0-dev.6
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/dist/exports/sql.d.ts +5 -5
- package/dist/exports/sql.d.ts.map +1 -0
- package/dist/index.d.ts +5 -116
- package/dist/index.d.ts.map +1 -0
- 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 +10 -9
- 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 +289 -0
- package/src/sql/predicate-builder.ts +130 -0
- package/src/sql/projection.ts +112 -0
- package/src/sql/select-builder.ts +449 -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 +40 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import type { ParamDescriptor } from '@prisma-next/contract/types';
|
|
2
|
+
import type { SqlContract, SqlStorage, StorageColumn } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type {
|
|
4
|
+
BinaryExpr,
|
|
5
|
+
ColumnRef,
|
|
6
|
+
Direction,
|
|
7
|
+
IncludeAst,
|
|
8
|
+
IncludeRef,
|
|
9
|
+
JoinAst,
|
|
10
|
+
OperationExpr,
|
|
11
|
+
TableRef,
|
|
12
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
13
|
+
import {
|
|
14
|
+
createColumnRef,
|
|
15
|
+
createJoinOnBuilder,
|
|
16
|
+
createOrderByItem,
|
|
17
|
+
createSelectAst,
|
|
18
|
+
createTableRef,
|
|
19
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
20
|
+
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
21
|
+
import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
|
|
22
|
+
import type {
|
|
23
|
+
AnyBinaryBuilder,
|
|
24
|
+
AnyOrderBuilder,
|
|
25
|
+
BinaryBuilder,
|
|
26
|
+
BuildOptions,
|
|
27
|
+
InferNestedProjectionRow,
|
|
28
|
+
JoinOnBuilder,
|
|
29
|
+
JoinOnPredicate,
|
|
30
|
+
NestedProjection,
|
|
31
|
+
OrderBuilder,
|
|
32
|
+
SqlBuilderOptions,
|
|
33
|
+
} from '@prisma-next/sql-relational-core/types';
|
|
34
|
+
import { getOperationExpr } from '@prisma-next/sql-relational-core/utils/guards';
|
|
35
|
+
import type { ProjectionInput } from '../types/internal';
|
|
36
|
+
import { assertColumnBuilder } from '../utils/assertions';
|
|
37
|
+
import { checkIncludeCapabilities } from '../utils/capabilities';
|
|
38
|
+
import {
|
|
39
|
+
errorChildProjectionEmpty,
|
|
40
|
+
errorFromMustBeCalled,
|
|
41
|
+
errorIncludeAliasCollision,
|
|
42
|
+
errorLimitMustBeNonNegativeInteger,
|
|
43
|
+
errorMissingAlias,
|
|
44
|
+
errorMissingColumnForAlias,
|
|
45
|
+
errorSelectMustBeCalled,
|
|
46
|
+
errorSelfJoinNotSupported,
|
|
47
|
+
errorUnknownTable,
|
|
48
|
+
} from '../utils/errors';
|
|
49
|
+
import type { BuilderState, IncludeState, JoinState, ProjectionState } from '../utils/state';
|
|
50
|
+
import {
|
|
51
|
+
buildIncludeAst,
|
|
52
|
+
type IncludeChildBuilder,
|
|
53
|
+
IncludeChildBuilderImpl,
|
|
54
|
+
} from './include-builder';
|
|
55
|
+
import { buildJoinAst } from './join-builder';
|
|
56
|
+
import { buildMeta } from './plan';
|
|
57
|
+
import { buildWhereExpr } from './predicate-builder';
|
|
58
|
+
import { buildProjectionState } from './projection';
|
|
59
|
+
|
|
60
|
+
export class SelectBuilderImpl<
|
|
61
|
+
TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
|
|
62
|
+
Row = unknown,
|
|
63
|
+
CodecTypes extends Record<string, { readonly output: unknown }> = Record<string, never>,
|
|
64
|
+
Includes extends Record<string, unknown> = Record<string, never>,
|
|
65
|
+
> {
|
|
66
|
+
private readonly contract: TContract;
|
|
67
|
+
private readonly codecTypes: CodecTypes;
|
|
68
|
+
private readonly context: QueryLaneContext<TContract>;
|
|
69
|
+
private state: BuilderState = {};
|
|
70
|
+
|
|
71
|
+
constructor(options: SqlBuilderOptions<TContract>, state?: BuilderState) {
|
|
72
|
+
this.context = options.context;
|
|
73
|
+
this.contract = options.context.contract;
|
|
74
|
+
this.codecTypes = options.context.contract.mappings.codecTypes as CodecTypes;
|
|
75
|
+
if (state) {
|
|
76
|
+
this.state = state;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
from(table: TableRef): SelectBuilderImpl<TContract, unknown, CodecTypes, Record<string, never>> {
|
|
81
|
+
return new SelectBuilderImpl<TContract, unknown, CodecTypes, Record<string, never>>(
|
|
82
|
+
{
|
|
83
|
+
context: this.context,
|
|
84
|
+
},
|
|
85
|
+
{ ...this.state, from: table },
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
innerJoin(
|
|
90
|
+
table: TableRef,
|
|
91
|
+
on: (on: JoinOnBuilder) => JoinOnPredicate,
|
|
92
|
+
): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
93
|
+
return this._addJoin('inner', table, on);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
leftJoin(
|
|
97
|
+
table: TableRef,
|
|
98
|
+
on: (on: JoinOnBuilder) => JoinOnPredicate,
|
|
99
|
+
): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
100
|
+
return this._addJoin('left', table, on);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
rightJoin(
|
|
104
|
+
table: TableRef,
|
|
105
|
+
on: (on: JoinOnBuilder) => JoinOnPredicate,
|
|
106
|
+
): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
107
|
+
return this._addJoin('right', table, on);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fullJoin(
|
|
111
|
+
table: TableRef,
|
|
112
|
+
on: (on: JoinOnBuilder) => JoinOnPredicate,
|
|
113
|
+
): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
114
|
+
return this._addJoin('full', table, on);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
includeMany<
|
|
118
|
+
ChildProjection extends NestedProjection,
|
|
119
|
+
ChildRow = InferNestedProjectionRow<ChildProjection, CodecTypes>,
|
|
120
|
+
AliasName extends string = string,
|
|
121
|
+
>(
|
|
122
|
+
childTable: TableRef,
|
|
123
|
+
on: (on: JoinOnBuilder) => JoinOnPredicate,
|
|
124
|
+
childBuilder: (
|
|
125
|
+
child: IncludeChildBuilder<TContract, CodecTypes, unknown>,
|
|
126
|
+
) => IncludeChildBuilder<TContract, CodecTypes, ChildRow>,
|
|
127
|
+
options?: { alias?: AliasName },
|
|
128
|
+
): SelectBuilderImpl<TContract, Row, CodecTypes, Includes & { [K in AliasName]: ChildRow }> {
|
|
129
|
+
checkIncludeCapabilities(this.contract);
|
|
130
|
+
|
|
131
|
+
if (!this.contract.storage.tables[childTable.name]) {
|
|
132
|
+
errorUnknownTable(childTable.name);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const joinOnBuilder = createJoinOnBuilder();
|
|
136
|
+
const onPredicate = on(joinOnBuilder);
|
|
137
|
+
|
|
138
|
+
// Validate ON uses column equality
|
|
139
|
+
// TypeScript can't narrow ColumnBuilder properly, so we assert
|
|
140
|
+
const onLeft = onPredicate.left as { table: string; column: string };
|
|
141
|
+
const onRight = onPredicate.right as { table: string; column: string };
|
|
142
|
+
if (onLeft.table === onRight.table) {
|
|
143
|
+
errorSelfJoinNotSupported();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Build child builder
|
|
147
|
+
const childBuilderImpl = new IncludeChildBuilderImpl<TContract, CodecTypes, unknown>(
|
|
148
|
+
this.contract,
|
|
149
|
+
this.codecTypes,
|
|
150
|
+
childTable,
|
|
151
|
+
);
|
|
152
|
+
const builtChild = childBuilder(
|
|
153
|
+
childBuilderImpl as IncludeChildBuilder<TContract, CodecTypes, unknown>,
|
|
154
|
+
);
|
|
155
|
+
const childState = (
|
|
156
|
+
builtChild as IncludeChildBuilderImpl<TContract, CodecTypes, ChildRow>
|
|
157
|
+
).getState();
|
|
158
|
+
|
|
159
|
+
// Validate child projection is non-empty
|
|
160
|
+
if (childState.childProjection.aliases.length === 0) {
|
|
161
|
+
errorChildProjectionEmpty();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Determine alias
|
|
165
|
+
const alias = options?.alias ?? childTable.name;
|
|
166
|
+
|
|
167
|
+
// Check for alias collisions with existing projection
|
|
168
|
+
if (this.state.projection) {
|
|
169
|
+
if (this.state.projection.aliases.includes(alias)) {
|
|
170
|
+
errorIncludeAliasCollision(alias, 'projection');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check for alias collisions with existing includes
|
|
175
|
+
const existingIncludes = this.state.includes ?? [];
|
|
176
|
+
if (existingIncludes.some((inc) => inc.alias === alias)) {
|
|
177
|
+
errorIncludeAliasCollision(alias, 'include');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const includeState: IncludeState = {
|
|
181
|
+
alias,
|
|
182
|
+
table: childTable,
|
|
183
|
+
on: onPredicate,
|
|
184
|
+
childProjection: childState.childProjection,
|
|
185
|
+
...(childState.childWhere !== undefined ? { childWhere: childState.childWhere } : {}),
|
|
186
|
+
...(childState.childOrderBy !== undefined ? { childOrderBy: childState.childOrderBy } : {}),
|
|
187
|
+
...(childState.childLimit !== undefined ? { childLimit: childState.childLimit } : {}),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const newIncludes = [...existingIncludes, includeState];
|
|
191
|
+
|
|
192
|
+
// Type-level: Update Includes map with new include
|
|
193
|
+
// The AliasName generic parameter is inferred from options.alias, allowing TypeScript
|
|
194
|
+
// to track include definitions across multiple includeMany() calls and infer correct
|
|
195
|
+
// array types when select() includes boolean true for include references
|
|
196
|
+
type NewIncludes = Includes & { [K in AliasName]: ChildRow };
|
|
197
|
+
|
|
198
|
+
return new SelectBuilderImpl<TContract, Row, CodecTypes, NewIncludes>(
|
|
199
|
+
{
|
|
200
|
+
context: this.context,
|
|
201
|
+
},
|
|
202
|
+
{ ...this.state, includes: newIncludes },
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private _addJoin(
|
|
207
|
+
joinType: 'inner' | 'left' | 'right' | 'full',
|
|
208
|
+
table: TableRef,
|
|
209
|
+
on: (on: JoinOnBuilder) => JoinOnPredicate,
|
|
210
|
+
): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
211
|
+
const fromTable = this.ensureFrom();
|
|
212
|
+
|
|
213
|
+
if (!this.contract.storage.tables[table.name]) {
|
|
214
|
+
errorUnknownTable(table.name);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (table.name === fromTable.name) {
|
|
218
|
+
errorSelfJoinNotSupported();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const joinOnBuilder = createJoinOnBuilder();
|
|
222
|
+
const onPredicate = on(joinOnBuilder);
|
|
223
|
+
|
|
224
|
+
const joinState: JoinState = {
|
|
225
|
+
joinType,
|
|
226
|
+
table,
|
|
227
|
+
on: onPredicate,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const existingJoins = this.state.joins ?? [];
|
|
231
|
+
const newJoins = [...existingJoins, joinState];
|
|
232
|
+
|
|
233
|
+
return new SelectBuilderImpl<TContract, Row, CodecTypes, Includes>(
|
|
234
|
+
{
|
|
235
|
+
context: this.context,
|
|
236
|
+
},
|
|
237
|
+
{ ...this.state, joins: newJoins },
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
where(expr: AnyBinaryBuilder): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
242
|
+
return new SelectBuilderImpl<TContract, Row, CodecTypes, Includes>(
|
|
243
|
+
{
|
|
244
|
+
context: this.context,
|
|
245
|
+
},
|
|
246
|
+
{ ...this.state, where: expr },
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
select<P extends ProjectionInput>(
|
|
251
|
+
projection: P,
|
|
252
|
+
): SelectBuilderImpl<
|
|
253
|
+
TContract,
|
|
254
|
+
InferNestedProjectionRow<P, CodecTypes, Includes>,
|
|
255
|
+
CodecTypes,
|
|
256
|
+
Includes
|
|
257
|
+
> {
|
|
258
|
+
const table = this.ensureFrom();
|
|
259
|
+
const projectionState = buildProjectionState(table, projection, this.state.includes);
|
|
260
|
+
|
|
261
|
+
return new SelectBuilderImpl<
|
|
262
|
+
TContract,
|
|
263
|
+
InferNestedProjectionRow<P, CodecTypes, Includes>,
|
|
264
|
+
CodecTypes,
|
|
265
|
+
Includes
|
|
266
|
+
>(
|
|
267
|
+
{
|
|
268
|
+
context: this.context,
|
|
269
|
+
},
|
|
270
|
+
{ ...this.state, projection: projectionState },
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
orderBy(order: AnyOrderBuilder): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
275
|
+
return new SelectBuilderImpl<TContract, Row, CodecTypes, Includes>(
|
|
276
|
+
{
|
|
277
|
+
context: this.context,
|
|
278
|
+
},
|
|
279
|
+
{ ...this.state, orderBy: order },
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
limit(count: number): SelectBuilderImpl<TContract, Row, CodecTypes, Includes> {
|
|
284
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
285
|
+
errorLimitMustBeNonNegativeInteger();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return new SelectBuilderImpl<TContract, Row, CodecTypes, Includes>(
|
|
289
|
+
{
|
|
290
|
+
context: this.context,
|
|
291
|
+
},
|
|
292
|
+
{ ...this.state, limit: count },
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
build(options?: BuildOptions): SqlQueryPlan<Row> {
|
|
297
|
+
const table = this.ensureFrom();
|
|
298
|
+
const projection = this.ensureProjection();
|
|
299
|
+
|
|
300
|
+
const paramsMap = (options?.params ?? {}) as Record<string, unknown>;
|
|
301
|
+
const contractTable = this.contract.storage.tables[table.name];
|
|
302
|
+
|
|
303
|
+
if (!contractTable) {
|
|
304
|
+
errorUnknownTable(table.name);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const paramDescriptors: ParamDescriptor[] = [];
|
|
308
|
+
const paramValues: unknown[] = [];
|
|
309
|
+
const paramCodecs: Record<string, string> = {};
|
|
310
|
+
|
|
311
|
+
const whereResult = this.state.where
|
|
312
|
+
? buildWhereExpr(this.contract, this.state.where, paramsMap, paramDescriptors, paramValues)
|
|
313
|
+
: undefined;
|
|
314
|
+
const whereExpr = whereResult?.expr;
|
|
315
|
+
|
|
316
|
+
if (whereResult?.codecId && whereResult.paramName) {
|
|
317
|
+
paramCodecs[whereResult.paramName] = whereResult.codecId;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const orderByClause = this.state.orderBy
|
|
321
|
+
? (() => {
|
|
322
|
+
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)];
|
|
332
|
+
})()
|
|
333
|
+
: undefined;
|
|
334
|
+
|
|
335
|
+
const joins = this.state.joins?.map((join) => buildJoinAst(join));
|
|
336
|
+
|
|
337
|
+
const includes = this.state.includes?.map((include) =>
|
|
338
|
+
buildIncludeAst(include, this.contract, paramsMap, paramDescriptors, paramValues),
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Build projection with support for includeRef and OperationExpr
|
|
342
|
+
const projectEntries: Array<{ alias: string; expr: ColumnRef | IncludeRef | OperationExpr }> =
|
|
343
|
+
[];
|
|
344
|
+
for (let i = 0; i < projection.aliases.length; i++) {
|
|
345
|
+
const alias = projection.aliases[i];
|
|
346
|
+
if (!alias) {
|
|
347
|
+
errorMissingAlias(i);
|
|
348
|
+
}
|
|
349
|
+
const column = projection.columns[i];
|
|
350
|
+
|
|
351
|
+
// Check if this alias matches an include alias first
|
|
352
|
+
// Include placeholders have null columns
|
|
353
|
+
const matchingInclude = this.state.includes?.find((inc) => inc.alias === alias);
|
|
354
|
+
if (matchingInclude) {
|
|
355
|
+
// This is an include reference - column can be null for placeholders
|
|
356
|
+
projectEntries.push({
|
|
357
|
+
alias,
|
|
358
|
+
expr: { kind: 'includeRef', alias },
|
|
359
|
+
});
|
|
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
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const ast = createSelectAst({
|
|
386
|
+
from: createTableRef(table.name),
|
|
387
|
+
joins,
|
|
388
|
+
includes,
|
|
389
|
+
project: projectEntries,
|
|
390
|
+
where: whereExpr,
|
|
391
|
+
orderBy: orderByClause,
|
|
392
|
+
limit: this.state.limit,
|
|
393
|
+
} as {
|
|
394
|
+
from: TableRef;
|
|
395
|
+
joins?: ReadonlyArray<JoinAst>;
|
|
396
|
+
includes?: ReadonlyArray<IncludeAst>;
|
|
397
|
+
project: ReadonlyArray<{ alias: string; expr: ColumnRef | IncludeRef | OperationExpr }>;
|
|
398
|
+
where?: BinaryExpr;
|
|
399
|
+
orderBy?: ReadonlyArray<{ expr: ColumnRef | OperationExpr; dir: Direction }>;
|
|
400
|
+
limit?: number;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const planMeta = buildMeta({
|
|
404
|
+
contract: this.contract,
|
|
405
|
+
table,
|
|
406
|
+
projection,
|
|
407
|
+
joins: this.state.joins,
|
|
408
|
+
includes: this.state.includes,
|
|
409
|
+
paramDescriptors,
|
|
410
|
+
paramCodecs,
|
|
411
|
+
where: this.state.where,
|
|
412
|
+
orderBy: this.state.orderBy,
|
|
413
|
+
} as {
|
|
414
|
+
contract: SqlContract<SqlStorage>;
|
|
415
|
+
table: TableRef;
|
|
416
|
+
projection: ProjectionState;
|
|
417
|
+
joins?: ReadonlyArray<JoinState>;
|
|
418
|
+
includes?: ReadonlyArray<IncludeState>;
|
|
419
|
+
where?: BinaryBuilder;
|
|
420
|
+
orderBy?: AnyOrderBuilder;
|
|
421
|
+
paramDescriptors: ParamDescriptor[];
|
|
422
|
+
paramCodecs?: Record<string, string>;
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const queryPlan: SqlQueryPlan<Row> = Object.freeze({
|
|
426
|
+
ast,
|
|
427
|
+
params: paramValues,
|
|
428
|
+
meta: planMeta,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return queryPlan;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private ensureFrom() {
|
|
435
|
+
if (!this.state.from) {
|
|
436
|
+
errorFromMustBeCalled();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return this.state.from;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private ensureProjection() {
|
|
443
|
+
if (!this.state.projection) {
|
|
444
|
+
errorSelectMustBeCalled();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return this.state.projection;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ParamDescriptor } from '@prisma-next/contract/types';
|
|
2
|
+
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type { TableRef } from '@prisma-next/sql-relational-core/ast';
|
|
4
|
+
import type {
|
|
5
|
+
AnyBinaryBuilder,
|
|
6
|
+
AnyColumnBuilder,
|
|
7
|
+
AnyOrderBuilder,
|
|
8
|
+
NestedProjection,
|
|
9
|
+
} from '@prisma-next/sql-relational-core/types';
|
|
10
|
+
import type { ProjectionState } from '../utils/state';
|
|
11
|
+
|
|
12
|
+
export type ProjectionInput = Record<string, AnyColumnBuilder | boolean | NestedProjection>;
|
|
13
|
+
|
|
14
|
+
export interface MetaBuildArgs {
|
|
15
|
+
readonly contract: SqlContract<SqlStorage>;
|
|
16
|
+
readonly table: TableRef;
|
|
17
|
+
readonly projection: ProjectionState;
|
|
18
|
+
readonly joins?: ReadonlyArray<{
|
|
19
|
+
readonly joinType: 'inner' | 'left' | 'right' | 'full';
|
|
20
|
+
readonly table: TableRef;
|
|
21
|
+
readonly on: {
|
|
22
|
+
readonly left: unknown;
|
|
23
|
+
readonly right: unknown;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
readonly includes?: ReadonlyArray<{
|
|
27
|
+
readonly alias: string;
|
|
28
|
+
readonly table: TableRef;
|
|
29
|
+
readonly on: {
|
|
30
|
+
readonly left: unknown;
|
|
31
|
+
readonly right: unknown;
|
|
32
|
+
};
|
|
33
|
+
readonly childProjection: ProjectionState;
|
|
34
|
+
readonly childWhere?: AnyBinaryBuilder;
|
|
35
|
+
readonly childOrderBy?: AnyOrderBuilder;
|
|
36
|
+
}>;
|
|
37
|
+
readonly where?: AnyBinaryBuilder;
|
|
38
|
+
readonly orderBy?: AnyOrderBuilder;
|
|
39
|
+
readonly paramDescriptors: ParamDescriptor[];
|
|
40
|
+
readonly paramCodecs?: Record<string, string>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
2
|
+
import type { TableRef } from '@prisma-next/sql-relational-core/ast';
|
|
3
|
+
import type { ParamPlaceholder, RawFactory } from '@prisma-next/sql-relational-core/types';
|
|
4
|
+
import type { DeleteBuilder, InsertBuilder, UpdateBuilder } from '../sql/mutation-builder';
|
|
5
|
+
import type { SelectBuilderImpl } from '../sql/select-builder';
|
|
6
|
+
|
|
7
|
+
export type { TableRef } from '@prisma-next/sql-relational-core/ast';
|
|
8
|
+
export type {
|
|
9
|
+
AnyColumnBuilder,
|
|
10
|
+
BuildOptions,
|
|
11
|
+
InferReturningRow,
|
|
12
|
+
ParamPlaceholder,
|
|
13
|
+
RawFactory,
|
|
14
|
+
SqlBuilderOptions,
|
|
15
|
+
} from '@prisma-next/sql-relational-core/types';
|
|
16
|
+
|
|
17
|
+
export type SelectBuilder<
|
|
18
|
+
TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
|
|
19
|
+
Row = unknown,
|
|
20
|
+
CodecTypes extends Record<string, { readonly output: unknown }> = Record<string, never>,
|
|
21
|
+
Includes extends Record<string, unknown> = Record<string, never>,
|
|
22
|
+
> = SelectBuilderImpl<TContract, Row, CodecTypes, Includes> & {
|
|
23
|
+
readonly raw: RawFactory;
|
|
24
|
+
insert(
|
|
25
|
+
table: TableRef,
|
|
26
|
+
values: Record<string, ParamPlaceholder>,
|
|
27
|
+
): InsertBuilder<TContract, CodecTypes>;
|
|
28
|
+
update(
|
|
29
|
+
table: TableRef,
|
|
30
|
+
set: Record<string, ParamPlaceholder>,
|
|
31
|
+
): UpdateBuilder<TContract, CodecTypes>;
|
|
32
|
+
delete(table: TableRef): DeleteBuilder<TContract, CodecTypes>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type { IncludeChildBuilder } from '../sql/include-builder';
|
|
36
|
+
export type { DeleteBuilder, InsertBuilder, UpdateBuilder } from '../sql/mutation-builder';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { planInvalid } from '@prisma-next/plan';
|
|
2
|
+
import type { AnyColumnBuilder } from '@prisma-next/sql-relational-core/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Asserts that a ColumnBuilder has table and column properties.
|
|
6
|
+
*/
|
|
7
|
+
export function assertColumnBuilder(col: unknown, context: string): AnyColumnBuilder {
|
|
8
|
+
if (
|
|
9
|
+
typeof col === 'object' &&
|
|
10
|
+
col !== null &&
|
|
11
|
+
'table' in col &&
|
|
12
|
+
'column' in col &&
|
|
13
|
+
typeof (col as { table: unknown }).table === 'string' &&
|
|
14
|
+
typeof (col as { column: unknown }).column === 'string'
|
|
15
|
+
) {
|
|
16
|
+
return col as AnyColumnBuilder;
|
|
17
|
+
}
|
|
18
|
+
throw planInvalid(`ColumnBuilder missing table/column in ${context}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Asserts that a JoinOnPredicate has valid left and right columns.
|
|
23
|
+
*/
|
|
24
|
+
export function assertJoinOnPredicate(on: {
|
|
25
|
+
left?: { table?: string; column?: string };
|
|
26
|
+
right?: { table?: string; column?: string };
|
|
27
|
+
}): asserts on is {
|
|
28
|
+
left: { table: string; column: string };
|
|
29
|
+
right: { table: string; column: string };
|
|
30
|
+
} {
|
|
31
|
+
if (!on.left?.table || !on.left?.column || !on.right?.table || !on.right?.column) {
|
|
32
|
+
throw planInvalid('JoinOnPredicate missing required table/column properties');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
2
|
+
import {
|
|
3
|
+
errorIncludeCapabilitiesNotTrue,
|
|
4
|
+
errorIncludeRequiresCapabilities,
|
|
5
|
+
errorReturningCapabilityNotTrue,
|
|
6
|
+
errorReturningRequiresCapability,
|
|
7
|
+
} from './errors';
|
|
8
|
+
|
|
9
|
+
export function checkIncludeCapabilities(contract: SqlContract<SqlStorage>): void {
|
|
10
|
+
const target = contract.target;
|
|
11
|
+
const contractCapabilities = contract.capabilities;
|
|
12
|
+
const declaredTargetCapabilities = contractCapabilities?.[target];
|
|
13
|
+
|
|
14
|
+
if (!contractCapabilities || !declaredTargetCapabilities) {
|
|
15
|
+
errorIncludeRequiresCapabilities(target);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
declaredTargetCapabilities['lateral'] !== true ||
|
|
20
|
+
declaredTargetCapabilities['jsonAgg'] !== true
|
|
21
|
+
) {
|
|
22
|
+
errorIncludeCapabilitiesNotTrue(target, {
|
|
23
|
+
lateral: declaredTargetCapabilities['lateral'],
|
|
24
|
+
jsonAgg: declaredTargetCapabilities['jsonAgg'],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function checkReturningCapability(contract: SqlContract<SqlStorage>): void {
|
|
30
|
+
const target = contract.target;
|
|
31
|
+
const capabilities = contract.capabilities;
|
|
32
|
+
if (!capabilities || !capabilities[target]) {
|
|
33
|
+
errorReturningRequiresCapability(target);
|
|
34
|
+
}
|
|
35
|
+
const targetCapabilities = capabilities[target];
|
|
36
|
+
if (targetCapabilities['returning'] !== true) {
|
|
37
|
+
errorReturningCapabilityNotTrue(target, targetCapabilities['returning']);
|
|
38
|
+
}
|
|
39
|
+
}
|