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.
- package/dist/index.cjs +539 -412
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +225 -176
- package/dist/index.d.ts +225 -176
- package/dist/index.js +519 -412
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- 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.ts +79 -38
- package/src/core/dialect/base/upsert-strategy.ts +45 -0
- package/src/core/dialect/capabilities/procedure-compiler.ts +10 -8
- 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 +24 -371
- 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 +24 -117
- 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 +34 -101
- 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 +15 -70
- package/src/core/dialect/sqlite/returning.ts +30 -0
- package/src/core/dialect/sqlite/upsert.ts +43 -0
- package/src/index.ts +22 -10
|
@@ -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,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ProcedureCallNode } from '../../ast/procedure.js';
|
|
2
|
+
import type { JsonPathNode, IsDistinctExpressionNode } from '../../ast/expression.js';
|
|
2
3
|
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
4
|
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
6
5
|
import { MysqlFunctionStrategy } from './functions.js';
|
|
7
|
-
import {
|
|
6
|
+
import { MySqlProcedureCompiler } from './procedure-compiler.js';
|
|
7
|
+
import { MySqlUpsertStrategy } from './upsert.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
value.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* MySQL dialect implementation
|
|
14
|
-
*/
|
|
9
|
+
/** MySQL dialect assembled from reusable compiler components. */
|
|
15
10
|
export class MySqlDialect extends SqlDialectBase implements ProcedureCompiler {
|
|
16
11
|
protected readonly dialect = 'mysql';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
12
|
+
private readonly procedureCompiler: MySqlProcedureCompiler;
|
|
13
|
+
|
|
20
14
|
public constructor() {
|
|
21
|
-
super(
|
|
15
|
+
super({
|
|
16
|
+
functionStrategy: new MysqlFunctionStrategy(),
|
|
17
|
+
upsertStrategy: new MySqlUpsertStrategy()
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
this.procedureCompiler = new MySqlProcedureCompiler({
|
|
21
|
+
quoteIdentifier: id => this.quoteIdentifier(id),
|
|
22
|
+
createCompilerContext: () => this.createCompilerContext(),
|
|
23
|
+
compileOperand: (node, ctx) => this.compileOperand(node, ctx)
|
|
24
|
+
});
|
|
22
25
|
|
|
23
26
|
this.registerExpressionCompiler(
|
|
24
27
|
'IsDistinctExpression',
|
|
25
|
-
(node: IsDistinctExpressionNode, ctx
|
|
28
|
+
(node: IsDistinctExpressionNode, ctx): string => {
|
|
26
29
|
const left = this.compileOperand(node.left, ctx);
|
|
27
30
|
const right = this.compileOperand(node.right, ctx);
|
|
28
31
|
const spaceship = `${left} <=> ${right}`;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return `NOT (${spaceship})`;
|
|
32
|
+
return node.operator === 'IS NOT DISTINCT FROM'
|
|
33
|
+
? spaceship
|
|
34
|
+
: `NOT (${spaceship})`;
|
|
35
35
|
}
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Quotes an identifier using MySQL backtick syntax
|
|
41
|
-
* @param id - Identifier to quote
|
|
42
|
-
* @returns Quoted identifier
|
|
43
|
-
*/
|
|
44
39
|
quoteIdentifier(id: string): string {
|
|
45
40
|
return `\`${id}\``;
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
/**
|
|
49
|
-
* Compiles JSON path expression using MySQL syntax
|
|
50
|
-
* @param node - JSON path node
|
|
51
|
-
* @returns MySQL JSON path expression
|
|
52
|
-
*/
|
|
53
43
|
protected compileJsonPath(node: JsonPathNode): string {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
return `${col}->'${node.path}'`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
protected compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
60
|
-
if (!ast.onConflict) return '';
|
|
61
|
-
|
|
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
|
-
}
|
|
71
|
-
|
|
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
|
-
}
|
|
78
|
-
|
|
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}`;
|
|
44
|
+
const column = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
45
|
+
return `${column}->'${node.path}'`;
|
|
87
46
|
}
|
|
88
47
|
|
|
89
48
|
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
|
-
};
|
|
49
|
+
return this.procedureCompiler.compileProcedureCall(ast);
|
|
143
50
|
}
|
|
144
51
|
}
|
|
@@ -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
|
+
}
|
|
@@ -1,41 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ProcedureCallNode } from '../../ast/procedure.js';
|
|
2
|
+
import type { BitwiseExpressionNode, ColumnNode, JsonPathNode } from '../../ast/expression.js';
|
|
3
|
+
import type { TableNode } from '../../ast/query.js';
|
|
2
4
|
import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
|
|
3
|
-
import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
|
|
4
|
-
import { InsertQueryNode, TableNode } from '../../ast/query.js';
|
|
5
5
|
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
6
6
|
import { PostgresFunctionStrategy } from './functions.js';
|
|
7
7
|
import { PostgresTableFunctionStrategy } from './table-functions.js';
|
|
8
|
-
import {
|
|
8
|
+
import { PostgresProcedureCompiler } from './procedure-compiler.js';
|
|
9
|
+
import { PostgresReturningStrategy } from './returning.js';
|
|
10
|
+
import { PostgresUpsertStrategy } from './upsert.js';
|
|
9
11
|
|
|
10
|
-
/**
|
|
11
|
-
* PostgreSQL dialect implementation
|
|
12
|
-
*/
|
|
12
|
+
/** PostgreSQL dialect assembled from reusable compiler components. */
|
|
13
13
|
export class PostgresDialect extends SqlDialectBase implements ProcedureCompiler {
|
|
14
14
|
protected readonly dialect = 'postgres';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*/
|
|
15
|
+
private readonly procedureCompiler: PostgresProcedureCompiler;
|
|
16
|
+
|
|
18
17
|
public constructor() {
|
|
19
|
-
super(
|
|
18
|
+
super({
|
|
19
|
+
functionStrategy: new PostgresFunctionStrategy(),
|
|
20
|
+
tableFunctionStrategy: new PostgresTableFunctionStrategy(),
|
|
21
|
+
returningStrategy: new PostgresReturningStrategy(),
|
|
22
|
+
upsertStrategy: new PostgresUpsertStrategy(),
|
|
23
|
+
supportsDmlReturning: true
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.procedureCompiler = new PostgresProcedureCompiler({
|
|
27
|
+
quoteIdentifier: id => this.quoteIdentifier(id),
|
|
28
|
+
createCompilerContext: () => this.createCompilerContext(),
|
|
29
|
+
compileOperand: (node, ctx) => this.compileOperand(node, ctx)
|
|
30
|
+
});
|
|
31
|
+
|
|
20
32
|
this.registerExpressionCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
|
|
21
33
|
const left = this.compileOperand(node.left, ctx);
|
|
22
34
|
const right = this.compileOperand(node.right, ctx);
|
|
23
|
-
const
|
|
24
|
-
return `${left} ${
|
|
35
|
+
const operator = node.operator === '^' ? '#' : node.operator;
|
|
36
|
+
return `${left} ${operator} ${right}`;
|
|
25
37
|
});
|
|
26
38
|
this.registerOperandCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
|
|
27
39
|
const left = this.compileOperand(node.left, ctx);
|
|
28
40
|
const right = this.compileOperand(node.right, ctx);
|
|
29
|
-
const
|
|
30
|
-
return `(${left} ${
|
|
41
|
+
const operator = node.operator === '^' ? '#' : node.operator;
|
|
42
|
+
return `(${left} ${operator} ${right})`;
|
|
31
43
|
});
|
|
32
44
|
}
|
|
33
45
|
|
|
34
|
-
/**
|
|
35
|
-
* Quotes an identifier using PostgreSQL double-quote syntax
|
|
36
|
-
* @param id - Identifier to quote
|
|
37
|
-
* @returns Quoted identifier
|
|
38
|
-
*/
|
|
39
46
|
quoteIdentifier(id: string): string {
|
|
40
47
|
return `"${id}"`;
|
|
41
48
|
}
|
|
@@ -44,92 +51,18 @@ export class PostgresDialect extends SqlDialectBase implements ProcedureCompiler
|
|
|
44
51
|
return `$${index}`;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
/**
|
|
48
|
-
* Compiles JSON path expression using PostgreSQL syntax
|
|
49
|
-
* @param node - JSON path node
|
|
50
|
-
* @returns PostgreSQL JSON path expression
|
|
51
|
-
*/
|
|
52
54
|
protected compileJsonPath(node: JsonPathNode): string {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
return `${col}->>'${node.path}'`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
59
|
-
void ctx;
|
|
60
|
-
if (!returning || returning.length === 0) return '';
|
|
61
|
-
const columns = this.formatReturningColumns(returning);
|
|
62
|
-
return ` RETURNING ${columns}`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
protected compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
66
|
-
if (!ast.onConflict) return '';
|
|
67
|
-
|
|
68
|
-
const clause = ast.onConflict;
|
|
69
|
-
const target = clause.target.constraint
|
|
70
|
-
? ` ON CONFLICT ON CONSTRAINT ${this.quoteIdentifier(clause.target.constraint)}`
|
|
71
|
-
: (() => {
|
|
72
|
-
this.ensureConflictColumns(
|
|
73
|
-
clause,
|
|
74
|
-
'PostgreSQL ON CONFLICT requires conflict columns or a constraint name.'
|
|
75
|
-
);
|
|
76
|
-
const cols = clause.target.columns.map(col => this.quoteIdentifier(col.name)).join(', ');
|
|
77
|
-
return ` ON CONFLICT (${cols})`;
|
|
78
|
-
})();
|
|
79
|
-
|
|
80
|
-
if (clause.action.type === 'DoNothing') {
|
|
81
|
-
return `${target} DO NOTHING`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!clause.action.set.length) {
|
|
85
|
-
throw new Error('PostgreSQL ON CONFLICT DO UPDATE requires at least one assignment.');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const assignments = this.compileUpdateAssignments(clause.action.set, ast.into, ctx);
|
|
89
|
-
const where = clause.action.where
|
|
90
|
-
? ` WHERE ${this.compileExpression(clause.action.where, ctx)}`
|
|
91
|
-
: '';
|
|
92
|
-
return `${target} DO UPDATE SET ${assignments}${where}`;
|
|
55
|
+
const column = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
56
|
+
return `${column}->>'${node.path}'`;
|
|
93
57
|
}
|
|
94
58
|
|
|
95
|
-
|
|
96
|
-
|
|
59
|
+
/** PostgreSQL requires unqualified column names in SET clauses. */
|
|
60
|
+
protected compileSetTarget(column: ColumnNode, _table: TableNode): string {
|
|
61
|
+
void _table;
|
|
62
|
+
return this.quoteIdentifier(column.name);
|
|
97
63
|
}
|
|
98
64
|
|
|
99
65
|
compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
|
|
100
|
-
|
|
101
|
-
const qualifiedName = ast.ref.schema
|
|
102
|
-
? `${this.quoteIdentifier(ast.ref.schema)}.${this.quoteIdentifier(ast.ref.name)}`
|
|
103
|
-
: this.quoteIdentifier(ast.ref.name);
|
|
104
|
-
|
|
105
|
-
const args: string[] = [];
|
|
106
|
-
for (const param of ast.params) {
|
|
107
|
-
if (param.direction === 'out') continue;
|
|
108
|
-
if (!param.value) {
|
|
109
|
-
throw new Error(`Procedure parameter "${param.name}" requires a value for direction "${param.direction}".`);
|
|
110
|
-
}
|
|
111
|
-
args.push(this.compileOperand(param.value, ctx));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const outNames = ast.params
|
|
115
|
-
.filter(param => param.direction === 'out' || param.direction === 'inout')
|
|
116
|
-
.map(param => param.name);
|
|
117
|
-
|
|
118
|
-
const rawSql = `CALL ${qualifiedName}(${args.join(', ')})`;
|
|
119
|
-
return {
|
|
120
|
-
sql: `${rawSql};`,
|
|
121
|
-
params: [...ctx.params],
|
|
122
|
-
outParams: {
|
|
123
|
-
source: outNames.length ? 'firstResultSet' : 'none',
|
|
124
|
-
names: outNames
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* PostgreSQL requires unqualified column names in SET clause
|
|
131
|
-
*/
|
|
132
|
-
protected compileSetTarget(column: ColumnNode, _table: TableNode): string {
|
|
133
|
-
return this.quoteIdentifier(column.name);
|
|
66
|
+
return this.procedureCompiler.compileProcedureCall(ast);
|
|
134
67
|
}
|
|
135
68
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
export class PostgresProcedureCompiler implements ProcedureCompiler {
|
|
9
|
+
constructor(private readonly services: ProcedureCompilerServices) {}
|
|
10
|
+
|
|
11
|
+
compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
|
|
12
|
+
const ctx = this.services.createCompilerContext();
|
|
13
|
+
const qualifiedName = ast.ref.schema
|
|
14
|
+
? `${this.services.quoteIdentifier(ast.ref.schema)}.${this.services.quoteIdentifier(ast.ref.name)}`
|
|
15
|
+
: this.services.quoteIdentifier(ast.ref.name);
|
|
16
|
+
|
|
17
|
+
const args: string[] = [];
|
|
18
|
+
for (const param of ast.params) {
|
|
19
|
+
if (param.direction === 'out') continue;
|
|
20
|
+
if (!param.value) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Procedure parameter "${param.name}" requires a value for direction "${param.direction}".`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
args.push(this.services.compileOperand(param.value, ctx));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const outNames = ast.params
|
|
29
|
+
.filter(param => param.direction === 'out' || param.direction === 'inout')
|
|
30
|
+
.map(param => param.name);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
sql: `CALL ${qualifiedName}(${args.join(', ')});`,
|
|
34
|
+
params: [...ctx.params],
|
|
35
|
+
outParams: {
|
|
36
|
+
source: outNames.length ? 'firstResultSet' : 'none',
|
|
37
|
+
names: outNames
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|