metal-orm 1.1.24 → 1.1.25

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 (31) hide show
  1. package/dist/index.cjs +539 -412
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +225 -176
  4. package/dist/index.d.ts +225 -176
  5. package/dist/index.js +519 -412
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/core/dialect/base/returning-strategy.ts +40 -39
  9. package/src/core/dialect/base/sql-compiler-set.ts +33 -0
  10. package/src/core/dialect/base/sql-dialect.ts +79 -38
  11. package/src/core/dialect/base/upsert-strategy.ts +45 -0
  12. package/src/core/dialect/capabilities/procedure-compiler.ts +10 -8
  13. package/src/core/dialect/mssql/compiler-factory.ts +12 -0
  14. package/src/core/dialect/mssql/delete-compiler.ts +40 -0
  15. package/src/core/dialect/mssql/index.ts +24 -371
  16. package/src/core/dialect/mssql/insert-compiler.ts +112 -0
  17. package/src/core/dialect/mssql/output.ts +46 -0
  18. package/src/core/dialect/mssql/procedure-compiler.ts +81 -0
  19. package/src/core/dialect/mssql/select-compiler.ts +116 -0
  20. package/src/core/dialect/mssql/update-compiler.ts +37 -0
  21. package/src/core/dialect/mysql/index.ts +24 -117
  22. package/src/core/dialect/mysql/procedure-compiler.ts +67 -0
  23. package/src/core/dialect/mysql/upsert.ts +42 -0
  24. package/src/core/dialect/postgres/index.ts +34 -101
  25. package/src/core/dialect/postgres/procedure-compiler.ts +41 -0
  26. package/src/core/dialect/postgres/returning.ts +4 -0
  27. package/src/core/dialect/postgres/upsert.ts +43 -0
  28. package/src/core/dialect/sqlite/index.ts +15 -70
  29. package/src/core/dialect/sqlite/returning.ts +30 -0
  30. package/src/core/dialect/sqlite/upsert.ts +43 -0
  31. package/src/index.ts +22 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.1.24",
3
+ "version": "1.1.25",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,51 +1,39 @@
1
- import { ColumnNode } from '../../ast/expression.js';
2
- import { CompilerContext } from '../abstract.js';
1
+ import type { ColumnNode } from '../../ast/expression.js';
2
+ import type { CompilerContext } from '../abstract.js';
3
3
 
4
- /**
5
- * Strategy interface for handling RETURNING clauses in DML statements (INSERT, UPDATE, DELETE).
6
- * Different SQL dialects have varying levels of support for RETURNING clauses.
7
- */
4
+ export type QuoteIdentifier = (id: string) => string;
5
+
6
+ /** Backend-specific RETURNING/OUTPUT rendering strategy. */
8
7
  export interface ReturningStrategy {
9
- /**
10
- * Compiles a RETURNING clause for DML statements.
11
- * @param returning - Array of columns to return, or undefined if none.
12
- * @param ctx - The compiler context for expression compilation.
13
- * @returns SQL RETURNING clause or empty string if not supported.
14
- * @throws Error if RETURNING is not supported by this dialect.
15
- */
16
- compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string;
17
- /**
18
- * Formats column list for RETURNING clause.
19
- * @param returning - Array of columns to format.
20
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
21
- * @returns Formatted column list (e.g., "table.col1, table.col2").
22
- */
23
- formatReturningColumns(returning: ColumnNode[], quoteIdentifier: (id: string) => string): string;
8
+ compileReturning(
9
+ returning: ColumnNode[] | undefined,
10
+ ctx: CompilerContext,
11
+ quoteIdentifier: QuoteIdentifier
12
+ ): string;
13
+
14
+ formatReturningColumns(
15
+ returning: ColumnNode[],
16
+ quoteIdentifier: QuoteIdentifier
17
+ ): string;
24
18
  }
25
19
 
26
- /**
27
- * Default RETURNING strategy that throws an error when RETURNING is used.
28
- * Use this for dialects that don't support RETURNING clauses.
29
- */
20
+ /** Default RETURNING strategy for dialects without support. */
30
21
  export class NoReturningStrategy implements ReturningStrategy {
31
- /**
32
- * Throws an error as RETURNING is not supported.
33
- * @param returning - Columns to return (causes error if non-empty).
34
- * @param _ctx - Compiler context (unused).
35
- * @throws Error indicating RETURNING is not supported.
36
- */
37
- compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
22
+ compileReturning(
23
+ returning: ColumnNode[] | undefined,
24
+ _ctx: CompilerContext,
25
+ _quoteIdentifier: QuoteIdentifier
26
+ ): string {
38
27
  void _ctx;
28
+ void _quoteIdentifier;
39
29
  if (!returning || returning.length === 0) return '';
40
30
  throw new Error('RETURNING is not supported by this dialect.');
41
31
  }
42
- /**
43
- * Formats column names for RETURNING clause.
44
- * @param returning - Columns to format.
45
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
46
- * @returns Simple comma-separated column names.
47
- */
48
- formatReturningColumns(returning: ColumnNode[], quoteIdentifier: (id: string) => string): string {
32
+
33
+ formatReturningColumns(
34
+ returning: ColumnNode[],
35
+ quoteIdentifier: QuoteIdentifier
36
+ ): string {
49
37
  return returning
50
38
  .map(column => {
51
39
  const tablePart = column.table ? `${quoteIdentifier(column.table)}.` : '';
@@ -55,3 +43,16 @@ export class NoReturningStrategy implements ReturningStrategy {
55
43
  .join(', ');
56
44
  }
57
45
  }
46
+
47
+ /** Standard SQL RETURNING implementation with qualified column support. */
48
+ export class StandardReturningStrategy extends NoReturningStrategy {
49
+ override compileReturning(
50
+ returning: ColumnNode[] | undefined,
51
+ _ctx: CompilerContext,
52
+ quoteIdentifier: QuoteIdentifier
53
+ ): string {
54
+ void _ctx;
55
+ if (!returning || returning.length === 0) return '';
56
+ return ` RETURNING ${this.formatReturningColumns(returning, quoteIdentifier)}`;
57
+ }
58
+ }
@@ -0,0 +1,33 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type {
3
+ DeleteQueryNode,
4
+ InsertQueryNode,
5
+ SelectQueryNode,
6
+ UpdateQueryNode
7
+ } from '../../ast/query.js';
8
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
9
+ import type { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
10
+
11
+ export interface SqlAstCompiler<TAst> {
12
+ compile(ast: TAst, ctx: CompilerContext): string;
13
+ }
14
+
15
+ export interface SqlCompilerSet {
16
+ select: SqlAstCompiler<SelectQueryNode>;
17
+ insert: SqlAstCompiler<InsertQueryNode>;
18
+ update: SqlAstCompiler<UpdateQueryNode>;
19
+ delete: SqlAstCompiler<DeleteQueryNode>;
20
+ }
21
+
22
+ export interface SqlCompilerAssemblyContext {
23
+ services: StandardSqlCompilerServices;
24
+ sources: StandardSqlSourceCompiler;
25
+ }
26
+
27
+ /**
28
+ * Allows a backend to replace only the standard query compilers whose SQL
29
+ * grammar genuinely differs from the common implementation.
30
+ */
31
+ export type SqlCompilerFactory = (
32
+ context: SqlCompilerAssemblyContext
33
+ ) => Partial<SqlCompilerSet>;
@@ -1,55 +1,71 @@
1
1
  import { DialectBase } from '../abstract.js';
2
2
  import type { CompilerContext } from '../abstract.js';
3
3
  import type {
4
- SelectQueryNode,
5
- InsertQueryNode,
6
- UpdateQueryNode,
7
4
  DeleteQueryNode,
8
- UpsertClause,
9
- TableSourceNode,
10
5
  DerivedTableNode,
11
6
  FunctionTableNode,
7
+ InsertQueryNode,
12
8
  OrderByNode,
13
- TableNode
9
+ SelectQueryNode,
10
+ TableNode,
11
+ TableSourceNode,
12
+ UpdateAssignmentNode,
13
+ UpdateQueryNode,
14
+ UpsertClause
14
15
  } from '../../ast/query.js';
15
- import type { ColumnNode, OperandNode } from '../../ast/expression.js';
16
+ import type { ColumnNode } from '../../ast/expression.js';
16
17
  import type { FunctionStrategy } from '../../functions/types.js';
17
18
  import type { TableFunctionStrategy } from '../../functions/table-types.js';
18
19
  import { StandardLimitOffsetPagination } from './pagination-strategy.js';
19
20
  import type { PaginationStrategy } from './pagination-strategy.js';
20
21
  import { NoReturningStrategy } from './returning-strategy.js';
21
22
  import type { ReturningStrategy } from './returning-strategy.js';
23
+ import { NoUpsertStrategy } from './upsert-strategy.js';
24
+ import type { UpsertStrategy } from './upsert-strategy.js';
22
25
  import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
23
26
  import { StandardSelectCompiler } from './standard-select-compiler.js';
24
27
  import { StandardInsertCompiler } from './standard-insert-compiler.js';
25
28
  import { StandardUpdateCompiler } from './standard-update-compiler.js';
26
29
  import { StandardDeleteCompiler } from './standard-delete-compiler.js';
27
30
  import type { StandardSqlCompilerServices } from './standard-sql-services.js';
31
+ import type { SqlCompilerFactory, SqlCompilerSet } from './sql-compiler-set.js';
32
+
33
+ export interface SqlDialectBaseOptions {
34
+ functionStrategy?: FunctionStrategy;
35
+ tableFunctionStrategy?: TableFunctionStrategy;
36
+ paginationStrategy?: PaginationStrategy;
37
+ returningStrategy?: ReturningStrategy;
38
+ upsertStrategy?: UpsertStrategy;
39
+ compilerFactory?: SqlCompilerFactory;
40
+ supportsDmlReturning?: boolean;
41
+ }
28
42
 
29
43
  /**
30
- * Thin assembly base for dialects that use MetalORM's standard SQL compilers.
44
+ * Thin assembly base for dialects that use MetalORM's reusable SQL compiler pieces.
31
45
  *
32
- * SELECT/INSERT/UPDATE/DELETE orchestration lives in independent compiler
33
- * objects. This class only wires dialect-specific syntax hooks and strategies
34
- * into those components.
46
+ * Query orchestration, source rendering, upsert behavior and returning behavior are
47
+ * injected components. Concrete dialects keep only syntax hooks that are genuinely
48
+ * intrinsic to that backend.
35
49
  */
36
50
  export abstract class SqlDialectBase extends DialectBase {
37
51
  abstract quoteIdentifier(id: string): string;
38
52
 
39
- protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
40
- protected returningStrategy: ReturningStrategy = new NoReturningStrategy();
53
+ protected readonly paginationStrategy: PaginationStrategy;
54
+ protected readonly returningStrategy: ReturningStrategy;
55
+ protected readonly upsertStrategy: UpsertStrategy;
41
56
 
57
+ private readonly dmlReturningSupported: boolean;
42
58
  private readonly sourceCompiler: StandardSqlSourceCompiler;
43
- private readonly selectCompiler: StandardSelectCompiler;
44
- private readonly insertCompiler: StandardInsertCompiler;
45
- private readonly updateCompiler: StandardUpdateCompiler;
46
- private readonly deleteCompiler: StandardDeleteCompiler;
59
+ private readonly standardUpdateCompiler: StandardUpdateCompiler;
60
+ private readonly compilerSet: SqlCompilerSet;
47
61
 
48
- protected constructor(
49
- functionStrategy?: FunctionStrategy,
50
- tableFunctionStrategy?: TableFunctionStrategy
51
- ) {
52
- super(functionStrategy, tableFunctionStrategy);
62
+ protected constructor(options: SqlDialectBaseOptions = {}) {
63
+ super(options.functionStrategy, options.tableFunctionStrategy);
64
+
65
+ this.paginationStrategy = options.paginationStrategy ?? new StandardLimitOffsetPagination();
66
+ this.returningStrategy = options.returningStrategy ?? new NoReturningStrategy();
67
+ this.upsertStrategy = options.upsertStrategy ?? new NoUpsertStrategy();
68
+ this.dmlReturningSupported = options.supportsDmlReturning ?? false;
53
69
 
54
70
  const services: StandardSqlCompilerServices = {
55
71
  getDialectName: () => this.dialect,
@@ -69,48 +85,73 @@ export abstract class SqlDialectBase extends DialectBase {
69
85
  };
70
86
 
71
87
  this.sourceCompiler = new StandardSqlSourceCompiler(services);
72
- this.selectCompiler = new StandardSelectCompiler(services, this.sourceCompiler);
73
- this.insertCompiler = new StandardInsertCompiler(services, this.sourceCompiler);
74
- this.updateCompiler = new StandardUpdateCompiler(services, this.sourceCompiler);
75
- this.deleteCompiler = new StandardDeleteCompiler(services, this.sourceCompiler);
88
+ const standardSelect = new StandardSelectCompiler(services, this.sourceCompiler);
89
+ const standardInsert = new StandardInsertCompiler(services, this.sourceCompiler);
90
+ this.standardUpdateCompiler = new StandardUpdateCompiler(services, this.sourceCompiler);
91
+ const standardDelete = new StandardDeleteCompiler(services, this.sourceCompiler);
92
+
93
+ const overrides = options.compilerFactory?.({
94
+ services,
95
+ sources: this.sourceCompiler
96
+ }) ?? {};
97
+
98
+ this.compilerSet = {
99
+ select: overrides.select ?? standardSelect,
100
+ insert: overrides.insert ?? standardInsert,
101
+ update: overrides.update ?? this.standardUpdateCompiler,
102
+ delete: overrides.delete ?? standardDelete
103
+ };
104
+ }
105
+
106
+ override supportsDmlReturningClause(): boolean {
107
+ return this.dmlReturningSupported;
76
108
  }
77
109
 
78
110
  protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
79
- return this.selectCompiler.compile(ast, ctx);
111
+ return this.compilerSet.select.compile(ast, ctx);
80
112
  }
81
113
 
82
114
  protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
83
- return this.insertCompiler.compile(ast, ctx);
115
+ return this.compilerSet.insert.compile(ast, ctx);
84
116
  }
85
117
 
86
118
  protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
87
- return this.updateCompiler.compile(ast, ctx);
119
+ return this.compilerSet.update.compile(ast, ctx);
88
120
  }
89
121
 
90
122
  protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
91
- return this.deleteCompiler.compile(ast, ctx);
123
+ return this.compilerSet.delete.compile(ast, ctx);
92
124
  }
93
125
 
94
- protected compileUpsertClause(ast: InsertQueryNode, _ctx: CompilerContext): string {
95
- void _ctx;
96
- if (!ast.onConflict) return '';
97
- throw new Error(`UPSERT/ON CONFLICT is not supported by dialect "${this.dialect}".`);
126
+ protected compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string {
127
+ return this.upsertStrategy.compile(ast, ctx, {
128
+ getDialectName: () => this.dialect,
129
+ quoteIdentifier: id => this.quoteIdentifier(id),
130
+ compileOperand: (node, compilerContext) => this.compileOperand(node, compilerContext),
131
+ compileExpression: (node, compilerContext) => this.compileExpression(node, compilerContext),
132
+ compileUpdateAssignments: (assignments, table, compilerContext) =>
133
+ this.standardUpdateCompiler.compileAssignments(assignments, table, compilerContext)
134
+ });
98
135
  }
99
136
 
100
137
  protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
101
- return this.returningStrategy.compileReturning(returning, ctx);
138
+ return this.returningStrategy.compileReturning(
139
+ returning,
140
+ ctx,
141
+ id => this.quoteIdentifier(id)
142
+ );
102
143
  }
103
144
 
104
145
  protected ensureConflictColumns(clause: UpsertClause, message: string): void {
105
- this.insertCompiler.ensureConflictColumns(clause, message);
146
+ if (!clause.target.columns.length) throw new Error(message);
106
147
  }
107
148
 
108
149
  protected compileUpdateAssignments(
109
- assignments: { column: ColumnNode; value: OperandNode }[],
150
+ assignments: UpdateAssignmentNode[],
110
151
  table: TableNode,
111
152
  ctx: CompilerContext
112
153
  ): string {
113
- return this.updateCompiler.compileAssignments(assignments, table, ctx);
154
+ return this.standardUpdateCompiler.compileAssignments(assignments, table, ctx);
114
155
  }
115
156
 
116
157
  protected compileSetTarget(column: ColumnNode, table: TableNode): string {
@@ -0,0 +1,45 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type {
3
+ InsertQueryNode,
4
+ TableNode,
5
+ UpdateAssignmentNode
6
+ } from '../../ast/query.js';
7
+ import type { ExpressionNode, OperandNode } from '../../ast/expression.js';
8
+ import type { DialectName } from '../../sql/sql.js';
9
+
10
+ /** Narrow services needed by backend-specific UPSERT implementations. */
11
+ export interface UpsertCompilationServices {
12
+ getDialectName(): DialectName;
13
+ quoteIdentifier(id: string): string;
14
+ compileOperand(node: OperandNode, ctx: CompilerContext): string;
15
+ compileExpression(node: ExpressionNode, ctx: CompilerContext): string;
16
+ compileUpdateAssignments(
17
+ assignments: UpdateAssignmentNode[],
18
+ table: TableNode,
19
+ ctx: CompilerContext
20
+ ): string;
21
+ }
22
+
23
+ /** Backend-specific INSERT conflict/upsert rendering strategy. */
24
+ export interface UpsertStrategy {
25
+ compile(
26
+ ast: InsertQueryNode,
27
+ ctx: CompilerContext,
28
+ services: UpsertCompilationServices
29
+ ): string;
30
+ }
31
+
32
+ /** Default strategy for dialects without UPSERT support. */
33
+ export class NoUpsertStrategy implements UpsertStrategy {
34
+ compile(
35
+ ast: InsertQueryNode,
36
+ _ctx: CompilerContext,
37
+ services: UpsertCompilationServices
38
+ ): string {
39
+ void _ctx;
40
+ if (!ast.onConflict) return '';
41
+ throw new Error(
42
+ `UPSERT/ON CONFLICT is not supported by dialect "${services.getDialectName()}".`
43
+ );
44
+ }
45
+ }
@@ -1,5 +1,6 @@
1
1
  import type { ProcedureCallNode } from '../../ast/procedure.js';
2
- import type { CompiledQuery } from '../abstract.js';
2
+ import type { OperandNode } from '../../ast/expression.js';
3
+ import type { CompiledQuery, CompilerContext } from '../abstract.js';
3
4
 
4
5
  export interface CompiledProcedureCall extends CompiledQuery {
5
6
  outParams: {
@@ -8,13 +9,14 @@ export interface CompiledProcedureCall extends CompiledQuery {
8
9
  };
9
10
  }
10
11
 
11
- /**
12
- * Optional dialect capability for stored-procedure compilation.
13
- *
14
- * Dialects that do not support procedures simply do not implement this
15
- * interface; unsupported behavior is resolved at the capability boundary
16
- * rather than through mandatory methods that only throw.
17
- */
12
+ /** Narrow SQL services consumed by reusable procedure compiler components. */
13
+ export interface ProcedureCompilerServices {
14
+ quoteIdentifier(id: string): string;
15
+ createCompilerContext(): CompilerContext;
16
+ compileOperand(node: OperandNode, ctx: CompilerContext): string;
17
+ }
18
+
19
+ /** Optional dialect capability for stored-procedure compilation. */
18
20
  export interface ProcedureCompiler {
19
21
  compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall;
20
22
  }
@@ -0,0 +1,12 @@
1
+ import type { SqlCompilerFactory } from '../base/sql-compiler-set.js';
2
+ import { MssqlDeleteCompiler } from './delete-compiler.js';
3
+ import { MssqlInsertCompiler } from './insert-compiler.js';
4
+ import { MssqlSelectCompiler } from './select-compiler.js';
5
+ import { MssqlUpdateCompiler } from './update-compiler.js';
6
+
7
+ export const createMssqlCompilerSet: SqlCompilerFactory = ({ services, sources }) => ({
8
+ select: new MssqlSelectCompiler(services, sources),
9
+ insert: new MssqlInsertCompiler(services, sources),
10
+ update: new MssqlUpdateCompiler(services, sources),
11
+ delete: new MssqlDeleteCompiler(services, sources)
12
+ });
@@ -0,0 +1,40 @@
1
+ import type { DeleteQueryNode } from '../../ast/query.js';
2
+ import type { CompilerContext } from '../abstract.js';
3
+ import { JoinCompiler } from '../base/join-compiler.js';
4
+ import type { SqlAstCompiler } from '../base/sql-compiler-set.js';
5
+ import type { StandardSqlCompilerServices } from '../base/standard-sql-services.js';
6
+ import type { StandardSqlSourceCompiler } from '../base/standard-sql-source-compiler.js';
7
+ import { MssqlOutputStrategy } from './output.js';
8
+
9
+ export class MssqlDeleteCompiler implements SqlAstCompiler<DeleteQueryNode> {
10
+ private readonly output = new MssqlOutputStrategy();
11
+
12
+ constructor(
13
+ private readonly services: StandardSqlCompilerServices,
14
+ private readonly sources: StandardSqlSourceCompiler
15
+ ) {}
16
+
17
+ compile(ast: DeleteQueryNode, ctx: CompilerContext): string {
18
+ if (ast.using) {
19
+ throw new Error('DELETE ... USING is not supported in the MSSQL dialect; use join() instead.');
20
+ }
21
+
22
+ const alias = ast.from.alias ?? ast.from.name;
23
+ const target = this.sources.compileTableReference(ast.from);
24
+ const joins = JoinCompiler.compileJoins(
25
+ ast.joins,
26
+ ctx,
27
+ (source, compilerContext) => this.sources.compileFrom(source, compilerContext),
28
+ (expression, compilerContext) => this.services.compileExpression(expression, compilerContext)
29
+ );
30
+ const where = ast.where
31
+ ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}`
32
+ : '';
33
+ const returning = this.output.compileOutput(
34
+ ast.returning,
35
+ 'deleted',
36
+ id => this.services.quoteIdentifier(id)
37
+ );
38
+ return `DELETE ${this.services.quoteIdentifier(alias)}${returning} FROM ${target}${joins}${where}`;
39
+ }
40
+ }