@simplysm/orm-common 13.0.99 → 14.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/dist/create-db-context.d.ts +10 -10
  2. package/dist/create-db-context.js +312 -276
  3. package/dist/create-db-context.js.map +1 -6
  4. package/dist/ddl/column-ddl.d.ts +4 -4
  5. package/dist/ddl/column-ddl.js +41 -35
  6. package/dist/ddl/column-ddl.js.map +1 -6
  7. package/dist/ddl/initialize.d.ts +17 -17
  8. package/dist/ddl/initialize.js +200 -142
  9. package/dist/ddl/initialize.js.map +1 -6
  10. package/dist/ddl/relation-ddl.d.ts +6 -6
  11. package/dist/ddl/relation-ddl.js +55 -48
  12. package/dist/ddl/relation-ddl.js.map +1 -6
  13. package/dist/ddl/schema-ddl.d.ts +4 -4
  14. package/dist/ddl/schema-ddl.js +21 -15
  15. package/dist/ddl/schema-ddl.js.map +1 -6
  16. package/dist/ddl/table-ddl.d.ts +20 -20
  17. package/dist/ddl/table-ddl.js +139 -93
  18. package/dist/ddl/table-ddl.js.map +1 -6
  19. package/dist/define-db-context.js +10 -13
  20. package/dist/define-db-context.js.map +1 -6
  21. package/dist/errors/db-transaction-error.d.ts +15 -15
  22. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  23. package/dist/errors/db-transaction-error.js +53 -19
  24. package/dist/errors/db-transaction-error.js.map +1 -6
  25. package/dist/exec/executable.d.ts +23 -23
  26. package/dist/exec/executable.js +94 -40
  27. package/dist/exec/executable.js.map +1 -6
  28. package/dist/exec/queryable.d.ts +97 -97
  29. package/dist/exec/queryable.js +1310 -1204
  30. package/dist/exec/queryable.js.map +1 -6
  31. package/dist/exec/search-parser.d.ts +31 -31
  32. package/dist/exec/search-parser.d.ts.map +1 -1
  33. package/dist/exec/search-parser.js +158 -59
  34. package/dist/exec/search-parser.js.map +1 -6
  35. package/dist/expr/expr-unit.d.ts +4 -4
  36. package/dist/expr/expr-unit.js +24 -18
  37. package/dist/expr/expr-unit.js.map +1 -6
  38. package/dist/expr/expr.d.ts +6 -6
  39. package/dist/expr/expr.js +1872 -1844
  40. package/dist/expr/expr.js.map +1 -6
  41. package/dist/index.js +23 -1
  42. package/dist/index.js.map +1 -6
  43. package/dist/models/system-migration.js +7 -7
  44. package/dist/models/system-migration.js.map +1 -6
  45. package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
  46. package/dist/query-builder/base/expr-renderer-base.js +27 -21
  47. package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
  48. package/dist/query-builder/base/query-builder-base.d.ts +21 -21
  49. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  50. package/dist/query-builder/base/query-builder-base.js +90 -80
  51. package/dist/query-builder/base/query-builder-base.js.map +1 -6
  52. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
  53. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  54. package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
  55. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
  56. package/dist/query-builder/mssql/mssql-query-builder.js +483 -443
  57. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
  58. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
  59. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  60. package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
  61. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
  62. package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
  63. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
  64. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
  65. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  66. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
  67. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
  68. package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
  69. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
  70. package/dist/query-builder/query-builder.d.ts +1 -1
  71. package/dist/query-builder/query-builder.js +13 -13
  72. package/dist/query-builder/query-builder.js.map +1 -6
  73. package/dist/schema/factory/column-builder.d.ts +84 -84
  74. package/dist/schema/factory/column-builder.js +248 -185
  75. package/dist/schema/factory/column-builder.js.map +1 -6
  76. package/dist/schema/factory/index-builder.d.ts +38 -38
  77. package/dist/schema/factory/index-builder.js +144 -85
  78. package/dist/schema/factory/index-builder.js.map +1 -6
  79. package/dist/schema/factory/relation-builder.d.ts +91 -91
  80. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  81. package/dist/schema/factory/relation-builder.js +274 -136
  82. package/dist/schema/factory/relation-builder.js.map +1 -6
  83. package/dist/schema/procedure-builder.d.ts +51 -51
  84. package/dist/schema/procedure-builder.d.ts.map +1 -1
  85. package/dist/schema/procedure-builder.js +205 -131
  86. package/dist/schema/procedure-builder.js.map +1 -6
  87. package/dist/schema/table-builder.d.ts +55 -55
  88. package/dist/schema/table-builder.d.ts.map +1 -1
  89. package/dist/schema/table-builder.js +274 -205
  90. package/dist/schema/table-builder.js.map +1 -6
  91. package/dist/schema/view-builder.d.ts +44 -44
  92. package/dist/schema/view-builder.d.ts.map +1 -1
  93. package/dist/schema/view-builder.js +189 -116
  94. package/dist/schema/view-builder.js.map +1 -6
  95. package/dist/types/column.js +60 -30
  96. package/dist/types/column.js.map +1 -6
  97. package/dist/types/db-context-def.d.ts +9 -9
  98. package/dist/types/db-context-def.js +2 -1
  99. package/dist/types/db-context-def.js.map +1 -6
  100. package/dist/types/db.d.ts +47 -47
  101. package/dist/types/db.js +15 -5
  102. package/dist/types/db.js.map +1 -6
  103. package/dist/types/expr.d.ts +81 -81
  104. package/dist/types/expr.d.ts.map +1 -1
  105. package/dist/types/expr.js +3 -1
  106. package/dist/types/expr.js.map +1 -6
  107. package/dist/types/query-def.d.ts +46 -46
  108. package/dist/types/query-def.d.ts.map +1 -1
  109. package/dist/types/query-def.js +31 -24
  110. package/dist/types/query-def.js.map +1 -6
  111. package/dist/utils/result-parser.js +362 -221
  112. package/dist/utils/result-parser.js.map +1 -6
  113. package/package.json +5 -7
  114. package/src/create-db-context.ts +31 -31
  115. package/src/ddl/column-ddl.ts +4 -4
  116. package/src/ddl/initialize.ts +38 -38
  117. package/src/ddl/relation-ddl.ts +6 -6
  118. package/src/ddl/schema-ddl.ts +4 -4
  119. package/src/ddl/table-ddl.ts +24 -24
  120. package/src/errors/db-transaction-error.ts +13 -13
  121. package/src/exec/executable.ts +25 -25
  122. package/src/exec/queryable.ts +134 -134
  123. package/src/exec/search-parser.ts +50 -50
  124. package/src/expr/expr-unit.ts +4 -4
  125. package/src/expr/expr.ts +13 -13
  126. package/src/index.ts +8 -8
  127. package/src/models/system-migration.ts +1 -1
  128. package/src/query-builder/base/expr-renderer-base.ts +21 -21
  129. package/src/query-builder/base/query-builder-base.ts +33 -33
  130. package/src/query-builder/mssql/mssql-expr-renderer.ts +11 -11
  131. package/src/query-builder/mssql/mssql-query-builder.ts +11 -11
  132. package/src/query-builder/mysql/mysql-expr-renderer.ts +15 -15
  133. package/src/query-builder/mysql/mysql-query-builder.ts +3 -3
  134. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +9 -9
  135. package/src/query-builder/postgresql/postgresql-query-builder.ts +7 -7
  136. package/src/query-builder/query-builder.ts +1 -1
  137. package/src/schema/factory/column-builder.ts +86 -86
  138. package/src/schema/factory/index-builder.ts +38 -38
  139. package/src/schema/factory/relation-builder.ts +93 -93
  140. package/src/schema/procedure-builder.ts +52 -52
  141. package/src/schema/table-builder.ts +56 -56
  142. package/src/schema/view-builder.ts +45 -45
  143. package/src/types/column.ts +1 -1
  144. package/src/types/db-context-def.ts +15 -15
  145. package/src/types/db.ts +50 -50
  146. package/src/types/expr.ts +103 -103
  147. package/src/types/query-def.ts +50 -50
  148. package/src/utils/result-parser.ts +39 -39
  149. package/README.md +0 -192
  150. package/docs/core.md +0 -234
  151. package/docs/expression.md +0 -234
  152. package/docs/query-builder.md +0 -93
  153. package/docs/queryable.md +0 -198
  154. package/docs/schema-builders.md +0 -463
  155. package/docs/types.md +0 -445
  156. package/docs/utilities.md +0 -27
  157. package/tests/db-context/create-db-context.spec.ts +0 -193
  158. package/tests/db-context/define-db-context.spec.ts +0 -17
  159. package/tests/ddl/basic.expected.ts +0 -341
  160. package/tests/ddl/basic.spec.ts +0 -557
  161. package/tests/ddl/column-builder.expected.ts +0 -310
  162. package/tests/ddl/column-builder.spec.ts +0 -525
  163. package/tests/ddl/index-builder.expected.ts +0 -38
  164. package/tests/ddl/index-builder.spec.ts +0 -148
  165. package/tests/ddl/procedure-builder.expected.ts +0 -52
  166. package/tests/ddl/procedure-builder.spec.ts +0 -128
  167. package/tests/ddl/relation-builder.expected.ts +0 -36
  168. package/tests/ddl/relation-builder.spec.ts +0 -171
  169. package/tests/ddl/table-builder.expected.ts +0 -113
  170. package/tests/ddl/table-builder.spec.ts +0 -399
  171. package/tests/ddl/view-builder.expected.ts +0 -38
  172. package/tests/ddl/view-builder.spec.ts +0 -116
  173. package/tests/dml/delete.expected.ts +0 -96
  174. package/tests/dml/delete.spec.ts +0 -127
  175. package/tests/dml/insert.expected.ts +0 -192
  176. package/tests/dml/insert.spec.ts +0 -210
  177. package/tests/dml/update.expected.ts +0 -176
  178. package/tests/dml/update.spec.ts +0 -222
  179. package/tests/dml/upsert.expected.ts +0 -215
  180. package/tests/dml/upsert.spec.ts +0 -190
  181. package/tests/errors/queryable-errors.spec.ts +0 -126
  182. package/tests/escape.spec.ts +0 -59
  183. package/tests/examples/pivot.expected.ts +0 -211
  184. package/tests/examples/pivot.spec.ts +0 -200
  185. package/tests/examples/sampling.expected.ts +0 -69
  186. package/tests/examples/sampling.spec.ts +0 -42
  187. package/tests/examples/unpivot.expected.ts +0 -120
  188. package/tests/examples/unpivot.spec.ts +0 -161
  189. package/tests/exec/search-parser.spec.ts +0 -267
  190. package/tests/executable/basic.expected.ts +0 -18
  191. package/tests/executable/basic.spec.ts +0 -54
  192. package/tests/expr/comparison.expected.ts +0 -282
  193. package/tests/expr/comparison.spec.ts +0 -334
  194. package/tests/expr/conditional.expected.ts +0 -134
  195. package/tests/expr/conditional.spec.ts +0 -249
  196. package/tests/expr/date.expected.ts +0 -332
  197. package/tests/expr/date.spec.ts +0 -459
  198. package/tests/expr/math.expected.ts +0 -62
  199. package/tests/expr/math.spec.ts +0 -59
  200. package/tests/expr/string.expected.ts +0 -218
  201. package/tests/expr/string.spec.ts +0 -300
  202. package/tests/expr/utility.expected.ts +0 -147
  203. package/tests/expr/utility.spec.ts +0 -155
  204. package/tests/select/basic.expected.ts +0 -322
  205. package/tests/select/basic.spec.ts +0 -433
  206. package/tests/select/filter.expected.ts +0 -357
  207. package/tests/select/filter.spec.ts +0 -954
  208. package/tests/select/group.expected.ts +0 -169
  209. package/tests/select/group.spec.ts +0 -159
  210. package/tests/select/join.expected.ts +0 -582
  211. package/tests/select/join.spec.ts +0 -692
  212. package/tests/select/order.expected.ts +0 -150
  213. package/tests/select/order.spec.ts +0 -140
  214. package/tests/select/recursive-cte.expected.ts +0 -244
  215. package/tests/select/recursive-cte.spec.ts +0 -514
  216. package/tests/select/result-meta.spec.ts +0 -270
  217. package/tests/select/subquery.expected.ts +0 -363
  218. package/tests/select/subquery.spec.ts +0 -441
  219. package/tests/select/view.expected.ts +0 -155
  220. package/tests/select/view.spec.ts +0 -235
  221. package/tests/select/window.expected.ts +0 -345
  222. package/tests/select/window.spec.ts +0 -433
  223. package/tests/setup/MockExecutor.ts +0 -18
  224. package/tests/setup/TestDbContext.ts +0 -59
  225. package/tests/setup/models/Company.ts +0 -13
  226. package/tests/setup/models/Employee.ts +0 -10
  227. package/tests/setup/models/MonthlySales.ts +0 -11
  228. package/tests/setup/models/Post.ts +0 -16
  229. package/tests/setup/models/Sales.ts +0 -10
  230. package/tests/setup/models/User.ts +0 -19
  231. package/tests/setup/procedure/GetAllUsers.ts +0 -9
  232. package/tests/setup/procedure/GetUserById.ts +0 -12
  233. package/tests/setup/test-utils.ts +0 -72
  234. package/tests/setup/views/ActiveUsers.ts +0 -8
  235. package/tests/setup/views/UserSummary.ts +0 -11
  236. package/tests/types/nullable-queryable-record.spec.ts +0 -97
  237. package/tests/utils/result-parser-perf.spec.ts +0 -143
  238. package/tests/utils/result-parser.spec.ts +0 -667
@@ -1,423 +1,450 @@
1
1
  import { bytes, DateOnly, DateTime, Time, Uuid } from "@simplysm/core-common";
2
2
  import { ExprRendererBase } from "../base/expr-renderer-base.js";
3
- class MssqlExprRenderer extends ExprRendererBase {
4
- //#region ========== Utilities (public - also used by QueryBuilder) ==========
5
- /** Wrap identifier */
6
- wrap(name) {
7
- return `[${name.replace(/]/g, "]]")}]`;
8
- }
9
- /** Escape for SQL string literals (returns without quotes) */
10
- escapeString(value) {
11
- return value.replace(/'/g, "''");
12
- }
13
- /** Value escape */
14
- escapeValue(value) {
15
- if (value == null) {
16
- return "NULL";
17
- }
18
- if (typeof value === "string") {
19
- return `N'${value.replace(/'/g, "''")}'`;
20
- }
21
- if (typeof value === "number") {
22
- return String(value);
23
- }
24
- if (typeof value === "boolean") {
25
- return value ? "1" : "0";
26
- }
27
- if (value instanceof DateTime) {
28
- return `'${value.toFormatString("yyyy-MM-dd HH:mm:ss")}'`;
29
- }
30
- if (value instanceof DateOnly) {
31
- return `'${value.toFormatString("yyyy-MM-dd")}'`;
32
- }
33
- if (value instanceof Time) {
34
- return `'${value.toFormatString("HH:mm:ss")}'`;
35
- }
36
- if (value instanceof Uuid) {
37
- return `'${value.toString()}'`;
38
- }
39
- if (value instanceof Uint8Array) {
40
- return `0x${bytes.toHex(value)}`;
41
- }
42
- throw new Error(`Unknown value type: ${typeof value}`);
43
- }
44
- /** DataType → SQL type */
45
- renderDataType(dataType) {
46
- switch (dataType.type) {
47
- case "int":
48
- return "INT";
49
- case "bigint":
50
- return "BIGINT";
51
- case "float":
52
- return "REAL";
53
- case "double":
54
- return "FLOAT";
55
- case "decimal":
56
- return dataType.scale != null ? `DECIMAL(${dataType.precision}, ${dataType.scale})` : `DECIMAL(${dataType.precision})`;
57
- case "varchar":
58
- return `NVARCHAR(${dataType.length})`;
59
- case "char":
60
- return `NCHAR(${dataType.length})`;
61
- case "text":
62
- return "NVARCHAR(MAX)";
63
- case "binary":
64
- return "VARBINARY(MAX)";
65
- case "boolean":
66
- return "BIT";
67
- case "datetime":
68
- return "DATETIME2";
69
- case "date":
70
- return "DATE";
71
- case "time":
72
- return "TIME";
73
- case "uuid":
74
- return "UNIQUEIDENTIFIER";
75
- }
76
- }
77
- //#endregion
78
- //#region ========== value ==========
79
- column(expr) {
80
- return expr.path.map((p) => this.wrap(p)).join(".");
81
- }
82
- value(expr) {
83
- return this.escapeValue(expr.value);
84
- }
85
- raw(expr) {
86
- return expr.sql.replace(/\$(\d+)/g, (_, num) => {
87
- const idx = parseInt(num) - 1;
88
- return idx < expr.params.length ? this.render(expr.params[idx]) : `$${num}`;
89
- });
90
- }
91
- //#endregion
92
- //#region ========== comparison (null-safe) ==========
93
- eq(expr) {
94
- const left = this.render(expr.source);
95
- const right = this.render(expr.target);
96
- return `((${left} IS NULL AND ${right} IS NULL) OR ${left} = ${right})`;
97
- }
98
- gt(expr) {
99
- return `${this.render(expr.source)} > ${this.render(expr.target)}`;
100
- }
101
- lt(expr) {
102
- return `${this.render(expr.source)} < ${this.render(expr.target)}`;
103
- }
104
- gte(expr) {
105
- return `${this.render(expr.source)} >= ${this.render(expr.target)}`;
106
- }
107
- lte(expr) {
108
- return `${this.render(expr.source)} <= ${this.render(expr.target)}`;
109
- }
110
- between(expr) {
111
- const source = this.render(expr.source);
112
- if (expr.from != null && expr.to != null) {
113
- return `${source} BETWEEN ${this.render(expr.from)} AND ${this.render(expr.to)}`;
114
- }
115
- if (expr.from != null) {
116
- return `${source} >= ${this.render(expr.from)}`;
117
- }
118
- if (expr.to != null) {
119
- return `${source} <= ${this.render(expr.to)}`;
120
- }
121
- return "1=1";
122
- }
123
- null(expr) {
124
- return `${this.render(expr.arg)} IS NULL`;
125
- }
126
- like(expr) {
127
- return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\'`;
128
- }
129
- regexp(_expr) {
130
- throw new Error("MSSQL does not natively support REGEXP.");
131
- }
132
- in(expr) {
133
- if (expr.values.length === 0) {
134
- return "1=0";
135
- }
136
- const values = expr.values.map((v) => this.render(v)).join(", ");
137
- return `${this.render(expr.source)} IN (${values})`;
138
- }
139
- inQuery(expr) {
140
- return `${this.render(expr.source)} IN (${this.buildSelect(expr.query)})`;
141
- }
142
- exists(expr) {
143
- const subquery = this.buildSelect({
144
- ...expr.query,
145
- select: { _: { type: "value", value: 1 } }
146
- });
147
- return `EXISTS (${subquery})`;
148
- }
149
- //#endregion
150
- //#region ========== logic ==========
151
- not(expr) {
152
- return `NOT (${this.render(expr.arg)})`;
153
- }
154
- and(expr) {
155
- if (expr.conditions.length === 0) return "1=1";
156
- return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
157
- }
158
- or(expr) {
159
- if (expr.conditions.length === 0) return "1=0";
160
- return `(${expr.conditions.map((c) => this.render(c)).join(" OR ")})`;
161
- }
162
- //#endregion
163
- //#region ========== String (null handling) ==========
164
- concat(expr) {
165
- const args = expr.args.map((a) => this.render(a)).join(", ");
166
- return `CONCAT(${args})`;
167
- }
168
- left(expr) {
169
- return `LEFT(${this.render(expr.source)}, ${this.render(expr.length)})`;
170
- }
171
- right(expr) {
172
- return `RIGHT(${this.render(expr.source)}, ${this.render(expr.length)})`;
173
- }
174
- trim(expr) {
175
- return `RTRIM(LTRIM(${this.render(expr.arg)}))`;
176
- }
177
- padStart(expr) {
178
- const source = this.render(expr.source);
179
- const len = this.render(expr.length);
180
- const fill = this.render(expr.fillString);
181
- return `RIGHT(REPLICATE(${fill}, ${len}) + ${source}, ${len})`;
182
- }
183
- replace(expr) {
184
- return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
185
- }
186
- upper(expr) {
187
- return `UPPER(${this.render(expr.arg)})`;
188
- }
189
- lower(expr) {
190
- return `LOWER(${this.render(expr.arg)})`;
191
- }
192
- length(expr) {
193
- return `LEN(ISNULL(${this.render(expr.arg)}, N''))`;
194
- }
195
- byteLength(expr) {
196
- return `DATALENGTH(ISNULL(${this.render(expr.arg)}, N''))`;
197
- }
198
- substring(expr) {
199
- if (expr.length != null) {
200
- return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
201
- }
202
- return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, LEN(${this.render(expr.source)}))`;
203
- }
204
- indexOf(expr) {
205
- return `CHARINDEX(${this.render(expr.search)}, ${this.render(expr.source)})`;
206
- }
207
- //#endregion
208
- //#region ========== Number ==========
209
- abs(expr) {
210
- return `ABS(${this.render(expr.arg)})`;
211
- }
212
- round(expr) {
213
- return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
214
- }
215
- ceil(expr) {
216
- return `CEILING(${this.render(expr.arg)})`;
217
- }
218
- floor(expr) {
219
- return `FLOOR(${this.render(expr.arg)})`;
220
- }
221
- //#endregion
222
- //#region ========== Date ==========
223
- year(expr) {
224
- return `YEAR(${this.render(expr.arg)})`;
225
- }
226
- month(expr) {
227
- return `MONTH(${this.render(expr.arg)})`;
228
- }
229
- day(expr) {
230
- return `DAY(${this.render(expr.arg)})`;
231
- }
232
- hour(expr) {
233
- return `DATEPART(HOUR, ${this.render(expr.arg)})`;
234
- }
235
- minute(expr) {
236
- return `DATEPART(MINUTE, ${this.render(expr.arg)})`;
237
- }
238
- second(expr) {
239
- return `DATEPART(SECOND, ${this.render(expr.arg)})`;
240
- }
241
- isoWeek(expr) {
242
- const src = this.render(expr.arg);
243
- return `DATEPART(ISO_WEEK, ${src})`;
244
- }
245
- isoWeekStartDate(expr) {
246
- const src = this.render(expr.arg);
247
- const weekDay = `((DATEDIFF(DAY, 0, ${src}) + 6) % 7 + 1)`;
248
- return `DATEADD(DAY, 1 - ${weekDay}, CAST(${src} AS DATE))`;
249
- }
250
- isoYearMonth(expr) {
251
- const src = this.render(expr.arg);
252
- return `FORMAT(${src}, 'yyyyMM')`;
253
- }
254
- dateDiff(expr) {
255
- const from = this.render(expr.from);
256
- const to = this.render(expr.to);
257
- const unit = this.dateUnitToSql(expr.unit);
258
- return `DATEDIFF(${unit}, ${from}, ${to})`;
259
- }
260
- dateAdd(expr) {
261
- const source = this.render(expr.source);
262
- const value = this.render(expr.value);
263
- const unit = this.dateUnitToSql(expr.unit);
264
- return `DATEADD(${unit}, ${value}, ${source})`;
265
- }
266
- formatDate(expr) {
267
- const mssqlFormat = this.convertDateFormat(expr.format);
268
- return `FORMAT(${this.render(expr.source)}, '${mssqlFormat}')`;
269
- }
270
- dateUnitToSql(unit) {
271
- switch (unit) {
272
- case "year":
273
- return "YEAR";
274
- case "month":
275
- return "MONTH";
276
- case "day":
277
- return "DAY";
278
- case "hour":
279
- return "HOUR";
280
- case "minute":
281
- return "MINUTE";
282
- case "second":
283
- return "SECOND";
284
- }
285
- }
286
- convertDateFormat(format) {
287
- return format;
288
- }
289
- //#endregion
290
- //#region ========== condition ==========
291
- coalesce(expr) {
292
- if (expr.args.length === 0) return "NULL";
293
- if (expr.args.length === 1) return this.render(expr.args[0]);
294
- return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
295
- }
296
- nullIf(expr) {
297
- return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
298
- }
299
- is(expr) {
300
- return `CASE WHEN ${this.render(expr.condition)} THEN 1 ELSE 0 END`;
301
- }
302
- switch(expr) {
303
- const cases = expr.cases.map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`).join(" ");
304
- return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
305
- }
306
- if(expr) {
307
- const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
308
- return `CASE WHEN ${this.render(expr.condition)} THEN ${this.render(expr.then)} ELSE ${elseVal} END`;
309
- }
310
- //#endregion
311
- //#region ========== aggregation ==========
312
- count(expr) {
313
- if (expr.arg != null) {
314
- const distinct = expr.distinct ? "DISTINCT " : "";
315
- return `COUNT(${distinct}${this.render(expr.arg)})`;
316
- }
317
- return "COUNT(*)";
318
- }
319
- sum(expr) {
320
- return `SUM(${this.render(expr.arg)})`;
321
- }
322
- avg(expr) {
323
- return `AVG(${this.render(expr.arg)})`;
324
- }
325
- max(expr) {
326
- return `MAX(${this.render(expr.arg)})`;
327
- }
328
- min(expr) {
329
- return `MIN(${this.render(expr.arg)})`;
330
- }
331
- //#endregion
332
- //#region ========== Other ==========
333
- greatest(expr) {
334
- if (expr.args.length === 0) throw new Error("greatest requires at least one argument.");
335
- if (expr.args.length === 1) return this.render(expr.args[0]);
336
- const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
337
- return `(SELECT MAX(v) FROM (VALUES ${values}) AS t(v))`;
338
- }
339
- least(expr) {
340
- if (expr.args.length === 0) throw new Error("least requires at least one argument.");
341
- if (expr.args.length === 1) return this.render(expr.args[0]);
342
- const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
343
- return `(SELECT MIN(v) FROM (VALUES ${values}) AS t(v))`;
344
- }
345
- rowNum(_expr) {
346
- return "ROW_NUMBER() OVER (ORDER BY (SELECT NULL))";
347
- }
348
- random(_expr) {
349
- return "NEWID()";
350
- }
351
- cast(expr) {
352
- return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
353
- }
354
- //#endregion
355
- //#region ========== Window ==========
356
- window(expr) {
357
- const fn = this.renderWindowFn(expr.fn);
358
- let over = this.renderWindowSpec(expr.spec);
359
- if (expr.fn.type === "lastValue" && over.length > 0) {
360
- over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
361
- }
362
- return `${fn} OVER (${over})`;
363
- }
364
- renderWindowFn(fn) {
365
- switch (fn.type) {
366
- case "rowNumber":
367
- return "ROW_NUMBER()";
368
- case "rank":
369
- return "RANK()";
370
- case "denseRank":
371
- return "DENSE_RANK()";
372
- case "ntile":
373
- return `NTILE(${fn.n})`;
374
- case "lag": {
375
- const offset = fn.offset ?? 1;
376
- const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
377
- return `LAG(${this.render(fn.column)}, ${offset}${def})`;
378
- }
379
- case "lead": {
380
- const offset = fn.offset ?? 1;
381
- const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
382
- return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
383
- }
384
- case "firstValue":
385
- return `FIRST_VALUE(${this.render(fn.column)})`;
386
- case "lastValue":
387
- return `LAST_VALUE(${this.render(fn.column)})`;
388
- case "sum":
389
- return `SUM(${this.render(fn.column)})`;
390
- case "avg":
391
- return `AVG(${this.render(fn.column)})`;
392
- case "count":
393
- return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
394
- case "min":
395
- return `MIN(${this.render(fn.column)})`;
396
- case "max":
397
- return `MAX(${this.render(fn.column)})`;
398
- }
399
- }
400
- renderWindowSpec(spec) {
401
- const parts = [];
402
- if (spec.partitionBy != null && spec.partitionBy.length > 0) {
403
- parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
404
- }
405
- if (spec.orderBy != null && spec.orderBy.length > 0) {
406
- const orderParts = spec.orderBy.map(
407
- ([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`
408
- );
409
- parts.push(`ORDER BY ${orderParts.join(", ")}`);
410
- }
411
- return parts.join(" ");
412
- }
413
- //#endregion
414
- //#region ========== System ==========
415
- subquery(expr) {
416
- return `(${this.buildSelect(expr.queryDef)})`;
417
- }
418
- //#endregion
3
+ /**
4
+ * MSSQL 표현식 렌더러
5
+ */
6
+ export class MssqlExprRenderer extends ExprRendererBase {
7
+ //#region ========== 유틸리티 (public - QueryBuilder에서도 사용) ==========
8
+ /** 식별자 감싸기 */
9
+ wrap(name) {
10
+ return `[${name.replace(/]/g, "]]")}]`;
11
+ }
12
+ /** SQL 문자열 리터럴용 이스케이프 (따옴표 제외) */
13
+ escapeString(value) {
14
+ return value.replace(/'/g, "''");
15
+ }
16
+ /** 값 이스케이프 */
17
+ escapeValue(value) {
18
+ if (value == null) {
19
+ return "NULL";
20
+ }
21
+ if (typeof value === "string") {
22
+ return `N'${value.replace(/'/g, "''")}'`;
23
+ }
24
+ if (typeof value === "number") {
25
+ return String(value);
26
+ }
27
+ if (typeof value === "boolean") {
28
+ return value ? "1" : "0";
29
+ }
30
+ if (value instanceof DateTime) {
31
+ return `'${value.toFormatString("yyyy-MM-dd HH:mm:ss")}'`;
32
+ }
33
+ if (value instanceof DateOnly) {
34
+ return `'${value.toFormatString("yyyy-MM-dd")}'`;
35
+ }
36
+ if (value instanceof Time) {
37
+ return `'${value.toFormatString("HH:mm:ss")}'`;
38
+ }
39
+ if (value instanceof Uuid) {
40
+ return `'${value.toString()}'`;
41
+ }
42
+ if (value instanceof Uint8Array) {
43
+ return `0x${bytes.toHex(value)}`;
44
+ }
45
+ throw new Error(`알 수 없는 값 타입: ${typeof value}`);
46
+ }
47
+ /** DataType → SQL type */
48
+ renderDataType(dataType) {
49
+ switch (dataType.type) {
50
+ case "int":
51
+ return "INT";
52
+ case "bigint":
53
+ return "BIGINT";
54
+ case "float":
55
+ return "REAL";
56
+ case "double":
57
+ return "FLOAT";
58
+ case "decimal":
59
+ return dataType.scale != null
60
+ ? `DECIMAL(${dataType.precision}, ${dataType.scale})`
61
+ : `DECIMAL(${dataType.precision})`;
62
+ case "varchar":
63
+ return `NVARCHAR(${dataType.length})`;
64
+ case "char":
65
+ return `NCHAR(${dataType.length})`;
66
+ case "text":
67
+ return "NVARCHAR(MAX)";
68
+ case "binary":
69
+ return "VARBINARY(MAX)";
70
+ case "boolean":
71
+ return "BIT";
72
+ case "datetime":
73
+ return "DATETIME2";
74
+ case "date":
75
+ return "DATE";
76
+ case "time":
77
+ return "TIME";
78
+ case "uuid":
79
+ return "UNIQUEIDENTIFIER";
80
+ }
81
+ }
82
+ //#endregion
83
+ //#region ========== value ==========
84
+ column(expr) {
85
+ return expr.path.map((p) => this.wrap(p)).join(".");
86
+ }
87
+ value(expr) {
88
+ return this.escapeValue(expr.value);
89
+ }
90
+ raw(expr) {
91
+ return expr.sql.replace(/\$(\d+)/g, (_, num) => {
92
+ const idx = parseInt(num) - 1;
93
+ return idx < expr.params.length ? this.render(expr.params[idx]) : `$${num}`;
94
+ });
95
+ }
96
+ //#endregion
97
+ //#region ========== comparison (null-safe) ==========
98
+ eq(expr) {
99
+ // MSSQL: null-safe equal (OR Pattern)
100
+ const left = this.render(expr.source);
101
+ const right = this.render(expr.target);
102
+ return `((${left} IS NULL AND ${right} IS NULL) OR ${left} = ${right})`;
103
+ }
104
+ gt(expr) {
105
+ return `${this.render(expr.source)} > ${this.render(expr.target)}`;
106
+ }
107
+ lt(expr) {
108
+ return `${this.render(expr.source)} < ${this.render(expr.target)}`;
109
+ }
110
+ gte(expr) {
111
+ return `${this.render(expr.source)} >= ${this.render(expr.target)}`;
112
+ }
113
+ lte(expr) {
114
+ return `${this.render(expr.source)} <= ${this.render(expr.target)}`;
115
+ }
116
+ between(expr) {
117
+ const source = this.render(expr.source);
118
+ if (expr.from != null && expr.to != null) {
119
+ return `${source} BETWEEN ${this.render(expr.from)} AND ${this.render(expr.to)}`;
120
+ }
121
+ if (expr.from != null) {
122
+ return `${source} >= ${this.render(expr.from)}`;
123
+ }
124
+ if (expr.to != null) {
125
+ return `${source} <= ${this.render(expr.to)}`;
126
+ }
127
+ return "1=1";
128
+ }
129
+ null(expr) {
130
+ return `${this.render(expr.arg)} IS NULL`;
131
+ }
132
+ like(expr) {
133
+ // 항상 ESCAPE '\' 추가
134
+ return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\'`;
135
+ }
136
+ regexp(_expr) {
137
+ // MSSQL은 REGEXP을 지원하지 않음 - LIKE 패턴 또는 CLR 필요
138
+ throw new Error("MSSQL은 기본적으로 REGEXP을 지원하지 않습니다.");
139
+ }
140
+ in(expr) {
141
+ if (expr.values.length === 0) {
142
+ return "1=0"; // empty IN is always false
143
+ }
144
+ const values = expr.values.map((v) => this.render(v)).join(", ");
145
+ return `${this.render(expr.source)} IN (${values})`;
146
+ }
147
+ inQuery(expr) {
148
+ return `${this.render(expr.source)} IN (${this.buildSelect(expr.query)})`;
149
+ }
150
+ exists(expr) {
151
+ // Render as SELECT 1
152
+ const subquery = this.buildSelect({
153
+ ...expr.query,
154
+ select: { _: { type: "value", value: 1 } },
155
+ });
156
+ return `EXISTS (${subquery})`;
157
+ }
158
+ //#endregion
159
+ //#region ========== logic ==========
160
+ not(expr) {
161
+ return `NOT (${this.render(expr.arg)})`;
162
+ }
163
+ and(expr) {
164
+ if (expr.conditions.length === 0)
165
+ return "1=1";
166
+ return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
167
+ }
168
+ or(expr) {
169
+ if (expr.conditions.length === 0)
170
+ return "1=0";
171
+ return `(${expr.conditions.map((c) => this.render(c)).join(" OR ")})`;
172
+ }
173
+ //#endregion
174
+ //#region ========== String (null handling) ==========
175
+ concat(expr) {
176
+ // MSSQL 2012+: CONCAT function automatically treats NULL as empty string
177
+ const args = expr.args.map((a) => this.render(a)).join(", ");
178
+ return `CONCAT(${args})`;
179
+ }
180
+ left(expr) {
181
+ return `LEFT(${this.render(expr.source)}, ${this.render(expr.length)})`;
182
+ }
183
+ right(expr) {
184
+ return `RIGHT(${this.render(expr.source)}, ${this.render(expr.length)})`;
185
+ }
186
+ trim(expr) {
187
+ return `RTRIM(LTRIM(${this.render(expr.arg)}))`;
188
+ }
189
+ padStart(expr) {
190
+ // MSSQL: RIGHT(REPLICATE(fill, len) + source, len)
191
+ const source = this.render(expr.source);
192
+ const len = this.render(expr.length);
193
+ const fill = this.render(expr.fillString);
194
+ return `RIGHT(REPLICATE(${fill}, ${len}) + ${source}, ${len})`;
195
+ }
196
+ replace(expr) {
197
+ return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
198
+ }
199
+ upper(expr) {
200
+ return `UPPER(${this.render(expr.arg)})`;
201
+ }
202
+ lower(expr) {
203
+ return `LOWER(${this.render(expr.arg)})`;
204
+ }
205
+ length(expr) {
206
+ // MSSQL: LEN() (null handling)
207
+ return `LEN(ISNULL(${this.render(expr.arg)}, N''))`;
208
+ }
209
+ byteLength(expr) {
210
+ // MSSQL: DATALENGTH() (null handling)
211
+ return `DATALENGTH(ISNULL(${this.render(expr.arg)}, N''))`;
212
+ }
213
+ substring(expr) {
214
+ if (expr.length != null) {
215
+ return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
216
+ }
217
+ // MSSQL: if no length, go to end
218
+ return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, LEN(${this.render(expr.source)}))`;
219
+ }
220
+ indexOf(expr) {
221
+ return `CHARINDEX(${this.render(expr.search)}, ${this.render(expr.source)})`;
222
+ }
223
+ //#endregion
224
+ //#region ========== Number ==========
225
+ abs(expr) {
226
+ return `ABS(${this.render(expr.arg)})`;
227
+ }
228
+ round(expr) {
229
+ return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
230
+ }
231
+ ceil(expr) {
232
+ return `CEILING(${this.render(expr.arg)})`;
233
+ }
234
+ floor(expr) {
235
+ return `FLOOR(${this.render(expr.arg)})`;
236
+ }
237
+ //#endregion
238
+ //#region ========== Date ==========
239
+ year(expr) {
240
+ return `YEAR(${this.render(expr.arg)})`;
241
+ }
242
+ month(expr) {
243
+ return `MONTH(${this.render(expr.arg)})`;
244
+ }
245
+ day(expr) {
246
+ return `DAY(${this.render(expr.arg)})`;
247
+ }
248
+ hour(expr) {
249
+ return `DATEPART(HOUR, ${this.render(expr.arg)})`;
250
+ }
251
+ minute(expr) {
252
+ return `DATEPART(MINUTE, ${this.render(expr.arg)})`;
253
+ }
254
+ second(expr) {
255
+ return `DATEPART(SECOND, ${this.render(expr.arg)})`;
256
+ }
257
+ isoWeek(expr) {
258
+ const src = this.render(expr.arg);
259
+ return `DATEPART(ISO_WEEK, ${src})`;
260
+ }
261
+ isoWeekStartDate(expr) {
262
+ const src = this.render(expr.arg);
263
+ // ISO week start date (Monday) - always returns Monday regardless of @@DATEFIRST
264
+ // Principle: DATEDIFF(DAY, 0, date) is the number of days from 1900-01-01 (Monday)
265
+ // (days + 6) % 7 + 1 = 1(Mon), 2(Tue), ..., 7(Sun)
266
+ const weekDay = `((DATEDIFF(DAY, 0, ${src}) + 6) % 7 + 1)`;
267
+ return `DATEADD(DAY, 1 - ${weekDay}, CAST(${src} AS DATE))`;
268
+ }
269
+ isoYearMonth(expr) {
270
+ const src = this.render(expr.arg);
271
+ return `FORMAT(${src}, 'yyyyMM')`;
272
+ }
273
+ dateDiff(expr) {
274
+ const from = this.render(expr.from);
275
+ const to = this.render(expr.to);
276
+ const unit = this.dateUnitToSql(expr.unit);
277
+ return `DATEDIFF(${unit}, ${from}, ${to})`;
278
+ }
279
+ dateAdd(expr) {
280
+ const source = this.render(expr.source);
281
+ const value = this.render(expr.value);
282
+ const unit = this.dateUnitToSql(expr.unit);
283
+ return `DATEADD(${unit}, ${value}, ${source})`;
284
+ }
285
+ formatDate(expr) {
286
+ // JS format → MSSQL FORMAT style
287
+ const mssqlFormat = this.convertDateFormat(expr.format);
288
+ return `FORMAT(${this.render(expr.source)}, '${mssqlFormat}')`;
289
+ }
290
+ dateUnitToSql(unit) {
291
+ switch (unit) {
292
+ case "year":
293
+ return "YEAR";
294
+ case "month":
295
+ return "MONTH";
296
+ case "day":
297
+ return "DAY";
298
+ case "hour":
299
+ return "HOUR";
300
+ case "minute":
301
+ return "MINUTE";
302
+ case "second":
303
+ return "SECOND";
304
+ }
305
+ }
306
+ convertDateFormat(format) {
307
+ // For MSSQL FORMAT function (uses the same format)
308
+ return format;
309
+ }
310
+ //#endregion
311
+ //#region ========== condition ==========
312
+ coalesce(expr) {
313
+ if (expr.args.length === 0)
314
+ return "NULL";
315
+ if (expr.args.length === 1)
316
+ return this.render(expr.args[0]);
317
+ // MSSQL: COALESCE
318
+ return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
319
+ }
320
+ nullIf(expr) {
321
+ return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
322
+ }
323
+ is(expr) {
324
+ return `CASE WHEN ${this.render(expr.condition)} THEN 1 ELSE 0 END`;
325
+ }
326
+ switch(expr) {
327
+ const cases = expr.cases
328
+ .map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`)
329
+ .join(" ");
330
+ return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
331
+ }
332
+ if(expr) {
333
+ const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
334
+ return `CASE WHEN ${this.render(expr.condition)} THEN ${this.render(expr.then)} ELSE ${elseVal} END`;
335
+ }
336
+ //#endregion
337
+ //#region ========== aggregation ==========
338
+ count(expr) {
339
+ if (expr.arg != null) {
340
+ const distinct = expr.distinct ? "DISTINCT " : "";
341
+ return `COUNT(${distinct}${this.render(expr.arg)})`;
342
+ }
343
+ return "COUNT(*)";
344
+ }
345
+ sum(expr) {
346
+ return `SUM(${this.render(expr.arg)})`;
347
+ }
348
+ avg(expr) {
349
+ return `AVG(${this.render(expr.arg)})`;
350
+ }
351
+ max(expr) {
352
+ return `MAX(${this.render(expr.arg)})`;
353
+ }
354
+ min(expr) {
355
+ return `MIN(${this.render(expr.arg)})`;
356
+ }
357
+ //#endregion
358
+ //#region ========== Other ==========
359
+ greatest(expr) {
360
+ if (expr.args.length === 0)
361
+ throw new Error("greatest에는 최소 1개의 인수가 필요합니다.");
362
+ if (expr.args.length === 1)
363
+ return this.render(expr.args[0]);
364
+ // MSSQL 2012+: VALUES + MAX approach
365
+ const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
366
+ return `(SELECT MAX(v) FROM (VALUES ${values}) AS t(v))`;
367
+ }
368
+ least(expr) {
369
+ if (expr.args.length === 0)
370
+ throw new Error("least에는 최소 1개의 인수가 필요합니다.");
371
+ if (expr.args.length === 1)
372
+ return this.render(expr.args[0]);
373
+ // MSSQL 2012+: VALUES + MIN approach
374
+ const values = expr.args.map((a) => `(${this.render(a)})`).join(", ");
375
+ return `(SELECT MIN(v) FROM (VALUES ${values}) AS t(v))`;
376
+ }
377
+ rowNum(_expr) {
378
+ return "ROW_NUMBER() OVER (ORDER BY (SELECT NULL))";
379
+ }
380
+ random(_expr) {
381
+ return "NEWID()";
382
+ }
383
+ cast(expr) {
384
+ return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
385
+ }
386
+ //#endregion
387
+ //#region ========== Window ==========
388
+ window(expr) {
389
+ const fn = this.renderWindowFn(expr.fn);
390
+ let over = this.renderWindowSpec(expr.spec);
391
+ // LAST_VALUE default frame only sees up to CURRENT ROW, so full frame must be specified
392
+ if (expr.fn.type === "lastValue" && over.length > 0) {
393
+ over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
394
+ }
395
+ return `${fn} OVER (${over})`;
396
+ }
397
+ renderWindowFn(fn) {
398
+ switch (fn.type) {
399
+ case "rowNumber":
400
+ return "ROW_NUMBER()";
401
+ case "rank":
402
+ return "RANK()";
403
+ case "denseRank":
404
+ return "DENSE_RANK()";
405
+ case "ntile":
406
+ return `NTILE(${fn.n})`;
407
+ case "lag": {
408
+ const offset = fn.offset ?? 1;
409
+ const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
410
+ return `LAG(${this.render(fn.column)}, ${offset}${def})`;
411
+ }
412
+ case "lead": {
413
+ const offset = fn.offset ?? 1;
414
+ const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
415
+ return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
416
+ }
417
+ case "firstValue":
418
+ return `FIRST_VALUE(${this.render(fn.column)})`;
419
+ case "lastValue":
420
+ return `LAST_VALUE(${this.render(fn.column)})`;
421
+ case "sum":
422
+ return `SUM(${this.render(fn.column)})`;
423
+ case "avg":
424
+ return `AVG(${this.render(fn.column)})`;
425
+ case "count":
426
+ return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
427
+ case "min":
428
+ return `MIN(${this.render(fn.column)})`;
429
+ case "max":
430
+ return `MAX(${this.render(fn.column)})`;
431
+ }
432
+ }
433
+ renderWindowSpec(spec) {
434
+ const parts = [];
435
+ if (spec.partitionBy != null && spec.partitionBy.length > 0) {
436
+ parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
437
+ }
438
+ if (spec.orderBy != null && spec.orderBy.length > 0) {
439
+ const orderParts = spec.orderBy.map(([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`);
440
+ parts.push(`ORDER BY ${orderParts.join(", ")}`);
441
+ }
442
+ return parts.join(" ");
443
+ }
444
+ //#endregion
445
+ //#region ========== System ==========
446
+ subquery(expr) {
447
+ return `(${this.buildSelect(expr.queryDef)})`;
448
+ }
419
449
  }
420
- export {
421
- MssqlExprRenderer
422
- };
423
- //# sourceMappingURL=mssql-expr-renderer.js.map
450
+ //# sourceMappingURL=mssql-expr-renderer.js.map