metal-orm 1.0.40 → 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 +1244 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +655 -30
- package/dist/index.d.ts +655 -30
- package/dist/index.js +1240 -122
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/typescript.ts +6 -2
- package/src/core/ast/expression-builders.ts +25 -4
- package/src/core/ast/expression-nodes.ts +3 -1
- package/src/core/ast/query.ts +24 -2
- package/src/core/dialect/abstract.ts +6 -2
- package/src/core/dialect/base/join-compiler.ts +9 -12
- package/src/core/dialect/base/sql-dialect.ts +98 -17
- package/src/core/dialect/mssql/index.ts +30 -62
- 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/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/insert-query-state.ts +131 -61
- package/src/query-builder/insert.ts +27 -1
- package/src/query-builder/update-query-state.ts +114 -77
- package/src/query-builder/update.ts +38 -1
package/package.json
CHANGED
|
@@ -293,9 +293,13 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
|
|
|
293
293
|
*/
|
|
294
294
|
private printInExpression(inExpr: InExpressionNode): string {
|
|
295
295
|
const left = this.printOperand(inExpr.left);
|
|
296
|
-
const values = inExpr.right.map(v => this.printOperand(v)).join(', ');
|
|
297
296
|
const fn = this.mapOp(inExpr.operator);
|
|
298
|
-
|
|
297
|
+
if (Array.isArray(inExpr.right)) {
|
|
298
|
+
const values = inExpr.right.map(v => this.printOperand(v)).join(', ');
|
|
299
|
+
return `${fn}(${left}, [${values}])`;
|
|
300
|
+
}
|
|
301
|
+
const subquery = this.inlineChain(this.buildSelectLines(inExpr.right.query));
|
|
302
|
+
return `${fn}(${left}, (${subquery}))`;
|
|
299
303
|
}
|
|
300
304
|
|
|
301
305
|
/**
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
NullExpressionNode,
|
|
16
16
|
InExpressionNode,
|
|
17
17
|
ExistsExpressionNode,
|
|
18
|
+
InExpressionRight,
|
|
19
|
+
ScalarSubqueryNode,
|
|
18
20
|
BetweenExpressionNode,
|
|
19
21
|
isOperandNode,
|
|
20
22
|
AliasRefNode,
|
|
@@ -65,6 +67,19 @@ const toOperand = (val: OperandNode | ColumnRef | LiteralValue): OperandNode =>
|
|
|
65
67
|
return toNode(val);
|
|
66
68
|
};
|
|
67
69
|
|
|
70
|
+
export type SelectQueryInput = SelectQueryNode | { getAST(): SelectQueryNode };
|
|
71
|
+
|
|
72
|
+
const hasQueryAst = (value: SelectQueryInput): value is { getAST(): SelectQueryNode } =>
|
|
73
|
+
typeof (value as { getAST?: unknown }).getAST === 'function';
|
|
74
|
+
|
|
75
|
+
const resolveSelectQueryNode = (query: SelectQueryInput): SelectQueryNode =>
|
|
76
|
+
hasQueryAst(query) ? query.getAST() : query;
|
|
77
|
+
|
|
78
|
+
const toScalarSubqueryNode = (query: SelectQueryInput): ScalarSubqueryNode => ({
|
|
79
|
+
type: 'ScalarSubquery',
|
|
80
|
+
query: resolveSelectQueryNode(query)
|
|
81
|
+
});
|
|
82
|
+
|
|
68
83
|
export const columnOperand = (col: ColumnRef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
|
|
69
84
|
/**
|
|
70
85
|
* Marks a column reference as an outer-scope reference for correlated subqueries.
|
|
@@ -222,12 +237,12 @@ export const isNotNull = (left: OperandNode | ColumnRef): NullExpressionNode =>
|
|
|
222
237
|
const createInExpression = (
|
|
223
238
|
operator: 'IN' | 'NOT IN',
|
|
224
239
|
left: OperandNode | ColumnRef,
|
|
225
|
-
|
|
240
|
+
right: InExpressionRight
|
|
226
241
|
): InExpressionNode => ({
|
|
227
242
|
type: 'InExpression',
|
|
228
243
|
left: toNode(left),
|
|
229
244
|
operator,
|
|
230
|
-
right
|
|
245
|
+
right
|
|
231
246
|
});
|
|
232
247
|
|
|
233
248
|
/**
|
|
@@ -237,7 +252,7 @@ const createInExpression = (
|
|
|
237
252
|
* @returns IN expression node
|
|
238
253
|
*/
|
|
239
254
|
export const inList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
|
|
240
|
-
createInExpression('IN', left, values);
|
|
255
|
+
createInExpression('IN', left, values.map(v => toOperand(v)));
|
|
241
256
|
|
|
242
257
|
/**
|
|
243
258
|
* Creates a NOT IN expression (value NOT IN list)
|
|
@@ -246,7 +261,13 @@ export const inList = (left: OperandNode | ColumnRef, values: (string | number |
|
|
|
246
261
|
* @returns NOT IN expression node
|
|
247
262
|
*/
|
|
248
263
|
export const notInList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
|
|
249
|
-
createInExpression('NOT IN', left, values);
|
|
264
|
+
createInExpression('NOT IN', left, values.map(v => toOperand(v)));
|
|
265
|
+
|
|
266
|
+
export const inSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
|
|
267
|
+
createInExpression('IN', left, toScalarSubqueryNode(subquery));
|
|
268
|
+
|
|
269
|
+
export const notInSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
|
|
270
|
+
createInExpression('NOT IN', left, toScalarSubqueryNode(subquery));
|
|
250
271
|
|
|
251
272
|
const createBetweenExpression = (
|
|
252
273
|
operator: 'BETWEEN' | 'NOT BETWEEN',
|
|
@@ -80,6 +80,8 @@ export interface ScalarSubqueryNode {
|
|
|
80
80
|
alias?: string;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
export type InExpressionRight = OperandNode[] | ScalarSubqueryNode;
|
|
84
|
+
|
|
83
85
|
/**
|
|
84
86
|
* AST node representing a CASE expression
|
|
85
87
|
*/
|
|
@@ -201,7 +203,7 @@ export interface InExpressionNode {
|
|
|
201
203
|
/** IN/NOT IN operator */
|
|
202
204
|
operator: 'IN' | 'NOT IN';
|
|
203
205
|
/** Values to check against */
|
|
204
|
-
right:
|
|
206
|
+
right: InExpressionRight;
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
/**
|
package/src/core/ast/query.ts
CHANGED
|
@@ -144,14 +144,28 @@ export interface SelectQueryNode {
|
|
|
144
144
|
setOps?: SetOperationNode[];
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
export interface InsertValuesSourceNode {
|
|
148
|
+
type: 'InsertValues';
|
|
149
|
+
/** Rows of values for INSERT rows */
|
|
150
|
+
rows: OperandNode[][];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface InsertSelectSourceNode {
|
|
154
|
+
type: 'InsertSelect';
|
|
155
|
+
/** SELECT query providing rows */
|
|
156
|
+
query: SelectQueryNode;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type InsertSourceNode = InsertValuesSourceNode | InsertSelectSourceNode;
|
|
160
|
+
|
|
147
161
|
export interface InsertQueryNode {
|
|
148
162
|
type: 'InsertQuery';
|
|
149
163
|
/** Target table */
|
|
150
164
|
into: TableNode;
|
|
151
165
|
/** Column order for inserted values */
|
|
152
166
|
columns: ColumnNode[];
|
|
153
|
-
/**
|
|
154
|
-
|
|
167
|
+
/** Source of inserted rows (either literal values or a SELECT query) */
|
|
168
|
+
source: InsertSourceNode;
|
|
155
169
|
/** Optional RETURNING clause */
|
|
156
170
|
returning?: ColumnNode[];
|
|
157
171
|
}
|
|
@@ -167,6 +181,10 @@ export interface UpdateQueryNode {
|
|
|
167
181
|
type: 'UpdateQuery';
|
|
168
182
|
/** Table being updated */
|
|
169
183
|
table: TableNode;
|
|
184
|
+
/** Optional FROM clause for multi-table updates */
|
|
185
|
+
from?: TableSourceNode;
|
|
186
|
+
/** Optional joins applied to the FROM/USING tables */
|
|
187
|
+
joins?: JoinNode[];
|
|
170
188
|
/** Assignments for SET clause */
|
|
171
189
|
set: UpdateAssignmentNode[];
|
|
172
190
|
/** Optional WHERE clause */
|
|
@@ -179,6 +197,10 @@ export interface DeleteQueryNode {
|
|
|
179
197
|
type: 'DeleteQuery';
|
|
180
198
|
/** Table to delete from */
|
|
181
199
|
from: TableNode;
|
|
200
|
+
/** Optional USING clause for multi-table deletes */
|
|
201
|
+
using?: TableSourceNode;
|
|
202
|
+
/** Optional joins applied to the USING clause */
|
|
203
|
+
joins?: JoinNode[];
|
|
182
204
|
/** Optional WHERE clause */
|
|
183
205
|
where?: ExpressionNode;
|
|
184
206
|
/** Optional RETURNING clause */
|
|
@@ -408,8 +408,12 @@ export abstract class Dialect
|
|
|
408
408
|
|
|
409
409
|
this.registerExpressionCompiler('InExpression', (inExpr: InExpressionNode, ctx) => {
|
|
410
410
|
const left = this.compileOperand(inExpr.left, ctx);
|
|
411
|
-
|
|
412
|
-
|
|
411
|
+
if (Array.isArray(inExpr.right)) {
|
|
412
|
+
const values = inExpr.right.map(v => this.compileOperand(v, ctx)).join(', ');
|
|
413
|
+
return `${left} ${inExpr.operator} (${values})`;
|
|
414
|
+
}
|
|
415
|
+
const subquerySql = this.compileSelectAst(inExpr.right.query, ctx).trim().replace(/;$/, '');
|
|
416
|
+
return `${left} ${inExpr.operator} (${subquerySql})`;
|
|
413
417
|
});
|
|
414
418
|
|
|
415
419
|
this.registerExpressionCompiler('ExistsExpression', (existsExpr: ExistsExpressionNode, ctx) => {
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import { SelectQueryNode } from '../../ast/query.js';
|
|
2
1
|
import { CompilerContext } from '../abstract.js';
|
|
2
|
+
import { JoinNode } from '../../ast/join.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Compiler for JOIN clauses in SELECT statements.
|
|
6
6
|
* Handles compilation of all join types (INNER, LEFT, RIGHT, FULL, CROSS).
|
|
7
7
|
*/
|
|
8
8
|
export class JoinCompiler {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
static compileJoins(ast: SelectQueryNode, ctx: CompilerContext, compileFrom: (from: any, ctx: CompilerContext) => string, compileExpression: (expr: any, ctx: CompilerContext) => string): string {
|
|
18
|
-
if (!ast.joins || ast.joins.length === 0) return '';
|
|
19
|
-
const parts = ast.joins.map(j => {
|
|
9
|
+
static compileJoins(
|
|
10
|
+
joins: JoinNode[] | undefined,
|
|
11
|
+
ctx: CompilerContext,
|
|
12
|
+
compileFrom: (from: any, ctx: CompilerContext) => string,
|
|
13
|
+
compileExpression: (expr: any, ctx: CompilerContext) => string
|
|
14
|
+
): string {
|
|
15
|
+
if (!joins || joins.length === 0) return '';
|
|
16
|
+
const parts = joins.map(j => {
|
|
20
17
|
const table = compileFrom(j.table as any, ctx);
|
|
21
18
|
const cond = compileExpression(j.condition, ctx);
|
|
22
19
|
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { CompilerContext, Dialect } from '../abstract.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
SelectQueryNode,
|
|
4
|
+
InsertQueryNode,
|
|
5
|
+
UpdateQueryNode,
|
|
6
|
+
DeleteQueryNode,
|
|
7
|
+
InsertSourceNode,
|
|
8
|
+
TableSourceNode,
|
|
9
|
+
DerivedTableNode,
|
|
10
|
+
FunctionTableNode,
|
|
11
|
+
OrderByNode,
|
|
12
|
+
TableNode
|
|
13
|
+
} from '../../ast/query.js';
|
|
3
14
|
import { ColumnNode } from '../../ast/expression.js';
|
|
4
15
|
import { FunctionTableFormatter } from './function-table-formatter.js';
|
|
5
16
|
import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
|
|
@@ -57,31 +68,49 @@ export abstract class SqlDialectBase extends Dialect {
|
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
71
|
+
if (!ast.columns.length) {
|
|
72
|
+
throw new Error('INSERT queries must specify columns.');
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
const table = this.compileTableName(ast.into);
|
|
61
76
|
const columnList = this.compileInsertColumnList(ast.columns);
|
|
62
|
-
const
|
|
77
|
+
const source = this.compileInsertSource(ast.source, ctx);
|
|
63
78
|
const returning = this.compileReturning(ast.returning, ctx);
|
|
64
|
-
return `INSERT INTO ${table} (${columnList})
|
|
79
|
+
return `INSERT INTO ${table} (${columnList}) ${source}${returning}`;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
68
83
|
return this.returningStrategy.compileReturning(returning, ctx);
|
|
69
84
|
}
|
|
70
85
|
|
|
71
|
-
private
|
|
72
|
-
|
|
86
|
+
private compileInsertSource(source: InsertSourceNode, ctx: CompilerContext): string {
|
|
87
|
+
if (source.type === 'InsertValues') {
|
|
88
|
+
if (!source.rows.length) {
|
|
89
|
+
throw new Error('INSERT ... VALUES requires at least one row.');
|
|
90
|
+
}
|
|
91
|
+
const values = source.rows
|
|
92
|
+
.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
|
|
93
|
+
.join(', ');
|
|
94
|
+
return `VALUES ${values}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const normalized = this.normalizeSelectAst(source.query);
|
|
98
|
+
return this.compileSelectAst(normalized, ctx).trim();
|
|
73
99
|
}
|
|
74
100
|
|
|
75
|
-
private
|
|
76
|
-
return
|
|
77
|
-
.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
|
|
78
|
-
.join(', ');
|
|
101
|
+
private compileInsertColumnList(columns: ColumnNode[]): string {
|
|
102
|
+
return columns.map(column => this.quoteIdentifier(column.name)).join(', ');
|
|
79
103
|
}
|
|
80
104
|
|
|
81
105
|
private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
82
106
|
const columns = this.compileSelectColumns(ast, ctx);
|
|
83
107
|
const from = this.compileFrom(ast.from, ctx);
|
|
84
|
-
const joins = JoinCompiler.compileJoins(
|
|
108
|
+
const joins = JoinCompiler.compileJoins(
|
|
109
|
+
ast.joins,
|
|
110
|
+
ctx,
|
|
111
|
+
this.compileFrom.bind(this),
|
|
112
|
+
this.compileExpression.bind(this)
|
|
113
|
+
);
|
|
85
114
|
const whereClause = this.compileWhere(ast.where, ctx);
|
|
86
115
|
const groupBy = GroupByCompiler.compileGroupBy(ast, term => this.compileOrderingTerm(term, ctx));
|
|
87
116
|
const having = this.compileHaving(ast, ctx);
|
|
@@ -96,32 +125,49 @@ export abstract class SqlDialectBase extends Dialect {
|
|
|
96
125
|
}
|
|
97
126
|
|
|
98
127
|
protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
99
|
-
const
|
|
100
|
-
const assignments = this.compileUpdateAssignments(ast.set, ctx);
|
|
128
|
+
const target = this.compileTableReference(ast.table);
|
|
129
|
+
const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
|
|
130
|
+
const fromClause = this.compileUpdateFromClause(ast, ctx);
|
|
101
131
|
const whereClause = this.compileWhere(ast.where, ctx);
|
|
102
132
|
const returning = this.compileReturning(ast.returning, ctx);
|
|
103
|
-
return `UPDATE ${
|
|
133
|
+
return `UPDATE ${target} SET ${assignments}${fromClause}${whereClause}${returning}`;
|
|
104
134
|
}
|
|
105
135
|
|
|
106
136
|
private compileUpdateAssignments(
|
|
107
137
|
assignments: { column: ColumnNode; value: any }[],
|
|
138
|
+
table: TableNode,
|
|
108
139
|
ctx: CompilerContext
|
|
109
140
|
): string {
|
|
110
141
|
return assignments
|
|
111
142
|
.map(assignment => {
|
|
112
143
|
const col = assignment.column;
|
|
113
|
-
const target = this.
|
|
144
|
+
const target = this.compileQualifiedColumn(col, table);
|
|
114
145
|
const value = this.compileOperand(assignment.value, ctx);
|
|
115
146
|
return `${target} = ${value}`;
|
|
116
147
|
})
|
|
117
148
|
.join(', ');
|
|
118
149
|
}
|
|
119
150
|
|
|
151
|
+
protected compileQualifiedColumn(column: ColumnNode, table: TableNode): string {
|
|
152
|
+
const baseTableName = table.name;
|
|
153
|
+
const alias = table.alias;
|
|
154
|
+
const columnTable = column.table ?? alias ?? baseTableName;
|
|
155
|
+
const tableQualifier =
|
|
156
|
+
alias && column.table === baseTableName ? alias : columnTable;
|
|
157
|
+
|
|
158
|
+
if (!tableQualifier) {
|
|
159
|
+
return this.quoteIdentifier(column.name);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
120
165
|
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
121
|
-
const
|
|
166
|
+
const target = this.compileTableReference(ast.from);
|
|
167
|
+
const usingClause = this.compileDeleteUsingClause(ast, ctx);
|
|
122
168
|
const whereClause = this.compileWhere(ast.where, ctx);
|
|
123
169
|
const returning = this.compileReturning(ast.returning, ctx);
|
|
124
|
-
return `DELETE FROM ${
|
|
170
|
+
return `DELETE FROM ${target}${usingClause}${whereClause}${returning}`;
|
|
125
171
|
}
|
|
126
172
|
|
|
127
173
|
protected formatReturningColumns(returning: ColumnNode[]): string {
|
|
@@ -180,13 +226,48 @@ export abstract class SqlDialectBase extends Dialect {
|
|
|
180
226
|
return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
|
|
181
227
|
}
|
|
182
228
|
|
|
183
|
-
protected compileTableName(table: { name: string; schema?: string }): string {
|
|
229
|
+
protected compileTableName(table: { name: string; schema?: string; alias?: string }): string {
|
|
184
230
|
if (table.schema) {
|
|
185
231
|
return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
|
|
186
232
|
}
|
|
187
233
|
return this.quoteIdentifier(table.name);
|
|
188
234
|
}
|
|
189
235
|
|
|
236
|
+
protected compileTableReference(table: { name: string; schema?: string; alias?: string }): string {
|
|
237
|
+
const base = this.compileTableName(table);
|
|
238
|
+
return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private compileUpdateFromClause(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
242
|
+
if (!ast.from && (!ast.joins || ast.joins.length === 0)) return '';
|
|
243
|
+
if (!ast.from) {
|
|
244
|
+
throw new Error('UPDATE with JOINs requires an explicit FROM clause.');
|
|
245
|
+
}
|
|
246
|
+
const from = this.compileFrom(ast.from, ctx);
|
|
247
|
+
const joins = JoinCompiler.compileJoins(
|
|
248
|
+
ast.joins,
|
|
249
|
+
ctx,
|
|
250
|
+
this.compileFrom.bind(this),
|
|
251
|
+
this.compileExpression.bind(this)
|
|
252
|
+
);
|
|
253
|
+
return ` FROM ${from}${joins}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private compileDeleteUsingClause(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
257
|
+
if (!ast.using && (!ast.joins || ast.joins.length === 0)) return '';
|
|
258
|
+
if (!ast.using) {
|
|
259
|
+
throw new Error('DELETE with JOINs requires a USING clause.');
|
|
260
|
+
}
|
|
261
|
+
const usingTable = this.compileFrom(ast.using, ctx);
|
|
262
|
+
const joins = JoinCompiler.compileJoins(
|
|
263
|
+
ast.joins,
|
|
264
|
+
ctx,
|
|
265
|
+
this.compileFrom.bind(this),
|
|
266
|
+
this.compileExpression.bind(this)
|
|
267
|
+
);
|
|
268
|
+
return ` USING ${usingTable}${joins}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
190
271
|
protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
191
272
|
if (!ast.having) return '';
|
|
192
273
|
return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
|
|
@@ -1,14 +1,21 @@
|
|
|
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 { FunctionTableFormatter } from '../base/function-table-formatter.js';
|
|
6
11
|
import { OrderByCompiler } from '../base/orderby-compiler.js';
|
|
12
|
+
import { JoinCompiler } from '../base/join-compiler.js';
|
|
13
|
+
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
7
14
|
|
|
8
15
|
/**
|
|
9
16
|
* Microsoft SQL Server dialect implementation
|
|
10
17
|
*/
|
|
11
|
-
export class SqlServerDialect extends
|
|
18
|
+
export class SqlServerDialect extends SqlDialectBase {
|
|
12
19
|
protected readonly dialect = 'mssql';
|
|
13
20
|
/**
|
|
14
21
|
* Creates a new SqlServerDialect instance
|
|
@@ -60,7 +67,7 @@ export class SqlServerDialect extends Dialect {
|
|
|
60
67
|
? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
|
|
61
68
|
: ast;
|
|
62
69
|
|
|
63
|
-
const baseSelect = this.
|
|
70
|
+
const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
|
|
64
71
|
|
|
65
72
|
if (!hasSetOps) {
|
|
66
73
|
return `${ctes}${baseSelect}`;
|
|
@@ -77,35 +84,29 @@ export class SqlServerDialect extends Dialect {
|
|
|
77
84
|
return `${ctes}${combined}${tail}`;
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
81
|
-
const table = this.quoteIdentifier(ast.into.name);
|
|
82
|
-
const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
|
|
83
|
-
const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
|
|
84
|
-
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
88
|
-
const table = this.quoteIdentifier(ast.table.name);
|
|
89
|
-
const assignments = ast.set.map(assignment => {
|
|
90
|
-
const col = assignment.column;
|
|
91
|
-
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
92
|
-
const value = this.compileOperand(assignment.value, ctx);
|
|
93
|
-
return `${target} = ${value}`;
|
|
94
|
-
}).join(', ');
|
|
95
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
96
|
-
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
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
|
+
|
|
100
92
|
if (ast.from.type !== 'Table') {
|
|
101
93
|
throw new Error('DELETE only supports base tables in the MSSQL dialect.');
|
|
102
94
|
}
|
|
103
|
-
|
|
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
|
+
);
|
|
104
104
|
const whereClause = this.compileWhere(ast.where, ctx);
|
|
105
|
-
|
|
105
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
106
|
+
return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
private
|
|
109
|
+
private compileSelectCoreForMssql(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
109
110
|
const columns = ast.columns.map(c => {
|
|
110
111
|
let expr = '';
|
|
111
112
|
if (c.type === 'Function') {
|
|
@@ -126,10 +127,10 @@ export class SqlServerDialect extends Dialect {
|
|
|
126
127
|
}).join(', ');
|
|
127
128
|
|
|
128
129
|
const distinct = ast.distinct ? 'DISTINCT ' : '';
|
|
129
|
-
const from = this.compileTableSource(ast.from
|
|
130
|
+
const from = this.compileTableSource(ast.from);
|
|
130
131
|
|
|
131
132
|
const joins = ast.joins.map(j => {
|
|
132
|
-
const table = this.compileTableSource(j.table
|
|
133
|
+
const table = this.compileTableSource(j.table);
|
|
133
134
|
const cond = this.compileExpression(j.condition, ctx);
|
|
134
135
|
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
135
136
|
}).join(' ');
|
|
@@ -176,35 +177,6 @@ export class SqlServerDialect extends Dialect {
|
|
|
176
177
|
return pagination;
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
private renderOrderByNulls(order: OrderByNode): string | undefined {
|
|
180
|
-
return order.nulls ? ` NULLS ${order.nulls}` : '';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private renderOrderByCollation(order: OrderByNode): string | undefined {
|
|
184
|
-
return order.collation ? ` COLLATE ${order.collation}` : '';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private compileTableSource(table: TableSourceNode, ctx: CompilerContext): string {
|
|
188
|
-
if (table.type === 'FunctionTable') {
|
|
189
|
-
return FunctionTableFormatter.format(table, ctx, this as any);
|
|
190
|
-
}
|
|
191
|
-
if (table.type === 'DerivedTable') {
|
|
192
|
-
return this.compileDerivedTable(table, ctx);
|
|
193
|
-
}
|
|
194
|
-
const base = table.schema
|
|
195
|
-
? `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`
|
|
196
|
-
: this.quoteIdentifier(table.name);
|
|
197
|
-
return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private compileDerivedTable(table: DerivedTableNode, ctx: CompilerContext): string {
|
|
201
|
-
const sub = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, '');
|
|
202
|
-
const cols = table.columnAliases?.length
|
|
203
|
-
? ` (${table.columnAliases.map(c => this.quoteIdentifier(c)).join(', ')})`
|
|
204
|
-
: '';
|
|
205
|
-
return `(${sub}) AS ${this.quoteIdentifier(table.alias)}${cols}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
180
|
private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
209
181
|
if (!ast.ctes || ast.ctes.length === 0) return '';
|
|
210
182
|
// MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
|
|
@@ -217,8 +189,4 @@ export class SqlServerDialect extends Dialect {
|
|
|
217
189
|
return `WITH ${defs} `;
|
|
218
190
|
}
|
|
219
191
|
|
|
220
|
-
private wrapSetOperand(sql: string): string {
|
|
221
|
-
const trimmed = sql.trim().replace(/;$/, '');
|
|
222
|
-
return `(${trimmed})`;
|
|
223
|
-
}
|
|
224
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
|
+
}
|