metal-orm 1.0.39 → 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.
@@ -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 quoteIdentifier - Function to quote identifiers according to dialect rules.
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, quoteIdentifier: (id: string) => string): string {
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 quoteIdentifier - Function to quote identifiers according to dialect rules.
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(ast: SelectQueryNode, quoteIdentifier: (id: string) => string): string {
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
- .map(o => `${quoteIdentifier(o.column.table)}.${quoteIdentifier(o.column.name)} ${o.direction}`)
18
- .join(', ');
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(ast, this.quoteIdentifier.bind(this));
49
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
50
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
51
- return `${ctes}${combined}${orderBy}${pagination}`;
52
- }
53
-
54
- protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
55
- const table = this.compileTableName(ast.into);
56
- const columnList = this.compileInsertColumnList(ast.columns);
57
- const values = this.compileInsertValues(ast.values, ctx);
58
- const returning = this.compileReturning(ast.returning, ctx);
59
- return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
60
- }
61
-
62
- protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
63
- return this.returningStrategy.compileReturning(returning, ctx);
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.quoteIdentifier.bind(this));
82
- const having = this.compileHaving(ast, ctx);
83
- const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
84
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
85
- return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
86
- }
87
-
88
- protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
89
- const table = this.compileTableName(ast.table);
90
- const assignments = this.compileUpdateAssignments(ast.set, ctx);
91
- const whereClause = this.compileWhere(ast.where, ctx);
92
- const returning = this.compileReturning(ast.returning, ctx);
93
- return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
94
- }
95
-
96
- private compileUpdateAssignments(
97
- assignments: { column: ColumnNode; value: any }[],
98
- ctx: CompilerContext
99
- ): string {
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
  }