@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
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region ========== Core ==========
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// 함수형 API (권장)
|
|
4
4
|
export * from "./define-db-context";
|
|
5
5
|
export * from "./create-db-context";
|
|
6
6
|
export * from "./types/db-context-def";
|
|
@@ -25,12 +25,12 @@ export * from "./expr/expr-unit";
|
|
|
25
25
|
|
|
26
26
|
//#region ========== Schema Builders ==========
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// 테이블 / 뷰 / 프로시저
|
|
29
29
|
export * from "./schema/table-builder";
|
|
30
30
|
export * from "./schema/view-builder";
|
|
31
31
|
export * from "./schema/procedure-builder";
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// 팩토리
|
|
34
34
|
export * from "./schema/factory/column-builder";
|
|
35
35
|
export * from "./schema/factory/index-builder";
|
|
36
36
|
export * from "./schema/factory/relation-builder";
|
|
@@ -59,19 +59,19 @@ export * from "./query-builder/postgresql/postgresql-expr-renderer";
|
|
|
59
59
|
|
|
60
60
|
//#region ========== Types ==========
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// 데이터베이스 타입
|
|
63
63
|
export * from "./types/db";
|
|
64
64
|
|
|
65
|
-
//
|
|
65
|
+
// 결과 파싱
|
|
66
66
|
export * from "./utils/result-parser";
|
|
67
67
|
|
|
68
|
-
//
|
|
68
|
+
// column 타입
|
|
69
69
|
export * from "./types/column";
|
|
70
70
|
|
|
71
|
-
//
|
|
71
|
+
// 표현식 타입
|
|
72
72
|
export * from "./types/expr";
|
|
73
73
|
|
|
74
|
-
// QueryDef
|
|
74
|
+
// QueryDef 타입
|
|
75
75
|
export * from "./types/query-def";
|
|
76
76
|
|
|
77
77
|
//#endregion
|
|
@@ -68,12 +68,12 @@ import type {
|
|
|
68
68
|
import type { SelectQueryDef } from "../../types/query-def";
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Expr → SQL
|
|
71
|
+
* Expr → SQL 렌더링 추상 기본 클래스
|
|
72
72
|
*
|
|
73
|
-
*
|
|
74
|
-
* -
|
|
75
|
-
* -
|
|
76
|
-
* -
|
|
73
|
+
* 기본 원칙:
|
|
74
|
+
* - 모든 dialect에서 100% 동일한 로직만 구현 (dispatch)
|
|
75
|
+
* - 조금이라도 다르면 abstract로 처리
|
|
76
|
+
* - 메서드 이름이 expr.type과 동일 (동적 dispatch 가능)
|
|
77
77
|
*/
|
|
78
78
|
export abstract class ExprRendererBase {
|
|
79
79
|
constructor(protected buildSelect: (def: SelectQueryDef) => string) {}
|
|
@@ -81,31 +81,31 @@ export abstract class ExprRendererBase {
|
|
|
81
81
|
//#region ========== Public Utilities ==========
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
84
|
+
* 식별자 감싸기 (테이블명, column명 등)
|
|
85
85
|
* MySQL: `name`, MSSQL: [name], PostgreSQL: "name"
|
|
86
86
|
*/
|
|
87
87
|
abstract wrap(name: string): string;
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
90
|
+
* SQL 문자열 리터럴용 이스케이프
|
|
91
|
+
* 동적 SQL이나 시스템 쿼리에서 문자열 값으로 사용할 때 호출
|
|
92
|
+
* 예: WHERE schema_name = 'escaped_value'
|
|
93
93
|
*/
|
|
94
94
|
abstract escapeString(value: string): string;
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
*
|
|
97
|
+
* 값 이스케이프 (타입에 맞는 SQL 리터럴로 변환)
|
|
98
98
|
*/
|
|
99
99
|
abstract escapeValue(value: unknown): string;
|
|
100
100
|
|
|
101
101
|
//#endregion
|
|
102
102
|
|
|
103
|
-
//#region ========== Dispatch (100%
|
|
103
|
+
//#region ========== Dispatch (100% 동일) ==========
|
|
104
104
|
|
|
105
105
|
render(expr: Expr | WhereExpr): string {
|
|
106
106
|
const method = this[expr.type as keyof this];
|
|
107
107
|
if (typeof method !== "function") {
|
|
108
|
-
throw new Error(
|
|
108
|
+
throw new Error(`알 수 없는 Expr 타입: ${expr.type}`);
|
|
109
109
|
}
|
|
110
110
|
return (method as (e: Expr | WhereExpr) => string).call(this, expr);
|
|
111
111
|
}
|
|
@@ -125,7 +125,7 @@ export abstract class ExprRendererBase {
|
|
|
125
125
|
|
|
126
126
|
//#endregion
|
|
127
127
|
|
|
128
|
-
//#region ========== Abstract -
|
|
128
|
+
//#region ========== Abstract - 비교 (null-safe 필수) ==========
|
|
129
129
|
|
|
130
130
|
protected abstract eq(expr: ExprEq): string;
|
|
131
131
|
protected abstract gt(expr: ExprGt): string;
|
|
@@ -142,7 +142,7 @@ export abstract class ExprRendererBase {
|
|
|
142
142
|
|
|
143
143
|
//#endregion
|
|
144
144
|
|
|
145
|
-
//#region ========== Abstract -
|
|
145
|
+
//#region ========== Abstract - 논리 ==========
|
|
146
146
|
|
|
147
147
|
protected abstract not(expr: ExprNot): string;
|
|
148
148
|
protected abstract and(expr: ExprAnd): string;
|
|
@@ -150,7 +150,7 @@ export abstract class ExprRendererBase {
|
|
|
150
150
|
|
|
151
151
|
//#endregion
|
|
152
152
|
|
|
153
|
-
//#region ========== Abstract -
|
|
153
|
+
//#region ========== Abstract - 문자열 (null 처리 필수) ==========
|
|
154
154
|
|
|
155
155
|
protected abstract concat(expr: ExprConcat): string;
|
|
156
156
|
protected abstract left(expr: ExprLeft): string;
|
|
@@ -167,7 +167,7 @@ export abstract class ExprRendererBase {
|
|
|
167
167
|
|
|
168
168
|
//#endregion
|
|
169
169
|
|
|
170
|
-
//#region ========== Abstract -
|
|
170
|
+
//#region ========== Abstract - 숫자 ==========
|
|
171
171
|
|
|
172
172
|
protected abstract abs(expr: ExprAbs): string;
|
|
173
173
|
protected abstract round(expr: ExprRound): string;
|
|
@@ -176,7 +176,7 @@ export abstract class ExprRendererBase {
|
|
|
176
176
|
|
|
177
177
|
//#endregion
|
|
178
178
|
|
|
179
|
-
//#region ========== Abstract -
|
|
179
|
+
//#region ========== Abstract - 날짜 ==========
|
|
180
180
|
|
|
181
181
|
protected abstract year(expr: ExprYear): string;
|
|
182
182
|
protected abstract month(expr: ExprMonth): string;
|
|
@@ -193,7 +193,7 @@ export abstract class ExprRendererBase {
|
|
|
193
193
|
|
|
194
194
|
//#endregion
|
|
195
195
|
|
|
196
|
-
//#region ========== Abstract -
|
|
196
|
+
//#region ========== Abstract - 조건 ==========
|
|
197
197
|
|
|
198
198
|
protected abstract coalesce(expr: ExprCoalesce): string;
|
|
199
199
|
protected abstract nullIf(expr: ExprNullIf): string;
|
|
@@ -203,7 +203,7 @@ export abstract class ExprRendererBase {
|
|
|
203
203
|
|
|
204
204
|
//#endregion
|
|
205
205
|
|
|
206
|
-
//#region ========== Abstract -
|
|
206
|
+
//#region ========== Abstract - 집계 ==========
|
|
207
207
|
|
|
208
208
|
protected abstract count(expr: ExprCount): string;
|
|
209
209
|
protected abstract sum(expr: ExprSum): string;
|
|
@@ -213,7 +213,7 @@ export abstract class ExprRendererBase {
|
|
|
213
213
|
|
|
214
214
|
//#endregion
|
|
215
215
|
|
|
216
|
-
//#region ========== Abstract -
|
|
216
|
+
//#region ========== Abstract - 기타 ==========
|
|
217
217
|
|
|
218
218
|
protected abstract greatest(expr: ExprGreatest): string;
|
|
219
219
|
protected abstract least(expr: ExprLeast): string;
|
|
@@ -229,7 +229,7 @@ export abstract class ExprRendererBase {
|
|
|
229
229
|
|
|
230
230
|
//#endregion
|
|
231
231
|
|
|
232
|
-
//#region ========== Abstract -
|
|
232
|
+
//#region ========== Abstract - 시스템 ==========
|
|
233
233
|
|
|
234
234
|
protected abstract subquery(expr: ExprSubquery): string;
|
|
235
235
|
|
|
@@ -37,14 +37,14 @@ import type { QueryBuildResult } from "../../types/db";
|
|
|
37
37
|
import type { ExprRendererBase } from "./expr-renderer-base";
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* QueryDef → SQL
|
|
40
|
+
* QueryDef → SQL 렌더링 추상 기본 클래스
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
* -
|
|
44
|
-
* -
|
|
45
|
-
* -
|
|
42
|
+
* 기본 원칙:
|
|
43
|
+
* - 모든 dialect에서 100% 동일한 로직만 구현 (dispatch)
|
|
44
|
+
* - 조금이라도 다르면 abstract로 처리
|
|
45
|
+
* - 메서드 이름이 def.type과 동일 (동적 dispatch 가능)
|
|
46
46
|
*/
|
|
47
|
-
/**
|
|
47
|
+
/** 기본(비 LATERAL) JOIN의 속성 목록 */
|
|
48
48
|
const BASIC_JOIN_PROPS: ReadonlySet<string> = new Set<
|
|
49
49
|
keyof Pick<SelectQueryDefJoin, "type" | "from" | "as" | "where" | "isSingle">
|
|
50
50
|
>(["type", "from", "as", "where", "isSingle"]);
|
|
@@ -52,35 +52,35 @@ const BASIC_JOIN_PROPS: ReadonlySet<string> = new Set<
|
|
|
52
52
|
export abstract class QueryBuilderBase {
|
|
53
53
|
protected abstract expr: ExprRendererBase;
|
|
54
54
|
|
|
55
|
-
//#region ========== Dispatch (100%
|
|
55
|
+
//#region ========== Dispatch (100% 동일) ==========
|
|
56
56
|
|
|
57
57
|
build(def: QueryDef): QueryBuildResult {
|
|
58
58
|
const method = this[def.type as keyof this];
|
|
59
59
|
if (typeof method !== "function") {
|
|
60
|
-
throw new Error(
|
|
60
|
+
throw new Error(`알 수 없는 QueryDef 타입: ${def.type}`);
|
|
61
61
|
}
|
|
62
62
|
return (method as (d: QueryDef) => QueryBuildResult).call(this, def);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/**
|
|
65
|
+
/** SQL을 QueryBuildResult로 래핑하는 헬퍼 */
|
|
66
66
|
protected result(sql: string, resultSetIndex?: number): QueryBuildResult {
|
|
67
67
|
return resultSetIndex != null ? { sql, resultSetIndex } : { sql };
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
//#endregion
|
|
71
71
|
|
|
72
|
-
//#region ==========
|
|
72
|
+
//#region ========== 공통 렌더링 메서드 (100% 동일) ==========
|
|
73
73
|
|
|
74
|
-
/**
|
|
74
|
+
/** 테이블명 렌더링 (dialect마다 다르므로 abstract) */
|
|
75
75
|
protected abstract tableName(obj: QueryDefObjectName): string;
|
|
76
76
|
|
|
77
|
-
/** WHERE
|
|
77
|
+
/** WHERE 절 렌더링 */
|
|
78
78
|
protected renderWhere(wheres: WhereExpr[] | undefined): string {
|
|
79
79
|
if (wheres == null || wheres.length === 0) return "";
|
|
80
80
|
return ` WHERE ${this.expr.renderWhere(wheres)}`;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/** ORDER BY
|
|
83
|
+
/** ORDER BY 절 렌더링 */
|
|
84
84
|
protected renderOrderBy(orderBy: [Expr, ("ASC" | "DESC")?][] | undefined): string {
|
|
85
85
|
if (orderBy == null || orderBy.length === 0) return "";
|
|
86
86
|
const parts = orderBy.map(
|
|
@@ -89,58 +89,58 @@ export abstract class QueryBuilderBase {
|
|
|
89
89
|
return ` ORDER BY ${parts.join(", ")}`;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/** GROUP BY
|
|
92
|
+
/** GROUP BY 절 렌더링 */
|
|
93
93
|
protected renderGroupBy(groupBy: Expr[] | undefined): string {
|
|
94
94
|
if (groupBy == null || groupBy.length === 0) return "";
|
|
95
95
|
return ` GROUP BY ${groupBy.map((g) => this.expr.render(g)).join(", ")}`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/** HAVING
|
|
98
|
+
/** HAVING 절 렌더링 */
|
|
99
99
|
protected renderHaving(having: WhereExpr[] | undefined): string {
|
|
100
100
|
if (having == null || having.length === 0) return "";
|
|
101
101
|
return ` HAVING ${this.expr.renderWhere(having)}`;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/** JOIN
|
|
104
|
+
/** JOIN 절 렌더링 */
|
|
105
105
|
protected renderJoins(joins: SelectQueryDefJoin[] | undefined): string {
|
|
106
106
|
if (joins == null || joins.length === 0) return "";
|
|
107
107
|
return joins.map((j) => this.renderJoin(j)).join("");
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/**
|
|
110
|
+
/** 단일 JOIN 렌더링 (dialect마다 다르므로 abstract) */
|
|
111
111
|
protected abstract renderJoin(join: SelectQueryDefJoin): string;
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
114
|
+
* JOIN에 LATERAL/CROSS APPLY가 필요한지 감지
|
|
115
115
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
116
|
+
* JOIN이 기본 속성(type, from, as, where, isSingle)만 가지면 일반 JOIN으로 처리.
|
|
117
|
+
* 그 외의 경우 서브쿼리가 필요하므로 LATERAL JOIN 사용:
|
|
118
118
|
*
|
|
119
|
-
* - select: column
|
|
120
|
-
* - joins:
|
|
121
|
-
* - orderBy, top, limit:
|
|
122
|
-
* - groupBy, having:
|
|
123
|
-
* - distinct:
|
|
124
|
-
* - from (array): UNION ALL
|
|
119
|
+
* - select: column 변환/집계가 필요 (일반 JOIN은 전체 테이블 참조)
|
|
120
|
+
* - joins: 중첩 JOIN은 서브쿼리 내부에서 처리
|
|
121
|
+
* - orderBy, top, limit: 정렬/제한은 서브쿼리 내부에서 적용
|
|
122
|
+
* - groupBy, having: 집계는 서브쿼리 내부에서 수행
|
|
123
|
+
* - distinct: 중복 제거는 서브쿼리 내부에서 적용
|
|
124
|
+
* - from (array): UNION ALL 패턴
|
|
125
125
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* select/joins
|
|
126
|
+
* 참고: select와 joins는 중첩 join 시 자동 생성되므로 basicJoinProps에 없음.
|
|
127
|
+
* 사용자가 직접 .select()를 호출하지 않더라도 내부 .joinSingle()이
|
|
128
|
+
* select/joins를 추가할 수 있으며, 이 경우에도 서브쿼리가 필요함.
|
|
129
129
|
*/
|
|
130
130
|
protected needsLateral(join: SelectQueryDefJoin): boolean {
|
|
131
|
-
//
|
|
131
|
+
// from이 배열이면 항상 LATERAL (UNION ALL 패턴)
|
|
132
132
|
if (Array.isArray(join.from)) {
|
|
133
133
|
return true;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
//
|
|
136
|
+
// 기본 JOIN 속성 외 추가 속성이 있으면 LATERAL 필요
|
|
137
137
|
return Object.keys(join).some((key) => !BASIC_JOIN_PROPS.has(key));
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
/** FROM
|
|
140
|
+
/** FROM 절 소스 렌더링 */
|
|
141
141
|
protected renderFrom(from: SelectQueryDef["from"]): string {
|
|
142
142
|
if (from == null) {
|
|
143
|
-
throw new Error("FROM
|
|
143
|
+
throw new Error("FROM 절이 필요합니다.");
|
|
144
144
|
}
|
|
145
145
|
if (typeof from === "string") {
|
|
146
146
|
return this.expr.wrap(from);
|
|
@@ -69,22 +69,22 @@ import type { DataType } from "../../types/column";
|
|
|
69
69
|
import { ExprRendererBase } from "../base/expr-renderer-base";
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* MSSQL
|
|
72
|
+
* MSSQL 표현식 렌더러
|
|
73
73
|
*/
|
|
74
74
|
export class MssqlExprRenderer 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.replace(/'/g, "''");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/**
|
|
87
|
+
/** 값 이스케이프 */
|
|
88
88
|
escapeValue(value: unknown): string {
|
|
89
89
|
if (value == null) {
|
|
90
90
|
return "NULL";
|
|
@@ -113,10 +113,10 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
113
113
|
if (value instanceof Uint8Array) {
|
|
114
114
|
return `0x${bytes.toHex(value)}`;
|
|
115
115
|
}
|
|
116
|
-
throw new Error(
|
|
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})`;
|
|
@@ -217,18 +217,18 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
protected like(expr: ExprLike): string {
|
|
220
|
-
//
|
|
220
|
+
// 항상 ESCAPE '\' 추가
|
|
221
221
|
return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\'`;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
protected regexp(_expr: ExprRegexp): string {
|
|
225
|
-
// MSSQL
|
|
226
|
-
throw new Error("MSSQL
|
|
225
|
+
// MSSQL은 REGEXP을 지원하지 않음 - LIKE 패턴 또는 CLR 필요
|
|
226
|
+
throw new Error("MSSQL은 기본적으로 REGEXP을 지원하지 않습니다.");
|
|
227
227
|
}
|
|
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
|
|
|
@@ -502,17 +502,17 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
502
502
|
//#region ========== Other ==========
|
|
503
503
|
|
|
504
504
|
protected greatest(expr: ExprGreatest): string {
|
|
505
|
-
if (expr.args.length === 0) throw new Error("greatest
|
|
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
|
}
|
|
511
511
|
|
|
512
512
|
protected least(expr: ExprLeast): string {
|
|
513
|
-
if (expr.args.length === 0) throw new Error("least
|
|
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
|
}
|