@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,422 +1,454 @@
1
1
  import { bytes, DateOnly, DateTime, Time, Uuid } from "@simplysm/core-common";
2
2
  import { ExprRendererBase } from "../base/expr-renderer-base.js";
3
- class MysqlExprRenderer 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, "\\\\").replace(/'/g, "''").replace(/\0/g, "\\0").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
12
- }
13
- /** value escape */
14
- escapeValue(value) {
15
- if (value == null) {
16
- return "NULL";
17
- }
18
- if (typeof value === "string") {
19
- return `'${this.escapeString(value)}'`;
20
- }
21
- if (typeof value === "number") {
22
- return String(value);
23
- }
24
- if (typeof value === "boolean") {
25
- return value ? "TRUE" : "FALSE";
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 `0x${bytes.toHex(value.toBytes())}`;
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 "FLOAT";
53
- case "double":
54
- return "DOUBLE";
55
- case "decimal":
56
- return dataType.scale != null ? `DECIMAL(${dataType.precision}, ${dataType.scale})` : `DECIMAL(${dataType.precision})`;
57
- case "varchar":
58
- return `VARCHAR(${dataType.length})`;
59
- case "char":
60
- return `CHAR(${dataType.length})`;
61
- case "text":
62
- return "LONGTEXT";
63
- case "binary":
64
- return "LONGBLOB";
65
- case "boolean":
66
- return "BOOLEAN";
67
- case "datetime":
68
- return "DATETIME";
69
- case "date":
70
- return "DATE";
71
- case "time":
72
- return "TIME";
73
- case "uuid":
74
- return "BINARY(16)";
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
- return `${this.render(expr.source)} <=> ${this.render(expr.target)}`;
95
- }
96
- gt(expr) {
97
- return `${this.render(expr.source)} > ${this.render(expr.target)}`;
98
- }
99
- lt(expr) {
100
- return `${this.render(expr.source)} < ${this.render(expr.target)}`;
101
- }
102
- gte(expr) {
103
- return `${this.render(expr.source)} >= ${this.render(expr.target)}`;
104
- }
105
- lte(expr) {
106
- return `${this.render(expr.source)} <= ${this.render(expr.target)}`;
107
- }
108
- between(expr) {
109
- const source = this.render(expr.source);
110
- if (expr.from != null && expr.to != null) {
111
- return `${source} BETWEEN ${this.render(expr.from)} AND ${this.render(expr.to)}`;
112
- }
113
- if (expr.from != null) {
114
- return `${source} >= ${this.render(expr.from)}`;
115
- }
116
- if (expr.to != null) {
117
- return `${source} <= ${this.render(expr.to)}`;
118
- }
119
- return "1=1";
120
- }
121
- null(expr) {
122
- return `${this.render(expr.arg)} IS NULL`;
123
- }
124
- like(expr) {
125
- return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\\\'`;
126
- }
127
- regexp(expr) {
128
- return `${this.render(expr.source)} REGEXP ${this.render(expr.pattern)}`;
129
- }
130
- in(expr) {
131
- if (expr.values.length === 0) {
132
- return "1=0";
133
- }
134
- const values = expr.values.map((v) => this.render(v)).join(", ");
135
- return `${this.render(expr.source)} IN (${values})`;
136
- }
137
- inQuery(expr) {
138
- return `${this.render(expr.source)} IN (${this.buildSelect(expr.query)})`;
139
- }
140
- exists(expr) {
141
- const subquery = this.buildSelect({
142
- ...expr.query,
143
- select: { _: { type: "value", value: 1 } }
144
- });
145
- return `EXISTS (${subquery})`;
146
- }
147
- //#endregion
148
- //#region ========== logic ==========
149
- not(expr) {
150
- return `NOT (${this.render(expr.arg)})`;
151
- }
152
- and(expr) {
153
- if (expr.conditions.length === 0) return "1=1";
154
- return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
155
- }
156
- or(expr) {
157
- if (expr.conditions.length === 0) return "1=0";
158
- return `(${expr.conditions.map((c) => this.render(c)).join(" OR ")})`;
159
- }
160
- //#endregion
161
- //#region ========== String (null handling) ==========
162
- concat(expr) {
163
- const args = expr.args.map((a) => `IFNULL(${this.render(a)}, '')`);
164
- return `CONCAT(${args.join(", ")})`;
165
- }
166
- left(expr) {
167
- return `LEFT(${this.render(expr.source)}, ${this.render(expr.length)})`;
168
- }
169
- right(expr) {
170
- return `RIGHT(${this.render(expr.source)}, ${this.render(expr.length)})`;
171
- }
172
- trim(expr) {
173
- return `TRIM(${this.render(expr.arg)})`;
174
- }
175
- padStart(expr) {
176
- return `LPAD(${this.render(expr.source)}, ${this.render(expr.length)}, ${this.render(expr.fillString)})`;
177
- }
178
- replace(expr) {
179
- return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
180
- }
181
- upper(expr) {
182
- return `UPPER(${this.render(expr.arg)})`;
183
- }
184
- lower(expr) {
185
- return `LOWER(${this.render(expr.arg)})`;
186
- }
187
- length(expr) {
188
- return `CHAR_LENGTH(IFNULL(${this.render(expr.arg)}, ''))`;
189
- }
190
- byteLength(expr) {
191
- return `LENGTH(IFNULL(${this.render(expr.arg)}, ''))`;
192
- }
193
- substring(expr) {
194
- if (expr.length != null) {
195
- return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
196
- }
197
- return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)})`;
198
- }
199
- indexOf(expr) {
200
- return `LOCATE(${this.render(expr.search)}, ${this.render(expr.source)})`;
201
- }
202
- //#endregion
203
- //#region ========== Number ==========
204
- abs(expr) {
205
- return `ABS(${this.render(expr.arg)})`;
206
- }
207
- round(expr) {
208
- return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
209
- }
210
- ceil(expr) {
211
- return `CEIL(${this.render(expr.arg)})`;
212
- }
213
- floor(expr) {
214
- return `FLOOR(${this.render(expr.arg)})`;
215
- }
216
- //#endregion
217
- //#region ========== Date ==========
218
- year(expr) {
219
- return `YEAR(${this.render(expr.arg)})`;
220
- }
221
- month(expr) {
222
- return `MONTH(${this.render(expr.arg)})`;
223
- }
224
- day(expr) {
225
- return `DAY(${this.render(expr.arg)})`;
226
- }
227
- hour(expr) {
228
- return `HOUR(${this.render(expr.arg)})`;
229
- }
230
- minute(expr) {
231
- return `MINUTE(${this.render(expr.arg)})`;
232
- }
233
- second(expr) {
234
- return `SECOND(${this.render(expr.arg)})`;
235
- }
236
- isoWeek(expr) {
237
- return `WEEK(${this.render(expr.arg)}, 1)`;
238
- }
239
- isoWeekStartDate(expr) {
240
- return `DATE_SUB(${this.render(expr.arg)}, INTERVAL (WEEKDAY(${this.render(expr.arg)})) DAY)`;
241
- }
242
- isoYearMonth(expr) {
243
- return `DATE_FORMAT(${this.render(expr.arg)}, '%Y%m')`;
244
- }
245
- dateDiff(expr) {
246
- const from = this.render(expr.from);
247
- const to = this.render(expr.to);
248
- switch (expr.unit) {
249
- case "year":
250
- return `TIMESTAMPDIFF(YEAR, ${from}, ${to})`;
251
- case "month":
252
- return `TIMESTAMPDIFF(MONTH, ${from}, ${to})`;
253
- case "day":
254
- return `TIMESTAMPDIFF(DAY, ${from}, ${to})`;
255
- case "hour":
256
- return `TIMESTAMPDIFF(HOUR, ${from}, ${to})`;
257
- case "minute":
258
- return `TIMESTAMPDIFF(MINUTE, ${from}, ${to})`;
259
- case "second":
260
- return `TIMESTAMPDIFF(SECOND, ${from}, ${to})`;
261
- }
262
- }
263
- dateAdd(expr) {
264
- const source = this.render(expr.source);
265
- const value = this.render(expr.value);
266
- const unit = this.dateUnitToSql(expr.unit);
267
- return `DATE_ADD(${source}, INTERVAL ${value} ${unit})`;
268
- }
269
- formatDate(expr) {
270
- const mysqlFormat = this.convertDateFormat(expr.format);
271
- return `DATE_FORMAT(${this.render(expr.source)}, '${mysqlFormat}')`;
272
- }
273
- dateUnitToSql(unit) {
274
- switch (unit) {
275
- case "year":
276
- return "YEAR";
277
- case "month":
278
- return "MONTH";
279
- case "day":
280
- return "DAY";
281
- case "hour":
282
- return "HOUR";
283
- case "minute":
284
- return "MINUTE";
285
- case "second":
286
- return "SECOND";
287
- }
288
- }
289
- convertDateFormat(format) {
290
- return format.replace(/yyyy/g, "%Y").replace(/MM/g, "%m").replace(/dd/g, "%d").replace(/HH/g, "%H").replace(/mm/g, "%i").replace(/ss/g, "%s");
291
- }
292
- //#endregion
293
- //#region ========== condition ==========
294
- coalesce(expr) {
295
- if (expr.args.length === 0) return "NULL";
296
- if (expr.args.length === 1) return this.render(expr.args[0]);
297
- return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
298
- }
299
- nullIf(expr) {
300
- return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
301
- }
302
- is(expr) {
303
- return `(${this.render(expr.condition)})`;
304
- }
305
- switch(expr) {
306
- const cases = expr.cases.map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`).join(" ");
307
- return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
308
- }
309
- if(expr) {
310
- const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
311
- return `IF(${this.render(expr.condition)}, ${this.render(expr.then)}, ${elseVal})`;
312
- }
313
- //#endregion
314
- //#region ========== aggregation ==========
315
- count(expr) {
316
- if (expr.arg != null) {
317
- const distinct = expr.distinct ? "DISTINCT " : "";
318
- return `COUNT(${distinct}${this.render(expr.arg)})`;
319
- }
320
- return "COUNT(*)";
321
- }
322
- sum(expr) {
323
- return `SUM(${this.render(expr.arg)})`;
324
- }
325
- avg(expr) {
326
- return `AVG(${this.render(expr.arg)})`;
327
- }
328
- max(expr) {
329
- return `MAX(${this.render(expr.arg)})`;
330
- }
331
- min(expr) {
332
- return `MIN(${this.render(expr.arg)})`;
333
- }
334
- //#endregion
335
- //#region ========== Other ==========
336
- greatest(expr) {
337
- if (expr.args.length === 0) throw new Error("greatest requires at least one argument.");
338
- return `GREATEST(${expr.args.map((a) => this.render(a)).join(", ")})`;
339
- }
340
- least(expr) {
341
- if (expr.args.length === 0) throw new Error("least requires at least one argument.");
342
- return `LEAST(${expr.args.map((a) => this.render(a)).join(", ")})`;
343
- }
344
- rowNum(_expr) {
345
- return "ROW_NUMBER() OVER ()";
346
- }
347
- random(_expr) {
348
- return "RAND()";
349
- }
350
- cast(expr) {
351
- return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
352
- }
353
- //#endregion
354
- //#region ========== Window ==========
355
- window(expr) {
356
- const fn = this.renderWindowFn(expr.fn);
357
- let over = this.renderWindowSpec(expr.spec);
358
- if (expr.fn.type === "lastValue" && over.length > 0) {
359
- over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
360
- }
361
- return `${fn} OVER (${over})`;
362
- }
363
- renderWindowFn(fn) {
364
- switch (fn.type) {
365
- case "rowNumber":
366
- return "ROW_NUMBER()";
367
- case "rank":
368
- return "RANK()";
369
- case "denseRank":
370
- return "DENSE_RANK()";
371
- case "ntile":
372
- return `NTILE(${fn.n})`;
373
- case "lag": {
374
- const offset = fn.offset ?? 1;
375
- const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
376
- return `LAG(${this.render(fn.column)}, ${offset}${def})`;
377
- }
378
- case "lead": {
379
- const offset = fn.offset ?? 1;
380
- const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
381
- return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
382
- }
383
- case "firstValue":
384
- return `FIRST_VALUE(${this.render(fn.column)})`;
385
- case "lastValue":
386
- return `LAST_VALUE(${this.render(fn.column)})`;
387
- case "sum":
388
- return `SUM(${this.render(fn.column)})`;
389
- case "avg":
390
- return `AVG(${this.render(fn.column)})`;
391
- case "count":
392
- return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
393
- case "min":
394
- return `MIN(${this.render(fn.column)})`;
395
- case "max":
396
- return `MAX(${this.render(fn.column)})`;
397
- }
398
- }
399
- renderWindowSpec(spec) {
400
- const parts = [];
401
- if (spec.partitionBy != null && spec.partitionBy.length > 0) {
402
- parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
403
- }
404
- if (spec.orderBy != null && spec.orderBy.length > 0) {
405
- const orderParts = spec.orderBy.map(
406
- ([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`
407
- );
408
- parts.push(`ORDER BY ${orderParts.join(", ")}`);
409
- }
410
- return parts.join(" ");
411
- }
412
- //#endregion
413
- //#region ========== System ==========
414
- subquery(expr) {
415
- return `(${this.buildSelect(expr.queryDef)})`;
416
- }
417
- //#endregion
3
+ /**
4
+ * MySQL 표현식 렌더러
5
+ */
6
+ export class MysqlExprRenderer extends ExprRendererBase {
7
+ //#region ========== 유틸리티 (public - QueryBuilder에서도 사용) ==========
8
+ /** 식별자 감싸기 */
9
+ wrap(name) {
10
+ return `\`${name.replace(/`/g, "``")}\``;
11
+ }
12
+ /** SQL 문자열 리터럴용 이스케이프 (따옴표 제외) */
13
+ escapeString(value) {
14
+ return value
15
+ .replace(/\\/g, "\\\\") // 백슬래시 (최우선)
16
+ .replace(/'/g, "''") // 작은따옴표
17
+ .replace(/\0/g, "\\0") // NULL 바이트
18
+ .replace(/\n/g, "\\n") // 줄바꿈
19
+ .replace(/\r/g, "\\r") // 캐리지 리턴
20
+ .replace(/\t/g, "\\t"); // 탭
21
+ }
22
+ /** 값 이스케이프 */
23
+ escapeValue(value) {
24
+ if (value == null) {
25
+ return "NULL";
26
+ }
27
+ if (typeof value === "string") {
28
+ return `'${this.escapeString(value)}'`;
29
+ }
30
+ if (typeof value === "number") {
31
+ return String(value);
32
+ }
33
+ if (typeof value === "boolean") {
34
+ return value ? "TRUE" : "FALSE";
35
+ }
36
+ if (value instanceof DateTime) {
37
+ return `'${value.toFormatString("yyyy-MM-dd HH:mm:ss")}'`;
38
+ }
39
+ if (value instanceof DateOnly) {
40
+ return `'${value.toFormatString("yyyy-MM-dd")}'`;
41
+ }
42
+ if (value instanceof Time) {
43
+ return `'${value.toFormatString("HH:mm:ss")}'`;
44
+ }
45
+ if (value instanceof Uuid) {
46
+ return `0x${bytes.toHex(value.toBytes())}`;
47
+ }
48
+ if (value instanceof Uint8Array) {
49
+ return `0x${bytes.toHex(value)}`;
50
+ }
51
+ throw new Error(`알 수 없는 값 타입: ${typeof value}`);
52
+ }
53
+ /** DataType → SQL type */
54
+ renderDataType(dataType) {
55
+ switch (dataType.type) {
56
+ case "int":
57
+ return "INT";
58
+ case "bigint":
59
+ return "BIGINT";
60
+ case "float":
61
+ return "FLOAT";
62
+ case "double":
63
+ return "DOUBLE";
64
+ case "decimal":
65
+ return dataType.scale != null
66
+ ? `DECIMAL(${dataType.precision}, ${dataType.scale})`
67
+ : `DECIMAL(${dataType.precision})`;
68
+ case "varchar":
69
+ return `VARCHAR(${dataType.length})`;
70
+ case "char":
71
+ return `CHAR(${dataType.length})`;
72
+ case "text":
73
+ return "LONGTEXT";
74
+ case "binary":
75
+ return "LONGBLOB";
76
+ case "boolean":
77
+ return "BOOLEAN";
78
+ case "datetime":
79
+ return "DATETIME";
80
+ case "date":
81
+ return "DATE";
82
+ case "time":
83
+ return "TIME";
84
+ case "uuid":
85
+ return "BINARY(16)";
86
+ }
87
+ }
88
+ //#endregion
89
+ //#region ========== value ==========
90
+ column(expr) {
91
+ return expr.path.map((p) => this.wrap(p)).join(".");
92
+ }
93
+ value(expr) {
94
+ return this.escapeValue(expr.value);
95
+ }
96
+ raw(expr) {
97
+ return expr.sql.replace(/\$(\d+)/g, (_, num) => {
98
+ const idx = parseInt(num) - 1;
99
+ return idx < expr.params.length ? this.render(expr.params[idx]) : `$${num}`;
100
+ });
101
+ }
102
+ //#endregion
103
+ //#region ========== comparison (null-safe) ==========
104
+ eq(expr) {
105
+ // MySQL: <=> operator (null-safe equal)
106
+ return `${this.render(expr.source)} <=> ${this.render(expr.target)}`;
107
+ }
108
+ gt(expr) {
109
+ return `${this.render(expr.source)} > ${this.render(expr.target)}`;
110
+ }
111
+ lt(expr) {
112
+ return `${this.render(expr.source)} < ${this.render(expr.target)}`;
113
+ }
114
+ gte(expr) {
115
+ return `${this.render(expr.source)} >= ${this.render(expr.target)}`;
116
+ }
117
+ lte(expr) {
118
+ return `${this.render(expr.source)} <= ${this.render(expr.target)}`;
119
+ }
120
+ between(expr) {
121
+ const source = this.render(expr.source);
122
+ if (expr.from != null && expr.to != null) {
123
+ return `${source} BETWEEN ${this.render(expr.from)} AND ${this.render(expr.to)}`;
124
+ }
125
+ if (expr.from != null) {
126
+ return `${source} >= ${this.render(expr.from)}`;
127
+ }
128
+ if (expr.to != null) {
129
+ return `${source} <= ${this.render(expr.to)}`;
130
+ }
131
+ return "1=1";
132
+ }
133
+ null(expr) {
134
+ return `${this.render(expr.arg)} IS NULL`;
135
+ }
136
+ like(expr) {
137
+ // 항상 ESCAPE '\' 추가
138
+ return `${this.render(expr.source)} LIKE ${this.render(expr.pattern)} ESCAPE '\\\\'`;
139
+ }
140
+ regexp(expr) {
141
+ return `${this.render(expr.source)} REGEXP ${this.render(expr.pattern)}`;
142
+ }
143
+ in(expr) {
144
+ if (expr.values.length === 0) {
145
+ return "1=0"; // empty IN is always false
146
+ }
147
+ const values = expr.values.map((v) => this.render(v)).join(", ");
148
+ return `${this.render(expr.source)} IN (${values})`;
149
+ }
150
+ inQuery(expr) {
151
+ return `${this.render(expr.source)} IN (${this.buildSelect(expr.query)})`;
152
+ }
153
+ exists(expr) {
154
+ // Render as SELECT 1
155
+ const subquery = this.buildSelect({
156
+ ...expr.query,
157
+ select: { _: { type: "value", value: 1 } },
158
+ });
159
+ return `EXISTS (${subquery})`;
160
+ }
161
+ //#endregion
162
+ //#region ========== logic ==========
163
+ not(expr) {
164
+ return `NOT (${this.render(expr.arg)})`;
165
+ }
166
+ and(expr) {
167
+ if (expr.conditions.length === 0)
168
+ return "1=1";
169
+ return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
170
+ }
171
+ or(expr) {
172
+ if (expr.conditions.length === 0)
173
+ return "1=0";
174
+ return `(${expr.conditions.map((c) => this.render(c)).join(" OR ")})`;
175
+ }
176
+ //#endregion
177
+ //#region ========== String (null handling) ==========
178
+ concat(expr) {
179
+ // null handling: IFNULL(arg, '')
180
+ const args = expr.args.map((a) => `IFNULL(${this.render(a)}, '')`);
181
+ return `CONCAT(${args.join(", ")})`;
182
+ }
183
+ left(expr) {
184
+ return `LEFT(${this.render(expr.source)}, ${this.render(expr.length)})`;
185
+ }
186
+ right(expr) {
187
+ return `RIGHT(${this.render(expr.source)}, ${this.render(expr.length)})`;
188
+ }
189
+ trim(expr) {
190
+ return `TRIM(${this.render(expr.arg)})`;
191
+ }
192
+ padStart(expr) {
193
+ return `LPAD(${this.render(expr.source)}, ${this.render(expr.length)}, ${this.render(expr.fillString)})`;
194
+ }
195
+ replace(expr) {
196
+ return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
197
+ }
198
+ upper(expr) {
199
+ return `UPPER(${this.render(expr.arg)})`;
200
+ }
201
+ lower(expr) {
202
+ return `LOWER(${this.render(expr.arg)})`;
203
+ }
204
+ length(expr) {
205
+ // null handling: IFNULL(arg, '')
206
+ return `CHAR_LENGTH(IFNULL(${this.render(expr.arg)}, ''))`;
207
+ }
208
+ byteLength(expr) {
209
+ // null handling: IFNULL(arg, '')
210
+ return `LENGTH(IFNULL(${this.render(expr.arg)}, ''))`;
211
+ }
212
+ substring(expr) {
213
+ if (expr.length != null) {
214
+ return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
215
+ }
216
+ return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)})`;
217
+ }
218
+ indexOf(expr) {
219
+ return `LOCATE(${this.render(expr.search)}, ${this.render(expr.source)})`;
220
+ }
221
+ //#endregion
222
+ //#region ========== Number ==========
223
+ abs(expr) {
224
+ return `ABS(${this.render(expr.arg)})`;
225
+ }
226
+ round(expr) {
227
+ return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
228
+ }
229
+ ceil(expr) {
230
+ return `CEIL(${this.render(expr.arg)})`;
231
+ }
232
+ floor(expr) {
233
+ return `FLOOR(${this.render(expr.arg)})`;
234
+ }
235
+ //#endregion
236
+ //#region ========== Date ==========
237
+ year(expr) {
238
+ return `YEAR(${this.render(expr.arg)})`;
239
+ }
240
+ month(expr) {
241
+ return `MONTH(${this.render(expr.arg)})`;
242
+ }
243
+ day(expr) {
244
+ return `DAY(${this.render(expr.arg)})`;
245
+ }
246
+ hour(expr) {
247
+ return `HOUR(${this.render(expr.arg)})`;
248
+ }
249
+ minute(expr) {
250
+ return `MINUTE(${this.render(expr.arg)})`;
251
+ }
252
+ second(expr) {
253
+ return `SECOND(${this.render(expr.arg)})`;
254
+ }
255
+ isoWeek(expr) {
256
+ return `WEEK(${this.render(expr.arg)}, 1)`;
257
+ }
258
+ isoWeekStartDate(expr) {
259
+ // ISO week start date (Monday)
260
+ return `DATE_SUB(${this.render(expr.arg)}, INTERVAL (WEEKDAY(${this.render(expr.arg)})) DAY)`;
261
+ }
262
+ isoYearMonth(expr) {
263
+ return `DATE_FORMAT(${this.render(expr.arg)}, '%Y%m')`;
264
+ }
265
+ dateDiff(expr) {
266
+ const from = this.render(expr.from);
267
+ const to = this.render(expr.to);
268
+ switch (expr.unit) {
269
+ case "year":
270
+ return `TIMESTAMPDIFF(YEAR, ${from}, ${to})`;
271
+ case "month":
272
+ return `TIMESTAMPDIFF(MONTH, ${from}, ${to})`;
273
+ case "day":
274
+ return `TIMESTAMPDIFF(DAY, ${from}, ${to})`;
275
+ case "hour":
276
+ return `TIMESTAMPDIFF(HOUR, ${from}, ${to})`;
277
+ case "minute":
278
+ return `TIMESTAMPDIFF(MINUTE, ${from}, ${to})`;
279
+ case "second":
280
+ return `TIMESTAMPDIFF(SECOND, ${from}, ${to})`;
281
+ }
282
+ }
283
+ dateAdd(expr) {
284
+ const source = this.render(expr.source);
285
+ const value = this.render(expr.value);
286
+ const unit = this.dateUnitToSql(expr.unit);
287
+ return `DATE_ADD(${source}, INTERVAL ${value} ${unit})`;
288
+ }
289
+ formatDate(expr) {
290
+ // JS format MySQL format
291
+ const mysqlFormat = this.convertDateFormat(expr.format);
292
+ return `DATE_FORMAT(${this.render(expr.source)}, '${mysqlFormat}')`;
293
+ }
294
+ dateUnitToSql(unit) {
295
+ switch (unit) {
296
+ case "year":
297
+ return "YEAR";
298
+ case "month":
299
+ return "MONTH";
300
+ case "day":
301
+ return "DAY";
302
+ case "hour":
303
+ return "HOUR";
304
+ case "minute":
305
+ return "MINUTE";
306
+ case "second":
307
+ return "SECOND";
308
+ }
309
+ }
310
+ convertDateFormat(format) {
311
+ // Simple transform (yyyy-MM-dd HH:mm:ss format)
312
+ return format
313
+ .replace(/yyyy/g, "%Y")
314
+ .replace(/MM/g, "%m")
315
+ .replace(/dd/g, "%d")
316
+ .replace(/HH/g, "%H")
317
+ .replace(/mm/g, "%i")
318
+ .replace(/ss/g, "%s");
319
+ }
320
+ //#endregion
321
+ //#region ========== condition ==========
322
+ coalesce(expr) {
323
+ if (expr.args.length === 0)
324
+ return "NULL";
325
+ if (expr.args.length === 1)
326
+ return this.render(expr.args[0]);
327
+ // Render as COALESCE (first non-null among multiple values)
328
+ return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
329
+ }
330
+ nullIf(expr) {
331
+ return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
332
+ }
333
+ is(expr) {
334
+ return `(${this.render(expr.condition)})`;
335
+ }
336
+ switch(expr) {
337
+ const cases = expr.cases
338
+ .map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`)
339
+ .join(" ");
340
+ return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
341
+ }
342
+ if(expr) {
343
+ const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
344
+ return `IF(${this.render(expr.condition)}, ${this.render(expr.then)}, ${elseVal})`;
345
+ }
346
+ //#endregion
347
+ //#region ========== aggregation ==========
348
+ count(expr) {
349
+ if (expr.arg != null) {
350
+ const distinct = expr.distinct ? "DISTINCT " : "";
351
+ return `COUNT(${distinct}${this.render(expr.arg)})`;
352
+ }
353
+ return "COUNT(*)";
354
+ }
355
+ sum(expr) {
356
+ return `SUM(${this.render(expr.arg)})`;
357
+ }
358
+ avg(expr) {
359
+ return `AVG(${this.render(expr.arg)})`;
360
+ }
361
+ max(expr) {
362
+ return `MAX(${this.render(expr.arg)})`;
363
+ }
364
+ min(expr) {
365
+ return `MIN(${this.render(expr.arg)})`;
366
+ }
367
+ //#endregion
368
+ //#region ========== Other ==========
369
+ greatest(expr) {
370
+ if (expr.args.length === 0)
371
+ throw new Error("greatest에는 최소 1개의 인수가 필요합니다.");
372
+ return `GREATEST(${expr.args.map((a) => this.render(a)).join(", ")})`;
373
+ }
374
+ least(expr) {
375
+ if (expr.args.length === 0)
376
+ throw new Error("least에는 최소 1개의 인수가 필요합니다.");
377
+ return `LEAST(${expr.args.map((a) => this.render(a)).join(", ")})`;
378
+ }
379
+ rowNum(_expr) {
380
+ // MySQL: use variables or ROW_NUMBER() window function
381
+ // Implemented with ROW_NUMBER() here (MySQL 8.0+)
382
+ return "ROW_NUMBER() OVER ()";
383
+ }
384
+ random(_expr) {
385
+ return "RAND()";
386
+ }
387
+ cast(expr) {
388
+ return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
389
+ }
390
+ //#endregion
391
+ //#region ========== Window ==========
392
+ window(expr) {
393
+ const fn = this.renderWindowFn(expr.fn);
394
+ let over = this.renderWindowSpec(expr.spec);
395
+ // LAST_VALUE default frame only sees up to CURRENT ROW, so full frame must be specified
396
+ if (expr.fn.type === "lastValue" && over.length > 0) {
397
+ over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
398
+ }
399
+ return `${fn} OVER (${over})`;
400
+ }
401
+ renderWindowFn(fn) {
402
+ switch (fn.type) {
403
+ case "rowNumber":
404
+ return "ROW_NUMBER()";
405
+ case "rank":
406
+ return "RANK()";
407
+ case "denseRank":
408
+ return "DENSE_RANK()";
409
+ case "ntile":
410
+ return `NTILE(${fn.n})`;
411
+ case "lag": {
412
+ const offset = fn.offset ?? 1;
413
+ const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
414
+ return `LAG(${this.render(fn.column)}, ${offset}${def})`;
415
+ }
416
+ case "lead": {
417
+ const offset = fn.offset ?? 1;
418
+ const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
419
+ return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
420
+ }
421
+ case "firstValue":
422
+ return `FIRST_VALUE(${this.render(fn.column)})`;
423
+ case "lastValue":
424
+ return `LAST_VALUE(${this.render(fn.column)})`;
425
+ case "sum":
426
+ return `SUM(${this.render(fn.column)})`;
427
+ case "avg":
428
+ return `AVG(${this.render(fn.column)})`;
429
+ case "count":
430
+ return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
431
+ case "min":
432
+ return `MIN(${this.render(fn.column)})`;
433
+ case "max":
434
+ return `MAX(${this.render(fn.column)})`;
435
+ }
436
+ }
437
+ renderWindowSpec(spec) {
438
+ const parts = [];
439
+ if (spec.partitionBy != null && spec.partitionBy.length > 0) {
440
+ parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
441
+ }
442
+ if (spec.orderBy != null && spec.orderBy.length > 0) {
443
+ const orderParts = spec.orderBy.map(([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`);
444
+ parts.push(`ORDER BY ${orderParts.join(", ")}`);
445
+ }
446
+ return parts.join(" ");
447
+ }
448
+ //#endregion
449
+ //#region ========== System ==========
450
+ subquery(expr) {
451
+ return `(${this.buildSelect(expr.queryDef)})`;
452
+ }
418
453
  }
419
- export {
420
- MysqlExprRenderer
421
- };
422
- //# sourceMappingURL=mysql-expr-renderer.js.map
454
+ //# sourceMappingURL=mysql-expr-renderer.js.map