metal-orm 1.0.83 → 1.0.86

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.86",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -1,188 +1,19 @@
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';
2
+ import { visitSelectQuery } from './query-visitor.js';
4
3
 
5
- const hasParamOperandsInExpression = (expr: ExpressionNode): boolean => {
6
- let hasParams = false;
4
+ export const findFirstParamOperandName = (ast: SelectQueryNode): string | undefined => {
5
+ let name: string | undefined;
7
6
 
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
- };
145
-
146
- 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;
7
+ visitSelectQuery(ast, {
8
+ visitParam: (node) => {
9
+ if (!name) {
10
+ name = node.name;
159
11
  }
160
12
  }
161
13
  });
162
14
 
163
- ast.orderBy?.forEach(ord => {
164
- if (ord.term) {
165
- if (hasParamOperandsInOperand(ord.term as OperandNode)) {
166
- return true;
167
- }
168
- }
169
- });
170
-
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;
15
+ return name;
188
16
  };
17
+
18
+ export const hasParamOperandsInQuery = (ast: SelectQueryNode): boolean =>
19
+ !!findFirstParamOperandName(ast);
@@ -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 =>
@@ -21,6 +21,7 @@ import {
21
21
  BitwiseExpressionNode,
22
22
  ParamNode
23
23
  } from './expression-nodes.js';
24
+ import type { SelectQueryNode } from './query.js';
24
25
 
25
26
  /**
26
27
  * Visitor for expression nodes
@@ -34,6 +35,8 @@ export interface ExpressionVisitor<R> {
34
35
  visitBetweenExpression?(node: BetweenExpressionNode): R;
35
36
  visitArithmeticExpression?(node: ArithmeticExpressionNode): R;
36
37
  visitBitwiseExpression?(node: BitwiseExpressionNode): R;
38
+ visitOperand?(node: OperandNode): R;
39
+ visitSelectQuery?(node: SelectQueryNode): R;
37
40
  otherwise?(node: ExpressionNode): R;
38
41
  }
39
42
 
@@ -50,6 +53,10 @@ export interface OperandVisitor<R> {
50
53
  visitCaseExpression?(node: CaseExpressionNode): R;
51
54
  visitCast?(node: CastExpressionNode): R;
52
55
  visitWindowFunction?(node: WindowFunctionNode): R;
56
+ visitArithmeticExpression?(node: ArithmeticExpressionNode): R;
57
+ visitBitwiseExpression?(node: BitwiseExpressionNode): R;
58
+ visitExpression?(node: ExpressionNode): R;
59
+ visitSelectQuery?(node: SelectQueryNode): R;
53
60
  visitCollate?(node: CollateExpressionNode): R;
54
61
  visitAliasRef?(node: AliasRefNode): R;
55
62
  otherwise?(node: OperandNode): R;
@@ -111,6 +118,12 @@ export const registerOperandDispatcher = (type: string, dispatcher: OperandDispa
111
118
  operandRegistry = operandRegistry.register(type, dispatcher);
112
119
  };
113
120
 
121
+ export const hasExpressionDispatcher = (type: string): boolean =>
122
+ expressionRegistry.get(type) !== undefined;
123
+
124
+ export const hasOperandDispatcher = (type: string): boolean =>
125
+ operandRegistry.get(type) !== undefined;
126
+
114
127
  /**
115
128
  * Clears all registered dispatchers. Primarily for tests.
116
129
  */
@@ -122,8 +135,15 @@ export const clearOperandDispatchers = (): void => {
122
135
  operandRegistry = operandRegistry.clear();
123
136
  };
124
137
 
125
- const getNodeType = (node: { type?: string } | null | undefined): string | undefined =>
126
- typeof node === 'object' && node !== null && typeof node.type === 'string' ? node.type : undefined;
138
+ const getNodeType = (node: { type?: string } | null | undefined): string | undefined => {
139
+ if (typeof node !== 'object' || node === null) return undefined;
140
+ const descriptor = Object.getOwnPropertyDescriptor(node, 'type');
141
+ if (descriptor && typeof descriptor.value === 'string') {
142
+ return descriptor.value;
143
+ }
144
+ const type = node.type;
145
+ return typeof type === 'string' ? type : undefined;
146
+ };
127
147
 
128
148
  const unsupportedExpression = (node: ExpressionNode): never => {
129
149
  throw new Error(`Unsupported expression type "${getNodeType(node) ?? 'unknown'}"`);
@@ -138,33 +158,34 @@ const unsupportedOperand = (node: OperandNode): never => {
138
158
  * @param visitor - Visitor implementation
139
159
  */
140
160
  export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
141
- const dynamic = expressionRegistry.get(node.type);
161
+ const type = getNodeType(node);
162
+ const dynamic = type ? expressionRegistry.get(type) : undefined;
142
163
  if (dynamic) return dynamic(node, visitor);
143
164
 
144
- switch (node.type) {
165
+ switch (type) {
145
166
  case 'BinaryExpression':
146
- if (visitor.visitBinaryExpression) return visitor.visitBinaryExpression(node);
167
+ if (visitor.visitBinaryExpression) return visitor.visitBinaryExpression(node as BinaryExpressionNode);
147
168
  break;
148
169
  case 'LogicalExpression':
149
- if (visitor.visitLogicalExpression) return visitor.visitLogicalExpression(node);
170
+ if (visitor.visitLogicalExpression) return visitor.visitLogicalExpression(node as LogicalExpressionNode);
150
171
  break;
151
172
  case 'NullExpression':
152
- if (visitor.visitNullExpression) return visitor.visitNullExpression(node);
173
+ if (visitor.visitNullExpression) return visitor.visitNullExpression(node as NullExpressionNode);
153
174
  break;
154
175
  case 'InExpression':
155
- if (visitor.visitInExpression) return visitor.visitInExpression(node);
176
+ if (visitor.visitInExpression) return visitor.visitInExpression(node as InExpressionNode);
156
177
  break;
157
178
  case 'ExistsExpression':
158
- if (visitor.visitExistsExpression) return visitor.visitExistsExpression(node);
179
+ if (visitor.visitExistsExpression) return visitor.visitExistsExpression(node as ExistsExpressionNode);
159
180
  break;
160
181
  case 'BetweenExpression':
161
- if (visitor.visitBetweenExpression) return visitor.visitBetweenExpression(node);
182
+ if (visitor.visitBetweenExpression) return visitor.visitBetweenExpression(node as BetweenExpressionNode);
162
183
  break;
163
184
  case 'ArithmeticExpression':
164
- if (visitor.visitArithmeticExpression) return visitor.visitArithmeticExpression(node);
185
+ if (visitor.visitArithmeticExpression) return visitor.visitArithmeticExpression(node as ArithmeticExpressionNode);
165
186
  break;
166
187
  case 'BitwiseExpression':
167
- if (visitor.visitBitwiseExpression) return visitor.visitBitwiseExpression(node);
188
+ if (visitor.visitBitwiseExpression) return visitor.visitBitwiseExpression(node as BitwiseExpressionNode);
168
189
  break;
169
190
  default:
170
191
  break;
@@ -179,42 +200,49 @@ export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisi
179
200
  * @param visitor - Visitor implementation
180
201
  */
181
202
  export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
182
- const dynamic = operandRegistry.get(node.type);
203
+ const type = getNodeType(node);
204
+ const dynamic = type ? operandRegistry.get(type) : undefined;
183
205
  if (dynamic) return dynamic(node, visitor);
184
206
 
185
- switch (node.type) {
207
+ switch (type) {
186
208
  case 'Column':
187
- if (visitor.visitColumn) return visitor.visitColumn(node);
209
+ if (visitor.visitColumn) return visitor.visitColumn(node as ColumnNode);
188
210
  break;
189
211
  case 'Literal':
190
- if (visitor.visitLiteral) return visitor.visitLiteral(node);
212
+ if (visitor.visitLiteral) return visitor.visitLiteral(node as LiteralNode);
191
213
  break;
192
214
  case 'Param':
193
- if (visitor.visitParam) return visitor.visitParam(node);
215
+ if (visitor.visitParam) return visitor.visitParam(node as ParamNode);
194
216
  break;
195
217
  case 'Function':
196
- if (visitor.visitFunction) return visitor.visitFunction(node);
218
+ if (visitor.visitFunction) return visitor.visitFunction(node as FunctionNode);
197
219
  break;
198
220
  case 'JsonPath':
199
- if (visitor.visitJsonPath) return visitor.visitJsonPath(node);
221
+ if (visitor.visitJsonPath) return visitor.visitJsonPath(node as JsonPathNode);
200
222
  break;
201
223
  case 'ScalarSubquery':
202
- if (visitor.visitScalarSubquery) return visitor.visitScalarSubquery(node);
224
+ if (visitor.visitScalarSubquery) return visitor.visitScalarSubquery(node as ScalarSubqueryNode);
203
225
  break;
204
226
  case 'CaseExpression':
205
- if (visitor.visitCaseExpression) return visitor.visitCaseExpression(node);
227
+ if (visitor.visitCaseExpression) return visitor.visitCaseExpression(node as CaseExpressionNode);
206
228
  break;
207
229
  case 'WindowFunction':
208
- if (visitor.visitWindowFunction) return visitor.visitWindowFunction(node);
230
+ if (visitor.visitWindowFunction) return visitor.visitWindowFunction(node as WindowFunctionNode);
209
231
  break;
210
232
  case 'AliasRef':
211
- if (visitor.visitAliasRef) return visitor.visitAliasRef(node);
233
+ if (visitor.visitAliasRef) return visitor.visitAliasRef(node as AliasRefNode);
212
234
  break;
213
235
  case 'Cast':
214
- if (visitor.visitCast) return visitor.visitCast(node);
236
+ if (visitor.visitCast) return visitor.visitCast(node as CastExpressionNode);
215
237
  break;
216
238
  case 'Collate':
217
- if (visitor.visitCollate) return visitor.visitCollate(node);
239
+ if (visitor.visitCollate) return visitor.visitCollate(node as CollateExpressionNode);
240
+ break;
241
+ case 'ArithmeticExpression':
242
+ if (visitor.visitArithmeticExpression) return visitor.visitArithmeticExpression(node as ArithmeticExpressionNode);
243
+ break;
244
+ case 'BitwiseExpression':
245
+ if (visitor.visitBitwiseExpression) return visitor.visitBitwiseExpression(node as BitwiseExpressionNode);
218
246
  break;
219
247
  default:
220
248
  break;