@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/exec/queryable.ts
CHANGED
|
@@ -35,7 +35,7 @@ import { expr } from "../expr/expr";
|
|
|
35
35
|
/**
|
|
36
36
|
* JOIN query builder
|
|
37
37
|
*
|
|
38
|
-
*
|
|
38
|
+
* join/joinSingle 메서드 내부에서 조인할 table을 지정하는 데 사용
|
|
39
39
|
*/
|
|
40
40
|
class JoinQueryable {
|
|
41
41
|
constructor(
|
|
@@ -44,20 +44,20 @@ class JoinQueryable {
|
|
|
44
44
|
) {}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* 조인할 table 지정
|
|
48
48
|
*
|
|
49
|
-
* @param table -
|
|
50
|
-
* @returns
|
|
49
|
+
* @param table - 조인할 table
|
|
50
|
+
* @returns 조인된 Queryable
|
|
51
51
|
*/
|
|
52
52
|
from<T extends TableBuilder<any, any>>(table: T): Queryable<T["$inferSelect"], T> {
|
|
53
53
|
return queryable(this._db, table, this._joinAlias)();
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
57
|
+
* 조인 결과의 column을 직접 지정
|
|
58
58
|
*
|
|
59
|
-
* @param columns -
|
|
60
|
-
* @returns
|
|
59
|
+
* @param columns - 커스텀 column 정의
|
|
60
|
+
* @returns 커스텀 column이 적용된 Queryable
|
|
61
61
|
*/
|
|
62
62
|
select<R extends DataRecord>(columns: QueryableRecord<R>): Queryable<R, never> {
|
|
63
63
|
return new Queryable({
|
|
@@ -69,15 +69,15 @@ class JoinQueryable {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* 여러 Queryable을 UNION으로 결합
|
|
73
73
|
*
|
|
74
|
-
* @param queries -
|
|
75
|
-
* @returns UNION
|
|
76
|
-
* @throws
|
|
74
|
+
* @param queries - UNION할 Queryable 배열 (최소 2개)
|
|
75
|
+
* @returns UNION된 Queryable
|
|
76
|
+
* @throws 2개 미만의 queryable이 전달되면 에러
|
|
77
77
|
*/
|
|
78
78
|
union<TData extends DataRecord>(...queries: Queryable<TData, any>[]): Queryable<TData, never> {
|
|
79
79
|
if (queries.length < 2) {
|
|
80
|
-
throw new ArgumentError("union
|
|
80
|
+
throw new ArgumentError("union은 최소 2개의 queryable이 필요합니다.", {
|
|
81
81
|
provided: queries.length,
|
|
82
82
|
minimum: 2,
|
|
83
83
|
});
|
|
@@ -87,7 +87,7 @@ class JoinQueryable {
|
|
|
87
87
|
|
|
88
88
|
return new Queryable({
|
|
89
89
|
db: first.meta.db,
|
|
90
|
-
from: queries, //
|
|
90
|
+
from: queries, // Queryable[] 배열로 저장
|
|
91
91
|
as: this._joinAlias,
|
|
92
92
|
columns: transformColumnsAlias(first.meta.columns, this._joinAlias, ""),
|
|
93
93
|
});
|
|
@@ -95,9 +95,9 @@ class JoinQueryable {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
98
|
+
* 재귀 CTE (Common Table Expression) builder
|
|
99
99
|
*
|
|
100
|
-
*
|
|
100
|
+
* recursive() 메서드 내부에서 사용되며, 재귀 쿼리의 본문을 정의한다
|
|
101
101
|
*
|
|
102
102
|
* @template TBaseData - Base query data type
|
|
103
103
|
*/
|
|
@@ -108,10 +108,10 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
108
108
|
) {}
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
111
|
+
* 재귀 query의 대상 table 지정
|
|
112
112
|
*
|
|
113
|
-
* @param table -
|
|
114
|
-
* @returns
|
|
113
|
+
* @param table - 재귀할 대상 table
|
|
114
|
+
* @returns self 속성이 추가된 Queryable (자기 참조용)
|
|
115
115
|
*/
|
|
116
116
|
from<T extends TableBuilder<any, any>>(
|
|
117
117
|
table: T,
|
|
@@ -132,10 +132,10 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
|
-
*
|
|
135
|
+
* 재귀 query의 column을 직접 지정
|
|
136
136
|
*
|
|
137
|
-
* @param columns -
|
|
138
|
-
* @returns
|
|
137
|
+
* @param columns - 커스텀 column 정의
|
|
138
|
+
* @returns self 속성이 추가된 Queryable
|
|
139
139
|
*/
|
|
140
140
|
select<R extends DataRecord>(
|
|
141
141
|
columns: QueryableRecord<R>,
|
|
@@ -161,17 +161,17 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
164
|
+
* 여러 Queryable을 UNION으로 결합 (재귀 query용)
|
|
165
165
|
*
|
|
166
|
-
* @param queries -
|
|
167
|
-
* @returns
|
|
168
|
-
* @throws
|
|
166
|
+
* @param queries - UNION할 Queryable 배열 (최소 2개)
|
|
167
|
+
* @returns self 속성이 추가된 UNION Queryable
|
|
168
|
+
* @throws 2개 미만의 queryable이 전달되면 에러
|
|
169
169
|
*/
|
|
170
170
|
union<TData extends DataRecord>(
|
|
171
171
|
...queries: Queryable<TData, any>[]
|
|
172
172
|
): Queryable<TData & { self?: TBaseData[] }, never> {
|
|
173
173
|
if (queries.length < 2) {
|
|
174
|
-
throw new ArgumentError("union
|
|
174
|
+
throw new ArgumentError("union은 최소 2개의 queryable이 필요합니다.", {
|
|
175
175
|
provided: queries.length,
|
|
176
176
|
minimum: 2,
|
|
177
177
|
});
|
|
@@ -183,7 +183,7 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
183
183
|
|
|
184
184
|
return new Queryable<any, never>({
|
|
185
185
|
db: first.meta.db,
|
|
186
|
-
from: queries, //
|
|
186
|
+
from: queries, // Queryable[] 배열로 저장
|
|
187
187
|
as: this._cteName,
|
|
188
188
|
columns: transformColumnsAlias(first.meta.columns, this._cteName, ""),
|
|
189
189
|
}).join(
|
|
@@ -201,12 +201,12 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* Query builder
|
|
204
|
+
* Query builder 클래스
|
|
205
205
|
*
|
|
206
|
-
*
|
|
206
|
+
* 체이닝 방식으로 table/view에 대한 SELECT, INSERT, UPDATE, DELETE query를 구성
|
|
207
207
|
*
|
|
208
|
-
* @template TData -
|
|
209
|
-
* @template TFrom -
|
|
208
|
+
* @template TData - Query 결과의 데이터 타입
|
|
209
|
+
* @template TFrom - 소스 table (CUD 연산에 필요)
|
|
210
210
|
*
|
|
211
211
|
* @example
|
|
212
212
|
* ```typescript
|
|
@@ -227,17 +227,17 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
227
227
|
*/
|
|
228
228
|
export class Queryable<
|
|
229
229
|
TData extends DataRecord,
|
|
230
|
-
TFrom extends TableBuilder<any, any> | never, //
|
|
230
|
+
TFrom extends TableBuilder<any, any> | never, // CUD 연산은 TableBuilder만 지원
|
|
231
231
|
> {
|
|
232
232
|
constructor(readonly meta: QueryableMeta<TData>) {}
|
|
233
233
|
|
|
234
234
|
//#region ========== option - SELECT / DISTINCT / LOCK ==========
|
|
235
235
|
|
|
236
236
|
/**
|
|
237
|
-
*
|
|
237
|
+
* SELECT할 column 지정.
|
|
238
238
|
*
|
|
239
|
-
* @param fn - Column
|
|
240
|
-
* @returns
|
|
239
|
+
* @param fn - Column 매핑 함수. 원본 column을 받아 새 column 구조를 반환
|
|
240
|
+
* @returns 새 column 구조가 적용된 Queryable
|
|
241
241
|
*
|
|
242
242
|
* @example
|
|
243
243
|
* ```typescript
|
|
@@ -269,9 +269,9 @@ export class Queryable<
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
|
-
*
|
|
272
|
+
* 중복 행 제거를 위한 DISTINCT 옵션 적용
|
|
273
273
|
*
|
|
274
|
-
* @returns
|
|
274
|
+
* @returns DISTINCT가 적용된 Queryable
|
|
275
275
|
*
|
|
276
276
|
* @example
|
|
277
277
|
* ```typescript
|
|
@@ -296,11 +296,11 @@ export class Queryable<
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
|
-
*
|
|
299
|
+
* 행 잠금 적용 (FOR UPDATE)
|
|
300
300
|
*
|
|
301
|
-
*
|
|
301
|
+
* 트랜잭션 내에서 선택된 행에 대한 배타적 잠금 획득
|
|
302
302
|
*
|
|
303
|
-
* @returns
|
|
303
|
+
* @returns 잠금이 적용된 Queryable
|
|
304
304
|
*
|
|
305
305
|
* @example
|
|
306
306
|
* ```typescript
|
|
@@ -332,10 +332,10 @@ export class Queryable<
|
|
|
332
332
|
//#region ========== restrict - TOP / LIMIT ==========
|
|
333
333
|
|
|
334
334
|
/**
|
|
335
|
-
*
|
|
335
|
+
* 상위 N개 행만 선택 (ORDER BY 없이도 사용 가능)
|
|
336
336
|
*
|
|
337
|
-
* @param count -
|
|
338
|
-
* @returns
|
|
337
|
+
* @param count - 선택할 행 수
|
|
338
|
+
* @returns TOP이 적용된 Queryable
|
|
339
339
|
*
|
|
340
340
|
* @example
|
|
341
341
|
* ```typescript
|
|
@@ -361,13 +361,13 @@ export class Queryable<
|
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
364
|
+
* 페이지네이션을 위한 LIMIT/OFFSET 설정.
|
|
365
|
+
* 먼저 orderBy()를 호출해야 함.
|
|
366
366
|
*
|
|
367
|
-
* @param skip -
|
|
368
|
-
* @param take -
|
|
369
|
-
* @returns
|
|
370
|
-
* @throws
|
|
367
|
+
* @param skip - 건너뛸 행 수 (OFFSET)
|
|
368
|
+
* @param take - 가져올 행 수 (LIMIT)
|
|
369
|
+
* @returns 페이지네이션이 적용된 Queryable
|
|
370
|
+
* @throws ORDER BY 절이 없으면 에러
|
|
371
371
|
*
|
|
372
372
|
* @example
|
|
373
373
|
* ```typescript
|
|
@@ -386,7 +386,7 @@ export class Queryable<
|
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
if (!this.meta.orderBy) {
|
|
389
|
-
throw new ArgumentError("limit()
|
|
389
|
+
throw new ArgumentError("limit()은 ORDER BY 절이 필요합니다.", {
|
|
390
390
|
method: "limit",
|
|
391
391
|
required: "orderBy",
|
|
392
392
|
});
|
|
@@ -403,11 +403,11 @@ export class Queryable<
|
|
|
403
403
|
//#region ========== sorting - ORDER BY ==========
|
|
404
404
|
|
|
405
405
|
/**
|
|
406
|
-
*
|
|
406
|
+
* 정렬 조건 추가. 여러 번 호출 시 순서대로 적용됨.
|
|
407
407
|
*
|
|
408
|
-
* @param fn -
|
|
409
|
-
* @param orderBy -
|
|
410
|
-
* @returns
|
|
408
|
+
* @param fn - 정렬할 column을 반환하는 함수
|
|
409
|
+
* @param orderBy - 정렬 방향 (ASC/DESC). 기본값: ASC
|
|
410
|
+
* @returns 정렬 조건이 추가된 Queryable
|
|
411
411
|
*
|
|
412
412
|
* @example
|
|
413
413
|
* ```typescript
|
|
@@ -441,10 +441,10 @@ export class Queryable<
|
|
|
441
441
|
//#region ========== Search - WHERE ==========
|
|
442
442
|
|
|
443
443
|
/**
|
|
444
|
-
*
|
|
444
|
+
* WHERE 조건 추가. 여러 번 호출 시 AND로 결합됨.
|
|
445
445
|
*
|
|
446
|
-
* @param predicate -
|
|
447
|
-
* @returns
|
|
446
|
+
* @param predicate - 조건 배열을 반환하는 함수
|
|
447
|
+
* @returns 조건이 추가된 Queryable
|
|
448
448
|
*
|
|
449
449
|
* @example
|
|
450
450
|
* ```typescript
|
|
@@ -471,16 +471,16 @@ export class Queryable<
|
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
/**
|
|
474
|
-
*
|
|
474
|
+
* 텍스트 검색 수행
|
|
475
475
|
*
|
|
476
|
-
*
|
|
477
|
-
* -
|
|
478
|
-
* -
|
|
479
|
-
* -
|
|
476
|
+
* 검색 구문은 {@link parseSearchQuery} 참조
|
|
477
|
+
* - 공백으로 구분된 단어는 OR 조건
|
|
478
|
+
* - `+`로 시작하는 단어는 필수 포함 (AND 조건)
|
|
479
|
+
* - `-`로 시작하는 단어는 제외 (NOT 조건)
|
|
480
480
|
*
|
|
481
|
-
* @param fn -
|
|
482
|
-
* @param searchText -
|
|
483
|
-
* @returns
|
|
481
|
+
* @param fn - 검색 대상 column을 반환하는 함수
|
|
482
|
+
* @param searchText - 검색 텍스트
|
|
483
|
+
* @returns 검색 조건이 추가된 Queryable
|
|
484
484
|
*
|
|
485
485
|
* @example
|
|
486
486
|
* ```typescript
|
|
@@ -509,7 +509,7 @@ export class Queryable<
|
|
|
509
509
|
|
|
510
510
|
const conditions: WhereExprUnit[] = [];
|
|
511
511
|
|
|
512
|
-
// OR
|
|
512
|
+
// OR 조건: 아무 column에서 아무 패턴이 매칭되면 일치
|
|
513
513
|
if (parsed.or.length === 1) {
|
|
514
514
|
const pattern = parsed.or[0];
|
|
515
515
|
const columnMatches = columns.map((col) => expr.like(expr.lower(col), pattern.toLowerCase()));
|
|
@@ -524,13 +524,13 @@ export class Queryable<
|
|
|
524
524
|
conditions.push(expr.or(orConditions));
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
-
// MUST
|
|
527
|
+
// MUST 조건: 각 패턴이 최소 하나의 column에서 매칭되어야 함 (AND)
|
|
528
528
|
for (const pattern of parsed.must) {
|
|
529
529
|
const columnMatches = columns.map((col) => expr.like(expr.lower(col), pattern.toLowerCase()));
|
|
530
530
|
conditions.push(expr.or(columnMatches));
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
-
// NOT
|
|
533
|
+
// NOT 조건: 아무 column에서도 매칭되지 않아야 함 (AND NOT)
|
|
534
534
|
for (const pattern of parsed.not) {
|
|
535
535
|
const columnMatches = columns.map((col) => expr.like(expr.lower(col), pattern.toLowerCase()));
|
|
536
536
|
conditions.push(expr.not(expr.or(columnMatches)));
|
|
@@ -548,10 +548,10 @@ export class Queryable<
|
|
|
548
548
|
//#region ========== Group - GROUP BY / HAVING ==========
|
|
549
549
|
|
|
550
550
|
/**
|
|
551
|
-
*
|
|
551
|
+
* GROUP BY 절 추가
|
|
552
552
|
*
|
|
553
|
-
* @param fn -
|
|
554
|
-
* @returns
|
|
553
|
+
* @param fn - 그룹화할 column을 반환하는 함수
|
|
554
|
+
* @returns GROUP BY가 적용된 Queryable
|
|
555
555
|
*
|
|
556
556
|
* @example
|
|
557
557
|
* ```typescript
|
|
@@ -580,10 +580,10 @@ export class Queryable<
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
/**
|
|
583
|
-
*
|
|
583
|
+
* HAVING 절 추가 (GROUP BY 이후 필터링)
|
|
584
584
|
*
|
|
585
|
-
* @param predicate -
|
|
586
|
-
* @returns
|
|
585
|
+
* @param predicate - 조건 배열을 반환하는 함수
|
|
586
|
+
* @returns HAVING이 적용된 Queryable
|
|
587
587
|
*
|
|
588
588
|
* @example
|
|
589
589
|
* ```typescript
|
|
@@ -618,11 +618,11 @@ export class Queryable<
|
|
|
618
618
|
//#region ========== join - JOIN / JOIN SINGLE ==========
|
|
619
619
|
|
|
620
620
|
/**
|
|
621
|
-
*
|
|
621
|
+
* 1:N 관계에 대한 LEFT OUTER JOIN 수행 (결과에 배열로 추가)
|
|
622
622
|
*
|
|
623
|
-
* @param as -
|
|
624
|
-
* @param fn -
|
|
625
|
-
* @returns
|
|
623
|
+
* @param as - 결과에 추가할 속성 이름
|
|
624
|
+
* @param fn - 조인 조건을 정의하는 콜백 함수
|
|
625
|
+
* @returns 조인 결과가 배열로 추가된 Queryable
|
|
626
626
|
*
|
|
627
627
|
* @example
|
|
628
628
|
* ```typescript
|
|
@@ -647,16 +647,16 @@ export class Queryable<
|
|
|
647
647
|
});
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
// 1.
|
|
650
|
+
// 1. JOIN 별칭 생성
|
|
651
651
|
const joinAlias = `${this.meta.as}.${as}`;
|
|
652
652
|
|
|
653
|
-
// 2.
|
|
653
|
+
// 2. 대상을 Queryable로 변환 (별칭 전달)
|
|
654
654
|
const joinQr = new JoinQueryable(this.meta.db, joinAlias);
|
|
655
655
|
|
|
656
|
-
// 3.
|
|
656
|
+
// 3. fn 실행 (where 등 조건이 추가된 Queryable 반환)
|
|
657
657
|
const resultQr = fn(joinQr, this.meta.columns);
|
|
658
658
|
|
|
659
|
-
// 4.
|
|
659
|
+
// 4. JOIN 결과를 새 column에 추가
|
|
660
660
|
const joinColumns = transformColumnsAlias(resultQr.meta.columns, joinAlias);
|
|
661
661
|
|
|
662
662
|
return new Queryable({
|
|
@@ -671,11 +671,11 @@ export class Queryable<
|
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
/**
|
|
674
|
-
*
|
|
674
|
+
* N:1 또는 1:1 관계에 대한 LEFT OUTER JOIN 수행 (결과에 단일 객체로 추가)
|
|
675
675
|
*
|
|
676
|
-
* @param as -
|
|
677
|
-
* @param fn -
|
|
678
|
-
* @returns
|
|
676
|
+
* @param as - 결과에 추가할 속성 이름
|
|
677
|
+
* @param fn - 조인 조건을 정의하는 콜백 함수
|
|
678
|
+
* @returns 조인 결과가 단일 객체로 추가된 Queryable
|
|
679
679
|
*
|
|
680
680
|
* @example
|
|
681
681
|
* ```typescript
|
|
@@ -703,16 +703,16 @@ export class Queryable<
|
|
|
703
703
|
});
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
-
// 1.
|
|
706
|
+
// 1. JOIN 별칭 생성
|
|
707
707
|
const joinAlias = `${this.meta.as}.${as}`;
|
|
708
708
|
|
|
709
|
-
// 2.
|
|
709
|
+
// 2. 대상을 Queryable로 변환 (별칭 전달)
|
|
710
710
|
const joinQr = new JoinQueryable(this.meta.db, joinAlias);
|
|
711
711
|
|
|
712
|
-
// 3.
|
|
712
|
+
// 3. fn 실행 (where 등 조건이 추가된 Queryable 반환)
|
|
713
713
|
const resultQr = fn(joinQr, this.meta.columns);
|
|
714
714
|
|
|
715
|
-
// 4.
|
|
715
|
+
// 4. JOIN 결과를 새 column에 추가
|
|
716
716
|
const joinColumns = transformColumnsAlias(resultQr.meta.columns, joinAlias);
|
|
717
717
|
|
|
718
718
|
return new Queryable({
|
|
@@ -731,12 +731,12 @@ export class Queryable<
|
|
|
731
731
|
//#region ========== join - INCLUDE ==========
|
|
732
732
|
|
|
733
733
|
/**
|
|
734
|
-
*
|
|
735
|
-
*
|
|
734
|
+
* 관련 table을 자동으로 JOIN.
|
|
735
|
+
* TableBuilder에 정의된 FK/FKT 관계를 기반으로 동작.
|
|
736
736
|
*
|
|
737
|
-
* @param fn -
|
|
738
|
-
* @returns
|
|
739
|
-
* @throws
|
|
737
|
+
* @param fn - 포함할 관계를 선택하는 함수 (PathProxy를 통해 타입 체크)
|
|
738
|
+
* @returns JOIN이 추가된 Queryable
|
|
739
|
+
* @throws 관계가 정의되지 않은 경우 에러
|
|
740
740
|
*
|
|
741
741
|
* @example
|
|
742
742
|
* ```typescript
|
|
@@ -778,17 +778,17 @@ export class Queryable<
|
|
|
778
778
|
|
|
779
779
|
for (const relationName of relationNames) {
|
|
780
780
|
if (!(currentTable instanceof TableBuilder)) {
|
|
781
|
-
throw new Error("include()
|
|
781
|
+
throw new Error("include()는 TableBuilder 기반 queryable에서만 사용할 수 있습니다.");
|
|
782
782
|
}
|
|
783
783
|
|
|
784
784
|
const parentChain = chainParts.join(".");
|
|
785
785
|
chainParts.push(relationName);
|
|
786
786
|
|
|
787
|
-
//
|
|
787
|
+
// 이미 JOIN된 경우 중복 추가 방지
|
|
788
788
|
const targetAlias = `${result.meta.as}.${chainParts.join(".")}`;
|
|
789
789
|
const existingJoin = result.meta.joins?.find((j) => j.queryable.meta.as === targetAlias);
|
|
790
790
|
if (existingJoin) {
|
|
791
|
-
//
|
|
791
|
+
// 기존 JOIN의 table로 currentTable 갱신 후 계속
|
|
792
792
|
const existingFrom = existingJoin.queryable.meta.from;
|
|
793
793
|
if (existingFrom instanceof TableBuilder) {
|
|
794
794
|
currentTable = existingFrom;
|
|
@@ -798,12 +798,12 @@ export class Queryable<
|
|
|
798
798
|
|
|
799
799
|
const relationDef = currentTable.meta.relations?.[relationName];
|
|
800
800
|
if (relationDef == null) {
|
|
801
|
-
throw new Error(
|
|
801
|
+
throw new Error(`관계 '${relationName}'을(를) 찾을 수 없습니다.`);
|
|
802
802
|
}
|
|
803
803
|
|
|
804
804
|
if (relationDef instanceof ForeignKeyBuilder || relationDef instanceof RelationKeyBuilder) {
|
|
805
805
|
// FK/RelationKey (N:1): Post.user → User
|
|
806
|
-
//
|
|
806
|
+
// 조건: Post.userId = User.id
|
|
807
807
|
const targetTable = relationDef.meta.targetFn();
|
|
808
808
|
const fkColKeys = relationDef.meta.columns;
|
|
809
809
|
const targetPkColKeys = getMatchedPrimaryKeys(fkColKeys, targetTable);
|
|
@@ -811,7 +811,7 @@ export class Queryable<
|
|
|
811
811
|
result = result.joinSingle(chainParts.join("."), (joinQr, parentCols) => {
|
|
812
812
|
const qr = joinQr.from(targetTable);
|
|
813
813
|
|
|
814
|
-
// FKT
|
|
814
|
+
// FKT JOIN은 배열로 저장되므로, 배열이면 첫 번째 요소 사용
|
|
815
815
|
const srcColsRaw = parentChain ? parentCols[parentChain] : parentCols;
|
|
816
816
|
const srcCols = (
|
|
817
817
|
Array.isArray(srcColsRaw) ? srcColsRaw[0] : srcColsRaw
|
|
@@ -833,15 +833,15 @@ export class Queryable<
|
|
|
833
833
|
relationDef instanceof ForeignKeyTargetBuilder ||
|
|
834
834
|
relationDef instanceof RelationKeyTargetBuilder
|
|
835
835
|
) {
|
|
836
|
-
// FKT/RelationKeyTarget (1:N
|
|
837
|
-
//
|
|
836
|
+
// FKT/RelationKeyTarget (1:N 또는 1:1): User.posts → Post[]
|
|
837
|
+
// 조건: Post.userId = User.id
|
|
838
838
|
const targetTable = relationDef.meta.targetTableFn();
|
|
839
839
|
const fkRelName = relationDef.meta.relationName;
|
|
840
840
|
const sourceFk = targetTable.meta.relations?.[fkRelName];
|
|
841
841
|
if (!(sourceFk instanceof ForeignKeyBuilder) && !(sourceFk instanceof RelationKeyBuilder)) {
|
|
842
842
|
throw new Error(
|
|
843
|
-
`'${
|
|
844
|
-
|
|
843
|
+
`'${relationName}'이(가) 참조하는 '${fkRelName}'은(는) ` +
|
|
844
|
+
`${targetTable.meta.name} 테이블에서 유효한 ForeignKey/RelationKey가 아닙니다.`,
|
|
845
845
|
);
|
|
846
846
|
}
|
|
847
847
|
const sourceTable = targetTable;
|
|
@@ -853,7 +853,7 @@ export class Queryable<
|
|
|
853
853
|
const buildJoin = (joinQr: JoinQueryable, parentCols: QueryableRecord<DataRecord>) => {
|
|
854
854
|
const qr = joinQr.from(sourceTable);
|
|
855
855
|
|
|
856
|
-
// FKT
|
|
856
|
+
// FKT JOIN은 배열로 저장되므로, 배열이면 첫 번째 요소 사용
|
|
857
857
|
const srcColsRaw = parentChain ? parentCols[parentChain] : parentCols;
|
|
858
858
|
const srcCols = (
|
|
859
859
|
Array.isArray(srcColsRaw) ? srcColsRaw[0] : srcColsRaw
|
|
@@ -903,7 +903,7 @@ export class Queryable<
|
|
|
903
903
|
* ```
|
|
904
904
|
*/
|
|
905
905
|
wrap(): Queryable<TData, never> {
|
|
906
|
-
//
|
|
906
|
+
// 현재 Queryable을 서브쿼리로 래핑
|
|
907
907
|
const wrapAlias = this.meta.db.getNextAlias();
|
|
908
908
|
return new Queryable({
|
|
909
909
|
db: this.meta.db,
|
|
@@ -932,7 +932,7 @@ export class Queryable<
|
|
|
932
932
|
...queries: Queryable<TData, any>[]
|
|
933
933
|
): Queryable<TData, never> {
|
|
934
934
|
if (queries.length < 2) {
|
|
935
|
-
throw new ArgumentError("union
|
|
935
|
+
throw new ArgumentError("union은 최소 2개의 queryable이 필요합니다.", {
|
|
936
936
|
provided: queries.length,
|
|
937
937
|
minimum: 2,
|
|
938
938
|
});
|
|
@@ -942,7 +942,7 @@ export class Queryable<
|
|
|
942
942
|
const unionAlias = first.meta.db.getNextAlias();
|
|
943
943
|
return new Queryable({
|
|
944
944
|
db: first.meta.db,
|
|
945
|
-
from: queries, //
|
|
945
|
+
from: queries, // Queryable[] 배열로 저장
|
|
946
946
|
as: unionAlias,
|
|
947
947
|
columns: transformColumnsAlias(first.meta.columns, unionAlias, ""),
|
|
948
948
|
});
|
|
@@ -982,13 +982,13 @@ export class Queryable<
|
|
|
982
982
|
columns: transformColumnsAlias(newFroms[0].meta.columns, this.meta.as, ""),
|
|
983
983
|
});
|
|
984
984
|
}
|
|
985
|
-
//
|
|
985
|
+
// 동적 CTE 이름 생성
|
|
986
986
|
const cteName = this.meta.db.getNextAlias();
|
|
987
987
|
|
|
988
|
-
// 2.
|
|
988
|
+
// 2. 대상을 Queryable로 변환 (CTE 이름 전달)
|
|
989
989
|
const cteQr = new RecursiveQueryable(this, cteName);
|
|
990
990
|
|
|
991
|
-
// 3.
|
|
991
|
+
// 3. fn 실행 (where 등 조건이 추가된 Queryable 반환)
|
|
992
992
|
const resultQr = fn(cteQr);
|
|
993
993
|
|
|
994
994
|
return new Queryable({
|
|
@@ -998,7 +998,7 @@ export class Queryable<
|
|
|
998
998
|
columns: transformColumnsAlias(this.meta.columns, this.meta.as, ""),
|
|
999
999
|
with: {
|
|
1000
1000
|
name: cteName,
|
|
1001
|
-
base: this as any, //
|
|
1001
|
+
base: this as any, // 순환 참조 타입 추론 차단
|
|
1002
1002
|
recursive: resultQr,
|
|
1003
1003
|
},
|
|
1004
1004
|
});
|
|
@@ -1044,7 +1044,7 @@ export class Queryable<
|
|
|
1044
1044
|
async single(): Promise<TData | undefined> {
|
|
1045
1045
|
const result = await this.top(2).execute();
|
|
1046
1046
|
if (result.length > 1) {
|
|
1047
|
-
throw new ArgumentError("
|
|
1047
|
+
throw new ArgumentError("단일 결과를 기대했으나 복수 결과가 반환되었습니다.", {
|
|
1048
1048
|
table: this._getSourceName(),
|
|
1049
1049
|
resultCount: result.length,
|
|
1050
1050
|
});
|
|
@@ -1053,7 +1053,7 @@ export class Queryable<
|
|
|
1053
1053
|
}
|
|
1054
1054
|
|
|
1055
1055
|
/**
|
|
1056
|
-
*
|
|
1056
|
+
* Query 소스 이름 반환 (에러 메시지용)
|
|
1057
1057
|
*/
|
|
1058
1058
|
private _getSourceName(): string {
|
|
1059
1059
|
const from = this.meta.from;
|
|
@@ -1099,10 +1099,10 @@ export class Queryable<
|
|
|
1099
1099
|
*/
|
|
1100
1100
|
async count(fn?: (cols: QueryableRecord<TData>) => ExprUnit<ColumnPrimitive>): Promise<number> {
|
|
1101
1101
|
if (this.meta.distinct) {
|
|
1102
|
-
throw new Error("
|
|
1102
|
+
throw new Error("distinct() 이후에 count()를 사용할 수 없습니다. wrap()을 먼저 사용하세요.");
|
|
1103
1103
|
}
|
|
1104
1104
|
if (this.meta.groupBy) {
|
|
1105
|
-
throw new Error("
|
|
1105
|
+
throw new Error("groupBy() 이후에 count()를 사용할 수 없습니다. wrap()을 먼저 사용하세요.");
|
|
1106
1106
|
}
|
|
1107
1107
|
|
|
1108
1108
|
const countQr = fn
|
|
@@ -1193,7 +1193,7 @@ export class Queryable<
|
|
|
1193
1193
|
} else if (typeof val === "object" && val != null) {
|
|
1194
1194
|
Object.assign(result, this._buildSelectDef(val, fullKey));
|
|
1195
1195
|
} else {
|
|
1196
|
-
//
|
|
1196
|
+
// 일반 값 (string, number, boolean 등) — Expr로 변환
|
|
1197
1197
|
result[fullKey] = expr.toExpr(val);
|
|
1198
1198
|
}
|
|
1199
1199
|
}
|
|
@@ -1230,16 +1230,16 @@ export class Queryable<
|
|
|
1230
1230
|
if (outputColumns && !outputColumns.includes(fullKey)) continue;
|
|
1231
1231
|
|
|
1232
1232
|
if (val instanceof ExprUnit) {
|
|
1233
|
-
//
|
|
1233
|
+
// 원시 column
|
|
1234
1234
|
columns[fullKey] = val.dataType;
|
|
1235
1235
|
} else if (Array.isArray(val)) {
|
|
1236
|
-
//
|
|
1236
|
+
// 배열 (1:N 관계)
|
|
1237
1237
|
if (val.length > 0) {
|
|
1238
1238
|
joins[fullKey] = { isSingle: false };
|
|
1239
1239
|
buildResultMeta(val[0], fullKey);
|
|
1240
1240
|
}
|
|
1241
1241
|
} else if (typeof val === "object") {
|
|
1242
|
-
//
|
|
1242
|
+
// 단일 객체 (N:1, 1:1 관계)
|
|
1243
1243
|
joins[fullKey] = { isSingle: true };
|
|
1244
1244
|
buildResultMeta(val, fullKey);
|
|
1245
1245
|
}
|
|
@@ -1291,7 +1291,7 @@ export class Queryable<
|
|
|
1291
1291
|
return outputColumns ? [] : undefined;
|
|
1292
1292
|
}
|
|
1293
1293
|
|
|
1294
|
-
//
|
|
1294
|
+
// MSSQL의 1000행 제한을 위해 청크로 분할
|
|
1295
1295
|
const CHUNK_SIZE = 1000;
|
|
1296
1296
|
const allResults: Pick<TFrom["$inferColumns"], K>[] = [];
|
|
1297
1297
|
|
|
@@ -1389,7 +1389,7 @@ export class Queryable<
|
|
|
1389
1389
|
const from = this.meta.from as TableBuilder<any, any> | ViewBuilder<any, any, any>;
|
|
1390
1390
|
const outputDef = this._getCudOutputDef();
|
|
1391
1391
|
|
|
1392
|
-
//
|
|
1392
|
+
// AI column에 명시적 값이 있으면 overrideIdentity 설정
|
|
1393
1393
|
const overrideIdentity =
|
|
1394
1394
|
outputDef.aiColName != null &&
|
|
1395
1395
|
records.some((r) => (r as Record<string, unknown>)[outputDef.aiColName!] !== undefined);
|
|
@@ -1679,14 +1679,14 @@ export class Queryable<
|
|
|
1679
1679
|
|
|
1680
1680
|
const { select: _sel, ...existsSelectQuery } = this.getSelectQueryDef();
|
|
1681
1681
|
|
|
1682
|
-
//
|
|
1682
|
+
// updateRecord 생성
|
|
1683
1683
|
const updateQrRecord = updateRecordFn(this.meta.columns);
|
|
1684
1684
|
const updateRecord: Record<string, Expr> = {};
|
|
1685
1685
|
for (const [key, value] of Object.entries(updateQrRecord)) {
|
|
1686
1686
|
updateRecord[key] = expr.toExpr(value);
|
|
1687
1687
|
}
|
|
1688
1688
|
|
|
1689
|
-
//
|
|
1689
|
+
// insertRecord 생성 (updateRecordRaw를 두 번째 인자로 전달)
|
|
1690
1690
|
const insertRecordRaw = insertRecordFn(updateQrRecord);
|
|
1691
1691
|
const insertRecord = Object.fromEntries(
|
|
1692
1692
|
Object.entries(insertRecordRaw).map(([key, value]) => [key, expr.toExpr(value)]),
|
|
@@ -1713,13 +1713,13 @@ export class Queryable<
|
|
|
1713
1713
|
//#region ========== DDL Helper ==========
|
|
1714
1714
|
|
|
1715
1715
|
/**
|
|
1716
|
-
* FK
|
|
1716
|
+
* FK 제약조건 활성화/비활성화 (트랜잭션 내에서 사용 가능)
|
|
1717
1717
|
*/
|
|
1718
1718
|
async switchFk(enabled: boolean): Promise<void> {
|
|
1719
1719
|
const from = this.meta.from;
|
|
1720
1720
|
if (!(from instanceof TableBuilder) && !(from instanceof ViewBuilder)) {
|
|
1721
1721
|
throw new Error(
|
|
1722
|
-
"switchFk
|
|
1722
|
+
"switchFk는 TableBuilder 또는 ViewBuilder 기반 queryable에서만 사용할 수 있습니다.",
|
|
1723
1723
|
);
|
|
1724
1724
|
}
|
|
1725
1725
|
await this.meta.db.switchFk(this.meta.db.getQueryDefObjectName(from), enabled);
|
|
@@ -1737,7 +1737,7 @@ export class Queryable<
|
|
|
1737
1737
|
|
|
1738
1738
|
if (from instanceof TableBuilder) {
|
|
1739
1739
|
if (from.meta.columns == null) {
|
|
1740
|
-
throw new Error(
|
|
1740
|
+
throw new Error(`테이블 '${from.meta.name}'에 Column 정의가 없습니다.`);
|
|
1741
1741
|
}
|
|
1742
1742
|
|
|
1743
1743
|
let aiColName: string | undefined;
|
|
@@ -1753,7 +1753,7 @@ export class Queryable<
|
|
|
1753
1753
|
};
|
|
1754
1754
|
}
|
|
1755
1755
|
|
|
1756
|
-
throw new Error("CUD
|
|
1756
|
+
throw new Error("CUD 작업은 TableBuilder 기반 queryable에서만 사용할 수 있습니다.");
|
|
1757
1757
|
}
|
|
1758
1758
|
|
|
1759
1759
|
//#endregion
|
|
@@ -1879,7 +1879,7 @@ export type QueryableWriteRecord<TData> = {
|
|
|
1879
1879
|
};
|
|
1880
1880
|
|
|
1881
1881
|
export type NullableQueryableRecord<TData extends DataRecord> = {
|
|
1882
|
-
//
|
|
1882
|
+
// 원시값 — 항상 | undefined (LEFT JOIN NULL 전파)
|
|
1883
1883
|
[K in keyof TData]: TData[K] extends ColumnPrimitive
|
|
1884
1884
|
? ExprUnit<TData[K] | undefined>
|
|
1885
1885
|
: TData[K] extends (infer U)[]
|
|
@@ -1898,9 +1898,9 @@ export type NullableQueryableRecord<TData extends DataRecord> = {
|
|
|
1898
1898
|
};
|
|
1899
1899
|
|
|
1900
1900
|
/**
|
|
1901
|
-
*
|
|
1901
|
+
* QueryableRecord에서 DataRecord로 역변환
|
|
1902
1902
|
*
|
|
1903
|
-
*
|
|
1903
|
+
* ExprUnit<T>를 T로 언래핑, 중첩 객체/배열을 재귀적으로 언래핑
|
|
1904
1904
|
*/
|
|
1905
1905
|
export type UnwrapQueryableRecord<R> = {
|
|
1906
1906
|
[K in keyof R]: R[K] extends ExprUnit<infer T>
|
|
@@ -1917,27 +1917,27 @@ export type UnwrapQueryableRecord<R> = {
|
|
|
1917
1917
|
//#region ========== PathProxy - Type-safe path builder for include ==========
|
|
1918
1918
|
|
|
1919
1919
|
/**
|
|
1920
|
-
*
|
|
1921
|
-
*
|
|
1920
|
+
* include()에서 타입 안전하게 관계 경로를 지정하기 위한 Proxy 타입
|
|
1921
|
+
* non-ColumnPrimitive 필드(FK, FKT 관계)만 접근 가능
|
|
1922
1922
|
*
|
|
1923
1923
|
* @example
|
|
1924
1924
|
* ```typescript
|
|
1925
|
-
* //
|
|
1925
|
+
* // item.user.company 접근 시 내부적으로 경로 ["user", "company"]를 수집
|
|
1926
1926
|
* db.post.include(item => item.user.company)
|
|
1927
1927
|
*
|
|
1928
|
-
* // item.title
|
|
1929
|
-
* db.post.include(item => item.title) //
|
|
1928
|
+
* // item.title은 string(ColumnPrimitive)이므로 컴파일 에러
|
|
1929
|
+
* db.post.include(item => item.title) // 컴파일 에러
|
|
1930
1930
|
* ```
|
|
1931
1931
|
*/
|
|
1932
1932
|
/**
|
|
1933
|
-
*
|
|
1933
|
+
* 배열이면 요소 타입 추출
|
|
1934
1934
|
*/
|
|
1935
1935
|
type UnwrapArray<TArray> = TArray extends (infer TElement)[] ? TElement : TArray;
|
|
1936
1936
|
|
|
1937
1937
|
const PATH_SYMBOL = Symbol("path");
|
|
1938
1938
|
|
|
1939
1939
|
/**
|
|
1940
|
-
*
|
|
1940
|
+
* include()용 타입 안전 path proxy
|
|
1941
1941
|
*/
|
|
1942
1942
|
export type PathProxy<TObject> = {
|
|
1943
1943
|
[K in keyof TObject as TObject[K] extends ColumnPrimitive ? never : K]-?: PathProxy<
|
|
@@ -1946,8 +1946,8 @@ export type PathProxy<TObject> = {
|
|
|
1946
1946
|
} & { readonly [PATH_SYMBOL]: string[] };
|
|
1947
1947
|
|
|
1948
1948
|
/**
|
|
1949
|
-
*
|
|
1950
|
-
*
|
|
1949
|
+
* PathProxy 인스턴스 생성
|
|
1950
|
+
* Proxy를 사용하여 속성 접근을 가로채고 경로를 수집
|
|
1951
1951
|
*/
|
|
1952
1952
|
function createPathProxy<TObject>(path: string[] = []): PathProxy<TObject> {
|
|
1953
1953
|
return new Proxy({} as PathProxy<TObject>, {
|
|
@@ -1962,22 +1962,22 @@ function createPathProxy<TObject>(path: string[] = []): PathProxy<TObject> {
|
|
|
1962
1962
|
//#endregion
|
|
1963
1963
|
|
|
1964
1964
|
/**
|
|
1965
|
-
*
|
|
1965
|
+
* Table 또는 View용 Queryable factory 함수 생성
|
|
1966
1966
|
*
|
|
1967
|
-
*
|
|
1967
|
+
* DbContext에서 Table/View별 getter를 정의할 때 사용
|
|
1968
1968
|
*
|
|
1969
|
-
* @param db - DbContext
|
|
1970
|
-
* @param tableOrView - TableBuilder
|
|
1971
|
-
* @param as - Alias
|
|
1972
|
-
* @returns
|
|
1969
|
+
* @param db - DbContext 인스턴스
|
|
1970
|
+
* @param tableOrView - TableBuilder 또는 ViewBuilder 인스턴스
|
|
1971
|
+
* @param as - Alias 지정 (선택, 미지정 시 자동 생성)
|
|
1972
|
+
* @returns Queryable을 반환하는 factory 함수
|
|
1973
1973
|
*
|
|
1974
1974
|
* @example
|
|
1975
1975
|
* ```typescript
|
|
1976
1976
|
* class AppDbContext extends DbContext {
|
|
1977
|
-
* //
|
|
1977
|
+
* // 호출할 때마다 새 alias가 할당됨
|
|
1978
1978
|
* user = queryable(this, User);
|
|
1979
1979
|
*
|
|
1980
|
-
* //
|
|
1980
|
+
* // 사용 예시
|
|
1981
1981
|
* async getActiveUsers() {
|
|
1982
1982
|
* return this.user()
|
|
1983
1983
|
* .where((u) => [expr.eq(u.isActive, true)])
|
|
@@ -1992,8 +1992,8 @@ export function queryable<TBuilder extends TableBuilder<any, any> | ViewBuilder<
|
|
|
1992
1992
|
as?: string,
|
|
1993
1993
|
): () => Queryable<TBuilder["$inferSelect"], TBuilder extends TableBuilder<any, any> ? TBuilder : never> {
|
|
1994
1994
|
return () => {
|
|
1995
|
-
//
|
|
1996
|
-
//
|
|
1995
|
+
// as가 미지정이면 db.getNextAlias() 사용 (카운터 증가)
|
|
1996
|
+
// as가 지정되면 그대로 사용 (카운터 증가 없음)
|
|
1997
1997
|
const finalAs = as ?? db.getNextAlias();
|
|
1998
1998
|
|
|
1999
1999
|
// TableBuilder + columns
|
|
@@ -2017,7 +2017,7 @@ export function queryable<TBuilder extends TableBuilder<any, any> | ViewBuilder<
|
|
|
2017
2017
|
if (tableOrView instanceof ViewBuilder && tableOrView.meta.viewFn != null) {
|
|
2018
2018
|
const baseQr = tableOrView.meta.viewFn(db);
|
|
2019
2019
|
|
|
2020
|
-
//
|
|
2020
|
+
// TFrom을 ViewBuilder로 설정하여 반환
|
|
2021
2021
|
return new Queryable({
|
|
2022
2022
|
db,
|
|
2023
2023
|
from: tableOrView,
|