@simplysm/orm-common 14.0.16 → 14.0.17
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 +25 -19
- package/dist/db-context.d.ts +133 -0
- package/dist/db-context.d.ts.map +1 -0
- package/dist/db-context.js +325 -0
- package/dist/db-context.js.map +1 -0
- package/dist/ddl/initialize.d.ts +5 -3
- package/dist/ddl/initialize.d.ts.map +1 -1
- package/dist/ddl/initialize.js +39 -32
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/exec/executable.js +1 -1
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.js +2 -2
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/exec/search-parser.js +2 -1
- package/dist/exec/search-parser.js.map +1 -1
- package/dist/expr/expr.d.ts +2 -2
- package/dist/expr/expr.js +2 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +7 -0
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +86 -16
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/schema/factory/relation-builder.d.ts +44 -67
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +37 -78
- package/dist/schema/factory/relation-builder.js.map +1 -1
- package/dist/schema/view-builder.d.ts +1 -1
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +0 -2
- package/dist/schema/view-builder.js.map +1 -1
- package/dist/types/column.js +1 -1
- package/dist/types/column.js.map +1 -1
- package/dist/types/db-context-def.d.ts +2 -42
- package/dist/types/db-context-def.d.ts.map +1 -1
- package/dist/utils/result-parser.js +31 -23
- package/dist/utils/result-parser.js.map +1 -1
- package/docs/core.md +110 -50
- package/docs/schema-builders.md +13 -19
- package/docs/types.md +2 -41
- package/package.json +3 -3
- package/src/db-context.ts +455 -0
- package/src/ddl/initialize.ts +49 -37
- package/src/exec/executable.ts +1 -1
- package/src/exec/queryable.ts +2 -2
- package/src/exec/search-parser.ts +2 -1
- package/src/expr/expr.ts +2 -2
- package/src/index.ts +2 -3
- package/src/query-builder/mssql/mssql-expr-renderer.ts +1 -1
- package/src/query-builder/mysql/mysql-expr-renderer.ts +1 -1
- package/src/query-builder/mysql/mysql-query-builder.ts +1 -1
- package/src/query-builder/postgresql/postgresql-query-builder.ts +93 -14
- package/src/schema/factory/relation-builder.ts +56 -87
- package/src/schema/view-builder.ts +1 -3
- package/src/types/column.ts +1 -1
- package/src/types/db-context-def.ts +2 -60
- package/src/utils/result-parser.ts +29 -22
- package/dist/create-db-context.d.ts +0 -34
- package/dist/create-db-context.d.ts.map +0 -1
- package/dist/create-db-context.js +0 -329
- package/dist/create-db-context.js.map +0 -1
- package/dist/define-db-context.d.ts +0 -15
- package/dist/define-db-context.d.ts.map +0 -1
- package/dist/define-db-context.js +0 -12
- package/dist/define-db-context.js.map +0 -1
- package/src/create-db-context.ts +0 -409
- package/src/define-db-context.ts +0 -28
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import type { DbContextBase, DbContextStatus } from "./types/db-context-def";
|
|
2
|
+
import type { DataRecord, DbContextExecutor, IsolationLevel, Migration, ResultMeta } from "./types/db";
|
|
3
|
+
import type { QueryDef, QueryDefObjectName } from "./types/query-def";
|
|
4
|
+
import { DDL_TYPES } from "./types/query-def";
|
|
5
|
+
|
|
6
|
+
const DDL_TYPE_SET: ReadonlySet<string> = new Set(DDL_TYPES);
|
|
7
|
+
import { DbErrorCode, DbTransactionError } from "./errors/db-transaction-error";
|
|
8
|
+
import { TableBuilder } from "./schema/table-builder";
|
|
9
|
+
import { ViewBuilder } from "./schema/view-builder";
|
|
10
|
+
import type { ProcedureBuilder } from "./schema/procedure-builder";
|
|
11
|
+
import { queryable as createQueryable } from "./exec/queryable";
|
|
12
|
+
import type { Queryable } from "./exec/queryable";
|
|
13
|
+
import { executable as createExecutable } from "./exec/executable";
|
|
14
|
+
import type { Executable } from "./exec/executable";
|
|
15
|
+
|
|
16
|
+
// DDL import
|
|
17
|
+
import * as tableDdl from "./ddl/table-ddl";
|
|
18
|
+
import { getQueryDefObjectName as getQueryDefObjectNameImpl } from "./ddl/table-ddl";
|
|
19
|
+
import * as columnDdl from "./ddl/column-ddl";
|
|
20
|
+
import * as relationDdl from "./ddl/relation-ddl";
|
|
21
|
+
import * as schemaDdl from "./ddl/schema-ddl";
|
|
22
|
+
import {
|
|
23
|
+
initialize as initializeImpl,
|
|
24
|
+
validateRelations as validateRelationsImpl,
|
|
25
|
+
} from "./ddl/initialize";
|
|
26
|
+
|
|
27
|
+
import type { ColumnBuilder } from "./schema/factory/column-builder";
|
|
28
|
+
import type { ForeignKeyBuilder } from "./schema/factory/relation-builder";
|
|
29
|
+
import type { IndexBuilder } from "./schema/factory/index-builder";
|
|
30
|
+
import { _Migration } from "./models/system-migration";
|
|
31
|
+
|
|
32
|
+
export const SD_BUILDER = Symbol("sdBuilder");
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* DbContext base class
|
|
36
|
+
*
|
|
37
|
+
* 테이블/뷰/프로시저를 class 프로퍼티로 등록하고,
|
|
38
|
+
* 연결/트랜잭션/DDL/초기화를 제공한다.
|
|
39
|
+
*
|
|
40
|
+
* defineDbContext/createDbContext의 class 기반 대체.
|
|
41
|
+
* 각 프로퍼티가 독립 직렬화되어 40+ 테이블에서도 TS7056이 발생하지 않는다.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* class MainDb extends DbContext {
|
|
46
|
+
* user = this.queryable(User);
|
|
47
|
+
* post = this.queryable(Post);
|
|
48
|
+
* activeUsers = this.queryable(ActiveUsers);
|
|
49
|
+
* getUserById = this.executable(GetUserById);
|
|
50
|
+
*
|
|
51
|
+
* migrations = [{ name: "001", up: async (db) => { ... } }];
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* const db = new MainDb(executor, { database: "mydb" });
|
|
55
|
+
* await db.connect(async () => {
|
|
56
|
+
* const users = await db.user().execute();
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export abstract class DbContext implements DbContextBase {
|
|
61
|
+
// ── 상태 ──
|
|
62
|
+
status: DbContextStatus = "ready";
|
|
63
|
+
private _aliasCounter = 0;
|
|
64
|
+
private _relationsValidated = false;
|
|
65
|
+
|
|
66
|
+
// ── 시스템 테이블 ──
|
|
67
|
+
_migration = this.queryable(_Migration);
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
private readonly _executor: DbContextExecutor,
|
|
71
|
+
private readonly _opt: { database: string; schema?: string },
|
|
72
|
+
) {}
|
|
73
|
+
|
|
74
|
+
// ── DbContextBase 구현 ──
|
|
75
|
+
|
|
76
|
+
get database(): string | undefined {
|
|
77
|
+
return this._opt.database;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get schema(): string | undefined {
|
|
81
|
+
return this._opt.schema;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getNextAlias(): string {
|
|
85
|
+
return `T${++this._aliasCounter}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
resetAliasCounter(): void {
|
|
89
|
+
this._aliasCounter = 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
executeDefs<T = DataRecord>(
|
|
93
|
+
defs: QueryDef[],
|
|
94
|
+
resultMetas?: (ResultMeta | undefined)[],
|
|
95
|
+
): Promise<T[][]> {
|
|
96
|
+
if (this.status === "transact" && defs.some((d) => DDL_TYPE_SET.has(d.type))) {
|
|
97
|
+
throw new Error("TRANSACTION 상태에서는 DDL을 실행할 수 없습니다.");
|
|
98
|
+
}
|
|
99
|
+
return this._executor.executeDefs(defs, resultMetas);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getQueryDefObjectName(
|
|
103
|
+
tableOrView: TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
104
|
+
): QueryDefObjectName {
|
|
105
|
+
return getQueryDefObjectNameImpl(this, tableOrView);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async switchFk(table: QueryDefObjectName, enabled: boolean): Promise<void> {
|
|
109
|
+
await this.executeDefs([schemaDdl.getSwitchFkQueryDef(table, enabled)]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── 등록 메서드 ──
|
|
113
|
+
|
|
114
|
+
protected queryable<T extends TableBuilder<any, any> | ViewBuilder<any, any, any>>(
|
|
115
|
+
builder: T,
|
|
116
|
+
): () => Queryable<T["$inferSelect"], T extends TableBuilder<any, any> ? T : never> {
|
|
117
|
+
const fn = createQueryable(this, builder);
|
|
118
|
+
Object.defineProperty(fn, SD_BUILDER, { value: builder });
|
|
119
|
+
return fn;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected executable<T extends ProcedureBuilder<any, any>>(
|
|
123
|
+
builder: T,
|
|
124
|
+
): () => Executable<T["$params"], T["$returns"]> {
|
|
125
|
+
const fn = createExecutable(this, builder);
|
|
126
|
+
Object.defineProperty(fn, SD_BUILDER, { value: builder });
|
|
127
|
+
return fn;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── 연결 관리 ──
|
|
131
|
+
|
|
132
|
+
async connect<TResult>(
|
|
133
|
+
fn: () => Promise<TResult>,
|
|
134
|
+
isolationLevel?: IsolationLevel,
|
|
135
|
+
): Promise<TResult> {
|
|
136
|
+
if (this.status !== "ready") {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`이미 ${this.status === "connect" ? "CONNECT" : "TRANSACTION"} 상태입니다.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (!this._relationsValidated) {
|
|
142
|
+
validateRelationsImpl(this);
|
|
143
|
+
this._relationsValidated = true;
|
|
144
|
+
}
|
|
145
|
+
this.resetAliasCounter();
|
|
146
|
+
|
|
147
|
+
await this._executor.connect();
|
|
148
|
+
this.status = "connect";
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
await this._executor.beginTransaction(isolationLevel);
|
|
152
|
+
this.status = "transact";
|
|
153
|
+
|
|
154
|
+
let result: TResult;
|
|
155
|
+
try {
|
|
156
|
+
result = await fn();
|
|
157
|
+
|
|
158
|
+
await this._executor.commitTransaction();
|
|
159
|
+
this.status = "connect";
|
|
160
|
+
} catch (err) {
|
|
161
|
+
try {
|
|
162
|
+
await this._executor.rollbackTransaction();
|
|
163
|
+
} catch (err1) {
|
|
164
|
+
if (
|
|
165
|
+
!(err1 instanceof DbTransactionError) ||
|
|
166
|
+
err1.code !== DbErrorCode.NO_ACTIVE_TRANSACTION
|
|
167
|
+
) {
|
|
168
|
+
(err as Error).cause = err1;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
this.status = "connect";
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
} finally {
|
|
177
|
+
try {
|
|
178
|
+
await this._executor.close();
|
|
179
|
+
} finally {
|
|
180
|
+
this.status = "ready";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async connectWithoutTransaction<TResult>(callback: () => Promise<TResult>): Promise<TResult> {
|
|
186
|
+
if (this.status !== "ready") {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`이미 ${this.status === "connect" ? "CONNECT" : "TRANSACTION"} 상태입니다.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (!this._relationsValidated) {
|
|
192
|
+
validateRelationsImpl(this);
|
|
193
|
+
this._relationsValidated = true;
|
|
194
|
+
}
|
|
195
|
+
this.resetAliasCounter();
|
|
196
|
+
|
|
197
|
+
await this._executor.connect();
|
|
198
|
+
this.status = "connect";
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
return await callback();
|
|
202
|
+
} finally {
|
|
203
|
+
try {
|
|
204
|
+
await this._executor.close();
|
|
205
|
+
} finally {
|
|
206
|
+
this.status = "ready";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async transaction<TResult>(
|
|
212
|
+
fn: () => Promise<TResult>,
|
|
213
|
+
isolationLevel?: IsolationLevel,
|
|
214
|
+
): Promise<TResult> {
|
|
215
|
+
if (this.status === "transact") {
|
|
216
|
+
throw new Error("이미 TRANSACTION 상태입니다.");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await this._executor.beginTransaction(isolationLevel);
|
|
220
|
+
this.status = "transact";
|
|
221
|
+
|
|
222
|
+
let result: TResult;
|
|
223
|
+
try {
|
|
224
|
+
result = await fn();
|
|
225
|
+
|
|
226
|
+
await this._executor.commitTransaction();
|
|
227
|
+
this.status = "connect";
|
|
228
|
+
} catch (err) {
|
|
229
|
+
try {
|
|
230
|
+
await this._executor.rollbackTransaction();
|
|
231
|
+
} catch (err1) {
|
|
232
|
+
if (
|
|
233
|
+
!(err1 instanceof DbTransactionError) ||
|
|
234
|
+
err1.code !== DbErrorCode.NO_ACTIVE_TRANSACTION
|
|
235
|
+
) {
|
|
236
|
+
(err as Error).cause = err1;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
this.status = "connect";
|
|
240
|
+
throw err;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── DDL 실행 메서드 ──
|
|
247
|
+
|
|
248
|
+
async createTable(table: TableBuilder<any, any>): Promise<void> {
|
|
249
|
+
await this.executeDefs([tableDdl.getCreateTableQueryDef(this, table)]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async dropTable(table: QueryDefObjectName): Promise<void> {
|
|
253
|
+
await this.executeDefs([tableDdl.getDropTableQueryDef(table)]);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async renameTable(table: QueryDefObjectName, newName: string): Promise<void> {
|
|
257
|
+
await this.executeDefs([tableDdl.getRenameTableQueryDef(table, newName)]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async createView(view: ViewBuilder<any, any, any>): Promise<void> {
|
|
261
|
+
await this.executeDefs([tableDdl.getCreateViewQueryDef(this as any, view)]);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async dropView(view: QueryDefObjectName): Promise<void> {
|
|
265
|
+
await this.executeDefs([tableDdl.getDropViewQueryDef(view)]);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async createProc(procedure: ProcedureBuilder<any, any>): Promise<void> {
|
|
269
|
+
await this.executeDefs([tableDdl.getCreateProcQueryDef(this, procedure)]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async dropProc(procedure: QueryDefObjectName): Promise<void> {
|
|
273
|
+
await this.executeDefs([tableDdl.getDropProcQueryDef(procedure)]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async addColumn(
|
|
277
|
+
table: QueryDefObjectName,
|
|
278
|
+
columnName: string,
|
|
279
|
+
column: ColumnBuilder<any, any>,
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
await this.executeDefs([columnDdl.getAddColumnQueryDef(table, columnName, column)]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async dropColumn(table: QueryDefObjectName, column: string): Promise<void> {
|
|
285
|
+
await this.executeDefs([columnDdl.getDropColumnQueryDef(table, column)]);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async modifyColumn(
|
|
289
|
+
table: QueryDefObjectName,
|
|
290
|
+
columnName: string,
|
|
291
|
+
column: ColumnBuilder<any, any>,
|
|
292
|
+
): Promise<void> {
|
|
293
|
+
await this.executeDefs([columnDdl.getModifyColumnQueryDef(table, columnName, column)]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async renameColumn(table: QueryDefObjectName, column: string, newName: string): Promise<void> {
|
|
297
|
+
await this.executeDefs([columnDdl.getRenameColumnQueryDef(table, column, newName)]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async addPrimaryKey(table: QueryDefObjectName, columns: string[]): Promise<void> {
|
|
301
|
+
await this.executeDefs([relationDdl.getAddPrimaryKeyQueryDef(table, columns)]);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async dropPrimaryKey(table: QueryDefObjectName): Promise<void> {
|
|
305
|
+
await this.executeDefs([relationDdl.getDropPrimaryKeyQueryDef(table)]);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async addForeignKey(
|
|
309
|
+
table: QueryDefObjectName,
|
|
310
|
+
relationName: string,
|
|
311
|
+
relationDef: ForeignKeyBuilder<any, any>,
|
|
312
|
+
): Promise<void> {
|
|
313
|
+
await this.executeDefs([
|
|
314
|
+
relationDdl.getAddForeignKeyQueryDef(this, table, relationName, relationDef),
|
|
315
|
+
]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async addIndex(table: QueryDefObjectName, indexBuilder: IndexBuilder<string[]>): Promise<void> {
|
|
319
|
+
await this.executeDefs([relationDdl.getAddIndexQueryDef(table, indexBuilder)]);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async dropForeignKey(table: QueryDefObjectName, relationName: string): Promise<void> {
|
|
323
|
+
await this.executeDefs([relationDdl.getDropForeignKeyQueryDef(table, relationName)]);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async dropIndex(table: QueryDefObjectName, columns: string[]): Promise<void> {
|
|
327
|
+
await this.executeDefs([relationDdl.getDropIndexQueryDef(table, columns)]);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async clearSchema(params: { database: string; schema?: string }): Promise<void> {
|
|
331
|
+
await this.executeDefs([schemaDdl.getClearSchemaQueryDef(params)]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async schemaExists(database: string, schema?: string): Promise<boolean> {
|
|
335
|
+
const result = await this.executeDefs([schemaDdl.getSchemaExistsQueryDef(database, schema)]);
|
|
336
|
+
return result[0].length > 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async truncate(table: QueryDefObjectName): Promise<void> {
|
|
340
|
+
await this.executeDefs([schemaDdl.getTruncateQueryDef(table)]);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ── DDL QueryDef 생성기 ──
|
|
344
|
+
|
|
345
|
+
getCreateTableQueryDef(table: TableBuilder<any, any>): QueryDef {
|
|
346
|
+
return tableDdl.getCreateTableQueryDef(this, table);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
getCreateViewQueryDef(view: ViewBuilder<any, any, any>): QueryDef {
|
|
350
|
+
return tableDdl.getCreateViewQueryDef(this as any, view);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
getCreateProcQueryDef(procedure: ProcedureBuilder<any, any>): QueryDef {
|
|
354
|
+
return tableDdl.getCreateProcQueryDef(this, procedure);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
getCreateObjectQueryDef(
|
|
358
|
+
builder: TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>,
|
|
359
|
+
): QueryDef {
|
|
360
|
+
return tableDdl.getCreateObjectQueryDef(this as any, builder);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
getDropTableQueryDef(table: QueryDefObjectName): QueryDef {
|
|
364
|
+
return tableDdl.getDropTableQueryDef(table);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
getRenameTableQueryDef(table: QueryDefObjectName, newName: string): QueryDef {
|
|
368
|
+
return tableDdl.getRenameTableQueryDef(table, newName);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
getDropViewQueryDef(view: QueryDefObjectName): QueryDef {
|
|
372
|
+
return tableDdl.getDropViewQueryDef(view);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
getDropProcQueryDef(procedure: QueryDefObjectName): QueryDef {
|
|
376
|
+
return tableDdl.getDropProcQueryDef(procedure);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
getAddColumnQueryDef(
|
|
380
|
+
table: QueryDefObjectName,
|
|
381
|
+
columnName: string,
|
|
382
|
+
column: ColumnBuilder<any, any>,
|
|
383
|
+
): QueryDef {
|
|
384
|
+
return columnDdl.getAddColumnQueryDef(table, columnName, column);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
getDropColumnQueryDef(table: QueryDefObjectName, column: string): QueryDef {
|
|
388
|
+
return columnDdl.getDropColumnQueryDef(table, column);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
getModifyColumnQueryDef(
|
|
392
|
+
table: QueryDefObjectName,
|
|
393
|
+
columnName: string,
|
|
394
|
+
column: ColumnBuilder<any, any>,
|
|
395
|
+
): QueryDef {
|
|
396
|
+
return columnDdl.getModifyColumnQueryDef(table, columnName, column);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getRenameColumnQueryDef(table: QueryDefObjectName, column: string, newName: string): QueryDef {
|
|
400
|
+
return columnDdl.getRenameColumnQueryDef(table, column, newName);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getAddPrimaryKeyQueryDef(table: QueryDefObjectName, columns: string[]): QueryDef {
|
|
404
|
+
return relationDdl.getAddPrimaryKeyQueryDef(table, columns);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
getDropPrimaryKeyQueryDef(table: QueryDefObjectName): QueryDef {
|
|
408
|
+
return relationDdl.getDropPrimaryKeyQueryDef(table);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getAddForeignKeyQueryDef(
|
|
412
|
+
table: QueryDefObjectName,
|
|
413
|
+
relationName: string,
|
|
414
|
+
relationDef: ForeignKeyBuilder<any, any>,
|
|
415
|
+
): QueryDef {
|
|
416
|
+
return relationDdl.getAddForeignKeyQueryDef(this, table, relationName, relationDef);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
getAddIndexQueryDef(table: QueryDefObjectName, indexBuilder: IndexBuilder<string[]>): QueryDef {
|
|
420
|
+
return relationDdl.getAddIndexQueryDef(table, indexBuilder);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
getDropForeignKeyQueryDef(table: QueryDefObjectName, relationName: string): QueryDef {
|
|
424
|
+
return relationDdl.getDropForeignKeyQueryDef(table, relationName);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
getDropIndexQueryDef(table: QueryDefObjectName, columns: string[]): QueryDef {
|
|
428
|
+
return relationDdl.getDropIndexQueryDef(table, columns);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
getClearSchemaQueryDef(params: { database: string; schema?: string }): QueryDef {
|
|
432
|
+
return schemaDdl.getClearSchemaQueryDef(params);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
getSchemaExistsQueryDef(database: string, schema?: string): QueryDef {
|
|
436
|
+
return schemaDdl.getSchemaExistsQueryDef(database, schema);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
getTruncateQueryDef(table: QueryDefObjectName): QueryDef {
|
|
440
|
+
return schemaDdl.getTruncateQueryDef(table);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
getSwitchFkQueryDef(table: QueryDefObjectName, enabled: boolean): QueryDef {
|
|
444
|
+
return schemaDdl.getSwitchFkQueryDef(table, enabled);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ── 초기화 ──
|
|
448
|
+
|
|
449
|
+
async initialize(options?: { dbs?: string[]; force?: boolean }): Promise<void> {
|
|
450
|
+
await initializeImpl(this, options);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** 마이그레이션 정의 — 서브클래스에서 오버라이드 */
|
|
454
|
+
migrations: Migration[] = [];
|
|
455
|
+
}
|
package/src/ddl/initialize.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { DbContextBase,
|
|
1
|
+
import type { DbContextBase, DbContextDdlMethods } from "../types/db-context-def";
|
|
2
2
|
import type { Queryable } from "../exec/queryable";
|
|
3
3
|
import type { QueryDef } from "../types/query-def";
|
|
4
|
+
import type { Migration } from "../types/db";
|
|
4
5
|
import { TableBuilder } from "../schema/table-builder";
|
|
5
6
|
import { ViewBuilder } from "../schema/view-builder";
|
|
6
7
|
import { ProcedureBuilder } from "../schema/procedure-builder";
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
import { getCreateObjectQueryDef } from "./table-ddl";
|
|
14
15
|
import { getAddForeignKeyQueryDef, getAddIndexQueryDef } from "./relation-ddl";
|
|
15
16
|
import { getClearSchemaQueryDef, getSchemaExistsQueryDef } from "./schema-ddl";
|
|
17
|
+
import { SD_BUILDER } from "../db-context";
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Code First 데이터베이스 초기화
|
|
@@ -35,8 +37,11 @@ import { getClearSchemaQueryDef, getSchemaExistsQueryDef } from "./schema-ddl";
|
|
|
35
37
|
* - _Migration 테이블 있음: 미적용 migration만 실행
|
|
36
38
|
*/
|
|
37
39
|
export async function initialize(
|
|
38
|
-
db: DbContextBase &
|
|
39
|
-
|
|
40
|
+
db: DbContextBase &
|
|
41
|
+
DbContextDdlMethods & {
|
|
42
|
+
_migration: () => Queryable<{ code: string }, any>;
|
|
43
|
+
migrations: Migration[];
|
|
44
|
+
},
|
|
40
45
|
options?: { dbs?: string[]; force?: boolean },
|
|
41
46
|
): Promise<void> {
|
|
42
47
|
const dbNames = options?.dbs ?? (db.database !== undefined ? [db.database] : []);
|
|
@@ -45,6 +50,8 @@ export async function initialize(
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
const force = options?.force ?? false;
|
|
53
|
+
const builders = collectBuilders(db);
|
|
54
|
+
const migrations = db.migrations;
|
|
48
55
|
|
|
49
56
|
// 1. DB 존재 여부 확인
|
|
50
57
|
for (const dbName of dbNames) {
|
|
@@ -62,14 +69,18 @@ export async function initialize(
|
|
|
62
69
|
const clearDef = getClearSchemaQueryDef({ database: dbName, schema: db.schema });
|
|
63
70
|
await db.executeDefs([clearDef]);
|
|
64
71
|
}
|
|
65
|
-
|
|
72
|
+
|
|
73
|
+
// 각 대상 DB에 객체 생성
|
|
74
|
+
for (const dbName of dbNames) {
|
|
75
|
+
await createAllObjects(db, builders, dbName);
|
|
76
|
+
}
|
|
66
77
|
|
|
67
78
|
// 모든 migration을 "적용됨"으로 등록
|
|
68
|
-
if (
|
|
69
|
-
await db._migration().insert(
|
|
79
|
+
if (migrations.length > 0) {
|
|
80
|
+
await db._migration().insert(migrations.map((m) => ({ code: m.name })));
|
|
70
81
|
}
|
|
71
82
|
} else {
|
|
72
|
-
// 3. Migration 기반 초기화
|
|
83
|
+
// 3. Migration 기반 초기화 — 각 대상 DB에 대해 수행
|
|
73
84
|
let appliedMigrations: { code: string }[] | undefined;
|
|
74
85
|
try {
|
|
75
86
|
appliedMigrations = await db._migration().execute();
|
|
@@ -81,17 +92,19 @@ export async function initialize(
|
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
if (appliedMigrations == null) {
|
|
84
|
-
// 새 환경: 전체 생성
|
|
85
|
-
|
|
95
|
+
// 새 환경: 각 대상 DB에 전체 생성
|
|
96
|
+
for (const dbName of dbNames) {
|
|
97
|
+
await createAllObjects(db, builders, dbName);
|
|
98
|
+
}
|
|
86
99
|
|
|
87
100
|
// 모든 migration을 "적용됨"으로 등록
|
|
88
|
-
if (
|
|
89
|
-
await db._migration().insert(
|
|
101
|
+
if (migrations.length > 0) {
|
|
102
|
+
await db._migration().insert(migrations.map((m) => ({ code: m.name })));
|
|
90
103
|
}
|
|
91
104
|
} else {
|
|
92
105
|
// 기존 환경: 미적용 migration만 실행
|
|
93
106
|
const appliedCodes = new Set(appliedMigrations.map((m) => m.code));
|
|
94
|
-
const pendingMigrations =
|
|
107
|
+
const pendingMigrations = migrations.filter((m) => !appliedCodes.has(m.name));
|
|
95
108
|
|
|
96
109
|
for (const migration of pendingMigrations) {
|
|
97
110
|
await migration.up(db);
|
|
@@ -103,15 +116,26 @@ export async function initialize(
|
|
|
103
116
|
|
|
104
117
|
/**
|
|
105
118
|
* 모든 객체 생성 (table/view/procedure/FK/index)
|
|
119
|
+
*
|
|
120
|
+
* @param db - DbContext 인스턴스
|
|
121
|
+
* @param builders - 생성할 builder 목록
|
|
122
|
+
* @param targetDatabase - 대상 데이터베이스. builder에 database가 지정된 경우 해당 builder의 database가 targetDatabase와
|
|
123
|
+
* 일치할 때만 생성한다. 미지정 builder는 targetDatabase에 생성한다.
|
|
106
124
|
*/
|
|
107
125
|
async function createAllObjects(
|
|
108
126
|
db: DbContextBase,
|
|
109
|
-
|
|
127
|
+
builders: (TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>)[],
|
|
128
|
+
targetDatabase: string,
|
|
110
129
|
): Promise<void> {
|
|
130
|
+
// targetDatabase에 해당하는 builder만 필터링
|
|
131
|
+
const targetBuilders = builders.filter((b) => {
|
|
132
|
+
const builderDb = b.meta.database;
|
|
133
|
+
return builderDb == null || builderDb === targetDatabase;
|
|
134
|
+
});
|
|
135
|
+
|
|
111
136
|
// 1. Table/View/Procedure 생성
|
|
112
|
-
const builders = getBuilders(def);
|
|
113
137
|
const createDefs: QueryDef[] = [];
|
|
114
|
-
for (const builder of
|
|
138
|
+
for (const builder of targetBuilders) {
|
|
115
139
|
createDefs.push(getCreateObjectQueryDef(db, builder));
|
|
116
140
|
}
|
|
117
141
|
if (createDefs.length > 0) {
|
|
@@ -119,7 +143,7 @@ async function createAllObjects(
|
|
|
119
143
|
}
|
|
120
144
|
|
|
121
145
|
// 2. FK 생성 (TableBuilder만)
|
|
122
|
-
const tables =
|
|
146
|
+
const tables = targetBuilders.filter((b) => b instanceof TableBuilder);
|
|
123
147
|
const addFkDefs: QueryDef[] = [];
|
|
124
148
|
for (const table of tables) {
|
|
125
149
|
const relations = table.meta.relations;
|
|
@@ -153,10 +177,10 @@ async function createAllObjects(
|
|
|
153
177
|
}
|
|
154
178
|
|
|
155
179
|
/**
|
|
156
|
-
* DbContext
|
|
180
|
+
* DbContext 인스턴스에서 SD_BUILDER 태그가 붙은 builder를 수집
|
|
157
181
|
*/
|
|
158
|
-
function
|
|
159
|
-
|
|
182
|
+
function collectBuilders(
|
|
183
|
+
dbContext: object,
|
|
160
184
|
): (TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>)[] {
|
|
161
185
|
const builders: (
|
|
162
186
|
| TableBuilder<any, any>
|
|
@@ -164,22 +188,10 @@ function getBuilders(
|
|
|
164
188
|
| ProcedureBuilder<any, any>
|
|
165
189
|
)[] = [];
|
|
166
190
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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);
|
|
191
|
+
for (const value of Object.values(dbContext)) {
|
|
192
|
+
if (typeof value === "function" && SD_BUILDER in value) {
|
|
193
|
+
builders.push(value[SD_BUILDER as keyof typeof value] as TableBuilder<any, any>);
|
|
194
|
+
}
|
|
183
195
|
}
|
|
184
196
|
|
|
185
197
|
return builders;
|
|
@@ -189,8 +201,8 @@ function getBuilders(
|
|
|
189
201
|
* ForeignKeyTarget/RelationKeyTarget 관계 검증
|
|
190
202
|
* - targetTableFn()이 반환하는 테이블에 relationName과 일치하는 FK/RelationKey가 있는지 확인
|
|
191
203
|
*/
|
|
192
|
-
export function validateRelations(
|
|
193
|
-
const builders =
|
|
204
|
+
export function validateRelations(dbContext: object): void {
|
|
205
|
+
const builders = collectBuilders(dbContext);
|
|
194
206
|
const tables = builders.filter((b) => b instanceof TableBuilder);
|
|
195
207
|
|
|
196
208
|
for (const table of tables) {
|
package/src/exec/executable.ts
CHANGED
|
@@ -47,7 +47,7 @@ export class Executable<TParams extends ColumnBuilderRecord, TReturns extends Co
|
|
|
47
47
|
params:
|
|
48
48
|
params && meta.params
|
|
49
49
|
? Object.fromEntries(
|
|
50
|
-
Object.keys(params).map((key) => [
|
|
50
|
+
Object.keys(meta.params).map((key) => [
|
|
51
51
|
key,
|
|
52
52
|
params[key] instanceof ExprUnit
|
|
53
53
|
? params[key].expr
|
package/src/exec/queryable.ts
CHANGED
|
@@ -1127,8 +1127,8 @@ export class Queryable<
|
|
|
1127
1127
|
* ```
|
|
1128
1128
|
*/
|
|
1129
1129
|
async exists(): Promise<boolean> {
|
|
1130
|
-
const
|
|
1131
|
-
return
|
|
1130
|
+
const result = await this.top(1).execute();
|
|
1131
|
+
return result.length > 0;
|
|
1132
1132
|
}
|
|
1133
1133
|
|
|
1134
1134
|
getSelectQueryDef(): SelectQueryDef {
|
|
@@ -96,7 +96,8 @@ export function parseSearchQuery(searchText: string): ParsedSearchQuery {
|
|
|
96
96
|
.replace(/\\%/g, ESC.PERCENT)
|
|
97
97
|
.replace(/\\"/g, ESC.QUOTE)
|
|
98
98
|
.replace(/\\\+/g, ESC.PLUS)
|
|
99
|
-
.replace(/\\-/g, ESC.MINUS)
|
|
99
|
+
.replace(/\\-/g, ESC.MINUS)
|
|
100
|
+
.replace(/\\(.)/g, "$1"); // 미정의 이스케이프: backslash 제거, 리터럴 문자 유지
|
|
100
101
|
|
|
101
102
|
// 따옴표 구간 추출
|
|
102
103
|
const quotedRegex = /([+-]?)"([^"]*)"/g;
|
package/src/expr/expr.ts
CHANGED
|
@@ -912,7 +912,7 @@ export const expr = {
|
|
|
912
912
|
* @example
|
|
913
913
|
* ```typescript
|
|
914
914
|
* db.order().select((o) => ({
|
|
915
|
-
* pages: expr.ceil(expr.
|
|
915
|
+
* pages: expr.ceil(expr.multiply(o.itemCount, 0.1)),
|
|
916
916
|
* }))
|
|
917
917
|
* // SELECT CEILING(itemCount / 10) AS pages
|
|
918
918
|
* // 25 / 10 = 2.5 → 3
|
|
@@ -934,7 +934,7 @@ export const expr = {
|
|
|
934
934
|
* @example
|
|
935
935
|
* ```typescript
|
|
936
936
|
* db.user().select((u) => ({
|
|
937
|
-
* ageGroup: expr.floor(expr.
|
|
937
|
+
* ageGroup: expr.floor(expr.multiply(u.age, 0.1)),
|
|
938
938
|
* }))
|
|
939
939
|
* // SELECT FLOOR(age / 10) AS ageGroup
|
|
940
940
|
* // 25 / 10 = 2.5 → 2
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
//#region ========== Core ==========
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./create-db-context";
|
|
3
|
+
// Class 기반 API
|
|
4
|
+
export * from "./db-context";
|
|
6
5
|
export * from "./types/db-context-def";
|
|
7
6
|
export * from "./errors/db-transaction-error";
|
|
8
7
|
|