metal-orm 1.0.39 → 1.0.41
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 +1466 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +723 -51
- package/dist/index.d.ts +723 -51
- package/dist/index.js +1457 -189
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/typescript.ts +66 -5
- package/src/core/ast/aggregate-functions.ts +15 -15
- package/src/core/ast/expression-builders.ts +378 -316
- package/src/core/ast/expression-nodes.ts +210 -186
- package/src/core/ast/expression-visitor.ts +40 -30
- package/src/core/ast/query.ts +164 -132
- package/src/core/ast/window-functions.ts +86 -86
- package/src/core/dialect/abstract.ts +509 -479
- package/src/core/dialect/base/groupby-compiler.ts +6 -6
- package/src/core/dialect/base/join-compiler.ts +9 -12
- package/src/core/dialect/base/orderby-compiler.ts +20 -6
- package/src/core/dialect/base/sql-dialect.ts +237 -138
- package/src/core/dialect/mssql/index.ts +164 -185
- package/src/core/dialect/sqlite/index.ts +39 -34
- package/src/core/execution/db-executor.ts +46 -6
- package/src/core/execution/executors/mssql-executor.ts +39 -22
- package/src/core/execution/executors/mysql-executor.ts +23 -6
- package/src/core/execution/executors/sqlite-executor.ts +29 -3
- package/src/core/execution/pooling/pool-types.ts +30 -0
- package/src/core/execution/pooling/pool.ts +268 -0
- package/src/core/functions/standard-strategy.ts +46 -37
- package/src/decorators/bootstrap.ts +7 -7
- package/src/index.ts +6 -0
- package/src/orm/domain-event-bus.ts +49 -0
- package/src/orm/entity-metadata.ts +9 -9
- package/src/orm/entity.ts +58 -0
- package/src/orm/orm-session.ts +465 -270
- package/src/orm/orm.ts +61 -11
- package/src/orm/pooled-executor-factory.ts +131 -0
- package/src/orm/query-logger.ts +6 -12
- package/src/orm/relation-change-processor.ts +75 -0
- package/src/orm/relations/many-to-many.ts +4 -2
- package/src/orm/save-graph.ts +303 -0
- package/src/orm/transaction-runner.ts +3 -3
- package/src/orm/unit-of-work.ts +128 -0
- package/src/query-builder/delete-query-state.ts +67 -38
- package/src/query-builder/delete.ts +37 -1
- package/src/query-builder/hydration-manager.ts +93 -79
- package/src/query-builder/insert-query-state.ts +131 -61
- package/src/query-builder/insert.ts +27 -1
- package/src/query-builder/query-ast-service.ts +207 -170
- package/src/query-builder/select-query-state.ts +169 -162
- package/src/query-builder/select.ts +15 -23
- package/src/query-builder/update-query-state.ts +114 -77
- package/src/query-builder/update.ts +38 -1
|
@@ -1,213 +1,192 @@
|
|
|
1
|
-
import { CompilerContext
|
|
2
|
-
import {
|
|
1
|
+
import { CompilerContext } from '../abstract.js';
|
|
2
|
+
import {
|
|
3
|
+
SelectQueryNode,
|
|
4
|
+
DeleteQueryNode,
|
|
5
|
+
TableSourceNode,
|
|
6
|
+
DerivedTableNode,
|
|
7
|
+
OrderByNode
|
|
8
|
+
} from '../../ast/query.js';
|
|
3
9
|
import { JsonPathNode } from '../../ast/expression.js';
|
|
4
10
|
import { MssqlFunctionStrategy } from './functions.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
* @
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
|
|
82
|
-
const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
|
|
83
|
-
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
87
|
-
const table = this.quoteIdentifier(ast.table.name);
|
|
88
|
-
const assignments = ast.set.map(assignment => {
|
|
89
|
-
const col = assignment.column;
|
|
90
|
-
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
91
|
-
const value = this.compileOperand(assignment.value, ctx);
|
|
92
|
-
return `${target} = ${value}`;
|
|
93
|
-
}).join(', ');
|
|
94
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
95
|
-
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
96
|
-
}
|
|
97
|
-
|
|
11
|
+
import { OrderByCompiler } from '../base/orderby-compiler.js';
|
|
12
|
+
import { JoinCompiler } from '../base/join-compiler.js';
|
|
13
|
+
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Microsoft SQL Server dialect implementation
|
|
17
|
+
*/
|
|
18
|
+
export class SqlServerDialect extends SqlDialectBase {
|
|
19
|
+
protected readonly dialect = 'mssql';
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new SqlServerDialect instance
|
|
22
|
+
*/
|
|
23
|
+
public constructor() {
|
|
24
|
+
super(new MssqlFunctionStrategy());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Quotes an identifier using SQL Server bracket syntax
|
|
29
|
+
* @param id - Identifier to quote
|
|
30
|
+
* @returns Quoted identifier
|
|
31
|
+
*/
|
|
32
|
+
quoteIdentifier(id: string): string {
|
|
33
|
+
return `[${id}]`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compiles JSON path expression using SQL Server syntax
|
|
38
|
+
* @param node - JSON path node
|
|
39
|
+
* @returns SQL Server JSON path expression
|
|
40
|
+
*/
|
|
41
|
+
protected compileJsonPath(node: JsonPathNode): string {
|
|
42
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
43
|
+
// SQL Server uses JSON_VALUE(col, '$.path')
|
|
44
|
+
return `JSON_VALUE(${col}, '${node.path}')`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Formats parameter placeholders using SQL Server named parameter syntax
|
|
49
|
+
* @param index - Parameter index
|
|
50
|
+
* @returns Named parameter placeholder
|
|
51
|
+
*/
|
|
52
|
+
protected formatPlaceholder(index: number): string {
|
|
53
|
+
return `@p${index}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compiles SELECT query AST to SQL Server SQL
|
|
58
|
+
* @param ast - Query AST
|
|
59
|
+
* @param ctx - Compiler context
|
|
60
|
+
* @returns SQL Server SQL string
|
|
61
|
+
*/
|
|
62
|
+
protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
63
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
64
|
+
const ctes = this.compileCtes(ast, ctx);
|
|
65
|
+
|
|
66
|
+
const baseAst: SelectQueryNode = hasSetOps
|
|
67
|
+
? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
|
|
68
|
+
: ast;
|
|
69
|
+
|
|
70
|
+
const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
|
|
71
|
+
|
|
72
|
+
if (!hasSetOps) {
|
|
73
|
+
return `${ctes}${baseSelect}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const compound = ast.setOps!
|
|
77
|
+
.map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
|
|
78
|
+
.join(' ');
|
|
79
|
+
|
|
80
|
+
const orderBy = this.compileOrderBy(ast, ctx);
|
|
81
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
82
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
83
|
+
const tail = pagination || orderBy;
|
|
84
|
+
return `${ctes}${combined}${tail}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
98
87
|
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
88
|
+
if (ast.using) {
|
|
89
|
+
throw new Error('DELETE ... USING is not supported in the MSSQL dialect; use join() instead.');
|
|
90
|
+
}
|
|
91
|
+
|
|
99
92
|
if (ast.from.type !== 'Table') {
|
|
100
93
|
throw new Error('DELETE only supports base tables in the MSSQL dialect.');
|
|
101
94
|
}
|
|
102
|
-
|
|
95
|
+
|
|
96
|
+
const alias = ast.from.alias ?? ast.from.name;
|
|
97
|
+
const target = this.compileTableReference(ast.from);
|
|
98
|
+
const joins = JoinCompiler.compileJoins(
|
|
99
|
+
ast.joins,
|
|
100
|
+
ctx,
|
|
101
|
+
this.compileFrom.bind(this),
|
|
102
|
+
this.compileExpression.bind(this)
|
|
103
|
+
);
|
|
103
104
|
const whereClause = this.compileWhere(ast.where, ctx);
|
|
104
|
-
|
|
105
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
106
|
+
return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
private
|
|
108
|
-
const columns = ast.columns.map(c => {
|
|
109
|
-
let expr = '';
|
|
110
|
-
if (c.type === 'Function') {
|
|
111
|
-
expr = this.compileOperand(c, ctx);
|
|
112
|
-
} else if (c.type === 'Column') {
|
|
113
|
-
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
114
|
-
} else if (c.type === 'ScalarSubquery') {
|
|
115
|
-
expr = this.compileOperand(c, ctx);
|
|
116
|
-
} else if (c.type === 'WindowFunction') {
|
|
117
|
-
expr = this.compileOperand(c, ctx);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (c.alias) {
|
|
121
|
-
if (c.alias.includes('(')) return c.alias;
|
|
122
|
-
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
123
|
-
}
|
|
124
|
-
return expr;
|
|
125
|
-
}).join(', ');
|
|
109
|
+
private compileSelectCoreForMssql(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
110
|
+
const columns = ast.columns.map(c => {
|
|
111
|
+
let expr = '';
|
|
112
|
+
if (c.type === 'Function') {
|
|
113
|
+
expr = this.compileOperand(c, ctx);
|
|
114
|
+
} else if (c.type === 'Column') {
|
|
115
|
+
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
116
|
+
} else if (c.type === 'ScalarSubquery') {
|
|
117
|
+
expr = this.compileOperand(c, ctx);
|
|
118
|
+
} else if (c.type === 'WindowFunction') {
|
|
119
|
+
expr = this.compileOperand(c, ctx);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (c.alias) {
|
|
123
|
+
if (c.alias.includes('(')) return c.alias;
|
|
124
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
125
|
+
}
|
|
126
|
+
return expr;
|
|
127
|
+
}).join(', ');
|
|
126
128
|
|
|
127
129
|
const distinct = ast.distinct ? 'DISTINCT ' : '';
|
|
128
|
-
const from = this.compileTableSource(ast.from
|
|
130
|
+
const from = this.compileTableSource(ast.from);
|
|
129
131
|
|
|
130
132
|
const joins = ast.joins.map(j => {
|
|
131
|
-
const table = this.compileTableSource(j.table
|
|
133
|
+
const table = this.compileTableSource(j.table);
|
|
132
134
|
const cond = this.compileExpression(j.condition, ctx);
|
|
133
135
|
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
134
136
|
}).join(' ');
|
|
135
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
136
|
-
|
|
137
|
-
const groupBy = ast.groupBy && ast.groupBy.length > 0
|
|
138
|
-
? ' GROUP BY ' + ast.groupBy.map(
|
|
139
|
-
: '';
|
|
140
|
-
|
|
141
|
-
const having = ast.having
|
|
142
|
-
? ` HAVING ${this.compileExpression(ast.having, ctx)}`
|
|
143
|
-
: '';
|
|
144
|
-
|
|
145
|
-
const orderBy = this.compileOrderBy(ast);
|
|
146
|
-
const pagination = this.compilePagination(ast, orderBy);
|
|
147
|
-
|
|
148
|
-
if (pagination) {
|
|
149
|
-
return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination}`;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
private compileOrderBy(ast: SelectQueryNode): string {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
|
|
161
|
-
|
|
137
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
138
|
+
|
|
139
|
+
const groupBy = ast.groupBy && ast.groupBy.length > 0
|
|
140
|
+
? ' GROUP BY ' + ast.groupBy.map(term => this.compileOrderingTerm(term, ctx)).join(', ')
|
|
141
|
+
: '';
|
|
142
|
+
|
|
143
|
+
const having = ast.having
|
|
144
|
+
? ` HAVING ${this.compileExpression(ast.having, ctx)}`
|
|
145
|
+
: '';
|
|
146
|
+
|
|
147
|
+
const orderBy = this.compileOrderBy(ast, ctx);
|
|
148
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
149
|
+
|
|
150
|
+
if (pagination) {
|
|
151
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private compileOrderBy(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
158
|
+
return OrderByCompiler.compileOrderBy(
|
|
159
|
+
ast,
|
|
160
|
+
term => this.compileOrderingTerm(term, ctx),
|
|
161
|
+
this.renderOrderByNulls.bind(this),
|
|
162
|
+
this.renderOrderByCollation.bind(this)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
162
166
|
private compilePagination(ast: SelectQueryNode, orderBy: string): string {
|
|
163
167
|
const hasLimit = ast.limit !== undefined;
|
|
164
168
|
const hasOffset = ast.offset !== undefined;
|
|
165
169
|
if (!hasLimit && !hasOffset) return '';
|
|
166
|
-
|
|
167
|
-
const off = ast.offset ?? 0;
|
|
168
|
-
const orderClause = orderBy || ' ORDER BY (SELECT NULL)';
|
|
169
|
-
let pagination = `${orderClause} OFFSET ${off} ROWS`;
|
|
170
|
+
|
|
171
|
+
const off = ast.offset ?? 0;
|
|
172
|
+
const orderClause = orderBy || ' ORDER BY (SELECT NULL)';
|
|
173
|
+
let pagination = `${orderClause} OFFSET ${off} ROWS`;
|
|
170
174
|
if (hasLimit) {
|
|
171
175
|
pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
|
|
172
176
|
}
|
|
173
177
|
return pagination;
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
private
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
|
|
180
|
+
private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
181
|
+
if (!ast.ctes || ast.ctes.length === 0) return '';
|
|
182
|
+
// MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
|
|
183
|
+
const defs = ast.ctes.map(cte => {
|
|
184
|
+
const name = this.quoteIdentifier(cte.name);
|
|
185
|
+
const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
|
|
186
|
+
const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, '');
|
|
187
|
+
return `${name}${cols} AS (${query})`;
|
|
188
|
+
}).join(', ');
|
|
189
|
+
return `WITH ${defs} `;
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
|
|
190
|
-
const sub = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, '');
|
|
191
|
-
const cols = table.columnAliases?.length
|
|
192
|
-
? ` (${table.columnAliases.map(c => this.quoteIdentifier(c)).join(', ')})`
|
|
193
|
-
: '';
|
|
194
|
-
return `(${sub}) AS ${this.quoteIdentifier(table.alias)}${cols}`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
198
|
-
if (!ast.ctes || ast.ctes.length === 0) return '';
|
|
199
|
-
// MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
|
|
200
|
-
const defs = ast.ctes.map(cte => {
|
|
201
|
-
const name = this.quoteIdentifier(cte.name);
|
|
202
|
-
const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
|
|
203
|
-
const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, '');
|
|
204
|
-
return `${name}${cols} AS (${query})`;
|
|
205
|
-
}).join(', ');
|
|
206
|
-
return `WITH ${defs} `;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private wrapSetOperand(sql: string): string {
|
|
210
|
-
const trimmed = sql.trim().replace(/;$/, '');
|
|
211
|
-
return `(${trimmed})`;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
192
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { CompilerContext } from '../abstract.js';
|
|
2
|
-
import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { CompilerContext } from '../abstract.js';
|
|
2
|
+
import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
|
|
3
|
+
import { TableNode } from '../../ast/query.js';
|
|
4
|
+
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
5
|
+
import { SqliteFunctionStrategy } from './functions.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SQLite dialect implementation
|
|
9
|
+
*/
|
|
9
10
|
export class SqliteDialect extends SqlDialectBase {
|
|
10
11
|
protected readonly dialect = 'sqlite';
|
|
11
12
|
/**
|
|
@@ -14,27 +15,31 @@ export class SqliteDialect extends SqlDialectBase {
|
|
|
14
15
|
public constructor() {
|
|
15
16
|
super(new SqliteFunctionStrategy());
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Quotes an identifier using SQLite double-quote syntax
|
|
20
|
-
* @param id - Identifier to quote
|
|
21
|
-
* @returns Quoted identifier
|
|
22
|
-
*/
|
|
23
|
-
quoteIdentifier(id: string): string {
|
|
24
|
-
return `"${id}"`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Compiles JSON path expression using SQLite syntax
|
|
29
|
-
* @param node - JSON path node
|
|
30
|
-
* @returns SQLite JSON path expression
|
|
31
|
-
*/
|
|
32
|
-
protected compileJsonPath(node: JsonPathNode): string {
|
|
33
|
-
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
34
|
-
// SQLite uses json_extract(col, '$.path')
|
|
35
|
-
return `json_extract(${col}, '${node.path}')`;
|
|
36
|
-
}
|
|
37
|
-
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Quotes an identifier using SQLite double-quote syntax
|
|
21
|
+
* @param id - Identifier to quote
|
|
22
|
+
* @returns Quoted identifier
|
|
23
|
+
*/
|
|
24
|
+
quoteIdentifier(id: string): string {
|
|
25
|
+
return `"${id}"`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compiles JSON path expression using SQLite syntax
|
|
30
|
+
* @param node - JSON path node
|
|
31
|
+
* @returns SQLite JSON path expression
|
|
32
|
+
*/
|
|
33
|
+
protected compileJsonPath(node: JsonPathNode): string {
|
|
34
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
35
|
+
// SQLite uses json_extract(col, '$.path')
|
|
36
|
+
return `json_extract(${col}, '${node.path}')`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected compileQualifiedColumn(column: ColumnNode, _table: TableNode): string {
|
|
40
|
+
return this.quoteIdentifier(column.name);
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
39
44
|
if (!returning || returning.length === 0) return '';
|
|
40
45
|
const columns = this.formatReturningColumns(returning);
|
|
@@ -49,8 +54,8 @@ export class SqliteDialect extends SqlDialectBase {
|
|
|
49
54
|
})
|
|
50
55
|
.join(', ');
|
|
51
56
|
}
|
|
52
|
-
|
|
53
|
-
supportsReturning(): boolean {
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
+
|
|
58
|
+
supportsReturning(): boolean {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -7,11 +7,20 @@ export type QueryResult = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export interface DbExecutor {
|
|
10
|
+
/** Capability flags so the runtime can make correct decisions without relying on optional methods. */
|
|
11
|
+
readonly capabilities: {
|
|
12
|
+
/** True if begin/commit/rollback are real and should be used to provide atomicity. */
|
|
13
|
+
transactions: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
10
16
|
executeSql(sql: string, params?: unknown[]): Promise<QueryResult[]>;
|
|
11
17
|
|
|
12
|
-
beginTransaction
|
|
13
|
-
commitTransaction
|
|
14
|
-
rollbackTransaction
|
|
18
|
+
beginTransaction(): Promise<void>;
|
|
19
|
+
commitTransaction(): Promise<void>;
|
|
20
|
+
rollbackTransaction(): Promise<void>;
|
|
21
|
+
|
|
22
|
+
/** Release any underlying resources (connections, pool leases, etc). Must be idempotent. */
|
|
23
|
+
dispose(): Promise<void>;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
// --- helpers ---
|
|
@@ -39,9 +48,14 @@ export interface SimpleQueryRunner {
|
|
|
39
48
|
sql: string,
|
|
40
49
|
params?: unknown[]
|
|
41
50
|
): Promise<Array<Record<string, unknown>>>;
|
|
51
|
+
|
|
52
|
+
/** Optional: used to support real transactions. */
|
|
42
53
|
beginTransaction?(): Promise<void>;
|
|
43
54
|
commitTransaction?(): Promise<void>;
|
|
44
55
|
rollbackTransaction?(): Promise<void>;
|
|
56
|
+
|
|
57
|
+
/** Optional: release resources (connection close, pool lease release, etc). */
|
|
58
|
+
dispose?(): Promise<void>;
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
/**
|
|
@@ -50,14 +64,40 @@ export interface SimpleQueryRunner {
|
|
|
50
64
|
export function createExecutorFromQueryRunner(
|
|
51
65
|
runner: SimpleQueryRunner
|
|
52
66
|
): DbExecutor {
|
|
67
|
+
const supportsTransactions =
|
|
68
|
+
typeof runner.beginTransaction === 'function' &&
|
|
69
|
+
typeof runner.commitTransaction === 'function' &&
|
|
70
|
+
typeof runner.rollbackTransaction === 'function';
|
|
71
|
+
|
|
53
72
|
return {
|
|
73
|
+
capabilities: {
|
|
74
|
+
transactions: supportsTransactions,
|
|
75
|
+
},
|
|
54
76
|
async executeSql(sql, params) {
|
|
55
77
|
const rows = await runner.query(sql, params);
|
|
56
78
|
const result = rowsToQueryResult(rows);
|
|
57
79
|
return [result];
|
|
58
80
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
async beginTransaction() {
|
|
82
|
+
if (!supportsTransactions) {
|
|
83
|
+
throw new Error('Transactions are not supported by this executor');
|
|
84
|
+
}
|
|
85
|
+
await runner.beginTransaction!.call(runner);
|
|
86
|
+
},
|
|
87
|
+
async commitTransaction() {
|
|
88
|
+
if (!supportsTransactions) {
|
|
89
|
+
throw new Error('Transactions are not supported by this executor');
|
|
90
|
+
}
|
|
91
|
+
await runner.commitTransaction!.call(runner);
|
|
92
|
+
},
|
|
93
|
+
async rollbackTransaction() {
|
|
94
|
+
if (!supportsTransactions) {
|
|
95
|
+
throw new Error('Transactions are not supported by this executor');
|
|
96
|
+
}
|
|
97
|
+
await runner.rollbackTransaction!.call(runner);
|
|
98
|
+
},
|
|
99
|
+
async dispose() {
|
|
100
|
+
await runner.dispose?.call(runner);
|
|
101
|
+
},
|
|
62
102
|
};
|
|
63
103
|
}
|
|
@@ -17,23 +17,40 @@ export interface MssqlClientLike {
|
|
|
17
17
|
export function createMssqlExecutor(
|
|
18
18
|
client: MssqlClientLike
|
|
19
19
|
): DbExecutor {
|
|
20
|
+
const supportsTransactions =
|
|
21
|
+
typeof client.beginTransaction === 'function' &&
|
|
22
|
+
typeof client.commit === 'function' &&
|
|
23
|
+
typeof client.rollback === 'function';
|
|
24
|
+
|
|
20
25
|
return {
|
|
26
|
+
capabilities: {
|
|
27
|
+
transactions: supportsTransactions,
|
|
28
|
+
},
|
|
21
29
|
async executeSql(sql, params) {
|
|
22
30
|
const { recordset } = await client.query(sql, params);
|
|
23
31
|
const result = rowsToQueryResult(recordset ?? []);
|
|
24
32
|
return [result];
|
|
25
33
|
},
|
|
26
34
|
async beginTransaction() {
|
|
27
|
-
if (!
|
|
28
|
-
|
|
35
|
+
if (!supportsTransactions) {
|
|
36
|
+
throw new Error('Transactions are not supported by this executor');
|
|
37
|
+
}
|
|
38
|
+
await client.beginTransaction!();
|
|
29
39
|
},
|
|
30
40
|
async commitTransaction() {
|
|
31
|
-
if (!
|
|
32
|
-
|
|
41
|
+
if (!supportsTransactions) {
|
|
42
|
+
throw new Error('Transactions are not supported by this executor');
|
|
43
|
+
}
|
|
44
|
+
await client.commit!();
|
|
33
45
|
},
|
|
34
46
|
async rollbackTransaction() {
|
|
35
|
-
if (!
|
|
36
|
-
|
|
47
|
+
if (!supportsTransactions) {
|
|
48
|
+
throw new Error('Transactions are not supported by this executor');
|
|
49
|
+
}
|
|
50
|
+
await client.rollback!();
|
|
51
|
+
},
|
|
52
|
+
async dispose() {
|
|
53
|
+
// Connection lifecycle is owned by the caller/driver. Pool lease executors should implement dispose.
|
|
37
54
|
},
|
|
38
55
|
};
|
|
39
56
|
}
|
|
@@ -53,7 +70,7 @@ export interface TediousRequest {
|
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
export interface TediousRequestCtor {
|
|
56
|
-
new
|
|
73
|
+
new(sql: string, callback: (err?: Error | null) => void): TediousRequest;
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
export interface TediousTypes {
|
|
@@ -140,29 +157,29 @@ export function createTediousMssqlClient(
|
|
|
140
157
|
|
|
141
158
|
beginTransaction: connection.beginTransaction
|
|
142
159
|
? () =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
new Promise<void>((resolve, reject) => {
|
|
161
|
+
connection.beginTransaction!(err =>
|
|
162
|
+
err ? reject(err) : resolve()
|
|
163
|
+
);
|
|
164
|
+
})
|
|
148
165
|
: undefined,
|
|
149
166
|
|
|
150
167
|
commit: connection.commitTransaction
|
|
151
168
|
? () =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
new Promise<void>((resolve, reject) => {
|
|
170
|
+
connection.commitTransaction!(err =>
|
|
171
|
+
err ? reject(err) : resolve()
|
|
172
|
+
);
|
|
173
|
+
})
|
|
157
174
|
: undefined,
|
|
158
175
|
|
|
159
176
|
rollback: connection.rollbackTransaction
|
|
160
177
|
? () =>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
new Promise<void>((resolve, reject) => {
|
|
179
|
+
connection.rollbackTransaction!(err =>
|
|
180
|
+
err ? reject(err) : resolve()
|
|
181
|
+
);
|
|
182
|
+
})
|
|
166
183
|
: undefined,
|
|
167
184
|
};
|
|
168
185
|
}
|