metal-orm 1.1.22 → 1.1.24

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.
@@ -0,0 +1,88 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type {
3
+ DerivedTableNode,
4
+ FunctionTableNode,
5
+ TableSourceNode
6
+ } from '../../ast/query.js';
7
+ import { FunctionTableFormatter } from './function-table-formatter.js';
8
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
9
+
10
+ /** Shared FROM/table-source rendering used by the standard query compilers. */
11
+ export class StandardSqlSourceCompiler {
12
+ public constructor(private readonly services: StandardSqlCompilerServices) {}
13
+
14
+ compileFrom(source: TableSourceNode, ctx?: CompilerContext): string {
15
+ if (source.type === 'FunctionTable') return this.compileFunctionTable(source, ctx);
16
+ if (source.type === 'DerivedTable') return this.compileDerivedTable(source, ctx);
17
+ return this.compileTableSource(source);
18
+ }
19
+
20
+ compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
21
+ const key = fn.key ?? fn.name;
22
+
23
+ if (ctx) {
24
+ const renderer = this.services.getTableFunctionStrategy().getRenderer(key);
25
+ if (renderer) {
26
+ const compiledArgs = (fn.args ?? []).map(arg => this.services.compileOperand(arg, ctx));
27
+ return renderer({
28
+ node: fn,
29
+ compiledArgs,
30
+ compileOperand: operand => this.services.compileOperand(operand, ctx),
31
+ quoteIdentifier: id => this.services.quoteIdentifier(id)
32
+ });
33
+ }
34
+
35
+ if (fn.key) {
36
+ throw new Error(
37
+ `Table function "${key}" is not supported by dialect "${this.services.getDialectName()}".`
38
+ );
39
+ }
40
+ }
41
+
42
+ return FunctionTableFormatter.format(fn, ctx, {
43
+ quoteIdentifier: id => this.services.quoteIdentifier(id),
44
+ compileOperand: (node, compilerContext) => this.services.compileOperand(node, compilerContext)
45
+ });
46
+ }
47
+
48
+ compileDerivedTable(table: DerivedTableNode, ctx?: CompilerContext): string {
49
+ if (!table.alias) throw new Error('Derived tables must have an alias.');
50
+ if (!ctx) throw new Error('Derived table compilation requires a compiler context.');
51
+
52
+ const normalized = this.services.normalizeSelectAst(table.query);
53
+ const subquery = this.services.compileSelectAst(normalized, ctx).trim().replace(/;$/, '');
54
+ const columns = table.columnAliases?.length
55
+ ? ` (${table.columnAliases.map(column => this.services.quoteIdentifier(column)).join(', ')})`
56
+ : '';
57
+ return `(${subquery}) AS ${this.services.quoteIdentifier(table.alias)}${columns}`;
58
+ }
59
+
60
+ compileTableSource(table: TableSourceNode): string {
61
+ if (table.type === 'FunctionTable') return this.compileFunctionTable(table);
62
+ if (table.type === 'DerivedTable') {
63
+ throw new Error('Derived table compilation requires a compiler context.');
64
+ }
65
+ const base = this.compileTableName(table);
66
+ return table.alias ? `${base} AS ${this.services.quoteIdentifier(table.alias)}` : base;
67
+ }
68
+
69
+ compileTableName(table: { name: string; schema?: string }): string {
70
+ if (table.schema) {
71
+ return `${this.services.quoteIdentifier(table.schema)}.${this.services.quoteIdentifier(table.name)}`;
72
+ }
73
+ return this.services.quoteIdentifier(table.name);
74
+ }
75
+
76
+ compileTableReference(table: { name: string; schema?: string; alias?: string }): string {
77
+ const base = this.compileTableName(table);
78
+ return table.alias ? `${base} AS ${this.services.quoteIdentifier(table.alias)}` : base;
79
+ }
80
+
81
+ stripTrailingSemicolon(sql: string): string {
82
+ return sql.trim().replace(/;$/, '');
83
+ }
84
+
85
+ wrapSetOperand(sql: string): string {
86
+ return `(${this.stripTrailingSemicolon(sql)})`;
87
+ }
88
+ }
@@ -0,0 +1,53 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { UpdateQueryNode, TableNode } from '../../ast/query.js';
3
+ import type { ColumnNode, OperandNode } from '../../ast/expression.js';
4
+ import { JoinCompiler } from './join-compiler.js';
5
+ import { StandardSqlSourceCompiler } from './standard-sql-source-compiler.js';
6
+ import type { StandardSqlCompilerServices } from './standard-sql-services.js';
7
+
8
+ /** Standard UPDATE orchestration, independent from concrete dialect classes. */
9
+ export class StandardUpdateCompiler {
10
+ public constructor(
11
+ private readonly services: StandardSqlCompilerServices,
12
+ private readonly sources: StandardSqlSourceCompiler
13
+ ) {}
14
+
15
+ compile(ast: UpdateQueryNode, ctx: CompilerContext): string {
16
+ const target = this.sources.compileTableReference(ast.table);
17
+ const assignments = this.compileAssignments(ast.set, ast.table, ctx);
18
+ const from = this.compileFromClause(ast, ctx);
19
+ const where = ast.where ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}` : '';
20
+ const returning = this.services.compileReturning(ast.returning, ctx);
21
+ return `UPDATE ${target} SET ${assignments}${from}${where}${returning}`;
22
+ }
23
+
24
+ compileAssignments(
25
+ assignments: { column: ColumnNode; value: OperandNode }[],
26
+ table: TableNode,
27
+ ctx: CompilerContext
28
+ ): string {
29
+ return assignments
30
+ .map(assignment => {
31
+ const target = this.services.compileSetTarget(assignment.column, table);
32
+ const value = this.services.compileOperand(assignment.value, ctx);
33
+ return `${target} = ${value}`;
34
+ })
35
+ .join(', ');
36
+ }
37
+
38
+ private compileFromClause(ast: UpdateQueryNode, ctx: CompilerContext): string {
39
+ if (!ast.from && (!ast.joins || ast.joins.length === 0)) return '';
40
+ if (!ast.from) {
41
+ throw new Error('UPDATE with JOINs requires an explicit FROM clause.');
42
+ }
43
+
44
+ const from = this.sources.compileFrom(ast.from, ctx);
45
+ const joins = JoinCompiler.compileJoins(
46
+ ast.joins,
47
+ ctx,
48
+ (source, compilerContext) => this.sources.compileFrom(source, compilerContext),
49
+ (expression, compilerContext) => this.services.compileExpression(expression, compilerContext)
50
+ );
51
+ return ` FROM ${from}${joins}`;
52
+ }
53
+ }
@@ -0,0 +1,30 @@
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type { CompiledQuery } from '../abstract.js';
3
+
4
+ export interface CompiledProcedureCall extends CompiledQuery {
5
+ outParams: {
6
+ source: 'none' | 'firstResultSet' | 'lastResultSet';
7
+ names: string[];
8
+ };
9
+ }
10
+
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
+ */
18
+ export interface ProcedureCompiler {
19
+ compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall;
20
+ }
21
+
22
+ export const isProcedureCompiler = (value: unknown): value is ProcedureCompiler =>
23
+ typeof (value as { compileProcedureCall?: unknown } | null)?.compileProcedureCall === 'function';
24
+
25
+ export const requireProcedureCompiler = (value: unknown): ProcedureCompiler => {
26
+ if (!isProcedureCompiler(value)) {
27
+ throw new Error('Stored procedures are not supported by this dialect.');
28
+ }
29
+ return value;
30
+ };
@@ -1,7 +1,7 @@
1
1
  // Dialect factory for the SQL DSL.
2
2
  // Centralizes how we go from a symbolic name ("sqlite") to a concrete Dialect instance.
3
3
 
4
- import { Dialect } from './abstract.js';
4
+ import type { Dialect } from './abstract.js';
5
5
  import { PostgresDialect } from './postgres/index.js';
6
6
  import { MySqlDialect } from './mysql/index.js';
7
7
  import { SqliteDialect } from './sqlite/index.js';
@@ -42,9 +42,8 @@ export class DialectFactory {
42
42
  /**
43
43
  * Register (or override) a dialect factory for a key.
44
44
  *
45
- * Examples:
46
- * DialectFactory.register('sqlite', () => new SqliteDialect());
47
- * DialectFactory.register('my-tenant-dialect', () => new CustomDialect());
45
+ * Implementations are structural: extending DialectBase/SqlDialectBase is
46
+ * optional. A composed object satisfying Dialect is a valid registration.
48
47
  */
49
48
  public static register(key: DialectKey, factory: DialectFactoryFn): void {
50
49
  this.registry.set(key, factory);
@@ -1,4 +1,5 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
2
3
  import {
3
4
  SelectQueryNode,
4
5
  InsertQueryNode,
@@ -21,7 +22,7 @@ const toProcedureParamReference = (value: string): string =>
21
22
  /**
22
23
  * Microsoft SQL Server dialect implementation
23
24
  */
24
- export class SqlServerDialect extends SqlDialectBase {
25
+ export class SqlServerDialect extends SqlDialectBase implements ProcedureCompiler {
25
26
  protected readonly dialect = 'mssql';
26
27
  /**
27
28
  * Creates a new SqlServerDialect instance
@@ -1,4 +1,5 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
2
3
  import { JsonPathNode, IsDistinctExpressionNode } from '../../ast/expression.js';
3
4
  import { InsertQueryNode } from '../../ast/query.js';
4
5
  import { SqlDialectBase } from '../base/sql-dialect.js';
@@ -11,7 +12,7 @@ const sanitizeVariableSuffix = (value: string): string =>
11
12
  /**
12
13
  * MySQL dialect implementation
13
14
  */
14
- export class MySqlDialect extends SqlDialectBase {
15
+ export class MySqlDialect extends SqlDialectBase implements ProcedureCompiler {
15
16
  protected readonly dialect = 'mysql';
16
17
  /**
17
18
  * Creates a new MySqlDialect instance
@@ -1,4 +1,5 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
+ import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
2
3
  import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
3
4
  import { InsertQueryNode, TableNode } from '../../ast/query.js';
4
5
  import { SqlDialectBase } from '../base/sql-dialect.js';
@@ -9,7 +10,7 @@ import { ProcedureCallNode } from '../../ast/procedure.js';
9
10
  /**
10
11
  * PostgreSQL dialect implementation
11
12
  */
12
- export class PostgresDialect extends SqlDialectBase {
13
+ export class PostgresDialect extends SqlDialectBase implements ProcedureCompiler {
13
14
  protected readonly dialect = 'postgres';
14
15
  /**
15
16
  * Creates a new PostgresDialect instance
@@ -1,9 +1,8 @@
1
- import { CompilerContext, CompiledProcedureCall } from '../abstract.js';
1
+ import { CompilerContext } from '../abstract.js';
2
2
  import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
3
3
  import { InsertQueryNode, TableNode } from '../../ast/query.js';
4
4
  import { SqlDialectBase } from '../base/sql-dialect.js';
5
5
  import { SqliteFunctionStrategy } from './functions.js';
6
- import { ProcedureCallNode } from '../../ast/procedure.js';
7
6
 
8
7
  /**
9
8
  * SQLite dialect implementation
@@ -104,9 +103,4 @@ export class SqliteDialect extends SqlDialectBase {
104
103
  supportsDmlReturningClause(): boolean {
105
104
  return true;
106
105
  }
107
-
108
- compileProcedureCall(_ast: ProcedureCallNode): CompiledProcedureCall {
109
- void _ast;
110
- throw new Error('Stored procedures are not supported by the SQLite dialect.');
111
- }
112
106
  }
package/src/index.ts CHANGED
@@ -17,6 +17,15 @@ export * from './core/ast/expression.js';
17
17
  export * from './core/ast/procedure.js';
18
18
  export * from './core/ast/window-functions.js';
19
19
  export * from './core/hydration/types.js';
20
+ export * from './core/dialect/abstract.js';
21
+ export * from './core/dialect/dialect-factory.js';
22
+ export * from './core/dialect/capabilities/procedure-compiler.js';
23
+ export * from './core/dialect/base/standard-sql-services.js';
24
+ export * from './core/dialect/base/standard-sql-source-compiler.js';
25
+ export * from './core/dialect/base/standard-select-compiler.js';
26
+ export * from './core/dialect/base/standard-insert-compiler.js';
27
+ export * from './core/dialect/base/standard-update-compiler.js';
28
+ export * from './core/dialect/base/standard-delete-compiler.js';
20
29
  export * from './core/dialect/mysql/index.js';
21
30
  export * from './core/dialect/mssql/index.js';
22
31
  export * from './core/dialect/sqlite/index.js';
@@ -85,4 +94,4 @@ export * from './tree/index.js';
85
94
  export * from './cache/index.js';
86
95
 
87
96
  // Bulk operations module
88
- export * from './bulk/index.js';
97
+ export * from './bulk/index.js';
@@ -1,7 +1,8 @@
1
1
  import type { ProcedureCallNode } from '../core/ast/procedure.js';
2
2
  import type { QueryResult } from '../core/execution/db-executor.js';
3
3
  import { payloadResultSets } from '../core/execution/db-executor.js';
4
- import type { CompiledProcedureCall } from '../core/dialect/abstract.js';
4
+ import type { CompiledProcedureCall } from '../core/dialect/capabilities/procedure-compiler.js';
5
+ import { requireProcedureCompiler } from '../core/dialect/capabilities/procedure-compiler.js';
5
6
  import type { OrmSession } from './orm-session.js';
6
7
 
7
8
  export interface ProcedureExecutionResult {
@@ -63,7 +64,7 @@ export const executeProcedureAst = async (
63
64
  ast: ProcedureCallNode
64
65
  ): Promise<ProcedureExecutionResult> => {
65
66
  const execCtx = session.getExecutionContext();
66
- const compiled = execCtx.dialect.compileProcedureCall(ast);
67
+ const compiled = requireProcedureCompiler(execCtx.dialect).compileProcedureCall(ast);
67
68
  const payload = await execCtx.interceptors.run(
68
69
  { sql: compiled.sql, params: compiled.params },
69
70
  execCtx.executor
@@ -1,5 +1,7 @@
1
1
  import type { ProcedureCallNode, ProcedureParamNode } from '../core/ast/procedure.js';
2
- import type { CompiledProcedureCall, Dialect } from '../core/dialect/abstract.js';
2
+ import type { Dialect } from '../core/dialect/abstract.js';
3
+ import type { CompiledProcedureCall } from '../core/dialect/capabilities/procedure-compiler.js';
4
+ import { requireProcedureCompiler } from '../core/dialect/capabilities/procedure-compiler.js';
3
5
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
4
6
  import { valueToOperand, ValueOperandInput } from '../core/ast/expression-builders.js';
5
7
  import type { OrmSession } from '../orm/orm-session.js';
@@ -82,8 +84,7 @@ export class ProcedureCallBuilder {
82
84
 
83
85
  compile(dialect: ProcedureDialectInput): CompiledProcedureCall {
84
86
  const resolved = resolveDialectInput(dialect);
85
- this.validateMssqlOutDbType(resolved);
86
- return resolved.compileProcedureCall(this.getAST());
87
+ return requireProcedureCompiler(resolved).compileProcedureCall(this.getAST());
87
88
  }
88
89
 
89
90
  toSql(dialect: ProcedureDialectInput): string {
@@ -99,23 +100,8 @@ export class ProcedureCallBuilder {
99
100
  }
100
101
 
101
102
  async execute(session: OrmSession): Promise<ProcedureExecutionResult> {
102
- this.validateMssqlOutDbType(session.getExecutionContext().dialect);
103
103
  return executeProcedureAst(session, this.getAST());
104
104
  }
105
-
106
- private validateMssqlOutDbType(dialect: Dialect): void {
107
- const isMssqlDialect = dialect.constructor.name === 'SqlServerDialect';
108
- if (!isMssqlDialect) return;
109
-
110
- for (const param of this.ast.params) {
111
- const needsDbType = param.direction === 'out' || param.direction === 'inout';
112
- if (needsDbType && !param.dbType) {
113
- throw new Error(
114
- `MSSQL requires "dbType" for procedure parameter "${param.name}" with direction "${param.direction}".`
115
- );
116
- }
117
- }
118
- }
119
105
  }
120
106
 
121
107
  export const callProcedure = (name: string, options?: CallProcedureOptions): ProcedureCallBuilder =>