metal-orm 1.0.11 → 1.0.13
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/README.md +21 -18
- package/dist/decorators/index.cjs +317 -34
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +317 -34
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +1965 -267
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +273 -23
- package/dist/index.d.ts +273 -23
- package/dist/index.js +1947 -267
- package/dist/index.js.map +1 -1
- package/dist/{select-654m4qy8.d.cts → select-CCp1oz9p.d.cts} +254 -4
- package/dist/{select-654m4qy8.d.ts → select-CCp1oz9p.d.ts} +254 -4
- package/package.json +3 -2
- package/src/core/ast/query.ts +40 -22
- package/src/core/ddl/dialects/base-schema-dialect.ts +48 -0
- package/src/core/ddl/dialects/index.ts +5 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +97 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +109 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +99 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +103 -0
- package/src/core/ddl/introspect/mssql.ts +149 -0
- package/src/core/ddl/introspect/mysql.ts +99 -0
- package/src/core/ddl/introspect/postgres.ts +154 -0
- package/src/core/ddl/introspect/sqlite.ts +66 -0
- package/src/core/ddl/introspect/types.ts +19 -0
- package/src/core/ddl/introspect/utils.ts +27 -0
- package/src/core/ddl/schema-diff.ts +179 -0
- package/src/core/ddl/schema-generator.ts +229 -0
- package/src/core/ddl/schema-introspect.ts +32 -0
- package/src/core/ddl/schema-types.ts +39 -0
- package/src/core/dialect/abstract.ts +122 -37
- package/src/core/dialect/base/sql-dialect.ts +204 -0
- package/src/core/dialect/mssql/index.ts +125 -80
- package/src/core/dialect/mysql/index.ts +18 -112
- package/src/core/dialect/postgres/index.ts +29 -126
- package/src/core/dialect/sqlite/index.ts +28 -129
- package/src/index.ts +4 -0
- package/src/orm/execute.ts +25 -16
- package/src/orm/orm-context.ts +60 -55
- package/src/orm/query-logger.ts +38 -0
- package/src/orm/relations/belongs-to.ts +42 -26
- package/src/orm/relations/has-many.ts +41 -25
- package/src/orm/relations/many-to-many.ts +43 -27
- package/src/orm/unit-of-work.ts +60 -23
- package/src/query-builder/hydration-manager.ts +229 -25
- package/src/query-builder/query-ast-service.ts +27 -12
- package/src/query-builder/select-query-state.ts +24 -12
- package/src/query-builder/select.ts +58 -14
- package/src/schema/column.ts +206 -27
- package/src/schema/table.ts +89 -32
- package/src/schema/types.ts +8 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DialectName } from './schema-generator.js';
|
|
2
|
+
import { DatabaseSchema } from './schema-types.js';
|
|
3
|
+
import { DbExecutor } from '../../orm/db-executor.js';
|
|
4
|
+
import type { IntrospectOptions, SchemaIntrospector } from './introspect/types.js';
|
|
5
|
+
import { postgresIntrospector } from './introspect/postgres.js';
|
|
6
|
+
import { mysqlIntrospector } from './introspect/mysql.js';
|
|
7
|
+
import { sqliteIntrospector } from './introspect/sqlite.js';
|
|
8
|
+
import { mssqlIntrospector } from './introspect/mssql.js';
|
|
9
|
+
|
|
10
|
+
const INTROSPECTORS: Record<DialectName, SchemaIntrospector> = {
|
|
11
|
+
postgres: postgresIntrospector,
|
|
12
|
+
mysql: mysqlIntrospector,
|
|
13
|
+
sqlite: sqliteIntrospector,
|
|
14
|
+
mssql: mssqlIntrospector
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Introspects an existing database schema using the dialect-specific strategy.
|
|
19
|
+
*/
|
|
20
|
+
export const introspectSchema = async (
|
|
21
|
+
executor: DbExecutor,
|
|
22
|
+
dialect: DialectName,
|
|
23
|
+
options: IntrospectOptions = {}
|
|
24
|
+
): Promise<DatabaseSchema> => {
|
|
25
|
+
const handler = INTROSPECTORS[dialect];
|
|
26
|
+
if (!handler) {
|
|
27
|
+
throw new Error(`Unsupported dialect for introspection: ${dialect}`);
|
|
28
|
+
}
|
|
29
|
+
return handler.introspect(executor, options);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type { IntrospectOptions, SchemaIntrospector };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ForeignKeyReference } from '../../schema/column.js';
|
|
2
|
+
import { IndexColumn } from '../../schema/table.js';
|
|
3
|
+
|
|
4
|
+
export interface DatabaseColumn {
|
|
5
|
+
name: string;
|
|
6
|
+
type: string;
|
|
7
|
+
notNull?: boolean;
|
|
8
|
+
default?: unknown;
|
|
9
|
+
autoIncrement?: boolean;
|
|
10
|
+
generated?: 'always' | 'byDefault';
|
|
11
|
+
unique?: boolean | string;
|
|
12
|
+
references?: ForeignKeyReference;
|
|
13
|
+
check?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DatabaseIndex {
|
|
17
|
+
name: string;
|
|
18
|
+
columns: IndexColumn[];
|
|
19
|
+
unique?: boolean;
|
|
20
|
+
where?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DatabaseCheck {
|
|
24
|
+
name?: string;
|
|
25
|
+
expression: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DatabaseTable {
|
|
29
|
+
name: string;
|
|
30
|
+
schema?: string;
|
|
31
|
+
columns: DatabaseColumn[];
|
|
32
|
+
primaryKey?: string[];
|
|
33
|
+
indexes?: DatabaseIndex[];
|
|
34
|
+
checks?: DatabaseCheck[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DatabaseSchema {
|
|
38
|
+
tables: DatabaseTable[];
|
|
39
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
SelectQueryNode,
|
|
3
|
+
InsertQueryNode,
|
|
4
|
+
UpdateQueryNode,
|
|
5
|
+
DeleteQueryNode,
|
|
6
|
+
SetOperationKind,
|
|
7
|
+
CommonTableExpressionNode
|
|
8
|
+
} from '../ast/query.js';
|
|
2
9
|
import {
|
|
3
10
|
ExpressionNode,
|
|
4
11
|
BinaryExpressionNode,
|
|
@@ -56,22 +63,23 @@ export interface DeleteCompiler {
|
|
|
56
63
|
/**
|
|
57
64
|
* Abstract base class for SQL dialect implementations
|
|
58
65
|
*/
|
|
59
|
-
export abstract class Dialect
|
|
60
|
-
implements SelectCompiler, InsertCompiler, UpdateCompiler, DeleteCompiler
|
|
61
|
-
{
|
|
66
|
+
export abstract class Dialect
|
|
67
|
+
implements SelectCompiler, InsertCompiler, UpdateCompiler, DeleteCompiler
|
|
68
|
+
{
|
|
62
69
|
/**
|
|
63
70
|
* Compiles a SELECT query AST to SQL
|
|
64
71
|
* @param ast - Query AST to compile
|
|
65
72
|
* @returns Compiled query with SQL and parameters
|
|
66
73
|
*/
|
|
67
|
-
compileSelect(ast: SelectQueryNode): CompiledQuery {
|
|
68
|
-
const ctx = this.createCompilerContext();
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
compileSelect(ast: SelectQueryNode): CompiledQuery {
|
|
75
|
+
const ctx = this.createCompilerContext();
|
|
76
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
77
|
+
const rawSql = this.compileSelectAst(normalized, ctx).trim();
|
|
78
|
+
const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
|
|
79
|
+
return {
|
|
80
|
+
sql,
|
|
81
|
+
params: [...ctx.params]
|
|
82
|
+
};
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
compileInsert(ast: InsertQueryNode): CompiledQuery {
|
|
@@ -94,15 +102,19 @@ export abstract class Dialect
|
|
|
94
102
|
};
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
compileDelete(ast: DeleteQueryNode): CompiledQuery {
|
|
98
|
-
const ctx = this.createCompilerContext();
|
|
99
|
-
const rawSql = this.compileDeleteAst(ast, ctx).trim();
|
|
105
|
+
compileDelete(ast: DeleteQueryNode): CompiledQuery {
|
|
106
|
+
const ctx = this.createCompilerContext();
|
|
107
|
+
const rawSql = this.compileDeleteAst(ast, ctx).trim();
|
|
100
108
|
const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
|
|
101
109
|
return {
|
|
102
110
|
sql,
|
|
103
111
|
params: [...ctx.params]
|
|
104
|
-
};
|
|
105
|
-
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
supportsReturning(): boolean {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
106
118
|
|
|
107
119
|
/**
|
|
108
120
|
* Compiles SELECT query AST to SQL (to be implemented by concrete dialects)
|
|
@@ -110,7 +122,7 @@ export abstract class Dialect
|
|
|
110
122
|
* @param ctx - Compiler context
|
|
111
123
|
* @returns SQL string
|
|
112
124
|
*/
|
|
113
|
-
protected abstract compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
|
|
125
|
+
protected abstract compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
|
|
114
126
|
|
|
115
127
|
protected abstract compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string;
|
|
116
128
|
protected abstract compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string;
|
|
@@ -149,16 +161,23 @@ export abstract class Dialect
|
|
|
149
161
|
* Does not add ';' at the end
|
|
150
162
|
* @param ast - Query AST
|
|
151
163
|
* @param ctx - Compiler context
|
|
152
|
-
* @returns SQL for EXISTS subquery
|
|
153
|
-
*/
|
|
154
|
-
protected compileSelectForExists(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
* @returns SQL for EXISTS subquery
|
|
165
|
+
*/
|
|
166
|
+
protected compileSelectForExists(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
167
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
168
|
+
const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, '');
|
|
169
|
+
|
|
170
|
+
// When the subquery is a set operation, wrap it as a derived table to keep valid syntax.
|
|
171
|
+
if (normalized.setOps && normalized.setOps.length > 0) {
|
|
172
|
+
return `SELECT 1 FROM (${full}) AS _exists`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const upper = full.toUpperCase();
|
|
176
|
+
const fromIndex = upper.indexOf(' FROM ');
|
|
177
|
+
if (fromIndex === -1) {
|
|
178
|
+
return full;
|
|
179
|
+
}
|
|
180
|
+
|
|
162
181
|
const tail = full.slice(fromIndex);
|
|
163
182
|
return `SELECT 1${tail}`;
|
|
164
183
|
}
|
|
@@ -185,15 +204,81 @@ export abstract class Dialect
|
|
|
185
204
|
* @param index - Parameter index
|
|
186
205
|
* @returns Formatted placeholder string
|
|
187
206
|
*/
|
|
188
|
-
protected formatPlaceholder(index: number): string {
|
|
189
|
-
return '?';
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
207
|
+
protected formatPlaceholder(index: number): string {
|
|
208
|
+
return '?';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Whether the current dialect supports a given set operation.
|
|
213
|
+
* Override in concrete dialects to restrict support.
|
|
214
|
+
*/
|
|
215
|
+
protected supportsSetOperation(kind: SetOperationKind): boolean {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Validates set-operation semantics:
|
|
221
|
+
* - Ensures the dialect supports requested operators.
|
|
222
|
+
* - Enforces that only the outermost compound query may have ORDER/LIMIT/OFFSET.
|
|
223
|
+
* @param ast - Query to validate
|
|
224
|
+
* @param isOutermost - Whether this node is the outermost compound query
|
|
225
|
+
*/
|
|
226
|
+
protected validateSetOperations(ast: SelectQueryNode, isOutermost = true): void {
|
|
227
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
228
|
+
if (!isOutermost && (ast.orderBy || ast.limit !== undefined || ast.offset !== undefined)) {
|
|
229
|
+
throw new Error('ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (hasSetOps) {
|
|
233
|
+
for (const op of ast.setOps!) {
|
|
234
|
+
if (!this.supportsSetOperation(op.operator)) {
|
|
235
|
+
throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
|
|
236
|
+
}
|
|
237
|
+
this.validateSetOperations(op.query, false);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Hoists CTEs from set-operation operands to the outermost query so WITH appears once.
|
|
244
|
+
* @param ast - Query AST
|
|
245
|
+
* @returns Normalized AST without inner CTEs and a list of hoisted CTEs
|
|
246
|
+
*/
|
|
247
|
+
private hoistCtes(ast: SelectQueryNode): { normalized: SelectQueryNode; hoistedCtes: CommonTableExpressionNode[] } {
|
|
248
|
+
let hoisted: CommonTableExpressionNode[] = [];
|
|
249
|
+
|
|
250
|
+
const normalizedSetOps = ast.setOps?.map(op => {
|
|
251
|
+
const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
|
|
252
|
+
const childCtes = child.ctes ?? [];
|
|
253
|
+
if (childCtes.length) {
|
|
254
|
+
hoisted = hoisted.concat(childCtes);
|
|
255
|
+
}
|
|
256
|
+
hoisted = hoisted.concat(childHoisted);
|
|
257
|
+
const queryWithoutCtes = childCtes.length ? { ...child, ctes: undefined } : child;
|
|
258
|
+
return { ...op, query: queryWithoutCtes };
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const normalized: SelectQueryNode = normalizedSetOps ? { ...ast, setOps: normalizedSetOps } : ast;
|
|
262
|
+
return { normalized, hoistedCtes: hoisted };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Normalizes a SELECT AST before compilation (validation + CTE hoisting).
|
|
267
|
+
* @param ast - Query AST
|
|
268
|
+
* @returns Normalized query AST
|
|
269
|
+
*/
|
|
270
|
+
protected normalizeSelectAst(ast: SelectQueryNode): SelectQueryNode {
|
|
271
|
+
this.validateSetOperations(ast, true);
|
|
272
|
+
const { normalized, hoistedCtes } = this.hoistCtes(ast);
|
|
273
|
+
const combinedCtes = [...(normalized.ctes ?? []), ...hoistedCtes];
|
|
274
|
+
return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private readonly expressionCompilers: Map<string, (node: ExpressionNode, ctx: CompilerContext) => string>;
|
|
278
|
+
private readonly operandCompilers: Map<string, (node: OperandNode, ctx: CompilerContext) => string>;
|
|
279
|
+
|
|
280
|
+
protected constructor() {
|
|
281
|
+
this.expressionCompilers = new Map();
|
|
197
282
|
this.operandCompilers = new Map();
|
|
198
283
|
this.registerDefaultOperandCompilers();
|
|
199
284
|
this.registerDefaultExpressionCompilers();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { CompilerContext, Dialect } from '../abstract.js';
|
|
2
|
+
import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
|
|
3
|
+
import { ColumnNode } from '../../ast/expression.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared SQL compiler for dialects with standard LIMIT/OFFSET pagination.
|
|
7
|
+
* Concrete dialects override only the minimal hooks (identifier quoting,
|
|
8
|
+
* JSON path, placeholders, RETURNING support) instead of re-implementing
|
|
9
|
+
* the entire compile pipeline.
|
|
10
|
+
*/
|
|
11
|
+
export abstract class SqlDialectBase extends Dialect {
|
|
12
|
+
/**
|
|
13
|
+
* Quotes an identifier (dialect-specific).
|
|
14
|
+
*/
|
|
15
|
+
abstract quoteIdentifier(id: string): string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compiles SELECT query AST to SQL using common rules.
|
|
19
|
+
*/
|
|
20
|
+
protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
21
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
22
|
+
const ctes = this.compileCtes(ast, ctx);
|
|
23
|
+
|
|
24
|
+
// When set operations exist, omit ORDER BY/OFFSET/LIMIT from the operands and apply at the end.
|
|
25
|
+
const baseAst: SelectQueryNode = hasSetOps
|
|
26
|
+
? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
|
|
27
|
+
: ast;
|
|
28
|
+
|
|
29
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
30
|
+
|
|
31
|
+
if (!hasSetOps) {
|
|
32
|
+
return `${ctes}${baseSelect}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const compound = ast.setOps!
|
|
36
|
+
.map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
|
|
37
|
+
.join(' ');
|
|
38
|
+
|
|
39
|
+
const orderBy = this.compileOrderBy(ast);
|
|
40
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
41
|
+
|
|
42
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
43
|
+
return `${ctes}${combined}${orderBy}${pagination}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
47
|
+
const table = this.compileTableName(ast.into);
|
|
48
|
+
const columnList = ast.columns
|
|
49
|
+
.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`)
|
|
50
|
+
.join(', ');
|
|
51
|
+
const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
|
|
52
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
53
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compiles a single SELECT (no set operations, no CTE prefix).
|
|
58
|
+
*/
|
|
59
|
+
private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
60
|
+
const columns = this.compileSelectColumns(ast, ctx);
|
|
61
|
+
const from = this.compileFrom(ast.from);
|
|
62
|
+
const joins = this.compileJoins(ast, ctx);
|
|
63
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
64
|
+
const groupBy = this.compileGroupBy(ast);
|
|
65
|
+
const having = this.compileHaving(ast, ctx);
|
|
66
|
+
const orderBy = this.compileOrderBy(ast);
|
|
67
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
68
|
+
|
|
69
|
+
return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
73
|
+
const table = this.compileTableName(ast.table);
|
|
74
|
+
const assignments = ast.set.map(assignment => {
|
|
75
|
+
const col = assignment.column;
|
|
76
|
+
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
77
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
78
|
+
return `${target} = ${value}`;
|
|
79
|
+
}).join(', ');
|
|
80
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
81
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
82
|
+
return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
86
|
+
const table = this.compileTableName(ast.from);
|
|
87
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
88
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
89
|
+
return `DELETE FROM ${table}${whereClause}${returning}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Default RETURNING compilation: no support.
|
|
94
|
+
*/
|
|
95
|
+
protected compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
|
|
96
|
+
if (!returning || returning.length === 0) return '';
|
|
97
|
+
throw new Error('RETURNING is not supported by this dialect.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected formatReturningColumns(returning: ColumnNode[]): string {
|
|
101
|
+
return returning
|
|
102
|
+
.map(column => {
|
|
103
|
+
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : '';
|
|
104
|
+
const aliasPart = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : '';
|
|
105
|
+
return `${tablePart}${this.quoteIdentifier(column.name)}${aliasPart}`;
|
|
106
|
+
})
|
|
107
|
+
.join(', ');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* DISTINCT clause. Override for DISTINCT ON support.
|
|
112
|
+
*/
|
|
113
|
+
protected compileDistinct(ast: SelectQueryNode): string {
|
|
114
|
+
return ast.distinct ? 'DISTINCT ' : '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
118
|
+
return ast.columns.map(c => {
|
|
119
|
+
const expr = this.compileOperand(c, ctx);
|
|
120
|
+
if (c.alias) {
|
|
121
|
+
if (c.alias.includes('(')) return c.alias;
|
|
122
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
123
|
+
}
|
|
124
|
+
return expr;
|
|
125
|
+
}).join(', ');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected compileFrom(ast: SelectQueryNode['from']): string {
|
|
129
|
+
const base = this.compileTableName(ast);
|
|
130
|
+
return ast.alias ? `${base} AS ${this.quoteIdentifier(ast.alias)}` : base;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected compileTableName(table: { name: string; schema?: string }): string {
|
|
134
|
+
if (table.schema) {
|
|
135
|
+
return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
|
|
136
|
+
}
|
|
137
|
+
return this.quoteIdentifier(table.name);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected compileJoins(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
141
|
+
if (!ast.joins || ast.joins.length === 0) return '';
|
|
142
|
+
const parts = ast.joins.map(j => {
|
|
143
|
+
const table = this.compileFrom(j.table);
|
|
144
|
+
const cond = this.compileExpression(j.condition, ctx);
|
|
145
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
146
|
+
});
|
|
147
|
+
return ` ${parts.join(' ')}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected compileGroupBy(ast: SelectQueryNode): string {
|
|
151
|
+
if (!ast.groupBy || ast.groupBy.length === 0) return '';
|
|
152
|
+
const cols = ast.groupBy
|
|
153
|
+
.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`)
|
|
154
|
+
.join(', ');
|
|
155
|
+
return ` GROUP BY ${cols}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
159
|
+
if (!ast.having) return '';
|
|
160
|
+
return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected compileOrderBy(ast: SelectQueryNode): string {
|
|
164
|
+
if (!ast.orderBy || ast.orderBy.length === 0) return '';
|
|
165
|
+
const parts = ast.orderBy
|
|
166
|
+
.map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`)
|
|
167
|
+
.join(', ');
|
|
168
|
+
return ` ORDER BY ${parts}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Default LIMIT/OFFSET pagination clause.
|
|
173
|
+
*/
|
|
174
|
+
protected compilePagination(ast: SelectQueryNode, _orderByClause: string): string {
|
|
175
|
+
const parts: string[] = [];
|
|
176
|
+
if (ast.limit !== undefined) parts.push(`LIMIT ${ast.limit}`);
|
|
177
|
+
if (ast.offset !== undefined) parts.push(`OFFSET ${ast.offset}`);
|
|
178
|
+
return parts.length ? ` ${parts.join(' ')}` : '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
182
|
+
if (!ast.ctes || ast.ctes.length === 0) return '';
|
|
183
|
+
const hasRecursive = ast.ctes.some(cte => cte.recursive);
|
|
184
|
+
const prefix = hasRecursive ? 'WITH RECURSIVE ' : 'WITH ';
|
|
185
|
+
const cteDefs = ast.ctes.map(cte => {
|
|
186
|
+
const name = this.quoteIdentifier(cte.name);
|
|
187
|
+
const cols = cte.columns && cte.columns.length
|
|
188
|
+
? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})`
|
|
189
|
+
: '';
|
|
190
|
+
const query = this.stripTrailingSemicolon(this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx));
|
|
191
|
+
return `${name}${cols} AS (${query})`;
|
|
192
|
+
}).join(', ');
|
|
193
|
+
return `${prefix}${cteDefs} `;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
protected stripTrailingSemicolon(sql: string): string {
|
|
197
|
+
return sql.trim().replace(/;$/, '');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected wrapSetOperand(sql: string): string {
|
|
201
|
+
const trimmed = this.stripTrailingSemicolon(sql);
|
|
202
|
+
return `(${trimmed})`;
|
|
203
|
+
}
|
|
204
|
+
}
|