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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.82",
3
+ "version": "1.0.85",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -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
- JsonPathNode,
8
- OperandNode,
9
- CaseExpressionNode,
10
- CastExpressionNode,
11
- BinaryExpressionNode,
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
- * Converts a ColumnRef to a ColumnNode
53
- * @throws Error if the ColumnRef doesn't have a table specified
54
- */
55
- const columnRefToNode = (col: ColumnRef): ColumnNode => {
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
- // Already an operand node
72
- if (isOperandNode(value)) {
73
- return value;
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
- if (isOperandNode(value)) {
92
- return value;
93
- }
94
- return toLiteralNode(value);
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 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;
@@ -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
+ };