metal-orm 1.0.81 → 1.0.83
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 +327 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.js +326 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/typescript.ts +40 -29
- package/src/core/ast/ast-validation.ts +188 -0
- package/src/core/ast/expression-builders.ts +43 -24
- package/src/core/ast/expression-nodes.ts +32 -21
- package/src/core/ast/expression-visitor.ts +6 -1
- package/src/core/ast/expression.ts +1 -0
- package/src/core/ast/param-proxy.ts +47 -0
- package/src/core/dialect/abstract.ts +70 -48
- package/src/openapi/query-parameters.ts +1 -0
- package/src/query-builder/relation-filter-utils.ts +1 -0
- package/src/query-builder/select.ts +128 -109
package/package.json
CHANGED
|
@@ -17,13 +17,14 @@ import {
|
|
|
17
17
|
LiteralNode,
|
|
18
18
|
FunctionNode,
|
|
19
19
|
AliasRefNode,
|
|
20
|
-
CastExpressionNode,
|
|
21
|
-
CollateExpressionNode,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
CastExpressionNode,
|
|
21
|
+
CollateExpressionNode,
|
|
22
|
+
ParamNode,
|
|
23
|
+
ExpressionVisitor,
|
|
24
|
+
OperandVisitor,
|
|
25
|
+
visitExpression,
|
|
26
|
+
visitOperand
|
|
27
|
+
} from '../core/ast/expression.js';
|
|
27
28
|
import { SQL_OPERATOR_REGISTRY } from '../core/sql/sql-operator-config.js';
|
|
28
29
|
import { SqlOperator } from '../core/sql/sql.js';
|
|
29
30
|
import { isRelationAlias } from '../query-builder/relation-alias.js';
|
|
@@ -190,13 +191,14 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
|
|
|
190
191
|
case 'ScalarSubquery':
|
|
191
192
|
case 'CaseExpression':
|
|
192
193
|
case 'WindowFunction':
|
|
193
|
-
case 'Cast':
|
|
194
|
-
case 'Collate':
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
case 'Cast':
|
|
195
|
+
case 'Collate':
|
|
196
|
+
case 'Param':
|
|
197
|
+
return this.printOperand(term);
|
|
198
|
+
default:
|
|
199
|
+
return this.printExpression(term);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
200
202
|
|
|
201
203
|
private getSelectionKey(selection: SelectionColumn, index: number): string {
|
|
202
204
|
if (selection.alias) {
|
|
@@ -244,13 +246,17 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
|
|
|
244
246
|
return this.printColumnOperand(node);
|
|
245
247
|
}
|
|
246
248
|
|
|
247
|
-
public visitLiteral(node: LiteralNode): string {
|
|
248
|
-
return this.printLiteralOperand(node);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
public
|
|
252
|
-
return this.
|
|
253
|
-
}
|
|
249
|
+
public visitLiteral(node: LiteralNode): string {
|
|
250
|
+
return this.printLiteralOperand(node);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public visitParam(node: ParamNode): string {
|
|
254
|
+
return this.printParamOperand(node);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public visitFunction(node: FunctionNode): string {
|
|
258
|
+
return this.printFunctionOperand(node);
|
|
259
|
+
}
|
|
254
260
|
|
|
255
261
|
public visitJsonPath(node: JsonPathNode): string {
|
|
256
262
|
return this.printJsonPathOperand(node);
|
|
@@ -379,14 +385,19 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
|
|
|
379
385
|
* @param literal - Literal node
|
|
380
386
|
* @returns TypeScript code representation
|
|
381
387
|
*/
|
|
382
|
-
private printLiteralOperand(literal: LiteralNode): string {
|
|
383
|
-
if (literal.value === null) return 'null';
|
|
384
|
-
return typeof literal.value === 'string' ? `'${literal.value}'` : String(literal.value);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
388
|
+
private printLiteralOperand(literal: LiteralNode): string {
|
|
389
|
+
if (literal.value === null) return 'null';
|
|
390
|
+
return typeof literal.value === 'string' ? `'${literal.value}'` : String(literal.value);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private printParamOperand(param: ParamNode): string {
|
|
394
|
+
const name = param.name.replace(/'/g, "\\'");
|
|
395
|
+
return `{ type: 'Param', name: '${name}' }`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Prints a function operand to TypeScript code
|
|
400
|
+
* @param fn - Function node
|
|
390
401
|
* @returns TypeScript code representation
|
|
391
402
|
*/
|
|
392
403
|
private printFunctionOperand(fn: FunctionNode): string {
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { ExpressionNode, OperandNode } from './expression-nodes.js';
|
|
2
|
+
import { visitExpression, visitOperand } from './expression-visitor.js';
|
|
3
|
+
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
|
+
};
|
|
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;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
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;
|
|
188
|
+
};
|
|
@@ -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
|
|
@@ -2,14 +2,23 @@ import type { SelectQueryNode, OrderByNode } from './query.js';
|
|
|
2
2
|
import { SqlOperator, BitwiseOperator } from '../sql/sql.js';
|
|
3
3
|
import { ColumnRef } from './types.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* AST node representing a literal value
|
|
7
|
-
*/
|
|
5
|
+
/**
|
|
6
|
+
* AST node representing a literal value
|
|
7
|
+
*/
|
|
8
8
|
export interface LiteralNode {
|
|
9
9
|
type: 'Literal';
|
|
10
10
|
/** The literal value (string, number, boolean, Date, or null) */
|
|
11
11
|
value: string | number | boolean | Date | null;
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* AST node representing a named parameter placeholder
|
|
16
|
+
*/
|
|
17
|
+
export interface ParamNode {
|
|
18
|
+
type: 'Param';
|
|
19
|
+
/** Stable parameter name */
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
24
|
* AST node representing a reference to a SELECT alias (for ORDER BY / GROUP BY).
|
|
@@ -149,29 +158,31 @@ export interface ArithmeticExpressionNode {
|
|
|
149
158
|
/**
|
|
150
159
|
* Union type representing any operand that can be used in expressions
|
|
151
160
|
*/
|
|
152
|
-
export type OperandNode =
|
|
153
|
-
| AliasRefNode
|
|
154
|
-
| ColumnNode
|
|
155
|
-
| LiteralNode
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
+
export type OperandNode =
|
|
162
|
+
| AliasRefNode
|
|
163
|
+
| ColumnNode
|
|
164
|
+
| LiteralNode
|
|
165
|
+
| ParamNode
|
|
166
|
+
| FunctionNode
|
|
167
|
+
| JsonPathNode
|
|
168
|
+
| ScalarSubqueryNode
|
|
169
|
+
| CaseExpressionNode
|
|
170
|
+
| CastExpressionNode
|
|
161
171
|
| WindowFunctionNode
|
|
162
172
|
| ArithmeticExpressionNode
|
|
163
173
|
| BitwiseExpressionNode
|
|
164
174
|
| CollateExpressionNode;
|
|
165
175
|
|
|
166
|
-
const operandTypes = new Set<OperandNode['type']>([
|
|
167
|
-
'AliasRef',
|
|
168
|
-
'Column',
|
|
169
|
-
'Literal',
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
176
|
+
const operandTypes = new Set<OperandNode['type']>([
|
|
177
|
+
'AliasRef',
|
|
178
|
+
'Column',
|
|
179
|
+
'Literal',
|
|
180
|
+
'Param',
|
|
181
|
+
'Function',
|
|
182
|
+
'JsonPath',
|
|
183
|
+
'ScalarSubquery',
|
|
184
|
+
'CaseExpression',
|
|
185
|
+
'Cast',
|
|
175
186
|
'WindowFunction',
|
|
176
187
|
'ArithmeticExpression',
|
|
177
188
|
'BitwiseExpression',
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
WindowFunctionNode,
|
|
19
19
|
CollateExpressionNode,
|
|
20
20
|
AliasRefNode,
|
|
21
|
-
BitwiseExpressionNode
|
|
21
|
+
BitwiseExpressionNode,
|
|
22
|
+
ParamNode
|
|
22
23
|
} from './expression-nodes.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -42,6 +43,7 @@ export interface ExpressionVisitor<R> {
|
|
|
42
43
|
export interface OperandVisitor<R> {
|
|
43
44
|
visitColumn?(node: ColumnNode): R;
|
|
44
45
|
visitLiteral?(node: LiteralNode): R;
|
|
46
|
+
visitParam?(node: ParamNode): R;
|
|
45
47
|
visitFunction?(node: FunctionNode): R;
|
|
46
48
|
visitJsonPath?(node: JsonPathNode): R;
|
|
47
49
|
visitScalarSubquery?(node: ScalarSubqueryNode): R;
|
|
@@ -187,6 +189,9 @@ export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>):
|
|
|
187
189
|
case 'Literal':
|
|
188
190
|
if (visitor.visitLiteral) return visitor.visitLiteral(node);
|
|
189
191
|
break;
|
|
192
|
+
case 'Param':
|
|
193
|
+
if (visitor.visitParam) return visitor.visitParam(node);
|
|
194
|
+
break;
|
|
190
195
|
case 'Function':
|
|
191
196
|
if (visitor.visitFunction) return visitor.visitFunction(node);
|
|
192
197
|
break;
|
|
@@ -9,6 +9,7 @@ export * from './expression-builders.js';
|
|
|
9
9
|
export * from './window-functions.js';
|
|
10
10
|
export * from './aggregate-functions.js';
|
|
11
11
|
export * from './expression-visitor.js';
|
|
12
|
+
export * from './param-proxy.js';
|
|
12
13
|
export type { ColumnRef, TableRef as AstTableRef } from './types.js';
|
|
13
14
|
export * from './adapters.js';
|
|
14
15
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ParamNode } from './expression-nodes.js';
|
|
2
|
+
|
|
3
|
+
export type ParamProxy = ParamNode & {
|
|
4
|
+
[key: string]: ParamProxy;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type ParamProxyRoot = {
|
|
8
|
+
[key: string]: ParamProxy;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const buildParamProxy = (name: string): ParamProxy => {
|
|
12
|
+
const target: ParamNode = { type: 'Param', name };
|
|
13
|
+
return new Proxy(target, {
|
|
14
|
+
get(t, prop, receiver) {
|
|
15
|
+
if (prop === 'then') return undefined;
|
|
16
|
+
if (typeof prop === 'symbol') {
|
|
17
|
+
return Reflect.get(t, prop, receiver);
|
|
18
|
+
}
|
|
19
|
+
if (typeof prop === 'string' && prop.startsWith('$')) {
|
|
20
|
+
const trimmed = prop.slice(1);
|
|
21
|
+
const nextName = name ? `${name}.${trimmed}` : trimmed;
|
|
22
|
+
return buildParamProxy(nextName);
|
|
23
|
+
}
|
|
24
|
+
if (prop in t && name === '') {
|
|
25
|
+
return (t as unknown as Record<string, unknown>)[prop];
|
|
26
|
+
}
|
|
27
|
+
const nextName = name ? `${name}.${prop}` : prop;
|
|
28
|
+
return buildParamProxy(nextName);
|
|
29
|
+
}
|
|
30
|
+
}) as ParamProxy;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const createParamProxy = (): ParamProxyRoot => {
|
|
34
|
+
const target: Record<string, unknown> = {};
|
|
35
|
+
return new Proxy(target, {
|
|
36
|
+
get(t, prop, receiver) {
|
|
37
|
+
if (prop === 'then') return undefined;
|
|
38
|
+
if (typeof prop === 'symbol') {
|
|
39
|
+
return Reflect.get(t, prop, receiver);
|
|
40
|
+
}
|
|
41
|
+
if (typeof prop === 'string' && prop.startsWith('$')) {
|
|
42
|
+
return buildParamProxy(prop.slice(1));
|
|
43
|
+
}
|
|
44
|
+
return buildParamProxy(String(prop));
|
|
45
|
+
}
|
|
46
|
+
}) as ParamProxyRoot;
|
|
47
|
+
};
|