@simplysm/orm-common 14.0.1 → 14.0.5
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 +163 -0
- package/dist/exec/queryable.js +18 -18
- package/dist/exec/queryable.js.map +1 -1
- package/dist/expr/expr.d.ts +102 -102
- package/dist/expr/expr.js +105 -105
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +17 -17
- 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 +26 -26
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +14 -14
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- 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 +67 -67
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +13 -13
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- 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 +47 -47
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/schema/factory/relation-builder.d.ts +8 -8
- package/dist/schema/factory/relation-builder.js +9 -9
- package/dist/schema/factory/relation-builder.js.map +1 -1
- package/dist/schema/view-builder.js +2 -2
- package/dist/schema/view-builder.js.map +1 -1
- package/dist/types/column.d.ts +21 -21
- package/dist/types/column.js +5 -5
- package/dist/utils/result-parser.d.ts +11 -11
- package/dist/utils/result-parser.js +48 -48
- package/dist/utils/result-parser.js.map +1 -1
- package/docs/core.md +157 -0
- package/docs/expression.md +220 -0
- package/docs/query-builder.md +150 -0
- package/docs/queryable.md +261 -0
- package/docs/schema-builders.md +294 -0
- package/docs/types.md +520 -0
- package/package.json +7 -3
- package/src/exec/queryable.ts +18 -18
- package/src/expr/expr.ts +105 -105
- package/src/query-builder/mssql/mssql-expr-renderer.ts +17 -17
- package/src/query-builder/mssql/mssql-query-builder.ts +26 -26
- package/src/query-builder/mysql/mysql-expr-renderer.ts +14 -14
- package/src/query-builder/mysql/mysql-query-builder.ts +67 -67
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +13 -13
- package/src/query-builder/postgresql/postgresql-query-builder.ts +47 -47
- package/src/schema/factory/relation-builder.ts +9 -9
- package/src/schema/view-builder.ts +2 -2
- package/src/types/column.ts +23 -23
- package/src/utils/result-parser.ts +49 -49
|
@@ -116,7 +116,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
116
116
|
throw new Error(`알 수 없는 값 타입: ${typeof value}`);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
/** DataType → SQL
|
|
119
|
+
/** DataType → SQL 타입 변환 */
|
|
120
120
|
renderDataType(dataType: DataType): string {
|
|
121
121
|
switch (dataType.type) {
|
|
122
122
|
case "int":
|
|
@@ -176,7 +176,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
176
176
|
//#region ========== comparison (null-safe) ==========
|
|
177
177
|
|
|
178
178
|
protected eq(expr: ExprEq): string {
|
|
179
|
-
// MSSQL:
|
|
179
|
+
// MSSQL: NULL 안전 동등 비교 (OR 패턴)
|
|
180
180
|
const left = this.render(expr.source);
|
|
181
181
|
const right = this.render(expr.target);
|
|
182
182
|
return `((${left} IS NULL AND ${right} IS NULL) OR ${left} = ${right})`;
|
|
@@ -228,7 +228,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
228
228
|
|
|
229
229
|
protected in(expr: ExprIn): string {
|
|
230
230
|
if (expr.values.length === 0) {
|
|
231
|
-
return "1=0"; //
|
|
231
|
+
return "1=0"; // 빈 IN은 항상 false
|
|
232
232
|
}
|
|
233
233
|
const values = expr.values.map((v) => this.render(v)).join(", ");
|
|
234
234
|
return `${this.render(expr.source)} IN (${values})`;
|
|
@@ -239,7 +239,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
protected exists(expr: ExprExists): string {
|
|
242
|
-
//
|
|
242
|
+
// SELECT 1로 렌더링
|
|
243
243
|
const subquery = this.buildSelect({
|
|
244
244
|
...expr.query,
|
|
245
245
|
select: { _: { type: "value", value: 1 } },
|
|
@@ -270,7 +270,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
270
270
|
//#region ========== String (null handling) ==========
|
|
271
271
|
|
|
272
272
|
protected concat(expr: ExprConcat): string {
|
|
273
|
-
// MSSQL 2012+: CONCAT
|
|
273
|
+
// MSSQL 2012+: CONCAT 함수는 NULL을 자동으로 빈 문자열로 처리
|
|
274
274
|
const args = expr.args.map((a) => this.render(a)).join(", ");
|
|
275
275
|
return `CONCAT(${args})`;
|
|
276
276
|
}
|
|
@@ -288,7 +288,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
protected padStart(expr: ExprPadStart): string {
|
|
291
|
-
// MSSQL: RIGHT(REPLICATE(
|
|
291
|
+
// MSSQL: RIGHT(REPLICATE(채움문자, 길이) + 원본, 길이)
|
|
292
292
|
const source = this.render(expr.source);
|
|
293
293
|
const len = this.render(expr.length);
|
|
294
294
|
const fill = this.render(expr.fillString);
|
|
@@ -308,12 +308,12 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
protected length(expr: ExprLength): string {
|
|
311
|
-
// MSSQL: LEN() (null
|
|
311
|
+
// MSSQL: LEN() (null 처리)
|
|
312
312
|
return `LEN(ISNULL(${this.render(expr.arg)}, N''))`;
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
protected byteLength(expr: ExprByteLength): string {
|
|
316
|
-
// MSSQL: DATALENGTH() (null
|
|
316
|
+
// MSSQL: DATALENGTH() (null 처리)
|
|
317
317
|
return `DATALENGTH(ISNULL(${this.render(expr.arg)}, N''))`;
|
|
318
318
|
}
|
|
319
319
|
|
|
@@ -321,7 +321,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
321
321
|
if (expr.length != null) {
|
|
322
322
|
return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
|
|
323
323
|
}
|
|
324
|
-
// MSSQL:
|
|
324
|
+
// MSSQL: 길이 미지정 시 끝까지
|
|
325
325
|
return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, LEN(${this.render(expr.source)}))`;
|
|
326
326
|
}
|
|
327
327
|
|
|
@@ -384,9 +384,9 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
384
384
|
|
|
385
385
|
protected isoWeekStartDate(expr: ExprIsoWeekStartDate): string {
|
|
386
386
|
const src = this.render(expr.arg);
|
|
387
|
-
// ISO
|
|
388
|
-
//
|
|
389
|
-
// (days + 6) % 7 + 1 = 1(
|
|
387
|
+
// ISO 주 시작일 (월요일) - @@DATEFIRST와 무관하게 항상 월요일 반환
|
|
388
|
+
// 원리: DATEDIFF(DAY, 0, date)는 1900-01-01(월요일)부터의 일수
|
|
389
|
+
// (days + 6) % 7 + 1 = 1(월), 2(화), ..., 7(일)
|
|
390
390
|
const weekDay = `((DATEDIFF(DAY, 0, ${src}) + 6) % 7 + 1)`;
|
|
391
391
|
return `DATEADD(DAY, 1 - ${weekDay}, CAST(${src} AS DATE))`;
|
|
392
392
|
}
|
|
@@ -411,7 +411,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
protected formatDate(expr: ExprFormatDate): string {
|
|
414
|
-
// JS
|
|
414
|
+
// JS 포맷 → MSSQL FORMAT 스타일
|
|
415
415
|
const mssqlFormat = this.convertDateFormat(expr.format);
|
|
416
416
|
return `FORMAT(${this.render(expr.source)}, '${mssqlFormat}')`;
|
|
417
417
|
}
|
|
@@ -434,7 +434,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
private convertDateFormat(format: string): string {
|
|
437
|
-
//
|
|
437
|
+
// MSSQL FORMAT 함수용 (동일 포맷 사용)
|
|
438
438
|
return format;
|
|
439
439
|
}
|
|
440
440
|
|
|
@@ -504,7 +504,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
504
504
|
protected greatest(expr: ExprGreatest): string {
|
|
505
505
|
if (expr.args.length === 0) throw new Error("greatest에는 최소 1개의 인수가 필요합니다.");
|
|
506
506
|
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
507
|
-
// MSSQL 2012+: VALUES + MAX
|
|
507
|
+
// MSSQL 2012+: VALUES + MAX 방식
|
|
508
508
|
const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
|
|
509
509
|
return `(SELECT MAX(v) FROM (VALUES ${values}) AS t(v))`;
|
|
510
510
|
}
|
|
@@ -512,7 +512,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
512
512
|
protected least(expr: ExprLeast): string {
|
|
513
513
|
if (expr.args.length === 0) throw new Error("least에는 최소 1개의 인수가 필요합니다.");
|
|
514
514
|
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
515
|
-
// MSSQL 2012+: VALUES + MIN
|
|
515
|
+
// MSSQL 2012+: VALUES + MIN 방식
|
|
516
516
|
const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
|
|
517
517
|
return `(SELECT MIN(v) FROM (VALUES ${values}) AS t(v))`;
|
|
518
518
|
}
|
|
@@ -537,7 +537,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
537
537
|
const fn = this.renderWindowFn(expr.fn);
|
|
538
538
|
let over = this.renderWindowSpec(expr.spec);
|
|
539
539
|
|
|
540
|
-
// LAST_VALUE
|
|
540
|
+
// LAST_VALUE 기본 프레임은 CURRENT ROW까지만 보므로 전체 프레임을 지정해야 함
|
|
541
541
|
if (expr.fn.type === "lastValue" && over.length > 0) {
|
|
542
542
|
over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
|
|
543
543
|
}
|
|
@@ -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
|
}
|
|
@@ -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) => {
|
|
@@ -626,7 +626,7 @@ EXEC sp_executesql @sql;`,
|
|
|
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}')
|
|
@@ -122,7 +122,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
122
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
|
|
|
@@ -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
|
|
|
@@ -522,8 +522,8 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
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
|
}
|