@simplysm/orm-node 13.0.99 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connections/mssql-db-conn.d.ts +5 -5
- package/dist/connections/mssql-db-conn.js +356 -369
- package/dist/connections/mssql-db-conn.js.map +1 -6
- package/dist/connections/mysql-db-conn.d.ts +3 -3
- package/dist/connections/mysql-db-conn.js +227 -215
- package/dist/connections/mysql-db-conn.js.map +1 -6
- package/dist/connections/postgresql-db-conn.d.ts +3 -3
- package/dist/connections/postgresql-db-conn.js +185 -183
- package/dist/connections/postgresql-db-conn.js.map +1 -6
- package/dist/create-db-conn.d.ts +3 -3
- package/dist/create-db-conn.js +43 -27
- package/dist/create-db-conn.js.map +1 -6
- package/dist/create-orm.d.ts +18 -18
- package/dist/create-orm.js +62 -31
- package/dist/create-orm.js.map +1 -6
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -6
- package/dist/node-db-context-executor.d.ts +28 -28
- package/dist/node-db-context-executor.js +125 -117
- package/dist/node-db-context-executor.js.map +1 -6
- package/dist/types/db-conn.d.ts +37 -37
- package/dist/types/db-conn.js +26 -17
- package/dist/types/db-conn.js.map +1 -6
- package/package.json +9 -9
- package/src/connections/mssql-db-conn.ts +22 -22
- package/src/connections/mysql-db-conn.ts +27 -27
- package/src/connections/postgresql-db-conn.ts +16 -16
- package/src/create-db-conn.ts +7 -7
- package/src/create-orm.ts +21 -21
- package/src/index.ts +3 -3
- package/src/node-db-context-executor.ts +32 -32
- package/src/types/db-conn.ts +40 -40
- package/README.md +0 -227
package/dist/types/db-conn.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// ============================================
|
|
2
|
+
// 공통 상수
|
|
3
|
+
// ============================================
|
|
4
|
+
/**
|
|
5
|
+
* DB 연결 수립 타임아웃 (10초)
|
|
6
|
+
*/
|
|
7
|
+
export const DB_CONN_CONNECT_TIMEOUT = 10 * 1000;
|
|
8
|
+
/**
|
|
9
|
+
* DB 쿼리 기본 타임아웃 (10분)
|
|
10
|
+
*/
|
|
11
|
+
export const DB_CONN_DEFAULT_TIMEOUT = 10 * 60 * 1000;
|
|
12
|
+
/**
|
|
13
|
+
* DB 연결 오류 메시지
|
|
14
|
+
*/
|
|
15
|
+
export const DB_CONN_ERRORS = {
|
|
16
|
+
NOT_CONNECTED: "'Connection'이 연결되어 있지 않습니다.",
|
|
17
|
+
ALREADY_CONNECTED: "'Connection'이 이미 연결되어 있습니다.",
|
|
6
18
|
};
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
/**
|
|
20
|
+
* DbConnConfig에서 Dialect를 추출한다
|
|
21
|
+
*/
|
|
22
|
+
export function getDialectFromConfig(config) {
|
|
23
|
+
if (config.dialect === "mssql-azure") {
|
|
24
|
+
return "mssql";
|
|
25
|
+
}
|
|
26
|
+
return config.dialect;
|
|
12
27
|
}
|
|
13
|
-
|
|
14
|
-
DB_CONN_CONNECT_TIMEOUT,
|
|
15
|
-
DB_CONN_DEFAULT_TIMEOUT,
|
|
16
|
-
DB_CONN_ERRORS,
|
|
17
|
-
getDialectFromConfig
|
|
18
|
-
};
|
|
19
|
-
//# sourceMappingURL=db-conn.js.map
|
|
28
|
+
//# sourceMappingURL=db-conn.js.map
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/types/db-conn.ts"],
|
|
4
|
-
"mappings": "AAUO,MAAM,0BAA0B,KAAK;AAKrC,MAAM,0BAA0B,KAAK,KAAK;AAK1C,MAAM,iBAAiB;AAAA,EAC5B,eAAe;AAAA,EACf,mBAAmB;AACrB;AAoJO,SAAS,qBAAqB,QAA+B;AAClE,MAAI,OAAO,YAAY,eAAe;AACpC,WAAO;AAAA,EACT;AACA,SAAO,OAAO;AAChB;",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
|
1
|
+
{"version":3,"file":"db-conn.js","sourceRoot":"","sources":["..\\..\\src\\types\\db-conn.ts"],"names":[],"mappings":"AAGA,+CAA+C;AAC/C,QAAQ;AACR,+CAA+C;AAE/C;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEtD;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,aAAa,EAAE,6BAA6B;IAC5C,iBAAiB,EAAE,6BAA6B;CACxC,CAAC;AAiJX;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,IAAI,MAAM,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;QACrC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/orm-node",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "14.0.1",
|
|
4
|
+
"description": "심플리즘 패키지 - ORM (node)",
|
|
5
|
+
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"sideEffects": false,
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"consola": "^3.4.2",
|
|
22
|
-
"@simplysm/core-common": "
|
|
23
|
-
"@simplysm/orm-common": "
|
|
22
|
+
"@simplysm/core-common": "14.0.1",
|
|
23
|
+
"@simplysm/orm-common": "14.0.1"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/pg": "^8.20.0",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"tedious": "^19.2.1"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"mysql2": "^3
|
|
35
|
-
"pg": "^8
|
|
36
|
-
"pg-copy-streams": "^7
|
|
37
|
-
"tedious": "^19
|
|
34
|
+
"mysql2": "^3",
|
|
35
|
+
"pg": "^8",
|
|
36
|
+
"pg-copy-streams": "^7",
|
|
37
|
+
"tedious": "^19"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"mysql2": {
|
|
@@ -24,9 +24,9 @@ import type { DataType as TediousDataType } from "tedious/lib/data-type";
|
|
|
24
24
|
const logger = consola.withTag("mssql-db-conn");
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* MSSQL
|
|
27
|
+
* MSSQL 데이터베이스 연결 클래스
|
|
28
28
|
*
|
|
29
|
-
*
|
|
29
|
+
* tedious 라이브러리를 사용하여 MSSQL/Azure SQL 연결을 관리한다.
|
|
30
30
|
*/
|
|
31
31
|
export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn {
|
|
32
32
|
private readonly _timeout = DB_CONN_DEFAULT_TIMEOUT;
|
|
@@ -72,15 +72,15 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
conn.on("infoMessage", (info) => {
|
|
75
|
-
logger.debug("
|
|
75
|
+
logger.debug("정보", info.message);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
conn.on("errorMessage", (error) => {
|
|
79
|
-
logger.error("
|
|
79
|
+
logger.error("오류 메시지", error.message);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
conn.on("error", (error) => {
|
|
83
|
-
logger.error("
|
|
83
|
+
logger.error("오류", error.message);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
conn.on("end", () => {
|
|
@@ -114,11 +114,11 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
114
114
|
|
|
115
115
|
const conn = this._conn;
|
|
116
116
|
|
|
117
|
-
//
|
|
117
|
+
// 진행 중인 요청 취소
|
|
118
118
|
conn.cancel();
|
|
119
119
|
await wait.until(() => this._requests.length < 1, 30000, 100);
|
|
120
120
|
|
|
121
|
-
//
|
|
121
|
+
// 연결 종료 대기
|
|
122
122
|
await new Promise<void>((resolve) => {
|
|
123
123
|
conn.on("end", () => {
|
|
124
124
|
wait.until(() => this._conn == null, 30000, 100)
|
|
@@ -213,7 +213,7 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
213
213
|
|
|
214
214
|
const results: Record<string, unknown>[][] = [];
|
|
215
215
|
|
|
216
|
-
logger.debug("
|
|
216
|
+
logger.debug("쿼리 실행", { queryLength: query.length, params });
|
|
217
217
|
await new Promise<void>((resolve, reject) => {
|
|
218
218
|
let rejected = false;
|
|
219
219
|
const queryRequest = new this._tedious.Request(query, (err) => {
|
|
@@ -223,17 +223,17 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
223
223
|
|
|
224
224
|
const errCode = "code" in err ? err.code : undefined;
|
|
225
225
|
if (errCode === "ECANCEL") {
|
|
226
|
-
reject(new SdError(err, "
|
|
226
|
+
reject(new SdError(err, "쿼리가 취소되었습니다."));
|
|
227
227
|
} else {
|
|
228
228
|
const lineNumber = "lineNumber" in err && typeof err.lineNumber === "number" ? err.lineNumber : undefined;
|
|
229
229
|
if (lineNumber != null && lineNumber > 0) {
|
|
230
230
|
const splitQuery = query.split("\n");
|
|
231
231
|
splitQuery[lineNumber - 1] = "==> " + splitQuery[lineNumber - 1];
|
|
232
232
|
reject(
|
|
233
|
-
new SdError(err,
|
|
233
|
+
new SdError(err, `쿼리 실행 오류\n-- query\n${splitQuery.join("\n")}\n--`),
|
|
234
234
|
);
|
|
235
235
|
} else {
|
|
236
|
-
reject(new SdError(err,
|
|
236
|
+
reject(new SdError(err, `쿼리 실행 오류\n-- query\n${query}\n--`));
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
}
|
|
@@ -267,7 +267,7 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
267
267
|
|
|
268
268
|
rejected = true;
|
|
269
269
|
this._requests = this._requests.filter((r) => r !== queryRequest);
|
|
270
|
-
reject(new SdError(err,
|
|
270
|
+
reject(new SdError(err, `쿼리 실행 오류\n-- query\n${query}\n--`));
|
|
271
271
|
})
|
|
272
272
|
.on("requestCompleted", () => {
|
|
273
273
|
this._startTimeout();
|
|
@@ -320,7 +320,7 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
320
320
|
reject(
|
|
321
321
|
new SdError(
|
|
322
322
|
err,
|
|
323
|
-
`Bulk Insert
|
|
323
|
+
`Bulk Insert 오류\n${json.stringify(tediousColumnDefs)}\n-- data\n${json.stringify(records).substring(0, 10000)}...\n--`,
|
|
324
324
|
),
|
|
325
325
|
);
|
|
326
326
|
return;
|
|
@@ -334,7 +334,7 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
334
334
|
bulkLoad.addColumn(tediousColumnDef.name, tediousColumnDef.type, tediousColumnDef.options);
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
//
|
|
337
|
+
// 레코드를 행 배열로 변환 (컬럼 순서 유지, 값 변환 포함)
|
|
338
338
|
const rows = records.map((record) =>
|
|
339
339
|
colNames.map((colName) => {
|
|
340
340
|
const val = record[colName];
|
|
@@ -353,7 +353,7 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
// ─────────────────────────────────────────────
|
|
356
|
-
//
|
|
356
|
+
// 내부 헬퍼
|
|
357
357
|
// ─────────────────────────────────────────────
|
|
358
358
|
|
|
359
359
|
private _assertConnected(): void {
|
|
@@ -391,7 +391,7 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
391
391
|
this._stopTimeout();
|
|
392
392
|
this._connTimeout = setTimeout(() => {
|
|
393
393
|
this.close().catch((err) => {
|
|
394
|
-
logger.error("
|
|
394
|
+
logger.error("연결 종료 오류", err instanceof Error ? err.message : String(err));
|
|
395
395
|
});
|
|
396
396
|
}, this._timeout * 2);
|
|
397
397
|
}
|
|
@@ -457,19 +457,19 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
457
457
|
case "uuid":
|
|
458
458
|
return { type: this._tedious.TYPES.UniqueIdentifier };
|
|
459
459
|
default:
|
|
460
|
-
throw new SdError(
|
|
460
|
+
throw new SdError(`지원하지 않는 DataType: ${JSON.stringify(dataType)}`);
|
|
461
461
|
}
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
/**
|
|
465
|
-
*
|
|
465
|
+
* 값의 타입을 추론하여 Tedious 데이터 타입을 반환한다
|
|
466
466
|
*
|
|
467
|
-
* @param value -
|
|
468
|
-
* @throws
|
|
467
|
+
* @param value - 타입을 추론할 값 (null/undefined 전달 시 오류 발생)
|
|
468
|
+
* @throws null/undefined 전달 시 오류
|
|
469
469
|
*/
|
|
470
470
|
private _guessTediousType(value: unknown): TediousDataType {
|
|
471
471
|
if (value == null) {
|
|
472
|
-
throw new SdError("_guessTediousType: null/undefined
|
|
472
|
+
throw new SdError("_guessTediousType: null/undefined 값은 지원하지 않습니다.");
|
|
473
473
|
}
|
|
474
474
|
if (typeof value === "string") {
|
|
475
475
|
return this._tedious.TYPES.NVarChar;
|
|
@@ -484,7 +484,7 @@ export class MssqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
484
484
|
if (value instanceof Uuid) return this._tedious.TYPES.UniqueIdentifier;
|
|
485
485
|
if (value instanceof Uint8Array) return this._tedious.TYPES.VarBinary;
|
|
486
486
|
|
|
487
|
-
throw new SdError(
|
|
487
|
+
throw new SdError(`알 수 없는 값 타입: ${typeof value}`);
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
|
|
@@ -25,9 +25,9 @@ import {
|
|
|
25
25
|
const logger = consola.withTag("mysql-db-conn");
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* MySQL
|
|
28
|
+
* MySQL 데이터베이스 연결 클래스
|
|
29
29
|
*
|
|
30
|
-
*
|
|
30
|
+
* mysql2/promise 라이브러리를 사용하여 MySQL 연결을 관리한다.
|
|
31
31
|
*/
|
|
32
32
|
export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn {
|
|
33
33
|
private static readonly _ROOT_USER = "root";
|
|
@@ -56,12 +56,12 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
56
56
|
port: this.config.port,
|
|
57
57
|
user: this.config.username,
|
|
58
58
|
password: this.config.password,
|
|
59
|
-
//
|
|
60
|
-
//
|
|
59
|
+
// root 사용자는 특정 데이터베이스에 바인딩하지 않고 연결
|
|
60
|
+
// 모든 데이터베이스에 접근할 수 있도록 (관리 작업용)
|
|
61
61
|
database: this.config.username === MysqlDbConn._ROOT_USER ? undefined : this.config.database,
|
|
62
62
|
multipleStatements: true,
|
|
63
63
|
charset: "utf8mb4",
|
|
64
|
-
infileStreamFactory: (filePath: string) => fs.createReadStream(filePath), //
|
|
64
|
+
infileStreamFactory: (filePath: string) => fs.createReadStream(filePath), // LOAD DATA LOCAL INFILE 지원
|
|
65
65
|
} as Parameters<typeof this._mysql2.createConnection>[0]);
|
|
66
66
|
|
|
67
67
|
conn.on("end", () => {
|
|
@@ -70,7 +70,7 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
conn.on("error", (error) => {
|
|
73
|
-
logger.error("DB
|
|
73
|
+
logger.error("DB 연결 오류", error.message);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
this._conn = conn;
|
|
@@ -100,13 +100,13 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
100
100
|
"READ_UNCOMMITTED"
|
|
101
101
|
).replace(/_/g, " ");
|
|
102
102
|
|
|
103
|
-
//
|
|
103
|
+
// 격리 수준을 먼저 설정 (다음 트랜잭션에 적용)
|
|
104
104
|
await conn.query({
|
|
105
105
|
sql: `SET SESSION TRANSACTION ISOLATION LEVEL ${level}`,
|
|
106
106
|
timeout: this._timeout,
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
//
|
|
109
|
+
// 그 후 트랜잭션 시작
|
|
110
110
|
await conn.beginTransaction();
|
|
111
111
|
|
|
112
112
|
this.isInTransaction = true;
|
|
@@ -139,7 +139,7 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
139
139
|
): Promise<Record<string, unknown>[][]> {
|
|
140
140
|
const conn = this._assertConnected();
|
|
141
141
|
|
|
142
|
-
logger.debug("
|
|
142
|
+
logger.debug("쿼리 실행", { queryLength: query.length, params });
|
|
143
143
|
|
|
144
144
|
try {
|
|
145
145
|
const [queryResults] = await conn.query({
|
|
@@ -150,9 +150,9 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
150
150
|
|
|
151
151
|
this._startTimeout();
|
|
152
152
|
|
|
153
|
-
// MySQL
|
|
154
|
-
//
|
|
155
|
-
// ResultSetHeader
|
|
153
|
+
// MySQL은 INSERT/UPDATE/DELETE 문에 대해 ResultSetHeader를 반환한다
|
|
154
|
+
// ResultSetHeader 객체를 필터링하여 SELECT 결과만 추출
|
|
155
|
+
// ResultSetHeader에는 affectedRows, fieldCount 등의 필드가 있다
|
|
156
156
|
const result: Record<string, unknown>[] = [];
|
|
157
157
|
if (queryResults instanceof Array) {
|
|
158
158
|
for (const queryResult of queryResults.filter(
|
|
@@ -174,7 +174,7 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
174
174
|
const error = err as Error & { sql?: string };
|
|
175
175
|
throw new SdError(
|
|
176
176
|
error,
|
|
177
|
-
"
|
|
177
|
+
"쿼리 실행 오류" +
|
|
178
178
|
(error.sql != null ? "\n-- query\n" + error.sql.trim() + "\n--" : ""),
|
|
179
179
|
);
|
|
180
180
|
}
|
|
@@ -191,12 +191,12 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
191
191
|
|
|
192
192
|
const colNames = Object.keys(columnMetas);
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// 임시 CSV 파일 생성
|
|
195
195
|
const tmpDir = os.tmpdir();
|
|
196
196
|
const tmpFile = path.join(tmpDir, `mysql_bulk_${randomUUID()}.csv`);
|
|
197
197
|
|
|
198
198
|
try {
|
|
199
|
-
//
|
|
199
|
+
// CSV 데이터 생성
|
|
200
200
|
const csvLines: string[] = [];
|
|
201
201
|
for (const record of records) {
|
|
202
202
|
const row = colNames.map((colName) =>
|
|
@@ -206,10 +206,10 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
206
206
|
}
|
|
207
207
|
const csvContent = csvLines.join("\n");
|
|
208
208
|
|
|
209
|
-
//
|
|
209
|
+
// 파일 쓰기
|
|
210
210
|
await fs.promises.writeFile(tmpFile, csvContent, "utf8");
|
|
211
211
|
|
|
212
|
-
// UUID/binary
|
|
212
|
+
// UUID/binary 컬럼은 임시 변수로 읽어들인 후 SET 절에서 UNHEX()로 변환
|
|
213
213
|
const binaryColNames = colNames.filter((c) => {
|
|
214
214
|
const dt = columnMetas[c].dataType.type;
|
|
215
215
|
return dt === "uuid" || dt === "binary";
|
|
@@ -220,7 +220,7 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
220
220
|
});
|
|
221
221
|
const setClauses = binaryColNames.map((c) => `\`${c}\` = UNHEX(@_${c})`);
|
|
222
222
|
|
|
223
|
-
//
|
|
223
|
+
// LOAD DATA LOCAL INFILE 실행
|
|
224
224
|
let query = `LOAD DATA LOCAL INFILE ? INTO TABLE ${tableName} FIELDS TERMINATED BY '\\t' LINES TERMINATED BY '\\n' (${normalCols.join(", ")})`;
|
|
225
225
|
if (setClauses.length > 0) {
|
|
226
226
|
query += ` SET ${setClauses.join(", ")}`;
|
|
@@ -228,25 +228,25 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
228
228
|
|
|
229
229
|
await conn.query({ sql: query, timeout: this._timeout, values: [tmpFile] });
|
|
230
230
|
} finally {
|
|
231
|
-
//
|
|
231
|
+
// 임시 파일 삭제
|
|
232
232
|
try {
|
|
233
233
|
await fs.promises.unlink(tmpFile);
|
|
234
234
|
} catch {
|
|
235
|
-
//
|
|
235
|
+
// 삭제 실패 무시
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
// ─────────────────────────────────────────────
|
|
241
|
-
//
|
|
241
|
+
// 내부 헬퍼
|
|
242
242
|
// ─────────────────────────────────────────────
|
|
243
243
|
|
|
244
244
|
/**
|
|
245
|
-
*
|
|
245
|
+
* MySQL LOAD DATA INFILE용 값 이스케이프
|
|
246
246
|
*/
|
|
247
247
|
private _escapeForCsv(value: unknown, dataType: DataType): string {
|
|
248
248
|
if (value == null) {
|
|
249
|
-
return "\\N"; // MySQL NULL
|
|
249
|
+
return "\\N"; // MySQL NULL 표현
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
switch (dataType.type) {
|
|
@@ -264,7 +264,7 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
264
264
|
case "char":
|
|
265
265
|
case "text": {
|
|
266
266
|
const strVal = value as string;
|
|
267
|
-
//
|
|
267
|
+
// 탭, 줄바꿈, 백슬래시 이스케이프
|
|
268
268
|
return strVal
|
|
269
269
|
.replace(/\\/g, "\\\\")
|
|
270
270
|
.replace(/\0/g, "\\0")
|
|
@@ -283,13 +283,13 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
283
283
|
return (value as Time).toFormatString("HH:mm:ss");
|
|
284
284
|
|
|
285
285
|
case "uuid":
|
|
286
|
-
return (value as Uuid).toString().replace(/-/g, ""); //
|
|
286
|
+
return (value as Uuid).toString().replace(/-/g, ""); // BINARY(16) 저장용 Hex
|
|
287
287
|
|
|
288
288
|
case "binary":
|
|
289
289
|
return bytes.toHex(value as Uint8Array);
|
|
290
290
|
|
|
291
291
|
default:
|
|
292
|
-
throw new SdError(
|
|
292
|
+
throw new SdError(`지원하지 않는 DataType: ${JSON.stringify(dataType)}`);
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
|
|
@@ -317,7 +317,7 @@ export class MysqlDbConn extends EventEmitter<{ close: void }> implements DbConn
|
|
|
317
317
|
this._stopTimeout();
|
|
318
318
|
this._connTimeout = setTimeout(() => {
|
|
319
319
|
this.close().catch((err) => {
|
|
320
|
-
logger.error("
|
|
320
|
+
logger.error("연결 종료 오류", err instanceof Error ? err.message : String(err));
|
|
321
321
|
});
|
|
322
322
|
}, this._timeout * 2);
|
|
323
323
|
}
|
|
@@ -23,9 +23,9 @@ import type { Client } from "pg";
|
|
|
23
23
|
const logger = consola.withTag("postgresql-db-conn");
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* PostgreSQL
|
|
26
|
+
* PostgreSQL 데이터베이스 연결 클래스
|
|
27
27
|
*
|
|
28
|
-
*
|
|
28
|
+
* pg 라이브러리를 사용하여 PostgreSQL 연결을 관리한다.
|
|
29
29
|
*/
|
|
30
30
|
export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements DbConn {
|
|
31
31
|
private readonly _timeout = DB_CONN_DEFAULT_TIMEOUT;
|
|
@@ -65,7 +65,7 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
client.on("error", (error) => {
|
|
68
|
-
logger.error("DB
|
|
68
|
+
logger.error("DB 연결 오류", error.message);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
await client.connect();
|
|
@@ -130,19 +130,19 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
130
130
|
): Promise<Record<string, unknown>[][]> {
|
|
131
131
|
this._assertConnected();
|
|
132
132
|
|
|
133
|
-
logger.debug("
|
|
133
|
+
logger.debug("쿼리 실행", { queryLength: query.length, params });
|
|
134
134
|
|
|
135
135
|
try {
|
|
136
136
|
const result = await this._client!.query(query, params);
|
|
137
137
|
|
|
138
138
|
this._startTimeout();
|
|
139
139
|
|
|
140
|
-
// PostgreSQL
|
|
140
|
+
// PostgreSQL은 단일 결과 집합을 반환한다
|
|
141
141
|
return [result.rows];
|
|
142
142
|
} catch (err) {
|
|
143
143
|
this._startTimeout();
|
|
144
144
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
145
|
-
throw new SdError(error, "
|
|
145
|
+
throw new SdError(error, "쿼리 실행 오류\n-- query\n" + query.trim() + "\n--");
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -158,11 +158,11 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
158
158
|
const colNames = Object.keys(columnMetas);
|
|
159
159
|
const wrappedCols = colNames.map((c) => `"${c}"`).join(", ");
|
|
160
160
|
|
|
161
|
-
//
|
|
161
|
+
// COPY FROM STDIN 스트림 생성
|
|
162
162
|
const copyQuery = `COPY ${tableName} (${wrappedCols}) FROM STDIN WITH (FORMAT csv, NULL '\\N')`;
|
|
163
163
|
const stream = this._client!.query(this._pgCopyStreams.from(copyQuery));
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// CSV 데이터 생성
|
|
166
166
|
const csvLines: string[] = [];
|
|
167
167
|
for (const record of records) {
|
|
168
168
|
const row = colNames.map((colName) =>
|
|
@@ -172,7 +172,7 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
172
172
|
}
|
|
173
173
|
const csvContent = csvLines.join("\n") + "\n";
|
|
174
174
|
|
|
175
|
-
//
|
|
175
|
+
// 스트림을 통해 데이터 전송
|
|
176
176
|
await new Promise<void>((resolve, reject) => {
|
|
177
177
|
const readable = Readable.from([csvContent]);
|
|
178
178
|
|
|
@@ -185,15 +185,15 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// ─────────────────────────────────────────────
|
|
188
|
-
//
|
|
188
|
+
// 내부 헬퍼
|
|
189
189
|
// ─────────────────────────────────────────────
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
*
|
|
192
|
+
* PostgreSQL COPY CSV용 값 이스케이프
|
|
193
193
|
*/
|
|
194
194
|
private _escapeForCsv(value: unknown, dataType: DataType): string {
|
|
195
195
|
if (value == null) {
|
|
196
|
-
return "\\N"; // NULL
|
|
196
|
+
return "\\N"; // NULL 표현
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
switch (dataType.type) {
|
|
@@ -211,7 +211,7 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
211
211
|
case "char":
|
|
212
212
|
case "text": {
|
|
213
213
|
const strVal = value as string;
|
|
214
|
-
// CSV
|
|
214
|
+
// CSV 형식: 큰따옴표로 감싸고, 내부 큰따옴표는 이중 큰따옴표로 이스케이프
|
|
215
215
|
if (
|
|
216
216
|
strVal.includes('"') ||
|
|
217
217
|
strVal.includes(",") ||
|
|
@@ -237,10 +237,10 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
237
237
|
return (value as Uuid).toString();
|
|
238
238
|
|
|
239
239
|
case "binary":
|
|
240
|
-
return '"\\x' + bytes.toHex(value as Uint8Array) + '"'; // PostgreSQL bytea hex
|
|
240
|
+
return '"\\x' + bytes.toHex(value as Uint8Array) + '"'; // PostgreSQL bytea hex 형식 (CSV 큰따옴표로 감쌈)
|
|
241
241
|
|
|
242
242
|
default:
|
|
243
|
-
throw new SdError(
|
|
243
|
+
throw new SdError(`지원하지 않는 DataType: ${JSON.stringify(dataType)}`);
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -267,7 +267,7 @@ export class PostgresqlDbConn extends EventEmitter<{ close: void }> implements D
|
|
|
267
267
|
this._stopTimeout();
|
|
268
268
|
this._connTimeout = setTimeout(() => {
|
|
269
269
|
this.close().catch((err) => {
|
|
270
|
-
logger.error("
|
|
270
|
+
logger.error("연결 종료 오류", err instanceof Error ? err.message : String(err));
|
|
271
271
|
});
|
|
272
272
|
}, this._timeout * 2);
|
|
273
273
|
}
|
package/src/create-db-conn.ts
CHANGED
|
@@ -4,13 +4,13 @@ import { MssqlDbConn } from "./connections/mssql-db-conn";
|
|
|
4
4
|
import { PostgresqlDbConn } from "./connections/postgresql-db-conn";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* DB
|
|
7
|
+
* DB 연결 팩토리
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* 데이터베이스 연결 인스턴스를 생성한다.
|
|
10
|
+
* MSSQL, MySQL, PostgreSQL을 지원한다.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// 지연 로딩 모듈 캐시
|
|
14
14
|
const modules: {
|
|
15
15
|
tedious?: typeof import("tedious");
|
|
16
16
|
mysql?: typeof import("mysql2/promise");
|
|
@@ -19,10 +19,10 @@ const modules: {
|
|
|
19
19
|
} = {};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* DB 연결 생성
|
|
23
23
|
*
|
|
24
|
-
* @param config -
|
|
25
|
-
* @returns DB
|
|
24
|
+
* @param config - 데이터베이스 연결 설정
|
|
25
|
+
* @returns DB 연결 객체 (아직 연결되지 않음 - connect()를 별도로 호출해야 함)
|
|
26
26
|
*/
|
|
27
27
|
export async function createDbConn(config: DbConnConfig): Promise<DbConn> {
|
|
28
28
|
if (config.dialect === "mysql") {
|