metal-orm 1.1.10 → 1.1.16

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