@simplysm/orm-common 13.0.100 → 14.0.4
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 +90 -147
- 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 +108 -108
- 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 +5 -5
- 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.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- 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 +5 -5
- 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.d.ts +10 -10
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- 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 +5 -5
- 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.d.ts +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- 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 +99 -99
- 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.d.ts +21 -21
- 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.d.ts +11 -11
- package/dist/utils/result-parser.js +362 -221
- package/dist/utils/result-parser.js.map +1 -6
- package/docs/core.md +117 -145
- package/docs/expression.md +186 -203
- package/docs/query-builder.md +75 -42
- package/docs/queryable.md +189 -151
- package/docs/schema-builders.md +172 -283
- package/docs/types.md +229 -173
- package/package.json +7 -5
- 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 +152 -152
- package/src/exec/search-parser.ts +50 -50
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +118 -118
- 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 +28 -28
- package/src/query-builder/mssql/mssql-query-builder.ts +37 -37
- package/src/query-builder/mysql/mysql-expr-renderer.ts +29 -29
- package/src/query-builder/mysql/mysql-query-builder.ts +70 -70
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +22 -22
- package/src/query-builder/postgresql/postgresql-query-builder.ts +54 -54
- 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 +102 -102
- package/src/schema/procedure-builder.ts +52 -52
- package/src/schema/table-builder.ts +56 -56
- package/src/schema/view-builder.ts +47 -47
- package/src/types/column.ts +24 -24
- 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 +88 -88
- 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 고유 사항:
|
|
7
|
+
* - OUTPUT: RETURNING 절 사용 (네이티브 지원)
|
|
8
|
+
* - TRUNCATE: RESTART IDENTITY 옵션 필요
|
|
9
|
+
* - UPSERT: CTE 방식 (INSERT ... ON CONFLICT는 단일 유니크 제약조건만 지원)
|
|
10
|
+
* - AUTO_INCREMENT: GENERATED BY DEFAULT AS IDENTITY (명시적 값 할당 가능)
|
|
11
|
+
* - FK 추가 시 별도 인덱스 생성 필요 (MySQL과 다름)
|
|
12
|
+
*/
|
|
13
|
+
export class PostgresqlQueryBuilder extends QueryBuilderBase {
|
|
14
|
+
expr = new PostgresqlExprRenderer((def) => this.select(def).sql);
|
|
15
|
+
//#region ========== Utilities ==========
|
|
16
|
+
/** 테이블명 렌더링 (PostgreSQL: database는 연결에서 처리, schema.table만 사용) */
|
|
17
|
+
tableName(obj) {
|
|
18
|
+
const schema = obj.schema ?? "public";
|
|
19
|
+
return `${this.expr.wrap(schema)}.${this.expr.wrap(obj.name)}`;
|
|
20
|
+
}
|
|
21
|
+
/** LIMIT...OFFSET 절 렌더링 */
|
|
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
|
+
// LATERAL JOIN이 필요한지 감지
|
|
35
|
+
if (this.needsLateral(join)) {
|
|
36
|
+
// from이 배열(UNION ALL)이면 renderFrom(join.from) 사용,
|
|
37
|
+
// 그 외(orderBy, top, select 등)이면 renderFrom(join)으로 서브쿼리 생성
|
|
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
|
+
// 일반 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 추가)
|
|
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이므로 명시적 값 삽입에 추가 구문 불필요
|
|
108
|
+
// overrideIdentity 파라미터는 MSSQL (SET IDENTITY_INSERT) 호환성을 위해 유지하지만
|
|
109
|
+
// PostgreSQL의 GENERATED BY DEFAULT는 자동으로 명시적 값을 허용
|
|
110
|
+
// (참고: GENERATED ALWAYS를 사용했다면 OVERRIDING SYSTEM VALUE가 필요)
|
|
111
|
+
sql += ` VALUES ${valuesList.join(", ")}`;
|
|
112
|
+
// RETURNING (PostgreSQL 네이티브 지원)
|
|
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
|
+
// existsSelectQuery를 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
|
+
// INSERT INTO SELECT에서 column 추출
|
|
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: JOIN은 FROM 절로 처리
|
|
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
|
+
// JOIN ON 조건을 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: JOIN은 USING 절로 처리
|
|
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
|
+
// JOIN ON 조건을 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: 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 방식 (ON CONFLICT는 단일 유니크 제약조건만 지원하므로 범용성을 위해 CTE 사용)
|
|
233
|
+
const table = this.tableName(def.table);
|
|
234
|
+
const alias = this.expr.wrap(def.existsSelectQuery.as);
|
|
235
|
+
// UPDATE SET 부분
|
|
236
|
+
const updateSetParts = Object.entries(def.updateRecord).map(([col, e]) => `${this.expr.wrap(col)} = ${this.expr.render(e)}`);
|
|
237
|
+
// INSERT 부분
|
|
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 조건
|
|
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 기반 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, 아니면 → 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
|
+
// CONSTRAINT 이름이 포함된 Primary Key
|
|
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: 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은 여러 ALTER 문이 필요
|
|
336
|
+
const parts = [];
|
|
337
|
+
// TYPE 변경
|
|
338
|
+
parts.push(`ALTER COLUMN ${this.expr.wrap(col.name)} TYPE ${this.expr.renderDataType(col.dataType)}`);
|
|
339
|
+
// NULL 제약조건 변경
|
|
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
|
+
// 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: 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: 인덱스는 스키마 수준에서 고유하므로 테이블명은 불필요하지만 스키마 지정 필요
|
|
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
|
+
// 파라미터 처리
|
|
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
|
+
// 반환 타입 처리
|
|
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: 테이블의 모든 FK 트리거 활성화
|
|
509
|
+
return { sql: `ALTER TABLE ${table} ENABLE TRIGGER ALL` };
|
|
510
|
+
}
|
|
511
|
+
// PostgreSQL: 테이블의 모든 FK 트리거 비활성화
|
|
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
|