metal-orm 1.0.83 → 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 +228 -222
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +228 -222
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/ast-validation.ts +6 -180
- package/src/core/ast/expression-nodes.ts +35 -25
- package/src/core/ast/expression-visitor.ts +34 -25
- package/src/core/ast/query-visitor.ts +227 -0
- package/src/core/dialect/abstract.ts +11 -7
- package/src/query-builder/query-ast-service.ts +13 -0
package/package.json
CHANGED
|
@@ -1,188 +1,14 @@
|
|
|
1
|
-
import type { ExpressionNode, OperandNode } from './expression-nodes.js';
|
|
2
|
-
import { visitExpression, visitOperand } from './expression-visitor.js';
|
|
3
1
|
import type { SelectQueryNode } from './query.js';
|
|
4
|
-
|
|
5
|
-
const hasParamOperandsInExpression = (expr: ExpressionNode): boolean => {
|
|
6
|
-
let hasParams = false;
|
|
7
|
-
|
|
8
|
-
visitExpression(expr, {
|
|
9
|
-
visitBinaryExpression: (node) => {
|
|
10
|
-
visitOperand(node.left, {
|
|
11
|
-
visitParam: () => { hasParams = true; },
|
|
12
|
-
otherwise: () => {}
|
|
13
|
-
});
|
|
14
|
-
visitOperand(node.right, {
|
|
15
|
-
visitParam: () => { hasParams = true; },
|
|
16
|
-
otherwise: () => {}
|
|
17
|
-
});
|
|
18
|
-
if (node.escape) {
|
|
19
|
-
visitOperand(node.escape, {
|
|
20
|
-
visitParam: () => { hasParams = true; },
|
|
21
|
-
otherwise: () => {}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
visitLogicalExpression: (node) => {
|
|
26
|
-
node.operands.forEach(operand => {
|
|
27
|
-
if (hasParamOperandsInExpression(operand)) {
|
|
28
|
-
hasParams = true;
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
visitNullExpression: () => {},
|
|
33
|
-
visitInExpression: (node) => {
|
|
34
|
-
visitOperand(node.left, {
|
|
35
|
-
visitParam: () => { hasParams = true; },
|
|
36
|
-
otherwise: () => {}
|
|
37
|
-
});
|
|
38
|
-
if (Array.isArray(node.right)) {
|
|
39
|
-
node.right.forEach(operand => visitOperand(operand, {
|
|
40
|
-
visitParam: () => { hasParams = true; },
|
|
41
|
-
otherwise: () => {}
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
visitExistsExpression: () => {},
|
|
46
|
-
visitBetweenExpression: (node) => {
|
|
47
|
-
visitOperand(node.left, {
|
|
48
|
-
visitParam: () => { hasParams = true; },
|
|
49
|
-
otherwise: () => {}
|
|
50
|
-
});
|
|
51
|
-
visitOperand(node.lower, {
|
|
52
|
-
visitParam: () => { hasParams = true; },
|
|
53
|
-
otherwise: () => {}
|
|
54
|
-
});
|
|
55
|
-
visitOperand(node.upper, {
|
|
56
|
-
visitParam: () => { hasParams = true; },
|
|
57
|
-
otherwise: () => {}
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
visitArithmeticExpression: (node) => {
|
|
61
|
-
visitOperand(node.left, {
|
|
62
|
-
visitParam: () => { hasParams = true; },
|
|
63
|
-
otherwise: () => {}
|
|
64
|
-
});
|
|
65
|
-
visitOperand(node.right, {
|
|
66
|
-
visitParam: () => { hasParams = true; },
|
|
67
|
-
otherwise: () => {}
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
visitBitwiseExpression: (node) => {
|
|
71
|
-
visitOperand(node.left, {
|
|
72
|
-
visitParam: () => { hasParams = true; },
|
|
73
|
-
otherwise: () => {}
|
|
74
|
-
});
|
|
75
|
-
visitOperand(node.right, {
|
|
76
|
-
visitParam: () => { hasParams = true; },
|
|
77
|
-
otherwise: () => {}
|
|
78
|
-
});
|
|
79
|
-
},
|
|
80
|
-
otherwise: () => {}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return hasParams;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const hasParamOperandsInOperand = (operand: OperandNode): boolean => {
|
|
87
|
-
let hasParams = false;
|
|
88
|
-
|
|
89
|
-
visitOperand(operand, {
|
|
90
|
-
visitColumn: () => {},
|
|
91
|
-
visitLiteral: () => {},
|
|
92
|
-
visitParam: () => { hasParams = true; },
|
|
93
|
-
visitFunction: (node) => {
|
|
94
|
-
node.args?.forEach(arg => {
|
|
95
|
-
if (hasParamOperandsInOperand(arg)) {
|
|
96
|
-
hasParams = true;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
},
|
|
100
|
-
visitJsonPath: () => {},
|
|
101
|
-
visitScalarSubquery: () => {},
|
|
102
|
-
visitCaseExpression: (node) => {
|
|
103
|
-
node.conditions.forEach(cond => {
|
|
104
|
-
if (hasParamOperandsInExpression(cond.when)) {
|
|
105
|
-
hasParams = true;
|
|
106
|
-
}
|
|
107
|
-
if (hasParamOperandsInOperand(cond.then)) {
|
|
108
|
-
hasParams = true;
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
if (node.else && hasParamOperandsInOperand(node.else)) {
|
|
112
|
-
hasParams = true;
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
visitCast: (node) => {
|
|
116
|
-
if (hasParamOperandsInOperand(node.expression)) {
|
|
117
|
-
hasParams = true;
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
visitWindowFunction: (node) => {
|
|
121
|
-
node.args?.forEach(arg => {
|
|
122
|
-
if (hasParamOperandsInOperand(arg)) {
|
|
123
|
-
hasParams = true;
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
node.orderBy?.forEach(ord => {
|
|
127
|
-
if (ord.term) {
|
|
128
|
-
if (hasParamOperandsInOperand(ord.term as OperandNode)) {
|
|
129
|
-
hasParams = true;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
},
|
|
134
|
-
visitCollate: (node) => {
|
|
135
|
-
if (hasParamOperandsInOperand(node.expression)) {
|
|
136
|
-
hasParams = true;
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
visitAliasRef: () => {},
|
|
140
|
-
otherwise: () => {}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
return hasParams;
|
|
144
|
-
};
|
|
2
|
+
import { visitSelectQuery } from './query-visitor.js';
|
|
145
3
|
|
|
146
4
|
export const hasParamOperandsInQuery = (ast: SelectQueryNode): boolean => {
|
|
147
|
-
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (ast.having && hasParamOperandsInExpression(ast.having)) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
ast.columns?.forEach(col => {
|
|
156
|
-
if (typeof col === 'object' && col !== null && 'type' in col) {
|
|
157
|
-
if (hasParamOperandsInOperand(col as OperandNode)) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
});
|
|
5
|
+
let hasParams = false;
|
|
162
6
|
|
|
163
|
-
ast
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
7
|
+
visitSelectQuery(ast, {
|
|
8
|
+
visitParam: () => {
|
|
9
|
+
hasParams = true;
|
|
168
10
|
}
|
|
169
11
|
});
|
|
170
12
|
|
|
171
|
-
|
|
172
|
-
for (const cte of ast.ctes) {
|
|
173
|
-
if (cte.query.where && hasParamOperandsInExpression(cte.query.where)) {
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (ast.setOps) {
|
|
180
|
-
for (const op of ast.setOps) {
|
|
181
|
-
if (hasParamOperandsInQuery(op.query)) {
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return false;
|
|
13
|
+
return hasParams;
|
|
188
14
|
};
|
|
@@ -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;
|
|
@@ -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
|
+
};
|
|
@@ -387,13 +387,17 @@ export abstract class Dialect
|
|
|
387
387
|
* @param ctx - Compiler context
|
|
388
388
|
* @returns Compiled SQL operand
|
|
389
389
|
*/
|
|
390
|
-
protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
390
|
+
protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
391
|
+
const descriptor = Object.getOwnPropertyDescriptor(node, 'type');
|
|
392
|
+
const nodeType = typeof descriptor?.value === 'string'
|
|
393
|
+
? descriptor.value
|
|
394
|
+
: (typeof node.type === 'string' ? node.type : undefined);
|
|
395
|
+
const compiler = nodeType ? this.operandCompilers.get(nodeType) : undefined;
|
|
396
|
+
if (!compiler) {
|
|
397
|
+
throw new Error(`Unsupported operand node type "${nodeType ?? 'unknown'}" for ${this.constructor.name}`);
|
|
398
|
+
}
|
|
399
|
+
return compiler(node, ctx);
|
|
400
|
+
}
|
|
397
401
|
|
|
398
402
|
/**
|
|
399
403
|
* Compiles an ordering term (operand, expression, or alias reference).
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
CastExpressionNode,
|
|
19
19
|
WindowFunctionNode,
|
|
20
20
|
ScalarSubqueryNode,
|
|
21
|
+
ParamNode,
|
|
21
22
|
and,
|
|
22
23
|
isExpressionSelectionNode,
|
|
23
24
|
isOperandNode
|
|
@@ -258,6 +259,10 @@ export class QueryAstService {
|
|
|
258
259
|
* @returns Normalized ordering term
|
|
259
260
|
*/
|
|
260
261
|
private normalizeOrderingTerm(term: ColumnDef | OrderingTerm): OrderingTerm {
|
|
262
|
+
const paramNode = this.toParamNode(term);
|
|
263
|
+
if (paramNode) {
|
|
264
|
+
return paramNode;
|
|
265
|
+
}
|
|
261
266
|
const from = this.state.ast.from;
|
|
262
267
|
const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
|
|
263
268
|
const termType = (term as { type?: string }).type;
|
|
@@ -284,4 +289,12 @@ export class QueryAstService {
|
|
|
284
289
|
return buildColumnNode(tableRef, term as ColumnDef);
|
|
285
290
|
}
|
|
286
291
|
|
|
292
|
+
private toParamNode(value: unknown): ParamNode | undefined {
|
|
293
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
294
|
+
const type = Object.getOwnPropertyDescriptor(value, 'type')?.value;
|
|
295
|
+
if (type !== 'Param') return undefined;
|
|
296
|
+
const name = Object.getOwnPropertyDescriptor(value, 'name')?.value;
|
|
297
|
+
if (typeof name !== 'string') return undefined;
|
|
298
|
+
return { type: 'Param', name };
|
|
299
|
+
}
|
|
287
300
|
}
|