@simplysm/orm-common 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/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 +6 -6
- 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 +4 -4
- 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.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 +4 -4
- 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.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 +4 -4
- 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.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 +91 -91
- 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.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.js +362 -221
- package/dist/utils/result-parser.js.map +1 -6
- package/package.json +5 -7
- 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 +134 -134
- package/src/exec/search-parser.ts +50 -50
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +13 -13
- 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 +11 -11
- package/src/query-builder/mssql/mssql-query-builder.ts +11 -11
- package/src/query-builder/mysql/mysql-expr-renderer.ts +15 -15
- package/src/query-builder/mysql/mysql-query-builder.ts +3 -3
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +9 -9
- package/src/query-builder/postgresql/postgresql-query-builder.ts +7 -7
- 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 +93 -93
- package/src/schema/procedure-builder.ts +52 -52
- package/src/schema/table-builder.ts +56 -56
- package/src/schema/view-builder.ts +45 -45
- package/src/types/column.ts +1 -1
- 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 +39 -39
- package/README.md +0 -192
- package/docs/core.md +0 -234
- package/docs/expression.md +0 -234
- package/docs/query-builder.md +0 -93
- package/docs/queryable.md +0 -198
- package/docs/schema-builders.md +0 -463
- package/docs/types.md +0 -445
- 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
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 검색 쿼리 파싱 결과 (LIKE 패턴)
|
|
3
3
|
*/
|
|
4
4
|
export interface ParsedSearchQuery {
|
|
5
|
-
/**
|
|
5
|
+
/** 일반 검색어 (OR 조건) - LIKE 패턴 */
|
|
6
6
|
or: string[];
|
|
7
|
-
/**
|
|
7
|
+
/** 필수 검색어 (AND 조건, + 접두사 또는 따옴표) - LIKE 패턴 */
|
|
8
8
|
must: string[];
|
|
9
|
-
/**
|
|
9
|
+
/** 제외 검색어 (NOT 조건, - 접두사) - LIKE 패턴 */
|
|
10
10
|
not: string[];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// 이스케이프 시퀀스를 플레이스홀더로 치환
|
|
14
14
|
const ESC = {
|
|
15
15
|
BACKSLASH: "\x00BACKSLASH\x00",
|
|
16
16
|
ASTERISK: "\x00ASTERISK\x00",
|
|
@@ -21,38 +21,38 @@ const ESC = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* 검색 쿼리 문자열을 파싱하여 SQL LIKE 패턴으로 변환한다.
|
|
25
25
|
*
|
|
26
|
-
* ##
|
|
26
|
+
* ## 검색 구문
|
|
27
27
|
*
|
|
28
|
-
* |
|
|
28
|
+
* | 구문 | 의미 | 예시 |
|
|
29
29
|
* |--------|---------|---------|
|
|
30
|
-
* | `term1 term2` | OR (
|
|
31
|
-
* | `+term` |
|
|
32
|
-
* | `-term` |
|
|
33
|
-
* | `"exact phrase"` |
|
|
34
|
-
* | `*` |
|
|
30
|
+
* | `term1 term2` | OR (하나 이상 일치) | `apple banana` |
|
|
31
|
+
* | `+term` | 필수 포함 (AND) | `+apple +banana` |
|
|
32
|
+
* | `-term` | 제외 (NOT) | `apple -banana` |
|
|
33
|
+
* | `"exact phrase"` | 정확한 구문 일치 (필수) | `"맛있는 과일"` |
|
|
34
|
+
* | `*` | 와일드카드 | `app*` → `app%` |
|
|
35
35
|
*
|
|
36
|
-
* ##
|
|
36
|
+
* ## 이스케이프 시퀀스
|
|
37
37
|
*
|
|
38
|
-
* |
|
|
38
|
+
* | 입력 | 의미 |
|
|
39
39
|
* |-------|---------|
|
|
40
|
-
* | `\\` |
|
|
41
|
-
* | `\*` |
|
|
42
|
-
* | `\%` |
|
|
43
|
-
* | `\"` |
|
|
44
|
-
* | `\+` |
|
|
45
|
-
* | `\-` |
|
|
40
|
+
* | `\\` | 리터럴 `\` |
|
|
41
|
+
* | `\*` | 리터럴 `*` |
|
|
42
|
+
* | `\%` | 리터럴 `%` |
|
|
43
|
+
* | `\"` | 리터럴 `"` |
|
|
44
|
+
* | `\+` | 리터럴 `+` |
|
|
45
|
+
* | `\-` | 리터럴 `-` |
|
|
46
46
|
*
|
|
47
|
-
* ##
|
|
47
|
+
* ## 특수 케이스
|
|
48
48
|
*
|
|
49
|
-
* -
|
|
49
|
+
* - 닫히지 않은 따옴표(`"text`)는 따옴표를 포함한 일반 텍스트로 처리된다.
|
|
50
50
|
*
|
|
51
|
-
* ##
|
|
51
|
+
* ## 예시
|
|
52
52
|
*
|
|
53
53
|
* ```typescript
|
|
54
54
|
* parseSearchQuery('apple "delicious fruit" -banana +strawberry')
|
|
55
|
-
* //
|
|
55
|
+
* // 결과:
|
|
56
56
|
* // {
|
|
57
57
|
* // or: ["%apple%"],
|
|
58
58
|
* // must: ["%delicious fruit%", "%strawberry%"],
|
|
@@ -60,24 +60,24 @@ const ESC = {
|
|
|
60
60
|
* // }
|
|
61
61
|
*
|
|
62
62
|
* parseSearchQuery('app* test')
|
|
63
|
-
* //
|
|
63
|
+
* // 결과:
|
|
64
64
|
* // {
|
|
65
|
-
* // or: ["app%", "%test%"], // app
|
|
65
|
+
* // or: ["app%", "%test%"], // app*는 "app"으로 시작, test는 부분 문자열 검색
|
|
66
66
|
* // must: [],
|
|
67
67
|
* // not: []
|
|
68
68
|
* // }
|
|
69
69
|
*
|
|
70
|
-
* parseSearchQuery('app\\*test') //
|
|
71
|
-
* //
|
|
70
|
+
* parseSearchQuery('app\\*test') // 이스케이프된 *
|
|
71
|
+
* // 결과:
|
|
72
72
|
* // {
|
|
73
|
-
* // or: ["%app*test%"], //
|
|
73
|
+
* // or: ["%app*test%"], // 리터럴 *
|
|
74
74
|
* // must: [],
|
|
75
75
|
* // not: []
|
|
76
76
|
* // }
|
|
77
77
|
* ```
|
|
78
78
|
*
|
|
79
|
-
* @param searchText
|
|
80
|
-
* @returns
|
|
79
|
+
* @param searchText 검색 쿼리 문자열
|
|
80
|
+
* @returns 파싱된 검색 쿼리 객체 (LIKE 패턴)
|
|
81
81
|
*/
|
|
82
82
|
export function parseSearchQuery(searchText: string): ParsedSearchQuery {
|
|
83
83
|
const result: ParsedSearchQuery = {
|
|
@@ -98,7 +98,7 @@ export function parseSearchQuery(searchText: string): ParsedSearchQuery {
|
|
|
98
98
|
.replace(/\\\+/g, ESC.PLUS)
|
|
99
99
|
.replace(/\\-/g, ESC.MINUS);
|
|
100
100
|
|
|
101
|
-
//
|
|
101
|
+
// 따옴표 구간 추출
|
|
102
102
|
const quotedRegex = /([+-]?)"([^"]*)"/g;
|
|
103
103
|
let match: RegExpExecArray | null;
|
|
104
104
|
|
|
@@ -115,21 +115,21 @@ export function parseSearchQuery(searchText: string): ParsedSearchQuery {
|
|
|
115
115
|
} else if (prefix === "-") {
|
|
116
116
|
result.not.push(pattern);
|
|
117
117
|
} else {
|
|
118
|
-
//
|
|
118
|
+
// 따옴표 텍스트는 must로 처리 (정확한 일치 = 필수)
|
|
119
119
|
result.must.push(pattern);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
//
|
|
123
|
+
// 따옴표 구간 제거
|
|
124
124
|
processed = processed.replace(/[+-]?"[^"]*"/g, " ");
|
|
125
125
|
|
|
126
|
-
//
|
|
126
|
+
// 공백으로 토큰 분리
|
|
127
127
|
const tokens = processed.split(/\s+/).filter((t) => t.length > 0);
|
|
128
128
|
|
|
129
129
|
for (let token of tokens) {
|
|
130
130
|
let targetArray = result.or;
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// + 또는 - 접두사 처리
|
|
133
133
|
if (token.startsWith("+")) {
|
|
134
134
|
targetArray = result.must;
|
|
135
135
|
token = token.slice(1);
|
|
@@ -148,32 +148,32 @@ export function parseSearchQuery(searchText: string): ParsedSearchQuery {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
*
|
|
151
|
+
* 검색어를 SQL LIKE 패턴으로 변환 (내부 함수)
|
|
152
152
|
*
|
|
153
|
-
*
|
|
154
|
-
* - `apple` (
|
|
155
|
-
* - `apple*` → `apple%` (
|
|
156
|
-
* - `*apple` → `%apple` (
|
|
157
|
-
* - `*apple*` → `%apple%` (
|
|
158
|
-
* - `a*ple` → `a%ple` (
|
|
153
|
+
* 와일드카드 규칙:
|
|
154
|
+
* - `apple` (와일드카드 없음) → `%apple%` (부분 문자열 검색, 기본값)
|
|
155
|
+
* - `apple*` → `apple%` (시작 일치)
|
|
156
|
+
* - `*apple` → `%apple` (끝 일치)
|
|
157
|
+
* - `*apple*` → `%apple%` (명시적 부분 문자열)
|
|
158
|
+
* - `a*ple` → `a%ple` (중간 일치)
|
|
159
159
|
*/
|
|
160
160
|
function termToLikePattern(term: string): string {
|
|
161
|
-
//
|
|
161
|
+
// 와일드카드 *를 임시 마커로 변환 (이스케이프된 것은 이미 ESC.ASTERISK로 처리됨)
|
|
162
162
|
const WILDCARD = "\x01WILDCARD\x01";
|
|
163
163
|
const hasWildcard = term.includes("*");
|
|
164
164
|
let pattern = term.replace(/\*/g, WILDCARD);
|
|
165
165
|
|
|
166
|
-
//
|
|
166
|
+
// SQL LIKE 특수 문자 이스케이프 (\ → \\, % → \%, _ → \_, [ → \[)
|
|
167
167
|
pattern = pattern
|
|
168
168
|
.replace(/\\/g, "\\\\")
|
|
169
169
|
.replace(/%/g, "\\%")
|
|
170
170
|
.replace(/_/g, "\\_")
|
|
171
171
|
.replace(/\[/g, "\\[");
|
|
172
172
|
|
|
173
|
-
//
|
|
173
|
+
// 와일드카드 마커 → % (SQL 와일드카드)로 변환
|
|
174
174
|
pattern = pattern.replaceAll(WILDCARD, "%");
|
|
175
175
|
|
|
176
|
-
//
|
|
176
|
+
// 이스케이프 플레이스홀더 복원
|
|
177
177
|
pattern = pattern
|
|
178
178
|
.replaceAll(ESC.BACKSLASH, "\\\\") // \\ → SQL \\
|
|
179
179
|
.replaceAll(ESC.ASTERISK, "*") // \* → literal * (not special in SQL)
|
|
@@ -182,8 +182,8 @@ function termToLikePattern(term: string): string {
|
|
|
182
182
|
.replaceAll(ESC.PLUS, "+")
|
|
183
183
|
.replaceAll(ESC.MINUS, "-");
|
|
184
184
|
|
|
185
|
-
//
|
|
186
|
-
//
|
|
185
|
+
// 와일드카드가 없으면 양쪽에 % 추가 (기본 부분 문자열 검색)
|
|
186
|
+
// 와일드카드가 있으면 사용자가 지정한 패턴 그대로 사용
|
|
187
187
|
if (hasWildcard) {
|
|
188
188
|
return pattern;
|
|
189
189
|
}
|
package/src/expr/expr-unit.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { ColumnPrimitive, ColumnPrimitiveStr } from "../types/column";
|
|
|
2
2
|
import type { Expr, WhereExpr } from "../types/expr";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 타입 안전 표현식 래퍼
|
|
6
|
+
* TypeScript 제네릭을 사용하여 표현식 반환 타입을 추적한다
|
|
7
7
|
*/
|
|
8
8
|
export class ExprUnit<TPrimitive extends ColumnPrimitive> {
|
|
9
9
|
readonly $infer!: TPrimitive;
|
|
@@ -19,13 +19,13 @@ export class ExprUnit<TPrimitive extends ColumnPrimitive> {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* WHERE 절용 표현식 래퍼
|
|
23
23
|
*/
|
|
24
24
|
export class WhereExprUnit {
|
|
25
25
|
constructor(readonly expr: WhereExpr) {}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* ExprUnit 또는 리터럴 값을 받는 입력 타입
|
|
30
30
|
*/
|
|
31
31
|
export type ExprInput<TPrimitive extends ColumnPrimitive> = ExprUnit<TPrimitive> | TPrimitive;
|
package/src/expr/expr.ts
CHANGED
|
@@ -14,14 +14,14 @@ import type { Expr, DateUnit, WhereExpr, WinSpec } from "../types/expr";
|
|
|
14
14
|
import type { SelectQueryDef } from "../types/query-def";
|
|
15
15
|
import type { Queryable } from "../exec/queryable";
|
|
16
16
|
|
|
17
|
-
// Window
|
|
17
|
+
// Window 함수 Spec 입력 (사용자 API용)
|
|
18
18
|
interface WinSpecInput {
|
|
19
19
|
partitionBy?: ExprInput<ColumnPrimitive>[];
|
|
20
20
|
orderBy?: [ExprInput<ColumnPrimitive>, ("ASC" | "DESC")?][];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Switch expression builder
|
|
24
|
+
* Switch expression builder 인터페이스
|
|
25
25
|
*/
|
|
26
26
|
export interface SwitchExprBuilder<TPrimitive extends ColumnPrimitive> {
|
|
27
27
|
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): SwitchExprBuilder<TPrimitive>;
|
|
@@ -29,10 +29,10 @@ export interface SwitchExprBuilder<TPrimitive extends ColumnPrimitive> {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Dialect
|
|
32
|
+
* Dialect 독립적 SQL expression builder
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* SQL 문자열 대신 JSON AST(Expr)를 생성하며, QueryBuilder가
|
|
35
|
+
* 각 DBMS(MySQL, MSSQL, PostgreSQL)로 변환
|
|
36
36
|
*
|
|
37
37
|
* @example
|
|
38
38
|
* ```typescript
|
|
@@ -55,11 +55,11 @@ export interface SwitchExprBuilder<TPrimitive extends ColumnPrimitive> {
|
|
|
55
55
|
* }))
|
|
56
56
|
* ```
|
|
57
57
|
*
|
|
58
|
-
* @see {@link Queryable} Query builder
|
|
59
|
-
* @see {@link ExprUnit} Expression
|
|
58
|
+
* @see {@link Queryable} Query builder 클래스
|
|
59
|
+
* @see {@link ExprUnit} Expression 래퍼 클래스
|
|
60
60
|
*/
|
|
61
61
|
export const expr = {
|
|
62
|
-
//#region ==========
|
|
62
|
+
//#region ========== 값 생성 ==========
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Wrap literal value as ExprUnit
|
|
@@ -1394,7 +1394,7 @@ export const expr = {
|
|
|
1394
1394
|
//#endregion
|
|
1395
1395
|
|
|
1396
1396
|
//#region ========== SELECT - Aggregate ==========
|
|
1397
|
-
//
|
|
1397
|
+
// SUM, AVG, MAX 등 집계는 모든 값이 NULL이거나 행이 없을 때만 NULL 반환 (NULL 값을 가진 행은 무시됨)
|
|
1398
1398
|
|
|
1399
1399
|
/**
|
|
1400
1400
|
* Count rows (COUNT)
|
|
@@ -2049,7 +2049,7 @@ export const expr = {
|
|
|
2049
2049
|
|
|
2050
2050
|
//#region ========== Internal Helpers ==========
|
|
2051
2051
|
|
|
2052
|
-
//
|
|
2052
|
+
// 여러 값 중 첫 번째 non-null 값 반환 (COALESCE)
|
|
2053
2053
|
function coalesce<TPrimitive extends ColumnPrimitive>(
|
|
2054
2054
|
...args: [
|
|
2055
2055
|
ExprInput<TPrimitive | undefined>,
|
|
@@ -2071,7 +2071,7 @@ function coalesce<TPrimitive extends ColumnPrimitive>(
|
|
|
2071
2071
|
|
|
2072
2072
|
function createSwitchBuilder<TPrimitive extends ColumnPrimitive>(): SwitchExprBuilder<TPrimitive> {
|
|
2073
2073
|
const cases: { when: WhereExpr; then: Expr }[] = [];
|
|
2074
|
-
const thenValues: ExprInput<TPrimitive>[] = []; //
|
|
2074
|
+
const thenValues: ExprInput<TPrimitive>[] = []; // then 값 저장
|
|
2075
2075
|
|
|
2076
2076
|
return {
|
|
2077
2077
|
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): typeof this {
|
|
@@ -2084,7 +2084,7 @@ function createSwitchBuilder<TPrimitive extends ColumnPrimitive>(): SwitchExprBu
|
|
|
2084
2084
|
},
|
|
2085
2085
|
default(value: ExprInput<TPrimitive>): ExprUnit<TPrimitive> {
|
|
2086
2086
|
const allValues = [...thenValues, value];
|
|
2087
|
-
// 1.
|
|
2087
|
+
// 1. ExprUnit에서 dataType 찾기
|
|
2088
2088
|
const exprUnit = allValues.find((v): v is ExprUnit<TPrimitive> => v instanceof ExprUnit);
|
|
2089
2089
|
if (exprUnit) {
|
|
2090
2090
|
return new ExprUnit(exprUnit.dataType, {
|
|
@@ -2094,7 +2094,7 @@ function createSwitchBuilder<TPrimitive extends ColumnPrimitive>(): SwitchExprBu
|
|
|
2094
2094
|
});
|
|
2095
2095
|
}
|
|
2096
2096
|
|
|
2097
|
-
// 2.
|
|
2097
|
+
// 2. non-null 리터럴에서 추론
|
|
2098
2098
|
const nonNullLiteral = allValues.find((v) => v != null) as ColumnPrimitive;
|
|
2099
2099
|
if (nonNullLiteral == null) {
|
|
2100
2100
|
throw new Error("At least one of switch's case/default must be non-null.");
|
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);
|