metal-orm 1.1.23 → 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.
- package/dist/index.cjs +319 -197
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -16
- package/dist/index.d.ts +104 -16
- package/dist/index.js +314 -197
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/dialect/base/sql-dialect.ts +73 -211
- package/src/core/dialect/base/standard-delete-compiler.ts +37 -0
- package/src/core/dialect/base/standard-insert-compiler.ts +49 -0
- package/src/core/dialect/base/standard-select-compiler.ts +82 -0
- package/src/core/dialect/base/standard-sql-services.ts +44 -0
- package/src/core/dialect/base/standard-sql-source-compiler.ts +88 -0
- package/src/core/dialect/base/standard-update-compiler.ts +53 -0
- package/src/index.ts +6 -0
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
InsertQueryNode,
|
|
6
6
|
UpdateQueryNode,
|
|
7
7
|
DeleteQueryNode,
|
|
8
|
-
InsertSourceNode,
|
|
9
8
|
UpsertClause,
|
|
10
9
|
TableSourceNode,
|
|
11
10
|
DerivedTableNode,
|
|
@@ -14,19 +13,25 @@ import type {
|
|
|
14
13
|
TableNode
|
|
15
14
|
} from '../../ast/query.js';
|
|
16
15
|
import type { ColumnNode, OperandNode } from '../../ast/expression.js';
|
|
17
|
-
import {
|
|
16
|
+
import type { FunctionStrategy } from '../../functions/types.js';
|
|
17
|
+
import type { TableFunctionStrategy } from '../../functions/table-types.js';
|
|
18
18
|
import { StandardLimitOffsetPagination } from './pagination-strategy.js';
|
|
19
19
|
import type { PaginationStrategy } from './pagination-strategy.js';
|
|
20
|
-
import { CteCompiler } from './cte-compiler.js';
|
|
21
20
|
import { NoReturningStrategy } from './returning-strategy.js';
|
|
22
21
|
import type { ReturningStrategy } from './returning-strategy.js';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
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';
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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.
|
|
30
35
|
*/
|
|
31
36
|
export abstract class SqlDialectBase extends DialectBase {
|
|
32
37
|
abstract quoteIdentifier(id: string): string;
|
|
@@ -34,55 +39,56 @@ export abstract class SqlDialectBase extends DialectBase {
|
|
|
34
39
|
protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
|
|
35
40
|
protected returningStrategy: ReturningStrategy = new NoReturningStrategy();
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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);
|
|
53
76
|
}
|
|
54
77
|
|
|
55
|
-
|
|
56
|
-
ast
|
|
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}`;
|
|
78
|
+
protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
79
|
+
return this.selectCompiler.compile(ast, ctx);
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
|
79
89
|
|
|
80
|
-
|
|
81
|
-
|
|
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}`;
|
|
90
|
+
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
91
|
+
return this.deleteCompiler.compile(ast, ctx);
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
protected compileUpsertClause(ast: InsertQueryNode, _ctx: CompilerContext): string {
|
|
@@ -95,58 +101,8 @@ export abstract class SqlDialectBase extends DialectBase {
|
|
|
95
101
|
return this.returningStrategy.compileReturning(returning, ctx);
|
|
96
102
|
}
|
|
97
103
|
|
|
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();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
protected compileInsertColumnList(columns: ColumnNode[]): string {
|
|
114
|
-
return columns.map(column => this.quoteIdentifier(column.name)).join(', ');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
104
|
protected ensureConflictColumns(clause: UpsertClause, message: string): void {
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
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,
|
|
126
|
-
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)
|
|
138
|
-
);
|
|
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
|
-
}
|
|
142
|
-
|
|
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}`;
|
|
105
|
+
this.insertCompiler.ensureConflictColumns(clause, message);
|
|
150
106
|
}
|
|
151
107
|
|
|
152
108
|
protected compileUpdateAssignments(
|
|
@@ -154,13 +110,7 @@ export abstract class SqlDialectBase extends DialectBase {
|
|
|
154
110
|
table: TableNode,
|
|
155
111
|
ctx: CompilerContext
|
|
156
112
|
): 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(', ');
|
|
113
|
+
return this.updateCompiler.compileAssignments(assignments, table, ctx);
|
|
164
114
|
}
|
|
165
115
|
|
|
166
116
|
protected compileSetTarget(column: ColumnNode, table: TableNode): string {
|
|
@@ -177,131 +127,43 @@ export abstract class SqlDialectBase extends DialectBase {
|
|
|
177
127
|
return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
|
|
178
128
|
}
|
|
179
129
|
|
|
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
130
|
protected formatReturningColumns(returning: ColumnNode[]): string {
|
|
189
|
-
return this.returningStrategy.formatReturningColumns(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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(', ');
|
|
131
|
+
return this.returningStrategy.formatReturningColumns(
|
|
132
|
+
returning,
|
|
133
|
+
id => this.quoteIdentifier(id)
|
|
134
|
+
);
|
|
206
135
|
}
|
|
207
136
|
|
|
208
|
-
protected compileFrom(
|
|
209
|
-
|
|
210
|
-
if (ast.type === 'DerivedTable') return this.compileDerivedTable(ast, ctx);
|
|
211
|
-
return this.compileTableSource(ast);
|
|
137
|
+
protected compileFrom(source: TableSourceNode, ctx?: CompilerContext): string {
|
|
138
|
+
return this.sourceCompiler.compileFrom(source, ctx);
|
|
212
139
|
}
|
|
213
140
|
|
|
214
141
|
protected compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
|
|
215
|
-
|
|
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
|
-
});
|
|
142
|
+
return this.sourceCompiler.compileFunctionTable(fn, ctx);
|
|
238
143
|
}
|
|
239
144
|
|
|
240
145
|
protected compileDerivedTable(table: DerivedTableNode, ctx?: CompilerContext): string {
|
|
241
|
-
|
|
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}`;
|
|
146
|
+
return this.sourceCompiler.compileDerivedTable(table, ctx);
|
|
247
147
|
}
|
|
248
148
|
|
|
249
149
|
protected compileTableSource(table: TableSourceNode): string {
|
|
250
|
-
|
|
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;
|
|
150
|
+
return this.sourceCompiler.compileTableSource(table);
|
|
254
151
|
}
|
|
255
152
|
|
|
256
|
-
protected compileTableName(table: { name: string; schema?: string
|
|
257
|
-
|
|
258
|
-
return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
|
|
259
|
-
}
|
|
260
|
-
return this.quoteIdentifier(table.name);
|
|
153
|
+
protected compileTableName(table: { name: string; schema?: string }): string {
|
|
154
|
+
return this.sourceCompiler.compileTableName(table);
|
|
261
155
|
}
|
|
262
156
|
|
|
263
157
|
protected compileTableReference(table: { name: string; schema?: string; alias?: string }): string {
|
|
264
|
-
|
|
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)}`;
|
|
158
|
+
return this.sourceCompiler.compileTableReference(table);
|
|
297
159
|
}
|
|
298
160
|
|
|
299
161
|
protected stripTrailingSemicolon(sql: string): string {
|
|
300
|
-
return
|
|
162
|
+
return this.sourceCompiler.stripTrailingSemicolon(sql);
|
|
301
163
|
}
|
|
302
164
|
|
|
303
165
|
protected wrapSetOperand(sql: string): string {
|
|
304
|
-
return
|
|
166
|
+
return this.sourceCompiler.wrapSetOperand(sql);
|
|
305
167
|
}
|
|
306
168
|
|
|
307
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
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { CompilerContext } from '../abstract.js';
|
|
2
|
+
import type {
|
|
3
|
+
DerivedTableNode,
|
|
4
|
+
FunctionTableNode,
|
|
5
|
+
TableSourceNode
|
|
6
|
+
} from '../../ast/query.js';
|
|
7
|
+
import { FunctionTableFormatter } from './function-table-formatter.js';
|
|
8
|
+
import type { StandardSqlCompilerServices } from './standard-sql-services.js';
|
|
9
|
+
|
|
10
|
+
/** Shared FROM/table-source rendering used by the standard query compilers. */
|
|
11
|
+
export class StandardSqlSourceCompiler {
|
|
12
|
+
public constructor(private readonly services: StandardSqlCompilerServices) {}
|
|
13
|
+
|
|
14
|
+
compileFrom(source: TableSourceNode, ctx?: CompilerContext): string {
|
|
15
|
+
if (source.type === 'FunctionTable') return this.compileFunctionTable(source, ctx);
|
|
16
|
+
if (source.type === 'DerivedTable') return this.compileDerivedTable(source, ctx);
|
|
17
|
+
return this.compileTableSource(source);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
|
|
21
|
+
const key = fn.key ?? fn.name;
|
|
22
|
+
|
|
23
|
+
if (ctx) {
|
|
24
|
+
const renderer = this.services.getTableFunctionStrategy().getRenderer(key);
|
|
25
|
+
if (renderer) {
|
|
26
|
+
const compiledArgs = (fn.args ?? []).map(arg => this.services.compileOperand(arg, ctx));
|
|
27
|
+
return renderer({
|
|
28
|
+
node: fn,
|
|
29
|
+
compiledArgs,
|
|
30
|
+
compileOperand: operand => this.services.compileOperand(operand, ctx),
|
|
31
|
+
quoteIdentifier: id => this.services.quoteIdentifier(id)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (fn.key) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Table function "${key}" is not supported by dialect "${this.services.getDialectName()}".`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return FunctionTableFormatter.format(fn, ctx, {
|
|
43
|
+
quoteIdentifier: id => this.services.quoteIdentifier(id),
|
|
44
|
+
compileOperand: (node, compilerContext) => this.services.compileOperand(node, compilerContext)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
compileDerivedTable(table: DerivedTableNode, ctx?: CompilerContext): string {
|
|
49
|
+
if (!table.alias) throw new Error('Derived tables must have an alias.');
|
|
50
|
+
if (!ctx) throw new Error('Derived table compilation requires a compiler context.');
|
|
51
|
+
|
|
52
|
+
const normalized = this.services.normalizeSelectAst(table.query);
|
|
53
|
+
const subquery = this.services.compileSelectAst(normalized, ctx).trim().replace(/;$/, '');
|
|
54
|
+
const columns = table.columnAliases?.length
|
|
55
|
+
? ` (${table.columnAliases.map(column => this.services.quoteIdentifier(column)).join(', ')})`
|
|
56
|
+
: '';
|
|
57
|
+
return `(${subquery}) AS ${this.services.quoteIdentifier(table.alias)}${columns}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
compileTableSource(table: TableSourceNode): string {
|
|
61
|
+
if (table.type === 'FunctionTable') return this.compileFunctionTable(table);
|
|
62
|
+
if (table.type === 'DerivedTable') {
|
|
63
|
+
throw new Error('Derived table compilation requires a compiler context.');
|
|
64
|
+
}
|
|
65
|
+
const base = this.compileTableName(table);
|
|
66
|
+
return table.alias ? `${base} AS ${this.services.quoteIdentifier(table.alias)}` : base;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
compileTableName(table: { name: string; schema?: string }): string {
|
|
70
|
+
if (table.schema) {
|
|
71
|
+
return `${this.services.quoteIdentifier(table.schema)}.${this.services.quoteIdentifier(table.name)}`;
|
|
72
|
+
}
|
|
73
|
+
return this.services.quoteIdentifier(table.name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
compileTableReference(table: { name: string; schema?: string; alias?: string }): string {
|
|
77
|
+
const base = this.compileTableName(table);
|
|
78
|
+
return table.alias ? `${base} AS ${this.services.quoteIdentifier(table.alias)}` : base;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
stripTrailingSemicolon(sql: string): string {
|
|
82
|
+
return sql.trim().replace(/;$/, '');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
wrapSetOperand(sql: string): string {
|
|
86
|
+
return `(${this.stripTrailingSemicolon(sql)})`;
|
|
87
|
+
}
|
|
88
|
+
}
|