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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.83",
3
+ "version": "1.0.85",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -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
- if (ast.where && hasParamOperandsInExpression(ast.where)) {
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.orderBy?.forEach(ord => {
164
- if (ord.term) {
165
- if (hasParamOperandsInOperand(ord.term as OperandNode)) {
166
- return true;
167
- }
7
+ visitSelectQuery(ast, {
8
+ visitParam: () => {
9
+ hasParams = true;
168
10
  }
169
11
  });
170
12
 
171
- if (ast.ctes) {
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 hasTypeProperty = (value: unknown): value is { type?: string } =>
193
- typeof value === 'object' && value !== null && 'type' in value;
194
-
195
- export const isOperandNode = (node: unknown): node is OperandNode => {
196
- if (!hasTypeProperty(node)) return false;
197
- return operandTypes.has(node.type as OperandNode['type']);
198
- };
199
-
200
- export const isFunctionNode = (node: unknown): node is FunctionNode =>
201
- isOperandNode(node) && node.type === 'Function';
202
- export const isCaseExpressionNode = (node: unknown): node is CaseExpressionNode =>
203
- isOperandNode(node) && node.type === 'CaseExpression';
204
-
205
- export const isCastExpressionNode = (node: unknown): node is CastExpressionNode =>
206
- isOperandNode(node) && node.type === 'Cast';
207
- export const isCollateExpressionNode = (node: unknown): node is CollateExpressionNode =>
208
- isOperandNode(node) && node.type === 'Collate';
209
- export const isWindowFunctionNode = (node: unknown): node is WindowFunctionNode =>
210
- isOperandNode(node) && node.type === 'WindowFunction';
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 === 'object' && node !== null && typeof node.type === 'string' ? node.type : undefined;
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 dynamic = expressionRegistry.get(node.type);
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 (node.type) {
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 dynamic = operandRegistry.get(node.type);
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 (node.type) {
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 compiler = this.operandCompilers.get(node.type);
392
- if (!compiler) {
393
- throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
394
- }
395
- return compiler(node, ctx);
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
  }