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.
- package/dist/index.cjs +770 -710
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -318
- package/dist/index.d.ts +294 -318
- package/dist/index.js +745 -709
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/dialect/abstract.ts +7 -229
- package/src/core/dialect/base/returning-strategy.ts +40 -39
- package/src/core/dialect/base/sql-compiler-set.ts +33 -0
- package/src/core/dialect/base/sql-dialect-composer.ts +294 -0
- package/src/core/dialect/base/standard-sql-services.ts +2 -6
- package/src/core/dialect/base/upsert-strategy.ts +44 -0
- package/src/core/dialect/capabilities/procedure-compiler.ts +10 -8
- package/src/core/dialect/dialect-factory.ts +17 -49
- package/src/core/dialect/mssql/compiler-factory.ts +12 -0
- package/src/core/dialect/mssql/delete-compiler.ts +40 -0
- package/src/core/dialect/mssql/index.ts +52 -370
- package/src/core/dialect/mssql/insert-compiler.ts +112 -0
- package/src/core/dialect/mssql/output.ts +46 -0
- package/src/core/dialect/mssql/procedure-compiler.ts +81 -0
- package/src/core/dialect/mssql/select-compiler.ts +116 -0
- package/src/core/dialect/mssql/update-compiler.ts +37 -0
- package/src/core/dialect/mysql/index.ts +66 -126
- package/src/core/dialect/mysql/procedure-compiler.ts +67 -0
- package/src/core/dialect/mysql/upsert.ts +42 -0
- package/src/core/dialect/postgres/index.ts +75 -114
- package/src/core/dialect/postgres/procedure-compiler.ts +41 -0
- package/src/core/dialect/postgres/returning.ts +4 -0
- package/src/core/dialect/postgres/upsert.ts +43 -0
- package/src/core/dialect/sqlite/index.ts +65 -90
- package/src/core/dialect/sqlite/returning.ts +30 -0
- package/src/core/dialect/sqlite/upsert.ts +43 -0
- package/src/index.ts +22 -10
- 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 {
|
|
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 {
|
|
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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
58
|
+
return this.impl.quoteIdentifier(id);
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
60
|
-
|
|
65
|
+
compileSelect(ast: SelectQueryNode): CompiledQuery {
|
|
66
|
+
return this.impl.compileSelect(ast);
|
|
67
|
+
}
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
+
}
|