@simplysm/orm-common 13.0.68 → 13.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -1447
- package/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +9 -9
- package/dist/create-db-context.js.map +1 -1
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +2 -2
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/table-ddl.d.ts +24 -24
- package/dist/ddl/table-ddl.js +4 -4
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +3 -3
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.d.ts +160 -160
- package/dist/exec/queryable.js +119 -119
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.d.ts +37 -37
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr.d.ts +257 -257
- package/dist/expr/expr.js +265 -265
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
- package/dist/query-builder/base/expr-renderer-base.js +2 -2
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
- package/dist/query-builder/base/query-builder-base.d.ts +26 -26
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +22 -22
- package/dist/query-builder/base/query-builder-base.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +18 -18
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +17 -17
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +17 -17
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +79 -79
- package/dist/schema/factory/column-builder.js +42 -42
- package/dist/schema/factory/index-builder.d.ts +39 -39
- package/dist/schema/factory/index-builder.js +26 -26
- package/dist/schema/factory/relation-builder.d.ts +99 -99
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +38 -38
- package/dist/schema/procedure-builder.d.ts +49 -49
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +33 -33
- package/dist/schema/table-builder.d.ts +59 -59
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +43 -43
- package/dist/schema/view-builder.d.ts +49 -49
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +32 -32
- package/dist/types/column.d.ts +22 -22
- package/dist/types/column.js +1 -1
- package/dist/types/column.js.map +1 -1
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +59 -59
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +44 -44
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/utils/result-parser.d.ts +11 -11
- package/dist/utils/result-parser.js +3 -3
- package/dist/utils/result-parser.js.map +1 -1
- package/package.json +5 -5
- package/src/create-db-context.ts +20 -20
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +259 -259
- package/src/ddl/relation-ddl.ts +89 -89
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +189 -189
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +2033 -2033
- package/src/exec/search-parser.ts +57 -57
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +2140 -2140
- package/src/query-builder/base/expr-renderer-base.ts +237 -237
- package/src/query-builder/base/query-builder-base.ts +213 -213
- package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
- package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
- package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
- package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
- package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
- package/src/query-builder/query-builder.ts +19 -19
- package/src/schema/factory/column-builder.ts +423 -423
- package/src/schema/factory/index-builder.ts +164 -164
- package/src/schema/factory/relation-builder.ts +453 -453
- package/src/schema/procedure-builder.ts +232 -232
- package/src/schema/table-builder.ts +319 -319
- package/src/schema/view-builder.ts +221 -221
- package/src/types/column.ts +188 -188
- package/src/types/db.ts +208 -208
- package/src/types/expr.ts +697 -697
- package/src/types/query-def.ts +513 -513
- package/src/utils/result-parser.ts +458 -458
- package/tests/db-context/create-db-context.spec.ts +224 -0
- package/tests/db-context/define-db-context.spec.ts +68 -0
- package/tests/ddl/basic.expected.ts +341 -0
- package/tests/ddl/basic.spec.ts +714 -0
- package/tests/ddl/column-builder.expected.ts +310 -0
- package/tests/ddl/column-builder.spec.ts +637 -0
- package/tests/ddl/index-builder.expected.ts +38 -0
- package/tests/ddl/index-builder.spec.ts +202 -0
- package/tests/ddl/procedure-builder.expected.ts +52 -0
- package/tests/ddl/procedure-builder.spec.ts +234 -0
- package/tests/ddl/relation-builder.expected.ts +36 -0
- package/tests/ddl/relation-builder.spec.ts +372 -0
- package/tests/ddl/table-builder.expected.ts +113 -0
- package/tests/ddl/table-builder.spec.ts +433 -0
- package/tests/ddl/view-builder.expected.ts +38 -0
- package/tests/ddl/view-builder.spec.ts +176 -0
- package/tests/dml/delete.expected.ts +96 -0
- package/tests/dml/delete.spec.ts +160 -0
- package/tests/dml/insert.expected.ts +192 -0
- package/tests/dml/insert.spec.ts +288 -0
- package/tests/dml/update.expected.ts +176 -0
- package/tests/dml/update.spec.ts +318 -0
- package/tests/dml/upsert.expected.ts +215 -0
- package/tests/dml/upsert.spec.ts +242 -0
- package/tests/errors/queryable-errors.spec.ts +177 -0
- package/tests/escape.spec.ts +100 -0
- package/tests/examples/pivot.expected.ts +211 -0
- package/tests/examples/pivot.spec.ts +533 -0
- package/tests/examples/sampling.expected.ts +69 -0
- package/tests/examples/sampling.spec.ts +104 -0
- package/tests/examples/unpivot.expected.ts +120 -0
- package/tests/examples/unpivot.spec.ts +226 -0
- package/tests/exec/search-parser.spec.ts +283 -0
- package/tests/executable/basic.expected.ts +18 -0
- package/tests/executable/basic.spec.ts +54 -0
- package/tests/expr/comparison.expected.ts +282 -0
- package/tests/expr/comparison.spec.ts +400 -0
- package/tests/expr/conditional.expected.ts +134 -0
- package/tests/expr/conditional.spec.ts +276 -0
- package/tests/expr/date.expected.ts +332 -0
- package/tests/expr/date.spec.ts +526 -0
- package/tests/expr/math.expected.ts +62 -0
- package/tests/expr/math.spec.ts +106 -0
- package/tests/expr/string.expected.ts +218 -0
- package/tests/expr/string.spec.ts +356 -0
- package/tests/expr/utility.expected.ts +147 -0
- package/tests/expr/utility.spec.ts +182 -0
- package/tests/select/basic.expected.ts +322 -0
- package/tests/select/basic.spec.ts +502 -0
- package/tests/select/filter.expected.ts +357 -0
- package/tests/select/filter.spec.ts +1068 -0
- package/tests/select/group.expected.ts +169 -0
- package/tests/select/group.spec.ts +244 -0
- package/tests/select/join.expected.ts +582 -0
- package/tests/select/join.spec.ts +805 -0
- package/tests/select/order.expected.ts +150 -0
- package/tests/select/order.spec.ts +189 -0
- package/tests/select/recursive-cte.expected.ts +244 -0
- package/tests/select/recursive-cte.spec.ts +514 -0
- package/tests/select/result-meta.spec.ts +270 -0
- package/tests/select/subquery.expected.ts +363 -0
- package/tests/select/subquery.spec.ts +537 -0
- package/tests/select/view.expected.ts +155 -0
- package/tests/select/view.spec.ts +235 -0
- package/tests/select/window.expected.ts +345 -0
- package/tests/select/window.spec.ts +618 -0
- package/tests/setup/MockExecutor.ts +18 -0
- package/tests/setup/TestDbContext.ts +59 -0
- package/tests/setup/models/Company.ts +13 -0
- package/tests/setup/models/Employee.ts +10 -0
- package/tests/setup/models/MonthlySales.ts +11 -0
- package/tests/setup/models/Post.ts +16 -0
- package/tests/setup/models/Sales.ts +10 -0
- package/tests/setup/models/User.ts +19 -0
- package/tests/setup/procedure/GetAllUsers.ts +9 -0
- package/tests/setup/procedure/GetUserById.ts +12 -0
- package/tests/setup/test-utils.ts +72 -0
- package/tests/setup/views/ActiveUsers.ts +8 -0
- package/tests/setup/views/UserSummary.ts +11 -0
- package/tests/types/nullable-queryable-record.spec.ts +145 -0
- package/tests/utils/result-parser-perf.spec.ts +210 -0
- package/tests/utils/result-parser.spec.ts +701 -0
- package/docs/expressions.md +0 -172
- package/docs/queries.md +0 -444
- package/docs/schema.md +0 -245
package/src/ddl/initialize.ts
CHANGED
|
@@ -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 -
|
|
27
|
-
* @param options.force - true
|
|
28
|
-
* @throws {Error}
|
|
29
|
-
* @throws {Error}
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* - **force=true**: clearSchema →
|
|
33
|
-
* - **force=false** (
|
|
34
|
-
* - _Migration
|
|
35
|
-
* - _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(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
88
|
-
if (def.meta.migrations.length > 0) {
|
|
89
|
-
await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
//
|
|
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
|
-
* 전체
|
|
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
|
|
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
|
|
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()이 반환하는
|
|
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 all → register 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
|
+
}
|