metal-orm 1.0.38 → 1.0.40
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 +230 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -24
- package/dist/index.d.ts +71 -24
- package/dist/index.js +225 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-entities.mjs +3 -3
- package/src/codegen/typescript.ts +60 -3
- package/src/core/ast/aggregate-functions.ts +15 -15
- package/src/core/ast/expression-builders.ts +357 -316
- package/src/core/ast/expression-nodes.ts +208 -186
- package/src/core/ast/expression-visitor.ts +40 -30
- package/src/core/ast/query.ts +142 -132
- package/src/core/ast/window-functions.ts +86 -86
- package/src/core/dialect/abstract.ts +505 -479
- package/src/core/dialect/base/groupby-compiler.ts +6 -6
- package/src/core/dialect/base/orderby-compiler.ts +20 -6
- package/src/core/dialect/base/sql-dialect.ts +154 -136
- package/src/core/dialect/mssql/index.ts +172 -161
- package/src/core/functions/standard-strategy.ts +46 -37
- package/src/query-builder/hydration-manager.ts +93 -79
- package/src/query-builder/query-ast-service.ts +207 -170
- package/src/query-builder/select-query-state.ts +169 -162
- package/src/query-builder/select.ts +15 -23
|
@@ -7,63 +7,63 @@ import { ProjectionNode, SelectQueryState } from './select-query-state.js';
|
|
|
7
7
|
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
8
8
|
import { createJoinNode } from '../core/ast/join-node.js';
|
|
9
9
|
import { JOIN_KINDS } from '../core/sql/sql.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Manages hydration planning for query results
|
|
13
|
-
*/
|
|
14
|
-
export class HydrationManager {
|
|
15
|
-
/**
|
|
16
|
-
* Creates a new HydrationManager instance
|
|
17
|
-
* @param table - Table definition
|
|
18
|
-
* @param planner - Hydration planner
|
|
19
|
-
*/
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly table: TableDef,
|
|
22
|
-
private readonly planner: HydrationPlanner
|
|
23
|
-
) {}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Creates a new HydrationManager with updated planner
|
|
27
|
-
* @param nextPlanner - Updated hydration planner
|
|
28
|
-
* @returns New HydrationManager instance
|
|
29
|
-
*/
|
|
30
|
-
private clone(nextPlanner: HydrationPlanner): HydrationManager {
|
|
31
|
-
return new HydrationManager(this.table, nextPlanner);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Handles column selection for hydration planning
|
|
36
|
-
* @param state - Current query state
|
|
37
|
-
* @param newColumns - Newly selected columns
|
|
38
|
-
* @returns Updated HydrationManager with captured columns
|
|
39
|
-
*/
|
|
40
|
-
onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
|
|
41
|
-
const updated = this.planner.captureRootColumns(newColumns);
|
|
42
|
-
return this.clone(updated);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Handles relation inclusion for hydration planning
|
|
47
|
-
* @param state - Current query state
|
|
48
|
-
* @param relation - Relation definition
|
|
49
|
-
* @param relationName - Name of the relation
|
|
50
|
-
* @param aliasPrefix - Alias prefix for the relation
|
|
51
|
-
* @param targetColumns - Target columns to include
|
|
52
|
-
* @returns Updated HydrationManager with included relation
|
|
53
|
-
*/
|
|
54
|
-
onRelationIncluded(
|
|
55
|
-
state: SelectQueryState,
|
|
56
|
-
relation: RelationDef,
|
|
57
|
-
relationName: string,
|
|
58
|
-
aliasPrefix: string,
|
|
59
|
-
targetColumns: string[],
|
|
60
|
-
pivot?: { aliasPrefix: string; columns: string[] }
|
|
61
|
-
): HydrationManager {
|
|
62
|
-
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
63
|
-
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
64
|
-
return this.clone(next);
|
|
65
|
-
}
|
|
66
|
-
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manages hydration planning for query results
|
|
13
|
+
*/
|
|
14
|
+
export class HydrationManager {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new HydrationManager instance
|
|
17
|
+
* @param table - Table definition
|
|
18
|
+
* @param planner - Hydration planner
|
|
19
|
+
*/
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly table: TableDef,
|
|
22
|
+
private readonly planner: HydrationPlanner
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new HydrationManager with updated planner
|
|
27
|
+
* @param nextPlanner - Updated hydration planner
|
|
28
|
+
* @returns New HydrationManager instance
|
|
29
|
+
*/
|
|
30
|
+
private clone(nextPlanner: HydrationPlanner): HydrationManager {
|
|
31
|
+
return new HydrationManager(this.table, nextPlanner);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Handles column selection for hydration planning
|
|
36
|
+
* @param state - Current query state
|
|
37
|
+
* @param newColumns - Newly selected columns
|
|
38
|
+
* @returns Updated HydrationManager with captured columns
|
|
39
|
+
*/
|
|
40
|
+
onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
|
|
41
|
+
const updated = this.planner.captureRootColumns(newColumns);
|
|
42
|
+
return this.clone(updated);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handles relation inclusion for hydration planning
|
|
47
|
+
* @param state - Current query state
|
|
48
|
+
* @param relation - Relation definition
|
|
49
|
+
* @param relationName - Name of the relation
|
|
50
|
+
* @param aliasPrefix - Alias prefix for the relation
|
|
51
|
+
* @param targetColumns - Target columns to include
|
|
52
|
+
* @returns Updated HydrationManager with included relation
|
|
53
|
+
*/
|
|
54
|
+
onRelationIncluded(
|
|
55
|
+
state: SelectQueryState,
|
|
56
|
+
relation: RelationDef,
|
|
57
|
+
relationName: string,
|
|
58
|
+
aliasPrefix: string,
|
|
59
|
+
targetColumns: string[],
|
|
60
|
+
pivot?: { aliasPrefix: string; columns: string[] }
|
|
61
|
+
): HydrationManager {
|
|
62
|
+
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
63
|
+
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
64
|
+
return this.clone(next);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
67
|
/**
|
|
68
68
|
* Applies hydration plan to the AST
|
|
69
69
|
* @param ast - Query AST to modify
|
|
@@ -82,11 +82,11 @@ export class HydrationManager {
|
|
|
82
82
|
const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
|
|
83
83
|
return this.attachHydrationMeta(rewritten, plan);
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Gets the current hydration plan
|
|
88
|
-
* @returns Hydration plan or undefined if none exists
|
|
89
|
-
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gets the current hydration plan
|
|
88
|
+
* @returns Hydration plan or undefined if none exists
|
|
89
|
+
*/
|
|
90
90
|
getPlan(): HydrationPlan | undefined {
|
|
91
91
|
return this.planner.getPlan();
|
|
92
92
|
}
|
|
@@ -251,38 +251,52 @@ export class HydrationManager {
|
|
|
251
251
|
const mapped: OrderByNode[] = [];
|
|
252
252
|
|
|
253
253
|
for (const ob of orderBy) {
|
|
254
|
-
|
|
255
|
-
if (
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
254
|
+
const mappedTerm = this.mapOrderingTerm(ob.term, plan, projectionAliases, baseAlias, availableColumns);
|
|
255
|
+
if (!mappedTerm) return null;
|
|
258
256
|
|
|
259
|
-
|
|
260
|
-
if (!availableColumns.has(alias)) {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
mapped.push({
|
|
265
|
-
type: 'OrderBy',
|
|
266
|
-
column: { type: 'Column', table: baseAlias, name: alias },
|
|
267
|
-
direction: ob.direction
|
|
268
|
-
});
|
|
257
|
+
mapped.push({ ...ob, term: mappedTerm });
|
|
269
258
|
}
|
|
270
259
|
|
|
271
260
|
return mapped;
|
|
272
261
|
}
|
|
273
262
|
|
|
263
|
+
private mapOrderingTerm(
|
|
264
|
+
term: OrderByNode['term'],
|
|
265
|
+
plan: HydrationPlan,
|
|
266
|
+
projectionAliases: Map<string, string>,
|
|
267
|
+
baseAlias: string,
|
|
268
|
+
availableColumns: Set<string>
|
|
269
|
+
): OrderByNode['term'] | null {
|
|
270
|
+
if ((term as any).type === 'Column') {
|
|
271
|
+
const col = term as ColumnNode;
|
|
272
|
+
if (col.table !== plan.rootTable) return null;
|
|
273
|
+
const alias = projectionAliases.get(`${col.table}.${col.name}`) ?? col.name;
|
|
274
|
+
if (!availableColumns.has(alias)) return null;
|
|
275
|
+
return { type: 'Column', table: baseAlias, name: alias };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if ((term as any).type === 'AliasRef') {
|
|
279
|
+
const aliasName = (term as any).name;
|
|
280
|
+
if (!availableColumns.has(aliasName)) return null;
|
|
281
|
+
return { type: 'Column', table: baseAlias, name: aliasName };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
274
287
|
private buildPagingColumns(primaryKey: string, orderBy: OrderByNode[] | undefined, tableAlias: string): ColumnNode[] {
|
|
275
288
|
const columns: ColumnNode[] = [{ type: 'Column', table: tableAlias, name: primaryKey, alias: primaryKey }];
|
|
276
289
|
|
|
277
290
|
if (!orderBy) return columns;
|
|
278
291
|
|
|
279
292
|
for (const ob of orderBy) {
|
|
280
|
-
|
|
293
|
+
const term = ob.term as ColumnNode;
|
|
294
|
+
if (!columns.some(col => col.name === term.name)) {
|
|
281
295
|
columns.push({
|
|
282
296
|
type: 'Column',
|
|
283
297
|
table: tableAlias,
|
|
284
|
-
name:
|
|
285
|
-
alias:
|
|
298
|
+
name: term.name,
|
|
299
|
+
alias: term.name
|
|
286
300
|
});
|
|
287
301
|
}
|
|
288
302
|
}
|
|
@@ -1,52 +1,61 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { ColumnDef } from '../schema/column.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { ColumnDef } from '../schema/column.js';
|
|
3
|
+
import {
|
|
4
|
+
SelectQueryNode,
|
|
5
|
+
CommonTableExpressionNode,
|
|
6
|
+
SetOperationKind,
|
|
7
|
+
SetOperationNode,
|
|
8
|
+
TableSourceNode,
|
|
9
|
+
OrderingTerm
|
|
10
|
+
} from '../core/ast/query.js';
|
|
11
|
+
import { buildColumnNode } from '../core/ast/builders.js';
|
|
12
|
+
import {
|
|
13
|
+
AliasRefNode,
|
|
14
|
+
ColumnNode,
|
|
15
|
+
ExpressionNode,
|
|
16
|
+
FunctionNode,
|
|
17
|
+
CaseExpressionNode,
|
|
18
|
+
WindowFunctionNode,
|
|
19
|
+
ScalarSubqueryNode,
|
|
20
|
+
and,
|
|
21
|
+
isExpressionSelectionNode,
|
|
22
|
+
isOperandNode
|
|
23
|
+
} from '../core/ast/expression.js';
|
|
24
|
+
import { JoinNode } from '../core/ast/join.js';
|
|
25
|
+
import { SelectQueryState, ProjectionNode } from './select-query-state.js';
|
|
26
|
+
import { OrderDirection } from '../core/sql/sql.js';
|
|
27
|
+
import { parseRawColumn } from './raw-column-parser.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Result of column selection operation
|
|
31
|
+
*/
|
|
32
|
+
export interface ColumnSelectionResult {
|
|
33
|
+
/**
|
|
34
|
+
* Updated query state
|
|
35
|
+
*/
|
|
36
|
+
state: SelectQueryState;
|
|
37
|
+
/**
|
|
38
|
+
* Columns that were added
|
|
39
|
+
*/
|
|
40
|
+
addedColumns: ProjectionNode[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Service for manipulating query AST (Abstract Syntax Tree)
|
|
45
|
+
*/
|
|
46
|
+
export class QueryAstService {
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new QueryAstService instance
|
|
49
|
+
* @param table - Table definition
|
|
50
|
+
* @param state - Current query state
|
|
51
|
+
*/
|
|
52
|
+
constructor(private readonly table: TableDef, private readonly state: SelectQueryState) {}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Selects columns for the query
|
|
56
|
+
* @param columns - Columns to select (key: alias, value: column definition or expression)
|
|
57
|
+
* @returns Column selection result with updated state and added columns
|
|
58
|
+
*/
|
|
50
59
|
select(
|
|
51
60
|
columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>
|
|
52
61
|
): ColumnSelectionResult {
|
|
@@ -77,16 +86,16 @@ export class QueryAstService {
|
|
|
77
86
|
} as ColumnNode);
|
|
78
87
|
return acc;
|
|
79
88
|
}, []);
|
|
80
|
-
|
|
81
|
-
const nextState = this.state.withColumns(newCols);
|
|
82
|
-
return { state: nextState, addedColumns: newCols };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Selects raw column expressions (best-effort parser for simple references/functions)
|
|
87
|
-
* @param cols - Raw column expressions
|
|
88
|
-
* @returns Column selection result with updated state and added columns
|
|
89
|
-
*/
|
|
89
|
+
|
|
90
|
+
const nextState = this.state.withColumns(newCols);
|
|
91
|
+
return { state: nextState, addedColumns: newCols };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Selects raw column expressions (best-effort parser for simple references/functions)
|
|
96
|
+
* @param cols - Raw column expressions
|
|
97
|
+
* @returns Column selection result with updated state and added columns
|
|
98
|
+
*/
|
|
90
99
|
selectRaw(cols: string[]): ColumnSelectionResult {
|
|
91
100
|
const from = this.state.ast.from;
|
|
92
101
|
const defaultTable = from.type === 'Table' && from.alias ? from.alias : this.table.name;
|
|
@@ -94,15 +103,15 @@ export class QueryAstService {
|
|
|
94
103
|
const nextState = this.state.withColumns(newCols);
|
|
95
104
|
return { state: nextState, addedColumns: newCols };
|
|
96
105
|
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Adds a Common Table Expression (CTE) to the query
|
|
100
|
-
* @param name - Name of the CTE
|
|
101
|
-
* @param query - Query for the CTE
|
|
102
|
-
* @param columns - Optional column names for the CTE
|
|
103
|
-
* @param recursive - Whether the CTE is recursive
|
|
104
|
-
* @returns Updated query state with CTE
|
|
105
|
-
*/
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
109
|
+
* @param name - Name of the CTE
|
|
110
|
+
* @param query - Query for the CTE
|
|
111
|
+
* @param columns - Optional column names for the CTE
|
|
112
|
+
* @param recursive - Whether the CTE is recursive
|
|
113
|
+
* @returns Updated query state with CTE
|
|
114
|
+
*/
|
|
106
115
|
withCte(name: string, query: SelectQueryNode, columns?: string[], recursive = false): SelectQueryState {
|
|
107
116
|
const cte: CommonTableExpressionNode = {
|
|
108
117
|
type: 'CommonTableExpression',
|
|
@@ -138,107 +147,135 @@ export class QueryAstService {
|
|
|
138
147
|
withFrom(from: TableSourceNode): SelectQueryState {
|
|
139
148
|
return this.state.withFrom(from);
|
|
140
149
|
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Selects a subquery as a column
|
|
144
|
-
* @param alias - Alias for the subquery
|
|
145
|
-
* @param query - Subquery to select
|
|
146
|
-
* @returns Updated query state with subquery selection
|
|
147
|
-
*/
|
|
148
|
-
selectSubquery(alias: string, query: SelectQueryNode): SelectQueryState {
|
|
149
|
-
const node: ScalarSubqueryNode = { type: 'ScalarSubquery', query, alias };
|
|
150
|
-
return this.state.withColumns([node]);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Adds a JOIN clause to the query
|
|
155
|
-
* @param join - Join node to add
|
|
156
|
-
* @returns Updated query state with JOIN
|
|
157
|
-
*/
|
|
158
|
-
withJoin(join: JoinNode): SelectQueryState {
|
|
159
|
-
return this.state.withJoin(join);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Adds a WHERE clause to the query
|
|
164
|
-
* @param expr - Expression for the WHERE clause
|
|
165
|
-
* @returns Updated query state with WHERE clause
|
|
166
|
-
*/
|
|
167
|
-
withWhere(expr: ExpressionNode): SelectQueryState {
|
|
168
|
-
const combined = this.combineExpressions(this.state.ast.where, expr);
|
|
169
|
-
return this.state.withWhere(combined);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Adds a GROUP BY clause to the query
|
|
174
|
-
* @param col - Column to group by
|
|
175
|
-
* @returns Updated query state with GROUP BY clause
|
|
176
|
-
*/
|
|
177
|
-
withGroupBy(col: ColumnDef |
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
*
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
*
|
|
196
|
-
* @
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Selects a subquery as a column
|
|
153
|
+
* @param alias - Alias for the subquery
|
|
154
|
+
* @param query - Subquery to select
|
|
155
|
+
* @returns Updated query state with subquery selection
|
|
156
|
+
*/
|
|
157
|
+
selectSubquery(alias: string, query: SelectQueryNode): SelectQueryState {
|
|
158
|
+
const node: ScalarSubqueryNode = { type: 'ScalarSubquery', query, alias };
|
|
159
|
+
return this.state.withColumns([node]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Adds a JOIN clause to the query
|
|
164
|
+
* @param join - Join node to add
|
|
165
|
+
* @returns Updated query state with JOIN
|
|
166
|
+
*/
|
|
167
|
+
withJoin(join: JoinNode): SelectQueryState {
|
|
168
|
+
return this.state.withJoin(join);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Adds a WHERE clause to the query
|
|
173
|
+
* @param expr - Expression for the WHERE clause
|
|
174
|
+
* @returns Updated query state with WHERE clause
|
|
175
|
+
*/
|
|
176
|
+
withWhere(expr: ExpressionNode): SelectQueryState {
|
|
177
|
+
const combined = this.combineExpressions(this.state.ast.where, expr);
|
|
178
|
+
return this.state.withWhere(combined);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Adds a GROUP BY clause to the query
|
|
183
|
+
* @param col - Column to group by
|
|
184
|
+
* @returns Updated query state with GROUP BY clause
|
|
185
|
+
*/
|
|
186
|
+
withGroupBy(col: ColumnDef | OrderingTerm): SelectQueryState {
|
|
187
|
+
const term = this.normalizeOrderingTerm(col);
|
|
188
|
+
return this.state.withGroupBy([term]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Adds a HAVING clause to the query
|
|
193
|
+
* @param expr - Expression for the HAVING clause
|
|
194
|
+
* @returns Updated query state with HAVING clause
|
|
195
|
+
*/
|
|
196
|
+
withHaving(expr: ExpressionNode): SelectQueryState {
|
|
197
|
+
const combined = this.combineExpressions(this.state.ast.having, expr);
|
|
198
|
+
return this.state.withHaving(combined);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Adds an ORDER BY clause to the query
|
|
203
|
+
* @param col - Column to order by
|
|
204
|
+
* @param direction - Order direction (ASC/DESC)
|
|
205
|
+
* @returns Updated query state with ORDER BY clause
|
|
206
|
+
*/
|
|
207
|
+
withOrderBy(
|
|
208
|
+
term: ColumnDef | OrderingTerm,
|
|
209
|
+
direction: OrderDirection,
|
|
210
|
+
nulls?: 'FIRST' | 'LAST',
|
|
211
|
+
collation?: string
|
|
212
|
+
): SelectQueryState {
|
|
213
|
+
const normalized = this.normalizeOrderingTerm(term);
|
|
214
|
+
return this.state.withOrderBy([{ type: 'OrderBy', term: normalized, direction, nulls, collation }]);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Adds a DISTINCT clause to the query
|
|
219
|
+
* @param cols - Columns to make distinct
|
|
220
|
+
* @returns Updated query state with DISTINCT clause
|
|
221
|
+
*/
|
|
222
|
+
withDistinct(cols: ColumnNode[]): SelectQueryState {
|
|
223
|
+
return this.state.withDistinct(cols);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Adds a LIMIT clause to the query
|
|
228
|
+
* @param limit - Maximum number of rows to return
|
|
229
|
+
* @returns Updated query state with LIMIT clause
|
|
230
|
+
*/
|
|
231
|
+
withLimit(limit: number): SelectQueryState {
|
|
232
|
+
return this.state.withLimit(limit);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Adds an OFFSET clause to the query
|
|
237
|
+
* @param offset - Number of rows to skip
|
|
238
|
+
* @returns Updated query state with OFFSET clause
|
|
239
|
+
*/
|
|
240
|
+
withOffset(offset: number): SelectQueryState {
|
|
241
|
+
return this.state.withOffset(offset);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Combines expressions with AND operator
|
|
246
|
+
* @param existing - Existing expression
|
|
247
|
+
* @param next - New expression to combine
|
|
248
|
+
* @returns Combined expression
|
|
249
|
+
*/
|
|
250
|
+
private combineExpressions(existing: ExpressionNode | undefined, next: ExpressionNode): ExpressionNode {
|
|
251
|
+
return existing ? and(existing, next) : next;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private normalizeOrderingTerm(term: ColumnDef | OrderingTerm): OrderingTerm {
|
|
201
255
|
const from = this.state.ast.from;
|
|
202
256
|
const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
* @returns Updated query state with OFFSET clause
|
|
229
|
-
*/
|
|
230
|
-
withOffset(offset: number): SelectQueryState {
|
|
231
|
-
return this.state.withOffset(offset);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Combines expressions with AND operator
|
|
236
|
-
* @param existing - Existing expression
|
|
237
|
-
* @param next - New expression to combine
|
|
238
|
-
* @returns Combined expression
|
|
239
|
-
*/
|
|
240
|
-
private combineExpressions(existing: ExpressionNode | undefined, next: ExpressionNode): ExpressionNode {
|
|
241
|
-
return existing ? and(existing, next) : next;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
}
|
|
257
|
+
const termType = (term as any)?.type;
|
|
258
|
+
if (termType === 'Column') {
|
|
259
|
+
return term as ColumnNode;
|
|
260
|
+
}
|
|
261
|
+
if (termType === 'AliasRef') {
|
|
262
|
+
return term as AliasRefNode;
|
|
263
|
+
}
|
|
264
|
+
if (isOperandNode(term)) {
|
|
265
|
+
return term as OrderingTerm;
|
|
266
|
+
}
|
|
267
|
+
if (
|
|
268
|
+
termType === 'BinaryExpression' ||
|
|
269
|
+
termType === 'LogicalExpression' ||
|
|
270
|
+
termType === 'NullExpression' ||
|
|
271
|
+
termType === 'InExpression' ||
|
|
272
|
+
termType === 'ExistsExpression' ||
|
|
273
|
+
termType === 'BetweenExpression' ||
|
|
274
|
+
termType === 'ArithmeticExpression'
|
|
275
|
+
) {
|
|
276
|
+
return term as ExpressionNode;
|
|
277
|
+
}
|
|
278
|
+
return buildColumnNode(tableRef, term as ColumnDef);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
}
|