metal-orm 1.0.5 → 1.0.7
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 +299 -113
- package/docs/CHANGES.md +104 -0
- package/docs/advanced-features.md +92 -1
- package/docs/api-reference.md +13 -4
- package/docs/dml-operations.md +156 -0
- package/docs/getting-started.md +122 -55
- package/docs/hydration.md +78 -13
- package/docs/index.md +19 -14
- package/docs/multi-dialect-support.md +25 -0
- package/docs/query-builder.md +60 -0
- package/docs/runtime.md +105 -0
- package/docs/schema-definition.md +52 -1
- package/package.json +1 -1
- package/src/ast/expression.ts +38 -18
- package/src/builder/hydration-planner.ts +74 -74
- package/src/builder/select.ts +427 -395
- package/src/constants/sql-operator-config.ts +3 -0
- package/src/constants/sql.ts +38 -32
- package/src/index.ts +16 -8
- package/src/playground/features/playground/data/scenarios/types.ts +18 -15
- package/src/playground/features/playground/data/schema.ts +10 -10
- package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
- package/src/runtime/entity-meta.ts +52 -0
- package/src/runtime/entity.ts +252 -0
- package/src/runtime/execute.ts +36 -0
- package/src/runtime/hydration.ts +99 -49
- package/src/runtime/lazy-batch.ts +205 -0
- package/src/runtime/orm-context.ts +539 -0
- package/src/runtime/relations/belongs-to.ts +92 -0
- package/src/runtime/relations/has-many.ts +111 -0
- package/src/runtime/relations/many-to-many.ts +149 -0
- package/src/schema/column.ts +15 -1
- package/src/schema/relation.ts +82 -58
- package/src/schema/table.ts +34 -22
- package/src/schema/types.ts +76 -0
- package/tests/orm-runtime.test.ts +254 -0
package/src/builder/select.ts
CHANGED
|
@@ -1,404 +1,436 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table';
|
|
2
|
-
import { ColumnDef } from '../schema/column';
|
|
3
|
-
import { SelectQueryNode, HydrationPlan } from '../ast/query';
|
|
4
|
-
import {
|
|
5
|
-
ColumnNode,
|
|
6
|
-
ExpressionNode,
|
|
7
|
-
FunctionNode,
|
|
8
|
-
LiteralNode,
|
|
9
|
-
BinaryExpressionNode,
|
|
10
|
-
CaseExpressionNode,
|
|
11
|
-
WindowFunctionNode,
|
|
12
|
-
exists,
|
|
13
|
-
notExists
|
|
14
|
-
} from '../ast/expression';
|
|
15
|
-
import { CompiledQuery, Dialect } from '../dialect/abstract';
|
|
16
|
-
import { SelectQueryState } from './select-query-state';
|
|
17
|
-
import { HydrationManager } from './hydration-manager';
|
|
18
|
-
import {
|
|
19
|
-
defaultSelectQueryBuilderDependencies,
|
|
20
|
-
SelectQueryBuilderContext,
|
|
21
|
-
SelectQueryBuilderDependencies,
|
|
22
|
-
SelectQueryBuilderEnvironment
|
|
23
|
-
} from './select-query-builder-deps';
|
|
24
|
-
import { ColumnSelector } from './operations/column-selector';
|
|
25
|
-
import { CteManager } from './operations/cte-manager';
|
|
26
|
-
import { JoinManager } from './operations/join-manager';
|
|
27
|
-
import { FilterManager } from './operations/filter-manager';
|
|
28
|
-
import { PaginationManager } from './operations/pagination-manager';
|
|
29
|
-
import { RelationManager } from './operations/relation-manager';
|
|
30
|
-
import { RelationIncludeOptions } from './relation-types';
|
|
31
|
-
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../constants/sql';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
private readonly
|
|
43
|
-
private readonly
|
|
44
|
-
private readonly
|
|
45
|
-
private readonly
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { TableDef } from '../schema/table';
|
|
2
|
+
import { ColumnDef } from '../schema/column';
|
|
3
|
+
import { SelectQueryNode, HydrationPlan } from '../ast/query';
|
|
4
|
+
import {
|
|
5
|
+
ColumnNode,
|
|
6
|
+
ExpressionNode,
|
|
7
|
+
FunctionNode,
|
|
8
|
+
LiteralNode,
|
|
9
|
+
BinaryExpressionNode,
|
|
10
|
+
CaseExpressionNode,
|
|
11
|
+
WindowFunctionNode,
|
|
12
|
+
exists,
|
|
13
|
+
notExists
|
|
14
|
+
} from '../ast/expression';
|
|
15
|
+
import { CompiledQuery, Dialect } from '../dialect/abstract';
|
|
16
|
+
import { SelectQueryState } from './select-query-state';
|
|
17
|
+
import { HydrationManager } from './hydration-manager';
|
|
18
|
+
import {
|
|
19
|
+
defaultSelectQueryBuilderDependencies,
|
|
20
|
+
SelectQueryBuilderContext,
|
|
21
|
+
SelectQueryBuilderDependencies,
|
|
22
|
+
SelectQueryBuilderEnvironment
|
|
23
|
+
} from './select-query-builder-deps';
|
|
24
|
+
import { ColumnSelector } from './operations/column-selector';
|
|
25
|
+
import { CteManager } from './operations/cte-manager';
|
|
26
|
+
import { JoinManager } from './operations/join-manager';
|
|
27
|
+
import { FilterManager } from './operations/filter-manager';
|
|
28
|
+
import { PaginationManager } from './operations/pagination-manager';
|
|
29
|
+
import { RelationManager } from './operations/relation-manager';
|
|
30
|
+
import { RelationIncludeOptions } from './relation-types';
|
|
31
|
+
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../constants/sql';
|
|
32
|
+
import { Entity, RelationMap } from '../schema/types';
|
|
33
|
+
import { OrmContext } from '../runtime/orm-context';
|
|
34
|
+
import { executeHydrated } from '../runtime/execute';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Main query builder class for constructing SQL SELECT queries
|
|
38
|
+
* @typeParam T - Result type for projections (unused)
|
|
39
|
+
* @typeParam TTable - Table definition being queried
|
|
40
|
+
*/
|
|
41
|
+
export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
42
|
+
private readonly env: SelectQueryBuilderEnvironment;
|
|
43
|
+
private readonly context: SelectQueryBuilderContext;
|
|
44
|
+
private readonly columnSelector: ColumnSelector;
|
|
45
|
+
private readonly cteManager: CteManager;
|
|
46
|
+
private readonly joinManager: JoinManager;
|
|
47
|
+
private readonly filterManager: FilterManager;
|
|
48
|
+
private readonly paginationManager: PaginationManager;
|
|
49
|
+
private readonly relationManager: RelationManager;
|
|
50
|
+
private readonly lazyRelations: Set<string>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates a new SelectQueryBuilder instance
|
|
54
|
+
* @param table - Table definition to query
|
|
55
|
+
* @param state - Optional initial query state
|
|
56
|
+
* @param hydration - Optional hydration manager
|
|
57
|
+
* @param dependencies - Optional query builder dependencies
|
|
58
|
+
*/
|
|
59
|
+
constructor(
|
|
60
|
+
table: TTable,
|
|
61
|
+
state?: SelectQueryState,
|
|
62
|
+
hydration?: HydrationManager,
|
|
63
|
+
dependencies?: SelectQueryBuilderDependencies,
|
|
64
|
+
lazyRelations?: Set<string>
|
|
65
|
+
) {
|
|
66
|
+
const deps = dependencies ?? defaultSelectQueryBuilderDependencies;
|
|
67
|
+
this.env = { table, deps };
|
|
68
|
+
const initialState = state ?? deps.createState(table);
|
|
69
|
+
const initialHydration = hydration ?? deps.createHydration(table);
|
|
70
|
+
this.context = {
|
|
71
|
+
state: initialState,
|
|
72
|
+
hydration: initialHydration
|
|
73
|
+
};
|
|
74
|
+
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
75
|
+
this.columnSelector = new ColumnSelector(this.env);
|
|
76
|
+
this.cteManager = new CteManager(this.env);
|
|
77
|
+
this.joinManager = new JoinManager(this.env);
|
|
78
|
+
this.filterManager = new FilterManager(this.env);
|
|
79
|
+
this.paginationManager = new PaginationManager(this.env);
|
|
80
|
+
this.relationManager = new RelationManager(this.env);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private clone(
|
|
84
|
+
context: SelectQueryBuilderContext = this.context,
|
|
85
|
+
lazyRelations = new Set(this.lazyRelations)
|
|
86
|
+
): SelectQueryBuilder<T, TTable> {
|
|
87
|
+
return new SelectQueryBuilder(this.env.table as TTable, context.state, context.hydration, this.env.deps, lazyRelations);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private resolveQueryNode(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryNode {
|
|
81
91
|
return typeof (query as any).getAST === 'function'
|
|
82
|
-
? (query as SelectQueryBuilder<any
|
|
92
|
+
? (query as SelectQueryBuilder<any, TableDef<any>>).getAST()
|
|
83
93
|
: (query as SelectQueryNode);
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
private createChildBuilder<R>(table:
|
|
95
|
+
|
|
96
|
+
private createChildBuilder<R, TChild extends TableDef>(table: TChild): SelectQueryBuilder<R, TChild> {
|
|
87
97
|
return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
|
|
88
98
|
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Selects specific columns for the query
|
|
92
|
-
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
93
|
-
* @returns New query builder instance with selected columns
|
|
94
|
-
*/
|
|
95
|
-
select(columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>): SelectQueryBuilder<T> {
|
|
96
|
-
return this.clone(this.columnSelector.select(this.context, columns));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Selects raw column expressions
|
|
101
|
-
* @param cols - Column expressions as strings
|
|
102
|
-
* @returns New query builder instance with raw column selections
|
|
103
|
-
*/
|
|
104
|
-
selectRaw(...cols: string[]): SelectQueryBuilder<T> {
|
|
105
|
-
return this.clone(this.columnSelector.selectRaw(this.context, cols));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Adds a Common Table Expression (CTE) to the query
|
|
110
|
-
* @param name - Name of the CTE
|
|
111
|
-
* @param query - Query builder or query node for the CTE
|
|
112
|
-
* @param columns - Optional column names for the CTE
|
|
113
|
-
* @returns New query builder instance with the CTE
|
|
114
|
-
*/
|
|
115
|
-
with(name: string, query: SelectQueryBuilder<any
|
|
116
|
-
const subAst = this.resolveQueryNode(query);
|
|
117
|
-
const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, false);
|
|
118
|
-
return this.clone(nextContext);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Adds a recursive Common Table Expression (CTE) to the query
|
|
123
|
-
* @param name - Name of the CTE
|
|
124
|
-
* @param query - Query builder or query node for the CTE
|
|
125
|
-
* @param columns - Optional column names for the CTE
|
|
126
|
-
* @returns New query builder instance with the recursive CTE
|
|
127
|
-
*/
|
|
128
|
-
withRecursive(name: string, query: SelectQueryBuilder<any
|
|
129
|
-
const subAst = this.resolveQueryNode(query);
|
|
130
|
-
const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, true);
|
|
131
|
-
return this.clone(nextContext);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Selects a subquery as a column
|
|
136
|
-
* @param alias - Alias for the subquery column
|
|
137
|
-
* @param sub - Query builder or query node for the subquery
|
|
138
|
-
* @returns New query builder instance with the subquery selection
|
|
139
|
-
*/
|
|
140
|
-
selectSubquery(alias: string, sub: SelectQueryBuilder<any
|
|
141
|
-
const query = this.resolveQueryNode(sub);
|
|
142
|
-
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Adds an INNER JOIN to the query
|
|
147
|
-
* @param table - Table to join
|
|
148
|
-
* @param condition - Join condition expression
|
|
149
|
-
* @returns New query builder instance with the INNER JOIN
|
|
150
|
-
*/
|
|
151
|
-
innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T> {
|
|
152
|
-
const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.INNER);
|
|
153
|
-
return this.clone(nextContext);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Adds a LEFT JOIN to the query
|
|
158
|
-
* @param table - Table to join
|
|
159
|
-
* @param condition - Join condition expression
|
|
160
|
-
* @returns New query builder instance with the LEFT JOIN
|
|
161
|
-
*/
|
|
162
|
-
leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T> {
|
|
163
|
-
const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
164
|
-
return this.clone(nextContext);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Adds a RIGHT JOIN to the query
|
|
169
|
-
* @param table - Table to join
|
|
170
|
-
* @param condition - Join condition expression
|
|
171
|
-
* @returns New query builder instance with the RIGHT JOIN
|
|
172
|
-
*/
|
|
173
|
-
rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T> {
|
|
174
|
-
const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
175
|
-
return this.clone(nextContext);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Matches records based on a relationship
|
|
180
|
-
* @param relationName - Name of the relationship to match
|
|
181
|
-
* @param predicate - Optional predicate expression
|
|
182
|
-
* @returns New query builder instance with the relationship match
|
|
183
|
-
*/
|
|
184
|
-
match(relationName: string, predicate?: ExpressionNode): SelectQueryBuilder<T> {
|
|
185
|
-
const nextContext = this.relationManager.match(this.context, relationName, predicate);
|
|
186
|
-
return this.clone(nextContext);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Joins a related table
|
|
191
|
-
* @param relationName - Name of the relationship to join
|
|
192
|
-
* @param joinKind - Type of join (defaults to INNER)
|
|
193
|
-
* @param extraCondition - Optional additional join condition
|
|
194
|
-
* @returns New query builder instance with the relationship join
|
|
195
|
-
*/
|
|
196
|
-
joinRelation(
|
|
197
|
-
relationName: string,
|
|
198
|
-
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
199
|
-
extraCondition?: ExpressionNode
|
|
200
|
-
): SelectQueryBuilder<T> {
|
|
201
|
-
const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
202
|
-
return this.clone(nextContext);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Includes related data in the query results
|
|
207
|
-
* @param relationName - Name of the relationship to include
|
|
208
|
-
* @param options - Optional include options
|
|
209
|
-
* @returns New query builder instance with the relationship inclusion
|
|
210
|
-
*/
|
|
211
|
-
include(relationName: string, options?: RelationIncludeOptions): SelectQueryBuilder<T> {
|
|
212
|
-
const nextContext = this.relationManager.include(this.context, relationName, options);
|
|
213
|
-
return this.clone(nextContext);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return this.
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
*
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
*
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
*
|
|
268
|
-
* @
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
278
|
-
* @
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
*
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
*
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
*
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Selects specific columns for the query
|
|
102
|
+
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
103
|
+
* @returns New query builder instance with selected columns
|
|
104
|
+
*/
|
|
105
|
+
select(columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>): SelectQueryBuilder<T, TTable> {
|
|
106
|
+
return this.clone(this.columnSelector.select(this.context, columns));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Selects raw column expressions
|
|
111
|
+
* @param cols - Column expressions as strings
|
|
112
|
+
* @returns New query builder instance with raw column selections
|
|
113
|
+
*/
|
|
114
|
+
selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
|
|
115
|
+
return this.clone(this.columnSelector.selectRaw(this.context, cols));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
120
|
+
* @param name - Name of the CTE
|
|
121
|
+
* @param query - Query builder or query node for the CTE
|
|
122
|
+
* @param columns - Optional column names for the CTE
|
|
123
|
+
* @returns New query builder instance with the CTE
|
|
124
|
+
*/
|
|
125
|
+
with(name: string, query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
126
|
+
const subAst = this.resolveQueryNode(query);
|
|
127
|
+
const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, false);
|
|
128
|
+
return this.clone(nextContext);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Adds a recursive Common Table Expression (CTE) to the query
|
|
133
|
+
* @param name - Name of the CTE
|
|
134
|
+
* @param query - Query builder or query node for the CTE
|
|
135
|
+
* @param columns - Optional column names for the CTE
|
|
136
|
+
* @returns New query builder instance with the recursive CTE
|
|
137
|
+
*/
|
|
138
|
+
withRecursive(name: string, query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
139
|
+
const subAst = this.resolveQueryNode(query);
|
|
140
|
+
const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, true);
|
|
141
|
+
return this.clone(nextContext);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Selects a subquery as a column
|
|
146
|
+
* @param alias - Alias for the subquery column
|
|
147
|
+
* @param sub - Query builder or query node for the subquery
|
|
148
|
+
* @returns New query builder instance with the subquery selection
|
|
149
|
+
*/
|
|
150
|
+
selectSubquery(alias: string, sub: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
151
|
+
const query = this.resolveQueryNode(sub);
|
|
152
|
+
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Adds an INNER JOIN to the query
|
|
157
|
+
* @param table - Table to join
|
|
158
|
+
* @param condition - Join condition expression
|
|
159
|
+
* @returns New query builder instance with the INNER JOIN
|
|
160
|
+
*/
|
|
161
|
+
innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
162
|
+
const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.INNER);
|
|
163
|
+
return this.clone(nextContext);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Adds a LEFT JOIN to the query
|
|
168
|
+
* @param table - Table to join
|
|
169
|
+
* @param condition - Join condition expression
|
|
170
|
+
* @returns New query builder instance with the LEFT JOIN
|
|
171
|
+
*/
|
|
172
|
+
leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
173
|
+
const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
174
|
+
return this.clone(nextContext);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Adds a RIGHT JOIN to the query
|
|
179
|
+
* @param table - Table to join
|
|
180
|
+
* @param condition - Join condition expression
|
|
181
|
+
* @returns New query builder instance with the RIGHT JOIN
|
|
182
|
+
*/
|
|
183
|
+
rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
184
|
+
const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
185
|
+
return this.clone(nextContext);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Matches records based on a relationship
|
|
190
|
+
* @param relationName - Name of the relationship to match
|
|
191
|
+
* @param predicate - Optional predicate expression
|
|
192
|
+
* @returns New query builder instance with the relationship match
|
|
193
|
+
*/
|
|
194
|
+
match(relationName: string, predicate?: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
195
|
+
const nextContext = this.relationManager.match(this.context, relationName, predicate);
|
|
196
|
+
return this.clone(nextContext);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Joins a related table
|
|
201
|
+
* @param relationName - Name of the relationship to join
|
|
202
|
+
* @param joinKind - Type of join (defaults to INNER)
|
|
203
|
+
* @param extraCondition - Optional additional join condition
|
|
204
|
+
* @returns New query builder instance with the relationship join
|
|
205
|
+
*/
|
|
206
|
+
joinRelation(
|
|
207
|
+
relationName: string,
|
|
208
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
209
|
+
extraCondition?: ExpressionNode
|
|
210
|
+
): SelectQueryBuilder<T, TTable> {
|
|
211
|
+
const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
212
|
+
return this.clone(nextContext);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Includes related data in the query results
|
|
217
|
+
* @param relationName - Name of the relationship to include
|
|
218
|
+
* @param options - Optional include options
|
|
219
|
+
* @returns New query builder instance with the relationship inclusion
|
|
220
|
+
*/
|
|
221
|
+
include(relationName: string, options?: RelationIncludeOptions): SelectQueryBuilder<T, TTable> {
|
|
222
|
+
const nextContext = this.relationManager.include(this.context, relationName, options);
|
|
223
|
+
return this.clone(nextContext);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
includeLazy<K extends keyof RelationMap<TTable>>(relationName: K): SelectQueryBuilder<T, TTable> {
|
|
227
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
228
|
+
nextLazy.add(relationName as string);
|
|
229
|
+
return this.clone(this.context, nextLazy);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getLazyRelations(): (keyof RelationMap<TTable>)[] {
|
|
233
|
+
return Array.from(this.lazyRelations) as (keyof RelationMap<TTable>)[];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getTable(): TTable {
|
|
237
|
+
return this.env.table as TTable;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async execute(ctx: OrmContext): Promise<Entity<TTable>[]> {
|
|
241
|
+
return executeHydrated(ctx, this);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Adds a WHERE condition to the query
|
|
246
|
+
* @param expr - Expression for the WHERE clause
|
|
247
|
+
* @returns New query builder instance with the WHERE condition
|
|
248
|
+
*/
|
|
249
|
+
where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
250
|
+
const nextContext = this.filterManager.where(this.context, expr);
|
|
251
|
+
return this.clone(nextContext);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Adds a GROUP BY clause to the query
|
|
256
|
+
* @param col - Column definition or column node to group by
|
|
257
|
+
* @returns New query builder instance with the GROUP BY clause
|
|
258
|
+
*/
|
|
259
|
+
groupBy(col: ColumnDef | ColumnNode): SelectQueryBuilder<T, TTable> {
|
|
260
|
+
const nextContext = this.filterManager.groupBy(this.context, col);
|
|
261
|
+
return this.clone(nextContext);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Adds a HAVING condition to the query
|
|
266
|
+
* @param expr - Expression for the HAVING clause
|
|
267
|
+
* @returns New query builder instance with the HAVING condition
|
|
268
|
+
*/
|
|
269
|
+
having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
270
|
+
const nextContext = this.filterManager.having(this.context, expr);
|
|
271
|
+
return this.clone(nextContext);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Adds an ORDER BY clause to the query
|
|
276
|
+
* @param col - Column definition or column node to order by
|
|
277
|
+
* @param direction - Order direction (defaults to ASC)
|
|
278
|
+
* @returns New query builder instance with the ORDER BY clause
|
|
279
|
+
*/
|
|
280
|
+
orderBy(col: ColumnDef | ColumnNode, direction: OrderDirection = ORDER_DIRECTIONS.ASC): SelectQueryBuilder<T, TTable> {
|
|
281
|
+
const nextContext = this.filterManager.orderBy(this.context, col, direction);
|
|
282
|
+
return this.clone(nextContext);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Adds a DISTINCT clause to the query
|
|
287
|
+
* @param cols - Columns to make distinct
|
|
288
|
+
* @returns New query builder instance with the DISTINCT clause
|
|
289
|
+
*/
|
|
290
|
+
distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
|
|
291
|
+
return this.clone(this.columnSelector.distinct(this.context, cols));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Adds a LIMIT clause to the query
|
|
296
|
+
* @param n - Maximum number of rows to return
|
|
297
|
+
* @returns New query builder instance with the LIMIT clause
|
|
298
|
+
*/
|
|
299
|
+
limit(n: number): SelectQueryBuilder<T, TTable> {
|
|
300
|
+
const nextContext = this.paginationManager.limit(this.context, n);
|
|
301
|
+
return this.clone(nextContext);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Adds an OFFSET clause to the query
|
|
306
|
+
* @param n - Number of rows to skip
|
|
307
|
+
* @returns New query builder instance with the OFFSET clause
|
|
308
|
+
*/
|
|
309
|
+
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
310
|
+
const nextContext = this.paginationManager.offset(this.context, n);
|
|
311
|
+
return this.clone(nextContext);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Adds a WHERE EXISTS condition to the query
|
|
316
|
+
* @param subquery - Subquery to check for existence
|
|
317
|
+
* @returns New query builder instance with the WHERE EXISTS condition
|
|
318
|
+
*/
|
|
319
|
+
whereExists(subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
320
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
321
|
+
return this.where(exists(subAst));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Adds a WHERE NOT EXISTS condition to the query
|
|
326
|
+
* @param subquery - Subquery to check for non-existence
|
|
327
|
+
* @returns New query builder instance with the WHERE NOT EXISTS condition
|
|
328
|
+
*/
|
|
329
|
+
whereNotExists(subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
330
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
331
|
+
return this.where(notExists(subAst));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Adds a WHERE EXISTS condition based on a relationship
|
|
336
|
+
* @param relationName - Name of the relationship to check
|
|
337
|
+
* @param callback - Optional callback to modify the relationship query
|
|
338
|
+
* @returns New query builder instance with the relationship existence check
|
|
339
|
+
*/
|
|
312
340
|
whereHas(
|
|
313
341
|
relationName: string,
|
|
314
|
-
callback?:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
*
|
|
335
|
-
* @
|
|
336
|
-
|
|
342
|
+
callback?: <TChildTable extends TableDef>(
|
|
343
|
+
qb: SelectQueryBuilder<any, TChildTable>
|
|
344
|
+
) => SelectQueryBuilder<any, TChildTable>
|
|
345
|
+
): SelectQueryBuilder<T, TTable> {
|
|
346
|
+
const relation = this.env.table.relations[relationName];
|
|
347
|
+
if (!relation) {
|
|
348
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let subQb = this.createChildBuilder<any, typeof relation.target>(relation.target);
|
|
352
|
+
if (callback) {
|
|
353
|
+
subQb = callback(subQb);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const subAst = subQb.getAST();
|
|
357
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
|
|
358
|
+
return this.where(exists(finalSubAst));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
363
|
+
* @param relationName - Name of the relationship to check
|
|
364
|
+
* @param callback - Optional callback to modify the relationship query
|
|
365
|
+
* @returns New query builder instance with the relationship non-existence check
|
|
366
|
+
*/
|
|
337
367
|
whereHasNot(
|
|
338
368
|
relationName: string,
|
|
339
|
-
callback?:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
*
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
*
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
*
|
|
395
|
-
* @
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
*
|
|
403
|
-
|
|
404
|
-
|
|
369
|
+
callback?: <TChildTable extends TableDef>(
|
|
370
|
+
qb: SelectQueryBuilder<any, TChildTable>
|
|
371
|
+
) => SelectQueryBuilder<any, TChildTable>
|
|
372
|
+
): SelectQueryBuilder<T, TTable> {
|
|
373
|
+
const relation = this.env.table.relations[relationName];
|
|
374
|
+
if (!relation) {
|
|
375
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let subQb = this.createChildBuilder<any, typeof relation.target>(relation.target);
|
|
379
|
+
if (callback) {
|
|
380
|
+
subQb = callback(subQb);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const subAst = subQb.getAST();
|
|
384
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
|
|
385
|
+
return this.where(notExists(finalSubAst));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Compiles the query to SQL for a specific dialect
|
|
390
|
+
* @param dialect - Database dialect to compile for
|
|
391
|
+
* @returns Compiled query with SQL and parameters
|
|
392
|
+
*/
|
|
393
|
+
compile(dialect: Dialect): CompiledQuery {
|
|
394
|
+
return dialect.compileSelect(this.context.state.ast);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Converts the query to SQL string for a specific dialect
|
|
399
|
+
* @param dialect - Database dialect to generate SQL for
|
|
400
|
+
* @returns SQL string representation of the query
|
|
401
|
+
*/
|
|
402
|
+
toSql(dialect: Dialect): string {
|
|
403
|
+
return this.compile(dialect).sql;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Gets the hydration plan for the query
|
|
408
|
+
* @returns Hydration plan or undefined if none exists
|
|
409
|
+
*/
|
|
410
|
+
getHydrationPlan(): HydrationPlan | undefined {
|
|
411
|
+
return this.context.hydration.getPlan();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Gets the Abstract Syntax Tree (AST) representation of the query
|
|
416
|
+
* @returns Query AST with hydration applied
|
|
417
|
+
*/
|
|
418
|
+
getAST(): SelectQueryNode {
|
|
419
|
+
return this.context.hydration.applyToAst(this.context.state.ast);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Creates a column node for use in expressions
|
|
425
|
+
* @param table - Table name
|
|
426
|
+
* @param name - Column name
|
|
427
|
+
* @returns ColumnNode with the specified table and name
|
|
428
|
+
*/
|
|
429
|
+
export const createColumn = (table: string, name: string): ColumnNode => ({ type: 'Column', table, name });
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Creates a literal value node for use in expressions
|
|
433
|
+
* @param val - Literal value (string or number)
|
|
434
|
+
* @returns LiteralNode with the specified value
|
|
435
|
+
*/
|
|
436
|
+
export const createLiteral = (val: string | number): LiteralNode => ({ type: 'Literal', value: val });
|