metal-orm 1.0.40 → 1.0.42

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 (45) hide show
  1. package/README.md +53 -14
  2. package/dist/index.cjs +1298 -126
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +676 -30
  5. package/dist/index.d.ts +676 -30
  6. package/dist/index.js +1293 -126
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/codegen/typescript.ts +6 -2
  10. package/src/core/ast/expression-builders.ts +25 -4
  11. package/src/core/ast/expression-nodes.ts +3 -1
  12. package/src/core/ast/expression.ts +2 -2
  13. package/src/core/ast/query.ts +24 -2
  14. package/src/core/dialect/abstract.ts +6 -2
  15. package/src/core/dialect/base/join-compiler.ts +9 -12
  16. package/src/core/dialect/base/sql-dialect.ts +98 -17
  17. package/src/core/dialect/mssql/index.ts +30 -62
  18. package/src/core/dialect/sqlite/index.ts +39 -34
  19. package/src/core/execution/db-executor.ts +46 -6
  20. package/src/core/execution/executors/mssql-executor.ts +39 -22
  21. package/src/core/execution/executors/mysql-executor.ts +23 -6
  22. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  23. package/src/core/execution/pooling/pool-types.ts +30 -0
  24. package/src/core/execution/pooling/pool.ts +268 -0
  25. package/src/decorators/bootstrap.ts +7 -7
  26. package/src/index.ts +6 -0
  27. package/src/orm/domain-event-bus.ts +49 -0
  28. package/src/orm/entity-metadata.ts +9 -9
  29. package/src/orm/entity.ts +58 -0
  30. package/src/orm/orm-session.ts +465 -270
  31. package/src/orm/orm.ts +61 -11
  32. package/src/orm/pooled-executor-factory.ts +131 -0
  33. package/src/orm/query-logger.ts +6 -12
  34. package/src/orm/relation-change-processor.ts +75 -0
  35. package/src/orm/relations/many-to-many.ts +4 -2
  36. package/src/orm/save-graph.ts +303 -0
  37. package/src/orm/transaction-runner.ts +3 -3
  38. package/src/orm/unit-of-work.ts +128 -0
  39. package/src/query-builder/delete-query-state.ts +67 -38
  40. package/src/query-builder/delete.ts +37 -1
  41. package/src/query-builder/insert-query-state.ts +131 -61
  42. package/src/query-builder/insert.ts +27 -1
  43. package/src/query-builder/update-query-state.ts +114 -77
  44. package/src/query-builder/update.ts +38 -1
  45. package/src/schema/table.ts +210 -115
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.40",
3
+ "version": "1.0.42",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -293,9 +293,13 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
293
293
  */
294
294
  private printInExpression(inExpr: InExpressionNode): string {
295
295
  const left = this.printOperand(inExpr.left);
296
- const values = inExpr.right.map(v => this.printOperand(v)).join(', ');
297
296
  const fn = this.mapOp(inExpr.operator);
298
- return `${fn}(${left}, [${values}])`;
297
+ if (Array.isArray(inExpr.right)) {
298
+ const values = inExpr.right.map(v => this.printOperand(v)).join(', ');
299
+ return `${fn}(${left}, [${values}])`;
300
+ }
301
+ const subquery = this.inlineChain(this.buildSelectLines(inExpr.right.query));
302
+ return `${fn}(${left}, (${subquery}))`;
299
303
  }
300
304
 
301
305
  /**
@@ -15,6 +15,8 @@ import {
15
15
  NullExpressionNode,
16
16
  InExpressionNode,
17
17
  ExistsExpressionNode,
18
+ InExpressionRight,
19
+ ScalarSubqueryNode,
18
20
  BetweenExpressionNode,
19
21
  isOperandNode,
20
22
  AliasRefNode,
@@ -65,6 +67,19 @@ const toOperand = (val: OperandNode | ColumnRef | LiteralValue): OperandNode =>
65
67
  return toNode(val);
66
68
  };
67
69
 
70
+ export type SelectQueryInput = SelectQueryNode | { getAST(): SelectQueryNode };
71
+
72
+ const hasQueryAst = (value: SelectQueryInput): value is { getAST(): SelectQueryNode } =>
73
+ typeof (value as { getAST?: unknown }).getAST === 'function';
74
+
75
+ const resolveSelectQueryNode = (query: SelectQueryInput): SelectQueryNode =>
76
+ hasQueryAst(query) ? query.getAST() : query;
77
+
78
+ const toScalarSubqueryNode = (query: SelectQueryInput): ScalarSubqueryNode => ({
79
+ type: 'ScalarSubquery',
80
+ query: resolveSelectQueryNode(query)
81
+ });
82
+
68
83
  export const columnOperand = (col: ColumnRef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
69
84
  /**
70
85
  * Marks a column reference as an outer-scope reference for correlated subqueries.
@@ -222,12 +237,12 @@ export const isNotNull = (left: OperandNode | ColumnRef): NullExpressionNode =>
222
237
  const createInExpression = (
223
238
  operator: 'IN' | 'NOT IN',
224
239
  left: OperandNode | ColumnRef,
225
- values: (string | number | LiteralNode)[]
240
+ right: InExpressionRight
226
241
  ): InExpressionNode => ({
227
242
  type: 'InExpression',
228
243
  left: toNode(left),
229
244
  operator,
230
- right: values.map(v => toOperand(v))
245
+ right
231
246
  });
232
247
 
233
248
  /**
@@ -237,7 +252,7 @@ const createInExpression = (
237
252
  * @returns IN expression node
238
253
  */
239
254
  export const inList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
240
- createInExpression('IN', left, values);
255
+ createInExpression('IN', left, values.map(v => toOperand(v)));
241
256
 
242
257
  /**
243
258
  * Creates a NOT IN expression (value NOT IN list)
@@ -246,7 +261,13 @@ export const inList = (left: OperandNode | ColumnRef, values: (string | number |
246
261
  * @returns NOT IN expression node
247
262
  */
248
263
  export const notInList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
249
- createInExpression('NOT IN', left, values);
264
+ createInExpression('NOT IN', left, values.map(v => toOperand(v)));
265
+
266
+ export const inSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
267
+ createInExpression('IN', left, toScalarSubqueryNode(subquery));
268
+
269
+ export const notInSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
270
+ createInExpression('NOT IN', left, toScalarSubqueryNode(subquery));
250
271
 
251
272
  const createBetweenExpression = (
252
273
  operator: 'BETWEEN' | 'NOT BETWEEN',
@@ -80,6 +80,8 @@ export interface ScalarSubqueryNode {
80
80
  alias?: string;
81
81
  }
82
82
 
83
+ export type InExpressionRight = OperandNode[] | ScalarSubqueryNode;
84
+
83
85
  /**
84
86
  * AST node representing a CASE expression
85
87
  */
@@ -201,7 +203,7 @@ export interface InExpressionNode {
201
203
  /** IN/NOT IN operator */
202
204
  operator: 'IN' | 'NOT IN';
203
205
  /** Values to check against */
204
- right: OperandNode[];
206
+ right: InExpressionRight;
205
207
  }
206
208
 
207
209
  /**
@@ -1,7 +1,7 @@
1
- export * from './expression-nodes.js';
1
+ export * from './expression-nodes.js';
2
2
  export * from './expression-builders.js';
3
3
  export * from './window-functions.js';
4
4
  export * from './aggregate-functions.js';
5
5
  export * from './expression-visitor.js';
6
- export * from './types.js';
6
+ export type { ColumnRef, TableRef as AstTableRef } from './types.js';
7
7
  export * from './adapters.js';
@@ -144,14 +144,28 @@ export interface SelectQueryNode {
144
144
  setOps?: SetOperationNode[];
145
145
  }
146
146
 
147
+ export interface InsertValuesSourceNode {
148
+ type: 'InsertValues';
149
+ /** Rows of values for INSERT rows */
150
+ rows: OperandNode[][];
151
+ }
152
+
153
+ export interface InsertSelectSourceNode {
154
+ type: 'InsertSelect';
155
+ /** SELECT query providing rows */
156
+ query: SelectQueryNode;
157
+ }
158
+
159
+ export type InsertSourceNode = InsertValuesSourceNode | InsertSelectSourceNode;
160
+
147
161
  export interface InsertQueryNode {
148
162
  type: 'InsertQuery';
149
163
  /** Target table */
150
164
  into: TableNode;
151
165
  /** Column order for inserted values */
152
166
  columns: ColumnNode[];
153
- /** Rows of values to insert */
154
- values: OperandNode[][];
167
+ /** Source of inserted rows (either literal values or a SELECT query) */
168
+ source: InsertSourceNode;
155
169
  /** Optional RETURNING clause */
156
170
  returning?: ColumnNode[];
157
171
  }
@@ -167,6 +181,10 @@ export interface UpdateQueryNode {
167
181
  type: 'UpdateQuery';
168
182
  /** Table being updated */
169
183
  table: TableNode;
184
+ /** Optional FROM clause for multi-table updates */
185
+ from?: TableSourceNode;
186
+ /** Optional joins applied to the FROM/USING tables */
187
+ joins?: JoinNode[];
170
188
  /** Assignments for SET clause */
171
189
  set: UpdateAssignmentNode[];
172
190
  /** Optional WHERE clause */
@@ -179,6 +197,10 @@ export interface DeleteQueryNode {
179
197
  type: 'DeleteQuery';
180
198
  /** Table to delete from */
181
199
  from: TableNode;
200
+ /** Optional USING clause for multi-table deletes */
201
+ using?: TableSourceNode;
202
+ /** Optional joins applied to the USING clause */
203
+ joins?: JoinNode[];
182
204
  /** Optional WHERE clause */
183
205
  where?: ExpressionNode;
184
206
  /** Optional RETURNING clause */
@@ -408,8 +408,12 @@ export abstract class Dialect
408
408
 
409
409
  this.registerExpressionCompiler('InExpression', (inExpr: InExpressionNode, ctx) => {
410
410
  const left = this.compileOperand(inExpr.left, ctx);
411
- const values = inExpr.right.map(v => this.compileOperand(v, ctx)).join(', ');
412
- return `${left} ${inExpr.operator} (${values})`;
411
+ if (Array.isArray(inExpr.right)) {
412
+ const values = inExpr.right.map(v => this.compileOperand(v, ctx)).join(', ');
413
+ return `${left} ${inExpr.operator} (${values})`;
414
+ }
415
+ const subquerySql = this.compileSelectAst(inExpr.right.query, ctx).trim().replace(/;$/, '');
416
+ return `${left} ${inExpr.operator} (${subquerySql})`;
413
417
  });
414
418
 
415
419
  this.registerExpressionCompiler('ExistsExpression', (existsExpr: ExistsExpressionNode, ctx) => {
@@ -1,22 +1,19 @@
1
- import { SelectQueryNode } from '../../ast/query.js';
2
1
  import { CompilerContext } from '../abstract.js';
2
+ import { JoinNode } from '../../ast/join.js';
3
3
 
4
4
  /**
5
5
  * Compiler for JOIN clauses in SELECT statements.
6
6
  * Handles compilation of all join types (INNER, LEFT, RIGHT, FULL, CROSS).
7
7
  */
8
8
  export class JoinCompiler {
9
- /**
10
- * Compiles all JOIN clauses from a SELECT query AST.
11
- * @param ast - The SELECT query AST containing join definitions.
12
- * @param ctx - The compiler context for expression compilation.
13
- * @param compileFrom - Function to compile table sources (tables or subqueries).
14
- * @param compileExpression - Function to compile join condition expressions.
15
- * @returns SQL JOIN clauses (e.g., " LEFT JOIN table ON condition") or empty string if no joins.
16
- */
17
- static compileJoins(ast: SelectQueryNode, ctx: CompilerContext, compileFrom: (from: any, ctx: CompilerContext) => string, compileExpression: (expr: any, ctx: CompilerContext) => string): string {
18
- if (!ast.joins || ast.joins.length === 0) return '';
19
- const parts = ast.joins.map(j => {
9
+ static compileJoins(
10
+ joins: JoinNode[] | undefined,
11
+ ctx: CompilerContext,
12
+ compileFrom: (from: any, ctx: CompilerContext) => string,
13
+ compileExpression: (expr: any, ctx: CompilerContext) => string
14
+ ): string {
15
+ if (!joins || joins.length === 0) return '';
16
+ const parts = joins.map(j => {
20
17
  const table = compileFrom(j.table as any, ctx);
21
18
  const cond = compileExpression(j.condition, ctx);
22
19
  return `${j.kind} JOIN ${table} ON ${cond}`;
@@ -1,5 +1,16 @@
1
1
  import { CompilerContext, Dialect } from '../abstract.js';
2
- import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode, DerivedTableNode, FunctionTableNode, OrderByNode } from '../../ast/query.js';
2
+ import {
3
+ SelectQueryNode,
4
+ InsertQueryNode,
5
+ UpdateQueryNode,
6
+ DeleteQueryNode,
7
+ InsertSourceNode,
8
+ TableSourceNode,
9
+ DerivedTableNode,
10
+ FunctionTableNode,
11
+ OrderByNode,
12
+ TableNode
13
+ } from '../../ast/query.js';
3
14
  import { ColumnNode } from '../../ast/expression.js';
4
15
  import { FunctionTableFormatter } from './function-table-formatter.js';
5
16
  import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
@@ -57,31 +68,49 @@ export abstract class SqlDialectBase extends Dialect {
57
68
  }
58
69
 
59
70
  protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
71
+ if (!ast.columns.length) {
72
+ throw new Error('INSERT queries must specify columns.');
73
+ }
74
+
60
75
  const table = this.compileTableName(ast.into);
61
76
  const columnList = this.compileInsertColumnList(ast.columns);
62
- const values = this.compileInsertValues(ast.values, ctx);
77
+ const source = this.compileInsertSource(ast.source, ctx);
63
78
  const returning = this.compileReturning(ast.returning, ctx);
64
- return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
79
+ return `INSERT INTO ${table} (${columnList}) ${source}${returning}`;
65
80
  }
66
81
 
67
82
  protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
68
83
  return this.returningStrategy.compileReturning(returning, ctx);
69
84
  }
70
85
 
71
- private compileInsertColumnList(columns: ColumnNode[]): string {
72
- return columns.map(column => this.quoteIdentifier(column.name)).join(', ');
86
+ private compileInsertSource(source: InsertSourceNode, ctx: CompilerContext): string {
87
+ if (source.type === 'InsertValues') {
88
+ if (!source.rows.length) {
89
+ throw new Error('INSERT ... VALUES requires at least one row.');
90
+ }
91
+ const values = source.rows
92
+ .map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
93
+ .join(', ');
94
+ return `VALUES ${values}`;
95
+ }
96
+
97
+ const normalized = this.normalizeSelectAst(source.query);
98
+ return this.compileSelectAst(normalized, ctx).trim();
73
99
  }
74
100
 
75
- private compileInsertValues(values: any[][], ctx: CompilerContext): string {
76
- return values
77
- .map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
78
- .join(', ');
101
+ private compileInsertColumnList(columns: ColumnNode[]): string {
102
+ return columns.map(column => this.quoteIdentifier(column.name)).join(', ');
79
103
  }
80
104
 
81
105
  private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
82
106
  const columns = this.compileSelectColumns(ast, ctx);
83
107
  const from = this.compileFrom(ast.from, ctx);
84
- const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
108
+ const joins = JoinCompiler.compileJoins(
109
+ ast.joins,
110
+ ctx,
111
+ this.compileFrom.bind(this),
112
+ this.compileExpression.bind(this)
113
+ );
85
114
  const whereClause = this.compileWhere(ast.where, ctx);
86
115
  const groupBy = GroupByCompiler.compileGroupBy(ast, term => this.compileOrderingTerm(term, ctx));
87
116
  const having = this.compileHaving(ast, ctx);
@@ -96,32 +125,49 @@ export abstract class SqlDialectBase extends Dialect {
96
125
  }
97
126
 
98
127
  protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
99
- const table = this.compileTableName(ast.table);
100
- const assignments = this.compileUpdateAssignments(ast.set, ctx);
128
+ const target = this.compileTableReference(ast.table);
129
+ const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
130
+ const fromClause = this.compileUpdateFromClause(ast, ctx);
101
131
  const whereClause = this.compileWhere(ast.where, ctx);
102
132
  const returning = this.compileReturning(ast.returning, ctx);
103
- return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
133
+ return `UPDATE ${target} SET ${assignments}${fromClause}${whereClause}${returning}`;
104
134
  }
105
135
 
106
136
  private compileUpdateAssignments(
107
137
  assignments: { column: ColumnNode; value: any }[],
138
+ table: TableNode,
108
139
  ctx: CompilerContext
109
140
  ): string {
110
141
  return assignments
111
142
  .map(assignment => {
112
143
  const col = assignment.column;
113
- const target = this.quoteIdentifier(col.name);
144
+ const target = this.compileQualifiedColumn(col, table);
114
145
  const value = this.compileOperand(assignment.value, ctx);
115
146
  return `${target} = ${value}`;
116
147
  })
117
148
  .join(', ');
118
149
  }
119
150
 
151
+ protected compileQualifiedColumn(column: ColumnNode, table: TableNode): string {
152
+ const baseTableName = table.name;
153
+ const alias = table.alias;
154
+ const columnTable = column.table ?? alias ?? baseTableName;
155
+ const tableQualifier =
156
+ alias && column.table === baseTableName ? alias : columnTable;
157
+
158
+ if (!tableQualifier) {
159
+ return this.quoteIdentifier(column.name);
160
+ }
161
+
162
+ return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
163
+ }
164
+
120
165
  protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
121
- const table = this.compileTableName(ast.from);
166
+ const target = this.compileTableReference(ast.from);
167
+ const usingClause = this.compileDeleteUsingClause(ast, ctx);
122
168
  const whereClause = this.compileWhere(ast.where, ctx);
123
169
  const returning = this.compileReturning(ast.returning, ctx);
124
- return `DELETE FROM ${table}${whereClause}${returning}`;
170
+ return `DELETE FROM ${target}${usingClause}${whereClause}${returning}`;
125
171
  }
126
172
 
127
173
  protected formatReturningColumns(returning: ColumnNode[]): string {
@@ -180,13 +226,48 @@ export abstract class SqlDialectBase extends Dialect {
180
226
  return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
181
227
  }
182
228
 
183
- protected compileTableName(table: { name: string; schema?: string }): string {
229
+ protected compileTableName(table: { name: string; schema?: string; alias?: string }): string {
184
230
  if (table.schema) {
185
231
  return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
186
232
  }
187
233
  return this.quoteIdentifier(table.name);
188
234
  }
189
235
 
236
+ protected compileTableReference(table: { name: string; schema?: string; alias?: string }): string {
237
+ const base = this.compileTableName(table);
238
+ return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
239
+ }
240
+
241
+ private compileUpdateFromClause(ast: UpdateQueryNode, ctx: CompilerContext): string {
242
+ if (!ast.from && (!ast.joins || ast.joins.length === 0)) return '';
243
+ if (!ast.from) {
244
+ throw new Error('UPDATE with JOINs requires an explicit FROM clause.');
245
+ }
246
+ const from = this.compileFrom(ast.from, ctx);
247
+ const joins = JoinCompiler.compileJoins(
248
+ ast.joins,
249
+ ctx,
250
+ this.compileFrom.bind(this),
251
+ this.compileExpression.bind(this)
252
+ );
253
+ return ` FROM ${from}${joins}`;
254
+ }
255
+
256
+ private compileDeleteUsingClause(ast: DeleteQueryNode, ctx: CompilerContext): string {
257
+ if (!ast.using && (!ast.joins || ast.joins.length === 0)) return '';
258
+ if (!ast.using) {
259
+ throw new Error('DELETE with JOINs requires a USING clause.');
260
+ }
261
+ const usingTable = this.compileFrom(ast.using, ctx);
262
+ const joins = JoinCompiler.compileJoins(
263
+ ast.joins,
264
+ ctx,
265
+ this.compileFrom.bind(this),
266
+ this.compileExpression.bind(this)
267
+ );
268
+ return ` USING ${usingTable}${joins}`;
269
+ }
270
+
190
271
  protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
191
272
  if (!ast.having) return '';
192
273
  return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
@@ -1,14 +1,21 @@
1
- import { CompilerContext, Dialect } from '../abstract.js';
2
- import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode, DerivedTableNode, OrderByNode } from '../../ast/query.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import {
3
+ SelectQueryNode,
4
+ DeleteQueryNode,
5
+ TableSourceNode,
6
+ DerivedTableNode,
7
+ OrderByNode
8
+ } from '../../ast/query.js';
3
9
  import { JsonPathNode } from '../../ast/expression.js';
4
10
  import { MssqlFunctionStrategy } from './functions.js';
5
- import { FunctionTableFormatter } from '../base/function-table-formatter.js';
6
11
  import { OrderByCompiler } from '../base/orderby-compiler.js';
12
+ import { JoinCompiler } from '../base/join-compiler.js';
13
+ import { SqlDialectBase } from '../base/sql-dialect.js';
7
14
 
8
15
  /**
9
16
  * Microsoft SQL Server dialect implementation
10
17
  */
11
- export class SqlServerDialect extends Dialect {
18
+ export class SqlServerDialect extends SqlDialectBase {
12
19
  protected readonly dialect = 'mssql';
13
20
  /**
14
21
  * Creates a new SqlServerDialect instance
@@ -60,7 +67,7 @@ export class SqlServerDialect extends Dialect {
60
67
  ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
61
68
  : ast;
62
69
 
63
- const baseSelect = this.compileSelectCore(baseAst, ctx);
70
+ const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
64
71
 
65
72
  if (!hasSetOps) {
66
73
  return `${ctes}${baseSelect}`;
@@ -77,35 +84,29 @@ export class SqlServerDialect extends Dialect {
77
84
  return `${ctes}${combined}${tail}`;
78
85
  }
79
86
 
80
- protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
81
- const table = this.quoteIdentifier(ast.into.name);
82
- const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
83
- const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
84
- return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
85
- }
86
-
87
- protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
88
- const table = this.quoteIdentifier(ast.table.name);
89
- const assignments = ast.set.map(assignment => {
90
- const col = assignment.column;
91
- const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
92
- const value = this.compileOperand(assignment.value, ctx);
93
- return `${target} = ${value}`;
94
- }).join(', ');
95
- const whereClause = this.compileWhere(ast.where, ctx);
96
- return `UPDATE ${table} SET ${assignments}${whereClause};`;
97
- }
98
-
99
87
  protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
88
+ if (ast.using) {
89
+ throw new Error('DELETE ... USING is not supported in the MSSQL dialect; use join() instead.');
90
+ }
91
+
100
92
  if (ast.from.type !== 'Table') {
101
93
  throw new Error('DELETE only supports base tables in the MSSQL dialect.');
102
94
  }
103
- const table = this.quoteIdentifier(ast.from.name);
95
+
96
+ const alias = ast.from.alias ?? ast.from.name;
97
+ const target = this.compileTableReference(ast.from);
98
+ const joins = JoinCompiler.compileJoins(
99
+ ast.joins,
100
+ ctx,
101
+ this.compileFrom.bind(this),
102
+ this.compileExpression.bind(this)
103
+ );
104
104
  const whereClause = this.compileWhere(ast.where, ctx);
105
- return `DELETE FROM ${table}${whereClause};`;
105
+ const returning = this.compileReturning(ast.returning, ctx);
106
+ return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
106
107
  }
107
108
 
108
- private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
109
+ private compileSelectCoreForMssql(ast: SelectQueryNode, ctx: CompilerContext): string {
109
110
  const columns = ast.columns.map(c => {
110
111
  let expr = '';
111
112
  if (c.type === 'Function') {
@@ -126,10 +127,10 @@ export class SqlServerDialect extends Dialect {
126
127
  }).join(', ');
127
128
 
128
129
  const distinct = ast.distinct ? 'DISTINCT ' : '';
129
- const from = this.compileTableSource(ast.from, ctx);
130
+ const from = this.compileTableSource(ast.from);
130
131
 
131
132
  const joins = ast.joins.map(j => {
132
- const table = this.compileTableSource(j.table, ctx);
133
+ const table = this.compileTableSource(j.table);
133
134
  const cond = this.compileExpression(j.condition, ctx);
134
135
  return `${j.kind} JOIN ${table} ON ${cond}`;
135
136
  }).join(' ');
@@ -176,35 +177,6 @@ export class SqlServerDialect extends Dialect {
176
177
  return pagination;
177
178
  }
178
179
 
179
- private renderOrderByNulls(order: OrderByNode): string | undefined {
180
- return order.nulls ? ` NULLS ${order.nulls}` : '';
181
- }
182
-
183
- private renderOrderByCollation(order: OrderByNode): string | undefined {
184
- return order.collation ? ` COLLATE ${order.collation}` : '';
185
- }
186
-
187
- private compileTableSource(table: TableSourceNode, ctx: CompilerContext): string {
188
- if (table.type === 'FunctionTable') {
189
- return FunctionTableFormatter.format(table, ctx, this as any);
190
- }
191
- if (table.type === 'DerivedTable') {
192
- return this.compileDerivedTable(table, ctx);
193
- }
194
- const base = table.schema
195
- ? `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`
196
- : this.quoteIdentifier(table.name);
197
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
198
- }
199
-
200
- private compileDerivedTable(table: DerivedTableNode, ctx: CompilerContext): string {
201
- const sub = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, '');
202
- const cols = table.columnAliases?.length
203
- ? ` (${table.columnAliases.map(c => this.quoteIdentifier(c)).join(', ')})`
204
- : '';
205
- return `(${sub}) AS ${this.quoteIdentifier(table.alias)}${cols}`;
206
- }
207
-
208
180
  private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
209
181
  if (!ast.ctes || ast.ctes.length === 0) return '';
210
182
  // MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
@@ -217,8 +189,4 @@ export class SqlServerDialect extends Dialect {
217
189
  return `WITH ${defs} `;
218
190
  }
219
191
 
220
- private wrapSetOperand(sql: string): string {
221
- const trimmed = sql.trim().replace(/;$/, '');
222
- return `(${trimmed})`;
223
- }
224
192
  }
@@ -1,11 +1,12 @@
1
- import { CompilerContext } from '../abstract.js';
2
- import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
3
- import { SqlDialectBase } from '../base/sql-dialect.js';
4
- import { SqliteFunctionStrategy } from './functions.js';
5
-
6
- /**
7
- * SQLite dialect implementation
8
- */
1
+ import { CompilerContext } from '../abstract.js';
2
+ import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
3
+ import { TableNode } from '../../ast/query.js';
4
+ import { SqlDialectBase } from '../base/sql-dialect.js';
5
+ import { SqliteFunctionStrategy } from './functions.js';
6
+
7
+ /**
8
+ * SQLite dialect implementation
9
+ */
9
10
  export class SqliteDialect extends SqlDialectBase {
10
11
  protected readonly dialect = 'sqlite';
11
12
  /**
@@ -14,27 +15,31 @@ export class SqliteDialect extends SqlDialectBase {
14
15
  public constructor() {
15
16
  super(new SqliteFunctionStrategy());
16
17
  }
17
-
18
- /**
19
- * Quotes an identifier using SQLite double-quote syntax
20
- * @param id - Identifier to quote
21
- * @returns Quoted identifier
22
- */
23
- quoteIdentifier(id: string): string {
24
- return `"${id}"`;
25
- }
26
-
27
- /**
28
- * Compiles JSON path expression using SQLite syntax
29
- * @param node - JSON path node
30
- * @returns SQLite JSON path expression
31
- */
32
- protected compileJsonPath(node: JsonPathNode): string {
33
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
34
- // SQLite uses json_extract(col, '$.path')
35
- return `json_extract(${col}, '${node.path}')`;
36
- }
37
-
18
+
19
+ /**
20
+ * Quotes an identifier using SQLite double-quote syntax
21
+ * @param id - Identifier to quote
22
+ * @returns Quoted identifier
23
+ */
24
+ quoteIdentifier(id: string): string {
25
+ return `"${id}"`;
26
+ }
27
+
28
+ /**
29
+ * Compiles JSON path expression using SQLite syntax
30
+ * @param node - JSON path node
31
+ * @returns SQLite JSON path expression
32
+ */
33
+ protected compileJsonPath(node: JsonPathNode): string {
34
+ const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
35
+ // SQLite uses json_extract(col, '$.path')
36
+ return `json_extract(${col}, '${node.path}')`;
37
+ }
38
+
39
+ protected compileQualifiedColumn(column: ColumnNode, _table: TableNode): string {
40
+ return this.quoteIdentifier(column.name);
41
+ }
42
+
38
43
  protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
39
44
  if (!returning || returning.length === 0) return '';
40
45
  const columns = this.formatReturningColumns(returning);
@@ -49,8 +54,8 @@ export class SqliteDialect extends SqlDialectBase {
49
54
  })
50
55
  .join(', ');
51
56
  }
52
-
53
- supportsReturning(): boolean {
54
- return true;
55
- }
56
- }
57
+
58
+ supportsReturning(): boolean {
59
+ return true;
60
+ }
61
+ }