metal-orm 1.0.56 → 1.0.57
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/README.md +21 -20
- package/dist/index.cjs +821 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +429 -70
- package/dist/index.d.ts +429 -70
- package/dist/index.js +785 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/naming-strategy.ts +3 -1
- package/src/codegen/typescript.ts +20 -10
- package/src/core/ast/aggregate-functions.ts +14 -0
- package/src/core/ast/builders.ts +38 -20
- package/src/core/ast/expression-builders.ts +70 -2
- package/src/core/ast/expression-nodes.ts +305 -274
- package/src/core/ast/expression-visitor.ts +11 -1
- package/src/core/ast/expression.ts +4 -0
- package/src/core/ast/query.ts +3 -0
- package/src/core/ddl/introspect/catalogs/mysql.ts +5 -0
- package/src/core/ddl/introspect/catalogs/sqlite.ts +3 -0
- package/src/core/ddl/introspect/functions/mssql.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +4 -0
- package/src/core/ddl/introspect/mysql.ts +4 -0
- package/src/core/ddl/introspect/sqlite.ts +4 -0
- package/src/core/dialect/abstract.ts +552 -531
- package/src/core/dialect/base/function-table-formatter.ts +9 -30
- package/src/core/dialect/base/sql-dialect.ts +24 -0
- package/src/core/dialect/mssql/functions.ts +40 -2
- package/src/core/dialect/mysql/functions.ts +16 -2
- package/src/core/dialect/postgres/functions.ts +66 -2
- package/src/core/dialect/postgres/index.ts +17 -4
- package/src/core/dialect/postgres/table-functions.ts +27 -0
- package/src/core/dialect/sqlite/functions.ts +34 -0
- package/src/core/dialect/sqlite/index.ts +17 -1
- package/src/core/driver/database-driver.ts +9 -1
- package/src/core/driver/mssql-driver.ts +3 -0
- package/src/core/driver/mysql-driver.ts +3 -0
- package/src/core/driver/postgres-driver.ts +3 -0
- package/src/core/driver/sqlite-driver.ts +3 -0
- package/src/core/execution/executors/mssql-executor.ts +5 -0
- package/src/core/execution/executors/mysql-executor.ts +5 -0
- package/src/core/execution/executors/postgres-executor.ts +5 -0
- package/src/core/execution/executors/sqlite-executor.ts +5 -0
- package/src/core/functions/array.ts +26 -0
- package/src/core/functions/control-flow.ts +69 -0
- package/src/core/functions/datetime.ts +50 -0
- package/src/core/functions/definitions/aggregate.ts +16 -0
- package/src/core/functions/definitions/control-flow.ts +24 -0
- package/src/core/functions/definitions/datetime.ts +36 -0
- package/src/core/functions/definitions/helpers.ts +29 -0
- package/src/core/functions/definitions/json.ts +49 -0
- package/src/core/functions/definitions/numeric.ts +55 -0
- package/src/core/functions/definitions/string.ts +43 -0
- package/src/core/functions/function-registry.ts +48 -0
- package/src/core/functions/group-concat-helpers.ts +57 -0
- package/src/core/functions/json.ts +38 -0
- package/src/core/functions/numeric.ts +14 -0
- package/src/core/functions/standard-strategy.ts +86 -115
- package/src/core/functions/standard-table-strategy.ts +13 -0
- package/src/core/functions/table-types.ts +15 -0
- package/src/core/functions/text.ts +57 -0
- package/src/core/sql/sql.ts +59 -38
- package/src/decorators/bootstrap.ts +5 -4
- package/src/index.ts +18 -11
- package/src/orm/hydration-context.ts +10 -0
- package/src/orm/identity-map.ts +19 -0
- package/src/orm/interceptor-pipeline.ts +4 -0
- package/src/orm/relations/belongs-to.ts +17 -0
- package/src/orm/relations/has-one.ts +17 -0
- package/src/orm/relations/many-to-many.ts +41 -0
- package/src/query-builder/select.ts +68 -68
- package/src/schema/table-guards.ts +6 -0
- package/src/schema/types.ts +8 -1
|
@@ -1,544 +1,565 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SelectQueryNode,
|
|
3
|
-
InsertQueryNode,
|
|
4
|
-
UpdateQueryNode,
|
|
5
|
-
DeleteQueryNode,
|
|
6
|
-
SetOperationKind,
|
|
7
|
-
CommonTableExpressionNode,
|
|
8
|
-
OrderingTerm
|
|
9
|
-
} from '../ast/query.js';
|
|
10
|
-
import {
|
|
11
|
-
ExpressionNode,
|
|
12
|
-
BinaryExpressionNode,
|
|
13
|
-
LogicalExpressionNode,
|
|
14
|
-
NullExpressionNode,
|
|
15
|
-
InExpressionNode,
|
|
16
|
-
ExistsExpressionNode,
|
|
17
|
-
LiteralNode,
|
|
18
|
-
ColumnNode,
|
|
19
|
-
OperandNode,
|
|
20
|
-
FunctionNode,
|
|
21
|
-
JsonPathNode,
|
|
22
|
-
ScalarSubqueryNode,
|
|
23
|
-
CaseExpressionNode,
|
|
24
|
-
CastExpressionNode,
|
|
25
|
-
WindowFunctionNode,
|
|
26
|
-
BetweenExpressionNode,
|
|
27
|
-
ArithmeticExpressionNode,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
import {
|
|
2
|
+
SelectQueryNode,
|
|
3
|
+
InsertQueryNode,
|
|
4
|
+
UpdateQueryNode,
|
|
5
|
+
DeleteQueryNode,
|
|
6
|
+
SetOperationKind,
|
|
7
|
+
CommonTableExpressionNode,
|
|
8
|
+
OrderingTerm
|
|
9
|
+
} from '../ast/query.js';
|
|
10
|
+
import {
|
|
11
|
+
ExpressionNode,
|
|
12
|
+
BinaryExpressionNode,
|
|
13
|
+
LogicalExpressionNode,
|
|
14
|
+
NullExpressionNode,
|
|
15
|
+
InExpressionNode,
|
|
16
|
+
ExistsExpressionNode,
|
|
17
|
+
LiteralNode,
|
|
18
|
+
ColumnNode,
|
|
19
|
+
OperandNode,
|
|
20
|
+
FunctionNode,
|
|
21
|
+
JsonPathNode,
|
|
22
|
+
ScalarSubqueryNode,
|
|
23
|
+
CaseExpressionNode,
|
|
24
|
+
CastExpressionNode,
|
|
25
|
+
WindowFunctionNode,
|
|
26
|
+
BetweenExpressionNode,
|
|
27
|
+
ArithmeticExpressionNode,
|
|
28
|
+
BitwiseExpressionNode,
|
|
29
|
+
CollateExpressionNode,
|
|
30
|
+
AliasRefNode,
|
|
31
|
+
isOperandNode
|
|
32
|
+
} from '../ast/expression.js';
|
|
33
|
+
import { DialectName } from '../sql/sql.js';
|
|
32
34
|
import type { FunctionStrategy } from '../functions/types.js';
|
|
33
35
|
import { StandardFunctionStrategy } from '../functions/standard-strategy.js';
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*
|
|
133
|
-
* @
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
protected abstract
|
|
138
|
-
|
|
139
|
-
protected abstract
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
*
|
|
152
|
-
* @
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* @
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
*
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
* -
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
*
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
*
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
36
|
+
import type { TableFunctionStrategy } from '../functions/table-types.js';
|
|
37
|
+
import { StandardTableFunctionStrategy } from '../functions/standard-table-strategy.js';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Context for SQL compilation with parameter management
|
|
41
|
+
*/
|
|
42
|
+
export interface CompilerContext {
|
|
43
|
+
/** Array of parameters */
|
|
44
|
+
params: unknown[];
|
|
45
|
+
/** Function to add a parameter and get its placeholder */
|
|
46
|
+
addParameter(value: unknown): string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Result of SQL compilation
|
|
51
|
+
*/
|
|
52
|
+
export interface CompiledQuery {
|
|
53
|
+
/** Generated SQL string */
|
|
54
|
+
sql: string;
|
|
55
|
+
/** Parameters for the query */
|
|
56
|
+
params: unknown[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SelectCompiler {
|
|
60
|
+
compileSelect(ast: SelectQueryNode): CompiledQuery;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface InsertCompiler {
|
|
64
|
+
compileInsert(ast: InsertQueryNode): CompiledQuery;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface UpdateCompiler {
|
|
68
|
+
compileUpdate(ast: UpdateQueryNode): CompiledQuery;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface DeleteCompiler {
|
|
72
|
+
compileDelete(ast: DeleteQueryNode): CompiledQuery;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Abstract base class for SQL dialect implementations
|
|
77
|
+
*/
|
|
78
|
+
export abstract class Dialect
|
|
79
|
+
implements SelectCompiler, InsertCompiler, UpdateCompiler, DeleteCompiler {
|
|
80
|
+
/** Dialect identifier used for function rendering and formatting */
|
|
81
|
+
protected abstract readonly dialect: DialectName;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compiles a SELECT query AST to SQL
|
|
85
|
+
* @param ast - Query AST to compile
|
|
86
|
+
* @returns Compiled query with SQL and parameters
|
|
87
|
+
*/
|
|
88
|
+
compileSelect(ast: SelectQueryNode): CompiledQuery {
|
|
89
|
+
const ctx = this.createCompilerContext();
|
|
90
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
91
|
+
const rawSql = this.compileSelectAst(normalized, ctx).trim();
|
|
92
|
+
const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
|
|
93
|
+
return {
|
|
94
|
+
sql,
|
|
95
|
+
params: [...ctx.params]
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
compileInsert(ast: InsertQueryNode): CompiledQuery {
|
|
100
|
+
const ctx = this.createCompilerContext();
|
|
101
|
+
const rawSql = this.compileInsertAst(ast, ctx).trim();
|
|
102
|
+
const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
|
|
103
|
+
return {
|
|
104
|
+
sql,
|
|
105
|
+
params: [...ctx.params]
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
compileUpdate(ast: UpdateQueryNode): CompiledQuery {
|
|
110
|
+
const ctx = this.createCompilerContext();
|
|
111
|
+
const rawSql = this.compileUpdateAst(ast, ctx).trim();
|
|
112
|
+
const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
|
|
113
|
+
return {
|
|
114
|
+
sql,
|
|
115
|
+
params: [...ctx.params]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
compileDelete(ast: DeleteQueryNode): CompiledQuery {
|
|
120
|
+
const ctx = this.createCompilerContext();
|
|
121
|
+
const rawSql = this.compileDeleteAst(ast, ctx).trim();
|
|
122
|
+
const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
|
|
123
|
+
return {
|
|
124
|
+
sql,
|
|
125
|
+
params: [...ctx.params]
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
supportsReturning(): boolean {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Compiles SELECT query AST to SQL (to be implemented by concrete dialects)
|
|
135
|
+
* @param ast - Query AST
|
|
136
|
+
* @param ctx - Compiler context
|
|
137
|
+
* @returns SQL string
|
|
138
|
+
*/
|
|
139
|
+
protected abstract compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
|
|
140
|
+
|
|
141
|
+
protected abstract compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string;
|
|
142
|
+
protected abstract compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string;
|
|
143
|
+
protected abstract compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Quotes an SQL identifier (to be implemented by concrete dialects)
|
|
147
|
+
* @param id - Identifier to quote
|
|
148
|
+
* @returns Quoted identifier
|
|
149
|
+
*/
|
|
150
|
+
abstract quoteIdentifier(id: string): string;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Compiles a WHERE clause
|
|
154
|
+
* @param where - WHERE expression
|
|
155
|
+
* @param ctx - Compiler context
|
|
156
|
+
* @returns SQL WHERE clause or empty string
|
|
157
|
+
*/
|
|
158
|
+
protected compileWhere(where: ExpressionNode | undefined, ctx: CompilerContext): string {
|
|
159
|
+
if (!where) return '';
|
|
160
|
+
return ` WHERE ${this.compileExpression(where, ctx)}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected compileReturning(
|
|
164
|
+
returning: ColumnNode[] | undefined,
|
|
165
|
+
_ctx: CompilerContext
|
|
166
|
+
): string {
|
|
167
|
+
void _ctx;
|
|
168
|
+
if (!returning || returning.length === 0) return '';
|
|
169
|
+
throw new Error('RETURNING is not supported by this dialect.');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Generates subquery for EXISTS expressions
|
|
174
|
+
* Rule: Always forces SELECT 1, ignoring column list
|
|
175
|
+
* Maintains FROM, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT/OFFSET
|
|
176
|
+
* Does not add ';' at the end
|
|
177
|
+
* @param ast - Query AST
|
|
178
|
+
* @param ctx - Compiler context
|
|
179
|
+
* @returns SQL for EXISTS subquery
|
|
180
|
+
*/
|
|
181
|
+
protected compileSelectForExists(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
182
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
183
|
+
const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, '');
|
|
184
|
+
|
|
185
|
+
// When the subquery is a set operation, wrap it as a derived table to keep valid syntax.
|
|
186
|
+
if (normalized.setOps && normalized.setOps.length > 0) {
|
|
187
|
+
return `SELECT 1 FROM (${full}) AS _exists`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const upper = full.toUpperCase();
|
|
191
|
+
const fromIndex = upper.indexOf(' FROM ');
|
|
192
|
+
if (fromIndex === -1) {
|
|
193
|
+
return full;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const tail = full.slice(fromIndex);
|
|
197
|
+
return `SELECT 1${tail}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Creates a new compiler context
|
|
202
|
+
* @returns Compiler context with parameter management
|
|
203
|
+
*/
|
|
204
|
+
protected createCompilerContext(): CompilerContext {
|
|
205
|
+
const params: unknown[] = [];
|
|
206
|
+
let counter = 0;
|
|
207
|
+
return {
|
|
208
|
+
params,
|
|
209
|
+
addParameter: (value: unknown) => {
|
|
210
|
+
counter += 1;
|
|
211
|
+
params.push(value);
|
|
212
|
+
return this.formatPlaceholder(counter);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Formats a parameter placeholder
|
|
219
|
+
* @param index - Parameter index
|
|
220
|
+
* @returns Formatted placeholder string
|
|
221
|
+
*/
|
|
222
|
+
protected formatPlaceholder(_index: number): string {
|
|
223
|
+
void _index;
|
|
224
|
+
return '?';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Whether the current dialect supports a given set operation.
|
|
229
|
+
* Override in concrete dialects to restrict support.
|
|
230
|
+
*/
|
|
231
|
+
protected supportsSetOperation(_kind: SetOperationKind): boolean {
|
|
232
|
+
void _kind;
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Validates set-operation semantics:
|
|
238
|
+
* - Ensures the dialect supports requested operators.
|
|
239
|
+
* - Enforces that only the outermost compound query may have ORDER/LIMIT/OFFSET.
|
|
240
|
+
* @param ast - Query to validate
|
|
241
|
+
* @param isOutermost - Whether this node is the outermost compound query
|
|
242
|
+
*/
|
|
243
|
+
protected validateSetOperations(ast: SelectQueryNode, isOutermost = true): void {
|
|
244
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
245
|
+
if (!isOutermost && (ast.orderBy || ast.limit !== undefined || ast.offset !== undefined)) {
|
|
246
|
+
throw new Error('ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (hasSetOps) {
|
|
250
|
+
for (const op of ast.setOps!) {
|
|
251
|
+
if (!this.supportsSetOperation(op.operator)) {
|
|
252
|
+
throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
|
|
253
|
+
}
|
|
254
|
+
this.validateSetOperations(op.query, false);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Hoists CTEs from set-operation operands to the outermost query so WITH appears once.
|
|
261
|
+
* @param ast - Query AST
|
|
262
|
+
* @returns Normalized AST without inner CTEs and a list of hoisted CTEs
|
|
263
|
+
*/
|
|
264
|
+
private hoistCtes(ast: SelectQueryNode): { normalized: SelectQueryNode; hoistedCtes: CommonTableExpressionNode[] } {
|
|
265
|
+
let hoisted: CommonTableExpressionNode[] = [];
|
|
266
|
+
|
|
267
|
+
const normalizedSetOps = ast.setOps?.map(op => {
|
|
268
|
+
const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
|
|
269
|
+
const childCtes = child.ctes ?? [];
|
|
270
|
+
if (childCtes.length) {
|
|
271
|
+
hoisted = hoisted.concat(childCtes);
|
|
272
|
+
}
|
|
273
|
+
hoisted = hoisted.concat(childHoisted);
|
|
274
|
+
const queryWithoutCtes = childCtes.length ? { ...child, ctes: undefined } : child;
|
|
275
|
+
return { ...op, query: queryWithoutCtes };
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const normalized: SelectQueryNode = normalizedSetOps ? { ...ast, setOps: normalizedSetOps } : ast;
|
|
279
|
+
return { normalized, hoistedCtes: hoisted };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Normalizes a SELECT AST before compilation (validation + CTE hoisting).
|
|
284
|
+
* @param ast - Query AST
|
|
285
|
+
* @returns Normalized query AST
|
|
286
|
+
*/
|
|
287
|
+
protected normalizeSelectAst(ast: SelectQueryNode): SelectQueryNode {
|
|
288
|
+
this.validateSetOperations(ast, true);
|
|
289
|
+
const { normalized, hoistedCtes } = this.hoistCtes(ast);
|
|
290
|
+
const combinedCtes = [...(normalized.ctes ?? []), ...hoistedCtes];
|
|
291
|
+
return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
|
|
292
|
+
}
|
|
293
|
+
|
|
290
294
|
private readonly expressionCompilers: Map<string, (node: ExpressionNode, ctx: CompilerContext) => string>;
|
|
291
295
|
private readonly operandCompilers: Map<string, (node: OperandNode, ctx: CompilerContext) => string>;
|
|
292
296
|
protected readonly functionStrategy: FunctionStrategy;
|
|
297
|
+
protected readonly tableFunctionStrategy: TableFunctionStrategy;
|
|
293
298
|
|
|
294
|
-
protected constructor(functionStrategy?: FunctionStrategy) {
|
|
299
|
+
protected constructor(functionStrategy?: FunctionStrategy, tableFunctionStrategy?: TableFunctionStrategy) {
|
|
295
300
|
this.expressionCompilers = new Map();
|
|
296
301
|
this.operandCompilers = new Map();
|
|
297
302
|
this.functionStrategy = functionStrategy || new StandardFunctionStrategy();
|
|
303
|
+
this.tableFunctionStrategy = tableFunctionStrategy || new StandardTableFunctionStrategy();
|
|
298
304
|
this.registerDefaultOperandCompilers();
|
|
299
305
|
this.registerDefaultExpressionCompilers();
|
|
300
306
|
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Creates a new Dialect instance (for testing purposes)
|
|
304
|
-
* @param functionStrategy - Optional function strategy
|
|
305
|
-
* @returns New Dialect instance
|
|
306
|
-
*/
|
|
307
|
-
static create(functionStrategy?: FunctionStrategy): Dialect {
|
|
308
|
-
// Create a minimal concrete implementation for testing
|
|
309
|
-
class TestDialect extends Dialect {
|
|
310
|
-
protected readonly dialect: DialectName = 'sqlite';
|
|
311
|
-
quoteIdentifier(id: string): string {
|
|
312
|
-
return `"${id}"`;
|
|
313
|
-
}
|
|
314
|
-
protected compileSelectAst(): never {
|
|
315
|
-
throw new Error('Not implemented');
|
|
316
|
-
}
|
|
317
|
-
protected compileInsertAst(): never {
|
|
318
|
-
throw new Error('Not implemented');
|
|
319
|
-
}
|
|
320
|
-
protected compileUpdateAst(): never {
|
|
321
|
-
throw new Error('Not implemented');
|
|
322
|
-
}
|
|
323
|
-
protected compileDeleteAst(): never {
|
|
324
|
-
throw new Error('Not implemented');
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return new TestDialect(functionStrategy);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Registers an expression compiler for a specific node type
|
|
332
|
-
* @param type - Expression node type
|
|
333
|
-
* @param compiler - Compiler function
|
|
334
|
-
*/
|
|
335
|
-
protected registerExpressionCompiler<T extends ExpressionNode>(type: T['type'], compiler: (node: T, ctx: CompilerContext) => string): void {
|
|
336
|
-
this.expressionCompilers.set(type, compiler as (node: ExpressionNode, ctx: CompilerContext) => string);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Registers an operand compiler for a specific node type
|
|
341
|
-
* @param type - Operand node type
|
|
342
|
-
* @param compiler - Compiler function
|
|
343
|
-
*/
|
|
344
|
-
protected registerOperandCompiler<T extends OperandNode>(type: T['type'], compiler: (node: T, ctx: CompilerContext) => string): void {
|
|
345
|
-
this.operandCompilers.set(type, compiler as (node: OperandNode, ctx: CompilerContext) => string);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Compiles an expression node
|
|
350
|
-
* @param node - Expression node to compile
|
|
351
|
-
* @param ctx - Compiler context
|
|
352
|
-
* @returns Compiled SQL expression
|
|
353
|
-
*/
|
|
354
|
-
protected compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
|
|
355
|
-
const compiler = this.expressionCompilers.get(node.type);
|
|
356
|
-
if (!compiler) {
|
|
357
|
-
throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
|
|
358
|
-
}
|
|
359
|
-
return compiler(node, ctx);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Compiles an operand node
|
|
364
|
-
* @param node - Operand node to compile
|
|
365
|
-
* @param ctx - Compiler context
|
|
366
|
-
* @returns Compiled SQL operand
|
|
367
|
-
*/
|
|
368
|
-
protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
369
|
-
const compiler = this.operandCompilers.get(node.type);
|
|
370
|
-
if (!compiler) {
|
|
371
|
-
throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
|
|
372
|
-
}
|
|
373
|
-
return compiler(node, ctx);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Compiles an ordering term (operand, expression, or alias reference).
|
|
378
|
-
*/
|
|
379
|
-
protected compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string {
|
|
380
|
-
if (isOperandNode(term)) {
|
|
381
|
-
return this.compileOperand(term, ctx);
|
|
382
|
-
}
|
|
383
|
-
// At this point, term must be an ExpressionNode
|
|
384
|
-
const expr = this.compileExpression(term as ExpressionNode, ctx);
|
|
385
|
-
return `(${expr})`;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
private registerDefaultExpressionCompilers(): void {
|
|
389
|
-
this.registerExpressionCompiler('BinaryExpression', (binary: BinaryExpressionNode, ctx) => {
|
|
390
|
-
const left = this.compileOperand(binary.left, ctx);
|
|
391
|
-
const right = this.compileOperand(binary.right, ctx);
|
|
392
|
-
const base = `${left} ${binary.operator} ${right}`;
|
|
393
|
-
if (binary.escape) {
|
|
394
|
-
const escapeOperand = this.compileOperand(binary.escape, ctx);
|
|
395
|
-
return `${base} ESCAPE ${escapeOperand}`;
|
|
396
|
-
}
|
|
397
|
-
return base;
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
this.registerExpressionCompiler('LogicalExpression', (logical: LogicalExpressionNode, ctx) => {
|
|
401
|
-
if (logical.operands.length === 0) return '';
|
|
402
|
-
const parts = logical.operands.map(op => {
|
|
403
|
-
const compiled = this.compileExpression(op, ctx);
|
|
404
|
-
return op.type === 'LogicalExpression' ? `(${compiled})` : compiled;
|
|
405
|
-
});
|
|
406
|
-
return parts.join(` ${logical.operator} `);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
this.registerExpressionCompiler('NullExpression', (nullExpr: NullExpressionNode, ctx) => {
|
|
410
|
-
const left = this.compileOperand(nullExpr.left, ctx);
|
|
411
|
-
return `${left} ${nullExpr.operator}`;
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
this.registerExpressionCompiler('InExpression', (inExpr: InExpressionNode, ctx) => {
|
|
415
|
-
const left = this.compileOperand(inExpr.left, ctx);
|
|
416
|
-
if (Array.isArray(inExpr.right)) {
|
|
417
|
-
const values = inExpr.right.map(v => this.compileOperand(v, ctx)).join(', ');
|
|
418
|
-
return `${left} ${inExpr.operator} (${values})`;
|
|
419
|
-
}
|
|
420
|
-
const subquerySql = this.compileSelectAst(inExpr.right.query, ctx).trim().replace(/;$/, '');
|
|
421
|
-
return `${left} ${inExpr.operator} (${subquerySql})`;
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
this.registerExpressionCompiler('ExistsExpression', (existsExpr: ExistsExpressionNode, ctx) => {
|
|
425
|
-
const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
|
|
426
|
-
return `${existsExpr.operator} (${subquerySql})`;
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
this.registerExpressionCompiler('BetweenExpression', (betweenExpr: BetweenExpressionNode, ctx) => {
|
|
430
|
-
const left = this.compileOperand(betweenExpr.left, ctx);
|
|
431
|
-
const lower = this.compileOperand(betweenExpr.lower, ctx);
|
|
432
|
-
const upper = this.compileOperand(betweenExpr.upper, ctx);
|
|
433
|
-
return `${left} ${betweenExpr.operator} ${lower} AND ${upper}`;
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
this.registerExpressionCompiler('ArithmeticExpression', (arith: ArithmeticExpressionNode, ctx) => {
|
|
437
|
-
const left = this.compileOperand(arith.left, ctx);
|
|
438
|
-
const right = this.compileOperand(arith.right, ctx);
|
|
439
|
-
return `${left} ${arith.operator} ${right}`;
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
private registerDefaultOperandCompilers(): void {
|
|
444
|
-
this.registerOperandCompiler('Literal', (literal: LiteralNode, ctx) => ctx.addParameter(literal.value));
|
|
445
|
-
|
|
446
|
-
this.registerOperandCompiler('AliasRef', (alias: AliasRefNode, _ctx) => {
|
|
447
|
-
void _ctx;
|
|
448
|
-
return this.quoteIdentifier(alias.name);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
this.registerOperandCompiler('Column', (column: ColumnNode, _ctx) => {
|
|
452
|
-
void _ctx;
|
|
453
|
-
return `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`;
|
|
454
|
-
});
|
|
455
|
-
this.registerOperandCompiler('Function', (fnNode: FunctionNode, ctx) =>
|
|
456
|
-
this.compileFunctionOperand(fnNode, ctx)
|
|
457
|
-
);
|
|
458
|
-
this.registerOperandCompiler('JsonPath', (path: JsonPathNode, _ctx) => {
|
|
459
|
-
void _ctx;
|
|
460
|
-
return this.compileJsonPath(path);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
this.registerOperandCompiler('ScalarSubquery', (node: ScalarSubqueryNode, ctx) => {
|
|
464
|
-
const sql = this.compileSelectAst(node.query, ctx).trim().replace(/;$/, '');
|
|
465
|
-
return `(${sql})`;
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
this.registerOperandCompiler('CaseExpression', (node: CaseExpressionNode, ctx) => {
|
|
469
|
-
const parts = ['CASE'];
|
|
470
|
-
for (const { when, then } of node.conditions) {
|
|
471
|
-
parts.push(`WHEN ${this.compileExpression(when, ctx)} THEN ${this.compileOperand(then, ctx)}`);
|
|
472
|
-
}
|
|
473
|
-
if (node.else) {
|
|
474
|
-
parts.push(`ELSE ${this.compileOperand(node.else, ctx)}`);
|
|
475
|
-
}
|
|
476
|
-
parts.push('END');
|
|
477
|
-
return parts.join(' ');
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
this.registerOperandCompiler('Cast', (node: CastExpressionNode, ctx) => {
|
|
481
|
-
const value = this.compileOperand(node.expression, ctx);
|
|
482
|
-
return `CAST(${value} AS ${node.castType})`;
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
this.registerOperandCompiler('WindowFunction', (node: WindowFunctionNode, ctx) => {
|
|
486
|
-
let result = `${node.name}(`;
|
|
487
|
-
if (node.args.length > 0) {
|
|
488
|
-
result += node.args.map(arg => this.compileOperand(arg, ctx)).join(', ');
|
|
489
|
-
}
|
|
490
|
-
result += ') OVER (';
|
|
491
|
-
|
|
492
|
-
const parts: string[] = [];
|
|
493
|
-
|
|
494
|
-
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
495
|
-
const partitionClause = 'PARTITION BY ' + node.partitionBy.map(col =>
|
|
496
|
-
`${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`
|
|
497
|
-
).join(', ');
|
|
498
|
-
parts.push(partitionClause);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (node.orderBy && node.orderBy.length > 0) {
|
|
502
|
-
const orderClause = 'ORDER BY ' + node.orderBy.map(o => {
|
|
503
|
-
const term = this.compileOrderingTerm(o.term, ctx);
|
|
504
|
-
const collation = o.collation ? ` COLLATE ${o.collation}` : '';
|
|
505
|
-
const nulls = o.nulls ? ` NULLS ${o.nulls}` : '';
|
|
506
|
-
return `${term} ${o.direction}${collation}${nulls}`;
|
|
507
|
-
}).join(', ');
|
|
508
|
-
parts.push(orderClause);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
result += parts.join(' ');
|
|
512
|
-
result += ')';
|
|
513
|
-
|
|
514
|
-
return result;
|
|
515
|
-
});
|
|
516
|
-
this.registerOperandCompiler('ArithmeticExpression', (node: ArithmeticExpressionNode, ctx) => {
|
|
517
|
-
const left = this.compileOperand(node.left, ctx);
|
|
518
|
-
const right = this.compileOperand(node.right, ctx);
|
|
519
|
-
return `(${left} ${node.operator} ${right})`;
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Default fallback, should be overridden by dialects if supported
|
|
524
|
-
protected compileJsonPath(_node: JsonPathNode): string {
|
|
525
|
-
void _node;
|
|
526
|
-
throw new Error("JSON Path not supported by this dialect");
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Compiles a function operand, using the dialect's function strategy.
|
|
531
|
-
*/
|
|
532
|
-
protected compileFunctionOperand(fnNode: FunctionNode, ctx: CompilerContext): string {
|
|
533
|
-
const compiledArgs = fnNode.args.map(arg => this.compileOperand(arg, ctx));
|
|
534
|
-
const renderer = this.functionStrategy.getRenderer(fnNode.name);
|
|
535
|
-
if (renderer) {
|
|
536
|
-
return renderer({
|
|
537
|
-
node: fnNode,
|
|
538
|
-
compiledArgs,
|
|
539
|
-
compileOperand: operand => this.compileOperand(operand, ctx)
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
return `${fnNode.name}(${compiledArgs.join(', ')})`;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Creates a new Dialect instance (for testing purposes)
|
|
310
|
+
* @param functionStrategy - Optional function strategy
|
|
311
|
+
* @returns New Dialect instance
|
|
312
|
+
*/
|
|
313
|
+
static create(functionStrategy?: FunctionStrategy, tableFunctionStrategy?: TableFunctionStrategy): Dialect {
|
|
314
|
+
// Create a minimal concrete implementation for testing
|
|
315
|
+
class TestDialect extends Dialect {
|
|
316
|
+
protected readonly dialect: DialectName = 'sqlite';
|
|
317
|
+
quoteIdentifier(id: string): string {
|
|
318
|
+
return `"${id}"`;
|
|
319
|
+
}
|
|
320
|
+
protected compileSelectAst(): never {
|
|
321
|
+
throw new Error('Not implemented');
|
|
322
|
+
}
|
|
323
|
+
protected compileInsertAst(): never {
|
|
324
|
+
throw new Error('Not implemented');
|
|
325
|
+
}
|
|
326
|
+
protected compileUpdateAst(): never {
|
|
327
|
+
throw new Error('Not implemented');
|
|
328
|
+
}
|
|
329
|
+
protected compileDeleteAst(): never {
|
|
330
|
+
throw new Error('Not implemented');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return new TestDialect(functionStrategy, tableFunctionStrategy);
|
|
543
334
|
}
|
|
544
|
-
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Registers an expression compiler for a specific node type
|
|
338
|
+
* @param type - Expression node type
|
|
339
|
+
* @param compiler - Compiler function
|
|
340
|
+
*/
|
|
341
|
+
protected registerExpressionCompiler<T extends ExpressionNode>(type: T['type'], compiler: (node: T, ctx: CompilerContext) => string): void {
|
|
342
|
+
this.expressionCompilers.set(type, compiler as (node: ExpressionNode, ctx: CompilerContext) => string);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Registers an operand compiler for a specific node type
|
|
347
|
+
* @param type - Operand node type
|
|
348
|
+
* @param compiler - Compiler function
|
|
349
|
+
*/
|
|
350
|
+
protected registerOperandCompiler<T extends OperandNode>(type: T['type'], compiler: (node: T, ctx: CompilerContext) => string): void {
|
|
351
|
+
this.operandCompilers.set(type, compiler as (node: OperandNode, ctx: CompilerContext) => string);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Compiles an expression node
|
|
356
|
+
* @param node - Expression node to compile
|
|
357
|
+
* @param ctx - Compiler context
|
|
358
|
+
* @returns Compiled SQL expression
|
|
359
|
+
*/
|
|
360
|
+
protected compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
|
|
361
|
+
const compiler = this.expressionCompilers.get(node.type);
|
|
362
|
+
if (!compiler) {
|
|
363
|
+
throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
|
|
364
|
+
}
|
|
365
|
+
return compiler(node, ctx);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Compiles an operand node
|
|
370
|
+
* @param node - Operand node to compile
|
|
371
|
+
* @param ctx - Compiler context
|
|
372
|
+
* @returns Compiled SQL operand
|
|
373
|
+
*/
|
|
374
|
+
protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
375
|
+
const compiler = this.operandCompilers.get(node.type);
|
|
376
|
+
if (!compiler) {
|
|
377
|
+
throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
|
|
378
|
+
}
|
|
379
|
+
return compiler(node, ctx);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Compiles an ordering term (operand, expression, or alias reference).
|
|
384
|
+
*/
|
|
385
|
+
protected compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string {
|
|
386
|
+
if (isOperandNode(term)) {
|
|
387
|
+
return this.compileOperand(term, ctx);
|
|
388
|
+
}
|
|
389
|
+
// At this point, term must be an ExpressionNode
|
|
390
|
+
const expr = this.compileExpression(term as ExpressionNode, ctx);
|
|
391
|
+
return `(${expr})`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private registerDefaultExpressionCompilers(): void {
|
|
395
|
+
this.registerExpressionCompiler('BinaryExpression', (binary: BinaryExpressionNode, ctx) => {
|
|
396
|
+
const left = this.compileOperand(binary.left, ctx);
|
|
397
|
+
const right = this.compileOperand(binary.right, ctx);
|
|
398
|
+
const base = `${left} ${binary.operator} ${right}`;
|
|
399
|
+
if (binary.escape) {
|
|
400
|
+
const escapeOperand = this.compileOperand(binary.escape, ctx);
|
|
401
|
+
return `${base} ESCAPE ${escapeOperand}`;
|
|
402
|
+
}
|
|
403
|
+
return base;
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
this.registerExpressionCompiler('LogicalExpression', (logical: LogicalExpressionNode, ctx) => {
|
|
407
|
+
if (logical.operands.length === 0) return '';
|
|
408
|
+
const parts = logical.operands.map(op => {
|
|
409
|
+
const compiled = this.compileExpression(op, ctx);
|
|
410
|
+
return op.type === 'LogicalExpression' ? `(${compiled})` : compiled;
|
|
411
|
+
});
|
|
412
|
+
return parts.join(` ${logical.operator} `);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
this.registerExpressionCompiler('NullExpression', (nullExpr: NullExpressionNode, ctx) => {
|
|
416
|
+
const left = this.compileOperand(nullExpr.left, ctx);
|
|
417
|
+
return `${left} ${nullExpr.operator}`;
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
this.registerExpressionCompiler('InExpression', (inExpr: InExpressionNode, ctx) => {
|
|
421
|
+
const left = this.compileOperand(inExpr.left, ctx);
|
|
422
|
+
if (Array.isArray(inExpr.right)) {
|
|
423
|
+
const values = inExpr.right.map(v => this.compileOperand(v, ctx)).join(', ');
|
|
424
|
+
return `${left} ${inExpr.operator} (${values})`;
|
|
425
|
+
}
|
|
426
|
+
const subquerySql = this.compileSelectAst(inExpr.right.query, ctx).trim().replace(/;$/, '');
|
|
427
|
+
return `${left} ${inExpr.operator} (${subquerySql})`;
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
this.registerExpressionCompiler('ExistsExpression', (existsExpr: ExistsExpressionNode, ctx) => {
|
|
431
|
+
const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
|
|
432
|
+
return `${existsExpr.operator} (${subquerySql})`;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
this.registerExpressionCompiler('BetweenExpression', (betweenExpr: BetweenExpressionNode, ctx) => {
|
|
436
|
+
const left = this.compileOperand(betweenExpr.left, ctx);
|
|
437
|
+
const lower = this.compileOperand(betweenExpr.lower, ctx);
|
|
438
|
+
const upper = this.compileOperand(betweenExpr.upper, ctx);
|
|
439
|
+
return `${left} ${betweenExpr.operator} ${lower} AND ${upper}`;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
this.registerExpressionCompiler('ArithmeticExpression', (arith: ArithmeticExpressionNode, ctx) => {
|
|
443
|
+
const left = this.compileOperand(arith.left, ctx);
|
|
444
|
+
const right = this.compileOperand(arith.right, ctx);
|
|
445
|
+
return `${left} ${arith.operator} ${right}`;
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
this.registerExpressionCompiler('BitwiseExpression', (bitwise: BitwiseExpressionNode, ctx) => {
|
|
449
|
+
const left = this.compileOperand(bitwise.left, ctx);
|
|
450
|
+
const right = this.compileOperand(bitwise.right, ctx);
|
|
451
|
+
return `${left} ${bitwise.operator} ${right}`;
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private registerDefaultOperandCompilers(): void {
|
|
456
|
+
this.registerOperandCompiler('Literal', (literal: LiteralNode, ctx) => ctx.addParameter(literal.value));
|
|
457
|
+
|
|
458
|
+
this.registerOperandCompiler('AliasRef', (alias: AliasRefNode, _ctx) => {
|
|
459
|
+
void _ctx;
|
|
460
|
+
return this.quoteIdentifier(alias.name);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
this.registerOperandCompiler('Column', (column: ColumnNode, _ctx) => {
|
|
464
|
+
void _ctx;
|
|
465
|
+
return `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`;
|
|
466
|
+
});
|
|
467
|
+
this.registerOperandCompiler('Function', (fnNode: FunctionNode, ctx) =>
|
|
468
|
+
this.compileFunctionOperand(fnNode, ctx)
|
|
469
|
+
);
|
|
470
|
+
this.registerOperandCompiler('JsonPath', (path: JsonPathNode, _ctx) => {
|
|
471
|
+
void _ctx;
|
|
472
|
+
return this.compileJsonPath(path);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
this.registerOperandCompiler('ScalarSubquery', (node: ScalarSubqueryNode, ctx) => {
|
|
476
|
+
const sql = this.compileSelectAst(node.query, ctx).trim().replace(/;$/, '');
|
|
477
|
+
return `(${sql})`;
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
this.registerOperandCompiler('CaseExpression', (node: CaseExpressionNode, ctx) => {
|
|
481
|
+
const parts = ['CASE'];
|
|
482
|
+
for (const { when, then } of node.conditions) {
|
|
483
|
+
parts.push(`WHEN ${this.compileExpression(when, ctx)} THEN ${this.compileOperand(then, ctx)}`);
|
|
484
|
+
}
|
|
485
|
+
if (node.else) {
|
|
486
|
+
parts.push(`ELSE ${this.compileOperand(node.else, ctx)}`);
|
|
487
|
+
}
|
|
488
|
+
parts.push('END');
|
|
489
|
+
return parts.join(' ');
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
this.registerOperandCompiler('Cast', (node: CastExpressionNode, ctx) => {
|
|
493
|
+
const value = this.compileOperand(node.expression, ctx);
|
|
494
|
+
return `CAST(${value} AS ${node.castType})`;
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
this.registerOperandCompiler('WindowFunction', (node: WindowFunctionNode, ctx) => {
|
|
498
|
+
let result = `${node.name}(`;
|
|
499
|
+
if (node.args.length > 0) {
|
|
500
|
+
result += node.args.map(arg => this.compileOperand(arg, ctx)).join(', ');
|
|
501
|
+
}
|
|
502
|
+
result += ') OVER (';
|
|
503
|
+
|
|
504
|
+
const parts: string[] = [];
|
|
505
|
+
|
|
506
|
+
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
507
|
+
const partitionClause = 'PARTITION BY ' + node.partitionBy.map(col =>
|
|
508
|
+
`${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`
|
|
509
|
+
).join(', ');
|
|
510
|
+
parts.push(partitionClause);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (node.orderBy && node.orderBy.length > 0) {
|
|
514
|
+
const orderClause = 'ORDER BY ' + node.orderBy.map(o => {
|
|
515
|
+
const term = this.compileOrderingTerm(o.term, ctx);
|
|
516
|
+
const collation = o.collation ? ` COLLATE ${o.collation}` : '';
|
|
517
|
+
const nulls = o.nulls ? ` NULLS ${o.nulls}` : '';
|
|
518
|
+
return `${term} ${o.direction}${collation}${nulls}`;
|
|
519
|
+
}).join(', ');
|
|
520
|
+
parts.push(orderClause);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
result += parts.join(' ');
|
|
524
|
+
result += ')';
|
|
525
|
+
|
|
526
|
+
return result;
|
|
527
|
+
});
|
|
528
|
+
this.registerOperandCompiler('ArithmeticExpression', (node: ArithmeticExpressionNode, ctx) => {
|
|
529
|
+
const left = this.compileOperand(node.left, ctx);
|
|
530
|
+
const right = this.compileOperand(node.right, ctx);
|
|
531
|
+
return `(${left} ${node.operator} ${right})`;
|
|
532
|
+
});
|
|
533
|
+
this.registerOperandCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
|
|
534
|
+
const left = this.compileOperand(node.left, ctx);
|
|
535
|
+
const right = this.compileOperand(node.right, ctx);
|
|
536
|
+
return `(${left} ${node.operator} ${right})`;
|
|
537
|
+
});
|
|
538
|
+
this.registerOperandCompiler('Collate', (node: CollateExpressionNode, ctx) => {
|
|
539
|
+
const expr = this.compileOperand(node.expression, ctx);
|
|
540
|
+
return `${expr} COLLATE ${node.collation}`;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Default fallback, should be overridden by dialects if supported
|
|
545
|
+
protected compileJsonPath(_node: JsonPathNode): string {
|
|
546
|
+
void _node;
|
|
547
|
+
throw new Error("JSON Path not supported by this dialect");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Compiles a function operand, using the dialect's function strategy.
|
|
552
|
+
*/
|
|
553
|
+
protected compileFunctionOperand(fnNode: FunctionNode, ctx: CompilerContext): string {
|
|
554
|
+
const compiledArgs = fnNode.args.map(arg => this.compileOperand(arg, ctx));
|
|
555
|
+
const renderer = this.functionStrategy.getRenderer(fnNode.name);
|
|
556
|
+
if (renderer) {
|
|
557
|
+
return renderer({
|
|
558
|
+
node: fnNode,
|
|
559
|
+
compiledArgs,
|
|
560
|
+
compileOperand: operand => this.compileOperand(operand, ctx)
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
return `${fnNode.name}(${compiledArgs.join(', ')})`;
|
|
564
|
+
}
|
|
565
|
+
}
|