metal-orm 1.0.58 → 1.0.59
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 +34 -31
- package/dist/index.cjs +1463 -1003
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +148 -129
- package/dist/index.d.ts +148 -129
- package/dist/index.js +1459 -1003
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ddl/schema-generator.ts +44 -1
- package/src/decorators/bootstrap.ts +183 -146
- package/src/decorators/column-decorator.ts +8 -49
- package/src/decorators/decorator-metadata.ts +10 -46
- package/src/decorators/entity.ts +30 -40
- package/src/decorators/relations.ts +30 -56
- package/src/orm/entity-hydration.ts +72 -0
- package/src/orm/entity-meta.ts +13 -11
- package/src/orm/entity-metadata.ts +240 -238
- package/src/orm/entity-relation-cache.ts +39 -0
- package/src/orm/entity-relations.ts +207 -0
- package/src/orm/entity.ts +124 -410
- package/src/orm/execute.ts +4 -4
- package/src/orm/lazy-batch/belongs-to-many.ts +134 -0
- package/src/orm/lazy-batch/belongs-to.ts +108 -0
- package/src/orm/lazy-batch/has-many.ts +69 -0
- package/src/orm/lazy-batch/has-one.ts +68 -0
- package/src/orm/lazy-batch/shared.ts +125 -0
- package/src/orm/lazy-batch.ts +4 -492
- package/src/orm/relations/many-to-many.ts +2 -1
- package/src/query-builder/relation-cte-builder.ts +63 -0
- package/src/query-builder/relation-filter-utils.ts +159 -0
- package/src/query-builder/relation-include-strategies.ts +177 -0
- package/src/query-builder/relation-join-planner.ts +80 -0
- package/src/query-builder/relation-service.ts +119 -479
- package/src/query-builder/relation-types.ts +41 -10
- package/src/query-builder/select/projection-facet.ts +23 -23
- package/src/query-builder/select/select-operations.ts +145 -0
- package/src/query-builder/select.ts +351 -422
- package/src/schema/relation.ts +22 -18
- package/src/schema/table.ts +22 -9
- package/src/schema/types.ts +14 -12
|
@@ -2,21 +2,20 @@ import { TableDef } from '../schema/table.js';
|
|
|
2
2
|
import { ColumnDef } from '../schema/column-types.js';
|
|
3
3
|
import { OrderingTerm, SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
|
|
4
4
|
import { HydrationPlan } from '../core/hydration/types.js';
|
|
5
|
-
import {
|
|
6
|
-
ColumnNode,
|
|
7
|
-
ExpressionNode,
|
|
8
|
-
FunctionNode,
|
|
9
|
-
BinaryExpressionNode,
|
|
10
|
-
CaseExpressionNode,
|
|
11
|
-
WindowFunctionNode,
|
|
12
|
-
and,
|
|
13
|
-
exists,
|
|
14
|
-
notExists,
|
|
15
|
-
OperandNode
|
|
16
|
-
} from '../core/ast/expression.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
5
|
+
import {
|
|
6
|
+
ColumnNode,
|
|
7
|
+
ExpressionNode,
|
|
8
|
+
FunctionNode,
|
|
9
|
+
BinaryExpressionNode,
|
|
10
|
+
CaseExpressionNode,
|
|
11
|
+
WindowFunctionNode,
|
|
12
|
+
and,
|
|
13
|
+
exists,
|
|
14
|
+
notExists,
|
|
15
|
+
OperandNode
|
|
16
|
+
} from '../core/ast/expression.js';
|
|
17
|
+
import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
|
|
18
|
+
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
20
19
|
|
|
21
20
|
type SelectDialectInput = Dialect | DialectKey;
|
|
22
21
|
|
|
@@ -28,42 +27,46 @@ import {
|
|
|
28
27
|
SelectQueryBuilderDependencies,
|
|
29
28
|
SelectQueryBuilderEnvironment
|
|
30
29
|
} from './select-query-builder-deps.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import { RelationIncludeOptions } from './relation-types.js';
|
|
35
|
-
import { RelationKinds, type RelationDef } from '../schema/relation.js';
|
|
30
|
+
import { ColumnSelector } from './column-selector.js';
|
|
31
|
+
import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
|
|
32
|
+
import { RelationKinds } from '../schema/relation.js';
|
|
36
33
|
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
|
|
37
|
-
import { EntityInstance, RelationMap
|
|
38
|
-
import { OrmSession } from '../orm/orm-session.ts';
|
|
39
|
-
import { ExecutionContext } from '../orm/execution-context.js';
|
|
40
|
-
import { HydrationContext } from '../orm/hydration-context.js';
|
|
41
|
-
import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
34
|
+
import { EntityInstance, RelationMap } from '../schema/types.js';
|
|
35
|
+
import { OrmSession } from '../orm/orm-session.ts';
|
|
36
|
+
import { ExecutionContext } from '../orm/execution-context.js';
|
|
37
|
+
import { HydrationContext } from '../orm/hydration-context.js';
|
|
38
|
+
import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
|
|
39
|
+
import { resolveSelectQuery } from './query-resolution.js';
|
|
40
|
+
import {
|
|
41
|
+
applyOrderBy,
|
|
42
|
+
buildWhereHasPredicate,
|
|
43
|
+
executeCount,
|
|
44
|
+
executePagedQuery,
|
|
45
|
+
RelationCallback,
|
|
46
|
+
WhereHasOptions
|
|
47
|
+
} from './select/select-operations.js';
|
|
48
|
+
import { SelectFromFacet } from './select/from-facet.js';
|
|
49
|
+
import { SelectJoinFacet } from './select/join-facet.js';
|
|
50
|
+
import { SelectProjectionFacet } from './select/projection-facet.js';
|
|
51
|
+
import { SelectPredicateFacet } from './select/predicate-facet.js';
|
|
52
|
+
import { SelectCTEFacet } from './select/cte-facet.js';
|
|
53
|
+
import { SelectSetOpFacet } from './select/setop-facet.js';
|
|
54
|
+
import { SelectRelationFacet } from './select/relation-facet.js';
|
|
44
55
|
|
|
45
56
|
|
|
46
57
|
type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
|
|
47
58
|
|
|
48
|
-
type DeepSelectEntry<TTable extends TableDef> = {
|
|
49
|
-
type: 'root';
|
|
50
|
-
columns: (keyof TTable['columns'] & string)[];
|
|
51
|
-
} | {
|
|
52
|
-
type: 'relation';
|
|
53
|
-
relationName: keyof TTable['relations'] & string;
|
|
54
|
-
columns: string[];
|
|
55
|
-
};
|
|
59
|
+
type DeepSelectEntry<TTable extends TableDef> = {
|
|
60
|
+
type: 'root';
|
|
61
|
+
columns: (keyof TTable['columns'] & string)[];
|
|
62
|
+
} | {
|
|
63
|
+
type: 'relation';
|
|
64
|
+
relationName: keyof TTable['relations'] & string;
|
|
65
|
+
columns: string[];
|
|
66
|
+
};
|
|
56
67
|
|
|
57
68
|
type DeepSelectConfig<TTable extends TableDef> = DeepSelectEntry<TTable>[];
|
|
58
69
|
|
|
59
|
-
type WhereHasOptions = {
|
|
60
|
-
correlate?: ExpressionNode;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
type RelationCallback = <TChildTable extends TableDef>(
|
|
64
|
-
qb: SelectQueryBuilder<unknown, TChildTable>
|
|
65
|
-
) => SelectQueryBuilder<unknown, TChildTable>;
|
|
66
|
-
|
|
67
70
|
|
|
68
71
|
/**
|
|
69
72
|
* Main query builder class for constructing SQL SELECT queries
|
|
@@ -71,12 +74,18 @@ type RelationCallback = <TChildTable extends TableDef>(
|
|
|
71
74
|
* @typeParam TTable - Table definition being queried
|
|
72
75
|
*/
|
|
73
76
|
export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef> {
|
|
74
|
-
private readonly env: SelectQueryBuilderEnvironment;
|
|
75
|
-
private readonly context: SelectQueryBuilderContext;
|
|
76
|
-
private readonly columnSelector: ColumnSelector;
|
|
77
|
-
private readonly
|
|
78
|
-
private readonly
|
|
79
|
-
private readonly
|
|
77
|
+
private readonly env: SelectQueryBuilderEnvironment;
|
|
78
|
+
private readonly context: SelectQueryBuilderContext;
|
|
79
|
+
private readonly columnSelector: ColumnSelector;
|
|
80
|
+
private readonly fromFacet: SelectFromFacet;
|
|
81
|
+
private readonly joinFacet: SelectJoinFacet;
|
|
82
|
+
private readonly projectionFacet: SelectProjectionFacet;
|
|
83
|
+
private readonly predicateFacet: SelectPredicateFacet;
|
|
84
|
+
private readonly cteFacet: SelectCTEFacet;
|
|
85
|
+
private readonly setOpFacet: SelectSetOpFacet;
|
|
86
|
+
private readonly relationFacet: SelectRelationFacet;
|
|
87
|
+
private readonly lazyRelations: Set<string>;
|
|
88
|
+
private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
|
|
80
89
|
|
|
81
90
|
/**
|
|
82
91
|
* Creates a new SelectQueryBuilder instance
|
|
@@ -92,20 +101,28 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
92
101
|
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
93
102
|
lazyRelations?: Set<string>,
|
|
94
103
|
lazyRelationOptions?: Map<string, RelationIncludeOptions>
|
|
95
|
-
) {
|
|
96
|
-
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
97
|
-
this.env = { table, deps };
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
108
|
-
|
|
104
|
+
) {
|
|
105
|
+
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
106
|
+
this.env = { table, deps };
|
|
107
|
+
const createAstService = (nextState: SelectQueryState) => deps.createQueryAstService(table, nextState);
|
|
108
|
+
const initialState = state ?? deps.createState(table);
|
|
109
|
+
const initialHydration = hydration ?? deps.createHydration(table);
|
|
110
|
+
this.context = {
|
|
111
|
+
state: initialState,
|
|
112
|
+
hydration: initialHydration
|
|
113
|
+
};
|
|
114
|
+
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
115
|
+
this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
|
|
116
|
+
this.columnSelector = deps.createColumnSelector(this.env);
|
|
117
|
+
const relationManager = deps.createRelationManager(this.env);
|
|
118
|
+
this.fromFacet = new SelectFromFacet(this.env, createAstService);
|
|
119
|
+
this.joinFacet = new SelectJoinFacet(this.env, createAstService);
|
|
120
|
+
this.projectionFacet = new SelectProjectionFacet(this.columnSelector);
|
|
121
|
+
this.predicateFacet = new SelectPredicateFacet(this.env, createAstService);
|
|
122
|
+
this.cteFacet = new SelectCTEFacet(this.env, createAstService);
|
|
123
|
+
this.setOpFacet = new SelectSetOpFacet(this.env, createAstService);
|
|
124
|
+
this.relationFacet = new SelectRelationFacet(relationManager);
|
|
125
|
+
}
|
|
109
126
|
|
|
110
127
|
/**
|
|
111
128
|
* Creates a new SelectQueryBuilder instance with updated context and lazy relations
|
|
@@ -132,15 +149,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
132
149
|
* Applies an alias to the root FROM table.
|
|
133
150
|
* @param alias - Alias to apply
|
|
134
151
|
*/
|
|
135
|
-
as(alias: string): SelectQueryBuilder<T, TTable> {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
const nextFrom = { ...from, alias };
|
|
141
|
-
const nextContext = this.applyAst(this.context, service => service.withFrom(nextFrom));
|
|
142
|
-
return this.clone(nextContext);
|
|
143
|
-
}
|
|
152
|
+
as(alias: string): SelectQueryBuilder<T, TTable> {
|
|
153
|
+
const nextContext = this.fromFacet.as(this.context, alias);
|
|
154
|
+
return this.clone(nextContext);
|
|
155
|
+
}
|
|
144
156
|
|
|
145
157
|
|
|
146
158
|
|
|
@@ -168,52 +180,19 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
168
180
|
return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
/**
|
|
172
|
-
* Applies
|
|
173
|
-
* @param
|
|
174
|
-
* @param
|
|
175
|
-
* @returns Updated query context
|
|
176
|
-
*/
|
|
177
|
-
private
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
): SelectQueryBuilderContext {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Applies a join to the query context
|
|
188
|
-
* @param context - Current query context
|
|
189
|
-
* @param table - Table to join
|
|
190
|
-
* @param condition - Join condition
|
|
191
|
-
* @param kind - Join kind
|
|
192
|
-
* @returns Updated query context with join applied
|
|
193
|
-
*/
|
|
194
|
-
private applyJoin(
|
|
195
|
-
context: SelectQueryBuilderContext,
|
|
196
|
-
table: TableDef,
|
|
197
|
-
condition: BinaryExpressionNode,
|
|
198
|
-
kind: JoinKind
|
|
199
|
-
): SelectQueryBuilderContext {
|
|
200
|
-
const joinNode = createJoinNode(kind, { type: 'Table', name: table.name, schema: table.schema }, condition);
|
|
201
|
-
return this.applyAst(context, service => service.withJoin(joinNode));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Applies a set operation to the query
|
|
206
|
-
* @param operator - Set operation kind
|
|
207
|
-
* @param query - Query to combine with
|
|
208
|
-
* @returns Updated query context with set operation
|
|
209
|
-
*/
|
|
210
|
-
private applySetOperation<TSub extends TableDef>(
|
|
211
|
-
operator: SetOperationKind,
|
|
212
|
-
query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
213
|
-
): SelectQueryBuilderContext {
|
|
214
|
-
const subAst = resolveSelectQuery(query);
|
|
215
|
-
return this.applyAst(this.context, service => service.withSetOperation(operator, subAst));
|
|
216
|
-
}
|
|
183
|
+
/**
|
|
184
|
+
* Applies a set operation to the query
|
|
185
|
+
* @param operator - Set operation kind
|
|
186
|
+
* @param query - Query to combine with
|
|
187
|
+
* @returns Updated query context with set operation
|
|
188
|
+
*/
|
|
189
|
+
private applySetOperation<TSub extends TableDef>(
|
|
190
|
+
operator: SetOperationKind,
|
|
191
|
+
query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
192
|
+
): SelectQueryBuilderContext {
|
|
193
|
+
const subAst = resolveSelectQuery(query);
|
|
194
|
+
return this.setOpFacet.applySetOperation(this.context, operator, subAst);
|
|
195
|
+
}
|
|
217
196
|
|
|
218
197
|
|
|
219
198
|
/**
|
|
@@ -230,10 +209,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
230
209
|
...args: K[] | [Record<string, ColumnSelectionValue>]
|
|
231
210
|
): SelectQueryBuilder<T, TTable> {
|
|
232
211
|
// If first arg is an object (not a string), treat as projection map
|
|
233
|
-
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
|
|
234
|
-
const columns = args[0] as Record<string, ColumnSelectionValue>;
|
|
235
|
-
return this.clone(this.
|
|
236
|
-
}
|
|
212
|
+
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
|
|
213
|
+
const columns = args[0] as Record<string, ColumnSelectionValue>;
|
|
214
|
+
return this.clone(this.projectionFacet.select(this.context, columns));
|
|
215
|
+
}
|
|
237
216
|
|
|
238
217
|
// Otherwise, treat as column names
|
|
239
218
|
const cols = args as K[];
|
|
@@ -246,17 +225,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
246
225
|
selection[key] = col;
|
|
247
226
|
}
|
|
248
227
|
|
|
249
|
-
return this.clone(this.
|
|
250
|
-
}
|
|
228
|
+
return this.clone(this.projectionFacet.select(this.context, selection));
|
|
229
|
+
}
|
|
251
230
|
|
|
252
231
|
/**
|
|
253
232
|
* Selects raw column expressions
|
|
254
233
|
* @param cols - Column expressions as strings
|
|
255
234
|
* @returns New query builder instance with raw column selections
|
|
256
235
|
*/
|
|
257
|
-
selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
|
|
258
|
-
return this.clone(this.
|
|
259
|
-
}
|
|
236
|
+
selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
|
|
237
|
+
return this.clone(this.projectionFacet.selectRaw(this.context, cols));
|
|
238
|
+
}
|
|
260
239
|
|
|
261
240
|
/**
|
|
262
241
|
* Adds a Common Table Expression (CTE) to the query
|
|
@@ -265,11 +244,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
265
244
|
* @param columns - Optional column names for the CTE
|
|
266
245
|
* @returns New query builder instance with the CTE
|
|
267
246
|
*/
|
|
268
|
-
with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
269
|
-
const subAst = resolveSelectQuery(query);
|
|
270
|
-
const nextContext = this.
|
|
271
|
-
return this.clone(nextContext);
|
|
272
|
-
}
|
|
247
|
+
with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
248
|
+
const subAst = resolveSelectQuery(query);
|
|
249
|
+
const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
|
|
250
|
+
return this.clone(nextContext);
|
|
251
|
+
}
|
|
273
252
|
|
|
274
253
|
/**
|
|
275
254
|
* Adds a recursive Common Table Expression (CTE) to the query
|
|
@@ -278,11 +257,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
278
257
|
* @param columns - Optional column names for the CTE
|
|
279
258
|
* @returns New query builder instance with the recursive CTE
|
|
280
259
|
*/
|
|
281
|
-
withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
282
|
-
const subAst = resolveSelectQuery(query);
|
|
283
|
-
const nextContext = this.
|
|
284
|
-
return this.clone(nextContext);
|
|
285
|
-
}
|
|
260
|
+
withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
261
|
+
const subAst = resolveSelectQuery(query);
|
|
262
|
+
const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
|
|
263
|
+
return this.clone(nextContext);
|
|
264
|
+
}
|
|
286
265
|
|
|
287
266
|
/**
|
|
288
267
|
* Replaces the FROM clause with a derived table (subquery with alias)
|
|
@@ -291,16 +270,15 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
291
270
|
* @param columnAliases - Optional column alias list
|
|
292
271
|
* @returns New query builder instance with updated FROM
|
|
293
272
|
*/
|
|
294
|
-
fromSubquery<TSub extends TableDef>(
|
|
295
|
-
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
296
|
-
alias: string,
|
|
297
|
-
columnAliases?: string[]
|
|
298
|
-
): SelectQueryBuilder<T, TTable> {
|
|
299
|
-
const subAst = resolveSelectQuery(subquery);
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
273
|
+
fromSubquery<TSub extends TableDef>(
|
|
274
|
+
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
275
|
+
alias: string,
|
|
276
|
+
columnAliases?: string[]
|
|
277
|
+
): SelectQueryBuilder<T, TTable> {
|
|
278
|
+
const subAst = resolveSelectQuery(subquery);
|
|
279
|
+
const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
|
|
280
|
+
return this.clone(nextContext);
|
|
281
|
+
}
|
|
304
282
|
|
|
305
283
|
/**
|
|
306
284
|
* Replaces the FROM clause with a function table expression.
|
|
@@ -309,16 +287,15 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
309
287
|
* @param alias - Optional alias for the function table
|
|
310
288
|
* @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
|
|
311
289
|
*/
|
|
312
|
-
fromFunctionTable(
|
|
313
|
-
name: string,
|
|
314
|
-
args: OperandNode[] = [],
|
|
315
|
-
alias?: string,
|
|
316
|
-
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
317
|
-
): SelectQueryBuilder<T, TTable> {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
290
|
+
fromFunctionTable(
|
|
291
|
+
name: string,
|
|
292
|
+
args: OperandNode[] = [],
|
|
293
|
+
alias?: string,
|
|
294
|
+
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
295
|
+
): SelectQueryBuilder<T, TTable> {
|
|
296
|
+
const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
|
|
297
|
+
return this.clone(nextContext);
|
|
298
|
+
}
|
|
322
299
|
|
|
323
300
|
/**
|
|
324
301
|
* Selects a subquery as a column
|
|
@@ -326,10 +303,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
326
303
|
* @param sub - Query builder or query node for the subquery
|
|
327
304
|
* @returns New query builder instance with the subquery selection
|
|
328
305
|
*/
|
|
329
|
-
selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
330
|
-
const query = resolveSelectQuery(sub);
|
|
331
|
-
return this.clone(this.
|
|
332
|
-
}
|
|
306
|
+
selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
307
|
+
const query = resolveSelectQuery(sub);
|
|
308
|
+
return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
|
|
309
|
+
}
|
|
333
310
|
|
|
334
311
|
/**
|
|
335
312
|
* Adds a JOIN against a derived table (subquery with alias)
|
|
@@ -340,18 +317,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
340
317
|
* @param columnAliases - Optional column alias list for the derived table
|
|
341
318
|
* @returns New query builder instance with the derived-table join
|
|
342
319
|
*/
|
|
343
|
-
joinSubquery<TSub extends TableDef>(
|
|
344
|
-
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
345
|
-
alias: string,
|
|
346
|
-
condition: BinaryExpressionNode,
|
|
347
|
-
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
348
|
-
columnAliases?: string[]
|
|
349
|
-
): SelectQueryBuilder<T, TTable> {
|
|
350
|
-
const subAst = resolveSelectQuery(subquery);
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
320
|
+
joinSubquery<TSub extends TableDef>(
|
|
321
|
+
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
322
|
+
alias: string,
|
|
323
|
+
condition: BinaryExpressionNode,
|
|
324
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
325
|
+
columnAliases?: string[]
|
|
326
|
+
): SelectQueryBuilder<T, TTable> {
|
|
327
|
+
const subAst = resolveSelectQuery(subquery);
|
|
328
|
+
const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
|
|
329
|
+
return this.clone(nextContext);
|
|
330
|
+
}
|
|
355
331
|
|
|
356
332
|
/**
|
|
357
333
|
* Adds a join against a function table (e.g., `generate_series`) using `fnTable` internally.
|
|
@@ -362,19 +338,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
362
338
|
* @param joinKind - Kind of join (defaults to INNER)
|
|
363
339
|
* @param options - Optional metadata (lateral, ordinality, column aliases, schema)
|
|
364
340
|
*/
|
|
365
|
-
joinFunctionTable(
|
|
366
|
-
name: string,
|
|
367
|
-
args: OperandNode[] = [],
|
|
368
|
-
alias: string,
|
|
369
|
-
condition: BinaryExpressionNode,
|
|
370
|
-
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
371
|
-
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
372
|
-
): SelectQueryBuilder<T, TTable> {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return this.clone(nextContext);
|
|
377
|
-
}
|
|
341
|
+
joinFunctionTable(
|
|
342
|
+
name: string,
|
|
343
|
+
args: OperandNode[] = [],
|
|
344
|
+
alias: string,
|
|
345
|
+
condition: BinaryExpressionNode,
|
|
346
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
347
|
+
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
348
|
+
): SelectQueryBuilder<T, TTable> {
|
|
349
|
+
const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
|
|
350
|
+
return this.clone(nextContext);
|
|
351
|
+
}
|
|
378
352
|
|
|
379
353
|
/**
|
|
380
354
|
* Adds an INNER JOIN to the query
|
|
@@ -382,10 +356,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
382
356
|
* @param condition - Join condition expression
|
|
383
357
|
* @returns New query builder instance with the INNER JOIN
|
|
384
358
|
*/
|
|
385
|
-
innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
386
|
-
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
387
|
-
return this.clone(nextContext);
|
|
388
|
-
}
|
|
359
|
+
innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
360
|
+
const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
361
|
+
return this.clone(nextContext);
|
|
362
|
+
}
|
|
389
363
|
|
|
390
364
|
/**
|
|
391
365
|
* Adds a LEFT JOIN to the query
|
|
@@ -393,10 +367,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
393
367
|
* @param condition - Join condition expression
|
|
394
368
|
* @returns New query builder instance with the LEFT JOIN
|
|
395
369
|
*/
|
|
396
|
-
leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
397
|
-
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
398
|
-
return this.clone(nextContext);
|
|
399
|
-
}
|
|
370
|
+
leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
371
|
+
const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
372
|
+
return this.clone(nextContext);
|
|
373
|
+
}
|
|
400
374
|
|
|
401
375
|
/**
|
|
402
376
|
* Adds a RIGHT JOIN to the query
|
|
@@ -404,10 +378,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
404
378
|
* @param condition - Join condition expression
|
|
405
379
|
* @returns New query builder instance with the RIGHT JOIN
|
|
406
380
|
*/
|
|
407
|
-
rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
408
|
-
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
409
|
-
return this.clone(nextContext);
|
|
410
|
-
}
|
|
381
|
+
rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
382
|
+
const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
383
|
+
return this.clone(nextContext);
|
|
384
|
+
}
|
|
411
385
|
|
|
412
386
|
/**
|
|
413
387
|
* Matches records based on a relationship
|
|
@@ -415,13 +389,13 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
415
389
|
* @param predicate - Optional predicate expression
|
|
416
390
|
* @returns New query builder instance with the relationship match
|
|
417
391
|
*/
|
|
418
|
-
match<K extends keyof TTable['relations'] & string>(
|
|
419
|
-
relationName: K,
|
|
420
|
-
predicate?: ExpressionNode
|
|
421
|
-
): SelectQueryBuilder<T, TTable> {
|
|
422
|
-
const nextContext = this.
|
|
423
|
-
return this.clone(nextContext);
|
|
424
|
-
}
|
|
392
|
+
match<K extends keyof TTable['relations'] & string>(
|
|
393
|
+
relationName: K,
|
|
394
|
+
predicate?: ExpressionNode
|
|
395
|
+
): SelectQueryBuilder<T, TTable> {
|
|
396
|
+
const nextContext = this.relationFacet.match(this.context, relationName, predicate);
|
|
397
|
+
return this.clone(nextContext);
|
|
398
|
+
}
|
|
425
399
|
|
|
426
400
|
/**
|
|
427
401
|
* Joins a related table
|
|
@@ -430,14 +404,14 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
430
404
|
* @param extraCondition - Optional additional join condition
|
|
431
405
|
* @returns New query builder instance with the relationship join
|
|
432
406
|
*/
|
|
433
|
-
joinRelation<K extends keyof TTable['relations'] & string>(
|
|
434
|
-
relationName: K,
|
|
435
|
-
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
436
|
-
extraCondition?: ExpressionNode
|
|
437
|
-
): SelectQueryBuilder<T, TTable> {
|
|
438
|
-
const nextContext = this.
|
|
439
|
-
return this.clone(nextContext);
|
|
440
|
-
}
|
|
407
|
+
joinRelation<K extends keyof TTable['relations'] & string>(
|
|
408
|
+
relationName: K,
|
|
409
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
410
|
+
extraCondition?: ExpressionNode
|
|
411
|
+
): SelectQueryBuilder<T, TTable> {
|
|
412
|
+
const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
413
|
+
return this.clone(nextContext);
|
|
414
|
+
}
|
|
441
415
|
|
|
442
416
|
/**
|
|
443
417
|
* Includes related data in the query results
|
|
@@ -445,13 +419,13 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
445
419
|
* @param options - Optional include options
|
|
446
420
|
* @returns New query builder instance with the relationship inclusion
|
|
447
421
|
*/
|
|
448
|
-
include<K extends keyof TTable['relations'] & string>(
|
|
449
|
-
relationName: K,
|
|
450
|
-
options?:
|
|
451
|
-
): SelectQueryBuilder<T, TTable> {
|
|
452
|
-
const nextContext = this.
|
|
453
|
-
return this.clone(nextContext);
|
|
454
|
-
}
|
|
422
|
+
include<K extends keyof TTable['relations'] & string>(
|
|
423
|
+
relationName: K,
|
|
424
|
+
options?: TypedRelationIncludeOptions<TTable['relations'][K]>
|
|
425
|
+
): SelectQueryBuilder<T, TTable> {
|
|
426
|
+
const nextContext = this.relationFacet.include(this.context, relationName, options);
|
|
427
|
+
return this.clone(nextContext);
|
|
428
|
+
}
|
|
455
429
|
|
|
456
430
|
/**
|
|
457
431
|
* Includes a relation lazily in the query results
|
|
@@ -461,45 +435,44 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
461
435
|
*/
|
|
462
436
|
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
463
437
|
relationName: K,
|
|
464
|
-
options?:
|
|
438
|
+
options?: TypedRelationIncludeOptions<TTable['relations'][K]>
|
|
465
439
|
): SelectQueryBuilder<T, TTable> {
|
|
466
|
-
let nextContext = this.context;
|
|
467
|
-
const relation = this.env.table.relations[relationName as string];
|
|
468
|
-
if (relation?.type === RelationKinds.BelongsTo) {
|
|
469
|
-
const foreignKey = relation.foreignKey;
|
|
470
|
-
const fkColumn = this.env.table.columns[foreignKey];
|
|
471
|
-
if (fkColumn) {
|
|
472
|
-
const hasAlias = nextContext.state.ast.columns.some(col => {
|
|
473
|
-
const node = col as { alias?: string; name?: string };
|
|
474
|
-
return (node.alias ?? node.name) === foreignKey;
|
|
475
|
-
});
|
|
476
|
-
if (!hasAlias) {
|
|
477
|
-
nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
const nextLazy = new Set(this.lazyRelations);
|
|
482
|
-
nextLazy.add(relationName as string);
|
|
483
|
-
const nextOptions = new Map(this.lazyRelationOptions);
|
|
484
|
-
if (options) {
|
|
485
|
-
nextOptions.set(relationName as string, options);
|
|
486
|
-
} else {
|
|
487
|
-
nextOptions.delete(relationName as string);
|
|
488
|
-
}
|
|
489
|
-
return this.clone(nextContext, nextLazy, nextOptions);
|
|
490
|
-
}
|
|
440
|
+
let nextContext = this.context;
|
|
441
|
+
const relation = this.env.table.relations[relationName as string];
|
|
442
|
+
if (relation?.type === RelationKinds.BelongsTo) {
|
|
443
|
+
const foreignKey = relation.foreignKey;
|
|
444
|
+
const fkColumn = this.env.table.columns[foreignKey];
|
|
445
|
+
if (fkColumn) {
|
|
446
|
+
const hasAlias = nextContext.state.ast.columns.some(col => {
|
|
447
|
+
const node = col as { alias?: string; name?: string };
|
|
448
|
+
return (node.alias ?? node.name) === foreignKey;
|
|
449
|
+
});
|
|
450
|
+
if (!hasAlias) {
|
|
451
|
+
nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
456
|
+
nextLazy.add(relationName as string);
|
|
457
|
+
const nextOptions = new Map(this.lazyRelationOptions);
|
|
458
|
+
if (options) {
|
|
459
|
+
nextOptions.set(relationName as string, options);
|
|
460
|
+
} else {
|
|
461
|
+
nextOptions.delete(relationName as string);
|
|
462
|
+
}
|
|
463
|
+
return this.clone(nextContext, nextLazy, nextOptions);
|
|
464
|
+
}
|
|
491
465
|
|
|
492
466
|
/**
|
|
493
467
|
* Convenience alias for including only specific columns from a relation.
|
|
494
468
|
*/
|
|
495
|
-
includePick<
|
|
496
|
-
K extends keyof TTable['relations'] & string,
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
469
|
+
includePick<
|
|
470
|
+
K extends keyof TTable['relations'] & string,
|
|
471
|
+
C extends RelationTargetColumns<TTable['relations'][K]>
|
|
472
|
+
>(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
|
|
473
|
+
const options = { columns: cols as readonly C[] } as unknown as TypedRelationIncludeOptions<TTable['relations'][K]>;
|
|
474
|
+
return this.include(relationName, options);
|
|
475
|
+
}
|
|
503
476
|
|
|
504
477
|
|
|
505
478
|
/**
|
|
@@ -511,13 +484,14 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
511
484
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
512
485
|
let currBuilder: SelectQueryBuilder<T, TTable> = this;
|
|
513
486
|
|
|
514
|
-
for (const entry of config) {
|
|
515
|
-
if (entry.type === 'root') {
|
|
516
|
-
currBuilder = currBuilder.select(...entry.columns);
|
|
517
|
-
} else {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
487
|
+
for (const entry of config) {
|
|
488
|
+
if (entry.type === 'root') {
|
|
489
|
+
currBuilder = currBuilder.select(...entry.columns);
|
|
490
|
+
} else {
|
|
491
|
+
const options = { columns: entry.columns } as unknown as TypedRelationIncludeOptions<TTable['relations'][typeof entry.relationName]>;
|
|
492
|
+
currBuilder = currBuilder.include(entry.relationName, options);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
521
495
|
|
|
522
496
|
return currBuilder;
|
|
523
497
|
}
|
|
@@ -555,63 +529,28 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
555
529
|
return executeHydrated(ctx, this);
|
|
556
530
|
}
|
|
557
531
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
columns: [{ type: 'Function', name: 'COUNT', args: [], alias: 'total' } as FunctionNode],
|
|
581
|
-
joins: []
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
const execCtx = session.getExecutionContext();
|
|
585
|
-
const compiled = execCtx.dialect.compileSelect(countQuery);
|
|
586
|
-
const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
587
|
-
const value = results[0]?.values?.[0]?.[0];
|
|
588
|
-
|
|
589
|
-
if (typeof value === 'number') return value;
|
|
590
|
-
if (typeof value === 'bigint') return Number(value);
|
|
591
|
-
if (typeof value === 'string') return Number(value);
|
|
592
|
-
return value === null || value === undefined ? 0 : Number(value);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
async executePaged(
|
|
596
|
-
session: OrmSession,
|
|
597
|
-
options: { page: number; pageSize: number }
|
|
598
|
-
): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
|
|
599
|
-
const { page, pageSize } = options;
|
|
600
|
-
if (!Number.isInteger(page) || page < 1) {
|
|
601
|
-
throw new Error('executePaged: page must be an integer >= 1');
|
|
602
|
-
}
|
|
603
|
-
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
604
|
-
throw new Error('executePaged: pageSize must be an integer >= 1');
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const offset = (page - 1) * pageSize;
|
|
608
|
-
const [items, totalItems] = await Promise.all([
|
|
609
|
-
this.limit(pageSize).offset(offset).execute(session),
|
|
610
|
-
this.count(session)
|
|
611
|
-
]);
|
|
612
|
-
|
|
613
|
-
return { items, totalItems };
|
|
614
|
-
}
|
|
532
|
+
/**
|
|
533
|
+
* Executes a count query for the current builder without LIMIT/OFFSET clauses.
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* const total = await qb.count(session);
|
|
537
|
+
*/
|
|
538
|
+
async count(session: OrmSession): Promise<number> {
|
|
539
|
+
return executeCount(this.context, this.env, session);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Executes the query and returns both the paged items and the total.
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
|
|
547
|
+
*/
|
|
548
|
+
async executePaged(
|
|
549
|
+
session: OrmSession,
|
|
550
|
+
options: { page: number; pageSize: number }
|
|
551
|
+
): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
|
|
552
|
+
return executePagedQuery(this, session, options, sess => this.count(sess));
|
|
553
|
+
}
|
|
615
554
|
|
|
616
555
|
/**
|
|
617
556
|
* Executes the query with provided execution and hydration contexts
|
|
@@ -628,81 +567,79 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
628
567
|
* @param expr - Expression for the WHERE clause
|
|
629
568
|
* @returns New query builder instance with the WHERE condition
|
|
630
569
|
*/
|
|
631
|
-
where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
632
|
-
const nextContext = this.
|
|
633
|
-
return this.clone(nextContext);
|
|
634
|
-
}
|
|
570
|
+
where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
571
|
+
const nextContext = this.predicateFacet.where(this.context, expr);
|
|
572
|
+
return this.clone(nextContext);
|
|
573
|
+
}
|
|
635
574
|
|
|
636
575
|
/**
|
|
637
576
|
* Adds a GROUP BY clause to the query
|
|
638
577
|
* @param term - Column definition or ordering term to group by
|
|
639
578
|
* @returns New query builder instance with the GROUP BY clause
|
|
640
579
|
*/
|
|
641
|
-
groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
|
|
642
|
-
const nextContext = this.
|
|
643
|
-
return this.clone(nextContext);
|
|
644
|
-
}
|
|
580
|
+
groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
|
|
581
|
+
const nextContext = this.predicateFacet.groupBy(this.context, term);
|
|
582
|
+
return this.clone(nextContext);
|
|
583
|
+
}
|
|
645
584
|
|
|
646
585
|
/**
|
|
647
586
|
* Adds a HAVING condition to the query
|
|
648
587
|
* @param expr - Expression for the HAVING clause
|
|
649
588
|
* @returns New query builder instance with the HAVING condition
|
|
650
589
|
*/
|
|
651
|
-
having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
652
|
-
const nextContext = this.
|
|
653
|
-
return this.clone(nextContext);
|
|
654
|
-
}
|
|
655
|
-
|
|
590
|
+
having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
591
|
+
const nextContext = this.predicateFacet.having(this.context, expr);
|
|
592
|
+
return this.clone(nextContext);
|
|
593
|
+
}
|
|
656
594
|
|
|
657
595
|
|
|
658
|
-
/**
|
|
659
|
-
* Adds an ORDER BY clause to the query
|
|
660
|
-
* @param term - Column definition or ordering term to order by
|
|
661
|
-
* @param directionOrOptions - Order direction or options (defaults to ASC)
|
|
662
|
-
* @returns New query builder instance with the ORDER BY clause
|
|
663
|
-
*/
|
|
664
|
-
orderBy(
|
|
665
|
-
term: ColumnDef | OrderingTerm,
|
|
666
|
-
directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
|
|
667
|
-
): SelectQueryBuilder<T, TTable> {
|
|
668
|
-
const options = typeof directionOrOptions === 'string' ? { direction: directionOrOptions } : directionOrOptions;
|
|
669
|
-
const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
|
|
670
|
-
|
|
671
|
-
const nextContext = this.applyAst(this.context, service =>
|
|
672
|
-
service.withOrderBy(term, dir, options.nulls, options.collation)
|
|
673
|
-
);
|
|
674
596
|
|
|
675
|
-
|
|
676
|
-
|
|
597
|
+
/**
|
|
598
|
+
* Adds an ORDER BY clause to the query
|
|
599
|
+
* @param term - Column definition or ordering term to order by
|
|
600
|
+
* @param directionOrOptions - Order direction or options (defaults to ASC)
|
|
601
|
+
* @returns New query builder instance with the ORDER BY clause
|
|
602
|
+
*
|
|
603
|
+
* @example
|
|
604
|
+
* qb.orderBy(userTable.columns.createdAt, 'DESC');
|
|
605
|
+
*/
|
|
606
|
+
orderBy(
|
|
607
|
+
term: ColumnDef | OrderingTerm,
|
|
608
|
+
directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
|
|
609
|
+
): SelectQueryBuilder<T, TTable> {
|
|
610
|
+
const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
|
|
611
|
+
|
|
612
|
+
return this.clone(nextContext);
|
|
613
|
+
}
|
|
677
614
|
|
|
678
615
|
/**
|
|
679
616
|
* Adds a DISTINCT clause to the query
|
|
680
617
|
* @param cols - Columns to make distinct
|
|
681
618
|
* @returns New query builder instance with the DISTINCT clause
|
|
682
619
|
*/
|
|
683
|
-
distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
|
|
684
|
-
return this.clone(this.
|
|
685
|
-
}
|
|
620
|
+
distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
|
|
621
|
+
return this.clone(this.projectionFacet.distinct(this.context, cols));
|
|
622
|
+
}
|
|
686
623
|
|
|
687
624
|
/**
|
|
688
625
|
* Adds a LIMIT clause to the query
|
|
689
626
|
* @param n - Maximum number of rows to return
|
|
690
627
|
* @returns New query builder instance with the LIMIT clause
|
|
691
628
|
*/
|
|
692
|
-
limit(n: number): SelectQueryBuilder<T, TTable> {
|
|
693
|
-
const nextContext = this.
|
|
694
|
-
return this.clone(nextContext);
|
|
695
|
-
}
|
|
629
|
+
limit(n: number): SelectQueryBuilder<T, TTable> {
|
|
630
|
+
const nextContext = this.predicateFacet.limit(this.context, n);
|
|
631
|
+
return this.clone(nextContext);
|
|
632
|
+
}
|
|
696
633
|
|
|
697
634
|
/**
|
|
698
635
|
* Adds an OFFSET clause to the query
|
|
699
636
|
* @param n - Number of rows to skip
|
|
700
637
|
* @returns New query builder instance with the OFFSET clause
|
|
701
638
|
*/
|
|
702
|
-
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
703
|
-
const nextContext = this.
|
|
704
|
-
return this.clone(nextContext);
|
|
705
|
-
}
|
|
639
|
+
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
640
|
+
const nextContext = this.predicateFacet.offset(this.context, n);
|
|
641
|
+
return this.clone(nextContext);
|
|
642
|
+
}
|
|
706
643
|
|
|
707
644
|
/**
|
|
708
645
|
* Combines this query with another using UNION
|
|
@@ -768,69 +705,61 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
768
705
|
return this.where(notExists(correlated));
|
|
769
706
|
}
|
|
770
707
|
|
|
771
|
-
/**
|
|
772
|
-
* Adds a WHERE EXISTS condition based on a relationship
|
|
773
|
-
* @param relationName - Name of the relationship to check
|
|
774
|
-
* @param callback - Optional callback to modify the relationship query
|
|
775
|
-
* @returns New query builder instance with the relationship existence check
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
|
|
799
|
-
|
|
800
|
-
return this.where(exists(finalSubAst));
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
805
|
-
* @param relationName - Name of the relationship to check
|
|
806
|
-
* @param callback - Optional callback to modify the relationship query
|
|
807
|
-
* @returns New query builder instance with the relationship non-existence check
|
|
808
|
-
*/
|
|
809
|
-
whereHasNot<K extends keyof TTable['relations'] & string>(
|
|
810
|
-
relationName: K,
|
|
811
|
-
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
812
|
-
maybeOptions?: WhereHasOptions
|
|
813
|
-
): SelectQueryBuilder<T, TTable> {
|
|
814
|
-
const relation = this.env.table.relations[relationName];
|
|
815
|
-
|
|
816
|
-
if (!relation) {
|
|
817
|
-
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions as RelationCallback : undefined;
|
|
821
|
-
const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as WhereHasOptions | undefined;
|
|
822
|
-
|
|
823
|
-
let subQb = this.createChildBuilder<unknown, typeof relation.target>(relation.target);
|
|
824
|
-
|
|
825
|
-
if (callback) {
|
|
826
|
-
subQb = callback(subQb);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const subAst = subQb.getAST();
|
|
830
|
-
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
|
|
708
|
+
/**
|
|
709
|
+
* Adds a WHERE EXISTS condition based on a relationship
|
|
710
|
+
* @param relationName - Name of the relationship to check
|
|
711
|
+
* @param callback - Optional callback to modify the relationship query
|
|
712
|
+
* @returns New query builder instance with the relationship existence check
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
|
|
716
|
+
*/
|
|
717
|
+
whereHas<K extends keyof TTable['relations'] & string>(
|
|
718
|
+
relationName: K,
|
|
719
|
+
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
720
|
+
maybeOptions?: WhereHasOptions
|
|
721
|
+
): SelectQueryBuilder<T, TTable> {
|
|
722
|
+
const predicate = buildWhereHasPredicate(
|
|
723
|
+
this.env,
|
|
724
|
+
this.context,
|
|
725
|
+
this.relationFacet,
|
|
726
|
+
table => this.createChildBuilder(table),
|
|
727
|
+
relationName,
|
|
728
|
+
callbackOrOptions,
|
|
729
|
+
maybeOptions,
|
|
730
|
+
false
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
return this.where(predicate);
|
|
734
|
+
}
|
|
831
735
|
|
|
832
|
-
|
|
833
|
-
|
|
736
|
+
/**
|
|
737
|
+
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
738
|
+
* @param relationName - Name of the relationship to check
|
|
739
|
+
* @param callback - Optional callback to modify the relationship query
|
|
740
|
+
* @returns New query builder instance with the relationship non-existence check
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
|
|
744
|
+
*/
|
|
745
|
+
whereHasNot<K extends keyof TTable['relations'] & string>(
|
|
746
|
+
relationName: K,
|
|
747
|
+
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
748
|
+
maybeOptions?: WhereHasOptions
|
|
749
|
+
): SelectQueryBuilder<T, TTable> {
|
|
750
|
+
const predicate = buildWhereHasPredicate(
|
|
751
|
+
this.env,
|
|
752
|
+
this.context,
|
|
753
|
+
this.relationFacet,
|
|
754
|
+
table => this.createChildBuilder(table),
|
|
755
|
+
relationName,
|
|
756
|
+
callbackOrOptions,
|
|
757
|
+
maybeOptions,
|
|
758
|
+
true
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
return this.where(predicate);
|
|
762
|
+
}
|
|
834
763
|
|
|
835
764
|
|
|
836
765
|
|