@simplysm/orm-common 13.0.100 → 14.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -147
- package/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +312 -276
- package/dist/create-db-context.js.map +1 -6
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/column-ddl.js +41 -35
- package/dist/ddl/column-ddl.js.map +1 -6
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +200 -142
- package/dist/ddl/initialize.js.map +1 -6
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/relation-ddl.js +55 -48
- package/dist/ddl/relation-ddl.js.map +1 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/schema-ddl.js +21 -15
- package/dist/ddl/schema-ddl.js.map +1 -6
- package/dist/ddl/table-ddl.d.ts +20 -20
- package/dist/ddl/table-ddl.js +139 -93
- package/dist/ddl/table-ddl.js.map +1 -6
- package/dist/define-db-context.js +10 -13
- package/dist/define-db-context.js.map +1 -6
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/errors/db-transaction-error.js +53 -19
- package/dist/errors/db-transaction-error.js.map +1 -6
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +94 -40
- package/dist/exec/executable.js.map +1 -6
- package/dist/exec/queryable.d.ts +97 -97
- package/dist/exec/queryable.js +1310 -1204
- package/dist/exec/queryable.js.map +1 -6
- package/dist/exec/search-parser.d.ts +31 -31
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/exec/search-parser.js +158 -59
- package/dist/exec/search-parser.js.map +1 -6
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr-unit.js +24 -18
- package/dist/expr/expr-unit.js.map +1 -6
- package/dist/expr/expr.d.ts +108 -108
- package/dist/expr/expr.js +1872 -1844
- package/dist/expr/expr.js.map +1 -6
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -6
- package/dist/models/system-migration.js +7 -7
- package/dist/models/system-migration.js.map +1 -6
- package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
- package/dist/query-builder/base/expr-renderer-base.js +27 -21
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
- package/dist/query-builder/base/query-builder-base.d.ts +21 -21
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +90 -80
- package/dist/query-builder/base/query-builder-base.js.map +1 -6
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +5 -5
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
- 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 +483 -443
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +5 -5
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +10 -10
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +5 -5
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/query-builder/query-builder.js +13 -13
- package/dist/query-builder/query-builder.js.map +1 -6
- package/dist/schema/factory/column-builder.d.ts +84 -84
- package/dist/schema/factory/column-builder.js +248 -185
- package/dist/schema/factory/column-builder.js.map +1 -6
- package/dist/schema/factory/index-builder.d.ts +38 -38
- package/dist/schema/factory/index-builder.js +144 -85
- package/dist/schema/factory/index-builder.js.map +1 -6
- 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 +274 -136
- package/dist/schema/factory/relation-builder.js.map +1 -6
- package/dist/schema/procedure-builder.d.ts +51 -51
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +205 -131
- package/dist/schema/procedure-builder.js.map +1 -6
- package/dist/schema/table-builder.d.ts +55 -55
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +274 -205
- package/dist/schema/table-builder.js.map +1 -6
- package/dist/schema/view-builder.d.ts +44 -44
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +189 -116
- package/dist/schema/view-builder.js.map +1 -6
- package/dist/types/column.d.ts +21 -21
- package/dist/types/column.js +60 -30
- package/dist/types/column.js.map +1 -6
- package/dist/types/db-context-def.d.ts +9 -9
- package/dist/types/db-context-def.js +2 -1
- package/dist/types/db-context-def.js.map +1 -6
- package/dist/types/db.d.ts +47 -47
- package/dist/types/db.js +15 -5
- package/dist/types/db.js.map +1 -6
- package/dist/types/expr.d.ts +81 -81
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/expr.js +3 -1
- package/dist/types/expr.js.map +1 -6
- package/dist/types/query-def.d.ts +46 -46
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/types/query-def.js +31 -24
- package/dist/types/query-def.js.map +1 -6
- package/dist/utils/result-parser.d.ts +11 -11
- package/dist/utils/result-parser.js +362 -221
- package/dist/utils/result-parser.js.map +1 -6
- package/docs/core.md +117 -145
- package/docs/expression.md +186 -203
- package/docs/query-builder.md +75 -42
- package/docs/queryable.md +189 -151
- package/docs/schema-builders.md +172 -283
- package/docs/types.md +229 -173
- package/package.json +7 -5
- package/src/create-db-context.ts +31 -31
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +38 -38
- package/src/ddl/relation-ddl.ts +6 -6
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +24 -24
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +152 -152
- package/src/exec/search-parser.ts +50 -50
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +118 -118
- package/src/index.ts +8 -8
- package/src/models/system-migration.ts +1 -1
- package/src/query-builder/base/expr-renderer-base.ts +21 -21
- package/src/query-builder/base/query-builder-base.ts +33 -33
- package/src/query-builder/mssql/mssql-expr-renderer.ts +28 -28
- package/src/query-builder/mssql/mssql-query-builder.ts +37 -37
- package/src/query-builder/mysql/mysql-expr-renderer.ts +29 -29
- package/src/query-builder/mysql/mysql-query-builder.ts +70 -70
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +22 -22
- package/src/query-builder/postgresql/postgresql-query-builder.ts +54 -54
- package/src/query-builder/query-builder.ts +1 -1
- package/src/schema/factory/column-builder.ts +86 -86
- package/src/schema/factory/index-builder.ts +38 -38
- package/src/schema/factory/relation-builder.ts +102 -102
- package/src/schema/procedure-builder.ts +52 -52
- package/src/schema/table-builder.ts +56 -56
- package/src/schema/view-builder.ts +47 -47
- package/src/types/column.ts +24 -24
- package/src/types/db-context-def.ts +15 -15
- package/src/types/db.ts +50 -50
- package/src/types/expr.ts +103 -103
- package/src/types/query-def.ts +50 -50
- package/src/utils/result-parser.ts +88 -88
- package/docs/utilities.md +0 -27
- package/tests/db-context/create-db-context.spec.ts +0 -193
- package/tests/db-context/define-db-context.spec.ts +0 -17
- package/tests/ddl/basic.expected.ts +0 -341
- package/tests/ddl/basic.spec.ts +0 -557
- package/tests/ddl/column-builder.expected.ts +0 -310
- package/tests/ddl/column-builder.spec.ts +0 -525
- package/tests/ddl/index-builder.expected.ts +0 -38
- package/tests/ddl/index-builder.spec.ts +0 -148
- package/tests/ddl/procedure-builder.expected.ts +0 -52
- package/tests/ddl/procedure-builder.spec.ts +0 -128
- package/tests/ddl/relation-builder.expected.ts +0 -36
- package/tests/ddl/relation-builder.spec.ts +0 -171
- package/tests/ddl/table-builder.expected.ts +0 -113
- package/tests/ddl/table-builder.spec.ts +0 -399
- package/tests/ddl/view-builder.expected.ts +0 -38
- package/tests/ddl/view-builder.spec.ts +0 -116
- package/tests/dml/delete.expected.ts +0 -96
- package/tests/dml/delete.spec.ts +0 -127
- package/tests/dml/insert.expected.ts +0 -192
- package/tests/dml/insert.spec.ts +0 -210
- package/tests/dml/update.expected.ts +0 -176
- package/tests/dml/update.spec.ts +0 -222
- package/tests/dml/upsert.expected.ts +0 -215
- package/tests/dml/upsert.spec.ts +0 -190
- package/tests/errors/queryable-errors.spec.ts +0 -126
- package/tests/escape.spec.ts +0 -59
- package/tests/examples/pivot.expected.ts +0 -211
- package/tests/examples/pivot.spec.ts +0 -200
- package/tests/examples/sampling.expected.ts +0 -69
- package/tests/examples/sampling.spec.ts +0 -42
- package/tests/examples/unpivot.expected.ts +0 -120
- package/tests/examples/unpivot.spec.ts +0 -161
- package/tests/exec/search-parser.spec.ts +0 -267
- package/tests/executable/basic.expected.ts +0 -18
- package/tests/executable/basic.spec.ts +0 -54
- package/tests/expr/comparison.expected.ts +0 -282
- package/tests/expr/comparison.spec.ts +0 -334
- package/tests/expr/conditional.expected.ts +0 -134
- package/tests/expr/conditional.spec.ts +0 -249
- package/tests/expr/date.expected.ts +0 -332
- package/tests/expr/date.spec.ts +0 -459
- package/tests/expr/math.expected.ts +0 -62
- package/tests/expr/math.spec.ts +0 -59
- package/tests/expr/string.expected.ts +0 -218
- package/tests/expr/string.spec.ts +0 -300
- package/tests/expr/utility.expected.ts +0 -147
- package/tests/expr/utility.spec.ts +0 -155
- package/tests/select/basic.expected.ts +0 -322
- package/tests/select/basic.spec.ts +0 -433
- package/tests/select/filter.expected.ts +0 -357
- package/tests/select/filter.spec.ts +0 -954
- package/tests/select/group.expected.ts +0 -169
- package/tests/select/group.spec.ts +0 -159
- package/tests/select/join.expected.ts +0 -582
- package/tests/select/join.spec.ts +0 -692
- package/tests/select/order.expected.ts +0 -150
- package/tests/select/order.spec.ts +0 -140
- package/tests/select/recursive-cte.expected.ts +0 -244
- package/tests/select/recursive-cte.spec.ts +0 -514
- package/tests/select/result-meta.spec.ts +0 -270
- package/tests/select/subquery.expected.ts +0 -363
- package/tests/select/subquery.spec.ts +0 -441
- package/tests/select/view.expected.ts +0 -155
- package/tests/select/view.spec.ts +0 -235
- package/tests/select/window.expected.ts +0 -345
- package/tests/select/window.spec.ts +0 -433
- package/tests/setup/MockExecutor.ts +0 -18
- package/tests/setup/TestDbContext.ts +0 -59
- package/tests/setup/models/Company.ts +0 -13
- package/tests/setup/models/Employee.ts +0 -10
- package/tests/setup/models/MonthlySales.ts +0 -11
- package/tests/setup/models/Post.ts +0 -16
- package/tests/setup/models/Sales.ts +0 -10
- package/tests/setup/models/User.ts +0 -19
- package/tests/setup/procedure/GetAllUsers.ts +0 -9
- package/tests/setup/procedure/GetUserById.ts +0 -12
- package/tests/setup/test-utils.ts +0 -72
- package/tests/setup/views/ActiveUsers.ts +0 -8
- package/tests/setup/views/UserSummary.ts +0 -11
- package/tests/types/nullable-queryable-record.spec.ts +0 -97
- package/tests/utils/result-parser-perf.spec.ts +0 -143
- package/tests/utils/result-parser.spec.ts +0 -667
|
@@ -43,7 +43,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
43
43
|
|
|
44
44
|
//#region ========== Utilities ==========
|
|
45
45
|
|
|
46
|
-
/**
|
|
46
|
+
/** 테이블명 렌더링 */
|
|
47
47
|
protected tableName(obj: QueryDefObjectName): string {
|
|
48
48
|
const parts: string[] = [];
|
|
49
49
|
if (obj.database != null) {
|
|
@@ -58,7 +58,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
58
58
|
return parts.join(".");
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
61
|
+
/** OFFSET...FETCH 절 렌더링 */
|
|
62
62
|
protected renderLimit(limit: [number, number] | undefined): string {
|
|
63
63
|
if (limit == null) return "";
|
|
64
64
|
const [offset, count] = limit;
|
|
@@ -68,15 +68,15 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
68
68
|
protected renderJoin(join: SelectQueryDefJoin): string {
|
|
69
69
|
const alias = this.expr.wrap(join.as);
|
|
70
70
|
|
|
71
|
-
//
|
|
71
|
+
// LATERAL JOIN이 필요한지 감지 → MSSQL은 OUTER APPLY 사용
|
|
72
72
|
if (this.needsLateral(join)) {
|
|
73
|
-
//
|
|
74
|
-
//
|
|
73
|
+
// from이 배열(UNION ALL)이면 renderFrom(join.from) 사용,
|
|
74
|
+
// 그 외(orderBy, top, select 등)이면 renderFrom(join)으로 서브쿼리 생성
|
|
75
75
|
const from = Array.isArray(join.from) ? this.renderFrom(join.from) : this.renderFrom(join);
|
|
76
76
|
return ` OUTER APPLY ${from} AS ${alias}`;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
//
|
|
79
|
+
// 일반 JOIN
|
|
80
80
|
const from = this.renderFrom(join.from);
|
|
81
81
|
const where =
|
|
82
82
|
join.where != null && join.where.length > 0
|
|
@@ -122,7 +122,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
122
122
|
const from = this.renderFrom(def.from);
|
|
123
123
|
sql += ` FROM ${from} AS ${this.expr.wrap(def.as)}`;
|
|
124
124
|
|
|
125
|
-
// LOCK (
|
|
125
|
+
// LOCK (ROWLOCK으로 행 수준 잠금 강제 - MySQL/PostgreSQL의 FOR UPDATE와 동일 동작)
|
|
126
126
|
if (def.lock) {
|
|
127
127
|
sql += " WITH (UPDLOCK, ROWLOCK)";
|
|
128
128
|
}
|
|
@@ -157,7 +157,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
157
157
|
const table = this.tableName(def.table);
|
|
158
158
|
|
|
159
159
|
if (def.records.length === 0) {
|
|
160
|
-
throw new Error("INSERT
|
|
160
|
+
throw new Error("INSERT에는 최소 1개의 레코드가 필요합니다.");
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
const columns = Object.keys(def.records[0]);
|
|
@@ -165,14 +165,14 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
165
165
|
|
|
166
166
|
let sql = "";
|
|
167
167
|
|
|
168
|
-
// IDENTITY_INSERT ON (
|
|
168
|
+
// IDENTITY_INSERT ON (AI column에 명시적 값을 삽입할 때)
|
|
169
169
|
if (def.overrideIdentity) {
|
|
170
170
|
sql += `SET IDENTITY_INSERT ${table} ON;\n`;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
sql += `INSERT INTO ${table} (${colList})`;
|
|
174
174
|
|
|
175
|
-
// OUTPUT (MSSQL
|
|
175
|
+
// OUTPUT (MSSQL 네이티브 지원)
|
|
176
176
|
if (def.output != null) {
|
|
177
177
|
const outputCols = def.output.columns.map((c) => `INSERTED.${this.expr.wrap(c)}`).join(", ");
|
|
178
178
|
sql += ` OUTPUT ${outputCols}`;
|
|
@@ -191,7 +191,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
191
191
|
sql += `;\nSET IDENTITY_INSERT ${table} OFF;`;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// overrideIdentity 사용 시: SET ON → results[0], INSERT → results[1], SET OFF → results[2]
|
|
195
195
|
return { sql, resultSetIndex: def.overrideIdentity ? 1 : undefined };
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -202,7 +202,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
202
202
|
const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
203
203
|
const values = columns.map((c) => this.expr.escapeValue(def.record[c])).join(", ");
|
|
204
204
|
|
|
205
|
-
//
|
|
205
|
+
// existsSelectQuery를 SELECT 1 AS _로 렌더링
|
|
206
206
|
const existsQuerySql = this.select({
|
|
207
207
|
...def.existsSelectQuery,
|
|
208
208
|
select: { _: { type: "value", value: 1 } },
|
|
@@ -210,7 +210,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
210
210
|
|
|
211
211
|
let sql = `INSERT INTO ${table} (${colList})`;
|
|
212
212
|
|
|
213
|
-
// OUTPUT (MSSQL: OUTPUT
|
|
213
|
+
// OUTPUT (MSSQL: OUTPUT은 SELECT 앞에 위치해야 함)
|
|
214
214
|
if (def.output != null) {
|
|
215
215
|
const outputCols = def.output.columns.map((c) => `INSERTED.${this.expr.wrap(c)}`).join(", ");
|
|
216
216
|
sql += ` OUTPUT ${outputCols}`;
|
|
@@ -225,7 +225,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
225
225
|
const table = this.tableName(def.table);
|
|
226
226
|
const selectSql = this.select(def.recordsSelectQuery).sql;
|
|
227
227
|
|
|
228
|
-
//
|
|
228
|
+
// INSERT INTO SELECT에서 column 추출
|
|
229
229
|
const selectDef = def.recordsSelectQuery;
|
|
230
230
|
const colList =
|
|
231
231
|
selectDef.select != null
|
|
@@ -292,7 +292,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
292
292
|
}
|
|
293
293
|
sql += ` ${alias}`;
|
|
294
294
|
|
|
295
|
-
// OUTPUT (MSSQL: DELETED
|
|
295
|
+
// OUTPUT (MSSQL: DELETE에는 DELETED 사용)
|
|
296
296
|
if (def.output != null) {
|
|
297
297
|
const outputCols = def.output.columns.map((c) => `DELETED.${this.expr.wrap(c)}`).join(", ");
|
|
298
298
|
sql += ` OUTPUT ${outputCols}`;
|
|
@@ -310,17 +310,17 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
310
310
|
//#region ========== DML - UPSERT ==========
|
|
311
311
|
|
|
312
312
|
protected upsert(def: UpsertQueryDef): QueryBuildResult {
|
|
313
|
-
// MSSQL:
|
|
313
|
+
// MSSQL: MERGE 사용
|
|
314
314
|
const table = this.tableName(def.table);
|
|
315
315
|
const alias = this.expr.wrap(def.existsSelectQuery.as);
|
|
316
316
|
const existsWhere = def.existsSelectQuery.where;
|
|
317
317
|
|
|
318
|
-
// UPDATE SET
|
|
318
|
+
// UPDATE SET 부분
|
|
319
319
|
const updateSetParts = Object.entries(def.updateRecord).map(
|
|
320
320
|
([col, e]) => `${this.expr.wrap(col)} = ${this.expr.render(e)}`,
|
|
321
321
|
);
|
|
322
322
|
|
|
323
|
-
// INSERT
|
|
323
|
+
// INSERT 부분
|
|
324
324
|
const insertColumns = Object.keys(def.insertRecord);
|
|
325
325
|
const insertColList = insertColumns.map((c) => this.expr.wrap(c)).join(", ");
|
|
326
326
|
const insertValues = insertColumns.map((c) => this.expr.render(def.insertRecord[c])).join(", ");
|
|
@@ -356,7 +356,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
356
356
|
const colDefs = def.columns.map((col) => {
|
|
357
357
|
let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
|
|
358
358
|
|
|
359
|
-
// nullable: true → NULL,
|
|
359
|
+
// nullable: true → NULL, 아니면 → NOT NULL
|
|
360
360
|
if (col.nullable === true) {
|
|
361
361
|
colSql += " NULL";
|
|
362
362
|
} else {
|
|
@@ -374,7 +374,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
374
374
|
return colSql;
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
-
//
|
|
377
|
+
// CONSTRAINT 이름이 포함된 Primary Key
|
|
378
378
|
if (def.primaryKey != null && def.primaryKey.length > 0) {
|
|
379
379
|
const pkCols = def.primaryKey.map((c) => this.expr.wrap(c)).join(", ");
|
|
380
380
|
const pkName = this.expr.wrap(`PK_${def.table.name}`);
|
|
@@ -396,7 +396,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
protected truncate(def: TruncateQueryDef): QueryBuildResult {
|
|
399
|
-
// MSSQL: TRUNCATE
|
|
399
|
+
// MSSQL: TRUNCATE는 IDENTITY를 자동으로 초기화
|
|
400
400
|
return { sql: `TRUNCATE TABLE ${this.tableName(def.table)}` };
|
|
401
401
|
}
|
|
402
402
|
|
|
@@ -447,13 +447,13 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
447
447
|
colSql += " NOT NULL";
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
// MSSQL: ALTER COLUMN (IDENTITY
|
|
450
|
+
// MSSQL: ALTER COLUMN (IDENTITY와 DEFAULT는 별도 처리 필요)
|
|
451
451
|
return { sql: `ALTER TABLE ${table} ALTER COLUMN ${colSql}` };
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
protected renameColumn(def: RenameColumnQueryDef): QueryBuildResult {
|
|
455
455
|
const table = this.tableName(def.table);
|
|
456
|
-
// MSSQL:
|
|
456
|
+
// MSSQL: sp_rename 사용
|
|
457
457
|
const tableCol = this.expr.escapeString(`${table}.${def.column}`);
|
|
458
458
|
const newName = this.expr.escapeString(def.newName);
|
|
459
459
|
return { sql: `EXEC sp_rename '${tableCol}', '${newName}', 'COLUMN'` };
|
|
@@ -487,7 +487,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
487
487
|
|
|
488
488
|
let sql = `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(fk.name)} FOREIGN KEY (${fkCols}) REFERENCES ${targetTable} (${targetCols})`;
|
|
489
489
|
|
|
490
|
-
// MSSQL/PostgreSQL:
|
|
490
|
+
// MSSQL/PostgreSQL: FK에 대한 별도 인덱스 생성 필요
|
|
491
491
|
const idxName = `IDX_${def.table.name}_${fk.name.replace(/^FK_/, "")}`;
|
|
492
492
|
sql += `;\nCREATE INDEX ${this.expr.wrap(idxName)} ON ${table} (${fkCols});`;
|
|
493
493
|
|
|
@@ -519,7 +519,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
519
519
|
protected createView(def: CreateViewQueryDef): QueryBuildResult {
|
|
520
520
|
const view = this.tableName(def.view);
|
|
521
521
|
const selectSql = this.select(def.queryDef).sql;
|
|
522
|
-
// MSSQL: CREATE OR ALTER VIEW (2016 SP1
|
|
522
|
+
// MSSQL: CREATE OR ALTER VIEW (2016 SP1 이상)
|
|
523
523
|
return { sql: `CREATE OR ALTER VIEW ${view} AS ${selectSql}` };
|
|
524
524
|
}
|
|
525
525
|
|
|
@@ -530,7 +530,7 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
530
530
|
protected createProc(def: CreateProcQueryDef): QueryBuildResult {
|
|
531
531
|
const proc = this.tableName(def.procedure);
|
|
532
532
|
|
|
533
|
-
//
|
|
533
|
+
// 파라미터 처리
|
|
534
534
|
const paramList =
|
|
535
535
|
def.params
|
|
536
536
|
?.map((p) => {
|
|
@@ -574,13 +574,13 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
574
574
|
//#region ========== Utils ==========
|
|
575
575
|
|
|
576
576
|
protected clearSchema(def: ClearSchemaQueryDef): QueryBuildResult {
|
|
577
|
-
// SQL
|
|
577
|
+
// SQL 인젝션 방지: 식별자 유효성 검사
|
|
578
578
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(def.database)) {
|
|
579
|
-
throw new Error(
|
|
579
|
+
throw new Error(`잘못된 데이터베이스 이름: ${def.database}`);
|
|
580
580
|
}
|
|
581
581
|
const schemaName = def.schema ?? "dbo";
|
|
582
582
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schemaName)) {
|
|
583
|
-
throw new Error(
|
|
583
|
+
throw new Error(`잘못된 schema 이름: ${schemaName}`);
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
const db = this.expr.wrap(def.database);
|
|
@@ -590,22 +590,22 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
590
590
|
DECLARE @sql NVARCHAR(MAX);
|
|
591
591
|
SET @sql = N'';
|
|
592
592
|
|
|
593
|
-
--
|
|
593
|
+
-- FK 제약조건 삭제
|
|
594
594
|
SELECT @sql = @sql + N'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' + QUOTENAME(OBJECT_NAME(parent_object_id)) + N' DROP CONSTRAINT ' + QUOTENAME(name) + N';' + CHAR(13)
|
|
595
595
|
FROM ${db}.sys.foreign_keys
|
|
596
596
|
WHERE OBJECT_SCHEMA_NAME(parent_object_id) = '${schema}';
|
|
597
597
|
|
|
598
|
-
--
|
|
598
|
+
-- 테이블 삭제
|
|
599
599
|
SELECT @sql = @sql + N'DROP TABLE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) + N';' + CHAR(13)
|
|
600
600
|
FROM ${db}.sys.tables
|
|
601
601
|
WHERE SCHEMA_NAME(schema_id) = '${schema}';
|
|
602
602
|
|
|
603
|
-
--
|
|
603
|
+
-- 뷰 삭제
|
|
604
604
|
SELECT @sql = @sql + N'DROP VIEW ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) + N';' + CHAR(13)
|
|
605
605
|
FROM ${db}.sys.views
|
|
606
606
|
WHERE schema_id = SCHEMA_ID('${schema}');
|
|
607
607
|
|
|
608
|
-
--
|
|
608
|
+
-- 프로시저 삭제
|
|
609
609
|
SELECT @sql = @sql + N'DROP PROCEDURE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) + N';' + CHAR(13)
|
|
610
610
|
FROM ${db}.sys.procedures
|
|
611
611
|
WHERE SCHEMA_NAME(schema_id) = '${schema}';
|
|
@@ -615,18 +615,18 @@ EXEC sp_executesql @sql;`,
|
|
|
615
615
|
}
|
|
616
616
|
|
|
617
617
|
protected schemaExists(def: SchemaExistsQueryDef): QueryBuildResult {
|
|
618
|
-
// SQL
|
|
618
|
+
// SQL 인젝션 방지: 식별자 유효성 검사
|
|
619
619
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(def.database)) {
|
|
620
|
-
throw new Error(
|
|
620
|
+
throw new Error(`잘못된 데이터베이스 이름: ${def.database}`);
|
|
621
621
|
}
|
|
622
622
|
const schemaName = def.schema ?? "dbo";
|
|
623
623
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schemaName)) {
|
|
624
|
-
throw new Error(
|
|
624
|
+
throw new Error(`잘못된 schema 이름: ${schemaName}`);
|
|
625
625
|
}
|
|
626
626
|
|
|
627
627
|
const dbName = this.expr.escapeString(def.database);
|
|
628
628
|
const schema = this.expr.escapeString(schemaName);
|
|
629
|
-
// MSSQL:
|
|
629
|
+
// MSSQL: 데이터베이스 존재 여부 확인 후 schema 확인 (동적 SQL 사용)
|
|
630
630
|
return {
|
|
631
631
|
sql: `DECLARE @result NVARCHAR(MAX) = NULL;
|
|
632
632
|
IF EXISTS (SELECT 1 FROM sys.databases WHERE name = '${dbName}')
|
|
@@ -69,28 +69,28 @@ import type { DataType } from "../../types/column";
|
|
|
69
69
|
import { ExprRendererBase } from "../base/expr-renderer-base";
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* MySQL
|
|
72
|
+
* MySQL 표현식 렌더러
|
|
73
73
|
*/
|
|
74
74
|
export class MysqlExprRenderer extends ExprRendererBase {
|
|
75
|
-
//#region ==========
|
|
75
|
+
//#region ========== 유틸리티 (public - QueryBuilder에서도 사용) ==========
|
|
76
76
|
|
|
77
|
-
/**
|
|
77
|
+
/** 식별자 감싸기 */
|
|
78
78
|
wrap(name: string): string {
|
|
79
79
|
return `\`${name.replace(/`/g, "``")}\``;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/**
|
|
82
|
+
/** SQL 문자열 리터럴용 이스케이프 (따옴표 제외) */
|
|
83
83
|
escapeString(value: string): string {
|
|
84
84
|
return value
|
|
85
|
-
.replace(/\\/g, "\\\\") //
|
|
86
|
-
.replace(/'/g, "''") //
|
|
87
|
-
.replace(/\0/g, "\\0") // NULL
|
|
88
|
-
.replace(/\n/g, "\\n") //
|
|
89
|
-
.replace(/\r/g, "\\r") //
|
|
90
|
-
.replace(/\t/g, "\\t"); //
|
|
85
|
+
.replace(/\\/g, "\\\\") // 백슬래시 (최우선)
|
|
86
|
+
.replace(/'/g, "''") // 작은따옴표
|
|
87
|
+
.replace(/\0/g, "\\0") // NULL 바이트
|
|
88
|
+
.replace(/\n/g, "\\n") // 줄바꿈
|
|
89
|
+
.replace(/\r/g, "\\r") // 캐리지 리턴
|
|
90
|
+
.replace(/\t/g, "\\t"); // 탭
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/**
|
|
93
|
+
/** 값 이스케이프 */
|
|
94
94
|
escapeValue(value: unknown): string {
|
|
95
95
|
if (value == null) {
|
|
96
96
|
return "NULL";
|
|
@@ -119,10 +119,10 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
119
119
|
if (value instanceof Uint8Array) {
|
|
120
120
|
return `0x${bytes.toHex(value)}`;
|
|
121
121
|
}
|
|
122
|
-
throw new Error(
|
|
122
|
+
throw new Error(`알 수 없는 값 타입: ${typeof value}`);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
/** DataType → SQL
|
|
125
|
+
/** DataType → SQL 타입 변환 */
|
|
126
126
|
renderDataType(dataType: DataType): string {
|
|
127
127
|
switch (dataType.type) {
|
|
128
128
|
case "int":
|
|
@@ -182,7 +182,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
182
182
|
//#region ========== comparison (null-safe) ==========
|
|
183
183
|
|
|
184
184
|
protected eq(expr: ExprEq): string {
|
|
185
|
-
// MySQL: <=>
|
|
185
|
+
// MySQL: <=> 연산자 (NULL 안전 동등 비교)
|
|
186
186
|
return `${this.render(expr.source)} <=> ${this.render(expr.target)}`;
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -221,7 +221,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
protected like(expr: ExprLike): string {
|
|
224
|
-
//
|
|
224
|
+
// 항상 ESCAPE '\' 추가
|
|
225
225
|
return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\\\'`;
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -231,7 +231,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
231
231
|
|
|
232
232
|
protected in(expr: ExprIn): string {
|
|
233
233
|
if (expr.values.length === 0) {
|
|
234
|
-
return "1=0"; //
|
|
234
|
+
return "1=0"; // 빈 IN은 항상 false
|
|
235
235
|
}
|
|
236
236
|
const values = expr.values.map((v) => this.render(v)).join(", ");
|
|
237
237
|
return `${this.render(expr.source)} IN (${values})`;
|
|
@@ -242,7 +242,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
protected exists(expr: ExprExists): string {
|
|
245
|
-
//
|
|
245
|
+
// SELECT 1로 렌더링
|
|
246
246
|
const subquery = this.buildSelect({
|
|
247
247
|
...expr.query,
|
|
248
248
|
select: { _: { type: "value", value: 1 } },
|
|
@@ -273,7 +273,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
273
273
|
//#region ========== String (null handling) ==========
|
|
274
274
|
|
|
275
275
|
protected concat(expr: ExprConcat): string {
|
|
276
|
-
// null
|
|
276
|
+
// null 처리: IFNULL(arg, '')
|
|
277
277
|
const args = expr.args.map((a) => `IFNULL(${this.render(a)}, '')`);
|
|
278
278
|
return `CONCAT(${args.join(", ")})`;
|
|
279
279
|
}
|
|
@@ -307,12 +307,12 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
protected length(expr: ExprLength): string {
|
|
310
|
-
// null
|
|
310
|
+
// null 처리: IFNULL(arg, '')
|
|
311
311
|
return `CHAR_LENGTH(IFNULL(${this.render(expr.arg)}, ''))`;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
protected byteLength(expr: ExprByteLength): string {
|
|
315
|
-
// null
|
|
315
|
+
// null 처리: IFNULL(arg, '')
|
|
316
316
|
return `LENGTH(IFNULL(${this.render(expr.arg)}, ''))`;
|
|
317
317
|
}
|
|
318
318
|
|
|
@@ -380,7 +380,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
protected isoWeekStartDate(expr: ExprIsoWeekStartDate): string {
|
|
383
|
-
// ISO
|
|
383
|
+
// ISO 주 시작일 (월요일)
|
|
384
384
|
return `DATE_SUB(${this.render(expr.arg)}, INTERVAL (WEEKDAY(${this.render(expr.arg)})) DAY)`;
|
|
385
385
|
}
|
|
386
386
|
|
|
@@ -415,7 +415,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
protected formatDate(expr: ExprFormatDate): string {
|
|
418
|
-
// JS
|
|
418
|
+
// JS 포맷 → MySQL 포맷
|
|
419
419
|
const mysqlFormat = this.convertDateFormat(expr.format);
|
|
420
420
|
return `DATE_FORMAT(${this.render(expr.source)}, '${mysqlFormat}')`;
|
|
421
421
|
}
|
|
@@ -438,7 +438,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
private convertDateFormat(format: string): string {
|
|
441
|
-
//
|
|
441
|
+
// 단순 변환 (yyyy-MM-dd HH:mm:ss 포맷)
|
|
442
442
|
return format
|
|
443
443
|
.replace(/yyyy/g, "%Y")
|
|
444
444
|
.replace(/MM/g, "%m")
|
|
@@ -455,7 +455,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
455
455
|
protected coalesce(expr: ExprCoalesce): string {
|
|
456
456
|
if (expr.args.length === 0) return "NULL";
|
|
457
457
|
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
458
|
-
//
|
|
458
|
+
// COALESCE로 렌더링 (여러 값 중 첫 번째 non-null 반환)
|
|
459
459
|
return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
|
|
460
460
|
}
|
|
461
461
|
|
|
@@ -512,18 +512,18 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
512
512
|
//#region ========== Other ==========
|
|
513
513
|
|
|
514
514
|
protected greatest(expr: ExprGreatest): string {
|
|
515
|
-
if (expr.args.length === 0) throw new Error("greatest
|
|
515
|
+
if (expr.args.length === 0) throw new Error("greatest에는 최소 1개의 인수가 필요합니다.");
|
|
516
516
|
return `GREATEST(${expr.args.map((a) => this.render(a)).join(", ")})`;
|
|
517
517
|
}
|
|
518
518
|
|
|
519
519
|
protected least(expr: ExprLeast): string {
|
|
520
|
-
if (expr.args.length === 0) throw new Error("least
|
|
520
|
+
if (expr.args.length === 0) throw new Error("least에는 최소 1개의 인수가 필요합니다.");
|
|
521
521
|
return `LEAST(${expr.args.map((a) => this.render(a)).join(", ")})`;
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
protected rowNum(_expr: ExprRowNum): string {
|
|
525
|
-
// MySQL:
|
|
526
|
-
//
|
|
525
|
+
// MySQL: 변수 또는 ROW_NUMBER() 윈도우 함수 사용
|
|
526
|
+
// 여기서는 ROW_NUMBER()로 구현 (MySQL 8.0+)
|
|
527
527
|
return "ROW_NUMBER() OVER ()";
|
|
528
528
|
}
|
|
529
529
|
|
|
@@ -543,7 +543,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
543
543
|
const fn = this.renderWindowFn(expr.fn);
|
|
544
544
|
let over = this.renderWindowSpec(expr.spec);
|
|
545
545
|
|
|
546
|
-
// LAST_VALUE
|
|
546
|
+
// LAST_VALUE 기본 프레임은 CURRENT ROW까지만 보므로 전체 프레임을 지정해야 함
|
|
547
547
|
if (expr.fn.type === "lastValue" && over.length > 0) {
|
|
548
548
|
over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
|
|
549
549
|
}
|