@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.
Files changed (238) hide show
  1. package/dist/create-db-context.d.ts +10 -10
  2. package/dist/create-db-context.js +312 -276
  3. package/dist/create-db-context.js.map +1 -6
  4. package/dist/ddl/column-ddl.d.ts +4 -4
  5. package/dist/ddl/column-ddl.js +41 -35
  6. package/dist/ddl/column-ddl.js.map +1 -6
  7. package/dist/ddl/initialize.d.ts +17 -17
  8. package/dist/ddl/initialize.js +200 -142
  9. package/dist/ddl/initialize.js.map +1 -6
  10. package/dist/ddl/relation-ddl.d.ts +6 -6
  11. package/dist/ddl/relation-ddl.js +55 -48
  12. package/dist/ddl/relation-ddl.js.map +1 -6
  13. package/dist/ddl/schema-ddl.d.ts +4 -4
  14. package/dist/ddl/schema-ddl.js +21 -15
  15. package/dist/ddl/schema-ddl.js.map +1 -6
  16. package/dist/ddl/table-ddl.d.ts +20 -20
  17. package/dist/ddl/table-ddl.js +139 -93
  18. package/dist/ddl/table-ddl.js.map +1 -6
  19. package/dist/define-db-context.js +10 -13
  20. package/dist/define-db-context.js.map +1 -6
  21. package/dist/errors/db-transaction-error.d.ts +15 -15
  22. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  23. package/dist/errors/db-transaction-error.js +53 -19
  24. package/dist/errors/db-transaction-error.js.map +1 -6
  25. package/dist/exec/executable.d.ts +23 -23
  26. package/dist/exec/executable.js +94 -40
  27. package/dist/exec/executable.js.map +1 -6
  28. package/dist/exec/queryable.d.ts +97 -97
  29. package/dist/exec/queryable.js +1310 -1204
  30. package/dist/exec/queryable.js.map +1 -6
  31. package/dist/exec/search-parser.d.ts +31 -31
  32. package/dist/exec/search-parser.d.ts.map +1 -1
  33. package/dist/exec/search-parser.js +158 -59
  34. package/dist/exec/search-parser.js.map +1 -6
  35. package/dist/expr/expr-unit.d.ts +4 -4
  36. package/dist/expr/expr-unit.js +24 -18
  37. package/dist/expr/expr-unit.js.map +1 -6
  38. package/dist/expr/expr.d.ts +6 -6
  39. package/dist/expr/expr.js +1872 -1844
  40. package/dist/expr/expr.js.map +1 -6
  41. package/dist/index.js +23 -1
  42. package/dist/index.js.map +1 -6
  43. package/dist/models/system-migration.js +7 -7
  44. package/dist/models/system-migration.js.map +1 -6
  45. package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
  46. package/dist/query-builder/base/expr-renderer-base.js +27 -21
  47. package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
  48. package/dist/query-builder/base/query-builder-base.d.ts +21 -21
  49. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  50. package/dist/query-builder/base/query-builder-base.js +90 -80
  51. package/dist/query-builder/base/query-builder-base.js.map +1 -6
  52. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
  53. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  54. package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
  55. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
  56. package/dist/query-builder/mssql/mssql-query-builder.js +483 -443
  57. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
  58. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
  59. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  60. package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
  61. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
  62. package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
  63. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
  64. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
  65. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  66. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
  67. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
  68. package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
  69. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
  70. package/dist/query-builder/query-builder.d.ts +1 -1
  71. package/dist/query-builder/query-builder.js +13 -13
  72. package/dist/query-builder/query-builder.js.map +1 -6
  73. package/dist/schema/factory/column-builder.d.ts +84 -84
  74. package/dist/schema/factory/column-builder.js +248 -185
  75. package/dist/schema/factory/column-builder.js.map +1 -6
  76. package/dist/schema/factory/index-builder.d.ts +38 -38
  77. package/dist/schema/factory/index-builder.js +144 -85
  78. package/dist/schema/factory/index-builder.js.map +1 -6
  79. package/dist/schema/factory/relation-builder.d.ts +91 -91
  80. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  81. package/dist/schema/factory/relation-builder.js +274 -136
  82. package/dist/schema/factory/relation-builder.js.map +1 -6
  83. package/dist/schema/procedure-builder.d.ts +51 -51
  84. package/dist/schema/procedure-builder.d.ts.map +1 -1
  85. package/dist/schema/procedure-builder.js +205 -131
  86. package/dist/schema/procedure-builder.js.map +1 -6
  87. package/dist/schema/table-builder.d.ts +55 -55
  88. package/dist/schema/table-builder.d.ts.map +1 -1
  89. package/dist/schema/table-builder.js +274 -205
  90. package/dist/schema/table-builder.js.map +1 -6
  91. package/dist/schema/view-builder.d.ts +44 -44
  92. package/dist/schema/view-builder.d.ts.map +1 -1
  93. package/dist/schema/view-builder.js +189 -116
  94. package/dist/schema/view-builder.js.map +1 -6
  95. package/dist/types/column.js +60 -30
  96. package/dist/types/column.js.map +1 -6
  97. package/dist/types/db-context-def.d.ts +9 -9
  98. package/dist/types/db-context-def.js +2 -1
  99. package/dist/types/db-context-def.js.map +1 -6
  100. package/dist/types/db.d.ts +47 -47
  101. package/dist/types/db.js +15 -5
  102. package/dist/types/db.js.map +1 -6
  103. package/dist/types/expr.d.ts +81 -81
  104. package/dist/types/expr.d.ts.map +1 -1
  105. package/dist/types/expr.js +3 -1
  106. package/dist/types/expr.js.map +1 -6
  107. package/dist/types/query-def.d.ts +46 -46
  108. package/dist/types/query-def.d.ts.map +1 -1
  109. package/dist/types/query-def.js +31 -24
  110. package/dist/types/query-def.js.map +1 -6
  111. package/dist/utils/result-parser.js +362 -221
  112. package/dist/utils/result-parser.js.map +1 -6
  113. package/package.json +5 -7
  114. package/src/create-db-context.ts +31 -31
  115. package/src/ddl/column-ddl.ts +4 -4
  116. package/src/ddl/initialize.ts +38 -38
  117. package/src/ddl/relation-ddl.ts +6 -6
  118. package/src/ddl/schema-ddl.ts +4 -4
  119. package/src/ddl/table-ddl.ts +24 -24
  120. package/src/errors/db-transaction-error.ts +13 -13
  121. package/src/exec/executable.ts +25 -25
  122. package/src/exec/queryable.ts +134 -134
  123. package/src/exec/search-parser.ts +50 -50
  124. package/src/expr/expr-unit.ts +4 -4
  125. package/src/expr/expr.ts +13 -13
  126. package/src/index.ts +8 -8
  127. package/src/models/system-migration.ts +1 -1
  128. package/src/query-builder/base/expr-renderer-base.ts +21 -21
  129. package/src/query-builder/base/query-builder-base.ts +33 -33
  130. package/src/query-builder/mssql/mssql-expr-renderer.ts +11 -11
  131. package/src/query-builder/mssql/mssql-query-builder.ts +11 -11
  132. package/src/query-builder/mysql/mysql-expr-renderer.ts +15 -15
  133. package/src/query-builder/mysql/mysql-query-builder.ts +3 -3
  134. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +9 -9
  135. package/src/query-builder/postgresql/postgresql-query-builder.ts +7 -7
  136. package/src/query-builder/query-builder.ts +1 -1
  137. package/src/schema/factory/column-builder.ts +86 -86
  138. package/src/schema/factory/index-builder.ts +38 -38
  139. package/src/schema/factory/relation-builder.ts +93 -93
  140. package/src/schema/procedure-builder.ts +52 -52
  141. package/src/schema/table-builder.ts +56 -56
  142. package/src/schema/view-builder.ts +45 -45
  143. package/src/types/column.ts +1 -1
  144. package/src/types/db-context-def.ts +15 -15
  145. package/src/types/db.ts +50 -50
  146. package/src/types/expr.ts +103 -103
  147. package/src/types/query-def.ts +50 -50
  148. package/src/utils/result-parser.ts +39 -39
  149. package/README.md +0 -192
  150. package/docs/core.md +0 -234
  151. package/docs/expression.md +0 -234
  152. package/docs/query-builder.md +0 -93
  153. package/docs/queryable.md +0 -198
  154. package/docs/schema-builders.md +0 -463
  155. package/docs/types.md +0 -445
  156. package/docs/utilities.md +0 -27
  157. package/tests/db-context/create-db-context.spec.ts +0 -193
  158. package/tests/db-context/define-db-context.spec.ts +0 -17
  159. package/tests/ddl/basic.expected.ts +0 -341
  160. package/tests/ddl/basic.spec.ts +0 -557
  161. package/tests/ddl/column-builder.expected.ts +0 -310
  162. package/tests/ddl/column-builder.spec.ts +0 -525
  163. package/tests/ddl/index-builder.expected.ts +0 -38
  164. package/tests/ddl/index-builder.spec.ts +0 -148
  165. package/tests/ddl/procedure-builder.expected.ts +0 -52
  166. package/tests/ddl/procedure-builder.spec.ts +0 -128
  167. package/tests/ddl/relation-builder.expected.ts +0 -36
  168. package/tests/ddl/relation-builder.spec.ts +0 -171
  169. package/tests/ddl/table-builder.expected.ts +0 -113
  170. package/tests/ddl/table-builder.spec.ts +0 -399
  171. package/tests/ddl/view-builder.expected.ts +0 -38
  172. package/tests/ddl/view-builder.spec.ts +0 -116
  173. package/tests/dml/delete.expected.ts +0 -96
  174. package/tests/dml/delete.spec.ts +0 -127
  175. package/tests/dml/insert.expected.ts +0 -192
  176. package/tests/dml/insert.spec.ts +0 -210
  177. package/tests/dml/update.expected.ts +0 -176
  178. package/tests/dml/update.spec.ts +0 -222
  179. package/tests/dml/upsert.expected.ts +0 -215
  180. package/tests/dml/upsert.spec.ts +0 -190
  181. package/tests/errors/queryable-errors.spec.ts +0 -126
  182. package/tests/escape.spec.ts +0 -59
  183. package/tests/examples/pivot.expected.ts +0 -211
  184. package/tests/examples/pivot.spec.ts +0 -200
  185. package/tests/examples/sampling.expected.ts +0 -69
  186. package/tests/examples/sampling.spec.ts +0 -42
  187. package/tests/examples/unpivot.expected.ts +0 -120
  188. package/tests/examples/unpivot.spec.ts +0 -161
  189. package/tests/exec/search-parser.spec.ts +0 -267
  190. package/tests/executable/basic.expected.ts +0 -18
  191. package/tests/executable/basic.spec.ts +0 -54
  192. package/tests/expr/comparison.expected.ts +0 -282
  193. package/tests/expr/comparison.spec.ts +0 -334
  194. package/tests/expr/conditional.expected.ts +0 -134
  195. package/tests/expr/conditional.spec.ts +0 -249
  196. package/tests/expr/date.expected.ts +0 -332
  197. package/tests/expr/date.spec.ts +0 -459
  198. package/tests/expr/math.expected.ts +0 -62
  199. package/tests/expr/math.spec.ts +0 -59
  200. package/tests/expr/string.expected.ts +0 -218
  201. package/tests/expr/string.spec.ts +0 -300
  202. package/tests/expr/utility.expected.ts +0 -147
  203. package/tests/expr/utility.spec.ts +0 -155
  204. package/tests/select/basic.expected.ts +0 -322
  205. package/tests/select/basic.spec.ts +0 -433
  206. package/tests/select/filter.expected.ts +0 -357
  207. package/tests/select/filter.spec.ts +0 -954
  208. package/tests/select/group.expected.ts +0 -169
  209. package/tests/select/group.spec.ts +0 -159
  210. package/tests/select/join.expected.ts +0 -582
  211. package/tests/select/join.spec.ts +0 -692
  212. package/tests/select/order.expected.ts +0 -150
  213. package/tests/select/order.spec.ts +0 -140
  214. package/tests/select/recursive-cte.expected.ts +0 -244
  215. package/tests/select/recursive-cte.spec.ts +0 -514
  216. package/tests/select/result-meta.spec.ts +0 -270
  217. package/tests/select/subquery.expected.ts +0 -363
  218. package/tests/select/subquery.spec.ts +0 -441
  219. package/tests/select/view.expected.ts +0 -155
  220. package/tests/select/view.spec.ts +0 -235
  221. package/tests/select/window.expected.ts +0 -345
  222. package/tests/select/window.spec.ts +0 -433
  223. package/tests/setup/MockExecutor.ts +0 -18
  224. package/tests/setup/TestDbContext.ts +0 -59
  225. package/tests/setup/models/Company.ts +0 -13
  226. package/tests/setup/models/Employee.ts +0 -10
  227. package/tests/setup/models/MonthlySales.ts +0 -11
  228. package/tests/setup/models/Post.ts +0 -16
  229. package/tests/setup/models/Sales.ts +0 -10
  230. package/tests/setup/models/User.ts +0 -19
  231. package/tests/setup/procedure/GetAllUsers.ts +0 -9
  232. package/tests/setup/procedure/GetUserById.ts +0 -12
  233. package/tests/setup/test-utils.ts +0 -72
  234. package/tests/setup/views/ActiveUsers.ts +0 -8
  235. package/tests/setup/views/UserSummary.ts +0 -11
  236. package/tests/types/nullable-queryable-record.spec.ts +0 -97
  237. package/tests/utils/result-parser-perf.spec.ts +0 -143
  238. 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
- class PostgresqlQueryBuilder extends QueryBuilderBase {
4
- expr = new PostgresqlExprRenderer((def) => this.select(def).sql);
5
- //#region ========== Utilities ==========
6
- /** Render table name (PostgreSQL: database is handled by connection, uses schema.table only) */
7
- tableName(obj) {
8
- const schema = obj.schema ?? "public";
9
- return `${this.expr.wrap(schema)}.${this.expr.wrap(obj.name)}`;
10
- }
11
- /** Render LIMIT...OFFSET clause */
12
- renderLimit(limit, top) {
13
- if (limit != null) {
14
- const [offset, count] = limit;
15
- return ` LIMIT ${count} OFFSET ${offset}`;
16
- }
17
- if (top != null) {
18
- return ` LIMIT ${top}`;
19
- }
20
- return "";
21
- }
22
- renderJoin(join) {
23
- const alias = this.expr.wrap(join.as);
24
- if (this.needsLateral(join)) {
25
- const from2 = Array.isArray(join.from) ? this.renderFrom(join.from) : this.renderFrom(join);
26
- return ` LEFT OUTER JOIN LATERAL ${from2} AS ${alias} ON TRUE`;
27
- }
28
- const from = this.renderFrom(join.from);
29
- const where = join.where != null && join.where.length > 0 ? ` ON ${this.expr.renderWhere(join.where)}` : " ON TRUE";
30
- return ` LEFT OUTER JOIN ${from} AS ${alias}${where}`;
31
- }
32
- //#endregion
33
- //#region ========== DML - SELECT ==========
34
- select(def) {
35
- let sql = "";
36
- if (def.with != null) {
37
- const { name, base, recursive } = def.with;
38
- sql += `WITH RECURSIVE ${this.expr.wrap(name)} AS (${this.select(base).sql} UNION ALL ${this.select(recursive).sql}) `;
39
- }
40
- sql += "SELECT";
41
- if (def.distinct) {
42
- sql += " DISTINCT";
43
- }
44
- if (def.select != null) {
45
- const cols = Object.entries(def.select).map(
46
- ([alias, expr]) => `${this.expr.render(expr)} AS ${this.expr.wrap(alias)}`
47
- );
48
- sql += ` ${cols.join(", ")}`;
49
- } else {
50
- sql += " *";
51
- }
52
- if (def.from != null) {
53
- const from = this.renderFrom(def.from);
54
- sql += ` FROM ${from} AS ${this.expr.wrap(def.as)}`;
55
- }
56
- sql += this.renderJoins(def.joins);
57
- sql += this.renderWhere(def.where);
58
- sql += this.renderGroupBy(def.groupBy);
59
- sql += this.renderHaving(def.having);
60
- sql += this.renderOrderBy(def.orderBy);
61
- sql += this.renderLimit(def.limit, def.top);
62
- if (def.lock) {
63
- sql += " FOR UPDATE";
64
- }
65
- return { sql };
66
- }
67
- //#endregion
68
- //#region ========== DML - INSERT ==========
69
- insert(def) {
70
- const table = this.tableName(def.table);
71
- if (def.records.length === 0) {
72
- throw new Error("INSERT requires at least one record.");
73
- }
74
- const columns = Object.keys(def.records[0]);
75
- const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
76
- const valuesList = def.records.map((record) => {
77
- const values = columns.map((c) => this.expr.escapeValue(record[c]));
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
- } else {
169
- sql += this.renderWhere(def.where);
170
- }
171
- if (def.output != null) {
172
- const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
173
- sql += ` RETURNING ${outputCols}`;
174
- }
175
- return { sql };
176
- }
177
- //#endregion
178
- //#region ========== DML - UPSERT ==========
179
- upsert(def) {
180
- const table = this.tableName(def.table);
181
- const alias = this.expr.wrap(def.existsSelectQuery.as);
182
- const updateSetParts = Object.entries(def.updateRecord).map(
183
- ([col, e]) => `${this.expr.wrap(col)} = ${this.expr.render(e)}`
184
- );
185
- const insertColumns = Object.keys(def.insertRecord);
186
- const insertColList = insertColumns.map((c) => this.expr.wrap(c)).join(", ");
187
- const insertValues = insertColumns.map((c) => this.expr.render(def.insertRecord[c])).join(", ");
188
- const whereCondition = def.existsSelectQuery.where != null && def.existsSelectQuery.where.length > 0 ? this.expr.renderWhere(def.existsSelectQuery.where) : "TRUE";
189
- const outputCols = def.output != null ? def.output.columns.map((c) => this.expr.wrap(c)).join(", ") : "*";
190
- let sql = `WITH matched AS (
191
- `;
192
- sql += ` SELECT ${alias}.* FROM ${table} AS ${alias} WHERE ${whereCondition}
193
- `;
194
- sql += `),
195
- `;
196
- sql += `updated AS (
197
- `;
198
- sql += ` UPDATE ${table} AS ${alias} SET ${updateSetParts.join(", ")}
199
- `;
200
- sql += ` WHERE ${whereCondition}
201
- `;
202
- sql += ` RETURNING ${outputCols}
203
- `;
204
- sql += `),
205
- `;
206
- sql += `inserted AS (
207
- `;
208
- sql += ` INSERT INTO ${table} (${insertColList})
209
- `;
210
- sql += ` SELECT ${insertValues}
211
- `;
212
- sql += ` WHERE NOT EXISTS (SELECT 1 FROM matched)
213
- `;
214
- sql += ` RETURNING ${outputCols}
215
- `;
216
- sql += `)
217
- `;
218
- sql += `SELECT * FROM updated UNION ALL SELECT * FROM inserted`;
219
- return { sql };
220
- }
221
- //#endregion
222
- //#region ========== DDL - Table ==========
223
- createTable(def) {
224
- const table = this.tableName(def.table);
225
- const colDefs = def.columns.map((col) => {
226
- let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
227
- if (col.nullable === true) {
228
- colSql += " NULL";
229
- } else {
230
- colSql += " NOT NULL";
231
- }
232
- if (col.autoIncrement) {
233
- colSql += " GENERATED BY DEFAULT AS IDENTITY";
234
- }
235
- if (col.default !== void 0) {
236
- colSql += ` DEFAULT ${this.expr.escapeValue(col.default)}`;
237
- }
238
- return colSql;
239
- });
240
- if (def.primaryKey != null && def.primaryKey.length > 0) {
241
- const pkCols = def.primaryKey.map((c) => this.expr.wrap(c)).join(", ");
242
- const pkName = this.expr.wrap(`PK_${def.table.name}`);
243
- colDefs.push(`CONSTRAINT ${pkName} PRIMARY KEY (${pkCols})`);
244
- }
245
- return { sql: `CREATE TABLE ${table} (
246
- ${colDefs.join(",\n ")}
247
- )` };
248
- }
249
- dropTable(def) {
250
- return { sql: `DROP TABLE ${this.tableName(def.table)}` };
251
- }
252
- renameTable(def) {
253
- return {
254
- sql: `ALTER TABLE ${this.tableName(def.table)} RENAME TO ${this.expr.wrap(def.newName)}`
255
- };
256
- }
257
- truncate(def) {
258
- return { sql: `TRUNCATE TABLE ${this.tableName(def.table)} RESTART IDENTITY` };
259
- }
260
- //#endregion
261
- //#region ========== DDL - Column ==========
262
- addColumn(def) {
263
- const table = this.tableName(def.table);
264
- const col = def.column;
265
- let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
266
- if (col.nullable === true) {
267
- colSql += " NULL";
268
- } else {
269
- colSql += " NOT NULL";
270
- }
271
- if (col.autoIncrement) {
272
- colSql += " GENERATED BY DEFAULT AS IDENTITY";
273
- }
274
- if (col.default !== void 0) {
275
- colSql += ` DEFAULT ${this.expr.escapeValue(col.default)}`;
276
- }
277
- return { sql: `ALTER TABLE ${table} ADD COLUMN ${colSql}` };
278
- }
279
- dropColumn(def) {
280
- return {
281
- sql: `ALTER TABLE ${this.tableName(def.table)} DROP COLUMN ${this.expr.wrap(def.column)}`
282
- };
283
- }
284
- modifyColumn(def) {
285
- const table = this.tableName(def.table);
286
- const col = def.column;
287
- const parts = [];
288
- parts.push(
289
- `ALTER COLUMN ${this.expr.wrap(col.name)} TYPE ${this.expr.renderDataType(col.dataType)}`
290
- );
291
- if (col.nullable === false) {
292
- parts.push(`ALTER COLUMN ${this.expr.wrap(col.name)} SET NOT NULL`);
293
- } else {
294
- parts.push(`ALTER COLUMN ${this.expr.wrap(col.name)} DROP NOT NULL`);
295
- }
296
- if (col.default !== void 0) {
297
- parts.push(
298
- `ALTER COLUMN ${this.expr.wrap(col.name)} SET DEFAULT ${this.expr.escapeValue(col.default)}`
299
- );
300
- }
301
- return { sql: `ALTER TABLE ${table} ${parts.join(", ")}` };
302
- }
303
- renameColumn(def) {
304
- const table = this.tableName(def.table);
305
- return {
306
- sql: `ALTER TABLE ${table} RENAME COLUMN ${this.expr.wrap(def.column)} TO ${this.expr.wrap(def.newName)}`
307
- };
308
- }
309
- //#endregion
310
- //#region ========== DDL - Constraint ==========
311
- addPrimaryKey(def) {
312
- const table = this.tableName(def.table);
313
- const cols = def.columns.map((c) => this.expr.wrap(c)).join(", ");
314
- const pkName = `PK_${def.table.name}`;
315
- return {
316
- sql: `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(pkName)} PRIMARY KEY (${cols})`
317
- };
318
- }
319
- dropPrimaryKey(def) {
320
- const table = this.tableName(def.table);
321
- const pkName = `PK_${def.table.name}`;
322
- return { sql: `ALTER TABLE ${table} DROP CONSTRAINT ${this.expr.wrap(pkName)}` };
323
- }
324
- addForeignKey(def) {
325
- const table = this.tableName(def.table);
326
- const fk = def.foreignKey;
327
- const fkCols = fk.fkColumns.map((c) => this.expr.wrap(c)).join(", ");
328
- const targetTable = this.tableName(fk.targetTable);
329
- const targetCols = fk.targetPkColumns.map((c) => this.expr.wrap(c)).join(", ");
330
- let sql = `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(fk.name)} FOREIGN KEY (${fkCols}) REFERENCES ${targetTable} (${targetCols})`;
331
- const idxName = `IDX_${def.table.name}_${fk.name.replace(/^FK_/, "")}`;
332
- sql += `;
333
- CREATE INDEX ${this.expr.wrap(idxName)} ON ${table} (${fkCols});`;
334
- return { sql };
335
- }
336
- dropForeignKey(def) {
337
- return {
338
- sql: `ALTER TABLE ${this.tableName(def.table)} DROP CONSTRAINT ${this.expr.wrap(def.foreignKey)}`
339
- };
340
- }
341
- addIndex(def) {
342
- const table = this.tableName(def.table);
343
- const idx = def.index;
344
- const cols = idx.columns.map((c) => `${this.expr.wrap(c.name)} ${c.orderBy}`).join(", ");
345
- const unique = idx.unique ? "UNIQUE " : "";
346
- return { sql: `CREATE ${unique}INDEX ${this.expr.wrap(idx.name)} ON ${table} (${cols})` };
347
- }
348
- dropIndex(def) {
349
- const schema = def.table.schema ?? "public";
350
- return { sql: `DROP INDEX ${this.expr.wrap(schema)}.${this.expr.wrap(def.index)}` };
351
- }
352
- //#endregion
353
- //#region ========== DDL - View/Procedure ==========
354
- createView(def) {
355
- const view = this.tableName(def.view);
356
- const selectSql = this.select(def.queryDef).sql;
357
- return { sql: `CREATE OR REPLACE VIEW ${view} AS ${selectSql}` };
358
- }
359
- dropView(def) {
360
- return { sql: `DROP VIEW IF EXISTS ${this.tableName(def.view)}` };
361
- }
362
- createProc(def) {
363
- var _a;
364
- const proc = this.tableName(def.procedure);
365
- const paramList = ((_a = def.params) == null ? void 0 : _a.map((p) => {
366
- let sql2 = `${this.expr.wrap(p.name)} ${this.expr.renderDataType(p.dataType)}`;
367
- if (p.default !== void 0) {
368
- sql2 += ` DEFAULT ${this.expr.escapeValue(p.default)}`;
369
- }
370
- return sql2;
371
- }).join(", ")) ?? "";
372
- let returnClause = "VOID";
373
- if (def.returns && def.returns.length > 0) {
374
- const returnFields = def.returns.map((r) => `${this.expr.wrap(r.name)} ${this.expr.renderDataType(r.dataType)}`).join(", ");
375
- returnClause = `TABLE(${returnFields})`;
376
- }
377
- let sql = `CREATE OR REPLACE FUNCTION ${proc}(${paramList})
378
- `;
379
- sql += `RETURNS ${returnClause} AS $$
380
- `;
381
- sql += `BEGIN
382
- `;
383
- sql += def.query;
384
- if (!def.query.trim().endsWith(";")) {
385
- sql += ";";
386
- }
387
- sql += `
388
- END;
389
- `;
390
- sql += `$$ LANGUAGE plpgsql`;
391
- return { sql };
392
- }
393
- dropProc(def) {
394
- return { sql: `DROP FUNCTION IF EXISTS ${this.tableName(def.procedure)}()` };
395
- }
396
- execProc(def) {
397
- const proc = this.tableName(def.procedure);
398
- if (def.params == null || Object.keys(def.params).length === 0) {
399
- return { sql: `SELECT ${proc}()` };
400
- }
401
- const params = Object.values(def.params).map((p) => this.expr.render(p)).join(", ");
402
- return { sql: `SELECT ${proc}(${params})` };
403
- }
404
- //#endregion
405
- //#region ========== Utils ==========
406
- clearSchema(def) {
407
- const schemaName = def.schema ?? "public";
408
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schemaName)) {
409
- throw new Error(`Invalid schema name: ${schemaName}`);
410
- }
411
- const schema = this.expr.escapeString(schemaName);
412
- return {
413
- sql: `
414
- DO $$
415
- DECLARE
416
- r RECORD;
417
- BEGIN
418
- -- Drop FK constraints
419
- FOR r IN (SELECT conname, conrelid::regclass AS tablename
420
- FROM pg_constraint
421
- WHERE contype = 'f' AND connamespace = '${schema}'::regnamespace)
422
- LOOP
423
- EXECUTE 'ALTER TABLE ' || r.tablename || ' DROP CONSTRAINT ' || quote_ident(r.conname);
424
- END LOOP;
425
-
426
- -- Drop tables
427
- FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = '${schema}')
428
- LOOP
429
- EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
430
- END LOOP;
431
-
432
- -- Drop views
433
- FOR r IN (SELECT viewname FROM pg_views WHERE schemaname = '${schema}')
434
- LOOP
435
- EXECUTE 'DROP VIEW IF EXISTS ' || quote_ident(r.viewname) || ' CASCADE';
436
- END LOOP;
437
-
438
- -- Drop functions
439
- FOR r IN (SELECT proname, pg_get_function_identity_arguments(oid) AS args
440
- FROM pg_proc WHERE pronamespace = '${schema}'::regnamespace)
441
- LOOP
442
- EXECUTE 'DROP FUNCTION IF EXISTS ' || quote_ident(r.proname) || '(' || r.args || ') CASCADE';
443
- END LOOP;
444
- END $$`
445
- };
446
- }
447
- schemaExists(def) {
448
- const schemaName = def.schema ?? "public";
449
- const schema = this.expr.escapeString(schemaName);
450
- return { sql: `SELECT nspname FROM pg_namespace WHERE nspname = '${schema}'` };
451
- }
452
- switchFk(def) {
453
- const table = this.tableName(def.table);
454
- if (def.enabled) {
455
- return { sql: `ALTER TABLE ${table} ENABLE TRIGGER ALL` };
456
- }
457
- return { sql: `ALTER TABLE ${table} DISABLE TRIGGER ALL` };
458
- }
459
- //#endregion
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
- export {
462
- PostgresqlQueryBuilder
463
- };
464
- //# sourceMappingURL=postgresql-query-builder.js.map
515
+ //# sourceMappingURL=postgresql-query-builder.js.map