metal-orm 1.0.59 → 1.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +222 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +253 -1
- package/dist/index.d.ts +253 -1
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -7
- package/src/query-builder/select.ts +534 -355
|
@@ -2,20 +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 { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
|
|
18
|
-
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';
|
|
19
19
|
|
|
20
20
|
type SelectDialectInput = Dialect | DialectKey;
|
|
21
21
|
|
|
@@ -27,65 +27,63 @@ import {
|
|
|
27
27
|
SelectQueryBuilderDependencies,
|
|
28
28
|
SelectQueryBuilderEnvironment
|
|
29
29
|
} from './select-query-builder-deps.js';
|
|
30
|
-
import { ColumnSelector } from './column-selector.js';
|
|
31
|
-
import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
|
|
32
|
-
import { RelationKinds } 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';
|
|
33
33
|
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
|
|
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';
|
|
55
|
-
|
|
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';
|
|
56
55
|
|
|
57
56
|
type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
|
|
58
57
|
|
|
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
|
-
};
|
|
58
|
+
type DeepSelectEntry<TTable extends TableDef> = {
|
|
59
|
+
type: 'root';
|
|
60
|
+
columns: (keyof TTable['columns'] & string)[];
|
|
61
|
+
} | {
|
|
62
|
+
type: 'relation';
|
|
63
|
+
relationName: keyof TTable['relations'] & string;
|
|
64
|
+
columns: string[];
|
|
65
|
+
};
|
|
67
66
|
|
|
68
67
|
type DeepSelectConfig<TTable extends TableDef> = DeepSelectEntry<TTable>[];
|
|
69
68
|
|
|
70
|
-
|
|
71
69
|
/**
|
|
72
70
|
* Main query builder class for constructing SQL SELECT queries
|
|
73
71
|
* @typeParam T - Result type for projections (unused)
|
|
74
72
|
* @typeParam TTable - Table definition being queried
|
|
75
73
|
*/
|
|
76
74
|
export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef> {
|
|
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>;
|
|
75
|
+
private readonly env: SelectQueryBuilderEnvironment;
|
|
76
|
+
private readonly context: SelectQueryBuilderContext;
|
|
77
|
+
private readonly columnSelector: ColumnSelector;
|
|
78
|
+
private readonly fromFacet: SelectFromFacet;
|
|
79
|
+
private readonly joinFacet: SelectJoinFacet;
|
|
80
|
+
private readonly projectionFacet: SelectProjectionFacet;
|
|
81
|
+
private readonly predicateFacet: SelectPredicateFacet;
|
|
82
|
+
private readonly cteFacet: SelectCTEFacet;
|
|
83
|
+
private readonly setOpFacet: SelectSetOpFacet;
|
|
84
|
+
private readonly relationFacet: SelectRelationFacet;
|
|
85
|
+
private readonly lazyRelations: Set<string>;
|
|
86
|
+
private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
|
|
89
87
|
|
|
90
88
|
/**
|
|
91
89
|
* Creates a new SelectQueryBuilder instance
|
|
@@ -101,28 +99,28 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
101
99
|
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
102
100
|
lazyRelations?: Set<string>,
|
|
103
101
|
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
|
-
}
|
|
102
|
+
) {
|
|
103
|
+
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
104
|
+
this.env = { table, deps };
|
|
105
|
+
const createAstService = (nextState: SelectQueryState) => deps.createQueryAstService(table, nextState);
|
|
106
|
+
const initialState = state ?? deps.createState(table);
|
|
107
|
+
const initialHydration = hydration ?? deps.createHydration(table);
|
|
108
|
+
this.context = {
|
|
109
|
+
state: initialState,
|
|
110
|
+
hydration: initialHydration
|
|
111
|
+
};
|
|
112
|
+
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
113
|
+
this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
|
|
114
|
+
this.columnSelector = deps.createColumnSelector(this.env);
|
|
115
|
+
const relationManager = deps.createRelationManager(this.env);
|
|
116
|
+
this.fromFacet = new SelectFromFacet(this.env, createAstService);
|
|
117
|
+
this.joinFacet = new SelectJoinFacet(this.env, createAstService);
|
|
118
|
+
this.projectionFacet = new SelectProjectionFacet(this.columnSelector);
|
|
119
|
+
this.predicateFacet = new SelectPredicateFacet(this.env, createAstService);
|
|
120
|
+
this.cteFacet = new SelectCTEFacet(this.env, createAstService);
|
|
121
|
+
this.setOpFacet = new SelectSetOpFacet(this.env, createAstService);
|
|
122
|
+
this.relationFacet = new SelectRelationFacet(relationManager);
|
|
123
|
+
}
|
|
126
124
|
|
|
127
125
|
/**
|
|
128
126
|
* Creates a new SelectQueryBuilder instance with updated context and lazy relations
|
|
@@ -148,13 +146,13 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
148
146
|
/**
|
|
149
147
|
* Applies an alias to the root FROM table.
|
|
150
148
|
* @param alias - Alias to apply
|
|
149
|
+
* @example
|
|
150
|
+
* const qb = new SelectQueryBuilder(userTable).as('u');
|
|
151
151
|
*/
|
|
152
|
-
as(alias: string): SelectQueryBuilder<T, TTable> {
|
|
153
|
-
const nextContext = this.fromFacet.as(this.context, alias);
|
|
154
|
-
return this.clone(nextContext);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
152
|
+
as(alias: string): SelectQueryBuilder<T, TTable> {
|
|
153
|
+
const nextContext = this.fromFacet.as(this.context, alias);
|
|
154
|
+
return this.clone(nextContext);
|
|
155
|
+
}
|
|
158
156
|
|
|
159
157
|
/**
|
|
160
158
|
* Applies correlation expression to the query AST
|
|
@@ -180,26 +178,34 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
180
178
|
return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
|
|
181
179
|
}
|
|
182
180
|
|
|
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
|
-
}
|
|
196
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Applies a set operation to the query
|
|
183
|
+
* @param operator - Set operation kind
|
|
184
|
+
* @param query - Query to combine with
|
|
185
|
+
* @returns Updated query context with set operation
|
|
186
|
+
*/
|
|
187
|
+
private applySetOperation<TSub extends TableDef>(
|
|
188
|
+
operator: SetOperationKind,
|
|
189
|
+
query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
190
|
+
): SelectQueryBuilderContext {
|
|
191
|
+
const subAst = resolveSelectQuery(query);
|
|
192
|
+
return this.setOpFacet.applySetOperation(this.context, operator, subAst);
|
|
193
|
+
}
|
|
197
194
|
|
|
198
195
|
/**
|
|
199
196
|
* Selects columns for the query (unified overloaded method).
|
|
200
197
|
* Can be called with column names or a projection object.
|
|
201
198
|
* @param args - Column names or projection object
|
|
202
199
|
* @returns New query builder instance with selected columns
|
|
200
|
+
* @example
|
|
201
|
+
* // Select specific columns
|
|
202
|
+
* qb.select('id', 'name', 'email');
|
|
203
|
+
* @example
|
|
204
|
+
* // Select with aliases and expressions
|
|
205
|
+
* qb.select({
|
|
206
|
+
* id: userTable.columns.id,
|
|
207
|
+
* fullName: concat(userTable.columns.firstName, ' ', userTable.columns.lastName)
|
|
208
|
+
* });
|
|
203
209
|
*/
|
|
204
210
|
select<K extends keyof TTable['columns'] & string>(
|
|
205
211
|
...args: K[]
|
|
@@ -209,10 +215,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
209
215
|
...args: K[] | [Record<string, ColumnSelectionValue>]
|
|
210
216
|
): SelectQueryBuilder<T, TTable> {
|
|
211
217
|
// If first arg is an object (not a string), treat as projection map
|
|
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
|
-
}
|
|
218
|
+
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
|
|
219
|
+
const columns = args[0] as Record<string, ColumnSelectionValue>;
|
|
220
|
+
return this.clone(this.projectionFacet.select(this.context, columns));
|
|
221
|
+
}
|
|
216
222
|
|
|
217
223
|
// Otherwise, treat as column names
|
|
218
224
|
const cols = args as K[];
|
|
@@ -225,17 +231,19 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
225
231
|
selection[key] = col;
|
|
226
232
|
}
|
|
227
233
|
|
|
228
|
-
return this.clone(this.projectionFacet.select(this.context, selection));
|
|
229
|
-
}
|
|
234
|
+
return this.clone(this.projectionFacet.select(this.context, selection));
|
|
235
|
+
}
|
|
230
236
|
|
|
231
237
|
/**
|
|
232
238
|
* Selects raw column expressions
|
|
233
239
|
* @param cols - Column expressions as strings
|
|
234
240
|
* @returns New query builder instance with raw column selections
|
|
241
|
+
* @example
|
|
242
|
+
* qb.selectRaw('COUNT(*) as total', 'UPPER(name) as upper_name');
|
|
235
243
|
*/
|
|
236
|
-
selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
|
|
237
|
-
return this.clone(this.projectionFacet.selectRaw(this.context, cols));
|
|
238
|
-
}
|
|
244
|
+
selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
|
|
245
|
+
return this.clone(this.projectionFacet.selectRaw(this.context, cols));
|
|
246
|
+
}
|
|
239
247
|
|
|
240
248
|
/**
|
|
241
249
|
* Adds a Common Table Expression (CTE) to the query
|
|
@@ -243,12 +251,18 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
243
251
|
* @param query - Query builder or query node for the CTE
|
|
244
252
|
* @param columns - Optional column names for the CTE
|
|
245
253
|
* @returns New query builder instance with the CTE
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
254
|
+
* @example
|
|
255
|
+
* const recentUsers = new SelectQueryBuilder(userTable)
|
|
256
|
+
* .where(gt(userTable.columns.createdAt, subDays(now(), 30)));
|
|
257
|
+
* const qb = new SelectQueryBuilder(userTable)
|
|
258
|
+
* .with('recent_users', recentUsers)
|
|
259
|
+
* .from('recent_users');
|
|
260
|
+
*/
|
|
261
|
+
with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
262
|
+
const subAst = resolveSelectQuery(query);
|
|
263
|
+
const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
|
|
264
|
+
return this.clone(nextContext);
|
|
265
|
+
}
|
|
252
266
|
|
|
253
267
|
/**
|
|
254
268
|
* Adds a recursive Common Table Expression (CTE) to the query
|
|
@@ -256,12 +270,25 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
256
270
|
* @param query - Query builder or query node for the CTE
|
|
257
271
|
* @param columns - Optional column names for the CTE
|
|
258
272
|
* @returns New query builder instance with the recursive CTE
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
273
|
+
* @example
|
|
274
|
+
* // Base case: select root nodes
|
|
275
|
+
* const baseQuery = new SelectQueryBuilder(orgTable)
|
|
276
|
+
* .where(eq(orgTable.columns.parentId, 1));
|
|
277
|
+
* // Recursive case: join with the CTE itself
|
|
278
|
+
* const recursiveQuery = new SelectQueryBuilder(orgTable)
|
|
279
|
+
* .join('org_hierarchy', 'oh', eq(orgTable.columns.parentId, col('oh.id')));
|
|
280
|
+
* // Combine base and recursive parts
|
|
281
|
+
* const orgHierarchy = baseQuery.union(recursiveQuery);
|
|
282
|
+
* // Use in main query
|
|
283
|
+
* const qb = new SelectQueryBuilder(orgTable)
|
|
284
|
+
* .withRecursive('org_hierarchy', orgHierarchy)
|
|
285
|
+
* .from('org_hierarchy');
|
|
286
|
+
*/
|
|
287
|
+
withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
|
|
288
|
+
const subAst = resolveSelectQuery(query);
|
|
289
|
+
const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
|
|
290
|
+
return this.clone(nextContext);
|
|
291
|
+
}
|
|
265
292
|
|
|
266
293
|
/**
|
|
267
294
|
* Replaces the FROM clause with a derived table (subquery with alias)
|
|
@@ -269,16 +296,21 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
269
296
|
* @param alias - Alias for the derived table
|
|
270
297
|
* @param columnAliases - Optional column alias list
|
|
271
298
|
* @returns New query builder instance with updated FROM
|
|
299
|
+
* @example
|
|
300
|
+
* const subquery = new SelectQueryBuilder(userTable)
|
|
301
|
+
* .select('id', 'name')
|
|
302
|
+
* .where(gt(userTable.columns.score, 100));
|
|
303
|
+
* qb.fromSubquery(subquery, 'high_scorers', ['userId', 'userName']);
|
|
272
304
|
*/
|
|
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
|
-
}
|
|
305
|
+
fromSubquery<TSub extends TableDef>(
|
|
306
|
+
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
307
|
+
alias: string,
|
|
308
|
+
columnAliases?: string[]
|
|
309
|
+
): SelectQueryBuilder<T, TTable> {
|
|
310
|
+
const subAst = resolveSelectQuery(subquery);
|
|
311
|
+
const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
|
|
312
|
+
return this.clone(nextContext);
|
|
313
|
+
}
|
|
282
314
|
|
|
283
315
|
/**
|
|
284
316
|
* Replaces the FROM clause with a function table expression.
|
|
@@ -286,27 +318,40 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
286
318
|
* @param args - Optional function arguments
|
|
287
319
|
* @param alias - Optional alias for the function table
|
|
288
320
|
* @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
321
|
+
* @example
|
|
322
|
+
* qb.fromFunctionTable(
|
|
323
|
+
* 'generate_series',
|
|
324
|
+
* [literal(1), literal(10), literal(1)],
|
|
325
|
+
* 'series',
|
|
326
|
+
* { columnAliases: ['value'] }
|
|
327
|
+
* );
|
|
328
|
+
*/
|
|
329
|
+
fromFunctionTable(
|
|
330
|
+
name: string,
|
|
331
|
+
args: OperandNode[] = [],
|
|
332
|
+
alias?: string,
|
|
333
|
+
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
334
|
+
): SelectQueryBuilder<T, TTable> {
|
|
335
|
+
const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
|
|
336
|
+
return this.clone(nextContext);
|
|
337
|
+
}
|
|
299
338
|
|
|
300
339
|
/**
|
|
301
340
|
* Selects a subquery as a column
|
|
302
341
|
* @param alias - Alias for the subquery column
|
|
303
342
|
* @param sub - Query builder or query node for the subquery
|
|
304
343
|
* @returns New query builder instance with the subquery selection
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
344
|
+
* @example
|
|
345
|
+
* const postCount = new SelectQueryBuilder(postTable)
|
|
346
|
+
* .select(count(postTable.columns.id))
|
|
347
|
+
* .where(eq(postTable.columns.userId, col('u.id')));
|
|
348
|
+
* qb.select('id', 'name')
|
|
349
|
+
* .selectSubquery('postCount', postCount);
|
|
350
|
+
*/
|
|
351
|
+
selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
352
|
+
const query = resolveSelectQuery(sub);
|
|
353
|
+
return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
|
|
354
|
+
}
|
|
310
355
|
|
|
311
356
|
/**
|
|
312
357
|
* Adds a JOIN against a derived table (subquery with alias)
|
|
@@ -316,18 +361,27 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
316
361
|
* @param joinKind - Join kind (defaults to INNER)
|
|
317
362
|
* @param columnAliases - Optional column alias list for the derived table
|
|
318
363
|
* @returns New query builder instance with the derived-table join
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
364
|
+
* @example
|
|
365
|
+
* const activeUsers = new SelectQueryBuilder(userTable)
|
|
366
|
+
* .where(eq(userTable.columns.active, true));
|
|
367
|
+
* qb.joinSubquery(
|
|
368
|
+
* activeUsers,
|
|
369
|
+
* 'au',
|
|
370
|
+
* eq(col('t.userId'), col('au.id')),
|
|
371
|
+
* JOIN_KINDS.LEFT
|
|
372
|
+
* );
|
|
373
|
+
*/
|
|
374
|
+
joinSubquery<TSub extends TableDef>(
|
|
375
|
+
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
376
|
+
alias: string,
|
|
377
|
+
condition: BinaryExpressionNode,
|
|
378
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
379
|
+
columnAliases?: string[]
|
|
380
|
+
): SelectQueryBuilder<T, TTable> {
|
|
381
|
+
const subAst = resolveSelectQuery(subquery);
|
|
382
|
+
const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
|
|
383
|
+
return this.clone(nextContext);
|
|
384
|
+
}
|
|
331
385
|
|
|
332
386
|
/**
|
|
333
387
|
* Adds a join against a function table (e.g., `generate_series`) using `fnTable` internally.
|
|
@@ -337,65 +391,91 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
337
391
|
* @param condition - Join condition expression
|
|
338
392
|
* @param joinKind - Kind of join (defaults to INNER)
|
|
339
393
|
* @param options - Optional metadata (lateral, ordinality, column aliases, schema)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
394
|
+
* @example
|
|
395
|
+
* qb.joinFunctionTable(
|
|
396
|
+
* 'generate_series',
|
|
397
|
+
* [literal(1), literal(10)],
|
|
398
|
+
* 'gs',
|
|
399
|
+
* eq(col('t.value'), col('gs.value')),
|
|
400
|
+
* JOIN_KINDS.INNER,
|
|
401
|
+
* { columnAliases: ['value'] }
|
|
402
|
+
* );
|
|
403
|
+
*/
|
|
404
|
+
joinFunctionTable(
|
|
405
|
+
name: string,
|
|
406
|
+
args: OperandNode[] = [],
|
|
407
|
+
alias: string,
|
|
408
|
+
condition: BinaryExpressionNode,
|
|
409
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
410
|
+
options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
|
|
411
|
+
): SelectQueryBuilder<T, TTable> {
|
|
412
|
+
const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
|
|
413
|
+
return this.clone(nextContext);
|
|
414
|
+
}
|
|
352
415
|
|
|
353
416
|
/**
|
|
354
417
|
* Adds an INNER JOIN to the query
|
|
355
418
|
* @param table - Table to join
|
|
356
419
|
* @param condition - Join condition expression
|
|
357
420
|
* @returns New query builder instance with the INNER JOIN
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
421
|
+
* @example
|
|
422
|
+
* qb.innerJoin(
|
|
423
|
+
* postTable,
|
|
424
|
+
* eq(userTable.columns.id, postTable.columns.userId)
|
|
425
|
+
* );
|
|
426
|
+
*/
|
|
427
|
+
innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
428
|
+
const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
429
|
+
return this.clone(nextContext);
|
|
430
|
+
}
|
|
363
431
|
|
|
364
432
|
/**
|
|
365
433
|
* Adds a LEFT JOIN to the query
|
|
366
434
|
* @param table - Table to join
|
|
367
435
|
* @param condition - Join condition expression
|
|
368
436
|
* @returns New query builder instance with the LEFT JOIN
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
437
|
+
* @example
|
|
438
|
+
* qb.leftJoin(
|
|
439
|
+
* postTable,
|
|
440
|
+
* eq(userTable.columns.id, postTable.columns.userId)
|
|
441
|
+
* );
|
|
442
|
+
*/
|
|
443
|
+
leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
444
|
+
const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
445
|
+
return this.clone(nextContext);
|
|
446
|
+
}
|
|
374
447
|
|
|
375
448
|
/**
|
|
376
449
|
* Adds a RIGHT JOIN to the query
|
|
377
450
|
* @param table - Table to join
|
|
378
451
|
* @param condition - Join condition expression
|
|
379
452
|
* @returns New query builder instance with the RIGHT JOIN
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
453
|
+
* @example
|
|
454
|
+
* qb.rightJoin(
|
|
455
|
+
* postTable,
|
|
456
|
+
* eq(userTable.columns.id, postTable.columns.userId)
|
|
457
|
+
* );
|
|
458
|
+
*/
|
|
459
|
+
rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
460
|
+
const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
461
|
+
return this.clone(nextContext);
|
|
462
|
+
}
|
|
385
463
|
|
|
386
464
|
/**
|
|
387
465
|
* Matches records based on a relationship
|
|
388
466
|
* @param relationName - Name of the relationship to match
|
|
389
467
|
* @param predicate - Optional predicate expression
|
|
390
468
|
* @returns New query builder instance with the relationship match
|
|
469
|
+
* @example
|
|
470
|
+
* qb.match('posts', eq(postTable.columns.published, true));
|
|
391
471
|
*/
|
|
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
|
-
}
|
|
472
|
+
match<K extends keyof TTable['relations'] & string>(
|
|
473
|
+
relationName: K,
|
|
474
|
+
predicate?: ExpressionNode
|
|
475
|
+
): SelectQueryBuilder<T, TTable> {
|
|
476
|
+
const nextContext = this.relationFacet.match(this.context, relationName, predicate);
|
|
477
|
+
return this.clone(nextContext);
|
|
478
|
+
}
|
|
399
479
|
|
|
400
480
|
/**
|
|
401
481
|
* Joins a related table
|
|
@@ -403,40 +483,58 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
403
483
|
* @param joinKind - Type of join (defaults to INNER)
|
|
404
484
|
* @param extraCondition - Optional additional join condition
|
|
405
485
|
* @returns New query builder instance with the relationship join
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
486
|
+
* @example
|
|
487
|
+
* qb.joinRelation('posts', JOIN_KINDS.LEFT);
|
|
488
|
+
* @example
|
|
489
|
+
* qb.joinRelation('posts', JOIN_KINDS.INNER, eq(postTable.columns.published, true));
|
|
490
|
+
*/
|
|
491
|
+
joinRelation<K extends keyof TTable['relations'] & string>(
|
|
492
|
+
relationName: K,
|
|
493
|
+
joinKind: JoinKind = JOIN_KINDS.INNER,
|
|
494
|
+
extraCondition?: ExpressionNode
|
|
495
|
+
): SelectQueryBuilder<T, TTable> {
|
|
496
|
+
const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
497
|
+
return this.clone(nextContext);
|
|
498
|
+
}
|
|
415
499
|
|
|
416
500
|
/**
|
|
417
501
|
* Includes related data in the query results
|
|
418
502
|
* @param relationName - Name of the relationship to include
|
|
419
503
|
* @param options - Optional include options
|
|
420
504
|
* @returns New query builder instance with the relationship inclusion
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
505
|
+
* @example
|
|
506
|
+
* qb.include('posts');
|
|
507
|
+
* @example
|
|
508
|
+
* qb.include('posts', { columns: ['id', 'title', 'published'] });
|
|
509
|
+
* @example
|
|
510
|
+
* qb.include('posts', {
|
|
511
|
+
* columns: ['id', 'title'],
|
|
512
|
+
* where: eq(postTable.columns.published, true)
|
|
513
|
+
* });
|
|
514
|
+
*/
|
|
515
|
+
include<K extends keyof TTable['relations'] & string>(
|
|
516
|
+
relationName: K,
|
|
517
|
+
options?: TypedRelationIncludeOptions<TTable['relations'][K]>
|
|
518
|
+
): SelectQueryBuilder<T, TTable> {
|
|
519
|
+
const nextContext = this.relationFacet.include(this.context, relationName, options);
|
|
520
|
+
return this.clone(nextContext);
|
|
521
|
+
}
|
|
429
522
|
|
|
430
523
|
/**
|
|
431
524
|
* Includes a relation lazily in the query results
|
|
432
525
|
* @param relationName - Name of the relation to include lazily
|
|
433
526
|
* @param options - Optional include options for lazy loading
|
|
434
527
|
* @returns New query builder instance with lazy relation inclusion
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
528
|
+
* @example
|
|
529
|
+
* const qb = new SelectQueryBuilder(userTable).includeLazy('posts');
|
|
530
|
+
* const users = await qb.execute(session);
|
|
531
|
+
* // Access posts later - they will be loaded on demand
|
|
532
|
+
* const posts = await users[0].posts;
|
|
533
|
+
*/
|
|
534
|
+
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
535
|
+
relationName: K,
|
|
536
|
+
options?: TypedRelationIncludeOptions<TTable['relations'][K]>
|
|
537
|
+
): SelectQueryBuilder<T, TTable> {
|
|
440
538
|
let nextContext = this.context;
|
|
441
539
|
const relation = this.env.table.relations[relationName as string];
|
|
442
540
|
if (relation?.type === RelationKinds.BelongsTo) {
|
|
@@ -465,33 +563,39 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
465
563
|
|
|
466
564
|
/**
|
|
467
565
|
* Convenience alias for including only specific columns from a relation.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
566
|
+
* @example
|
|
567
|
+
* qb.includePick('posts', ['id', 'title', 'createdAt']);
|
|
568
|
+
*/
|
|
569
|
+
includePick<
|
|
570
|
+
K extends keyof TTable['relations'] & string,
|
|
571
|
+
C extends RelationTargetColumns<TTable['relations'][K]>
|
|
572
|
+
>(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
|
|
573
|
+
const options = { columns: cols as readonly C[] } as unknown as TypedRelationIncludeOptions<TTable['relations'][K]>;
|
|
574
|
+
return this.include(relationName, options);
|
|
575
|
+
}
|
|
477
576
|
|
|
478
577
|
/**
|
|
479
578
|
* Selects columns for the root table and relations from an array of entries
|
|
480
579
|
* @param config - Configuration array for deep column selection
|
|
481
580
|
* @returns New query builder instance with deep column selections
|
|
581
|
+
* @example
|
|
582
|
+
* qb.selectColumnsDeep([
|
|
583
|
+
* { type: 'root', columns: ['id', 'name'] },
|
|
584
|
+
* { type: 'relation', relationName: 'posts', columns: ['id', 'title'] }
|
|
585
|
+
* ]);
|
|
482
586
|
*/
|
|
483
587
|
selectColumnsDeep(config: DeepSelectConfig<TTable>): SelectQueryBuilder<T, TTable> {
|
|
484
588
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
485
589
|
let currBuilder: SelectQueryBuilder<T, TTable> = this;
|
|
486
590
|
|
|
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
|
-
}
|
|
591
|
+
for (const entry of config) {
|
|
592
|
+
if (entry.type === 'root') {
|
|
593
|
+
currBuilder = currBuilder.select(...entry.columns);
|
|
594
|
+
} else {
|
|
595
|
+
const options = { columns: entry.columns } as unknown as TypedRelationIncludeOptions<TTable['relations'][typeof entry.relationName]>;
|
|
596
|
+
currBuilder = currBuilder.include(entry.relationName, options);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
495
599
|
|
|
496
600
|
return currBuilder;
|
|
497
601
|
}
|
|
@@ -524,39 +628,47 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
524
628
|
* Executes the query and returns hydrated results
|
|
525
629
|
* @param ctx - ORM session context
|
|
526
630
|
* @returns Promise of entity instances
|
|
631
|
+
* @example
|
|
632
|
+
* const users = await qb.select('id', 'name')
|
|
633
|
+
* .where(eq(userTable.columns.active, true))
|
|
634
|
+
* .execute(session);
|
|
527
635
|
*/
|
|
528
636
|
async execute(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
|
|
529
637
|
return executeHydrated(ctx, this);
|
|
530
638
|
}
|
|
531
639
|
|
|
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
|
-
}
|
|
640
|
+
/**
|
|
641
|
+
* Executes a count query for the current builder without LIMIT/OFFSET clauses.
|
|
642
|
+
*
|
|
643
|
+
* @example
|
|
644
|
+
* const total = await qb.count(session);
|
|
645
|
+
*/
|
|
646
|
+
async count(session: OrmSession): Promise<number> {
|
|
647
|
+
return executeCount(this.context, this.env, session);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Executes the query and returns both the paged items and the total.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
|
|
655
|
+
*/
|
|
656
|
+
async executePaged(
|
|
657
|
+
session: OrmSession,
|
|
658
|
+
options: { page: number; pageSize: number }
|
|
659
|
+
): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
|
|
660
|
+
return executePagedQuery(this, session, options, sess => this.count(sess));
|
|
661
|
+
}
|
|
554
662
|
|
|
555
663
|
/**
|
|
556
664
|
* Executes the query with provided execution and hydration contexts
|
|
557
665
|
* @param execCtx - Execution context
|
|
558
666
|
* @param hydCtx - Hydration context
|
|
559
667
|
* @returns Promise of entity instances
|
|
668
|
+
* @example
|
|
669
|
+
* const execCtx = new ExecutionContext(session);
|
|
670
|
+
* const hydCtx = new HydrationContext();
|
|
671
|
+
* const users = await qb.executeWithContexts(execCtx, hydCtx);
|
|
560
672
|
*/
|
|
561
673
|
async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<EntityInstance<TTable>[]> {
|
|
562
674
|
return executeHydratedWithContexts(execCtx, hydCtx, this);
|
|
@@ -566,85 +678,115 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
566
678
|
* Adds a WHERE condition to the query
|
|
567
679
|
* @param expr - Expression for the WHERE clause
|
|
568
680
|
* @returns New query builder instance with the WHERE condition
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
681
|
+
* @example
|
|
682
|
+
* qb.where(eq(userTable.columns.id, 1));
|
|
683
|
+
* @example
|
|
684
|
+
* qb.where(and(
|
|
685
|
+
* eq(userTable.columns.active, true),
|
|
686
|
+
* gt(userTable.columns.createdAt, subDays(now(), 30))
|
|
687
|
+
* ));
|
|
688
|
+
*/
|
|
689
|
+
where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
690
|
+
const nextContext = this.predicateFacet.where(this.context, expr);
|
|
691
|
+
return this.clone(nextContext);
|
|
692
|
+
}
|
|
574
693
|
|
|
575
694
|
/**
|
|
576
695
|
* Adds a GROUP BY clause to the query
|
|
577
696
|
* @param term - Column definition or ordering term to group by
|
|
578
697
|
* @returns New query builder instance with the GROUP BY clause
|
|
698
|
+
* @example
|
|
699
|
+
* qb.select('departmentId', count(userTable.columns.id))
|
|
700
|
+
* .groupBy(userTable.columns.departmentId);
|
|
579
701
|
*/
|
|
580
|
-
groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
|
|
581
|
-
const nextContext = this.predicateFacet.groupBy(this.context, term);
|
|
582
|
-
return this.clone(nextContext);
|
|
583
|
-
}
|
|
702
|
+
groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
|
|
703
|
+
const nextContext = this.predicateFacet.groupBy(this.context, term);
|
|
704
|
+
return this.clone(nextContext);
|
|
705
|
+
}
|
|
584
706
|
|
|
585
707
|
/**
|
|
586
708
|
* Adds a HAVING condition to the query
|
|
587
709
|
* @param expr - Expression for the HAVING clause
|
|
588
710
|
* @returns New query builder instance with the HAVING condition
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
711
|
+
* @example
|
|
712
|
+
* qb.select('departmentId', count(userTable.columns.id))
|
|
713
|
+
* .groupBy(userTable.columns.departmentId)
|
|
714
|
+
* .having(gt(count(userTable.columns.id), 5));
|
|
715
|
+
*/
|
|
716
|
+
having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
|
|
717
|
+
const nextContext = this.predicateFacet.having(this.context, expr);
|
|
718
|
+
return this.clone(nextContext);
|
|
719
|
+
}
|
|
595
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Adds an ORDER BY clause to the query
|
|
723
|
+
* @param term - Column definition or ordering term to order by
|
|
724
|
+
* @param directionOrOptions - Order direction or options (defaults to ASC)
|
|
725
|
+
* @returns New query builder instance with the ORDER BY clause
|
|
726
|
+
*
|
|
727
|
+
* @example
|
|
728
|
+
* qb.orderBy(userTable.columns.createdAt, 'DESC');
|
|
729
|
+
*/
|
|
730
|
+
orderBy(
|
|
731
|
+
term: ColumnDef | OrderingTerm,
|
|
732
|
+
directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
|
|
733
|
+
): SelectQueryBuilder<T, TTable> {
|
|
734
|
+
const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
|
|
596
735
|
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
}
|
|
736
|
+
return this.clone(nextContext);
|
|
737
|
+
}
|
|
614
738
|
|
|
615
739
|
/**
|
|
616
740
|
* Adds a DISTINCT clause to the query
|
|
617
741
|
* @param cols - Columns to make distinct
|
|
618
742
|
* @returns New query builder instance with the DISTINCT clause
|
|
743
|
+
* @example
|
|
744
|
+
* qb.distinct(userTable.columns.email);
|
|
745
|
+
* @example
|
|
746
|
+
* qb.distinct(userTable.columns.firstName, userTable.columns.lastName);
|
|
619
747
|
*/
|
|
620
|
-
distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
|
|
621
|
-
return this.clone(this.projectionFacet.distinct(this.context, cols));
|
|
622
|
-
}
|
|
748
|
+
distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
|
|
749
|
+
return this.clone(this.projectionFacet.distinct(this.context, cols));
|
|
750
|
+
}
|
|
623
751
|
|
|
624
752
|
/**
|
|
625
753
|
* Adds a LIMIT clause to the query
|
|
626
754
|
* @param n - Maximum number of rows to return
|
|
627
755
|
* @returns New query builder instance with the LIMIT clause
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
756
|
+
* @example
|
|
757
|
+
* qb.limit(10);
|
|
758
|
+
* @example
|
|
759
|
+
* qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
|
|
760
|
+
*/
|
|
761
|
+
limit(n: number): SelectQueryBuilder<T, TTable> {
|
|
762
|
+
const nextContext = this.predicateFacet.limit(this.context, n);
|
|
763
|
+
return this.clone(nextContext);
|
|
764
|
+
}
|
|
633
765
|
|
|
634
766
|
/**
|
|
635
767
|
* Adds an OFFSET clause to the query
|
|
636
768
|
* @param n - Number of rows to skip
|
|
637
769
|
* @returns New query builder instance with the OFFSET clause
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
770
|
+
* @example
|
|
771
|
+
* qb.offset(10);
|
|
772
|
+
* @example
|
|
773
|
+
* qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
|
|
774
|
+
*/
|
|
775
|
+
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
776
|
+
const nextContext = this.predicateFacet.offset(this.context, n);
|
|
777
|
+
return this.clone(nextContext);
|
|
778
|
+
}
|
|
643
779
|
|
|
644
780
|
/**
|
|
645
781
|
* Combines this query with another using UNION
|
|
646
782
|
* @param query - Query to union with
|
|
647
783
|
* @returns New query builder instance with the set operation
|
|
784
|
+
* @example
|
|
785
|
+
* const activeUsers = new SelectQueryBuilder(userTable)
|
|
786
|
+
* .where(eq(userTable.columns.active, true));
|
|
787
|
+
* const inactiveUsers = new SelectQueryBuilder(userTable)
|
|
788
|
+
* .where(eq(userTable.columns.active, false));
|
|
789
|
+
* qb.union(activeUsers).union(inactiveUsers);
|
|
648
790
|
*/
|
|
649
791
|
union<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
650
792
|
return this.clone(this.applySetOperation('UNION', query));
|
|
@@ -654,6 +796,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
654
796
|
* Combines this query with another using UNION ALL
|
|
655
797
|
* @param query - Query to union with
|
|
656
798
|
* @returns New query builder instance with the set operation
|
|
799
|
+
* @example
|
|
800
|
+
* const q1 = new SelectQueryBuilder(userTable).where(gt(userTable.columns.score, 80));
|
|
801
|
+
* const q2 = new SelectQueryBuilder(userTable).where(lt(userTable.columns.score, 20));
|
|
802
|
+
* qb.unionAll(q1).unionAll(q2);
|
|
657
803
|
*/
|
|
658
804
|
unionAll<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
659
805
|
return this.clone(this.applySetOperation('UNION ALL', query));
|
|
@@ -663,6 +809,12 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
663
809
|
* Combines this query with another using INTERSECT
|
|
664
810
|
* @param query - Query to intersect with
|
|
665
811
|
* @returns New query builder instance with the set operation
|
|
812
|
+
* @example
|
|
813
|
+
* const activeUsers = new SelectQueryBuilder(userTable)
|
|
814
|
+
* .where(eq(userTable.columns.active, true));
|
|
815
|
+
* const premiumUsers = new SelectQueryBuilder(userTable)
|
|
816
|
+
* .where(eq(userTable.columns.premium, true));
|
|
817
|
+
* qb.intersect(activeUsers).intersect(premiumUsers);
|
|
666
818
|
*/
|
|
667
819
|
intersect<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
668
820
|
return this.clone(this.applySetOperation('INTERSECT', query));
|
|
@@ -672,6 +824,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
672
824
|
* Combines this query with another using EXCEPT
|
|
673
825
|
* @param query - Query to subtract
|
|
674
826
|
* @returns New query builder instance with the set operation
|
|
827
|
+
* @example
|
|
828
|
+
* const allUsers = new SelectQueryBuilder(userTable);
|
|
829
|
+
* const inactiveUsers = new SelectQueryBuilder(userTable)
|
|
830
|
+
* .where(eq(userTable.columns.active, false));
|
|
831
|
+
* qb.except(allUsers).except(inactiveUsers); // Only active users
|
|
675
832
|
*/
|
|
676
833
|
except<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
677
834
|
return this.clone(this.applySetOperation('EXCEPT', query));
|
|
@@ -681,6 +838,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
681
838
|
* Adds a WHERE EXISTS condition to the query
|
|
682
839
|
* @param subquery - Subquery to check for existence
|
|
683
840
|
* @returns New query builder instance with the WHERE EXISTS condition
|
|
841
|
+
* @example
|
|
842
|
+
* const postsQuery = new SelectQueryBuilder(postTable)
|
|
843
|
+
* .where(eq(postTable.columns.userId, col('u.id')));
|
|
844
|
+
* qb.whereExists(postsQuery);
|
|
684
845
|
*/
|
|
685
846
|
whereExists<TSub extends TableDef>(
|
|
686
847
|
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
@@ -695,6 +856,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
695
856
|
* Adds a WHERE NOT EXISTS condition to the query
|
|
696
857
|
* @param subquery - Subquery to check for non-existence
|
|
697
858
|
* @returns New query builder instance with the WHERE NOT EXISTS condition
|
|
859
|
+
* @example
|
|
860
|
+
* const postsQuery = new SelectQueryBuilder(postTable)
|
|
861
|
+
* .where(eq(postTable.columns.userId, col('u.id')));
|
|
862
|
+
* qb.whereNotExists(postsQuery); // Users without posts
|
|
698
863
|
*/
|
|
699
864
|
whereNotExists<TSub extends TableDef>(
|
|
700
865
|
subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
|
|
@@ -705,68 +870,71 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
705
870
|
return this.where(notExists(correlated));
|
|
706
871
|
}
|
|
707
872
|
|
|
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
|
-
}
|
|
873
|
+
/**
|
|
874
|
+
* Adds a WHERE EXISTS condition based on a relationship
|
|
875
|
+
* @param relationName - Name of the relationship to check
|
|
876
|
+
* @param callback - Optional callback to modify the relationship query
|
|
877
|
+
* @returns New query builder instance with the relationship existence check
|
|
878
|
+
*
|
|
879
|
+
* @example
|
|
880
|
+
* qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
|
|
881
|
+
*/
|
|
882
|
+
whereHas<K extends keyof TTable['relations'] & string>(
|
|
883
|
+
relationName: K,
|
|
884
|
+
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
885
|
+
maybeOptions?: WhereHasOptions
|
|
886
|
+
): SelectQueryBuilder<T, TTable> {
|
|
887
|
+
const predicate = buildWhereHasPredicate(
|
|
888
|
+
this.env,
|
|
889
|
+
this.context,
|
|
890
|
+
this.relationFacet,
|
|
891
|
+
table => this.createChildBuilder(table),
|
|
892
|
+
relationName,
|
|
893
|
+
callbackOrOptions,
|
|
894
|
+
maybeOptions,
|
|
895
|
+
false
|
|
896
|
+
);
|
|
763
897
|
|
|
898
|
+
return this.where(predicate);
|
|
899
|
+
}
|
|
764
900
|
|
|
901
|
+
/**
|
|
902
|
+
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
903
|
+
* @param relationName - Name of the relationship to check
|
|
904
|
+
* @param callback - Optional callback to modify the relationship query
|
|
905
|
+
* @returns New query builder instance with the relationship non-existence check
|
|
906
|
+
*
|
|
907
|
+
* @example
|
|
908
|
+
* qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
|
|
909
|
+
*/
|
|
910
|
+
whereHasNot<K extends keyof TTable['relations'] & string>(
|
|
911
|
+
relationName: K,
|
|
912
|
+
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
913
|
+
maybeOptions?: WhereHasOptions
|
|
914
|
+
): SelectQueryBuilder<T, TTable> {
|
|
915
|
+
const predicate = buildWhereHasPredicate(
|
|
916
|
+
this.env,
|
|
917
|
+
this.context,
|
|
918
|
+
this.relationFacet,
|
|
919
|
+
table => this.createChildBuilder(table),
|
|
920
|
+
relationName,
|
|
921
|
+
callbackOrOptions,
|
|
922
|
+
maybeOptions,
|
|
923
|
+
true
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
return this.where(predicate);
|
|
927
|
+
}
|
|
765
928
|
|
|
766
929
|
/**
|
|
767
930
|
* Compiles the query to SQL for a specific dialect
|
|
768
931
|
* @param dialect - Database dialect to compile for
|
|
769
932
|
* @returns Compiled query with SQL and parameters
|
|
933
|
+
* @example
|
|
934
|
+
* const compiled = qb.select('id', 'name')
|
|
935
|
+
* .where(eq(userTable.columns.active, true))
|
|
936
|
+
* .compile('postgres');
|
|
937
|
+
* console.log(compiled.sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
|
|
770
938
|
*/
|
|
771
939
|
compile(dialect: SelectDialectInput): CompiledQuery {
|
|
772
940
|
const resolved = resolveDialectInput(dialect);
|
|
@@ -777,6 +945,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
777
945
|
* Converts the query to SQL string for a specific dialect
|
|
778
946
|
* @param dialect - Database dialect to generate SQL for
|
|
779
947
|
* @returns SQL string representation of the query
|
|
948
|
+
* @example
|
|
949
|
+
* const sql = qb.select('id', 'name')
|
|
950
|
+
* .where(eq(userTable.columns.active, true))
|
|
951
|
+
* .toSql('postgres');
|
|
952
|
+
* console.log(sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
|
|
780
953
|
*/
|
|
781
954
|
toSql(dialect: SelectDialectInput): string {
|
|
782
955
|
return this.compile(dialect).sql;
|
|
@@ -785,6 +958,9 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
785
958
|
/**
|
|
786
959
|
* Gets the hydration plan for the query
|
|
787
960
|
* @returns Hydration plan or undefined if none exists
|
|
961
|
+
* @example
|
|
962
|
+
* const plan = qb.include('posts').getHydrationPlan();
|
|
963
|
+
* console.log(plan?.relations); // Information about included relations
|
|
788
964
|
*/
|
|
789
965
|
getHydrationPlan(): HydrationPlan | undefined {
|
|
790
966
|
return this.context.hydration.getPlan();
|
|
@@ -793,9 +969,12 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
793
969
|
/**
|
|
794
970
|
* Gets the Abstract Syntax Tree (AST) representation of the query
|
|
795
971
|
* @returns Query AST with hydration applied
|
|
972
|
+
* @example
|
|
973
|
+
* const ast = qb.select('id', 'name').getAST();
|
|
974
|
+
* console.log(ast.columns); // Array of column nodes
|
|
975
|
+
* console.log(ast.from); // From clause information
|
|
796
976
|
*/
|
|
797
977
|
getAST(): SelectQueryNode {
|
|
798
978
|
return this.context.hydration.applyToAst(this.context.state.ast);
|
|
799
979
|
}
|
|
800
980
|
}
|
|
801
|
-
|