metal-orm 1.0.11 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/decorators/index.cjs +15 -2
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +15 -2
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +1394 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +257 -23
- package/dist/index.d.ts +257 -23
- package/dist/index.js +1376 -149
- package/dist/index.js.map +1 -1
- package/dist/{select-654m4qy8.d.cts → select-BKlr2ivY.d.cts} +141 -4
- package/dist/{select-654m4qy8.d.ts → select-BKlr2ivY.d.ts} +141 -4
- package/package.json +1 -1
- package/src/core/ddl/dialects/base-schema-dialect.ts +48 -0
- package/src/core/ddl/dialects/index.ts +5 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +97 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +109 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +99 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +103 -0
- package/src/core/ddl/introspect/mssql.ts +149 -0
- package/src/core/ddl/introspect/mysql.ts +99 -0
- package/src/core/ddl/introspect/postgres.ts +154 -0
- package/src/core/ddl/introspect/sqlite.ts +66 -0
- package/src/core/ddl/introspect/types.ts +19 -0
- package/src/core/ddl/introspect/utils.ts +27 -0
- package/src/core/ddl/schema-diff.ts +179 -0
- package/src/core/ddl/schema-generator.ts +229 -0
- package/src/core/ddl/schema-introspect.ts +32 -0
- package/src/core/ddl/schema-types.ts +39 -0
- package/src/core/dialect/base/sql-dialect.ts +161 -0
- package/src/core/dialect/mysql/index.ts +18 -112
- package/src/core/dialect/postgres/index.ts +30 -126
- package/src/core/dialect/sqlite/index.ts +29 -129
- package/src/index.ts +4 -0
- package/src/schema/column.ts +206 -27
- package/src/schema/table.ts +89 -32
- package/src/schema/types.ts +8 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DialectName } from './schema-generator.js';
|
|
2
|
+
import { DatabaseSchema } from './schema-types.js';
|
|
3
|
+
import { DbExecutor } from '../../orm/db-executor.js';
|
|
4
|
+
import type { IntrospectOptions, SchemaIntrospector } from './introspect/types.js';
|
|
5
|
+
import { postgresIntrospector } from './introspect/postgres.js';
|
|
6
|
+
import { mysqlIntrospector } from './introspect/mysql.js';
|
|
7
|
+
import { sqliteIntrospector } from './introspect/sqlite.js';
|
|
8
|
+
import { mssqlIntrospector } from './introspect/mssql.js';
|
|
9
|
+
|
|
10
|
+
const INTROSPECTORS: Record<DialectName, SchemaIntrospector> = {
|
|
11
|
+
postgres: postgresIntrospector,
|
|
12
|
+
mysql: mysqlIntrospector,
|
|
13
|
+
sqlite: sqliteIntrospector,
|
|
14
|
+
mssql: mssqlIntrospector
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Introspects an existing database schema using the dialect-specific strategy.
|
|
19
|
+
*/
|
|
20
|
+
export const introspectSchema = async (
|
|
21
|
+
executor: DbExecutor,
|
|
22
|
+
dialect: DialectName,
|
|
23
|
+
options: IntrospectOptions = {}
|
|
24
|
+
): Promise<DatabaseSchema> => {
|
|
25
|
+
const handler = INTROSPECTORS[dialect];
|
|
26
|
+
if (!handler) {
|
|
27
|
+
throw new Error(`Unsupported dialect for introspection: ${dialect}`);
|
|
28
|
+
}
|
|
29
|
+
return handler.introspect(executor, options);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type { IntrospectOptions, SchemaIntrospector };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ForeignKeyReference } from '../../schema/column.js';
|
|
2
|
+
import { IndexColumn } from '../../schema/table.js';
|
|
3
|
+
|
|
4
|
+
export interface DatabaseColumn {
|
|
5
|
+
name: string;
|
|
6
|
+
type: string;
|
|
7
|
+
notNull?: boolean;
|
|
8
|
+
default?: unknown;
|
|
9
|
+
autoIncrement?: boolean;
|
|
10
|
+
generated?: 'always' | 'byDefault';
|
|
11
|
+
unique?: boolean | string;
|
|
12
|
+
references?: ForeignKeyReference;
|
|
13
|
+
check?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DatabaseIndex {
|
|
17
|
+
name: string;
|
|
18
|
+
columns: IndexColumn[];
|
|
19
|
+
unique?: boolean;
|
|
20
|
+
where?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DatabaseCheck {
|
|
24
|
+
name?: string;
|
|
25
|
+
expression: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DatabaseTable {
|
|
29
|
+
name: string;
|
|
30
|
+
schema?: string;
|
|
31
|
+
columns: DatabaseColumn[];
|
|
32
|
+
primaryKey?: string[];
|
|
33
|
+
indexes?: DatabaseIndex[];
|
|
34
|
+
checks?: DatabaseCheck[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DatabaseSchema {
|
|
38
|
+
tables: DatabaseTable[];
|
|
39
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { CompilerContext, Dialect } from '../abstract.js';
|
|
2
|
+
import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
|
|
3
|
+
import { ColumnNode } from '../../ast/expression.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared SQL compiler for dialects with standard LIMIT/OFFSET pagination.
|
|
7
|
+
* Concrete dialects override only the minimal hooks (identifier quoting,
|
|
8
|
+
* JSON path, placeholders, RETURNING support) instead of re-implementing
|
|
9
|
+
* the entire compile pipeline.
|
|
10
|
+
*/
|
|
11
|
+
export abstract class SqlDialectBase extends Dialect {
|
|
12
|
+
/**
|
|
13
|
+
* Quotes an identifier (dialect-specific).
|
|
14
|
+
*/
|
|
15
|
+
abstract quoteIdentifier(id: string): string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compiles SELECT query AST to SQL using common rules.
|
|
19
|
+
*/
|
|
20
|
+
protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
21
|
+
const ctes = this.compileCtes(ast, ctx);
|
|
22
|
+
const columns = this.compileSelectColumns(ast, ctx);
|
|
23
|
+
const from = this.compileFrom(ast.from);
|
|
24
|
+
const joins = this.compileJoins(ast, ctx);
|
|
25
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
26
|
+
const groupBy = this.compileGroupBy(ast);
|
|
27
|
+
const having = this.compileHaving(ast, ctx);
|
|
28
|
+
const orderBy = this.compileOrderBy(ast);
|
|
29
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
30
|
+
|
|
31
|
+
return `${ctes}SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
35
|
+
const table = this.compileTableName(ast.into);
|
|
36
|
+
const columnList = ast.columns
|
|
37
|
+
.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`)
|
|
38
|
+
.join(', ');
|
|
39
|
+
const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
|
|
40
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
41
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
45
|
+
const table = this.compileTableName(ast.table);
|
|
46
|
+
const assignments = ast.set.map(assignment => {
|
|
47
|
+
const col = assignment.column;
|
|
48
|
+
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
49
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
50
|
+
return `${target} = ${value}`;
|
|
51
|
+
}).join(', ');
|
|
52
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
53
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
54
|
+
return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
58
|
+
const table = this.compileTableName(ast.from);
|
|
59
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
60
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
61
|
+
return `DELETE FROM ${table}${whereClause}${returning}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Default RETURNING compilation: no support.
|
|
66
|
+
*/
|
|
67
|
+
protected compileReturning(returning: ColumnNode[] | undefined, _ctx: CompilerContext): string {
|
|
68
|
+
if (!returning || returning.length === 0) return '';
|
|
69
|
+
throw new Error('RETURNING is not supported by this dialect.');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* DISTINCT clause. Override for DISTINCT ON support.
|
|
74
|
+
*/
|
|
75
|
+
protected compileDistinct(ast: SelectQueryNode): string {
|
|
76
|
+
return ast.distinct ? 'DISTINCT ' : '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected compileSelectColumns(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
80
|
+
return ast.columns.map(c => {
|
|
81
|
+
const expr = this.compileOperand(c, ctx);
|
|
82
|
+
if (c.alias) {
|
|
83
|
+
if (c.alias.includes('(')) return c.alias;
|
|
84
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
85
|
+
}
|
|
86
|
+
return expr;
|
|
87
|
+
}).join(', ');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected compileFrom(ast: SelectQueryNode['from']): string {
|
|
91
|
+
const base = this.compileTableName(ast);
|
|
92
|
+
return ast.alias ? `${base} AS ${this.quoteIdentifier(ast.alias)}` : base;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected compileTableName(table: { name: string; schema?: string }): string {
|
|
96
|
+
if (table.schema) {
|
|
97
|
+
return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
|
|
98
|
+
}
|
|
99
|
+
return this.quoteIdentifier(table.name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
protected compileJoins(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
103
|
+
if (!ast.joins || ast.joins.length === 0) return '';
|
|
104
|
+
const parts = ast.joins.map(j => {
|
|
105
|
+
const table = this.compileFrom(j.table);
|
|
106
|
+
const cond = this.compileExpression(j.condition, ctx);
|
|
107
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
108
|
+
});
|
|
109
|
+
return ` ${parts.join(' ')}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected compileGroupBy(ast: SelectQueryNode): string {
|
|
113
|
+
if (!ast.groupBy || ast.groupBy.length === 0) return '';
|
|
114
|
+
const cols = ast.groupBy
|
|
115
|
+
.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`)
|
|
116
|
+
.join(', ');
|
|
117
|
+
return ` GROUP BY ${cols}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected compileHaving(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
121
|
+
if (!ast.having) return '';
|
|
122
|
+
return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
protected compileOrderBy(ast: SelectQueryNode): string {
|
|
126
|
+
if (!ast.orderBy || ast.orderBy.length === 0) return '';
|
|
127
|
+
const parts = ast.orderBy
|
|
128
|
+
.map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`)
|
|
129
|
+
.join(', ');
|
|
130
|
+
return ` ORDER BY ${parts}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Default LIMIT/OFFSET pagination clause.
|
|
135
|
+
*/
|
|
136
|
+
protected compilePagination(ast: SelectQueryNode, _orderByClause: string): string {
|
|
137
|
+
const parts: string[] = [];
|
|
138
|
+
if (ast.limit !== undefined) parts.push(`LIMIT ${ast.limit}`);
|
|
139
|
+
if (ast.offset !== undefined) parts.push(`OFFSET ${ast.offset}`);
|
|
140
|
+
return parts.length ? ` ${parts.join(' ')}` : '';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
144
|
+
if (!ast.ctes || ast.ctes.length === 0) return '';
|
|
145
|
+
const hasRecursive = ast.ctes.some(cte => cte.recursive);
|
|
146
|
+
const prefix = hasRecursive ? 'WITH RECURSIVE ' : 'WITH ';
|
|
147
|
+
const cteDefs = ast.ctes.map(cte => {
|
|
148
|
+
const name = this.quoteIdentifier(cte.name);
|
|
149
|
+
const cols = cte.columns && cte.columns.length
|
|
150
|
+
? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})`
|
|
151
|
+
: '';
|
|
152
|
+
const query = this.stripTrailingSemicolon(this.compileSelectAst(cte.query, ctx));
|
|
153
|
+
return `${name}${cols} AS (${query})`;
|
|
154
|
+
}).join(', ');
|
|
155
|
+
return `${prefix}${cteDefs} `;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected stripTrailingSemicolon(sql: string): string {
|
|
159
|
+
return sql.trim().replace(/;$/, '');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
super();
|
|
1
|
+
import { JsonPathNode } from '../../ast/expression.js';
|
|
2
|
+
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MySQL dialect implementation
|
|
6
|
+
*/
|
|
7
|
+
export class MySqlDialect extends SqlDialectBase {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new MySqlDialect instance
|
|
10
|
+
*/
|
|
11
|
+
public constructor() {
|
|
12
|
+
super();
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -27,102 +26,9 @@ export class MySqlDialect extends Dialect {
|
|
|
27
26
|
* @param node - JSON path node
|
|
28
27
|
* @returns MySQL JSON path expression
|
|
29
28
|
*/
|
|
30
|
-
protected compileJsonPath(node: JsonPathNode): string {
|
|
31
|
-
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
32
|
-
// MySQL 5.7+ uses col->'$.path'
|
|
33
|
-
return `${col}->'${node.path}'`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Compiles SELECT query AST to MySQL SQL
|
|
38
|
-
* @param ast - Query AST
|
|
39
|
-
* @param ctx - Compiler context
|
|
40
|
-
* @returns MySQL SQL string
|
|
41
|
-
*/
|
|
42
|
-
protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
|
|
43
|
-
const columns = ast.columns.map(c => {
|
|
44
|
-
let expr = '';
|
|
45
|
-
if (c.type === 'Function') {
|
|
46
|
-
expr = this.compileOperand(c, ctx);
|
|
47
|
-
} else if (c.type === 'Column') {
|
|
48
|
-
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
49
|
-
} else if (c.type === 'ScalarSubquery') {
|
|
50
|
-
expr = this.compileOperand(c, ctx);
|
|
51
|
-
} else if (c.type === 'WindowFunction') {
|
|
52
|
-
expr = this.compileOperand(c, ctx);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (c.alias) {
|
|
56
|
-
if (c.alias.includes('(')) return c.alias;
|
|
57
|
-
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
58
|
-
}
|
|
59
|
-
return expr;
|
|
60
|
-
}).join(', ');
|
|
61
|
-
|
|
62
|
-
const distinct = ast.distinct ? 'DISTINCT ' : '';
|
|
63
|
-
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
64
|
-
|
|
65
|
-
const joins = ast.joins.map(j => {
|
|
66
|
-
const table = this.quoteIdentifier(j.table.name);
|
|
67
|
-
const cond = this.compileExpression(j.condition, ctx);
|
|
68
|
-
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
69
|
-
}).join(' ');
|
|
70
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
71
|
-
|
|
72
|
-
const groupBy = ast.groupBy && ast.groupBy.length > 0
|
|
73
|
-
? ' GROUP BY ' + ast.groupBy.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(', ')
|
|
74
|
-
: '';
|
|
75
|
-
|
|
76
|
-
const having = ast.having
|
|
77
|
-
? ` HAVING ${this.compileExpression(ast.having, ctx)}`
|
|
78
|
-
: '';
|
|
79
|
-
|
|
80
|
-
const orderBy = ast.orderBy && ast.orderBy.length > 0
|
|
81
|
-
? ' ORDER BY ' + ast.orderBy.map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(', ')
|
|
82
|
-
: '';
|
|
83
|
-
|
|
84
|
-
const limit = ast.limit ? ` LIMIT ${ast.limit}` : '';
|
|
85
|
-
const offset = ast.offset ? ` OFFSET ${ast.offset}` : '';
|
|
86
|
-
|
|
87
|
-
const ctes = ast.ctes && ast.ctes.length > 0
|
|
88
|
-
? (() => {
|
|
89
|
-
const hasRecursive = ast.ctes.some(cte => cte.recursive);
|
|
90
|
-
const prefix = hasRecursive ? 'WITH RECURSIVE ' : 'WITH ';
|
|
91
|
-
const cteDefs = ast.ctes.map(cte => {
|
|
92
|
-
const name = this.quoteIdentifier(cte.name);
|
|
93
|
-
const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
|
|
94
|
-
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, '');
|
|
95
|
-
return `${name}${cols} AS (${query})`;
|
|
96
|
-
}).join(', ');
|
|
97
|
-
return prefix + cteDefs + ' ';
|
|
98
|
-
})()
|
|
99
|
-
: '';
|
|
100
|
-
|
|
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
|
-
}
|
|
128
|
-
}
|
|
29
|
+
protected compileJsonPath(node: JsonPathNode): string {
|
|
30
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
31
|
+
// MySQL 5.7+ uses col->'$.path'
|
|
32
|
+
return `${col}->'${node.path}'`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { CompilerContext
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* PostgreSQL dialect implementation
|
|
7
|
-
*/
|
|
8
|
-
export class PostgresDialect extends
|
|
9
|
-
/**
|
|
10
|
-
* Creates a new PostgresDialect instance
|
|
11
|
-
*/
|
|
12
|
-
public constructor() {
|
|
13
|
-
super();
|
|
1
|
+
import { CompilerContext } from '../abstract.js';
|
|
2
|
+
import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
|
|
3
|
+
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* PostgreSQL dialect implementation
|
|
7
|
+
*/
|
|
8
|
+
export class PostgresDialect extends SqlDialectBase {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new PostgresDialect instance
|
|
11
|
+
*/
|
|
12
|
+
public constructor() {
|
|
13
|
+
super();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -27,116 +27,20 @@ export class PostgresDialect extends Dialect {
|
|
|
27
27
|
* @param node - JSON path node
|
|
28
28
|
* @returns PostgreSQL JSON path expression
|
|
29
29
|
*/
|
|
30
|
-
protected compileJsonPath(node: JsonPathNode): string {
|
|
31
|
-
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
32
|
-
// Postgres uses col->>'path' for text extraction
|
|
33
|
-
return `${col}->>'${node.path}'`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} else if (c.type === 'Column') {
|
|
48
|
-
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
49
|
-
} else if (c.type === 'ScalarSubquery') {
|
|
50
|
-
expr = this.compileOperand(c, ctx);
|
|
51
|
-
} else if (c.type === 'WindowFunction') {
|
|
52
|
-
expr = this.compileOperand(c, ctx);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (c.alias) {
|
|
56
|
-
if (c.alias.includes('(')) return c.alias;
|
|
57
|
-
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
58
|
-
}
|
|
59
|
-
return expr;
|
|
60
|
-
}).join(', ');
|
|
61
|
-
|
|
62
|
-
const distinct = ast.distinct ? 'DISTINCT ' : '';
|
|
63
|
-
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
64
|
-
|
|
65
|
-
const joins = ast.joins.map(j => {
|
|
66
|
-
const table = this.quoteIdentifier(j.table.name);
|
|
67
|
-
const cond = this.compileExpression(j.condition, ctx);
|
|
68
|
-
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
69
|
-
}).join(' ');
|
|
70
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
71
|
-
|
|
72
|
-
const groupBy = ast.groupBy && ast.groupBy.length > 0
|
|
73
|
-
? ' GROUP BY ' + ast.groupBy.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(', ')
|
|
74
|
-
: '';
|
|
75
|
-
|
|
76
|
-
const having = ast.having
|
|
77
|
-
? ` HAVING ${this.compileExpression(ast.having, ctx)}`
|
|
78
|
-
: '';
|
|
79
|
-
|
|
80
|
-
const orderBy = ast.orderBy && ast.orderBy.length > 0
|
|
81
|
-
? ' ORDER BY ' + ast.orderBy.map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(', ')
|
|
82
|
-
: '';
|
|
83
|
-
|
|
84
|
-
const limit = ast.limit ? ` LIMIT ${ast.limit}` : '';
|
|
85
|
-
const offset = ast.offset ? ` OFFSET ${ast.offset}` : '';
|
|
86
|
-
|
|
87
|
-
const ctes = ast.ctes && ast.ctes.length > 0
|
|
88
|
-
? (() => {
|
|
89
|
-
const hasRecursive = ast.ctes.some(cte => cte.recursive);
|
|
90
|
-
const prefix = hasRecursive ? 'WITH RECURSIVE ' : 'WITH ';
|
|
91
|
-
const cteDefs = ast.ctes.map(cte => {
|
|
92
|
-
const name = this.quoteIdentifier(cte.name);
|
|
93
|
-
const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
|
|
94
|
-
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, '');
|
|
95
|
-
return `${name}${cols} AS (${query})`;
|
|
96
|
-
}).join(', ');
|
|
97
|
-
return prefix + cteDefs + ' ';
|
|
98
|
-
})()
|
|
99
|
-
: '';
|
|
100
|
-
|
|
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
|
-
}
|
|
142
|
-
}
|
|
30
|
+
protected compileJsonPath(node: JsonPathNode): string {
|
|
31
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
32
|
+
// Postgres uses col->>'path' for text extraction
|
|
33
|
+
return `${col}->>'${node.path}'`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
37
|
+
if (!returning || returning.length === 0) return '';
|
|
38
|
+
const columns = returning
|
|
39
|
+
.map(column => {
|
|
40
|
+
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : '';
|
|
41
|
+
return `${tablePart}${this.quoteIdentifier(column.name)}`;
|
|
42
|
+
})
|
|
43
|
+
.join(', ');
|
|
44
|
+
return ` RETURNING ${columns}`;
|
|
45
|
+
}
|
|
46
|
+
}
|