metal-orm 1.1.24 → 1.1.26
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 +770 -710
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -318
- package/dist/index.d.ts +294 -318
- package/dist/index.js +745 -709
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/dialect/abstract.ts +7 -229
- package/src/core/dialect/base/returning-strategy.ts +40 -39
- package/src/core/dialect/base/sql-compiler-set.ts +33 -0
- package/src/core/dialect/base/sql-dialect-composer.ts +294 -0
- package/src/core/dialect/base/standard-sql-services.ts +2 -6
- package/src/core/dialect/base/upsert-strategy.ts +44 -0
- package/src/core/dialect/capabilities/procedure-compiler.ts +10 -8
- package/src/core/dialect/dialect-factory.ts +17 -49
- package/src/core/dialect/mssql/compiler-factory.ts +12 -0
- package/src/core/dialect/mssql/delete-compiler.ts +40 -0
- package/src/core/dialect/mssql/index.ts +52 -370
- package/src/core/dialect/mssql/insert-compiler.ts +112 -0
- package/src/core/dialect/mssql/output.ts +46 -0
- package/src/core/dialect/mssql/procedure-compiler.ts +81 -0
- package/src/core/dialect/mssql/select-compiler.ts +116 -0
- package/src/core/dialect/mssql/update-compiler.ts +37 -0
- package/src/core/dialect/mysql/index.ts +66 -126
- package/src/core/dialect/mysql/procedure-compiler.ts +67 -0
- package/src/core/dialect/mysql/upsert.ts +42 -0
- package/src/core/dialect/postgres/index.ts +75 -114
- package/src/core/dialect/postgres/procedure-compiler.ts +41 -0
- package/src/core/dialect/postgres/returning.ts +4 -0
- package/src/core/dialect/postgres/upsert.ts +43 -0
- package/src/core/dialect/sqlite/index.ts +65 -90
- package/src/core/dialect/sqlite/returning.ts +30 -0
- package/src/core/dialect/sqlite/upsert.ts +43 -0
- package/src/index.ts +22 -10
- package/src/core/dialect/base/sql-dialect.ts +0 -176
package/package.json
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
SelectQueryNode,
|
|
3
|
-
InsertQueryNode,
|
|
4
|
-
UpdateQueryNode,
|
|
5
2
|
DeleteQueryNode,
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
InsertQueryNode,
|
|
4
|
+
SelectQueryNode,
|
|
5
|
+
UpdateQueryNode
|
|
8
6
|
} from '../ast/query.js';
|
|
9
|
-
import type {
|
|
10
|
-
ExpressionNode,
|
|
11
|
-
ColumnNode,
|
|
12
|
-
OperandNode,
|
|
13
|
-
FunctionNode,
|
|
14
|
-
JsonPathNode
|
|
15
|
-
} from '../ast/expression.js';
|
|
16
|
-
import type { DialectName } from '../sql/sql.js';
|
|
17
|
-
import type { FunctionStrategy } from '../functions/types.js';
|
|
18
|
-
import { StandardFunctionStrategy } from '../functions/standard-strategy.js';
|
|
19
|
-
import type { TableFunctionStrategy } from '../functions/table-types.js';
|
|
20
|
-
import { StandardTableFunctionStrategy } from '../functions/standard-table-strategy.js';
|
|
21
|
-
import { ExpressionCompilerRegistry } from './base/expression-compiler-registry.js';
|
|
22
|
-
import { SelectAstNormalizer } from './base/select-ast-normalizer.js';
|
|
23
7
|
|
|
24
8
|
/** Context for SQL compilation with parameter management. */
|
|
25
9
|
export interface CompilerContext {
|
|
@@ -50,219 +34,13 @@ export interface DeleteCompiler {
|
|
|
50
34
|
}
|
|
51
35
|
|
|
52
36
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
37
|
+
* Structural contract consumed by query builders and the ORM runtime.
|
|
38
|
+
*
|
|
39
|
+
* A dialect is assembled from compiler components. There is intentionally no
|
|
40
|
+
* base class: inheritance is not part of the extension model.
|
|
57
41
|
*/
|
|
58
42
|
export interface Dialect
|
|
59
43
|
extends SelectCompiler, InsertCompiler, UpdateCompiler, DeleteCompiler {
|
|
60
44
|
quoteIdentifier(id: string): string;
|
|
61
45
|
supportsDmlReturningClause(): boolean;
|
|
62
46
|
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Shared implementation infrastructure for SQL dialects.
|
|
66
|
-
*
|
|
67
|
-
* This is deliberately separate from the public Dialect contract: custom
|
|
68
|
-
* dialects may extend this class, extend SqlDialectBase, or use composition.
|
|
69
|
-
*/
|
|
70
|
-
export abstract class DialectBase implements Dialect {
|
|
71
|
-
protected abstract readonly dialect: DialectName;
|
|
72
|
-
|
|
73
|
-
private readonly expressionCompilerRegistry: ExpressionCompilerRegistry;
|
|
74
|
-
private readonly selectAstNormalizer: SelectAstNormalizer;
|
|
75
|
-
protected readonly functionStrategy: FunctionStrategy;
|
|
76
|
-
protected readonly tableFunctionStrategy: TableFunctionStrategy;
|
|
77
|
-
|
|
78
|
-
protected constructor(
|
|
79
|
-
functionStrategy?: FunctionStrategy,
|
|
80
|
-
tableFunctionStrategy?: TableFunctionStrategy
|
|
81
|
-
) {
|
|
82
|
-
this.functionStrategy = functionStrategy ?? new StandardFunctionStrategy();
|
|
83
|
-
this.tableFunctionStrategy = tableFunctionStrategy ?? new StandardTableFunctionStrategy();
|
|
84
|
-
this.selectAstNormalizer = new SelectAstNormalizer(kind => this.supportsSetOperation(kind));
|
|
85
|
-
this.expressionCompilerRegistry = new ExpressionCompilerRegistry({
|
|
86
|
-
quoteIdentifier: id => this.quoteIdentifier(id),
|
|
87
|
-
compileSelectAst: (ast, ctx) => this.compileSelectAst(ast, ctx),
|
|
88
|
-
compileSelectForExists: (ast, ctx) => this.compileSelectForExists(ast, ctx),
|
|
89
|
-
compileJsonPath: node => this.compileJsonPath(node),
|
|
90
|
-
compileFunctionOperand: (node, ctx) => this.compileFunctionOperand(node, ctx),
|
|
91
|
-
describe: () => this.constructor.name
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
compileSelect(ast: SelectQueryNode): CompiledQuery {
|
|
96
|
-
const ctx = this.createCompilerContext();
|
|
97
|
-
const normalized = this.normalizeSelectAst(ast);
|
|
98
|
-
const rawSql = this.compileSelectAst(normalized, ctx).trim();
|
|
99
|
-
return {
|
|
100
|
-
sql: rawSql.endsWith(';') ? rawSql : `${rawSql};`,
|
|
101
|
-
params: [...ctx.params]
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
compileInsert(ast: InsertQueryNode): CompiledQuery {
|
|
106
|
-
const ctx = this.createCompilerContext();
|
|
107
|
-
const rawSql = this.compileInsertAst(ast, ctx).trim();
|
|
108
|
-
return {
|
|
109
|
-
sql: rawSql.endsWith(';') ? rawSql : `${rawSql};`,
|
|
110
|
-
params: [...ctx.params]
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
compileUpdate(ast: UpdateQueryNode): CompiledQuery {
|
|
115
|
-
const ctx = this.createCompilerContext();
|
|
116
|
-
const rawSql = this.compileUpdateAst(ast, ctx).trim();
|
|
117
|
-
return {
|
|
118
|
-
sql: rawSql.endsWith(';') ? rawSql : `${rawSql};`,
|
|
119
|
-
params: [...ctx.params]
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
compileDelete(ast: DeleteQueryNode): CompiledQuery {
|
|
124
|
-
const ctx = this.createCompilerContext();
|
|
125
|
-
const rawSql = this.compileDeleteAst(ast, ctx).trim();
|
|
126
|
-
return {
|
|
127
|
-
sql: rawSql.endsWith(';') ? rawSql : `${rawSql};`,
|
|
128
|
-
params: [...ctx.params]
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
supportsDmlReturningClause(): boolean {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
protected abstract compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
|
|
137
|
-
protected abstract compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string;
|
|
138
|
-
protected abstract compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string;
|
|
139
|
-
protected abstract compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string;
|
|
140
|
-
|
|
141
|
-
abstract quoteIdentifier(id: string): string;
|
|
142
|
-
|
|
143
|
-
protected compileWhere(where: ExpressionNode | undefined, ctx: CompilerContext): string {
|
|
144
|
-
if (!where) return '';
|
|
145
|
-
return ` WHERE ${this.compileExpression(where, ctx)}`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
protected compileReturning(
|
|
149
|
-
returning: ColumnNode[] | undefined,
|
|
150
|
-
_ctx: CompilerContext
|
|
151
|
-
): string {
|
|
152
|
-
void _ctx;
|
|
153
|
-
if (!returning || returning.length === 0) return '';
|
|
154
|
-
throw new Error('RETURNING is not supported by this dialect.');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
protected compileSelectForExists(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
158
|
-
const normalized = this.normalizeSelectAst(ast);
|
|
159
|
-
const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, '');
|
|
160
|
-
|
|
161
|
-
if (normalized.setOps && normalized.setOps.length > 0) {
|
|
162
|
-
return `SELECT 1 FROM (${full}) AS _exists`;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const upper = full.toUpperCase();
|
|
166
|
-
const fromIndex = upper.indexOf(' FROM ');
|
|
167
|
-
if (fromIndex === -1) return full;
|
|
168
|
-
|
|
169
|
-
return `SELECT 1${full.slice(fromIndex)}`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
protected createCompilerContext(): CompilerContext {
|
|
173
|
-
const params: unknown[] = [];
|
|
174
|
-
let counter = 0;
|
|
175
|
-
return {
|
|
176
|
-
params,
|
|
177
|
-
addParameter: (value: unknown) => {
|
|
178
|
-
counter += 1;
|
|
179
|
-
params.push(value);
|
|
180
|
-
return this.formatPlaceholder(counter);
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
protected formatPlaceholder(_index: number): string {
|
|
186
|
-
void _index;
|
|
187
|
-
return '?';
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
protected supportsSetOperation(_kind: SetOperationKind): boolean {
|
|
191
|
-
void _kind;
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
protected normalizeSelectAst(ast: SelectQueryNode): SelectQueryNode {
|
|
196
|
-
return this.selectAstNormalizer.normalize(ast);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
protected registerExpressionCompiler<T extends ExpressionNode>(
|
|
200
|
-
type: T['type'],
|
|
201
|
-
compiler: (node: T, ctx: CompilerContext) => string
|
|
202
|
-
): void {
|
|
203
|
-
this.expressionCompilerRegistry.registerExpressionCompiler(type, compiler);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
protected registerOperandCompiler<T extends OperandNode>(
|
|
207
|
-
type: T['type'],
|
|
208
|
-
compiler: (node: T, ctx: CompilerContext) => string
|
|
209
|
-
): void {
|
|
210
|
-
this.expressionCompilerRegistry.registerOperandCompiler(type, compiler);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
protected compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
|
|
214
|
-
return this.expressionCompilerRegistry.compileExpression(node, ctx);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
218
|
-
return this.expressionCompilerRegistry.compileOperand(node, ctx);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
protected compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string {
|
|
222
|
-
return this.expressionCompilerRegistry.compileOrderingTerm(term, ctx);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
protected compileJsonPath(_node: JsonPathNode): string {
|
|
226
|
-
void _node;
|
|
227
|
-
throw new Error('JSON Path not supported by this dialect');
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
protected compileFunctionOperand(fnNode: FunctionNode, ctx: CompilerContext): string {
|
|
231
|
-
const compiledArgs = fnNode.args.map(arg => this.compileOperand(arg, ctx));
|
|
232
|
-
const renderer = this.functionStrategy.getRenderer(fnNode.name);
|
|
233
|
-
if (renderer) {
|
|
234
|
-
return renderer({
|
|
235
|
-
node: fnNode,
|
|
236
|
-
compiledArgs,
|
|
237
|
-
compileOperand: operand => this.compileOperand(operand, ctx)
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return `${fnNode.name}(${compiledArgs.join(', ')})`;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Creates a minimal dialect implementation for isolated compiler tests. */
|
|
244
|
-
static create(
|
|
245
|
-
functionStrategy?: FunctionStrategy,
|
|
246
|
-
tableFunctionStrategy?: TableFunctionStrategy
|
|
247
|
-
): Dialect {
|
|
248
|
-
class TestDialect extends DialectBase {
|
|
249
|
-
protected readonly dialect: DialectName = 'sqlite';
|
|
250
|
-
quoteIdentifier(id: string): string {
|
|
251
|
-
return `"${id}"`;
|
|
252
|
-
}
|
|
253
|
-
protected compileSelectAst(): never {
|
|
254
|
-
throw new Error('Not implemented');
|
|
255
|
-
}
|
|
256
|
-
protected compileInsertAst(): never {
|
|
257
|
-
throw new Error('Not implemented');
|
|
258
|
-
}
|
|
259
|
-
protected compileUpdateAst(): never {
|
|
260
|
-
throw new Error('Not implemented');
|
|
261
|
-
}
|
|
262
|
-
protected compileDeleteAst(): never {
|
|
263
|
-
throw new Error('Not implemented');
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return new TestDialect(functionStrategy, tableFunctionStrategy);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
4
|
+
export type QuoteIdentifier = (id: string) => string;
|
|
5
|
+
|
|
6
|
+
/** Backend-specific RETURNING/OUTPUT rendering strategy. */
|
|
8
7
|
export interface ReturningStrategy {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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>;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DeleteQueryNode,
|
|
3
|
+
InsertQueryNode,
|
|
4
|
+
OrderByNode,
|
|
5
|
+
OrderingTerm,
|
|
6
|
+
SelectQueryNode,
|
|
7
|
+
SetOperationKind,
|
|
8
|
+
TableNode,
|
|
9
|
+
UpdateQueryNode
|
|
10
|
+
} from '../../ast/query.js';
|
|
11
|
+
import type {
|
|
12
|
+
ColumnNode,
|
|
13
|
+
ExpressionNode,
|
|
14
|
+
FunctionNode,
|
|
15
|
+
JsonPathNode,
|
|
16
|
+
OperandNode
|
|
17
|
+
} from '../../ast/expression.js';
|
|
18
|
+
import type { FunctionStrategy } from '../../functions/types.js';
|
|
19
|
+
import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
|
|
20
|
+
import type { TableFunctionStrategy } from '../../functions/table-types.js';
|
|
21
|
+
import { StandardTableFunctionStrategy } from '../../functions/standard-table-strategy.js';
|
|
22
|
+
import type { CompilerContext, Dialect } from '../abstract.js';
|
|
23
|
+
import type { ProcedureCompilerServices } from '../capabilities/procedure-compiler.js';
|
|
24
|
+
import { ExpressionCompilerRegistry } from './expression-compiler-registry.js';
|
|
25
|
+
import { SelectAstNormalizer } from './select-ast-normalizer.js';
|
|
26
|
+
import { StandardLimitOffsetPagination } from './pagination-strategy.js';
|
|
27
|
+
import type { PaginationStrategy } from './pagination-strategy.js';
|
|
28
|
+
import { NoReturningStrategy } from './returning-strategy.js';
|
|
29
|
+
import type { ReturningStrategy } from './returning-strategy.js';
|
|
30
|
+
import { NoUpsertStrategy } from './upsert-strategy.js';
|
|
31
|
+
import type { UpsertStrategy } from './upsert-strategy.js';
|
|
32
|
+
import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
|
|
33
|
+
import { StandardSelectCompiler } from './standard-select-compiler.js';
|
|
34
|
+
import { StandardInsertCompiler } from './standard-insert-compiler.js';
|
|
35
|
+
import { StandardUpdateCompiler } from './standard-update-compiler.js';
|
|
36
|
+
import { StandardDeleteCompiler } from './standard-delete-compiler.js';
|
|
37
|
+
import type { StandardSqlCompilerServices } from './standard-sql-services.js';
|
|
38
|
+
import type { SqlCompilerFactory, SqlCompilerSet } from './sql-compiler-set.js';
|
|
39
|
+
|
|
40
|
+
export interface SqlDialectExpressionApi {
|
|
41
|
+
registerExpressionCompiler<T extends ExpressionNode>(
|
|
42
|
+
type: T['type'],
|
|
43
|
+
compiler: (node: T, ctx: CompilerContext) => string
|
|
44
|
+
): void;
|
|
45
|
+
registerOperandCompiler<T extends OperandNode>(
|
|
46
|
+
type: T['type'],
|
|
47
|
+
compiler: (node: T, ctx: CompilerContext) => string
|
|
48
|
+
): void;
|
|
49
|
+
compileExpression(node: ExpressionNode, ctx: CompilerContext): string;
|
|
50
|
+
compileOperand(node: OperandNode, ctx: CompilerContext): string;
|
|
51
|
+
compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SqlDialectRuntimeServices extends ProcedureCompilerServices {
|
|
55
|
+
compileExpression(node: ExpressionNode, ctx: CompilerContext): string;
|
|
56
|
+
compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string;
|
|
57
|
+
normalizeSelectAst(ast: SelectQueryNode): SelectQueryNode;
|
|
58
|
+
compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SqlDialectComposition {
|
|
62
|
+
dialect: Dialect;
|
|
63
|
+
runtime: SqlDialectRuntimeServices;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface SqlDialectConfig {
|
|
67
|
+
/** Human-readable/backend identifier used by diagnostics and strategies. */
|
|
68
|
+
name: string;
|
|
69
|
+
quoteIdentifier(id: string): string;
|
|
70
|
+
formatPlaceholder?(index: number): string;
|
|
71
|
+
compileJsonPath?(node: JsonPathNode): string;
|
|
72
|
+
functionStrategy?: FunctionStrategy;
|
|
73
|
+
tableFunctionStrategy?: TableFunctionStrategy;
|
|
74
|
+
paginationStrategy?: PaginationStrategy;
|
|
75
|
+
returningStrategy?: ReturningStrategy;
|
|
76
|
+
upsertStrategy?: UpsertStrategy;
|
|
77
|
+
compilerFactory?: SqlCompilerFactory;
|
|
78
|
+
supportsDmlReturning?: boolean;
|
|
79
|
+
supportsSetOperation?(kind: SetOperationKind): boolean;
|
|
80
|
+
compileSetTarget?(column: ColumnNode, table: TableNode): string;
|
|
81
|
+
renderOrderByNulls?(order: OrderByNode): string | undefined;
|
|
82
|
+
renderOrderByCollation?(order: OrderByNode): string | undefined;
|
|
83
|
+
configureExpressions?(api: SqlDialectExpressionApi): void;
|
|
84
|
+
describe?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const terminate = (sql: string): string => {
|
|
88
|
+
const trimmed = sql.trim();
|
|
89
|
+
return trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Assembles a full SQL dialect from independent compiler components.
|
|
94
|
+
* No inheritance or concrete dialect class participates in the compilation path.
|
|
95
|
+
*/
|
|
96
|
+
export const composeSqlDialect = (config: SqlDialectConfig): SqlDialectComposition => {
|
|
97
|
+
const functionStrategy = config.functionStrategy ?? new StandardFunctionStrategy();
|
|
98
|
+
const tableFunctionStrategy = config.tableFunctionStrategy ?? new StandardTableFunctionStrategy();
|
|
99
|
+
const paginationStrategy = config.paginationStrategy ?? new StandardLimitOffsetPagination();
|
|
100
|
+
const returningStrategy = config.returningStrategy ?? new NoReturningStrategy();
|
|
101
|
+
const upsertStrategy = config.upsertStrategy ?? new NoUpsertStrategy();
|
|
102
|
+
const selectAstNormalizer = new SelectAstNormalizer(
|
|
103
|
+
kind => config.supportsSetOperation?.(kind) ?? true
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
let compilerSet!: SqlCompilerSet;
|
|
107
|
+
let expressionRegistry!: ExpressionCompilerRegistry;
|
|
108
|
+
|
|
109
|
+
const createCompilerContext = (): CompilerContext => {
|
|
110
|
+
const params: unknown[] = [];
|
|
111
|
+
let counter = 0;
|
|
112
|
+
return {
|
|
113
|
+
params,
|
|
114
|
+
addParameter(value: unknown): string {
|
|
115
|
+
counter += 1;
|
|
116
|
+
params.push(value);
|
|
117
|
+
return config.formatPlaceholder?.(counter) ?? '?';
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const compileSelectAst = (ast: SelectQueryNode, ctx: CompilerContext): string =>
|
|
123
|
+
compilerSet.select.compile(ast, ctx);
|
|
124
|
+
|
|
125
|
+
const normalizeSelectAst = (ast: SelectQueryNode): SelectQueryNode =>
|
|
126
|
+
selectAstNormalizer.normalize(ast);
|
|
127
|
+
|
|
128
|
+
const compileSelectForExists = (ast: SelectQueryNode, ctx: CompilerContext): string => {
|
|
129
|
+
const normalized = normalizeSelectAst(ast);
|
|
130
|
+
const full = compileSelectAst(normalized, ctx).trim().replace(/;$/, '');
|
|
131
|
+
if (normalized.setOps && normalized.setOps.length > 0) {
|
|
132
|
+
return `SELECT 1 FROM (${full}) AS _exists`;
|
|
133
|
+
}
|
|
134
|
+
const fromIndex = full.toUpperCase().indexOf(' FROM ');
|
|
135
|
+
return fromIndex === -1 ? full : `SELECT 1${full.slice(fromIndex)}`;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const compileJsonPath = (node: JsonPathNode): string => {
|
|
139
|
+
if (!config.compileJsonPath) {
|
|
140
|
+
throw new Error(`JSON Path not supported by dialect "${config.name}".`);
|
|
141
|
+
}
|
|
142
|
+
return config.compileJsonPath(node);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const compileFunctionOperand = (node: FunctionNode, ctx: CompilerContext): string => {
|
|
146
|
+
const compiledArgs = node.args.map(arg => expressionRegistry.compileOperand(arg, ctx));
|
|
147
|
+
const renderer = functionStrategy.getRenderer(node.name);
|
|
148
|
+
if (renderer) {
|
|
149
|
+
return renderer({
|
|
150
|
+
node,
|
|
151
|
+
compiledArgs,
|
|
152
|
+
compileOperand: operand => expressionRegistry.compileOperand(operand, ctx)
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return `${node.name}(${compiledArgs.join(', ')})`;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
expressionRegistry = new ExpressionCompilerRegistry({
|
|
159
|
+
quoteIdentifier: config.quoteIdentifier,
|
|
160
|
+
compileSelectAst,
|
|
161
|
+
compileSelectForExists,
|
|
162
|
+
compileJsonPath,
|
|
163
|
+
compileFunctionOperand,
|
|
164
|
+
describe: () => config.describe ?? config.name
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const compileSetTarget = (column: ColumnNode, table: TableNode): string => {
|
|
168
|
+
if (config.compileSetTarget) return config.compileSetTarget(column, table);
|
|
169
|
+
const columnTable = column.table ?? table.alias ?? table.name;
|
|
170
|
+
const tableQualifier = table.alias && column.table === table.name
|
|
171
|
+
? table.alias
|
|
172
|
+
: columnTable;
|
|
173
|
+
return tableQualifier
|
|
174
|
+
? `${config.quoteIdentifier(tableQualifier)}.${config.quoteIdentifier(column.name)}`
|
|
175
|
+
: config.quoteIdentifier(column.name);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
let standardUpdate!: StandardUpdateCompiler;
|
|
179
|
+
const services: StandardSqlCompilerServices = {
|
|
180
|
+
getDialectName: () => config.name,
|
|
181
|
+
getPaginationStrategy: () => paginationStrategy,
|
|
182
|
+
getTableFunctionStrategy: () => tableFunctionStrategy,
|
|
183
|
+
quoteIdentifier: config.quoteIdentifier,
|
|
184
|
+
compileOperand: (node, ctx) => expressionRegistry.compileOperand(node, ctx),
|
|
185
|
+
compileExpression: (node, ctx) => expressionRegistry.compileExpression(node, ctx),
|
|
186
|
+
compileOrderingTerm: (term, ctx) => expressionRegistry.compileOrderingTerm(term, ctx),
|
|
187
|
+
normalizeSelectAst: normalizeSelectAst,
|
|
188
|
+
compileSelectAst,
|
|
189
|
+
compileReturning: (returning, ctx) =>
|
|
190
|
+
returningStrategy.compileReturning(returning, ctx, config.quoteIdentifier),
|
|
191
|
+
compileUpsertClause: (ast, ctx) =>
|
|
192
|
+
upsertStrategy.compile(ast, ctx, {
|
|
193
|
+
getDialectName: () => config.name,
|
|
194
|
+
quoteIdentifier: config.quoteIdentifier,
|
|
195
|
+
compileOperand: (node, compilerContext) =>
|
|
196
|
+
expressionRegistry.compileOperand(node, compilerContext),
|
|
197
|
+
compileExpression: (node, compilerContext) =>
|
|
198
|
+
expressionRegistry.compileExpression(node, compilerContext),
|
|
199
|
+
compileUpdateAssignments: (assignments, table, compilerContext) =>
|
|
200
|
+
standardUpdate.compileAssignments(assignments, table, compilerContext)
|
|
201
|
+
}),
|
|
202
|
+
compileSetTarget,
|
|
203
|
+
renderOrderByNulls: order =>
|
|
204
|
+
config.renderOrderByNulls?.(order) ?? (order.nulls ? ` NULLS ${order.nulls}` : ''),
|
|
205
|
+
renderOrderByCollation: order =>
|
|
206
|
+
config.renderOrderByCollation?.(order) ?? (order.collation ? ` COLLATE ${order.collation}` : '')
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const sources = new StandardSqlSourceCompiler(services);
|
|
210
|
+
const standardSelect = new StandardSelectCompiler(services, sources);
|
|
211
|
+
const standardInsert = new StandardInsertCompiler(services, sources);
|
|
212
|
+
standardUpdate = new StandardUpdateCompiler(services, sources);
|
|
213
|
+
const standardDelete = new StandardDeleteCompiler(services, sources);
|
|
214
|
+
const overrides = config.compilerFactory?.({ services, sources }) ?? {};
|
|
215
|
+
compilerSet = {
|
|
216
|
+
select: overrides.select ?? standardSelect,
|
|
217
|
+
insert: overrides.insert ?? standardInsert,
|
|
218
|
+
update: overrides.update ?? standardUpdate,
|
|
219
|
+
delete: overrides.delete ?? standardDelete
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const expressionApi: SqlDialectExpressionApi = {
|
|
223
|
+
registerExpressionCompiler<T extends ExpressionNode>(
|
|
224
|
+
type: T['type'],
|
|
225
|
+
compiler: (node: T, ctx: CompilerContext) => string
|
|
226
|
+
): void {
|
|
227
|
+
expressionRegistry.registerExpressionCompiler(type, compiler);
|
|
228
|
+
},
|
|
229
|
+
registerOperandCompiler<T extends OperandNode>(
|
|
230
|
+
type: T['type'],
|
|
231
|
+
compiler: (node: T, ctx: CompilerContext) => string
|
|
232
|
+
): void {
|
|
233
|
+
expressionRegistry.registerOperandCompiler(type, compiler);
|
|
234
|
+
},
|
|
235
|
+
compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
|
|
236
|
+
return expressionRegistry.compileExpression(node, ctx);
|
|
237
|
+
},
|
|
238
|
+
compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
239
|
+
return expressionRegistry.compileOperand(node, ctx);
|
|
240
|
+
},
|
|
241
|
+
compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string {
|
|
242
|
+
return expressionRegistry.compileOrderingTerm(term, ctx);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
config.configureExpressions?.(expressionApi);
|
|
246
|
+
|
|
247
|
+
const dialect: Dialect = {
|
|
248
|
+
quoteIdentifier: config.quoteIdentifier,
|
|
249
|
+
supportsDmlReturningClause: () => config.supportsDmlReturning ?? false,
|
|
250
|
+
compileSelect(ast: SelectQueryNode) {
|
|
251
|
+
const ctx = createCompilerContext();
|
|
252
|
+
return {
|
|
253
|
+
sql: terminate(compileSelectAst(normalizeSelectAst(ast), ctx)),
|
|
254
|
+
params: [...ctx.params]
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
compileInsert(ast: InsertQueryNode) {
|
|
258
|
+
const ctx = createCompilerContext();
|
|
259
|
+
return {
|
|
260
|
+
sql: terminate(compilerSet.insert.compile(ast, ctx)),
|
|
261
|
+
params: [...ctx.params]
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
compileUpdate(ast: UpdateQueryNode) {
|
|
265
|
+
const ctx = createCompilerContext();
|
|
266
|
+
return {
|
|
267
|
+
sql: terminate(compilerSet.update.compile(ast, ctx)),
|
|
268
|
+
params: [...ctx.params]
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
compileDelete(ast: DeleteQueryNode) {
|
|
272
|
+
const ctx = createCompilerContext();
|
|
273
|
+
return {
|
|
274
|
+
sql: terminate(compilerSet.delete.compile(ast, ctx)),
|
|
275
|
+
params: [...ctx.params]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const runtime: SqlDialectRuntimeServices = {
|
|
281
|
+
quoteIdentifier: config.quoteIdentifier,
|
|
282
|
+
createCompilerContext,
|
|
283
|
+
compileOperand: (node, ctx) => expressionRegistry.compileOperand(node, ctx),
|
|
284
|
+
compileExpression: (node, ctx) => expressionRegistry.compileExpression(node, ctx),
|
|
285
|
+
compileOrderingTerm: (term, ctx) => expressionRegistry.compileOrderingTerm(term, ctx),
|
|
286
|
+
normalizeSelectAst: normalizeSelectAst,
|
|
287
|
+
compileSelectAst
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
return { dialect, runtime };
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const createSqlDialect = (config: SqlDialectConfig): Dialect =>
|
|
294
|
+
composeSqlDialect(config).dialect;
|