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