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.
Files changed (32) hide show
  1. package/dist/index.cjs +1151 -510
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +281 -248
  4. package/dist/index.d.ts +281 -248
  5. package/dist/index.js +1133 -508
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/core/ddl/dialects/index.ts +5 -6
  9. package/src/core/ddl/dialects/mssql-schema-dialect.ts +129 -126
  10. package/src/core/ddl/dialects/mysql-schema-dialect.ts +119 -111
  11. package/src/core/ddl/dialects/postgres-schema-dialect.ts +173 -164
  12. package/src/core/ddl/dialects/render-reference.test.ts +37 -57
  13. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +110 -121
  14. package/src/core/ddl/schema-dialect-composer.ts +129 -0
  15. package/src/core/ddl/schema-dialect.ts +40 -27
  16. package/src/core/ddl/schema-diff.ts +119 -90
  17. package/src/core/dialect/abstract.ts +7 -229
  18. package/src/core/dialect/base/sql-dialect-composer.ts +294 -0
  19. package/src/core/dialect/base/standard-sql-services.ts +2 -6
  20. package/src/core/dialect/base/upsert-strategy.ts +1 -2
  21. package/src/core/dialect/dialect-factory.ts +17 -49
  22. package/src/core/dialect/mssql/index.ts +56 -27
  23. package/src/core/dialect/mysql/index.ts +69 -36
  24. package/src/core/dialect/postgres/index.ts +71 -43
  25. package/src/core/dialect/sqlite/index.ts +68 -38
  26. package/src/core/driver/mssql-driver.ts +6 -8
  27. package/src/core/driver/mysql-driver.ts +6 -8
  28. package/src/core/driver/postgres-driver.ts +6 -8
  29. package/src/core/driver/sqlite-driver.ts +6 -8
  30. package/src/index.ts +13 -1
  31. package/src/core/ddl/dialects/base-schema-dialect.ts +0 -96
  32. 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) => (schema ? `${schema}.${name}` : name);
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, colName: string, dialect: SchemaDialect): string => {
51
- const column = table.columns[colName];
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 => (value || '').toLowerCase().replace(/\s+/g, ' ').trim();
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 = (expected: ColumnDef, actual: DatabaseColumn, dialect: SchemaDialect): ColumnDiff => {
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
- expected.default !== undefined ? normalizeDefault(dialect.renderDefault(expected.default, expected)) : undefined;
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
- * Computes the differences between expected and actual database schemas.
78
- * @param expectedTables - The expected table definitions.
79
- * @param actualSchema - The actual database schema.
80
- * @param dialect - The schema dialect.
81
- * @param options - Options for the diff.
82
- * @returns The schema plan with changes and warnings.
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
- // Columns
112
- const actualCols = new Map(actual.columns.map(c => [c.name, c]));
113
- for (const colName of Object.keys(table.columns)) {
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 ${colName} to ${key}`,
119
- statements: [buildAddColumnSql(table, colName, dialect)],
114
+ description: `Add column ${columnName} to ${key}`,
115
+ statements: [buildAddColumnSql(table, columnName, dialect)],
120
116
  safe: true
121
117
  });
122
- } else {
123
- const expectedCol = table.columns[colName];
124
- const actualCol = actualCols.get(colName)!;
125
- const colDiff = diffColumn(expectedCol, actualCol, dialect);
126
- const shouldAlter =
127
- colDiff.typeChanged || colDiff.nullabilityChanged || colDiff.defaultChanged || colDiff.autoIncrementChanged;
128
- if (shouldAlter) {
129
- const statements = dialect.alterColumnSql?.(table, expectedCol, actualCol, colDiff) ?? [];
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 ${colName} on ${key}`,
138
+ description: `Alter column ${columnName} on ${key}`,
135
139
  statements,
136
140
  safe: true
137
141
  });
138
142
  }
139
- const warning = dialect.warnAlterColumn?.(table, expectedCol, actualCol, colDiff);
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
- for (const colName of actualCols.keys()) {
145
- if (!table.columns[colName]) {
146
- plan.changes.push({
147
- kind: 'dropColumn',
148
- table: key,
149
- description: `Drop column ${colName} from ${key}`,
150
- statements: allowDestructive ? dialect.dropColumnSql(actual, colName) : [],
151
- safe: false
152
- });
153
- const warning = dialect.warnDropColumn?.(actual, colName);
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(idx => [idx.name, idx]));
178
+ const actualIndexMap = new Map(actualIndexes.map(index => [index.name, index]));
162
179
 
163
- for (const idx of expectedIndexes) {
164
- const name = idx.name || deriveIndexName(table, idx);
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, { ...idx, name })],
187
+ statements: [dialect.renderIndex(table, { ...index, name })],
171
188
  safe: true
172
189
  });
173
190
  }
174
191
  }
175
192
 
176
- for (const idx of actualIndexes) {
177
- if (idx.name && !expectedIndexes.find(expected => (expected.name || deriveIndexName(table, expected)) === idx.name)) {
178
- plan.changes.push({
179
- kind: 'dropIndex',
180
- table: key,
181
- description: `Drop index ${idx.name} on ${key}`,
182
- statements: allowDestructive ? dialect.dropIndexSql(actual, idx.name) : [],
183
- safe: false
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 (!expectedTables.find(t => tableKey(t.name, t.schema) === key)) {
193
- plan.changes.push({
194
- kind: 'dropTable',
195
- table: key,
196
- description: `Drop table ${key}`,
197
- statements: allowDestructive ? dialect.dropTableSql(actual) : [],
198
- safe: false
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
- SetOperationKind,
7
- OrderingTerm
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
- * Public dialect contract consumed by builders and the ORM runtime.
54
- * Optional backend features such as stored procedures live in dedicated
55
- * capability interfaces; mutation-wide behavior shared by the runtime stays
56
- * in this small core contract.
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
- }