@prisma-next/sql-orm-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/orm.d.ts +3 -5
- package/dist/exports/orm.d.ts.map +1 -0
- package/dist/index.d.ts +4 -42
- package/dist/index.d.ts.map +1 -0
- package/dist/mutations/delete-builder.d.ts +9 -0
- package/dist/mutations/delete-builder.d.ts.map +1 -0
- package/dist/mutations/insert-builder.d.ts +7 -0
- package/dist/mutations/insert-builder.d.ts.map +1 -0
- package/dist/mutations/update-builder.d.ts +9 -0
- package/dist/mutations/update-builder.d.ts.map +1 -0
- package/dist/orm/builder.d.ts +38 -0
- package/dist/orm/builder.d.ts.map +1 -0
- package/dist/orm/capabilities.d.ts +3 -0
- package/dist/orm/capabilities.d.ts.map +1 -0
- package/dist/orm/context.d.ts +5 -0
- package/dist/orm/context.d.ts.map +1 -0
- package/dist/orm/state.d.ts +45 -0
- package/dist/orm/state.d.ts.map +1 -0
- package/dist/orm-include-child.d.ts +35 -0
- package/dist/orm-include-child.d.ts.map +1 -0
- package/dist/orm-relation-filter.d.ts +19 -0
- package/dist/orm-relation-filter.d.ts.map +1 -0
- package/dist/{orm-DAnGd7z2.d.ts → orm-types.d.ts} +16 -27
- package/dist/orm-types.d.ts.map +1 -0
- package/dist/orm.d.ts +5 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/plan/lowering.d.ts +2 -0
- package/dist/plan/lowering.d.ts.map +1 -0
- package/dist/plan/plan-assembly.d.ts +24 -0
- package/dist/plan/plan-assembly.d.ts.map +1 -0
- package/dist/plan/result-typing.d.ts +2 -0
- package/dist/plan/result-typing.d.ts.map +1 -0
- package/dist/relations/include-plan.d.ts +38 -0
- package/dist/relations/include-plan.d.ts.map +1 -0
- package/dist/selection/join.d.ts +3 -0
- package/dist/selection/join.d.ts.map +1 -0
- package/dist/selection/ordering.d.ts +11 -0
- package/dist/selection/ordering.d.ts.map +1 -0
- package/dist/selection/pagination.d.ts +6 -0
- package/dist/selection/pagination.d.ts.map +1 -0
- package/dist/selection/predicates.d.ts +10 -0
- package/dist/selection/predicates.d.ts.map +1 -0
- package/dist/selection/projection.d.ts +28 -0
- package/dist/selection/projection.d.ts.map +1 -0
- package/dist/selection/select-builder.d.ts +22 -0
- package/dist/selection/select-builder.d.ts.map +1 -0
- package/dist/types/internal.d.ts +4 -0
- package/dist/types/internal.d.ts.map +1 -0
- package/dist/utils/ast.d.ts +2 -0
- package/dist/utils/ast.d.ts.map +1 -0
- package/dist/utils/errors.d.ts +29 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/param-descriptor.d.ts +10 -0
- package/dist/utils/param-descriptor.d.ts.map +1 -0
- package/package.json +12 -11
- package/src/exports/orm.ts +12 -0
- package/src/index.ts +11 -0
- package/src/mutations/delete-builder.ts +87 -0
- package/src/mutations/insert-builder.ts +148 -0
- package/src/mutations/update-builder.ts +141 -0
- package/src/orm/builder.ts +744 -0
- package/src/orm/capabilities.ts +14 -0
- package/src/orm/context.ts +10 -0
- package/src/orm/state.ts +52 -0
- package/src/orm-include-child.ts +169 -0
- package/src/orm-relation-filter.ts +93 -0
- package/src/orm-types.ts +271 -0
- package/src/orm.ts +51 -0
- package/src/plan/lowering.ts +1 -0
- package/src/plan/plan-assembly.ts +297 -0
- package/src/plan/result-typing.ts +1 -0
- package/src/relations/include-plan.ts +324 -0
- package/src/selection/join.ts +13 -0
- package/src/selection/ordering.ts +52 -0
- package/src/selection/pagination.ts +11 -0
- package/src/selection/predicates.ts +120 -0
- package/src/selection/projection.ts +136 -0
- package/src/selection/select-builder.ts +82 -0
- package/src/types/internal.ts +3 -0
- package/src/utils/ast.ts +12 -0
- package/src/utils/errors.ts +130 -0
- package/src/utils/param-descriptor.ts +19 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
import type { ParamDescriptor } from '@prisma-next/contract/types';
|
|
2
|
+
import { planInvalid } from '@prisma-next/plan';
|
|
3
|
+
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
4
|
+
import type { SelectAst, TableRef } from '@prisma-next/sql-relational-core/ast';
|
|
5
|
+
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
6
|
+
import type { QueryLaneContext } from '@prisma-next/sql-relational-core/query-lane-context';
|
|
7
|
+
import { schema } from '@prisma-next/sql-relational-core/schema';
|
|
8
|
+
import type {
|
|
9
|
+
AnyBinaryBuilder,
|
|
10
|
+
AnyColumnBuilder,
|
|
11
|
+
AnyOrderBuilder,
|
|
12
|
+
BinaryBuilder,
|
|
13
|
+
BuildOptions,
|
|
14
|
+
InferNestedProjectionRow,
|
|
15
|
+
NestedProjection,
|
|
16
|
+
OrderBuilder,
|
|
17
|
+
} from '@prisma-next/sql-relational-core/types';
|
|
18
|
+
import { buildDeletePlan } from '../mutations/delete-builder';
|
|
19
|
+
import { buildInsertPlan } from '../mutations/insert-builder';
|
|
20
|
+
import { buildUpdatePlan } from '../mutations/update-builder';
|
|
21
|
+
import type { OrmIncludeChildBuilder } from '../orm-include-child';
|
|
22
|
+
import { OrmIncludeChildBuilderImpl } from '../orm-include-child';
|
|
23
|
+
import { OrmRelationFilterBuilderImpl } from '../orm-relation-filter';
|
|
24
|
+
import type {
|
|
25
|
+
IncludeAccumulator,
|
|
26
|
+
ModelColumnAccessor,
|
|
27
|
+
OrmBuilderOptions,
|
|
28
|
+
OrmIncludeAccessor,
|
|
29
|
+
OrmModelBuilder,
|
|
30
|
+
OrmRelationFilterBuilder,
|
|
31
|
+
OrmWhereProperty,
|
|
32
|
+
} from '../orm-types';
|
|
33
|
+
import { buildMeta, type MetaBuildArgs } from '../plan/plan-assembly';
|
|
34
|
+
import {
|
|
35
|
+
buildExistsSubqueries,
|
|
36
|
+
buildIncludeAsts,
|
|
37
|
+
combineWhereClauses,
|
|
38
|
+
} from '../relations/include-plan';
|
|
39
|
+
import { buildOrderByClause } from '../selection/ordering';
|
|
40
|
+
import { buildWhereExpr } from '../selection/predicates';
|
|
41
|
+
import { buildProjectionState, type ProjectionInput } from '../selection/projection';
|
|
42
|
+
import { buildProjectionItems, buildSelectAst } from '../selection/select-builder';
|
|
43
|
+
import { createTableRef } from '../utils/ast';
|
|
44
|
+
import { errorModelNotFound, errorTableNotFound, errorUnknownTable } from '../utils/errors';
|
|
45
|
+
import { createOrmContext } from './context';
|
|
46
|
+
import type { OrmIncludeState, RelationFilter } from './state';
|
|
47
|
+
|
|
48
|
+
export class OrmModelBuilderImpl<
|
|
49
|
+
TContract extends SqlContract<SqlStorage>,
|
|
50
|
+
CodecTypes extends Record<string, { output: unknown }> = Record<string, never>,
|
|
51
|
+
ModelName extends string = string,
|
|
52
|
+
Includes extends Record<string, unknown> = Record<string, never>,
|
|
53
|
+
Row = unknown,
|
|
54
|
+
> implements OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row>
|
|
55
|
+
{
|
|
56
|
+
private readonly context: QueryLaneContext<TContract>;
|
|
57
|
+
private readonly contract: TContract;
|
|
58
|
+
private readonly modelName: ModelName;
|
|
59
|
+
private table: TableRef;
|
|
60
|
+
private wherePredicate: AnyBinaryBuilder | undefined = undefined;
|
|
61
|
+
private relationFilters: RelationFilter[] = [];
|
|
62
|
+
private includes: OrmIncludeState[] = [];
|
|
63
|
+
private orderByExpr: AnyOrderBuilder | undefined = undefined;
|
|
64
|
+
private limitValue: number | undefined = undefined;
|
|
65
|
+
private offsetValue: number | undefined = undefined;
|
|
66
|
+
private projection: Record<string, AnyColumnBuilder | boolean | NestedProjection> | undefined =
|
|
67
|
+
undefined;
|
|
68
|
+
|
|
69
|
+
constructor(options: OrmBuilderOptions<TContract>, modelName: ModelName) {
|
|
70
|
+
this.context = options.context;
|
|
71
|
+
this.contract = options.context.contract;
|
|
72
|
+
this.modelName = modelName;
|
|
73
|
+
|
|
74
|
+
const tableName = this.contract.mappings.modelToTable?.[modelName];
|
|
75
|
+
if (!tableName) {
|
|
76
|
+
errorModelNotFound(modelName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const schemaHandle = schema(options.context);
|
|
80
|
+
const table = schemaHandle.tables[tableName];
|
|
81
|
+
if (!table) {
|
|
82
|
+
errorTableNotFound(tableName);
|
|
83
|
+
}
|
|
84
|
+
this.table = table;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get where(): OrmWhereProperty<TContract, CodecTypes, ModelName, Includes, Row> {
|
|
88
|
+
const whereFn = (
|
|
89
|
+
fn: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
|
|
90
|
+
): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row> => {
|
|
91
|
+
const builder = new OrmModelBuilderImpl<TContract, CodecTypes, ModelName, Includes, Row>(
|
|
92
|
+
{ context: this.context },
|
|
93
|
+
this.modelName,
|
|
94
|
+
);
|
|
95
|
+
builder['table'] = this.table;
|
|
96
|
+
builder.wherePredicate = fn(this._getModelAccessor());
|
|
97
|
+
builder.relationFilters = this.relationFilters;
|
|
98
|
+
builder.includes = this.includes;
|
|
99
|
+
builder.orderByExpr = this.orderByExpr;
|
|
100
|
+
builder.limitValue = this.limitValue;
|
|
101
|
+
builder.offsetValue = this.offsetValue;
|
|
102
|
+
builder.projection = this.projection;
|
|
103
|
+
return builder;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Add related property using Proxy
|
|
107
|
+
const related = this._createRelatedProxy();
|
|
108
|
+
|
|
109
|
+
return Object.assign(whereFn, { related }) as OrmWhereProperty<
|
|
110
|
+
TContract,
|
|
111
|
+
CodecTypes,
|
|
112
|
+
ModelName,
|
|
113
|
+
Includes,
|
|
114
|
+
Row
|
|
115
|
+
>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get include(): OrmIncludeAccessor<TContract, CodecTypes, ModelName, Includes, Row> {
|
|
119
|
+
return this._createIncludeProxy();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private _createIncludeProxy(): OrmIncludeAccessor<
|
|
123
|
+
TContract,
|
|
124
|
+
CodecTypes,
|
|
125
|
+
ModelName,
|
|
126
|
+
Includes,
|
|
127
|
+
Row
|
|
128
|
+
> {
|
|
129
|
+
const self = this;
|
|
130
|
+
// Relations are keyed by table name, not model name
|
|
131
|
+
const tableName = this.contract.mappings.modelToTable?.[this.modelName];
|
|
132
|
+
if (!tableName) {
|
|
133
|
+
return {} as OrmIncludeAccessor<TContract, CodecTypes, ModelName, Includes, Row>;
|
|
134
|
+
}
|
|
135
|
+
const modelRelations = this.contract.relations?.[tableName];
|
|
136
|
+
if (!modelRelations || typeof modelRelations !== 'object') {
|
|
137
|
+
return {} as OrmIncludeAccessor<TContract, CodecTypes, ModelName, Includes, Row>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new Proxy({} as OrmIncludeAccessor<TContract, CodecTypes, ModelName, Includes, Row>, {
|
|
141
|
+
get(_target, prop) {
|
|
142
|
+
if (typeof prop !== 'string') {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const relation = (modelRelations as Record<string, { to?: string }>)[prop];
|
|
147
|
+
if (!relation || typeof relation !== 'object' || !('to' in relation)) {
|
|
148
|
+
throw planInvalid(`Relation ${prop} not found on model ${self.modelName}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const childModelName = relation.to as string;
|
|
152
|
+
const relationDef = relation as {
|
|
153
|
+
to: string;
|
|
154
|
+
cardinality: string;
|
|
155
|
+
on: { parentCols: readonly string[]; childCols: readonly string[] };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const relationName = prop as keyof typeof modelRelations & string;
|
|
159
|
+
|
|
160
|
+
const includeFn = (<ChildRow>(
|
|
161
|
+
child: (
|
|
162
|
+
child: OrmIncludeChildBuilder<TContract, CodecTypes, typeof childModelName>,
|
|
163
|
+
) => OrmIncludeChildBuilder<TContract, CodecTypes, typeof childModelName, ChildRow>,
|
|
164
|
+
) => {
|
|
165
|
+
return self._applyInclude<typeof relationName, ChildRow>(
|
|
166
|
+
relationName,
|
|
167
|
+
childModelName,
|
|
168
|
+
child,
|
|
169
|
+
relationDef,
|
|
170
|
+
);
|
|
171
|
+
}) as OrmIncludeAccessor<
|
|
172
|
+
TContract,
|
|
173
|
+
CodecTypes,
|
|
174
|
+
ModelName,
|
|
175
|
+
Includes,
|
|
176
|
+
Row
|
|
177
|
+
>[typeof relationName];
|
|
178
|
+
|
|
179
|
+
return includeFn;
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private _applyInclude<RelationName extends string, ChildRow>(
|
|
185
|
+
relationName: RelationName,
|
|
186
|
+
childModelName: string,
|
|
187
|
+
childBuilderFn: (
|
|
188
|
+
child: OrmIncludeChildBuilder<TContract, CodecTypes, string>,
|
|
189
|
+
) => OrmIncludeChildBuilder<TContract, CodecTypes, string, ChildRow>,
|
|
190
|
+
relationDef: {
|
|
191
|
+
to: string;
|
|
192
|
+
cardinality: string;
|
|
193
|
+
on: { parentCols: readonly string[]; childCols: readonly string[] };
|
|
194
|
+
},
|
|
195
|
+
): OrmModelBuilder<
|
|
196
|
+
TContract,
|
|
197
|
+
CodecTypes,
|
|
198
|
+
ModelName,
|
|
199
|
+
IncludeAccumulator<Includes, RelationName, ChildRow>,
|
|
200
|
+
Row
|
|
201
|
+
> {
|
|
202
|
+
// Get child table
|
|
203
|
+
const childTableName = this.contract.mappings.modelToTable?.[childModelName];
|
|
204
|
+
if (!childTableName) {
|
|
205
|
+
errorModelNotFound(childModelName);
|
|
206
|
+
}
|
|
207
|
+
const childTable: TableRef = { kind: 'table', name: childTableName };
|
|
208
|
+
|
|
209
|
+
// Create child builder and apply callback
|
|
210
|
+
const childBuilder = new OrmIncludeChildBuilderImpl<TContract, CodecTypes, string>(
|
|
211
|
+
{ context: this.context },
|
|
212
|
+
childModelName,
|
|
213
|
+
);
|
|
214
|
+
const builtChild = childBuilderFn(
|
|
215
|
+
childBuilder as OrmIncludeChildBuilder<TContract, CodecTypes, string>,
|
|
216
|
+
);
|
|
217
|
+
const childState = (
|
|
218
|
+
builtChild as OrmIncludeChildBuilderImpl<TContract, CodecTypes, string, ChildRow>
|
|
219
|
+
).getState();
|
|
220
|
+
|
|
221
|
+
// Store the include
|
|
222
|
+
// Note: Child projection validation happens in findMany() when compiling to includeMany
|
|
223
|
+
const includeState: OrmIncludeState = {
|
|
224
|
+
relationName,
|
|
225
|
+
childModelName,
|
|
226
|
+
childTable,
|
|
227
|
+
childWhere: childState.childWhere,
|
|
228
|
+
childOrderBy: childState.childOrderBy,
|
|
229
|
+
childLimit: childState.childLimit,
|
|
230
|
+
childProjection: childState.childProjection,
|
|
231
|
+
alias: relationName,
|
|
232
|
+
relation: relationDef,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const builder = new OrmModelBuilderImpl<
|
|
236
|
+
TContract,
|
|
237
|
+
CodecTypes,
|
|
238
|
+
ModelName,
|
|
239
|
+
IncludeAccumulator<Includes, RelationName, ChildRow>,
|
|
240
|
+
Row
|
|
241
|
+
>({ context: this.context }, this.modelName);
|
|
242
|
+
builder['table'] = this.table;
|
|
243
|
+
builder.wherePredicate = this.wherePredicate;
|
|
244
|
+
builder.relationFilters = this.relationFilters;
|
|
245
|
+
builder.includes = [...this.includes, includeState];
|
|
246
|
+
builder.orderByExpr = this.orderByExpr;
|
|
247
|
+
builder.limitValue = this.limitValue;
|
|
248
|
+
builder.offsetValue = this.offsetValue;
|
|
249
|
+
builder.projection = this.projection;
|
|
250
|
+
return builder;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private _createRelatedProxy(): OrmWhereProperty<
|
|
254
|
+
TContract,
|
|
255
|
+
CodecTypes,
|
|
256
|
+
ModelName,
|
|
257
|
+
Includes,
|
|
258
|
+
Row
|
|
259
|
+
>['related'] {
|
|
260
|
+
const self = this;
|
|
261
|
+
// Relations are keyed by table name, not model name
|
|
262
|
+
const tableName = this.contract.mappings.modelToTable?.[this.modelName];
|
|
263
|
+
if (!tableName) {
|
|
264
|
+
return {} as OrmWhereProperty<TContract, CodecTypes, ModelName, Includes, Row>['related'];
|
|
265
|
+
}
|
|
266
|
+
const modelRelations = this.contract.relations?.[tableName];
|
|
267
|
+
if (!modelRelations || typeof modelRelations !== 'object') {
|
|
268
|
+
return {} as OrmWhereProperty<TContract, CodecTypes, ModelName, Includes, Row>['related'];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return new Proxy(
|
|
272
|
+
{} as OrmWhereProperty<TContract, CodecTypes, ModelName, Includes, Row>['related'],
|
|
273
|
+
{
|
|
274
|
+
get(_target, prop) {
|
|
275
|
+
if (typeof prop !== 'string') {
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const relation = (modelRelations as Record<string, { to?: string }>)[prop];
|
|
280
|
+
if (!relation || typeof relation !== 'object' || !('to' in relation)) {
|
|
281
|
+
throw planInvalid(`Relation ${prop} not found on model ${self.modelName}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const childModelName = relation.to as string;
|
|
285
|
+
const relationDef = relation as {
|
|
286
|
+
to: string;
|
|
287
|
+
cardinality: string;
|
|
288
|
+
on: { parentCols: readonly string[]; childCols: readonly string[] };
|
|
289
|
+
};
|
|
290
|
+
const filterBuilder = new OrmRelationFilterBuilderImpl<
|
|
291
|
+
TContract,
|
|
292
|
+
CodecTypes,
|
|
293
|
+
typeof childModelName
|
|
294
|
+
>({ context: self.context }, childModelName);
|
|
295
|
+
// Expose model accessor directly on the builder for convenience
|
|
296
|
+
const modelAccessor = filterBuilder.getModelAccessor();
|
|
297
|
+
const builderWithAccessor = Object.assign(
|
|
298
|
+
filterBuilder,
|
|
299
|
+
modelAccessor,
|
|
300
|
+
) as OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName> &
|
|
301
|
+
ModelColumnAccessor<TContract, CodecTypes, typeof childModelName>;
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
some: (
|
|
305
|
+
fn: (
|
|
306
|
+
child:
|
|
307
|
+
| OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>
|
|
308
|
+
| ModelColumnAccessor<TContract, CodecTypes, typeof childModelName>,
|
|
309
|
+
) =>
|
|
310
|
+
| OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>
|
|
311
|
+
| AnyBinaryBuilder,
|
|
312
|
+
) => {
|
|
313
|
+
const result = fn(builderWithAccessor);
|
|
314
|
+
// If result is a AnyBinaryBuilder, wrap it in a builder
|
|
315
|
+
if (result && 'kind' in result && result.kind === 'binary') {
|
|
316
|
+
const wrappedBuilder = new OrmRelationFilterBuilderImpl<
|
|
317
|
+
TContract,
|
|
318
|
+
CodecTypes,
|
|
319
|
+
typeof childModelName
|
|
320
|
+
>({ context: self.context }, childModelName);
|
|
321
|
+
wrappedBuilder['wherePredicate'] = result as AnyBinaryBuilder;
|
|
322
|
+
return self._applyRelationFilter(
|
|
323
|
+
prop,
|
|
324
|
+
childModelName,
|
|
325
|
+
'some',
|
|
326
|
+
() => wrappedBuilder,
|
|
327
|
+
relationDef,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return self._applyRelationFilter(
|
|
331
|
+
prop,
|
|
332
|
+
childModelName,
|
|
333
|
+
'some',
|
|
334
|
+
() =>
|
|
335
|
+
result as OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>,
|
|
336
|
+
relationDef,
|
|
337
|
+
);
|
|
338
|
+
},
|
|
339
|
+
none: (
|
|
340
|
+
fn: (
|
|
341
|
+
child:
|
|
342
|
+
| OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>
|
|
343
|
+
| ModelColumnAccessor<TContract, CodecTypes, typeof childModelName>,
|
|
344
|
+
) =>
|
|
345
|
+
| OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>
|
|
346
|
+
| AnyBinaryBuilder,
|
|
347
|
+
) => {
|
|
348
|
+
const result = fn(builderWithAccessor);
|
|
349
|
+
if (result && 'kind' in result && result.kind === 'binary') {
|
|
350
|
+
const wrappedBuilder = new OrmRelationFilterBuilderImpl<
|
|
351
|
+
TContract,
|
|
352
|
+
CodecTypes,
|
|
353
|
+
typeof childModelName
|
|
354
|
+
>({ context: self.context }, childModelName);
|
|
355
|
+
wrappedBuilder['wherePredicate'] = result as AnyBinaryBuilder;
|
|
356
|
+
return self._applyRelationFilter(
|
|
357
|
+
prop,
|
|
358
|
+
childModelName,
|
|
359
|
+
'none',
|
|
360
|
+
() => wrappedBuilder,
|
|
361
|
+
relationDef,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
return self._applyRelationFilter(
|
|
365
|
+
prop,
|
|
366
|
+
childModelName,
|
|
367
|
+
'none',
|
|
368
|
+
() =>
|
|
369
|
+
result as OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>,
|
|
370
|
+
relationDef,
|
|
371
|
+
);
|
|
372
|
+
},
|
|
373
|
+
every: (
|
|
374
|
+
fn: (
|
|
375
|
+
child:
|
|
376
|
+
| OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>
|
|
377
|
+
| ModelColumnAccessor<TContract, CodecTypes, typeof childModelName>,
|
|
378
|
+
) =>
|
|
379
|
+
| OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>
|
|
380
|
+
| AnyBinaryBuilder,
|
|
381
|
+
) => {
|
|
382
|
+
const result = fn(builderWithAccessor);
|
|
383
|
+
if (result && 'kind' in result && result.kind === 'binary') {
|
|
384
|
+
const wrappedBuilder = new OrmRelationFilterBuilderImpl<
|
|
385
|
+
TContract,
|
|
386
|
+
CodecTypes,
|
|
387
|
+
typeof childModelName
|
|
388
|
+
>({ context: self.context }, childModelName);
|
|
389
|
+
wrappedBuilder['wherePredicate'] = result as AnyBinaryBuilder;
|
|
390
|
+
return self._applyRelationFilter(
|
|
391
|
+
prop,
|
|
392
|
+
childModelName,
|
|
393
|
+
'every',
|
|
394
|
+
() => wrappedBuilder,
|
|
395
|
+
relationDef,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return self._applyRelationFilter(
|
|
399
|
+
prop,
|
|
400
|
+
childModelName,
|
|
401
|
+
'every',
|
|
402
|
+
() =>
|
|
403
|
+
result as OrmRelationFilterBuilder<TContract, CodecTypes, typeof childModelName>,
|
|
404
|
+
relationDef,
|
|
405
|
+
);
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private _applyRelationFilter(
|
|
414
|
+
relationName: string,
|
|
415
|
+
childModelName: string,
|
|
416
|
+
filterType: 'some' | 'none' | 'every',
|
|
417
|
+
fn: (
|
|
418
|
+
child: OrmRelationFilterBuilder<TContract, CodecTypes, string>,
|
|
419
|
+
) => OrmRelationFilterBuilder<TContract, CodecTypes, string>,
|
|
420
|
+
relationDef: {
|
|
421
|
+
to: string;
|
|
422
|
+
cardinality: string;
|
|
423
|
+
on: { parentCols: readonly string[]; childCols: readonly string[] };
|
|
424
|
+
},
|
|
425
|
+
): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row> {
|
|
426
|
+
// Create a relation filter builder and apply the callback
|
|
427
|
+
const filterBuilder = new OrmRelationFilterBuilderImpl<TContract, CodecTypes, string>(
|
|
428
|
+
{ context: this.context },
|
|
429
|
+
childModelName,
|
|
430
|
+
);
|
|
431
|
+
const appliedFilter = fn(
|
|
432
|
+
filterBuilder as OrmRelationFilterBuilder<TContract, CodecTypes, string>,
|
|
433
|
+
);
|
|
434
|
+
const childWhere = (
|
|
435
|
+
appliedFilter as OrmRelationFilterBuilderImpl<TContract, CodecTypes, string>
|
|
436
|
+
).getWherePredicate();
|
|
437
|
+
|
|
438
|
+
// Store the relation filter
|
|
439
|
+
const relationFilter: RelationFilter = {
|
|
440
|
+
relationName,
|
|
441
|
+
childModelName,
|
|
442
|
+
filterType,
|
|
443
|
+
childWhere,
|
|
444
|
+
relation: relationDef,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const builder = new OrmModelBuilderImpl<TContract, CodecTypes, ModelName, Includes, Row>(
|
|
448
|
+
{ context: this.context },
|
|
449
|
+
this.modelName,
|
|
450
|
+
);
|
|
451
|
+
builder['table'] = this.table;
|
|
452
|
+
builder.wherePredicate = this.wherePredicate;
|
|
453
|
+
builder.relationFilters = [...this.relationFilters, relationFilter];
|
|
454
|
+
builder.includes = this.includes;
|
|
455
|
+
builder.orderByExpr = this.orderByExpr;
|
|
456
|
+
builder.limitValue = this.limitValue;
|
|
457
|
+
builder.offsetValue = this.offsetValue;
|
|
458
|
+
builder.projection = this.projection;
|
|
459
|
+
return builder;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
orderBy(
|
|
463
|
+
fn: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => OrderBuilder,
|
|
464
|
+
): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row> {
|
|
465
|
+
const builder = new OrmModelBuilderImpl<TContract, CodecTypes, ModelName, Includes, Row>(
|
|
466
|
+
{ context: this.context },
|
|
467
|
+
this.modelName,
|
|
468
|
+
);
|
|
469
|
+
builder['table'] = this.table;
|
|
470
|
+
builder.wherePredicate = this.wherePredicate;
|
|
471
|
+
builder.relationFilters = this.relationFilters;
|
|
472
|
+
builder.includes = this.includes;
|
|
473
|
+
builder.orderByExpr = fn(this._getModelAccessor());
|
|
474
|
+
builder.limitValue = this.limitValue;
|
|
475
|
+
builder.offsetValue = this.offsetValue;
|
|
476
|
+
builder.projection = this.projection;
|
|
477
|
+
return builder;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
take(n: number): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row> {
|
|
481
|
+
const builder = new OrmModelBuilderImpl<TContract, CodecTypes, ModelName, Includes, Row>(
|
|
482
|
+
{ context: this.context },
|
|
483
|
+
this.modelName,
|
|
484
|
+
);
|
|
485
|
+
builder['table'] = this.table;
|
|
486
|
+
builder.wherePredicate = this.wherePredicate;
|
|
487
|
+
builder.relationFilters = this.relationFilters;
|
|
488
|
+
builder.includes = this.includes;
|
|
489
|
+
builder.orderByExpr = this.orderByExpr;
|
|
490
|
+
builder.limitValue = n;
|
|
491
|
+
builder.offsetValue = this.offsetValue;
|
|
492
|
+
builder.projection = this.projection;
|
|
493
|
+
return builder;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
skip(n: number): OrmModelBuilder<TContract, CodecTypes, ModelName, Includes, Row> {
|
|
497
|
+
// TODO: SQL lane doesn't support offset yet - this is a placeholder
|
|
498
|
+
// When offset is added to SelectAst, implement it here
|
|
499
|
+
const builder = new OrmModelBuilderImpl<TContract, CodecTypes, ModelName, Includes, Row>(
|
|
500
|
+
{ context: this.context },
|
|
501
|
+
this.modelName,
|
|
502
|
+
);
|
|
503
|
+
builder['table'] = this.table;
|
|
504
|
+
builder.wherePredicate = this.wherePredicate;
|
|
505
|
+
builder.relationFilters = this.relationFilters;
|
|
506
|
+
builder.includes = this.includes;
|
|
507
|
+
builder.orderByExpr = this.orderByExpr;
|
|
508
|
+
builder.limitValue = this.limitValue;
|
|
509
|
+
builder.offsetValue = n;
|
|
510
|
+
builder.projection = this.projection;
|
|
511
|
+
return builder;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
select<Projection extends Record<string, AnyColumnBuilder | boolean | NestedProjection>>(
|
|
515
|
+
fn: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => Projection,
|
|
516
|
+
): OrmModelBuilder<
|
|
517
|
+
TContract,
|
|
518
|
+
CodecTypes,
|
|
519
|
+
ModelName,
|
|
520
|
+
Includes,
|
|
521
|
+
InferNestedProjectionRow<Projection, CodecTypes, Includes>
|
|
522
|
+
> {
|
|
523
|
+
const builder = new OrmModelBuilderImpl<
|
|
524
|
+
TContract,
|
|
525
|
+
CodecTypes,
|
|
526
|
+
ModelName,
|
|
527
|
+
Includes,
|
|
528
|
+
InferNestedProjectionRow<Projection, CodecTypes, Includes>
|
|
529
|
+
>({ context: this.context }, this.modelName);
|
|
530
|
+
builder['table'] = this.table;
|
|
531
|
+
builder.wherePredicate = this.wherePredicate;
|
|
532
|
+
builder.relationFilters = this.relationFilters;
|
|
533
|
+
builder.includes = this.includes;
|
|
534
|
+
builder.orderByExpr = this.orderByExpr;
|
|
535
|
+
builder.limitValue = this.limitValue;
|
|
536
|
+
builder.offsetValue = this.offsetValue;
|
|
537
|
+
builder.projection = fn(this._getModelAccessor());
|
|
538
|
+
return builder as OrmModelBuilder<
|
|
539
|
+
TContract,
|
|
540
|
+
CodecTypes,
|
|
541
|
+
ModelName,
|
|
542
|
+
Includes,
|
|
543
|
+
InferNestedProjectionRow<Projection, CodecTypes, Includes>
|
|
544
|
+
>;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
findMany(options?: BuildOptions): SqlQueryPlan<Row> {
|
|
548
|
+
const paramsMap = (options?.params ?? {}) as Record<string, unknown>;
|
|
549
|
+
const contractTable = this.contract.storage.tables[this.table.name];
|
|
550
|
+
|
|
551
|
+
if (!contractTable) {
|
|
552
|
+
errorUnknownTable(this.table.name);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const paramDescriptors: ParamDescriptor[] = [];
|
|
556
|
+
const paramValues: unknown[] = [];
|
|
557
|
+
const paramCodecs: Record<string, string> = {};
|
|
558
|
+
|
|
559
|
+
// Build projection state
|
|
560
|
+
const projectionInput: ProjectionInput =
|
|
561
|
+
this.projection ??
|
|
562
|
+
(() => {
|
|
563
|
+
const modelAccessor = this._getModelAccessor();
|
|
564
|
+
const defaultProjection: Record<string, AnyColumnBuilder> = {};
|
|
565
|
+
for (const fieldName in modelAccessor) {
|
|
566
|
+
defaultProjection[fieldName] = modelAccessor[fieldName];
|
|
567
|
+
}
|
|
568
|
+
return defaultProjection;
|
|
569
|
+
})();
|
|
570
|
+
|
|
571
|
+
// Build includes AST
|
|
572
|
+
const { includesAst, includesForMeta } = buildIncludeAsts({
|
|
573
|
+
includes: this.includes,
|
|
574
|
+
contract: this.contract,
|
|
575
|
+
context: this.context,
|
|
576
|
+
modelName: this.modelName,
|
|
577
|
+
paramsMap,
|
|
578
|
+
paramDescriptors,
|
|
579
|
+
paramValues,
|
|
580
|
+
paramCodecs,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Build projection state
|
|
584
|
+
const projectionState = buildProjectionState(
|
|
585
|
+
this.table,
|
|
586
|
+
projectionInput,
|
|
587
|
+
includesForMeta.length > 0
|
|
588
|
+
? (includesForMeta as unknown as Parameters<typeof buildProjectionState>[2])
|
|
589
|
+
: undefined,
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
// Build where clause
|
|
593
|
+
const whereResult = this.wherePredicate
|
|
594
|
+
? buildWhereExpr(this.wherePredicate, this.contract, paramsMap, paramDescriptors, paramValues)
|
|
595
|
+
: undefined;
|
|
596
|
+
const whereExpr = whereResult?.expr;
|
|
597
|
+
if (whereResult?.codecId && whereResult.paramName) {
|
|
598
|
+
paramCodecs[whereResult.paramName] = whereResult.codecId;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Build orderBy clause
|
|
602
|
+
const orderByClause = buildOrderByClause(this.orderByExpr);
|
|
603
|
+
|
|
604
|
+
// Build main projection items
|
|
605
|
+
const projectEntries = buildProjectionItems(projectionState, includesForMeta);
|
|
606
|
+
|
|
607
|
+
// Build SELECT AST
|
|
608
|
+
const ast = buildSelectAst({
|
|
609
|
+
table: this.table,
|
|
610
|
+
projectEntries,
|
|
611
|
+
...(includesAst.length > 0 ? { includesAst } : {}),
|
|
612
|
+
...(whereExpr ? { whereExpr } : {}),
|
|
613
|
+
...(orderByClause ? { orderByClause } : {}),
|
|
614
|
+
...(this.limitValue !== undefined ? { limit: this.limitValue } : {}),
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// Build plan metadata
|
|
618
|
+
const planMeta = buildMeta({
|
|
619
|
+
contract: this.contract,
|
|
620
|
+
table: createTableRef(this.table.name),
|
|
621
|
+
projection: projectionState,
|
|
622
|
+
includes: includesForMeta.length > 0 ? includesForMeta : undefined,
|
|
623
|
+
paramDescriptors,
|
|
624
|
+
paramCodecs: Object.keys(paramCodecs).length > 0 ? paramCodecs : undefined,
|
|
625
|
+
where: this.wherePredicate as BinaryBuilder | undefined,
|
|
626
|
+
orderBy: this.orderByExpr,
|
|
627
|
+
} as MetaBuildArgs);
|
|
628
|
+
|
|
629
|
+
// Compile relation filters to EXISTS subqueries and combine with main where clause
|
|
630
|
+
if (this.relationFilters.length > 0) {
|
|
631
|
+
const existsExprs = buildExistsSubqueries(
|
|
632
|
+
this.relationFilters,
|
|
633
|
+
this.contract,
|
|
634
|
+
this.modelName,
|
|
635
|
+
options,
|
|
636
|
+
);
|
|
637
|
+
if (existsExprs.length > 0) {
|
|
638
|
+
const combinedWhere = combineWhereClauses(ast.where, existsExprs);
|
|
639
|
+
const modifiedAst: SelectAst = {
|
|
640
|
+
...ast,
|
|
641
|
+
...(combinedWhere !== undefined ? { where: combinedWhere } : {}),
|
|
642
|
+
};
|
|
643
|
+
return Object.freeze({
|
|
644
|
+
ast: modifiedAst,
|
|
645
|
+
params: paramValues,
|
|
646
|
+
meta: {
|
|
647
|
+
...planMeta,
|
|
648
|
+
lane: 'orm',
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return Object.freeze({
|
|
655
|
+
ast,
|
|
656
|
+
params: paramValues,
|
|
657
|
+
meta: {
|
|
658
|
+
...planMeta,
|
|
659
|
+
lane: 'orm',
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
findFirst(options?: BuildOptions): SqlQueryPlan<Row> {
|
|
665
|
+
const queryPlan = this.take(1).findMany(options);
|
|
666
|
+
return queryPlan;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
findUnique(
|
|
670
|
+
where: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
|
|
671
|
+
options?: BuildOptions,
|
|
672
|
+
): SqlQueryPlan<Row> {
|
|
673
|
+
return this.where(where).take(1).findMany(options);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
create(data: Record<string, unknown>, options?: BuildOptions): SqlQueryPlan<number> {
|
|
677
|
+
const context = createOrmContext(this.context);
|
|
678
|
+
return buildInsertPlan(context, this.modelName, data, options);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
update(
|
|
682
|
+
where: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
|
|
683
|
+
data: Record<string, unknown>,
|
|
684
|
+
options?: BuildOptions,
|
|
685
|
+
): SqlQueryPlan<number> {
|
|
686
|
+
const context = createOrmContext(this.context);
|
|
687
|
+
return buildUpdatePlan<TContract, CodecTypes, ModelName>(
|
|
688
|
+
context,
|
|
689
|
+
this.modelName,
|
|
690
|
+
where,
|
|
691
|
+
() => this._getModelAccessor(),
|
|
692
|
+
data,
|
|
693
|
+
options,
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
delete(
|
|
698
|
+
where: (model: ModelColumnAccessor<TContract, CodecTypes, ModelName>) => AnyBinaryBuilder,
|
|
699
|
+
options?: BuildOptions,
|
|
700
|
+
): SqlQueryPlan<number> {
|
|
701
|
+
const context = createOrmContext(this.context);
|
|
702
|
+
return buildDeletePlan<TContract, CodecTypes, ModelName>(
|
|
703
|
+
context,
|
|
704
|
+
this.modelName,
|
|
705
|
+
where,
|
|
706
|
+
() => this._getModelAccessor(),
|
|
707
|
+
options,
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private _getModelAccessor(): ModelColumnAccessor<TContract, CodecTypes, ModelName> {
|
|
712
|
+
const tableName = this.contract.mappings.modelToTable?.[this.modelName];
|
|
713
|
+
if (!tableName) {
|
|
714
|
+
errorModelNotFound(this.modelName);
|
|
715
|
+
}
|
|
716
|
+
const schemaHandle = schema(this.context);
|
|
717
|
+
const table = schemaHandle.tables[tableName];
|
|
718
|
+
if (!table) {
|
|
719
|
+
errorTableNotFound(tableName);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const accessor: Record<string, AnyColumnBuilder> = {};
|
|
723
|
+
const model = this.contract.models[this.modelName];
|
|
724
|
+
if (!model || typeof model !== 'object' || !('fields' in model)) {
|
|
725
|
+
throw planInvalid(`Model ${this.modelName} does not have fields`);
|
|
726
|
+
}
|
|
727
|
+
const modelFields = model.fields as Record<string, { column?: string }>;
|
|
728
|
+
|
|
729
|
+
for (const fieldName in modelFields) {
|
|
730
|
+
const field = modelFields[fieldName];
|
|
731
|
+
if (!field) continue;
|
|
732
|
+
const columnName =
|
|
733
|
+
this.contract.mappings.fieldToColumn?.[this.modelName]?.[fieldName] ??
|
|
734
|
+
field.column ??
|
|
735
|
+
fieldName;
|
|
736
|
+
const column = table.columns[columnName];
|
|
737
|
+
if (column) {
|
|
738
|
+
accessor[fieldName] = column as AnyColumnBuilder;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return accessor as ModelColumnAccessor<TContract, CodecTypes, ModelName>;
|
|
743
|
+
}
|
|
744
|
+
}
|