metal-orm 1.1.22 → 1.1.23

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.
@@ -0,0 +1,254 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { OrderingTerm, SelectQueryNode } from '../../ast/query.js';
3
+ import {
4
+ type ExpressionNode,
5
+ type BinaryExpressionNode,
6
+ type LogicalExpressionNode,
7
+ type NotExpressionNode,
8
+ type NullExpressionNode,
9
+ type InExpressionNode,
10
+ type ExistsExpressionNode,
11
+ type LiteralNode,
12
+ type ColumnNode,
13
+ type OperandNode,
14
+ type FunctionNode,
15
+ type JsonPathNode,
16
+ type ScalarSubqueryNode,
17
+ type CaseExpressionNode,
18
+ type CastExpressionNode,
19
+ type WindowFunctionNode,
20
+ type BetweenExpressionNode,
21
+ type ArithmeticExpressionNode,
22
+ type BitwiseExpressionNode,
23
+ type CollateExpressionNode,
24
+ type AliasRefNode,
25
+ type IsDistinctExpressionNode,
26
+ isOperandNode
27
+ } from '../../ast/expression.js';
28
+
29
+ export interface ExpressionCompilerHost {
30
+ quoteIdentifier(id: string): string;
31
+ compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
32
+ compileSelectForExists(ast: SelectQueryNode, ctx: CompilerContext): string;
33
+ compileJsonPath(node: JsonPathNode): string;
34
+ compileFunctionOperand(node: FunctionNode, ctx: CompilerContext): string;
35
+ describe(): string;
36
+ }
37
+
38
+ type ExpressionCompiler = (node: ExpressionNode, ctx: CompilerContext) => string;
39
+ type OperandCompiler = (node: OperandNode, ctx: CompilerContext) => string;
40
+
41
+ /**
42
+ * Owns expression/operand dispatch independently from dialect query compilation.
43
+ * Dialects can replace individual node compilers without inheriting a second AST
44
+ * dispatcher or duplicating the default SQL expression behavior.
45
+ */
46
+ export class ExpressionCompilerRegistry {
47
+ private readonly expressionCompilers = new Map<string, ExpressionCompiler>();
48
+ private readonly operandCompilers = new Map<string, OperandCompiler>();
49
+
50
+ constructor(private readonly host: ExpressionCompilerHost) {
51
+ this.registerDefaultOperandCompilers();
52
+ this.registerDefaultExpressionCompilers();
53
+ }
54
+
55
+ registerExpressionCompiler<T extends ExpressionNode>(
56
+ type: T['type'],
57
+ compiler: (node: T, ctx: CompilerContext) => string
58
+ ): void {
59
+ this.expressionCompilers.set(type, compiler as ExpressionCompiler);
60
+ }
61
+
62
+ registerOperandCompiler<T extends OperandNode>(
63
+ type: T['type'],
64
+ compiler: (node: T, ctx: CompilerContext) => string
65
+ ): void {
66
+ this.operandCompilers.set(type, compiler as OperandCompiler);
67
+ }
68
+
69
+ compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
70
+ const compiler = this.expressionCompilers.get(node.type);
71
+ if (!compiler) {
72
+ throw new Error(`Unsupported expression node type "${node.type}" for ${this.host.describe()}`);
73
+ }
74
+ return compiler(node, ctx);
75
+ }
76
+
77
+ compileOperand(node: OperandNode, ctx: CompilerContext): string {
78
+ const compiler = this.operandCompilers.get(node.type);
79
+ if (!compiler) {
80
+ throw new Error(`Unsupported operand node type "${node.type}" for ${this.host.describe()}`);
81
+ }
82
+ return compiler(node, ctx);
83
+ }
84
+
85
+ compileOrderingTerm(term: OrderingTerm, ctx: CompilerContext): string {
86
+ if (isOperandNode(term)) {
87
+ return this.compileOperand(term, ctx);
88
+ }
89
+ return `(${this.compileExpression(term as ExpressionNode, ctx)})`;
90
+ }
91
+
92
+ private registerDefaultExpressionCompilers(): void {
93
+ this.registerExpressionCompiler('BinaryExpression', (binary: BinaryExpressionNode, ctx) => {
94
+ const left = this.compileOperand(binary.left, ctx);
95
+ const right = this.compileOperand(binary.right, ctx);
96
+ const base = `${left} ${binary.operator} ${right}`;
97
+ if (binary.escape) {
98
+ const escapeOperand = this.compileOperand(binary.escape, ctx);
99
+ return `${base} ESCAPE ${escapeOperand}`;
100
+ }
101
+ return base;
102
+ });
103
+
104
+ this.registerExpressionCompiler('LogicalExpression', (logical: LogicalExpressionNode, ctx) => {
105
+ if (logical.operands.length === 0) return '';
106
+ const parts = logical.operands.map(op => {
107
+ const compiled = this.compileExpression(op, ctx);
108
+ return op.type === 'LogicalExpression' ? `(${compiled})` : compiled;
109
+ });
110
+ return parts.join(` ${logical.operator} `);
111
+ });
112
+
113
+ this.registerExpressionCompiler('NotExpression', (notExpr: NotExpressionNode, ctx) => {
114
+ const operand = this.compileExpression(notExpr.operand, ctx);
115
+ return `NOT (${operand})`;
116
+ });
117
+
118
+ this.registerExpressionCompiler('NullExpression', (nullExpr: NullExpressionNode, ctx) => {
119
+ const left = this.compileOperand(nullExpr.left, ctx);
120
+ return `${left} ${nullExpr.operator}`;
121
+ });
122
+
123
+ this.registerExpressionCompiler('InExpression', (inExpr: InExpressionNode, ctx) => {
124
+ const left = this.compileOperand(inExpr.left, ctx);
125
+ if (Array.isArray(inExpr.right)) {
126
+ const values = inExpr.right.map(v => this.compileOperand(v, ctx)).join(', ');
127
+ return `${left} ${inExpr.operator} (${values})`;
128
+ }
129
+ const subquerySql = this.host.compileSelectAst(inExpr.right.query, ctx).trim().replace(/;$/, '');
130
+ return `${left} ${inExpr.operator} (${subquerySql})`;
131
+ });
132
+
133
+ this.registerExpressionCompiler('ExistsExpression', (existsExpr: ExistsExpressionNode, ctx) => {
134
+ const subquerySql = this.host.compileSelectForExists(existsExpr.subquery, ctx);
135
+ return `${existsExpr.operator} (${subquerySql})`;
136
+ });
137
+
138
+ this.registerExpressionCompiler('BetweenExpression', (betweenExpr: BetweenExpressionNode, ctx) => {
139
+ const left = this.compileOperand(betweenExpr.left, ctx);
140
+ const lower = this.compileOperand(betweenExpr.lower, ctx);
141
+ const upper = this.compileOperand(betweenExpr.upper, ctx);
142
+ return `${left} ${betweenExpr.operator} ${lower} AND ${upper}`;
143
+ });
144
+
145
+ this.registerExpressionCompiler('ArithmeticExpression', (arith: ArithmeticExpressionNode, ctx) => {
146
+ const left = this.compileOperand(arith.left, ctx);
147
+ const right = this.compileOperand(arith.right, ctx);
148
+ return `${left} ${arith.operator} ${right}`;
149
+ });
150
+
151
+ this.registerExpressionCompiler('BitwiseExpression', (bitwise: BitwiseExpressionNode, ctx) => {
152
+ const left = this.compileOperand(bitwise.left, ctx);
153
+ const right = this.compileOperand(bitwise.right, ctx);
154
+ return `${left} ${bitwise.operator} ${right}`;
155
+ });
156
+
157
+ this.registerExpressionCompiler('IsDistinctExpression', (node: IsDistinctExpressionNode, ctx) => {
158
+ const left = this.compileOperand(node.left, ctx);
159
+ const right = this.compileOperand(node.right, ctx);
160
+ return `${left} ${node.operator} ${right}`;
161
+ });
162
+ }
163
+
164
+ private registerDefaultOperandCompilers(): void {
165
+ this.registerOperandCompiler('Literal', (literal: LiteralNode, ctx) =>
166
+ ctx.addParameter(literal.value)
167
+ );
168
+
169
+ this.registerOperandCompiler('AliasRef', (alias: AliasRefNode) =>
170
+ this.host.quoteIdentifier(alias.name)
171
+ );
172
+
173
+ this.registerOperandCompiler('Column', (column: ColumnNode) =>
174
+ `${this.host.quoteIdentifier(column.table)}.${this.host.quoteIdentifier(column.name)}`
175
+ );
176
+
177
+ this.registerOperandCompiler('Function', (fnNode: FunctionNode, ctx) =>
178
+ this.host.compileFunctionOperand(fnNode, ctx)
179
+ );
180
+
181
+ this.registerOperandCompiler('JsonPath', (path: JsonPathNode) =>
182
+ this.host.compileJsonPath(path)
183
+ );
184
+
185
+ this.registerOperandCompiler('ScalarSubquery', (node: ScalarSubqueryNode, ctx) => {
186
+ const sql = this.host.compileSelectAst(node.query, ctx).trim().replace(/;$/, '');
187
+ return `(${sql})`;
188
+ });
189
+
190
+ this.registerOperandCompiler('CaseExpression', (node: CaseExpressionNode, ctx) => {
191
+ const parts = ['CASE'];
192
+ for (const { when, then } of node.conditions) {
193
+ parts.push(`WHEN ${this.compileExpression(when, ctx)} THEN ${this.compileOperand(then, ctx)}`);
194
+ }
195
+ if (node.else) {
196
+ parts.push(`ELSE ${this.compileOperand(node.else, ctx)}`);
197
+ }
198
+ parts.push('END');
199
+ return parts.join(' ');
200
+ });
201
+
202
+ this.registerOperandCompiler('Cast', (node: CastExpressionNode, ctx) => {
203
+ const value = this.compileOperand(node.expression, ctx);
204
+ return `CAST(${value} AS ${node.castType})`;
205
+ });
206
+
207
+ this.registerOperandCompiler('WindowFunction', (node: WindowFunctionNode, ctx) => {
208
+ let result = `${node.name}(`;
209
+ if (node.args.length > 0) {
210
+ result += node.args.map(arg => this.compileOperand(arg, ctx)).join(', ');
211
+ }
212
+ result += ') OVER (';
213
+
214
+ const parts: string[] = [];
215
+ if (node.partitionBy && node.partitionBy.length > 0) {
216
+ const partitionClause = 'PARTITION BY ' + node.partitionBy.map(col =>
217
+ `${this.host.quoteIdentifier(col.table)}.${this.host.quoteIdentifier(col.name)}`
218
+ ).join(', ');
219
+ parts.push(partitionClause);
220
+ }
221
+
222
+ if (node.orderBy && node.orderBy.length > 0) {
223
+ const orderClause = 'ORDER BY ' + node.orderBy.map(o => {
224
+ const term = this.compileOrderingTerm(o.term, ctx);
225
+ const collation = o.collation ? ` COLLATE ${o.collation}` : '';
226
+ const nulls = o.nulls ? ` NULLS ${o.nulls}` : '';
227
+ return `${term} ${o.direction}${collation}${nulls}`;
228
+ }).join(', ');
229
+ parts.push(orderClause);
230
+ }
231
+
232
+ result += parts.join(' ');
233
+ result += ')';
234
+ return result;
235
+ });
236
+
237
+ this.registerOperandCompiler('ArithmeticExpression', (node: ArithmeticExpressionNode, ctx) => {
238
+ const left = this.compileOperand(node.left, ctx);
239
+ const right = this.compileOperand(node.right, ctx);
240
+ return `(${left} ${node.operator} ${right})`;
241
+ });
242
+
243
+ this.registerOperandCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
244
+ const left = this.compileOperand(node.left, ctx);
245
+ const right = this.compileOperand(node.right, ctx);
246
+ return `(${left} ${node.operator} ${right})`;
247
+ });
248
+
249
+ this.registerOperandCompiler('Collate', (node: CollateExpressionNode, ctx) => {
250
+ const expr = this.compileOperand(node.expression, ctx);
251
+ return `${expr} COLLATE ${node.collation}`;
252
+ });
253
+ }
254
+ }
@@ -1,112 +1,74 @@
1
- import { CompilerContext } from '../abstract.js';
2
- import { OperandNode } from '../../ast/expression.js';
3
- import { FunctionTableNode } from '../../ast/query.js';
4
- import { SqlDialectBase } from './sql-dialect.js';
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { OperandNode } from '../../ast/expression.js';
3
+ import type { FunctionTableNode } from '../../ast/query.js';
4
+
5
+ export interface FunctionTableFormattingContext {
6
+ quoteIdentifier(id: string): string;
7
+ compileOperand(node: OperandNode, ctx: CompilerContext): string;
8
+ }
5
9
 
6
10
  /**
7
- * Formatter for function table expressions (e.g., LATERAL unnest(...) WITH ORDINALITY).
8
- * Encapsulates logic for generating SQL function table syntax including LATERAL, aliases, and column lists.
11
+ * Formats function-table expressions without depending on a dialect base class.
12
+ * SQL compilers provide only the two operations this formatter actually needs.
9
13
  */
10
14
  export class FunctionTableFormatter {
11
- /**
12
- * Formats a function table node into SQL syntax.
13
- * @param fn - The function table node containing schema, name, args, and aliases.
14
- * @param ctx - Optional compiler context for operand compilation.
15
- * @param dialect - The dialect instance for compiling operands.
16
- * @returns SQL function table expression (e.g., "LATERAL schema.func(args) WITH ORDINALITY AS alias(col1, col2)").
17
- */
18
- static format(fn: FunctionTableNode, ctx?: CompilerContext, dialect?: SqlDialectBase): string {
19
- const schemaPart = this.formatSchema(fn, dialect);
20
- const args = this.formatArgs(fn, ctx, dialect);
15
+ static format(
16
+ fn: FunctionTableNode,
17
+ ctx: CompilerContext | undefined,
18
+ formatter: FunctionTableFormattingContext
19
+ ): string {
20
+ const schemaPart = this.formatSchema(fn, formatter);
21
+ const args = this.formatArgs(fn, ctx, formatter);
21
22
  const base = this.formatBase(fn, schemaPart, args);
22
23
  const lateral = this.formatLateral(fn);
23
- const alias = this.formatAlias(fn, dialect);
24
- const colAliases = this.formatColumnAliases(fn, dialect);
24
+ const alias = this.formatAlias(fn, formatter);
25
+ const colAliases = this.formatColumnAliases(fn, formatter);
25
26
  return `${lateral}${base}${alias}${colAliases}`;
26
27
  }
27
28
 
28
- /**
29
- * Formats the schema prefix for the function name.
30
- * @param fn - The function table node.
31
- * @param dialect - The dialect instance for quoting identifiers.
32
- * @returns Schema prefix (e.g., "schema.") or empty string.
33
- * @internal
34
- */
35
- private static formatSchema(fn: FunctionTableNode, dialect?: SqlDialectBase): string {
29
+ private static formatSchema(
30
+ fn: FunctionTableNode,
31
+ formatter: FunctionTableFormattingContext
32
+ ): string {
36
33
  if (!fn.schema) return '';
37
- const quoted = dialect ? dialect.quoteIdentifier(fn.schema) : fn.schema;
38
- return `${quoted}.`;
34
+ return `${formatter.quoteIdentifier(fn.schema)}.`;
39
35
  }
40
36
 
41
- /**
42
- * Formats function arguments into SQL syntax.
43
- * @param fn - The function table node containing arguments.
44
- * @param ctx - Optional compiler context for operand compilation.
45
- * @param dialect - The dialect instance for compiling operands.
46
- * @returns Comma-separated function arguments.
47
- * @internal
48
- */
49
- private static formatArgs(fn: FunctionTableNode, ctx?: CompilerContext, dialect?: SqlDialectBase): string {
37
+ private static formatArgs(
38
+ fn: FunctionTableNode,
39
+ ctx: CompilerContext | undefined,
40
+ formatter: FunctionTableFormattingContext
41
+ ): string {
50
42
  return (fn.args || [])
51
- .map((a: OperandNode) => {
52
- if (ctx && dialect) {
53
- return (dialect as unknown as { compileOperand(n: OperandNode, c: CompilerContext): string }).compileOperand(a, ctx);
54
- }
55
- return String(a);
56
- })
43
+ .map((arg: OperandNode) => ctx ? formatter.compileOperand(arg, ctx) : String(arg))
57
44
  .join(', ');
58
45
  }
59
46
 
60
- /**
61
- * Formats the base function call with WITH ORDINALITY if present.
62
- * @param fn - The function table node.
63
- * @param schemaPart - Formatted schema prefix.
64
- * @param args - Formatted function arguments.
65
- * @param dialect - The dialect instance for quoting identifiers.
66
- * @returns Base function call expression (e.g., "schema.func(args) WITH ORDINALITY").
67
- * @internal
68
- */
69
47
  private static formatBase(fn: FunctionTableNode, schemaPart: string, args: string): string {
70
48
  const ordinality = fn.withOrdinality ? ' WITH ORDINALITY' : '';
71
49
  return `${schemaPart}${fn.name}(${args})${ordinality}`;
72
50
  }
73
51
 
74
- /**
75
- * Formats the LATERAL keyword if present.
76
- * @param fn - The function table node.
77
- * @returns "LATERAL " or empty string.
78
- * @internal
79
- */
80
52
  private static formatLateral(fn: FunctionTableNode): string {
81
53
  return fn.lateral ? 'LATERAL ' : '';
82
54
  }
83
55
 
84
- /**
85
- * Formats the table alias for the function table.
86
- * @param fn - The function table node.
87
- * @param dialect - The dialect instance for quoting identifiers.
88
- * @returns " AS alias" or empty string.
89
- * @internal
90
- */
91
- private static formatAlias(fn: FunctionTableNode, dialect?: SqlDialectBase): string {
56
+ private static formatAlias(
57
+ fn: FunctionTableNode,
58
+ formatter: FunctionTableFormattingContext
59
+ ): string {
92
60
  if (!fn.alias) return '';
93
- const quoted = dialect ? dialect.quoteIdentifier(fn.alias) : fn.alias;
94
- return ` AS ${quoted}`;
61
+ return ` AS ${formatter.quoteIdentifier(fn.alias)}`;
95
62
  }
96
63
 
97
- /**
98
- * Formats column aliases for the function table result columns.
99
- * @param fn - The function table node containing column aliases.
100
- * @param dialect - The dialect instance for quoting identifiers.
101
- * @returns "(col1, col2, ...)" or empty string.
102
- * @internal
103
- */
104
- private static formatColumnAliases(fn: FunctionTableNode, dialect?: SqlDialectBase): string {
64
+ private static formatColumnAliases(
65
+ fn: FunctionTableNode,
66
+ formatter: FunctionTableFormattingContext
67
+ ): string {
105
68
  if (!fn.columnAliases || !fn.columnAliases.length) return '';
106
69
  const aliases = fn.columnAliases
107
- .map(col => dialect ? dialect.quoteIdentifier(col) : col)
70
+ .map(col => formatter.quoteIdentifier(col))
108
71
  .join(', ');
109
72
  return `(${aliases})`;
110
73
  }
111
74
  }
112
-
@@ -0,0 +1,61 @@
1
+ import type {
2
+ CommonTableExpressionNode,
3
+ SelectQueryNode,
4
+ SetOperationKind
5
+ } from '../../ast/query.js';
6
+
7
+ export type SetOperationSupport = (kind: SetOperationKind) => boolean;
8
+
9
+ /**
10
+ * Normalizes compound SELECT ASTs independently from SQL rendering.
11
+ *
12
+ * Responsibilities are intentionally limited to set-operation validation and
13
+ * CTE hoisting, so dialect SQL compilers consume one stable normalized shape.
14
+ */
15
+ export class SelectAstNormalizer {
16
+ constructor(private readonly supportsSetOperation: SetOperationSupport) {}
17
+
18
+ normalize(ast: SelectQueryNode): SelectQueryNode {
19
+ this.validateSetOperations(ast, true);
20
+ const { normalized, hoistedCtes } = this.hoistCtes(ast);
21
+ const combinedCtes = [...(normalized.ctes ?? []), ...hoistedCtes];
22
+ return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
23
+ }
24
+
25
+ private validateSetOperations(ast: SelectQueryNode, isOutermost: boolean): void {
26
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
27
+ if (!isOutermost && (ast.orderBy || ast.limit !== undefined || ast.offset !== undefined)) {
28
+ throw new Error('ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.');
29
+ }
30
+
31
+ if (!hasSetOps) return;
32
+
33
+ for (const op of ast.setOps!) {
34
+ if (!this.supportsSetOperation(op.operator)) {
35
+ throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
36
+ }
37
+ this.validateSetOperations(op.query, false);
38
+ }
39
+ }
40
+
41
+ private hoistCtes(ast: SelectQueryNode): {
42
+ normalized: SelectQueryNode;
43
+ hoistedCtes: CommonTableExpressionNode[];
44
+ } {
45
+ let hoisted: CommonTableExpressionNode[] = [];
46
+
47
+ const normalizedSetOps = ast.setOps?.map(op => {
48
+ const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
49
+ const childCtes = child.ctes ?? [];
50
+ if (childCtes.length) hoisted = hoisted.concat(childCtes);
51
+ hoisted = hoisted.concat(childHoisted);
52
+ const queryWithoutCtes = childCtes.length ? { ...child, ctes: undefined } : child;
53
+ return { ...op, query: queryWithoutCtes };
54
+ });
55
+
56
+ const normalized: SelectQueryNode = normalizedSetOps
57
+ ? { ...ast, setOps: normalizedSetOps }
58
+ : ast;
59
+ return { normalized, hoistedCtes: hoisted };
60
+ }
61
+ }
@@ -1,5 +1,6 @@
1
- import { CompilerContext, Dialect } from '../abstract.js';
2
- import {
1
+ import { DialectBase } from '../abstract.js';
2
+ import type { CompilerContext } from '../abstract.js';
3
+ import type {
3
4
  SelectQueryNode,
4
5
  InsertQueryNode,
5
6
  UpdateQueryNode,
@@ -12,22 +13,22 @@ import {
12
13
  OrderByNode,
13
14
  TableNode
14
15
  } from '../../ast/query.js';
15
- import { ColumnNode, OperandNode } from '../../ast/expression.js';
16
+ import type { ColumnNode, OperandNode } from '../../ast/expression.js';
16
17
  import { FunctionTableFormatter } from './function-table-formatter.js';
17
- import { PaginationStrategy, StandardLimitOffsetPagination } from './pagination-strategy.js';
18
+ import { StandardLimitOffsetPagination } from './pagination-strategy.js';
19
+ import type { PaginationStrategy } from './pagination-strategy.js';
18
20
  import { CteCompiler } from './cte-compiler.js';
19
- import { ReturningStrategy, NoReturningStrategy } from './returning-strategy.js';
21
+ import { NoReturningStrategy } from './returning-strategy.js';
22
+ import type { ReturningStrategy } from './returning-strategy.js';
20
23
  import { JoinCompiler } from './join-compiler.js';
21
24
  import { GroupByCompiler } from './groupby-compiler.js';
22
25
  import { OrderByCompiler } from './orderby-compiler.js';
23
26
 
24
-
25
27
  /**
26
- * Base class for SQL dialects.
27
- * Provides a common framework for compiling AST nodes into SQL strings.
28
- * Specific dialects should extend this class and implement dialect-specific logic.
28
+ * Reusable SQL implementation built on the structural Dialect contract.
29
+ * Dialects extend this only when its standard SELECT/DML behavior is useful.
29
30
  */
30
- export abstract class SqlDialectBase extends Dialect {
31
+ export abstract class SqlDialectBase extends DialectBase {
31
32
  abstract quoteIdentifier(id: string): string;
32
33
 
33
34
  protected paginationStrategy: PaginationStrategy = new StandardLimitOffsetPagination();
@@ -40,16 +41,14 @@ export abstract class SqlDialectBase extends Dialect {
40
41
  ctx,
41
42
  this.quoteIdentifier.bind(this),
42
43
  this.compileSelectAst.bind(this),
43
- this.normalizeSelectAst?.bind(this) ?? ((a) => a),
44
+ this.normalizeSelectAst.bind(this),
44
45
  this.stripTrailingSemicolon.bind(this)
45
46
  );
46
47
  const baseAst: SelectQueryNode = hasSetOps
47
48
  ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
48
49
  : ast;
49
50
  const baseSelect = this.compileSelectCore(baseAst, ctx);
50
- if (!hasSetOps) {
51
- return `${ctes}${baseSelect}`;
52
- }
51
+ if (!hasSetOps) return `${ctes}${baseSelect}`;
53
52
  return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
54
53
  }
55
54
 
@@ -116,9 +115,7 @@ export abstract class SqlDialectBase extends Dialect {
116
115
  }
117
116
 
118
117
  protected ensureConflictColumns(clause: UpsertClause, message: string): void {
119
- if (!clause.target.columns.length) {
120
- throw new Error(message);
121
- }
118
+ if (!clause.target.columns.length) throw new Error(message);
122
119
  }
123
120
 
124
121
  private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
@@ -159,8 +156,7 @@ export abstract class SqlDialectBase extends Dialect {
159
156
  ): string {
160
157
  return assignments
161
158
  .map(assignment => {
162
- const col = assignment.column;
163
- const target = this.compileSetTarget(col, table);
159
+ const target = this.compileSetTarget(assignment.column, table);
164
160
  const value = this.compileOperand(assignment.value, ctx);
165
161
  return `${target} = ${value}`;
166
162
  })
@@ -175,13 +171,9 @@ export abstract class SqlDialectBase extends Dialect {
175
171
  const baseTableName = table.name;
176
172
  const alias = table.alias;
177
173
  const columnTable = column.table ?? alias ?? baseTableName;
178
- const tableQualifier =
179
- alias && column.table === baseTableName ? alias : columnTable;
180
-
181
- if (!tableQualifier) {
182
- return this.quoteIdentifier(column.name);
183
- }
174
+ const tableQualifier = alias && column.table === baseTableName ? alias : columnTable;
184
175
 
176
+ if (!tableQualifier) return this.quoteIdentifier(column.name);
185
177
  return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
186
178
  }
187
179
 
@@ -202,28 +194,21 @@ export abstract class SqlDialectBase extends Dialect {
202
194
  }
203
195
 
204
196
  protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
205
- if (!ast.columns || ast.columns.length === 0) {
206
- return '*';
207
- }
208
- return ast.columns.map(c => {
209
- const expr = this.compileOperand(c, ctx);
210
- if (c.alias) {
211
- if (c.alias.includes('(')) return c.alias;
212
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
197
+ if (!ast.columns || ast.columns.length === 0) return '*';
198
+ return ast.columns.map(column => {
199
+ const expr = this.compileOperand(column, ctx);
200
+ if (column.alias) {
201
+ if (column.alias.includes('(')) return column.alias;
202
+ return `${expr} AS ${this.quoteIdentifier(column.alias)}`;
213
203
  }
214
204
  return expr;
215
205
  }).join(', ');
216
206
  }
217
207
 
218
208
  protected compileFrom(ast: SelectQueryNode['from'], ctx?: CompilerContext): string {
219
- const tableSource = ast;
220
- if (tableSource.type === 'FunctionTable') {
221
- return this.compileFunctionTable(tableSource, ctx);
222
- }
223
- if (tableSource.type === 'DerivedTable') {
224
- return this.compileDerivedTable(tableSource, ctx);
225
- }
226
- return this.compileTableSource(tableSource);
209
+ if (ast.type === 'FunctionTable') return this.compileFunctionTable(ast, ctx);
210
+ if (ast.type === 'DerivedTable') return this.compileDerivedTable(ast, ctx);
211
+ return this.compileTableSource(ast);
227
212
  }
228
213
 
229
214
  protected compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
@@ -246,13 +231,14 @@ export abstract class SqlDialectBase extends Dialect {
246
231
  }
247
232
  }
248
233
 
249
- return FunctionTableFormatter.format(fn, ctx, this);
234
+ return FunctionTableFormatter.format(fn, ctx, {
235
+ quoteIdentifier: id => this.quoteIdentifier(id),
236
+ compileOperand: (node, compilerContext) => this.compileOperand(node, compilerContext)
237
+ });
250
238
  }
251
239
 
252
240
  protected compileDerivedTable(table: DerivedTableNode, ctx?: CompilerContext): string {
253
- if (!table.alias) {
254
- throw new Error('Derived tables must have an alias.');
255
- }
241
+ if (!table.alias) throw new Error('Derived tables must have an alias.');
256
242
  const subquery = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx!).trim().replace(/;$/, '');
257
243
  const columns = table.columnAliases?.length
258
244
  ? ` (${table.columnAliases.map(c => this.quoteIdentifier(c)).join(', ')})`
@@ -261,12 +247,8 @@ export abstract class SqlDialectBase extends Dialect {
261
247
  }
262
248
 
263
249
  protected compileTableSource(table: TableSourceNode): string {
264
- if (table.type === 'FunctionTable') {
265
- return this.compileFunctionTable(table as FunctionTableNode);
266
- }
267
- if (table.type === 'DerivedTable') {
268
- return this.compileDerivedTable(table as DerivedTableNode);
269
- }
250
+ if (table.type === 'FunctionTable') return this.compileFunctionTable(table as FunctionTableNode);
251
+ if (table.type === 'DerivedTable') return this.compileDerivedTable(table as DerivedTableNode);
270
252
  const base = this.compileTableName(table);
271
253
  return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
272
254
  }
@@ -285,9 +267,7 @@ export abstract class SqlDialectBase extends Dialect {
285
267
 
286
268
  private compileUpdateFromClause(ast: UpdateQueryNode, ctx: CompilerContext): string {
287
269
  if (!ast.from && (!ast.joins || ast.joins.length === 0)) return '';
288
- if (!ast.from) {
289
- throw new Error('UPDATE with JOINs requires an explicit FROM clause.');
290
- }
270
+ if (!ast.from) throw new Error('UPDATE with JOINs requires an explicit FROM clause.');
291
271
  const from = this.compileFrom(ast.from, ctx);
292
272
  const joins = JoinCompiler.compileJoins(
293
273
  ast.joins,
@@ -300,9 +280,7 @@ export abstract class SqlDialectBase extends Dialect {
300
280
 
301
281
  private compileDeleteUsingClause(ast: DeleteQueryNode, ctx: CompilerContext): string {
302
282
  if (!ast.using && (!ast.joins || ast.joins.length === 0)) return '';
303
- if (!ast.using) {
304
- throw new Error('DELETE with JOINs requires a USING clause.');
305
- }
283
+ if (!ast.using) throw new Error('DELETE with JOINs requires a USING clause.');
306
284
  const usingTable = this.compileFrom(ast.using, ctx);
307
285
  const joins = JoinCompiler.compileJoins(
308
286
  ast.joins,
@@ -323,8 +301,7 @@ export abstract class SqlDialectBase extends Dialect {
323
301
  }
324
302
 
325
303
  protected wrapSetOperand(sql: string): string {
326
- const trimmed = this.stripTrailingSemicolon(sql);
327
- return `(${trimmed})`;
304
+ return `(${this.stripTrailingSemicolon(sql)})`;
328
305
  }
329
306
 
330
307
  protected renderOrderByNulls(order: OrderByNode): string | undefined {