metal-orm 1.0.57 → 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 +23 -13
- package/dist/index.cjs +1750 -733
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +244 -157
- package/dist/index.d.ts +244 -157
- package/dist/index.js +1745 -733
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/core/ddl/schema-generator.ts +44 -1
- package/src/decorators/bootstrap.ts +186 -113
- 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 +18 -13
- 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 -343
- package/src/orm/execute.ts +87 -20
- 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 -309
- package/src/orm/relations/belongs-to.ts +2 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +2 -2
- package/src/orm/relations/many-to-many.ts +29 -14
- package/src/orm/save-graph-types.ts +2 -2
- package/src/orm/save-graph.ts +18 -18
- package/src/query-builder/relation-conditions.ts +80 -59
- 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 +103 -159
- package/src/query-builder/relation-types.ts +43 -12
- 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 +373 -426
- package/src/schema/relation.ts +22 -18
- package/src/schema/table.ts +22 -9
- package/src/schema/types.ts +103 -84
|
@@ -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 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,11 +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
|
|
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>;
|
|
79
89
|
|
|
80
90
|
/**
|
|
81
91
|
* Creates a new SelectQueryBuilder instance
|
|
@@ -89,20 +99,30 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
89
99
|
state?: SelectQueryState,
|
|
90
100
|
hydration?: HydrationManager,
|
|
91
101
|
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
92
|
-
lazyRelations?: Set<string
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
|
|
102
|
+
lazyRelations?: Set<string>,
|
|
103
|
+
lazyRelationOptions?: Map<string, RelationIncludeOptions>
|
|
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
|
+
}
|
|
106
126
|
|
|
107
127
|
/**
|
|
108
128
|
* Creates a new SelectQueryBuilder instance with updated context and lazy relations
|
|
@@ -112,24 +132,27 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
112
132
|
*/
|
|
113
133
|
private clone(
|
|
114
134
|
context: SelectQueryBuilderContext = this.context,
|
|
115
|
-
lazyRelations = new Set(this.lazyRelations)
|
|
135
|
+
lazyRelations = new Set(this.lazyRelations),
|
|
136
|
+
lazyRelationOptions = new Map(this.lazyRelationOptions)
|
|
116
137
|
): SelectQueryBuilder<T, TTable> {
|
|
117
|
-
return new SelectQueryBuilder(
|
|
138
|
+
return new SelectQueryBuilder(
|
|
139
|
+
this.env.table as TTable,
|
|
140
|
+
context.state,
|
|
141
|
+
context.hydration,
|
|
142
|
+
this.env.deps,
|
|
143
|
+
lazyRelations,
|
|
144
|
+
lazyRelationOptions
|
|
145
|
+
);
|
|
118
146
|
}
|
|
119
147
|
|
|
120
148
|
/**
|
|
121
149
|
* Applies an alias to the root FROM table.
|
|
122
150
|
* @param alias - Alias to apply
|
|
123
151
|
*/
|
|
124
|
-
as(alias: string): SelectQueryBuilder<T, TTable> {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
const nextFrom = { ...from, alias };
|
|
130
|
-
const nextContext = this.applyAst(this.context, service => service.withFrom(nextFrom));
|
|
131
|
-
return this.clone(nextContext);
|
|
132
|
-
}
|
|
152
|
+
as(alias: string): SelectQueryBuilder<T, TTable> {
|
|
153
|
+
const nextContext = this.fromFacet.as(this.context, alias);
|
|
154
|
+
return this.clone(nextContext);
|
|
155
|
+
}
|
|
133
156
|
|
|
134
157
|
|
|
135
158
|
|
|
@@ -157,52 +180,19 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
157
180
|
return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
|
|
158
181
|
}
|
|
159
182
|
|
|
160
|
-
/**
|
|
161
|
-
* Applies
|
|
162
|
-
* @param
|
|
163
|
-
* @param
|
|
164
|
-
* @returns Updated query context
|
|
165
|
-
*/
|
|
166
|
-
private
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
): SelectQueryBuilderContext {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Applies a join to the query context
|
|
177
|
-
* @param context - Current query context
|
|
178
|
-
* @param table - Table to join
|
|
179
|
-
* @param condition - Join condition
|
|
180
|
-
* @param kind - Join kind
|
|
181
|
-
* @returns Updated query context with join applied
|
|
182
|
-
*/
|
|
183
|
-
private applyJoin(
|
|
184
|
-
context: SelectQueryBuilderContext,
|
|
185
|
-
table: TableDef,
|
|
186
|
-
condition: BinaryExpressionNode,
|
|
187
|
-
kind: JoinKind
|
|
188
|
-
): SelectQueryBuilderContext {
|
|
189
|
-
const joinNode = createJoinNode(kind, { type: 'Table', name: table.name, schema: table.schema }, condition);
|
|
190
|
-
return this.applyAst(context, service => service.withJoin(joinNode));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Applies a set operation to the query
|
|
195
|
-
* @param operator - Set operation kind
|
|
196
|
-
* @param query - Query to combine with
|
|
197
|
-
* @returns Updated query context with set operation
|
|
198
|
-
*/
|
|
199
|
-
private applySetOperation<TSub extends TableDef>(
|
|
200
|
-
operator: SetOperationKind,
|
|
201
|
-
query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
202
|
-
): SelectQueryBuilderContext {
|
|
203
|
-
const subAst = resolveSelectQuery(query);
|
|
204
|
-
return this.applyAst(this.context, service => service.withSetOperation(operator, subAst));
|
|
205
|
-
}
|
|
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
|
+
}
|
|
206
196
|
|
|
207
197
|
|
|
208
198
|
/**
|
|
@@ -219,10 +209,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
219
209
|
...args: K[] | [Record<string, ColumnSelectionValue>]
|
|
220
210
|
): SelectQueryBuilder<T, TTable> {
|
|
221
211
|
// If first arg is an object (not a string), treat as projection map
|
|
222
|
-
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
|
|
223
|
-
const columns = args[0] as Record<string, ColumnSelectionValue>;
|
|
224
|
-
return this.clone(this.
|
|
225
|
-
}
|
|
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
|
+
}
|
|
226
216
|
|
|
227
217
|
// Otherwise, treat as column names
|
|
228
218
|
const cols = args as K[];
|
|
@@ -235,17 +225,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
235
225
|
selection[key] = col;
|
|
236
226
|
}
|
|
237
227
|
|
|
238
|
-
return this.clone(this.
|
|
239
|
-
}
|
|
228
|
+
return this.clone(this.projectionFacet.select(this.context, selection));
|
|
229
|
+
}
|
|
240
230
|
|
|
241
231
|
/**
|
|
242
232
|
* Selects raw column expressions
|
|
243
233
|
* @param cols - Column expressions as strings
|
|
244
234
|
* @returns New query builder instance with raw column selections
|
|
245
235
|
*/
|
|
246
|
-
selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
|
|
247
|
-
return this.clone(this.
|
|
248
|
-
}
|
|
236
|
+
selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
|
|
237
|
+
return this.clone(this.projectionFacet.selectRaw(this.context, cols));
|
|
238
|
+
}
|
|
249
239
|
|
|
250
240
|
/**
|
|
251
241
|
* Adds a Common Table Expression (CTE) to the query
|
|
@@ -254,11 +244,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
254
244
|
* @param columns - Optional column names for the CTE
|
|
255
245
|
* @returns New query builder instance with the CTE
|
|
256
246
|
*/
|
|
257
|
-
with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
258
|
-
const subAst = resolveSelectQuery(query);
|
|
259
|
-
const nextContext = this.
|
|
260
|
-
return this.clone(nextContext);
|
|
261
|
-
}
|
|
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
|
+
}
|
|
262
252
|
|
|
263
253
|
/**
|
|
264
254
|
* Adds a recursive Common Table Expression (CTE) to the query
|
|
@@ -267,11 +257,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
267
257
|
* @param columns - Optional column names for the CTE
|
|
268
258
|
* @returns New query builder instance with the recursive CTE
|
|
269
259
|
*/
|
|
270
|
-
withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
271
|
-
const subAst = resolveSelectQuery(query);
|
|
272
|
-
const nextContext = this.
|
|
273
|
-
return this.clone(nextContext);
|
|
274
|
-
}
|
|
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
|
+
}
|
|
275
265
|
|
|
276
266
|
/**
|
|
277
267
|
* Replaces the FROM clause with a derived table (subquery with alias)
|
|
@@ -280,16 +270,15 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
280
270
|
* @param columnAliases - Optional column alias list
|
|
281
271
|
* @returns New query builder instance with updated FROM
|
|
282
272
|
*/
|
|
283
|
-
fromSubquery<TSub extends TableDef>(
|
|
284
|
-
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
285
|
-
alias: string,
|
|
286
|
-
columnAliases?: string[]
|
|
287
|
-
): SelectQueryBuilder<T, TTable> {
|
|
288
|
-
const subAst = resolveSelectQuery(subquery);
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
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
|
+
}
|
|
293
282
|
|
|
294
283
|
/**
|
|
295
284
|
* Replaces the FROM clause with a function table expression.
|
|
@@ -298,16 +287,15 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
298
287
|
* @param alias - Optional alias for the function table
|
|
299
288
|
* @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
|
|
300
289
|
*/
|
|
301
|
-
fromFunctionTable(
|
|
302
|
-
name: string,
|
|
303
|
-
args: OperandNode[] = [],
|
|
304
|
-
alias?: string,
|
|
305
|
-
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
306
|
-
): SelectQueryBuilder<T, TTable> {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
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
|
+
}
|
|
311
299
|
|
|
312
300
|
/**
|
|
313
301
|
* Selects a subquery as a column
|
|
@@ -315,10 +303,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
315
303
|
* @param sub - Query builder or query node for the subquery
|
|
316
304
|
* @returns New query builder instance with the subquery selection
|
|
317
305
|
*/
|
|
318
|
-
selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
319
|
-
const query = resolveSelectQuery(sub);
|
|
320
|
-
return this.clone(this.
|
|
321
|
-
}
|
|
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
|
+
}
|
|
322
310
|
|
|
323
311
|
/**
|
|
324
312
|
* Adds a JOIN against a derived table (subquery with alias)
|
|
@@ -329,18 +317,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
329
317
|
* @param columnAliases - Optional column alias list for the derived table
|
|
330
318
|
* @returns New query builder instance with the derived-table join
|
|
331
319
|
*/
|
|
332
|
-
joinSubquery<TSub extends TableDef>(
|
|
333
|
-
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
334
|
-
alias: string,
|
|
335
|
-
condition: BinaryExpressionNode,
|
|
336
|
-
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
337
|
-
columnAliases?: string[]
|
|
338
|
-
): SelectQueryBuilder<T, TTable> {
|
|
339
|
-
const subAst = resolveSelectQuery(subquery);
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
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
|
+
}
|
|
344
331
|
|
|
345
332
|
/**
|
|
346
333
|
* Adds a join against a function table (e.g., `generate_series`) using `fnTable` internally.
|
|
@@ -351,19 +338,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
351
338
|
* @param joinKind - Kind of join (defaults to INNER)
|
|
352
339
|
* @param options - Optional metadata (lateral, ordinality, column aliases, schema)
|
|
353
340
|
*/
|
|
354
|
-
joinFunctionTable(
|
|
355
|
-
name: string,
|
|
356
|
-
args: OperandNode[] = [],
|
|
357
|
-
alias: string,
|
|
358
|
-
condition: BinaryExpressionNode,
|
|
359
|
-
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
360
|
-
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
361
|
-
): SelectQueryBuilder<T, TTable> {
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return this.clone(nextContext);
|
|
366
|
-
}
|
|
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
|
+
}
|
|
367
352
|
|
|
368
353
|
/**
|
|
369
354
|
* Adds an INNER JOIN to the query
|
|
@@ -371,10 +356,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
371
356
|
* @param condition - Join condition expression
|
|
372
357
|
* @returns New query builder instance with the INNER JOIN
|
|
373
358
|
*/
|
|
374
|
-
innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
375
|
-
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
376
|
-
return this.clone(nextContext);
|
|
377
|
-
}
|
|
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
|
+
}
|
|
378
363
|
|
|
379
364
|
/**
|
|
380
365
|
* Adds a LEFT JOIN to the query
|
|
@@ -382,10 +367,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
382
367
|
* @param condition - Join condition expression
|
|
383
368
|
* @returns New query builder instance with the LEFT JOIN
|
|
384
369
|
*/
|
|
385
|
-
leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
386
|
-
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
387
|
-
return this.clone(nextContext);
|
|
388
|
-
}
|
|
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
|
+
}
|
|
389
374
|
|
|
390
375
|
/**
|
|
391
376
|
* Adds a RIGHT JOIN to the query
|
|
@@ -393,10 +378,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
393
378
|
* @param condition - Join condition expression
|
|
394
379
|
* @returns New query builder instance with the RIGHT JOIN
|
|
395
380
|
*/
|
|
396
|
-
rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
397
|
-
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
398
|
-
return this.clone(nextContext);
|
|
399
|
-
}
|
|
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
|
+
}
|
|
400
385
|
|
|
401
386
|
/**
|
|
402
387
|
* Matches records based on a relationship
|
|
@@ -404,13 +389,13 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
404
389
|
* @param predicate - Optional predicate expression
|
|
405
390
|
* @returns New query builder instance with the relationship match
|
|
406
391
|
*/
|
|
407
|
-
match<K extends keyof TTable['relations'] & string>(
|
|
408
|
-
relationName: K,
|
|
409
|
-
predicate?: ExpressionNode
|
|
410
|
-
): SelectQueryBuilder<T, TTable> {
|
|
411
|
-
const nextContext = this.
|
|
412
|
-
return this.clone(nextContext);
|
|
413
|
-
}
|
|
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
|
+
}
|
|
414
399
|
|
|
415
400
|
/**
|
|
416
401
|
* Joins a related table
|
|
@@ -419,14 +404,14 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
419
404
|
* @param extraCondition - Optional additional join condition
|
|
420
405
|
* @returns New query builder instance with the relationship join
|
|
421
406
|
*/
|
|
422
|
-
joinRelation<K extends keyof TTable['relations'] & string>(
|
|
423
|
-
relationName: K,
|
|
424
|
-
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
425
|
-
extraCondition?: ExpressionNode
|
|
426
|
-
): SelectQueryBuilder<T, TTable> {
|
|
427
|
-
const nextContext = this.
|
|
428
|
-
return this.clone(nextContext);
|
|
429
|
-
}
|
|
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
|
+
}
|
|
430
415
|
|
|
431
416
|
/**
|
|
432
417
|
* Includes related data in the query results
|
|
@@ -434,62 +419,60 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
434
419
|
* @param options - Optional include options
|
|
435
420
|
* @returns New query builder instance with the relationship inclusion
|
|
436
421
|
*/
|
|
437
|
-
include<K extends keyof TTable['relations'] & string>(
|
|
438
|
-
relationName: K,
|
|
439
|
-
options?:
|
|
440
|
-
): SelectQueryBuilder<T, TTable> {
|
|
441
|
-
const nextContext = this.
|
|
442
|
-
return this.clone(nextContext);
|
|
443
|
-
}
|
|
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
|
+
}
|
|
444
429
|
|
|
445
430
|
/**
|
|
446
431
|
* Includes a relation lazily in the query results
|
|
447
432
|
* @param relationName - Name of the relation to include lazily
|
|
433
|
+
* @param options - Optional include options for lazy loading
|
|
448
434
|
* @returns New query builder instance with lazy relation inclusion
|
|
449
435
|
*/
|
|
450
|
-
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
436
|
+
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
437
|
+
relationName: K,
|
|
438
|
+
options?: TypedRelationIncludeOptions<TTable['relations'][K]>
|
|
439
|
+
): SelectQueryBuilder<T, TTable> {
|
|
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
|
+
}
|
|
451
455
|
const nextLazy = new Set(this.lazyRelations);
|
|
452
456
|
nextLazy.add(relationName as string);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
*/
|
|
459
|
-
selectRelationColumns<
|
|
460
|
-
K extends keyof TTable['relations'] & string,
|
|
461
|
-
TRel extends RelationDef = TTable['relations'][K],
|
|
462
|
-
TTarget extends TableDef = RelationTargetTable<TRel>,
|
|
463
|
-
C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
|
|
464
|
-
>(relationName: K, ...cols: C[]): SelectQueryBuilder<T, TTable> {
|
|
465
|
-
const relation = this.env.table.relations[relationName] as RelationDef | undefined;
|
|
466
|
-
if (!relation) {
|
|
467
|
-
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
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);
|
|
468
462
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
for (const col of cols) {
|
|
472
|
-
if (!target.columns[col]) {
|
|
473
|
-
throw new Error(
|
|
474
|
-
`Column '${col}' not found on related table '${target.name}' for relation '${relationName}'`
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
return this.include(relationName as string, { columns: cols as string[] });
|
|
463
|
+
return this.clone(nextContext, nextLazy, nextOptions);
|
|
480
464
|
}
|
|
481
465
|
|
|
482
466
|
/**
|
|
483
|
-
* Convenience alias for
|
|
467
|
+
* Convenience alias for including only specific columns from a relation.
|
|
484
468
|
*/
|
|
485
|
-
includePick<
|
|
486
|
-
K extends keyof TTable['relations'] & string,
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
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
|
+
}
|
|
493
476
|
|
|
494
477
|
|
|
495
478
|
/**
|
|
@@ -501,13 +484,14 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
501
484
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
502
485
|
let currBuilder: SelectQueryBuilder<T, TTable> = this;
|
|
503
486
|
|
|
504
|
-
for (const entry of config) {
|
|
505
|
-
if (entry.type === 'root') {
|
|
506
|
-
currBuilder = currBuilder.select(...entry.columns);
|
|
507
|
-
} else {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
+
}
|
|
511
495
|
|
|
512
496
|
return currBuilder;
|
|
513
497
|
}
|
|
@@ -520,6 +504,14 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
520
504
|
return Array.from(this.lazyRelations) as (keyof RelationMap<TTable>)[];
|
|
521
505
|
}
|
|
522
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Gets lazy relation include options
|
|
509
|
+
* @returns Map of relation names to include options
|
|
510
|
+
*/
|
|
511
|
+
getLazyRelationOptions(): Map<string, RelationIncludeOptions> {
|
|
512
|
+
return new Map(this.lazyRelationOptions);
|
|
513
|
+
}
|
|
514
|
+
|
|
523
515
|
/**
|
|
524
516
|
* Gets the table definition for this query builder
|
|
525
517
|
* @returns Table definition
|
|
@@ -537,63 +529,28 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
537
529
|
return executeHydrated(ctx, this);
|
|
538
530
|
}
|
|
539
531
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
columns: [{ type: 'Function', name: 'COUNT', args: [], alias: 'total' } as FunctionNode],
|
|
563
|
-
joins: []
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
const execCtx = session.getExecutionContext();
|
|
567
|
-
const compiled = execCtx.dialect.compileSelect(countQuery);
|
|
568
|
-
const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
569
|
-
const value = results[0]?.values?.[0]?.[0];
|
|
570
|
-
|
|
571
|
-
if (typeof value === 'number') return value;
|
|
572
|
-
if (typeof value === 'bigint') return Number(value);
|
|
573
|
-
if (typeof value === 'string') return Number(value);
|
|
574
|
-
return value === null || value === undefined ? 0 : Number(value);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
async executePaged(
|
|
578
|
-
session: OrmSession,
|
|
579
|
-
options: { page: number; pageSize: number }
|
|
580
|
-
): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
|
|
581
|
-
const { page, pageSize } = options;
|
|
582
|
-
if (!Number.isInteger(page) || page < 1) {
|
|
583
|
-
throw new Error('executePaged: page must be an integer >= 1');
|
|
584
|
-
}
|
|
585
|
-
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
586
|
-
throw new Error('executePaged: pageSize must be an integer >= 1');
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const offset = (page - 1) * pageSize;
|
|
590
|
-
const [items, totalItems] = await Promise.all([
|
|
591
|
-
this.limit(pageSize).offset(offset).execute(session),
|
|
592
|
-
this.count(session)
|
|
593
|
-
]);
|
|
594
|
-
|
|
595
|
-
return { items, totalItems };
|
|
596
|
-
}
|
|
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
|
+
}
|
|
597
554
|
|
|
598
555
|
/**
|
|
599
556
|
* Executes the query with provided execution and hydration contexts
|
|
@@ -610,81 +567,79 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
610
567
|
* @param expr - Expression for the WHERE clause
|
|
611
568
|
* @returns New query builder instance with the WHERE condition
|
|
612
569
|
*/
|
|
613
|
-
where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
614
|
-
const nextContext = this.
|
|
615
|
-
return this.clone(nextContext);
|
|
616
|
-
}
|
|
570
|
+
where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
571
|
+
const nextContext = this.predicateFacet.where(this.context, expr);
|
|
572
|
+
return this.clone(nextContext);
|
|
573
|
+
}
|
|
617
574
|
|
|
618
575
|
/**
|
|
619
576
|
* Adds a GROUP BY clause to the query
|
|
620
577
|
* @param term - Column definition or ordering term to group by
|
|
621
578
|
* @returns New query builder instance with the GROUP BY clause
|
|
622
579
|
*/
|
|
623
|
-
groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
|
|
624
|
-
const nextContext = this.
|
|
625
|
-
return this.clone(nextContext);
|
|
626
|
-
}
|
|
580
|
+
groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
|
|
581
|
+
const nextContext = this.predicateFacet.groupBy(this.context, term);
|
|
582
|
+
return this.clone(nextContext);
|
|
583
|
+
}
|
|
627
584
|
|
|
628
585
|
/**
|
|
629
586
|
* Adds a HAVING condition to the query
|
|
630
587
|
* @param expr - Expression for the HAVING clause
|
|
631
588
|
* @returns New query builder instance with the HAVING condition
|
|
632
589
|
*/
|
|
633
|
-
having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
634
|
-
const nextContext = this.
|
|
635
|
-
return this.clone(nextContext);
|
|
636
|
-
}
|
|
637
|
-
|
|
590
|
+
having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
591
|
+
const nextContext = this.predicateFacet.having(this.context, expr);
|
|
592
|
+
return this.clone(nextContext);
|
|
593
|
+
}
|
|
638
594
|
|
|
639
595
|
|
|
640
|
-
/**
|
|
641
|
-
* Adds an ORDER BY clause to the query
|
|
642
|
-
* @param term - Column definition or ordering term to order by
|
|
643
|
-
* @param directionOrOptions - Order direction or options (defaults to ASC)
|
|
644
|
-
* @returns New query builder instance with the ORDER BY clause
|
|
645
|
-
*/
|
|
646
|
-
orderBy(
|
|
647
|
-
term: ColumnDef | OrderingTerm,
|
|
648
|
-
directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
|
|
649
|
-
): SelectQueryBuilder<T, TTable> {
|
|
650
|
-
const options = typeof directionOrOptions === 'string' ? { direction: directionOrOptions } : directionOrOptions;
|
|
651
|
-
const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
|
|
652
|
-
|
|
653
|
-
const nextContext = this.applyAst(this.context, service =>
|
|
654
|
-
service.withOrderBy(term, dir, options.nulls, options.collation)
|
|
655
|
-
);
|
|
656
596
|
|
|
657
|
-
|
|
658
|
-
|
|
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
|
+
}
|
|
659
614
|
|
|
660
615
|
/**
|
|
661
616
|
* Adds a DISTINCT clause to the query
|
|
662
617
|
* @param cols - Columns to make distinct
|
|
663
618
|
* @returns New query builder instance with the DISTINCT clause
|
|
664
619
|
*/
|
|
665
|
-
distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
|
|
666
|
-
return this.clone(this.
|
|
667
|
-
}
|
|
620
|
+
distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
|
|
621
|
+
return this.clone(this.projectionFacet.distinct(this.context, cols));
|
|
622
|
+
}
|
|
668
623
|
|
|
669
624
|
/**
|
|
670
625
|
* Adds a LIMIT clause to the query
|
|
671
626
|
* @param n - Maximum number of rows to return
|
|
672
627
|
* @returns New query builder instance with the LIMIT clause
|
|
673
628
|
*/
|
|
674
|
-
limit(n: number): SelectQueryBuilder<T, TTable> {
|
|
675
|
-
const nextContext = this.
|
|
676
|
-
return this.clone(nextContext);
|
|
677
|
-
}
|
|
629
|
+
limit(n: number): SelectQueryBuilder<T, TTable> {
|
|
630
|
+
const nextContext = this.predicateFacet.limit(this.context, n);
|
|
631
|
+
return this.clone(nextContext);
|
|
632
|
+
}
|
|
678
633
|
|
|
679
634
|
/**
|
|
680
635
|
* Adds an OFFSET clause to the query
|
|
681
636
|
* @param n - Number of rows to skip
|
|
682
637
|
* @returns New query builder instance with the OFFSET clause
|
|
683
638
|
*/
|
|
684
|
-
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
685
|
-
const nextContext = this.
|
|
686
|
-
return this.clone(nextContext);
|
|
687
|
-
}
|
|
639
|
+
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
640
|
+
const nextContext = this.predicateFacet.offset(this.context, n);
|
|
641
|
+
return this.clone(nextContext);
|
|
642
|
+
}
|
|
688
643
|
|
|
689
644
|
/**
|
|
690
645
|
* Combines this query with another using UNION
|
|
@@ -750,69 +705,61 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
750
705
|
return this.where(notExists(correlated));
|
|
751
706
|
}
|
|
752
707
|
|
|
753
|
-
/**
|
|
754
|
-
* Adds a WHERE EXISTS condition based on a relationship
|
|
755
|
-
* @param relationName - Name of the relationship to check
|
|
756
|
-
* @param callback - Optional callback to modify the relationship query
|
|
757
|
-
* @returns New query builder instance with the relationship existence check
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
*
|
|
787
|
-
* @
|
|
788
|
-
*
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
subQb = callback(subQb);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const subAst = subQb.getAST();
|
|
812
|
-
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
|
|
813
|
-
|
|
814
|
-
return this.where(notExists(finalSubAst));
|
|
815
|
-
}
|
|
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
|
+
}
|
|
735
|
+
|
|
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
|
+
}
|
|
816
763
|
|
|
817
764
|
|
|
818
765
|
|