metal-orm 1.1.24 → 1.1.25

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.
Files changed (31) hide show
  1. package/dist/index.cjs +539 -412
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +225 -176
  4. package/dist/index.d.ts +225 -176
  5. package/dist/index.js +519 -412
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/core/dialect/base/returning-strategy.ts +40 -39
  9. package/src/core/dialect/base/sql-compiler-set.ts +33 -0
  10. package/src/core/dialect/base/sql-dialect.ts +79 -38
  11. package/src/core/dialect/base/upsert-strategy.ts +45 -0
  12. package/src/core/dialect/capabilities/procedure-compiler.ts +10 -8
  13. package/src/core/dialect/mssql/compiler-factory.ts +12 -0
  14. package/src/core/dialect/mssql/delete-compiler.ts +40 -0
  15. package/src/core/dialect/mssql/index.ts +24 -371
  16. package/src/core/dialect/mssql/insert-compiler.ts +112 -0
  17. package/src/core/dialect/mssql/output.ts +46 -0
  18. package/src/core/dialect/mssql/procedure-compiler.ts +81 -0
  19. package/src/core/dialect/mssql/select-compiler.ts +116 -0
  20. package/src/core/dialect/mssql/update-compiler.ts +37 -0
  21. package/src/core/dialect/mysql/index.ts +24 -117
  22. package/src/core/dialect/mysql/procedure-compiler.ts +67 -0
  23. package/src/core/dialect/mysql/upsert.ts +42 -0
  24. package/src/core/dialect/postgres/index.ts +34 -101
  25. package/src/core/dialect/postgres/procedure-compiler.ts +41 -0
  26. package/src/core/dialect/postgres/returning.ts +4 -0
  27. package/src/core/dialect/postgres/upsert.ts +43 -0
  28. package/src/core/dialect/sqlite/index.ts +15 -70
  29. package/src/core/dialect/sqlite/returning.ts +30 -0
  30. package/src/core/dialect/sqlite/upsert.ts +43 -0
  31. package/src/index.ts +22 -10
@@ -0,0 +1,116 @@
1
+ import type { OperandNode } from '../../ast/expression.js';
2
+ import type { SelectQueryNode } from '../../ast/query.js';
3
+ import type { CompilerContext } from '../abstract.js';
4
+ import { OrderByCompiler } from '../base/orderby-compiler.js';
5
+ import type { SqlAstCompiler } from '../base/sql-compiler-set.js';
6
+ import type { StandardSqlCompilerServices } from '../base/standard-sql-services.js';
7
+ import type { StandardSqlSourceCompiler } from '../base/standard-sql-source-compiler.js';
8
+
9
+ export class MssqlSelectCompiler implements SqlAstCompiler<SelectQueryNode> {
10
+ constructor(
11
+ private readonly services: StandardSqlCompilerServices,
12
+ private readonly sources: StandardSqlSourceCompiler
13
+ ) {}
14
+
15
+ compile(ast: SelectQueryNode, ctx: CompilerContext): string {
16
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
17
+ const ctes = this.compileCtes(ast, ctx);
18
+ const baseAst: SelectQueryNode = hasSetOps
19
+ ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
20
+ : ast;
21
+ const baseSelect = this.compileCore(baseAst, ctx);
22
+
23
+ if (!hasSetOps) return `${ctes}${baseSelect}`;
24
+
25
+ const compound = ast.setOps!
26
+ .map(op => `${op.operator} ${this.sources.wrapSetOperand(this.services.compileSelectAst(op.query, ctx))}`)
27
+ .join(' ');
28
+ const orderBy = this.compileOrderBy(ast, ctx);
29
+ const pagination = this.compilePagination(ast, orderBy);
30
+ const combined = `${this.sources.wrapSetOperand(baseSelect)} ${compound}`;
31
+ return `${ctes}${combined}${pagination || orderBy}`;
32
+ }
33
+
34
+ private compileCore(ast: SelectQueryNode, ctx: CompilerContext): string {
35
+ const columns = ast.columns
36
+ .map(column => {
37
+ const expr = column.type === 'Column'
38
+ ? `${this.services.quoteIdentifier(column.table)}.${this.services.quoteIdentifier(column.name)}`
39
+ : this.services.compileOperand(column as OperandNode, ctx);
40
+ if (!column.alias) return expr;
41
+ if (column.alias.includes('(')) return column.alias;
42
+ return `${expr} AS ${this.services.quoteIdentifier(column.alias)}`;
43
+ })
44
+ .join(', ');
45
+
46
+ const distinct = ast.distinct ? 'DISTINCT ' : '';
47
+ const from = this.sources.compileFrom(ast.from, ctx);
48
+ const joins = ast.joins
49
+ .map(join => {
50
+ const table = this.sources.compileFrom(join.table, ctx);
51
+ const condition = this.services.compileExpression(join.condition, ctx);
52
+ return `${join.kind} JOIN ${table} ON ${condition}`;
53
+ })
54
+ .join(' ');
55
+ const where = ast.where
56
+ ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}`
57
+ : '';
58
+ const groupBy = ast.groupBy && ast.groupBy.length > 0
59
+ ? ` GROUP BY ${ast.groupBy.map(term => this.services.compileOrderingTerm(term, ctx)).join(', ')}`
60
+ : '';
61
+ const having = ast.having
62
+ ? ` HAVING ${this.services.compileExpression(ast.having, ctx)}`
63
+ : '';
64
+ const orderBy = this.compileOrderBy(ast, ctx);
65
+ const pagination = this.compilePagination(ast, orderBy);
66
+
67
+ if (pagination) {
68
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ` ${joins}` : ''}${where}${groupBy}${having}${pagination}`;
69
+ }
70
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ` ${joins}` : ''}${where}${groupBy}${having}${orderBy}`;
71
+ }
72
+
73
+ private compileOrderBy(ast: SelectQueryNode, ctx: CompilerContext): string {
74
+ return OrderByCompiler.compileOrderBy(
75
+ ast,
76
+ term => this.services.compileOrderingTerm(term, ctx),
77
+ order => this.services.renderOrderByNulls(order),
78
+ order => this.services.renderOrderByCollation(order)
79
+ );
80
+ }
81
+
82
+ private compilePagination(ast: SelectQueryNode, orderBy: string): string {
83
+ const hasLimit = ast.limit !== undefined;
84
+ const hasOffset = ast.offset !== undefined;
85
+ if (!hasLimit && !hasOffset) return '';
86
+
87
+ const offset = ast.offset ?? 0;
88
+ let orderClause = orderBy;
89
+ if (!orderClause) {
90
+ orderClause = ast.distinct && ast.distinct.length > 0
91
+ ? ' ORDER BY 1'
92
+ : ' ORDER BY (SELECT NULL)';
93
+ }
94
+
95
+ let pagination = `${orderClause} OFFSET ${offset} ROWS`;
96
+ if (hasLimit) pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
97
+ return pagination;
98
+ }
99
+
100
+ private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
101
+ if (!ast.ctes || ast.ctes.length === 0) return '';
102
+ const definitions = ast.ctes
103
+ .map(cte => {
104
+ const name = this.services.quoteIdentifier(cte.name);
105
+ const columns = cte.columns
106
+ ? `(${cte.columns.map(column => this.services.quoteIdentifier(column)).join(', ')})`
107
+ : '';
108
+ const query = this.sources.stripTrailingSemicolon(
109
+ this.services.compileSelectAst(this.services.normalizeSelectAst(cte.query), ctx)
110
+ );
111
+ return `${name}${columns} AS (${query})`;
112
+ })
113
+ .join(', ');
114
+ return `WITH ${definitions} `;
115
+ }
116
+ }
@@ -0,0 +1,37 @@
1
+ import type { UpdateQueryNode } from '../../ast/query.js';
2
+ import type { CompilerContext } from '../abstract.js';
3
+ import type { SqlAstCompiler } from '../base/sql-compiler-set.js';
4
+ import type { StandardSqlCompilerServices } from '../base/standard-sql-services.js';
5
+ import { StandardUpdateCompiler } from '../base/standard-update-compiler.js';
6
+ import type { StandardSqlSourceCompiler } from '../base/standard-sql-source-compiler.js';
7
+
8
+ export class MssqlUpdateCompiler implements SqlAstCompiler<UpdateQueryNode> {
9
+ private readonly standardUpdate: StandardUpdateCompiler;
10
+
11
+ constructor(
12
+ private readonly services: StandardSqlCompilerServices,
13
+ private readonly sources: StandardSqlSourceCompiler
14
+ ) {
15
+ this.standardUpdate = new StandardUpdateCompiler(services, sources);
16
+ }
17
+
18
+ compile(ast: UpdateQueryNode, ctx: CompilerContext): string {
19
+ const target = this.sources.compileTableReference(ast.table);
20
+ const assignments = this.standardUpdate.compileAssignments(ast.set, ast.table, ctx);
21
+ const output = this.services.compileReturning(ast.returning, ctx);
22
+ const from = ast.from ? ` FROM ${this.sources.compileFrom(ast.from, ctx)}` : '';
23
+ const joins = ast.joins
24
+ ? ast.joins
25
+ .map(join => {
26
+ const table = this.sources.compileFrom(join.table, ctx);
27
+ const condition = this.services.compileExpression(join.condition, ctx);
28
+ return ` ${join.kind} JOIN ${table} ON ${condition}`;
29
+ })
30
+ .join('')
31
+ : '';
32
+ const where = ast.where
33
+ ? ` WHERE ${this.services.compileExpression(ast.where, ctx)}`
34
+ : '';
35
+ return `UPDATE ${target} SET ${assignments}${output}${from}${joins}${where}`;
36
+ }
37
+ }
@@ -1,144 +1,51 @@
1
- import { CompilerContext } from '../abstract.js';
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type { JsonPathNode, IsDistinctExpressionNode } from '../../ast/expression.js';
2
3
  import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
3
- import { JsonPathNode, IsDistinctExpressionNode } from '../../ast/expression.js';
4
- import { InsertQueryNode } from '../../ast/query.js';
5
4
  import { SqlDialectBase } from '../base/sql-dialect.js';
6
5
  import { MysqlFunctionStrategy } from './functions.js';
7
- import { ProcedureCallNode } from '../../ast/procedure.js';
6
+ import { MySqlProcedureCompiler } from './procedure-compiler.js';
7
+ import { MySqlUpsertStrategy } from './upsert.js';
8
8
 
9
- const sanitizeVariableSuffix = (value: string): string =>
10
- value.replace(/[^a-zA-Z0-9_]/g, '_');
11
-
12
- /**
13
- * MySQL dialect implementation
14
- */
9
+ /** MySQL dialect assembled from reusable compiler components. */
15
10
  export class MySqlDialect extends SqlDialectBase implements ProcedureCompiler {
16
11
  protected readonly dialect = 'mysql';
17
- /**
18
- * Creates a new MySqlDialect instance
19
- */
12
+ private readonly procedureCompiler: MySqlProcedureCompiler;
13
+
20
14
  public constructor() {
21
- super(new MysqlFunctionStrategy());
15
+ super({
16
+ functionStrategy: new MysqlFunctionStrategy(),
17
+ upsertStrategy: new MySqlUpsertStrategy()
18
+ });
19
+
20
+ this.procedureCompiler = new MySqlProcedureCompiler({
21
+ quoteIdentifier: id => this.quoteIdentifier(id),
22
+ createCompilerContext: () => this.createCompilerContext(),
23
+ compileOperand: (node, ctx) => this.compileOperand(node, ctx)
24
+ });
22
25
 
23
26
  this.registerExpressionCompiler(
24
27
  'IsDistinctExpression',
25
- (node: IsDistinctExpressionNode, ctx: CompilerContext): string => {
28
+ (node: IsDistinctExpressionNode, ctx): string => {
26
29
  const left = this.compileOperand(node.left, ctx);
27
30
  const right = this.compileOperand(node.right, ctx);
28
31
  const spaceship = `${left} <=> ${right}`;
29
-
30
- if (node.operator === 'IS NOT DISTINCT FROM') {
31
- return spaceship;
32
- }
33
-
34
- return `NOT (${spaceship})`;
32
+ return node.operator === 'IS NOT DISTINCT FROM'
33
+ ? spaceship
34
+ : `NOT (${spaceship})`;
35
35
  }
36
36
  );
37
37
  }
38
38
 
39
- /**
40
- * Quotes an identifier using MySQL backtick syntax
41
- * @param id - Identifier to quote
42
- * @returns Quoted identifier
43
- */
44
39
  quoteIdentifier(id: string): string {
45
40
  return `\`${id}\``;
46
41
  }
47
42
 
48
- /**
49
- * Compiles JSON path expression using MySQL syntax
50
- * @param node - JSON path node
51
- * @returns MySQL JSON path expression
52
- */
53
43
  protected compileJsonPath(node: JsonPathNode): string {
54
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
55
- // MySQL 5.7+ uses col->'$.path'
56
- return `${col}->'${node.path}'`;
57
- }
58
-
59
- protected compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string {
60
- if (!ast.onConflict) return '';
61
-
62
- const clause = ast.onConflict;
63
- if (clause.action.type === 'DoNothing') {
64
- const noOpColumn = clause.target.columns[0] ?? ast.columns[0];
65
- if (!noOpColumn) {
66
- throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one target column.');
67
- }
68
- const col = this.quoteIdentifier(noOpColumn.name);
69
- return ` ON DUPLICATE KEY UPDATE ${col} = ${col}`;
70
- }
71
-
72
- if (clause.action.where) {
73
- throw new Error('MySQL ON DUPLICATE KEY UPDATE does not support a WHERE clause.');
74
- }
75
- if (!clause.action.set.length) {
76
- throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one assignment.');
77
- }
78
-
79
- const assignments = clause.action.set
80
- .map(assignment => {
81
- const target = this.quoteIdentifier(assignment.column.name);
82
- const value = this.compileOperand(assignment.value, ctx);
83
- return `${target} = ${value}`;
84
- })
85
- .join(', ');
86
- return ` ON DUPLICATE KEY UPDATE ${assignments}`;
44
+ const column = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
45
+ return `${column}->'${node.path}'`;
87
46
  }
88
47
 
89
48
  compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
90
- const ctx = this.createCompilerContext();
91
- const qualifiedName = ast.ref.schema
92
- ? `${this.quoteIdentifier(ast.ref.schema)}.${this.quoteIdentifier(ast.ref.name)}`
93
- : this.quoteIdentifier(ast.ref.name);
94
-
95
- const prelude: string[] = [];
96
- const callArgs: string[] = [];
97
- const outVars: Array<{ variable: string; name: string }> = [];
98
-
99
- ast.params.forEach((param, index) => {
100
- const suffix = sanitizeVariableSuffix(param.name || `p${index + 1}`);
101
- const variable = `@__metal_${suffix}_${index + 1}`;
102
-
103
- if (param.direction === 'in') {
104
- if (!param.value) {
105
- throw new Error(`Procedure parameter "${param.name}" requires a value for direction "in".`);
106
- }
107
- callArgs.push(this.compileOperand(param.value, ctx));
108
- return;
109
- }
110
-
111
- if (param.direction === 'inout') {
112
- if (!param.value) {
113
- throw new Error(`Procedure parameter "${param.name}" requires a value for direction "inout".`);
114
- }
115
- prelude.push(`SET ${variable} = ${this.compileOperand(param.value, ctx)};`);
116
- }
117
-
118
- callArgs.push(variable);
119
- outVars.push({ variable, name: param.name });
120
- });
121
-
122
- const statements: string[] = [];
123
- if (prelude.length) {
124
- statements.push(...prelude);
125
- }
126
- statements.push(`CALL ${qualifiedName}(${callArgs.join(', ')});`);
127
-
128
- if (outVars.length) {
129
- const selectOut = outVars
130
- .map(({ variable, name }) => `${variable} AS ${this.quoteIdentifier(name)}`)
131
- .join(', ');
132
- statements.push(`SELECT ${selectOut};`);
133
- }
134
-
135
- return {
136
- sql: statements.join(' '),
137
- params: [...ctx.params],
138
- outParams: {
139
- source: outVars.length ? 'lastResultSet' : 'none',
140
- names: outVars.map(item => item.name)
141
- }
142
- };
49
+ return this.procedureCompiler.compileProcedureCall(ast);
143
50
  }
144
51
  }
@@ -0,0 +1,67 @@
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type {
3
+ CompiledProcedureCall,
4
+ ProcedureCompiler,
5
+ ProcedureCompilerServices
6
+ } from '../capabilities/procedure-compiler.js';
7
+
8
+ const sanitizeVariableSuffix = (value: string): string =>
9
+ value.replace(/[^a-zA-Z0-9_]/g, '_');
10
+
11
+ export class MySqlProcedureCompiler implements ProcedureCompiler {
12
+ constructor(private readonly services: ProcedureCompilerServices) {}
13
+
14
+ compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
15
+ const ctx = this.services.createCompilerContext();
16
+ const qualifiedName = ast.ref.schema
17
+ ? `${this.services.quoteIdentifier(ast.ref.schema)}.${this.services.quoteIdentifier(ast.ref.name)}`
18
+ : this.services.quoteIdentifier(ast.ref.name);
19
+
20
+ const prelude: string[] = [];
21
+ const callArgs: string[] = [];
22
+ const outVars: Array<{ variable: string; name: string }> = [];
23
+
24
+ ast.params.forEach((param, index) => {
25
+ const suffix = sanitizeVariableSuffix(param.name || `p${index + 1}`);
26
+ const variable = `@__metal_${suffix}_${index + 1}`;
27
+
28
+ if (param.direction === 'in') {
29
+ if (!param.value) {
30
+ throw new Error(`Procedure parameter "${param.name}" requires a value for direction "in".`);
31
+ }
32
+ callArgs.push(this.services.compileOperand(param.value, ctx));
33
+ return;
34
+ }
35
+
36
+ if (param.direction === 'inout') {
37
+ if (!param.value) {
38
+ throw new Error(`Procedure parameter "${param.name}" requires a value for direction "inout".`);
39
+ }
40
+ prelude.push(`SET ${variable} = ${this.services.compileOperand(param.value, ctx)};`);
41
+ }
42
+
43
+ callArgs.push(variable);
44
+ outVars.push({ variable, name: param.name });
45
+ });
46
+
47
+ const statements: string[] = [];
48
+ if (prelude.length) statements.push(...prelude);
49
+ statements.push(`CALL ${qualifiedName}(${callArgs.join(', ')});`);
50
+
51
+ if (outVars.length) {
52
+ const selectOut = outVars
53
+ .map(({ variable, name }) => `${variable} AS ${this.services.quoteIdentifier(name)}`)
54
+ .join(', ');
55
+ statements.push(`SELECT ${selectOut};`);
56
+ }
57
+
58
+ return {
59
+ sql: statements.join(' '),
60
+ params: [...ctx.params],
61
+ outParams: {
62
+ source: outVars.length ? 'lastResultSet' : 'none',
63
+ names: outVars.map(item => item.name)
64
+ }
65
+ };
66
+ }
67
+ }
@@ -0,0 +1,42 @@
1
+ import type { CompilerContext } from '../abstract.js';
2
+ import type { InsertQueryNode } from '../../ast/query.js';
3
+ import type {
4
+ UpsertCompilationServices,
5
+ UpsertStrategy
6
+ } from '../base/upsert-strategy.js';
7
+
8
+ export class MySqlUpsertStrategy implements UpsertStrategy {
9
+ compile(
10
+ ast: InsertQueryNode,
11
+ ctx: CompilerContext,
12
+ services: UpsertCompilationServices
13
+ ): string {
14
+ if (!ast.onConflict) return '';
15
+
16
+ const clause = ast.onConflict;
17
+ if (clause.action.type === 'DoNothing') {
18
+ const noOpColumn = clause.target.columns[0] ?? ast.columns[0];
19
+ if (!noOpColumn) {
20
+ throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one target column.');
21
+ }
22
+ const col = services.quoteIdentifier(noOpColumn.name);
23
+ return ` ON DUPLICATE KEY UPDATE ${col} = ${col}`;
24
+ }
25
+
26
+ if (clause.action.where) {
27
+ throw new Error('MySQL ON DUPLICATE KEY UPDATE does not support a WHERE clause.');
28
+ }
29
+ if (!clause.action.set.length) {
30
+ throw new Error('MySQL ON DUPLICATE KEY UPDATE requires at least one assignment.');
31
+ }
32
+
33
+ const assignments = clause.action.set
34
+ .map(assignment => {
35
+ const target = services.quoteIdentifier(assignment.column.name);
36
+ const value = services.compileOperand(assignment.value, ctx);
37
+ return `${target} = ${value}`;
38
+ })
39
+ .join(', ');
40
+ return ` ON DUPLICATE KEY UPDATE ${assignments}`;
41
+ }
42
+ }
@@ -1,41 +1,48 @@
1
- import { CompilerContext } from '../abstract.js';
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type { BitwiseExpressionNode, ColumnNode, JsonPathNode } from '../../ast/expression.js';
3
+ import type { TableNode } from '../../ast/query.js';
2
4
  import type { CompiledProcedureCall, ProcedureCompiler } from '../capabilities/procedure-compiler.js';
3
- import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
4
- import { InsertQueryNode, TableNode } from '../../ast/query.js';
5
5
  import { SqlDialectBase } from '../base/sql-dialect.js';
6
6
  import { PostgresFunctionStrategy } from './functions.js';
7
7
  import { PostgresTableFunctionStrategy } from './table-functions.js';
8
- import { ProcedureCallNode } from '../../ast/procedure.js';
8
+ import { PostgresProcedureCompiler } from './procedure-compiler.js';
9
+ import { PostgresReturningStrategy } from './returning.js';
10
+ import { PostgresUpsertStrategy } from './upsert.js';
9
11
 
10
- /**
11
- * PostgreSQL dialect implementation
12
- */
12
+ /** PostgreSQL dialect assembled from reusable compiler components. */
13
13
  export class PostgresDialect extends SqlDialectBase implements ProcedureCompiler {
14
14
  protected readonly dialect = 'postgres';
15
- /**
16
- * Creates a new PostgresDialect instance
17
- */
15
+ private readonly procedureCompiler: PostgresProcedureCompiler;
16
+
18
17
  public constructor() {
19
- super(new PostgresFunctionStrategy(), new PostgresTableFunctionStrategy());
18
+ super({
19
+ functionStrategy: new PostgresFunctionStrategy(),
20
+ tableFunctionStrategy: new PostgresTableFunctionStrategy(),
21
+ returningStrategy: new PostgresReturningStrategy(),
22
+ upsertStrategy: new PostgresUpsertStrategy(),
23
+ supportsDmlReturning: true
24
+ });
25
+
26
+ this.procedureCompiler = new PostgresProcedureCompiler({
27
+ quoteIdentifier: id => this.quoteIdentifier(id),
28
+ createCompilerContext: () => this.createCompilerContext(),
29
+ compileOperand: (node, ctx) => this.compileOperand(node, ctx)
30
+ });
31
+
20
32
  this.registerExpressionCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
21
33
  const left = this.compileOperand(node.left, ctx);
22
34
  const right = this.compileOperand(node.right, ctx);
23
- const op = node.operator === '^' ? '#' : node.operator;
24
- return `${left} ${op} ${right}`;
35
+ const operator = node.operator === '^' ? '#' : node.operator;
36
+ return `${left} ${operator} ${right}`;
25
37
  });
26
38
  this.registerOperandCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
27
39
  const left = this.compileOperand(node.left, ctx);
28
40
  const right = this.compileOperand(node.right, ctx);
29
- const op = node.operator === '^' ? '#' : node.operator;
30
- return `(${left} ${op} ${right})`;
41
+ const operator = node.operator === '^' ? '#' : node.operator;
42
+ return `(${left} ${operator} ${right})`;
31
43
  });
32
44
  }
33
45
 
34
- /**
35
- * Quotes an identifier using PostgreSQL double-quote syntax
36
- * @param id - Identifier to quote
37
- * @returns Quoted identifier
38
- */
39
46
  quoteIdentifier(id: string): string {
40
47
  return `"${id}"`;
41
48
  }
@@ -44,92 +51,18 @@ export class PostgresDialect extends SqlDialectBase implements ProcedureCompiler
44
51
  return `$${index}`;
45
52
  }
46
53
 
47
- /**
48
- * Compiles JSON path expression using PostgreSQL syntax
49
- * @param node - JSON path node
50
- * @returns PostgreSQL JSON path expression
51
- */
52
54
  protected compileJsonPath(node: JsonPathNode): string {
53
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
54
- // Postgres uses col->>'path' for text extraction
55
- return `${col}->>'${node.path}'`;
56
- }
57
-
58
- protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
59
- void ctx;
60
- if (!returning || returning.length === 0) return '';
61
- const columns = this.formatReturningColumns(returning);
62
- return ` RETURNING ${columns}`;
63
- }
64
-
65
- protected compileUpsertClause(ast: InsertQueryNode, ctx: CompilerContext): string {
66
- if (!ast.onConflict) return '';
67
-
68
- const clause = ast.onConflict;
69
- const target = clause.target.constraint
70
- ? ` ON CONFLICT ON CONSTRAINT ${this.quoteIdentifier(clause.target.constraint)}`
71
- : (() => {
72
- this.ensureConflictColumns(
73
- clause,
74
- 'PostgreSQL ON CONFLICT requires conflict columns or a constraint name.'
75
- );
76
- const cols = clause.target.columns.map(col => this.quoteIdentifier(col.name)).join(', ');
77
- return ` ON CONFLICT (${cols})`;
78
- })();
79
-
80
- if (clause.action.type === 'DoNothing') {
81
- return `${target} DO NOTHING`;
82
- }
83
-
84
- if (!clause.action.set.length) {
85
- throw new Error('PostgreSQL ON CONFLICT DO UPDATE requires at least one assignment.');
86
- }
87
-
88
- const assignments = this.compileUpdateAssignments(clause.action.set, ast.into, ctx);
89
- const where = clause.action.where
90
- ? ` WHERE ${this.compileExpression(clause.action.where, ctx)}`
91
- : '';
92
- return `${target} DO UPDATE SET ${assignments}${where}`;
55
+ const column = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
56
+ return `${column}->>'${node.path}'`;
93
57
  }
94
58
 
95
- supportsDmlReturningClause(): boolean {
96
- return true;
59
+ /** PostgreSQL requires unqualified column names in SET clauses. */
60
+ protected compileSetTarget(column: ColumnNode, _table: TableNode): string {
61
+ void _table;
62
+ return this.quoteIdentifier(column.name);
97
63
  }
98
64
 
99
65
  compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
100
- const ctx = this.createCompilerContext();
101
- const qualifiedName = ast.ref.schema
102
- ? `${this.quoteIdentifier(ast.ref.schema)}.${this.quoteIdentifier(ast.ref.name)}`
103
- : this.quoteIdentifier(ast.ref.name);
104
-
105
- const args: string[] = [];
106
- for (const param of ast.params) {
107
- if (param.direction === 'out') continue;
108
- if (!param.value) {
109
- throw new Error(`Procedure parameter "${param.name}" requires a value for direction "${param.direction}".`);
110
- }
111
- args.push(this.compileOperand(param.value, ctx));
112
- }
113
-
114
- const outNames = ast.params
115
- .filter(param => param.direction === 'out' || param.direction === 'inout')
116
- .map(param => param.name);
117
-
118
- const rawSql = `CALL ${qualifiedName}(${args.join(', ')})`;
119
- return {
120
- sql: `${rawSql};`,
121
- params: [...ctx.params],
122
- outParams: {
123
- source: outNames.length ? 'firstResultSet' : 'none',
124
- names: outNames
125
- }
126
- };
127
- }
128
-
129
- /**
130
- * PostgreSQL requires unqualified column names in SET clause
131
- */
132
- protected compileSetTarget(column: ColumnNode, _table: TableNode): string {
133
- return this.quoteIdentifier(column.name);
66
+ return this.procedureCompiler.compileProcedureCall(ast);
134
67
  }
135
68
  }
@@ -0,0 +1,41 @@
1
+ import type { ProcedureCallNode } from '../../ast/procedure.js';
2
+ import type {
3
+ CompiledProcedureCall,
4
+ ProcedureCompiler,
5
+ ProcedureCompilerServices
6
+ } from '../capabilities/procedure-compiler.js';
7
+
8
+ export class PostgresProcedureCompiler implements ProcedureCompiler {
9
+ constructor(private readonly services: ProcedureCompilerServices) {}
10
+
11
+ compileProcedureCall(ast: ProcedureCallNode): CompiledProcedureCall {
12
+ const ctx = this.services.createCompilerContext();
13
+ const qualifiedName = ast.ref.schema
14
+ ? `${this.services.quoteIdentifier(ast.ref.schema)}.${this.services.quoteIdentifier(ast.ref.name)}`
15
+ : this.services.quoteIdentifier(ast.ref.name);
16
+
17
+ const args: string[] = [];
18
+ for (const param of ast.params) {
19
+ if (param.direction === 'out') continue;
20
+ if (!param.value) {
21
+ throw new Error(
22
+ `Procedure parameter "${param.name}" requires a value for direction "${param.direction}".`
23
+ );
24
+ }
25
+ args.push(this.services.compileOperand(param.value, ctx));
26
+ }
27
+
28
+ const outNames = ast.params
29
+ .filter(param => param.direction === 'out' || param.direction === 'inout')
30
+ .map(param => param.name);
31
+
32
+ return {
33
+ sql: `CALL ${qualifiedName}(${args.join(', ')});`,
34
+ params: [...ctx.params],
35
+ outParams: {
36
+ source: outNames.length ? 'firstResultSet' : 'none',
37
+ names: outNames
38
+ }
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,4 @@
1
+ import { StandardReturningStrategy } from '../base/returning-strategy.js';
2
+
3
+ /** PostgreSQL uses standard SQL RETURNING with qualified columns. */
4
+ export class PostgresReturningStrategy extends StandardReturningStrategy {}