metal-orm 1.0.82 → 1.0.85
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 +300 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +300 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/ast-validation.ts +14 -0
- package/src/core/ast/expression-builders.ts +43 -24
- package/src/core/ast/expression-nodes.ts +35 -25
- package/src/core/ast/expression-visitor.ts +34 -25
- package/src/core/ast/param-proxy.ts +1 -4
- package/src/core/ast/query-visitor.ts +227 -0
- package/src/core/dialect/abstract.ts +70 -46
- package/src/query-builder/query-ast-service.ts +13 -0
- package/src/query-builder/select.ts +128 -109
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SelectQueryNode } from './query.js';
|
|
2
|
+
import { visitSelectQuery } from './query-visitor.js';
|
|
3
|
+
|
|
4
|
+
export const hasParamOperandsInQuery = (ast: SelectQueryNode): boolean => {
|
|
5
|
+
let hasParams = false;
|
|
6
|
+
|
|
7
|
+
visitSelectQuery(ast, {
|
|
8
|
+
visitParam: () => {
|
|
9
|
+
hasParams = true;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return hasParams;
|
|
14
|
+
};
|
|
@@ -2,13 +2,14 @@ import { SelectQueryNode } from './query.js';
|
|
|
2
2
|
import { SqlOperator, BitwiseOperator } from '../sql/sql.js';
|
|
3
3
|
import { ColumnRef } from './types.js';
|
|
4
4
|
import {
|
|
5
|
-
ColumnNode,
|
|
6
|
-
LiteralNode,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
ColumnNode,
|
|
6
|
+
LiteralNode,
|
|
7
|
+
ParamNode,
|
|
8
|
+
JsonPathNode,
|
|
9
|
+
OperandNode,
|
|
10
|
+
CaseExpressionNode,
|
|
11
|
+
CastExpressionNode,
|
|
12
|
+
BinaryExpressionNode,
|
|
12
13
|
ExpressionNode,
|
|
13
14
|
LogicalExpressionNode,
|
|
14
15
|
NullExpressionNode,
|
|
@@ -47,12 +48,21 @@ const toLiteralNode = (value: LiteralValue): LiteralNode => ({
|
|
|
47
48
|
type: 'Literal',
|
|
48
49
|
value: value instanceof Date ? value.toISOString() : value
|
|
49
50
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
51
|
+
|
|
52
|
+
const toParamNode = (value: unknown): ParamNode | undefined => {
|
|
53
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
54
|
+
const type = Object.getOwnPropertyDescriptor(value, 'type')?.value;
|
|
55
|
+
if (type !== 'Param') return undefined;
|
|
56
|
+
const name = Object.getOwnPropertyDescriptor(value, 'name')?.value;
|
|
57
|
+
if (typeof name !== 'string') return undefined;
|
|
58
|
+
return { type: 'Param', name };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Converts a ColumnRef to a ColumnNode
|
|
63
|
+
* @throws Error if the ColumnRef doesn't have a table specified
|
|
64
|
+
*/
|
|
65
|
+
const columnRefToNode = (col: ColumnRef): ColumnNode => {
|
|
56
66
|
if (!col.table) {
|
|
57
67
|
throw new Error(
|
|
58
68
|
`Column "${col.name}" requires a table reference. ` +
|
|
@@ -67,11 +77,16 @@ const columnRefToNode = (col: ColumnRef): ColumnNode => {
|
|
|
67
77
|
* @param value - Value to convert (OperandNode, ColumnRef, or literal value)
|
|
68
78
|
* @returns OperandNode representing the value
|
|
69
79
|
*/
|
|
70
|
-
const toOperandNode = (value: OperandNode | ColumnRef | LiteralValue): OperandNode => {
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
return
|
|
74
|
-
}
|
|
80
|
+
const toOperandNode = (value: OperandNode | ColumnRef | LiteralValue): OperandNode => {
|
|
81
|
+
const paramNode = toParamNode(value);
|
|
82
|
+
if (paramNode) {
|
|
83
|
+
return paramNode;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Already an operand node
|
|
87
|
+
if (isOperandNode(value)) {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
75
90
|
|
|
76
91
|
// Literal value
|
|
77
92
|
if (isLiteralValue(value)) {
|
|
@@ -87,12 +102,16 @@ const toOperandNode = (value: OperandNode | ColumnRef | LiteralValue): OperandNo
|
|
|
87
102
|
* @param value - Value or operand to normalize
|
|
88
103
|
* @returns OperandNode representing the value
|
|
89
104
|
*/
|
|
90
|
-
export const valueToOperand = (value: ValueOperandInput): OperandNode => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
105
|
+
export const valueToOperand = (value: ValueOperandInput): OperandNode => {
|
|
106
|
+
const paramNode = toParamNode(value);
|
|
107
|
+
if (paramNode) {
|
|
108
|
+
return paramNode;
|
|
109
|
+
}
|
|
110
|
+
if (isOperandNode(value)) {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
return toLiteralNode(value);
|
|
114
|
+
};
|
|
96
115
|
|
|
97
116
|
/**
|
|
98
117
|
* Converts various input types to an OperandNode
|
|
@@ -183,31 +183,41 @@ const operandTypes = new Set<OperandNode['type']>([
|
|
|
183
183
|
'ScalarSubquery',
|
|
184
184
|
'CaseExpression',
|
|
185
185
|
'Cast',
|
|
186
|
-
'WindowFunction',
|
|
187
|
-
'ArithmeticExpression',
|
|
188
|
-
'BitwiseExpression',
|
|
189
|
-
'Collate'
|
|
190
|
-
]);
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
typeof value
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
export const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
186
|
+
'WindowFunction',
|
|
187
|
+
'ArithmeticExpression',
|
|
188
|
+
'BitwiseExpression',
|
|
189
|
+
'Collate'
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
const getNodeType = (value: unknown): string | undefined => {
|
|
193
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
194
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, 'type');
|
|
195
|
+
if (descriptor && typeof descriptor.value === 'string') {
|
|
196
|
+
return descriptor.value;
|
|
197
|
+
}
|
|
198
|
+
if ('type' in value) {
|
|
199
|
+
const type = (value as { type?: unknown }).type;
|
|
200
|
+
return typeof type === 'string' ? type : undefined;
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const isOperandNode = (node: unknown): node is OperandNode => {
|
|
206
|
+
const type = getNodeType(node);
|
|
207
|
+
return type !== undefined && operandTypes.has(type as OperandNode['type']);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const isFunctionNode = (node: unknown): node is FunctionNode =>
|
|
211
|
+
isOperandNode(node) && getNodeType(node) === 'Function';
|
|
212
|
+
export const isCaseExpressionNode = (node: unknown): node is CaseExpressionNode =>
|
|
213
|
+
isOperandNode(node) && getNodeType(node) === 'CaseExpression';
|
|
214
|
+
|
|
215
|
+
export const isCastExpressionNode = (node: unknown): node is CastExpressionNode =>
|
|
216
|
+
isOperandNode(node) && getNodeType(node) === 'Cast';
|
|
217
|
+
export const isCollateExpressionNode = (node: unknown): node is CollateExpressionNode =>
|
|
218
|
+
isOperandNode(node) && getNodeType(node) === 'Collate';
|
|
219
|
+
export const isWindowFunctionNode = (node: unknown): node is WindowFunctionNode =>
|
|
220
|
+
isOperandNode(node) && getNodeType(node) === 'WindowFunction';
|
|
211
221
|
export const isExpressionSelectionNode = (
|
|
212
222
|
node: ColumnRef | FunctionNode | CaseExpressionNode | CastExpressionNode | WindowFunctionNode
|
|
213
223
|
): node is FunctionNode | CaseExpressionNode | CastExpressionNode | WindowFunctionNode =>
|
|
@@ -122,8 +122,15 @@ export const clearOperandDispatchers = (): void => {
|
|
|
122
122
|
operandRegistry = operandRegistry.clear();
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
-
const getNodeType = (node: { type?: string } | null | undefined): string | undefined =>
|
|
126
|
-
typeof node
|
|
125
|
+
const getNodeType = (node: { type?: string } | null | undefined): string | undefined => {
|
|
126
|
+
if (typeof node !== 'object' || node === null) return undefined;
|
|
127
|
+
const descriptor = Object.getOwnPropertyDescriptor(node, 'type');
|
|
128
|
+
if (descriptor && typeof descriptor.value === 'string') {
|
|
129
|
+
return descriptor.value;
|
|
130
|
+
}
|
|
131
|
+
const type = node.type;
|
|
132
|
+
return typeof type === 'string' ? type : undefined;
|
|
133
|
+
};
|
|
127
134
|
|
|
128
135
|
const unsupportedExpression = (node: ExpressionNode): never => {
|
|
129
136
|
throw new Error(`Unsupported expression type "${getNodeType(node) ?? 'unknown'}"`);
|
|
@@ -138,33 +145,34 @@ const unsupportedOperand = (node: OperandNode): never => {
|
|
|
138
145
|
* @param visitor - Visitor implementation
|
|
139
146
|
*/
|
|
140
147
|
export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
|
|
141
|
-
const
|
|
148
|
+
const type = getNodeType(node);
|
|
149
|
+
const dynamic = type ? expressionRegistry.get(type) : undefined;
|
|
142
150
|
if (dynamic) return dynamic(node, visitor);
|
|
143
151
|
|
|
144
|
-
switch (
|
|
152
|
+
switch (type) {
|
|
145
153
|
case 'BinaryExpression':
|
|
146
|
-
if (visitor.visitBinaryExpression) return visitor.visitBinaryExpression(node);
|
|
154
|
+
if (visitor.visitBinaryExpression) return visitor.visitBinaryExpression(node as BinaryExpressionNode);
|
|
147
155
|
break;
|
|
148
156
|
case 'LogicalExpression':
|
|
149
|
-
if (visitor.visitLogicalExpression) return visitor.visitLogicalExpression(node);
|
|
157
|
+
if (visitor.visitLogicalExpression) return visitor.visitLogicalExpression(node as LogicalExpressionNode);
|
|
150
158
|
break;
|
|
151
159
|
case 'NullExpression':
|
|
152
|
-
if (visitor.visitNullExpression) return visitor.visitNullExpression(node);
|
|
160
|
+
if (visitor.visitNullExpression) return visitor.visitNullExpression(node as NullExpressionNode);
|
|
153
161
|
break;
|
|
154
162
|
case 'InExpression':
|
|
155
|
-
if (visitor.visitInExpression) return visitor.visitInExpression(node);
|
|
163
|
+
if (visitor.visitInExpression) return visitor.visitInExpression(node as InExpressionNode);
|
|
156
164
|
break;
|
|
157
165
|
case 'ExistsExpression':
|
|
158
|
-
if (visitor.visitExistsExpression) return visitor.visitExistsExpression(node);
|
|
166
|
+
if (visitor.visitExistsExpression) return visitor.visitExistsExpression(node as ExistsExpressionNode);
|
|
159
167
|
break;
|
|
160
168
|
case 'BetweenExpression':
|
|
161
|
-
if (visitor.visitBetweenExpression) return visitor.visitBetweenExpression(node);
|
|
169
|
+
if (visitor.visitBetweenExpression) return visitor.visitBetweenExpression(node as BetweenExpressionNode);
|
|
162
170
|
break;
|
|
163
171
|
case 'ArithmeticExpression':
|
|
164
|
-
if (visitor.visitArithmeticExpression) return visitor.visitArithmeticExpression(node);
|
|
172
|
+
if (visitor.visitArithmeticExpression) return visitor.visitArithmeticExpression(node as ArithmeticExpressionNode);
|
|
165
173
|
break;
|
|
166
174
|
case 'BitwiseExpression':
|
|
167
|
-
if (visitor.visitBitwiseExpression) return visitor.visitBitwiseExpression(node);
|
|
175
|
+
if (visitor.visitBitwiseExpression) return visitor.visitBitwiseExpression(node as BitwiseExpressionNode);
|
|
168
176
|
break;
|
|
169
177
|
default:
|
|
170
178
|
break;
|
|
@@ -179,42 +187,43 @@ export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisi
|
|
|
179
187
|
* @param visitor - Visitor implementation
|
|
180
188
|
*/
|
|
181
189
|
export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
|
|
182
|
-
const
|
|
190
|
+
const type = getNodeType(node);
|
|
191
|
+
const dynamic = type ? operandRegistry.get(type) : undefined;
|
|
183
192
|
if (dynamic) return dynamic(node, visitor);
|
|
184
193
|
|
|
185
|
-
switch (
|
|
194
|
+
switch (type) {
|
|
186
195
|
case 'Column':
|
|
187
|
-
if (visitor.visitColumn) return visitor.visitColumn(node);
|
|
196
|
+
if (visitor.visitColumn) return visitor.visitColumn(node as ColumnNode);
|
|
188
197
|
break;
|
|
189
198
|
case 'Literal':
|
|
190
|
-
if (visitor.visitLiteral) return visitor.visitLiteral(node);
|
|
199
|
+
if (visitor.visitLiteral) return visitor.visitLiteral(node as LiteralNode);
|
|
191
200
|
break;
|
|
192
201
|
case 'Param':
|
|
193
|
-
if (visitor.visitParam) return visitor.visitParam(node);
|
|
202
|
+
if (visitor.visitParam) return visitor.visitParam(node as ParamNode);
|
|
194
203
|
break;
|
|
195
204
|
case 'Function':
|
|
196
|
-
if (visitor.visitFunction) return visitor.visitFunction(node);
|
|
205
|
+
if (visitor.visitFunction) return visitor.visitFunction(node as FunctionNode);
|
|
197
206
|
break;
|
|
198
207
|
case 'JsonPath':
|
|
199
|
-
if (visitor.visitJsonPath) return visitor.visitJsonPath(node);
|
|
208
|
+
if (visitor.visitJsonPath) return visitor.visitJsonPath(node as JsonPathNode);
|
|
200
209
|
break;
|
|
201
210
|
case 'ScalarSubquery':
|
|
202
|
-
if (visitor.visitScalarSubquery) return visitor.visitScalarSubquery(node);
|
|
211
|
+
if (visitor.visitScalarSubquery) return visitor.visitScalarSubquery(node as ScalarSubqueryNode);
|
|
203
212
|
break;
|
|
204
213
|
case 'CaseExpression':
|
|
205
|
-
if (visitor.visitCaseExpression) return visitor.visitCaseExpression(node);
|
|
214
|
+
if (visitor.visitCaseExpression) return visitor.visitCaseExpression(node as CaseExpressionNode);
|
|
206
215
|
break;
|
|
207
216
|
case 'WindowFunction':
|
|
208
|
-
if (visitor.visitWindowFunction) return visitor.visitWindowFunction(node);
|
|
217
|
+
if (visitor.visitWindowFunction) return visitor.visitWindowFunction(node as WindowFunctionNode);
|
|
209
218
|
break;
|
|
210
219
|
case 'AliasRef':
|
|
211
|
-
if (visitor.visitAliasRef) return visitor.visitAliasRef(node);
|
|
220
|
+
if (visitor.visitAliasRef) return visitor.visitAliasRef(node as AliasRefNode);
|
|
212
221
|
break;
|
|
213
222
|
case 'Cast':
|
|
214
|
-
if (visitor.visitCast) return visitor.visitCast(node);
|
|
223
|
+
if (visitor.visitCast) return visitor.visitCast(node as CastExpressionNode);
|
|
215
224
|
break;
|
|
216
225
|
case 'Collate':
|
|
217
|
-
if (visitor.visitCollate) return visitor.visitCollate(node);
|
|
226
|
+
if (visitor.visitCollate) return visitor.visitCollate(node as CollateExpressionNode);
|
|
218
227
|
break;
|
|
219
228
|
default:
|
|
220
229
|
break;
|
|
@@ -21,7 +21,7 @@ const buildParamProxy = (name: string): ParamProxy => {
|
|
|
21
21
|
const nextName = name ? `${name}.${trimmed}` : trimmed;
|
|
22
22
|
return buildParamProxy(nextName);
|
|
23
23
|
}
|
|
24
|
-
if (prop in t) {
|
|
24
|
+
if (prop in t && name === '') {
|
|
25
25
|
return (t as unknown as Record<string, unknown>)[prop];
|
|
26
26
|
}
|
|
27
27
|
const nextName = name ? `${name}.${prop}` : prop;
|
|
@@ -41,9 +41,6 @@ export const createParamProxy = (): ParamProxyRoot => {
|
|
|
41
41
|
if (typeof prop === 'string' && prop.startsWith('$')) {
|
|
42
42
|
return buildParamProxy(prop.slice(1));
|
|
43
43
|
}
|
|
44
|
-
if (prop in t) {
|
|
45
|
-
return (t as unknown as Record<string, unknown>)[prop];
|
|
46
|
-
}
|
|
47
44
|
return buildParamProxy(String(prop));
|
|
48
45
|
}
|
|
49
46
|
}) as ParamProxyRoot;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CommonTableExpressionNode,
|
|
3
|
+
DerivedTableNode,
|
|
4
|
+
FunctionTableNode,
|
|
5
|
+
OrderByNode,
|
|
6
|
+
OrderingTerm,
|
|
7
|
+
SelectQueryNode,
|
|
8
|
+
SetOperationNode,
|
|
9
|
+
TableSourceNode
|
|
10
|
+
} from './query.js';
|
|
11
|
+
import type { JoinNode } from './join.js';
|
|
12
|
+
import type {
|
|
13
|
+
ExpressionNode,
|
|
14
|
+
OperandNode,
|
|
15
|
+
ParamNode
|
|
16
|
+
} from './expression-nodes.js';
|
|
17
|
+
import { isOperandNode } from './expression-nodes.js';
|
|
18
|
+
|
|
19
|
+
export interface SelectQueryVisitor {
|
|
20
|
+
visitSelectQuery?(node: SelectQueryNode): void;
|
|
21
|
+
visitTableSource?(node: TableSourceNode): void;
|
|
22
|
+
visitDerivedTable?(node: DerivedTableNode): void;
|
|
23
|
+
visitFunctionTable?(node: FunctionTableNode): void;
|
|
24
|
+
visitJoin?(node: JoinNode): void;
|
|
25
|
+
visitCte?(node: CommonTableExpressionNode): void;
|
|
26
|
+
visitSetOperation?(node: SetOperationNode): void;
|
|
27
|
+
visitOrderBy?(node: OrderByNode): void;
|
|
28
|
+
visitExpression?(node: ExpressionNode): void;
|
|
29
|
+
visitOperand?(node: OperandNode): void;
|
|
30
|
+
visitParam?(node: ParamNode): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const getNodeType = (value: unknown): string | undefined => {
|
|
34
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
35
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, 'type');
|
|
36
|
+
if (descriptor && typeof descriptor.value === 'string') {
|
|
37
|
+
return descriptor.value;
|
|
38
|
+
}
|
|
39
|
+
if ('type' in value) {
|
|
40
|
+
const type = (value as { type?: unknown }).type;
|
|
41
|
+
return typeof type === 'string' ? type : undefined;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const visitOrderingTerm = (term: OrderingTerm, visitor: SelectQueryVisitor): void => {
|
|
47
|
+
if (isOperandNode(term)) {
|
|
48
|
+
visitOperandNode(term, visitor);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
visitExpressionNode(term as ExpressionNode, visitor);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const visitOrderByNode = (node: OrderByNode, visitor: SelectQueryVisitor): void => {
|
|
55
|
+
visitor.visitOrderBy?.(node);
|
|
56
|
+
visitOrderingTerm(node.term, visitor);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const visitTableSource = (source: TableSourceNode, visitor: SelectQueryVisitor): void => {
|
|
60
|
+
visitor.visitTableSource?.(source);
|
|
61
|
+
if (source.type === 'DerivedTable') {
|
|
62
|
+
visitor.visitDerivedTable?.(source);
|
|
63
|
+
visitSelectQuery(source.query, visitor);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (source.type === 'FunctionTable') {
|
|
67
|
+
visitor.visitFunctionTable?.(source);
|
|
68
|
+
source.args?.forEach(arg => visitOperandNode(arg, visitor));
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const visitExpressionNode = (node: ExpressionNode, visitor: SelectQueryVisitor): void => {
|
|
73
|
+
visitor.visitExpression?.(node);
|
|
74
|
+
const type = getNodeType(node);
|
|
75
|
+
if (!type) return;
|
|
76
|
+
switch (type) {
|
|
77
|
+
case 'BinaryExpression':
|
|
78
|
+
visitOperandNode(node.left, visitor);
|
|
79
|
+
visitOperandNode(node.right, visitor);
|
|
80
|
+
if (node.escape) {
|
|
81
|
+
visitOperandNode(node.escape, visitor);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
case 'LogicalExpression':
|
|
85
|
+
node.operands.forEach(operand => visitExpressionNode(operand, visitor));
|
|
86
|
+
return;
|
|
87
|
+
case 'NullExpression':
|
|
88
|
+
visitOperandNode(node.left, visitor);
|
|
89
|
+
return;
|
|
90
|
+
case 'InExpression':
|
|
91
|
+
visitOperandNode(node.left, visitor);
|
|
92
|
+
if (Array.isArray(node.right)) {
|
|
93
|
+
node.right.forEach(operand => visitOperandNode(operand, visitor));
|
|
94
|
+
} else {
|
|
95
|
+
visitOperandNode(node.right, visitor);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
case 'ExistsExpression':
|
|
99
|
+
visitSelectQuery(node.subquery, visitor);
|
|
100
|
+
return;
|
|
101
|
+
case 'BetweenExpression':
|
|
102
|
+
visitOperandNode(node.left, visitor);
|
|
103
|
+
visitOperandNode(node.lower, visitor);
|
|
104
|
+
visitOperandNode(node.upper, visitor);
|
|
105
|
+
return;
|
|
106
|
+
case 'ArithmeticExpression':
|
|
107
|
+
visitOperandNode(node.left, visitor);
|
|
108
|
+
visitOperandNode(node.right, visitor);
|
|
109
|
+
return;
|
|
110
|
+
case 'BitwiseExpression':
|
|
111
|
+
visitOperandNode(node.left, visitor);
|
|
112
|
+
visitOperandNode(node.right, visitor);
|
|
113
|
+
return;
|
|
114
|
+
default: {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const visitOperandNode = (node: OperandNode, visitor: SelectQueryVisitor): void => {
|
|
121
|
+
visitor.visitOperand?.(node);
|
|
122
|
+
const type = getNodeType(node);
|
|
123
|
+
if (type === 'Param') {
|
|
124
|
+
visitor.visitParam?.(node);
|
|
125
|
+
}
|
|
126
|
+
if (!type) return;
|
|
127
|
+
switch (type) {
|
|
128
|
+
case 'Column':
|
|
129
|
+
case 'Literal':
|
|
130
|
+
case 'Param':
|
|
131
|
+
case 'AliasRef':
|
|
132
|
+
return;
|
|
133
|
+
case 'Function':
|
|
134
|
+
node.args?.forEach(arg => visitOperandNode(arg, visitor));
|
|
135
|
+
node.orderBy?.forEach(order => visitOrderByNode(order, visitor));
|
|
136
|
+
if (node.separator) {
|
|
137
|
+
visitOperandNode(node.separator, visitor);
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
case 'JsonPath':
|
|
141
|
+
visitOperandNode(node.column, visitor);
|
|
142
|
+
return;
|
|
143
|
+
case 'ScalarSubquery':
|
|
144
|
+
visitSelectQuery(node.query, visitor);
|
|
145
|
+
return;
|
|
146
|
+
case 'CaseExpression':
|
|
147
|
+
node.conditions.forEach(cond => {
|
|
148
|
+
visitExpressionNode(cond.when, visitor);
|
|
149
|
+
visitOperandNode(cond.then, visitor);
|
|
150
|
+
});
|
|
151
|
+
if (node.else) {
|
|
152
|
+
visitOperandNode(node.else, visitor);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
case 'Cast':
|
|
156
|
+
visitOperandNode(node.expression, visitor);
|
|
157
|
+
return;
|
|
158
|
+
case 'WindowFunction':
|
|
159
|
+
node.args?.forEach(arg => visitOperandNode(arg, visitor));
|
|
160
|
+
node.partitionBy?.forEach(term => visitOperandNode(term, visitor));
|
|
161
|
+
node.orderBy?.forEach(order => visitOrderByNode(order, visitor));
|
|
162
|
+
return;
|
|
163
|
+
case 'ArithmeticExpression':
|
|
164
|
+
visitOperandNode(node.left, visitor);
|
|
165
|
+
visitOperandNode(node.right, visitor);
|
|
166
|
+
return;
|
|
167
|
+
case 'BitwiseExpression':
|
|
168
|
+
visitOperandNode(node.left, visitor);
|
|
169
|
+
visitOperandNode(node.right, visitor);
|
|
170
|
+
return;
|
|
171
|
+
case 'Collate':
|
|
172
|
+
visitOperandNode(node.expression, visitor);
|
|
173
|
+
return;
|
|
174
|
+
default: {
|
|
175
|
+
const _exhaustive: never = node;
|
|
176
|
+
return _exhaustive;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const visitSelectQuery = (ast: SelectQueryNode, visitor: SelectQueryVisitor): void => {
|
|
182
|
+
visitor.visitSelectQuery?.(ast);
|
|
183
|
+
|
|
184
|
+
if (ast.ctes) {
|
|
185
|
+
for (const cte of ast.ctes) {
|
|
186
|
+
visitor.visitCte?.(cte);
|
|
187
|
+
visitSelectQuery(cte.query, visitor);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
visitTableSource(ast.from, visitor);
|
|
192
|
+
|
|
193
|
+
ast.columns?.forEach(col => {
|
|
194
|
+
visitOperandNode(col as OperandNode, visitor);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
ast.joins?.forEach(join => {
|
|
198
|
+
visitor.visitJoin?.(join);
|
|
199
|
+
visitTableSource(join.table, visitor);
|
|
200
|
+
visitExpressionNode(join.condition, visitor);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (ast.where) {
|
|
204
|
+
visitExpressionNode(ast.where, visitor);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ast.groupBy?.forEach(term => {
|
|
208
|
+
visitOrderingTerm(term, visitor);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (ast.having) {
|
|
212
|
+
visitExpressionNode(ast.having, visitor);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ast.orderBy?.forEach(order => {
|
|
216
|
+
visitOrderByNode(order, visitor);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
ast.distinct?.forEach(col => {
|
|
220
|
+
visitOperandNode(col, visitor);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
ast.setOps?.forEach(op => {
|
|
224
|
+
visitor.visitSetOperation?.(op);
|
|
225
|
+
visitSelectQuery(op.query, visitor);
|
|
226
|
+
});
|
|
227
|
+
};
|