@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.
Files changed (246) hide show
  1. package/README.md +90 -147
  2. package/dist/create-db-context.d.ts +10 -10
  3. package/dist/create-db-context.js +312 -276
  4. package/dist/create-db-context.js.map +1 -6
  5. package/dist/ddl/column-ddl.d.ts +4 -4
  6. package/dist/ddl/column-ddl.js +41 -35
  7. package/dist/ddl/column-ddl.js.map +1 -6
  8. package/dist/ddl/initialize.d.ts +17 -17
  9. package/dist/ddl/initialize.js +200 -142
  10. package/dist/ddl/initialize.js.map +1 -6
  11. package/dist/ddl/relation-ddl.d.ts +6 -6
  12. package/dist/ddl/relation-ddl.js +55 -48
  13. package/dist/ddl/relation-ddl.js.map +1 -6
  14. package/dist/ddl/schema-ddl.d.ts +4 -4
  15. package/dist/ddl/schema-ddl.js +21 -15
  16. package/dist/ddl/schema-ddl.js.map +1 -6
  17. package/dist/ddl/table-ddl.d.ts +20 -20
  18. package/dist/ddl/table-ddl.js +139 -93
  19. package/dist/ddl/table-ddl.js.map +1 -6
  20. package/dist/define-db-context.js +10 -13
  21. package/dist/define-db-context.js.map +1 -6
  22. package/dist/errors/db-transaction-error.d.ts +15 -15
  23. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  24. package/dist/errors/db-transaction-error.js +53 -19
  25. package/dist/errors/db-transaction-error.js.map +1 -6
  26. package/dist/exec/executable.d.ts +23 -23
  27. package/dist/exec/executable.js +94 -40
  28. package/dist/exec/executable.js.map +1 -6
  29. package/dist/exec/queryable.d.ts +97 -97
  30. package/dist/exec/queryable.js +1310 -1204
  31. package/dist/exec/queryable.js.map +1 -6
  32. package/dist/exec/search-parser.d.ts +31 -31
  33. package/dist/exec/search-parser.d.ts.map +1 -1
  34. package/dist/exec/search-parser.js +158 -59
  35. package/dist/exec/search-parser.js.map +1 -6
  36. package/dist/expr/expr-unit.d.ts +4 -4
  37. package/dist/expr/expr-unit.js +24 -18
  38. package/dist/expr/expr-unit.js.map +1 -6
  39. package/dist/expr/expr.d.ts +108 -108
  40. package/dist/expr/expr.js +1872 -1844
  41. package/dist/expr/expr.js.map +1 -6
  42. package/dist/index.js +23 -1
  43. package/dist/index.js.map +1 -6
  44. package/dist/models/system-migration.js +7 -7
  45. package/dist/models/system-migration.js.map +1 -6
  46. package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
  47. package/dist/query-builder/base/expr-renderer-base.js +27 -21
  48. package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
  49. package/dist/query-builder/base/query-builder-base.d.ts +21 -21
  50. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  51. package/dist/query-builder/base/query-builder-base.js +90 -80
  52. package/dist/query-builder/base/query-builder-base.js.map +1 -6
  53. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +5 -5
  54. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  55. package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
  56. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
  57. package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
  58. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  59. package/dist/query-builder/mssql/mssql-query-builder.js +483 -443
  60. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
  61. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +5 -5
  62. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  63. package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
  64. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
  65. package/dist/query-builder/mysql/mysql-query-builder.d.ts +10 -10
  66. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  67. package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
  68. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
  69. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +5 -5
  70. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  71. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
  72. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
  73. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +8 -8
  74. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  75. package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
  76. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
  77. package/dist/query-builder/query-builder.d.ts +1 -1
  78. package/dist/query-builder/query-builder.js +13 -13
  79. package/dist/query-builder/query-builder.js.map +1 -6
  80. package/dist/schema/factory/column-builder.d.ts +84 -84
  81. package/dist/schema/factory/column-builder.js +248 -185
  82. package/dist/schema/factory/column-builder.js.map +1 -6
  83. package/dist/schema/factory/index-builder.d.ts +38 -38
  84. package/dist/schema/factory/index-builder.js +144 -85
  85. package/dist/schema/factory/index-builder.js.map +1 -6
  86. package/dist/schema/factory/relation-builder.d.ts +99 -99
  87. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  88. package/dist/schema/factory/relation-builder.js +274 -136
  89. package/dist/schema/factory/relation-builder.js.map +1 -6
  90. package/dist/schema/procedure-builder.d.ts +51 -51
  91. package/dist/schema/procedure-builder.d.ts.map +1 -1
  92. package/dist/schema/procedure-builder.js +205 -131
  93. package/dist/schema/procedure-builder.js.map +1 -6
  94. package/dist/schema/table-builder.d.ts +55 -55
  95. package/dist/schema/table-builder.d.ts.map +1 -1
  96. package/dist/schema/table-builder.js +274 -205
  97. package/dist/schema/table-builder.js.map +1 -6
  98. package/dist/schema/view-builder.d.ts +44 -44
  99. package/dist/schema/view-builder.d.ts.map +1 -1
  100. package/dist/schema/view-builder.js +189 -116
  101. package/dist/schema/view-builder.js.map +1 -6
  102. package/dist/types/column.d.ts +21 -21
  103. package/dist/types/column.js +60 -30
  104. package/dist/types/column.js.map +1 -6
  105. package/dist/types/db-context-def.d.ts +9 -9
  106. package/dist/types/db-context-def.js +2 -1
  107. package/dist/types/db-context-def.js.map +1 -6
  108. package/dist/types/db.d.ts +47 -47
  109. package/dist/types/db.js +15 -5
  110. package/dist/types/db.js.map +1 -6
  111. package/dist/types/expr.d.ts +81 -81
  112. package/dist/types/expr.d.ts.map +1 -1
  113. package/dist/types/expr.js +3 -1
  114. package/dist/types/expr.js.map +1 -6
  115. package/dist/types/query-def.d.ts +46 -46
  116. package/dist/types/query-def.d.ts.map +1 -1
  117. package/dist/types/query-def.js +31 -24
  118. package/dist/types/query-def.js.map +1 -6
  119. package/dist/utils/result-parser.d.ts +11 -11
  120. package/dist/utils/result-parser.js +362 -221
  121. package/dist/utils/result-parser.js.map +1 -6
  122. package/docs/core.md +117 -145
  123. package/docs/expression.md +186 -203
  124. package/docs/query-builder.md +75 -42
  125. package/docs/queryable.md +189 -151
  126. package/docs/schema-builders.md +172 -283
  127. package/docs/types.md +229 -173
  128. package/package.json +7 -5
  129. package/src/create-db-context.ts +31 -31
  130. package/src/ddl/column-ddl.ts +4 -4
  131. package/src/ddl/initialize.ts +38 -38
  132. package/src/ddl/relation-ddl.ts +6 -6
  133. package/src/ddl/schema-ddl.ts +4 -4
  134. package/src/ddl/table-ddl.ts +24 -24
  135. package/src/errors/db-transaction-error.ts +13 -13
  136. package/src/exec/executable.ts +25 -25
  137. package/src/exec/queryable.ts +152 -152
  138. package/src/exec/search-parser.ts +50 -50
  139. package/src/expr/expr-unit.ts +4 -4
  140. package/src/expr/expr.ts +118 -118
  141. package/src/index.ts +8 -8
  142. package/src/models/system-migration.ts +1 -1
  143. package/src/query-builder/base/expr-renderer-base.ts +21 -21
  144. package/src/query-builder/base/query-builder-base.ts +33 -33
  145. package/src/query-builder/mssql/mssql-expr-renderer.ts +28 -28
  146. package/src/query-builder/mssql/mssql-query-builder.ts +37 -37
  147. package/src/query-builder/mysql/mysql-expr-renderer.ts +29 -29
  148. package/src/query-builder/mysql/mysql-query-builder.ts +70 -70
  149. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +22 -22
  150. package/src/query-builder/postgresql/postgresql-query-builder.ts +54 -54
  151. package/src/query-builder/query-builder.ts +1 -1
  152. package/src/schema/factory/column-builder.ts +86 -86
  153. package/src/schema/factory/index-builder.ts +38 -38
  154. package/src/schema/factory/relation-builder.ts +102 -102
  155. package/src/schema/procedure-builder.ts +52 -52
  156. package/src/schema/table-builder.ts +56 -56
  157. package/src/schema/view-builder.ts +47 -47
  158. package/src/types/column.ts +24 -24
  159. package/src/types/db-context-def.ts +15 -15
  160. package/src/types/db.ts +50 -50
  161. package/src/types/expr.ts +103 -103
  162. package/src/types/query-def.ts +50 -50
  163. package/src/utils/result-parser.ts +88 -88
  164. package/docs/utilities.md +0 -27
  165. package/tests/db-context/create-db-context.spec.ts +0 -193
  166. package/tests/db-context/define-db-context.spec.ts +0 -17
  167. package/tests/ddl/basic.expected.ts +0 -341
  168. package/tests/ddl/basic.spec.ts +0 -557
  169. package/tests/ddl/column-builder.expected.ts +0 -310
  170. package/tests/ddl/column-builder.spec.ts +0 -525
  171. package/tests/ddl/index-builder.expected.ts +0 -38
  172. package/tests/ddl/index-builder.spec.ts +0 -148
  173. package/tests/ddl/procedure-builder.expected.ts +0 -52
  174. package/tests/ddl/procedure-builder.spec.ts +0 -128
  175. package/tests/ddl/relation-builder.expected.ts +0 -36
  176. package/tests/ddl/relation-builder.spec.ts +0 -171
  177. package/tests/ddl/table-builder.expected.ts +0 -113
  178. package/tests/ddl/table-builder.spec.ts +0 -399
  179. package/tests/ddl/view-builder.expected.ts +0 -38
  180. package/tests/ddl/view-builder.spec.ts +0 -116
  181. package/tests/dml/delete.expected.ts +0 -96
  182. package/tests/dml/delete.spec.ts +0 -127
  183. package/tests/dml/insert.expected.ts +0 -192
  184. package/tests/dml/insert.spec.ts +0 -210
  185. package/tests/dml/update.expected.ts +0 -176
  186. package/tests/dml/update.spec.ts +0 -222
  187. package/tests/dml/upsert.expected.ts +0 -215
  188. package/tests/dml/upsert.spec.ts +0 -190
  189. package/tests/errors/queryable-errors.spec.ts +0 -126
  190. package/tests/escape.spec.ts +0 -59
  191. package/tests/examples/pivot.expected.ts +0 -211
  192. package/tests/examples/pivot.spec.ts +0 -200
  193. package/tests/examples/sampling.expected.ts +0 -69
  194. package/tests/examples/sampling.spec.ts +0 -42
  195. package/tests/examples/unpivot.expected.ts +0 -120
  196. package/tests/examples/unpivot.spec.ts +0 -161
  197. package/tests/exec/search-parser.spec.ts +0 -267
  198. package/tests/executable/basic.expected.ts +0 -18
  199. package/tests/executable/basic.spec.ts +0 -54
  200. package/tests/expr/comparison.expected.ts +0 -282
  201. package/tests/expr/comparison.spec.ts +0 -334
  202. package/tests/expr/conditional.expected.ts +0 -134
  203. package/tests/expr/conditional.spec.ts +0 -249
  204. package/tests/expr/date.expected.ts +0 -332
  205. package/tests/expr/date.spec.ts +0 -459
  206. package/tests/expr/math.expected.ts +0 -62
  207. package/tests/expr/math.spec.ts +0 -59
  208. package/tests/expr/string.expected.ts +0 -218
  209. package/tests/expr/string.spec.ts +0 -300
  210. package/tests/expr/utility.expected.ts +0 -147
  211. package/tests/expr/utility.spec.ts +0 -155
  212. package/tests/select/basic.expected.ts +0 -322
  213. package/tests/select/basic.spec.ts +0 -433
  214. package/tests/select/filter.expected.ts +0 -357
  215. package/tests/select/filter.spec.ts +0 -954
  216. package/tests/select/group.expected.ts +0 -169
  217. package/tests/select/group.spec.ts +0 -159
  218. package/tests/select/join.expected.ts +0 -582
  219. package/tests/select/join.spec.ts +0 -692
  220. package/tests/select/order.expected.ts +0 -150
  221. package/tests/select/order.spec.ts +0 -140
  222. package/tests/select/recursive-cte.expected.ts +0 -244
  223. package/tests/select/recursive-cte.spec.ts +0 -514
  224. package/tests/select/result-meta.spec.ts +0 -270
  225. package/tests/select/subquery.expected.ts +0 -363
  226. package/tests/select/subquery.spec.ts +0 -441
  227. package/tests/select/view.expected.ts +0 -155
  228. package/tests/select/view.spec.ts +0 -235
  229. package/tests/select/window.expected.ts +0 -345
  230. package/tests/select/window.spec.ts +0 -433
  231. package/tests/setup/MockExecutor.ts +0 -18
  232. package/tests/setup/TestDbContext.ts +0 -59
  233. package/tests/setup/models/Company.ts +0 -13
  234. package/tests/setup/models/Employee.ts +0 -10
  235. package/tests/setup/models/MonthlySales.ts +0 -11
  236. package/tests/setup/models/Post.ts +0 -16
  237. package/tests/setup/models/Sales.ts +0 -10
  238. package/tests/setup/models/User.ts +0 -19
  239. package/tests/setup/procedure/GetAllUsers.ts +0 -9
  240. package/tests/setup/procedure/GetUserById.ts +0 -12
  241. package/tests/setup/test-utils.ts +0 -72
  242. package/tests/setup/views/ActiveUsers.ts +0 -8
  243. package/tests/setup/views/UserSummary.ts +0 -11
  244. package/tests/types/nullable-queryable-record.spec.ts +0 -97
  245. package/tests/utils/result-parser-perf.spec.ts +0 -143
  246. package/tests/utils/result-parser.spec.ts +0 -667
@@ -39,20 +39,20 @@ import { MysqlExprRenderer } from "./mysql-expr-renderer";
39
39
  /**
40
40
  * MySQL QueryBuilder
41
41
  *
42
- * MySQL specifics:
43
- * - No OUTPUT support: workaround via multi-statement pattern (INSERT + SET @var + SELECT)
44
- * - INSERT OUTPUT: uses LAST_INSERT_ID() for AI column, extracts PK from record for non-AI
45
- * - UPDATE/UPSERT OUTPUT: saves PK to temp table first since WHERE condition may change after UPDATE, then SELECT
46
- * - DELETE OUTPUT: saves output columns to temp table before delete
47
- * - switchFk: global setting (SET FOREIGN_KEY_CHECKS), table parameter is ignored
48
- * - Index is automatically created when adding FK
42
+ * MySQL 고유 사항:
43
+ * - OUTPUT 미지원: multi-statement 패턴으로 우회 (INSERT + SET @var + SELECT)
44
+ * - INSERT OUTPUT: AI column은 LAST_INSERT_ID() 사용, AI 레코드에서 PK 추출
45
+ * - UPDATE/UPSERT OUTPUT: UPDATE WHERE 조건이 변경될 있으므로 먼저 PK를 임시 테이블에 저장 SELECT
46
+ * - DELETE OUTPUT: 삭제 output column을 임시 테이블에 저장
47
+ * - switchFk: 전역 설정 (SET FOREIGN_KEY_CHECKS), table 파라미터 무시
48
+ * - FK 추가 인덱스 자동 생성
49
49
  */
50
50
  export class MysqlQueryBuilder extends QueryBuilderBase {
51
51
  protected expr = new MysqlExprRenderer((def) => this.select(def).sql);
52
52
 
53
53
  //#region ========== Utilities ==========
54
54
 
55
- /** Render table name (MySQL: ignores schema, uses database.table only) */
55
+ /** 테이블명 렌더링 (MySQL: schema 무시, database.table 사용) */
56
56
  protected tableName(obj: QueryDefObjectName): string {
57
57
  if (obj.database != null) {
58
58
  return `${this.expr.wrap(obj.database)}.${this.expr.wrap(obj.name)}`;
@@ -60,7 +60,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
60
60
  return this.expr.wrap(obj.name);
61
61
  }
62
62
 
63
- /** Render LIMIT clause */
63
+ /** LIMIT 렌더링 */
64
64
  protected renderLimit(limit: [number, number] | undefined, top: number | undefined): string {
65
65
  if (limit != null) {
66
66
  const [offset, count] = limit;
@@ -75,15 +75,15 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
75
75
  protected renderJoin(join: SelectQueryDefJoin): string {
76
76
  const alias = this.expr.wrap(join.as);
77
77
 
78
- // Detect if LATERAL JOIN is needed
78
+ // LATERAL JOIN 필요한지 감지
79
79
  if (this.needsLateral(join)) {
80
- // If from is an array (UNION ALL), use renderFrom(join.from),
81
- // otherwise (orderBy, top, select, etc.) use renderFrom(join) to generate subquery
80
+ // from 배열(UNION ALL)이면 renderFrom(join.from) 사용,
81
+ // (orderBy, top, select )이면 renderFrom(join)으로 서브쿼리 생성
82
82
  const from = Array.isArray(join.from) ? this.renderFrom(join.from) : this.renderFrom(join);
83
83
  return ` LEFT OUTER JOIN LATERAL ${from} AS ${alias} ON TRUE`;
84
84
  }
85
85
 
86
- // Normal JOIN
86
+ // 일반 JOIN
87
87
  const from = this.renderFrom(join.from);
88
88
  const where =
89
89
  join.where != null && join.where.length > 0
@@ -128,7 +128,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
128
128
 
129
129
  // LOCK
130
130
  if (def.lock) {
131
- // MySQL: SELECT ... FOR UPDATE (appended at the end)
131
+ // MySQL: SELECT ... FOR UPDATE (끝에 추가)
132
132
  }
133
133
 
134
134
  // JOINs
@@ -149,7 +149,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
149
149
  // LIMIT
150
150
  sql += this.renderLimit(def.limit, def.top);
151
151
 
152
- // LOCK (FOR UPDATE at end)
152
+ // LOCK (끝에 FOR UPDATE 추가)
153
153
  if (def.lock) {
154
154
  sql += " FOR UPDATE";
155
155
  }
@@ -165,13 +165,13 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
165
165
  const table = this.tableName(def.table);
166
166
 
167
167
  if (def.records.length === 0) {
168
- throw new Error("INSERT requires at least one record.");
168
+ throw new Error("INSERT에는 최소 1개의 레코드가 필요합니다.");
169
169
  }
170
170
 
171
171
  const columns = Object.keys(def.records[0]);
172
172
  const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
173
173
 
174
- // No OUTPUT needed: simple batch INSERT
174
+ // OUTPUT 불필요: 단순 배치 INSERT
175
175
  if (def.output == null) {
176
176
  const valuesList = def.records.map((record) => {
177
177
  const values = columns.map((c) => this.expr.escapeValue(record[c]));
@@ -180,9 +180,9 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
180
180
  return { sql: `INSERT INTO ${table} (${colList}) VALUES ${valuesList.join(", ")}` };
181
181
  }
182
182
 
183
- // OUTPUT needed: execute INSERT + SELECT via multi-statement
184
- // Result sets: [INSERT result, SELECT result, INSERT result, SELECT result, ...]
185
- // → Extract only SELECT results with resultSetIndex=1, resultSetStride=2
183
+ // OUTPUT 필요: multi-statement로 INSERT + SELECT 실행
184
+ // 결과 셋: [INSERT 결과, SELECT 결과, INSERT 결과, SELECT 결과, ...]
185
+ // → resultSetIndex=1, resultSetStride=2로 SELECT 결과만 추출
186
186
  const output = def.output;
187
187
  const outputCols = output.columns.map((c) => this.expr.wrap(c)).join(", ");
188
188
  const statements: string[] = [];
@@ -191,7 +191,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
191
191
  const values = columns.map((c) => this.expr.escapeValue(record[c])).join(", ");
192
192
  statements.push(`INSERT INTO ${table} (${colList}) VALUES (${values})`);
193
193
 
194
- // SELECT by PK (uses LAST_INSERT_ID() for aiColName)
194
+ // PK로 SELECT (aiColName에는 LAST_INSERT_ID() 사용)
195
195
  const whereForSelect = output.pkColNames.map((pk) => {
196
196
  const wrappedPk = this.expr.wrap(pk);
197
197
  if (pk === output.aiColName) {
@@ -216,23 +216,23 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
216
216
  const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
217
217
  const values = columns.map((c) => this.expr.escapeValue(def.record[c])).join(", ");
218
218
 
219
- // Render existsSelectQuery as SELECT 1 AS _
219
+ // existsSelectQuery SELECT 1 AS _로 렌더링
220
220
  const existsQuerySql = this.select({
221
221
  ...def.existsSelectQuery,
222
222
  select: { _: { type: "value", value: 1 } },
223
223
  }).sql;
224
224
 
225
- // No OUTPUT needed: simple INSERT IF NOT EXISTS
225
+ // OUTPUT 불필요: 단순 INSERT IF NOT EXISTS
226
226
  if (def.output == null) {
227
227
  const sql = `INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`;
228
228
  return { sql };
229
229
  }
230
230
 
231
- // OUTPUT needed: multi-statement (INSERT + SET @affected + SELECT)
231
+ // OUTPUT 필요: multi-statement (INSERT + SET @affected + SELECT)
232
232
  const output = def.output;
233
233
  const outputCols = output.columns.map((c) => this.expr.wrap(c)).join(", ");
234
234
 
235
- // SELECT WHERE condition for OUTPUT
235
+ // OUTPUT용 SELECT WHERE 조건
236
236
  const whereForSelect = output.pkColNames.map((pk) => {
237
237
  const wrappedPk = this.expr.wrap(pk);
238
238
  if (pk === output.aiColName) {
@@ -241,14 +241,14 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
241
241
  return `${wrappedPk} = ${this.expr.escapeValue(def.record[pk])}`;
242
242
  });
243
243
 
244
- // multi-statement: INSERT → SET @affected → SELECT (result only if inserted)
244
+ // multi-statement: INSERT → SET @affected → SELECT (삽입된 경우에만 결과 반환)
245
245
  const statements = [
246
246
  `INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`,
247
247
  `SET @sd_affected = ROW_COUNT()`,
248
248
  `SELECT ${outputCols} FROM ${table} WHERE ${whereForSelect.join(" AND ")} AND @sd_affected > 0`,
249
249
  ];
250
250
 
251
- // results[0]=INSERT, results[1]=SET(empty result), results[2]=SELECT
251
+ // results[0]=INSERT, results[1]=SET( 결과), results[2]=SELECT
252
252
  return { sql: statements.join(";\n"), resultSetIndex: 2 };
253
253
  }
254
254
 
@@ -256,7 +256,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
256
256
  const table = this.tableName(def.table);
257
257
  const selectSql = this.select(def.recordsSelectQuery).sql;
258
258
 
259
- // Extract columns from INSERT INTO SELECT
259
+ // INSERT INTO SELECT에서 column 추출
260
260
  const selectDef = def.recordsSelectQuery;
261
261
  const colList =
262
262
  selectDef.select != null
@@ -265,15 +265,15 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
265
265
  .join(", ")
266
266
  : "*";
267
267
 
268
- // No OUTPUT needed: simple INSERT INTO SELECT
268
+ // OUTPUT 불필요: 단순 INSERT INTO SELECT
269
269
  if (def.output == null) {
270
270
  return { sql: `INSERT INTO ${table} (${colList}) ${selectSql}` };
271
271
  }
272
272
 
273
- // OUTPUT needed: multi-statement
273
+ // OUTPUT 필요: multi-statement
274
274
  const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
275
275
 
276
- // When PK is AI: query range via LAST_INSERT_ID() + ROW_COUNT()
276
+ // PK AI 경우: LAST_INSERT_ID() + ROW_COUNT()로 범위 조회
277
277
  if (def.output.aiColName != null) {
278
278
  const aiCol = this.expr.wrap(def.output.aiColName);
279
279
 
@@ -283,14 +283,14 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
283
283
  `SELECT ${outputCols} FROM ${table} WHERE ${aiCol} >= @sd_first_id AND ${aiCol} < @sd_first_id + @sd_count`,
284
284
  ];
285
285
 
286
- // results[0]=INSERT, results[1]=SET(empty result), results[2]=SELECT
286
+ // results[0]=INSERT, results[1]=SET( 결과), results[2]=SELECT
287
287
  return { sql: statements.join(";\n"), resultSetIndex: 2 };
288
288
  }
289
289
 
290
- // PK is not AI: save PKs to temp table then query
290
+ // PK AI가 아닌 경우: PK를 임시 테이블에 저장 조회
291
291
  const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
292
292
 
293
- // Generate SELECT extracting only PK columns from recordsSelectQuery
293
+ // recordsSelectQuery에서 PK column만 추출하는 SELECT 생성
294
294
  const pkSelectDef: SelectQueryDef = {
295
295
  ...def.recordsSelectQuery,
296
296
  select: Object.fromEntries(
@@ -299,7 +299,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
299
299
  };
300
300
  const pkSelectSql = this.select(pkSelectDef).sql;
301
301
 
302
- // SELECT from target using PK from temp table
302
+ // 임시 테이블의 PK 사용하여 대상에서 SELECT
303
303
  const pkConditions = def.output.pkColNames.map((pk) => {
304
304
  const wrappedPk = this.expr.wrap(pk);
305
305
  return `${table}.${wrappedPk} = ${tempTableName}.${wrappedPk}`;
@@ -312,7 +312,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
312
312
  `DROP TEMPORARY TABLE ${tempTableName}`,
313
313
  ];
314
314
 
315
- // results[0]=CREATE, results[1]=INSERT, results[2]=SELECT, results[3]=DROP
315
+ // results[0]=CREATE, results[1]=INSERT, results[2]=SELECT, results[3]=DROP 결과
316
316
  return { sql: statements.join(";\n"), resultSetIndex: 2 };
317
317
  }
318
318
 
@@ -329,7 +329,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
329
329
  ([col, expr]) => `${alias}.${this.expr.wrap(col)} = ${this.expr.render(expr)}`,
330
330
  );
331
331
 
332
- // No OUTPUT needed: simple UPDATE
332
+ // OUTPUT 불필요: 단순 UPDATE
333
333
  if (def.output == null) {
334
334
  let sql = `UPDATE ${table} AS ${alias}`;
335
335
  sql += this.renderJoins(def.joins);
@@ -341,11 +341,11 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
341
341
  return { sql };
342
342
  }
343
343
 
344
- // OUTPUT needed: multi-statement (save PK to temp table + UPDATE + SELECT + DROP)
344
+ // OUTPUT 필요: multi-statement (PK 임시 테이블에 저장 + UPDATE + SELECT + DROP)
345
345
  const outputCols = def.output.columns.map((c) => `${alias}.${this.expr.wrap(c)}`).join(", ");
346
346
  const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
347
347
 
348
- // Save target PKs to temp table (since WHERE condition may change after UPDATE)
348
+ // 대상 PK를 임시 테이블에 저장 (UPDATE WHERE 조건이 변경될 있으므로)
349
349
  const pkSelectCols = def.output.pkColNames
350
350
  .map((pk) => `${alias}.${this.expr.wrap(pk)} AS ${this.expr.wrap(pk)}`)
351
351
  .join(", ");
@@ -360,14 +360,14 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
360
360
  updateSql += this.renderWhere(def.where);
361
361
  if (def.top != null) updateSql += ` LIMIT ${def.top}`;
362
362
 
363
- // SELECT using PK from temp table (query updated values)
363
+ // 임시 테이블의 PK SELECT (업데이트된 조회)
364
364
  const pkConditions = def.output.pkColNames.map((pk) => {
365
365
  const wrappedPk = this.expr.wrap(pk);
366
366
  return `${alias}.${wrappedPk} = ${tempTableName}.${wrappedPk}`;
367
367
  });
368
368
  const selectSql = `SELECT ${outputCols} FROM ${table} AS ${alias}, ${tempTableName} WHERE ${pkConditions.join(" AND ")}`;
369
369
 
370
- // Drop temp table
370
+ // 임시 테이블 삭제
371
371
  const dropSql = `DROP TEMPORARY TABLE ${tempTableName}`;
372
372
 
373
373
  const statements = [createTempSql, updateSql, selectSql, dropSql];
@@ -382,7 +382,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
382
382
  const table = this.tableName(def.table);
383
383
  const alias = this.expr.wrap(def.as);
384
384
 
385
- // No OUTPUT needed: simple DELETE
385
+ // OUTPUT 불필요: 단순 DELETE
386
386
  if (def.output == null) {
387
387
  let sql = `DELETE ${alias} FROM ${table} AS ${alias}`;
388
388
  sql += this.renderJoins(def.joins);
@@ -393,25 +393,25 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
393
393
  return { sql };
394
394
  }
395
395
 
396
- // OUTPUT needed: multi-statement (save to temp table before delete + DELETE + SELECT + DROP)
396
+ // OUTPUT 필요: multi-statement (삭제 임시 테이블에 저장 + DELETE + SELECT + DROP)
397
397
  const outputCols = def.output.columns.map((c) => `${alias}.${this.expr.wrap(c)}`).join(", ");
398
398
  const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
399
399
 
400
- // Save to temp table before delete
400
+ // 삭제 임시 테이블에 저장
401
401
  let createTempSql = `CREATE TEMPORARY TABLE ${tempTableName} AS SELECT ${outputCols} FROM ${table} AS ${alias}`;
402
402
  createTempSql += this.renderJoins(def.joins);
403
403
  createTempSql += this.renderWhere(def.where);
404
404
 
405
- // Execute DELETE
405
+ // DELETE 실행
406
406
  let deleteSql = `DELETE ${alias} FROM ${table} AS ${alias}`;
407
407
  deleteSql += this.renderJoins(def.joins);
408
408
  deleteSql += this.renderWhere(def.where);
409
409
  if (def.top != null) deleteSql += ` LIMIT ${def.top}`;
410
410
 
411
- // Return results from temp table
411
+ // 임시 테이블에서 결과 반환
412
412
  const selectSql = `SELECT * FROM ${tempTableName}`;
413
413
 
414
- // Drop temp table
414
+ // 임시 테이블 삭제
415
415
  const dropSql = `DROP TEMPORARY TABLE ${tempTableName}`;
416
416
 
417
417
  const statements = [createTempSql, deleteSql, selectSql, dropSql];
@@ -427,17 +427,17 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
427
427
  const alias = this.expr.wrap(def.existsSelectQuery.as);
428
428
  const existsQuerySql = this.select(def.existsSelectQuery).sql;
429
429
 
430
- // UPDATE SET part (alias.column format)
430
+ // UPDATE SET 부분 (alias.column 형식)
431
431
  const updateSetParts = Object.entries(def.updateRecord).map(
432
432
  ([col, e]) => `${alias}.${this.expr.wrap(col)} = ${this.expr.render(e)}`,
433
433
  );
434
434
 
435
- // INSERT part
435
+ // INSERT 부분
436
436
  const insertColumns = Object.keys(def.insertRecord);
437
437
  const insertColList = insertColumns.map((c) => this.expr.wrap(c)).join(", ");
438
438
  const insertValues = insertColumns.map((c) => this.expr.render(def.insertRecord[c])).join(", ");
439
439
 
440
- // Extract WHERE condition (from existsSelectQuery's where)
440
+ // WHERE 조건 추출 (existsSelectQuery where에서)
441
441
  const whereCondition =
442
442
  def.existsSelectQuery.where != null && def.existsSelectQuery.where.length > 0
443
443
  ? this.expr.renderWhere(def.existsSelectQuery.where)
@@ -454,22 +454,22 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
454
454
  return { sql: statements.join(";\n") };
455
455
  }
456
456
 
457
- // OUTPUT needed: multi-statement (CREATE TEMP + UPDATE + INSERT + SELECT + DROP)
457
+ // OUTPUT 필요: multi-statement (CREATE TEMP + UPDATE + INSERT + SELECT + DROP)
458
458
  const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
459
459
  const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
460
460
 
461
- // Save target PKs to temp table (since WHERE condition may change after UPDATE)
461
+ // 대상 PK를 임시 테이블에 저장 (UPDATE WHERE 조건이 변경될 있으므로)
462
462
  const pkSelectCols = def.output.pkColNames.map((pk) => this.expr.wrap(pk)).join(", ");
463
463
  const createTempSql = `CREATE TEMPORARY TABLE ${tempTableName} AS SELECT ${pkSelectCols} FROM ${table} AS ${alias} WHERE ${whereCondition}`;
464
464
 
465
- // UPDATE (update if exists)
465
+ // UPDATE (존재하면 업데이트)
466
466
  const updateSql = `UPDATE ${table} AS ${alias} SET ${updateSetParts.join(", ")} WHERE ${whereCondition}`;
467
467
 
468
- // INSERT (NOT EXISTS Pattern)
468
+ // INSERT (NOT EXISTS 패턴)
469
469
  const insertSql = `INSERT INTO ${table} (${insertColList}) SELECT ${insertValues} WHERE NOT EXISTS (${existsQuerySql})`;
470
470
 
471
- // SELECT: query UPDATE result or INSERT result (merged with UNION ALL)
472
- // UPDATE case: query by PK from temp table
471
+ // SELECT: UPDATE 결과 또는 INSERT 결과 조회 (UNION ALL로 병합)
472
+ // UPDATE 경우: 임시 테이블의 PK 조회
473
473
  const output = def.output;
474
474
  const updatePkConditions = output.pkColNames.map((pk) => {
475
475
  const wrappedPk = this.expr.wrap(pk);
@@ -477,7 +477,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
477
477
  });
478
478
  const selectUpdateSql = `SELECT ${outputCols} FROM ${table} WHERE ${updatePkConditions.join(" AND ")}`;
479
479
 
480
- // INSERT case: query by PK from insertRecord (LAST_INSERT_ID() for AI, only when temp table is empty)
480
+ // INSERT 경우: insertRecord의 PK 조회 (AI에는 LAST_INSERT_ID() 사용, 임시 테이블이 비어있을 때만)
481
481
  const insertPkConditions = output.pkColNames.map((pk) => {
482
482
  const wrappedPk = this.expr.wrap(pk);
483
483
  if (pk === output.aiColName) {
@@ -490,7 +490,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
490
490
 
491
491
  const selectSql = `${selectUpdateSql} UNION ALL ${selectInsertSql}`;
492
492
 
493
- // DROP
493
+ // 임시 테이블 삭제
494
494
  const dropSql = `DROP TEMPORARY TABLE ${tempTableName}`;
495
495
 
496
496
  const statements = [createTempSql, updateSql, insertSql, selectSql, dropSql];
@@ -507,7 +507,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
507
507
  const colDefs = def.columns.map((col) => {
508
508
  let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
509
509
 
510
- // nullable: true → NULL, else → NOT NULL
510
+ // nullable: true → NULL, 아니면 → NOT NULL
511
511
  if (col.nullable === true) {
512
512
  colSql += " NULL";
513
513
  } else {
@@ -525,7 +525,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
525
525
  return colSql;
526
526
  });
527
527
 
528
- // Primary Key with CONSTRAINT name
528
+ // CONSTRAINT 이름이 포함된 Primary Key
529
529
  if (def.primaryKey != null && def.primaryKey.length > 0) {
530
530
  const pkCols = def.primaryKey.map((c) => this.expr.wrap(c)).join(", ");
531
531
  const pkName = this.expr.wrap(`PK_${def.table.name}`);
@@ -544,7 +544,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
544
544
  }
545
545
 
546
546
  protected truncate(def: TruncateQueryDef): QueryBuildResult {
547
- // MySQL: TRUNCATE automatically resets AUTO_INCREMENT
547
+ // MySQL: TRUNCATE AUTO_INCREMENT를 자동으로 초기화
548
548
  return { sql: `TRUNCATE TABLE ${this.tableName(def.table)}` };
549
549
  }
550
550
 
@@ -608,7 +608,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
608
608
 
609
609
  protected renameColumn(def: RenameColumnQueryDef): QueryBuildResult {
610
610
  const table = this.tableName(def.table);
611
- // MySQL 8.0+: RENAME COLUMN supported
611
+ // MySQL 8.0+: RENAME COLUMN 지원
612
612
  return {
613
613
  sql: `ALTER TABLE ${table} RENAME COLUMN ${this.expr.wrap(def.column)} TO ${this.expr.wrap(def.newName)}`,
614
614
  };
@@ -635,7 +635,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
635
635
  const targetTable = this.tableName(fk.targetTable);
636
636
  const targetCols = fk.targetPkColumns.map((c) => this.expr.wrap(c)).join(", ");
637
637
 
638
- // MySQL automatically creates index when adding FK, so no separate index needed
638
+ // MySQL FK 추가 인덱스를 자동 생성하므로 별도 인덱스 불필요
639
639
  return {
640
640
  sql: `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(fk.name)} FOREIGN KEY (${fkCols}) REFERENCES ${targetTable} (${targetCols})`,
641
641
  };
@@ -676,7 +676,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
676
676
  protected createProc(def: CreateProcQueryDef): QueryBuildResult {
677
677
  const proc = this.tableName(def.procedure);
678
678
 
679
- // Process params
679
+ // 파라미터 처리
680
680
  const paramList =
681
681
  def.params
682
682
  ?.map((p) => {
@@ -719,11 +719,11 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
719
719
  //#region ========== Utils ==========
720
720
 
721
721
  protected clearSchema(def: ClearSchemaQueryDef): QueryBuildResult {
722
- // MySQL: DROP all tables (in MySQL, database and schema are synonymous)
723
- // Query table list from information_schema then DROP
724
- // SQL injection prevention: identifier validation
722
+ // MySQL: 모든 테이블 삭제 (MySQL에서 database schema 동의어)
723
+ // information_schema에서 테이블 목록 조회 DROP
724
+ // SQL 인젝션 방지: 식별자 유효성 검사
725
725
  if (!/^[a-zA-Z0-9_]+$/.test(def.database)) {
726
- throw new Error(`Invalid database name: ${def.database}`);
726
+ throw new Error(`잘못된 데이터베이스 이름: ${def.database}`);
727
727
  }
728
728
 
729
729
  const dbName = this.expr.escapeString(def.database);
@@ -741,14 +741,14 @@ SET FOREIGN_KEY_CHECKS = 1`,
741
741
  }
742
742
 
743
743
  protected schemaExists(def: SchemaExistsQueryDef): QueryBuildResult {
744
- // MySQL: database and schema are synonymous
744
+ // MySQL: database schema 동의어
745
745
  const dbName = this.expr.escapeString(def.database);
746
746
  return {
747
747
  sql: `SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${dbName}'`,
748
748
  };
749
749
  }
750
750
 
751
- /** MySQL only supports global setting (table parameter is ignored) */
751
+ /** MySQL 전역 설정만 지원 (table 파라미터 무시) */
752
752
  protected switchFk(def: SwitchFkQueryDef): QueryBuildResult {
753
753
  return def.enabled
754
754
  ? { sql: "SET FOREIGN_KEY_CHECKS = 1" }
@@ -69,22 +69,22 @@ import type { DataType } from "../../types/column";
69
69
  import { ExprRendererBase } from "../base/expr-renderer-base";
70
70
 
71
71
  /**
72
- * PostgreSQL expression renderer
72
+ * PostgreSQL 표현식 렌더러
73
73
  */
74
74
  export class PostgresqlExprRenderer extends ExprRendererBase {
75
- //#region ========== Utilities (public - also used by QueryBuilder) ==========
75
+ //#region ========== 유틸리티 (public - QueryBuilder에서도 사용) ==========
76
76
 
77
- /** Wrap identifier */
77
+ /** 식별자 감싸기 */
78
78
  wrap(name: string): string {
79
79
  return `"${name.replace(/"/g, '""')}"`;
80
80
  }
81
81
 
82
- /** Escape for SQL string literals (returns without quotes) */
82
+ /** SQL 문자열 리터럴용 이스케이프 (따옴표 제외) */
83
83
  escapeString(value: string): string {
84
84
  return value.replace(/'/g, "''");
85
85
  }
86
86
 
87
- /** value escape */
87
+ /** 이스케이프 */
88
88
  escapeValue(value: unknown): string {
89
89
  if (value == null) {
90
90
  return "NULL";
@@ -113,10 +113,10 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
113
113
  if (value instanceof Uint8Array) {
114
114
  return `'\\x${bytes.toHex(value)}'::bytea`;
115
115
  }
116
- throw new Error(`Unknown value type: ${typeof value}`);
116
+ throw new Error(`알 없는 값 타입: ${typeof value}`);
117
117
  }
118
118
 
119
- /** DataType → SQL type */
119
+ /** DataType → SQL 타입 변환 */
120
120
  renderDataType(dataType: DataType): string {
121
121
  switch (dataType.type) {
122
122
  case "int":
@@ -176,7 +176,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
176
176
  //#region ========== comparison (null-safe) ==========
177
177
 
178
178
  protected eq(expr: ExprEq): string {
179
- // PostgreSQL: null-safe equal (uses IS NOT DISTINCT FROM operator)
179
+ // PostgreSQL: NULL 안전 동등 비교 (IS NOT DISTINCT FROM 연산자 사용)
180
180
  const left = this.render(expr.source);
181
181
  const right = this.render(expr.target);
182
182
  return `${left} IS NOT DISTINCT FROM ${right}`;
@@ -217,7 +217,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
217
217
  }
218
218
 
219
219
  protected like(expr: ExprLike): string {
220
- // Always add ESCAPE '\'
220
+ // 항상 ESCAPE '\' 추가
221
221
  return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\'`;
222
222
  }
223
223
 
@@ -228,7 +228,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
228
228
 
229
229
  protected in(expr: ExprIn): string {
230
230
  if (expr.values.length === 0) {
231
- return "FALSE"; // empty IN is always false
231
+ return "FALSE"; // IN 항상 false
232
232
  }
233
233
  const values = expr.values.map((v) => this.render(v)).join(", ");
234
234
  return `${this.render(expr.source)} IN (${values})`;
@@ -239,7 +239,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
239
239
  }
240
240
 
241
241
  protected exists(expr: ExprExists): string {
242
- // Render as SELECT 1
242
+ // SELECT 1로 렌더링
243
243
  const subquery = this.buildSelect({
244
244
  ...expr.query,
245
245
  select: { _: { type: "value", value: 1 } },
@@ -270,7 +270,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
270
270
  //#region ========== String (null handling) ==========
271
271
 
272
272
  protected concat(expr: ExprConcat): string {
273
- // PostgreSQL: uses || operator with COALESCE
273
+ // PostgreSQL: COALESCE와 || 연산자 사용
274
274
  const args = expr.args.map((a) => `COALESCE(${this.render(a)}, '')`);
275
275
  return args.join(" || ");
276
276
  }
@@ -304,12 +304,12 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
304
304
  }
305
305
 
306
306
  protected length(expr: ExprLength): string {
307
- // PostgreSQL: LENGTH() (null handling)
307
+ // PostgreSQL: LENGTH() (null 처리)
308
308
  return `LENGTH(COALESCE(${this.render(expr.arg)}, ''))`;
309
309
  }
310
310
 
311
311
  protected byteLength(expr: ExprByteLength): string {
312
- // PostgreSQL: OCTET_LENGTH() (null handling)
312
+ // PostgreSQL: OCTET_LENGTH() (null 처리)
313
313
  return `OCTET_LENGTH(COALESCE(${this.render(expr.arg)}, ''))`;
314
314
  }
315
315
 
@@ -378,7 +378,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
378
378
 
379
379
  protected isoWeekStartDate(expr: ExprIsoWeekStartDate): string {
380
380
  const src = this.render(expr.arg);
381
- // ISO week start date (Monday)
381
+ // ISO 시작일 (월요일)
382
382
  return `DATE_TRUNC('week', ${src})::DATE`;
383
383
  }
384
384
 
@@ -413,7 +413,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
413
413
  }
414
414
 
415
415
  protected formatDate(expr: ExprFormatDate): string {
416
- // JS format → PostgreSQL TO_CHAR format
416
+ // JS 포맷 → PostgreSQL TO_CHAR 포맷
417
417
  const pgFormat = this.convertDateFormat(expr.format);
418
418
  return `TO_CHAR(${this.render(expr.source)}, '${pgFormat}')`;
419
419
  }
@@ -436,7 +436,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
436
436
  }
437
437
 
438
438
  private convertDateFormat(format: string): string {
439
- // JS format → PostgreSQL TO_CHAR format
439
+ // JS 포맷 → PostgreSQL TO_CHAR 포맷
440
440
  return format
441
441
  .replace(/yyyy/g, "YYYY")
442
442
  .replace(/MM/g, "MM")
@@ -510,14 +510,14 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
510
510
  //#region ========== Other ==========
511
511
 
512
512
  protected greatest(expr: ExprGreatest): string {
513
- if (expr.args.length === 0) throw new Error("greatest requires at least one argument.");
514
- // PostgreSQL: native GREATEST support
513
+ if (expr.args.length === 0) throw new Error("greatest에는 최소 1개의 인수가 필요합니다.");
514
+ // PostgreSQL: GREATEST 네이티브 지원
515
515
  return `GREATEST(${expr.args.map((a) => this.render(a)).join(", ")})`;
516
516
  }
517
517
 
518
518
  protected least(expr: ExprLeast): string {
519
- if (expr.args.length === 0) throw new Error("least requires at least one argument.");
520
- // PostgreSQL: native LEAST support
519
+ if (expr.args.length === 0) throw new Error("least에는 최소 1개의 인수가 필요합니다.");
520
+ // PostgreSQL: LEAST 네이티브 지원
521
521
  return `LEAST(${expr.args.map((a) => this.render(a)).join(", ")})`;
522
522
  }
523
523
 
@@ -541,7 +541,7 @@ export class PostgresqlExprRenderer extends ExprRendererBase {
541
541
  const fn = this.renderWindowFn(expr.fn);
542
542
  let over = this.renderWindowSpec(expr.spec);
543
543
 
544
- // LAST_VALUE default frame only sees up to CURRENT ROW, so full frame must be specified
544
+ // LAST_VALUE 기본 프레임은 CURRENT ROW까지만 보므로 전체 프레임을 지정해야
545
545
  if (expr.fn.type === "lastValue" && over.length > 0) {
546
546
  over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
547
547
  }