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,226 +1,226 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
2
- import {
3
- SelectQueryNode,
4
- InsertQueryNode,
5
- UpdateQueryNode,
6
- DeleteQueryNode
7
- } from '../../ast/query.js';
8
- import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
9
- import { MssqlFunctionStrategy } from './functions.js';
10
- import { OrderByCompiler } from '../base/orderby-compiler.js';
11
- import { JoinCompiler } from '../base/join-compiler.js';
12
- import { SqlDialectBase } from '../base/sql-dialect.js';
13
- import { ProcedureCallNode } from '../../ast/procedure.js';
14
-
15
- const sanitizeVariableSuffix = (value: string): string =>
16
- value.replace(/[^a-zA-Z0-9_]/g, '_');
17
-
18
- const toProcedureParamReference = (value: string): string =>
19
- value.startsWith('@') ? value : `@${value}`;
20
-
21
- /**
22
- * Microsoft SQL Server dialect implementation
23
- */
24
- export class SqlServerDialect extends SqlDialectBase {
25
- protected readonly dialect = 'mssql';
26
- /**
27
- * Creates a new SqlServerDialect instance
28
- */
29
- public constructor() {
30
- super(new MssqlFunctionStrategy());
31
- }
32
-
33
- /**
34
- * Quotes an identifier using SQL Server bracket syntax
35
- * @param id - Identifier to quote
36
- * @returns Quoted identifier
37
- */
38
- quoteIdentifier(id: string): string {
39
- return `[${id}]`;
40
- }
41
-
42
- /**
43
- * Compiles JSON path expression using SQL Server syntax
44
- * @param node - JSON path node
45
- * @returns SQL Server JSON path expression
46
- */
47
- protected compileJsonPath(node: JsonPathNode): string {
48
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
49
- // SQL Server uses JSON_VALUE(col, '$.path')
50
- return `JSON_VALUE(${col}, '${node.path}')`;
51
- }
52
-
53
- /**
54
- * Formats parameter placeholders using SQL Server named parameter syntax
55
- * @param index - Parameter index
56
- * @returns Named parameter placeholder
57
- */
58
- protected formatPlaceholder(index: number): string {
59
- return `@p${index}`;
60
- }
61
-
62
- /**
63
- * Compiles SELECT query AST to SQL Server SQL
64
- * @param ast - Query AST
65
- * @param ctx - Compiler context
66
- * @returns SQL Server SQL string
67
- */
68
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
69
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
70
- const ctes = this.compileCtes(ast, ctx);
71
-
72
- const baseAst: SelectQueryNode = hasSetOps
73
- ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
74
- : ast;
75
-
76
- const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
77
-
78
- if (!hasSetOps) {
79
- return `${ctes}${baseSelect}`;
80
- }
81
-
82
- const compound = ast.setOps!
83
- .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
84
- .join(' ');
85
-
86
- const orderBy = this.compileOrderBy(ast, ctx);
87
- const pagination = this.compilePagination(ast, orderBy);
88
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
89
- const tail = pagination || orderBy;
90
- return `${ctes}${combined}${tail}`;
91
- }
92
-
93
- protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
94
- if (ast.using) {
95
- throw new Error('DELETE ... USING is not supported in the MSSQL dialect; use join() instead.');
96
- }
97
-
98
- if (ast.from.type !== 'Table') {
99
- throw new Error('DELETE only supports base tables in the MSSQL dialect.');
100
- }
101
-
102
- const alias = ast.from.alias ?? ast.from.name;
103
- const target = this.compileTableReference(ast.from);
104
- const joins = JoinCompiler.compileJoins(
105
- ast.joins,
106
- ctx,
107
- this.compileFrom.bind(this),
108
- this.compileExpression.bind(this)
109
- );
110
- const whereClause = this.compileWhere(ast.where, ctx);
111
- const returning = this.compileOutputClause(ast.returning, 'deleted');
112
- return `DELETE ${this.quoteIdentifier(alias)}${returning} FROM ${target}${joins}${whereClause}`;
113
- }
114
-
115
- protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
116
- const target = this.compileTableReference(ast.table);
117
- const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
118
- const output = this.compileReturning(ast.returning, ctx);
119
- const fromClause = ast.from ? ` FROM ${this.compileFrom(ast.from, ctx)}` : '';
120
- const joins = ast.joins
121
- ? ast.joins.map(j => {
122
- const table = this.compileFrom(j.table, ctx);
123
- const cond = this.compileExpression(j.condition, ctx);
124
- return ` ${j.kind} JOIN ${table} ON ${cond}`;
125
- }).join('')
126
- : '';
127
- const whereClause = this.compileWhere(ast.where, ctx);
128
- return `UPDATE ${target} SET ${assignments}${output}${fromClause}${joins}${whereClause}`;
129
- }
130
-
131
- private compileSelectCoreForMssql(ast: SelectQueryNode, ctx: CompilerContext): string {
132
- const columns = ast.columns.map(c => {
133
- // Default to full operand compilation for all projection node types (Function, Column, Cast, Case, Window, etc)
134
- const expr = c.type === 'Column'
135
- ? `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`
136
- : this.compileOperand(c as unknown as import('../../ast/expression.js').OperandNode, ctx);
137
-
138
- if (c.alias) {
139
- if (c.alias.includes('(')) return c.alias;
140
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
141
- }
142
- return expr;
143
- }).join(', ');
144
-
145
- const distinct = ast.distinct ? 'DISTINCT ' : '';
146
- const from = this.compileFrom(ast.from, ctx);
147
-
148
- const joins = ast.joins.map(j => {
149
- const table = this.compileFrom(j.table, ctx);
150
- const cond = this.compileExpression(j.condition, ctx);
151
- return `${j.kind} JOIN ${table} ON ${cond}`;
152
- }).join(' ');
153
- const whereClause = this.compileWhere(ast.where, ctx);
154
-
155
- const groupBy = ast.groupBy && ast.groupBy.length > 0
156
- ? ' GROUP BY ' + ast.groupBy.map(term => this.compileOrderingTerm(term, ctx)).join(', ')
157
- : '';
158
-
159
- const having = ast.having
160
- ? ` HAVING ${this.compileExpression(ast.having, ctx)}`
161
- : '';
162
-
163
- const orderBy = this.compileOrderBy(ast, ctx);
164
- const pagination = this.compilePagination(ast, orderBy);
165
-
166
- if (pagination) {
167
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination}`;
168
- }
169
-
170
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}`;
171
- }
172
-
173
- private compileOrderBy(ast: SelectQueryNode, ctx: CompilerContext): string {
174
- return OrderByCompiler.compileOrderBy(
175
- ast,
176
- term => this.compileOrderingTerm(term, ctx),
177
- this.renderOrderByNulls.bind(this),
178
- this.renderOrderByCollation.bind(this)
179
- );
180
- }
181
-
182
- private compilePagination(ast: SelectQueryNode, orderBy: string): string {
183
- const hasLimit = ast.limit !== undefined;
184
- const hasOffset = ast.offset !== undefined;
185
- if (!hasLimit && !hasOffset) return '';
186
-
187
- const off = ast.offset ?? 0;
188
- let orderClause = orderBy;
189
- if (!orderClause) {
190
- // SQL Server requires ORDER BY items to appear in the SELECT list when DISTINCT is used.
191
- // For paginated DISTINCT queries without explicit ORDER BY, use ORDER BY 1 (first projection).
192
- orderClause = ast.distinct && ast.distinct.length > 0
193
- ? ' ORDER BY 1'
194
- : ' ORDER BY (SELECT NULL)';
195
- }
196
- let pagination = `${orderClause} OFFSET ${off} ROWS`;
197
- if (hasLimit) {
198
- pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
199
- }
200
- return pagination;
201
- }
202
-
203
- supportsDmlReturningClause(): boolean {
204
- return true;
205
- }
206
-
207
- protected compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
208
- void _ctx;
209
- return this.compileOutputClause(returning, 'inserted');
210
- }
211
-
212
- private compileOutputClause(returning: ColumnNode[] | undefined, prefix: 'inserted' | 'deleted'): string {
213
- if (!returning || returning.length === 0) return '';
214
- const columns = returning
215
- .map(column => {
216
- const colName = this.quoteIdentifier(column.name);
217
- const alias = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : '';
218
- return `${prefix}.${colName}${alias}`;
219
- })
220
- .join(', ');
221
- return ` OUTPUT ${columns}`;
222
- }
223
-
1
+ import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
2
+ import {
3
+ SelectQueryNode,
4
+ InsertQueryNode,
5
+ UpdateQueryNode,
6
+ DeleteQueryNode
7
+ } from '../../ast/query.js';
8
+ import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
9
+ import { MssqlFunctionStrategy } from './functions.js';
10
+ import { OrderByCompiler } from '../base/orderby-compiler.js';
11
+ import { JoinCompiler } from '../base/join-compiler.js';
12
+ import { SqlDialectBase } from '../base/sql-dialect.js';
13
+ import { ProcedureCallNode } from '../../ast/procedure.js';
14
+
15
+ const sanitizeVariableSuffix = (value: string): string =>
16
+ value.replace(/[^a-zA-Z0-9_]/g, '_');
17
+
18
+ const toProcedureParamReference = (value: string): string =>
19
+ value.startsWith('@') ? value : `@${value}`;
20
+
21
+ /**
22
+ * Microsoft SQL Server dialect implementation
23
+ */
24
+ export class SqlServerDialect extends SqlDialectBase {
25
+ protected readonly dialect = 'mssql';
26
+ /**
27
+ * Creates a new SqlServerDialect instance
28
+ */
29
+ public constructor() {
30
+ super(new MssqlFunctionStrategy());
31
+ }
32
+
33
+ /**
34
+ * Quotes an identifier using SQL Server bracket syntax
35
+ * @param id - Identifier to quote
36
+ * @returns Quoted identifier
37
+ */
38
+ quoteIdentifier(id: string): string {
39
+ return `[${id}]`;
40
+ }
41
+
42
+ /**
43
+ * Compiles JSON path expression using SQL Server syntax
44
+ * @param node - JSON path node
45
+ * @returns SQL Server JSON path expression
46
+ */
47
+ protected compileJsonPath(node: JsonPathNode): string {
48
+ const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
49
+ // SQL Server uses JSON_VALUE(col, '$.path')
50
+ return `JSON_VALUE(${col}, '${node.path}')`;
51
+ }
52
+
53
+ /**
54
+ * Formats parameter placeholders using SQL Server named parameter syntax
55
+ * @param index - Parameter index
56
+ * @returns Named parameter placeholder
57
+ */
58
+ protected formatPlaceholder(index: number): string {
59
+ return `@p${index}`;
60
+ }
61
+
62
+ /**
63
+ * Compiles SELECT query AST to SQL Server SQL
64
+ * @param ast - Query AST
65
+ * @param ctx - Compiler context
66
+ * @returns SQL Server SQL string
67
+ */
68
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
69
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
70
+ const ctes = this.compileCtes(ast, ctx);
71
+
72
+ const baseAst: SelectQueryNode = hasSetOps
73
+ ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
74
+ : ast;
75
+
76
+ const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
77
+
78
+ if (!hasSetOps) {
79
+ return `${ctes}${baseSelect}`;
80
+ }
81
+
82
+ const compound = ast.setOps!
83
+ .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
84
+ .join(' ');
85
+
86
+ const orderBy = this.compileOrderBy(ast, ctx);
87
+ const pagination = this.compilePagination(ast, orderBy);
88
+ const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
89
+ const tail = pagination || orderBy;
90
+ return `${ctes}${combined}${tail}`;
91
+ }
92
+
93
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
94
+ if (ast.using) {
95
+ throw new Error('DELETE ... USING is not supported in the MSSQL dialect; use join() instead.');
96
+ }
97
+
98
+ if (ast.from.type !== 'Table') {
99
+ throw new Error('DELETE only supports base tables in the MSSQL dialect.');
100
+ }
101
+
102
+ const alias = ast.from.alias ?? ast.from.name;
103
+ const target = this.compileTableReference(ast.from);
104
+ const joins = JoinCompiler.compileJoins(
105
+ ast.joins,
106
+ ctx,
107
+ this.compileFrom.bind(this),
108
+ this.compileExpression.bind(this)
109
+ );
110
+ const whereClause = this.compileWhere(ast.where, ctx);
111
+ const returning = this.compileOutputClause(ast.returning, 'deleted');
112
+ return `DELETE ${this.quoteIdentifier(alias)}${returning} FROM ${target}${joins}${whereClause}`;
113
+ }
114
+
115
+ protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
116
+ const target = this.compileTableReference(ast.table);
117
+ const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
118
+ const output = this.compileReturning(ast.returning, ctx);
119
+ const fromClause = ast.from ? ` FROM ${this.compileFrom(ast.from, ctx)}` : '';
120
+ const joins = ast.joins
121
+ ? ast.joins.map(j => {
122
+ const table = this.compileFrom(j.table, ctx);
123
+ const cond = this.compileExpression(j.condition, ctx);
124
+ return ` ${j.kind} JOIN ${table} ON ${cond}`;
125
+ }).join('')
126
+ : '';
127
+ const whereClause = this.compileWhere(ast.where, ctx);
128
+ return `UPDATE ${target} SET ${assignments}${output}${fromClause}${joins}${whereClause}`;
129
+ }
130
+
131
+ private compileSelectCoreForMssql(ast: SelectQueryNode, ctx: CompilerContext): string {
132
+ const columns = ast.columns.map(c => {
133
+ // Default to full operand compilation for all projection node types (Function, Column, Cast, Case, Window, etc)
134
+ const expr = c.type === 'Column'
135
+ ? `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`
136
+ : this.compileOperand(c as unknown as import('../../ast/expression.js').OperandNode, ctx);
137
+
138
+ if (c.alias) {
139
+ if (c.alias.includes('(')) return c.alias;
140
+ return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
141
+ }
142
+ return expr;
143
+ }).join(', ');
144
+
145
+ const distinct = ast.distinct ? 'DISTINCT ' : '';
146
+ const from = this.compileFrom(ast.from, ctx);
147
+
148
+ const joins = ast.joins.map(j => {
149
+ const table = this.compileFrom(j.table, ctx);
150
+ const cond = this.compileExpression(j.condition, ctx);
151
+ return `${j.kind} JOIN ${table} ON ${cond}`;
152
+ }).join(' ');
153
+ const whereClause = this.compileWhere(ast.where, ctx);
154
+
155
+ const groupBy = ast.groupBy && ast.groupBy.length > 0
156
+ ? ' GROUP BY ' + ast.groupBy.map(term => this.compileOrderingTerm(term, ctx)).join(', ')
157
+ : '';
158
+
159
+ const having = ast.having
160
+ ? ` HAVING ${this.compileExpression(ast.having, ctx)}`
161
+ : '';
162
+
163
+ const orderBy = this.compileOrderBy(ast, ctx);
164
+ const pagination = this.compilePagination(ast, orderBy);
165
+
166
+ if (pagination) {
167
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination}`;
168
+ }
169
+
170
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}`;
171
+ }
172
+
173
+ private compileOrderBy(ast: SelectQueryNode, ctx: CompilerContext): string {
174
+ return OrderByCompiler.compileOrderBy(
175
+ ast,
176
+ term => this.compileOrderingTerm(term, ctx),
177
+ this.renderOrderByNulls.bind(this),
178
+ this.renderOrderByCollation.bind(this)
179
+ );
180
+ }
181
+
182
+ private compilePagination(ast: SelectQueryNode, orderBy: string): string {
183
+ const hasLimit = ast.limit !== undefined;
184
+ const hasOffset = ast.offset !== undefined;
185
+ if (!hasLimit && !hasOffset) return '';
186
+
187
+ const off = ast.offset ?? 0;
188
+ let orderClause = orderBy;
189
+ if (!orderClause) {
190
+ // SQL Server requires ORDER BY items to appear in the SELECT list when DISTINCT is used.
191
+ // For paginated DISTINCT queries without explicit ORDER BY, use ORDER BY 1 (first projection).
192
+ orderClause = ast.distinct && ast.distinct.length > 0
193
+ ? ' ORDER BY 1'
194
+ : ' ORDER BY (SELECT NULL)';
195
+ }
196
+ let pagination = `${orderClause} OFFSET ${off} ROWS`;
197
+ if (hasLimit) {
198
+ pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
199
+ }
200
+ return pagination;
201
+ }
202
+
203
+ supportsDmlReturningClause(): boolean {
204
+ return true;
205
+ }
206
+
207
+ protected compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
208
+ void _ctx;
209
+ return this.compileOutputClause(returning, 'inserted');
210
+ }
211
+
212
+ private compileOutputClause(returning: ColumnNode[] | undefined, prefix: 'inserted' | 'deleted'): string {
213
+ if (!returning || returning.length === 0) return '';
214
+ const columns = returning
215
+ .map(column => {
216
+ const colName = this.quoteIdentifier(column.name);
217
+ const alias = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : '';
218
+ return `${prefix}.${colName}${alias}`;
219
+ })
220
+ .join(', ');
221
+ return ` OUTPUT ${columns}`;
222
+ }
223
+
224
224
  protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
225
225
  if (!ast.columns.length) {
226
226
  throw new Error('INSERT queries must specify columns.');
@@ -296,97 +296,97 @@ export class SqlServerDialect extends SqlDialectBase {
296
296
  const selectSql = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, '');
297
297
  return `(${selectSql})`;
298
298
  }
299
-
300
- private compileInsertValues(ast: InsertQueryNode, ctx: CompilerContext): string {
301
- const source = ast.source;
302
- if (source.type === 'InsertValues') {
303
- if (!source.rows.length) {
304
- throw new Error('INSERT ... VALUES requires at least one row.');
305
- }
306
- const values = source.rows
307
- .map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
308
- .join(', ');
309
- return `VALUES ${values}`;
310
- }
311
- const normalized = this.normalizeSelectAst(source.query);
312
- return this.compileSelectAst(normalized, ctx).trim();
313
- }
314
-
315
- private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
316
- if (!ast.ctes || ast.ctes.length === 0) return '';
317
- // MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
318
- const defs = ast.ctes.map(cte => {
319
- const name = this.quoteIdentifier(cte.name);
320
- const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
321
- const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, '');
322
- return `${name}${cols} AS (${query})`;
323
- }).join(', ');
324
- return `WITH ${defs} `;
325
- }
326
-
327
- compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
328
- const ctx = this.createCompilerContext();
329
- const qualifiedName = ast.ref.schema
330
- ? `${this.quoteIdentifier(ast.ref.schema)}.${this.quoteIdentifier(ast.ref.name)}`
331
- : this.quoteIdentifier(ast.ref.name);
332
-
333
- const declarations: string[] = [];
334
- const assignments: string[] = [];
335
- const execArgs: string[] = [];
336
- const outVars: Array<{ variable: string; name: string }> = [];
337
-
338
- ast.params.forEach((param, index) => {
339
- const targetParam = toProcedureParamReference(param.name);
340
- if (param.direction === 'in') {
341
- if (!param.value) {
342
- throw new Error(`Procedure parameter "${param.name}" requires a value for direction "in".`);
343
- }
344
- execArgs.push(`${targetParam} = ${this.compileOperand(param.value, ctx)}`);
345
- return;
346
- }
347
-
348
- if (!param.dbType) {
349
- throw new Error(
350
- `MSSQL procedure parameter "${param.name}" requires "dbType" for direction "${param.direction}".`
351
- );
352
- }
353
-
354
- const suffix = sanitizeVariableSuffix(param.name || `p${index + 1}`);
355
- const variable = `@__metal_${suffix}_${index + 1}`;
356
- declarations.push(`DECLARE ${variable} ${param.dbType};`);
357
-
358
- if (param.direction === 'inout') {
359
- if (!param.value) {
360
- throw new Error(`Procedure parameter "${param.name}" requires a value for direction "inout".`);
361
- }
362
- assignments.push(`SET ${variable} = ${this.compileOperand(param.value, ctx)};`);
363
- }
364
-
365
- execArgs.push(`${targetParam} = ${variable} OUTPUT`);
366
- outVars.push({ variable, name: param.name });
367
- });
368
-
369
- const statements: string[] = [];
370
- if (declarations.length) statements.push(...declarations);
371
- if (assignments.length) statements.push(...assignments);
372
- const argsSql = execArgs.length ? ` ${execArgs.join(', ')}` : '';
373
- statements.push(`EXEC ${qualifiedName}${argsSql};`);
374
-
375
- if (outVars.length) {
376
- const selectOut = outVars
377
- .map(({ variable, name }) => `${variable} AS ${this.quoteIdentifier(name)}`)
378
- .join(', ');
379
- statements.push(`SELECT ${selectOut};`);
380
- }
381
-
382
- return {
383
- sql: statements.join(' '),
384
- params: [...ctx.params],
385
- outParams: {
386
- source: outVars.length ? 'lastResultSet' : 'none',
387
- names: outVars.map(item => item.name)
388
- }
389
- };
390
- }
391
-
392
- }
299
+
300
+ private compileInsertValues(ast: InsertQueryNode, ctx: CompilerContext): string {
301
+ const source = ast.source;
302
+ if (source.type === 'InsertValues') {
303
+ if (!source.rows.length) {
304
+ throw new Error('INSERT ... VALUES requires at least one row.');
305
+ }
306
+ const values = source.rows
307
+ .map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
308
+ .join(', ');
309
+ return `VALUES ${values}`;
310
+ }
311
+ const normalized = this.normalizeSelectAst(source.query);
312
+ return this.compileSelectAst(normalized, ctx).trim();
313
+ }
314
+
315
+ private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
316
+ if (!ast.ctes || ast.ctes.length === 0) return '';
317
+ // MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
318
+ const defs = ast.ctes.map(cte => {
319
+ const name = this.quoteIdentifier(cte.name);
320
+ const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
321
+ const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, '');
322
+ return `${name}${cols} AS (${query})`;
323
+ }).join(', ');
324
+ return `WITH ${defs} `;
325
+ }
326
+
327
+ compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
328
+ const ctx = this.createCompilerContext();
329
+ const qualifiedName = ast.ref.schema
330
+ ? `${this.quoteIdentifier(ast.ref.schema)}.${this.quoteIdentifier(ast.ref.name)}`
331
+ : this.quoteIdentifier(ast.ref.name);
332
+
333
+ const declarations: string[] = [];
334
+ const assignments: string[] = [];
335
+ const execArgs: string[] = [];
336
+ const outVars: Array<{ variable: string; name: string }> = [];
337
+
338
+ ast.params.forEach((param, index) => {
339
+ const targetParam = toProcedureParamReference(param.name);
340
+ if (param.direction === 'in') {
341
+ if (!param.value) {
342
+ throw new Error(`Procedure parameter "${param.name}" requires a value for direction "in".`);
343
+ }
344
+ execArgs.push(`${targetParam} = ${this.compileOperand(param.value, ctx)}`);
345
+ return;
346
+ }
347
+
348
+ if (!param.dbType) {
349
+ throw new Error(
350
+ `MSSQL procedure parameter "${param.name}" requires "dbType" for direction "${param.direction}".`
351
+ );
352
+ }
353
+
354
+ const suffix = sanitizeVariableSuffix(param.name || `p${index + 1}`);
355
+ const variable = `@__metal_${suffix}_${index + 1}`;
356
+ declarations.push(`DECLARE ${variable} ${param.dbType};`);
357
+
358
+ if (param.direction === 'inout') {
359
+ if (!param.value) {
360
+ throw new Error(`Procedure parameter "${param.name}" requires a value for direction "inout".`);
361
+ }
362
+ assignments.push(`SET ${variable} = ${this.compileOperand(param.value, ctx)};`);
363
+ }
364
+
365
+ execArgs.push(`${targetParam} = ${variable} OUTPUT`);
366
+ outVars.push({ variable, name: param.name });
367
+ });
368
+
369
+ const statements: string[] = [];
370
+ if (declarations.length) statements.push(...declarations);
371
+ if (assignments.length) statements.push(...assignments);
372
+ const argsSql = execArgs.length ? ` ${execArgs.join(', ')}` : '';
373
+ statements.push(`EXEC ${qualifiedName}${argsSql};`);
374
+
375
+ if (outVars.length) {
376
+ const selectOut = outVars
377
+ .map(({ variable, name }) => `${variable} AS ${this.quoteIdentifier(name)}`)
378
+ .join(', ');
379
+ statements.push(`SELECT ${selectOut};`);
380
+ }
381
+
382
+ return {
383
+ sql: statements.join(' '),
384
+ params: [...ctx.params],
385
+ outParams: {
386
+ source: outVars.length ? 'lastResultSet' : 'none',
387
+ names: outVars.map(item => item.name)
388
+ }
389
+ };
390
+ }
391
+
392
+ }