metal-orm 1.1.24 → 1.1.26

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 (35) hide show
  1. package/dist/index.cjs +770 -710
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +294 -318
  4. package/dist/index.d.ts +294 -318
  5. package/dist/index.js +745 -709
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/core/dialect/abstract.ts +7 -229
  9. package/src/core/dialect/base/returning-strategy.ts +40 -39
  10. package/src/core/dialect/base/sql-compiler-set.ts +33 -0
  11. package/src/core/dialect/base/sql-dialect-composer.ts +294 -0
  12. package/src/core/dialect/base/standard-sql-services.ts +2 -6
  13. package/src/core/dialect/base/upsert-strategy.ts +44 -0
  14. package/src/core/dialect/capabilities/procedure-compiler.ts +10 -8
  15. package/src/core/dialect/dialect-factory.ts +17 -49
  16. package/src/core/dialect/mssql/compiler-factory.ts +12 -0
  17. package/src/core/dialect/mssql/delete-compiler.ts +40 -0
  18. package/src/core/dialect/mssql/index.ts +52 -370
  19. package/src/core/dialect/mssql/insert-compiler.ts +112 -0
  20. package/src/core/dialect/mssql/output.ts +46 -0
  21. package/src/core/dialect/mssql/procedure-compiler.ts +81 -0
  22. package/src/core/dialect/mssql/select-compiler.ts +116 -0
  23. package/src/core/dialect/mssql/update-compiler.ts +37 -0
  24. package/src/core/dialect/mysql/index.ts +66 -126
  25. package/src/core/dialect/mysql/procedure-compiler.ts +67 -0
  26. package/src/core/dialect/mysql/upsert.ts +42 -0
  27. package/src/core/dialect/postgres/index.ts +75 -114
  28. package/src/core/dialect/postgres/procedure-compiler.ts +41 -0
  29. package/src/core/dialect/postgres/returning.ts +4 -0
  30. package/src/core/dialect/postgres/upsert.ts +43 -0
  31. package/src/core/dialect/sqlite/index.ts +65 -90
  32. package/src/core/dialect/sqlite/returning.ts +30 -0
  33. package/src/core/dialect/sqlite/upsert.ts +43 -0
  34. package/src/index.ts +22 -10
  35. package/src/core/dialect/base/sql-dialect.ts +0 -176
@@ -0,0 +1,116 @@
1
+ import type { OperandNode } from '../../ast/expression.js';
2
+ import type { SelectQueryNode } from '../../ast/query.js';
3
+ import type { CompilerContext } from '../abstract.js';
4
+ import { OrderByCompiler } from '../base/orderby-compiler.js';
5
+ import type { SqlAstCompiler } from '../base/sql-compiler-set.js';
6
+ import type { StandardSqlCompilerServices } from '../base/standard-sql-services.js';
7
+ import type { StandardSqlSourceCompiler } from '../base/standard-sql-source-compiler.js';
8
+
9
+ export class MssqlSelectCompiler implements SqlAstCompiler<SelectQueryNode> {
10
+ constructor(
11
+ private readonly services: StandardSqlCompilerServices,
12
+ private readonly sources: StandardSqlSourceCompiler
13
+ ) {}
14
+
15
+ compile(ast: SelectQueryNode, ctx: CompilerContext): string {
16
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
17
+ const ctes = this.compileCtes(ast, ctx);
18
+ const baseAst: SelectQueryNode = hasSetOps
19
+ ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
20
+ : ast;
21
+ const baseSelect = this.compileCore(baseAst, ctx);
22
+
23
+ if (!hasSetOps) return `${ctes}${baseSelect}`;
24
+
25
+ const compound = ast.setOps!
26
+ .map(op => `${op.operator} ${this.sources.wrapSetOperand(this.services.compileSelectAst(op.query, ctx))}`)
27
+ .join(' ');
28
+ const orderBy = this.compileOrderBy(ast, ctx);
29
+ const pagination = this.compilePagination(ast, orderBy);
30
+ const combined = `${this.sources.wrapSetOperand(baseSelect)} ${compound}`;
31
+ return `${ctes}${combined}${pagination || orderBy}`;
32
+ }
33
+
34
+ private compileCore(ast: SelectQueryNode, ctx: CompilerContext): string {
35
+ const columns = ast.columns
36
+ .map(column => {
37
+ const expr = column.type === 'Column'
38
+ ? `${this.services.quoteIdentifier(column.table)}.${this.services.quoteIdentifier(column.name)}`
39
+ : this.services.compileOperand(column as OperandNode, ctx);
40
+ if (!column.alias) return expr;
41
+ if (column.alias.includes('(')) return column.alias;
42
+ return `${expr} AS ${this.services.quoteIdentifier(column.alias)}`;
43
+ })
44
+ .join(', ');
45
+
46
+ const distinct = ast.distinct ? 'DISTINCT ' : '';
47
+ const from = this.sources.compileFrom(ast.from, ctx);
48
+ const joins = ast.joins
49
+ .map(join => {
50
+ const table = this.sources.compileFrom(join.table, ctx);
51
+ const condition = this.services.compileExpression(join.condition, ctx);
52
+ return `${join.kind} JOIN ${table} ON ${condition}`;
53
+ })
54
+ .join(' ');
55
+ const where = ast.where
56
+ ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}`
57
+ : '';
58
+ const groupBy = ast.groupBy && ast.groupBy.length > 0
59
+ ? ` GROUP BY ${ast.groupBy.map(term => this.services.compileOrderingTerm(term, ctx)).join(', ')}`
60
+ : '';
61
+ const having = ast.having
62
+ ? ` HAVING ${this.services.compileExpression(ast.having, ctx)}`
63
+ : '';
64
+ const orderBy = this.compileOrderBy(ast, ctx);
65
+ const pagination = this.compilePagination(ast, orderBy);
66
+
67
+ if (pagination) {
68
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ` ${joins}` : ''}${where}${groupBy}${having}${pagination}`;
69
+ }
70
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ` ${joins}` : ''}${where}${groupBy}${having}${orderBy}`;
71
+ }
72
+
73
+ private compileOrderBy(ast: SelectQueryNode, ctx: CompilerContext): string {
74
+ return OrderByCompiler.compileOrderBy(
75
+ ast,
76
+ term => this.services.compileOrderingTerm(term, ctx),
77
+ order => this.services.renderOrderByNulls(order),
78
+ order => this.services.renderOrderByCollation(order)
79
+ );
80
+ }
81
+
82
+ private compilePagination(ast: SelectQueryNode, orderBy: string): string {
83
+ const hasLimit = ast.limit !== undefined;
84
+ const hasOffset = ast.offset !== undefined;
85
+ if (!hasLimit && !hasOffset) return '';
86
+
87
+ const offset = ast.offset ?? 0;
88
+ let orderClause = orderBy;
89
+ if (!orderClause) {
90
+ orderClause = ast.distinct && ast.distinct.length > 0
91
+ ? ' ORDER BY 1'
92
+ : ' ORDER BY (SELECT NULL)';
93
+ }
94
+
95
+ let pagination = `${orderClause} OFFSET ${offset} ROWS`;
96
+ if (hasLimit) pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
97
+ return pagination;
98
+ }
99
+
100
+ private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
101
+ if (!ast.ctes || ast.ctes.length === 0) return '';
102
+ const definitions = ast.ctes
103
+ .map(cte => {
104
+ const name = this.services.quoteIdentifier(cte.name);
105
+ const columns = cte.columns
106
+ ? `(${cte.columns.map(column => this.services.quoteIdentifier(column)).join(', ')})`
107
+ : '';
108
+ const query = this.sources.stripTrailingSemicolon(
109
+ this.services.compileSelectAst(this.services.normalizeSelectAst(cte.query), ctx)
110
+ );
111
+ return `${name}${columns} AS (${query})`;
112
+ })
113
+ .join(', ');
114
+ return `WITH ${definitions} `;
115
+ }
116
+ }
@@ -0,0 +1,37 @@
1
+ import type { UpdateQueryNode } from '../../ast/query.js';
2
+ import type { CompilerContext } from '../abstract.js';
3
+ import type { SqlAstCompiler } from '../base/sql-compiler-set.js';
4
+ import type { StandardSqlCompilerServices } from '../base/standard-sql-services.js';
5
+ import { StandardUpdateCompiler } from '../base/standard-update-compiler.js';
6
+ import type { StandardSqlSourceCompiler } from '../base/standard-sql-source-compiler.js';
7
+
8
+ export class MssqlUpdateCompiler implements SqlAstCompiler<UpdateQueryNode> {
9
+ private readonly standardUpdate: StandardUpdateCompiler;
10
+
11
+ constructor(
12
+ private readonly services: StandardSqlCompilerServices,
13
+ private readonly sources: StandardSqlSourceCompiler
14
+ ) {
15
+ this.standardUpdate = new StandardUpdateCompiler(services, sources);
16
+ }
17
+
18
+ compile(ast: UpdateQueryNode, ctx: CompilerContext): string {
19
+ const target = this.sources.compileTableReference(ast.table);
20
+ const assignments = this.standardUpdate.compileAssignments(ast.set, ast.table, ctx);
21
+ const output = this.services.compileReturning(ast.returning, ctx);
22
+ const from = ast.from ? ` FROM ${this.sources.compileFrom(ast.from, ctx)}` : '';
23
+ const joins = ast.joins
24
+ ? ast.joins
25
+ .map(join => {
26
+ const table = this.sources.compileFrom(join.table, ctx);
27
+ const condition = this.services.compileExpression(join.condition, ctx);
28
+ return ` ${join.kind} JOIN ${table} ON ${condition}`;
29
+ })
30
+ .join('')
31
+ : '';
32
+ const where = ast.where
33
+ ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}`
34
+ : '';
35
+ return `UPDATE ${target} SET ${assignments}${output}${from}${joins}${where}`;
36
+ }
37
+ }
@@ -1,144 +1,84 @@
1
- import { CompilerContext } from '../abstract.js';
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type { IsDistinctExpressionNode, JsonPathNode } from '../../ast/expression.js';
3
+ import type {
4
+ DeleteQueryNode,
5
+ InsertQueryNode,
6
+ SelectQueryNode,
7
+ UpdateQueryNode
8
+ } from '../../ast/query.js';
9
+ import type { CompiledQuery, Dialect } from '../abstract.js';
2
10
  import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
3
- import { JsonPathNode, IsDistinctExpressionNode } from '../../ast/expression.js';
4
- import { InsertQueryNode } from '../../ast/query.js';
5
- import { SqlDialectBase } from '../base/sql-dialect.js';
11
+ import { composeSqlDialect } from '../base/sql-dialect-composer.js';
6
12
  import { MysqlFunctionStrategy } from './functions.js';
7
- import { ProcedureCallNode } from '../../ast/procedure.js';
8
-
9
- const sanitizeVariableSuffix = (value: string): string =>
10
- value.replace(/[^a-zA-Z0-9_]/g, '_');
11
-
12
- /**
13
- * MySQL dialect implementation
14
- */
15
- export class MySqlDialect extends SqlDialectBase implements ProcedureCompiler {
16
- protected readonly dialect = 'mysql';
17
- /**
18
- * Creates a new MySqlDialect instance
19
- */
20
- public constructor() {
21
- super(new MysqlFunctionStrategy());
22
-
23
- this.registerExpressionCompiler(
24
- 'IsDistinctExpression',
25
- (node: IsDistinctExpressionNode, ctx: CompilerContext): string => {
26
- const left = this.compileOperand(node.left, ctx);
27
- const right = this.compileOperand(node.right, ctx);
28
- const spaceship = `${left} <=> ${right}`;
29
-
30
- if (node.operator === 'IS NOT DISTINCT FROM') {
31
- return spaceship;
13
+ import { MySqlProcedureCompiler } from './procedure-compiler.js';
14
+ import { MySqlUpsertStrategy } from './upsert.js';
15
+
16
+ const quoteIdentifier = (id: string): string => `\`${id}\``;
17
+
18
+ export type MySqlDialectImplementation = Dialect & ProcedureCompiler;
19
+
20
+ /** Creates the MySQL dialect entirely from composable compiler components. */
21
+ export const createMySqlDialect = (): MySqlDialectImplementation => {
22
+ const composition = composeSqlDialect({
23
+ name: 'mysql',
24
+ quoteIdentifier,
25
+ functionStrategy: new MysqlFunctionStrategy(),
26
+ upsertStrategy: new MySqlUpsertStrategy(),
27
+ compileJsonPath(node: JsonPathNode): string {
28
+ const column = `${quoteIdentifier(node.column.table)}.${quoteIdentifier(node.column.name)}`;
29
+ return `${column}->'${node.path}'`;
30
+ },
31
+ configureExpressions(api) {
32
+ api.registerExpressionCompiler(
33
+ 'IsDistinctExpression',
34
+ (node: IsDistinctExpressionNode, ctx): string => {
35
+ const left = api.compileOperand(node.left, ctx);
36
+ const right = api.compileOperand(node.right, ctx);
37
+ const spaceship = `${left} <=> ${right}`;
38
+ return node.operator === 'IS NOT DISTINCT FROM'
39
+ ? spaceship
40
+ : `NOT (${spaceship})`;
32
41
  }
42
+ );
43
+ }
44
+ });
33
45
 
34
- return `NOT (${spaceship})`;
35
- }
36
- );
37
- }
46
+ const procedures = new MySqlProcedureCompiler(composition.runtime);
47
+ return {
48
+ ...composition.dialect,
49
+ compileProcedureCall: ast => procedures.compileProcedureCall(ast)
50
+ };
51
+ };
52
+
53
+ /** Ergonomic constructor facade over the composed MySQL dialect. */
54
+ export class MySqlDialect implements Dialect, ProcedureCompiler {
55
+ private readonly impl: MySqlDialectImplementation = createMySqlDialect();
38
56
 
39
- /**
40
- * Quotes an identifier using MySQL backtick syntax
41
- * @param id - Identifier to quote
42
- * @returns Quoted identifier
43
- */
44
57
  quoteIdentifier(id: string): string {
45
- return `\`${id}\``;
58
+ return this.impl.quoteIdentifier(id);
46
59
  }
47
60
 
48
- /**
49
- * Compiles JSON path expression using MySQL syntax
50
- * @param node - JSON path node
51
- * @returns MySQL JSON path expression
52
- */
53
- protected compileJsonPath(node: JsonPathNode): string {
54
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
55
- // MySQL 5.7+ uses col->'$.path'
56
- return `${col}->'${node.path}'`;
61
+ supportsDmlReturningClause(): boolean {
62
+ return this.impl.supportsDmlReturningClause();
57
63
  }
58
64
 
59
- protected compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string {
60
- if (!ast.onConflict) return '';
65
+ compileSelect(ast: SelectQueryNode): CompiledQuery {
66
+ return this.impl.compileSelect(ast);
67
+ }
61
68
 
62
- const clause = ast.onConflict;
63
- if (clause.action.type === 'DoNothing') {
64
- const noOpColumn = clause.target.columns[0] ?? ast.columns[0];
65
- if (!noOpColumn) {
66
- throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one target column.');
67
- }
68
- const col = this.quoteIdentifier(noOpColumn.name);
69
- return ` ON DUPLICATE KEY UPDATE ${col} = ${col}`;
70
- }
69
+ compileInsert(ast: InsertQueryNode): CompiledQuery {
70
+ return this.impl.compileInsert(ast);
71
+ }
71
72
 
72
- if (clause.action.where) {
73
- throw new Error('MySQL ON DUPLICATE KEY UPDATE does not support a WHERE clause.');
74
- }
75
- if (!clause.action.set.length) {
76
- throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one assignment.');
77
- }
73
+ compileUpdate(ast: UpdateQueryNode): CompiledQuery {
74
+ return this.impl.compileUpdate(ast);
75
+ }
78
76
 
79
- const assignments = clause.action.set
80
- .map(assignment => {
81
- const target = this.quoteIdentifier(assignment.column.name);
82
- const value = this.compileOperand(assignment.value, ctx);
83
- return `${target} = ${value}`;
84
- })
85
- .join(', ');
86
- return ` ON DUPLICATE KEY UPDATE ${assignments}`;
77
+ compileDelete(ast: DeleteQueryNode): CompiledQuery {
78
+ return this.impl.compileDelete(ast);
87
79
  }
88
80
 
89
81
  compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
90
- const ctx = this.createCompilerContext();
91
- const qualifiedName = ast.ref.schema
92
- ? `${this.quoteIdentifier(ast.ref.schema)}.${this.quoteIdentifier(ast.ref.name)}`
93
- : this.quoteIdentifier(ast.ref.name);
94
-
95
- const prelude: string[] = [];
96
- const callArgs: string[] = [];
97
- const outVars: Array<{ variable: string; name: string }> = [];
98
-
99
- ast.params.forEach((param, index) => {
100
- const suffix = sanitizeVariableSuffix(param.name || `p${index + 1}`);
101
- const variable = `@__metal_${suffix}_${index + 1}`;
102
-
103
- if (param.direction === 'in') {
104
- if (!param.value) {
105
- throw new Error(`Procedure parameter "${param.name}" requires a value for direction "in".`);
106
- }
107
- callArgs.push(this.compileOperand(param.value, ctx));
108
- return;
109
- }
110
-
111
- if (param.direction === 'inout') {
112
- if (!param.value) {
113
- throw new Error(`Procedure parameter "${param.name}" requires a value for direction "inout".`);
114
- }
115
- prelude.push(`SET ${variable} = ${this.compileOperand(param.value, ctx)};`);
116
- }
117
-
118
- callArgs.push(variable);
119
- outVars.push({ variable, name: param.name });
120
- });
121
-
122
- const statements: string[] = [];
123
- if (prelude.length) {
124
- statements.push(...prelude);
125
- }
126
- statements.push(`CALL ${qualifiedName}(${callArgs.join(', ')});`);
127
-
128
- if (outVars.length) {
129
- const selectOut = outVars
130
- .map(({ variable, name }) => `${variable} AS ${this.quoteIdentifier(name)}`)
131
- .join(', ');
132
- statements.push(`SELECT ${selectOut};`);
133
- }
134
-
135
- return {
136
- sql: statements.join(' '),
137
- params: [...ctx.params],
138
- outParams: {
139
- source: outVars.length ? 'lastResultSet' : 'none',
140
- names: outVars.map(item => item.name)
141
- }
142
- };
82
+ return this.impl.compileProcedureCall(ast);
143
83
  }
144
84
  }
@@ -0,0 +1,67 @@
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type {
3
+ CompiledProcedureCall,
4
+ ProcedureCompiler,
5
+ ProcedureCompilerServices
6
+ } from '../capabilities/procedure-compiler.js';
7
+
8
+ const sanitizeVariableSuffix = (value: string): string =>
9
+ value.replace(/[^a-zA-Z0-9_]/g, '_');
10
+
11
+ export class MySqlProcedureCompiler implements ProcedureCompiler {
12
+ constructor(private readonly services: ProcedureCompilerServices) {}
13
+
14
+ compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
15
+ const ctx = this.services.createCompilerContext();
16
+ const qualifiedName = ast.ref.schema
17
+ ? `${this.services.quoteIdentifier(ast.ref.schema)}.${this.services.quoteIdentifier(ast.ref.name)}`
18
+ : this.services.quoteIdentifier(ast.ref.name);
19
+
20
+ const prelude: string[] = [];
21
+ const callArgs: string[] = [];
22
+ const outVars: Array<{ variable: string; name: string }> = [];
23
+
24
+ ast.params.forEach((param, index) => {
25
+ const suffix = sanitizeVariableSuffix(param.name || `p${index + 1}`);
26
+ const variable = `@__metal_${suffix}_${index + 1}`;
27
+
28
+ if (param.direction === 'in') {
29
+ if (!param.value) {
30
+ throw new Error(`Procedure parameter "${param.name}" requires a value for direction "in".`);
31
+ }
32
+ callArgs.push(this.services.compileOperand(param.value, ctx));
33
+ return;
34
+ }
35
+
36
+ if (param.direction === 'inout') {
37
+ if (!param.value) {
38
+ throw new Error(`Procedure parameter "${param.name}" requires a value for direction "inout".`);
39
+ }
40
+ prelude.push(`SET ${variable} = ${this.services.compileOperand(param.value, ctx)};`);
41
+ }
42
+
43
+ callArgs.push(variable);
44
+ outVars.push({ variable, name: param.name });
45
+ });
46
+
47
+ const statements: string[] = [];
48
+ if (prelude.length) statements.push(...prelude);
49
+ statements.push(`CALL ${qualifiedName}(${callArgs.join(', ')});`);
50
+
51
+ if (outVars.length) {
52
+ const selectOut = outVars
53
+ .map(({ variable, name }) => `${variable} AS ${this.services.quoteIdentifier(name)}`)
54
+ .join(', ');
55
+ statements.push(`SELECT ${selectOut};`);
56
+ }
57
+
58
+ return {
59
+ sql: statements.join(' '),
60
+ params: [...ctx.params],
61
+ outParams: {
62
+ source: outVars.length ? 'lastResultSet' : 'none',
63
+ names: outVars.map(item => item.name)
64
+ }
65
+ };
66
+ }
67
+ }
@@ -0,0 +1,42 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { InsertQueryNode } from '../../ast/query.js';
3
+ import type {
4
+ UpsertCompilationServices,
5
+ UpsertStrategy
6
+ } from '../base/upsert-strategy.js';
7
+
8
+ export class MySqlUpsertStrategy implements UpsertStrategy {
9
+ compile(
10
+ ast: InsertQueryNode,
11
+ ctx: CompilerContext,
12
+ services: UpsertCompilationServices
13
+ ): string {
14
+ if (!ast.onConflict) return '';
15
+
16
+ const clause = ast.onConflict;
17
+ if (clause.action.type === 'DoNothing') {
18
+ const noOpColumn = clause.target.columns[0] ?? ast.columns[0];
19
+ if (!noOpColumn) {
20
+ throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one target column.');
21
+ }
22
+ const col = services.quoteIdentifier(noOpColumn.name);
23
+ return ` ON DUPLICATE KEY UPDATE ${col} = ${col}`;
24
+ }
25
+
26
+ if (clause.action.where) {
27
+ throw new Error('MySQL ON DUPLICATE KEY UPDATE does not support a WHERE clause.');
28
+ }
29
+ if (!clause.action.set.length) {
30
+ throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one assignment.');
31
+ }
32
+
33
+ const assignments = clause.action.set
34
+ .map(assignment => {
35
+ const target = services.quoteIdentifier(assignment.column.name);
36
+ const value = services.compileOperand(assignment.value, ctx);
37
+ return `${target} = ${value}`;
38
+ })
39
+ .join(', ');
40
+ return ` ON DUPLICATE KEY UPDATE ${assignments}`;
41
+ }
42
+ }