metal-orm 1.0.4 → 1.0.5

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,6 +1,6 @@
1
1
  import { TableDef } from '../schema/table';
2
2
  import { ColumnDef } from '../schema/column';
3
- import { RelationDef } from '../schema/relation';
3
+ import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation';
4
4
  import { SelectQueryNode } from '../ast/query';
5
5
  import {
6
6
  ColumnNode,
@@ -13,11 +13,16 @@ import { QueryAstService } from './query-ast-service';
13
13
  import { findPrimaryKey } from './hydration-planner';
14
14
  import { RelationProjectionHelper } from './relation-projection-helper';
15
15
  import type { RelationResult } from './relation-projection-helper';
16
- import { buildRelationJoinCondition, buildRelationCorrelation } from './relation-conditions';
16
+ import {
17
+ buildRelationJoinCondition,
18
+ buildRelationCorrelation,
19
+ buildBelongsToManyJoins
20
+ } from './relation-conditions';
17
21
  import { JoinKind, JOIN_KINDS } from '../constants/sql';
18
- import { RelationIncludeJoinKind } from './relation-types';
22
+ import { RelationIncludeOptions } from './relation-types';
19
23
  import { createJoinNode } from '../utils/join-node';
20
24
  import { makeRelationAlias } from '../utils/relation-alias';
25
+ import { buildDefaultPivotColumns } from './relation-utils';
21
26
 
22
27
  /**
23
28
  * Service for handling relation operations (joins, includes, etc.)
@@ -82,10 +87,7 @@ export class RelationService {
82
87
  * @param options - Options for relation inclusion
83
88
  * @returns Relation result with updated state and hydration
84
89
  */
85
- include(
86
- relationName: string,
87
- options?: { columns?: string[]; aliasPrefix?: string; filter?: ExpressionNode; joinKind?: RelationIncludeJoinKind }
88
- ): RelationResult {
90
+ include(relationName: string, options?: RelationIncludeOptions): RelationResult {
89
91
  let state = this.state;
90
92
  let hydration = this.hydration;
91
93
 
@@ -106,16 +108,66 @@ export class RelationService {
106
108
  ? options.columns
107
109
  : Object.keys(relation.target.columns);
108
110
 
109
- const relationSelection = targetColumns.reduce((acc, key) => {
110
- const def = (relation.target.columns as any)[key];
111
- if (!def) {
112
- throw new Error(`Column '${key}' not found on relation '${relationName}'`);
113
- }
114
- acc[makeRelationAlias(aliasPrefix, key)] = def;
115
- return acc;
116
- }, {} as Record<string, ColumnDef>);
111
+ const buildTypedSelection = (
112
+ columns: Record<string, ColumnDef>,
113
+ prefix: string,
114
+ keys: string[],
115
+ missingMsg: (col: string) => string
116
+ ) : Record<string, ColumnDef> => {
117
+ return keys.reduce((acc, key) => {
118
+ const def = columns[key];
119
+ if (!def) {
120
+ throw new Error(missingMsg(key));
121
+ }
122
+ acc[makeRelationAlias(prefix, key)] = def;
123
+ return acc;
124
+ }, {} as Record<string, ColumnDef>);
125
+ };
126
+
127
+ const targetSelection = buildTypedSelection(
128
+ relation.target.columns as Record<string, ColumnDef>,
129
+ aliasPrefix,
130
+ targetColumns,
131
+ key => `Column '${key}' not found on relation '${relationName}'`
132
+ );
133
+
134
+ if (relation.type !== RelationKinds.BelongsToMany) {
135
+ const relationSelectionResult = this.selectColumns(state, hydration, targetSelection);
136
+ state = relationSelectionResult.state;
137
+ hydration = relationSelectionResult.hydration;
117
138
 
118
- const relationSelectionResult = this.selectColumns(state, hydration, relationSelection);
139
+ hydration = hydration.onRelationIncluded(
140
+ state,
141
+ relation,
142
+ relationName,
143
+ aliasPrefix,
144
+ targetColumns
145
+ );
146
+
147
+ return { state, hydration };
148
+ }
149
+
150
+ const many = relation as BelongsToManyRelation;
151
+ const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
152
+ const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
153
+ const pivotColumns =
154
+ options?.pivot?.columns ??
155
+ many.defaultPivotColumns ??
156
+ buildDefaultPivotColumns(many, pivotPk);
157
+
158
+ const pivotSelection = buildTypedSelection(
159
+ many.pivotTable.columns as Record<string, ColumnDef>,
160
+ pivotAliasPrefix,
161
+ pivotColumns,
162
+ key => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
163
+ );
164
+
165
+ const combinedSelection = {
166
+ ...targetSelection,
167
+ ...pivotSelection
168
+ };
169
+
170
+ const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
119
171
  state = relationSelectionResult.state;
120
172
  hydration = relationSelectionResult.hydration;
121
173
 
@@ -124,7 +176,8 @@ export class RelationService {
124
176
  relation,
125
177
  relationName,
126
178
  aliasPrefix,
127
- targetColumns
179
+ targetColumns,
180
+ { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
128
181
  );
129
182
 
130
183
  return { state, hydration };
@@ -167,8 +220,18 @@ export class RelationService {
167
220
  extraCondition?: ExpressionNode
168
221
  ): SelectQueryState {
169
222
  const relation = this.getRelation(relationName);
170
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
223
+ if (relation.type === RelationKinds.BelongsToMany) {
224
+ const joins = buildBelongsToManyJoins(
225
+ this.table,
226
+ relationName,
227
+ relation as BelongsToManyRelation,
228
+ joinKind,
229
+ extraCondition
230
+ );
231
+ return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
232
+ }
171
233
 
234
+ const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
172
235
  const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
173
236
 
174
237
  return this.astService(state).withJoin(joinNode);
@@ -1,6 +1,21 @@
1
+ import { ExpressionNode } from '../ast/expression';
1
2
  import { JOIN_KINDS } from '../constants/sql';
2
3
 
3
4
  /**
4
5
  * Join kinds allowed when including a relation using `.include(...)`.
5
6
  */
6
7
  export type RelationIncludeJoinKind = typeof JOIN_KINDS.LEFT | typeof JOIN_KINDS.INNER;
8
+
9
+ /**
10
+ * Options for including a relation in a query
11
+ */
12
+ export interface RelationIncludeOptions {
13
+ columns?: string[];
14
+ aliasPrefix?: string;
15
+ filter?: ExpressionNode;
16
+ joinKind?: RelationIncludeJoinKind;
17
+ pivot?: {
18
+ columns?: string[];
19
+ aliasPrefix?: string;
20
+ };
21
+ }
@@ -0,0 +1,12 @@
1
+ import { BelongsToManyRelation } from '../schema/relation';
2
+
3
+ /**
4
+ * Builds a default set of pivot columns, excluding keys used for joins.
5
+ */
6
+ export const buildDefaultPivotColumns = (
7
+ rel: BelongsToManyRelation,
8
+ pivotPk: string
9
+ ): string[] => {
10
+ const excluded = new Set([pivotPk, rel.pivotForeignKeyToRoot, rel.pivotForeignKeyToTarget]);
11
+ return Object.keys(rel.pivotTable.columns).filter(col => !excluded.has(col));
12
+ };
@@ -26,7 +26,8 @@ import { CteManager } from './operations/cte-manager';
26
26
  import { JoinManager } from './operations/join-manager';
27
27
  import { FilterManager } from './operations/filter-manager';
28
28
  import { PaginationManager } from './operations/pagination-manager';
29
- import { RelationManager, RelationIncludeOptions } from './operations/relation-manager';
29
+ import { RelationManager } from './operations/relation-manager';
30
+ import { RelationIncludeOptions } from './relation-types';
30
31
  import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../constants/sql';
31
32
 
32
33
  /**
@@ -0,0 +1,59 @@
1
+ import { TableDef } from '../schema/table';
2
+ import { ColumnNode, ExpressionNode, valueToOperand } from '../ast/expression';
3
+ import { TableNode, UpdateQueryNode, UpdateAssignmentNode } from '../ast/query';
4
+
5
+ const createTableNode = (table: TableDef): TableNode => ({
6
+ type: 'Table',
7
+ name: table.name
8
+ });
9
+
10
+ /**
11
+ * Immutable state for UPDATE queries
12
+ */
13
+ export class UpdateQueryState {
14
+ public readonly table: TableDef;
15
+ public readonly ast: UpdateQueryNode;
16
+
17
+ constructor(table: TableDef, ast?: UpdateQueryNode) {
18
+ this.table = table;
19
+ this.ast = ast ?? {
20
+ type: 'UpdateQuery',
21
+ table: createTableNode(table),
22
+ set: []
23
+ };
24
+ }
25
+
26
+ private clone(nextAst: UpdateQueryNode): UpdateQueryState {
27
+ return new UpdateQueryState(this.table, nextAst);
28
+ }
29
+
30
+ withSet(values: Record<string, unknown>): UpdateQueryState {
31
+ const assignments: UpdateAssignmentNode[] = Object.entries(values).map(([column, value]) => ({
32
+ column: {
33
+ type: 'Column',
34
+ table: this.table.name,
35
+ name: column
36
+ },
37
+ value: valueToOperand(value)
38
+ }));
39
+
40
+ return this.clone({
41
+ ...this.ast,
42
+ set: assignments
43
+ });
44
+ }
45
+
46
+ withWhere(expr: ExpressionNode): UpdateQueryState {
47
+ return this.clone({
48
+ ...this.ast,
49
+ where: expr
50
+ });
51
+ }
52
+
53
+ withReturning(columns: ColumnNode[]): UpdateQueryState {
54
+ return this.clone({
55
+ ...this.ast,
56
+ returning: [...columns]
57
+ });
58
+ }
59
+ }
@@ -0,0 +1,61 @@
1
+ import { TableDef } from '../schema/table';
2
+ import { ColumnDef } from '../schema/column';
3
+ import { ColumnNode, ExpressionNode } from '../ast/expression';
4
+ import { CompiledQuery, UpdateCompiler } from '../dialect/abstract';
5
+ import { UpdateQueryNode } from '../ast/query';
6
+ import { UpdateQueryState } from './update-query-state';
7
+
8
+ const buildColumnNode = (table: TableDef, column: ColumnDef | ColumnNode): ColumnNode => {
9
+ if ((column as ColumnNode).type === 'Column') {
10
+ return column as ColumnNode;
11
+ }
12
+ const def = column as ColumnDef;
13
+ return {
14
+ type: 'Column',
15
+ table: def.table || table.name,
16
+ name: def.name
17
+ };
18
+ };
19
+
20
+ /**
21
+ * Builder for UPDATE queries
22
+ */
23
+ export class UpdateQueryBuilder<T> {
24
+ private readonly table: TableDef;
25
+ private readonly state: UpdateQueryState;
26
+
27
+ constructor(table: TableDef, state?: UpdateQueryState) {
28
+ this.table = table;
29
+ this.state = state ?? new UpdateQueryState(table);
30
+ }
31
+
32
+ private clone(state: UpdateQueryState): UpdateQueryBuilder<T> {
33
+ return new UpdateQueryBuilder(this.table, state);
34
+ }
35
+
36
+ set(values: Record<string, unknown>): UpdateQueryBuilder<T> {
37
+ return this.clone(this.state.withSet(values));
38
+ }
39
+
40
+ where(expr: ExpressionNode): UpdateQueryBuilder<T> {
41
+ return this.clone(this.state.withWhere(expr));
42
+ }
43
+
44
+ returning(...columns: (ColumnDef | ColumnNode)[]): UpdateQueryBuilder<T> {
45
+ if (!columns.length) return this;
46
+ const nodes = columns.map(column => buildColumnNode(this.table, column));
47
+ return this.clone(this.state.withReturning(nodes));
48
+ }
49
+
50
+ compile(compiler: UpdateCompiler): CompiledQuery {
51
+ return compiler.compileUpdate(this.state.ast);
52
+ }
53
+
54
+ toSql(compiler: UpdateCompiler): string {
55
+ return this.compile(compiler).sql;
56
+ }
57
+
58
+ getAST(): UpdateQueryNode {
59
+ return this.state.ast;
60
+ }
61
+ }
@@ -1,4 +1,4 @@
1
- import { SelectQueryNode } from '../ast/query';
1
+ import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../ast/query';
2
2
  import {
3
3
  ExpressionNode,
4
4
  BinaryExpressionNode,
@@ -30,39 +30,91 @@ export interface CompilerContext {
30
30
  /**
31
31
  * Result of SQL compilation
32
32
  */
33
- export interface CompiledQuery {
34
- /** Generated SQL string */
35
- sql: string;
36
- /** Parameters for the query */
37
- params: unknown[];
38
- }
39
-
40
- /**
41
- * Abstract base class for SQL dialect implementations
42
- */
43
- export abstract class Dialect {
33
+ export interface CompiledQuery {
34
+ /** Generated SQL string */
35
+ sql: string;
36
+ /** Parameters for the query */
37
+ params: unknown[];
38
+ }
39
+
40
+ export interface SelectCompiler {
41
+ compileSelect(ast: SelectQueryNode): CompiledQuery;
42
+ }
43
+
44
+ export interface InsertCompiler {
45
+ compileInsert(ast: InsertQueryNode): CompiledQuery;
46
+ }
47
+
48
+ export interface UpdateCompiler {
49
+ compileUpdate(ast: UpdateQueryNode): CompiledQuery;
50
+ }
51
+
52
+ export interface DeleteCompiler {
53
+ compileDelete(ast: DeleteQueryNode): CompiledQuery;
54
+ }
55
+
56
+ /**
57
+ * Abstract base class for SQL dialect implementations
58
+ */
59
+ export abstract class Dialect
60
+ implements SelectCompiler, InsertCompiler, UpdateCompiler, DeleteCompiler
61
+ {
44
62
  /**
45
63
  * Compiles a SELECT query AST to SQL
46
64
  * @param ast - Query AST to compile
47
65
  * @returns Compiled query with SQL and parameters
48
66
  */
49
- compileSelect(ast: SelectQueryNode): CompiledQuery {
50
- const ctx = this.createCompilerContext();
51
- const rawSql = this.compileSelectAst(ast, ctx).trim();
52
- const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
53
- return {
54
- sql,
55
- params: [...ctx.params]
56
- };
57
- }
58
-
59
- /**
60
- * Compiles SELECT query AST to SQL (to be implemented by concrete dialects)
61
- * @param ast - Query AST
62
- * @param ctx - Compiler context
63
- * @returns SQL string
64
- */
65
- protected abstract compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
67
+ compileSelect(ast: SelectQueryNode): CompiledQuery {
68
+ const ctx = this.createCompilerContext();
69
+ const rawSql = this.compileSelectAst(ast, ctx).trim();
70
+ const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
71
+ return {
72
+ sql,
73
+ params: [...ctx.params]
74
+ };
75
+ }
76
+
77
+ compileInsert(ast: InsertQueryNode): CompiledQuery {
78
+ const ctx = this.createCompilerContext();
79
+ const rawSql = this.compileInsertAst(ast, ctx).trim();
80
+ const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
81
+ return {
82
+ sql,
83
+ params: [...ctx.params]
84
+ };
85
+ }
86
+
87
+ compileUpdate(ast: UpdateQueryNode): CompiledQuery {
88
+ const ctx = this.createCompilerContext();
89
+ const rawSql = this.compileUpdateAst(ast, ctx).trim();
90
+ const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
91
+ return {
92
+ sql,
93
+ params: [...ctx.params]
94
+ };
95
+ }
96
+
97
+ compileDelete(ast: DeleteQueryNode): CompiledQuery {
98
+ const ctx = this.createCompilerContext();
99
+ const rawSql = this.compileDeleteAst(ast, ctx).trim();
100
+ const sql = rawSql.endsWith(';') ? rawSql : `${rawSql};`;
101
+ return {
102
+ sql,
103
+ params: [...ctx.params]
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Compiles SELECT query AST to SQL (to be implemented by concrete dialects)
109
+ * @param ast - Query AST
110
+ * @param ctx - Compiler context
111
+ * @returns SQL string
112
+ */
113
+ protected abstract compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string;
114
+
115
+ protected abstract compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string;
116
+ protected abstract compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string;
117
+ protected abstract compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string;
66
118
 
67
119
  /**
68
120
  * Quotes an SQL identifier (to be implemented by concrete dialects)
@@ -77,10 +129,18 @@ export abstract class Dialect {
77
129
  * @param ctx - Compiler context
78
130
  * @returns SQL WHERE clause or empty string
79
131
  */
80
- protected compileWhere(where: ExpressionNode | undefined, ctx: CompilerContext): string {
81
- if (!where) return '';
82
- return ` WHERE ${this.compileExpression(where, ctx)}`;
83
- }
132
+ protected compileWhere(where: ExpressionNode | undefined, ctx: CompilerContext): string {
133
+ if (!where) return '';
134
+ return ` WHERE ${this.compileExpression(where, ctx)}`;
135
+ }
136
+
137
+ protected compileReturning(
138
+ returning: ColumnNode[] | undefined,
139
+ ctx: CompilerContext
140
+ ): string {
141
+ if (!returning || returning.length === 0) return '';
142
+ throw new Error('RETURNING is not supported by this dialect.');
143
+ }
84
144
 
85
145
  /**
86
146
  * Generates subquery for EXISTS expressions
@@ -163,13 +223,13 @@ export abstract class Dialect {
163
223
  * @param ctx - Compiler context
164
224
  * @returns Compiled SQL expression
165
225
  */
166
- protected compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
167
- const compiler = this.expressionCompilers.get(node.type);
168
- if (!compiler) {
169
- throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
170
- }
171
- return compiler(node, ctx);
172
- }
226
+ protected compileExpression(node: ExpressionNode, ctx: CompilerContext): string {
227
+ const compiler = this.expressionCompilers.get(node.type);
228
+ if (!compiler) {
229
+ throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
230
+ }
231
+ return compiler(node, ctx);
232
+ }
173
233
 
174
234
  /**
175
235
  * Compiles an operand node
@@ -177,13 +237,13 @@ export abstract class Dialect {
177
237
  * @param ctx - Compiler context
178
238
  * @returns Compiled SQL operand
179
239
  */
180
- protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
181
- const compiler = this.operandCompilers.get(node.type);
182
- if (!compiler) {
183
- throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
184
- }
185
- return compiler(node, ctx);
186
- }
240
+ protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
241
+ const compiler = this.operandCompilers.get(node.type);
242
+ if (!compiler) {
243
+ throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
244
+ }
245
+ return compiler(node, ctx);
246
+ }
187
247
 
188
248
  private registerDefaultExpressionCompilers(): void {
189
249
  this.registerExpressionCompiler('BinaryExpression', (binary: BinaryExpressionNode, ctx) => {
@@ -1,6 +1,6 @@
1
- import { CompilerContext, Dialect } from '../abstract';
2
- import { SelectQueryNode } from '../../ast/query';
3
- import { JsonPathNode } from '../../ast/expression';
1
+ import { CompilerContext, Dialect } from '../abstract';
2
+ import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query';
3
+ import { JsonPathNode } from '../../ast/expression';
4
4
 
5
5
  /**
6
6
  * Microsoft SQL Server dialect implementation
@@ -48,7 +48,7 @@ export class SqlServerDialect extends Dialect {
48
48
  * @param ctx - Compiler context
49
49
  * @returns SQL Server SQL string
50
50
  */
51
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
51
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
52
52
  const columns = ast.columns.map(c => {
53
53
  let expr = '';
54
54
  if (c.type === 'Function') {
@@ -111,6 +111,31 @@ export class SqlServerDialect extends Dialect {
111
111
  }).join(', ') + ' '
112
112
  : '';
113
113
 
114
- return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy};`;
115
- }
114
+ return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy};`;
115
+ }
116
+
117
+ protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
118
+ const table = this.quoteIdentifier(ast.into.name);
119
+ const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
120
+ const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
121
+ return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
122
+ }
123
+
124
+ protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
125
+ const table = this.quoteIdentifier(ast.table.name);
126
+ const assignments = ast.set.map(assignment => {
127
+ const col = assignment.column;
128
+ const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
129
+ const value = this.compileOperand(assignment.value, ctx);
130
+ return `${target} = ${value}`;
131
+ }).join(', ');
132
+ const whereClause = this.compileWhere(ast.where, ctx);
133
+ return `UPDATE ${table} SET ${assignments}${whereClause};`;
134
+ }
135
+
136
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
137
+ const table = this.quoteIdentifier(ast.from.name);
138
+ const whereClause = this.compileWhere(ast.where, ctx);
139
+ return `DELETE FROM ${table}${whereClause};`;
140
+ }
116
141
  }
@@ -1,6 +1,6 @@
1
- import { CompilerContext, Dialect } from '../abstract';
2
- import { SelectQueryNode } from '../../ast/query';
3
- import { JsonPathNode } from '../../ast/expression';
1
+ import { CompilerContext, Dialect } from '../abstract';
2
+ import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query';
3
+ import { JsonPathNode } from '../../ast/expression';
4
4
 
5
5
  /**
6
6
  * MySQL dialect implementation
@@ -39,7 +39,7 @@ export class MySqlDialect extends Dialect {
39
39
  * @param ctx - Compiler context
40
40
  * @returns MySQL SQL string
41
41
  */
42
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
42
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
43
43
  const columns = ast.columns.map(c => {
44
44
  let expr = '';
45
45
  if (c.type === 'Function') {
@@ -98,6 +98,31 @@ export class MySqlDialect extends Dialect {
98
98
  })()
99
99
  : '';
100
100
 
101
- return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
102
- }
101
+ return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
102
+ }
103
+
104
+ protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
105
+ const table = this.quoteIdentifier(ast.into.name);
106
+ const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
107
+ const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
108
+ return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
109
+ }
110
+
111
+ protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
112
+ const table = this.quoteIdentifier(ast.table.name);
113
+ const assignments = ast.set.map(assignment => {
114
+ const col = assignment.column;
115
+ const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
116
+ const value = this.compileOperand(assignment.value, ctx);
117
+ return `${target} = ${value}`;
118
+ }).join(', ');
119
+ const whereClause = this.compileWhere(ast.where, ctx);
120
+ return `UPDATE ${table} SET ${assignments}${whereClause};`;
121
+ }
122
+
123
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
124
+ const table = this.quoteIdentifier(ast.from.name);
125
+ const whereClause = this.compileWhere(ast.where, ctx);
126
+ return `DELETE FROM ${table}${whereClause};`;
127
+ }
103
128
  }
@@ -1,6 +1,6 @@
1
- import { CompilerContext, Dialect } from '../abstract';
2
- import { SelectQueryNode } from '../../ast/query';
3
- import { JsonPathNode } from '../../ast/expression';
1
+ import { CompilerContext, Dialect } from '../abstract';
2
+ import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query';
3
+ import { JsonPathNode, ColumnNode } from '../../ast/expression';
4
4
 
5
5
  /**
6
6
  * PostgreSQL dialect implementation
@@ -39,7 +39,7 @@ export class PostgresDialect extends Dialect {
39
39
  * @param ctx - Compiler context
40
40
  * @returns PostgreSQL SQL string
41
41
  */
42
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
42
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
43
43
  const columns = ast.columns.map(c => {
44
44
  let expr = '';
45
45
  if (c.type === 'Function') {
@@ -98,6 +98,45 @@ export class PostgresDialect extends Dialect {
98
98
  })()
99
99
  : '';
100
100
 
101
- return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
102
- }
101
+ return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
102
+ }
103
+
104
+ protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
105
+ const table = this.quoteIdentifier(ast.into.name);
106
+ const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
107
+ const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
108
+ const returning = this.compileReturning(ast.returning, ctx);
109
+ return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning};`;
110
+ }
111
+
112
+ protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
113
+ const table = this.quoteIdentifier(ast.table.name);
114
+ const assignments = ast.set.map(assignment => {
115
+ const col = assignment.column;
116
+ const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
117
+ const value = this.compileOperand(assignment.value, ctx);
118
+ return `${target} = ${value}`;
119
+ }).join(', ');
120
+ const whereClause = this.compileWhere(ast.where, ctx);
121
+ const returning = this.compileReturning(ast.returning, ctx);
122
+ return `UPDATE ${table} SET ${assignments}${whereClause}${returning};`;
123
+ }
124
+
125
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
126
+ const table = this.quoteIdentifier(ast.from.name);
127
+ const whereClause = this.compileWhere(ast.where, ctx);
128
+ const returning = this.compileReturning(ast.returning, ctx);
129
+ return `DELETE FROM ${table}${whereClause}${returning};`;
130
+ }
131
+
132
+ protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
133
+ if (!returning || returning.length === 0) return '';
134
+ const columns = returning
135
+ .map(column => {
136
+ const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : '';
137
+ return `${tablePart}${this.quoteIdentifier(column.name)}`;
138
+ })
139
+ .join(', ');
140
+ return ` RETURNING ${columns}`;
141
+ }
103
142
  }