metal-orm 1.1.23 → 1.1.25

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 (37) hide show
  1. package/dist/index.cjs +832 -583
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +281 -144
  4. package/dist/index.d.ts +281 -144
  5. package/dist/index.js +807 -583
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/core/dialect/base/returning-strategy.ts +40 -39
  9. package/src/core/dialect/base/sql-compiler-set.ts +33 -0
  10. package/src/core/dialect/base/sql-dialect.ts +125 -222
  11. package/src/core/dialect/base/standard-delete-compiler.ts +37 -0
  12. package/src/core/dialect/base/standard-insert-compiler.ts +49 -0
  13. package/src/core/dialect/base/standard-select-compiler.ts +82 -0
  14. package/src/core/dialect/base/standard-sql-services.ts +44 -0
  15. package/src/core/dialect/base/standard-sql-source-compiler.ts +88 -0
  16. package/src/core/dialect/base/standard-update-compiler.ts +53 -0
  17. package/src/core/dialect/base/upsert-strategy.ts +45 -0
  18. package/src/core/dialect/capabilities/procedure-compiler.ts +10 -8
  19. package/src/core/dialect/mssql/compiler-factory.ts +12 -0
  20. package/src/core/dialect/mssql/delete-compiler.ts +40 -0
  21. package/src/core/dialect/mssql/index.ts +24 -371
  22. package/src/core/dialect/mssql/insert-compiler.ts +112 -0
  23. package/src/core/dialect/mssql/output.ts +46 -0
  24. package/src/core/dialect/mssql/procedure-compiler.ts +81 -0
  25. package/src/core/dialect/mssql/select-compiler.ts +116 -0
  26. package/src/core/dialect/mssql/update-compiler.ts +37 -0
  27. package/src/core/dialect/mysql/index.ts +24 -117
  28. package/src/core/dialect/mysql/procedure-compiler.ts +67 -0
  29. package/src/core/dialect/mysql/upsert.ts +42 -0
  30. package/src/core/dialect/postgres/index.ts +34 -101
  31. package/src/core/dialect/postgres/procedure-compiler.ts +41 -0
  32. package/src/core/dialect/postgres/returning.ts +4 -0
  33. package/src/core/dialect/postgres/upsert.ts +43 -0
  34. package/src/core/dialect/sqlite/index.ts +15 -70
  35. package/src/core/dialect/sqlite/returning.ts +30 -0
  36. package/src/core/dialect/sqlite/upsert.ts +43 -0
  37. package/src/index.ts +28 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,51 +1,39 @@
1
- import { ColumnNode } from '../../ast/expression.js';
2
- import { CompilerContext } from '../abstract.js';
1
+ import type { ColumnNode } from '../../ast/expression.js';
2
+ import type { CompilerContext } from '../abstract.js';
3
3
 
4
- /**
5
- * Strategy interface for handling RETURNING clauses in DML statements (INSERT, UPDATE, DELETE).
6
- * Different SQL dialects have varying levels of support for RETURNING clauses.
7
- */
4
+ export type QuoteIdentifier = (id: string) => string;
5
+
6
+ /** Backend-specific RETURNING/OUTPUT rendering strategy. */
8
7
  export interface ReturningStrategy {
9
- /**
10
- * Compiles a RETURNING clause for DML statements.
11
- * @param returning - Array of columns to return, or undefined if none.
12
- * @param ctx - The compiler context for expression compilation.
13
- * @returns SQL RETURNING clause or empty string if not supported.
14
- * @throws Error if RETURNING is not supported by this dialect.
15
- */
16
- compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string;
17
- /**
18
- * Formats column list for RETURNING clause.
19
- * @param returning - Array of columns to format.
20
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
21
- * @returns Formatted column list (e.g., "table.col1, table.col2").
22
- */
23
- formatReturningColumns(returning: ColumnNode[], quoteIdentifier: (id: string) => string): string;
8
+ compileReturning(
9
+ returning: ColumnNode[] | undefined,
10
+ ctx: CompilerContext,
11
+ quoteIdentifier: QuoteIdentifier
12
+ ): string;
13
+
14
+ formatReturningColumns(
15
+ returning: ColumnNode[],
16
+ quoteIdentifier: QuoteIdentifier
17
+ ): string;
24
18
  }
25
19
 
26
- /**
27
- * Default RETURNING strategy that throws an error when RETURNING is used.
28
- * Use this for dialects that don't support RETURNING clauses.
29
- */
20
+ /** Default RETURNING strategy for dialects without support. */
30
21
  export class NoReturningStrategy implements ReturningStrategy {
31
- /**
32
- * Throws an error as RETURNING is not supported.
33
- * @param returning - Columns to return (causes error if non-empty).
34
- * @param _ctx - Compiler context (unused).
35
- * @throws Error indicating RETURNING is not supported.
36
- */
37
- compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
22
+ compileReturning(
23
+ returning: ColumnNode[] | undefined,
24
+ _ctx: CompilerContext,
25
+ _quoteIdentifier: QuoteIdentifier
26
+ ): string {
38
27
  void _ctx;
28
+ void _quoteIdentifier;
39
29
  if (!returning || returning.length === 0) return '';
40
30
  throw new Error('RETURNING is not supported by this dialect.');
41
31
  }
42
- /**
43
- * Formats column names for RETURNING clause.
44
- * @param returning - Columns to format.
45
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
46
- * @returns Simple comma-separated column names.
47
- */
48
- formatReturningColumns(returning: ColumnNode[], quoteIdentifier: (id: string) => string): string {
32
+
33
+ formatReturningColumns(
34
+ returning: ColumnNode[],
35
+ quoteIdentifier: QuoteIdentifier
36
+ ): string {
49
37
  return returning
50
38
  .map(column => {
51
39
  const tablePart = column.table ? `${quoteIdentifier(column.table)}.` : '';
@@ -55,3 +43,16 @@ export class NoReturningStrategy implements ReturningStrategy {
55
43
  .join(', ');
56
44
  }
57
45
  }
46
+
47
+ /** Standard SQL RETURNING implementation with qualified column support. */
48
+ export class StandardReturningStrategy extends NoReturningStrategy {
49
+ override compileReturning(
50
+ returning: ColumnNode[] | undefined,
51
+ _ctx: CompilerContext,
52
+ quoteIdentifier: QuoteIdentifier
53
+ ): string {
54
+ void _ctx;
55
+ if (!returning || returning.length === 0) return '';
56
+ return ` RETURNING ${this.formatReturningColumns(returning, quoteIdentifier)}`;
57
+ }
58
+ }
@@ -0,0 +1,33 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type {
3
+ DeleteQueryNode,
4
+ InsertQueryNode,
5
+ SelectQueryNode,
6
+ UpdateQueryNode
7
+ } from '../../ast/query.js';
8
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
9
+ import type { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
10
+
11
+ export interface SqlAstCompiler<TAst> {
12
+ compile(ast: TAst, ctx: CompilerContext): string;
13
+ }
14
+
15
+ export interface SqlCompilerSet {
16
+ select: SqlAstCompiler<SelectQueryNode>;
17
+ insert: SqlAstCompiler<InsertQueryNode>;
18
+ update: SqlAstCompiler<UpdateQueryNode>;
19
+ delete: SqlAstCompiler<DeleteQueryNode>;
20
+ }
21
+
22
+ export interface SqlCompilerAssemblyContext {
23
+ services: StandardSqlCompilerServices;
24
+ sources: StandardSqlSourceCompiler;
25
+ }
26
+
27
+ /**
28
+ * Allows a backend to replace only the standard query compilers whose SQL
29
+ * grammar genuinely differs from the common implementation.
30
+ */
31
+ export type SqlCompilerFactory = (
32
+ context: SqlCompilerAssemblyContext
33
+ ) => Partial<SqlCompilerSet>;
@@ -1,166 +1,157 @@
1
1
  import { DialectBase } from '../abstract.js';
2
2
  import type { CompilerContext } from '../abstract.js';
3
3
  import type {
4
- SelectQueryNode,
5
- InsertQueryNode,
6
- UpdateQueryNode,
7
4
  DeleteQueryNode,
8
- InsertSourceNode,
9
- UpsertClause,
10
- TableSourceNode,
11
5
  DerivedTableNode,
12
6
  FunctionTableNode,
7
+ InsertQueryNode,
13
8
  OrderByNode,
14
- TableNode
9
+ SelectQueryNode,
10
+ TableNode,
11
+ TableSourceNode,
12
+ UpdateAssignmentNode,
13
+ UpdateQueryNode,
14
+ UpsertClause
15
15
  } from '../../ast/query.js';
16
- import type { ColumnNode, OperandNode } from '../../ast/expression.js';
17
- import { FunctionTableFormatter } from './function-table-formatter.js';
16
+ import type { ColumnNode } from '../../ast/expression.js';
17
+ import type { FunctionStrategy } from '../../functions/types.js';
18
+ import type { TableFunctionStrategy } from '../../functions/table-types.js';
18
19
  import { StandardLimitOffsetPagination } from './pagination-strategy.js';
19
20
  import type { PaginationStrategy } from './pagination-strategy.js';
20
- import { CteCompiler } from './cte-compiler.js';
21
21
  import { NoReturningStrategy } from './returning-strategy.js';
22
22
  import type { ReturningStrategy } from './returning-strategy.js';
23
- import { JoinCompiler } from './join-compiler.js';
24
- import { GroupByCompiler } from './groupby-compiler.js';
25
- import { OrderByCompiler } from './orderby-compiler.js';
23
+ import { NoUpsertStrategy } from './upsert-strategy.js';
24
+ import type { UpsertStrategy } from './upsert-strategy.js';
25
+ import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
26
+ import { StandardSelectCompiler } from './standard-select-compiler.js';
27
+ import { StandardInsertCompiler } from './standard-insert-compiler.js';
28
+ import { StandardUpdateCompiler } from './standard-update-compiler.js';
29
+ import { StandardDeleteCompiler } from './standard-delete-compiler.js';
30
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
31
+ import type { SqlCompilerFactory, SqlCompilerSet } from './sql-compiler-set.js';
32
+
33
+ export interface SqlDialectBaseOptions {
34
+ functionStrategy?: FunctionStrategy;
35
+ tableFunctionStrategy?: TableFunctionStrategy;
36
+ paginationStrategy?: PaginationStrategy;
37
+ returningStrategy?: ReturningStrategy;
38
+ upsertStrategy?: UpsertStrategy;
39
+ compilerFactory?: SqlCompilerFactory;
40
+ supportsDmlReturning?: boolean;
41
+ }
26
42
 
27
43
  /**
28
- * Reusable SQL implementation built on the structural Dialect contract.
29
- * Dialects extend this only when its standard SELECT/DML behavior is useful.
44
+ * Thin assembly base for dialects that use MetalORM's reusable SQL compiler pieces.
45
+ *
46
+ * Query orchestration, source rendering, upsert behavior and returning behavior are
47
+ * injected components. Concrete dialects keep only syntax hooks that are genuinely
48
+ * intrinsic to that backend.
30
49
  */
31
50
  export abstract class SqlDialectBase extends DialectBase {
32
51
  abstract quoteIdentifier(id: string): string;
33
52
 
34
- protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
35
- protected returningStrategy: ReturningStrategy = new NoReturningStrategy();
53
+ protected readonly paginationStrategy: PaginationStrategy;
54
+ protected readonly returningStrategy: ReturningStrategy;
55
+ protected readonly upsertStrategy: UpsertStrategy;
36
56
 
37
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
38
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
39
- const ctes = CteCompiler.compileCtes(
40
- ast,
41
- ctx,
42
- this.quoteIdentifier.bind(this),
43
- this.compileSelectAst.bind(this),
44
- this.normalizeSelectAst.bind(this),
45
- this.stripTrailingSemicolon.bind(this)
46
- );
47
- const baseAst: SelectQueryNode = hasSetOps
48
- ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
49
- : ast;
50
- const baseSelect = this.compileSelectCore(baseAst, ctx);
51
- if (!hasSetOps) return `${ctes}${baseSelect}`;
52
- return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
53
- }
57
+ private readonly dmlReturningSupported: boolean;
58
+ private readonly sourceCompiler: StandardSqlSourceCompiler;
59
+ private readonly standardUpdateCompiler: StandardUpdateCompiler;
60
+ private readonly compilerSet: SqlCompilerSet;
54
61
 
55
- private compileSelectWithSetOps(
56
- ast: SelectQueryNode,
57
- baseSelect: string,
58
- ctes: string,
59
- ctx: CompilerContext
60
- ): string {
61
- const compound = ast.setOps!
62
- .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
63
- .join(' ');
64
- const orderBy = OrderByCompiler.compileOrderBy(
65
- ast,
66
- term => this.compileOrderingTerm(term, ctx),
67
- this.renderOrderByNulls.bind(this),
68
- this.renderOrderByCollation.bind(this)
69
- );
70
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
71
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
72
- return `${ctes}${combined}${orderBy}${pagination}`;
73
- }
62
+ protected constructor(options: SqlDialectBaseOptions = {}) {
63
+ super(options.functionStrategy, options.tableFunctionStrategy);
74
64
 
75
- protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
76
- if (!ast.columns.length) {
77
- throw new Error('INSERT queries must specify columns.');
78
- }
79
-
80
- const table = this.compileTableName(ast.into);
81
- const columnList = this.compileInsertColumnList(ast.columns);
82
- const source = this.compileInsertSource(ast.source, ctx);
83
- const upsert = this.compileUpsertClause(ast, ctx);
84
- const returning = this.compileReturning(ast.returning, ctx);
85
- return `INSERT INTO ${table} (${columnList}) ${source}${upsert}${returning}`;
65
+ this.paginationStrategy = options.paginationStrategy ?? new StandardLimitOffsetPagination();
66
+ this.returningStrategy = options.returningStrategy ?? new NoReturningStrategy();
67
+ this.upsertStrategy = options.upsertStrategy ?? new NoUpsertStrategy();
68
+ this.dmlReturningSupported = options.supportsDmlReturning ?? false;
69
+
70
+ const services: StandardSqlCompilerServices = {
71
+ getDialectName: () => this.dialect,
72
+ getPaginationStrategy: () => this.paginationStrategy,
73
+ getTableFunctionStrategy: () => this.tableFunctionStrategy,
74
+ quoteIdentifier: id => this.quoteIdentifier(id),
75
+ compileOperand: (node, ctx) => this.compileOperand(node, ctx),
76
+ compileExpression: (node, ctx) => this.compileExpression(node, ctx),
77
+ compileOrderingTerm: (term, ctx) => this.compileOrderingTerm(term, ctx),
78
+ normalizeSelectAst: ast => this.normalizeSelectAst(ast),
79
+ compileSelectAst: (ast, ctx) => this.compileSelectAst(ast, ctx),
80
+ compileReturning: (returning, ctx) => this.compileReturning(returning, ctx),
81
+ compileUpsertClause: (ast, ctx) => this.compileUpsertClause(ast, ctx),
82
+ compileSetTarget: (column, table) => this.compileSetTarget(column, table),
83
+ renderOrderByNulls: order => this.renderOrderByNulls(order),
84
+ renderOrderByCollation: order => this.renderOrderByCollation(order)
85
+ };
86
+
87
+ this.sourceCompiler = new StandardSqlSourceCompiler(services);
88
+ const standardSelect = new StandardSelectCompiler(services, this.sourceCompiler);
89
+ const standardInsert = new StandardInsertCompiler(services, this.sourceCompiler);
90
+ this.standardUpdateCompiler = new StandardUpdateCompiler(services, this.sourceCompiler);
91
+ const standardDelete = new StandardDeleteCompiler(services, this.sourceCompiler);
92
+
93
+ const overrides = options.compilerFactory?.({
94
+ services,
95
+ sources: this.sourceCompiler
96
+ }) ?? {};
97
+
98
+ this.compilerSet = {
99
+ select: overrides.select ?? standardSelect,
100
+ insert: overrides.insert ?? standardInsert,
101
+ update: overrides.update ?? this.standardUpdateCompiler,
102
+ delete: overrides.delete ?? standardDelete
103
+ };
104
+ }
105
+
106
+ override supportsDmlReturningClause(): boolean {
107
+ return this.dmlReturningSupported;
86
108
  }
87
109
 
88
- protected compileUpsertClause(ast: InsertQueryNode, _ctx: CompilerContext): string {
89
- void _ctx;
90
- if (!ast.onConflict) return '';
91
- throw new Error(`UPSERT/ON CONFLICT is not supported by dialect "${this.dialect}".`);
110
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
111
+ return this.compilerSet.select.compile(ast, ctx);
92
112
  }
93
113
 
94
- protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
95
- return this.returningStrategy.compileReturning(returning, ctx);
114
+ protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
115
+ return this.compilerSet.insert.compile(ast, ctx);
96
116
  }
97
117
 
98
- protected compileInsertSource(source: InsertSourceNode, ctx: CompilerContext): string {
99
- if (source.type === 'InsertValues') {
100
- if (!source.rows.length) {
101
- throw new Error('INSERT ... VALUES requires at least one row.');
102
- }
103
- const values = source.rows
104
- .map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
105
- .join(', ');
106
- return `VALUES ${values}`;
107
- }
108
-
109
- const normalized = this.normalizeSelectAst(source.query);
110
- return this.compileSelectAst(normalized, ctx).trim();
118
+ protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
119
+ return this.compilerSet.update.compile(ast, ctx);
111
120
  }
112
121
 
113
- protected compileInsertColumnList(columns: ColumnNode[]): string {
114
- return columns.map(column => this.quoteIdentifier(column.name)).join(', ');
122
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
123
+ return this.compilerSet.delete.compile(ast, ctx);
115
124
  }
116
125
 
117
- protected ensureConflictColumns(clause: UpsertClause, message: string): void {
118
- if (!clause.target.columns.length) throw new Error(message);
126
+ protected compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string {
127
+ return this.upsertStrategy.compile(ast, ctx, {
128
+ getDialectName: () => this.dialect,
129
+ quoteIdentifier: id => this.quoteIdentifier(id),
130
+ compileOperand: (node, compilerContext) => this.compileOperand(node, compilerContext),
131
+ compileExpression: (node, compilerContext) => this.compileExpression(node, compilerContext),
132
+ compileUpdateAssignments: (assignments, table, compilerContext) =>
133
+ this.standardUpdateCompiler.compileAssignments(assignments, table, compilerContext)
134
+ });
119
135
  }
120
136
 
121
- private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
122
- const columns = this.compileSelectColumns(ast, ctx);
123
- const from = this.compileFrom(ast.from, ctx);
124
- const joins = JoinCompiler.compileJoins(
125
- ast.joins,
137
+ protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
138
+ return this.returningStrategy.compileReturning(
139
+ returning,
126
140
  ctx,
127
- this.compileFrom.bind(this),
128
- this.compileExpression.bind(this)
129
- );
130
- const whereClause = this.compileWhere(ast.where, ctx);
131
- const groupBy = GroupByCompiler.compileGroupBy(ast, term => this.compileOrderingTerm(term, ctx));
132
- const having = this.compileHaving(ast, ctx);
133
- const orderBy = OrderByCompiler.compileOrderBy(
134
- ast,
135
- term => this.compileOrderingTerm(term, ctx),
136
- this.renderOrderByNulls.bind(this),
137
- this.renderOrderByCollation.bind(this)
141
+ id => this.quoteIdentifier(id)
138
142
  );
139
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
140
- return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
141
143
  }
142
144
 
143
- protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
144
- const target = this.compileTableReference(ast.table);
145
- const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
146
- const fromClause = this.compileUpdateFromClause(ast, ctx);
147
- const whereClause = this.compileWhere(ast.where, ctx);
148
- const returning = this.compileReturning(ast.returning, ctx);
149
- return `UPDATE ${target} SET ${assignments}${fromClause}${whereClause}${returning}`;
145
+ protected ensureConflictColumns(clause: UpsertClause, message: string): void {
146
+ if (!clause.target.columns.length) throw new Error(message);
150
147
  }
151
148
 
152
149
  protected compileUpdateAssignments(
153
- assignments: { column: ColumnNode; value: OperandNode }[],
150
+ assignments: UpdateAssignmentNode[],
154
151
  table: TableNode,
155
152
  ctx: CompilerContext
156
153
  ): string {
157
- return assignments
158
- .map(assignment => {
159
- const target = this.compileSetTarget(assignment.column, table);
160
- const value = this.compileOperand(assignment.value, ctx);
161
- return `${target} = ${value}`;
162
- })
163
- .join(', ');
154
+ return this.standardUpdateCompiler.compileAssignments(assignments, table, ctx);
164
155
  }
165
156
 
166
157
  protected compileSetTarget(column: ColumnNode, table: TableNode): string {
@@ -177,131 +168,43 @@ export abstract class SqlDialectBase extends DialectBase {
177
168
  return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
178
169
  }
179
170
 
180
- protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
181
- const target = this.compileTableReference(ast.from);
182
- const usingClause = this.compileDeleteUsingClause(ast, ctx);
183
- const whereClause = this.compileWhere(ast.where, ctx);
184
- const returning = this.compileReturning(ast.returning, ctx);
185
- return `DELETE FROM ${target}${usingClause}${whereClause}${returning}`;
186
- }
187
-
188
171
  protected formatReturningColumns(returning: ColumnNode[]): string {
189
- return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
190
- }
191
-
192
- protected compileDistinct(ast: SelectQueryNode): string {
193
- return ast.distinct ? 'DISTINCT ' : '';
194
- }
195
-
196
- protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
197
- if (!ast.columns || ast.columns.length === 0) return '*';
198
- return ast.columns.map(column => {
199
- const expr = this.compileOperand(column, ctx);
200
- if (column.alias) {
201
- if (column.alias.includes('(')) return column.alias;
202
- return `${expr} AS ${this.quoteIdentifier(column.alias)}`;
203
- }
204
- return expr;
205
- }).join(', ');
172
+ return this.returningStrategy.formatReturningColumns(
173
+ returning,
174
+ id => this.quoteIdentifier(id)
175
+ );
206
176
  }
207
177
 
208
- protected compileFrom(ast: SelectQueryNode['from'], ctx?: CompilerContext): string {
209
- if (ast.type === 'FunctionTable') return this.compileFunctionTable(ast, ctx);
210
- if (ast.type === 'DerivedTable') return this.compileDerivedTable(ast, ctx);
211
- return this.compileTableSource(ast);
178
+ protected compileFrom(source: TableSourceNode, ctx?: CompilerContext): string {
179
+ return this.sourceCompiler.compileFrom(source, ctx);
212
180
  }
213
181
 
214
182
  protected compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
215
- const key = fn.key ?? fn.name;
216
-
217
- if (ctx) {
218
- const renderer = this.tableFunctionStrategy.getRenderer(key);
219
- if (renderer) {
220
- const compiledArgs = (fn.args ?? []).map(arg => this.compileOperand(arg, ctx));
221
- return renderer({
222
- node: fn,
223
- compiledArgs,
224
- compileOperand: operand => this.compileOperand(operand, ctx),
225
- quoteIdentifier: this.quoteIdentifier.bind(this)
226
- });
227
- }
228
-
229
- if (fn.key) {
230
- throw new Error(`Table function "${key}" is not supported by dialect "${this.dialect}".`);
231
- }
232
- }
233
-
234
- return FunctionTableFormatter.format(fn, ctx, {
235
- quoteIdentifier: id => this.quoteIdentifier(id),
236
- compileOperand: (node, compilerContext) => this.compileOperand(node, compilerContext)
237
- });
183
+ return this.sourceCompiler.compileFunctionTable(fn, ctx);
238
184
  }
239
185
 
240
186
  protected compileDerivedTable(table: DerivedTableNode, ctx?: CompilerContext): string {
241
- if (!table.alias) throw new Error('Derived tables must have an alias.');
242
- const subquery = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx!).trim().replace(/;$/, '');
243
- const columns = table.columnAliases?.length
244
- ? ` (${table.columnAliases.map(c => this.quoteIdentifier(c)).join(', ')})`
245
- : '';
246
- return `(${subquery}) AS ${this.quoteIdentifier(table.alias)}${columns}`;
187
+ return this.sourceCompiler.compileDerivedTable(table, ctx);
247
188
  }
248
189
 
249
190
  protected compileTableSource(table: TableSourceNode): string {
250
- if (table.type === 'FunctionTable') return this.compileFunctionTable(table as FunctionTableNode);
251
- if (table.type === 'DerivedTable') return this.compileDerivedTable(table as DerivedTableNode);
252
- const base = this.compileTableName(table);
253
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
191
+ return this.sourceCompiler.compileTableSource(table);
254
192
  }
255
193
 
256
- protected compileTableName(table: { name: string; schema?: string; alias?: string }): string {
257
- if (table.schema) {
258
- return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
259
- }
260
- return this.quoteIdentifier(table.name);
194
+ protected compileTableName(table: { name: string; schema?: string }): string {
195
+ return this.sourceCompiler.compileTableName(table);
261
196
  }
262
197
 
263
198
  protected compileTableReference(table: { name: string; schema?: string; alias?: string }): string {
264
- const base = this.compileTableName(table);
265
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
266
- }
267
-
268
- private compileUpdateFromClause(ast: UpdateQueryNode, ctx: CompilerContext): string {
269
- if (!ast.from && (!ast.joins || ast.joins.length === 0)) return '';
270
- if (!ast.from) throw new Error('UPDATE with JOINs requires an explicit FROM clause.');
271
- const from = this.compileFrom(ast.from, ctx);
272
- const joins = JoinCompiler.compileJoins(
273
- ast.joins,
274
- ctx,
275
- this.compileFrom.bind(this),
276
- this.compileExpression.bind(this)
277
- );
278
- return ` FROM ${from}${joins}`;
279
- }
280
-
281
- private compileDeleteUsingClause(ast: DeleteQueryNode, ctx: CompilerContext): string {
282
- if (!ast.using && (!ast.joins || ast.joins.length === 0)) return '';
283
- if (!ast.using) throw new Error('DELETE with JOINs requires a USING clause.');
284
- const usingTable = this.compileFrom(ast.using, ctx);
285
- const joins = JoinCompiler.compileJoins(
286
- ast.joins,
287
- ctx,
288
- this.compileFrom.bind(this),
289
- this.compileExpression.bind(this)
290
- );
291
- return ` USING ${usingTable}${joins}`;
292
- }
293
-
294
- protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
295
- if (!ast.having) return '';
296
- return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
199
+ return this.sourceCompiler.compileTableReference(table);
297
200
  }
298
201
 
299
202
  protected stripTrailingSemicolon(sql: string): string {
300
- return sql.trim().replace(/;$/, '');
203
+ return this.sourceCompiler.stripTrailingSemicolon(sql);
301
204
  }
302
205
 
303
206
  protected wrapSetOperand(sql: string): string {
304
- return `(${this.stripTrailingSemicolon(sql)})`;
207
+ return this.sourceCompiler.wrapSetOperand(sql);
305
208
  }
306
209
 
307
210
  protected renderOrderByNulls(order: OrderByNode): string | undefined {
@@ -0,0 +1,37 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { DeleteQueryNode } from '../../ast/query.js';
3
+ import { JoinCompiler } from './join-compiler.js';
4
+ import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
5
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
6
+
7
+ /** Standard DELETE orchestration, independent from concrete dialect classes. */
8
+ export class StandardDeleteCompiler {
9
+ public constructor(
10
+ private readonly services: StandardSqlCompilerServices,
11
+ private readonly sources: StandardSqlSourceCompiler
12
+ ) {}
13
+
14
+ compile(ast: DeleteQueryNode, ctx: CompilerContext): string {
15
+ const target = this.sources.compileTableReference(ast.from);
16
+ const using = this.compileUsingClause(ast, ctx);
17
+ const where = ast.where ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}` : '';
18
+ const returning = this.services.compileReturning(ast.returning, ctx);
19
+ return `DELETE FROM ${target}${using}${where}${returning}`;
20
+ }
21
+
22
+ private compileUsingClause(ast: DeleteQueryNode, ctx: CompilerContext): string {
23
+ if (!ast.using && (!ast.joins || ast.joins.length === 0)) return '';
24
+ if (!ast.using) {
25
+ throw new Error('DELETE with JOINs requires a USING clause.');
26
+ }
27
+
28
+ const usingTable = this.sources.compileFrom(ast.using, ctx);
29
+ const joins = JoinCompiler.compileJoins(
30
+ ast.joins,
31
+ ctx,
32
+ (source, compilerContext) => this.sources.compileFrom(source, compilerContext),
33
+ (expression, compilerContext) => this.services.compileExpression(expression, compilerContext)
34
+ );
35
+ return ` USING ${usingTable}${joins}`;
36
+ }
37
+ }
@@ -0,0 +1,49 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { InsertQueryNode, InsertSourceNode, UpsertClause } from '../../ast/query.js';
3
+ import type { ColumnNode } from '../../ast/expression.js';
4
+ import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
5
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
6
+
7
+ /** Standard INSERT orchestration, including VALUES/SELECT sources and upsert hook. */
8
+ export class StandardInsertCompiler {
9
+ public constructor(
10
+ private readonly services: StandardSqlCompilerServices,
11
+ private readonly sources: StandardSqlSourceCompiler
12
+ ) {}
13
+
14
+ compile(ast: InsertQueryNode, ctx: CompilerContext): string {
15
+ if (!ast.columns.length) {
16
+ throw new Error('INSERT queries must specify columns.');
17
+ }
18
+
19
+ const table = this.sources.compileTableName(ast.into);
20
+ const columnList = this.compileColumnList(ast.columns);
21
+ const source = this.compileSource(ast.source, ctx);
22
+ const upsert = this.services.compileUpsertClause(ast, ctx);
23
+ const returning = this.services.compileReturning(ast.returning, ctx);
24
+ return `INSERT INTO ${table} (${columnList}) ${source}${upsert}${returning}`;
25
+ }
26
+
27
+ compileSource(source: InsertSourceNode, ctx: CompilerContext): string {
28
+ if (source.type === 'InsertValues') {
29
+ if (!source.rows.length) {
30
+ throw new Error('INSERT ... VALUES requires at least one row.');
31
+ }
32
+ const values = source.rows
33
+ .map(row => `(${row.map(value => this.services.compileOperand(value, ctx)).join(', ')})`)
34
+ .join(', ');
35
+ return `VALUES ${values}`;
36
+ }
37
+
38
+ const normalized = this.services.normalizeSelectAst(source.query);
39
+ return this.services.compileSelectAst(normalized, ctx).trim();
40
+ }
41
+
42
+ compileColumnList(columns: ColumnNode[]): string {
43
+ return columns.map(column => this.services.quoteIdentifier(column.name)).join(', ');
44
+ }
45
+
46
+ ensureConflictColumns(clause: UpsertClause, message: string): void {
47
+ if (!clause.target.columns.length) throw new Error(message);
48
+ }
49
+ }