metal-orm 1.0.11 → 1.0.13

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 (54) hide show
  1. package/README.md +21 -18
  2. package/dist/decorators/index.cjs +317 -34
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -1
  5. package/dist/decorators/index.d.ts +1 -1
  6. package/dist/decorators/index.js +317 -34
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +1965 -267
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +273 -23
  11. package/dist/index.d.ts +273 -23
  12. package/dist/index.js +1947 -267
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-654m4qy8.d.cts → select-CCp1oz9p.d.cts} +254 -4
  15. package/dist/{select-654m4qy8.d.ts → select-CCp1oz9p.d.ts} +254 -4
  16. package/package.json +3 -2
  17. package/src/core/ast/query.ts +40 -22
  18. package/src/core/ddl/dialects/base-schema-dialect.ts +48 -0
  19. package/src/core/ddl/dialects/index.ts +5 -0
  20. package/src/core/ddl/dialects/mssql-schema-dialect.ts +97 -0
  21. package/src/core/ddl/dialects/mysql-schema-dialect.ts +109 -0
  22. package/src/core/ddl/dialects/postgres-schema-dialect.ts +99 -0
  23. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +103 -0
  24. package/src/core/ddl/introspect/mssql.ts +149 -0
  25. package/src/core/ddl/introspect/mysql.ts +99 -0
  26. package/src/core/ddl/introspect/postgres.ts +154 -0
  27. package/src/core/ddl/introspect/sqlite.ts +66 -0
  28. package/src/core/ddl/introspect/types.ts +19 -0
  29. package/src/core/ddl/introspect/utils.ts +27 -0
  30. package/src/core/ddl/schema-diff.ts +179 -0
  31. package/src/core/ddl/schema-generator.ts +229 -0
  32. package/src/core/ddl/schema-introspect.ts +32 -0
  33. package/src/core/ddl/schema-types.ts +39 -0
  34. package/src/core/dialect/abstract.ts +122 -37
  35. package/src/core/dialect/base/sql-dialect.ts +204 -0
  36. package/src/core/dialect/mssql/index.ts +125 -80
  37. package/src/core/dialect/mysql/index.ts +18 -112
  38. package/src/core/dialect/postgres/index.ts +29 -126
  39. package/src/core/dialect/sqlite/index.ts +28 -129
  40. package/src/index.ts +4 -0
  41. package/src/orm/execute.ts +25 -16
  42. package/src/orm/orm-context.ts +60 -55
  43. package/src/orm/query-logger.ts +38 -0
  44. package/src/orm/relations/belongs-to.ts +42 -26
  45. package/src/orm/relations/has-many.ts +41 -25
  46. package/src/orm/relations/many-to-many.ts +43 -27
  47. package/src/orm/unit-of-work.ts +60 -23
  48. package/src/query-builder/hydration-manager.ts +229 -25
  49. package/src/query-builder/query-ast-service.ts +27 -12
  50. package/src/query-builder/select-query-state.ts +24 -12
  51. package/src/query-builder/select.ts +58 -14
  52. package/src/schema/column.ts +206 -27
  53. package/src/schema/table.ts +89 -32
  54. package/src/schema/types.ts +8 -5
@@ -1,6 +1,6 @@
1
- import { CompilerContext, Dialect } from '../abstract.js';
2
- import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
3
- import { JsonPathNode } from '../../ast/expression.js';
1
+ import { CompilerContext, Dialect } from '../abstract.js';
2
+ import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
3
+ import { JsonPathNode } from '../../ast/expression.js';
4
4
 
5
5
  /**
6
6
  * Microsoft SQL Server dialect implementation
@@ -42,77 +42,36 @@ export class SqlServerDialect extends Dialect {
42
42
  return `@p${index}`;
43
43
  }
44
44
 
45
- /**
46
- * Compiles SELECT query AST to SQL Server SQL
47
- * @param ast - Query AST
48
- * @param ctx - Compiler context
49
- * @returns SQL Server SQL string
50
- */
51
- protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
52
- const columns = ast.columns.map(c => {
53
- let expr = '';
54
- if (c.type === 'Function') {
55
- expr = this.compileOperand(c, ctx);
56
- } else if (c.type === 'Column') {
57
- expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
58
- } else if (c.type === 'ScalarSubquery') {
59
- expr = this.compileOperand(c, ctx);
60
- } else if (c.type === 'WindowFunction') {
61
- expr = this.compileOperand(c, ctx);
62
- }
63
-
64
- if (c.alias) {
65
- if (c.alias.includes('(')) return c.alias;
66
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
67
- }
68
- return expr;
69
- }).join(', ');
70
-
71
- const distinct = ast.distinct ? 'DISTINCT ' : '';
72
- const from = `${this.quoteIdentifier(ast.from.name)}`;
73
-
74
- const joins = ast.joins.map(j => {
75
- const table = this.quoteIdentifier(j.table.name);
76
- const cond = this.compileExpression(j.condition, ctx);
77
- return `${j.kind} JOIN ${table} ON ${cond}`;
78
- }).join(' ');
79
- const whereClause = this.compileWhere(ast.where, ctx);
80
-
81
- const groupBy = ast.groupBy && ast.groupBy.length > 0
82
- ? ' GROUP BY ' + ast.groupBy.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(', ')
83
- : '';
84
-
85
- const having = ast.having
86
- ? ` HAVING ${this.compileExpression(ast.having, ctx)}`
87
- : '';
88
-
89
- const orderBy = ast.orderBy && ast.orderBy.length > 0
90
- ? ' ORDER BY ' + ast.orderBy.map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(', ')
91
- : '';
92
-
93
- let pagination = '';
94
- if (ast.limit || ast.offset) {
95
- const off = ast.offset || 0;
96
- const orderClause = orderBy || ' ORDER BY (SELECT NULL)';
97
- pagination = `${orderClause} OFFSET ${off} ROWS`;
98
- if (ast.limit) {
99
- pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
100
- }
101
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination};`;
102
- }
103
-
104
- const ctes = ast.ctes && ast.ctes.length > 0
105
- ? 'WITH ' + ast.ctes.map(cte => {
106
- // MSSQL does not use RECURSIVE keyword
107
- const name = this.quoteIdentifier(cte.name);
108
- const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
109
- const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, '');
110
- return `${name}${cols} AS (${query})`;
111
- }).join(', ') + ' '
112
- : '';
113
-
114
- return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy};`;
115
- }
45
+ /**
46
+ * Compiles SELECT query AST to SQL Server SQL
47
+ * @param ast - Query AST
48
+ * @param ctx - Compiler context
49
+ * @returns SQL Server SQL string
50
+ */
51
+ protected compileSelectAst(ast: SelectQueryNode, ctx: CompilerContext): string {
52
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
53
+ const ctes = this.compileCtes(ast, ctx);
54
+
55
+ const baseAst: SelectQueryNode = hasSetOps
56
+ ? { ...ast, setOps: undefined, orderBy: undefined, limit: undefined, offset: undefined }
57
+ : ast;
58
+
59
+ const baseSelect = this.compileSelectCore(baseAst, ctx);
60
+
61
+ if (!hasSetOps) {
62
+ return `${ctes}${baseSelect}`;
63
+ }
64
+
65
+ const compound = ast.setOps!
66
+ .map(op => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`)
67
+ .join(' ');
68
+
69
+ const orderBy = this.compileOrderBy(ast);
70
+ const pagination = this.compilePagination(ast, orderBy);
71
+ const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
72
+ const tail = pagination || orderBy;
73
+ return `${ctes}${combined}${tail}`;
74
+ }
116
75
 
117
76
  protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
118
77
  const table = this.quoteIdentifier(ast.into.name);
@@ -133,9 +92,95 @@ export class SqlServerDialect extends Dialect {
133
92
  return `UPDATE ${table} SET ${assignments}${whereClause};`;
134
93
  }
135
94
 
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
- }
141
- }
95
+ protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
96
+ const table = this.quoteIdentifier(ast.from.name);
97
+ const whereClause = this.compileWhere(ast.where, ctx);
98
+ return `DELETE FROM ${table}${whereClause};`;
99
+ }
100
+
101
+ private compileSelectCore(ast: SelectQueryNode, ctx: CompilerContext): string {
102
+ const columns = ast.columns.map(c => {
103
+ let expr = '';
104
+ if (c.type === 'Function') {
105
+ expr = this.compileOperand(c, ctx);
106
+ } else if (c.type === 'Column') {
107
+ expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
108
+ } else if (c.type === 'ScalarSubquery') {
109
+ expr = this.compileOperand(c, ctx);
110
+ } else if (c.type === 'WindowFunction') {
111
+ expr = this.compileOperand(c, ctx);
112
+ }
113
+
114
+ if (c.alias) {
115
+ if (c.alias.includes('(')) return c.alias;
116
+ return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
117
+ }
118
+ return expr;
119
+ }).join(', ');
120
+
121
+ const distinct = ast.distinct ? 'DISTINCT ' : '';
122
+ const from = `${this.quoteIdentifier(ast.from.name)}`;
123
+
124
+ const joins = ast.joins.map(j => {
125
+ const table = this.quoteIdentifier(j.table.name);
126
+ const cond = this.compileExpression(j.condition, ctx);
127
+ return `${j.kind} JOIN ${table} ON ${cond}`;
128
+ }).join(' ');
129
+ const whereClause = this.compileWhere(ast.where, ctx);
130
+
131
+ const groupBy = ast.groupBy && ast.groupBy.length > 0
132
+ ? ' GROUP BY ' + ast.groupBy.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(', ')
133
+ : '';
134
+
135
+ const having = ast.having
136
+ ? ` HAVING ${this.compileExpression(ast.having, ctx)}`
137
+ : '';
138
+
139
+ const orderBy = this.compileOrderBy(ast);
140
+ const pagination = this.compilePagination(ast, orderBy);
141
+
142
+ if (pagination) {
143
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${pagination}`;
144
+ }
145
+
146
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}`;
147
+ }
148
+
149
+ private compileOrderBy(ast: SelectQueryNode): string {
150
+ if (!ast.orderBy || ast.orderBy.length === 0) return '';
151
+ return ' ORDER BY ' + ast.orderBy
152
+ .map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`)
153
+ .join(', ');
154
+ }
155
+
156
+ private compilePagination(ast: SelectQueryNode, orderBy: string): string {
157
+ const hasLimit = ast.limit !== undefined;
158
+ const hasOffset = ast.offset !== undefined;
159
+ if (!hasLimit && !hasOffset) return '';
160
+
161
+ const off = ast.offset ?? 0;
162
+ const orderClause = orderBy || ' ORDER BY (SELECT NULL)';
163
+ let pagination = `${orderClause} OFFSET ${off} ROWS`;
164
+ if (hasLimit) {
165
+ pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
166
+ }
167
+ return pagination;
168
+ }
169
+
170
+ private compileCtes(ast: SelectQueryNode, ctx: CompilerContext): string {
171
+ if (!ast.ctes || ast.ctes.length === 0) return '';
172
+ // MSSQL does not use RECURSIVE keyword, but supports recursion when CTE references itself.
173
+ const defs = ast.ctes.map(cte => {
174
+ const name = this.quoteIdentifier(cte.name);
175
+ const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
176
+ const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, '');
177
+ return `${name}${cols} AS (${query})`;
178
+ }).join(', ');
179
+ return `WITH ${defs} `;
180
+ }
181
+
182
+ private wrapSetOperand(sql: string): string {
183
+ const trimmed = sql.trim().replace(/;$/, '');
184
+ return `(${trimmed})`;
185
+ }
186
+ }
@@ -1,16 +1,15 @@
1
- import { CompilerContext, Dialect } from '../abstract.js';
2
- import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
3
- import { JsonPathNode } from '../../ast/expression.js';
4
-
5
- /**
6
- * MySQL dialect implementation
7
- */
8
- export class MySqlDialect extends Dialect {
9
- /**
10
- * Creates a new MySqlDialect instance
11
- */
12
- public constructor() {
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, Dialect } from '../abstract.js';
2
- import { SelectQueryNode, InsertQueryNode, UpdateQueryNode, DeleteQueryNode } from '../../ast/query.js';
3
- import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
4
-
5
- /**
6
- * PostgreSQL dialect implementation
7
- */
8
- export class PostgresDialect extends Dialect {
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,19 @@ 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
- * Compiles SELECT query AST to PostgreSQL SQL
38
- * @param ast - Query AST
39
- * @param ctx - Compiler context
40
- * @returns PostgreSQL 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
- 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 = this.formatReturningColumns(returning);
39
+ return ` RETURNING ${columns}`;
40
+ }
41
+
42
+ supportsReturning(): boolean {
43
+ return true;
44
+ }
45
+ }