@simplysm/orm-common 13.0.100 → 14.0.1
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/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +312 -276
- package/dist/create-db-context.js.map +1 -6
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/column-ddl.js +41 -35
- package/dist/ddl/column-ddl.js.map +1 -6
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +200 -142
- package/dist/ddl/initialize.js.map +1 -6
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/relation-ddl.js +55 -48
- package/dist/ddl/relation-ddl.js.map +1 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/schema-ddl.js +21 -15
- package/dist/ddl/schema-ddl.js.map +1 -6
- package/dist/ddl/table-ddl.d.ts +20 -20
- package/dist/ddl/table-ddl.js +139 -93
- package/dist/ddl/table-ddl.js.map +1 -6
- package/dist/define-db-context.js +10 -13
- package/dist/define-db-context.js.map +1 -6
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/errors/db-transaction-error.js +53 -19
- package/dist/errors/db-transaction-error.js.map +1 -6
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +94 -40
- package/dist/exec/executable.js.map +1 -6
- package/dist/exec/queryable.d.ts +97 -97
- package/dist/exec/queryable.js +1310 -1204
- package/dist/exec/queryable.js.map +1 -6
- package/dist/exec/search-parser.d.ts +31 -31
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/exec/search-parser.js +158 -59
- package/dist/exec/search-parser.js.map +1 -6
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr-unit.js +24 -18
- package/dist/expr/expr-unit.js.map +1 -6
- package/dist/expr/expr.d.ts +6 -6
- package/dist/expr/expr.js +1872 -1844
- package/dist/expr/expr.js.map +1 -6
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -6
- package/dist/models/system-migration.js +7 -7
- package/dist/models/system-migration.js.map +1 -6
- package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
- package/dist/query-builder/base/expr-renderer-base.js +27 -21
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
- package/dist/query-builder/base/query-builder-base.d.ts +21 -21
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +90 -80
- package/dist/query-builder/base/query-builder-base.js.map +1 -6
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
- package/dist/query-builder/mssql/mssql-query-builder.js +483 -443
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
- package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
- package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/query-builder/query-builder.js +13 -13
- package/dist/query-builder/query-builder.js.map +1 -6
- package/dist/schema/factory/column-builder.d.ts +84 -84
- package/dist/schema/factory/column-builder.js +248 -185
- package/dist/schema/factory/column-builder.js.map +1 -6
- package/dist/schema/factory/index-builder.d.ts +38 -38
- package/dist/schema/factory/index-builder.js +144 -85
- package/dist/schema/factory/index-builder.js.map +1 -6
- package/dist/schema/factory/relation-builder.d.ts +91 -91
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +274 -136
- package/dist/schema/factory/relation-builder.js.map +1 -6
- package/dist/schema/procedure-builder.d.ts +51 -51
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +205 -131
- package/dist/schema/procedure-builder.js.map +1 -6
- package/dist/schema/table-builder.d.ts +55 -55
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +274 -205
- package/dist/schema/table-builder.js.map +1 -6
- package/dist/schema/view-builder.d.ts +44 -44
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +189 -116
- package/dist/schema/view-builder.js.map +1 -6
- package/dist/types/column.js +60 -30
- package/dist/types/column.js.map +1 -6
- package/dist/types/db-context-def.d.ts +9 -9
- package/dist/types/db-context-def.js +2 -1
- package/dist/types/db-context-def.js.map +1 -6
- package/dist/types/db.d.ts +47 -47
- package/dist/types/db.js +15 -5
- package/dist/types/db.js.map +1 -6
- package/dist/types/expr.d.ts +81 -81
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/expr.js +3 -1
- package/dist/types/expr.js.map +1 -6
- package/dist/types/query-def.d.ts +46 -46
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/types/query-def.js +31 -24
- package/dist/types/query-def.js.map +1 -6
- package/dist/utils/result-parser.js +362 -221
- package/dist/utils/result-parser.js.map +1 -6
- package/package.json +5 -7
- package/src/create-db-context.ts +31 -31
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +38 -38
- package/src/ddl/relation-ddl.ts +6 -6
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +24 -24
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +134 -134
- package/src/exec/search-parser.ts +50 -50
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +13 -13
- package/src/index.ts +8 -8
- package/src/models/system-migration.ts +1 -1
- package/src/query-builder/base/expr-renderer-base.ts +21 -21
- package/src/query-builder/base/query-builder-base.ts +33 -33
- package/src/query-builder/mssql/mssql-expr-renderer.ts +11 -11
- package/src/query-builder/mssql/mssql-query-builder.ts +11 -11
- package/src/query-builder/mysql/mysql-expr-renderer.ts +15 -15
- package/src/query-builder/mysql/mysql-query-builder.ts +3 -3
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +9 -9
- package/src/query-builder/postgresql/postgresql-query-builder.ts +7 -7
- package/src/query-builder/query-builder.ts +1 -1
- package/src/schema/factory/column-builder.ts +86 -86
- package/src/schema/factory/index-builder.ts +38 -38
- package/src/schema/factory/relation-builder.ts +93 -93
- package/src/schema/procedure-builder.ts +52 -52
- package/src/schema/table-builder.ts +56 -56
- package/src/schema/view-builder.ts +45 -45
- package/src/types/column.ts +1 -1
- package/src/types/db-context-def.ts +15 -15
- package/src/types/db.ts +50 -50
- package/src/types/expr.ts +103 -103
- package/src/types/query-def.ts +50 -50
- package/src/utils/result-parser.ts +39 -39
- package/README.md +0 -192
- package/docs/core.md +0 -234
- package/docs/expression.md +0 -234
- package/docs/query-builder.md +0 -93
- package/docs/queryable.md +0 -198
- package/docs/schema-builders.md +0 -463
- package/docs/types.md +0 -445
- package/docs/utilities.md +0 -27
- package/tests/db-context/create-db-context.spec.ts +0 -193
- package/tests/db-context/define-db-context.spec.ts +0 -17
- package/tests/ddl/basic.expected.ts +0 -341
- package/tests/ddl/basic.spec.ts +0 -557
- package/tests/ddl/column-builder.expected.ts +0 -310
- package/tests/ddl/column-builder.spec.ts +0 -525
- package/tests/ddl/index-builder.expected.ts +0 -38
- package/tests/ddl/index-builder.spec.ts +0 -148
- package/tests/ddl/procedure-builder.expected.ts +0 -52
- package/tests/ddl/procedure-builder.spec.ts +0 -128
- package/tests/ddl/relation-builder.expected.ts +0 -36
- package/tests/ddl/relation-builder.spec.ts +0 -171
- package/tests/ddl/table-builder.expected.ts +0 -113
- package/tests/ddl/table-builder.spec.ts +0 -399
- package/tests/ddl/view-builder.expected.ts +0 -38
- package/tests/ddl/view-builder.spec.ts +0 -116
- package/tests/dml/delete.expected.ts +0 -96
- package/tests/dml/delete.spec.ts +0 -127
- package/tests/dml/insert.expected.ts +0 -192
- package/tests/dml/insert.spec.ts +0 -210
- package/tests/dml/update.expected.ts +0 -176
- package/tests/dml/update.spec.ts +0 -222
- package/tests/dml/upsert.expected.ts +0 -215
- package/tests/dml/upsert.spec.ts +0 -190
- package/tests/errors/queryable-errors.spec.ts +0 -126
- package/tests/escape.spec.ts +0 -59
- package/tests/examples/pivot.expected.ts +0 -211
- package/tests/examples/pivot.spec.ts +0 -200
- package/tests/examples/sampling.expected.ts +0 -69
- package/tests/examples/sampling.spec.ts +0 -42
- package/tests/examples/unpivot.expected.ts +0 -120
- package/tests/examples/unpivot.spec.ts +0 -161
- package/tests/exec/search-parser.spec.ts +0 -267
- package/tests/executable/basic.expected.ts +0 -18
- package/tests/executable/basic.spec.ts +0 -54
- package/tests/expr/comparison.expected.ts +0 -282
- package/tests/expr/comparison.spec.ts +0 -334
- package/tests/expr/conditional.expected.ts +0 -134
- package/tests/expr/conditional.spec.ts +0 -249
- package/tests/expr/date.expected.ts +0 -332
- package/tests/expr/date.spec.ts +0 -459
- package/tests/expr/math.expected.ts +0 -62
- package/tests/expr/math.spec.ts +0 -59
- package/tests/expr/string.expected.ts +0 -218
- package/tests/expr/string.spec.ts +0 -300
- package/tests/expr/utility.expected.ts +0 -147
- package/tests/expr/utility.spec.ts +0 -155
- package/tests/select/basic.expected.ts +0 -322
- package/tests/select/basic.spec.ts +0 -433
- package/tests/select/filter.expected.ts +0 -357
- package/tests/select/filter.spec.ts +0 -954
- package/tests/select/group.expected.ts +0 -169
- package/tests/select/group.spec.ts +0 -159
- package/tests/select/join.expected.ts +0 -582
- package/tests/select/join.spec.ts +0 -692
- package/tests/select/order.expected.ts +0 -150
- package/tests/select/order.spec.ts +0 -140
- package/tests/select/recursive-cte.expected.ts +0 -244
- package/tests/select/recursive-cte.spec.ts +0 -514
- package/tests/select/result-meta.spec.ts +0 -270
- package/tests/select/subquery.expected.ts +0 -363
- package/tests/select/subquery.spec.ts +0 -441
- package/tests/select/view.expected.ts +0 -155
- package/tests/select/view.spec.ts +0 -235
- package/tests/select/window.expected.ts +0 -345
- package/tests/select/window.spec.ts +0 -433
- package/tests/setup/MockExecutor.ts +0 -18
- package/tests/setup/TestDbContext.ts +0 -59
- package/tests/setup/models/Company.ts +0 -13
- package/tests/setup/models/Employee.ts +0 -10
- package/tests/setup/models/MonthlySales.ts +0 -11
- package/tests/setup/models/Post.ts +0 -16
- package/tests/setup/models/Sales.ts +0 -10
- package/tests/setup/models/User.ts +0 -19
- package/tests/setup/procedure/GetAllUsers.ts +0 -9
- package/tests/setup/procedure/GetUserById.ts +0 -12
- package/tests/setup/test-utils.ts +0 -72
- package/tests/setup/views/ActiveUsers.ts +0 -8
- package/tests/setup/views/UserSummary.ts +0 -11
- package/tests/types/nullable-queryable-record.spec.ts +0 -97
- package/tests/utils/result-parser-perf.spec.ts +0 -143
- package/tests/utils/result-parser.spec.ts +0 -667
|
@@ -1,464 +1,515 @@
|
|
|
1
1
|
import { QueryBuilderBase } from "../base/query-builder-base.js";
|
|
2
2
|
import { PostgresqlExprRenderer } from "./postgresql-expr-renderer.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return `(${values.join(", ")})`;
|
|
79
|
-
});
|
|
80
|
-
let sql = `INSERT INTO ${table} (${colList})`;
|
|
81
|
-
sql += ` VALUES ${valuesList.join(", ")}`;
|
|
82
|
-
if (def.output != null) {
|
|
83
|
-
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
84
|
-
sql += ` RETURNING ${outputCols}`;
|
|
85
|
-
}
|
|
86
|
-
return { sql };
|
|
87
|
-
}
|
|
88
|
-
insertIfNotExists(def) {
|
|
89
|
-
const table = this.tableName(def.table);
|
|
90
|
-
const columns = Object.keys(def.record);
|
|
91
|
-
const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
92
|
-
const values = columns.map((c) => this.expr.escapeValue(def.record[c])).join(", ");
|
|
93
|
-
const existsQuerySql = this.select({
|
|
94
|
-
...def.existsSelectQuery,
|
|
95
|
-
select: { _: { type: "value", value: 1 } }
|
|
96
|
-
}).sql;
|
|
97
|
-
let sql = `INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`;
|
|
98
|
-
if (def.output != null) {
|
|
99
|
-
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
100
|
-
sql += ` RETURNING ${outputCols}`;
|
|
101
|
-
}
|
|
102
|
-
return { sql };
|
|
103
|
-
}
|
|
104
|
-
insertInto(def) {
|
|
105
|
-
const table = this.tableName(def.table);
|
|
106
|
-
const selectSql = this.select(def.recordsSelectQuery).sql;
|
|
107
|
-
const selectDef = def.recordsSelectQuery;
|
|
108
|
-
const colList = selectDef.select != null ? Object.keys(selectDef.select).map((c) => this.expr.wrap(c)).join(", ") : "*";
|
|
109
|
-
let sql = `INSERT INTO ${table} (${colList}) ${selectSql}`;
|
|
110
|
-
if (def.output != null) {
|
|
111
|
-
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
112
|
-
sql += ` RETURNING ${outputCols}`;
|
|
113
|
-
}
|
|
114
|
-
return { sql };
|
|
115
|
-
}
|
|
116
|
-
//#endregion
|
|
117
|
-
//#region ========== DML - UPDATE ==========
|
|
118
|
-
update(def) {
|
|
119
|
-
const table = this.tableName(def.table);
|
|
120
|
-
const alias = this.expr.wrap(def.as);
|
|
121
|
-
const setParts = Object.entries(def.record).map(
|
|
122
|
-
([col, e]) => `${this.expr.wrap(col)} = ${this.expr.render(e)}`
|
|
123
|
-
);
|
|
124
|
-
let sql = `UPDATE ${table} AS ${alias} SET ${setParts.join(", ")}`;
|
|
125
|
-
if (def.joins != null && def.joins.length > 0) {
|
|
126
|
-
const joinTables = def.joins.map((j) => {
|
|
127
|
-
const from = this.renderFrom(j.from);
|
|
128
|
-
return `${from} AS ${this.expr.wrap(j.as)}`;
|
|
129
|
-
});
|
|
130
|
-
sql += ` FROM ${joinTables.join(", ")}`;
|
|
131
|
-
const joinConditions = def.joins.filter((j) => j.where != null && j.where.length > 0).map((j) => this.expr.renderWhere(j.where));
|
|
132
|
-
if (joinConditions.length > 0) {
|
|
133
|
-
const whereCondition = def.where != null && def.where.length > 0 ? this.expr.renderWhere(def.where) : null;
|
|
134
|
-
const allConditions = whereCondition != null ? [whereCondition, ...joinConditions] : joinConditions;
|
|
135
|
-
sql += ` WHERE ${allConditions.join(" AND ")}`;
|
|
136
|
-
} else {
|
|
137
|
-
sql += this.renderWhere(def.where);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
sql += this.renderWhere(def.where);
|
|
141
|
-
}
|
|
142
|
-
if (def.output != null) {
|
|
143
|
-
const outputCols = def.output.columns.map((c) => `${alias}.${this.expr.wrap(c)}`).join(", ");
|
|
144
|
-
sql += ` RETURNING ${outputCols}`;
|
|
145
|
-
}
|
|
146
|
-
return { sql };
|
|
147
|
-
}
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region ========== DML - DELETE ==========
|
|
150
|
-
delete(def) {
|
|
151
|
-
const table = this.tableName(def.table);
|
|
152
|
-
const alias = this.expr.wrap(def.as);
|
|
153
|
-
let sql = `DELETE FROM ${table} AS ${alias}`;
|
|
154
|
-
if (def.joins != null && def.joins.length > 0) {
|
|
155
|
-
const joinTables = def.joins.map((j) => {
|
|
156
|
-
const from = this.renderFrom(j.from);
|
|
157
|
-
return `${from} AS ${this.expr.wrap(j.as)}`;
|
|
158
|
-
});
|
|
159
|
-
sql += ` USING ${joinTables.join(", ")}`;
|
|
160
|
-
const joinConditions = def.joins.filter((j) => j.where != null && j.where.length > 0).map((j) => this.expr.renderWhere(j.where));
|
|
161
|
-
if (joinConditions.length > 0) {
|
|
162
|
-
const whereCondition = def.where != null && def.where.length > 0 ? this.expr.renderWhere(def.where) : null;
|
|
163
|
-
const allConditions = whereCondition != null ? [whereCondition, ...joinConditions] : joinConditions;
|
|
164
|
-
sql += ` WHERE ${allConditions.join(" AND ")}`;
|
|
165
|
-
} else {
|
|
3
|
+
/**
|
|
4
|
+
* PostgreSQL QueryBuilder
|
|
5
|
+
*
|
|
6
|
+
* PostgreSQL specifics:
|
|
7
|
+
* - OUTPUT: uses RETURNING clause (native support)
|
|
8
|
+
* - TRUNCATE: RESTART IDENTITY option required
|
|
9
|
+
* - UPSERT: CTE approach (INSERT ... ON CONFLICT only supports single unique constraint)
|
|
10
|
+
* - AUTO_INCREMENT: GENERATED BY DEFAULT AS IDENTITY (allows explicit value assignment)
|
|
11
|
+
* - Separate index generation needed when adding FK (unlike MySQL)
|
|
12
|
+
*/
|
|
13
|
+
export class PostgresqlQueryBuilder extends QueryBuilderBase {
|
|
14
|
+
expr = new PostgresqlExprRenderer((def) => this.select(def).sql);
|
|
15
|
+
//#region ========== Utilities ==========
|
|
16
|
+
/** Render table name (PostgreSQL: database is handled by connection, uses schema.table only) */
|
|
17
|
+
tableName(obj) {
|
|
18
|
+
const schema = obj.schema ?? "public";
|
|
19
|
+
return `${this.expr.wrap(schema)}.${this.expr.wrap(obj.name)}`;
|
|
20
|
+
}
|
|
21
|
+
/** Render LIMIT...OFFSET clause */
|
|
22
|
+
renderLimit(limit, top) {
|
|
23
|
+
if (limit != null) {
|
|
24
|
+
const [offset, count] = limit;
|
|
25
|
+
return ` LIMIT ${count} OFFSET ${offset}`;
|
|
26
|
+
}
|
|
27
|
+
if (top != null) {
|
|
28
|
+
return ` LIMIT ${top}`;
|
|
29
|
+
}
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
renderJoin(join) {
|
|
33
|
+
const alias = this.expr.wrap(join.as);
|
|
34
|
+
// Detect if LATERAL JOIN is needed
|
|
35
|
+
if (this.needsLateral(join)) {
|
|
36
|
+
// If from is an array (UNION ALL), use renderFrom(join.from),
|
|
37
|
+
// otherwise (orderBy, top, select, etc.) use renderFrom(join) to generate subquery
|
|
38
|
+
const from = Array.isArray(join.from) ? this.renderFrom(join.from) : this.renderFrom(join);
|
|
39
|
+
return ` LEFT OUTER JOIN LATERAL ${from} AS ${alias} ON TRUE`;
|
|
40
|
+
}
|
|
41
|
+
// Normal JOIN
|
|
42
|
+
const from = this.renderFrom(join.from);
|
|
43
|
+
const where = join.where != null && join.where.length > 0
|
|
44
|
+
? ` ON ${this.expr.renderWhere(join.where)}`
|
|
45
|
+
: " ON TRUE";
|
|
46
|
+
return ` LEFT OUTER JOIN ${from} AS ${alias}${where}`;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region ========== DML - SELECT ==========
|
|
50
|
+
select(def) {
|
|
51
|
+
// WITH (CTE)
|
|
52
|
+
let sql = "";
|
|
53
|
+
if (def.with != null) {
|
|
54
|
+
const { name, base, recursive } = def.with;
|
|
55
|
+
sql += `WITH RECURSIVE ${this.expr.wrap(name)} AS (${this.select(base).sql} UNION ALL ${this.select(recursive).sql}) `;
|
|
56
|
+
}
|
|
57
|
+
// SELECT
|
|
58
|
+
sql += "SELECT";
|
|
59
|
+
if (def.distinct) {
|
|
60
|
+
sql += " DISTINCT";
|
|
61
|
+
}
|
|
62
|
+
// columns
|
|
63
|
+
if (def.select != null) {
|
|
64
|
+
const cols = Object.entries(def.select).map(([alias, expr]) => `${this.expr.render(expr)} AS ${this.expr.wrap(alias)}`);
|
|
65
|
+
sql += ` ${cols.join(", ")}`;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
sql += " *";
|
|
69
|
+
}
|
|
70
|
+
// FROM
|
|
71
|
+
if (def.from != null) {
|
|
72
|
+
const from = this.renderFrom(def.from);
|
|
73
|
+
sql += ` FROM ${from} AS ${this.expr.wrap(def.as)}`;
|
|
74
|
+
}
|
|
75
|
+
// JOINs
|
|
76
|
+
sql += this.renderJoins(def.joins);
|
|
77
|
+
// WHERE
|
|
166
78
|
sql += this.renderWhere(def.where);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
`;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
`;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
`;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
79
|
+
// GROUP BY
|
|
80
|
+
sql += this.renderGroupBy(def.groupBy);
|
|
81
|
+
// HAVING
|
|
82
|
+
sql += this.renderHaving(def.having);
|
|
83
|
+
// ORDER BY
|
|
84
|
+
sql += this.renderOrderBy(def.orderBy);
|
|
85
|
+
// LIMIT
|
|
86
|
+
sql += this.renderLimit(def.limit, def.top);
|
|
87
|
+
// LOCK (FOR UPDATE at end)
|
|
88
|
+
if (def.lock) {
|
|
89
|
+
sql += " FOR UPDATE";
|
|
90
|
+
}
|
|
91
|
+
return { sql };
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region ========== DML - INSERT ==========
|
|
95
|
+
insert(def) {
|
|
96
|
+
const table = this.tableName(def.table);
|
|
97
|
+
if (def.records.length === 0) {
|
|
98
|
+
throw new Error("INSERT에는 최소 1개의 레코드가 필요합니다.");
|
|
99
|
+
}
|
|
100
|
+
const columns = Object.keys(def.records[0]);
|
|
101
|
+
const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
102
|
+
const valuesList = def.records.map((record) => {
|
|
103
|
+
const values = columns.map((c) => this.expr.escapeValue(record[c]));
|
|
104
|
+
return `(${values.join(", ")})`;
|
|
105
|
+
});
|
|
106
|
+
let sql = `INSERT INTO ${table} (${colList})`;
|
|
107
|
+
// GENERATED BY DEFAULT AS IDENTITY, so no additional clause needed for explicit value insertion
|
|
108
|
+
// overrideIdentity parameter is kept for MSSQL (SET IDENTITY_INSERT) compatibility, but
|
|
109
|
+
// PostgreSQL's GENERATED BY DEFAULT automatically allows explicit values
|
|
110
|
+
// (note: OVERRIDING SYSTEM VALUE would be needed if GENERATED ALWAYS was used)
|
|
111
|
+
sql += ` VALUES ${valuesList.join(", ")}`;
|
|
112
|
+
// RETURNING (PostgreSQL native support)
|
|
113
|
+
if (def.output != null) {
|
|
114
|
+
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
115
|
+
sql += ` RETURNING ${outputCols}`;
|
|
116
|
+
}
|
|
117
|
+
return { sql };
|
|
118
|
+
}
|
|
119
|
+
insertIfNotExists(def) {
|
|
120
|
+
const table = this.tableName(def.table);
|
|
121
|
+
const columns = Object.keys(def.record);
|
|
122
|
+
const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
123
|
+
const values = columns.map((c) => this.expr.escapeValue(def.record[c])).join(", ");
|
|
124
|
+
// Render existsSelectQuery as SELECT 1 AS _
|
|
125
|
+
const existsQuerySql = this.select({
|
|
126
|
+
...def.existsSelectQuery,
|
|
127
|
+
select: { _: { type: "value", value: 1 } },
|
|
128
|
+
}).sql;
|
|
129
|
+
let sql = `INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`;
|
|
130
|
+
// RETURNING
|
|
131
|
+
if (def.output != null) {
|
|
132
|
+
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
133
|
+
sql += ` RETURNING ${outputCols}`;
|
|
134
|
+
}
|
|
135
|
+
return { sql };
|
|
136
|
+
}
|
|
137
|
+
insertInto(def) {
|
|
138
|
+
const table = this.tableName(def.table);
|
|
139
|
+
const selectSql = this.select(def.recordsSelectQuery).sql;
|
|
140
|
+
// Extract columns from INSERT INTO SELECT
|
|
141
|
+
const selectDef = def.recordsSelectQuery;
|
|
142
|
+
const colList = selectDef.select != null
|
|
143
|
+
? Object.keys(selectDef.select)
|
|
144
|
+
.map((c) => this.expr.wrap(c))
|
|
145
|
+
.join(", ")
|
|
146
|
+
: "*";
|
|
147
|
+
let sql = `INSERT INTO ${table} (${colList}) ${selectSql}`;
|
|
148
|
+
// RETURNING
|
|
149
|
+
if (def.output != null) {
|
|
150
|
+
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
151
|
+
sql += ` RETURNING ${outputCols}`;
|
|
152
|
+
}
|
|
153
|
+
return { sql };
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region ========== DML - UPDATE ==========
|
|
157
|
+
update(def) {
|
|
158
|
+
const table = this.tableName(def.table);
|
|
159
|
+
const alias = this.expr.wrap(def.as);
|
|
160
|
+
// SET
|
|
161
|
+
const setParts = Object.entries(def.record).map(([col, e]) => `${this.expr.wrap(col)} = ${this.expr.render(e)}`);
|
|
162
|
+
let sql = `UPDATE ${table} AS ${alias} SET ${setParts.join(", ")}`;
|
|
163
|
+
// PostgreSQL: JOINs are handled via FROM clause
|
|
164
|
+
if (def.joins != null && def.joins.length > 0) {
|
|
165
|
+
const joinTables = def.joins.map((j) => {
|
|
166
|
+
const from = this.renderFrom(j.from);
|
|
167
|
+
return `${from} AS ${this.expr.wrap(j.as)}`;
|
|
168
|
+
});
|
|
169
|
+
sql += ` FROM ${joinTables.join(", ")}`;
|
|
170
|
+
// Add JOIN ON conditions to WHERE
|
|
171
|
+
const joinConditions = def.joins
|
|
172
|
+
.filter((j) => j.where != null && j.where.length > 0)
|
|
173
|
+
.map((j) => this.expr.renderWhere(j.where));
|
|
174
|
+
if (joinConditions.length > 0) {
|
|
175
|
+
const whereCondition = def.where != null && def.where.length > 0 ? this.expr.renderWhere(def.where) : null;
|
|
176
|
+
const allConditions = whereCondition != null ? [whereCondition, ...joinConditions] : joinConditions;
|
|
177
|
+
sql += ` WHERE ${allConditions.join(" AND ")}`;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
sql += this.renderWhere(def.where);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
sql += this.renderWhere(def.where);
|
|
185
|
+
}
|
|
186
|
+
// RETURNING
|
|
187
|
+
if (def.output != null) {
|
|
188
|
+
const outputCols = def.output.columns.map((c) => `${alias}.${this.expr.wrap(c)}`).join(", ");
|
|
189
|
+
sql += ` RETURNING ${outputCols}`;
|
|
190
|
+
}
|
|
191
|
+
return { sql };
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region ========== DML - DELETE ==========
|
|
195
|
+
delete(def) {
|
|
196
|
+
const table = this.tableName(def.table);
|
|
197
|
+
const alias = this.expr.wrap(def.as);
|
|
198
|
+
let sql = `DELETE FROM ${table} AS ${alias}`;
|
|
199
|
+
// PostgreSQL: JOINs are handled via USING clause
|
|
200
|
+
if (def.joins != null && def.joins.length > 0) {
|
|
201
|
+
const joinTables = def.joins.map((j) => {
|
|
202
|
+
const from = this.renderFrom(j.from);
|
|
203
|
+
return `${from} AS ${this.expr.wrap(j.as)}`;
|
|
204
|
+
});
|
|
205
|
+
sql += ` USING ${joinTables.join(", ")}`;
|
|
206
|
+
// Add JOIN ON conditions to WHERE
|
|
207
|
+
const joinConditions = def.joins
|
|
208
|
+
.filter((j) => j.where != null && j.where.length > 0)
|
|
209
|
+
.map((j) => this.expr.renderWhere(j.where));
|
|
210
|
+
if (joinConditions.length > 0) {
|
|
211
|
+
const whereCondition = def.where != null && def.where.length > 0 ? this.expr.renderWhere(def.where) : null;
|
|
212
|
+
const allConditions = whereCondition != null ? [whereCondition, ...joinConditions] : joinConditions;
|
|
213
|
+
sql += ` WHERE ${allConditions.join(" AND ")}`;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
sql += this.renderWhere(def.where);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
sql += this.renderWhere(def.where);
|
|
221
|
+
}
|
|
222
|
+
// RETURNING (PostgreSQL: also supported for DELETE)
|
|
223
|
+
if (def.output != null) {
|
|
224
|
+
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
225
|
+
sql += ` RETURNING ${outputCols}`;
|
|
226
|
+
}
|
|
227
|
+
return { sql };
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region ========== DML - UPSERT ==========
|
|
231
|
+
upsert(def) {
|
|
232
|
+
// PostgreSQL: CTE approach (uses CTE for generality since ON CONFLICT only supports single unique constraint)
|
|
233
|
+
const table = this.tableName(def.table);
|
|
234
|
+
const alias = this.expr.wrap(def.existsSelectQuery.as);
|
|
235
|
+
// UPDATE SET part
|
|
236
|
+
const updateSetParts = Object.entries(def.updateRecord).map(([col, e]) => `${this.expr.wrap(col)} = ${this.expr.render(e)}`);
|
|
237
|
+
// INSERT part
|
|
238
|
+
const insertColumns = Object.keys(def.insertRecord);
|
|
239
|
+
const insertColList = insertColumns.map((c) => this.expr.wrap(c)).join(", ");
|
|
240
|
+
const insertValues = insertColumns.map((c) => this.expr.render(def.insertRecord[c])).join(", ");
|
|
241
|
+
// WHERE condition
|
|
242
|
+
const whereCondition = def.existsSelectQuery.where != null && def.existsSelectQuery.where.length > 0
|
|
243
|
+
? this.expr.renderWhere(def.existsSelectQuery.where)
|
|
244
|
+
: "TRUE";
|
|
245
|
+
// OUTPUT column
|
|
246
|
+
const outputCols = def.output != null ? def.output.columns.map((c) => this.expr.wrap(c)).join(", ") : "*";
|
|
247
|
+
// CTE-based UPSERT
|
|
248
|
+
let sql = `WITH matched AS (\n`;
|
|
249
|
+
sql += ` SELECT ${alias}.* FROM ${table} AS ${alias} WHERE ${whereCondition}\n`;
|
|
250
|
+
sql += `),\n`;
|
|
251
|
+
sql += `updated AS (\n`;
|
|
252
|
+
sql += ` UPDATE ${table} AS ${alias} SET ${updateSetParts.join(", ")}\n`;
|
|
253
|
+
sql += ` WHERE ${whereCondition}\n`;
|
|
254
|
+
sql += ` RETURNING ${outputCols}\n`;
|
|
255
|
+
sql += `),\n`;
|
|
256
|
+
sql += `inserted AS (\n`;
|
|
257
|
+
sql += ` INSERT INTO ${table} (${insertColList})\n`;
|
|
258
|
+
sql += ` SELECT ${insertValues}\n`;
|
|
259
|
+
sql += ` WHERE NOT EXISTS (SELECT 1 FROM matched)\n`;
|
|
260
|
+
sql += ` RETURNING ${outputCols}\n`;
|
|
261
|
+
sql += `)\n`;
|
|
262
|
+
sql += `SELECT * FROM updated UNION ALL SELECT * FROM inserted`;
|
|
263
|
+
return { sql };
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region ========== DDL - Table ==========
|
|
267
|
+
createTable(def) {
|
|
268
|
+
const table = this.tableName(def.table);
|
|
269
|
+
const colDefs = def.columns.map((col) => {
|
|
270
|
+
let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
|
|
271
|
+
// nullable: true → NULL, else → NOT NULL
|
|
272
|
+
if (col.nullable === true) {
|
|
273
|
+
colSql += " NULL";
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
colSql += " NOT NULL";
|
|
277
|
+
}
|
|
278
|
+
if (col.autoIncrement) {
|
|
279
|
+
colSql += " GENERATED BY DEFAULT AS IDENTITY";
|
|
280
|
+
}
|
|
281
|
+
if (col.default !== undefined) {
|
|
282
|
+
colSql += ` DEFAULT ${this.expr.escapeValue(col.default)}`;
|
|
283
|
+
}
|
|
284
|
+
return colSql;
|
|
285
|
+
});
|
|
286
|
+
// Primary Key with CONSTRAINT name
|
|
287
|
+
if (def.primaryKey != null && def.primaryKey.length > 0) {
|
|
288
|
+
const pkCols = def.primaryKey.map((c) => this.expr.wrap(c)).join(", ");
|
|
289
|
+
const pkName = this.expr.wrap(`PK_${def.table.name}`);
|
|
290
|
+
colDefs.push(`CONSTRAINT ${pkName} PRIMARY KEY (${pkCols})`);
|
|
291
|
+
}
|
|
292
|
+
return { sql: `CREATE TABLE ${table} (\n ${colDefs.join(",\n ")}\n)` };
|
|
293
|
+
}
|
|
294
|
+
dropTable(def) {
|
|
295
|
+
return { sql: `DROP TABLE ${this.tableName(def.table)}` };
|
|
296
|
+
}
|
|
297
|
+
renameTable(def) {
|
|
298
|
+
return {
|
|
299
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} RENAME TO ${this.expr.wrap(def.newName)}`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
truncate(def) {
|
|
303
|
+
// PostgreSQL: reset sequence with RESTART IDENTITY
|
|
304
|
+
return { sql: `TRUNCATE TABLE ${this.tableName(def.table)} RESTART IDENTITY` };
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region ========== DDL - Column ==========
|
|
308
|
+
addColumn(def) {
|
|
309
|
+
const table = this.tableName(def.table);
|
|
310
|
+
const col = def.column;
|
|
311
|
+
let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
|
|
312
|
+
// nullable: true → NULL, else → NOT NULL
|
|
313
|
+
if (col.nullable === true) {
|
|
314
|
+
colSql += " NULL";
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
colSql += " NOT NULL";
|
|
318
|
+
}
|
|
319
|
+
if (col.autoIncrement) {
|
|
320
|
+
colSql += " GENERATED BY DEFAULT AS IDENTITY";
|
|
321
|
+
}
|
|
322
|
+
if (col.default !== undefined) {
|
|
323
|
+
colSql += ` DEFAULT ${this.expr.escapeValue(col.default)}`;
|
|
324
|
+
}
|
|
325
|
+
return { sql: `ALTER TABLE ${table} ADD COLUMN ${colSql}` };
|
|
326
|
+
}
|
|
327
|
+
dropColumn(def) {
|
|
328
|
+
return {
|
|
329
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP COLUMN ${this.expr.wrap(def.column)}`,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
modifyColumn(def) {
|
|
333
|
+
const table = this.tableName(def.table);
|
|
334
|
+
const col = def.column;
|
|
335
|
+
// PostgreSQL: ALTER COLUMN requires multiple ALTER statements
|
|
336
|
+
const parts = [];
|
|
337
|
+
// Change TYPE
|
|
338
|
+
parts.push(`ALTER COLUMN ${this.expr.wrap(col.name)} TYPE ${this.expr.renderDataType(col.dataType)}`);
|
|
339
|
+
// Change NULL constraint
|
|
340
|
+
if (col.nullable === false) {
|
|
341
|
+
parts.push(`ALTER COLUMN ${this.expr.wrap(col.name)} SET NOT NULL`);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
parts.push(`ALTER COLUMN ${this.expr.wrap(col.name)} DROP NOT NULL`);
|
|
345
|
+
}
|
|
346
|
+
// Change DEFAULT
|
|
347
|
+
if (col.default !== undefined) {
|
|
348
|
+
parts.push(`ALTER COLUMN ${this.expr.wrap(col.name)} SET DEFAULT ${this.expr.escapeValue(col.default)}`);
|
|
349
|
+
}
|
|
350
|
+
return { sql: `ALTER TABLE ${table} ${parts.join(", ")}` };
|
|
351
|
+
}
|
|
352
|
+
renameColumn(def) {
|
|
353
|
+
const table = this.tableName(def.table);
|
|
354
|
+
return {
|
|
355
|
+
sql: `ALTER TABLE ${table} RENAME COLUMN ${this.expr.wrap(def.column)} TO ${this.expr.wrap(def.newName)}`,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region ========== DDL - Constraint ==========
|
|
360
|
+
addPrimaryKey(def) {
|
|
361
|
+
const table = this.tableName(def.table);
|
|
362
|
+
const cols = def.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
363
|
+
const pkName = `PK_${def.table.name}`;
|
|
364
|
+
return {
|
|
365
|
+
sql: `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(pkName)} PRIMARY KEY (${cols})`,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
dropPrimaryKey(def) {
|
|
369
|
+
const table = this.tableName(def.table);
|
|
370
|
+
const pkName = `PK_${def.table.name}`;
|
|
371
|
+
return { sql: `ALTER TABLE ${table} DROP CONSTRAINT ${this.expr.wrap(pkName)}` };
|
|
372
|
+
}
|
|
373
|
+
addForeignKey(def) {
|
|
374
|
+
const table = this.tableName(def.table);
|
|
375
|
+
const fk = def.foreignKey;
|
|
376
|
+
const fkCols = fk.fkColumns.map((c) => this.expr.wrap(c)).join(", ");
|
|
377
|
+
const targetTable = this.tableName(fk.targetTable);
|
|
378
|
+
const targetCols = fk.targetPkColumns.map((c) => this.expr.wrap(c)).join(", ");
|
|
379
|
+
let sql = `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(fk.name)} FOREIGN KEY (${fkCols}) REFERENCES ${targetTable} (${targetCols})`;
|
|
380
|
+
// PostgreSQL: separate index generation needed for FK
|
|
381
|
+
const idxName = `IDX_${def.table.name}_${fk.name.replace(/^FK_/, "")}`;
|
|
382
|
+
sql += `;\nCREATE INDEX ${this.expr.wrap(idxName)} ON ${table} (${fkCols});`;
|
|
383
|
+
return { sql };
|
|
384
|
+
}
|
|
385
|
+
dropForeignKey(def) {
|
|
386
|
+
return {
|
|
387
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP CONSTRAINT ${this.expr.wrap(def.foreignKey)}`,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
addIndex(def) {
|
|
391
|
+
const table = this.tableName(def.table);
|
|
392
|
+
const idx = def.index;
|
|
393
|
+
const cols = idx.columns.map((c) => `${this.expr.wrap(c.name)} ${c.orderBy}`).join(", ");
|
|
394
|
+
const unique = idx.unique ? "UNIQUE " : "";
|
|
395
|
+
return { sql: `CREATE ${unique}INDEX ${this.expr.wrap(idx.name)} ON ${table} (${cols})` };
|
|
396
|
+
}
|
|
397
|
+
dropIndex(def) {
|
|
398
|
+
// PostgreSQL: indexes are unique at schema level so table name is not needed, but schema must be specified
|
|
399
|
+
const schema = def.table.schema ?? "public";
|
|
400
|
+
return { sql: `DROP INDEX ${this.expr.wrap(schema)}.${this.expr.wrap(def.index)}` };
|
|
401
|
+
}
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region ========== DDL - View/Procedure ==========
|
|
404
|
+
createView(def) {
|
|
405
|
+
const view = this.tableName(def.view);
|
|
406
|
+
const selectSql = this.select(def.queryDef).sql;
|
|
407
|
+
return { sql: `CREATE OR REPLACE VIEW ${view} AS ${selectSql}` };
|
|
408
|
+
}
|
|
409
|
+
dropView(def) {
|
|
410
|
+
return { sql: `DROP VIEW IF EXISTS ${this.tableName(def.view)}` };
|
|
411
|
+
}
|
|
412
|
+
createProc(def) {
|
|
413
|
+
const proc = this.tableName(def.procedure);
|
|
414
|
+
// Process params
|
|
415
|
+
const paramList = def.params
|
|
416
|
+
?.map((p) => {
|
|
417
|
+
let sql = `${this.expr.wrap(p.name)} ${this.expr.renderDataType(p.dataType)}`;
|
|
418
|
+
if (p.default !== undefined) {
|
|
419
|
+
sql += ` DEFAULT ${this.expr.escapeValue(p.default)}`;
|
|
420
|
+
}
|
|
421
|
+
return sql;
|
|
422
|
+
})
|
|
423
|
+
.join(", ") ?? "";
|
|
424
|
+
// Process returns
|
|
425
|
+
let returnClause = "VOID";
|
|
426
|
+
if (def.returns && def.returns.length > 0) {
|
|
427
|
+
const returnFields = def.returns
|
|
428
|
+
.map((r) => `${this.expr.wrap(r.name)} ${this.expr.renderDataType(r.dataType)}`)
|
|
429
|
+
.join(", ");
|
|
430
|
+
returnClause = `TABLE(${returnFields})`;
|
|
431
|
+
}
|
|
432
|
+
let sql = `CREATE OR REPLACE FUNCTION ${proc}(${paramList})\n`;
|
|
433
|
+
sql += `RETURNS ${returnClause} AS $$\n`;
|
|
434
|
+
sql += `BEGIN\n`;
|
|
435
|
+
sql += def.query;
|
|
436
|
+
if (!def.query.trim().endsWith(";")) {
|
|
437
|
+
sql += ";";
|
|
438
|
+
}
|
|
439
|
+
sql += `\nEND;\n`;
|
|
440
|
+
sql += `$$ LANGUAGE plpgsql`;
|
|
441
|
+
return { sql };
|
|
442
|
+
}
|
|
443
|
+
dropProc(def) {
|
|
444
|
+
return { sql: `DROP FUNCTION IF EXISTS ${this.tableName(def.procedure)}()` };
|
|
445
|
+
}
|
|
446
|
+
execProc(def) {
|
|
447
|
+
const proc = this.tableName(def.procedure);
|
|
448
|
+
if (def.params == null || Object.keys(def.params).length === 0) {
|
|
449
|
+
return { sql: `SELECT ${proc}()` };
|
|
450
|
+
}
|
|
451
|
+
const params = Object.values(def.params)
|
|
452
|
+
.map((p) => this.expr.render(p))
|
|
453
|
+
.join(", ");
|
|
454
|
+
return { sql: `SELECT ${proc}(${params})` };
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region ========== Utils ==========
|
|
458
|
+
clearSchema(def) {
|
|
459
|
+
const schemaName = def.schema ?? "public";
|
|
460
|
+
// SQL 인젝션 방지: schema 이름 유효성 검사
|
|
461
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schemaName)) {
|
|
462
|
+
throw new Error(`잘못된 schema 이름: ${schemaName}`);
|
|
463
|
+
}
|
|
464
|
+
const schema = this.expr.escapeString(schemaName);
|
|
465
|
+
return {
|
|
466
|
+
sql: `
|
|
467
|
+
DO $$
|
|
468
|
+
DECLARE
|
|
469
|
+
r RECORD;
|
|
470
|
+
BEGIN
|
|
471
|
+
-- FK 제약조건 삭제
|
|
472
|
+
FOR r IN (SELECT conname, conrelid::regclass AS tablename
|
|
473
|
+
FROM pg_constraint
|
|
474
|
+
WHERE contype = 'f' AND connamespace = '${schema}'::regnamespace)
|
|
475
|
+
LOOP
|
|
476
|
+
EXECUTE 'ALTER TABLE ' || r.tablename || ' DROP CONSTRAINT ' || quote_ident(r.conname);
|
|
477
|
+
END LOOP;
|
|
478
|
+
|
|
479
|
+
-- 테이블 삭제
|
|
480
|
+
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = '${schema}')
|
|
481
|
+
LOOP
|
|
482
|
+
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
|
|
483
|
+
END LOOP;
|
|
484
|
+
|
|
485
|
+
-- 뷰 삭제
|
|
486
|
+
FOR r IN (SELECT viewname FROM pg_views WHERE schemaname = '${schema}')
|
|
487
|
+
LOOP
|
|
488
|
+
EXECUTE 'DROP VIEW IF EXISTS ' || quote_ident(r.viewname) || ' CASCADE';
|
|
489
|
+
END LOOP;
|
|
490
|
+
|
|
491
|
+
-- 함수 삭제
|
|
492
|
+
FOR r IN (SELECT proname, pg_get_function_identity_arguments(oid) AS args
|
|
493
|
+
FROM pg_proc WHERE pronamespace = '${schema}'::regnamespace)
|
|
494
|
+
LOOP
|
|
495
|
+
EXECUTE 'DROP FUNCTION IF EXISTS ' || quote_ident(r.proname) || '(' || r.args || ') CASCADE';
|
|
496
|
+
END LOOP;
|
|
497
|
+
END $$`,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
schemaExists(def) {
|
|
501
|
+
const schemaName = def.schema ?? "public";
|
|
502
|
+
const schema = this.expr.escapeString(schemaName);
|
|
503
|
+
return { sql: `SELECT nspname FROM pg_namespace WHERE nspname = '${schema}'` };
|
|
504
|
+
}
|
|
505
|
+
switchFk(def) {
|
|
506
|
+
const table = this.tableName(def.table);
|
|
507
|
+
if (def.enabled) {
|
|
508
|
+
// PostgreSQL: enable all FK triggers on the table
|
|
509
|
+
return { sql: `ALTER TABLE ${table} ENABLE TRIGGER ALL` };
|
|
510
|
+
}
|
|
511
|
+
// PostgreSQL: disable all FK triggers on the table
|
|
512
|
+
return { sql: `ALTER TABLE ${table} DISABLE TRIGGER ALL` };
|
|
513
|
+
}
|
|
460
514
|
}
|
|
461
|
-
|
|
462
|
-
PostgresqlQueryBuilder
|
|
463
|
-
};
|
|
464
|
-
//# sourceMappingURL=postgresql-query-builder.js.map
|
|
515
|
+
//# sourceMappingURL=postgresql-query-builder.js.map
|