@simplysm/orm-common 13.0.100 → 14.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +90 -147
  2. package/dist/create-db-context.d.ts +10 -10
  3. package/dist/create-db-context.js +312 -276
  4. package/dist/create-db-context.js.map +1 -6
  5. package/dist/ddl/column-ddl.d.ts +4 -4
  6. package/dist/ddl/column-ddl.js +41 -35
  7. package/dist/ddl/column-ddl.js.map +1 -6
  8. package/dist/ddl/initialize.d.ts +17 -17
  9. package/dist/ddl/initialize.js +200 -142
  10. package/dist/ddl/initialize.js.map +1 -6
  11. package/dist/ddl/relation-ddl.d.ts +6 -6
  12. package/dist/ddl/relation-ddl.js +55 -48
  13. package/dist/ddl/relation-ddl.js.map +1 -6
  14. package/dist/ddl/schema-ddl.d.ts +4 -4
  15. package/dist/ddl/schema-ddl.js +21 -15
  16. package/dist/ddl/schema-ddl.js.map +1 -6
  17. package/dist/ddl/table-ddl.d.ts +20 -20
  18. package/dist/ddl/table-ddl.js +139 -93
  19. package/dist/ddl/table-ddl.js.map +1 -6
  20. package/dist/define-db-context.js +10 -13
  21. package/dist/define-db-context.js.map +1 -6
  22. package/dist/errors/db-transaction-error.d.ts +15 -15
  23. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  24. package/dist/errors/db-transaction-error.js +53 -19
  25. package/dist/errors/db-transaction-error.js.map +1 -6
  26. package/dist/exec/executable.d.ts +23 -23
  27. package/dist/exec/executable.js +94 -40
  28. package/dist/exec/executable.js.map +1 -6
  29. package/dist/exec/queryable.d.ts +97 -97
  30. package/dist/exec/queryable.js +1310 -1204
  31. package/dist/exec/queryable.js.map +1 -6
  32. package/dist/exec/search-parser.d.ts +31 -31
  33. package/dist/exec/search-parser.d.ts.map +1 -1
  34. package/dist/exec/search-parser.js +158 -59
  35. package/dist/exec/search-parser.js.map +1 -6
  36. package/dist/expr/expr-unit.d.ts +4 -4
  37. package/dist/expr/expr-unit.js +24 -18
  38. package/dist/expr/expr-unit.js.map +1 -6
  39. package/dist/expr/expr.d.ts +108 -108
  40. package/dist/expr/expr.js +1872 -1844
  41. package/dist/expr/expr.js.map +1 -6
  42. package/dist/index.js +23 -1
  43. package/dist/index.js.map +1 -6
  44. package/dist/models/system-migration.js +7 -7
  45. package/dist/models/system-migration.js.map +1 -6
  46. package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
  47. package/dist/query-builder/base/expr-renderer-base.js +27 -21
  48. package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
  49. package/dist/query-builder/base/query-builder-base.d.ts +21 -21
  50. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  51. package/dist/query-builder/base/query-builder-base.js +90 -80
  52. package/dist/query-builder/base/query-builder-base.js.map +1 -6
  53. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +5 -5
  54. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  55. package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
  56. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
  57. package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
  58. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  59. package/dist/query-builder/mssql/mssql-query-builder.js +483 -443
  60. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
  61. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +5 -5
  62. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  63. package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
  64. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
  65. package/dist/query-builder/mysql/mysql-query-builder.d.ts +10 -10
  66. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  67. package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
  68. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
  69. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +5 -5
  70. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  71. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
  72. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
  73. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +8 -8
  74. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  75. package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
  76. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
  77. package/dist/query-builder/query-builder.d.ts +1 -1
  78. package/dist/query-builder/query-builder.js +13 -13
  79. package/dist/query-builder/query-builder.js.map +1 -6
  80. package/dist/schema/factory/column-builder.d.ts +84 -84
  81. package/dist/schema/factory/column-builder.js +248 -185
  82. package/dist/schema/factory/column-builder.js.map +1 -6
  83. package/dist/schema/factory/index-builder.d.ts +38 -38
  84. package/dist/schema/factory/index-builder.js +144 -85
  85. package/dist/schema/factory/index-builder.js.map +1 -6
  86. package/dist/schema/factory/relation-builder.d.ts +99 -99
  87. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  88. package/dist/schema/factory/relation-builder.js +274 -136
  89. package/dist/schema/factory/relation-builder.js.map +1 -6
  90. package/dist/schema/procedure-builder.d.ts +51 -51
  91. package/dist/schema/procedure-builder.d.ts.map +1 -1
  92. package/dist/schema/procedure-builder.js +205 -131
  93. package/dist/schema/procedure-builder.js.map +1 -6
  94. package/dist/schema/table-builder.d.ts +55 -55
  95. package/dist/schema/table-builder.d.ts.map +1 -1
  96. package/dist/schema/table-builder.js +274 -205
  97. package/dist/schema/table-builder.js.map +1 -6
  98. package/dist/schema/view-builder.d.ts +44 -44
  99. package/dist/schema/view-builder.d.ts.map +1 -1
  100. package/dist/schema/view-builder.js +189 -116
  101. package/dist/schema/view-builder.js.map +1 -6
  102. package/dist/types/column.d.ts +21 -21
  103. package/dist/types/column.js +60 -30
  104. package/dist/types/column.js.map +1 -6
  105. package/dist/types/db-context-def.d.ts +9 -9
  106. package/dist/types/db-context-def.js +2 -1
  107. package/dist/types/db-context-def.js.map +1 -6
  108. package/dist/types/db.d.ts +47 -47
  109. package/dist/types/db.js +15 -5
  110. package/dist/types/db.js.map +1 -6
  111. package/dist/types/expr.d.ts +81 -81
  112. package/dist/types/expr.d.ts.map +1 -1
  113. package/dist/types/expr.js +3 -1
  114. package/dist/types/expr.js.map +1 -6
  115. package/dist/types/query-def.d.ts +46 -46
  116. package/dist/types/query-def.d.ts.map +1 -1
  117. package/dist/types/query-def.js +31 -24
  118. package/dist/types/query-def.js.map +1 -6
  119. package/dist/utils/result-parser.d.ts +11 -11
  120. package/dist/utils/result-parser.js +362 -221
  121. package/dist/utils/result-parser.js.map +1 -6
  122. package/docs/core.md +117 -145
  123. package/docs/expression.md +186 -203
  124. package/docs/query-builder.md +75 -42
  125. package/docs/queryable.md +189 -151
  126. package/docs/schema-builders.md +172 -283
  127. package/docs/types.md +229 -173
  128. package/package.json +7 -5
  129. package/src/create-db-context.ts +31 -31
  130. package/src/ddl/column-ddl.ts +4 -4
  131. package/src/ddl/initialize.ts +38 -38
  132. package/src/ddl/relation-ddl.ts +6 -6
  133. package/src/ddl/schema-ddl.ts +4 -4
  134. package/src/ddl/table-ddl.ts +24 -24
  135. package/src/errors/db-transaction-error.ts +13 -13
  136. package/src/exec/executable.ts +25 -25
  137. package/src/exec/queryable.ts +152 -152
  138. package/src/exec/search-parser.ts +50 -50
  139. package/src/expr/expr-unit.ts +4 -4
  140. package/src/expr/expr.ts +118 -118
  141. package/src/index.ts +8 -8
  142. package/src/models/system-migration.ts +1 -1
  143. package/src/query-builder/base/expr-renderer-base.ts +21 -21
  144. package/src/query-builder/base/query-builder-base.ts +33 -33
  145. package/src/query-builder/mssql/mssql-expr-renderer.ts +28 -28
  146. package/src/query-builder/mssql/mssql-query-builder.ts +37 -37
  147. package/src/query-builder/mysql/mysql-expr-renderer.ts +29 -29
  148. package/src/query-builder/mysql/mysql-query-builder.ts +70 -70
  149. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +22 -22
  150. package/src/query-builder/postgresql/postgresql-query-builder.ts +54 -54
  151. package/src/query-builder/query-builder.ts +1 -1
  152. package/src/schema/factory/column-builder.ts +86 -86
  153. package/src/schema/factory/index-builder.ts +38 -38
  154. package/src/schema/factory/relation-builder.ts +102 -102
  155. package/src/schema/procedure-builder.ts +52 -52
  156. package/src/schema/table-builder.ts +56 -56
  157. package/src/schema/view-builder.ts +47 -47
  158. package/src/types/column.ts +24 -24
  159. package/src/types/db-context-def.ts +15 -15
  160. package/src/types/db.ts +50 -50
  161. package/src/types/expr.ts +103 -103
  162. package/src/types/query-def.ts +50 -50
  163. package/src/utils/result-parser.ts +88 -88
  164. package/docs/utilities.md +0 -27
  165. package/tests/db-context/create-db-context.spec.ts +0 -193
  166. package/tests/db-context/define-db-context.spec.ts +0 -17
  167. package/tests/ddl/basic.expected.ts +0 -341
  168. package/tests/ddl/basic.spec.ts +0 -557
  169. package/tests/ddl/column-builder.expected.ts +0 -310
  170. package/tests/ddl/column-builder.spec.ts +0 -525
  171. package/tests/ddl/index-builder.expected.ts +0 -38
  172. package/tests/ddl/index-builder.spec.ts +0 -148
  173. package/tests/ddl/procedure-builder.expected.ts +0 -52
  174. package/tests/ddl/procedure-builder.spec.ts +0 -128
  175. package/tests/ddl/relation-builder.expected.ts +0 -36
  176. package/tests/ddl/relation-builder.spec.ts +0 -171
  177. package/tests/ddl/table-builder.expected.ts +0 -113
  178. package/tests/ddl/table-builder.spec.ts +0 -399
  179. package/tests/ddl/view-builder.expected.ts +0 -38
  180. package/tests/ddl/view-builder.spec.ts +0 -116
  181. package/tests/dml/delete.expected.ts +0 -96
  182. package/tests/dml/delete.spec.ts +0 -127
  183. package/tests/dml/insert.expected.ts +0 -192
  184. package/tests/dml/insert.spec.ts +0 -210
  185. package/tests/dml/update.expected.ts +0 -176
  186. package/tests/dml/update.spec.ts +0 -222
  187. package/tests/dml/upsert.expected.ts +0 -215
  188. package/tests/dml/upsert.spec.ts +0 -190
  189. package/tests/errors/queryable-errors.spec.ts +0 -126
  190. package/tests/escape.spec.ts +0 -59
  191. package/tests/examples/pivot.expected.ts +0 -211
  192. package/tests/examples/pivot.spec.ts +0 -200
  193. package/tests/examples/sampling.expected.ts +0 -69
  194. package/tests/examples/sampling.spec.ts +0 -42
  195. package/tests/examples/unpivot.expected.ts +0 -120
  196. package/tests/examples/unpivot.spec.ts +0 -161
  197. package/tests/exec/search-parser.spec.ts +0 -267
  198. package/tests/executable/basic.expected.ts +0 -18
  199. package/tests/executable/basic.spec.ts +0 -54
  200. package/tests/expr/comparison.expected.ts +0 -282
  201. package/tests/expr/comparison.spec.ts +0 -334
  202. package/tests/expr/conditional.expected.ts +0 -134
  203. package/tests/expr/conditional.spec.ts +0 -249
  204. package/tests/expr/date.expected.ts +0 -332
  205. package/tests/expr/date.spec.ts +0 -459
  206. package/tests/expr/math.expected.ts +0 -62
  207. package/tests/expr/math.spec.ts +0 -59
  208. package/tests/expr/string.expected.ts +0 -218
  209. package/tests/expr/string.spec.ts +0 -300
  210. package/tests/expr/utility.expected.ts +0 -147
  211. package/tests/expr/utility.spec.ts +0 -155
  212. package/tests/select/basic.expected.ts +0 -322
  213. package/tests/select/basic.spec.ts +0 -433
  214. package/tests/select/filter.expected.ts +0 -357
  215. package/tests/select/filter.spec.ts +0 -954
  216. package/tests/select/group.expected.ts +0 -169
  217. package/tests/select/group.spec.ts +0 -159
  218. package/tests/select/join.expected.ts +0 -582
  219. package/tests/select/join.spec.ts +0 -692
  220. package/tests/select/order.expected.ts +0 -150
  221. package/tests/select/order.spec.ts +0 -140
  222. package/tests/select/recursive-cte.expected.ts +0 -244
  223. package/tests/select/recursive-cte.spec.ts +0 -514
  224. package/tests/select/result-meta.spec.ts +0 -270
  225. package/tests/select/subquery.expected.ts +0 -363
  226. package/tests/select/subquery.spec.ts +0 -441
  227. package/tests/select/view.expected.ts +0 -155
  228. package/tests/select/view.spec.ts +0 -235
  229. package/tests/select/window.expected.ts +0 -345
  230. package/tests/select/window.spec.ts +0 -433
  231. package/tests/setup/MockExecutor.ts +0 -18
  232. package/tests/setup/TestDbContext.ts +0 -59
  233. package/tests/setup/models/Company.ts +0 -13
  234. package/tests/setup/models/Employee.ts +0 -10
  235. package/tests/setup/models/MonthlySales.ts +0 -11
  236. package/tests/setup/models/Post.ts +0 -16
  237. package/tests/setup/models/Sales.ts +0 -10
  238. package/tests/setup/models/User.ts +0 -19
  239. package/tests/setup/procedure/GetAllUsers.ts +0 -9
  240. package/tests/setup/procedure/GetUserById.ts +0 -12
  241. package/tests/setup/test-utils.ts +0 -72
  242. package/tests/setup/views/ActiveUsers.ts +0 -8
  243. package/tests/setup/views/UserSummary.ts +0 -11
  244. package/tests/types/nullable-queryable-record.spec.ts +0 -97
  245. package/tests/utils/result-parser-perf.spec.ts +0 -143
  246. 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 타입 변환 */
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 안전 동등 비교 (IS NOT DISTINCT FROM 연산자 사용)
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"; // 빈 IN은 항상 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
+ // 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: 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 처리)
203
+ return `LENGTH(COALESCE(${this.render(expr.arg)}, ''))`;
204
+ }
205
+ byteLength(expr) {
206
+ // PostgreSQL: OCTET_LENGTH() (null 처리)
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 시작일 (월요일)
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 포맷 → PostgreSQL TO_CHAR 포맷
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 포맷 PostgreSQL TO_CHAR 포맷
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: GREATEST 네이티브 지원
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: LEAST 네이티브 지원
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 기본 프레임은 CURRENT ROW까지만 보므로 전체 프레임을 지정해야 함
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