metal-orm 1.0.13 → 1.0.15

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 (115) hide show
  1. package/README.md +75 -82
  2. package/dist/decorators/index.cjs +1600 -27
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +6 -2
  5. package/dist/decorators/index.d.ts +6 -2
  6. package/dist/decorators/index.js +1599 -27
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +4608 -3429
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +511 -159
  11. package/dist/index.d.ts +511 -159
  12. package/dist/index.js +4526 -3415
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-CCp1oz9p.d.cts → select-Bkv8g8u_.d.cts} +193 -67
  15. package/dist/{select-CCp1oz9p.d.ts → select-Bkv8g8u_.d.ts} +193 -67
  16. package/package.json +1 -1
  17. package/src/codegen/typescript.ts +38 -35
  18. package/src/core/ast/adapters.ts +21 -0
  19. package/src/core/ast/aggregate-functions.ts +13 -13
  20. package/src/core/ast/builders.ts +56 -43
  21. package/src/core/ast/expression-builders.ts +34 -34
  22. package/src/core/ast/expression-nodes.ts +18 -16
  23. package/src/core/ast/expression-visitor.ts +122 -69
  24. package/src/core/ast/expression.ts +6 -4
  25. package/src/core/ast/join-metadata.ts +15 -0
  26. package/src/core/ast/join-node.ts +22 -20
  27. package/src/core/ast/join.ts +5 -5
  28. package/src/core/ast/query.ts +52 -88
  29. package/src/core/ast/types.ts +20 -0
  30. package/src/core/ast/window-functions.ts +55 -55
  31. package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
  32. package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
  33. package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
  34. package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
  35. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
  36. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  37. package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
  38. package/src/core/ddl/introspect/context.ts +9 -0
  39. package/src/core/ddl/introspect/functions/postgres.ts +26 -0
  40. package/src/core/ddl/introspect/mssql.ts +149 -149
  41. package/src/core/ddl/introspect/mysql.ts +99 -99
  42. package/src/core/ddl/introspect/postgres.ts +245 -154
  43. package/src/core/ddl/introspect/registry.ts +26 -0
  44. package/src/core/ddl/introspect/run-select.ts +25 -0
  45. package/src/core/ddl/introspect/sqlite.ts +7 -7
  46. package/src/core/ddl/introspect/types.ts +23 -19
  47. package/src/core/ddl/introspect/utils.ts +1 -1
  48. package/src/core/ddl/naming-strategy.ts +10 -0
  49. package/src/core/ddl/schema-dialect.ts +41 -0
  50. package/src/core/ddl/schema-diff.ts +211 -179
  51. package/src/core/ddl/schema-generator.ts +16 -90
  52. package/src/core/ddl/schema-introspect.ts +25 -32
  53. package/src/core/ddl/schema-plan-executor.ts +17 -0
  54. package/src/core/ddl/schema-types.ts +46 -39
  55. package/src/core/ddl/sql-writing.ts +170 -0
  56. package/src/core/dialect/abstract.ts +144 -126
  57. package/src/core/dialect/base/cte-compiler.ts +33 -0
  58. package/src/core/dialect/base/function-table-formatter.ts +132 -0
  59. package/src/core/dialect/base/groupby-compiler.ts +21 -0
  60. package/src/core/dialect/base/join-compiler.ts +26 -0
  61. package/src/core/dialect/base/orderby-compiler.ts +21 -0
  62. package/src/core/dialect/base/pagination-strategy.ts +32 -0
  63. package/src/core/dialect/base/returning-strategy.ts +56 -0
  64. package/src/core/dialect/base/sql-dialect.ts +181 -204
  65. package/src/core/dialect/dialect-factory.ts +91 -0
  66. package/src/core/dialect/mssql/functions.ts +101 -0
  67. package/src/core/dialect/mssql/index.ts +128 -126
  68. package/src/core/dialect/mysql/functions.ts +101 -0
  69. package/src/core/dialect/mysql/index.ts +20 -18
  70. package/src/core/dialect/postgres/functions.ts +95 -0
  71. package/src/core/dialect/postgres/index.ts +30 -28
  72. package/src/core/dialect/sqlite/functions.ts +115 -0
  73. package/src/core/dialect/sqlite/index.ts +30 -28
  74. package/src/core/driver/database-driver.ts +11 -0
  75. package/src/core/driver/mssql-driver.ts +20 -0
  76. package/src/core/driver/mysql-driver.ts +20 -0
  77. package/src/core/driver/postgres-driver.ts +20 -0
  78. package/src/core/driver/sqlite-driver.ts +20 -0
  79. package/src/core/execution/db-executor.ts +63 -0
  80. package/src/core/execution/executors/mssql-executor.ts +39 -0
  81. package/src/core/execution/executors/mysql-executor.ts +47 -0
  82. package/src/core/execution/executors/postgres-executor.ts +32 -0
  83. package/src/core/execution/executors/sqlite-executor.ts +31 -0
  84. package/src/core/functions/datetime.ts +132 -0
  85. package/src/core/functions/numeric.ts +179 -0
  86. package/src/core/functions/standard-strategy.ts +47 -0
  87. package/src/core/functions/text.ts +147 -0
  88. package/src/core/functions/types.ts +18 -0
  89. package/src/core/hydration/types.ts +57 -0
  90. package/src/decorators/bootstrap.ts +10 -0
  91. package/src/decorators/relations.ts +15 -0
  92. package/src/index.ts +30 -19
  93. package/src/orm/entity-metadata.ts +7 -0
  94. package/src/orm/entity.ts +58 -27
  95. package/src/orm/hydration.ts +25 -17
  96. package/src/orm/lazy-batch.ts +46 -2
  97. package/src/orm/orm-context.ts +60 -60
  98. package/src/orm/query-logger.ts +1 -1
  99. package/src/orm/relation-change-processor.ts +43 -2
  100. package/src/orm/relations/has-one.ts +139 -0
  101. package/src/orm/transaction-runner.ts +1 -1
  102. package/src/orm/unit-of-work.ts +60 -60
  103. package/src/query-builder/delete.ts +22 -5
  104. package/src/query-builder/hydration-manager.ts +2 -1
  105. package/src/query-builder/hydration-planner.ts +8 -7
  106. package/src/query-builder/insert.ts +22 -5
  107. package/src/query-builder/relation-conditions.ts +9 -8
  108. package/src/query-builder/relation-service.ts +3 -2
  109. package/src/query-builder/select.ts +66 -61
  110. package/src/query-builder/update.ts +22 -5
  111. package/src/schema/column.ts +246 -246
  112. package/src/schema/relation.ts +35 -1
  113. package/src/schema/table.ts +28 -28
  114. package/src/schema/types.ts +41 -31
  115. package/src/orm/db-executor.ts +0 -11
@@ -1,204 +1,181 @@
1
- import { CompilerContext, Dialect } from '../abstract.js';
2
- import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
3
- import { ColumnNode } from '../../ast/expression.js';
4
-
5
- /**
6
- * Shared SQL compiler for dialects with standard LIMIT/OFFSET pagination.
7
- * Concrete dialects override only the minimal hooks (identifier quoting,
8
- * JSON path, placeholders, RETURNING support) instead of re-implementing
9
- * the entire compile pipeline.
10
- */
11
- export abstract class SqlDialectBase extends Dialect {
12
- /**
13
- * Quotes an identifier (dialect-specific).
14
- */
15
- abstract quoteIdentifier(id: string): string;
16
-
17
- /**
18
- * Compiles SELECT query AST to SQL using common rules.
19
- */
20
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
21
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
22
- const ctes = this.compileCtes(ast, ctx);
23
-
24
- // When set operations exist, omit ORDER BY/OFFSET/LIMIT from the operands and apply at the end.
25
- const baseAst: SelectQueryNode = hasSetOps
26
- ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
27
- : ast;
28
-
29
- const baseSelect = this.compileSelectCore(baseAst, ctx);
30
-
31
- if (!hasSetOps) {
32
- return `${ctes}${baseSelect}`;
33
- }
34
-
35
- const compound = ast.setOps!
36
- .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
37
- .join(' ');
38
-
39
- const orderBy = this.compileOrderBy(ast);
40
- const pagination = this.compilePagination(ast, orderBy);
41
-
42
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
43
- return `${ctes}${combined}${orderBy}${pagination}`;
44
- }
45
-
46
- protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
47
- const table = this.compileTableName(ast.into);
48
- const columnList = ast.columns
49
- .map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`)
50
- .join(', ');
51
- const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
52
- const returning = this.compileReturning(ast.returning, ctx);
53
- return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
54
- }
55
-
56
- /**
57
- * Compiles a single SELECT (no set operations, no CTE prefix).
58
- */
59
- private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
60
- const columns = this.compileSelectColumns(ast, ctx);
61
- const from = this.compileFrom(ast.from);
62
- const joins = this.compileJoins(ast, ctx);
63
- const whereClause = this.compileWhere(ast.where, ctx);
64
- const groupBy = this.compileGroupBy(ast);
65
- const having = this.compileHaving(ast, ctx);
66
- const orderBy = this.compileOrderBy(ast);
67
- const pagination = this.compilePagination(ast, orderBy);
68
-
69
- return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
70
- }
71
-
72
- protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
73
- const table = this.compileTableName(ast.table);
74
- const assignments = ast.set.map(assignment => {
75
- const col = assignment.column;
76
- const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
77
- const value = this.compileOperand(assignment.value, ctx);
78
- return `${target} = ${value}`;
79
- }).join(', ');
80
- const whereClause = this.compileWhere(ast.where, ctx);
81
- const returning = this.compileReturning(ast.returning, ctx);
82
- return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
83
- }
84
-
85
- protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
86
- const table = this.compileTableName(ast.from);
87
- const whereClause = this.compileWhere(ast.where, ctx);
88
- const returning = this.compileReturning(ast.returning, ctx);
89
- return `DELETE FROM ${table}${whereClause}${returning}`;
90
- }
91
-
92
- /**
93
- * Default RETURNING compilation: no support.
94
- */
95
- protected compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
96
- if (!returning || returning.length === 0) return '';
97
- throw new Error('RETURNING is not supported by this dialect.');
98
- }
99
-
100
- protected formatReturningColumns(returning: ColumnNode[]): string {
101
- return returning
102
- .map(column => {
103
- const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : '';
104
- const aliasPart = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : '';
105
- return `${tablePart}${this.quoteIdentifier(column.name)}${aliasPart}`;
106
- })
107
- .join(', ');
108
- }
109
-
110
- /**
111
- * DISTINCT clause. Override for DISTINCT ON support.
112
- */
113
- protected compileDistinct(ast: SelectQueryNode): string {
114
- return ast.distinct ? 'DISTINCT ' : '';
115
- }
116
-
117
- protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
118
- return ast.columns.map(c => {
119
- const expr = this.compileOperand(c, ctx);
120
- if (c.alias) {
121
- if (c.alias.includes('(')) return c.alias;
122
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
123
- }
124
- return expr;
125
- }).join(', ');
126
- }
127
-
128
- protected compileFrom(ast: SelectQueryNode['from']): string {
129
- const base = this.compileTableName(ast);
130
- return ast.alias ? `${base} AS ${this.quoteIdentifier(ast.alias)}` : base;
131
- }
132
-
133
- protected compileTableName(table: { name: string; schema?: string }): string {
134
- if (table.schema) {
135
- return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
136
- }
137
- return this.quoteIdentifier(table.name);
138
- }
139
-
140
- protected compileJoins(ast: SelectQueryNode, ctx: CompilerContext): string {
141
- if (!ast.joins || ast.joins.length === 0) return '';
142
- const parts = ast.joins.map(j => {
143
- const table = this.compileFrom(j.table);
144
- const cond = this.compileExpression(j.condition, ctx);
145
- return `${j.kind} JOIN ${table} ON ${cond}`;
146
- });
147
- return ` ${parts.join(' ')}`;
148
- }
149
-
150
- protected compileGroupBy(ast: SelectQueryNode): string {
151
- if (!ast.groupBy || ast.groupBy.length === 0) return '';
152
- const cols = ast.groupBy
153
- .map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`)
154
- .join(', ');
155
- return ` GROUP BY ${cols}`;
156
- }
157
-
158
- protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
159
- if (!ast.having) return '';
160
- return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
161
- }
162
-
163
- protected compileOrderBy(ast: SelectQueryNode): string {
164
- if (!ast.orderBy || ast.orderBy.length === 0) return '';
165
- const parts = ast.orderBy
166
- .map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`)
167
- .join(', ');
168
- return ` ORDER BY ${parts}`;
169
- }
170
-
171
- /**
172
- * Default LIMIT/OFFSET pagination clause.
173
- */
174
- protected compilePagination(ast: SelectQueryNode, _orderByClause: string): string {
175
- const parts: string[] = [];
176
- if (ast.limit !== undefined) parts.push(`LIMIT ${ast.limit}`);
177
- if (ast.offset !== undefined) parts.push(`OFFSET ${ast.offset}`);
178
- return parts.length ? ` ${parts.join(' ')}` : '';
179
- }
180
-
181
- protected compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
182
- if (!ast.ctes || ast.ctes.length === 0) return '';
183
- const hasRecursive = ast.ctes.some(cte => cte.recursive);
184
- const prefix = hasRecursive ? 'WITH RECURSIVE ' : 'WITH ';
185
- const cteDefs = ast.ctes.map(cte => {
186
- const name = this.quoteIdentifier(cte.name);
187
- const cols = cte.columns && cte.columns.length
188
- ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})`
189
- : '';
190
- const query = this.stripTrailingSemicolon(this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx));
191
- return `${name}${cols} AS (${query})`;
192
- }).join(', ');
193
- return `${prefix}${cteDefs} `;
194
- }
195
-
196
- protected stripTrailingSemicolon(sql: string): string {
197
- return sql.trim().replace(/;$/, '');
198
- }
199
-
200
- protected wrapSetOperand(sql: string): string {
201
- const trimmed = this.stripTrailingSemicolon(sql);
202
- return `(${trimmed})`;
203
- }
204
- }
1
+ import { CompilerContext, Dialect } from '../abstract.js';
2
+ import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
3
+ import { ColumnNode } from '../../ast/expression.js';
4
+ import { FunctionTableFormatter, FunctionTableNode } from './function-table-formatter.js';
5
+ import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
6
+ import { CteCompiler } from './cte-compiler.js';
7
+ import { ReturningStrategy, NoReturningStrategy } from './returning-strategy.js';
8
+ import { JoinCompiler } from './join-compiler.js';
9
+ import { GroupByCompiler } from './groupby-compiler.js';
10
+ import { OrderByCompiler } from './orderby-compiler.js';
11
+
12
+
13
+ export abstract class SqlDialectBase extends Dialect {
14
+ abstract quoteIdentifier(id: string): string;
15
+
16
+ protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
17
+ protected returningStrategy: ReturningStrategy = new NoReturningStrategy();
18
+
19
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
20
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
21
+ const ctes = CteCompiler.compileCtes(
22
+ ast,
23
+ ctx,
24
+ this.quoteIdentifier.bind(this),
25
+ this.compileSelectAst.bind(this),
26
+ this.normalizeSelectAst?.bind(this) ?? ((a) => a),
27
+ this.stripTrailingSemicolon.bind(this)
28
+ );
29
+ const baseAst: SelectQueryNode = hasSetOps
30
+ ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
31
+ : ast;
32
+ const baseSelect = this.compileSelectCore(baseAst, ctx);
33
+ if (!hasSetOps) {
34
+ return `${ctes}${baseSelect}`;
35
+ }
36
+ return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
37
+ }
38
+
39
+ private compileSelectWithSetOps(
40
+ ast: SelectQueryNode,
41
+ baseSelect: string,
42
+ ctes: string,
43
+ ctx: CompilerContext
44
+ ): string {
45
+ const compound = ast.setOps!
46
+ .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
47
+ .join(' ');
48
+ const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
49
+ const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
50
+ const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
51
+ return `${ctes}${combined}${orderBy}${pagination}`;
52
+ }
53
+
54
+ protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
55
+ const table = this.compileTableName(ast.into);
56
+ const columnList = this.compileInsertColumnList(ast.columns);
57
+ const values = this.compileInsertValues(ast.values, ctx);
58
+ const returning = this.compileReturning(ast.returning, ctx);
59
+ return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
60
+ }
61
+
62
+ protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
63
+ return this.returningStrategy.compileReturning(returning, ctx);
64
+ }
65
+
66
+ private compileInsertColumnList(columns: ColumnNode[]): string {
67
+ return columns
68
+ .map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`)
69
+ .join(', ');
70
+ }
71
+
72
+ private compileInsertValues(values: any[][], ctx: CompilerContext): string {
73
+ return values
74
+ .map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
75
+ .join(', ');
76
+ }
77
+
78
+ private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
79
+ const columns = this.compileSelectColumns(ast, ctx);
80
+ const from = this.compileFrom(ast.from, ctx);
81
+ const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
82
+ const whereClause = this.compileWhere(ast.where, ctx);
83
+ const groupBy = GroupByCompiler.compileGroupBy(ast, this.quoteIdentifier.bind(this));
84
+ const having = this.compileHaving(ast, ctx);
85
+ const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
86
+ const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
87
+ return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
88
+ }
89
+
90
+ protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
91
+ const table = this.compileTableName(ast.table);
92
+ const assignments = this.compileUpdateAssignments(ast.set, ctx);
93
+ const whereClause = this.compileWhere(ast.where, ctx);
94
+ const returning = this.compileReturning(ast.returning, ctx);
95
+ return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
96
+ }
97
+
98
+ private compileUpdateAssignments(
99
+ assignments: { column: ColumnNode; value: any }[],
100
+ ctx: CompilerContext
101
+ ): string {
102
+ return assignments
103
+ .map(assignment => {
104
+ const col = assignment.column;
105
+ const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
106
+ const value = this.compileOperand(assignment.value, ctx);
107
+ return `${target} = ${value}`;
108
+ })
109
+ .join(', ');
110
+ }
111
+
112
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
113
+ const table = this.compileTableName(ast.from);
114
+ const whereClause = this.compileWhere(ast.where, ctx);
115
+ const returning = this.compileReturning(ast.returning, ctx);
116
+ return `DELETE FROM ${table}${whereClause}${returning}`;
117
+ }
118
+
119
+ protected formatReturningColumns(returning: ColumnNode[]): string {
120
+ return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
121
+ }
122
+
123
+ protected compileDistinct(ast: SelectQueryNode): string {
124
+ return ast.distinct ? 'DISTINCT ' : '';
125
+ }
126
+
127
+ protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
128
+ return ast.columns.map(c => {
129
+ const expr = this.compileOperand(c, ctx);
130
+ if (c.alias) {
131
+ if (c.alias.includes('(')) return c.alias;
132
+ return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
133
+ }
134
+ return expr;
135
+ }).join(', ');
136
+ }
137
+
138
+ protected compileFrom(ast: SelectQueryNode['from'], ctx?: CompilerContext): string {
139
+ const tableSource = ast as any;
140
+ if (tableSource.type === 'FunctionTable') {
141
+ return this.compileFunctionTable(tableSource, ctx);
142
+ }
143
+ return this.compileTableSource(tableSource);
144
+ }
145
+
146
+ protected compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
147
+ return FunctionTableFormatter.format(fn, ctx, this);
148
+ }
149
+
150
+ protected compileTableSource(table: TableSourceNode): string {
151
+ const base = this.compileTableName(table);
152
+ return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
153
+ }
154
+
155
+ protected compileTableName(table: { name: string; schema?: string }): string {
156
+ if (table.schema) {
157
+ return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
158
+ }
159
+ return this.quoteIdentifier(table.name);
160
+ }
161
+
162
+ protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
163
+ if (!ast.having) return '';
164
+ return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
165
+ }
166
+
167
+ protected stripTrailingSemicolon(sql: string): string {
168
+ return sql.trim().replace(/;$/, '');
169
+ }
170
+
171
+ protected wrapSetOperand(sql: string): string {
172
+ const trimmed = this.stripTrailingSemicolon(sql);
173
+ return `(${trimmed})`;
174
+ }
175
+ }
176
+
177
+ interface TableSourceNode {
178
+ name: string;
179
+ schema?: string;
180
+ alias?: string;
181
+ }
@@ -0,0 +1,91 @@
1
+ // Dialect factory for the SQL DSL.
2
+ // Centralizes how we go from a symbolic name ("sqlite") to a concrete Dialect instance.
3
+
4
+ import { Dialect } from './abstract.js';
5
+ import { PostgresDialect } from './postgres/index.js';
6
+ import { MySqlDialect } from './mysql/index.js';
7
+ import { SqliteDialect } from './sqlite/index.js';
8
+ import { SqlServerDialect } from './mssql/index.js';
9
+
10
+ export type DialectKey =
11
+ | 'postgres'
12
+ | 'mysql'
13
+ | 'sqlite'
14
+ | 'mssql'
15
+ | (string & {}); // allow user-defined keys without constraining too much
16
+
17
+ type DialectFactoryFn = () => Dialect;
18
+
19
+ export class DialectFactory {
20
+ private static registry = new Map<DialectKey, DialectFactoryFn>();
21
+ private static defaultsInitialized = false;
22
+
23
+ private static ensureDefaults(): void {
24
+ if (this.defaultsInitialized) return;
25
+ this.defaultsInitialized = true;
26
+
27
+ // Register built-in dialects only if no override exists yet.
28
+ if (!this.registry.has('postgres')) {
29
+ this.registry.set('postgres', () => new PostgresDialect());
30
+ }
31
+ if (!this.registry.has('mysql')) {
32
+ this.registry.set('mysql', () => new MySqlDialect());
33
+ }
34
+ if (!this.registry.has('sqlite')) {
35
+ this.registry.set('sqlite', () => new SqliteDialect());
36
+ }
37
+ if (!this.registry.has('mssql')) {
38
+ this.registry.set('mssql', () => new SqlServerDialect());
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Register (or override) a dialect factory for a key.
44
+ *
45
+ * Examples:
46
+ * DialectFactory.register('sqlite', () => new SqliteDialect());
47
+ * DialectFactory.register('my-tenant-dialect', () => new CustomDialect());
48
+ */
49
+ public static register(key: DialectKey, factory: DialectFactoryFn): void {
50
+ this.registry.set(key, factory);
51
+ }
52
+
53
+ /**
54
+ * Resolve a key into a Dialect instance.
55
+ * Throws if the key is not registered.
56
+ */
57
+ public static create(key: DialectKey): Dialect {
58
+ this.ensureDefaults();
59
+ const factory = this.registry.get(key);
60
+ if (!factory) {
61
+ throw new Error(
62
+ `Dialect "${String(
63
+ key
64
+ )}" is not registered. Use DialectFactory.register(...) to register it.`
65
+ );
66
+ }
67
+ return factory();
68
+ }
69
+
70
+ /**
71
+ * Clear all registrations (mainly for tests).
72
+ * Built-ins will be re-registered lazily on the next create().
73
+ */
74
+ public static clear(): void {
75
+ this.registry.clear();
76
+ this.defaultsInitialized = false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Helper to normalize either a Dialect instance OR a key into a Dialect instance.
82
+ * This is what query builders will use.
83
+ */
84
+ export const resolveDialectInput = (
85
+ dialect: Dialect | DialectKey
86
+ ): Dialect => {
87
+ if (typeof dialect === 'string') {
88
+ return DialectFactory.create(dialect);
89
+ }
90
+ return dialect;
91
+ };
@@ -0,0 +1,101 @@
1
+ import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
2
+ import { FunctionRenderContext } from '../../functions/types.js';
3
+ import { LiteralNode } from '../../ast/expression.js';
4
+
5
+ export class MssqlFunctionStrategy extends StandardFunctionStrategy {
6
+ constructor() {
7
+ super();
8
+ this.registerOverrides();
9
+ }
10
+
11
+ private registerOverrides() {
12
+ // Override Standard/Abstract definitions with MSSQL specifics
13
+
14
+ // Date/Time functions
15
+ this.add('NOW', () => `GETDATE()`);
16
+ this.add('CURRENT_DATE', () => `CAST(GETDATE() AS DATE)`);
17
+ this.add('CURRENT_TIME', () => `CAST(GETDATE() AS TIME)`);
18
+ this.add('UTC_NOW', () => `GETUTCDATE()`);
19
+
20
+ this.add('EXTRACT', ({ compiledArgs }) => {
21
+ if (compiledArgs.length !== 2) throw new Error('EXTRACT expects 2 arguments (part, date)');
22
+ const [part, date] = compiledArgs;
23
+ const partClean = part.replace(/['"]/g, '').toLowerCase();
24
+ return `DATEPART(${partClean}, ${date})`;
25
+ });
26
+
27
+ this.add('YEAR', ({ compiledArgs }) => {
28
+ if (compiledArgs.length !== 1) throw new Error('YEAR expects 1 argument');
29
+ return `YEAR(${compiledArgs[0]})`;
30
+ });
31
+
32
+ this.add('MONTH', ({ compiledArgs }) => {
33
+ if (compiledArgs.length !== 1) throw new Error('MONTH expects 1 argument');
34
+ return `MONTH(${compiledArgs[0]})`;
35
+ });
36
+
37
+ this.add('DAY', ({ compiledArgs }) => {
38
+ if (compiledArgs.length !== 1) throw new Error('DAY expects 1 argument');
39
+ return `DAY(${compiledArgs[0]})`;
40
+ });
41
+
42
+ this.add('DATE_ADD', ({ node, compiledArgs }) => {
43
+ if (compiledArgs.length !== 3) throw new Error('DATE_ADD expects 3 arguments (date, interval, unit)');
44
+ const [date, interval] = compiledArgs;
45
+ const unitArg = node.args[2] as LiteralNode;
46
+ const unitClean = String(unitArg.value).replace(/['"]/g, '').toLowerCase();
47
+ return `DATEADD(${unitClean}, ${interval}, ${date})`;
48
+ });
49
+
50
+ this.add('DATE_SUB', ({ node, compiledArgs }) => {
51
+ if (compiledArgs.length !== 3) throw new Error('DATE_SUB expects 3 arguments (date, interval, unit)');
52
+ const [date, interval] = compiledArgs;
53
+ const unitArg = node.args[2] as LiteralNode;
54
+ const unitClean = String(unitArg.value).replace(/['"]/g, '').toLowerCase();
55
+ return `DATEADD(${unitClean}, -${interval}, ${date})`;
56
+ });
57
+
58
+ this.add('DATE_DIFF', ({ compiledArgs }) => {
59
+ if (compiledArgs.length !== 2) throw new Error('DATE_DIFF expects 2 arguments');
60
+ const [date1, date2] = compiledArgs;
61
+ return `DATEDIFF(day, ${date2}, ${date1})`;
62
+ });
63
+
64
+ this.add('DATE_FORMAT', ({ compiledArgs }) => {
65
+ if (compiledArgs.length !== 2) throw new Error('DATE_FORMAT expects 2 arguments');
66
+ const [date, format] = compiledArgs;
67
+ return `FORMAT(${date}, ${format})`;
68
+ });
69
+
70
+ this.add('UNIX_TIMESTAMP', () => `DATEDIFF(SECOND, '1970-01-01', GETUTCDATE())`);
71
+
72
+ this.add('FROM_UNIXTIME', ({ compiledArgs }) => {
73
+ if (compiledArgs.length !== 1) throw new Error('FROM_UNIXTIME expects 1 argument');
74
+ return `DATEADD(SECOND, ${compiledArgs[0]}, '1970-01-01')`;
75
+ });
76
+
77
+ this.add('END_OF_MONTH', ({ compiledArgs }) => {
78
+ if (compiledArgs.length !== 1) throw new Error('END_OF_MONTH expects 1 argument');
79
+ return `EOMONTH(${compiledArgs[0]})`;
80
+ });
81
+
82
+ this.add('DAY_OF_WEEK', ({ compiledArgs }) => {
83
+ if (compiledArgs.length !== 1) throw new Error('DAY_OF_WEEK expects 1 argument');
84
+ return `DATEPART(dw, ${compiledArgs[0]})`;
85
+ });
86
+
87
+ this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
88
+ if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
89
+ return `DATEPART(wk, ${compiledArgs[0]})`;
90
+ });
91
+
92
+ this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
93
+ if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
94
+ const [, date] = compiledArgs;
95
+ const partArg = node.args[0] as LiteralNode;
96
+ const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
97
+ // SQL Server 2022+ has DATETRUNC
98
+ return `DATETRUNC(${partClean}, ${date})`;
99
+ });
100
+ }
101
+ }