@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,425 +1,452 @@
1
1
  import { bytes, DateOnly, DateTime, Time, Uuid } from "@simplysm/core-common";
2
2
  import { ExprRendererBase } from "../base/expr-renderer-base.js";
3
- class PostgresqlExprRenderer 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 `'${value.replace(/'/g, "''")}'`;
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")}'::timestamp`;
29
- }
30
- if (value instanceof DateOnly) {
31
- return `'${value.toFormatString("yyyy-MM-dd")}'::date`;
32
- }
33
- if (value instanceof Time) {
34
- return `'${value.toFormatString("HH:mm:ss")}'::time`;
35
- }
36
- if (value instanceof Uuid) {
37
- return `'${value.toString()}'::uuid`;
38
- }
39
- if (value instanceof Uint8Array) {
40
- return `'\\x${bytes.toHex(value)}'::bytea`;
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 "INTEGER";
49
- case "bigint":
50
- return "BIGINT";
51
- case "float":
52
- return "REAL";
53
- case "double":
54
- return "DOUBLE PRECISION";
55
- case "decimal":
56
- return dataType.scale != null ? `NUMERIC(${dataType.precision}, ${dataType.scale})` : `NUMERIC(${dataType.precision})`;
57
- case "varchar":
58
- return `VARCHAR(${dataType.length})`;
59
- case "char":
60
- return `CHAR(${dataType.length})`;
61
- case "text":
62
- return "TEXT";
63
- case "binary":
64
- return "BYTEA";
65
- case "boolean":
66
- return "BOOLEAN";
67
- case "datetime":
68
- return "TIMESTAMP";
69
- case "date":
70
- return "DATE";
71
- case "time":
72
- return "TIME";
73
- case "uuid":
74
- return "UUID";
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 NOT DISTINCT FROM ${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 "TRUE";
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
- return `${this.render(expr.source)} ~ ${this.render(expr.pattern)}`;
131
- }
132
- in(expr) {
133
- if (expr.values.length === 0) {
134
- return "FALSE";
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 "TRUE";
156
- return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
157
- }
158
- or(expr) {
159
- if (expr.conditions.length === 0) return "FALSE";
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) => `COALESCE(${this.render(a)}, '')`);
166
- return args.join(" || ");
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 `TRIM(${this.render(expr.arg)})`;
176
- }
177
- padStart(expr) {
178
- return `LPAD(${this.render(expr.source)}, ${this.render(expr.length)}, ${this.render(expr.fillString)})`;
179
- }
180
- replace(expr) {
181
- return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
182
- }
183
- upper(expr) {
184
- return `UPPER(${this.render(expr.arg)})`;
185
- }
186
- lower(expr) {
187
- return `LOWER(${this.render(expr.arg)})`;
188
- }
189
- length(expr) {
190
- return `LENGTH(COALESCE(${this.render(expr.arg)}, ''))`;
191
- }
192
- byteLength(expr) {
193
- return `OCTET_LENGTH(COALESCE(${this.render(expr.arg)}, ''))`;
194
- }
195
- substring(expr) {
196
- if (expr.length != null) {
197
- return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
198
- }
199
- return `SUBSTRING(${this.render(expr.source)} FROM ${this.render(expr.start)})`;
200
- }
201
- indexOf(expr) {
202
- return `POSITION(${this.render(expr.search)} IN ${this.render(expr.source)})`;
203
- }
204
- //#endregion
205
- //#region ========== Number ==========
206
- abs(expr) {
207
- return `ABS(${this.render(expr.arg)})`;
208
- }
209
- round(expr) {
210
- return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
211
- }
212
- ceil(expr) {
213
- return `CEIL(${this.render(expr.arg)})`;
214
- }
215
- floor(expr) {
216
- return `FLOOR(${this.render(expr.arg)})`;
217
- }
218
- //#endregion
219
- //#region ========== Date ==========
220
- year(expr) {
221
- return `EXTRACT(YEAR FROM ${this.render(expr.arg)})::INTEGER`;
222
- }
223
- month(expr) {
224
- return `EXTRACT(MONTH FROM ${this.render(expr.arg)})::INTEGER`;
225
- }
226
- day(expr) {
227
- return `EXTRACT(DAY FROM ${this.render(expr.arg)})::INTEGER`;
228
- }
229
- hour(expr) {
230
- return `EXTRACT(HOUR FROM ${this.render(expr.arg)})::INTEGER`;
231
- }
232
- minute(expr) {
233
- return `EXTRACT(MINUTE FROM ${this.render(expr.arg)})::INTEGER`;
234
- }
235
- second(expr) {
236
- return `EXTRACT(SECOND FROM ${this.render(expr.arg)})::INTEGER`;
237
- }
238
- isoWeek(expr) {
239
- return `EXTRACT(WEEK FROM ${this.render(expr.arg)})::INTEGER`;
240
- }
241
- isoWeekStartDate(expr) {
242
- const src = this.render(expr.arg);
243
- return `DATE_TRUNC('week', ${src})::DATE`;
244
- }
245
- isoYearMonth(expr) {
246
- return `TO_CHAR(${this.render(expr.arg)}, 'YYYYMM')`;
247
- }
248
- dateDiff(expr) {
249
- const from = this.render(expr.from);
250
- const to = this.render(expr.to);
251
- switch (expr.unit) {
252
- case "year":
253
- return `EXTRACT(YEAR FROM AGE(${to}, ${from}))::INTEGER`;
254
- case "month":
255
- return `(EXTRACT(YEAR FROM AGE(${to}, ${from})) * 12 + EXTRACT(MONTH FROM AGE(${to}, ${from})))::INTEGER`;
256
- case "day":
257
- return `(${to}::DATE - ${from}::DATE)`;
258
- case "hour":
259
- return `EXTRACT(EPOCH FROM (${to} - ${from}))::INTEGER / 3600`;
260
- case "minute":
261
- return `EXTRACT(EPOCH FROM (${to} - ${from}))::INTEGER / 60`;
262
- case "second":
263
- return `EXTRACT(EPOCH FROM (${to} - ${from}))::INTEGER`;
264
- }
265
- }
266
- dateAdd(expr) {
267
- const source = this.render(expr.source);
268
- const value = this.render(expr.value);
269
- const unit = this.dateUnitToSql(expr.unit);
270
- return `${source} + INTERVAL '1 ${unit}' * ${value}`;
271
- }
272
- formatDate(expr) {
273
- const pgFormat = this.convertDateFormat(expr.format);
274
- return `TO_CHAR(${this.render(expr.source)}, '${pgFormat}')`;
275
- }
276
- dateUnitToSql(unit) {
277
- switch (unit) {
278
- case "year":
279
- return "year";
280
- case "month":
281
- return "month";
282
- case "day":
283
- return "day";
284
- case "hour":
285
- return "hour";
286
- case "minute":
287
- return "minute";
288
- case "second":
289
- return "second";
290
- }
291
- }
292
- convertDateFormat(format) {
293
- return format.replace(/yyyy/g, "YYYY").replace(/MM/g, "MM").replace(/dd/g, "DD").replace(/HH/g, "HH24").replace(/mm/g, "MI").replace(/ss/g, "SS");
294
- }
295
- //#endregion
296
- //#region ========== condition ==========
297
- coalesce(expr) {
298
- if (expr.args.length === 0) return "NULL";
299
- if (expr.args.length === 1) return this.render(expr.args[0]);
300
- return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
301
- }
302
- nullIf(expr) {
303
- return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
304
- }
305
- is(expr) {
306
- return `(${this.render(expr.condition)})::INTEGER`;
307
- }
308
- switch(expr) {
309
- const cases = expr.cases.map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`).join(" ");
310
- return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
311
- }
312
- if(expr) {
313
- const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
314
- return `CASE WHEN ${this.render(expr.condition)} THEN ${this.render(expr.then)} ELSE ${elseVal} END`;
315
- }
316
- //#endregion
317
- //#region ========== aggregation ==========
318
- count(expr) {
319
- if (expr.arg != null) {
320
- const distinct = expr.distinct ? "DISTINCT " : "";
321
- return `COUNT(${distinct}${this.render(expr.arg)})`;
322
- }
323
- return "COUNT(*)";
324
- }
325
- sum(expr) {
326
- return `SUM(${this.render(expr.arg)})`;
327
- }
328
- avg(expr) {
329
- return `AVG(${this.render(expr.arg)})`;
330
- }
331
- max(expr) {
332
- return `MAX(${this.render(expr.arg)})`;
333
- }
334
- min(expr) {
335
- return `MIN(${this.render(expr.arg)})`;
336
- }
337
- //#endregion
338
- //#region ========== Other ==========
339
- greatest(expr) {
340
- if (expr.args.length === 0) throw new Error("greatest requires at least one argument.");
341
- return `GREATEST(${expr.args.map((a) => this.render(a)).join(", ")})`;
342
- }
343
- least(expr) {
344
- if (expr.args.length === 0) throw new Error("least requires at least one argument.");
345
- return `LEAST(${expr.args.map((a) => this.render(a)).join(", ")})`;
346
- }
347
- rowNum(_expr) {
348
- return "ROW_NUMBER() OVER ()";
349
- }
350
- random(_expr) {
351
- return "RANDOM()";
352
- }
353
- cast(expr) {
354
- return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
355
- }
356
- //#endregion
357
- //#region ========== Window ==========
358
- window(expr) {
359
- const fn = this.renderWindowFn(expr.fn);
360
- let over = this.renderWindowSpec(expr.spec);
361
- if (expr.fn.type === "lastValue" && over.length > 0) {
362
- over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
363
- }
364
- return `${fn} OVER (${over})`;
365
- }
366
- renderWindowFn(fn) {
367
- switch (fn.type) {
368
- case "rowNumber":
369
- return "ROW_NUMBER()";
370
- case "rank":
371
- return "RANK()";
372
- case "denseRank":
373
- return "DENSE_RANK()";
374
- case "ntile":
375
- return `NTILE(${fn.n})`;
376
- case "lag": {
377
- const offset = fn.offset ?? 1;
378
- const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
379
- return `LAG(${this.render(fn.column)}, ${offset}${def})`;
380
- }
381
- case "lead": {
382
- const offset = fn.offset ?? 1;
383
- const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
384
- return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
385
- }
386
- case "firstValue":
387
- return `FIRST_VALUE(${this.render(fn.column)})`;
388
- case "lastValue":
389
- return `LAST_VALUE(${this.render(fn.column)})`;
390
- case "sum":
391
- return `SUM(${this.render(fn.column)})`;
392
- case "avg":
393
- return `AVG(${this.render(fn.column)})`;
394
- case "count":
395
- return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
396
- case "min":
397
- return `MIN(${this.render(fn.column)})`;
398
- case "max":
399
- return `MAX(${this.render(fn.column)})`;
400
- }
401
- }
402
- renderWindowSpec(spec) {
403
- const parts = [];
404
- if (spec.partitionBy != null && spec.partitionBy.length > 0) {
405
- parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
406
- }
407
- if (spec.orderBy != null && spec.orderBy.length > 0) {
408
- const orderParts = spec.orderBy.map(
409
- ([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`
410
- );
411
- parts.push(`ORDER BY ${orderParts.join(", ")}`);
412
- }
413
- return parts.join(" ");
414
- }
415
- //#endregion
416
- //#region ========== System ==========
417
- subquery(expr) {
418
- return `(${this.buildSelect(expr.queryDef)})`;
419
- }
420
- //#endregion
3
+ /**
4
+ * PostgreSQL 표현식 렌더러
5
+ */
6
+ export class PostgresqlExprRenderer 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 `'${value.replace(/'/g, "''")}'`;
23
+ }
24
+ if (typeof value === "number") {
25
+ return String(value);
26
+ }
27
+ if (typeof value === "boolean") {
28
+ return value ? "TRUE" : "FALSE";
29
+ }
30
+ if (value instanceof DateTime) {
31
+ return `'${value.toFormatString("yyyy-MM-dd HH:mm:ss")}'::timestamp`;
32
+ }
33
+ if (value instanceof DateOnly) {
34
+ return `'${value.toFormatString("yyyy-MM-dd")}'::date`;
35
+ }
36
+ if (value instanceof Time) {
37
+ return `'${value.toFormatString("HH:mm:ss")}'::time`;
38
+ }
39
+ if (value instanceof Uuid) {
40
+ return `'${value.toString()}'::uuid`;
41
+ }
42
+ if (value instanceof Uint8Array) {
43
+ return `'\\x${bytes.toHex(value)}'::bytea`;
44
+ }
45
+ throw new Error(`알 수 없는 값 타입: ${typeof value}`);
46
+ }
47
+ /** DataType → SQL type */
48
+ renderDataType(dataType) {
49
+ switch (dataType.type) {
50
+ case "int":
51
+ return "INTEGER";
52
+ case "bigint":
53
+ return "BIGINT";
54
+ case "float":
55
+ return "REAL";
56
+ case "double":
57
+ return "DOUBLE PRECISION";
58
+ case "decimal":
59
+ return dataType.scale != null
60
+ ? `NUMERIC(${dataType.precision}, ${dataType.scale})`
61
+ : `NUMERIC(${dataType.precision})`;
62
+ case "varchar":
63
+ return `VARCHAR(${dataType.length})`;
64
+ case "char":
65
+ return `CHAR(${dataType.length})`;
66
+ case "text":
67
+ return "TEXT";
68
+ case "binary":
69
+ return "BYTEA";
70
+ case "boolean":
71
+ return "BOOLEAN";
72
+ case "datetime":
73
+ return "TIMESTAMP";
74
+ case "date":
75
+ return "DATE";
76
+ case "time":
77
+ return "TIME";
78
+ case "uuid":
79
+ return "UUID";
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
+ // PostgreSQL: null-safe equal (uses IS NOT DISTINCT FROM operator)
100
+ const left = this.render(expr.source);
101
+ const right = this.render(expr.target);
102
+ return `${left} IS NOT DISTINCT FROM ${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 "TRUE";
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
+ // PostgreSQL: ~ operator
138
+ return `${this.render(expr.source)} ~ ${this.render(expr.pattern)}`;
139
+ }
140
+ in(expr) {
141
+ if (expr.values.length === 0) {
142
+ return "FALSE"; // 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 "TRUE";
166
+ return `(${expr.conditions.map((c) => this.render(c)).join(" AND ")})`;
167
+ }
168
+ or(expr) {
169
+ if (expr.conditions.length === 0)
170
+ return "FALSE";
171
+ return `(${expr.conditions.map((c) => this.render(c)).join(" OR ")})`;
172
+ }
173
+ //#endregion
174
+ //#region ========== String (null handling) ==========
175
+ concat(expr) {
176
+ // PostgreSQL: uses || operator with COALESCE
177
+ const args = expr.args.map((a) => `COALESCE(${this.render(a)}, '')`);
178
+ return args.join(" || ");
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 `TRIM(${this.render(expr.arg)})`;
188
+ }
189
+ padStart(expr) {
190
+ return `LPAD(${this.render(expr.source)}, ${this.render(expr.length)}, ${this.render(expr.fillString)})`;
191
+ }
192
+ replace(expr) {
193
+ return `REPLACE(${this.render(expr.source)}, ${this.render(expr.from)}, ${this.render(expr.to)})`;
194
+ }
195
+ upper(expr) {
196
+ return `UPPER(${this.render(expr.arg)})`;
197
+ }
198
+ lower(expr) {
199
+ return `LOWER(${this.render(expr.arg)})`;
200
+ }
201
+ length(expr) {
202
+ // PostgreSQL: LENGTH() (null handling)
203
+ return `LENGTH(COALESCE(${this.render(expr.arg)}, ''))`;
204
+ }
205
+ byteLength(expr) {
206
+ // PostgreSQL: OCTET_LENGTH() (null handling)
207
+ return `OCTET_LENGTH(COALESCE(${this.render(expr.arg)}, ''))`;
208
+ }
209
+ substring(expr) {
210
+ if (expr.length != null) {
211
+ return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, ${this.render(expr.length)})`;
212
+ }
213
+ return `SUBSTRING(${this.render(expr.source)} FROM ${this.render(expr.start)})`;
214
+ }
215
+ indexOf(expr) {
216
+ return `POSITION(${this.render(expr.search)} IN ${this.render(expr.source)})`;
217
+ }
218
+ //#endregion
219
+ //#region ========== Number ==========
220
+ abs(expr) {
221
+ return `ABS(${this.render(expr.arg)})`;
222
+ }
223
+ round(expr) {
224
+ return `ROUND(${this.render(expr.arg)}, ${expr.digits})`;
225
+ }
226
+ ceil(expr) {
227
+ return `CEIL(${this.render(expr.arg)})`;
228
+ }
229
+ floor(expr) {
230
+ return `FLOOR(${this.render(expr.arg)})`;
231
+ }
232
+ //#endregion
233
+ //#region ========== Date ==========
234
+ year(expr) {
235
+ return `EXTRACT(YEAR FROM ${this.render(expr.arg)})::INTEGER`;
236
+ }
237
+ month(expr) {
238
+ return `EXTRACT(MONTH FROM ${this.render(expr.arg)})::INTEGER`;
239
+ }
240
+ day(expr) {
241
+ return `EXTRACT(DAY FROM ${this.render(expr.arg)})::INTEGER`;
242
+ }
243
+ hour(expr) {
244
+ return `EXTRACT(HOUR FROM ${this.render(expr.arg)})::INTEGER`;
245
+ }
246
+ minute(expr) {
247
+ return `EXTRACT(MINUTE FROM ${this.render(expr.arg)})::INTEGER`;
248
+ }
249
+ second(expr) {
250
+ return `EXTRACT(SECOND FROM ${this.render(expr.arg)})::INTEGER`;
251
+ }
252
+ isoWeek(expr) {
253
+ return `EXTRACT(WEEK FROM ${this.render(expr.arg)})::INTEGER`;
254
+ }
255
+ isoWeekStartDate(expr) {
256
+ const src = this.render(expr.arg);
257
+ // ISO week start date (Monday)
258
+ return `DATE_TRUNC('week', ${src})::DATE`;
259
+ }
260
+ isoYearMonth(expr) {
261
+ return `TO_CHAR(${this.render(expr.arg)}, 'YYYYMM')`;
262
+ }
263
+ dateDiff(expr) {
264
+ const from = this.render(expr.from);
265
+ const to = this.render(expr.to);
266
+ switch (expr.unit) {
267
+ case "year":
268
+ return `EXTRACT(YEAR FROM AGE(${to}, ${from}))::INTEGER`;
269
+ case "month":
270
+ return `(EXTRACT(YEAR FROM AGE(${to}, ${from})) * 12 + EXTRACT(MONTH FROM AGE(${to}, ${from})))::INTEGER`;
271
+ case "day":
272
+ return `(${to}::DATE - ${from}::DATE)`;
273
+ case "hour":
274
+ return `EXTRACT(EPOCH FROM (${to} - ${from}))::INTEGER / 3600`;
275
+ case "minute":
276
+ return `EXTRACT(EPOCH FROM (${to} - ${from}))::INTEGER / 60`;
277
+ case "second":
278
+ return `EXTRACT(EPOCH FROM (${to} - ${from}))::INTEGER`;
279
+ }
280
+ }
281
+ dateAdd(expr) {
282
+ const source = this.render(expr.source);
283
+ const value = this.render(expr.value);
284
+ const unit = this.dateUnitToSql(expr.unit);
285
+ return `${source} + INTERVAL '1 ${unit}' * ${value}`;
286
+ }
287
+ formatDate(expr) {
288
+ // JS format → PostgreSQL TO_CHAR format
289
+ const pgFormat = this.convertDateFormat(expr.format);
290
+ return `TO_CHAR(${this.render(expr.source)}, '${pgFormat}')`;
291
+ }
292
+ dateUnitToSql(unit) {
293
+ switch (unit) {
294
+ case "year":
295
+ return "year";
296
+ case "month":
297
+ return "month";
298
+ case "day":
299
+ return "day";
300
+ case "hour":
301
+ return "hour";
302
+ case "minute":
303
+ return "minute";
304
+ case "second":
305
+ return "second";
306
+ }
307
+ }
308
+ convertDateFormat(format) {
309
+ // JS format PostgreSQL TO_CHAR format
310
+ return format
311
+ .replace(/yyyy/g, "YYYY")
312
+ .replace(/MM/g, "MM")
313
+ .replace(/dd/g, "DD")
314
+ .replace(/HH/g, "HH24")
315
+ .replace(/mm/g, "MI")
316
+ .replace(/ss/g, "SS");
317
+ }
318
+ //#endregion
319
+ //#region ========== condition ==========
320
+ coalesce(expr) {
321
+ if (expr.args.length === 0)
322
+ return "NULL";
323
+ if (expr.args.length === 1)
324
+ return this.render(expr.args[0]);
325
+ // PostgreSQL: COALESCE
326
+ return `COALESCE(${expr.args.map((a) => this.render(a)).join(", ")})`;
327
+ }
328
+ nullIf(expr) {
329
+ return `NULLIF(${this.render(expr.source)}, ${this.render(expr.value)})`;
330
+ }
331
+ is(expr) {
332
+ return `(${this.render(expr.condition)})::INTEGER`;
333
+ }
334
+ switch(expr) {
335
+ const cases = expr.cases
336
+ .map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`)
337
+ .join(" ");
338
+ return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
339
+ }
340
+ if(expr) {
341
+ const elseVal = expr.else != null ? this.render(expr.else) : "NULL";
342
+ return `CASE WHEN ${this.render(expr.condition)} THEN ${this.render(expr.then)} ELSE ${elseVal} END`;
343
+ }
344
+ //#endregion
345
+ //#region ========== aggregation ==========
346
+ count(expr) {
347
+ if (expr.arg != null) {
348
+ const distinct = expr.distinct ? "DISTINCT " : "";
349
+ return `COUNT(${distinct}${this.render(expr.arg)})`;
350
+ }
351
+ return "COUNT(*)";
352
+ }
353
+ sum(expr) {
354
+ return `SUM(${this.render(expr.arg)})`;
355
+ }
356
+ avg(expr) {
357
+ return `AVG(${this.render(expr.arg)})`;
358
+ }
359
+ max(expr) {
360
+ return `MAX(${this.render(expr.arg)})`;
361
+ }
362
+ min(expr) {
363
+ return `MIN(${this.render(expr.arg)})`;
364
+ }
365
+ //#endregion
366
+ //#region ========== Other ==========
367
+ greatest(expr) {
368
+ if (expr.args.length === 0)
369
+ throw new Error("greatest에는 최소 1개의 인수가 필요합니다.");
370
+ // PostgreSQL: native GREATEST support
371
+ return `GREATEST(${expr.args.map((a) => this.render(a)).join(", ")})`;
372
+ }
373
+ least(expr) {
374
+ if (expr.args.length === 0)
375
+ throw new Error("least에는 최소 1개의 인수가 필요합니다.");
376
+ // PostgreSQL: native LEAST support
377
+ return `LEAST(${expr.args.map((a) => this.render(a)).join(", ")})`;
378
+ }
379
+ rowNum(_expr) {
380
+ return "ROW_NUMBER() OVER ()";
381
+ }
382
+ random(_expr) {
383
+ return "RANDOM()";
384
+ }
385
+ cast(expr) {
386
+ return `CAST(${this.render(expr.source)} AS ${this.renderDataType(expr.targetType)})`;
387
+ }
388
+ //#endregion
389
+ //#region ========== Window ==========
390
+ window(expr) {
391
+ const fn = this.renderWindowFn(expr.fn);
392
+ let over = this.renderWindowSpec(expr.spec);
393
+ // LAST_VALUE default frame only sees up to CURRENT ROW, so full frame must be specified
394
+ if (expr.fn.type === "lastValue" && over.length > 0) {
395
+ over += " ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING";
396
+ }
397
+ return `${fn} OVER (${over})`;
398
+ }
399
+ renderWindowFn(fn) {
400
+ switch (fn.type) {
401
+ case "rowNumber":
402
+ return "ROW_NUMBER()";
403
+ case "rank":
404
+ return "RANK()";
405
+ case "denseRank":
406
+ return "DENSE_RANK()";
407
+ case "ntile":
408
+ return `NTILE(${fn.n})`;
409
+ case "lag": {
410
+ const offset = fn.offset ?? 1;
411
+ const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
412
+ return `LAG(${this.render(fn.column)}, ${offset}${def})`;
413
+ }
414
+ case "lead": {
415
+ const offset = fn.offset ?? 1;
416
+ const def = fn.default != null ? `, ${this.render(fn.default)}` : "";
417
+ return `LEAD(${this.render(fn.column)}, ${offset}${def})`;
418
+ }
419
+ case "firstValue":
420
+ return `FIRST_VALUE(${this.render(fn.column)})`;
421
+ case "lastValue":
422
+ return `LAST_VALUE(${this.render(fn.column)})`;
423
+ case "sum":
424
+ return `SUM(${this.render(fn.column)})`;
425
+ case "avg":
426
+ return `AVG(${this.render(fn.column)})`;
427
+ case "count":
428
+ return fn.column != null ? `COUNT(${this.render(fn.column)})` : "COUNT(*)";
429
+ case "min":
430
+ return `MIN(${this.render(fn.column)})`;
431
+ case "max":
432
+ return `MAX(${this.render(fn.column)})`;
433
+ }
434
+ }
435
+ renderWindowSpec(spec) {
436
+ const parts = [];
437
+ if (spec.partitionBy != null && spec.partitionBy.length > 0) {
438
+ parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
439
+ }
440
+ if (spec.orderBy != null && spec.orderBy.length > 0) {
441
+ const orderParts = spec.orderBy.map(([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`);
442
+ parts.push(`ORDER BY ${orderParts.join(", ")}`);
443
+ }
444
+ return parts.join(" ");
445
+ }
446
+ //#endregion
447
+ //#region ========== System ==========
448
+ subquery(expr) {
449
+ return `(${this.buildSelect(expr.queryDef)})`;
450
+ }
421
451
  }
422
- export {
423
- PostgresqlExprRenderer
424
- };
425
- //# sourceMappingURL=postgresql-expr-renderer.js.map
452
+ //# sourceMappingURL=postgresql-expr-renderer.js.map