metal-orm 1.0.12 → 1.0.13
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 +17 -15
- package/dist/decorators/index.cjs +302 -32
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +302 -32
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +583 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +583 -130
- package/dist/index.js.map +1 -1
- package/dist/{select-BKlr2ivY.d.cts → select-CCp1oz9p.d.cts} +114 -1
- package/dist/{select-BKlr2ivY.d.ts → select-CCp1oz9p.d.ts} +114 -1
- package/package.json +3 -2
- package/src/core/ast/query.ts +40 -22
- package/src/core/dialect/abstract.ts +122 -37
- package/src/core/dialect/base/sql-dialect.ts +51 -8
- package/src/core/dialect/mssql/index.ts +125 -80
- package/src/core/dialect/postgres/index.ts +5 -6
- package/src/core/dialect/sqlite/index.ts +5 -6
- package/src/orm/execute.ts +25 -16
- package/src/orm/orm-context.ts +60 -55
- package/src/orm/query-logger.ts +38 -0
- package/src/orm/relations/belongs-to.ts +42 -26
- package/src/orm/relations/has-many.ts +41 -25
- package/src/orm/relations/many-to-many.ts +43 -27
- package/src/orm/unit-of-work.ts +60 -23
- package/src/query-builder/hydration-manager.ts +229 -25
- package/src/query-builder/query-ast-service.ts +27 -12
- package/src/query-builder/select-query-state.ts +24 -12
- package/src/query-builder/select.ts +58 -14
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { RelationDef } from '../schema/relation.js';
|
|
3
|
-
import {
|
|
4
|
-
import { HydrationPlanner } from './hydration-planner.js';
|
|
5
|
-
import {
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { RelationDef, RelationKinds } from '../schema/relation.js';
|
|
3
|
+
import { CommonTableExpressionNode, HydrationPlan, OrderByNode, SelectQueryNode } from '../core/ast/query.js';
|
|
4
|
+
import { HydrationPlanner } from './hydration-planner.js';
|
|
5
|
+
import { ProjectionNode, SelectQueryState } from './select-query-state.js';
|
|
6
|
+
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
7
|
+
import { createJoinNode } from '../core/ast/join-node.js';
|
|
8
|
+
import { JOIN_KINDS } from '../core/sql/sql.js';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Manages hydration planning for query results
|
|
@@ -60,28 +63,229 @@ export class HydrationManager {
|
|
|
60
63
|
return this.clone(next);
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
/**
|
|
64
|
-
* Applies hydration plan to the AST
|
|
65
|
-
* @param ast - Query AST to modify
|
|
66
|
-
* @returns AST with hydration metadata
|
|
67
|
-
*/
|
|
68
|
-
applyToAst(ast: SelectQueryNode): SelectQueryNode {
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Applies hydration plan to the AST
|
|
68
|
+
* @param ast - Query AST to modify
|
|
69
|
+
* @returns AST with hydration metadata
|
|
70
|
+
*/
|
|
71
|
+
applyToAst(ast: SelectQueryNode): SelectQueryNode {
|
|
72
|
+
// Hydration is not applied to compound set queries since row identity is ambiguous.
|
|
73
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
74
|
+
return ast;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const plan = this.planner.getPlan();
|
|
78
|
+
if (!plan) return ast;
|
|
79
|
+
|
|
80
|
+
const needsPaginationGuard = this.requiresParentPagination(ast, plan);
|
|
81
|
+
const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
|
|
82
|
+
return this.attachHydrationMeta(rewritten, plan);
|
|
83
|
+
}
|
|
79
84
|
|
|
80
85
|
/**
|
|
81
86
|
* Gets the current hydration plan
|
|
82
87
|
* @returns Hydration plan or undefined if none exists
|
|
83
88
|
*/
|
|
84
|
-
getPlan(): HydrationPlan | undefined {
|
|
85
|
-
return this.planner.getPlan();
|
|
86
|
-
}
|
|
87
|
-
|
|
89
|
+
getPlan(): HydrationPlan | undefined {
|
|
90
|
+
return this.planner.getPlan();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Attaches hydration metadata to a query AST node.
|
|
95
|
+
*/
|
|
96
|
+
private attachHydrationMeta(ast: SelectQueryNode, plan: HydrationPlan): SelectQueryNode {
|
|
97
|
+
return {
|
|
98
|
+
...ast,
|
|
99
|
+
meta: {
|
|
100
|
+
...(ast.meta || {}),
|
|
101
|
+
hydration: plan
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
|
|
108
|
+
* applied to parent rows when eager-loading multiplicative relations.
|
|
109
|
+
*/
|
|
110
|
+
private requiresParentPagination(ast: SelectQueryNode, plan: HydrationPlan): boolean {
|
|
111
|
+
const hasPagination = ast.limit !== undefined || ast.offset !== undefined;
|
|
112
|
+
return hasPagination && this.hasMultiplyingRelations(plan);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private hasMultiplyingRelations(plan: HydrationPlan): boolean {
|
|
116
|
+
return plan.relations.some(
|
|
117
|
+
rel => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Rewrites the query using CTEs so LIMIT/OFFSET target distinct parent rows
|
|
123
|
+
* instead of the joined result set.
|
|
124
|
+
*
|
|
125
|
+
* The strategy:
|
|
126
|
+
* - Hoist the original query (minus limit/offset) into a base CTE.
|
|
127
|
+
* - Select distinct parent ids from that base CTE with the original ordering and pagination.
|
|
128
|
+
* - Join the base CTE against the paged ids to retrieve the joined rows for just that page.
|
|
129
|
+
*/
|
|
130
|
+
private wrapForParentPagination(ast: SelectQueryNode, plan: HydrationPlan): SelectQueryNode {
|
|
131
|
+
const projectionNames = this.getProjectionNames(ast.columns);
|
|
132
|
+
if (!projectionNames) {
|
|
133
|
+
return ast;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const projectionAliases = this.buildProjectionAliasMap(ast.columns);
|
|
137
|
+
const projectionSet = new Set(projectionNames);
|
|
138
|
+
const rootPkAlias = projectionAliases.get(`${plan.rootTable}.${plan.rootPrimaryKey}`) ?? plan.rootPrimaryKey;
|
|
139
|
+
|
|
140
|
+
const baseCteName = this.nextCteName(ast.ctes, '__metal_pagination_base');
|
|
141
|
+
const baseQuery: SelectQueryNode = {
|
|
142
|
+
...ast,
|
|
143
|
+
ctes: undefined,
|
|
144
|
+
limit: undefined,
|
|
145
|
+
offset: undefined,
|
|
146
|
+
orderBy: undefined,
|
|
147
|
+
meta: undefined
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const baseCte: CommonTableExpressionNode = {
|
|
151
|
+
type: 'CommonTableExpression',
|
|
152
|
+
name: baseCteName,
|
|
153
|
+
query: baseQuery,
|
|
154
|
+
recursive: false
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const orderBy = this.mapOrderBy(ast.orderBy, plan, projectionAliases, baseCteName, projectionSet);
|
|
158
|
+
// When an order-by uses child-table columns we cannot safely rewrite pagination,
|
|
159
|
+
// so preserve the original query to avoid changing semantics.
|
|
160
|
+
if (orderBy === null) {
|
|
161
|
+
return ast;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const pageCteName = this.nextCteName([...(ast.ctes ?? []), baseCte], '__metal_pagination_page');
|
|
165
|
+
const pagingColumns = this.buildPagingColumns(rootPkAlias, orderBy, baseCteName);
|
|
166
|
+
|
|
167
|
+
const pageCte: CommonTableExpressionNode = {
|
|
168
|
+
type: 'CommonTableExpression',
|
|
169
|
+
name: pageCteName,
|
|
170
|
+
query: {
|
|
171
|
+
type: 'SelectQuery',
|
|
172
|
+
from: { type: 'Table', name: baseCteName },
|
|
173
|
+
columns: pagingColumns,
|
|
174
|
+
joins: [],
|
|
175
|
+
distinct: [{ type: 'Column', table: baseCteName, name: rootPkAlias }],
|
|
176
|
+
orderBy,
|
|
177
|
+
limit: ast.limit,
|
|
178
|
+
offset: ast.offset
|
|
179
|
+
},
|
|
180
|
+
recursive: false
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const joinCondition = eq(
|
|
184
|
+
{ type: 'Column', table: baseCteName, name: rootPkAlias },
|
|
185
|
+
{ type: 'Column', table: pageCteName, name: rootPkAlias }
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const outerColumns: ColumnNode[] = projectionNames.map(name => ({
|
|
189
|
+
type: 'Column',
|
|
190
|
+
table: baseCteName,
|
|
191
|
+
name,
|
|
192
|
+
alias: name
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
type: 'SelectQuery',
|
|
197
|
+
from: { type: 'Table', name: baseCteName },
|
|
198
|
+
columns: outerColumns,
|
|
199
|
+
joins: [createJoinNode(JOIN_KINDS.INNER, pageCteName, joinCondition)],
|
|
200
|
+
orderBy,
|
|
201
|
+
ctes: [...(ast.ctes ?? []), baseCte, pageCte]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private nextCteName(existing: CommonTableExpressionNode[] | undefined, baseName: string): string {
|
|
206
|
+
const names = new Set((existing ?? []).map(cte => cte.name));
|
|
207
|
+
let candidate = baseName;
|
|
208
|
+
let suffix = 1;
|
|
209
|
+
|
|
210
|
+
while (names.has(candidate)) {
|
|
211
|
+
suffix += 1;
|
|
212
|
+
candidate = `${baseName}_${suffix}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return candidate;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private getProjectionNames(columns: ProjectionNode[]): string[] | undefined {
|
|
219
|
+
const names: string[] = [];
|
|
220
|
+
for (const col of columns) {
|
|
221
|
+
const alias = (col as any).alias ?? (col as any).name;
|
|
222
|
+
if (!alias) return undefined;
|
|
223
|
+
names.push(alias);
|
|
224
|
+
}
|
|
225
|
+
return names;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private buildProjectionAliasMap(columns: ProjectionNode[]): Map<string, string> {
|
|
229
|
+
const map = new Map<string, string>();
|
|
230
|
+
for (const col of columns) {
|
|
231
|
+
if ((col as ColumnNode).type !== 'Column') continue;
|
|
232
|
+
const node = col as ColumnNode;
|
|
233
|
+
const key = `${node.table}.${node.name}`;
|
|
234
|
+
map.set(key, node.alias ?? node.name);
|
|
235
|
+
}
|
|
236
|
+
return map;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private mapOrderBy(
|
|
240
|
+
orderBy: OrderByNode[] | undefined,
|
|
241
|
+
plan: HydrationPlan,
|
|
242
|
+
projectionAliases: Map<string, string>,
|
|
243
|
+
baseAlias: string,
|
|
244
|
+
availableColumns: Set<string>
|
|
245
|
+
): OrderByNode[] | undefined | null {
|
|
246
|
+
if (!orderBy || orderBy.length === 0) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const mapped: OrderByNode[] = [];
|
|
251
|
+
|
|
252
|
+
for (const ob of orderBy) {
|
|
253
|
+
// Only rewrite when ordering by root columns; child columns would reintroduce the pagination bug.
|
|
254
|
+
if (ob.column.table !== plan.rootTable) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const alias = projectionAliases.get(`${ob.column.table}.${ob.column.name}`) ?? ob.column.name;
|
|
259
|
+
if (!availableColumns.has(alias)) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
mapped.push({
|
|
264
|
+
type: 'OrderBy',
|
|
265
|
+
column: { type: 'Column', table: baseAlias, name: alias },
|
|
266
|
+
direction: ob.direction
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return mapped;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private buildPagingColumns(primaryKey: string, orderBy: OrderByNode[] | undefined, tableAlias: string): ColumnNode[] {
|
|
274
|
+
const columns: ColumnNode[] = [{ type: 'Column', table: tableAlias, name: primaryKey, alias: primaryKey }];
|
|
275
|
+
|
|
276
|
+
if (!orderBy) return columns;
|
|
277
|
+
|
|
278
|
+
for (const ob of orderBy) {
|
|
279
|
+
if (!columns.some(col => col.name === ob.column.name)) {
|
|
280
|
+
columns.push({
|
|
281
|
+
type: 'Column',
|
|
282
|
+
table: tableAlias,
|
|
283
|
+
name: ob.column.name,
|
|
284
|
+
alias: ob.column.name
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return columns;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { ColumnDef } from '../schema/column.js';
|
|
3
|
-
import { SelectQueryNode, CommonTableExpressionNode } from '../core/ast/query.js';
|
|
3
|
+
import { SelectQueryNode, CommonTableExpressionNode, SetOperationKind, SetOperationNode } from '../core/ast/query.js';
|
|
4
4
|
import { buildColumnNode } from '../core/ast/builders.js';
|
|
5
5
|
import {
|
|
6
6
|
ColumnNode,
|
|
@@ -95,17 +95,32 @@ export class QueryAstService {
|
|
|
95
95
|
* @param recursive - Whether the CTE is recursive
|
|
96
96
|
* @returns Updated query state with CTE
|
|
97
97
|
*/
|
|
98
|
-
withCte(name: string, query: SelectQueryNode, columns?: string[], recursive = false): SelectQueryState {
|
|
99
|
-
const cte: CommonTableExpressionNode = {
|
|
100
|
-
type: 'CommonTableExpression',
|
|
101
|
-
name,
|
|
102
|
-
query,
|
|
103
|
-
columns,
|
|
104
|
-
recursive
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
return this.state.withCte(cte);
|
|
108
|
-
}
|
|
98
|
+
withCte(name: string, query: SelectQueryNode, columns?: string[], recursive = false): SelectQueryState {
|
|
99
|
+
const cte: CommonTableExpressionNode = {
|
|
100
|
+
type: 'CommonTableExpression',
|
|
101
|
+
name,
|
|
102
|
+
query,
|
|
103
|
+
columns,
|
|
104
|
+
recursive
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return this.state.withCte(cte);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Adds a set operation (UNION/UNION ALL/INTERSECT/EXCEPT) to the query
|
|
112
|
+
* @param operator - Set operator
|
|
113
|
+
* @param query - Right-hand side query
|
|
114
|
+
* @returns Updated query state with set operation
|
|
115
|
+
*/
|
|
116
|
+
withSetOperation(operator: SetOperationKind, query: SelectQueryNode): SelectQueryState {
|
|
117
|
+
const op: SetOperationNode = {
|
|
118
|
+
type: 'SetOperation',
|
|
119
|
+
operator,
|
|
120
|
+
query
|
|
121
|
+
};
|
|
122
|
+
return this.state.withSetOperation(op);
|
|
123
|
+
}
|
|
109
124
|
|
|
110
125
|
/**
|
|
111
126
|
* Selects a subquery as a column
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { SelectQueryNode, CommonTableExpressionNode, OrderByNode } from '../core/ast/query.js';
|
|
2
|
+
import { SelectQueryNode, CommonTableExpressionNode, OrderByNode, SetOperationNode } from '../core/ast/query.js';
|
|
3
3
|
import {
|
|
4
4
|
ColumnNode,
|
|
5
5
|
ExpressionNode,
|
|
@@ -166,14 +166,26 @@ export class SelectQueryState {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
* Adds a Common Table Expression (CTE) to the query
|
|
170
|
-
* @param cte - CTE node to add
|
|
171
|
-
* @returns New SelectQueryState with CTE
|
|
172
|
-
*/
|
|
173
|
-
withCte(cte: CommonTableExpressionNode): SelectQueryState {
|
|
174
|
-
return this.clone({
|
|
175
|
-
...this.ast,
|
|
176
|
-
ctes: [...(this.ast.ctes ?? []), cte]
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
169
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
170
|
+
* @param cte - CTE node to add
|
|
171
|
+
* @returns New SelectQueryState with CTE
|
|
172
|
+
*/
|
|
173
|
+
withCte(cte: CommonTableExpressionNode): SelectQueryState {
|
|
174
|
+
return this.clone({
|
|
175
|
+
...this.ast,
|
|
176
|
+
ctes: [...(this.ast.ctes ?? []), cte]
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Adds a set operation (UNION/INTERSECT/EXCEPT) to the query
|
|
182
|
+
* @param op - Set operation node to add
|
|
183
|
+
* @returns New SelectQueryState with set operation
|
|
184
|
+
*/
|
|
185
|
+
withSetOperation(op: SetOperationNode): SelectQueryState {
|
|
186
|
+
return this.clone({
|
|
187
|
+
...this.ast,
|
|
188
|
+
setOps: [...(this.ast.setOps ?? []), op]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { ColumnDef } from '../schema/column.js';
|
|
3
|
-
import { SelectQueryNode, HydrationPlan } from '../core/ast/query.js';
|
|
3
|
+
import { SelectQueryNode, HydrationPlan, SetOperationKind } from '../core/ast/query.js';
|
|
4
4
|
import {
|
|
5
5
|
ColumnNode,
|
|
6
6
|
ExpressionNode,
|
|
@@ -96,15 +96,23 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
96
96
|
return { state: nextState, hydration: context.hydration };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
private applyJoin(
|
|
100
|
-
context: SelectQueryBuilderContext,
|
|
101
|
-
table: TableDef,
|
|
102
|
-
condition: BinaryExpressionNode,
|
|
103
|
-
kind: JoinKind
|
|
104
|
-
): SelectQueryBuilderContext {
|
|
105
|
-
const joinNode = createJoinNode(kind, table.name, condition);
|
|
106
|
-
return this.applyAst(context, service => service.withJoin(joinNode));
|
|
107
|
-
}
|
|
99
|
+
private applyJoin(
|
|
100
|
+
context: SelectQueryBuilderContext,
|
|
101
|
+
table: TableDef,
|
|
102
|
+
condition: BinaryExpressionNode,
|
|
103
|
+
kind: JoinKind
|
|
104
|
+
): SelectQueryBuilderContext {
|
|
105
|
+
const joinNode = createJoinNode(kind, table.name, condition);
|
|
106
|
+
return this.applyAst(context, service => service.withJoin(joinNode));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private applySetOperation(
|
|
110
|
+
operator: SetOperationKind,
|
|
111
|
+
query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode
|
|
112
|
+
): SelectQueryBuilderContext {
|
|
113
|
+
const subAst = this.resolveQueryNode(query);
|
|
114
|
+
return this.applyAst(this.context, service => service.withSetOperation(operator, subAst));
|
|
115
|
+
}
|
|
108
116
|
|
|
109
117
|
/**
|
|
110
118
|
* Selects specific columns for the query
|
|
@@ -315,10 +323,46 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
|
|
|
315
323
|
* @param n - Number of rows to skip
|
|
316
324
|
* @returns New query builder instance with the OFFSET clause
|
|
317
325
|
*/
|
|
318
|
-
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
319
|
-
const nextContext = this.applyAst(this.context, service => service.withOffset(n));
|
|
320
|
-
return this.clone(nextContext);
|
|
321
|
-
}
|
|
326
|
+
offset(n: number): SelectQueryBuilder<T, TTable> {
|
|
327
|
+
const nextContext = this.applyAst(this.context, service => service.withOffset(n));
|
|
328
|
+
return this.clone(nextContext);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Combines this query with another using UNION
|
|
333
|
+
* @param query - Query to union with
|
|
334
|
+
* @returns New query builder instance with the set operation
|
|
335
|
+
*/
|
|
336
|
+
union(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
337
|
+
return this.clone(this.applySetOperation('UNION', query));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Combines this query with another using UNION ALL
|
|
342
|
+
* @param query - Query to union with
|
|
343
|
+
* @returns New query builder instance with the set operation
|
|
344
|
+
*/
|
|
345
|
+
unionAll(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
346
|
+
return this.clone(this.applySetOperation('UNION ALL', query));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Combines this query with another using INTERSECT
|
|
351
|
+
* @param query - Query to intersect with
|
|
352
|
+
* @returns New query builder instance with the set operation
|
|
353
|
+
*/
|
|
354
|
+
intersect(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
355
|
+
return this.clone(this.applySetOperation('INTERSECT', query));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Combines this query with another using EXCEPT
|
|
360
|
+
* @param query - Query to subtract
|
|
361
|
+
* @returns New query builder instance with the set operation
|
|
362
|
+
*/
|
|
363
|
+
except(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
364
|
+
return this.clone(this.applySetOperation('EXCEPT', query));
|
|
365
|
+
}
|
|
322
366
|
|
|
323
367
|
/**
|
|
324
368
|
* Adds a WHERE EXISTS condition to the query
|