metal-orm 1.1.22 → 1.1.24

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.
@@ -1,10 +1,10 @@
1
- import { CompilerContext, Dialect } from '../abstract.js';
2
- import {
1
+ import { DialectBase } from '../abstract.js';
2
+ import type { CompilerContext } from '../abstract.js';
3
+ import type {
3
4
  SelectQueryNode,
4
5
  InsertQueryNode,
5
6
  UpdateQueryNode,
6
7
  DeleteQueryNode,
7
- InsertSourceNode,
8
8
  UpsertClause,
9
9
  TableSourceNode,
10
10
  DerivedTableNode,
@@ -12,78 +12,83 @@ import {
12
12
  OrderByNode,
13
13
  TableNode
14
14
  } from '../../ast/query.js';
15
- import { ColumnNode, OperandNode } from '../../ast/expression.js';
16
- import { FunctionTableFormatter } from './function-table-formatter.js';
17
- import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
18
- import { CteCompiler } from './cte-compiler.js';
19
- import { ReturningStrategy, NoReturningStrategy } from './returning-strategy.js';
20
- import { JoinCompiler } from './join-compiler.js';
21
- import { GroupByCompiler } from './groupby-compiler.js';
22
- import { OrderByCompiler } from './orderby-compiler.js';
23
-
15
+ import type { ColumnNode, OperandNode } from '../../ast/expression.js';
16
+ import type { FunctionStrategy } from '../../functions/types.js';
17
+ import type { TableFunctionStrategy } from '../../functions/table-types.js';
18
+ import { StandardLimitOffsetPagination } from './pagination-strategy.js';
19
+ import type { PaginationStrategy } from './pagination-strategy.js';
20
+ import { NoReturningStrategy } from './returning-strategy.js';
21
+ import type { ReturningStrategy } from './returning-strategy.js';
22
+ import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
23
+ import { StandardSelectCompiler } from './standard-select-compiler.js';
24
+ import { StandardInsertCompiler } from './standard-insert-compiler.js';
25
+ import { StandardUpdateCompiler } from './standard-update-compiler.js';
26
+ import { StandardDeleteCompiler } from './standard-delete-compiler.js';
27
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
24
28
 
25
29
  /**
26
- * Base class for SQL dialects.
27
- * Provides a common framework for compiling AST nodes into SQL strings.
28
- * Specific dialects should extend this class and implement dialect-specific logic.
30
+ * Thin assembly base for dialects that use MetalORM's standard SQL compilers.
31
+ *
32
+ * SELECT/INSERT/UPDATE/DELETE orchestration lives in independent compiler
33
+ * objects. This class only wires dialect-specific syntax hooks and strategies
34
+ * into those components.
29
35
  */
30
- export abstract class SqlDialectBase extends Dialect {
36
+ export abstract class SqlDialectBase extends DialectBase {
31
37
  abstract quoteIdentifier(id: string): string;
32
38
 
33
39
  protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
34
40
  protected returningStrategy: ReturningStrategy = new NoReturningStrategy();
35
41
 
36
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
37
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
38
- const ctes = CteCompiler.compileCtes(
39
- ast,
40
- ctx,
41
- this.quoteIdentifier.bind(this),
42
- this.compileSelectAst.bind(this),
43
- this.normalizeSelectAst?.bind(this) ?? ((a) => a),
44
- this.stripTrailingSemicolon.bind(this)
45
- );
46
- const baseAst: SelectQueryNode = hasSetOps
47
- ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
48
- : ast;
49
- const baseSelect = this.compileSelectCore(baseAst, ctx);
50
- if (!hasSetOps) {
51
- return `${ctes}${baseSelect}`;
52
- }
53
- return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
42
+ private readonly sourceCompiler: StandardSqlSourceCompiler;
43
+ private readonly selectCompiler: StandardSelectCompiler;
44
+ private readonly insertCompiler: StandardInsertCompiler;
45
+ private readonly updateCompiler: StandardUpdateCompiler;
46
+ private readonly deleteCompiler: StandardDeleteCompiler;
47
+
48
+ protected constructor(
49
+ functionStrategy?: FunctionStrategy,
50
+ tableFunctionStrategy?: TableFunctionStrategy
51
+ ) {
52
+ super(functionStrategy, tableFunctionStrategy);
53
+
54
+ const services: StandardSqlCompilerServices = {
55
+ getDialectName: () => this.dialect,
56
+ getPaginationStrategy: () => this.paginationStrategy,
57
+ getTableFunctionStrategy: () => this.tableFunctionStrategy,
58
+ quoteIdentifier: id => this.quoteIdentifier(id),
59
+ compileOperand: (node, ctx) => this.compileOperand(node, ctx),
60
+ compileExpression: (node, ctx) => this.compileExpression(node, ctx),
61
+ compileOrderingTerm: (term, ctx) => this.compileOrderingTerm(term, ctx),
62
+ normalizeSelectAst: ast => this.normalizeSelectAst(ast),
63
+ compileSelectAst: (ast, ctx) => this.compileSelectAst(ast, ctx),
64
+ compileReturning: (returning, ctx) => this.compileReturning(returning, ctx),
65
+ compileUpsertClause: (ast, ctx) => this.compileUpsertClause(ast, ctx),
66
+ compileSetTarget: (column, table) => this.compileSetTarget(column, table),
67
+ renderOrderByNulls: order => this.renderOrderByNulls(order),
68
+ renderOrderByCollation: order => this.renderOrderByCollation(order)
69
+ };
70
+
71
+ this.sourceCompiler = new StandardSqlSourceCompiler(services);
72
+ this.selectCompiler = new StandardSelectCompiler(services, this.sourceCompiler);
73
+ this.insertCompiler = new StandardInsertCompiler(services, this.sourceCompiler);
74
+ this.updateCompiler = new StandardUpdateCompiler(services, this.sourceCompiler);
75
+ this.deleteCompiler = new StandardDeleteCompiler(services, this.sourceCompiler);
54
76
  }
55
77
 
56
- private compileSelectWithSetOps(
57
- ast: SelectQueryNode,
58
- baseSelect: string,
59
- ctes: string,
60
- ctx: CompilerContext
61
- ): string {
62
- const compound = ast.setOps!
63
- .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
64
- .join(' ');
65
- const orderBy = OrderByCompiler.compileOrderBy(
66
- ast,
67
- term => this.compileOrderingTerm(term, ctx),
68
- this.renderOrderByNulls.bind(this),
69
- this.renderOrderByCollation.bind(this)
70
- );
71
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
72
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
73
- return `${ctes}${combined}${orderBy}${pagination}`;
78
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
79
+ return this.selectCompiler.compile(ast, ctx);
74
80
  }
75
81
 
76
82
  protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
77
- if (!ast.columns.length) {
78
- throw new Error('INSERT queries must specify columns.');
79
- }
83
+ return this.insertCompiler.compile(ast, ctx);
84
+ }
85
+
86
+ protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
87
+ return this.updateCompiler.compile(ast, ctx);
88
+ }
80
89
 
81
- const table = this.compileTableName(ast.into);
82
- const columnList = this.compileInsertColumnList(ast.columns);
83
- const source = this.compileInsertSource(ast.source, ctx);
84
- const upsert = this.compileUpsertClause(ast, ctx);
85
- const returning = this.compileReturning(ast.returning, ctx);
86
- return `INSERT INTO ${table} (${columnList}) ${source}${upsert}${returning}`;
90
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
91
+ return this.deleteCompiler.compile(ast, ctx);
87
92
  }
88
93
 
89
94
  protected compileUpsertClause(ast: InsertQueryNode, _ctx: CompilerContext): string {
@@ -96,60 +101,8 @@ export abstract class SqlDialectBase extends Dialect {
96
101
  return this.returningStrategy.compileReturning(returning, ctx);
97
102
  }
98
103
 
99
- protected compileInsertSource(source: InsertSourceNode, ctx: CompilerContext): string {
100
- if (source.type === 'InsertValues') {
101
- if (!source.rows.length) {
102
- throw new Error('INSERT ... VALUES requires at least one row.');
103
- }
104
- const values = source.rows
105
- .map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
106
- .join(', ');
107
- return `VALUES ${values}`;
108
- }
109
-
110
- const normalized = this.normalizeSelectAst(source.query);
111
- return this.compileSelectAst(normalized, ctx).trim();
112
- }
113
-
114
- protected compileInsertColumnList(columns: ColumnNode[]): string {
115
- return columns.map(column => this.quoteIdentifier(column.name)).join(', ');
116
- }
117
-
118
104
  protected ensureConflictColumns(clause: UpsertClause, message: string): void {
119
- if (!clause.target.columns.length) {
120
- throw new Error(message);
121
- }
122
- }
123
-
124
- private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
125
- const columns = this.compileSelectColumns(ast, ctx);
126
- const from = this.compileFrom(ast.from, ctx);
127
- const joins = JoinCompiler.compileJoins(
128
- ast.joins,
129
- ctx,
130
- this.compileFrom.bind(this),
131
- this.compileExpression.bind(this)
132
- );
133
- const whereClause = this.compileWhere(ast.where, ctx);
134
- const groupBy = GroupByCompiler.compileGroupBy(ast, term => this.compileOrderingTerm(term, ctx));
135
- const having = this.compileHaving(ast, ctx);
136
- const orderBy = OrderByCompiler.compileOrderBy(
137
- ast,
138
- term => this.compileOrderingTerm(term, ctx),
139
- this.renderOrderByNulls.bind(this),
140
- this.renderOrderByCollation.bind(this)
141
- );
142
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
143
- return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
144
- }
145
-
146
- protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
147
- const target = this.compileTableReference(ast.table);
148
- const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
149
- const fromClause = this.compileUpdateFromClause(ast, ctx);
150
- const whereClause = this.compileWhere(ast.where, ctx);
151
- const returning = this.compileReturning(ast.returning, ctx);
152
- return `UPDATE ${target} SET ${assignments}${fromClause}${whereClause}${returning}`;
105
+ this.insertCompiler.ensureConflictColumns(clause, message);
153
106
  }
154
107
 
155
108
  protected compileUpdateAssignments(
@@ -157,14 +110,7 @@ export abstract class SqlDialectBase extends Dialect {
157
110
  table: TableNode,
158
111
  ctx: CompilerContext
159
112
  ): string {
160
- return assignments
161
- .map(assignment => {
162
- const col = assignment.column;
163
- const target = this.compileSetTarget(col, table);
164
- const value = this.compileOperand(assignment.value, ctx);
165
- return `${target} = ${value}`;
166
- })
167
- .join(', ');
113
+ return this.updateCompiler.compileAssignments(assignments, table, ctx);
168
114
  }
169
115
 
170
116
  protected compileSetTarget(column: ColumnNode, table: TableNode): string {
@@ -175,156 +121,49 @@ export abstract class SqlDialectBase extends Dialect {
175
121
  const baseTableName = table.name;
176
122
  const alias = table.alias;
177
123
  const columnTable = column.table ?? alias ?? baseTableName;
178
- const tableQualifier =
179
- alias && column.table === baseTableName ? alias : columnTable;
180
-
181
- if (!tableQualifier) {
182
- return this.quoteIdentifier(column.name);
183
- }
124
+ const tableQualifier = alias && column.table === baseTableName ? alias : columnTable;
184
125
 
126
+ if (!tableQualifier) return this.quoteIdentifier(column.name);
185
127
  return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
186
128
  }
187
129
 
188
- protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
189
- const target = this.compileTableReference(ast.from);
190
- const usingClause = this.compileDeleteUsingClause(ast, ctx);
191
- const whereClause = this.compileWhere(ast.where, ctx);
192
- const returning = this.compileReturning(ast.returning, ctx);
193
- return `DELETE FROM ${target}${usingClause}${whereClause}${returning}`;
194
- }
195
-
196
130
  protected formatReturningColumns(returning: ColumnNode[]): string {
197
- return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
198
- }
199
-
200
- protected compileDistinct(ast: SelectQueryNode): string {
201
- return ast.distinct ? 'DISTINCT ' : '';
202
- }
203
-
204
- protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
205
- if (!ast.columns || ast.columns.length === 0) {
206
- return '*';
207
- }
208
- return ast.columns.map(c => {
209
- const expr = this.compileOperand(c, ctx);
210
- if (c.alias) {
211
- if (c.alias.includes('(')) return c.alias;
212
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
213
- }
214
- return expr;
215
- }).join(', ');
131
+ return this.returningStrategy.formatReturningColumns(
132
+ returning,
133
+ id => this.quoteIdentifier(id)
134
+ );
216
135
  }
217
136
 
218
- protected compileFrom(ast: SelectQueryNode['from'], ctx?: CompilerContext): string {
219
- const tableSource = ast;
220
- if (tableSource.type === 'FunctionTable') {
221
- return this.compileFunctionTable(tableSource, ctx);
222
- }
223
- if (tableSource.type === 'DerivedTable') {
224
- return this.compileDerivedTable(tableSource, ctx);
225
- }
226
- return this.compileTableSource(tableSource);
137
+ protected compileFrom(source: TableSourceNode, ctx?: CompilerContext): string {
138
+ return this.sourceCompiler.compileFrom(source, ctx);
227
139
  }
228
140
 
229
141
  protected compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
230
- const key = fn.key ?? fn.name;
231
-
232
- if (ctx) {
233
- const renderer = this.tableFunctionStrategy.getRenderer(key);
234
- if (renderer) {
235
- const compiledArgs = (fn.args ?? []).map(arg => this.compileOperand(arg, ctx));
236
- return renderer({
237
- node: fn,
238
- compiledArgs,
239
- compileOperand: operand => this.compileOperand(operand, ctx),
240
- quoteIdentifier: this.quoteIdentifier.bind(this)
241
- });
242
- }
243
-
244
- if (fn.key) {
245
- throw new Error(`Table function "${key}" is not supported by dialect "${this.dialect}".`);
246
- }
247
- }
248
-
249
- return FunctionTableFormatter.format(fn, ctx, this);
142
+ return this.sourceCompiler.compileFunctionTable(fn, ctx);
250
143
  }
251
144
 
252
145
  protected compileDerivedTable(table: DerivedTableNode, ctx?: CompilerContext): string {
253
- if (!table.alias) {
254
- throw new Error('Derived tables must have an alias.');
255
- }
256
- const subquery = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx!).trim().replace(/;$/, '');
257
- const columns = table.columnAliases?.length
258
- ? ` (${table.columnAliases.map(c => this.quoteIdentifier(c)).join(', ')})`
259
- : '';
260
- return `(${subquery}) AS ${this.quoteIdentifier(table.alias)}${columns}`;
146
+ return this.sourceCompiler.compileDerivedTable(table, ctx);
261
147
  }
262
148
 
263
149
  protected compileTableSource(table: TableSourceNode): string {
264
- if (table.type === 'FunctionTable') {
265
- return this.compileFunctionTable(table as FunctionTableNode);
266
- }
267
- if (table.type === 'DerivedTable') {
268
- return this.compileDerivedTable(table as DerivedTableNode);
269
- }
270
- const base = this.compileTableName(table);
271
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
150
+ return this.sourceCompiler.compileTableSource(table);
272
151
  }
273
152
 
274
- protected compileTableName(table: { name: string; schema?: string; alias?: string }): string {
275
- if (table.schema) {
276
- return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
277
- }
278
- return this.quoteIdentifier(table.name);
153
+ protected compileTableName(table: { name: string; schema?: string }): string {
154
+ return this.sourceCompiler.compileTableName(table);
279
155
  }
280
156
 
281
157
  protected compileTableReference(table: { name: string; schema?: string; alias?: string }): string {
282
- const base = this.compileTableName(table);
283
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
284
- }
285
-
286
- private compileUpdateFromClause(ast: UpdateQueryNode, ctx: CompilerContext): string {
287
- if (!ast.from && (!ast.joins || ast.joins.length === 0)) return '';
288
- if (!ast.from) {
289
- throw new Error('UPDATE with JOINs requires an explicit FROM clause.');
290
- }
291
- const from = this.compileFrom(ast.from, ctx);
292
- const joins = JoinCompiler.compileJoins(
293
- ast.joins,
294
- ctx,
295
- this.compileFrom.bind(this),
296
- this.compileExpression.bind(this)
297
- );
298
- return ` FROM ${from}${joins}`;
299
- }
300
-
301
- private compileDeleteUsingClause(ast: DeleteQueryNode, ctx: CompilerContext): string {
302
- if (!ast.using && (!ast.joins || ast.joins.length === 0)) return '';
303
- if (!ast.using) {
304
- throw new Error('DELETE with JOINs requires a USING clause.');
305
- }
306
- const usingTable = this.compileFrom(ast.using, ctx);
307
- const joins = JoinCompiler.compileJoins(
308
- ast.joins,
309
- ctx,
310
- this.compileFrom.bind(this),
311
- this.compileExpression.bind(this)
312
- );
313
- return ` USING ${usingTable}${joins}`;
314
- }
315
-
316
- protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
317
- if (!ast.having) return '';
318
- return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
158
+ return this.sourceCompiler.compileTableReference(table);
319
159
  }
320
160
 
321
161
  protected stripTrailingSemicolon(sql: string): string {
322
- return sql.trim().replace(/;$/, '');
162
+ return this.sourceCompiler.stripTrailingSemicolon(sql);
323
163
  }
324
164
 
325
165
  protected wrapSetOperand(sql: string): string {
326
- const trimmed = this.stripTrailingSemicolon(sql);
327
- return `(${trimmed})`;
166
+ return this.sourceCompiler.wrapSetOperand(sql);
328
167
  }
329
168
 
330
169
  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
+ }
@@ -0,0 +1,82 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { SelectQueryNode } from '../../ast/query.js';
3
+ import { CteCompiler } from './cte-compiler.js';
4
+ import { JoinCompiler } from './join-compiler.js';
5
+ import { GroupByCompiler } from './groupby-compiler.js';
6
+ import { OrderByCompiler } from './orderby-compiler.js';
7
+ import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
8
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
9
+
10
+ /** Standard SELECT orchestration, independent from any dialect class hierarchy. */
11
+ export class StandardSelectCompiler {
12
+ public constructor(
13
+ private readonly services: StandardSqlCompilerServices,
14
+ private readonly sources: StandardSqlSourceCompiler
15
+ ) {}
16
+
17
+ compile(ast: SelectQueryNode, ctx: CompilerContext): string {
18
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
19
+ const ctes = CteCompiler.compileCtes(
20
+ ast,
21
+ ctx,
22
+ id => this.services.quoteIdentifier(id),
23
+ (query, compilerContext) => this.services.compileSelectAst(query, compilerContext),
24
+ query => this.services.normalizeSelectAst(query),
25
+ sql => this.sources.stripTrailingSemicolon(sql)
26
+ );
27
+
28
+ const baseAst: SelectQueryNode = hasSetOps
29
+ ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
30
+ : ast;
31
+ const baseSelect = this.compileCore(baseAst, ctx);
32
+
33
+ if (!hasSetOps) return `${ctes}${baseSelect}`;
34
+
35
+ const compound = ast.setOps!
36
+ .map(op => `${op.operator} ${this.sources.wrapSetOperand(this.services.compileSelectAst(op.query, ctx))}`)
37
+ .join(' ');
38
+ const orderBy = this.compileOrderBy(ast, ctx);
39
+ const pagination = this.services.getPaginationStrategy().compilePagination(ast.limit, ast.offset);
40
+ const combined = `${this.sources.wrapSetOperand(baseSelect)} ${compound}`;
41
+ return `${ctes}${combined}${orderBy}${pagination}`;
42
+ }
43
+
44
+ private compileCore(ast: SelectQueryNode, ctx: CompilerContext): string {
45
+ const columns = this.compileColumns(ast, ctx);
46
+ const from = this.sources.compileFrom(ast.from, ctx);
47
+ const joins = JoinCompiler.compileJoins(
48
+ ast.joins,
49
+ ctx,
50
+ (source, compilerContext) => this.sources.compileFrom(source, compilerContext),
51
+ (expression, compilerContext) => this.services.compileExpression(expression, compilerContext)
52
+ );
53
+ const where = ast.where ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}` : '';
54
+ const groupBy = GroupByCompiler.compileGroupBy(
55
+ ast,
56
+ term => this.services.compileOrderingTerm(term, ctx)
57
+ );
58
+ const having = ast.having ? ` HAVING ${this.services.compileExpression(ast.having, ctx)}` : '';
59
+ const orderBy = this.compileOrderBy(ast, ctx);
60
+ const pagination = this.services.getPaginationStrategy().compilePagination(ast.limit, ast.offset);
61
+ return `SELECT ${ast.distinct ? 'DISTINCT ' : ''}${columns} FROM ${from}${joins}${where}${groupBy}${having}${orderBy}${pagination}`;
62
+ }
63
+
64
+ private compileColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
65
+ if (!ast.columns || ast.columns.length === 0) return '*';
66
+ return ast.columns.map(column => {
67
+ const expr = this.services.compileOperand(column, ctx);
68
+ if (!column.alias) return expr;
69
+ if (column.alias.includes('(')) return column.alias;
70
+ return `${expr} AS ${this.services.quoteIdentifier(column.alias)}`;
71
+ }).join(', ');
72
+ }
73
+
74
+ private compileOrderBy(ast: SelectQueryNode, ctx: CompilerContext): string {
75
+ return OrderByCompiler.compileOrderBy(
76
+ ast,
77
+ term => this.services.compileOrderingTerm(term, ctx),
78
+ order => this.services.renderOrderByNulls(order),
79
+ order => this.services.renderOrderByCollation(order)
80
+ );
81
+ }
82
+ }
@@ -0,0 +1,44 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type {
3
+ SelectQueryNode,
4
+ InsertQueryNode,
5
+ OrderByNode,
6
+ OrderingTerm,
7
+ TableNode
8
+ } from '../../ast/query.js';
9
+ import type {
10
+ ColumnNode,
11
+ ExpressionNode,
12
+ OperandNode
13
+ } from '../../ast/expression.js';
14
+ import type { DialectName } from '../../sql/sql.js';
15
+ import type { PaginationStrategy } from './pagination-strategy.js';
16
+ import type { TableFunctionStrategy } from '../../functions/table-types.js';
17
+
18
+ /**
19
+ * Narrow callback surface consumed by the standard SQL compilers.
20
+ *
21
+ * The compilers deliberately know nothing about SqlDialectBase or any concrete
22
+ * backend class. A dialect can assemble these services through inheritance,
23
+ * composition, or a plain object.
24
+ */
25
+ export interface StandardSqlCompilerServices {
26
+ getDialectName(): DialectName;
27
+ getPaginationStrategy(): PaginationStrategy;
28
+ getTableFunctionStrategy(): TableFunctionStrategy;
29
+
30
+ quoteIdentifier(id: string): string;
31
+ compileOperand(node: OperandNode, ctx: CompilerContext): string;
32
+ compileExpression(node: ExpressionNode, ctx: CompilerContext): string;
33
+ compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string;
34
+
35
+ normalizeSelectAst(ast: SelectQueryNode): SelectQueryNode;
36
+ compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
37
+
38
+ compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string;
39
+ compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string;
40
+ compileSetTarget(column: ColumnNode, table: TableNode): string;
41
+
42
+ renderOrderByNulls(order: OrderByNode): string | undefined;
43
+ renderOrderByCollation(order: OrderByNode): string | undefined;
44
+ }