@simplysm/orm-common 13.0.69 → 13.0.70
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 +54 -1447
- package/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +9 -9
- package/dist/create-db-context.js.map +1 -1
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +2 -2
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/table-ddl.d.ts +24 -24
- package/dist/ddl/table-ddl.js +4 -4
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +3 -3
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.d.ts +160 -160
- package/dist/exec/queryable.js +119 -119
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.d.ts +37 -37
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr.d.ts +257 -257
- package/dist/expr/expr.js +265 -265
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
- package/dist/query-builder/base/expr-renderer-base.js +2 -2
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
- package/dist/query-builder/base/query-builder-base.d.ts +26 -26
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +22 -22
- package/dist/query-builder/base/query-builder-base.js.map +1 -1
- 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 +18 -18
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
- 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 +17 -17
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- 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 +17 -17
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +79 -79
- package/dist/schema/factory/column-builder.js +42 -42
- package/dist/schema/factory/index-builder.d.ts +39 -39
- package/dist/schema/factory/index-builder.js +26 -26
- 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 +38 -38
- package/dist/schema/procedure-builder.d.ts +49 -49
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +33 -33
- package/dist/schema/table-builder.d.ts +59 -59
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +43 -43
- package/dist/schema/view-builder.d.ts +49 -49
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +32 -32
- package/dist/types/column.d.ts +22 -22
- package/dist/types/column.js +1 -1
- package/dist/types/column.js.map +1 -1
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +59 -59
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +44 -44
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/utils/result-parser.d.ts +11 -11
- package/dist/utils/result-parser.js +3 -3
- package/dist/utils/result-parser.js.map +1 -1
- package/package.json +5 -5
- package/src/create-db-context.ts +20 -20
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +259 -259
- package/src/ddl/relation-ddl.ts +89 -89
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +189 -189
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +2033 -2033
- package/src/exec/search-parser.ts +57 -57
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +2140 -2140
- package/src/query-builder/base/expr-renderer-base.ts +237 -237
- package/src/query-builder/base/query-builder-base.ts +213 -213
- package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
- package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
- package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
- package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
- package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
- package/src/query-builder/query-builder.ts +19 -19
- package/src/schema/factory/column-builder.ts +423 -423
- package/src/schema/factory/index-builder.ts +164 -164
- package/src/schema/factory/relation-builder.ts +453 -453
- package/src/schema/procedure-builder.ts +232 -232
- package/src/schema/table-builder.ts +319 -319
- package/src/schema/view-builder.ts +221 -221
- package/src/types/column.ts +188 -188
- package/src/types/db.ts +208 -208
- package/src/types/expr.ts +697 -697
- package/src/types/query-def.ts +513 -513
- package/src/utils/result-parser.ts +458 -458
- package/tests/db-context/create-db-context.spec.ts +224 -0
- package/tests/db-context/define-db-context.spec.ts +68 -0
- package/tests/ddl/basic.expected.ts +341 -0
- package/tests/ddl/basic.spec.ts +714 -0
- package/tests/ddl/column-builder.expected.ts +310 -0
- package/tests/ddl/column-builder.spec.ts +637 -0
- package/tests/ddl/index-builder.expected.ts +38 -0
- package/tests/ddl/index-builder.spec.ts +202 -0
- package/tests/ddl/procedure-builder.expected.ts +52 -0
- package/tests/ddl/procedure-builder.spec.ts +234 -0
- package/tests/ddl/relation-builder.expected.ts +36 -0
- package/tests/ddl/relation-builder.spec.ts +372 -0
- package/tests/ddl/table-builder.expected.ts +113 -0
- package/tests/ddl/table-builder.spec.ts +433 -0
- package/tests/ddl/view-builder.expected.ts +38 -0
- package/tests/ddl/view-builder.spec.ts +176 -0
- package/tests/dml/delete.expected.ts +96 -0
- package/tests/dml/delete.spec.ts +160 -0
- package/tests/dml/insert.expected.ts +192 -0
- package/tests/dml/insert.spec.ts +288 -0
- package/tests/dml/update.expected.ts +176 -0
- package/tests/dml/update.spec.ts +318 -0
- package/tests/dml/upsert.expected.ts +215 -0
- package/tests/dml/upsert.spec.ts +242 -0
- package/tests/errors/queryable-errors.spec.ts +177 -0
- package/tests/escape.spec.ts +100 -0
- package/tests/examples/pivot.expected.ts +211 -0
- package/tests/examples/pivot.spec.ts +533 -0
- package/tests/examples/sampling.expected.ts +69 -0
- package/tests/examples/sampling.spec.ts +104 -0
- package/tests/examples/unpivot.expected.ts +120 -0
- package/tests/examples/unpivot.spec.ts +226 -0
- package/tests/exec/search-parser.spec.ts +283 -0
- package/tests/executable/basic.expected.ts +18 -0
- package/tests/executable/basic.spec.ts +54 -0
- package/tests/expr/comparison.expected.ts +282 -0
- package/tests/expr/comparison.spec.ts +400 -0
- package/tests/expr/conditional.expected.ts +134 -0
- package/tests/expr/conditional.spec.ts +276 -0
- package/tests/expr/date.expected.ts +332 -0
- package/tests/expr/date.spec.ts +526 -0
- package/tests/expr/math.expected.ts +62 -0
- package/tests/expr/math.spec.ts +106 -0
- package/tests/expr/string.expected.ts +218 -0
- package/tests/expr/string.spec.ts +356 -0
- package/tests/expr/utility.expected.ts +147 -0
- package/tests/expr/utility.spec.ts +182 -0
- package/tests/select/basic.expected.ts +322 -0
- package/tests/select/basic.spec.ts +502 -0
- package/tests/select/filter.expected.ts +357 -0
- package/tests/select/filter.spec.ts +1068 -0
- package/tests/select/group.expected.ts +169 -0
- package/tests/select/group.spec.ts +244 -0
- package/tests/select/join.expected.ts +582 -0
- package/tests/select/join.spec.ts +805 -0
- package/tests/select/order.expected.ts +150 -0
- package/tests/select/order.spec.ts +189 -0
- package/tests/select/recursive-cte.expected.ts +244 -0
- package/tests/select/recursive-cte.spec.ts +514 -0
- package/tests/select/result-meta.spec.ts +270 -0
- package/tests/select/subquery.expected.ts +363 -0
- package/tests/select/subquery.spec.ts +537 -0
- package/tests/select/view.expected.ts +155 -0
- package/tests/select/view.spec.ts +235 -0
- package/tests/select/window.expected.ts +345 -0
- package/tests/select/window.spec.ts +618 -0
- package/tests/setup/MockExecutor.ts +18 -0
- package/tests/setup/TestDbContext.ts +59 -0
- package/tests/setup/models/Company.ts +13 -0
- package/tests/setup/models/Employee.ts +10 -0
- package/tests/setup/models/MonthlySales.ts +11 -0
- package/tests/setup/models/Post.ts +16 -0
- package/tests/setup/models/Sales.ts +10 -0
- package/tests/setup/models/User.ts +19 -0
- package/tests/setup/procedure/GetAllUsers.ts +9 -0
- package/tests/setup/procedure/GetUserById.ts +12 -0
- package/tests/setup/test-utils.ts +72 -0
- package/tests/setup/views/ActiveUsers.ts +8 -0
- package/tests/setup/views/UserSummary.ts +11 -0
- package/tests/types/nullable-queryable-record.spec.ts +145 -0
- package/tests/utils/result-parser-perf.spec.ts +210 -0
- package/tests/utils/result-parser.spec.ts +701 -0
- package/docs/expressions.md +0 -172
- package/docs/queries.md +0 -444
- package/docs/schema.md +0 -245
package/src/expr/expr.ts
CHANGED
|
@@ -1,2140 +1,2140 @@
|
|
|
1
|
-
import { ArgumentError, type DateOnly, type DateTime, type Time } from "@simplysm/core-common";
|
|
2
|
-
import {
|
|
3
|
-
type ColumnPrimitive,
|
|
4
|
-
type ColumnPrimitiveMap,
|
|
5
|
-
type ColumnPrimitiveStr,
|
|
6
|
-
type DataType,
|
|
7
|
-
dataTypeStrToColumnPrimitiveStr,
|
|
8
|
-
type InferColumnPrimitiveFromDataType,
|
|
9
|
-
inferColumnPrimitiveStr,
|
|
10
|
-
} from "../types/column";
|
|
11
|
-
import type { ExprInput } from "./expr-unit";
|
|
12
|
-
import { ExprUnit, WhereExprUnit } from "./expr-unit";
|
|
13
|
-
import type { Expr, DateSeparator, WhereExpr, WinSpec } from "../types/expr";
|
|
14
|
-
import type { SelectQueryDef } from "../types/query-def";
|
|
15
|
-
import type { Queryable } from "../exec/queryable";
|
|
16
|
-
|
|
17
|
-
// Window Function Spec Input (
|
|
18
|
-
interface WinSpecInput {
|
|
19
|
-
partitionBy?: ExprInput<ColumnPrimitive>[];
|
|
20
|
-
orderBy?: [ExprInput<ColumnPrimitive>, ("ASC" | "DESC")?][];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Switch
|
|
25
|
-
*/
|
|
26
|
-
export interface SwitchExprBuilder<TPrimitive extends ColumnPrimitive> {
|
|
27
|
-
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): SwitchExprBuilder<TPrimitive>;
|
|
28
|
-
default(value: ExprInput<TPrimitive>): ExprUnit<TPrimitive>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Dialect
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```typescript
|
|
39
|
-
* // WHERE
|
|
40
|
-
* db.user().where((u) => [
|
|
41
|
-
* expr.eq(u.status, "active"),
|
|
42
|
-
* expr.gt(u.age, 18),
|
|
43
|
-
* ])
|
|
44
|
-
*
|
|
45
|
-
* // SELECT
|
|
46
|
-
* db.user().select((u) => ({
|
|
47
|
-
* name: expr.concat(u.firstName, " ", u.lastName),
|
|
48
|
-
* age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
|
|
49
|
-
* }))
|
|
50
|
-
*
|
|
51
|
-
* //
|
|
52
|
-
* db.order().groupBy((o) => o.userId).select((o) => ({
|
|
53
|
-
* userId: o.userId,
|
|
54
|
-
* total: expr.sum(o.amount),
|
|
55
|
-
* }))
|
|
56
|
-
* ```
|
|
57
|
-
*
|
|
58
|
-
* @see {@link Queryable}
|
|
59
|
-
* @see {@link ExprUnit}
|
|
60
|
-
*/
|
|
61
|
-
export const expr = {
|
|
62
|
-
//#region ==========
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* @param dataType -
|
|
70
|
-
* @param value - 래핑할
|
|
71
|
-
* @returns 래핑된 ExprUnit
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```typescript
|
|
75
|
-
* //
|
|
76
|
-
* expr.val("string", "active")
|
|
77
|
-
*
|
|
78
|
-
* //
|
|
79
|
-
* expr.val("number", 100)
|
|
80
|
-
*
|
|
81
|
-
* //
|
|
82
|
-
* expr.val("DateOnly", DateOnly.today())
|
|
83
|
-
*
|
|
84
|
-
* // undefined
|
|
85
|
-
* expr.val("string", undefined)
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
val<TStr extends ColumnPrimitiveStr, T extends ColumnPrimitiveMap[TStr] | undefined>(
|
|
89
|
-
dataType: TStr,
|
|
90
|
-
value: T,
|
|
91
|
-
): ExprUnit<
|
|
92
|
-
T extends undefined ? ColumnPrimitiveMap[TStr] | undefined : ColumnPrimitiveMap[TStr]
|
|
93
|
-
> {
|
|
94
|
-
return new ExprUnit(dataType, { type: "value", value });
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* @param dataType -
|
|
103
|
-
* @param path -
|
|
104
|
-
* @returns
|
|
105
|
-
*
|
|
106
|
-
* @example
|
|
107
|
-
* ```typescript
|
|
108
|
-
* //
|
|
109
|
-
* expr.col("string", "T1", "name")
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
col<TStr extends ColumnPrimitiveStr>(
|
|
113
|
-
dataType: ColumnPrimitiveStr,
|
|
114
|
-
...path: string[]
|
|
115
|
-
): ExprUnit<ColumnPrimitiveMap[TStr] | undefined> {
|
|
116
|
-
return new ExprUnit(dataType, { type: "column", path });
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Raw SQL
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* @param dataType -
|
|
126
|
-
* @returns
|
|
127
|
-
*
|
|
128
|
-
* @example
|
|
129
|
-
* ```typescript
|
|
130
|
-
* // MySQL JSON
|
|
131
|
-
* db.user().select((u) => ({
|
|
132
|
-
* name: u.name,
|
|
133
|
-
* data: expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`,
|
|
134
|
-
* }))
|
|
135
|
-
*
|
|
136
|
-
* // PostgreSQL
|
|
137
|
-
* expr.raw("number")`ARRAY_LENGTH(${u.tags}, 1)`
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
raw<T extends ColumnPrimitiveStr>(
|
|
141
|
-
dataType: T,
|
|
142
|
-
): (
|
|
143
|
-
strings: TemplateStringsArray,
|
|
144
|
-
...values: ExprInput<ColumnPrimitive>[]
|
|
145
|
-
) => ExprUnit<ColumnPrimitiveMap[T] | undefined> {
|
|
146
|
-
return (strings, ...values) => {
|
|
147
|
-
const sql = strings.reduce((acc, str, i) => {
|
|
148
|
-
if (i < values.length) {
|
|
149
|
-
return acc + str + `$${i + 1}`; //
|
|
150
|
-
}
|
|
151
|
-
return acc + str;
|
|
152
|
-
}, "");
|
|
153
|
-
|
|
154
|
-
const params = values.map((v) => toExpr(v));
|
|
155
|
-
|
|
156
|
-
return new ExprUnit(dataType, { type: "raw", sql, params });
|
|
157
|
-
};
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
//#endregion
|
|
161
|
-
|
|
162
|
-
//#region ========== WHERE -
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* @param source -
|
|
170
|
-
* @param target -
|
|
171
|
-
* @returns WHERE
|
|
172
|
-
*
|
|
173
|
-
* @example
|
|
174
|
-
* ```typescript
|
|
175
|
-
* db.user().where((u) => [expr.eq(u.status, "active")])
|
|
176
|
-
* // WHERE status <=> 'active' (MySQL)
|
|
177
|
-
* ```
|
|
178
|
-
*/
|
|
179
|
-
eq<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
180
|
-
return new WhereExprUnit({
|
|
181
|
-
type: "eq",
|
|
182
|
-
source: toExpr(source),
|
|
183
|
-
target: toExpr(target),
|
|
184
|
-
});
|
|
185
|
-
},
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* @param source -
|
|
191
|
-
* @param target -
|
|
192
|
-
* @returns WHERE
|
|
193
|
-
*
|
|
194
|
-
* @example
|
|
195
|
-
* ```typescript
|
|
196
|
-
* db.user().where((u) => [expr.gt(u.age, 18)])
|
|
197
|
-
* // WHERE age > 18
|
|
198
|
-
* ```
|
|
199
|
-
*/
|
|
200
|
-
gt<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
201
|
-
return new WhereExprUnit({
|
|
202
|
-
type: "gt",
|
|
203
|
-
source: toExpr(source),
|
|
204
|
-
target: toExpr(target),
|
|
205
|
-
});
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* @param source -
|
|
212
|
-
* @param target -
|
|
213
|
-
* @returns WHERE
|
|
214
|
-
*
|
|
215
|
-
* @example
|
|
216
|
-
* ```typescript
|
|
217
|
-
* db.user().where((u) => [expr.lt(u.score, 60)])
|
|
218
|
-
* // WHERE score < 60
|
|
219
|
-
* ```
|
|
220
|
-
*/
|
|
221
|
-
lt<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
222
|
-
return new WhereExprUnit({
|
|
223
|
-
type: "lt",
|
|
224
|
-
source: toExpr(source),
|
|
225
|
-
target: toExpr(target),
|
|
226
|
-
});
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* @param source -
|
|
233
|
-
* @param target -
|
|
234
|
-
* @returns WHERE
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```typescript
|
|
238
|
-
* db.user().where((u) => [expr.gte(u.age, 18)])
|
|
239
|
-
* // WHERE age >= 18
|
|
240
|
-
* ```
|
|
241
|
-
*/
|
|
242
|
-
gte<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
243
|
-
return new WhereExprUnit({
|
|
244
|
-
type: "gte",
|
|
245
|
-
source: toExpr(source),
|
|
246
|
-
target: toExpr(target),
|
|
247
|
-
});
|
|
248
|
-
},
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* @param source -
|
|
254
|
-
* @param target -
|
|
255
|
-
* @returns WHERE
|
|
256
|
-
*
|
|
257
|
-
* @example
|
|
258
|
-
* ```typescript
|
|
259
|
-
* db.user().where((u) => [expr.lte(u.score, 100)])
|
|
260
|
-
* // WHERE score <= 100
|
|
261
|
-
* ```
|
|
262
|
-
*/
|
|
263
|
-
lte<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
264
|
-
return new WhereExprUnit({
|
|
265
|
-
type: "lte",
|
|
266
|
-
source: toExpr(source),
|
|
267
|
-
target: toExpr(target),
|
|
268
|
-
});
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
* from/to가 undefined이면 해당 방향은 무제한
|
|
275
|
-
*
|
|
276
|
-
* @param source -
|
|
277
|
-
* @param from -
|
|
278
|
-
* @param to - 끝
|
|
279
|
-
* @returns WHERE
|
|
280
|
-
*
|
|
281
|
-
* @example
|
|
282
|
-
* ```typescript
|
|
283
|
-
* //
|
|
284
|
-
* db.user().where((u) => [expr.between(u.age, 18, 65)])
|
|
285
|
-
* // WHERE age BETWEEN 18 AND 65
|
|
286
|
-
*
|
|
287
|
-
* //
|
|
288
|
-
* db.user().where((u) => [expr.between(u.age, 18, undefined)])
|
|
289
|
-
* // WHERE age >= 18
|
|
290
|
-
* ```
|
|
291
|
-
*/
|
|
292
|
-
between<T extends ColumnPrimitive>(
|
|
293
|
-
source: ExprUnit<T>,
|
|
294
|
-
from?: ExprInput<T>,
|
|
295
|
-
to?: ExprInput<T>,
|
|
296
|
-
): WhereExprUnit {
|
|
297
|
-
return new WhereExprUnit({
|
|
298
|
-
type: "between",
|
|
299
|
-
source: toExpr(source),
|
|
300
|
-
from: from != null ? toExpr(from) : undefined,
|
|
301
|
-
to: to != null ? toExpr(to) : undefined,
|
|
302
|
-
});
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
//#endregion
|
|
306
|
-
|
|
307
|
-
//#region ========== WHERE - NULL
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* NULL 체크 (IS NULL)
|
|
311
|
-
*
|
|
312
|
-
* @param source - 체크할
|
|
313
|
-
* @returns WHERE
|
|
314
|
-
*
|
|
315
|
-
* @example
|
|
316
|
-
* ```typescript
|
|
317
|
-
* db.user().where((u) => [expr.null(u.deletedAt)])
|
|
318
|
-
* // WHERE deletedAt IS NULL
|
|
319
|
-
* ```
|
|
320
|
-
*/
|
|
321
|
-
null<T extends ColumnPrimitive>(source: ExprUnit<T>): WhereExprUnit {
|
|
322
|
-
return new WhereExprUnit({
|
|
323
|
-
type: "null",
|
|
324
|
-
arg: toExpr(source),
|
|
325
|
-
});
|
|
326
|
-
},
|
|
327
|
-
|
|
328
|
-
//#endregion
|
|
329
|
-
|
|
330
|
-
//#region ========== WHERE -
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* LIKE
|
|
334
|
-
*
|
|
335
|
-
* `%`는 0개 이상의 문자, `_`는 단일 문자와 매칭.
|
|
336
|
-
* 특수문자는 `\`로
|
|
337
|
-
*
|
|
338
|
-
* @param source - 검색할
|
|
339
|
-
* @param pattern - 검색
|
|
340
|
-
* @returns WHERE
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* ```typescript
|
|
344
|
-
* // 접두사 검색
|
|
345
|
-
* db.user().where((u) => [expr.like(u.name, "John%")])
|
|
346
|
-
* // WHERE name LIKE 'John%' ESCAPE '\'
|
|
347
|
-
*
|
|
348
|
-
* //
|
|
349
|
-
* db.user().where((u) => [expr.like(u.email, "%@gmail.com")])
|
|
350
|
-
* ```
|
|
351
|
-
*/
|
|
352
|
-
like(
|
|
353
|
-
source: ExprUnit<string | undefined>,
|
|
354
|
-
pattern: ExprInput<string | undefined>,
|
|
355
|
-
): WhereExprUnit {
|
|
356
|
-
return new WhereExprUnit({
|
|
357
|
-
type: "like",
|
|
358
|
-
source: toExpr(source),
|
|
359
|
-
pattern: toExpr(pattern),
|
|
360
|
-
});
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* DBMS별
|
|
367
|
-
*
|
|
368
|
-
* @param source - 검색할
|
|
369
|
-
* @param pattern -
|
|
370
|
-
* @returns WHERE
|
|
371
|
-
*
|
|
372
|
-
* @example
|
|
373
|
-
* ```typescript
|
|
374
|
-
* db.user().where((u) => [expr.regexp(u.email, "^[a-z]+@")])
|
|
375
|
-
* // MySQL: WHERE email REGEXP '^[a-z]+@'
|
|
376
|
-
* ```
|
|
377
|
-
*/
|
|
378
|
-
regexp(
|
|
379
|
-
source: ExprUnit<string | undefined>,
|
|
380
|
-
pattern: ExprInput<string | undefined>,
|
|
381
|
-
): WhereExprUnit {
|
|
382
|
-
return new WhereExprUnit({
|
|
383
|
-
type: "regexp",
|
|
384
|
-
source: toExpr(source),
|
|
385
|
-
pattern: toExpr(pattern),
|
|
386
|
-
});
|
|
387
|
-
},
|
|
388
|
-
|
|
389
|
-
//#endregion
|
|
390
|
-
|
|
391
|
-
//#region ========== WHERE - IN ==========
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* IN
|
|
395
|
-
*
|
|
396
|
-
* @param source -
|
|
397
|
-
* @param values - 비교할
|
|
398
|
-
* @returns WHERE
|
|
399
|
-
*
|
|
400
|
-
* @example
|
|
401
|
-
* ```typescript
|
|
402
|
-
* db.user().where((u) => [expr.in(u.status, ["active", "pending"])])
|
|
403
|
-
* // WHERE status IN ('active', 'pending')
|
|
404
|
-
* ```
|
|
405
|
-
*/
|
|
406
|
-
in<T extends ColumnPrimitive>(source: ExprUnit<T>, values: ExprInput<T>[]): WhereExprUnit {
|
|
407
|
-
return new WhereExprUnit({
|
|
408
|
-
type: "in",
|
|
409
|
-
source: toExpr(source),
|
|
410
|
-
values: values.map((v) => toExpr(v)),
|
|
411
|
-
});
|
|
412
|
-
},
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* IN (SELECT ...) -
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* @param source -
|
|
420
|
-
* @param query - 단일
|
|
421
|
-
* @returns WHERE
|
|
422
|
-
* @throws {Error}
|
|
423
|
-
*
|
|
424
|
-
* @example
|
|
425
|
-
* ```typescript
|
|
426
|
-
* db.user().where((u) => [
|
|
427
|
-
* expr.inQuery(
|
|
428
|
-
* u.id,
|
|
429
|
-
* db.order()
|
|
430
|
-
* .where((o) => [expr.gt(o.amount, 1000)])
|
|
431
|
-
* .select((o) => ({ userId: o.userId }))
|
|
432
|
-
* ),
|
|
433
|
-
* ])
|
|
434
|
-
* // WHERE id IN (SELECT userId FROM Order WHERE amount > 1000)
|
|
435
|
-
* ```
|
|
436
|
-
*/
|
|
437
|
-
inQuery<T extends ColumnPrimitive, TData extends Record<string, T>>(
|
|
438
|
-
source: ExprUnit<T>,
|
|
439
|
-
query: Queryable<TData, any>,
|
|
440
|
-
): WhereExprUnit {
|
|
441
|
-
const queryDef = query.getSelectQueryDef();
|
|
442
|
-
if (queryDef.select == null || Object.keys(queryDef.select).length !== 1) {
|
|
443
|
-
throw new Error("inQuery
|
|
444
|
-
}
|
|
445
|
-
return new WhereExprUnit({
|
|
446
|
-
type: "inQuery",
|
|
447
|
-
source: toExpr(source),
|
|
448
|
-
query: queryDef,
|
|
449
|
-
});
|
|
450
|
-
},
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* EXISTS (SELECT ...) -
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
* @param query - 존재 여부를 확인할 Queryable
|
|
458
|
-
* @returns WHERE
|
|
459
|
-
*
|
|
460
|
-
* @example
|
|
461
|
-
* ```typescript
|
|
462
|
-
* // 주문이 있는 사용자 조회
|
|
463
|
-
* db.user().where((u) => [
|
|
464
|
-
* expr.exists(
|
|
465
|
-
* db.order().where((o) => [expr.eq(o.userId, u.id)])
|
|
466
|
-
* ),
|
|
467
|
-
* ])
|
|
468
|
-
* // WHERE EXISTS (SELECT 1 FROM Order WHERE userId = User.id)
|
|
469
|
-
* ```
|
|
470
|
-
*/
|
|
471
|
-
exists(query: Queryable<any, any>): WhereExprUnit {
|
|
472
|
-
const { select: _, ...queryDefWithoutSelect } = query.getSelectQueryDef(); // EXISTS는 SELECT 절 불필요, 패킷 절약
|
|
473
|
-
return new WhereExprUnit({
|
|
474
|
-
type: "exists",
|
|
475
|
-
query: queryDefWithoutSelect,
|
|
476
|
-
});
|
|
477
|
-
},
|
|
478
|
-
|
|
479
|
-
//#endregion
|
|
480
|
-
|
|
481
|
-
//#region ========== WHERE -
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* NOT
|
|
485
|
-
*
|
|
486
|
-
* @param arg - 부정할
|
|
487
|
-
* @returns 부정된 WHERE
|
|
488
|
-
*
|
|
489
|
-
* @example
|
|
490
|
-
* ```typescript
|
|
491
|
-
* db.user().where((u) => [expr.not(expr.eq(u.status, "deleted"))])
|
|
492
|
-
* // WHERE NOT (status <=> 'deleted')
|
|
493
|
-
* ```
|
|
494
|
-
*/
|
|
495
|
-
not(arg: WhereExprUnit): WhereExprUnit {
|
|
496
|
-
return new WhereExprUnit({
|
|
497
|
-
type: "not",
|
|
498
|
-
arg: arg.expr,
|
|
499
|
-
});
|
|
500
|
-
},
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* AND
|
|
504
|
-
*
|
|
505
|
-
* 여러 조건을 AND로 결합. where() 메서드에 배열로 전달하면
|
|
506
|
-
*
|
|
507
|
-
* @param conditions - AND로 결합할
|
|
508
|
-
* @returns 결합된 WHERE
|
|
509
|
-
*
|
|
510
|
-
* @example
|
|
511
|
-
* ```typescript
|
|
512
|
-
* db.user().where((u) => [
|
|
513
|
-
* expr.and([
|
|
514
|
-
* expr.eq(u.status, "active"),
|
|
515
|
-
* expr.gte(u.age, 18),
|
|
516
|
-
* ]),
|
|
517
|
-
* ])
|
|
518
|
-
* // WHERE (status <=> 'active' AND age >= 18)
|
|
519
|
-
* ```
|
|
520
|
-
*/
|
|
521
|
-
and(conditions: WhereExprUnit[]): WhereExprUnit {
|
|
522
|
-
if (conditions.length === 0) {
|
|
523
|
-
throw new ArgumentError({ conditions: "
|
|
524
|
-
}
|
|
525
|
-
return new WhereExprUnit({
|
|
526
|
-
type: "and",
|
|
527
|
-
conditions: conditions.map((c) => c.expr),
|
|
528
|
-
});
|
|
529
|
-
},
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* OR
|
|
533
|
-
*
|
|
534
|
-
* @param conditions - OR로 결합할
|
|
535
|
-
* @returns 결합된 WHERE
|
|
536
|
-
*
|
|
537
|
-
* @example
|
|
538
|
-
* ```typescript
|
|
539
|
-
* db.user().where((u) => [
|
|
540
|
-
* expr.or([
|
|
541
|
-
* expr.eq(u.status, "active"),
|
|
542
|
-
* expr.eq(u.status, "pending"),
|
|
543
|
-
* ]),
|
|
544
|
-
* ])
|
|
545
|
-
* // WHERE (status <=> 'active' OR status <=> 'pending')
|
|
546
|
-
* ```
|
|
547
|
-
*/
|
|
548
|
-
or(conditions: WhereExprUnit[]): WhereExprUnit {
|
|
549
|
-
if (conditions.length === 0) {
|
|
550
|
-
throw new ArgumentError({ conditions: "
|
|
551
|
-
}
|
|
552
|
-
return new WhereExprUnit({
|
|
553
|
-
type: "or",
|
|
554
|
-
conditions: conditions.map((c) => c.expr),
|
|
555
|
-
});
|
|
556
|
-
},
|
|
557
|
-
|
|
558
|
-
//#endregion
|
|
559
|
-
|
|
560
|
-
//#region ========== SELECT - 문자열 ==========
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* 문자열 연결 (CONCAT)
|
|
564
|
-
*
|
|
565
|
-
* NULL 값은 빈 문자열로
|
|
566
|
-
*
|
|
567
|
-
* @param args - 연결할 문자열들
|
|
568
|
-
* @returns 연결된 문자열
|
|
569
|
-
*
|
|
570
|
-
* @example
|
|
571
|
-
* ```typescript
|
|
572
|
-
* db.user().select((u) => ({
|
|
573
|
-
* fullName: expr.concat(u.firstName, " ", u.lastName),
|
|
574
|
-
* }))
|
|
575
|
-
* // SELECT CONCAT(firstName, ' ', lastName) AS fullName
|
|
576
|
-
* ```
|
|
577
|
-
*/
|
|
578
|
-
concat(...args: ExprInput<string | undefined>[]): ExprUnit<string> {
|
|
579
|
-
return new ExprUnit("string", {
|
|
580
|
-
type: "concat",
|
|
581
|
-
args: args.map((arg) => toExpr(arg)),
|
|
582
|
-
});
|
|
583
|
-
},
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* 문자열 왼쪽에서 지정 길이만큼 추출 (LEFT)
|
|
587
|
-
*
|
|
588
|
-
* @param source -
|
|
589
|
-
* @param length - 추출할 문자 수
|
|
590
|
-
* @returns 추출된 문자열
|
|
591
|
-
*
|
|
592
|
-
* @example
|
|
593
|
-
* ```typescript
|
|
594
|
-
* db.user().select((u) => ({
|
|
595
|
-
* initial: expr.left(u.name, 1),
|
|
596
|
-
* }))
|
|
597
|
-
* // SELECT LEFT(name, 1) AS initial
|
|
598
|
-
* ```
|
|
599
|
-
*/
|
|
600
|
-
left<T extends string | undefined>(source: ExprUnit<T>, length: ExprInput<number>): ExprUnit<T> {
|
|
601
|
-
return new ExprUnit("string", {
|
|
602
|
-
type: "left",
|
|
603
|
-
source: toExpr(source),
|
|
604
|
-
length: toExpr(length),
|
|
605
|
-
});
|
|
606
|
-
},
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* 문자열 오른쪽에서 지정 길이만큼 추출 (RIGHT)
|
|
610
|
-
*
|
|
611
|
-
* @param source -
|
|
612
|
-
* @param length - 추출할 문자 수
|
|
613
|
-
* @returns 추출된 문자열
|
|
614
|
-
*
|
|
615
|
-
* @example
|
|
616
|
-
* ```typescript
|
|
617
|
-
* db.phone().select((p) => ({
|
|
618
|
-
* lastFour: expr.right(p.number, 4),
|
|
619
|
-
* }))
|
|
620
|
-
* // SELECT RIGHT(number, 4) AS lastFour
|
|
621
|
-
* ```
|
|
622
|
-
*/
|
|
623
|
-
right<T extends string | undefined>(source: ExprUnit<T>, length: ExprInput<number>): ExprUnit<T> {
|
|
624
|
-
return new ExprUnit("string", {
|
|
625
|
-
type: "right",
|
|
626
|
-
source: toExpr(source),
|
|
627
|
-
length: toExpr(length),
|
|
628
|
-
});
|
|
629
|
-
},
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* 문자열 양쪽 공백
|
|
633
|
-
*
|
|
634
|
-
* @param source -
|
|
635
|
-
* @returns 공백이 제거된 문자열
|
|
636
|
-
*
|
|
637
|
-
* @example
|
|
638
|
-
* ```typescript
|
|
639
|
-
* db.user().select((u) => ({
|
|
640
|
-
* name: expr.trim(u.name),
|
|
641
|
-
* }))
|
|
642
|
-
* // SELECT TRIM(name) AS name
|
|
643
|
-
* ```
|
|
644
|
-
*/
|
|
645
|
-
trim<T extends string | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
646
|
-
return new ExprUnit("string", {
|
|
647
|
-
type: "trim",
|
|
648
|
-
arg: toExpr(source),
|
|
649
|
-
});
|
|
650
|
-
},
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* 문자열 왼쪽 패딩 (LPAD)
|
|
654
|
-
*
|
|
655
|
-
* 지정 길이가 될 때까지 왼쪽에 fillString
|
|
656
|
-
*
|
|
657
|
-
* @param source -
|
|
658
|
-
* @param length - 목표 길이
|
|
659
|
-
* @param fillString - 패딩에 사용할 문자열
|
|
660
|
-
* @returns 패딩된 문자열
|
|
661
|
-
*
|
|
662
|
-
* @example
|
|
663
|
-
* ```typescript
|
|
664
|
-
* db.order().select((o) => ({
|
|
665
|
-
* orderNo: expr.padStart(expr.cast(o.id, { type: "varchar", length: 10 }), 8, "0"),
|
|
666
|
-
* }))
|
|
667
|
-
* // SELECT LPAD(CAST(id AS VARCHAR(10)), 8, '0') AS orderNo
|
|
668
|
-
* //
|
|
669
|
-
* ```
|
|
670
|
-
*/
|
|
671
|
-
padStart<T extends string | undefined>(
|
|
672
|
-
source: ExprUnit<T>,
|
|
673
|
-
length: ExprInput<number>,
|
|
674
|
-
fillString: ExprInput<string>,
|
|
675
|
-
): ExprUnit<T> {
|
|
676
|
-
return new ExprUnit("string", {
|
|
677
|
-
type: "padStart",
|
|
678
|
-
source: toExpr(source),
|
|
679
|
-
length: toExpr(length),
|
|
680
|
-
fillString: toExpr(fillString),
|
|
681
|
-
});
|
|
682
|
-
},
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* 문자열 치환 (REPLACE)
|
|
686
|
-
*
|
|
687
|
-
* @param source -
|
|
688
|
-
* @param from - 찾을 문자열
|
|
689
|
-
* @param to - 대체할 문자열
|
|
690
|
-
* @returns 치환된 문자열
|
|
691
|
-
*
|
|
692
|
-
* @example
|
|
693
|
-
* ```typescript
|
|
694
|
-
* db.user().select((u) => ({
|
|
695
|
-
* phone: expr.replace(u.phone, "-", ""),
|
|
696
|
-
* }))
|
|
697
|
-
* // SELECT REPLACE(phone, '-', '') AS phone
|
|
698
|
-
* ```
|
|
699
|
-
*/
|
|
700
|
-
replace<T extends string | undefined>(
|
|
701
|
-
source: ExprUnit<T>,
|
|
702
|
-
from: ExprInput<string>,
|
|
703
|
-
to: ExprInput<string>,
|
|
704
|
-
): ExprUnit<T> {
|
|
705
|
-
return new ExprUnit("string", {
|
|
706
|
-
type: "replace",
|
|
707
|
-
source: toExpr(source),
|
|
708
|
-
from: toExpr(from),
|
|
709
|
-
to: toExpr(to),
|
|
710
|
-
});
|
|
711
|
-
},
|
|
712
|
-
|
|
713
|
-
/**
|
|
714
|
-
* 문자열 대문자
|
|
715
|
-
*
|
|
716
|
-
* @param source -
|
|
717
|
-
* @returns 대문자로
|
|
718
|
-
*
|
|
719
|
-
* @example
|
|
720
|
-
* ```typescript
|
|
721
|
-
* db.user().select((u) => ({
|
|
722
|
-
* code: expr.upper(u.code),
|
|
723
|
-
* }))
|
|
724
|
-
* // SELECT UPPER(code) AS code
|
|
725
|
-
* ```
|
|
726
|
-
*/
|
|
727
|
-
upper<T extends string | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
728
|
-
return new ExprUnit("string", {
|
|
729
|
-
type: "upper",
|
|
730
|
-
arg: toExpr(source),
|
|
731
|
-
});
|
|
732
|
-
},
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* 문자열 소문자
|
|
736
|
-
*
|
|
737
|
-
* @param source -
|
|
738
|
-
* @returns 소문자로
|
|
739
|
-
*
|
|
740
|
-
* @example
|
|
741
|
-
* ```typescript
|
|
742
|
-
* db.user().select((u) => ({
|
|
743
|
-
* email: expr.lower(u.email),
|
|
744
|
-
* }))
|
|
745
|
-
* // SELECT LOWER(email) AS email
|
|
746
|
-
* ```
|
|
747
|
-
*/
|
|
748
|
-
lower<T extends string | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
749
|
-
return new ExprUnit("string", {
|
|
750
|
-
type: "lower",
|
|
751
|
-
arg: toExpr(source),
|
|
752
|
-
});
|
|
753
|
-
},
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* 문자열 길이 (문자 수)
|
|
757
|
-
*
|
|
758
|
-
* @param source -
|
|
759
|
-
* @returns 문자 수
|
|
760
|
-
*
|
|
761
|
-
* @example
|
|
762
|
-
* ```typescript
|
|
763
|
-
* db.user().select((u) => ({
|
|
764
|
-
* nameLength: expr.length(u.name),
|
|
765
|
-
* }))
|
|
766
|
-
* // SELECT CHAR_LENGTH(name) AS nameLength
|
|
767
|
-
* ```
|
|
768
|
-
*/
|
|
769
|
-
length(source: ExprUnit<string | undefined>): ExprUnit<number> {
|
|
770
|
-
return new ExprUnit("number", {
|
|
771
|
-
type: "length",
|
|
772
|
-
arg: toExpr(source),
|
|
773
|
-
});
|
|
774
|
-
},
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* 문자열 바이트 길이
|
|
778
|
-
*
|
|
779
|
-
* UTF-8 환경에서 한글은 3바이트
|
|
780
|
-
*
|
|
781
|
-
* @param source -
|
|
782
|
-
* @returns 바이트 수
|
|
783
|
-
*
|
|
784
|
-
* @example
|
|
785
|
-
* ```typescript
|
|
786
|
-
* db.user().select((u) => ({
|
|
787
|
-
* byteLen: expr.byteLength(u.name),
|
|
788
|
-
* }))
|
|
789
|
-
* // SELECT OCTET_LENGTH(name) AS byteLen
|
|
790
|
-
* ```
|
|
791
|
-
*/
|
|
792
|
-
byteLength(source: ExprUnit<string | undefined>): ExprUnit<number> {
|
|
793
|
-
return new ExprUnit("number", {
|
|
794
|
-
type: "byteLength",
|
|
795
|
-
arg: toExpr(source),
|
|
796
|
-
});
|
|
797
|
-
},
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* 문자열 일부 추출 (SUBSTRING)
|
|
801
|
-
*
|
|
802
|
-
* SQL 표준에 따라 1-based index 사용
|
|
803
|
-
*
|
|
804
|
-
* @param source -
|
|
805
|
-
* @param start -
|
|
806
|
-
* @param length - 추출할 길이 (생략 시 끝까지)
|
|
807
|
-
* @returns 추출된 문자열
|
|
808
|
-
*
|
|
809
|
-
* @example
|
|
810
|
-
* ```typescript
|
|
811
|
-
* db.user().select((u) => ({
|
|
812
|
-
* // "Hello World"에서
|
|
813
|
-
* prefix: expr.substring(u.name, 1, 5),
|
|
814
|
-
* }))
|
|
815
|
-
* // SELECT SUBSTRING(name, 1, 5) AS prefix
|
|
816
|
-
* ```
|
|
817
|
-
*/
|
|
818
|
-
substring<T extends string | undefined>(
|
|
819
|
-
source: ExprUnit<T>,
|
|
820
|
-
start: ExprInput<number>,
|
|
821
|
-
length?: ExprInput<number>,
|
|
822
|
-
): ExprUnit<T> {
|
|
823
|
-
return new ExprUnit("string", {
|
|
824
|
-
type: "substring",
|
|
825
|
-
source: toExpr(source),
|
|
826
|
-
start: toExpr(start),
|
|
827
|
-
...(length != null ? { length: toExpr(length) } : {}),
|
|
828
|
-
});
|
|
829
|
-
},
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* 문자열 내 위치 찾기 (LOCATE/CHARINDEX)
|
|
833
|
-
*
|
|
834
|
-
* 1-based index
|
|
835
|
-
*
|
|
836
|
-
* @param source - 검색할 문자열
|
|
837
|
-
* @param search - 찾을 문자열
|
|
838
|
-
* @returns 위치 (1부터
|
|
839
|
-
*
|
|
840
|
-
* @example
|
|
841
|
-
* ```typescript
|
|
842
|
-
* db.user().select((u) => ({
|
|
843
|
-
* atPos: expr.indexOf(u.email, "@"),
|
|
844
|
-
* }))
|
|
845
|
-
* // SELECT LOCATE('@', email) AS atPos (MySQL)
|
|
846
|
-
* // "john@example.com" → 5
|
|
847
|
-
* ```
|
|
848
|
-
*/
|
|
849
|
-
indexOf(source: ExprUnit<string | undefined>, search: ExprInput<string>): ExprUnit<number> {
|
|
850
|
-
return new ExprUnit("number", {
|
|
851
|
-
type: "indexOf",
|
|
852
|
-
source: toExpr(source),
|
|
853
|
-
search: toExpr(search),
|
|
854
|
-
});
|
|
855
|
-
},
|
|
856
|
-
|
|
857
|
-
//#endregion
|
|
858
|
-
|
|
859
|
-
//#region ========== SELECT -
|
|
860
|
-
|
|
861
|
-
/**
|
|
862
|
-
* 절대값 (ABS)
|
|
863
|
-
*
|
|
864
|
-
* @param source - 원본
|
|
865
|
-
* @returns 절대값
|
|
866
|
-
*
|
|
867
|
-
* @example
|
|
868
|
-
* ```typescript
|
|
869
|
-
* db.account().select((a) => ({
|
|
870
|
-
* balance: expr.abs(a.balance),
|
|
871
|
-
* }))
|
|
872
|
-
* // SELECT ABS(balance) AS balance
|
|
873
|
-
* ```
|
|
874
|
-
*/
|
|
875
|
-
abs<T extends number | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
876
|
-
return new ExprUnit("number", {
|
|
877
|
-
type: "abs",
|
|
878
|
-
arg: toExpr(source),
|
|
879
|
-
});
|
|
880
|
-
},
|
|
881
|
-
|
|
882
|
-
/**
|
|
883
|
-
* 반올림 (ROUND)
|
|
884
|
-
*
|
|
885
|
-
* @param source - 원본
|
|
886
|
-
* @param digits - 소수점 이하 자릿수
|
|
887
|
-
* @returns 반올림된
|
|
888
|
-
*
|
|
889
|
-
* @example
|
|
890
|
-
* ```typescript
|
|
891
|
-
* db.product().select((p) => ({
|
|
892
|
-
* price: expr.round(p.price, 2),
|
|
893
|
-
* }))
|
|
894
|
-
* // SELECT ROUND(price, 2) AS price
|
|
895
|
-
* // 123.456 → 123.46
|
|
896
|
-
* ```
|
|
897
|
-
*/
|
|
898
|
-
round<T extends number | undefined>(source: ExprUnit<T>, digits: number): ExprUnit<T> {
|
|
899
|
-
return new ExprUnit("number", {
|
|
900
|
-
type: "round",
|
|
901
|
-
arg: toExpr(source),
|
|
902
|
-
digits,
|
|
903
|
-
});
|
|
904
|
-
},
|
|
905
|
-
|
|
906
|
-
/**
|
|
907
|
-
* 올림 (CEILING)
|
|
908
|
-
*
|
|
909
|
-
* @param source - 원본
|
|
910
|
-
* @returns 올림된
|
|
911
|
-
*
|
|
912
|
-
* @example
|
|
913
|
-
* ```typescript
|
|
914
|
-
* db.order().select((o) => ({
|
|
915
|
-
* pages: expr.ceil(expr.divide(o.itemCount, 10)),
|
|
916
|
-
* }))
|
|
917
|
-
* // SELECT CEILING(itemCount / 10) AS pages
|
|
918
|
-
* // 25 / 10 = 2.5 → 3
|
|
919
|
-
* ```
|
|
920
|
-
*/
|
|
921
|
-
ceil<T extends number | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
922
|
-
return new ExprUnit("number", {
|
|
923
|
-
type: "ceil",
|
|
924
|
-
arg: toExpr(source),
|
|
925
|
-
});
|
|
926
|
-
},
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* 버림 (FLOOR)
|
|
930
|
-
*
|
|
931
|
-
* @param source - 원본
|
|
932
|
-
* @returns 버림된
|
|
933
|
-
*
|
|
934
|
-
* @example
|
|
935
|
-
* ```typescript
|
|
936
|
-
* db.user().select((u) => ({
|
|
937
|
-
* ageGroup: expr.floor(expr.divide(u.age, 10)),
|
|
938
|
-
* }))
|
|
939
|
-
* // SELECT FLOOR(age / 10) AS ageGroup
|
|
940
|
-
* // 25 / 10 = 2.5 → 2
|
|
941
|
-
* ```
|
|
942
|
-
*/
|
|
943
|
-
floor<T extends number | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
944
|
-
return new ExprUnit("number", {
|
|
945
|
-
type: "floor",
|
|
946
|
-
arg: toExpr(source),
|
|
947
|
-
});
|
|
948
|
-
},
|
|
949
|
-
|
|
950
|
-
//#endregion
|
|
951
|
-
|
|
952
|
-
//#region ========== SELECT -
|
|
953
|
-
|
|
954
|
-
/**
|
|
955
|
-
* 연도 추출 (YEAR)
|
|
956
|
-
*
|
|
957
|
-
* @param source - DateTime 또는 DateOnly
|
|
958
|
-
* @returns 연도 (4자리
|
|
959
|
-
*
|
|
960
|
-
* @example
|
|
961
|
-
* ```typescript
|
|
962
|
-
* db.user().select((u) => ({
|
|
963
|
-
* birthYear: expr.year(u.birthDate),
|
|
964
|
-
* }))
|
|
965
|
-
* // SELECT YEAR(birthDate) AS birthYear
|
|
966
|
-
* ```
|
|
967
|
-
*/
|
|
968
|
-
year<T extends DateTime | DateOnly | undefined>(
|
|
969
|
-
source: ExprUnit<T>,
|
|
970
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
971
|
-
return new ExprUnit("number", {
|
|
972
|
-
type: "year",
|
|
973
|
-
arg: toExpr(source),
|
|
974
|
-
});
|
|
975
|
-
},
|
|
976
|
-
|
|
977
|
-
/**
|
|
978
|
-
* 월 추출 (MONTH)
|
|
979
|
-
*
|
|
980
|
-
* @param source - DateTime 또는 DateOnly
|
|
981
|
-
* @returns 월 (1~12)
|
|
982
|
-
*
|
|
983
|
-
* @example
|
|
984
|
-
* ```typescript
|
|
985
|
-
* db.order().select((o) => ({
|
|
986
|
-
* orderMonth: expr.month(o.createdAt),
|
|
987
|
-
* }))
|
|
988
|
-
* // SELECT MONTH(createdAt) AS orderMonth
|
|
989
|
-
* ```
|
|
990
|
-
*/
|
|
991
|
-
month<T extends DateTime | DateOnly | undefined>(
|
|
992
|
-
source: ExprUnit<T>,
|
|
993
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
994
|
-
return new ExprUnit("number", {
|
|
995
|
-
type: "month",
|
|
996
|
-
arg: toExpr(source),
|
|
997
|
-
});
|
|
998
|
-
},
|
|
999
|
-
|
|
1000
|
-
/**
|
|
1001
|
-
* 일 추출 (DAY)
|
|
1002
|
-
*
|
|
1003
|
-
* @param source - DateTime 또는 DateOnly
|
|
1004
|
-
* @returns 일 (1~31)
|
|
1005
|
-
*
|
|
1006
|
-
* @example
|
|
1007
|
-
* ```typescript
|
|
1008
|
-
* db.user().select((u) => ({
|
|
1009
|
-
* birthDay: expr.day(u.birthDate),
|
|
1010
|
-
* }))
|
|
1011
|
-
* // SELECT DAY(birthDate) AS birthDay
|
|
1012
|
-
* ```
|
|
1013
|
-
*/
|
|
1014
|
-
day<T extends DateTime | DateOnly | undefined>(
|
|
1015
|
-
source: ExprUnit<T>,
|
|
1016
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1017
|
-
return new ExprUnit("number", {
|
|
1018
|
-
type: "day",
|
|
1019
|
-
arg: toExpr(source),
|
|
1020
|
-
});
|
|
1021
|
-
},
|
|
1022
|
-
|
|
1023
|
-
/**
|
|
1024
|
-
* 시 추출 (HOUR)
|
|
1025
|
-
*
|
|
1026
|
-
* @param source - DateTime 또는 Time
|
|
1027
|
-
* @returns 시 (0~23)
|
|
1028
|
-
*
|
|
1029
|
-
* @example
|
|
1030
|
-
* ```typescript
|
|
1031
|
-
* db.log().select((l) => ({
|
|
1032
|
-
* logHour: expr.hour(l.createdAt),
|
|
1033
|
-
* }))
|
|
1034
|
-
* // SELECT HOUR(createdAt) AS logHour
|
|
1035
|
-
* ```
|
|
1036
|
-
*/
|
|
1037
|
-
hour<T extends DateTime | Time | undefined>(
|
|
1038
|
-
source: ExprUnit<T>,
|
|
1039
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1040
|
-
return new ExprUnit("number", {
|
|
1041
|
-
type: "hour",
|
|
1042
|
-
arg: toExpr(source),
|
|
1043
|
-
});
|
|
1044
|
-
},
|
|
1045
|
-
|
|
1046
|
-
/**
|
|
1047
|
-
* 분 추출 (MINUTE)
|
|
1048
|
-
*
|
|
1049
|
-
* @param source - DateTime 또는 Time
|
|
1050
|
-
* @returns 분 (0~59)
|
|
1051
|
-
*
|
|
1052
|
-
* @example
|
|
1053
|
-
* ```typescript
|
|
1054
|
-
* db.log().select((l) => ({
|
|
1055
|
-
* logMinute: expr.minute(l.createdAt),
|
|
1056
|
-
* }))
|
|
1057
|
-
* // SELECT MINUTE(createdAt) AS logMinute
|
|
1058
|
-
* ```
|
|
1059
|
-
*/
|
|
1060
|
-
minute<T extends DateTime | Time | undefined>(
|
|
1061
|
-
source: ExprUnit<T>,
|
|
1062
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1063
|
-
return new ExprUnit("number", {
|
|
1064
|
-
type: "minute",
|
|
1065
|
-
arg: toExpr(source),
|
|
1066
|
-
});
|
|
1067
|
-
},
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* 초 추출 (SECOND)
|
|
1071
|
-
*
|
|
1072
|
-
* @param source - DateTime 또는 Time
|
|
1073
|
-
* @returns 초 (0~59)
|
|
1074
|
-
*
|
|
1075
|
-
* @example
|
|
1076
|
-
* ```typescript
|
|
1077
|
-
* db.log().select((l) => ({
|
|
1078
|
-
* logSecond: expr.second(l.createdAt),
|
|
1079
|
-
* }))
|
|
1080
|
-
* // SELECT SECOND(createdAt) AS logSecond
|
|
1081
|
-
* ```
|
|
1082
|
-
*/
|
|
1083
|
-
second<T extends DateTime | Time | undefined>(
|
|
1084
|
-
source: ExprUnit<T>,
|
|
1085
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1086
|
-
return new ExprUnit("number", {
|
|
1087
|
-
type: "second",
|
|
1088
|
-
arg: toExpr(source),
|
|
1089
|
-
});
|
|
1090
|
-
},
|
|
1091
|
-
|
|
1092
|
-
/**
|
|
1093
|
-
* ISO 주차 추출
|
|
1094
|
-
*
|
|
1095
|
-
* ISO 8601 기준 주차 (월요일
|
|
1096
|
-
*
|
|
1097
|
-
* @param source - DateOnly
|
|
1098
|
-
* @returns ISO 주차 번호
|
|
1099
|
-
*
|
|
1100
|
-
* @example
|
|
1101
|
-
* ```typescript
|
|
1102
|
-
* db.order().select((o) => ({
|
|
1103
|
-
* weekNum: expr.isoWeek(o.orderDate),
|
|
1104
|
-
* }))
|
|
1105
|
-
* // SELECT WEEK(orderDate, 3) AS weekNum (MySQL)
|
|
1106
|
-
* ```
|
|
1107
|
-
*/
|
|
1108
|
-
isoWeek<T extends DateOnly | undefined>(
|
|
1109
|
-
source: ExprUnit<T>,
|
|
1110
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1111
|
-
return new ExprUnit("number", {
|
|
1112
|
-
type: "isoWeek",
|
|
1113
|
-
arg: toExpr(source),
|
|
1114
|
-
});
|
|
1115
|
-
},
|
|
1116
|
-
|
|
1117
|
-
/**
|
|
1118
|
-
* ISO 주 시작일 (월요일)
|
|
1119
|
-
*
|
|
1120
|
-
* 해당
|
|
1121
|
-
*
|
|
1122
|
-
* @param source - DateOnly
|
|
1123
|
-
* @returns 주의
|
|
1124
|
-
*
|
|
1125
|
-
* @example
|
|
1126
|
-
* ```typescript
|
|
1127
|
-
* db.order().select((o) => ({
|
|
1128
|
-
* weekStart: expr.isoWeekStartDate(o.orderDate),
|
|
1129
|
-
* }))
|
|
1130
|
-
* // 2024-01-10 (수) → 2024-01-08 (월)
|
|
1131
|
-
* ```
|
|
1132
|
-
*/
|
|
1133
|
-
isoWeekStartDate<T extends DateOnly | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
1134
|
-
return new ExprUnit("DateOnly", {
|
|
1135
|
-
type: "isoWeekStartDate",
|
|
1136
|
-
arg: toExpr(source),
|
|
1137
|
-
});
|
|
1138
|
-
},
|
|
1139
|
-
|
|
1140
|
-
/**
|
|
1141
|
-
* ISO 연월 (해당 월의 1일)
|
|
1142
|
-
*
|
|
1143
|
-
* 해당
|
|
1144
|
-
*
|
|
1145
|
-
* @param source - DateOnly
|
|
1146
|
-
* @returns 월의 첫째 날
|
|
1147
|
-
*
|
|
1148
|
-
* @example
|
|
1149
|
-
* ```typescript
|
|
1150
|
-
* db.order().select((o) => ({
|
|
1151
|
-
* yearMonth: expr.isoYearMonth(o.orderDate),
|
|
1152
|
-
* }))
|
|
1153
|
-
* // 2024-01-15 → 2024-01-01
|
|
1154
|
-
* ```
|
|
1155
|
-
*/
|
|
1156
|
-
isoYearMonth<T extends DateOnly | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
1157
|
-
return new ExprUnit("DateOnly", {
|
|
1158
|
-
type: "isoYearMonth",
|
|
1159
|
-
arg: toExpr(source),
|
|
1160
|
-
});
|
|
1161
|
-
},
|
|
1162
|
-
|
|
1163
|
-
/**
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1166
|
-
* @param separator - 단위 ("year", "month", "day", "hour", "minute", "second")
|
|
1167
|
-
* @param from -
|
|
1168
|
-
* @param to - 끝
|
|
1169
|
-
* @returns 차이
|
|
1170
|
-
*
|
|
1171
|
-
* @example
|
|
1172
|
-
* ```typescript
|
|
1173
|
-
* db.user().select((u) => ({
|
|
1174
|
-
* age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
|
|
1175
|
-
* }))
|
|
1176
|
-
* // SELECT DATEDIFF(year, birthDate, '2024-01-15') AS age
|
|
1177
|
-
* ```
|
|
1178
|
-
*/
|
|
1179
|
-
dateDiff<T extends DateTime | DateOnly | Time | undefined>(
|
|
1180
|
-
separator: DateSeparator,
|
|
1181
|
-
from: ExprInput<T>,
|
|
1182
|
-
to: ExprInput<T>,
|
|
1183
|
-
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1184
|
-
return new ExprUnit("number", {
|
|
1185
|
-
type: "dateDiff",
|
|
1186
|
-
separator,
|
|
1187
|
-
from: toExpr(from),
|
|
1188
|
-
to: toExpr(to),
|
|
1189
|
-
});
|
|
1190
|
-
},
|
|
1191
|
-
|
|
1192
|
-
/**
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1195
|
-
* @param separator - 단위 ("year", "month", "day", "hour", "minute", "second")
|
|
1196
|
-
* @param source - 원본
|
|
1197
|
-
* @param value - 더할
|
|
1198
|
-
* @returns 계산된
|
|
1199
|
-
*
|
|
1200
|
-
* @example
|
|
1201
|
-
* ```typescript
|
|
1202
|
-
* db.subscription().select((s) => ({
|
|
1203
|
-
* expiresAt: expr.dateAdd("month", s.startDate, 12),
|
|
1204
|
-
* }))
|
|
1205
|
-
* // SELECT DATEADD(month, 12, startDate) AS expiresAt
|
|
1206
|
-
* ```
|
|
1207
|
-
*/
|
|
1208
|
-
dateAdd<T extends DateTime | DateOnly | Time | undefined>(
|
|
1209
|
-
separator: DateSeparator,
|
|
1210
|
-
source: ExprUnit<T>,
|
|
1211
|
-
value: ExprInput<number>,
|
|
1212
|
-
): ExprUnit<T> {
|
|
1213
|
-
return new ExprUnit(source.dataType, {
|
|
1214
|
-
type: "dateAdd",
|
|
1215
|
-
separator,
|
|
1216
|
-
source: toExpr(source),
|
|
1217
|
-
value: toExpr(value),
|
|
1218
|
-
});
|
|
1219
|
-
},
|
|
1220
|
-
|
|
1221
|
-
/**
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
1224
|
-
* DBMS별로 포맷 문자열 규칙이 다를 수 있음
|
|
1225
|
-
*
|
|
1226
|
-
* @param source -
|
|
1227
|
-
* @param format - 포맷 문자열 (예: "%Y-%m-%d")
|
|
1228
|
-
* @returns 포맷된 문자열
|
|
1229
|
-
*
|
|
1230
|
-
* @example
|
|
1231
|
-
* ```typescript
|
|
1232
|
-
* db.order().select((o) => ({
|
|
1233
|
-
* orderDate: expr.formatDate(o.createdAt, "%Y-%m-%d"),
|
|
1234
|
-
* }))
|
|
1235
|
-
* // SELECT DATE_FORMAT(createdAt, '%Y-%m-%d') AS orderDate (MySQL)
|
|
1236
|
-
* // 2024-01-15 10:30:00 → "2024-01-15"
|
|
1237
|
-
* ```
|
|
1238
|
-
*/
|
|
1239
|
-
formatDate<T extends DateTime | DateOnly | Time | undefined>(
|
|
1240
|
-
source: ExprUnit<T>,
|
|
1241
|
-
format: string,
|
|
1242
|
-
): ExprUnit<T extends undefined ? undefined : string> {
|
|
1243
|
-
return new ExprUnit("string", {
|
|
1244
|
-
type: "formatDate",
|
|
1245
|
-
source: toExpr(source),
|
|
1246
|
-
format,
|
|
1247
|
-
});
|
|
1248
|
-
},
|
|
1249
|
-
|
|
1250
|
-
//#endregion
|
|
1251
|
-
|
|
1252
|
-
//#region ========== SELECT -
|
|
1253
|
-
|
|
1254
|
-
/**
|
|
1255
|
-
* NULL 대체 (COALESCE/IFNULL)
|
|
1256
|
-
*
|
|
1257
|
-
* 첫 번째 non-null 값을
|
|
1258
|
-
*
|
|
1259
|
-
* @param args -
|
|
1260
|
-
* @returns 첫 번째 non-null
|
|
1261
|
-
*
|
|
1262
|
-
* @example
|
|
1263
|
-
* ```typescript
|
|
1264
|
-
* db.user().select((u) => ({
|
|
1265
|
-
* displayName: expr.ifNull(u.nickname, u.name, "Guest"),
|
|
1266
|
-
* }))
|
|
1267
|
-
* // SELECT COALESCE(nickname, name, 'Guest') AS displayName
|
|
1268
|
-
* ```
|
|
1269
|
-
*/
|
|
1270
|
-
ifNull,
|
|
1271
|
-
|
|
1272
|
-
/**
|
|
1273
|
-
* 특정 값이면 NULL
|
|
1274
|
-
*
|
|
1275
|
-
* source === value 이면 NULL
|
|
1276
|
-
*
|
|
1277
|
-
* @param source - 원본
|
|
1278
|
-
* @param value - 비교할
|
|
1279
|
-
* @returns NULL 또는 원본
|
|
1280
|
-
*
|
|
1281
|
-
* @example
|
|
1282
|
-
* ```typescript
|
|
1283
|
-
* db.user().select((u) => ({
|
|
1284
|
-
* // 빈 문자열을 NULL로
|
|
1285
|
-
* bio: expr.nullIf(u.bio, ""),
|
|
1286
|
-
* }))
|
|
1287
|
-
* // SELECT NULLIF(bio, '') AS bio
|
|
1288
|
-
* ```
|
|
1289
|
-
*/
|
|
1290
|
-
nullIf<T extends ColumnPrimitive>(
|
|
1291
|
-
source: ExprUnit<T>,
|
|
1292
|
-
value: ExprInput<T>,
|
|
1293
|
-
): ExprUnit<T | undefined> {
|
|
1294
|
-
return new ExprUnit(source.dataType, {
|
|
1295
|
-
type: "nullIf",
|
|
1296
|
-
source: toExpr(source),
|
|
1297
|
-
value: toExpr(value),
|
|
1298
|
-
});
|
|
1299
|
-
},
|
|
1300
|
-
|
|
1301
|
-
/**
|
|
1302
|
-
* WHERE 표현식을 boolean으로
|
|
1303
|
-
*
|
|
1304
|
-
* SELECT 절에서
|
|
1305
|
-
*
|
|
1306
|
-
* @param condition -
|
|
1307
|
-
* @returns boolean
|
|
1308
|
-
*
|
|
1309
|
-
* @example
|
|
1310
|
-
* ```typescript
|
|
1311
|
-
* db.user().select((u) => ({
|
|
1312
|
-
* isActive: expr.is(expr.eq(u.status, "active")),
|
|
1313
|
-
* }))
|
|
1314
|
-
* // SELECT (status <=> 'active') AS isActive
|
|
1315
|
-
* ```
|
|
1316
|
-
*/
|
|
1317
|
-
is(condition: WhereExprUnit): ExprUnit<boolean> {
|
|
1318
|
-
return new ExprUnit("boolean", {
|
|
1319
|
-
type: "is",
|
|
1320
|
-
condition: condition.expr,
|
|
1321
|
-
});
|
|
1322
|
-
},
|
|
1323
|
-
|
|
1324
|
-
/**
|
|
1325
|
-
* CASE WHEN
|
|
1326
|
-
*
|
|
1327
|
-
* 체이닝 방식으로
|
|
1328
|
-
*
|
|
1329
|
-
* @returns SwitchExprBuilder
|
|
1330
|
-
*
|
|
1331
|
-
* @example
|
|
1332
|
-
* ```typescript
|
|
1333
|
-
* db.user().select((u) => ({
|
|
1334
|
-
* grade: expr.switch<string>()
|
|
1335
|
-
* .case(expr.gte(u.score, 90), "A")
|
|
1336
|
-
* .case(expr.gte(u.score, 80), "B")
|
|
1337
|
-
* .case(expr.gte(u.score, 70), "C")
|
|
1338
|
-
* .default("F"),
|
|
1339
|
-
* }))
|
|
1340
|
-
* // SELECT CASE WHEN score >= 90 THEN 'A' ... ELSE 'F' END AS grade
|
|
1341
|
-
* ```
|
|
1342
|
-
*/
|
|
1343
|
-
switch<T extends ColumnPrimitive>(): SwitchExprBuilder<T> {
|
|
1344
|
-
return createSwitchBuilder<T>();
|
|
1345
|
-
},
|
|
1346
|
-
|
|
1347
|
-
/**
|
|
1348
|
-
* 단순 IF
|
|
1349
|
-
*
|
|
1350
|
-
* @param condition -
|
|
1351
|
-
* @param then -
|
|
1352
|
-
* @param else_ -
|
|
1353
|
-
* @returns 조건부
|
|
1354
|
-
*
|
|
1355
|
-
* @example
|
|
1356
|
-
* ```typescript
|
|
1357
|
-
* db.user().select((u) => ({
|
|
1358
|
-
* type: expr.if(expr.gte(u.age, 18), "adult", "minor"),
|
|
1359
|
-
* }))
|
|
1360
|
-
* // SELECT IF(age >= 18, 'adult', 'minor') AS type
|
|
1361
|
-
* ```
|
|
1362
|
-
*/
|
|
1363
|
-
if<T extends ColumnPrimitive>(
|
|
1364
|
-
condition: WhereExprUnit,
|
|
1365
|
-
then: ExprInput<T>,
|
|
1366
|
-
else_: ExprInput<T>,
|
|
1367
|
-
): ExprUnit<T> {
|
|
1368
|
-
const allValues = [then, else_];
|
|
1369
|
-
// 1. ExprUnit에서 dataType 찾기
|
|
1370
|
-
const exprUnit = allValues.find((v): v is ExprUnit<T> => v instanceof ExprUnit);
|
|
1371
|
-
if (exprUnit) {
|
|
1372
|
-
return new ExprUnit(exprUnit.dataType, {
|
|
1373
|
-
type: "if",
|
|
1374
|
-
condition: condition.expr,
|
|
1375
|
-
then: toExpr(then),
|
|
1376
|
-
else: toExpr(else_),
|
|
1377
|
-
});
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
// 2. non-null 리터럴에서 추론
|
|
1381
|
-
const nonNullLiteral = allValues.find((v) => v != null) as ColumnPrimitive;
|
|
1382
|
-
if (nonNullLiteral == null) {
|
|
1383
|
-
throw new Error("if
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
return new ExprUnit(inferColumnPrimitiveStr(nonNullLiteral), {
|
|
1387
|
-
type: "if",
|
|
1388
|
-
condition: condition.expr,
|
|
1389
|
-
then: toExpr(then),
|
|
1390
|
-
else: toExpr(else_),
|
|
1391
|
-
});
|
|
1392
|
-
},
|
|
1393
|
-
|
|
1394
|
-
//#endregion
|
|
1395
|
-
|
|
1396
|
-
//#region ========== SELECT -
|
|
1397
|
-
// SUM, AVG, MAX등의 집계는 모든 값이 NULL이거나 행이 없을 때만 NULL
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
* @param arg - 카운트할
|
|
1403
|
-
* @param distinct - true면 중복
|
|
1404
|
-
* @returns
|
|
1405
|
-
*
|
|
1406
|
-
* @example
|
|
1407
|
-
* ```typescript
|
|
1408
|
-
* // 전체
|
|
1409
|
-
* db.user().select(() => ({ total: expr.count() }))
|
|
1410
|
-
*
|
|
1411
|
-
* // 중복
|
|
1412
|
-
* db.order().select((o) => ({
|
|
1413
|
-
* uniqueCustomers: expr.count(o.customerId, true),
|
|
1414
|
-
* }))
|
|
1415
|
-
* ```
|
|
1416
|
-
*/
|
|
1417
|
-
count(arg?: ExprUnit<ColumnPrimitive>, distinct?: boolean): ExprUnit<number> {
|
|
1418
|
-
return new ExprUnit("number", {
|
|
1419
|
-
type: "count",
|
|
1420
|
-
arg: arg != null ? toExpr(arg) : undefined,
|
|
1421
|
-
distinct,
|
|
1422
|
-
});
|
|
1423
|
-
},
|
|
1424
|
-
|
|
1425
|
-
/**
|
|
1426
|
-
* 합계 (SUM)
|
|
1427
|
-
*
|
|
1428
|
-
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL
|
|
1429
|
-
*
|
|
1430
|
-
* @param arg - 합계를 구할
|
|
1431
|
-
* @returns 합계 (또는 NULL)
|
|
1432
|
-
*
|
|
1433
|
-
* @example
|
|
1434
|
-
* ```typescript
|
|
1435
|
-
* db.order().groupBy((o) => o.userId).select((o) => ({
|
|
1436
|
-
* userId: o.userId,
|
|
1437
|
-
* totalAmount: expr.sum(o.amount),
|
|
1438
|
-
* }))
|
|
1439
|
-
* ```
|
|
1440
|
-
*/
|
|
1441
|
-
sum(arg: ExprUnit<number | undefined>): ExprUnit<number | undefined> {
|
|
1442
|
-
return new ExprUnit("number", {
|
|
1443
|
-
type: "sum",
|
|
1444
|
-
arg: toExpr(arg),
|
|
1445
|
-
});
|
|
1446
|
-
},
|
|
1447
|
-
|
|
1448
|
-
/**
|
|
1449
|
-
* 평균 (AVG)
|
|
1450
|
-
*
|
|
1451
|
-
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL
|
|
1452
|
-
*
|
|
1453
|
-
* @param arg - 평균을 구할
|
|
1454
|
-
* @returns 평균 (또는 NULL)
|
|
1455
|
-
*
|
|
1456
|
-
* @example
|
|
1457
|
-
* ```typescript
|
|
1458
|
-
* db.product().groupBy((p) => p.categoryId).select((p) => ({
|
|
1459
|
-
* categoryId: p.categoryId,
|
|
1460
|
-
* avgPrice: expr.avg(p.price),
|
|
1461
|
-
* }))
|
|
1462
|
-
* ```
|
|
1463
|
-
*/
|
|
1464
|
-
avg(arg: ExprUnit<number | undefined>): ExprUnit<number | undefined> {
|
|
1465
|
-
return new ExprUnit("number", {
|
|
1466
|
-
type: "avg",
|
|
1467
|
-
arg: toExpr(arg),
|
|
1468
|
-
});
|
|
1469
|
-
},
|
|
1470
|
-
|
|
1471
|
-
/**
|
|
1472
|
-
* 최대값 (MAX)
|
|
1473
|
-
*
|
|
1474
|
-
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL
|
|
1475
|
-
*
|
|
1476
|
-
* @param arg - 최대값을 구할
|
|
1477
|
-
* @returns 최대값 (또는 NULL)
|
|
1478
|
-
*
|
|
1479
|
-
* @example
|
|
1480
|
-
* ```typescript
|
|
1481
|
-
* db.order().groupBy((o) => o.userId).select((o) => ({
|
|
1482
|
-
* userId: o.userId,
|
|
1483
|
-
* lastOrderDate: expr.max(o.createdAt),
|
|
1484
|
-
* }))
|
|
1485
|
-
* ```
|
|
1486
|
-
*/
|
|
1487
|
-
max<T extends ColumnPrimitive>(arg: ExprUnit<T>): ExprUnit<T | undefined> {
|
|
1488
|
-
return new ExprUnit(arg.dataType, {
|
|
1489
|
-
type: "max",
|
|
1490
|
-
arg: toExpr(arg),
|
|
1491
|
-
});
|
|
1492
|
-
},
|
|
1493
|
-
|
|
1494
|
-
/**
|
|
1495
|
-
* 최소값 (MIN)
|
|
1496
|
-
*
|
|
1497
|
-
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL
|
|
1498
|
-
*
|
|
1499
|
-
* @param arg - 최소값을 구할
|
|
1500
|
-
* @returns 최소값 (또는 NULL)
|
|
1501
|
-
*
|
|
1502
|
-
* @example
|
|
1503
|
-
* ```typescript
|
|
1504
|
-
* db.product().groupBy((p) => p.categoryId).select((p) => ({
|
|
1505
|
-
* categoryId: p.categoryId,
|
|
1506
|
-
* minPrice: expr.min(p.price),
|
|
1507
|
-
* }))
|
|
1508
|
-
* ```
|
|
1509
|
-
*/
|
|
1510
|
-
min<T extends ColumnPrimitive>(arg: ExprUnit<T>): ExprUnit<T | undefined> {
|
|
1511
|
-
return new ExprUnit(arg.dataType, {
|
|
1512
|
-
type: "min",
|
|
1513
|
-
arg: toExpr(arg),
|
|
1514
|
-
});
|
|
1515
|
-
},
|
|
1516
|
-
|
|
1517
|
-
//#endregion
|
|
1518
|
-
|
|
1519
|
-
//#region ========== SELECT -
|
|
1520
|
-
|
|
1521
|
-
/**
|
|
1522
|
-
* 여러
|
|
1523
|
-
*
|
|
1524
|
-
* @param args - 비교할 값들
|
|
1525
|
-
* @returns 최대값
|
|
1526
|
-
*
|
|
1527
|
-
* @example
|
|
1528
|
-
* ```typescript
|
|
1529
|
-
* db.product().select((p) => ({
|
|
1530
|
-
* effectivePrice: expr.greatest(p.price, p.minPrice),
|
|
1531
|
-
* }))
|
|
1532
|
-
* // SELECT GREATEST(price, minPrice) AS effectivePrice
|
|
1533
|
-
* ```
|
|
1534
|
-
*/
|
|
1535
|
-
greatest<T extends ColumnPrimitive>(...args: ExprInput<T>[]): ExprUnit<T> {
|
|
1536
|
-
return new ExprUnit(findDataType(args), {
|
|
1537
|
-
type: "greatest",
|
|
1538
|
-
args: args.map((a) => toExpr(a)),
|
|
1539
|
-
});
|
|
1540
|
-
},
|
|
1541
|
-
|
|
1542
|
-
/**
|
|
1543
|
-
* 여러
|
|
1544
|
-
*
|
|
1545
|
-
* @param args - 비교할 값들
|
|
1546
|
-
* @returns 최소값
|
|
1547
|
-
*
|
|
1548
|
-
* @example
|
|
1549
|
-
* ```typescript
|
|
1550
|
-
* db.product().select((p) => ({
|
|
1551
|
-
* effectivePrice: expr.least(p.price, p.maxDiscount),
|
|
1552
|
-
* }))
|
|
1553
|
-
* // SELECT LEAST(price, maxDiscount) AS effectivePrice
|
|
1554
|
-
* ```
|
|
1555
|
-
*/
|
|
1556
|
-
least<T extends ColumnPrimitive>(...args: ExprInput<T>[]): ExprUnit<T> {
|
|
1557
|
-
return new ExprUnit(findDataType(args), {
|
|
1558
|
-
type: "least",
|
|
1559
|
-
args: args.map((a) => toExpr(a)),
|
|
1560
|
-
});
|
|
1561
|
-
},
|
|
1562
|
-
|
|
1563
|
-
/**
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
* @returns
|
|
1567
|
-
*
|
|
1568
|
-
* @example
|
|
1569
|
-
* ```typescript
|
|
1570
|
-
* db.user().select((u) => ({
|
|
1571
|
-
* rowNum: expr.rowNum(),
|
|
1572
|
-
* name: u.name,
|
|
1573
|
-
* }))
|
|
1574
|
-
* ```
|
|
1575
|
-
*/
|
|
1576
|
-
rowNum(): ExprUnit<number> {
|
|
1577
|
-
return new ExprUnit("number", {
|
|
1578
|
-
type: "rowNum",
|
|
1579
|
-
});
|
|
1580
|
-
},
|
|
1581
|
-
|
|
1582
|
-
/**
|
|
1583
|
-
* 난수
|
|
1584
|
-
*
|
|
1585
|
-
* 0~1 사이의 난수
|
|
1586
|
-
*
|
|
1587
|
-
* @returns 0~1 사이의 난수
|
|
1588
|
-
*
|
|
1589
|
-
* @example
|
|
1590
|
-
* ```typescript
|
|
1591
|
-
* // 랜덤
|
|
1592
|
-
* db.user().orderBy(() => expr.random()).limit(10)
|
|
1593
|
-
* ```
|
|
1594
|
-
*/
|
|
1595
|
-
random(): ExprUnit<number> {
|
|
1596
|
-
return new ExprUnit("number", {
|
|
1597
|
-
type: "random",
|
|
1598
|
-
});
|
|
1599
|
-
},
|
|
1600
|
-
|
|
1601
|
-
/**
|
|
1602
|
-
*
|
|
1603
|
-
*
|
|
1604
|
-
* @param source -
|
|
1605
|
-
* @param targetType - 대상
|
|
1606
|
-
* @returns
|
|
1607
|
-
*
|
|
1608
|
-
* @example
|
|
1609
|
-
* ```typescript
|
|
1610
|
-
* db.order().select((o) => ({
|
|
1611
|
-
* idStr: expr.cast(o.id, { type: "varchar", length: 20 }),
|
|
1612
|
-
* }))
|
|
1613
|
-
* // SELECT CAST(id AS VARCHAR(20)) AS idStr
|
|
1614
|
-
* ```
|
|
1615
|
-
*/
|
|
1616
|
-
cast<T extends ColumnPrimitive, TDataType extends DataType>(
|
|
1617
|
-
source: ExprUnit<T>,
|
|
1618
|
-
targetType: TDataType,
|
|
1619
|
-
): ExprUnit<T extends undefined ? undefined : InferColumnPrimitiveFromDataType<TDataType>> {
|
|
1620
|
-
return new ExprUnit(dataTypeStrToColumnPrimitiveStr[targetType.type], {
|
|
1621
|
-
type: "cast",
|
|
1622
|
-
source: toExpr(source),
|
|
1623
|
-
targetType,
|
|
1624
|
-
});
|
|
1625
|
-
},
|
|
1626
|
-
|
|
1627
|
-
/**
|
|
1628
|
-
* 스칼라
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
1632
|
-
* @param dataType -
|
|
1633
|
-
* @param queryable - 스칼라 값을 반환하는 Queryable
|
|
1634
|
-
* @returns
|
|
1635
|
-
*
|
|
1636
|
-
* @example
|
|
1637
|
-
* ```typescript
|
|
1638
|
-
* db.user().select((u) => ({
|
|
1639
|
-
* id: u.id,
|
|
1640
|
-
* postCount: expr.subquery(
|
|
1641
|
-
* "number",
|
|
1642
|
-
* db.post()
|
|
1643
|
-
* .where((p) => [expr.eq(p.userId, u.id)])
|
|
1644
|
-
* .select(() => ({ cnt: expr.count() }))
|
|
1645
|
-
* ),
|
|
1646
|
-
* }))
|
|
1647
|
-
* // SELECT id, (SELECT COUNT(*) FROM Post WHERE userId = User.id) AS postCount
|
|
1648
|
-
* ```
|
|
1649
|
-
*/
|
|
1650
|
-
subquery<TStr extends ColumnPrimitiveStr>(
|
|
1651
|
-
dataType: TStr,
|
|
1652
|
-
queryable: { getSelectQueryDef(): SelectQueryDef },
|
|
1653
|
-
): ExprUnit<ColumnPrimitiveMap[TStr] | undefined> {
|
|
1654
|
-
return new ExprUnit(dataType, {
|
|
1655
|
-
type: "subquery",
|
|
1656
|
-
queryDef: queryable.getSelectQueryDef(),
|
|
1657
|
-
});
|
|
1658
|
-
},
|
|
1659
|
-
|
|
1660
|
-
//#endregion
|
|
1661
|
-
|
|
1662
|
-
//#region ========== SELECT - Window Functions ==========
|
|
1663
|
-
|
|
1664
|
-
/**
|
|
1665
|
-
* ROW_NUMBER() - 파티션 내
|
|
1666
|
-
*
|
|
1667
|
-
* 각 파티션 내에서 1부터 시작하는
|
|
1668
|
-
*
|
|
1669
|
-
* @param spec -
|
|
1670
|
-
* @returns
|
|
1671
|
-
*
|
|
1672
|
-
* @example
|
|
1673
|
-
* ```typescript
|
|
1674
|
-
* db.order().select((o) => ({
|
|
1675
|
-
* ...o,
|
|
1676
|
-
* rowNum: expr.rowNumber({
|
|
1677
|
-
* partitionBy: [o.userId],
|
|
1678
|
-
* orderBy: [[o.createdAt, "DESC"]],
|
|
1679
|
-
* }),
|
|
1680
|
-
* }))
|
|
1681
|
-
* // SELECT *, ROW_NUMBER() OVER (PARTITION BY userId ORDER BY createdAt DESC)
|
|
1682
|
-
* ```
|
|
1683
|
-
*/
|
|
1684
|
-
rowNumber(spec: WinSpecInput): ExprUnit<number> {
|
|
1685
|
-
return new ExprUnit("number", {
|
|
1686
|
-
type: "window",
|
|
1687
|
-
fn: { type: "rowNumber" },
|
|
1688
|
-
spec: toWinSpec(spec),
|
|
1689
|
-
});
|
|
1690
|
-
},
|
|
1691
|
-
|
|
1692
|
-
/**
|
|
1693
|
-
* RANK() - 파티션 내 순위 (동점 시 같은 순위, 다음 순위 건너뜀)
|
|
1694
|
-
*
|
|
1695
|
-
* @param spec -
|
|
1696
|
-
* @returns 순위 (동점 후 건너뜀: 1, 1, 3)
|
|
1697
|
-
*
|
|
1698
|
-
* @example
|
|
1699
|
-
* ```typescript
|
|
1700
|
-
* db.student().select((s) => ({
|
|
1701
|
-
* name: s.name,
|
|
1702
|
-
* rank: expr.rank({
|
|
1703
|
-
* orderBy: [[s.score, "DESC"]],
|
|
1704
|
-
* }),
|
|
1705
|
-
* }))
|
|
1706
|
-
* ```
|
|
1707
|
-
*/
|
|
1708
|
-
rank(spec: WinSpecInput): ExprUnit<number> {
|
|
1709
|
-
return new ExprUnit("number", {
|
|
1710
|
-
type: "window",
|
|
1711
|
-
fn: { type: "rank" },
|
|
1712
|
-
spec: toWinSpec(spec),
|
|
1713
|
-
});
|
|
1714
|
-
},
|
|
1715
|
-
|
|
1716
|
-
/**
|
|
1717
|
-
* DENSE_RANK() - 파티션 내 밀집 순위 (동점 시 같은 순위, 다음 순위 유지)
|
|
1718
|
-
*
|
|
1719
|
-
* @param spec -
|
|
1720
|
-
* @returns 밀집 순위 (동점 후 연속: 1, 1, 2)
|
|
1721
|
-
*
|
|
1722
|
-
* @example
|
|
1723
|
-
* ```typescript
|
|
1724
|
-
* db.student().select((s) => ({
|
|
1725
|
-
* name: s.name,
|
|
1726
|
-
* denseRank: expr.denseRank({
|
|
1727
|
-
* orderBy: [[s.score, "DESC"]],
|
|
1728
|
-
* }),
|
|
1729
|
-
* }))
|
|
1730
|
-
* ```
|
|
1731
|
-
*/
|
|
1732
|
-
denseRank(spec: WinSpecInput): ExprUnit<number> {
|
|
1733
|
-
return new ExprUnit("number", {
|
|
1734
|
-
type: "window",
|
|
1735
|
-
fn: { type: "denseRank" },
|
|
1736
|
-
spec: toWinSpec(spec),
|
|
1737
|
-
});
|
|
1738
|
-
},
|
|
1739
|
-
|
|
1740
|
-
/**
|
|
1741
|
-
* NTILE(n) - 파티션을 n개 그룹으로
|
|
1742
|
-
*
|
|
1743
|
-
* @param n - 분할할 그룹 수
|
|
1744
|
-
* @param spec -
|
|
1745
|
-
* @returns 그룹 번호 (1 ~ n)
|
|
1746
|
-
*
|
|
1747
|
-
* @example
|
|
1748
|
-
* ```typescript
|
|
1749
|
-
* // 상위 25%를 찾기 위한 사분위
|
|
1750
|
-
* db.user().select((u) => ({
|
|
1751
|
-
* name: u.name,
|
|
1752
|
-
* quartile: expr.ntile(4, {
|
|
1753
|
-
* orderBy: [[u.score, "DESC"]],
|
|
1754
|
-
* }),
|
|
1755
|
-
* }))
|
|
1756
|
-
* ```
|
|
1757
|
-
*/
|
|
1758
|
-
ntile(n: number, spec: WinSpecInput): ExprUnit<number> {
|
|
1759
|
-
return new ExprUnit("number", {
|
|
1760
|
-
type: "window",
|
|
1761
|
-
fn: { type: "ntile", n },
|
|
1762
|
-
spec: toWinSpec(spec),
|
|
1763
|
-
});
|
|
1764
|
-
},
|
|
1765
|
-
|
|
1766
|
-
/**
|
|
1767
|
-
* LAG() - 이전 행의
|
|
1768
|
-
*
|
|
1769
|
-
* @param column -
|
|
1770
|
-
* @param spec -
|
|
1771
|
-
* @param options - offset (
|
|
1772
|
-
* @returns 이전 행의
|
|
1773
|
-
*
|
|
1774
|
-
* @example
|
|
1775
|
-
* ```typescript
|
|
1776
|
-
* db.stock().select((s) => ({
|
|
1777
|
-
* date: s.date,
|
|
1778
|
-
* price: s.price,
|
|
1779
|
-
* prevPrice: expr.lag(s.price, {
|
|
1780
|
-
* partitionBy: [s.symbol],
|
|
1781
|
-
* orderBy: [[s.date, "ASC"]],
|
|
1782
|
-
* }),
|
|
1783
|
-
* }))
|
|
1784
|
-
* ```
|
|
1785
|
-
*/
|
|
1786
|
-
lag<T extends ColumnPrimitive>(
|
|
1787
|
-
column: ExprUnit<T>,
|
|
1788
|
-
spec: WinSpecInput,
|
|
1789
|
-
options?: { offset?: number; default?: ExprInput<T> },
|
|
1790
|
-
): ExprUnit<T | undefined> {
|
|
1791
|
-
return new ExprUnit(column.dataType, {
|
|
1792
|
-
type: "window",
|
|
1793
|
-
fn: {
|
|
1794
|
-
type: "lag",
|
|
1795
|
-
column: toExpr(column),
|
|
1796
|
-
offset: options?.offset,
|
|
1797
|
-
default: options?.default != null ? toExpr(options.default) : undefined,
|
|
1798
|
-
},
|
|
1799
|
-
spec: toWinSpec(spec),
|
|
1800
|
-
});
|
|
1801
|
-
},
|
|
1802
|
-
|
|
1803
|
-
/**
|
|
1804
|
-
* LEAD() - 다음 행의
|
|
1805
|
-
*
|
|
1806
|
-
* @param column -
|
|
1807
|
-
* @param spec -
|
|
1808
|
-
* @param options - offset (
|
|
1809
|
-
* @returns 다음 행의
|
|
1810
|
-
*
|
|
1811
|
-
* @example
|
|
1812
|
-
* ```typescript
|
|
1813
|
-
* db.stock().select((s) => ({
|
|
1814
|
-
* date: s.date,
|
|
1815
|
-
* price: s.price,
|
|
1816
|
-
* nextPrice: expr.lead(s.price, {
|
|
1817
|
-
* partitionBy: [s.symbol],
|
|
1818
|
-
* orderBy: [[s.date, "ASC"]],
|
|
1819
|
-
* }),
|
|
1820
|
-
* }))
|
|
1821
|
-
* ```
|
|
1822
|
-
*/
|
|
1823
|
-
lead<T extends ColumnPrimitive>(
|
|
1824
|
-
column: ExprUnit<T>,
|
|
1825
|
-
spec: WinSpecInput,
|
|
1826
|
-
options?: { offset?: number; default?: ExprInput<T> },
|
|
1827
|
-
): ExprUnit<T | undefined> {
|
|
1828
|
-
return new ExprUnit(column.dataType, {
|
|
1829
|
-
type: "window",
|
|
1830
|
-
fn: {
|
|
1831
|
-
type: "lead",
|
|
1832
|
-
column: toExpr(column),
|
|
1833
|
-
offset: options?.offset,
|
|
1834
|
-
default: options?.default != null ? toExpr(options.default) : undefined,
|
|
1835
|
-
},
|
|
1836
|
-
spec: toWinSpec(spec),
|
|
1837
|
-
});
|
|
1838
|
-
},
|
|
1839
|
-
|
|
1840
|
-
/**
|
|
1841
|
-
* FIRST_VALUE() - 파티션/프레임의 첫 번째
|
|
1842
|
-
*
|
|
1843
|
-
* @param column -
|
|
1844
|
-
* @param spec -
|
|
1845
|
-
* @returns 첫 번째
|
|
1846
|
-
*
|
|
1847
|
-
* @example
|
|
1848
|
-
* ```typescript
|
|
1849
|
-
* db.order().select((o) => ({
|
|
1850
|
-
* ...o,
|
|
1851
|
-
* firstOrderAmount: expr.firstValue(o.amount, {
|
|
1852
|
-
* partitionBy: [o.userId],
|
|
1853
|
-
* orderBy: [[o.createdAt, "ASC"]],
|
|
1854
|
-
* }),
|
|
1855
|
-
* }))
|
|
1856
|
-
* ```
|
|
1857
|
-
*/
|
|
1858
|
-
firstValue<T extends ColumnPrimitive>(
|
|
1859
|
-
column: ExprUnit<T>,
|
|
1860
|
-
spec: WinSpecInput,
|
|
1861
|
-
): ExprUnit<T | undefined> {
|
|
1862
|
-
return new ExprUnit(column.dataType, {
|
|
1863
|
-
type: "window",
|
|
1864
|
-
fn: { type: "firstValue", column: toExpr(column) },
|
|
1865
|
-
spec: toWinSpec(spec),
|
|
1866
|
-
});
|
|
1867
|
-
},
|
|
1868
|
-
|
|
1869
|
-
/**
|
|
1870
|
-
* LAST_VALUE() - 파티션/프레임의 마지막
|
|
1871
|
-
*
|
|
1872
|
-
* @param column -
|
|
1873
|
-
* @param spec -
|
|
1874
|
-
* @returns 마지막
|
|
1875
|
-
*
|
|
1876
|
-
* @example
|
|
1877
|
-
* ```typescript
|
|
1878
|
-
* db.order().select((o) => ({
|
|
1879
|
-
* ...o,
|
|
1880
|
-
* lastOrderAmount: expr.lastValue(o.amount, {
|
|
1881
|
-
* partitionBy: [o.userId],
|
|
1882
|
-
* orderBy: [[o.createdAt, "ASC"]],
|
|
1883
|
-
* }),
|
|
1884
|
-
* }))
|
|
1885
|
-
* ```
|
|
1886
|
-
*/
|
|
1887
|
-
lastValue<T extends ColumnPrimitive>(
|
|
1888
|
-
column: ExprUnit<T>,
|
|
1889
|
-
spec: WinSpecInput,
|
|
1890
|
-
): ExprUnit<T | undefined> {
|
|
1891
|
-
return new ExprUnit(column.dataType, {
|
|
1892
|
-
type: "window",
|
|
1893
|
-
fn: { type: "lastValue", column: toExpr(column) },
|
|
1894
|
-
spec: toWinSpec(spec),
|
|
1895
|
-
});
|
|
1896
|
-
},
|
|
1897
|
-
|
|
1898
|
-
/**
|
|
1899
|
-
* SUM() OVER -
|
|
1900
|
-
*
|
|
1901
|
-
* @param column - 합계를 구할
|
|
1902
|
-
* @param spec -
|
|
1903
|
-
* @returns
|
|
1904
|
-
*
|
|
1905
|
-
* @example
|
|
1906
|
-
* ```typescript
|
|
1907
|
-
* // 누적 합계
|
|
1908
|
-
* db.order().select((o) => ({
|
|
1909
|
-
* ...o,
|
|
1910
|
-
* runningTotal: expr.sumOver(o.amount, {
|
|
1911
|
-
* partitionBy: [o.userId],
|
|
1912
|
-
* orderBy: [[o.createdAt, "ASC"]],
|
|
1913
|
-
* }),
|
|
1914
|
-
* }))
|
|
1915
|
-
* ```
|
|
1916
|
-
*/
|
|
1917
|
-
sumOver(column: ExprUnit<number | undefined>, spec: WinSpecInput): ExprUnit<number | undefined> {
|
|
1918
|
-
return new ExprUnit("number", {
|
|
1919
|
-
type: "window",
|
|
1920
|
-
fn: { type: "sum", column: toExpr(column) },
|
|
1921
|
-
spec: toWinSpec(spec),
|
|
1922
|
-
});
|
|
1923
|
-
},
|
|
1924
|
-
|
|
1925
|
-
/**
|
|
1926
|
-
* AVG() OVER -
|
|
1927
|
-
*
|
|
1928
|
-
* @param column - 평균을 구할
|
|
1929
|
-
* @param spec -
|
|
1930
|
-
* @returns
|
|
1931
|
-
*
|
|
1932
|
-
* @example
|
|
1933
|
-
* ```typescript
|
|
1934
|
-
* //
|
|
1935
|
-
* db.stock().select((s) => ({
|
|
1936
|
-
* ...s,
|
|
1937
|
-
* movingAvg: expr.avgOver(s.price, {
|
|
1938
|
-
* partitionBy: [s.symbol],
|
|
1939
|
-
* orderBy: [[s.date, "ASC"]],
|
|
1940
|
-
* }),
|
|
1941
|
-
* }))
|
|
1942
|
-
* ```
|
|
1943
|
-
*/
|
|
1944
|
-
avgOver(column: ExprUnit<number | undefined>, spec: WinSpecInput): ExprUnit<number | undefined> {
|
|
1945
|
-
return new ExprUnit("number", {
|
|
1946
|
-
type: "window",
|
|
1947
|
-
fn: { type: "avg", column: toExpr(column) },
|
|
1948
|
-
spec: toWinSpec(spec),
|
|
1949
|
-
});
|
|
1950
|
-
},
|
|
1951
|
-
|
|
1952
|
-
/**
|
|
1953
|
-
* COUNT() OVER -
|
|
1954
|
-
*
|
|
1955
|
-
* @param spec -
|
|
1956
|
-
* @param column - 카운트할
|
|
1957
|
-
* @returns
|
|
1958
|
-
*
|
|
1959
|
-
* @example
|
|
1960
|
-
* ```typescript
|
|
1961
|
-
* db.order().select((o) => ({
|
|
1962
|
-
* ...o,
|
|
1963
|
-
* totalOrdersPerUser: expr.countOver({
|
|
1964
|
-
* partitionBy: [o.userId],
|
|
1965
|
-
* }),
|
|
1966
|
-
* }))
|
|
1967
|
-
* ```
|
|
1968
|
-
*/
|
|
1969
|
-
countOver(spec: WinSpecInput, column?: ExprUnit<ColumnPrimitive>): ExprUnit<number> {
|
|
1970
|
-
return new ExprUnit("number", {
|
|
1971
|
-
type: "window",
|
|
1972
|
-
fn: { type: "count", column: column != null ? toExpr(column) : undefined },
|
|
1973
|
-
spec: toWinSpec(spec),
|
|
1974
|
-
});
|
|
1975
|
-
},
|
|
1976
|
-
|
|
1977
|
-
/**
|
|
1978
|
-
* MIN() OVER -
|
|
1979
|
-
*
|
|
1980
|
-
* @param column - 최소값을 구할
|
|
1981
|
-
* @param spec -
|
|
1982
|
-
* @returns
|
|
1983
|
-
*
|
|
1984
|
-
* @example
|
|
1985
|
-
* ```typescript
|
|
1986
|
-
* db.stock().select((s) => ({
|
|
1987
|
-
* ...s,
|
|
1988
|
-
* minPriceInPeriod: expr.minOver(s.price, {
|
|
1989
|
-
* partitionBy: [s.symbol],
|
|
1990
|
-
* }),
|
|
1991
|
-
* }))
|
|
1992
|
-
* ```
|
|
1993
|
-
*/
|
|
1994
|
-
minOver<T extends ColumnPrimitive>(
|
|
1995
|
-
column: ExprUnit<T>,
|
|
1996
|
-
spec: WinSpecInput,
|
|
1997
|
-
): ExprUnit<T | undefined> {
|
|
1998
|
-
return new ExprUnit(column.dataType, {
|
|
1999
|
-
type: "window",
|
|
2000
|
-
fn: { type: "min", column: toExpr(column) },
|
|
2001
|
-
spec: toWinSpec(spec),
|
|
2002
|
-
});
|
|
2003
|
-
},
|
|
2004
|
-
|
|
2005
|
-
/**
|
|
2006
|
-
* MAX() OVER -
|
|
2007
|
-
*
|
|
2008
|
-
* @param column - 최대값을 구할
|
|
2009
|
-
* @param spec -
|
|
2010
|
-
* @returns
|
|
2011
|
-
*
|
|
2012
|
-
* @example
|
|
2013
|
-
* ```typescript
|
|
2014
|
-
* db.stock().select((s) => ({
|
|
2015
|
-
* ...s,
|
|
2016
|
-
* maxPriceInPeriod: expr.maxOver(s.price, {
|
|
2017
|
-
* partitionBy: [s.symbol],
|
|
2018
|
-
* }),
|
|
2019
|
-
* }))
|
|
2020
|
-
* ```
|
|
2021
|
-
*/
|
|
2022
|
-
maxOver<T extends ColumnPrimitive>(
|
|
2023
|
-
column: ExprUnit<T>,
|
|
2024
|
-
spec: WinSpecInput,
|
|
2025
|
-
): ExprUnit<T | undefined> {
|
|
2026
|
-
return new ExprUnit(column.dataType, {
|
|
2027
|
-
type: "window",
|
|
2028
|
-
fn: { type: "max", column: toExpr(column) },
|
|
2029
|
-
spec: toWinSpec(spec),
|
|
2030
|
-
});
|
|
2031
|
-
},
|
|
2032
|
-
|
|
2033
|
-
//#endregion
|
|
2034
|
-
|
|
2035
|
-
//#region ========== Helper ==========
|
|
2036
|
-
|
|
2037
|
-
/**
|
|
2038
|
-
* ExprInput을 Expr로
|
|
2039
|
-
*
|
|
2040
|
-
* @param value -
|
|
2041
|
-
* @returns Expr JSON AST
|
|
2042
|
-
*/
|
|
2043
|
-
toExpr(value: ExprInput<ColumnPrimitive>): Expr {
|
|
2044
|
-
return toExpr(value);
|
|
2045
|
-
},
|
|
2046
|
-
|
|
2047
|
-
//#endregion
|
|
2048
|
-
};
|
|
2049
|
-
|
|
2050
|
-
//#region ========== Internal Helpers ==========
|
|
2051
|
-
|
|
2052
|
-
// 여러
|
|
2053
|
-
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2054
|
-
...args: [
|
|
2055
|
-
ExprInput<TPrimitive | undefined>,
|
|
2056
|
-
...ExprInput<TPrimitive | undefined>[],
|
|
2057
|
-
ExprInput<NonNullable<TPrimitive>>,
|
|
2058
|
-
]
|
|
2059
|
-
): ExprUnit<NonNullable<TPrimitive>>;
|
|
2060
|
-
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2061
|
-
...args: ExprInput<TPrimitive>[]
|
|
2062
|
-
): ExprUnit<TPrimitive>;
|
|
2063
|
-
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2064
|
-
...args: ExprInput<TPrimitive>[]
|
|
2065
|
-
): ExprUnit<TPrimitive> {
|
|
2066
|
-
return new ExprUnit(findDataType(args), {
|
|
2067
|
-
type: "ifNull",
|
|
2068
|
-
args: args.map((a) => toExpr(a)),
|
|
2069
|
-
});
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
function createSwitchBuilder<TPrimitive extends ColumnPrimitive>(): SwitchExprBuilder<TPrimitive> {
|
|
2073
|
-
const cases: { when: WhereExpr; then: Expr }[] = [];
|
|
2074
|
-
const thenValues: ExprInput<TPrimitive>[] = []; // then 값들 저장
|
|
2075
|
-
|
|
2076
|
-
return {
|
|
2077
|
-
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): typeof this {
|
|
2078
|
-
cases.push({
|
|
2079
|
-
when: condition.expr,
|
|
2080
|
-
then: toExpr(then),
|
|
2081
|
-
});
|
|
2082
|
-
thenValues.push(then);
|
|
2083
|
-
return this;
|
|
2084
|
-
},
|
|
2085
|
-
default(value: ExprInput<TPrimitive>): ExprUnit<TPrimitive> {
|
|
2086
|
-
const allValues = [...thenValues, value];
|
|
2087
|
-
// 1. ExprUnit에서 dataType 찾기
|
|
2088
|
-
const exprUnit = allValues.find((v): v is ExprUnit<TPrimitive> => v instanceof ExprUnit);
|
|
2089
|
-
if (exprUnit) {
|
|
2090
|
-
return new ExprUnit(exprUnit.dataType, {
|
|
2091
|
-
type: "switch",
|
|
2092
|
-
cases,
|
|
2093
|
-
else: toExpr(value),
|
|
2094
|
-
});
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
// 2. non-null 리터럴에서 추론
|
|
2098
|
-
const nonNullLiteral = allValues.find((v) => v != null) as ColumnPrimitive;
|
|
2099
|
-
if (nonNullLiteral == null) {
|
|
2100
|
-
throw new Error("switch
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
return new ExprUnit(inferColumnPrimitiveStr(nonNullLiteral), {
|
|
2104
|
-
type: "switch",
|
|
2105
|
-
cases,
|
|
2106
|
-
else: toExpr(value),
|
|
2107
|
-
});
|
|
2108
|
-
},
|
|
2109
|
-
};
|
|
2110
|
-
}
|
|
2111
|
-
|
|
2112
|
-
export function toExpr(value: ExprInput<ColumnPrimitive>): Expr {
|
|
2113
|
-
if (value instanceof ExprUnit) {
|
|
2114
|
-
return value.expr;
|
|
2115
|
-
}
|
|
2116
|
-
return { type: "value", value };
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
function findDataType<TPrimitive extends ColumnPrimitive>(
|
|
2120
|
-
args: ExprInput<TPrimitive>[],
|
|
2121
|
-
): ColumnPrimitiveStr {
|
|
2122
|
-
const exprUnit = args.find((a): a is ExprUnit<TPrimitive> => a instanceof ExprUnit);
|
|
2123
|
-
if (!exprUnit) {
|
|
2124
|
-
throw new Error("
|
|
2125
|
-
}
|
|
2126
|
-
return exprUnit.dataType;
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
function toWinSpec(spec: WinSpecInput): WinSpec {
|
|
2130
|
-
const result: WinSpec = {};
|
|
2131
|
-
if (spec.partitionBy != null) {
|
|
2132
|
-
result.partitionBy = spec.partitionBy.map((e) => toExpr(e));
|
|
2133
|
-
}
|
|
2134
|
-
if (spec.orderBy != null) {
|
|
2135
|
-
result.orderBy = spec.orderBy.map(([e, dir]) => [toExpr(e), dir]);
|
|
2136
|
-
}
|
|
2137
|
-
return result;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
//#endregion
|
|
1
|
+
import { ArgumentError, type DateOnly, type DateTime, type Time } from "@simplysm/core-common";
|
|
2
|
+
import {
|
|
3
|
+
type ColumnPrimitive,
|
|
4
|
+
type ColumnPrimitiveMap,
|
|
5
|
+
type ColumnPrimitiveStr,
|
|
6
|
+
type DataType,
|
|
7
|
+
dataTypeStrToColumnPrimitiveStr,
|
|
8
|
+
type InferColumnPrimitiveFromDataType,
|
|
9
|
+
inferColumnPrimitiveStr,
|
|
10
|
+
} from "../types/column";
|
|
11
|
+
import type { ExprInput } from "./expr-unit";
|
|
12
|
+
import { ExprUnit, WhereExprUnit } from "./expr-unit";
|
|
13
|
+
import type { Expr, DateSeparator, WhereExpr, WinSpec } from "../types/expr";
|
|
14
|
+
import type { SelectQueryDef } from "../types/query-def";
|
|
15
|
+
import type { Queryable } from "../exec/queryable";
|
|
16
|
+
|
|
17
|
+
// Window Function Spec Input (for user API)
|
|
18
|
+
interface WinSpecInput {
|
|
19
|
+
partitionBy?: ExprInput<ColumnPrimitive>[];
|
|
20
|
+
orderBy?: [ExprInput<ColumnPrimitive>, ("ASC" | "DESC")?][];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Switch expression builder interface
|
|
25
|
+
*/
|
|
26
|
+
export interface SwitchExprBuilder<TPrimitive extends ColumnPrimitive> {
|
|
27
|
+
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): SwitchExprBuilder<TPrimitive>;
|
|
28
|
+
default(value: ExprInput<TPrimitive>): ExprUnit<TPrimitive>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Dialect-independent SQL expression builder
|
|
33
|
+
*
|
|
34
|
+
* Generates JSON AST (Expr) instead of SQL strings, which QueryBuilder
|
|
35
|
+
* converts to each DBMS (MySQL, MSSQL, PostgreSQL)
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // WHERE condition
|
|
40
|
+
* db.user().where((u) => [
|
|
41
|
+
* expr.eq(u.status, "active"),
|
|
42
|
+
* expr.gt(u.age, 18),
|
|
43
|
+
* ])
|
|
44
|
+
*
|
|
45
|
+
* // SELECT expression
|
|
46
|
+
* db.user().select((u) => ({
|
|
47
|
+
* name: expr.concat(u.firstName, " ", u.lastName),
|
|
48
|
+
* age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
|
|
49
|
+
* }))
|
|
50
|
+
*
|
|
51
|
+
* // Aggregate function
|
|
52
|
+
* db.order().groupBy((o) => o.userId).select((o) => ({
|
|
53
|
+
* userId: o.userId,
|
|
54
|
+
* total: expr.sum(o.amount),
|
|
55
|
+
* }))
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @see {@link Queryable} Query builder class
|
|
59
|
+
* @see {@link ExprUnit} Expression wrapper class
|
|
60
|
+
*/
|
|
61
|
+
export const expr = {
|
|
62
|
+
//#region ========== Value creation ==========
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Wrap literal value as ExprUnit
|
|
66
|
+
*
|
|
67
|
+
* Widen to base type matching dataType, remove literal type
|
|
68
|
+
*
|
|
69
|
+
* @param dataType - Value의 data type ("string", "number", "boolean", "DateTime", "DateOnly", "Time", "Uuid", "Buffer")
|
|
70
|
+
* @param value - 래핑할 value (undefined allow)
|
|
71
|
+
* @returns 래핑된 ExprUnit instance
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // String value
|
|
76
|
+
* expr.val("string", "active")
|
|
77
|
+
*
|
|
78
|
+
* // Number value
|
|
79
|
+
* expr.val("number", 100)
|
|
80
|
+
*
|
|
81
|
+
* // Date value
|
|
82
|
+
* expr.val("DateOnly", DateOnly.today())
|
|
83
|
+
*
|
|
84
|
+
* // undefined value
|
|
85
|
+
* expr.val("string", undefined)
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
val<TStr extends ColumnPrimitiveStr, T extends ColumnPrimitiveMap[TStr] | undefined>(
|
|
89
|
+
dataType: TStr,
|
|
90
|
+
value: T,
|
|
91
|
+
): ExprUnit<
|
|
92
|
+
T extends undefined ? ColumnPrimitiveMap[TStr] | undefined : ColumnPrimitiveMap[TStr]
|
|
93
|
+
> {
|
|
94
|
+
return new ExprUnit(dataType, { type: "value", value });
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate column reference
|
|
99
|
+
*
|
|
100
|
+
* Typically proxy objects are used inside Queryable callbacks
|
|
101
|
+
*
|
|
102
|
+
* @param dataType - Column data type
|
|
103
|
+
* @param path - Column path (table alias, column name, etc.)
|
|
104
|
+
* @returns Column reference ExprUnit instance
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // Direct column reference (internal use)
|
|
109
|
+
* expr.col("string", "T1", "name")
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
col<TStr extends ColumnPrimitiveStr>(
|
|
113
|
+
dataType: ColumnPrimitiveStr,
|
|
114
|
+
...path: string[]
|
|
115
|
+
): ExprUnit<ColumnPrimitiveMap[TStr] | undefined> {
|
|
116
|
+
return new ExprUnit(dataType, { type: "column", path });
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Raw SQL expression Generate (escape hatch)
|
|
121
|
+
*
|
|
122
|
+
* Use when you need to directly use DB-specific functions or syntax not supported by the ORM.
|
|
123
|
+
* Used as tagged template literal, interpolated values are automatically parameterized
|
|
124
|
+
*
|
|
125
|
+
* @param dataType - Data type of the returned value
|
|
126
|
+
* @returns Tagged template function
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // Using MySQL JSON function
|
|
131
|
+
* db.user().select((u) => ({
|
|
132
|
+
* name: u.name,
|
|
133
|
+
* data: expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`,
|
|
134
|
+
* }))
|
|
135
|
+
*
|
|
136
|
+
* // PostgreSQL array function
|
|
137
|
+
* expr.raw("number")`ARRAY_LENGTH(${u.tags}, 1)`
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
raw<T extends ColumnPrimitiveStr>(
|
|
141
|
+
dataType: T,
|
|
142
|
+
): (
|
|
143
|
+
strings: TemplateStringsArray,
|
|
144
|
+
...values: ExprInput<ColumnPrimitive>[]
|
|
145
|
+
) => ExprUnit<ColumnPrimitiveMap[T] | undefined> {
|
|
146
|
+
return (strings, ...values) => {
|
|
147
|
+
const sql = strings.reduce((acc, str, i) => {
|
|
148
|
+
if (i < values.length) {
|
|
149
|
+
return acc + str + `$${i + 1}`; // placeholder (transformed by ExprRenderer)
|
|
150
|
+
}
|
|
151
|
+
return acc + str;
|
|
152
|
+
}, "");
|
|
153
|
+
|
|
154
|
+
const params = values.map((v) => toExpr(v));
|
|
155
|
+
|
|
156
|
+
return new ExprUnit(dataType, { type: "raw", sql, params });
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
|
|
162
|
+
//#region ========== WHERE - Comparison operators ==========
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Equality comparison (NULL-safe)
|
|
166
|
+
*
|
|
167
|
+
* Safely compare even NULL values (MySQL: `<=>`, MSSQL/PostgreSQL: `IS NULL OR =`)
|
|
168
|
+
*
|
|
169
|
+
* @param source - Column or expression to compare
|
|
170
|
+
* @param target - Target value or expression for comparison
|
|
171
|
+
* @returns WHERE condition expression
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* db.user().where((u) => [expr.eq(u.status, "active")])
|
|
176
|
+
* // WHERE status <=> 'active' (MySQL)
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
eq<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
180
|
+
return new WhereExprUnit({
|
|
181
|
+
type: "eq",
|
|
182
|
+
source: toExpr(source),
|
|
183
|
+
target: toExpr(target),
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Greater than comparison (>)
|
|
189
|
+
*
|
|
190
|
+
* @param source - Column or expression to compare
|
|
191
|
+
* @param target - Target value or expression for comparison
|
|
192
|
+
* @returns WHERE condition expression
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* db.user().where((u) => [expr.gt(u.age, 18)])
|
|
197
|
+
* // WHERE age > 18
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
gt<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
201
|
+
return new WhereExprUnit({
|
|
202
|
+
type: "gt",
|
|
203
|
+
source: toExpr(source),
|
|
204
|
+
target: toExpr(target),
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Less than comparison (<)
|
|
210
|
+
*
|
|
211
|
+
* @param source - Column or expression to compare
|
|
212
|
+
* @param target - Target value or expression for comparison
|
|
213
|
+
* @returns WHERE condition expression
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* db.user().where((u) => [expr.lt(u.score, 60)])
|
|
218
|
+
* // WHERE score < 60
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
lt<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
222
|
+
return new WhereExprUnit({
|
|
223
|
+
type: "lt",
|
|
224
|
+
source: toExpr(source),
|
|
225
|
+
target: toExpr(target),
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Greater than or equal comparison (>=)
|
|
231
|
+
*
|
|
232
|
+
* @param source - Column or expression to compare
|
|
233
|
+
* @param target - Target value or expression for comparison
|
|
234
|
+
* @returns WHERE condition expression
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* db.user().where((u) => [expr.gte(u.age, 18)])
|
|
239
|
+
* // WHERE age >= 18
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
gte<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
243
|
+
return new WhereExprUnit({
|
|
244
|
+
type: "gte",
|
|
245
|
+
source: toExpr(source),
|
|
246
|
+
target: toExpr(target),
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Less than or equal comparison (<=)
|
|
252
|
+
*
|
|
253
|
+
* @param source - Column or expression to compare
|
|
254
|
+
* @param target - Target value or expression for comparison
|
|
255
|
+
* @returns WHERE condition expression
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* db.user().where((u) => [expr.lte(u.score, 100)])
|
|
260
|
+
* // WHERE score <= 100
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
lte<T extends ColumnPrimitive>(source: ExprUnit<T>, target: ExprInput<T>): WhereExprUnit {
|
|
264
|
+
return new WhereExprUnit({
|
|
265
|
+
type: "lte",
|
|
266
|
+
source: toExpr(source),
|
|
267
|
+
target: toExpr(target),
|
|
268
|
+
});
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* range comparison (BETWEEN)
|
|
273
|
+
*
|
|
274
|
+
* from/to가 undefined이면 해당 방향은 무제한
|
|
275
|
+
*
|
|
276
|
+
* @param source - Column or expression to compare
|
|
277
|
+
* @param from - start value (undefined이면 하한 N/A)
|
|
278
|
+
* @param to - 끝 value (undefined이면 상한 N/A)
|
|
279
|
+
* @returns WHERE condition expression
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* // range 지정
|
|
284
|
+
* db.user().where((u) => [expr.between(u.age, 18, 65)])
|
|
285
|
+
* // WHERE age BETWEEN 18 AND 65
|
|
286
|
+
*
|
|
287
|
+
* // Specify only lower bound
|
|
288
|
+
* db.user().where((u) => [expr.between(u.age, 18, undefined)])
|
|
289
|
+
* // WHERE age >= 18
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
between<T extends ColumnPrimitive>(
|
|
293
|
+
source: ExprUnit<T>,
|
|
294
|
+
from?: ExprInput<T>,
|
|
295
|
+
to?: ExprInput<T>,
|
|
296
|
+
): WhereExprUnit {
|
|
297
|
+
return new WhereExprUnit({
|
|
298
|
+
type: "between",
|
|
299
|
+
source: toExpr(source),
|
|
300
|
+
from: from != null ? toExpr(from) : undefined,
|
|
301
|
+
to: to != null ? toExpr(to) : undefined,
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
|
|
307
|
+
//#region ========== WHERE - NULL check ==========
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* NULL 체크 (IS NULL)
|
|
311
|
+
*
|
|
312
|
+
* @param source - 체크할 column 또는 expression
|
|
313
|
+
* @returns WHERE condition expression
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* db.user().where((u) => [expr.null(u.deletedAt)])
|
|
318
|
+
* // WHERE deletedAt IS NULL
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
null<T extends ColumnPrimitive>(source: ExprUnit<T>): WhereExprUnit {
|
|
322
|
+
return new WhereExprUnit({
|
|
323
|
+
type: "null",
|
|
324
|
+
arg: toExpr(source),
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
//#endregion
|
|
329
|
+
|
|
330
|
+
//#region ========== WHERE - String search ==========
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* LIKE pattern 매칭
|
|
334
|
+
*
|
|
335
|
+
* `%`는 0개 이상의 문자, `_`는 단일 문자와 매칭.
|
|
336
|
+
* 특수문자는 `\`로 escape됨
|
|
337
|
+
*
|
|
338
|
+
* @param source - 검색할 column 또는 expression
|
|
339
|
+
* @param pattern - 검색 pattern (%, _ wildcard 사용 가능)
|
|
340
|
+
* @returns WHERE condition expression
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```typescript
|
|
344
|
+
* // 접두사 검색
|
|
345
|
+
* db.user().where((u) => [expr.like(u.name, "John%")])
|
|
346
|
+
* // WHERE name LIKE 'John%' ESCAPE '\'
|
|
347
|
+
*
|
|
348
|
+
* // include 검색
|
|
349
|
+
* db.user().where((u) => [expr.like(u.email, "%@gmail.com")])
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
like(
|
|
353
|
+
source: ExprUnit<string | undefined>,
|
|
354
|
+
pattern: ExprInput<string | undefined>,
|
|
355
|
+
): WhereExprUnit {
|
|
356
|
+
return new WhereExprUnit({
|
|
357
|
+
type: "like",
|
|
358
|
+
source: toExpr(source),
|
|
359
|
+
pattern: toExpr(pattern),
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* regular expression pattern 매칭
|
|
365
|
+
*
|
|
366
|
+
* DBMS별 regular expression 문법 차이 주의 필요
|
|
367
|
+
*
|
|
368
|
+
* @param source - 검색할 column 또는 expression
|
|
369
|
+
* @param pattern - regular expression pattern
|
|
370
|
+
* @returns WHERE condition expression
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* db.user().where((u) => [expr.regexp(u.email, "^[a-z]+@")])
|
|
375
|
+
* // MySQL: WHERE email REGEXP '^[a-z]+@'
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
regexp(
|
|
379
|
+
source: ExprUnit<string | undefined>,
|
|
380
|
+
pattern: ExprInput<string | undefined>,
|
|
381
|
+
): WhereExprUnit {
|
|
382
|
+
return new WhereExprUnit({
|
|
383
|
+
type: "regexp",
|
|
384
|
+
source: toExpr(source),
|
|
385
|
+
pattern: toExpr(pattern),
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
//#endregion
|
|
390
|
+
|
|
391
|
+
//#region ========== WHERE - IN ==========
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* IN operator - Value 목록과 comparison
|
|
395
|
+
*
|
|
396
|
+
* @param source - Column or expression to compare
|
|
397
|
+
* @param values - 비교할 value 목록
|
|
398
|
+
* @returns WHERE condition expression
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```typescript
|
|
402
|
+
* db.user().where((u) => [expr.in(u.status, ["active", "pending"])])
|
|
403
|
+
* // WHERE status IN ('active', 'pending')
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
in<T extends ColumnPrimitive>(source: ExprUnit<T>, values: ExprInput<T>[]): WhereExprUnit {
|
|
407
|
+
return new WhereExprUnit({
|
|
408
|
+
type: "in",
|
|
409
|
+
source: toExpr(source),
|
|
410
|
+
values: values.map((v) => toExpr(v)),
|
|
411
|
+
});
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* IN (SELECT ...) - Subquery 결과와 comparison
|
|
416
|
+
*
|
|
417
|
+
* Subquery는 반드시 단일 column만 SELECT해야 함
|
|
418
|
+
*
|
|
419
|
+
* @param source - Column or expression to compare
|
|
420
|
+
* @param query - 단일 column을 반환하는 Queryable
|
|
421
|
+
* @returns WHERE condition expression
|
|
422
|
+
* @throws {Error} Subquery가 단일 column이 아닌 경우
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```typescript
|
|
426
|
+
* db.user().where((u) => [
|
|
427
|
+
* expr.inQuery(
|
|
428
|
+
* u.id,
|
|
429
|
+
* db.order()
|
|
430
|
+
* .where((o) => [expr.gt(o.amount, 1000)])
|
|
431
|
+
* .select((o) => ({ userId: o.userId }))
|
|
432
|
+
* ),
|
|
433
|
+
* ])
|
|
434
|
+
* // WHERE id IN (SELECT userId FROM Order WHERE amount > 1000)
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
inQuery<T extends ColumnPrimitive, TData extends Record<string, T>>(
|
|
438
|
+
source: ExprUnit<T>,
|
|
439
|
+
query: Queryable<TData, any>,
|
|
440
|
+
): WhereExprUnit {
|
|
441
|
+
const queryDef = query.getSelectQueryDef();
|
|
442
|
+
if (queryDef.select == null || Object.keys(queryDef.select).length !== 1) {
|
|
443
|
+
throw new Error("inQuery subquery must SELECT only a single column.");
|
|
444
|
+
}
|
|
445
|
+
return new WhereExprUnit({
|
|
446
|
+
type: "inQuery",
|
|
447
|
+
source: toExpr(source),
|
|
448
|
+
query: queryDef,
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* EXISTS (SELECT ...) - Subquery result 존재 여부 확인
|
|
454
|
+
*
|
|
455
|
+
* Subquery가 하나 이상의 행을 반환하면 true
|
|
456
|
+
*
|
|
457
|
+
* @param query - 존재 여부를 확인할 Queryable
|
|
458
|
+
* @returns WHERE condition expression
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* // 주문이 있는 사용자 조회
|
|
463
|
+
* db.user().where((u) => [
|
|
464
|
+
* expr.exists(
|
|
465
|
+
* db.order().where((o) => [expr.eq(o.userId, u.id)])
|
|
466
|
+
* ),
|
|
467
|
+
* ])
|
|
468
|
+
* // WHERE EXISTS (SELECT 1 FROM Order WHERE userId = User.id)
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
exists(query: Queryable<any, any>): WhereExprUnit {
|
|
472
|
+
const { select: _, ...queryDefWithoutSelect } = query.getSelectQueryDef(); // EXISTS는 SELECT 절 불필요, 패킷 절약
|
|
473
|
+
return new WhereExprUnit({
|
|
474
|
+
type: "exists",
|
|
475
|
+
query: queryDefWithoutSelect,
|
|
476
|
+
});
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
//#endregion
|
|
480
|
+
|
|
481
|
+
//#region ========== WHERE - Logical operators ==========
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* NOT operator - Condition 부정
|
|
485
|
+
*
|
|
486
|
+
* @param arg - 부정할 condition
|
|
487
|
+
* @returns 부정된 WHERE condition expression
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```typescript
|
|
491
|
+
* db.user().where((u) => [expr.not(expr.eq(u.status, "deleted"))])
|
|
492
|
+
* // WHERE NOT (status <=> 'deleted')
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
not(arg: WhereExprUnit): WhereExprUnit {
|
|
496
|
+
return new WhereExprUnit({
|
|
497
|
+
type: "not",
|
|
498
|
+
arg: arg.expr,
|
|
499
|
+
});
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* AND operator - 모든 condition 충족
|
|
504
|
+
*
|
|
505
|
+
* 여러 조건을 AND로 결합. where() 메서드에 배열로 전달하면 automatic으로 AND applied
|
|
506
|
+
*
|
|
507
|
+
* @param conditions - AND로 결합할 condition 목록
|
|
508
|
+
* @returns 결합된 WHERE condition expression
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* ```typescript
|
|
512
|
+
* db.user().where((u) => [
|
|
513
|
+
* expr.and([
|
|
514
|
+
* expr.eq(u.status, "active"),
|
|
515
|
+
* expr.gte(u.age, 18),
|
|
516
|
+
* ]),
|
|
517
|
+
* ])
|
|
518
|
+
* // WHERE (status <=> 'active' AND age >= 18)
|
|
519
|
+
* ```
|
|
520
|
+
*/
|
|
521
|
+
and(conditions: WhereExprUnit[]): WhereExprUnit {
|
|
522
|
+
if (conditions.length === 0) {
|
|
523
|
+
throw new ArgumentError({ conditions: "empty arrays are not allowed" });
|
|
524
|
+
}
|
|
525
|
+
return new WhereExprUnit({
|
|
526
|
+
type: "and",
|
|
527
|
+
conditions: conditions.map((c) => c.expr),
|
|
528
|
+
});
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* OR operator - 하나 이상의 condition 충족
|
|
533
|
+
*
|
|
534
|
+
* @param conditions - OR로 결합할 condition 목록
|
|
535
|
+
* @returns 결합된 WHERE condition expression
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```typescript
|
|
539
|
+
* db.user().where((u) => [
|
|
540
|
+
* expr.or([
|
|
541
|
+
* expr.eq(u.status, "active"),
|
|
542
|
+
* expr.eq(u.status, "pending"),
|
|
543
|
+
* ]),
|
|
544
|
+
* ])
|
|
545
|
+
* // WHERE (status <=> 'active' OR status <=> 'pending')
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
or(conditions: WhereExprUnit[]): WhereExprUnit {
|
|
549
|
+
if (conditions.length === 0) {
|
|
550
|
+
throw new ArgumentError({ conditions: "empty arrays are not allowed" });
|
|
551
|
+
}
|
|
552
|
+
return new WhereExprUnit({
|
|
553
|
+
type: "or",
|
|
554
|
+
conditions: conditions.map((c) => c.expr),
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
//#endregion
|
|
559
|
+
|
|
560
|
+
//#region ========== SELECT - 문자열 ==========
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 문자열 연결 (CONCAT)
|
|
564
|
+
*
|
|
565
|
+
* NULL 값은 빈 문자열로 processing됨 (DBMS별 automatic Transform)
|
|
566
|
+
*
|
|
567
|
+
* @param args - 연결할 문자열들
|
|
568
|
+
* @returns 연결된 문자열 expression
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* ```typescript
|
|
572
|
+
* db.user().select((u) => ({
|
|
573
|
+
* fullName: expr.concat(u.firstName, " ", u.lastName),
|
|
574
|
+
* }))
|
|
575
|
+
* // SELECT CONCAT(firstName, ' ', lastName) AS fullName
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
578
|
+
concat(...args: ExprInput<string | undefined>[]): ExprUnit<string> {
|
|
579
|
+
return new ExprUnit("string", {
|
|
580
|
+
type: "concat",
|
|
581
|
+
args: args.map((arg) => toExpr(arg)),
|
|
582
|
+
});
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* 문자열 왼쪽에서 지정 길이만큼 추출 (LEFT)
|
|
587
|
+
*
|
|
588
|
+
* @param source - original string
|
|
589
|
+
* @param length - 추출할 문자 수
|
|
590
|
+
* @returns 추출된 문자열 expression
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```typescript
|
|
594
|
+
* db.user().select((u) => ({
|
|
595
|
+
* initial: expr.left(u.name, 1),
|
|
596
|
+
* }))
|
|
597
|
+
* // SELECT LEFT(name, 1) AS initial
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
left<T extends string | undefined>(source: ExprUnit<T>, length: ExprInput<number>): ExprUnit<T> {
|
|
601
|
+
return new ExprUnit("string", {
|
|
602
|
+
type: "left",
|
|
603
|
+
source: toExpr(source),
|
|
604
|
+
length: toExpr(length),
|
|
605
|
+
});
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* 문자열 오른쪽에서 지정 길이만큼 추출 (RIGHT)
|
|
610
|
+
*
|
|
611
|
+
* @param source - original string
|
|
612
|
+
* @param length - 추출할 문자 수
|
|
613
|
+
* @returns 추출된 문자열 expression
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* ```typescript
|
|
617
|
+
* db.phone().select((p) => ({
|
|
618
|
+
* lastFour: expr.right(p.number, 4),
|
|
619
|
+
* }))
|
|
620
|
+
* // SELECT RIGHT(number, 4) AS lastFour
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
right<T extends string | undefined>(source: ExprUnit<T>, length: ExprInput<number>): ExprUnit<T> {
|
|
624
|
+
return new ExprUnit("string", {
|
|
625
|
+
type: "right",
|
|
626
|
+
source: toExpr(source),
|
|
627
|
+
length: toExpr(length),
|
|
628
|
+
});
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* 문자열 양쪽 공백 Remove (TRIM)
|
|
633
|
+
*
|
|
634
|
+
* @param source - original string
|
|
635
|
+
* @returns 공백이 제거된 문자열 expression
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```typescript
|
|
639
|
+
* db.user().select((u) => ({
|
|
640
|
+
* name: expr.trim(u.name),
|
|
641
|
+
* }))
|
|
642
|
+
* // SELECT TRIM(name) AS name
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
645
|
+
trim<T extends string | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
646
|
+
return new ExprUnit("string", {
|
|
647
|
+
type: "trim",
|
|
648
|
+
arg: toExpr(source),
|
|
649
|
+
});
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* 문자열 왼쪽 패딩 (LPAD)
|
|
654
|
+
*
|
|
655
|
+
* 지정 길이가 될 때까지 왼쪽에 fillString loop Add
|
|
656
|
+
*
|
|
657
|
+
* @param source - original string
|
|
658
|
+
* @param length - 목표 길이
|
|
659
|
+
* @param fillString - 패딩에 사용할 문자열
|
|
660
|
+
* @returns 패딩된 문자열 expression
|
|
661
|
+
*
|
|
662
|
+
* @example
|
|
663
|
+
* ```typescript
|
|
664
|
+
* db.order().select((o) => ({
|
|
665
|
+
* orderNo: expr.padStart(expr.cast(o.id, { type: "varchar", length: 10 }), 8, "0"),
|
|
666
|
+
* }))
|
|
667
|
+
* // SELECT LPAD(CAST(id AS VARCHAR(10)), 8, '0') AS orderNo
|
|
668
|
+
* // Result: "00000123"
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
padStart<T extends string | undefined>(
|
|
672
|
+
source: ExprUnit<T>,
|
|
673
|
+
length: ExprInput<number>,
|
|
674
|
+
fillString: ExprInput<string>,
|
|
675
|
+
): ExprUnit<T> {
|
|
676
|
+
return new ExprUnit("string", {
|
|
677
|
+
type: "padStart",
|
|
678
|
+
source: toExpr(source),
|
|
679
|
+
length: toExpr(length),
|
|
680
|
+
fillString: toExpr(fillString),
|
|
681
|
+
});
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* 문자열 치환 (REPLACE)
|
|
686
|
+
*
|
|
687
|
+
* @param source - original string
|
|
688
|
+
* @param from - 찾을 문자열
|
|
689
|
+
* @param to - 대체할 문자열
|
|
690
|
+
* @returns 치환된 문자열 expression
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```typescript
|
|
694
|
+
* db.user().select((u) => ({
|
|
695
|
+
* phone: expr.replace(u.phone, "-", ""),
|
|
696
|
+
* }))
|
|
697
|
+
* // SELECT REPLACE(phone, '-', '') AS phone
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
replace<T extends string | undefined>(
|
|
701
|
+
source: ExprUnit<T>,
|
|
702
|
+
from: ExprInput<string>,
|
|
703
|
+
to: ExprInput<string>,
|
|
704
|
+
): ExprUnit<T> {
|
|
705
|
+
return new ExprUnit("string", {
|
|
706
|
+
type: "replace",
|
|
707
|
+
source: toExpr(source),
|
|
708
|
+
from: toExpr(from),
|
|
709
|
+
to: toExpr(to),
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* 문자열 대문자 Transform (UPPER)
|
|
715
|
+
*
|
|
716
|
+
* @param source - original string
|
|
717
|
+
* @returns 대문자로 Transform된 문자열 expression
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```typescript
|
|
721
|
+
* db.user().select((u) => ({
|
|
722
|
+
* code: expr.upper(u.code),
|
|
723
|
+
* }))
|
|
724
|
+
* // SELECT UPPER(code) AS code
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
upper<T extends string | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
728
|
+
return new ExprUnit("string", {
|
|
729
|
+
type: "upper",
|
|
730
|
+
arg: toExpr(source),
|
|
731
|
+
});
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* 문자열 소문자 Transform (LOWER)
|
|
736
|
+
*
|
|
737
|
+
* @param source - original string
|
|
738
|
+
* @returns 소문자로 Transform된 문자열 expression
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```typescript
|
|
742
|
+
* db.user().select((u) => ({
|
|
743
|
+
* email: expr.lower(u.email),
|
|
744
|
+
* }))
|
|
745
|
+
* // SELECT LOWER(email) AS email
|
|
746
|
+
* ```
|
|
747
|
+
*/
|
|
748
|
+
lower<T extends string | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
749
|
+
return new ExprUnit("string", {
|
|
750
|
+
type: "lower",
|
|
751
|
+
arg: toExpr(source),
|
|
752
|
+
});
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* 문자열 길이 (문자 수)
|
|
757
|
+
*
|
|
758
|
+
* @param source - original string
|
|
759
|
+
* @returns 문자 수
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* ```typescript
|
|
763
|
+
* db.user().select((u) => ({
|
|
764
|
+
* nameLength: expr.length(u.name),
|
|
765
|
+
* }))
|
|
766
|
+
* // SELECT CHAR_LENGTH(name) AS nameLength
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
length(source: ExprUnit<string | undefined>): ExprUnit<number> {
|
|
770
|
+
return new ExprUnit("number", {
|
|
771
|
+
type: "length",
|
|
772
|
+
arg: toExpr(source),
|
|
773
|
+
});
|
|
774
|
+
},
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* 문자열 바이트 길이
|
|
778
|
+
*
|
|
779
|
+
* UTF-8 환경에서 한글은 3바이트
|
|
780
|
+
*
|
|
781
|
+
* @param source - original string
|
|
782
|
+
* @returns 바이트 수
|
|
783
|
+
*
|
|
784
|
+
* @example
|
|
785
|
+
* ```typescript
|
|
786
|
+
* db.user().select((u) => ({
|
|
787
|
+
* byteLen: expr.byteLength(u.name),
|
|
788
|
+
* }))
|
|
789
|
+
* // SELECT OCTET_LENGTH(name) AS byteLen
|
|
790
|
+
* ```
|
|
791
|
+
*/
|
|
792
|
+
byteLength(source: ExprUnit<string | undefined>): ExprUnit<number> {
|
|
793
|
+
return new ExprUnit("number", {
|
|
794
|
+
type: "byteLength",
|
|
795
|
+
arg: toExpr(source),
|
|
796
|
+
});
|
|
797
|
+
},
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* 문자열 일부 추출 (SUBSTRING)
|
|
801
|
+
*
|
|
802
|
+
* SQL 표준에 따라 1-based index 사용
|
|
803
|
+
*
|
|
804
|
+
* @param source - original string
|
|
805
|
+
* @param start - start 위치 (1부터 start)
|
|
806
|
+
* @param length - 추출할 길이 (생략 시 끝까지)
|
|
807
|
+
* @returns 추출된 문자열 expression
|
|
808
|
+
*
|
|
809
|
+
* @example
|
|
810
|
+
* ```typescript
|
|
811
|
+
* db.user().select((u) => ({
|
|
812
|
+
* // "Hello World"에서 Index 1부터 5글자: "Hello"
|
|
813
|
+
* prefix: expr.substring(u.name, 1, 5),
|
|
814
|
+
* }))
|
|
815
|
+
* // SELECT SUBSTRING(name, 1, 5) AS prefix
|
|
816
|
+
* ```
|
|
817
|
+
*/
|
|
818
|
+
substring<T extends string | undefined>(
|
|
819
|
+
source: ExprUnit<T>,
|
|
820
|
+
start: ExprInput<number>,
|
|
821
|
+
length?: ExprInput<number>,
|
|
822
|
+
): ExprUnit<T> {
|
|
823
|
+
return new ExprUnit("string", {
|
|
824
|
+
type: "substring",
|
|
825
|
+
source: toExpr(source),
|
|
826
|
+
start: toExpr(start),
|
|
827
|
+
...(length != null ? { length: toExpr(length) } : {}),
|
|
828
|
+
});
|
|
829
|
+
},
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* 문자열 내 위치 찾기 (LOCATE/CHARINDEX)
|
|
833
|
+
*
|
|
834
|
+
* 1-based index return, 없으면 0 return
|
|
835
|
+
*
|
|
836
|
+
* @param source - 검색할 문자열
|
|
837
|
+
* @param search - 찾을 문자열
|
|
838
|
+
* @returns 위치 (1부터 start, 없으면 0)
|
|
839
|
+
*
|
|
840
|
+
* @example
|
|
841
|
+
* ```typescript
|
|
842
|
+
* db.user().select((u) => ({
|
|
843
|
+
* atPos: expr.indexOf(u.email, "@"),
|
|
844
|
+
* }))
|
|
845
|
+
* // SELECT LOCATE('@', email) AS atPos (MySQL)
|
|
846
|
+
* // "john@example.com" → 5
|
|
847
|
+
* ```
|
|
848
|
+
*/
|
|
849
|
+
indexOf(source: ExprUnit<string | undefined>, search: ExprInput<string>): ExprUnit<number> {
|
|
850
|
+
return new ExprUnit("number", {
|
|
851
|
+
type: "indexOf",
|
|
852
|
+
source: toExpr(source),
|
|
853
|
+
search: toExpr(search),
|
|
854
|
+
});
|
|
855
|
+
},
|
|
856
|
+
|
|
857
|
+
//#endregion
|
|
858
|
+
|
|
859
|
+
//#region ========== SELECT - Number ==========
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* 절대값 (ABS)
|
|
863
|
+
*
|
|
864
|
+
* @param source - 원본 Number
|
|
865
|
+
* @returns 절대값 expression
|
|
866
|
+
*
|
|
867
|
+
* @example
|
|
868
|
+
* ```typescript
|
|
869
|
+
* db.account().select((a) => ({
|
|
870
|
+
* balance: expr.abs(a.balance),
|
|
871
|
+
* }))
|
|
872
|
+
* // SELECT ABS(balance) AS balance
|
|
873
|
+
* ```
|
|
874
|
+
*/
|
|
875
|
+
abs<T extends number | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
876
|
+
return new ExprUnit("number", {
|
|
877
|
+
type: "abs",
|
|
878
|
+
arg: toExpr(source),
|
|
879
|
+
});
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* 반올림 (ROUND)
|
|
884
|
+
*
|
|
885
|
+
* @param source - 원본 Number
|
|
886
|
+
* @param digits - 소수점 이하 자릿수
|
|
887
|
+
* @returns 반올림된 Number expression
|
|
888
|
+
*
|
|
889
|
+
* @example
|
|
890
|
+
* ```typescript
|
|
891
|
+
* db.product().select((p) => ({
|
|
892
|
+
* price: expr.round(p.price, 2),
|
|
893
|
+
* }))
|
|
894
|
+
* // SELECT ROUND(price, 2) AS price
|
|
895
|
+
* // 123.456 → 123.46
|
|
896
|
+
* ```
|
|
897
|
+
*/
|
|
898
|
+
round<T extends number | undefined>(source: ExprUnit<T>, digits: number): ExprUnit<T> {
|
|
899
|
+
return new ExprUnit("number", {
|
|
900
|
+
type: "round",
|
|
901
|
+
arg: toExpr(source),
|
|
902
|
+
digits,
|
|
903
|
+
});
|
|
904
|
+
},
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* 올림 (CEILING)
|
|
908
|
+
*
|
|
909
|
+
* @param source - 원본 Number
|
|
910
|
+
* @returns 올림된 Number expression
|
|
911
|
+
*
|
|
912
|
+
* @example
|
|
913
|
+
* ```typescript
|
|
914
|
+
* db.order().select((o) => ({
|
|
915
|
+
* pages: expr.ceil(expr.divide(o.itemCount, 10)),
|
|
916
|
+
* }))
|
|
917
|
+
* // SELECT CEILING(itemCount / 10) AS pages
|
|
918
|
+
* // 25 / 10 = 2.5 → 3
|
|
919
|
+
* ```
|
|
920
|
+
*/
|
|
921
|
+
ceil<T extends number | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
922
|
+
return new ExprUnit("number", {
|
|
923
|
+
type: "ceil",
|
|
924
|
+
arg: toExpr(source),
|
|
925
|
+
});
|
|
926
|
+
},
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* 버림 (FLOOR)
|
|
930
|
+
*
|
|
931
|
+
* @param source - 원본 Number
|
|
932
|
+
* @returns 버림된 Number expression
|
|
933
|
+
*
|
|
934
|
+
* @example
|
|
935
|
+
* ```typescript
|
|
936
|
+
* db.user().select((u) => ({
|
|
937
|
+
* ageGroup: expr.floor(expr.divide(u.age, 10)),
|
|
938
|
+
* }))
|
|
939
|
+
* // SELECT FLOOR(age / 10) AS ageGroup
|
|
940
|
+
* // 25 / 10 = 2.5 → 2
|
|
941
|
+
* ```
|
|
942
|
+
*/
|
|
943
|
+
floor<T extends number | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
944
|
+
return new ExprUnit("number", {
|
|
945
|
+
type: "floor",
|
|
946
|
+
arg: toExpr(source),
|
|
947
|
+
});
|
|
948
|
+
},
|
|
949
|
+
|
|
950
|
+
//#endregion
|
|
951
|
+
|
|
952
|
+
//#region ========== SELECT - Date ==========
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* 연도 추출 (YEAR)
|
|
956
|
+
*
|
|
957
|
+
* @param source - DateTime 또는 DateOnly expression
|
|
958
|
+
* @returns 연도 (4자리 Number)
|
|
959
|
+
*
|
|
960
|
+
* @example
|
|
961
|
+
* ```typescript
|
|
962
|
+
* db.user().select((u) => ({
|
|
963
|
+
* birthYear: expr.year(u.birthDate),
|
|
964
|
+
* }))
|
|
965
|
+
* // SELECT YEAR(birthDate) AS birthYear
|
|
966
|
+
* ```
|
|
967
|
+
*/
|
|
968
|
+
year<T extends DateTime | DateOnly | undefined>(
|
|
969
|
+
source: ExprUnit<T>,
|
|
970
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
971
|
+
return new ExprUnit("number", {
|
|
972
|
+
type: "year",
|
|
973
|
+
arg: toExpr(source),
|
|
974
|
+
});
|
|
975
|
+
},
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* 월 추출 (MONTH)
|
|
979
|
+
*
|
|
980
|
+
* @param source - DateTime 또는 DateOnly expression
|
|
981
|
+
* @returns 월 (1~12)
|
|
982
|
+
*
|
|
983
|
+
* @example
|
|
984
|
+
* ```typescript
|
|
985
|
+
* db.order().select((o) => ({
|
|
986
|
+
* orderMonth: expr.month(o.createdAt),
|
|
987
|
+
* }))
|
|
988
|
+
* // SELECT MONTH(createdAt) AS orderMonth
|
|
989
|
+
* ```
|
|
990
|
+
*/
|
|
991
|
+
month<T extends DateTime | DateOnly | undefined>(
|
|
992
|
+
source: ExprUnit<T>,
|
|
993
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
994
|
+
return new ExprUnit("number", {
|
|
995
|
+
type: "month",
|
|
996
|
+
arg: toExpr(source),
|
|
997
|
+
});
|
|
998
|
+
},
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* 일 추출 (DAY)
|
|
1002
|
+
*
|
|
1003
|
+
* @param source - DateTime 또는 DateOnly expression
|
|
1004
|
+
* @returns 일 (1~31)
|
|
1005
|
+
*
|
|
1006
|
+
* @example
|
|
1007
|
+
* ```typescript
|
|
1008
|
+
* db.user().select((u) => ({
|
|
1009
|
+
* birthDay: expr.day(u.birthDate),
|
|
1010
|
+
* }))
|
|
1011
|
+
* // SELECT DAY(birthDate) AS birthDay
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
day<T extends DateTime | DateOnly | undefined>(
|
|
1015
|
+
source: ExprUnit<T>,
|
|
1016
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1017
|
+
return new ExprUnit("number", {
|
|
1018
|
+
type: "day",
|
|
1019
|
+
arg: toExpr(source),
|
|
1020
|
+
});
|
|
1021
|
+
},
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* 시 추출 (HOUR)
|
|
1025
|
+
*
|
|
1026
|
+
* @param source - DateTime 또는 Time expression
|
|
1027
|
+
* @returns 시 (0~23)
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* db.log().select((l) => ({
|
|
1032
|
+
* logHour: expr.hour(l.createdAt),
|
|
1033
|
+
* }))
|
|
1034
|
+
* // SELECT HOUR(createdAt) AS logHour
|
|
1035
|
+
* ```
|
|
1036
|
+
*/
|
|
1037
|
+
hour<T extends DateTime | Time | undefined>(
|
|
1038
|
+
source: ExprUnit<T>,
|
|
1039
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1040
|
+
return new ExprUnit("number", {
|
|
1041
|
+
type: "hour",
|
|
1042
|
+
arg: toExpr(source),
|
|
1043
|
+
});
|
|
1044
|
+
},
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* 분 추출 (MINUTE)
|
|
1048
|
+
*
|
|
1049
|
+
* @param source - DateTime 또는 Time expression
|
|
1050
|
+
* @returns 분 (0~59)
|
|
1051
|
+
*
|
|
1052
|
+
* @example
|
|
1053
|
+
* ```typescript
|
|
1054
|
+
* db.log().select((l) => ({
|
|
1055
|
+
* logMinute: expr.minute(l.createdAt),
|
|
1056
|
+
* }))
|
|
1057
|
+
* // SELECT MINUTE(createdAt) AS logMinute
|
|
1058
|
+
* ```
|
|
1059
|
+
*/
|
|
1060
|
+
minute<T extends DateTime | Time | undefined>(
|
|
1061
|
+
source: ExprUnit<T>,
|
|
1062
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1063
|
+
return new ExprUnit("number", {
|
|
1064
|
+
type: "minute",
|
|
1065
|
+
arg: toExpr(source),
|
|
1066
|
+
});
|
|
1067
|
+
},
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* 초 추출 (SECOND)
|
|
1071
|
+
*
|
|
1072
|
+
* @param source - DateTime 또는 Time expression
|
|
1073
|
+
* @returns 초 (0~59)
|
|
1074
|
+
*
|
|
1075
|
+
* @example
|
|
1076
|
+
* ```typescript
|
|
1077
|
+
* db.log().select((l) => ({
|
|
1078
|
+
* logSecond: expr.second(l.createdAt),
|
|
1079
|
+
* }))
|
|
1080
|
+
* // SELECT SECOND(createdAt) AS logSecond
|
|
1081
|
+
* ```
|
|
1082
|
+
*/
|
|
1083
|
+
second<T extends DateTime | Time | undefined>(
|
|
1084
|
+
source: ExprUnit<T>,
|
|
1085
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1086
|
+
return new ExprUnit("number", {
|
|
1087
|
+
type: "second",
|
|
1088
|
+
arg: toExpr(source),
|
|
1089
|
+
});
|
|
1090
|
+
},
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* ISO 주차 추출
|
|
1094
|
+
*
|
|
1095
|
+
* ISO 8601 기준 주차 (월요일 start, 1~53)
|
|
1096
|
+
*
|
|
1097
|
+
* @param source - DateOnly expression
|
|
1098
|
+
* @returns ISO 주차 번호
|
|
1099
|
+
*
|
|
1100
|
+
* @example
|
|
1101
|
+
* ```typescript
|
|
1102
|
+
* db.order().select((o) => ({
|
|
1103
|
+
* weekNum: expr.isoWeek(o.orderDate),
|
|
1104
|
+
* }))
|
|
1105
|
+
* // SELECT WEEK(orderDate, 3) AS weekNum (MySQL)
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
isoWeek<T extends DateOnly | undefined>(
|
|
1109
|
+
source: ExprUnit<T>,
|
|
1110
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1111
|
+
return new ExprUnit("number", {
|
|
1112
|
+
type: "isoWeek",
|
|
1113
|
+
arg: toExpr(source),
|
|
1114
|
+
});
|
|
1115
|
+
},
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* ISO 주 시작일 (월요일)
|
|
1119
|
+
*
|
|
1120
|
+
* 해당 Date가 속한 주의 월요일 return
|
|
1121
|
+
*
|
|
1122
|
+
* @param source - DateOnly expression
|
|
1123
|
+
* @returns 주의 start Date (월요일)
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* ```typescript
|
|
1127
|
+
* db.order().select((o) => ({
|
|
1128
|
+
* weekStart: expr.isoWeekStartDate(o.orderDate),
|
|
1129
|
+
* }))
|
|
1130
|
+
* // 2024-01-10 (수) → 2024-01-08 (월)
|
|
1131
|
+
* ```
|
|
1132
|
+
*/
|
|
1133
|
+
isoWeekStartDate<T extends DateOnly | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
1134
|
+
return new ExprUnit("DateOnly", {
|
|
1135
|
+
type: "isoWeekStartDate",
|
|
1136
|
+
arg: toExpr(source),
|
|
1137
|
+
});
|
|
1138
|
+
},
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* ISO 연월 (해당 월의 1일)
|
|
1142
|
+
*
|
|
1143
|
+
* 해당 Date의 월 첫째 날 return
|
|
1144
|
+
*
|
|
1145
|
+
* @param source - DateOnly expression
|
|
1146
|
+
* @returns 월의 첫째 날
|
|
1147
|
+
*
|
|
1148
|
+
* @example
|
|
1149
|
+
* ```typescript
|
|
1150
|
+
* db.order().select((o) => ({
|
|
1151
|
+
* yearMonth: expr.isoYearMonth(o.orderDate),
|
|
1152
|
+
* }))
|
|
1153
|
+
* // 2024-01-15 → 2024-01-01
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
isoYearMonth<T extends DateOnly | undefined>(source: ExprUnit<T>): ExprUnit<T> {
|
|
1157
|
+
return new ExprUnit("DateOnly", {
|
|
1158
|
+
type: "isoYearMonth",
|
|
1159
|
+
arg: toExpr(source),
|
|
1160
|
+
});
|
|
1161
|
+
},
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Date 차이 계산 (DATEDIFF)
|
|
1165
|
+
*
|
|
1166
|
+
* @param separator - 단위 ("year", "month", "day", "hour", "minute", "second")
|
|
1167
|
+
* @param from - start Date
|
|
1168
|
+
* @param to - 끝 Date
|
|
1169
|
+
* @returns 차이 value (to - from)
|
|
1170
|
+
*
|
|
1171
|
+
* @example
|
|
1172
|
+
* ```typescript
|
|
1173
|
+
* db.user().select((u) => ({
|
|
1174
|
+
* age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
|
|
1175
|
+
* }))
|
|
1176
|
+
* // SELECT DATEDIFF(year, birthDate, '2024-01-15') AS age
|
|
1177
|
+
* ```
|
|
1178
|
+
*/
|
|
1179
|
+
dateDiff<T extends DateTime | DateOnly | Time | undefined>(
|
|
1180
|
+
separator: DateSeparator,
|
|
1181
|
+
from: ExprInput<T>,
|
|
1182
|
+
to: ExprInput<T>,
|
|
1183
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1184
|
+
return new ExprUnit("number", {
|
|
1185
|
+
type: "dateDiff",
|
|
1186
|
+
separator,
|
|
1187
|
+
from: toExpr(from),
|
|
1188
|
+
to: toExpr(to),
|
|
1189
|
+
});
|
|
1190
|
+
},
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Date 더하기 (DATEADD)
|
|
1194
|
+
*
|
|
1195
|
+
* @param separator - 단위 ("year", "month", "day", "hour", "minute", "second")
|
|
1196
|
+
* @param source - 원본 Date
|
|
1197
|
+
* @param value - 더할 value (음수 가능)
|
|
1198
|
+
* @returns 계산된 Date
|
|
1199
|
+
*
|
|
1200
|
+
* @example
|
|
1201
|
+
* ```typescript
|
|
1202
|
+
* db.subscription().select((s) => ({
|
|
1203
|
+
* expiresAt: expr.dateAdd("month", s.startDate, 12),
|
|
1204
|
+
* }))
|
|
1205
|
+
* // SELECT DATEADD(month, 12, startDate) AS expiresAt
|
|
1206
|
+
* ```
|
|
1207
|
+
*/
|
|
1208
|
+
dateAdd<T extends DateTime | DateOnly | Time | undefined>(
|
|
1209
|
+
separator: DateSeparator,
|
|
1210
|
+
source: ExprUnit<T>,
|
|
1211
|
+
value: ExprInput<number>,
|
|
1212
|
+
): ExprUnit<T> {
|
|
1213
|
+
return new ExprUnit(source.dataType, {
|
|
1214
|
+
type: "dateAdd",
|
|
1215
|
+
separator,
|
|
1216
|
+
source: toExpr(source),
|
|
1217
|
+
value: toExpr(value),
|
|
1218
|
+
});
|
|
1219
|
+
},
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* Date 포맷 (DATE_FORMAT)
|
|
1223
|
+
*
|
|
1224
|
+
* DBMS별로 포맷 문자열 규칙이 다를 수 있음
|
|
1225
|
+
*
|
|
1226
|
+
* @param source - Date expression
|
|
1227
|
+
* @param format - 포맷 문자열 (예: "%Y-%m-%d")
|
|
1228
|
+
* @returns 포맷된 문자열 expression
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```typescript
|
|
1232
|
+
* db.order().select((o) => ({
|
|
1233
|
+
* orderDate: expr.formatDate(o.createdAt, "%Y-%m-%d"),
|
|
1234
|
+
* }))
|
|
1235
|
+
* // SELECT DATE_FORMAT(createdAt, '%Y-%m-%d') AS orderDate (MySQL)
|
|
1236
|
+
* // 2024-01-15 10:30:00 → "2024-01-15"
|
|
1237
|
+
* ```
|
|
1238
|
+
*/
|
|
1239
|
+
formatDate<T extends DateTime | DateOnly | Time | undefined>(
|
|
1240
|
+
source: ExprUnit<T>,
|
|
1241
|
+
format: string,
|
|
1242
|
+
): ExprUnit<T extends undefined ? undefined : string> {
|
|
1243
|
+
return new ExprUnit("string", {
|
|
1244
|
+
type: "formatDate",
|
|
1245
|
+
source: toExpr(source),
|
|
1246
|
+
format,
|
|
1247
|
+
});
|
|
1248
|
+
},
|
|
1249
|
+
|
|
1250
|
+
//#endregion
|
|
1251
|
+
|
|
1252
|
+
//#region ========== SELECT - Condition ==========
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* NULL 대체 (COALESCE/IFNULL)
|
|
1256
|
+
*
|
|
1257
|
+
* 첫 번째 non-null 값을 return. 마지막 인자가 non-nullable이면 결과도 non-nullable
|
|
1258
|
+
*
|
|
1259
|
+
* @param args - Inspect할 값들 (마지막은 Default value)
|
|
1260
|
+
* @returns 첫 번째 non-null value
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* ```typescript
|
|
1264
|
+
* db.user().select((u) => ({
|
|
1265
|
+
* displayName: expr.ifNull(u.nickname, u.name, "Guest"),
|
|
1266
|
+
* }))
|
|
1267
|
+
* // SELECT COALESCE(nickname, name, 'Guest') AS displayName
|
|
1268
|
+
* ```
|
|
1269
|
+
*/
|
|
1270
|
+
ifNull,
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* 특정 값이면 NULL return (NULLIF)
|
|
1274
|
+
*
|
|
1275
|
+
* source === value 이면 NULL return, 아니면 source return
|
|
1276
|
+
*
|
|
1277
|
+
* @param source - 원본 value
|
|
1278
|
+
* @param value - 비교할 value
|
|
1279
|
+
* @returns NULL 또는 원본 value
|
|
1280
|
+
*
|
|
1281
|
+
* @example
|
|
1282
|
+
* ```typescript
|
|
1283
|
+
* db.user().select((u) => ({
|
|
1284
|
+
* // 빈 문자열을 NULL로 Transform
|
|
1285
|
+
* bio: expr.nullIf(u.bio, ""),
|
|
1286
|
+
* }))
|
|
1287
|
+
* // SELECT NULLIF(bio, '') AS bio
|
|
1288
|
+
* ```
|
|
1289
|
+
*/
|
|
1290
|
+
nullIf<T extends ColumnPrimitive>(
|
|
1291
|
+
source: ExprUnit<T>,
|
|
1292
|
+
value: ExprInput<T>,
|
|
1293
|
+
): ExprUnit<T | undefined> {
|
|
1294
|
+
return new ExprUnit(source.dataType, {
|
|
1295
|
+
type: "nullIf",
|
|
1296
|
+
source: toExpr(source),
|
|
1297
|
+
value: toExpr(value),
|
|
1298
|
+
});
|
|
1299
|
+
},
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* WHERE 표현식을 boolean으로 Transform
|
|
1303
|
+
*
|
|
1304
|
+
* SELECT 절에서 condition 결과를 boolean column으로 사용할 때 사용
|
|
1305
|
+
*
|
|
1306
|
+
* @param condition - Transform할 condition
|
|
1307
|
+
* @returns boolean expression
|
|
1308
|
+
*
|
|
1309
|
+
* @example
|
|
1310
|
+
* ```typescript
|
|
1311
|
+
* db.user().select((u) => ({
|
|
1312
|
+
* isActive: expr.is(expr.eq(u.status, "active")),
|
|
1313
|
+
* }))
|
|
1314
|
+
* // SELECT (status <=> 'active') AS isActive
|
|
1315
|
+
* ```
|
|
1316
|
+
*/
|
|
1317
|
+
is(condition: WhereExprUnit): ExprUnit<boolean> {
|
|
1318
|
+
return new ExprUnit("boolean", {
|
|
1319
|
+
type: "is",
|
|
1320
|
+
condition: condition.expr,
|
|
1321
|
+
});
|
|
1322
|
+
},
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* CASE WHEN expression builder
|
|
1326
|
+
*
|
|
1327
|
+
* 체이닝 방식으로 condition 분기를 구성
|
|
1328
|
+
*
|
|
1329
|
+
* @returns SwitchExprBuilder instance
|
|
1330
|
+
*
|
|
1331
|
+
* @example
|
|
1332
|
+
* ```typescript
|
|
1333
|
+
* db.user().select((u) => ({
|
|
1334
|
+
* grade: expr.switch<string>()
|
|
1335
|
+
* .case(expr.gte(u.score, 90), "A")
|
|
1336
|
+
* .case(expr.gte(u.score, 80), "B")
|
|
1337
|
+
* .case(expr.gte(u.score, 70), "C")
|
|
1338
|
+
* .default("F"),
|
|
1339
|
+
* }))
|
|
1340
|
+
* // SELECT CASE WHEN score >= 90 THEN 'A' ... ELSE 'F' END AS grade
|
|
1341
|
+
* ```
|
|
1342
|
+
*/
|
|
1343
|
+
switch<T extends ColumnPrimitive>(): SwitchExprBuilder<T> {
|
|
1344
|
+
return createSwitchBuilder<T>();
|
|
1345
|
+
},
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* 단순 IF condition (삼항 operator)
|
|
1349
|
+
*
|
|
1350
|
+
* @param condition - Condition
|
|
1351
|
+
* @param then - Condition이 참일 때 value
|
|
1352
|
+
* @param else_ - Condition이 거짓일 때 value
|
|
1353
|
+
* @returns 조건부 value expression
|
|
1354
|
+
*
|
|
1355
|
+
* @example
|
|
1356
|
+
* ```typescript
|
|
1357
|
+
* db.user().select((u) => ({
|
|
1358
|
+
* type: expr.if(expr.gte(u.age, 18), "adult", "minor"),
|
|
1359
|
+
* }))
|
|
1360
|
+
* // SELECT IF(age >= 18, 'adult', 'minor') AS type
|
|
1361
|
+
* ```
|
|
1362
|
+
*/
|
|
1363
|
+
if<T extends ColumnPrimitive>(
|
|
1364
|
+
condition: WhereExprUnit,
|
|
1365
|
+
then: ExprInput<T>,
|
|
1366
|
+
else_: ExprInput<T>,
|
|
1367
|
+
): ExprUnit<T> {
|
|
1368
|
+
const allValues = [then, else_];
|
|
1369
|
+
// 1. ExprUnit에서 dataType 찾기
|
|
1370
|
+
const exprUnit = allValues.find((v): v is ExprUnit<T> => v instanceof ExprUnit);
|
|
1371
|
+
if (exprUnit) {
|
|
1372
|
+
return new ExprUnit(exprUnit.dataType, {
|
|
1373
|
+
type: "if",
|
|
1374
|
+
condition: condition.expr,
|
|
1375
|
+
then: toExpr(then),
|
|
1376
|
+
else: toExpr(else_),
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// 2. non-null 리터럴에서 추론
|
|
1381
|
+
const nonNullLiteral = allValues.find((v) => v != null) as ColumnPrimitive;
|
|
1382
|
+
if (nonNullLiteral == null) {
|
|
1383
|
+
throw new Error("At least one of if's then/else must be non-null.");
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
return new ExprUnit(inferColumnPrimitiveStr(nonNullLiteral), {
|
|
1387
|
+
type: "if",
|
|
1388
|
+
condition: condition.expr,
|
|
1389
|
+
then: toExpr(then),
|
|
1390
|
+
else: toExpr(else_),
|
|
1391
|
+
});
|
|
1392
|
+
},
|
|
1393
|
+
|
|
1394
|
+
//#endregion
|
|
1395
|
+
|
|
1396
|
+
//#region ========== SELECT - Aggregate ==========
|
|
1397
|
+
// SUM, AVG, MAX등의 집계는 모든 값이 NULL이거나 행이 없을 때만 NULL return (값이 NULL인 행은 무시함)
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* row 수 카운트 (COUNT)
|
|
1401
|
+
*
|
|
1402
|
+
* @param arg - 카운트할 column (생략 시 전체 row 수)
|
|
1403
|
+
* @param distinct - true면 중복 Remove
|
|
1404
|
+
* @returns row 수
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* ```typescript
|
|
1408
|
+
* // 전체 row 수
|
|
1409
|
+
* db.user().select(() => ({ total: expr.count() }))
|
|
1410
|
+
*
|
|
1411
|
+
* // 중복 Remove 카운트
|
|
1412
|
+
* db.order().select((o) => ({
|
|
1413
|
+
* uniqueCustomers: expr.count(o.customerId, true),
|
|
1414
|
+
* }))
|
|
1415
|
+
* ```
|
|
1416
|
+
*/
|
|
1417
|
+
count(arg?: ExprUnit<ColumnPrimitive>, distinct?: boolean): ExprUnit<number> {
|
|
1418
|
+
return new ExprUnit("number", {
|
|
1419
|
+
type: "count",
|
|
1420
|
+
arg: arg != null ? toExpr(arg) : undefined,
|
|
1421
|
+
distinct,
|
|
1422
|
+
});
|
|
1423
|
+
},
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* 합계 (SUM)
|
|
1427
|
+
*
|
|
1428
|
+
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL return
|
|
1429
|
+
*
|
|
1430
|
+
* @param arg - 합계를 구할 Number column
|
|
1431
|
+
* @returns 합계 (또는 NULL)
|
|
1432
|
+
*
|
|
1433
|
+
* @example
|
|
1434
|
+
* ```typescript
|
|
1435
|
+
* db.order().groupBy((o) => o.userId).select((o) => ({
|
|
1436
|
+
* userId: o.userId,
|
|
1437
|
+
* totalAmount: expr.sum(o.amount),
|
|
1438
|
+
* }))
|
|
1439
|
+
* ```
|
|
1440
|
+
*/
|
|
1441
|
+
sum(arg: ExprUnit<number | undefined>): ExprUnit<number | undefined> {
|
|
1442
|
+
return new ExprUnit("number", {
|
|
1443
|
+
type: "sum",
|
|
1444
|
+
arg: toExpr(arg),
|
|
1445
|
+
});
|
|
1446
|
+
},
|
|
1447
|
+
|
|
1448
|
+
/**
|
|
1449
|
+
* 평균 (AVG)
|
|
1450
|
+
*
|
|
1451
|
+
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL return
|
|
1452
|
+
*
|
|
1453
|
+
* @param arg - 평균을 구할 Number column
|
|
1454
|
+
* @returns 평균 (또는 NULL)
|
|
1455
|
+
*
|
|
1456
|
+
* @example
|
|
1457
|
+
* ```typescript
|
|
1458
|
+
* db.product().groupBy((p) => p.categoryId).select((p) => ({
|
|
1459
|
+
* categoryId: p.categoryId,
|
|
1460
|
+
* avgPrice: expr.avg(p.price),
|
|
1461
|
+
* }))
|
|
1462
|
+
* ```
|
|
1463
|
+
*/
|
|
1464
|
+
avg(arg: ExprUnit<number | undefined>): ExprUnit<number | undefined> {
|
|
1465
|
+
return new ExprUnit("number", {
|
|
1466
|
+
type: "avg",
|
|
1467
|
+
arg: toExpr(arg),
|
|
1468
|
+
});
|
|
1469
|
+
},
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* 최대값 (MAX)
|
|
1473
|
+
*
|
|
1474
|
+
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL return
|
|
1475
|
+
*
|
|
1476
|
+
* @param arg - 최대값을 구할 column
|
|
1477
|
+
* @returns 최대값 (또는 NULL)
|
|
1478
|
+
*
|
|
1479
|
+
* @example
|
|
1480
|
+
* ```typescript
|
|
1481
|
+
* db.order().groupBy((o) => o.userId).select((o) => ({
|
|
1482
|
+
* userId: o.userId,
|
|
1483
|
+
* lastOrderDate: expr.max(o.createdAt),
|
|
1484
|
+
* }))
|
|
1485
|
+
* ```
|
|
1486
|
+
*/
|
|
1487
|
+
max<T extends ColumnPrimitive>(arg: ExprUnit<T>): ExprUnit<T | undefined> {
|
|
1488
|
+
return new ExprUnit(arg.dataType, {
|
|
1489
|
+
type: "max",
|
|
1490
|
+
arg: toExpr(arg),
|
|
1491
|
+
});
|
|
1492
|
+
},
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* 최소값 (MIN)
|
|
1496
|
+
*
|
|
1497
|
+
* NULL 값은 무시됨. 모든 값이 NULL이면 NULL return
|
|
1498
|
+
*
|
|
1499
|
+
* @param arg - 최소값을 구할 column
|
|
1500
|
+
* @returns 최소값 (또는 NULL)
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* ```typescript
|
|
1504
|
+
* db.product().groupBy((p) => p.categoryId).select((p) => ({
|
|
1505
|
+
* categoryId: p.categoryId,
|
|
1506
|
+
* minPrice: expr.min(p.price),
|
|
1507
|
+
* }))
|
|
1508
|
+
* ```
|
|
1509
|
+
*/
|
|
1510
|
+
min<T extends ColumnPrimitive>(arg: ExprUnit<T>): ExprUnit<T | undefined> {
|
|
1511
|
+
return new ExprUnit(arg.dataType, {
|
|
1512
|
+
type: "min",
|
|
1513
|
+
arg: toExpr(arg),
|
|
1514
|
+
});
|
|
1515
|
+
},
|
|
1516
|
+
|
|
1517
|
+
//#endregion
|
|
1518
|
+
|
|
1519
|
+
//#region ========== SELECT - Other ==========
|
|
1520
|
+
|
|
1521
|
+
/**
|
|
1522
|
+
* 여러 value 중 최대값 (GREATEST)
|
|
1523
|
+
*
|
|
1524
|
+
* @param args - 비교할 값들
|
|
1525
|
+
* @returns 최대값
|
|
1526
|
+
*
|
|
1527
|
+
* @example
|
|
1528
|
+
* ```typescript
|
|
1529
|
+
* db.product().select((p) => ({
|
|
1530
|
+
* effectivePrice: expr.greatest(p.price, p.minPrice),
|
|
1531
|
+
* }))
|
|
1532
|
+
* // SELECT GREATEST(price, minPrice) AS effectivePrice
|
|
1533
|
+
* ```
|
|
1534
|
+
*/
|
|
1535
|
+
greatest<T extends ColumnPrimitive>(...args: ExprInput<T>[]): ExprUnit<T> {
|
|
1536
|
+
return new ExprUnit(findDataType(args), {
|
|
1537
|
+
type: "greatest",
|
|
1538
|
+
args: args.map((a) => toExpr(a)),
|
|
1539
|
+
});
|
|
1540
|
+
},
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* 여러 value 중 최소값 (LEAST)
|
|
1544
|
+
*
|
|
1545
|
+
* @param args - 비교할 값들
|
|
1546
|
+
* @returns 최소값
|
|
1547
|
+
*
|
|
1548
|
+
* @example
|
|
1549
|
+
* ```typescript
|
|
1550
|
+
* db.product().select((p) => ({
|
|
1551
|
+
* effectivePrice: expr.least(p.price, p.maxDiscount),
|
|
1552
|
+
* }))
|
|
1553
|
+
* // SELECT LEAST(price, maxDiscount) AS effectivePrice
|
|
1554
|
+
* ```
|
|
1555
|
+
*/
|
|
1556
|
+
least<T extends ColumnPrimitive>(...args: ExprInput<T>[]): ExprUnit<T> {
|
|
1557
|
+
return new ExprUnit(findDataType(args), {
|
|
1558
|
+
type: "least",
|
|
1559
|
+
args: args.map((a) => toExpr(a)),
|
|
1560
|
+
});
|
|
1561
|
+
},
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* row 번호 (ROW_NUMBER 없이 전체 행에 대한 순번)
|
|
1565
|
+
*
|
|
1566
|
+
* @returns row 번호 (1부터 start)
|
|
1567
|
+
*
|
|
1568
|
+
* @example
|
|
1569
|
+
* ```typescript
|
|
1570
|
+
* db.user().select((u) => ({
|
|
1571
|
+
* rowNum: expr.rowNum(),
|
|
1572
|
+
* name: u.name,
|
|
1573
|
+
* }))
|
|
1574
|
+
* ```
|
|
1575
|
+
*/
|
|
1576
|
+
rowNum(): ExprUnit<number> {
|
|
1577
|
+
return new ExprUnit("number", {
|
|
1578
|
+
type: "rowNum",
|
|
1579
|
+
});
|
|
1580
|
+
},
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* 난수 Generate (RAND/RANDOM)
|
|
1584
|
+
*
|
|
1585
|
+
* 0~1 사이의 난수 return. ORDER BY에서 랜덤 정렬용으로 주로 사용
|
|
1586
|
+
*
|
|
1587
|
+
* @returns 0~1 사이의 난수
|
|
1588
|
+
*
|
|
1589
|
+
* @example
|
|
1590
|
+
* ```typescript
|
|
1591
|
+
* // 랜덤 sorting
|
|
1592
|
+
* db.user().orderBy(() => expr.random()).limit(10)
|
|
1593
|
+
* ```
|
|
1594
|
+
*/
|
|
1595
|
+
random(): ExprUnit<number> {
|
|
1596
|
+
return new ExprUnit("number", {
|
|
1597
|
+
type: "random",
|
|
1598
|
+
});
|
|
1599
|
+
},
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* type transformation (CAST)
|
|
1603
|
+
*
|
|
1604
|
+
* @param source - Transform할 expression
|
|
1605
|
+
* @param targetType - 대상 data type
|
|
1606
|
+
* @returns Transform된 expression
|
|
1607
|
+
*
|
|
1608
|
+
* @example
|
|
1609
|
+
* ```typescript
|
|
1610
|
+
* db.order().select((o) => ({
|
|
1611
|
+
* idStr: expr.cast(o.id, { type: "varchar", length: 20 }),
|
|
1612
|
+
* }))
|
|
1613
|
+
* // SELECT CAST(id AS VARCHAR(20)) AS idStr
|
|
1614
|
+
* ```
|
|
1615
|
+
*/
|
|
1616
|
+
cast<T extends ColumnPrimitive, TDataType extends DataType>(
|
|
1617
|
+
source: ExprUnit<T>,
|
|
1618
|
+
targetType: TDataType,
|
|
1619
|
+
): ExprUnit<T extends undefined ? undefined : InferColumnPrimitiveFromDataType<TDataType>> {
|
|
1620
|
+
return new ExprUnit(dataTypeStrToColumnPrimitiveStr[targetType.type], {
|
|
1621
|
+
type: "cast",
|
|
1622
|
+
source: toExpr(source),
|
|
1623
|
+
targetType,
|
|
1624
|
+
});
|
|
1625
|
+
},
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* 스칼라 Subquery - SELECT 절에서 단일 value return Subquery
|
|
1629
|
+
*
|
|
1630
|
+
* Subquery는 반드시 단일 row, 단일 column을 반환해야 함
|
|
1631
|
+
*
|
|
1632
|
+
* @param dataType - Data type of the returned value
|
|
1633
|
+
* @param queryable - 스칼라 값을 반환하는 Queryable
|
|
1634
|
+
* @returns Subquery result expression
|
|
1635
|
+
*
|
|
1636
|
+
* @example
|
|
1637
|
+
* ```typescript
|
|
1638
|
+
* db.user().select((u) => ({
|
|
1639
|
+
* id: u.id,
|
|
1640
|
+
* postCount: expr.subquery(
|
|
1641
|
+
* "number",
|
|
1642
|
+
* db.post()
|
|
1643
|
+
* .where((p) => [expr.eq(p.userId, u.id)])
|
|
1644
|
+
* .select(() => ({ cnt: expr.count() }))
|
|
1645
|
+
* ),
|
|
1646
|
+
* }))
|
|
1647
|
+
* // SELECT id, (SELECT COUNT(*) FROM Post WHERE userId = User.id) AS postCount
|
|
1648
|
+
* ```
|
|
1649
|
+
*/
|
|
1650
|
+
subquery<TStr extends ColumnPrimitiveStr>(
|
|
1651
|
+
dataType: TStr,
|
|
1652
|
+
queryable: { getSelectQueryDef(): SelectQueryDef },
|
|
1653
|
+
): ExprUnit<ColumnPrimitiveMap[TStr] | undefined> {
|
|
1654
|
+
return new ExprUnit(dataType, {
|
|
1655
|
+
type: "subquery",
|
|
1656
|
+
queryDef: queryable.getSelectQueryDef(),
|
|
1657
|
+
});
|
|
1658
|
+
},
|
|
1659
|
+
|
|
1660
|
+
//#endregion
|
|
1661
|
+
|
|
1662
|
+
//#region ========== SELECT - Window Functions ==========
|
|
1663
|
+
|
|
1664
|
+
/**
|
|
1665
|
+
* ROW_NUMBER() - 파티션 내 row 번호
|
|
1666
|
+
*
|
|
1667
|
+
* 각 파티션 내에서 1부터 시작하는 sequential 번호 부여
|
|
1668
|
+
*
|
|
1669
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1670
|
+
* @returns row 번호 (1부터 start)
|
|
1671
|
+
*
|
|
1672
|
+
* @example
|
|
1673
|
+
* ```typescript
|
|
1674
|
+
* db.order().select((o) => ({
|
|
1675
|
+
* ...o,
|
|
1676
|
+
* rowNum: expr.rowNumber({
|
|
1677
|
+
* partitionBy: [o.userId],
|
|
1678
|
+
* orderBy: [[o.createdAt, "DESC"]],
|
|
1679
|
+
* }),
|
|
1680
|
+
* }))
|
|
1681
|
+
* // SELECT *, ROW_NUMBER() OVER (PARTITION BY userId ORDER BY createdAt DESC)
|
|
1682
|
+
* ```
|
|
1683
|
+
*/
|
|
1684
|
+
rowNumber(spec: WinSpecInput): ExprUnit<number> {
|
|
1685
|
+
return new ExprUnit("number", {
|
|
1686
|
+
type: "window",
|
|
1687
|
+
fn: { type: "rowNumber" },
|
|
1688
|
+
spec: toWinSpec(spec),
|
|
1689
|
+
});
|
|
1690
|
+
},
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* RANK() - 파티션 내 순위 (동점 시 같은 순위, 다음 순위 건너뜀)
|
|
1694
|
+
*
|
|
1695
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1696
|
+
* @returns 순위 (동점 후 건너뜀: 1, 1, 3)
|
|
1697
|
+
*
|
|
1698
|
+
* @example
|
|
1699
|
+
* ```typescript
|
|
1700
|
+
* db.student().select((s) => ({
|
|
1701
|
+
* name: s.name,
|
|
1702
|
+
* rank: expr.rank({
|
|
1703
|
+
* orderBy: [[s.score, "DESC"]],
|
|
1704
|
+
* }),
|
|
1705
|
+
* }))
|
|
1706
|
+
* ```
|
|
1707
|
+
*/
|
|
1708
|
+
rank(spec: WinSpecInput): ExprUnit<number> {
|
|
1709
|
+
return new ExprUnit("number", {
|
|
1710
|
+
type: "window",
|
|
1711
|
+
fn: { type: "rank" },
|
|
1712
|
+
spec: toWinSpec(spec),
|
|
1713
|
+
});
|
|
1714
|
+
},
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* DENSE_RANK() - 파티션 내 밀집 순위 (동점 시 같은 순위, 다음 순위 유지)
|
|
1718
|
+
*
|
|
1719
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1720
|
+
* @returns 밀집 순위 (동점 후 연속: 1, 1, 2)
|
|
1721
|
+
*
|
|
1722
|
+
* @example
|
|
1723
|
+
* ```typescript
|
|
1724
|
+
* db.student().select((s) => ({
|
|
1725
|
+
* name: s.name,
|
|
1726
|
+
* denseRank: expr.denseRank({
|
|
1727
|
+
* orderBy: [[s.score, "DESC"]],
|
|
1728
|
+
* }),
|
|
1729
|
+
* }))
|
|
1730
|
+
* ```
|
|
1731
|
+
*/
|
|
1732
|
+
denseRank(spec: WinSpecInput): ExprUnit<number> {
|
|
1733
|
+
return new ExprUnit("number", {
|
|
1734
|
+
type: "window",
|
|
1735
|
+
fn: { type: "denseRank" },
|
|
1736
|
+
spec: toWinSpec(spec),
|
|
1737
|
+
});
|
|
1738
|
+
},
|
|
1739
|
+
|
|
1740
|
+
/**
|
|
1741
|
+
* NTILE(n) - 파티션을 n개 그룹으로 split
|
|
1742
|
+
*
|
|
1743
|
+
* @param n - 분할할 그룹 수
|
|
1744
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1745
|
+
* @returns 그룹 번호 (1 ~ n)
|
|
1746
|
+
*
|
|
1747
|
+
* @example
|
|
1748
|
+
* ```typescript
|
|
1749
|
+
* // 상위 25%를 찾기 위한 사분위 split
|
|
1750
|
+
* db.user().select((u) => ({
|
|
1751
|
+
* name: u.name,
|
|
1752
|
+
* quartile: expr.ntile(4, {
|
|
1753
|
+
* orderBy: [[u.score, "DESC"]],
|
|
1754
|
+
* }),
|
|
1755
|
+
* }))
|
|
1756
|
+
* ```
|
|
1757
|
+
*/
|
|
1758
|
+
ntile(n: number, spec: WinSpecInput): ExprUnit<number> {
|
|
1759
|
+
return new ExprUnit("number", {
|
|
1760
|
+
type: "window",
|
|
1761
|
+
fn: { type: "ntile", n },
|
|
1762
|
+
spec: toWinSpec(spec),
|
|
1763
|
+
});
|
|
1764
|
+
},
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* LAG() - 이전 행의 value 참조
|
|
1768
|
+
*
|
|
1769
|
+
* @param column - column to reference
|
|
1770
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1771
|
+
* @param options - offset (Basic 1), default (이전 행이 없을 때 Default value)
|
|
1772
|
+
* @returns 이전 행의 value (또는 Default value/NULL)
|
|
1773
|
+
*
|
|
1774
|
+
* @example
|
|
1775
|
+
* ```typescript
|
|
1776
|
+
* db.stock().select((s) => ({
|
|
1777
|
+
* date: s.date,
|
|
1778
|
+
* price: s.price,
|
|
1779
|
+
* prevPrice: expr.lag(s.price, {
|
|
1780
|
+
* partitionBy: [s.symbol],
|
|
1781
|
+
* orderBy: [[s.date, "ASC"]],
|
|
1782
|
+
* }),
|
|
1783
|
+
* }))
|
|
1784
|
+
* ```
|
|
1785
|
+
*/
|
|
1786
|
+
lag<T extends ColumnPrimitive>(
|
|
1787
|
+
column: ExprUnit<T>,
|
|
1788
|
+
spec: WinSpecInput,
|
|
1789
|
+
options?: { offset?: number; default?: ExprInput<T> },
|
|
1790
|
+
): ExprUnit<T | undefined> {
|
|
1791
|
+
return new ExprUnit(column.dataType, {
|
|
1792
|
+
type: "window",
|
|
1793
|
+
fn: {
|
|
1794
|
+
type: "lag",
|
|
1795
|
+
column: toExpr(column),
|
|
1796
|
+
offset: options?.offset,
|
|
1797
|
+
default: options?.default != null ? toExpr(options.default) : undefined,
|
|
1798
|
+
},
|
|
1799
|
+
spec: toWinSpec(spec),
|
|
1800
|
+
});
|
|
1801
|
+
},
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* LEAD() - 다음 행의 value 참조
|
|
1805
|
+
*
|
|
1806
|
+
* @param column - column to reference
|
|
1807
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1808
|
+
* @param options - offset (Basic 1), default (다음 행이 없을 때 Default value)
|
|
1809
|
+
* @returns 다음 행의 value (또는 Default value/NULL)
|
|
1810
|
+
*
|
|
1811
|
+
* @example
|
|
1812
|
+
* ```typescript
|
|
1813
|
+
* db.stock().select((s) => ({
|
|
1814
|
+
* date: s.date,
|
|
1815
|
+
* price: s.price,
|
|
1816
|
+
* nextPrice: expr.lead(s.price, {
|
|
1817
|
+
* partitionBy: [s.symbol],
|
|
1818
|
+
* orderBy: [[s.date, "ASC"]],
|
|
1819
|
+
* }),
|
|
1820
|
+
* }))
|
|
1821
|
+
* ```
|
|
1822
|
+
*/
|
|
1823
|
+
lead<T extends ColumnPrimitive>(
|
|
1824
|
+
column: ExprUnit<T>,
|
|
1825
|
+
spec: WinSpecInput,
|
|
1826
|
+
options?: { offset?: number; default?: ExprInput<T> },
|
|
1827
|
+
): ExprUnit<T | undefined> {
|
|
1828
|
+
return new ExprUnit(column.dataType, {
|
|
1829
|
+
type: "window",
|
|
1830
|
+
fn: {
|
|
1831
|
+
type: "lead",
|
|
1832
|
+
column: toExpr(column),
|
|
1833
|
+
offset: options?.offset,
|
|
1834
|
+
default: options?.default != null ? toExpr(options.default) : undefined,
|
|
1835
|
+
},
|
|
1836
|
+
spec: toWinSpec(spec),
|
|
1837
|
+
});
|
|
1838
|
+
},
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* FIRST_VALUE() - 파티션/프레임의 첫 번째 value
|
|
1842
|
+
*
|
|
1843
|
+
* @param column - column to reference
|
|
1844
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1845
|
+
* @returns 첫 번째 value
|
|
1846
|
+
*
|
|
1847
|
+
* @example
|
|
1848
|
+
* ```typescript
|
|
1849
|
+
* db.order().select((o) => ({
|
|
1850
|
+
* ...o,
|
|
1851
|
+
* firstOrderAmount: expr.firstValue(o.amount, {
|
|
1852
|
+
* partitionBy: [o.userId],
|
|
1853
|
+
* orderBy: [[o.createdAt, "ASC"]],
|
|
1854
|
+
* }),
|
|
1855
|
+
* }))
|
|
1856
|
+
* ```
|
|
1857
|
+
*/
|
|
1858
|
+
firstValue<T extends ColumnPrimitive>(
|
|
1859
|
+
column: ExprUnit<T>,
|
|
1860
|
+
spec: WinSpecInput,
|
|
1861
|
+
): ExprUnit<T | undefined> {
|
|
1862
|
+
return new ExprUnit(column.dataType, {
|
|
1863
|
+
type: "window",
|
|
1864
|
+
fn: { type: "firstValue", column: toExpr(column) },
|
|
1865
|
+
spec: toWinSpec(spec),
|
|
1866
|
+
});
|
|
1867
|
+
},
|
|
1868
|
+
|
|
1869
|
+
/**
|
|
1870
|
+
* LAST_VALUE() - 파티션/프레임의 마지막 value
|
|
1871
|
+
*
|
|
1872
|
+
* @param column - column to reference
|
|
1873
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1874
|
+
* @returns 마지막 value
|
|
1875
|
+
*
|
|
1876
|
+
* @example
|
|
1877
|
+
* ```typescript
|
|
1878
|
+
* db.order().select((o) => ({
|
|
1879
|
+
* ...o,
|
|
1880
|
+
* lastOrderAmount: expr.lastValue(o.amount, {
|
|
1881
|
+
* partitionBy: [o.userId],
|
|
1882
|
+
* orderBy: [[o.createdAt, "ASC"]],
|
|
1883
|
+
* }),
|
|
1884
|
+
* }))
|
|
1885
|
+
* ```
|
|
1886
|
+
*/
|
|
1887
|
+
lastValue<T extends ColumnPrimitive>(
|
|
1888
|
+
column: ExprUnit<T>,
|
|
1889
|
+
spec: WinSpecInput,
|
|
1890
|
+
): ExprUnit<T | undefined> {
|
|
1891
|
+
return new ExprUnit(column.dataType, {
|
|
1892
|
+
type: "window",
|
|
1893
|
+
fn: { type: "lastValue", column: toExpr(column) },
|
|
1894
|
+
spec: toWinSpec(spec),
|
|
1895
|
+
});
|
|
1896
|
+
},
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* SUM() OVER - Window 합계
|
|
1900
|
+
*
|
|
1901
|
+
* @param column - 합계를 구할 column
|
|
1902
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1903
|
+
* @returns Window 내 합계
|
|
1904
|
+
*
|
|
1905
|
+
* @example
|
|
1906
|
+
* ```typescript
|
|
1907
|
+
* // 누적 합계
|
|
1908
|
+
* db.order().select((o) => ({
|
|
1909
|
+
* ...o,
|
|
1910
|
+
* runningTotal: expr.sumOver(o.amount, {
|
|
1911
|
+
* partitionBy: [o.userId],
|
|
1912
|
+
* orderBy: [[o.createdAt, "ASC"]],
|
|
1913
|
+
* }),
|
|
1914
|
+
* }))
|
|
1915
|
+
* ```
|
|
1916
|
+
*/
|
|
1917
|
+
sumOver(column: ExprUnit<number | undefined>, spec: WinSpecInput): ExprUnit<number | undefined> {
|
|
1918
|
+
return new ExprUnit("number", {
|
|
1919
|
+
type: "window",
|
|
1920
|
+
fn: { type: "sum", column: toExpr(column) },
|
|
1921
|
+
spec: toWinSpec(spec),
|
|
1922
|
+
});
|
|
1923
|
+
},
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* AVG() OVER - Window 평균
|
|
1927
|
+
*
|
|
1928
|
+
* @param column - 평균을 구할 column
|
|
1929
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1930
|
+
* @returns Window 내 평균
|
|
1931
|
+
*
|
|
1932
|
+
* @example
|
|
1933
|
+
* ```typescript
|
|
1934
|
+
* // move 평균
|
|
1935
|
+
* db.stock().select((s) => ({
|
|
1936
|
+
* ...s,
|
|
1937
|
+
* movingAvg: expr.avgOver(s.price, {
|
|
1938
|
+
* partitionBy: [s.symbol],
|
|
1939
|
+
* orderBy: [[s.date, "ASC"]],
|
|
1940
|
+
* }),
|
|
1941
|
+
* }))
|
|
1942
|
+
* ```
|
|
1943
|
+
*/
|
|
1944
|
+
avgOver(column: ExprUnit<number | undefined>, spec: WinSpecInput): ExprUnit<number | undefined> {
|
|
1945
|
+
return new ExprUnit("number", {
|
|
1946
|
+
type: "window",
|
|
1947
|
+
fn: { type: "avg", column: toExpr(column) },
|
|
1948
|
+
spec: toWinSpec(spec),
|
|
1949
|
+
});
|
|
1950
|
+
},
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* COUNT() OVER - Window 카운트
|
|
1954
|
+
*
|
|
1955
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1956
|
+
* @param column - 카운트할 column (생략 시 전체 row 수)
|
|
1957
|
+
* @returns Window 내 row 수
|
|
1958
|
+
*
|
|
1959
|
+
* @example
|
|
1960
|
+
* ```typescript
|
|
1961
|
+
* db.order().select((o) => ({
|
|
1962
|
+
* ...o,
|
|
1963
|
+
* totalOrdersPerUser: expr.countOver({
|
|
1964
|
+
* partitionBy: [o.userId],
|
|
1965
|
+
* }),
|
|
1966
|
+
* }))
|
|
1967
|
+
* ```
|
|
1968
|
+
*/
|
|
1969
|
+
countOver(spec: WinSpecInput, column?: ExprUnit<ColumnPrimitive>): ExprUnit<number> {
|
|
1970
|
+
return new ExprUnit("number", {
|
|
1971
|
+
type: "window",
|
|
1972
|
+
fn: { type: "count", column: column != null ? toExpr(column) : undefined },
|
|
1973
|
+
spec: toWinSpec(spec),
|
|
1974
|
+
});
|
|
1975
|
+
},
|
|
1976
|
+
|
|
1977
|
+
/**
|
|
1978
|
+
* MIN() OVER - Window 최소값
|
|
1979
|
+
*
|
|
1980
|
+
* @param column - 최소값을 구할 column
|
|
1981
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
1982
|
+
* @returns Window 내 최소값
|
|
1983
|
+
*
|
|
1984
|
+
* @example
|
|
1985
|
+
* ```typescript
|
|
1986
|
+
* db.stock().select((s) => ({
|
|
1987
|
+
* ...s,
|
|
1988
|
+
* minPriceInPeriod: expr.minOver(s.price, {
|
|
1989
|
+
* partitionBy: [s.symbol],
|
|
1990
|
+
* }),
|
|
1991
|
+
* }))
|
|
1992
|
+
* ```
|
|
1993
|
+
*/
|
|
1994
|
+
minOver<T extends ColumnPrimitive>(
|
|
1995
|
+
column: ExprUnit<T>,
|
|
1996
|
+
spec: WinSpecInput,
|
|
1997
|
+
): ExprUnit<T | undefined> {
|
|
1998
|
+
return new ExprUnit(column.dataType, {
|
|
1999
|
+
type: "window",
|
|
2000
|
+
fn: { type: "min", column: toExpr(column) },
|
|
2001
|
+
spec: toWinSpec(spec),
|
|
2002
|
+
});
|
|
2003
|
+
},
|
|
2004
|
+
|
|
2005
|
+
/**
|
|
2006
|
+
* MAX() OVER - Window 최대값
|
|
2007
|
+
*
|
|
2008
|
+
* @param column - 최대값을 구할 column
|
|
2009
|
+
* @param spec - Window spec (partitionBy, orderBy)
|
|
2010
|
+
* @returns Window 내 최대값
|
|
2011
|
+
*
|
|
2012
|
+
* @example
|
|
2013
|
+
* ```typescript
|
|
2014
|
+
* db.stock().select((s) => ({
|
|
2015
|
+
* ...s,
|
|
2016
|
+
* maxPriceInPeriod: expr.maxOver(s.price, {
|
|
2017
|
+
* partitionBy: [s.symbol],
|
|
2018
|
+
* }),
|
|
2019
|
+
* }))
|
|
2020
|
+
* ```
|
|
2021
|
+
*/
|
|
2022
|
+
maxOver<T extends ColumnPrimitive>(
|
|
2023
|
+
column: ExprUnit<T>,
|
|
2024
|
+
spec: WinSpecInput,
|
|
2025
|
+
): ExprUnit<T | undefined> {
|
|
2026
|
+
return new ExprUnit(column.dataType, {
|
|
2027
|
+
type: "window",
|
|
2028
|
+
fn: { type: "max", column: toExpr(column) },
|
|
2029
|
+
spec: toWinSpec(spec),
|
|
2030
|
+
});
|
|
2031
|
+
},
|
|
2032
|
+
|
|
2033
|
+
//#endregion
|
|
2034
|
+
|
|
2035
|
+
//#region ========== Helper ==========
|
|
2036
|
+
|
|
2037
|
+
/**
|
|
2038
|
+
* ExprInput을 Expr로 Transform (내부용)
|
|
2039
|
+
*
|
|
2040
|
+
* @param value - Transform할 value
|
|
2041
|
+
* @returns Expr JSON AST
|
|
2042
|
+
*/
|
|
2043
|
+
toExpr(value: ExprInput<ColumnPrimitive>): Expr {
|
|
2044
|
+
return toExpr(value);
|
|
2045
|
+
},
|
|
2046
|
+
|
|
2047
|
+
//#endregion
|
|
2048
|
+
};
|
|
2049
|
+
|
|
2050
|
+
//#region ========== Internal Helpers ==========
|
|
2051
|
+
|
|
2052
|
+
// 여러 value 중 첫 번째 non-null return (COALESCE)
|
|
2053
|
+
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2054
|
+
...args: [
|
|
2055
|
+
ExprInput<TPrimitive | undefined>,
|
|
2056
|
+
...ExprInput<TPrimitive | undefined>[],
|
|
2057
|
+
ExprInput<NonNullable<TPrimitive>>,
|
|
2058
|
+
]
|
|
2059
|
+
): ExprUnit<NonNullable<TPrimitive>>;
|
|
2060
|
+
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2061
|
+
...args: ExprInput<TPrimitive>[]
|
|
2062
|
+
): ExprUnit<TPrimitive>;
|
|
2063
|
+
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2064
|
+
...args: ExprInput<TPrimitive>[]
|
|
2065
|
+
): ExprUnit<TPrimitive> {
|
|
2066
|
+
return new ExprUnit(findDataType(args), {
|
|
2067
|
+
type: "ifNull",
|
|
2068
|
+
args: args.map((a) => toExpr(a)),
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function createSwitchBuilder<TPrimitive extends ColumnPrimitive>(): SwitchExprBuilder<TPrimitive> {
|
|
2073
|
+
const cases: { when: WhereExpr; then: Expr }[] = [];
|
|
2074
|
+
const thenValues: ExprInput<TPrimitive>[] = []; // then 값들 저장
|
|
2075
|
+
|
|
2076
|
+
return {
|
|
2077
|
+
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): typeof this {
|
|
2078
|
+
cases.push({
|
|
2079
|
+
when: condition.expr,
|
|
2080
|
+
then: toExpr(then),
|
|
2081
|
+
});
|
|
2082
|
+
thenValues.push(then);
|
|
2083
|
+
return this;
|
|
2084
|
+
},
|
|
2085
|
+
default(value: ExprInput<TPrimitive>): ExprUnit<TPrimitive> {
|
|
2086
|
+
const allValues = [...thenValues, value];
|
|
2087
|
+
// 1. ExprUnit에서 dataType 찾기
|
|
2088
|
+
const exprUnit = allValues.find((v): v is ExprUnit<TPrimitive> => v instanceof ExprUnit);
|
|
2089
|
+
if (exprUnit) {
|
|
2090
|
+
return new ExprUnit(exprUnit.dataType, {
|
|
2091
|
+
type: "switch",
|
|
2092
|
+
cases,
|
|
2093
|
+
else: toExpr(value),
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// 2. non-null 리터럴에서 추론
|
|
2098
|
+
const nonNullLiteral = allValues.find((v) => v != null) as ColumnPrimitive;
|
|
2099
|
+
if (nonNullLiteral == null) {
|
|
2100
|
+
throw new Error("At least one of switch's case/default must be non-null.");
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
return new ExprUnit(inferColumnPrimitiveStr(nonNullLiteral), {
|
|
2104
|
+
type: "switch",
|
|
2105
|
+
cases,
|
|
2106
|
+
else: toExpr(value),
|
|
2107
|
+
});
|
|
2108
|
+
},
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
export function toExpr(value: ExprInput<ColumnPrimitive>): Expr {
|
|
2113
|
+
if (value instanceof ExprUnit) {
|
|
2114
|
+
return value.expr;
|
|
2115
|
+
}
|
|
2116
|
+
return { type: "value", value };
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
function findDataType<TPrimitive extends ColumnPrimitive>(
|
|
2120
|
+
args: ExprInput<TPrimitive>[],
|
|
2121
|
+
): ColumnPrimitiveStr {
|
|
2122
|
+
const exprUnit = args.find((a): a is ExprUnit<TPrimitive> => a instanceof ExprUnit);
|
|
2123
|
+
if (!exprUnit) {
|
|
2124
|
+
throw new Error("At least one of the arguments must be an ExprUnit.");
|
|
2125
|
+
}
|
|
2126
|
+
return exprUnit.dataType;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function toWinSpec(spec: WinSpecInput): WinSpec {
|
|
2130
|
+
const result: WinSpec = {};
|
|
2131
|
+
if (spec.partitionBy != null) {
|
|
2132
|
+
result.partitionBy = spec.partitionBy.map((e) => toExpr(e));
|
|
2133
|
+
}
|
|
2134
|
+
if (spec.orderBy != null) {
|
|
2135
|
+
result.orderBy = spec.orderBy.map(([e, dir]) => [toExpr(e), dir]);
|
|
2136
|
+
}
|
|
2137
|
+
return result;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
//#endregion
|