metal-orm 1.1.25 → 1.1.27
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 +1151 -510
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +281 -248
- package/dist/index.d.ts +281 -248
- package/dist/index.js +1133 -508
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ddl/dialects/index.ts +5 -6
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +129 -126
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +119 -111
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +173 -164
- package/src/core/ddl/dialects/render-reference.test.ts +37 -57
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +110 -121
- package/src/core/ddl/schema-dialect-composer.ts +129 -0
- package/src/core/ddl/schema-dialect.ts +40 -27
- package/src/core/ddl/schema-diff.ts +119 -90
- package/src/core/dialect/abstract.ts +7 -229
- 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 +1 -2
- package/src/core/dialect/dialect-factory.ts +17 -49
- package/src/core/dialect/mssql/index.ts +56 -27
- package/src/core/dialect/mysql/index.ts +69 -36
- package/src/core/dialect/postgres/index.ts +71 -43
- package/src/core/dialect/sqlite/index.ts +68 -38
- package/src/core/driver/mssql-driver.ts +6 -8
- package/src/core/driver/mysql-driver.ts +6 -8
- package/src/core/driver/postgres-driver.ts +6 -8
- package/src/core/driver/sqlite-driver.ts +6 -8
- package/src/index.ts +13 -1
- package/src/core/ddl/dialects/base-schema-dialect.ts +0 -96
- package/src/core/dialect/base/sql-dialect.ts +0 -217
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { TableDef } from '../../schema/table.js';
|
|
2
|
-
import { ColumnDef } from '../../schema/column-types.js';
|
|
1
|
+
import type { TableDef } from '../../schema/table.js';
|
|
2
|
+
import type { ColumnDef } from '../../schema/column-types.js';
|
|
3
3
|
import type { DbExecutor } from '../execution/db-executor.js';
|
|
4
|
-
import { SchemaDialect } from './schema-dialect.js';
|
|
4
|
+
import type { SchemaDialect } from './schema-dialect.js';
|
|
5
5
|
import { deriveIndexName } from './naming-strategy.js';
|
|
6
6
|
import { generateCreateTableSql, renderColumnDefinition } from './schema-generator.js';
|
|
7
|
-
import { ColumnDiff, DatabaseColumn, DatabaseSchema, DatabaseTable } from './schema-types.js';
|
|
7
|
+
import type { ColumnDiff, DatabaseColumn, DatabaseSchema, DatabaseTable } from './schema-types.js';
|
|
8
8
|
|
|
9
|
-
/** The kind of schema change. */
|
|
10
9
|
export type SchemaChangeKind =
|
|
11
10
|
| 'createTable'
|
|
12
11
|
| 'dropTable'
|
|
@@ -16,7 +15,6 @@ export type SchemaChangeKind =
|
|
|
16
15
|
| 'addIndex'
|
|
17
16
|
| 'dropIndex';
|
|
18
17
|
|
|
19
|
-
/** Represents a single schema change. */
|
|
20
18
|
export interface SchemaChange {
|
|
21
19
|
kind: SchemaChangeKind;
|
|
22
20
|
table: string;
|
|
@@ -25,45 +23,47 @@ export interface SchemaChange {
|
|
|
25
23
|
safe: boolean;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
/** Represents a plan of schema changes. */
|
|
29
26
|
export interface SchemaPlan {
|
|
30
27
|
changes: SchemaChange[];
|
|
31
28
|
warnings: string[];
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
/** Options for schema diffing. */
|
|
35
31
|
export interface SchemaDiffOptions {
|
|
36
|
-
/** Allow destructive operations (drops) */
|
|
37
32
|
allowDestructive?: boolean;
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
const tableKey = (name: string, schema?: string) =>
|
|
35
|
+
const tableKey = (name: string, schema?: string): string => schema ? `${schema}.${name}` : name;
|
|
41
36
|
|
|
42
|
-
const mapTables = (schema: DatabaseSchema) => {
|
|
37
|
+
const mapTables = (schema: DatabaseSchema): Map<string, DatabaseTable> => {
|
|
43
38
|
const map = new Map<string, DatabaseTable>();
|
|
44
|
-
for (const table of schema.tables)
|
|
45
|
-
map.set(tableKey(table.name, table.schema), table);
|
|
46
|
-
}
|
|
39
|
+
for (const table of schema.tables) map.set(tableKey(table.name, table.schema), table);
|
|
47
40
|
return map;
|
|
48
41
|
};
|
|
49
42
|
|
|
50
|
-
const buildAddColumnSql = (table: TableDef,
|
|
51
|
-
const column = table.columns[
|
|
43
|
+
const buildAddColumnSql = (table: TableDef, columnName: string, dialect: SchemaDialect): string => {
|
|
44
|
+
const column = table.columns[columnName];
|
|
52
45
|
const rendered = renderColumnDefinition(table, column, dialect);
|
|
53
46
|
return `ALTER TABLE ${dialect.formatTableName(table)} ADD ${rendered.sql};`;
|
|
54
47
|
};
|
|
55
48
|
|
|
56
|
-
const normalizeType = (value: string | undefined): string =>
|
|
49
|
+
const normalizeType = (value: string | undefined): string =>
|
|
50
|
+
(value || '').toLowerCase().replace(/\s+/g, ' ').trim();
|
|
51
|
+
|
|
57
52
|
const normalizeDefault = (value: unknown): string | undefined => {
|
|
58
53
|
if (value === undefined || value === null) return undefined;
|
|
59
54
|
return String(value).trim();
|
|
60
55
|
};
|
|
61
56
|
|
|
62
|
-
const diffColumn = (
|
|
57
|
+
const diffColumn = (
|
|
58
|
+
expected: ColumnDef,
|
|
59
|
+
actual: DatabaseColumn,
|
|
60
|
+
dialect: SchemaDialect
|
|
61
|
+
): ColumnDiff => {
|
|
63
62
|
const expectedType = normalizeType(dialect.renderColumnType(expected));
|
|
64
63
|
const actualType = normalizeType(actual.type);
|
|
65
|
-
const expectedDefault =
|
|
66
|
-
|
|
64
|
+
const expectedDefault = expected.default !== undefined
|
|
65
|
+
? normalizeDefault(dialect.renderDefault(expected.default, expected))
|
|
66
|
+
: undefined;
|
|
67
67
|
const actualDefault = normalizeDefault(actual.default);
|
|
68
68
|
return {
|
|
69
69
|
typeChanged: expectedType !== actualType,
|
|
@@ -73,14 +73,13 @@ const diffColumn = (expected: ColumnDef, actual: DatabaseColumn, dialect: Schema
|
|
|
73
73
|
};
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
*/
|
|
76
|
+
const unsupportedMutationWarning = (
|
|
77
|
+
dialect: SchemaDialect,
|
|
78
|
+
operation: string,
|
|
79
|
+
target: string
|
|
80
|
+
): string =>
|
|
81
|
+
`Dialect "${dialect.name}" does not provide the ${operation} capability for ${target}; manual migration is required.`;
|
|
82
|
+
|
|
84
83
|
export const diffSchema = (
|
|
85
84
|
expectedTables: TableDef[],
|
|
86
85
|
actualSchema: DatabaseSchema,
|
|
@@ -89,10 +88,8 @@ export const diffSchema = (
|
|
|
89
88
|
): SchemaPlan => {
|
|
90
89
|
const allowDestructive = options.allowDestructive ?? false;
|
|
91
90
|
const plan: SchemaPlan = { changes: [], warnings: [] };
|
|
92
|
-
|
|
93
91
|
const actualMap = mapTables(actualSchema);
|
|
94
92
|
|
|
95
|
-
// Create missing tables and indexes
|
|
96
93
|
for (const table of expectedTables) {
|
|
97
94
|
const key = tableKey(table.name, table.schema);
|
|
98
95
|
const actual = actualMap.get(key);
|
|
@@ -108,115 +105,148 @@ export const diffSchema = (
|
|
|
108
105
|
continue;
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
if (!actualCols.has(colName)) {
|
|
108
|
+
const actualColumns = new Map(actual.columns.map(column => [column.name, column]));
|
|
109
|
+
for (const columnName of Object.keys(table.columns)) {
|
|
110
|
+
if (!actualColumns.has(columnName)) {
|
|
115
111
|
plan.changes.push({
|
|
116
112
|
kind: 'addColumn',
|
|
117
113
|
table: key,
|
|
118
|
-
description: `Add column ${
|
|
119
|
-
statements: [buildAddColumnSql(table,
|
|
114
|
+
description: `Add column ${columnName} to ${key}`,
|
|
115
|
+
statements: [buildAddColumnSql(table, columnName, dialect)],
|
|
120
116
|
safe: true
|
|
121
117
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const expectedColumn = table.columns[columnName];
|
|
122
|
+
const actualColumn = actualColumns.get(columnName)!;
|
|
123
|
+
const columnDiff = diffColumn(expectedColumn, actualColumn, dialect);
|
|
124
|
+
const shouldAlter =
|
|
125
|
+
columnDiff.typeChanged
|
|
126
|
+
|| columnDiff.nullabilityChanged
|
|
127
|
+
|| columnDiff.defaultChanged
|
|
128
|
+
|| columnDiff.autoIncrementChanged;
|
|
129
|
+
|
|
130
|
+
if (shouldAlter) {
|
|
131
|
+
const capability = dialect.mutations.alterColumn;
|
|
132
|
+
if (capability) {
|
|
133
|
+
const statements = capability.compile(table, expectedColumn, actualColumn, columnDiff);
|
|
130
134
|
if (statements.length > 0) {
|
|
131
135
|
plan.changes.push({
|
|
132
136
|
kind: 'alterColumn',
|
|
133
137
|
table: key,
|
|
134
|
-
description: `Alter column ${
|
|
138
|
+
description: `Alter column ${columnName} on ${key}`,
|
|
135
139
|
statements,
|
|
136
140
|
safe: true
|
|
137
141
|
});
|
|
138
142
|
}
|
|
139
|
-
const warning =
|
|
143
|
+
const warning = capability.warning?.(table, expectedColumn, actualColumn, columnDiff);
|
|
140
144
|
if (warning) plan.warnings.push(warning);
|
|
145
|
+
} else {
|
|
146
|
+
plan.warnings.push(
|
|
147
|
+
unsupportedMutationWarning(dialect, 'ALTER COLUMN', `${key}.${columnName}`)
|
|
148
|
+
);
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
|
|
153
|
+
for (const columnName of actualColumns.keys()) {
|
|
154
|
+
if (table.columns[columnName]) continue;
|
|
155
|
+
const capability = dialect.mutations.dropColumn;
|
|
156
|
+
const statements = allowDestructive && capability
|
|
157
|
+
? capability.compile(actual, columnName)
|
|
158
|
+
: [];
|
|
159
|
+
plan.changes.push({
|
|
160
|
+
kind: 'dropColumn',
|
|
161
|
+
table: key,
|
|
162
|
+
description: `Drop column ${columnName} from ${key}`,
|
|
163
|
+
statements,
|
|
164
|
+
safe: false
|
|
165
|
+
});
|
|
166
|
+
if (!capability) {
|
|
167
|
+
plan.warnings.push(
|
|
168
|
+
unsupportedMutationWarning(dialect, 'DROP COLUMN', `${key}.${columnName}`)
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
const warning = capability.warning?.(actual, columnName);
|
|
154
172
|
if (warning) plan.warnings.push(warning);
|
|
155
173
|
}
|
|
156
174
|
}
|
|
157
175
|
|
|
158
|
-
// Indexes (naive: based on name or derived name)
|
|
159
176
|
const expectedIndexes = table.indexes ?? [];
|
|
160
177
|
const actualIndexes = actual.indexes ?? [];
|
|
161
|
-
const actualIndexMap = new Map(actualIndexes.map(
|
|
178
|
+
const actualIndexMap = new Map(actualIndexes.map(index => [index.name, index]));
|
|
162
179
|
|
|
163
|
-
for (const
|
|
164
|
-
const name =
|
|
180
|
+
for (const index of expectedIndexes) {
|
|
181
|
+
const name = index.name || deriveIndexName(table, index);
|
|
165
182
|
if (!actualIndexMap.has(name)) {
|
|
166
183
|
plan.changes.push({
|
|
167
184
|
kind: 'addIndex',
|
|
168
185
|
table: key,
|
|
169
186
|
description: `Create index ${name} on ${key}`,
|
|
170
|
-
statements: [dialect.renderIndex(table, { ...
|
|
187
|
+
statements: [dialect.renderIndex(table, { ...index, name })],
|
|
171
188
|
safe: true
|
|
172
189
|
});
|
|
173
190
|
}
|
|
174
191
|
}
|
|
175
192
|
|
|
176
|
-
for (const
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
193
|
+
for (const index of actualIndexes) {
|
|
194
|
+
if (!index.name) continue;
|
|
195
|
+
const expected = expectedIndexes.find(
|
|
196
|
+
candidate => (candidate.name || deriveIndexName(table, candidate)) === index.name
|
|
197
|
+
);
|
|
198
|
+
if (expected) continue;
|
|
199
|
+
|
|
200
|
+
const capability = dialect.mutations.dropIndex;
|
|
201
|
+
const statements = allowDestructive && capability
|
|
202
|
+
? capability.compile(actual, index.name)
|
|
203
|
+
: [];
|
|
204
|
+
plan.changes.push({
|
|
205
|
+
kind: 'dropIndex',
|
|
206
|
+
table: key,
|
|
207
|
+
description: `Drop index ${index.name} on ${key}`,
|
|
208
|
+
statements,
|
|
209
|
+
safe: false
|
|
210
|
+
});
|
|
211
|
+
if (!capability) {
|
|
212
|
+
plan.warnings.push(
|
|
213
|
+
unsupportedMutationWarning(dialect, 'DROP INDEX', `${key}.${index.name}`)
|
|
214
|
+
);
|
|
215
|
+
} else {
|
|
216
|
+
const warning = capability.warning?.(actual, index.name);
|
|
217
|
+
if (warning) plan.warnings.push(warning);
|
|
185
218
|
}
|
|
186
219
|
}
|
|
187
220
|
}
|
|
188
221
|
|
|
189
|
-
// Extra tables
|
|
190
222
|
for (const actual of actualSchema.tables) {
|
|
191
223
|
const key = tableKey(actual.name, actual.schema);
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
224
|
+
if (expectedTables.find(table => tableKey(table.name, table.schema) === key)) continue;
|
|
225
|
+
|
|
226
|
+
const capability = dialect.mutations.dropTable;
|
|
227
|
+
const statements = allowDestructive && capability ? capability.compile(actual) : [];
|
|
228
|
+
plan.changes.push({
|
|
229
|
+
kind: 'dropTable',
|
|
230
|
+
table: key,
|
|
231
|
+
description: `Drop table ${key}`,
|
|
232
|
+
statements,
|
|
233
|
+
safe: false
|
|
234
|
+
});
|
|
235
|
+
if (!capability) {
|
|
236
|
+
plan.warnings.push(unsupportedMutationWarning(dialect, 'DROP TABLE', key));
|
|
237
|
+
} else {
|
|
238
|
+
const warning = capability.warning?.(actual);
|
|
239
|
+
if (warning) plan.warnings.push(warning);
|
|
200
240
|
}
|
|
201
241
|
}
|
|
202
242
|
|
|
203
243
|
return plan;
|
|
204
244
|
};
|
|
205
245
|
|
|
206
|
-
/** Options for schema synchronization. */
|
|
207
246
|
export interface SynchronizeOptions extends SchemaDiffOptions {
|
|
208
247
|
dryRun?: boolean;
|
|
209
248
|
}
|
|
210
249
|
|
|
211
|
-
/**
|
|
212
|
-
* Synchronizes the database schema with the expected tables.
|
|
213
|
-
* @param expectedTables - The expected table definitions.
|
|
214
|
-
* @param actualSchema - The actual database schema.
|
|
215
|
-
* @param dialect - The schema dialect.
|
|
216
|
-
* @param executor - The database executor.
|
|
217
|
-
* @param options - Options for synchronization.
|
|
218
|
-
* @returns The schema plan with changes and warnings.
|
|
219
|
-
*/
|
|
220
250
|
export const synchronizeSchema = async (
|
|
221
251
|
expectedTables: TableDef[],
|
|
222
252
|
actualSchema: DatabaseSchema,
|
|
@@ -231,4 +261,3 @@ export const synchronizeSchema = async (
|
|
|
231
261
|
}
|
|
232
262
|
return plan;
|
|
233
263
|
};
|
|
234
|
-
|
|
@@ -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
|
-
}
|