metal-orm 1.0.16 → 1.0.18
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 +37 -40
- package/dist/decorators/index.cjs +344 -69
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +344 -69
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +567 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -30
- package/dist/index.d.ts +66 -30
- package/dist/index.js +559 -181
- package/dist/index.js.map +1 -1
- package/dist/{select-BKZrMRCQ.d.cts → select-BuMpVcVt.d.cts} +265 -74
- package/dist/{select-BKZrMRCQ.d.ts → select-BuMpVcVt.d.ts} +265 -74
- package/package.json +5 -1
- package/src/codegen/naming-strategy.ts +15 -10
- package/src/core/ast/aggregate-functions.ts +50 -4
- package/src/core/ast/builders.ts +23 -3
- package/src/core/ast/expression-builders.ts +36 -16
- package/src/core/ast/expression-nodes.ts +17 -9
- package/src/core/ast/join-node.ts +5 -3
- package/src/core/ast/join.ts +16 -16
- package/src/core/ast/query.ts +44 -29
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +18 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +11 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +9 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +9 -0
- package/src/core/ddl/introspect/functions/postgres.ts +2 -6
- package/src/core/dialect/abstract.ts +12 -8
- package/src/core/dialect/base/sql-dialect.ts +58 -46
- package/src/core/dialect/mssql/functions.ts +24 -15
- package/src/core/dialect/mssql/index.ts +53 -28
- package/src/core/dialect/postgres/functions.ts +33 -24
- package/src/core/dialect/sqlite/functions.ts +19 -12
- package/src/core/dialect/sqlite/index.ts +22 -13
- package/src/core/functions/datetime.ts +2 -1
- package/src/core/functions/numeric.ts +2 -1
- package/src/core/functions/standard-strategy.ts +52 -12
- package/src/core/functions/text.ts +2 -1
- package/src/core/functions/types.ts +8 -8
- package/src/index.ts +5 -4
- package/src/orm/domain-event-bus.ts +43 -25
- package/src/orm/entity-meta.ts +40 -0
- package/src/orm/execution-context.ts +6 -0
- package/src/orm/hydration-context.ts +6 -4
- package/src/orm/orm-session.ts +35 -24
- package/src/orm/orm.ts +10 -10
- package/src/orm/query-logger.ts +15 -0
- package/src/orm/runtime-types.ts +60 -2
- package/src/orm/transaction-runner.ts +7 -0
- package/src/orm/unit-of-work.ts +1 -0
- package/src/query-builder/column-selector.ts +9 -7
- package/src/query-builder/insert-query-state.ts +13 -3
- package/src/query-builder/query-ast-service.ts +59 -38
- package/src/query-builder/relation-conditions.ts +38 -34
- package/src/query-builder/relation-manager.ts +8 -3
- package/src/query-builder/relation-service.ts +59 -46
- package/src/query-builder/select-helpers.ts +50 -0
- package/src/query-builder/select-query-state.ts +19 -7
- package/src/query-builder/select.ts +339 -167
- package/src/query-builder/update-query-state.ts +31 -9
- package/src/schema/column.ts +75 -39
- package/src/schema/types.ts +17 -6
|
@@ -6,27 +6,30 @@ import { SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
|
|
|
6
6
|
|
|
7
7
|
import { HydrationPlan } from '../core/hydration/types.js';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
ColumnNode,
|
|
12
|
-
|
|
13
|
-
ExpressionNode,
|
|
14
|
-
|
|
15
|
-
FunctionNode,
|
|
16
|
-
|
|
17
|
-
LiteralNode,
|
|
18
|
-
|
|
19
|
-
BinaryExpressionNode,
|
|
20
|
-
|
|
21
|
-
CaseExpressionNode,
|
|
22
|
-
|
|
23
|
-
WindowFunctionNode,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
import {
|
|
10
|
+
|
|
11
|
+
ColumnNode,
|
|
12
|
+
|
|
13
|
+
ExpressionNode,
|
|
14
|
+
|
|
15
|
+
FunctionNode,
|
|
16
|
+
|
|
17
|
+
LiteralNode,
|
|
18
|
+
|
|
19
|
+
BinaryExpressionNode,
|
|
20
|
+
|
|
21
|
+
CaseExpressionNode,
|
|
22
|
+
|
|
23
|
+
WindowFunctionNode,
|
|
24
|
+
|
|
25
|
+
and,
|
|
26
|
+
|
|
27
|
+
exists,
|
|
28
|
+
|
|
29
|
+
notExists
|
|
30
|
+
|
|
31
|
+
} from '../core/ast/expression.js';
|
|
32
|
+
import { derivedTable } from '../core/ast/builders.js';
|
|
30
33
|
|
|
31
34
|
import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
|
|
32
35
|
|
|
@@ -54,15 +57,17 @@ import {
|
|
|
54
57
|
|
|
55
58
|
import { QueryAstService } from './query-ast-service.js';
|
|
56
59
|
|
|
57
|
-
import { ColumnSelector } from './column-selector.js';
|
|
58
|
-
|
|
59
|
-
import { RelationManager } from './relation-manager.js';
|
|
60
|
-
|
|
61
|
-
import { RelationIncludeOptions } from './relation-types.js';
|
|
60
|
+
import { ColumnSelector } from './column-selector.js';
|
|
61
|
+
|
|
62
|
+
import { RelationManager } from './relation-manager.js';
|
|
63
|
+
|
|
64
|
+
import { RelationIncludeOptions } from './relation-types.js';
|
|
65
|
+
|
|
66
|
+
import type { RelationDef } from '../schema/relation.js';
|
|
67
|
+
|
|
68
|
+
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
|
|
62
69
|
|
|
63
|
-
import {
|
|
64
|
-
|
|
65
|
-
import { Entity, RelationMap } from '../schema/types.js';
|
|
70
|
+
import { Entity, RelationMap, RelationTargetTable } from '../schema/types.js';
|
|
66
71
|
|
|
67
72
|
import { OrmSession } from '../orm/orm-session.ts';
|
|
68
73
|
|
|
@@ -72,11 +77,29 @@ import { HydrationContext } from '../orm/hydration-context.js';
|
|
|
72
77
|
|
|
73
78
|
import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
|
|
74
79
|
|
|
75
|
-
import { createJoinNode } from '../core/ast/join-node.js';
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
import { createJoinNode } from '../core/ast/join-node.js';
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
|
|
84
|
+
|
|
85
|
+
type DeepSelectConfig<TTable extends TableDef> = {
|
|
86
|
+
root?: (keyof TTable['columns'] & string)[];
|
|
87
|
+
} & {
|
|
88
|
+
[K in keyof TTable['relations'] & string]?: (
|
|
89
|
+
keyof RelationTargetTable<TTable['relations'][K]>['columns'] & string
|
|
90
|
+
)[];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type WhereHasOptions = {
|
|
94
|
+
correlate?: ExpressionNode;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type RelationCallback = <TChildTable extends TableDef>(
|
|
98
|
+
qb: SelectQueryBuilder<any, TChildTable>
|
|
99
|
+
) => SelectQueryBuilder<any, TChildTable>;
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
/**
|
|
80
103
|
|
|
81
104
|
* Main query builder class for constructing SQL SELECT queries
|
|
82
105
|
|
|
@@ -154,29 +177,52 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
154
177
|
|
|
155
178
|
|
|
156
179
|
|
|
157
|
-
private clone(
|
|
158
|
-
|
|
159
|
-
context: SelectQueryBuilderContext = this.context,
|
|
160
|
-
|
|
161
|
-
lazyRelations = new Set(this.lazyRelations)
|
|
162
|
-
|
|
163
|
-
): SelectQueryBuilder<T, TTable> {
|
|
164
|
-
|
|
165
|
-
return new SelectQueryBuilder(this.env.table as TTable, context.state, context.hydration, this.env.deps, lazyRelations);
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
private clone(
|
|
181
|
+
|
|
182
|
+
context: SelectQueryBuilderContext = this.context,
|
|
183
|
+
|
|
184
|
+
lazyRelations = new Set(this.lazyRelations)
|
|
185
|
+
|
|
186
|
+
): SelectQueryBuilder<T, TTable> {
|
|
187
|
+
|
|
188
|
+
return new SelectQueryBuilder(this.env.table as TTable, context.state, context.hydration, this.env.deps, lazyRelations);
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Applies an alias to the root FROM table.
|
|
194
|
+
* @param alias - Alias to apply
|
|
195
|
+
*/
|
|
196
|
+
as(alias: string): SelectQueryBuilder<T, TTable> {
|
|
197
|
+
const from = this.context.state.ast.from;
|
|
198
|
+
if (from.type !== 'Table') {
|
|
199
|
+
throw new Error('Cannot alias non-table FROM sources');
|
|
200
|
+
}
|
|
201
|
+
const nextFrom = { ...from, alias };
|
|
202
|
+
const nextContext = this.applyAst(this.context, service => service.withFrom(nextFrom));
|
|
203
|
+
return this.clone(nextContext);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
private resolveQueryNode(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryNode {
|
|
209
|
+
|
|
210
|
+
return typeof (query as any).getAST === 'function'
|
|
211
|
+
|
|
212
|
+
? (query as SelectQueryBuilder<any, TableDef<any>>).getAST()
|
|
213
|
+
|
|
214
|
+
: (query as SelectQueryNode);
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private applyCorrelation(ast: SelectQueryNode, correlation?: ExpressionNode): SelectQueryNode {
|
|
219
|
+
if (!correlation) return ast;
|
|
220
|
+
const combinedWhere = ast.where ? and(correlation, ast.where) : correlation;
|
|
221
|
+
return {
|
|
222
|
+
...ast,
|
|
223
|
+
where: combinedWhere
|
|
224
|
+
};
|
|
225
|
+
}
|
|
180
226
|
|
|
181
227
|
|
|
182
228
|
|
|
@@ -252,12 +298,31 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
252
298
|
|
|
253
299
|
*/
|
|
254
300
|
|
|
255
|
-
select(columns: Record<string,
|
|
256
|
-
|
|
257
|
-
return this.clone(this.columnSelector.select(this.context, columns));
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
301
|
+
select(columns: Record<string, ColumnSelectionValue>): SelectQueryBuilder<T, TTable> {
|
|
302
|
+
|
|
303
|
+
return this.clone(this.columnSelector.select(this.context, columns));
|
|
304
|
+
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Selects columns from the root table by name (typed).
|
|
310
|
+
* @param cols - Column names on the root table
|
|
311
|
+
*/
|
|
312
|
+
selectColumns<K extends keyof TTable['columns'] & string>(...cols: K[]): SelectQueryBuilder<T, TTable> {
|
|
313
|
+
const selection: Record<string, ColumnDef> = {};
|
|
314
|
+
|
|
315
|
+
for (const key of cols) {
|
|
316
|
+
const col = this.env.table.columns[key];
|
|
317
|
+
if (!col) {
|
|
318
|
+
throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
|
|
319
|
+
}
|
|
320
|
+
selection[key] = col;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return this.select(selection);
|
|
324
|
+
}
|
|
325
|
+
|
|
261
326
|
|
|
262
327
|
|
|
263
328
|
/**
|
|
@@ -318,15 +383,34 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
318
383
|
|
|
319
384
|
*/
|
|
320
385
|
|
|
321
|
-
withRecursive(name: string, query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
322
|
-
|
|
323
|
-
const subAst = this.resolveQueryNode(query);
|
|
324
|
-
|
|
325
|
-
const nextContext = this.applyAst(this.context, service => service.withCte(name, subAst, columns, true));
|
|
326
|
-
|
|
327
|
-
return this.clone(nextContext);
|
|
328
|
-
|
|
329
|
-
}
|
|
386
|
+
withRecursive(name: string, query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
387
|
+
|
|
388
|
+
const subAst = this.resolveQueryNode(query);
|
|
389
|
+
|
|
390
|
+
const nextContext = this.applyAst(this.context, service => service.withCte(name, subAst, columns, true));
|
|
391
|
+
|
|
392
|
+
return this.clone(nextContext);
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Replaces the FROM clause with a derived table (subquery with alias)
|
|
399
|
+
* @param subquery - Subquery to use as the FROM source
|
|
400
|
+
* @param alias - Alias for the derived table
|
|
401
|
+
* @param columnAliases - Optional column alias list
|
|
402
|
+
* @returns New query builder instance with updated FROM
|
|
403
|
+
*/
|
|
404
|
+
fromSubquery(
|
|
405
|
+
subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode,
|
|
406
|
+
alias: string,
|
|
407
|
+
columnAliases?: string[]
|
|
408
|
+
): SelectQueryBuilder<T, TTable> {
|
|
409
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
410
|
+
const fromNode = derivedTable(subAst, alias, columnAliases);
|
|
411
|
+
const nextContext = this.applyAst(this.context, service => service.withFrom(fromNode));
|
|
412
|
+
return this.clone(nextContext);
|
|
413
|
+
}
|
|
330
414
|
|
|
331
415
|
|
|
332
416
|
|
|
@@ -342,14 +426,37 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
342
426
|
|
|
343
427
|
*/
|
|
344
428
|
|
|
345
|
-
selectSubquery(alias: string, sub: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
346
|
-
|
|
347
|
-
const query = this.resolveQueryNode(sub);
|
|
348
|
-
|
|
349
|
-
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
429
|
+
selectSubquery(alias: string, sub: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
430
|
+
|
|
431
|
+
const query = this.resolveQueryNode(sub);
|
|
432
|
+
|
|
433
|
+
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
434
|
+
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Adds a JOIN against a derived table (subquery with alias)
|
|
440
|
+
* @param subquery - Subquery to join
|
|
441
|
+
* @param alias - Alias for the derived table
|
|
442
|
+
* @param condition - Join condition expression
|
|
443
|
+
* @param joinKind - Join kind (defaults to INNER)
|
|
444
|
+
* @param columnAliases - Optional column alias list for the derived table
|
|
445
|
+
* @returns New query builder instance with the derived-table join
|
|
446
|
+
*/
|
|
447
|
+
joinSubquery(
|
|
448
|
+
subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode,
|
|
449
|
+
alias: string,
|
|
450
|
+
condition: BinaryExpressionNode,
|
|
451
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
452
|
+
columnAliases?: string[]
|
|
453
|
+
): SelectQueryBuilder<T, TTable> {
|
|
454
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
455
|
+
const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
|
|
456
|
+
const nextContext = this.applyAst(this.context, service => service.withJoin(joinNode));
|
|
457
|
+
return this.clone(nextContext);
|
|
458
|
+
}
|
|
459
|
+
|
|
353
460
|
|
|
354
461
|
|
|
355
462
|
/**
|
|
@@ -494,16 +601,77 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
494
601
|
|
|
495
602
|
|
|
496
603
|
|
|
497
|
-
includeLazy<K extends keyof RelationMap<TTable>>(relationName: K): SelectQueryBuilder<T, TTable> {
|
|
498
|
-
|
|
499
|
-
const nextLazy = new Set(this.lazyRelations);
|
|
500
|
-
|
|
501
|
-
nextLazy.add(relationName as string);
|
|
502
|
-
|
|
503
|
-
return this.clone(this.context, nextLazy);
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
|
|
604
|
+
includeLazy<K extends keyof RelationMap<TTable>>(relationName: K): SelectQueryBuilder<T, TTable> {
|
|
605
|
+
|
|
606
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
607
|
+
|
|
608
|
+
nextLazy.add(relationName as string);
|
|
609
|
+
|
|
610
|
+
return this.clone(this.context, nextLazy);
|
|
611
|
+
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Selects columns for a related table in a single hop.
|
|
616
|
+
*/
|
|
617
|
+
selectRelationColumns<
|
|
618
|
+
K extends keyof TTable['relations'] & string,
|
|
619
|
+
TRel extends RelationDef = TTable['relations'][K],
|
|
620
|
+
TTarget extends TableDef = RelationTargetTable<TRel>,
|
|
621
|
+
C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
|
|
622
|
+
>(relationName: K, ...cols: C[]): SelectQueryBuilder<T, TTable> {
|
|
623
|
+
const relation = this.env.table.relations[relationName] as RelationDef | undefined;
|
|
624
|
+
if (!relation) {
|
|
625
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
626
|
+
}
|
|
627
|
+
const target = relation.target;
|
|
628
|
+
|
|
629
|
+
for (const col of cols) {
|
|
630
|
+
if (!target.columns[col]) {
|
|
631
|
+
throw new Error(
|
|
632
|
+
`Column '${col}' not found on related table '${target.name}' for relation '${relationName}'`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return this.include(relationName as string, { columns: cols as string[] });
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Convenience alias for selecting specific columns from a relation.
|
|
643
|
+
*/
|
|
644
|
+
includePick<
|
|
645
|
+
K extends keyof TTable['relations'] & string,
|
|
646
|
+
TRel extends RelationDef = TTable['relations'][K],
|
|
647
|
+
TTarget extends TableDef = RelationTargetTable<TRel>,
|
|
648
|
+
C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
|
|
649
|
+
>(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
|
|
650
|
+
return this.selectRelationColumns(relationName, ...cols);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Selects columns for the root table and relations from a single config object.
|
|
656
|
+
*/
|
|
657
|
+
selectColumnsDeep(config: DeepSelectConfig<TTable>): SelectQueryBuilder<T, TTable> {
|
|
658
|
+
let qb: SelectQueryBuilder<T, TTable> = this;
|
|
659
|
+
|
|
660
|
+
if (config.root?.length) {
|
|
661
|
+
qb = qb.selectColumns(...config.root);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
for (const key of Object.keys(config) as (keyof typeof config)[]) {
|
|
665
|
+
if (key === 'root') continue;
|
|
666
|
+
const relName = key as keyof TTable['relations'] & string;
|
|
667
|
+
const cols = config[relName as keyof DeepSelectConfig<TTable>] as string[] | undefined;
|
|
668
|
+
if (!cols || !cols.length) continue;
|
|
669
|
+
qb = qb.selectRelationColumns(relName, ...(cols as string[]));
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return qb;
|
|
673
|
+
}
|
|
674
|
+
|
|
507
675
|
|
|
508
676
|
|
|
509
677
|
getLazyRelations(): (keyof RelationMap<TTable>)[] {
|
|
@@ -760,13 +928,14 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
760
928
|
|
|
761
929
|
*/
|
|
762
930
|
|
|
763
|
-
whereExists(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
931
|
+
whereExists(
|
|
932
|
+
subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode,
|
|
933
|
+
correlate?: ExpressionNode
|
|
934
|
+
): SelectQueryBuilder<T, TTable> {
|
|
935
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
936
|
+
const correlated = this.applyCorrelation(subAst, correlate);
|
|
937
|
+
return this.where(exists(correlated));
|
|
938
|
+
}
|
|
770
939
|
|
|
771
940
|
|
|
772
941
|
|
|
@@ -780,13 +949,14 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
780
949
|
|
|
781
950
|
*/
|
|
782
951
|
|
|
783
|
-
whereNotExists(
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
952
|
+
whereNotExists(
|
|
953
|
+
subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode,
|
|
954
|
+
correlate?: ExpressionNode
|
|
955
|
+
): SelectQueryBuilder<T, TTable> {
|
|
956
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
957
|
+
const correlated = this.applyCorrelation(subAst, correlate);
|
|
958
|
+
return this.where(notExists(correlated));
|
|
959
|
+
}
|
|
790
960
|
|
|
791
961
|
|
|
792
962
|
|
|
@@ -802,45 +972,46 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
802
972
|
|
|
803
973
|
*/
|
|
804
974
|
|
|
805
|
-
whereHas(
|
|
806
|
-
|
|
807
|
-
relationName: string,
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
if (!relation) {
|
|
820
|
-
|
|
975
|
+
whereHas(
|
|
976
|
+
|
|
977
|
+
relationName: string,
|
|
978
|
+
|
|
979
|
+
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
980
|
+
|
|
981
|
+
maybeOptions?: WhereHasOptions
|
|
982
|
+
|
|
983
|
+
): SelectQueryBuilder<T, TTable> {
|
|
984
|
+
|
|
985
|
+
const relation = this.env.table.relations[relationName];
|
|
986
|
+
|
|
987
|
+
if (!relation) {
|
|
988
|
+
|
|
821
989
|
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
822
990
|
|
|
823
991
|
}
|
|
824
992
|
|
|
825
993
|
|
|
826
994
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
995
|
+
const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions as RelationCallback : undefined;
|
|
996
|
+
const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as WhereHasOptions | undefined;
|
|
997
|
+
|
|
998
|
+
let subQb = this.createChildBuilder<any, typeof relation.target>(relation.target);
|
|
999
|
+
|
|
1000
|
+
if (callback) {
|
|
1001
|
+
|
|
1002
|
+
subQb = callback(subQb);
|
|
1003
|
+
|
|
1004
|
+
}
|
|
834
1005
|
|
|
835
1006
|
|
|
836
1007
|
|
|
837
|
-
const subAst = subQb.getAST();
|
|
838
|
-
|
|
839
|
-
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
|
|
840
|
-
|
|
841
|
-
return this.where(exists(finalSubAst));
|
|
842
|
-
|
|
843
|
-
}
|
|
1008
|
+
const subAst = subQb.getAST();
|
|
1009
|
+
|
|
1010
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
|
|
1011
|
+
|
|
1012
|
+
return this.where(exists(finalSubAst));
|
|
1013
|
+
|
|
1014
|
+
}
|
|
844
1015
|
|
|
845
1016
|
|
|
846
1017
|
|
|
@@ -856,45 +1027,46 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
856
1027
|
|
|
857
1028
|
*/
|
|
858
1029
|
|
|
859
|
-
whereHasNot(
|
|
860
|
-
|
|
861
|
-
relationName: string,
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if (!relation) {
|
|
874
|
-
|
|
1030
|
+
whereHasNot(
|
|
1031
|
+
|
|
1032
|
+
relationName: string,
|
|
1033
|
+
|
|
1034
|
+
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
1035
|
+
|
|
1036
|
+
maybeOptions?: WhereHasOptions
|
|
1037
|
+
|
|
1038
|
+
): SelectQueryBuilder<T, TTable> {
|
|
1039
|
+
|
|
1040
|
+
const relation = this.env.table.relations[relationName];
|
|
1041
|
+
|
|
1042
|
+
if (!relation) {
|
|
1043
|
+
|
|
875
1044
|
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
876
1045
|
|
|
877
1046
|
}
|
|
878
1047
|
|
|
879
1048
|
|
|
880
1049
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1050
|
+
const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions as RelationCallback : undefined;
|
|
1051
|
+
const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as WhereHasOptions | undefined;
|
|
1052
|
+
|
|
1053
|
+
let subQb = this.createChildBuilder<any, typeof relation.target>(relation.target);
|
|
1054
|
+
|
|
1055
|
+
if (callback) {
|
|
1056
|
+
|
|
1057
|
+
subQb = callback(subQb);
|
|
1058
|
+
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
const subAst = subQb.getAST();
|
|
1064
|
+
|
|
1065
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
|
|
1066
|
+
|
|
1067
|
+
return this.where(notExists(finalSubAst));
|
|
1068
|
+
|
|
1069
|
+
}
|
|
898
1070
|
|
|
899
1071
|
|
|
900
1072
|
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { ColumnNode, ExpressionNode, valueToOperand } from '../core/ast/expression.js';
|
|
2
|
+
import { ColumnNode, ExpressionNode, OperandNode, isOperandNode, valueToOperand } from '../core/ast/expression.js';
|
|
3
3
|
import { TableNode, UpdateQueryNode, UpdateAssignmentNode } from '../core/ast/query.js';
|
|
4
4
|
import { createTableNode } from '../core/ast/builders.js';
|
|
5
|
+
type LiteralValue = string | number | boolean | null;
|
|
6
|
+
type UpdateValue = OperandNode | LiteralValue;
|
|
7
|
+
|
|
8
|
+
const isUpdateValue = (value: unknown): value is UpdateValue => {
|
|
9
|
+
if (value === null) return true;
|
|
10
|
+
switch (typeof value) {
|
|
11
|
+
case 'string':
|
|
12
|
+
case 'number':
|
|
13
|
+
case 'boolean':
|
|
14
|
+
return true;
|
|
15
|
+
default:
|
|
16
|
+
return isOperandNode(value);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
5
19
|
|
|
6
20
|
/**
|
|
7
21
|
* Immutable state for UPDATE queries
|
|
@@ -24,14 +38,22 @@ export class UpdateQueryState {
|
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
withSet(values: Record<string, unknown>): UpdateQueryState {
|
|
27
|
-
const assignments: UpdateAssignmentNode[] = Object.entries(values).map(([column,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
const assignments: UpdateAssignmentNode[] = Object.entries(values).map(([column, rawValue]) => {
|
|
42
|
+
if (!isUpdateValue(rawValue)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Invalid update value for column "${column}": only primitives, null, or OperandNodes are allowed`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
column: {
|
|
50
|
+
type: 'Column',
|
|
51
|
+
table: this.table.name,
|
|
52
|
+
name: column
|
|
53
|
+
},
|
|
54
|
+
value: valueToOperand(rawValue)
|
|
55
|
+
};
|
|
56
|
+
});
|
|
35
57
|
|
|
36
58
|
return this.clone({
|
|
37
59
|
...this.ast,
|