metal-orm 1.0.38 → 1.0.40
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 +230 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -24
- package/dist/index.d.ts +71 -24
- package/dist/index.js +225 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-entities.mjs +3 -3
- package/src/codegen/typescript.ts +60 -3
- package/src/core/ast/aggregate-functions.ts +15 -15
- package/src/core/ast/expression-builders.ts +357 -316
- package/src/core/ast/expression-nodes.ts +208 -186
- package/src/core/ast/expression-visitor.ts +40 -30
- package/src/core/ast/query.ts +142 -132
- package/src/core/ast/window-functions.ts +86 -86
- package/src/core/dialect/abstract.ts +505 -479
- package/src/core/dialect/base/groupby-compiler.ts +6 -6
- package/src/core/dialect/base/orderby-compiler.ts +20 -6
- package/src/core/dialect/base/sql-dialect.ts +154 -136
- package/src/core/dialect/mssql/index.ts +172 -161
- package/src/core/functions/standard-strategy.ts +46 -37
- package/src/query-builder/hydration-manager.ts +93 -79
- 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
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { SelectQueryNode } from '../../ast/query.js';
|
|
1
|
+
import { OrderingTerm, SelectQueryNode } from '../../ast/query.js';
|
|
2
|
+
|
|
3
|
+
type TermRenderer = (term: OrderingTerm) => string;
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Compiler for GROUP BY clauses in SELECT statements.
|
|
@@ -8,14 +10,12 @@ export class GroupByCompiler {
|
|
|
8
10
|
/**
|
|
9
11
|
* Compiles GROUP BY clause from a SELECT query AST.
|
|
10
12
|
* @param ast - The SELECT query AST containing grouping columns.
|
|
11
|
-
* @param
|
|
13
|
+
* @param renderTerm - Function to render a grouping term.
|
|
12
14
|
* @returns SQL GROUP BY clause (e.g., " GROUP BY table.col1, table.col2") or empty string if no grouping.
|
|
13
15
|
*/
|
|
14
|
-
static compileGroupBy(ast: SelectQueryNode,
|
|
16
|
+
static compileGroupBy(ast: SelectQueryNode, renderTerm: TermRenderer): string {
|
|
15
17
|
if (!ast.groupBy || ast.groupBy.length === 0) return '';
|
|
16
|
-
const cols = ast.groupBy
|
|
17
|
-
.map(c => `${quoteIdentifier(c.table)}.${quoteIdentifier(c.name)}`)
|
|
18
|
-
.join(', ');
|
|
18
|
+
const cols = ast.groupBy.map(renderTerm).join(', ');
|
|
19
19
|
return ` GROUP BY ${cols}`;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { SelectQueryNode } from '../../ast/query.js';
|
|
1
|
+
import { OrderByNode, SelectQueryNode } from '../../ast/query.js';
|
|
2
|
+
|
|
3
|
+
type NullsRenderer = (order: OrderByNode) => string | undefined;
|
|
4
|
+
type CollationRenderer = (order: OrderByNode) => string | undefined;
|
|
5
|
+
type TermRenderer = (term: OrderByNode['term']) => string;
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* Compiler for ORDER BY clauses in SELECT statements.
|
|
@@ -8,14 +12,24 @@ export class OrderByCompiler {
|
|
|
8
12
|
/**
|
|
9
13
|
* Compiles ORDER BY clause from a SELECT query AST.
|
|
10
14
|
* @param ast - The SELECT query AST containing sort specifications.
|
|
11
|
-
* @param
|
|
15
|
+
* @param renderTerm - Function to render an ordering term.
|
|
16
|
+
* @param renderNulls - Optional function to render NULLS FIRST/LAST.
|
|
17
|
+
* @param renderCollation - Optional function to render COLLATE clause.
|
|
12
18
|
* @returns SQL ORDER BY clause (e.g., " ORDER BY table.col1 ASC, table.col2 DESC") or empty string if no ordering.
|
|
13
19
|
*/
|
|
14
|
-
static compileOrderBy(
|
|
20
|
+
static compileOrderBy(
|
|
21
|
+
ast: SelectQueryNode,
|
|
22
|
+
renderTerm: TermRenderer,
|
|
23
|
+
renderNulls?: NullsRenderer,
|
|
24
|
+
renderCollation?: CollationRenderer
|
|
25
|
+
): string {
|
|
15
26
|
if (!ast.orderBy || ast.orderBy.length === 0) return '';
|
|
16
|
-
const parts = ast.orderBy
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
const parts = ast.orderBy.map(o => {
|
|
28
|
+
const term = renderTerm(o.term);
|
|
29
|
+
const collation = renderCollation ? renderCollation(o) : o.collation ? ` COLLATE ${o.collation}` : '';
|
|
30
|
+
const nulls = renderNulls ? renderNulls(o) : o.nulls ? ` NULLS ${o.nulls}` : '';
|
|
31
|
+
return `${term} ${o.direction}${collation}${nulls}`;
|
|
32
|
+
}).join(', ');
|
|
19
33
|
return ` ORDER BY ${parts}`;
|
|
20
34
|
}
|
|
21
35
|
}
|
|
@@ -1,102 +1,112 @@
|
|
|
1
|
-
import { CompilerContext, Dialect } from '../abstract.js';
|
|
2
|
-
import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode, DerivedTableNode, FunctionTableNode } from '../../ast/query.js';
|
|
3
|
-
import { ColumnNode } from '../../ast/expression.js';
|
|
1
|
+
import { CompilerContext, Dialect } from '../abstract.js';
|
|
2
|
+
import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode, DerivedTableNode, FunctionTableNode, OrderByNode } from '../../ast/query.js';
|
|
3
|
+
import { ColumnNode } from '../../ast/expression.js';
|
|
4
4
|
import { FunctionTableFormatter } from './function-table-formatter.js';
|
|
5
|
-
import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
|
|
6
|
-
import { CteCompiler } from './cte-compiler.js';
|
|
7
|
-
import { ReturningStrategy, NoReturningStrategy } from './returning-strategy.js';
|
|
8
|
-
import { JoinCompiler } from './join-compiler.js';
|
|
9
|
-
import { GroupByCompiler } from './groupby-compiler.js';
|
|
10
|
-
import { OrderByCompiler } from './orderby-compiler.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export abstract class SqlDialectBase extends Dialect {
|
|
14
|
-
abstract quoteIdentifier(id: string): string;
|
|
15
|
-
|
|
16
|
-
protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
|
|
17
|
-
protected returningStrategy: ReturningStrategy = new NoReturningStrategy();
|
|
18
|
-
|
|
19
|
-
protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
20
|
-
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
21
|
-
const ctes = CteCompiler.compileCtes(
|
|
22
|
-
ast,
|
|
23
|
-
ctx,
|
|
24
|
-
this.quoteIdentifier.bind(this),
|
|
25
|
-
this.compileSelectAst.bind(this),
|
|
26
|
-
this.normalizeSelectAst?.bind(this) ?? ((a) => a),
|
|
27
|
-
this.stripTrailingSemicolon.bind(this)
|
|
28
|
-
);
|
|
29
|
-
const baseAst: SelectQueryNode = hasSetOps
|
|
30
|
-
? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
|
|
31
|
-
: ast;
|
|
32
|
-
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
33
|
-
if (!hasSetOps) {
|
|
34
|
-
return `${ctes}${baseSelect}`;
|
|
35
|
-
}
|
|
36
|
-
return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private compileSelectWithSetOps(
|
|
40
|
-
ast: SelectQueryNode,
|
|
41
|
-
baseSelect: string,
|
|
42
|
-
ctes: string,
|
|
43
|
-
ctx: CompilerContext
|
|
44
|
-
): string {
|
|
45
|
-
const compound = ast.setOps!
|
|
46
|
-
.map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
|
|
47
|
-
.join(' ');
|
|
48
|
-
const orderBy = OrderByCompiler.compileOrderBy(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
5
|
+
import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
|
|
6
|
+
import { CteCompiler } from './cte-compiler.js';
|
|
7
|
+
import { ReturningStrategy, NoReturningStrategy } from './returning-strategy.js';
|
|
8
|
+
import { JoinCompiler } from './join-compiler.js';
|
|
9
|
+
import { GroupByCompiler } from './groupby-compiler.js';
|
|
10
|
+
import { OrderByCompiler } from './orderby-compiler.js';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export abstract class SqlDialectBase extends Dialect {
|
|
14
|
+
abstract quoteIdentifier(id: string): string;
|
|
15
|
+
|
|
16
|
+
protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
|
|
17
|
+
protected returningStrategy: ReturningStrategy = new NoReturningStrategy();
|
|
18
|
+
|
|
19
|
+
protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
20
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
21
|
+
const ctes = CteCompiler.compileCtes(
|
|
22
|
+
ast,
|
|
23
|
+
ctx,
|
|
24
|
+
this.quoteIdentifier.bind(this),
|
|
25
|
+
this.compileSelectAst.bind(this),
|
|
26
|
+
this.normalizeSelectAst?.bind(this) ?? ((a) => a),
|
|
27
|
+
this.stripTrailingSemicolon.bind(this)
|
|
28
|
+
);
|
|
29
|
+
const baseAst: SelectQueryNode = hasSetOps
|
|
30
|
+
? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
|
|
31
|
+
: ast;
|
|
32
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
33
|
+
if (!hasSetOps) {
|
|
34
|
+
return `${ctes}${baseSelect}`;
|
|
35
|
+
}
|
|
36
|
+
return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private compileSelectWithSetOps(
|
|
40
|
+
ast: SelectQueryNode,
|
|
41
|
+
baseSelect: string,
|
|
42
|
+
ctes: string,
|
|
43
|
+
ctx: CompilerContext
|
|
44
|
+
): string {
|
|
45
|
+
const compound = ast.setOps!
|
|
46
|
+
.map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
|
|
47
|
+
.join(' ');
|
|
48
|
+
const orderBy = OrderByCompiler.compileOrderBy(
|
|
49
|
+
ast,
|
|
50
|
+
term => this.compileOrderingTerm(term, ctx),
|
|
51
|
+
this.renderOrderByNulls.bind(this),
|
|
52
|
+
this.renderOrderByCollation.bind(this)
|
|
53
|
+
);
|
|
54
|
+
const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
|
|
55
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
56
|
+
return `${ctes}${combined}${orderBy}${pagination}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
60
|
+
const table = this.compileTableName(ast.into);
|
|
61
|
+
const columnList = this.compileInsertColumnList(ast.columns);
|
|
62
|
+
const values = this.compileInsertValues(ast.values, ctx);
|
|
63
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
64
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
68
|
+
return this.returningStrategy.compileReturning(returning, ctx);
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
private compileInsertColumnList(columns: ColumnNode[]): string {
|
|
67
72
|
return columns.map(column => this.quoteIdentifier(column.name)).join(', ');
|
|
68
73
|
}
|
|
69
|
-
|
|
70
|
-
private compileInsertValues(values: any[][], ctx: CompilerContext): string {
|
|
71
|
-
return values
|
|
72
|
-
.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
|
|
73
|
-
.join(', ');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
77
|
-
const columns = this.compileSelectColumns(ast, ctx);
|
|
78
|
-
const from = this.compileFrom(ast.from, ctx);
|
|
79
|
-
const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
|
|
80
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
81
|
-
const groupBy = GroupByCompiler.compileGroupBy(ast, this.
|
|
82
|
-
const having = this.compileHaving(ast, ctx);
|
|
83
|
-
const orderBy = OrderByCompiler.compileOrderBy(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
74
|
+
|
|
75
|
+
private compileInsertValues(values: any[][], ctx: CompilerContext): string {
|
|
76
|
+
return values
|
|
77
|
+
.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`)
|
|
78
|
+
.join(', ');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
82
|
+
const columns = this.compileSelectColumns(ast, ctx);
|
|
83
|
+
const from = this.compileFrom(ast.from, ctx);
|
|
84
|
+
const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
|
|
85
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
86
|
+
const groupBy = GroupByCompiler.compileGroupBy(ast, term => this.compileOrderingTerm(term, ctx));
|
|
87
|
+
const having = this.compileHaving(ast, ctx);
|
|
88
|
+
const orderBy = OrderByCompiler.compileOrderBy(
|
|
89
|
+
ast,
|
|
90
|
+
term => this.compileOrderingTerm(term, ctx),
|
|
91
|
+
this.renderOrderByNulls.bind(this),
|
|
92
|
+
this.renderOrderByCollation.bind(this)
|
|
93
|
+
);
|
|
94
|
+
const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
|
|
95
|
+
return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
99
|
+
const table = this.compileTableName(ast.table);
|
|
100
|
+
const assignments = this.compileUpdateAssignments(ast.set, ctx);
|
|
101
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
102
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
103
|
+
return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private compileUpdateAssignments(
|
|
107
|
+
assignments: { column: ColumnNode; value: any }[],
|
|
108
|
+
ctx: CompilerContext
|
|
109
|
+
): string {
|
|
100
110
|
return assignments
|
|
101
111
|
.map(assignment => {
|
|
102
112
|
const col = assignment.column;
|
|
@@ -106,33 +116,33 @@ export abstract class SqlDialectBase extends Dialect {
|
|
|
106
116
|
})
|
|
107
117
|
.join(', ');
|
|
108
118
|
}
|
|
109
|
-
|
|
110
|
-
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
111
|
-
const table = this.compileTableName(ast.from);
|
|
112
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
113
|
-
const returning = this.compileReturning(ast.returning, ctx);
|
|
114
|
-
return `DELETE FROM ${table}${whereClause}${returning}`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
protected formatReturningColumns(returning: ColumnNode[]): string {
|
|
118
|
-
return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
protected compileDistinct(ast: SelectQueryNode): string {
|
|
122
|
-
return ast.distinct ? 'DISTINCT ' : '';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
126
|
-
return ast.columns.map(c => {
|
|
127
|
-
const expr = this.compileOperand(c, ctx);
|
|
128
|
-
if (c.alias) {
|
|
129
|
-
if (c.alias.includes('(')) return c.alias;
|
|
130
|
-
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
131
|
-
}
|
|
132
|
-
return expr;
|
|
133
|
-
}).join(', ');
|
|
134
|
-
}
|
|
135
|
-
|
|
119
|
+
|
|
120
|
+
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
121
|
+
const table = this.compileTableName(ast.from);
|
|
122
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
123
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
124
|
+
return `DELETE FROM ${table}${whereClause}${returning}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
protected formatReturningColumns(returning: ColumnNode[]): string {
|
|
128
|
+
return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected compileDistinct(ast: SelectQueryNode): string {
|
|
132
|
+
return ast.distinct ? 'DISTINCT ' : '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
136
|
+
return ast.columns.map(c => {
|
|
137
|
+
const expr = this.compileOperand(c, ctx);
|
|
138
|
+
if (c.alias) {
|
|
139
|
+
if (c.alias.includes('(')) return c.alias;
|
|
140
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
141
|
+
}
|
|
142
|
+
return expr;
|
|
143
|
+
}).join(', ');
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
protected compileFrom(ast: SelectQueryNode['from'], ctx?: CompilerContext): string {
|
|
137
147
|
const tableSource = ast as any;
|
|
138
148
|
if (tableSource.type === 'FunctionTable') {
|
|
@@ -176,18 +186,26 @@ export abstract class SqlDialectBase extends Dialect {
|
|
|
176
186
|
}
|
|
177
187
|
return this.quoteIdentifier(table.name);
|
|
178
188
|
}
|
|
179
|
-
|
|
180
|
-
protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
181
|
-
if (!ast.having) return '';
|
|
182
|
-
return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
protected stripTrailingSemicolon(sql: string): string {
|
|
186
|
-
return sql.trim().replace(/;$/, '');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
protected wrapSetOperand(sql: string): string {
|
|
190
|
-
const trimmed = this.stripTrailingSemicolon(sql);
|
|
191
|
-
return `(${trimmed})`;
|
|
192
|
-
}
|
|
189
|
+
|
|
190
|
+
protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
191
|
+
if (!ast.having) return '';
|
|
192
|
+
return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected stripTrailingSemicolon(sql: string): string {
|
|
196
|
+
return sql.trim().replace(/;$/, '');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
protected wrapSetOperand(sql: string): string {
|
|
200
|
+
const trimmed = this.stripTrailingSemicolon(sql);
|
|
201
|
+
return `(${trimmed})`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
protected renderOrderByNulls(order: OrderByNode): string | undefined {
|
|
205
|
+
return order.nulls ? ` NULLS ${order.nulls}` : '';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected renderOrderByCollation(order: OrderByNode): string | undefined {
|
|
209
|
+
return order.collation ? ` COLLATE ${order.collation}` : '';
|
|
210
|
+
}
|
|
193
211
|
}
|