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