@simplysm/orm-common 13.0.69 → 13.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -1447
- package/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +9 -9
- package/dist/create-db-context.js.map +1 -1
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +2 -2
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/table-ddl.d.ts +24 -24
- package/dist/ddl/table-ddl.js +4 -4
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +3 -3
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.d.ts +160 -160
- package/dist/exec/queryable.js +119 -119
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.d.ts +37 -37
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr.d.ts +257 -257
- package/dist/expr/expr.js +265 -265
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
- package/dist/query-builder/base/expr-renderer-base.js +2 -2
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
- package/dist/query-builder/base/query-builder-base.d.ts +26 -26
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +22 -22
- package/dist/query-builder/base/query-builder-base.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +18 -18
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +17 -17
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +17 -17
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +79 -79
- package/dist/schema/factory/column-builder.js +42 -42
- package/dist/schema/factory/index-builder.d.ts +39 -39
- package/dist/schema/factory/index-builder.js +26 -26
- package/dist/schema/factory/relation-builder.d.ts +99 -99
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +38 -38
- package/dist/schema/procedure-builder.d.ts +49 -49
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +33 -33
- package/dist/schema/table-builder.d.ts +59 -59
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +43 -43
- package/dist/schema/view-builder.d.ts +49 -49
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +32 -32
- package/dist/types/column.d.ts +22 -22
- package/dist/types/column.js +1 -1
- package/dist/types/column.js.map +1 -1
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +59 -59
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +44 -44
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/utils/result-parser.d.ts +11 -11
- package/dist/utils/result-parser.js +3 -3
- package/dist/utils/result-parser.js.map +1 -1
- package/package.json +5 -5
- package/src/create-db-context.ts +20 -20
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +259 -259
- package/src/ddl/relation-ddl.ts +89 -89
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +189 -189
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +2033 -2033
- package/src/exec/search-parser.ts +57 -57
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +2140 -2140
- package/src/query-builder/base/expr-renderer-base.ts +237 -237
- package/src/query-builder/base/query-builder-base.ts +213 -213
- package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
- package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
- package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
- package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
- package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
- package/src/query-builder/query-builder.ts +19 -19
- package/src/schema/factory/column-builder.ts +423 -423
- package/src/schema/factory/index-builder.ts +164 -164
- package/src/schema/factory/relation-builder.ts +453 -453
- package/src/schema/procedure-builder.ts +232 -232
- package/src/schema/table-builder.ts +319 -319
- package/src/schema/view-builder.ts +221 -221
- package/src/types/column.ts +188 -188
- package/src/types/db.ts +208 -208
- package/src/types/expr.ts +697 -697
- package/src/types/query-def.ts +513 -513
- package/src/utils/result-parser.ts +458 -458
- package/tests/db-context/create-db-context.spec.ts +224 -0
- package/tests/db-context/define-db-context.spec.ts +68 -0
- package/tests/ddl/basic.expected.ts +341 -0
- package/tests/ddl/basic.spec.ts +714 -0
- package/tests/ddl/column-builder.expected.ts +310 -0
- package/tests/ddl/column-builder.spec.ts +637 -0
- package/tests/ddl/index-builder.expected.ts +38 -0
- package/tests/ddl/index-builder.spec.ts +202 -0
- package/tests/ddl/procedure-builder.expected.ts +52 -0
- package/tests/ddl/procedure-builder.spec.ts +234 -0
- package/tests/ddl/relation-builder.expected.ts +36 -0
- package/tests/ddl/relation-builder.spec.ts +372 -0
- package/tests/ddl/table-builder.expected.ts +113 -0
- package/tests/ddl/table-builder.spec.ts +433 -0
- package/tests/ddl/view-builder.expected.ts +38 -0
- package/tests/ddl/view-builder.spec.ts +176 -0
- package/tests/dml/delete.expected.ts +96 -0
- package/tests/dml/delete.spec.ts +160 -0
- package/tests/dml/insert.expected.ts +192 -0
- package/tests/dml/insert.spec.ts +288 -0
- package/tests/dml/update.expected.ts +176 -0
- package/tests/dml/update.spec.ts +318 -0
- package/tests/dml/upsert.expected.ts +215 -0
- package/tests/dml/upsert.spec.ts +242 -0
- package/tests/errors/queryable-errors.spec.ts +177 -0
- package/tests/escape.spec.ts +100 -0
- package/tests/examples/pivot.expected.ts +211 -0
- package/tests/examples/pivot.spec.ts +533 -0
- package/tests/examples/sampling.expected.ts +69 -0
- package/tests/examples/sampling.spec.ts +105 -0
- package/tests/examples/unpivot.expected.ts +120 -0
- package/tests/examples/unpivot.spec.ts +226 -0
- package/tests/exec/search-parser.spec.ts +283 -0
- package/tests/executable/basic.expected.ts +18 -0
- package/tests/executable/basic.spec.ts +54 -0
- package/tests/expr/comparison.expected.ts +282 -0
- package/tests/expr/comparison.spec.ts +400 -0
- package/tests/expr/conditional.expected.ts +134 -0
- package/tests/expr/conditional.spec.ts +276 -0
- package/tests/expr/date.expected.ts +332 -0
- package/tests/expr/date.spec.ts +526 -0
- package/tests/expr/math.expected.ts +62 -0
- package/tests/expr/math.spec.ts +106 -0
- package/tests/expr/string.expected.ts +218 -0
- package/tests/expr/string.spec.ts +356 -0
- package/tests/expr/utility.expected.ts +147 -0
- package/tests/expr/utility.spec.ts +182 -0
- package/tests/select/basic.expected.ts +322 -0
- package/tests/select/basic.spec.ts +502 -0
- package/tests/select/filter.expected.ts +357 -0
- package/tests/select/filter.spec.ts +1068 -0
- package/tests/select/group.expected.ts +169 -0
- package/tests/select/group.spec.ts +244 -0
- package/tests/select/join.expected.ts +582 -0
- package/tests/select/join.spec.ts +805 -0
- package/tests/select/order.expected.ts +150 -0
- package/tests/select/order.spec.ts +189 -0
- package/tests/select/recursive-cte.expected.ts +244 -0
- package/tests/select/recursive-cte.spec.ts +514 -0
- package/tests/select/result-meta.spec.ts +270 -0
- package/tests/select/subquery.expected.ts +363 -0
- package/tests/select/subquery.spec.ts +537 -0
- package/tests/select/view.expected.ts +155 -0
- package/tests/select/view.spec.ts +235 -0
- package/tests/select/window.expected.ts +345 -0
- package/tests/select/window.spec.ts +618 -0
- package/tests/setup/MockExecutor.ts +18 -0
- package/tests/setup/TestDbContext.ts +59 -0
- package/tests/setup/models/Company.ts +13 -0
- package/tests/setup/models/Employee.ts +10 -0
- package/tests/setup/models/MonthlySales.ts +11 -0
- package/tests/setup/models/Post.ts +16 -0
- package/tests/setup/models/Sales.ts +10 -0
- package/tests/setup/models/User.ts +19 -0
- package/tests/setup/procedure/GetAllUsers.ts +9 -0
- package/tests/setup/procedure/GetUserById.ts +12 -0
- package/tests/setup/test-utils.ts +72 -0
- package/tests/setup/views/ActiveUsers.ts +8 -0
- package/tests/setup/views/UserSummary.ts +11 -0
- package/tests/types/nullable-queryable-record.spec.ts +145 -0
- package/tests/utils/result-parser-perf.spec.ts +210 -0
- package/tests/utils/result-parser.spec.ts +701 -0
- package/docs/expressions.md +0 -172
- package/docs/queries.md +0 -444
- package/docs/schema.md +0 -245
|
@@ -1,607 +1,607 @@
|
|
|
1
|
-
import { DateOnly, DateTime, Time, Uuid, bytesToHex } from "@simplysm/core-common";
|
|
2
|
-
import type {
|
|
3
|
-
ExprColumn,
|
|
4
|
-
ExprValue,
|
|
5
|
-
ExprRaw,
|
|
6
|
-
ExprEq,
|
|
7
|
-
ExprGt,
|
|
8
|
-
ExprLt,
|
|
9
|
-
ExprGte,
|
|
10
|
-
ExprLte,
|
|
11
|
-
ExprBetween,
|
|
12
|
-
ExprIsNull,
|
|
13
|
-
ExprLike,
|
|
14
|
-
ExprRegexp,
|
|
15
|
-
ExprIn,
|
|
16
|
-
ExprInQuery,
|
|
17
|
-
ExprExists,
|
|
18
|
-
ExprNot,
|
|
19
|
-
ExprAnd,
|
|
20
|
-
ExprOr,
|
|
21
|
-
ExprConcat,
|
|
22
|
-
ExprLeft,
|
|
23
|
-
ExprRight,
|
|
24
|
-
ExprTrim,
|
|
25
|
-
ExprPadStart,
|
|
26
|
-
ExprReplace,
|
|
27
|
-
ExprUpper,
|
|
28
|
-
ExprLower,
|
|
29
|
-
ExprLength,
|
|
30
|
-
ExprByteLength,
|
|
31
|
-
ExprSubstring,
|
|
32
|
-
ExprIndexOf,
|
|
33
|
-
ExprAbs,
|
|
34
|
-
ExprRound,
|
|
35
|
-
ExprCeil,
|
|
36
|
-
ExprFloor,
|
|
37
|
-
ExprYear,
|
|
38
|
-
ExprMonth,
|
|
39
|
-
ExprDay,
|
|
40
|
-
ExprHour,
|
|
41
|
-
ExprMinute,
|
|
42
|
-
ExprSecond,
|
|
43
|
-
ExprIsoWeek,
|
|
44
|
-
ExprIsoWeekStartDate,
|
|
45
|
-
ExprIsoYearMonth,
|
|
46
|
-
ExprDateDiff,
|
|
47
|
-
ExprDateAdd,
|
|
48
|
-
ExprFormatDate,
|
|
49
|
-
ExprIfNull,
|
|
50
|
-
ExprNullIf,
|
|
51
|
-
ExprIs,
|
|
52
|
-
ExprSwitch,
|
|
53
|
-
ExprIf,
|
|
54
|
-
ExprCount,
|
|
55
|
-
ExprSum,
|
|
56
|
-
ExprAvg,
|
|
57
|
-
ExprMax,
|
|
58
|
-
ExprMin,
|
|
59
|
-
ExprGreatest,
|
|
60
|
-
ExprLeast,
|
|
61
|
-
ExprRowNum,
|
|
62
|
-
ExprCast,
|
|
63
|
-
ExprWindow,
|
|
64
|
-
ExprSubquery,
|
|
65
|
-
DateSeparator,
|
|
66
|
-
} from "../../types/expr";
|
|
67
|
-
import type { DataType } from "../../types/column";
|
|
68
|
-
import { ExprRendererBase } from "../base/expr-renderer-base";
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* MSSQL
|
|
72
|
-
*/
|
|
73
|
-
export class MssqlExprRenderer extends ExprRendererBase {
|
|
74
|
-
//#region ========== 유틸리티 (public - QueryBuilder에서도 사용) ==========
|
|
75
|
-
|
|
76
|
-
/** 식별자 감싸기 */
|
|
77
|
-
wrap(name: string): string {
|
|
78
|
-
return `[${name.replace(/]/g, "]]")}]`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** SQL 문자열 리터럴용
|
|
82
|
-
escapeString(value: string): string {
|
|
83
|
-
return value.replace(/'/g, "''");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
escapeValue(value: unknown): string {
|
|
88
|
-
if (value == null) {
|
|
89
|
-
return "NULL";
|
|
90
|
-
}
|
|
91
|
-
if (typeof value === "string") {
|
|
92
|
-
return `N'${value.replace(/'/g, "''")}'`;
|
|
93
|
-
}
|
|
94
|
-
if (typeof value === "number") {
|
|
95
|
-
return String(value);
|
|
96
|
-
}
|
|
97
|
-
if (typeof value === "boolean") {
|
|
98
|
-
return value ? "1" : "0";
|
|
99
|
-
}
|
|
100
|
-
if (value instanceof DateTime) {
|
|
101
|
-
return `'${value.toFormatString("yyyy-MM-dd HH:mm:ss")}'`;
|
|
102
|
-
}
|
|
103
|
-
if (value instanceof DateOnly) {
|
|
104
|
-
return `'${value.toFormatString("yyyy-MM-dd")}'`;
|
|
105
|
-
}
|
|
106
|
-
if (value instanceof Time) {
|
|
107
|
-
return `'${value.toFormatString("HH:mm:ss")}'`;
|
|
108
|
-
}
|
|
109
|
-
if (value instanceof Uuid) {
|
|
110
|
-
return `'${value.toString()}'`;
|
|
111
|
-
}
|
|
112
|
-
if (value instanceof Uint8Array) {
|
|
113
|
-
return `0x${bytesToHex(value)}`;
|
|
114
|
-
}
|
|
115
|
-
throw new Error(
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** DataType → SQL
|
|
119
|
-
renderDataType(dataType: DataType): string {
|
|
120
|
-
switch (dataType.type) {
|
|
121
|
-
case "int":
|
|
122
|
-
return "INT";
|
|
123
|
-
case "bigint":
|
|
124
|
-
return "BIGINT";
|
|
125
|
-
case "float":
|
|
126
|
-
return "REAL";
|
|
127
|
-
case "double":
|
|
128
|
-
return "FLOAT";
|
|
129
|
-
case "decimal":
|
|
130
|
-
return dataType.scale != null
|
|
131
|
-
? `DECIMAL(${dataType.precision}, ${dataType.scale})`
|
|
132
|
-
: `DECIMAL(${dataType.precision})`;
|
|
133
|
-
case "varchar":
|
|
134
|
-
return `NVARCHAR(${dataType.length})`;
|
|
135
|
-
case "char":
|
|
136
|
-
return `NCHAR(${dataType.length})`;
|
|
137
|
-
case "text":
|
|
138
|
-
return "NVARCHAR(MAX)";
|
|
139
|
-
case "binary":
|
|
140
|
-
return "VARBINARY(MAX)";
|
|
141
|
-
case "boolean":
|
|
142
|
-
return "BIT";
|
|
143
|
-
case "datetime":
|
|
144
|
-
return "DATETIME2";
|
|
145
|
-
case "date":
|
|
146
|
-
return "DATE";
|
|
147
|
-
case "time":
|
|
148
|
-
return "TIME";
|
|
149
|
-
case "uuid":
|
|
150
|
-
return "UNIQUEIDENTIFIER";
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
//#endregion
|
|
155
|
-
|
|
156
|
-
//#region ==========
|
|
157
|
-
|
|
158
|
-
protected column(expr: ExprColumn): string {
|
|
159
|
-
return expr.path.map((p) => this.wrap(p)).join(".");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
protected value(expr: ExprValue): string {
|
|
163
|
-
return this.escapeValue(expr.value);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
protected raw(expr: ExprRaw): string {
|
|
167
|
-
return expr.sql.replace(/\$(\d+)/g, (_, num) => {
|
|
168
|
-
const idx = parseInt(num) - 1;
|
|
169
|
-
return idx < expr.params.length ? this.render(expr.params[idx]) : `$${num}`;
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
//#endregion
|
|
174
|
-
|
|
175
|
-
//#region ==========
|
|
176
|
-
|
|
177
|
-
protected eq(expr: ExprEq): string {
|
|
178
|
-
// MSSQL: null-safe equal (OR
|
|
179
|
-
const left = this.render(expr.source);
|
|
180
|
-
const right = this.render(expr.target);
|
|
181
|
-
return `((${left} IS NULL AND ${right} IS NULL) OR ${left} = ${right})`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
protected gt(expr: ExprGt): string {
|
|
185
|
-
return `${this.render(expr.source)} > ${this.render(expr.target)}`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
protected lt(expr: ExprLt): string {
|
|
189
|
-
return `${this.render(expr.source)} < ${this.render(expr.target)}`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
protected gte(expr: ExprGte): string {
|
|
193
|
-
return `${this.render(expr.source)} >= ${this.render(expr.target)}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
protected lte(expr: ExprLte): string {
|
|
197
|
-
return `${this.render(expr.source)} <= ${this.render(expr.target)}`;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
protected between(expr: ExprBetween): string {
|
|
201
|
-
const source = this.render(expr.source);
|
|
202
|
-
if (expr.from != null && expr.to != null) {
|
|
203
|
-
return `${source} BETWEEN ${this.render(expr.from)} AND ${this.render(expr.to)}`;
|
|
204
|
-
}
|
|
205
|
-
if (expr.from != null) {
|
|
206
|
-
return `${source} >= ${this.render(expr.from)}`;
|
|
207
|
-
}
|
|
208
|
-
if (expr.to != null) {
|
|
209
|
-
return `${source} <= ${this.render(expr.to)}`;
|
|
210
|
-
}
|
|
211
|
-
return "1=1";
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
protected null(expr: ExprIsNull): string {
|
|
215
|
-
return `${this.render(expr.arg)} IS NULL`;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
protected like(expr: ExprLike): string {
|
|
219
|
-
// ESCAPE '\' 항상
|
|
220
|
-
return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\'`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
protected regexp(_expr: ExprRegexp): string {
|
|
224
|
-
// MSSQL은 REGEXP 미지원 - LIKE
|
|
225
|
-
throw new Error("MSSQL
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
protected in(expr: ExprIn): string {
|
|
229
|
-
if (expr.values.length === 0) {
|
|
230
|
-
return "1=0"; // 빈 IN은 항상 false
|
|
231
|
-
}
|
|
232
|
-
const values = expr.values.map((v) => this.render(v)).join(", ");
|
|
233
|
-
return `${this.render(expr.source)} IN (${values})`;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
protected inQuery(expr: ExprInQuery): string {
|
|
237
|
-
return `${this.render(expr.source)} IN (${this.buildSelect(expr.query)})`;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
protected exists(expr: ExprExists): string {
|
|
241
|
-
// SELECT 1로
|
|
242
|
-
const subquery = this.buildSelect({
|
|
243
|
-
...expr.query,
|
|
244
|
-
select: { _: { type: "value", value: 1 } },
|
|
245
|
-
});
|
|
246
|
-
return `EXISTS (${subquery})`;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
//#endregion
|
|
250
|
-
|
|
251
|
-
//#region ==========
|
|
252
|
-
|
|
253
|
-
protected not(expr: ExprNot): string {
|
|
254
|
-
return `NOT (${this.render(expr.arg)})`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
protected and(expr: ExprAnd): string {
|
|
258
|
-
if (expr.conditions.length === 0) return "1=1";
|
|
259
|
-
return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
protected or(expr: ExprOr): string {
|
|
263
|
-
if (expr.conditions.length === 0) return "1=0";
|
|
264
|
-
return `(${expr.conditions.map((c) => this.render(c)).join(" OR ")})`;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
//#endregion
|
|
268
|
-
|
|
269
|
-
//#region ========== 문자열 (null
|
|
270
|
-
|
|
271
|
-
protected concat(expr: ExprConcat): string {
|
|
272
|
-
// MSSQL 2012+: CONCAT 함수는 NULL을
|
|
273
|
-
const args = expr.args.map((a) => this.render(a)).join(", ");
|
|
274
|
-
return `CONCAT(${args})`;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
protected left(expr: ExprLeft): string {
|
|
278
|
-
return `LEFT(${this.render(expr.source)}, ${this.render(expr.length)})`;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
protected right(expr: ExprRight): string {
|
|
282
|
-
return `RIGHT(${this.render(expr.source)}, ${this.render(expr.length)})`;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
protected trim(expr: ExprTrim): string {
|
|
286
|
-
return `RTRIM(LTRIM(${this.render(expr.arg)}))`;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
protected padStart(expr: ExprPadStart): string {
|
|
290
|
-
// MSSQL: RIGHT(REPLICATE(fill, len) + source, len)
|
|
291
|
-
const source = this.render(expr.source);
|
|
292
|
-
const len = this.render(expr.length);
|
|
293
|
-
const fill = this.render(expr.fillString);
|
|
294
|
-
return `RIGHT(REPLICATE(${fill}, ${len}) + ${source}, ${len})`;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
protected replace(expr: ExprReplace): string {
|
|
298
|
-
return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
protected upper(expr: ExprUpper): string {
|
|
302
|
-
return `UPPER(${this.render(expr.arg)})`;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
protected lower(expr: ExprLower): string {
|
|
306
|
-
return `LOWER(${this.render(expr.arg)})`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
protected length(expr: ExprLength): string {
|
|
310
|
-
// MSSQL: LEN() (null
|
|
311
|
-
return `LEN(ISNULL(${this.render(expr.arg)}, N''))`;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
protected byteLength(expr: ExprByteLength): string {
|
|
315
|
-
// MSSQL: DATALENGTH() (null
|
|
316
|
-
return `DATALENGTH(ISNULL(${this.render(expr.arg)}, N''))`;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
protected substring(expr: ExprSubstring): string {
|
|
320
|
-
if (expr.length != null) {
|
|
321
|
-
return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
|
|
322
|
-
}
|
|
323
|
-
// MSSQL: length 없으면 끝까지
|
|
324
|
-
return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, LEN(${this.render(expr.source)}))`;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
protected indexOf(expr: ExprIndexOf): string {
|
|
328
|
-
return `CHARINDEX(${this.render(expr.search)}, ${this.render(expr.source)})`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
//#endregion
|
|
332
|
-
|
|
333
|
-
//#region ==========
|
|
334
|
-
|
|
335
|
-
protected abs(expr: ExprAbs): string {
|
|
336
|
-
return `ABS(${this.render(expr.arg)})`;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
protected round(expr: ExprRound): string {
|
|
340
|
-
return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
protected ceil(expr: ExprCeil): string {
|
|
344
|
-
return `CEILING(${this.render(expr.arg)})`;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
protected floor(expr: ExprFloor): string {
|
|
348
|
-
return `FLOOR(${this.render(expr.arg)})`;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
//#endregion
|
|
352
|
-
|
|
353
|
-
//#region ==========
|
|
354
|
-
|
|
355
|
-
protected year(expr: ExprYear): string {
|
|
356
|
-
return `YEAR(${this.render(expr.arg)})`;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
protected month(expr: ExprMonth): string {
|
|
360
|
-
return `MONTH(${this.render(expr.arg)})`;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
protected day(expr: ExprDay): string {
|
|
364
|
-
return `DAY(${this.render(expr.arg)})`;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
protected hour(expr: ExprHour): string {
|
|
368
|
-
return `DATEPART(HOUR, ${this.render(expr.arg)})`;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
protected minute(expr: ExprMinute): string {
|
|
372
|
-
return `DATEPART(MINUTE, ${this.render(expr.arg)})`;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
protected second(expr: ExprSecond): string {
|
|
376
|
-
return `DATEPART(SECOND, ${this.render(expr.arg)})`;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
protected isoWeek(expr: ExprIsoWeek): string {
|
|
380
|
-
const src = this.render(expr.arg);
|
|
381
|
-
return `DATEPART(ISO_WEEK, ${src})`;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
protected isoWeekStartDate(expr: ExprIsoWeekStartDate): string {
|
|
385
|
-
const src = this.render(expr.arg);
|
|
386
|
-
// ISO 주의 시작일 (월요일) - @@DATEFIRST 무관하게 항상 월요일
|
|
387
|
-
// 원리: DATEDIFF(DAY, 0, date)는 1900-01-01(월요일)부터의 일수
|
|
388
|
-
// (일수 + 6) % 7 + 1 = 1(월), 2(화), ..., 7(일)
|
|
389
|
-
const weekDay = `((DATEDIFF(DAY, 0, ${src}) + 6) % 7 + 1)`;
|
|
390
|
-
return `DATEADD(DAY, 1 - ${weekDay}, CAST(${src} AS DATE))`;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
protected isoYearMonth(expr: ExprIsoYearMonth): string {
|
|
394
|
-
const src = this.render(expr.arg);
|
|
395
|
-
return `FORMAT(${src}, 'yyyyMM')`;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
protected dateDiff(expr: ExprDateDiff): string {
|
|
399
|
-
const from = this.render(expr.from);
|
|
400
|
-
const to = this.render(expr.to);
|
|
401
|
-
const unit = this.dateSeparatorToUnit(expr.separator);
|
|
402
|
-
return `DATEDIFF(${unit}, ${from}, ${to})`;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
protected dateAdd(expr: ExprDateAdd): string {
|
|
406
|
-
const source = this.render(expr.source);
|
|
407
|
-
const value = this.render(expr.value);
|
|
408
|
-
const unit = this.dateSeparatorToUnit(expr.separator);
|
|
409
|
-
return `DATEADD(${unit}, ${value}, ${source})`;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
protected formatDate(expr: ExprFormatDate): string {
|
|
413
|
-
// JS format → MSSQL FORMAT style
|
|
414
|
-
const mssqlFormat = this.convertDateFormat(expr.format);
|
|
415
|
-
return `FORMAT(${this.render(expr.source)}, '${mssqlFormat}')`;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
private dateSeparatorToUnit(sep: DateSeparator): string {
|
|
419
|
-
switch (sep) {
|
|
420
|
-
case "year":
|
|
421
|
-
return "YEAR";
|
|
422
|
-
case "month":
|
|
423
|
-
return "MONTH";
|
|
424
|
-
case "day":
|
|
425
|
-
return "DAY";
|
|
426
|
-
case "hour":
|
|
427
|
-
return "HOUR";
|
|
428
|
-
case "minute":
|
|
429
|
-
return "MINUTE";
|
|
430
|
-
case "second":
|
|
431
|
-
return "SECOND";
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
private convertDateFormat(format: string): string {
|
|
436
|
-
// MSSQL FORMAT 함수용 (동일한 포맷 사용)
|
|
437
|
-
return format;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
//#endregion
|
|
441
|
-
|
|
442
|
-
//#region ==========
|
|
443
|
-
|
|
444
|
-
protected ifNull(expr: ExprIfNull): string {
|
|
445
|
-
if (expr.args.length === 0) return "NULL";
|
|
446
|
-
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
447
|
-
// MSSQL: COALESCE
|
|
448
|
-
return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
protected nullIf(expr: ExprNullIf): string {
|
|
452
|
-
return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
protected is(expr: ExprIs): string {
|
|
456
|
-
return `CASE WHEN ${this.render(expr.condition)} THEN 1 ELSE 0 END`;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
protected switch(expr: ExprSwitch): string {
|
|
460
|
-
const cases = expr.cases
|
|
461
|
-
.map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`)
|
|
462
|
-
.join(" ");
|
|
463
|
-
return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
protected if(expr: ExprIf): string {
|
|
467
|
-
const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
|
|
468
|
-
return `CASE WHEN ${this.render(expr.condition)} THEN ${this.render(expr.then)} ELSE ${elseVal} END`;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
//#endregion
|
|
472
|
-
|
|
473
|
-
//#region ==========
|
|
474
|
-
|
|
475
|
-
protected count(expr: ExprCount): string {
|
|
476
|
-
if (expr.arg != null) {
|
|
477
|
-
const distinct = expr.distinct ? "DISTINCT " : "";
|
|
478
|
-
return `COUNT(${distinct}${this.render(expr.arg)})`;
|
|
479
|
-
}
|
|
480
|
-
return "COUNT(*)";
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
protected sum(expr: ExprSum): string {
|
|
484
|
-
return `SUM(${this.render(expr.arg)})`;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
protected avg(expr: ExprAvg): string {
|
|
488
|
-
return `AVG(${this.render(expr.arg)})`;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
protected max(expr: ExprMax): string {
|
|
492
|
-
return `MAX(${this.render(expr.arg)})`;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
protected min(expr: ExprMin): string {
|
|
496
|
-
return `MIN(${this.render(expr.arg)})`;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
//#endregion
|
|
500
|
-
|
|
501
|
-
//#region ==========
|
|
502
|
-
|
|
503
|
-
protected greatest(expr: ExprGreatest): string {
|
|
504
|
-
if (expr.args.length === 0) throw new Error("greatest
|
|
505
|
-
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
506
|
-
// MSSQL 2012+: VALUES + MAX 방식
|
|
507
|
-
const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
|
|
508
|
-
return `(SELECT MAX(v) FROM (VALUES ${values}) AS t(v))`;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
protected least(expr: ExprLeast): string {
|
|
512
|
-
if (expr.args.length === 0) throw new Error("least
|
|
513
|
-
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
514
|
-
// MSSQL 2012+: VALUES + MIN 방식
|
|
515
|
-
const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
|
|
516
|
-
return `(SELECT MIN(v) FROM (VALUES ${values}) AS t(v))`;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
protected rowNum(_expr: ExprRowNum): string {
|
|
520
|
-
return "ROW_NUMBER() OVER (ORDER BY (SELECT NULL))";
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
protected random(): string {
|
|
524
|
-
return "NEWID()";
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
protected cast(expr: ExprCast): string {
|
|
528
|
-
return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
//#endregion
|
|
532
|
-
|
|
533
|
-
//#region ==========
|
|
534
|
-
|
|
535
|
-
protected window(expr: ExprWindow): string {
|
|
536
|
-
const fn = this.renderWindowFn(expr.fn);
|
|
537
|
-
let over = this.renderWindowSpec(expr.spec);
|
|
538
|
-
|
|
539
|
-
// LAST_VALUE는
|
|
540
|
-
if (expr.fn.type === "lastValue" && over.length > 0) {
|
|
541
|
-
over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return `${fn} OVER (${over})`;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
private renderWindowFn(fn: ExprWindow["fn"]): string {
|
|
548
|
-
switch (fn.type) {
|
|
549
|
-
case "rowNumber":
|
|
550
|
-
return "ROW_NUMBER()";
|
|
551
|
-
case "rank":
|
|
552
|
-
return "RANK()";
|
|
553
|
-
case "denseRank":
|
|
554
|
-
return "DENSE_RANK()";
|
|
555
|
-
case "ntile":
|
|
556
|
-
return `NTILE(${fn.n})`;
|
|
557
|
-
case "lag": {
|
|
558
|
-
const offset = fn.offset ?? 1;
|
|
559
|
-
const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
|
|
560
|
-
return `LAG(${this.render(fn.column)}, ${offset}${def})`;
|
|
561
|
-
}
|
|
562
|
-
case "lead": {
|
|
563
|
-
const offset = fn.offset ?? 1;
|
|
564
|
-
const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
|
|
565
|
-
return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
|
|
566
|
-
}
|
|
567
|
-
case "firstValue":
|
|
568
|
-
return `FIRST_VALUE(${this.render(fn.column)})`;
|
|
569
|
-
case "lastValue":
|
|
570
|
-
return `LAST_VALUE(${this.render(fn.column)})`;
|
|
571
|
-
case "sum":
|
|
572
|
-
return `SUM(${this.render(fn.column)})`;
|
|
573
|
-
case "avg":
|
|
574
|
-
return `AVG(${this.render(fn.column)})`;
|
|
575
|
-
case "count":
|
|
576
|
-
return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
|
|
577
|
-
case "min":
|
|
578
|
-
return `MIN(${this.render(fn.column)})`;
|
|
579
|
-
case "max":
|
|
580
|
-
return `MAX(${this.render(fn.column)})`;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
private renderWindowSpec(spec: ExprWindow["spec"]): string {
|
|
585
|
-
const parts: string[] = [];
|
|
586
|
-
if (spec.partitionBy != null && spec.partitionBy.length > 0) {
|
|
587
|
-
parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
|
|
588
|
-
}
|
|
589
|
-
if (spec.orderBy != null && spec.orderBy.length > 0) {
|
|
590
|
-
const orderParts = spec.orderBy.map(
|
|
591
|
-
([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`,
|
|
592
|
-
);
|
|
593
|
-
parts.push(`ORDER BY ${orderParts.join(", ")}`);
|
|
594
|
-
}
|
|
595
|
-
return parts.join(" ");
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
//#endregion
|
|
599
|
-
|
|
600
|
-
//#region ==========
|
|
601
|
-
|
|
602
|
-
protected subquery(expr: ExprSubquery): string {
|
|
603
|
-
return `(${this.buildSelect(expr.queryDef)})`;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
//#endregion
|
|
607
|
-
}
|
|
1
|
+
import { DateOnly, DateTime, Time, Uuid, bytesToHex } from "@simplysm/core-common";
|
|
2
|
+
import type {
|
|
3
|
+
ExprColumn,
|
|
4
|
+
ExprValue,
|
|
5
|
+
ExprRaw,
|
|
6
|
+
ExprEq,
|
|
7
|
+
ExprGt,
|
|
8
|
+
ExprLt,
|
|
9
|
+
ExprGte,
|
|
10
|
+
ExprLte,
|
|
11
|
+
ExprBetween,
|
|
12
|
+
ExprIsNull,
|
|
13
|
+
ExprLike,
|
|
14
|
+
ExprRegexp,
|
|
15
|
+
ExprIn,
|
|
16
|
+
ExprInQuery,
|
|
17
|
+
ExprExists,
|
|
18
|
+
ExprNot,
|
|
19
|
+
ExprAnd,
|
|
20
|
+
ExprOr,
|
|
21
|
+
ExprConcat,
|
|
22
|
+
ExprLeft,
|
|
23
|
+
ExprRight,
|
|
24
|
+
ExprTrim,
|
|
25
|
+
ExprPadStart,
|
|
26
|
+
ExprReplace,
|
|
27
|
+
ExprUpper,
|
|
28
|
+
ExprLower,
|
|
29
|
+
ExprLength,
|
|
30
|
+
ExprByteLength,
|
|
31
|
+
ExprSubstring,
|
|
32
|
+
ExprIndexOf,
|
|
33
|
+
ExprAbs,
|
|
34
|
+
ExprRound,
|
|
35
|
+
ExprCeil,
|
|
36
|
+
ExprFloor,
|
|
37
|
+
ExprYear,
|
|
38
|
+
ExprMonth,
|
|
39
|
+
ExprDay,
|
|
40
|
+
ExprHour,
|
|
41
|
+
ExprMinute,
|
|
42
|
+
ExprSecond,
|
|
43
|
+
ExprIsoWeek,
|
|
44
|
+
ExprIsoWeekStartDate,
|
|
45
|
+
ExprIsoYearMonth,
|
|
46
|
+
ExprDateDiff,
|
|
47
|
+
ExprDateAdd,
|
|
48
|
+
ExprFormatDate,
|
|
49
|
+
ExprIfNull,
|
|
50
|
+
ExprNullIf,
|
|
51
|
+
ExprIs,
|
|
52
|
+
ExprSwitch,
|
|
53
|
+
ExprIf,
|
|
54
|
+
ExprCount,
|
|
55
|
+
ExprSum,
|
|
56
|
+
ExprAvg,
|
|
57
|
+
ExprMax,
|
|
58
|
+
ExprMin,
|
|
59
|
+
ExprGreatest,
|
|
60
|
+
ExprLeast,
|
|
61
|
+
ExprRowNum,
|
|
62
|
+
ExprCast,
|
|
63
|
+
ExprWindow,
|
|
64
|
+
ExprSubquery,
|
|
65
|
+
DateSeparator,
|
|
66
|
+
} from "../../types/expr";
|
|
67
|
+
import type { DataType } from "../../types/column";
|
|
68
|
+
import { ExprRendererBase } from "../base/expr-renderer-base";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* MSSQL expression renderer
|
|
72
|
+
*/
|
|
73
|
+
export class MssqlExprRenderer extends ExprRendererBase {
|
|
74
|
+
//#region ========== 유틸리티 (public - QueryBuilder에서도 사용) ==========
|
|
75
|
+
|
|
76
|
+
/** 식별자 감싸기 */
|
|
77
|
+
wrap(name: string): string {
|
|
78
|
+
return `[${name.replace(/]/g, "]]")}]`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** SQL 문자열 리터럴용 escape (따옴표 없이 return) */
|
|
82
|
+
escapeString(value: string): string {
|
|
83
|
+
return value.replace(/'/g, "''");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** value escape */
|
|
87
|
+
escapeValue(value: unknown): string {
|
|
88
|
+
if (value == null) {
|
|
89
|
+
return "NULL";
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === "string") {
|
|
92
|
+
return `N'${value.replace(/'/g, "''")}'`;
|
|
93
|
+
}
|
|
94
|
+
if (typeof value === "number") {
|
|
95
|
+
return String(value);
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === "boolean") {
|
|
98
|
+
return value ? "1" : "0";
|
|
99
|
+
}
|
|
100
|
+
if (value instanceof DateTime) {
|
|
101
|
+
return `'${value.toFormatString("yyyy-MM-dd HH:mm:ss")}'`;
|
|
102
|
+
}
|
|
103
|
+
if (value instanceof DateOnly) {
|
|
104
|
+
return `'${value.toFormatString("yyyy-MM-dd")}'`;
|
|
105
|
+
}
|
|
106
|
+
if (value instanceof Time) {
|
|
107
|
+
return `'${value.toFormatString("HH:mm:ss")}'`;
|
|
108
|
+
}
|
|
109
|
+
if (value instanceof Uuid) {
|
|
110
|
+
return `'${value.toString()}'`;
|
|
111
|
+
}
|
|
112
|
+
if (value instanceof Uint8Array) {
|
|
113
|
+
return `0x${bytesToHex(value)}`;
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Unknown value type: ${typeof value}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** DataType → SQL type */
|
|
119
|
+
renderDataType(dataType: DataType): string {
|
|
120
|
+
switch (dataType.type) {
|
|
121
|
+
case "int":
|
|
122
|
+
return "INT";
|
|
123
|
+
case "bigint":
|
|
124
|
+
return "BIGINT";
|
|
125
|
+
case "float":
|
|
126
|
+
return "REAL";
|
|
127
|
+
case "double":
|
|
128
|
+
return "FLOAT";
|
|
129
|
+
case "decimal":
|
|
130
|
+
return dataType.scale != null
|
|
131
|
+
? `DECIMAL(${dataType.precision}, ${dataType.scale})`
|
|
132
|
+
: `DECIMAL(${dataType.precision})`;
|
|
133
|
+
case "varchar":
|
|
134
|
+
return `NVARCHAR(${dataType.length})`;
|
|
135
|
+
case "char":
|
|
136
|
+
return `NCHAR(${dataType.length})`;
|
|
137
|
+
case "text":
|
|
138
|
+
return "NVARCHAR(MAX)";
|
|
139
|
+
case "binary":
|
|
140
|
+
return "VARBINARY(MAX)";
|
|
141
|
+
case "boolean":
|
|
142
|
+
return "BIT";
|
|
143
|
+
case "datetime":
|
|
144
|
+
return "DATETIME2";
|
|
145
|
+
case "date":
|
|
146
|
+
return "DATE";
|
|
147
|
+
case "time":
|
|
148
|
+
return "TIME";
|
|
149
|
+
case "uuid":
|
|
150
|
+
return "UNIQUEIDENTIFIER";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
|
|
156
|
+
//#region ========== value ==========
|
|
157
|
+
|
|
158
|
+
protected column(expr: ExprColumn): string {
|
|
159
|
+
return expr.path.map((p) => this.wrap(p)).join(".");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected value(expr: ExprValue): string {
|
|
163
|
+
return this.escapeValue(expr.value);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected raw(expr: ExprRaw): string {
|
|
167
|
+
return expr.sql.replace(/\$(\d+)/g, (_, num) => {
|
|
168
|
+
const idx = parseInt(num) - 1;
|
|
169
|
+
return idx < expr.params.length ? this.render(expr.params[idx]) : `$${num}`;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
|
|
175
|
+
//#region ========== comparison (null-safe) ==========
|
|
176
|
+
|
|
177
|
+
protected eq(expr: ExprEq): string {
|
|
178
|
+
// MSSQL: null-safe equal (OR Pattern)
|
|
179
|
+
const left = this.render(expr.source);
|
|
180
|
+
const right = this.render(expr.target);
|
|
181
|
+
return `((${left} IS NULL AND ${right} IS NULL) OR ${left} = ${right})`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
protected gt(expr: ExprGt): string {
|
|
185
|
+
return `${this.render(expr.source)} > ${this.render(expr.target)}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
protected lt(expr: ExprLt): string {
|
|
189
|
+
return `${this.render(expr.source)} < ${this.render(expr.target)}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected gte(expr: ExprGte): string {
|
|
193
|
+
return `${this.render(expr.source)} >= ${this.render(expr.target)}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
protected lte(expr: ExprLte): string {
|
|
197
|
+
return `${this.render(expr.source)} <= ${this.render(expr.target)}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected between(expr: ExprBetween): string {
|
|
201
|
+
const source = this.render(expr.source);
|
|
202
|
+
if (expr.from != null && expr.to != null) {
|
|
203
|
+
return `${source} BETWEEN ${this.render(expr.from)} AND ${this.render(expr.to)}`;
|
|
204
|
+
}
|
|
205
|
+
if (expr.from != null) {
|
|
206
|
+
return `${source} >= ${this.render(expr.from)}`;
|
|
207
|
+
}
|
|
208
|
+
if (expr.to != null) {
|
|
209
|
+
return `${source} <= ${this.render(expr.to)}`;
|
|
210
|
+
}
|
|
211
|
+
return "1=1";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected null(expr: ExprIsNull): string {
|
|
215
|
+
return `${this.render(expr.arg)} IS NULL`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
protected like(expr: ExprLike): string {
|
|
219
|
+
// ESCAPE '\' 항상 Add
|
|
220
|
+
return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\'`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
protected regexp(_expr: ExprRegexp): string {
|
|
224
|
+
// MSSQL은 REGEXP 미지원 - LIKE pattern이나 CLR 사용 필요
|
|
225
|
+
throw new Error("MSSQL does not natively support REGEXP.");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
protected in(expr: ExprIn): string {
|
|
229
|
+
if (expr.values.length === 0) {
|
|
230
|
+
return "1=0"; // 빈 IN은 항상 false
|
|
231
|
+
}
|
|
232
|
+
const values = expr.values.map((v) => this.render(v)).join(", ");
|
|
233
|
+
return `${this.render(expr.source)} IN (${values})`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
protected inQuery(expr: ExprInQuery): string {
|
|
237
|
+
return `${this.render(expr.source)} IN (${this.buildSelect(expr.query)})`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
protected exists(expr: ExprExists): string {
|
|
241
|
+
// SELECT 1로 Render
|
|
242
|
+
const subquery = this.buildSelect({
|
|
243
|
+
...expr.query,
|
|
244
|
+
select: { _: { type: "value", value: 1 } },
|
|
245
|
+
});
|
|
246
|
+
return `EXISTS (${subquery})`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
//#endregion
|
|
250
|
+
|
|
251
|
+
//#region ========== logic ==========
|
|
252
|
+
|
|
253
|
+
protected not(expr: ExprNot): string {
|
|
254
|
+
return `NOT (${this.render(expr.arg)})`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
protected and(expr: ExprAnd): string {
|
|
258
|
+
if (expr.conditions.length === 0) return "1=1";
|
|
259
|
+
return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
protected or(expr: ExprOr): string {
|
|
263
|
+
if (expr.conditions.length === 0) return "1=0";
|
|
264
|
+
return `(${expr.conditions.map((c) => this.render(c)).join(" OR ")})`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
|
|
269
|
+
//#region ========== 문자열 (null Process) ==========
|
|
270
|
+
|
|
271
|
+
protected concat(expr: ExprConcat): string {
|
|
272
|
+
// MSSQL 2012+: CONCAT 함수는 NULL을 automatic으로 빈 문자열로 processing
|
|
273
|
+
const args = expr.args.map((a) => this.render(a)).join(", ");
|
|
274
|
+
return `CONCAT(${args})`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
protected left(expr: ExprLeft): string {
|
|
278
|
+
return `LEFT(${this.render(expr.source)}, ${this.render(expr.length)})`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
protected right(expr: ExprRight): string {
|
|
282
|
+
return `RIGHT(${this.render(expr.source)}, ${this.render(expr.length)})`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
protected trim(expr: ExprTrim): string {
|
|
286
|
+
return `RTRIM(LTRIM(${this.render(expr.arg)}))`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
protected padStart(expr: ExprPadStart): string {
|
|
290
|
+
// MSSQL: RIGHT(REPLICATE(fill, len) + source, len)
|
|
291
|
+
const source = this.render(expr.source);
|
|
292
|
+
const len = this.render(expr.length);
|
|
293
|
+
const fill = this.render(expr.fillString);
|
|
294
|
+
return `RIGHT(REPLICATE(${fill}, ${len}) + ${source}, ${len})`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
protected replace(expr: ExprReplace): string {
|
|
298
|
+
return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
protected upper(expr: ExprUpper): string {
|
|
302
|
+
return `UPPER(${this.render(expr.arg)})`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
protected lower(expr: ExprLower): string {
|
|
306
|
+
return `LOWER(${this.render(expr.arg)})`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
protected length(expr: ExprLength): string {
|
|
310
|
+
// MSSQL: LEN() (null Process)
|
|
311
|
+
return `LEN(ISNULL(${this.render(expr.arg)}, N''))`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
protected byteLength(expr: ExprByteLength): string {
|
|
315
|
+
// MSSQL: DATALENGTH() (null Process)
|
|
316
|
+
return `DATALENGTH(ISNULL(${this.render(expr.arg)}, N''))`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
protected substring(expr: ExprSubstring): string {
|
|
320
|
+
if (expr.length != null) {
|
|
321
|
+
return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
|
|
322
|
+
}
|
|
323
|
+
// MSSQL: length 없으면 끝까지
|
|
324
|
+
return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, LEN(${this.render(expr.source)}))`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
protected indexOf(expr: ExprIndexOf): string {
|
|
328
|
+
return `CHARINDEX(${this.render(expr.search)}, ${this.render(expr.source)})`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
|
|
333
|
+
//#region ========== Number ==========
|
|
334
|
+
|
|
335
|
+
protected abs(expr: ExprAbs): string {
|
|
336
|
+
return `ABS(${this.render(expr.arg)})`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
protected round(expr: ExprRound): string {
|
|
340
|
+
return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
protected ceil(expr: ExprCeil): string {
|
|
344
|
+
return `CEILING(${this.render(expr.arg)})`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
protected floor(expr: ExprFloor): string {
|
|
348
|
+
return `FLOOR(${this.render(expr.arg)})`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
//#endregion
|
|
352
|
+
|
|
353
|
+
//#region ========== Date ==========
|
|
354
|
+
|
|
355
|
+
protected year(expr: ExprYear): string {
|
|
356
|
+
return `YEAR(${this.render(expr.arg)})`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
protected month(expr: ExprMonth): string {
|
|
360
|
+
return `MONTH(${this.render(expr.arg)})`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
protected day(expr: ExprDay): string {
|
|
364
|
+
return `DAY(${this.render(expr.arg)})`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
protected hour(expr: ExprHour): string {
|
|
368
|
+
return `DATEPART(HOUR, ${this.render(expr.arg)})`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
protected minute(expr: ExprMinute): string {
|
|
372
|
+
return `DATEPART(MINUTE, ${this.render(expr.arg)})`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
protected second(expr: ExprSecond): string {
|
|
376
|
+
return `DATEPART(SECOND, ${this.render(expr.arg)})`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
protected isoWeek(expr: ExprIsoWeek): string {
|
|
380
|
+
const src = this.render(expr.arg);
|
|
381
|
+
return `DATEPART(ISO_WEEK, ${src})`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
protected isoWeekStartDate(expr: ExprIsoWeekStartDate): string {
|
|
385
|
+
const src = this.render(expr.arg);
|
|
386
|
+
// ISO 주의 시작일 (월요일) - @@DATEFIRST 무관하게 항상 월요일 return
|
|
387
|
+
// 원리: DATEDIFF(DAY, 0, date)는 1900-01-01(월요일)부터의 일수
|
|
388
|
+
// (일수 + 6) % 7 + 1 = 1(월), 2(화), ..., 7(일)
|
|
389
|
+
const weekDay = `((DATEDIFF(DAY, 0, ${src}) + 6) % 7 + 1)`;
|
|
390
|
+
return `DATEADD(DAY, 1 - ${weekDay}, CAST(${src} AS DATE))`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
protected isoYearMonth(expr: ExprIsoYearMonth): string {
|
|
394
|
+
const src = this.render(expr.arg);
|
|
395
|
+
return `FORMAT(${src}, 'yyyyMM')`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
protected dateDiff(expr: ExprDateDiff): string {
|
|
399
|
+
const from = this.render(expr.from);
|
|
400
|
+
const to = this.render(expr.to);
|
|
401
|
+
const unit = this.dateSeparatorToUnit(expr.separator);
|
|
402
|
+
return `DATEDIFF(${unit}, ${from}, ${to})`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
protected dateAdd(expr: ExprDateAdd): string {
|
|
406
|
+
const source = this.render(expr.source);
|
|
407
|
+
const value = this.render(expr.value);
|
|
408
|
+
const unit = this.dateSeparatorToUnit(expr.separator);
|
|
409
|
+
return `DATEADD(${unit}, ${value}, ${source})`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
protected formatDate(expr: ExprFormatDate): string {
|
|
413
|
+
// JS format → MSSQL FORMAT style
|
|
414
|
+
const mssqlFormat = this.convertDateFormat(expr.format);
|
|
415
|
+
return `FORMAT(${this.render(expr.source)}, '${mssqlFormat}')`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private dateSeparatorToUnit(sep: DateSeparator): string {
|
|
419
|
+
switch (sep) {
|
|
420
|
+
case "year":
|
|
421
|
+
return "YEAR";
|
|
422
|
+
case "month":
|
|
423
|
+
return "MONTH";
|
|
424
|
+
case "day":
|
|
425
|
+
return "DAY";
|
|
426
|
+
case "hour":
|
|
427
|
+
return "HOUR";
|
|
428
|
+
case "minute":
|
|
429
|
+
return "MINUTE";
|
|
430
|
+
case "second":
|
|
431
|
+
return "SECOND";
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private convertDateFormat(format: string): string {
|
|
436
|
+
// MSSQL FORMAT 함수용 (동일한 포맷 사용)
|
|
437
|
+
return format;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
//#endregion
|
|
441
|
+
|
|
442
|
+
//#region ========== condition ==========
|
|
443
|
+
|
|
444
|
+
protected ifNull(expr: ExprIfNull): string {
|
|
445
|
+
if (expr.args.length === 0) return "NULL";
|
|
446
|
+
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
447
|
+
// MSSQL: COALESCE
|
|
448
|
+
return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
protected nullIf(expr: ExprNullIf): string {
|
|
452
|
+
return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
protected is(expr: ExprIs): string {
|
|
456
|
+
return `CASE WHEN ${this.render(expr.condition)} THEN 1 ELSE 0 END`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
protected switch(expr: ExprSwitch): string {
|
|
460
|
+
const cases = expr.cases
|
|
461
|
+
.map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`)
|
|
462
|
+
.join(" ");
|
|
463
|
+
return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
protected if(expr: ExprIf): string {
|
|
467
|
+
const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
|
|
468
|
+
return `CASE WHEN ${this.render(expr.condition)} THEN ${this.render(expr.then)} ELSE ${elseVal} END`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
//#endregion
|
|
472
|
+
|
|
473
|
+
//#region ========== aggregation ==========
|
|
474
|
+
|
|
475
|
+
protected count(expr: ExprCount): string {
|
|
476
|
+
if (expr.arg != null) {
|
|
477
|
+
const distinct = expr.distinct ? "DISTINCT " : "";
|
|
478
|
+
return `COUNT(${distinct}${this.render(expr.arg)})`;
|
|
479
|
+
}
|
|
480
|
+
return "COUNT(*)";
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
protected sum(expr: ExprSum): string {
|
|
484
|
+
return `SUM(${this.render(expr.arg)})`;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
protected avg(expr: ExprAvg): string {
|
|
488
|
+
return `AVG(${this.render(expr.arg)})`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
protected max(expr: ExprMax): string {
|
|
492
|
+
return `MAX(${this.render(expr.arg)})`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
protected min(expr: ExprMin): string {
|
|
496
|
+
return `MIN(${this.render(expr.arg)})`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
//#endregion
|
|
500
|
+
|
|
501
|
+
//#region ========== Other ==========
|
|
502
|
+
|
|
503
|
+
protected greatest(expr: ExprGreatest): string {
|
|
504
|
+
if (expr.args.length === 0) throw new Error("greatest requires at least one argument.");
|
|
505
|
+
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
506
|
+
// MSSQL 2012+: VALUES + MAX 방식
|
|
507
|
+
const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
|
|
508
|
+
return `(SELECT MAX(v) FROM (VALUES ${values}) AS t(v))`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
protected least(expr: ExprLeast): string {
|
|
512
|
+
if (expr.args.length === 0) throw new Error("least requires at least one argument.");
|
|
513
|
+
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
514
|
+
// MSSQL 2012+: VALUES + MIN 방식
|
|
515
|
+
const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
|
|
516
|
+
return `(SELECT MIN(v) FROM (VALUES ${values}) AS t(v))`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
protected rowNum(_expr: ExprRowNum): string {
|
|
520
|
+
return "ROW_NUMBER() OVER (ORDER BY (SELECT NULL))";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
protected random(): string {
|
|
524
|
+
return "NEWID()";
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
protected cast(expr: ExprCast): string {
|
|
528
|
+
return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
//#endregion
|
|
532
|
+
|
|
533
|
+
//#region ========== Window ==========
|
|
534
|
+
|
|
535
|
+
protected window(expr: ExprWindow): string {
|
|
536
|
+
const fn = this.renderWindowFn(expr.fn);
|
|
537
|
+
let over = this.renderWindowSpec(expr.spec);
|
|
538
|
+
|
|
539
|
+
// LAST_VALUE는 Basic 프레임이 CURRENT ROW까지만 보므로 전체 프레임 명시 필요
|
|
540
|
+
if (expr.fn.type === "lastValue" && over.length > 0) {
|
|
541
|
+
over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return `${fn} OVER (${over})`;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private renderWindowFn(fn: ExprWindow["fn"]): string {
|
|
548
|
+
switch (fn.type) {
|
|
549
|
+
case "rowNumber":
|
|
550
|
+
return "ROW_NUMBER()";
|
|
551
|
+
case "rank":
|
|
552
|
+
return "RANK()";
|
|
553
|
+
case "denseRank":
|
|
554
|
+
return "DENSE_RANK()";
|
|
555
|
+
case "ntile":
|
|
556
|
+
return `NTILE(${fn.n})`;
|
|
557
|
+
case "lag": {
|
|
558
|
+
const offset = fn.offset ?? 1;
|
|
559
|
+
const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
|
|
560
|
+
return `LAG(${this.render(fn.column)}, ${offset}${def})`;
|
|
561
|
+
}
|
|
562
|
+
case "lead": {
|
|
563
|
+
const offset = fn.offset ?? 1;
|
|
564
|
+
const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
|
|
565
|
+
return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
|
|
566
|
+
}
|
|
567
|
+
case "firstValue":
|
|
568
|
+
return `FIRST_VALUE(${this.render(fn.column)})`;
|
|
569
|
+
case "lastValue":
|
|
570
|
+
return `LAST_VALUE(${this.render(fn.column)})`;
|
|
571
|
+
case "sum":
|
|
572
|
+
return `SUM(${this.render(fn.column)})`;
|
|
573
|
+
case "avg":
|
|
574
|
+
return `AVG(${this.render(fn.column)})`;
|
|
575
|
+
case "count":
|
|
576
|
+
return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
|
|
577
|
+
case "min":
|
|
578
|
+
return `MIN(${this.render(fn.column)})`;
|
|
579
|
+
case "max":
|
|
580
|
+
return `MAX(${this.render(fn.column)})`;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private renderWindowSpec(spec: ExprWindow["spec"]): string {
|
|
585
|
+
const parts: string[] = [];
|
|
586
|
+
if (spec.partitionBy != null && spec.partitionBy.length > 0) {
|
|
587
|
+
parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
|
|
588
|
+
}
|
|
589
|
+
if (spec.orderBy != null && spec.orderBy.length > 0) {
|
|
590
|
+
const orderParts = spec.orderBy.map(
|
|
591
|
+
([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`,
|
|
592
|
+
);
|
|
593
|
+
parts.push(`ORDER BY ${orderParts.join(", ")}`);
|
|
594
|
+
}
|
|
595
|
+
return parts.join(" ");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
//#endregion
|
|
599
|
+
|
|
600
|
+
//#region ========== System ==========
|
|
601
|
+
|
|
602
|
+
protected subquery(expr: ExprSubquery): string {
|
|
603
|
+
return `(${this.buildSelect(expr.queryDef)})`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
//#endregion
|
|
607
|
+
}
|