@simplysm/orm-common 13.0.69 → 13.0.71

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 (204) hide show
  1. package/README.md +54 -1447
  2. package/dist/create-db-context.d.ts +10 -10
  3. package/dist/create-db-context.js +9 -9
  4. package/dist/create-db-context.js.map +1 -1
  5. package/dist/ddl/column-ddl.d.ts +4 -4
  6. package/dist/ddl/initialize.d.ts +17 -17
  7. package/dist/ddl/initialize.js +2 -2
  8. package/dist/ddl/initialize.js.map +1 -1
  9. package/dist/ddl/relation-ddl.d.ts +6 -6
  10. package/dist/ddl/schema-ddl.d.ts +4 -4
  11. package/dist/ddl/table-ddl.d.ts +24 -24
  12. package/dist/ddl/table-ddl.js +4 -4
  13. package/dist/ddl/table-ddl.js.map +1 -1
  14. package/dist/errors/db-transaction-error.d.ts +15 -15
  15. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  16. package/dist/exec/executable.d.ts +23 -23
  17. package/dist/exec/executable.js +3 -3
  18. package/dist/exec/executable.js.map +1 -1
  19. package/dist/exec/queryable.d.ts +160 -160
  20. package/dist/exec/queryable.js +119 -119
  21. package/dist/exec/queryable.js.map +1 -1
  22. package/dist/exec/search-parser.d.ts +37 -37
  23. package/dist/exec/search-parser.d.ts.map +1 -1
  24. package/dist/expr/expr-unit.d.ts +4 -4
  25. package/dist/expr/expr.d.ts +257 -257
  26. package/dist/expr/expr.js +265 -265
  27. package/dist/expr/expr.js.map +1 -1
  28. package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
  29. package/dist/query-builder/base/expr-renderer-base.js +2 -2
  30. package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
  31. package/dist/query-builder/base/query-builder-base.d.ts +26 -26
  32. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  33. package/dist/query-builder/base/query-builder-base.js +22 -22
  34. package/dist/query-builder/base/query-builder-base.js.map +1 -1
  35. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
  36. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  37. package/dist/query-builder/mssql/mssql-expr-renderer.js +18 -18
  38. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
  39. package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
  40. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  41. package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
  42. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
  43. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
  44. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  45. package/dist/query-builder/mysql/mysql-expr-renderer.js +17 -17
  46. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
  47. package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
  48. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  49. package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
  50. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
  51. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
  52. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  53. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +17 -17
  54. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
  55. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
  56. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  57. package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
  58. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
  59. package/dist/query-builder/query-builder.d.ts +1 -1
  60. package/dist/schema/factory/column-builder.d.ts +79 -79
  61. package/dist/schema/factory/column-builder.js +42 -42
  62. package/dist/schema/factory/index-builder.d.ts +39 -39
  63. package/dist/schema/factory/index-builder.js +26 -26
  64. package/dist/schema/factory/relation-builder.d.ts +99 -99
  65. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  66. package/dist/schema/factory/relation-builder.js +38 -38
  67. package/dist/schema/procedure-builder.d.ts +49 -49
  68. package/dist/schema/procedure-builder.d.ts.map +1 -1
  69. package/dist/schema/procedure-builder.js +33 -33
  70. package/dist/schema/table-builder.d.ts +59 -59
  71. package/dist/schema/table-builder.d.ts.map +1 -1
  72. package/dist/schema/table-builder.js +43 -43
  73. package/dist/schema/view-builder.d.ts +49 -49
  74. package/dist/schema/view-builder.d.ts.map +1 -1
  75. package/dist/schema/view-builder.js +32 -32
  76. package/dist/types/column.d.ts +22 -22
  77. package/dist/types/column.js +1 -1
  78. package/dist/types/column.js.map +1 -1
  79. package/dist/types/db.d.ts +40 -40
  80. package/dist/types/expr.d.ts +59 -59
  81. package/dist/types/expr.d.ts.map +1 -1
  82. package/dist/types/query-def.d.ts +44 -44
  83. package/dist/types/query-def.d.ts.map +1 -1
  84. package/dist/utils/result-parser.d.ts +11 -11
  85. package/dist/utils/result-parser.js +3 -3
  86. package/dist/utils/result-parser.js.map +1 -1
  87. package/package.json +5 -5
  88. package/src/create-db-context.ts +20 -20
  89. package/src/ddl/column-ddl.ts +4 -4
  90. package/src/ddl/initialize.ts +259 -259
  91. package/src/ddl/relation-ddl.ts +89 -89
  92. package/src/ddl/schema-ddl.ts +4 -4
  93. package/src/ddl/table-ddl.ts +189 -189
  94. package/src/errors/db-transaction-error.ts +13 -13
  95. package/src/exec/executable.ts +25 -25
  96. package/src/exec/queryable.ts +2033 -2033
  97. package/src/exec/search-parser.ts +57 -57
  98. package/src/expr/expr-unit.ts +4 -4
  99. package/src/expr/expr.ts +2140 -2140
  100. package/src/query-builder/base/expr-renderer-base.ts +237 -237
  101. package/src/query-builder/base/query-builder-base.ts +213 -213
  102. package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
  103. package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
  104. package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
  105. package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
  106. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
  107. package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
  108. package/src/query-builder/query-builder.ts +19 -19
  109. package/src/schema/factory/column-builder.ts +423 -423
  110. package/src/schema/factory/index-builder.ts +164 -164
  111. package/src/schema/factory/relation-builder.ts +453 -453
  112. package/src/schema/procedure-builder.ts +232 -232
  113. package/src/schema/table-builder.ts +319 -319
  114. package/src/schema/view-builder.ts +221 -221
  115. package/src/types/column.ts +188 -188
  116. package/src/types/db.ts +208 -208
  117. package/src/types/expr.ts +697 -697
  118. package/src/types/query-def.ts +513 -513
  119. package/src/utils/result-parser.ts +458 -458
  120. package/tests/db-context/create-db-context.spec.ts +224 -0
  121. package/tests/db-context/define-db-context.spec.ts +68 -0
  122. package/tests/ddl/basic.expected.ts +341 -0
  123. package/tests/ddl/basic.spec.ts +714 -0
  124. package/tests/ddl/column-builder.expected.ts +310 -0
  125. package/tests/ddl/column-builder.spec.ts +637 -0
  126. package/tests/ddl/index-builder.expected.ts +38 -0
  127. package/tests/ddl/index-builder.spec.ts +202 -0
  128. package/tests/ddl/procedure-builder.expected.ts +52 -0
  129. package/tests/ddl/procedure-builder.spec.ts +234 -0
  130. package/tests/ddl/relation-builder.expected.ts +36 -0
  131. package/tests/ddl/relation-builder.spec.ts +372 -0
  132. package/tests/ddl/table-builder.expected.ts +113 -0
  133. package/tests/ddl/table-builder.spec.ts +433 -0
  134. package/tests/ddl/view-builder.expected.ts +38 -0
  135. package/tests/ddl/view-builder.spec.ts +176 -0
  136. package/tests/dml/delete.expected.ts +96 -0
  137. package/tests/dml/delete.spec.ts +160 -0
  138. package/tests/dml/insert.expected.ts +192 -0
  139. package/tests/dml/insert.spec.ts +288 -0
  140. package/tests/dml/update.expected.ts +176 -0
  141. package/tests/dml/update.spec.ts +318 -0
  142. package/tests/dml/upsert.expected.ts +215 -0
  143. package/tests/dml/upsert.spec.ts +242 -0
  144. package/tests/errors/queryable-errors.spec.ts +177 -0
  145. package/tests/escape.spec.ts +100 -0
  146. package/tests/examples/pivot.expected.ts +211 -0
  147. package/tests/examples/pivot.spec.ts +533 -0
  148. package/tests/examples/sampling.expected.ts +69 -0
  149. package/tests/examples/sampling.spec.ts +105 -0
  150. package/tests/examples/unpivot.expected.ts +120 -0
  151. package/tests/examples/unpivot.spec.ts +226 -0
  152. package/tests/exec/search-parser.spec.ts +283 -0
  153. package/tests/executable/basic.expected.ts +18 -0
  154. package/tests/executable/basic.spec.ts +54 -0
  155. package/tests/expr/comparison.expected.ts +282 -0
  156. package/tests/expr/comparison.spec.ts +400 -0
  157. package/tests/expr/conditional.expected.ts +134 -0
  158. package/tests/expr/conditional.spec.ts +276 -0
  159. package/tests/expr/date.expected.ts +332 -0
  160. package/tests/expr/date.spec.ts +526 -0
  161. package/tests/expr/math.expected.ts +62 -0
  162. package/tests/expr/math.spec.ts +106 -0
  163. package/tests/expr/string.expected.ts +218 -0
  164. package/tests/expr/string.spec.ts +356 -0
  165. package/tests/expr/utility.expected.ts +147 -0
  166. package/tests/expr/utility.spec.ts +182 -0
  167. package/tests/select/basic.expected.ts +322 -0
  168. package/tests/select/basic.spec.ts +502 -0
  169. package/tests/select/filter.expected.ts +357 -0
  170. package/tests/select/filter.spec.ts +1068 -0
  171. package/tests/select/group.expected.ts +169 -0
  172. package/tests/select/group.spec.ts +244 -0
  173. package/tests/select/join.expected.ts +582 -0
  174. package/tests/select/join.spec.ts +805 -0
  175. package/tests/select/order.expected.ts +150 -0
  176. package/tests/select/order.spec.ts +189 -0
  177. package/tests/select/recursive-cte.expected.ts +244 -0
  178. package/tests/select/recursive-cte.spec.ts +514 -0
  179. package/tests/select/result-meta.spec.ts +270 -0
  180. package/tests/select/subquery.expected.ts +363 -0
  181. package/tests/select/subquery.spec.ts +537 -0
  182. package/tests/select/view.expected.ts +155 -0
  183. package/tests/select/view.spec.ts +235 -0
  184. package/tests/select/window.expected.ts +345 -0
  185. package/tests/select/window.spec.ts +618 -0
  186. package/tests/setup/MockExecutor.ts +18 -0
  187. package/tests/setup/TestDbContext.ts +59 -0
  188. package/tests/setup/models/Company.ts +13 -0
  189. package/tests/setup/models/Employee.ts +10 -0
  190. package/tests/setup/models/MonthlySales.ts +11 -0
  191. package/tests/setup/models/Post.ts +16 -0
  192. package/tests/setup/models/Sales.ts +10 -0
  193. package/tests/setup/models/User.ts +19 -0
  194. package/tests/setup/procedure/GetAllUsers.ts +9 -0
  195. package/tests/setup/procedure/GetUserById.ts +12 -0
  196. package/tests/setup/test-utils.ts +72 -0
  197. package/tests/setup/views/ActiveUsers.ts +8 -0
  198. package/tests/setup/views/UserSummary.ts +11 -0
  199. package/tests/types/nullable-queryable-record.spec.ts +145 -0
  200. package/tests/utils/result-parser-perf.spec.ts +210 -0
  201. package/tests/utils/result-parser.spec.ts +701 -0
  202. package/docs/expressions.md +0 -172
  203. package/docs/queries.md +0 -444
  204. package/docs/schema.md +0 -245
@@ -1,259 +1,259 @@
1
- import type { DbContextBase, DbContextDef, DbContextDdlMethods } from "../types/db-context-def";
2
- import type { Queryable } from "../exec/queryable";
3
- import type { QueryDef } from "../types/query-def";
4
- import { TableBuilder } from "../schema/table-builder";
5
- import { ViewBuilder } from "../schema/view-builder";
6
- import { ProcedureBuilder } from "../schema/procedure-builder";
7
- import {
8
- ForeignKeyBuilder,
9
- RelationKeyBuilder,
10
- ForeignKeyTargetBuilder,
11
- RelationKeyTargetBuilder,
12
- } from "../schema/factory/relation-builder";
13
- import { getCreateObjectQueryDef } from "./table-ddl";
14
- import { getAddFkQueryDef, getAddIdxQueryDef } from "./relation-ddl";
15
- import { getClearSchemaQueryDef, getSchemaExistsQueryDef } from "./schema-ddl";
16
-
17
- /**
18
- * Code First 데이터베이스 초기화
19
- *
20
- * DbContext 정의된 테이블/뷰/프로시저를 데이터베이스에 생성하고,
21
- * 마이그레이션을 적용
22
- *
23
- * @param db - DbContext 인스턴스
24
- * @param def - DbContext 정의
25
- * @param options - 초기화 옵션
26
- * @param options.dbs - 초기화 대상 데이터베이스 목록 (미지정 현재 database)
27
- * @param options.force - true 기존 스키마 삭제 전체 재생성
28
- * @throws {Error} 초기화할 데이터베이스가 없을
29
- * @throws {Error} 지정한 데이터베이스가 존재하지 않을
30
- *
31
- * 동작 방식:
32
- * - **force=true**: clearSchema → 전체 생성모든 migration "적용됨" 등록
33
- * - **force=false** (기본):
34
- * - _Migration 테이블 없음: 전체 생성 + 모든 migration 등록
35
- * - _Migration 테이블 있음: 미적용 migration만 실행
36
- */
37
- export async function initialize(
38
- db: DbContextBase & DbContextDdlMethods & { _migration: () => Queryable<{ code: string }, any> },
39
- def: DbContextDef<any, any, any>,
40
- options?: { dbs?: string[]; force?: boolean },
41
- ): Promise<void> {
42
- const dbNames = options?.dbs ?? (db.database !== undefined ? [db.database] : []);
43
- if (dbNames.length < 1) {
44
- throw new Error("초기화할 데이터베이스가 없습니다.");
45
- }
46
-
47
- const force = options?.force ?? false;
48
-
49
- // 1. DB 존재 확인
50
- for (const dbName of dbNames) {
51
- const schemaExistsDef = getSchemaExistsQueryDef(dbName, db.schema);
52
- const result = await db.executeDefs([schemaExistsDef]);
53
- const schemaExists = result[0].length > 0;
54
- if (!schemaExists) {
55
- throw new Error(`데이터베이스 '${dbName}' 존재하지 않습니다.`);
56
- }
57
- }
58
-
59
- if (force) {
60
- // 2. force: dbs 전체 초기화
61
- for (const dbName of dbNames) {
62
- const clearDef = getClearSchemaQueryDef({ database: dbName, schema: db.schema });
63
- await db.executeDefs([clearDef]);
64
- }
65
- await createAllObjects(db, def);
66
-
67
- // 모든 migration을 "적용됨"으로 등록
68
- if (def.meta.migrations.length > 0) {
69
- await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
70
- }
71
- } else {
72
- // 3. Migration 기반 초기화
73
- let appliedMigrations: { code: string }[] | undefined;
74
- try {
75
- appliedMigrations = await db._migration().result();
76
- } catch (err) {
77
- // 테이블 없음 = 신규 환경
78
- if (!isTableNotExistsError(err)) {
79
- throw err;
80
- }
81
- }
82
-
83
- if (appliedMigrations == null) {
84
- // 신규 환경: 전체 생성
85
- await createAllObjects(db, def);
86
-
87
- // 모든 migration을 "적용됨"으로 등록
88
- if (def.meta.migrations.length > 0) {
89
- await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
90
- }
91
- } else {
92
- // 기존 환경: 미적용 migration만 실행
93
- const appliedCodes = new Set(appliedMigrations.map((m) => m.code));
94
- const pendingMigrations = def.meta.migrations.filter((m) => !appliedCodes.has(m.name));
95
-
96
- for (const migration of pendingMigrations) {
97
- await migration.up(db);
98
- await db._migration().insert([{ code: migration.name }]);
99
- }
100
- }
101
- }
102
- }
103
-
104
- /**
105
- * 전체 객체 생성 (테이블/뷰/프로시저/FK/Index)
106
- */
107
- async function createAllObjects(
108
- db: DbContextBase,
109
- def: DbContextDef<any, any, any>,
110
- ): Promise<void> {
111
- // 1. 테이블/뷰/프로시저 생성
112
- const builders = getBuilders(def);
113
- const createDefs: QueryDef[] = [];
114
- for (const builder of builders) {
115
- createDefs.push(getCreateObjectQueryDef(db, builder));
116
- }
117
- if (createDefs.length > 0) {
118
- await db.executeDefs(createDefs);
119
- }
120
-
121
- // 2. FK 생성 (TableBuilder만)
122
- const tables = builders.filter((b) => b instanceof TableBuilder);
123
- const addFkDefs: QueryDef[] = [];
124
- for (const table of tables) {
125
- const relations = table.meta.relations;
126
- if (relations == null) continue;
127
-
128
- const tableDef = db.getQueryDefObjectName(table);
129
- for (const [relationName, relationDef] of Object.entries(relations)) {
130
- if (!(relationDef instanceof ForeignKeyBuilder)) continue;
131
-
132
- addFkDefs.push(getAddFkQueryDef(db, tableDef, relationName, relationDef));
133
- }
134
- }
135
- if (addFkDefs.length > 0) {
136
- await db.executeDefs(addFkDefs);
137
- }
138
-
139
- // 3. Index 생성 (TableBuilder만)
140
- const createIndexDefs: QueryDef[] = [];
141
- for (const table of tables) {
142
- const indexes = table.meta.indexes;
143
- if (indexes == null || indexes.length === 0) continue;
144
-
145
- const indexTableDef = db.getQueryDefObjectName(table);
146
- for (const indexBuilder of indexes) {
147
- createIndexDefs.push(getAddIdxQueryDef(indexTableDef, indexBuilder));
148
- }
149
- }
150
- if (createIndexDefs.length > 0) {
151
- await db.executeDefs(createIndexDefs);
152
- }
153
- }
154
-
155
- /**
156
- * DbContext의 모든 Builder 수집 (Table/View/Procedure)
157
- */
158
- function getBuilders(
159
- def: DbContextDef<any, any, any>,
160
- ): (TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>)[] {
161
- const builders: (
162
- | TableBuilder<any, any>
163
- | ViewBuilder<any, any, any>
164
- | ProcedureBuilder<any, any>
165
- )[] = [];
166
-
167
- // Tables
168
- const tables: TableBuilder<any, any>[] = Object.values(def.meta.tables);
169
- for (const table of tables) {
170
- builders.push(table);
171
- }
172
-
173
- // Views
174
- const views: ViewBuilder<any, any, any>[] = Object.values(def.meta.views);
175
- for (const view of views) {
176
- builders.push(view);
177
- }
178
-
179
- // Procedures
180
- const procs: ProcedureBuilder<any, any>[] = Object.values(def.meta.procedures);
181
- for (const proc of procs) {
182
- builders.push(proc);
183
- }
184
-
185
- return builders;
186
- }
187
-
188
- /**
189
- * ForeignKeyTarget/RelationKeyTarget 관계의 유효성 검증
190
- * - targetTableFn()이 반환하는 테이블에 relationName에 해당하는 FK/RelationKey가 있는지 확인
191
- */
192
- export function validateRelations(def: DbContextDef<any, any, any>): void {
193
- const builders = getBuilders(def);
194
- const tables = builders.filter((b) => b instanceof TableBuilder);
195
-
196
- for (const table of tables) {
197
- const relations = table.meta.relations;
198
- if (relations == null) continue;
199
-
200
- for (const [relName, relDef] of Object.entries(relations)) {
201
- if (
202
- !(relDef instanceof ForeignKeyTargetBuilder) &&
203
- !(relDef instanceof RelationKeyTargetBuilder)
204
- ) {
205
- continue;
206
- }
207
-
208
- const targetTable = relDef.meta.targetTableFn();
209
- const fkRelName = relDef.meta.relationName;
210
- const fkRel = targetTable.meta.relations?.[fkRelName];
211
-
212
- if (!(fkRel instanceof ForeignKeyBuilder) && !(fkRel instanceof RelationKeyBuilder)) {
213
- throw new Error(
214
- `Invalid relation target: ${table.meta.name}.${relName}이 참조하는 ` +
215
- `'${fkRelName}'이(가) ${targetTable.meta.name}의 유효한 ForeignKey/RelationKey가 아닙니다.`,
216
- );
217
- }
218
- }
219
- }
220
- }
221
-
222
- /**
223
- * 테이블 없음 에러인지 확인
224
- *
225
- * DBMS별 에러 코드/메시지 패턴:
226
- * - MySQL: errno 1146 (ER_NO_SUCH_TABLE), "Table 'xxx' doesn't exist"
227
- * - MSSQL: number 208, "Invalid object name 'xxx'"
228
- * - PostgreSQL: code "42P01", "relation \"xxx\" does not exist"
229
- */
230
- function isTableNotExistsError(err: unknown): boolean {
231
- if (err == null) return false;
232
-
233
- // 에러 코드로 우선 확인 (다국어 환경에서도 안정적)
234
- const errObj = err as Record<string, unknown>;
235
- if (errObj["errno"] === 1146) return true; // MySQL ER_NO_SUCH_TABLE
236
- if (errObj["number"] === 208) return true; // MSSQL
237
- if (errObj["code"] === "42P01") return true; // PostgreSQL
238
-
239
- // 폴백: 메시지 매칭 (다국어 환경에서 불안정할 수 있음)
240
- const message = err instanceof Error ? err.message : String(err);
241
- const lowerMessage = message.toLowerCase();
242
-
243
- // MySQL: Table 'xxx' doesn't exist
244
- if (lowerMessage.includes("doesn't exist") && lowerMessage.includes("table")) {
245
- return true;
246
- }
247
-
248
- // MSSQL: Invalid object name 'xxx'
249
- if (lowerMessage.includes("invalid object name")) {
250
- return true;
251
- }
252
-
253
- // PostgreSQL: relation "xxx" does not exist
254
- if (lowerMessage.includes("does not exist") && lowerMessage.includes("relation")) {
255
- return true;
256
- }
257
-
258
- return false;
259
- }
1
+ import type { DbContextBase, DbContextDef, DbContextDdlMethods } from "../types/db-context-def";
2
+ import type { Queryable } from "../exec/queryable";
3
+ import type { QueryDef } from "../types/query-def";
4
+ import { TableBuilder } from "../schema/table-builder";
5
+ import { ViewBuilder } from "../schema/view-builder";
6
+ import { ProcedureBuilder } from "../schema/procedure-builder";
7
+ import {
8
+ ForeignKeyBuilder,
9
+ RelationKeyBuilder,
10
+ ForeignKeyTargetBuilder,
11
+ RelationKeyTargetBuilder,
12
+ } from "../schema/factory/relation-builder";
13
+ import { getCreateObjectQueryDef } from "./table-ddl";
14
+ import { getAddFkQueryDef, getAddIdxQueryDef } from "./relation-ddl";
15
+ import { getClearSchemaQueryDef, getSchemaExistsQueryDef } from "./schema-ddl";
16
+
17
+ /**
18
+ * Code First Database Initialize
19
+ *
20
+ * Creates Tables/Views/Procedures defined in DbContext to Database and
21
+ * applies migrations
22
+ *
23
+ * @param db - DbContext instance
24
+ * @param def - DbContext definition
25
+ * @param options - Initialize options
26
+ * @param options.dbs - List of target Databases for initialize (current database if not specified)
27
+ * @param options.force - If true, delete existing schema and recreate all
28
+ * @throws {Error} When there is no Database to initialize
29
+ * @throws {Error} When the specified Database does not exist
30
+ *
31
+ * Behavior:
32
+ * - **force=true**: clearSchema → create allregister all migrations as "applied"
33
+ * - **force=false** (default):
34
+ * - No _Migration Table: create all + register all migrations
35
+ * - _Migration Table exists: execute only unapplied migrations
36
+ */
37
+ export async function initialize(
38
+ db: DbContextBase & DbContextDdlMethods & { _migration: () => Queryable<{ code: string }, any> },
39
+ def: DbContextDef<any, any, any>,
40
+ options?: { dbs?: string[]; force?: boolean },
41
+ ): Promise<void> {
42
+ const dbNames = options?.dbs ?? (db.database !== undefined ? [db.database] : []);
43
+ if (dbNames.length < 1) {
44
+ throw new Error("No Database to initialize.");
45
+ }
46
+
47
+ const force = options?.force ?? false;
48
+
49
+ // 1. DB 존재 확인
50
+ for (const dbName of dbNames) {
51
+ const schemaExistsDef = getSchemaExistsQueryDef(dbName, db.schema);
52
+ const result = await db.executeDefs([schemaExistsDef]);
53
+ const schemaExists = result[0].length > 0;
54
+ if (!schemaExists) {
55
+ throw new Error(`Database '${dbName}' does not exist.`);
56
+ }
57
+ }
58
+
59
+ if (force) {
60
+ // 2. force: dbs 전체 Initialize
61
+ for (const dbName of dbNames) {
62
+ const clearDef = getClearSchemaQueryDef({ database: dbName, schema: db.schema });
63
+ await db.executeDefs([clearDef]);
64
+ }
65
+ await createAllObjects(db, def);
66
+
67
+ // Register all migrations as "applied"
68
+ if (def.meta.migrations.length > 0) {
69
+ await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
70
+ }
71
+ } else {
72
+ // 3. Migration 기반 Initialize
73
+ let appliedMigrations: { code: string }[] | undefined;
74
+ try {
75
+ appliedMigrations = await db._migration().result();
76
+ } catch (err) {
77
+ // No Table = new environment
78
+ if (!isTableNotExistsError(err)) {
79
+ throw err;
80
+ }
81
+ }
82
+
83
+ if (appliedMigrations == null) {
84
+ // New environment: create all
85
+ await createAllObjects(db, def);
86
+
87
+ // Register all migrations as "applied"
88
+ if (def.meta.migrations.length > 0) {
89
+ await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
90
+ }
91
+ } else {
92
+ // Existing environment: execute only unapplied migrations
93
+ const appliedCodes = new Set(appliedMigrations.map((m) => m.code));
94
+ const pendingMigrations = def.meta.migrations.filter((m) => !appliedCodes.has(m.name));
95
+
96
+ for (const migration of pendingMigrations) {
97
+ await migration.up(db);
98
+ await db._migration().insert([{ code: migration.name }]);
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * 전체 object Generate (table/View/Procedure/FK/Index)
106
+ */
107
+ async function createAllObjects(
108
+ db: DbContextBase,
109
+ def: DbContextDef<any, any, any>,
110
+ ): Promise<void> {
111
+ // 1. Table/View/Procedure Generate
112
+ const builders = getBuilders(def);
113
+ const createDefs: QueryDef[] = [];
114
+ for (const builder of builders) {
115
+ createDefs.push(getCreateObjectQueryDef(db, builder));
116
+ }
117
+ if (createDefs.length > 0) {
118
+ await db.executeDefs(createDefs);
119
+ }
120
+
121
+ // 2. FK Generate (TableBuilder만)
122
+ const tables = builders.filter((b) => b instanceof TableBuilder);
123
+ const addFkDefs: QueryDef[] = [];
124
+ for (const table of tables) {
125
+ const relations = table.meta.relations;
126
+ if (relations == null) continue;
127
+
128
+ const tableDef = db.getQueryDefObjectName(table);
129
+ for (const [relationName, relationDef] of Object.entries(relations)) {
130
+ if (!(relationDef instanceof ForeignKeyBuilder)) continue;
131
+
132
+ addFkDefs.push(getAddFkQueryDef(db, tableDef, relationName, relationDef));
133
+ }
134
+ }
135
+ if (addFkDefs.length > 0) {
136
+ await db.executeDefs(addFkDefs);
137
+ }
138
+
139
+ // 3. Index Generate (TableBuilder만)
140
+ const createIndexDefs: QueryDef[] = [];
141
+ for (const table of tables) {
142
+ const indexes = table.meta.indexes;
143
+ if (indexes == null || indexes.length === 0) continue;
144
+
145
+ const indexTableDef = db.getQueryDefObjectName(table);
146
+ for (const indexBuilder of indexes) {
147
+ createIndexDefs.push(getAddIdxQueryDef(indexTableDef, indexBuilder));
148
+ }
149
+ }
150
+ if (createIndexDefs.length > 0) {
151
+ await db.executeDefs(createIndexDefs);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * DbContext의 모든 Builder 수집 (Table/View/Procedure)
157
+ */
158
+ function getBuilders(
159
+ def: DbContextDef<any, any, any>,
160
+ ): (TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>)[] {
161
+ const builders: (
162
+ | TableBuilder<any, any>
163
+ | ViewBuilder<any, any, any>
164
+ | ProcedureBuilder<any, any>
165
+ )[] = [];
166
+
167
+ // Tables
168
+ const tables: TableBuilder<any, any>[] = Object.values(def.meta.tables);
169
+ for (const table of tables) {
170
+ builders.push(table);
171
+ }
172
+
173
+ // Views
174
+ const views: ViewBuilder<any, any, any>[] = Object.values(def.meta.views);
175
+ for (const view of views) {
176
+ builders.push(view);
177
+ }
178
+
179
+ // Procedures
180
+ const procs: ProcedureBuilder<any, any>[] = Object.values(def.meta.procedures);
181
+ for (const proc of procs) {
182
+ builders.push(proc);
183
+ }
184
+
185
+ return builders;
186
+ }
187
+
188
+ /**
189
+ * ForeignKeyTarget/RelationKeyTarget 관계의 유효성 Validation
190
+ * - targetTableFn()이 반환하는 Table에 relationName에 해당하는 FK/RelationKey가 있는지 확인
191
+ */
192
+ export function validateRelations(def: DbContextDef<any, any, any>): void {
193
+ const builders = getBuilders(def);
194
+ const tables = builders.filter((b) => b instanceof TableBuilder);
195
+
196
+ for (const table of tables) {
197
+ const relations = table.meta.relations;
198
+ if (relations == null) continue;
199
+
200
+ for (const [relName, relDef] of Object.entries(relations)) {
201
+ if (
202
+ !(relDef instanceof ForeignKeyTargetBuilder) &&
203
+ !(relDef instanceof RelationKeyTargetBuilder)
204
+ ) {
205
+ continue;
206
+ }
207
+
208
+ const targetTable = relDef.meta.targetTableFn();
209
+ const fkRelName = relDef.meta.relationName;
210
+ const fkRel = targetTable.meta.relations?.[fkRelName];
211
+
212
+ if (!(fkRel instanceof ForeignKeyBuilder) && !(fkRel instanceof RelationKeyBuilder)) {
213
+ throw new Error(
214
+ `Invalid relation target: ${table.meta.name}.${relName}이 참조하는 ` +
215
+ `'${fkRelName}'이(가) ${targetTable.meta.name}의 유효한 ForeignKey/RelationKey가 아닙니다.`,
216
+ );
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Table N/A 에러인지 확인
224
+ *
225
+ * DBMS별 error code/메시지 pattern:
226
+ * - MySQL: errno 1146 (ER_NO_SUCH_TABLE), "Table 'xxx' doesn't exist"
227
+ * - MSSQL: number 208, "Invalid object name 'xxx'"
228
+ * - PostgreSQL: code "42P01", "relation \"xxx\" does not exist"
229
+ */
230
+ function isTableNotExistsError(err: unknown): boolean {
231
+ if (err == null) return false;
232
+
233
+ // error code로 우선 확인 (multilingual 환경에서도 안정적)
234
+ const errObj = err as Record<string, unknown>;
235
+ if (errObj["errno"] === 1146) return true; // MySQL ER_NO_SUCH_TABLE
236
+ if (errObj["number"] === 208) return true; // MSSQL
237
+ if (errObj["code"] === "42P01") return true; // PostgreSQL
238
+
239
+ // 폴백: 메시지 매칭 (multilingual 환경에서 불안정할 수 있음)
240
+ const message = err instanceof Error ? err.message : String(err);
241
+ const lowerMessage = message.toLowerCase();
242
+
243
+ // MySQL: Table 'xxx' doesn't exist
244
+ if (lowerMessage.includes("doesn't exist") && lowerMessage.includes("table")) {
245
+ return true;
246
+ }
247
+
248
+ // MSSQL: Invalid object name 'xxx'
249
+ if (lowerMessage.includes("invalid object name")) {
250
+ return true;
251
+ }
252
+
253
+ // PostgreSQL: relation "xxx" does not exist
254
+ if (lowerMessage.includes("does not exist") && lowerMessage.includes("relation")) {
255
+ return true;
256
+ }
257
+
258
+ return false;
259
+ }