@simplysm/orm-common 13.0.68 → 13.0.70

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 (204) hide show
  1. package/README.md +54 -1447
  2. package/dist/create-db-context.d.ts +10 -10
  3. package/dist/create-db-context.js +9 -9
  4. package/dist/create-db-context.js.map +1 -1
  5. package/dist/ddl/column-ddl.d.ts +4 -4
  6. package/dist/ddl/initialize.d.ts +17 -17
  7. package/dist/ddl/initialize.js +2 -2
  8. package/dist/ddl/initialize.js.map +1 -1
  9. package/dist/ddl/relation-ddl.d.ts +6 -6
  10. package/dist/ddl/schema-ddl.d.ts +4 -4
  11. package/dist/ddl/table-ddl.d.ts +24 -24
  12. package/dist/ddl/table-ddl.js +4 -4
  13. package/dist/ddl/table-ddl.js.map +1 -1
  14. package/dist/errors/db-transaction-error.d.ts +15 -15
  15. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  16. package/dist/exec/executable.d.ts +23 -23
  17. package/dist/exec/executable.js +3 -3
  18. package/dist/exec/executable.js.map +1 -1
  19. package/dist/exec/queryable.d.ts +160 -160
  20. package/dist/exec/queryable.js +119 -119
  21. package/dist/exec/queryable.js.map +1 -1
  22. package/dist/exec/search-parser.d.ts +37 -37
  23. package/dist/exec/search-parser.d.ts.map +1 -1
  24. package/dist/expr/expr-unit.d.ts +4 -4
  25. package/dist/expr/expr.d.ts +257 -257
  26. package/dist/expr/expr.js +265 -265
  27. package/dist/expr/expr.js.map +1 -1
  28. package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
  29. package/dist/query-builder/base/expr-renderer-base.js +2 -2
  30. package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
  31. package/dist/query-builder/base/query-builder-base.d.ts +26 -26
  32. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  33. package/dist/query-builder/base/query-builder-base.js +22 -22
  34. package/dist/query-builder/base/query-builder-base.js.map +1 -1
  35. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
  36. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  37. package/dist/query-builder/mssql/mssql-expr-renderer.js +18 -18
  38. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
  39. package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
  40. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  41. package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
  42. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
  43. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
  44. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  45. package/dist/query-builder/mysql/mysql-expr-renderer.js +17 -17
  46. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
  47. package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
  48. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  49. package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
  50. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
  51. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
  52. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  53. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +17 -17
  54. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
  55. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
  56. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  57. package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
  58. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
  59. package/dist/query-builder/query-builder.d.ts +1 -1
  60. package/dist/schema/factory/column-builder.d.ts +79 -79
  61. package/dist/schema/factory/column-builder.js +42 -42
  62. package/dist/schema/factory/index-builder.d.ts +39 -39
  63. package/dist/schema/factory/index-builder.js +26 -26
  64. package/dist/schema/factory/relation-builder.d.ts +99 -99
  65. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  66. package/dist/schema/factory/relation-builder.js +38 -38
  67. package/dist/schema/procedure-builder.d.ts +49 -49
  68. package/dist/schema/procedure-builder.d.ts.map +1 -1
  69. package/dist/schema/procedure-builder.js +33 -33
  70. package/dist/schema/table-builder.d.ts +59 -59
  71. package/dist/schema/table-builder.d.ts.map +1 -1
  72. package/dist/schema/table-builder.js +43 -43
  73. package/dist/schema/view-builder.d.ts +49 -49
  74. package/dist/schema/view-builder.d.ts.map +1 -1
  75. package/dist/schema/view-builder.js +32 -32
  76. package/dist/types/column.d.ts +22 -22
  77. package/dist/types/column.js +1 -1
  78. package/dist/types/column.js.map +1 -1
  79. package/dist/types/db.d.ts +40 -40
  80. package/dist/types/expr.d.ts +59 -59
  81. package/dist/types/expr.d.ts.map +1 -1
  82. package/dist/types/query-def.d.ts +44 -44
  83. package/dist/types/query-def.d.ts.map +1 -1
  84. package/dist/utils/result-parser.d.ts +11 -11
  85. package/dist/utils/result-parser.js +3 -3
  86. package/dist/utils/result-parser.js.map +1 -1
  87. package/package.json +5 -5
  88. package/src/create-db-context.ts +20 -20
  89. package/src/ddl/column-ddl.ts +4 -4
  90. package/src/ddl/initialize.ts +259 -259
  91. package/src/ddl/relation-ddl.ts +89 -89
  92. package/src/ddl/schema-ddl.ts +4 -4
  93. package/src/ddl/table-ddl.ts +189 -189
  94. package/src/errors/db-transaction-error.ts +13 -13
  95. package/src/exec/executable.ts +25 -25
  96. package/src/exec/queryable.ts +2033 -2033
  97. package/src/exec/search-parser.ts +57 -57
  98. package/src/expr/expr-unit.ts +4 -4
  99. package/src/expr/expr.ts +2140 -2140
  100. package/src/query-builder/base/expr-renderer-base.ts +237 -237
  101. package/src/query-builder/base/query-builder-base.ts +213 -213
  102. package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
  103. package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
  104. package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
  105. package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
  106. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
  107. package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
  108. package/src/query-builder/query-builder.ts +19 -19
  109. package/src/schema/factory/column-builder.ts +423 -423
  110. package/src/schema/factory/index-builder.ts +164 -164
  111. package/src/schema/factory/relation-builder.ts +453 -453
  112. package/src/schema/procedure-builder.ts +232 -232
  113. package/src/schema/table-builder.ts +319 -319
  114. package/src/schema/view-builder.ts +221 -221
  115. package/src/types/column.ts +188 -188
  116. package/src/types/db.ts +208 -208
  117. package/src/types/expr.ts +697 -697
  118. package/src/types/query-def.ts +513 -513
  119. package/src/utils/result-parser.ts +458 -458
  120. package/tests/db-context/create-db-context.spec.ts +224 -0
  121. package/tests/db-context/define-db-context.spec.ts +68 -0
  122. package/tests/ddl/basic.expected.ts +341 -0
  123. package/tests/ddl/basic.spec.ts +714 -0
  124. package/tests/ddl/column-builder.expected.ts +310 -0
  125. package/tests/ddl/column-builder.spec.ts +637 -0
  126. package/tests/ddl/index-builder.expected.ts +38 -0
  127. package/tests/ddl/index-builder.spec.ts +202 -0
  128. package/tests/ddl/procedure-builder.expected.ts +52 -0
  129. package/tests/ddl/procedure-builder.spec.ts +234 -0
  130. package/tests/ddl/relation-builder.expected.ts +36 -0
  131. package/tests/ddl/relation-builder.spec.ts +372 -0
  132. package/tests/ddl/table-builder.expected.ts +113 -0
  133. package/tests/ddl/table-builder.spec.ts +433 -0
  134. package/tests/ddl/view-builder.expected.ts +38 -0
  135. package/tests/ddl/view-builder.spec.ts +176 -0
  136. package/tests/dml/delete.expected.ts +96 -0
  137. package/tests/dml/delete.spec.ts +160 -0
  138. package/tests/dml/insert.expected.ts +192 -0
  139. package/tests/dml/insert.spec.ts +288 -0
  140. package/tests/dml/update.expected.ts +176 -0
  141. package/tests/dml/update.spec.ts +318 -0
  142. package/tests/dml/upsert.expected.ts +215 -0
  143. package/tests/dml/upsert.spec.ts +242 -0
  144. package/tests/errors/queryable-errors.spec.ts +177 -0
  145. package/tests/escape.spec.ts +100 -0
  146. package/tests/examples/pivot.expected.ts +211 -0
  147. package/tests/examples/pivot.spec.ts +533 -0
  148. package/tests/examples/sampling.expected.ts +69 -0
  149. package/tests/examples/sampling.spec.ts +104 -0
  150. package/tests/examples/unpivot.expected.ts +120 -0
  151. package/tests/examples/unpivot.spec.ts +226 -0
  152. package/tests/exec/search-parser.spec.ts +283 -0
  153. package/tests/executable/basic.expected.ts +18 -0
  154. package/tests/executable/basic.spec.ts +54 -0
  155. package/tests/expr/comparison.expected.ts +282 -0
  156. package/tests/expr/comparison.spec.ts +400 -0
  157. package/tests/expr/conditional.expected.ts +134 -0
  158. package/tests/expr/conditional.spec.ts +276 -0
  159. package/tests/expr/date.expected.ts +332 -0
  160. package/tests/expr/date.spec.ts +526 -0
  161. package/tests/expr/math.expected.ts +62 -0
  162. package/tests/expr/math.spec.ts +106 -0
  163. package/tests/expr/string.expected.ts +218 -0
  164. package/tests/expr/string.spec.ts +356 -0
  165. package/tests/expr/utility.expected.ts +147 -0
  166. package/tests/expr/utility.spec.ts +182 -0
  167. package/tests/select/basic.expected.ts +322 -0
  168. package/tests/select/basic.spec.ts +502 -0
  169. package/tests/select/filter.expected.ts +357 -0
  170. package/tests/select/filter.spec.ts +1068 -0
  171. package/tests/select/group.expected.ts +169 -0
  172. package/tests/select/group.spec.ts +244 -0
  173. package/tests/select/join.expected.ts +582 -0
  174. package/tests/select/join.spec.ts +805 -0
  175. package/tests/select/order.expected.ts +150 -0
  176. package/tests/select/order.spec.ts +189 -0
  177. package/tests/select/recursive-cte.expected.ts +244 -0
  178. package/tests/select/recursive-cte.spec.ts +514 -0
  179. package/tests/select/result-meta.spec.ts +270 -0
  180. package/tests/select/subquery.expected.ts +363 -0
  181. package/tests/select/subquery.spec.ts +537 -0
  182. package/tests/select/view.expected.ts +155 -0
  183. package/tests/select/view.spec.ts +235 -0
  184. package/tests/select/window.expected.ts +345 -0
  185. package/tests/select/window.spec.ts +618 -0
  186. package/tests/setup/MockExecutor.ts +18 -0
  187. package/tests/setup/TestDbContext.ts +59 -0
  188. package/tests/setup/models/Company.ts +13 -0
  189. package/tests/setup/models/Employee.ts +10 -0
  190. package/tests/setup/models/MonthlySales.ts +11 -0
  191. package/tests/setup/models/Post.ts +16 -0
  192. package/tests/setup/models/Sales.ts +10 -0
  193. package/tests/setup/models/User.ts +19 -0
  194. package/tests/setup/procedure/GetAllUsers.ts +9 -0
  195. package/tests/setup/procedure/GetUserById.ts +12 -0
  196. package/tests/setup/test-utils.ts +72 -0
  197. package/tests/setup/views/ActiveUsers.ts +8 -0
  198. package/tests/setup/views/UserSummary.ts +11 -0
  199. package/tests/types/nullable-queryable-record.spec.ts +145 -0
  200. package/tests/utils/result-parser-perf.spec.ts +210 -0
  201. package/tests/utils/result-parser.spec.ts +701 -0
  202. package/docs/expressions.md +0 -172
  203. package/docs/queries.md +0 -444
  204. package/docs/schema.md +0 -245
@@ -0,0 +1,618 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createTestDb } from "../setup/TestDbContext";
3
+ import { expr } from "../../src/expr/expr";
4
+ import { createQueryBuilder } from "../../src/query-builder/query-builder";
5
+ import { dialects } from "../setup/test-utils";
6
+ import "../setup/test-utils"; // toMatchSql matcher
7
+ import * as expected from "./window.expected";
8
+
9
+ describe("SELECT - Window Functions", () => {
10
+ describe("ROW_NUMBER: Department salary ranking", () => {
11
+ const db = createTestDb();
12
+ const def = db
13
+ .employee()
14
+ .select((e) => ({
15
+ id: e.id,
16
+ name: e.name,
17
+ departmentId: e.departmentId,
18
+ rowNum: expr.rowNumber({ partitionBy: [e.departmentId], orderBy: [[e.id, "ASC"]] }),
19
+ }))
20
+ .getSelectQueryDef();
21
+
22
+ it("Verify QueryDef", () => {
23
+ expect(def).toEqual({
24
+ type: "select",
25
+ as: "T1",
26
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
27
+ select: {
28
+ id: { type: "column", path: ["T1", "id"] },
29
+ name: { type: "column", path: ["T1", "name"] },
30
+ departmentId: { type: "column", path: ["T1", "departmentId"] },
31
+ rowNum: {
32
+ type: "window",
33
+ fn: { type: "rowNumber" },
34
+ spec: {
35
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
36
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
37
+ },
38
+ },
39
+ },
40
+ });
41
+ });
42
+
43
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
44
+ const builder = createQueryBuilder(dialect);
45
+ expect(builder.build(def)).toMatchSql(expected.rowNumber[dialect]);
46
+ });
47
+ });
48
+
49
+ describe("RANK: Overall score ranking (skip on tie)", () => {
50
+ const db = createTestDb();
51
+ const def = db
52
+ .employee()
53
+ .select((e) => ({
54
+ id: e.id,
55
+ name: e.name,
56
+ rank: expr.rank({ orderBy: [[e.id, "DESC"]] }),
57
+ }))
58
+ .getSelectQueryDef();
59
+
60
+ it("Verify QueryDef", () => {
61
+ expect(def).toEqual({
62
+ type: "select",
63
+ as: "T1",
64
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
65
+ select: {
66
+ id: { type: "column", path: ["T1", "id"] },
67
+ name: { type: "column", path: ["T1", "name"] },
68
+ rank: {
69
+ type: "window",
70
+ fn: { type: "rank" },
71
+ spec: {
72
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "DESC"]],
73
+ },
74
+ },
75
+ },
76
+ });
77
+ });
78
+
79
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
80
+ const builder = createQueryBuilder(dialect);
81
+ expect(builder.build(def)).toMatchSql(expected.rank[dialect]);
82
+ });
83
+ });
84
+
85
+ describe("DENSE_RANK: Overall score ranking (consecutive on tie)", () => {
86
+ const db = createTestDb();
87
+ const def = db
88
+ .employee()
89
+ .select((e) => ({
90
+ id: e.id,
91
+ denseRank: expr.denseRank({ orderBy: [[e.id, "DESC"]] }),
92
+ }))
93
+ .getSelectQueryDef();
94
+
95
+ it("Verify QueryDef", () => {
96
+ expect(def).toEqual({
97
+ type: "select",
98
+ as: "T1",
99
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
100
+ select: {
101
+ id: { type: "column", path: ["T1", "id"] },
102
+ denseRank: {
103
+ type: "window",
104
+ fn: { type: "denseRank" },
105
+ spec: {
106
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "DESC"]],
107
+ },
108
+ },
109
+ },
110
+ });
111
+ });
112
+
113
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
114
+ const builder = createQueryBuilder(dialect);
115
+ expect(builder.build(def)).toMatchSql(expected.denseRank[dialect]);
116
+ });
117
+ });
118
+
119
+ describe("NTILE: Divide into quartiles", () => {
120
+ const db = createTestDb();
121
+ const def = db
122
+ .employee()
123
+ .select((e) => ({
124
+ id: e.id,
125
+ quartile: expr.ntile(4, { orderBy: [[e.id, "DESC"]] }),
126
+ }))
127
+ .getSelectQueryDef();
128
+
129
+ it("Verify QueryDef", () => {
130
+ expect(def).toEqual({
131
+ type: "select",
132
+ as: "T1",
133
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
134
+ select: {
135
+ id: { type: "column", path: ["T1", "id"] },
136
+ quartile: {
137
+ type: "window",
138
+ fn: { type: "ntile", n: 4 },
139
+ spec: {
140
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "DESC"]],
141
+ },
142
+ },
143
+ },
144
+ });
145
+ });
146
+
147
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
148
+ const builder = createQueryBuilder(dialect);
149
+ expect(builder.build(def)).toMatchSql(expected.ntile[dialect]);
150
+ });
151
+ });
152
+
153
+ describe("LAG: Get previous row value", () => {
154
+ const db = createTestDb();
155
+ const def = db
156
+ .employee()
157
+ .select((e) => ({
158
+ id: e.id,
159
+ name: e.name,
160
+ prevId: expr.lag(
161
+ e.id,
162
+ { partitionBy: [e.departmentId], orderBy: [[e.id, "ASC"]] },
163
+ { offset: 1 },
164
+ ),
165
+ }))
166
+ .getSelectQueryDef();
167
+
168
+ it("Verify QueryDef", () => {
169
+ expect(def).toEqual({
170
+ type: "select",
171
+ as: "T1",
172
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
173
+ select: {
174
+ id: { type: "column", path: ["T1", "id"] },
175
+ name: { type: "column", path: ["T1", "name"] },
176
+ prevId: {
177
+ type: "window",
178
+ fn: {
179
+ type: "lag",
180
+ column: { type: "column", path: ["T1", "id"] },
181
+ offset: 1,
182
+ },
183
+ spec: {
184
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
185
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
186
+ },
187
+ },
188
+ },
189
+ });
190
+ });
191
+
192
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
193
+ const builder = createQueryBuilder(dialect);
194
+ expect(builder.build(def)).toMatchSql(expected.lag[dialect]);
195
+ });
196
+ });
197
+
198
+ describe("LEAD: Get next row value", () => {
199
+ const db = createTestDb();
200
+ const def = db
201
+ .employee()
202
+ .select((e) => ({
203
+ id: e.id,
204
+ nextId: expr.lead(e.id, { orderBy: [[e.id, "ASC"]] }, { offset: 1 }),
205
+ }))
206
+ .getSelectQueryDef();
207
+
208
+ it("Verify QueryDef", () => {
209
+ expect(def).toEqual({
210
+ type: "select",
211
+ as: "T1",
212
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
213
+ select: {
214
+ id: { type: "column", path: ["T1", "id"] },
215
+ nextId: {
216
+ type: "window",
217
+ fn: {
218
+ type: "lead",
219
+ column: { type: "column", path: ["T1", "id"] },
220
+ offset: 1,
221
+ },
222
+ spec: {
223
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
224
+ },
225
+ },
226
+ },
227
+ });
228
+ });
229
+
230
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
231
+ const builder = createQueryBuilder(dialect);
232
+ expect(builder.build(def)).toMatchSql(expected.lead[dialect]);
233
+ });
234
+ });
235
+
236
+ describe("LAG with default: Default value when no previous row", () => {
237
+ const db = createTestDb();
238
+ const def = db
239
+ .employee()
240
+ .select((e) => ({
241
+ id: e.id,
242
+ prevId: expr.lag(e.id, { orderBy: [[e.id, "ASC"]] }, { offset: 1, default: 0 }),
243
+ }))
244
+ .getSelectQueryDef();
245
+
246
+ it("Verify QueryDef", () => {
247
+ expect(def).toEqual({
248
+ type: "select",
249
+ as: "T1",
250
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
251
+ select: {
252
+ id: { type: "column", path: ["T1", "id"] },
253
+ prevId: {
254
+ type: "window",
255
+ fn: {
256
+ type: "lag",
257
+ column: { type: "column", path: ["T1", "id"] },
258
+ offset: 1,
259
+ default: { type: "value", value: 0 },
260
+ },
261
+ spec: {
262
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
263
+ },
264
+ },
265
+ },
266
+ });
267
+ });
268
+
269
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
270
+ const builder = createQueryBuilder(dialect);
271
+ expect(builder.build(def)).toMatchSql(expected.lagWithDefault[dialect]);
272
+ });
273
+ });
274
+
275
+ describe("LEAD with default: Default value when no next row", () => {
276
+ const db = createTestDb();
277
+ const def = db
278
+ .employee()
279
+ .select((e) => ({
280
+ id: e.id,
281
+ nextId: expr.lead(e.id, { orderBy: [[e.id, "ASC"]] }, { offset: 1, default: -1 }),
282
+ }))
283
+ .getSelectQueryDef();
284
+
285
+ it("Verify QueryDef", () => {
286
+ expect(def).toEqual({
287
+ type: "select",
288
+ as: "T1",
289
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
290
+ select: {
291
+ id: { type: "column", path: ["T1", "id"] },
292
+ nextId: {
293
+ type: "window",
294
+ fn: {
295
+ type: "lead",
296
+ column: { type: "column", path: ["T1", "id"] },
297
+ offset: 1,
298
+ default: { type: "value", value: -1 },
299
+ },
300
+ spec: {
301
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
302
+ },
303
+ },
304
+ },
305
+ });
306
+ });
307
+
308
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
309
+ const builder = createQueryBuilder(dialect);
310
+ expect(builder.build(def)).toMatchSql(expected.leadWithDefault[dialect]);
311
+ });
312
+ });
313
+
314
+ describe("SUM OVER: Cumulative sum", () => {
315
+ const db = createTestDb();
316
+ const def = db
317
+ .employee()
318
+ .select((e) => ({
319
+ id: e.id,
320
+ runningTotal: expr.sumOver(e.id, {
321
+ partitionBy: [e.departmentId],
322
+ orderBy: [[e.id, "ASC"]],
323
+ }),
324
+ }))
325
+ .getSelectQueryDef();
326
+
327
+ it("Verify QueryDef", () => {
328
+ expect(def).toEqual({
329
+ type: "select",
330
+ as: "T1",
331
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
332
+ select: {
333
+ id: { type: "column", path: ["T1", "id"] },
334
+ runningTotal: {
335
+ type: "window",
336
+ fn: {
337
+ type: "sum",
338
+ column: { type: "column", path: ["T1", "id"] },
339
+ },
340
+ spec: {
341
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
342
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
343
+ },
344
+ },
345
+ },
346
+ });
347
+ });
348
+
349
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
350
+ const builder = createQueryBuilder(dialect);
351
+ expect(builder.build(def)).toMatchSql(expected.sumOver[dialect]);
352
+ });
353
+ });
354
+
355
+ describe("AVG OVER: moving average", () => {
356
+ const db = createTestDb();
357
+ const def = db
358
+ .employee()
359
+ .select((e) => ({
360
+ id: e.id,
361
+ avgId: expr.avgOver(e.id, { partitionBy: [e.departmentId] }),
362
+ }))
363
+ .getSelectQueryDef();
364
+
365
+ it("Verify QueryDef", () => {
366
+ expect(def).toEqual({
367
+ type: "select",
368
+ as: "T1",
369
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
370
+ select: {
371
+ id: { type: "column", path: ["T1", "id"] },
372
+ avgId: {
373
+ type: "window",
374
+ fn: {
375
+ type: "avg",
376
+ column: { type: "column", path: ["T1", "id"] },
377
+ },
378
+ spec: {
379
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
380
+ },
381
+ },
382
+ },
383
+ });
384
+ });
385
+
386
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
387
+ const builder = createQueryBuilder(dialect);
388
+ expect(builder.build(def)).toMatchSql(expected.avgOver[dialect]);
389
+ });
390
+ });
391
+
392
+ describe("COUNT OVER: count within partition", () => {
393
+ const db = createTestDb();
394
+ const def = db
395
+ .employee()
396
+ .select((e) => ({
397
+ id: e.id,
398
+ deptCount: expr.countOver({ partitionBy: [e.departmentId] }),
399
+ }))
400
+ .getSelectQueryDef();
401
+
402
+ it("Verify QueryDef", () => {
403
+ expect(def).toEqual({
404
+ type: "select",
405
+ as: "T1",
406
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
407
+ select: {
408
+ id: { type: "column", path: ["T1", "id"] },
409
+ deptCount: {
410
+ type: "window",
411
+ fn: { type: "count" },
412
+ spec: {
413
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
414
+ },
415
+ },
416
+ },
417
+ });
418
+ });
419
+
420
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
421
+ const builder = createQueryBuilder(dialect);
422
+ expect(builder.build(def)).toMatchSql(expected.countOver[dialect]);
423
+ });
424
+ });
425
+
426
+ describe("FIRST_VALUE / LAST_VALUE", () => {
427
+ const db = createTestDb();
428
+ const def = db
429
+ .employee()
430
+ .select((e) => ({
431
+ id: e.id,
432
+ firstInDept: expr.firstValue(e.name, {
433
+ partitionBy: [e.departmentId],
434
+ orderBy: [[e.id, "ASC"]],
435
+ }),
436
+ lastInDept: expr.lastValue(e.name, {
437
+ partitionBy: [e.departmentId],
438
+ orderBy: [[e.id, "ASC"]],
439
+ }),
440
+ }))
441
+ .getSelectQueryDef();
442
+
443
+ it("Verify QueryDef", () => {
444
+ expect(def).toEqual({
445
+ type: "select",
446
+ as: "T1",
447
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
448
+ select: {
449
+ id: { type: "column", path: ["T1", "id"] },
450
+ firstInDept: {
451
+ type: "window",
452
+ fn: {
453
+ type: "firstValue",
454
+ column: { type: "column", path: ["T1", "name"] },
455
+ },
456
+ spec: {
457
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
458
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
459
+ },
460
+ },
461
+ lastInDept: {
462
+ type: "window",
463
+ fn: {
464
+ type: "lastValue",
465
+ column: { type: "column", path: ["T1", "name"] },
466
+ },
467
+ spec: {
468
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
469
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
470
+ },
471
+ },
472
+ },
473
+ });
474
+ });
475
+
476
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
477
+ const builder = createQueryBuilder(dialect);
478
+ expect(builder.build(def)).toMatchSql(expected.firstLastValue[dialect]);
479
+ });
480
+ });
481
+
482
+ describe("multiple window function combinations", () => {
483
+ const db = createTestDb();
484
+ const def = db
485
+ .employee()
486
+ .select((e) => ({
487
+ id: e.id,
488
+ name: e.name,
489
+ rowNum: expr.rowNumber({ partitionBy: [e.departmentId], orderBy: [[e.id, "ASC"]] }),
490
+ rank: expr.rank({ partitionBy: [e.departmentId], orderBy: [[e.id, "DESC"]] }),
491
+ prevName: expr.lag(
492
+ e.name,
493
+ { partitionBy: [e.departmentId], orderBy: [[e.id, "ASC"]] },
494
+ { offset: 1 },
495
+ ),
496
+ }))
497
+ .getSelectQueryDef();
498
+
499
+ it("Verify QueryDef", () => {
500
+ expect(def).toEqual({
501
+ type: "select",
502
+ as: "T1",
503
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
504
+ select: {
505
+ id: { type: "column", path: ["T1", "id"] },
506
+ name: { type: "column", path: ["T1", "name"] },
507
+ rowNum: {
508
+ type: "window",
509
+ fn: { type: "rowNumber" },
510
+ spec: {
511
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
512
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
513
+ },
514
+ },
515
+ rank: {
516
+ type: "window",
517
+ fn: { type: "rank" },
518
+ spec: {
519
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
520
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "DESC"]],
521
+ },
522
+ },
523
+ prevName: {
524
+ type: "window",
525
+ fn: {
526
+ type: "lag",
527
+ column: { type: "column", path: ["T1", "name"] },
528
+ offset: 1,
529
+ },
530
+ spec: {
531
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
532
+ orderBy: [[{ type: "column", path: ["T1", "id"] }, "ASC"]],
533
+ },
534
+ },
535
+ },
536
+ });
537
+ });
538
+
539
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
540
+ const builder = createQueryBuilder(dialect);
541
+ expect(builder.build(def)).toMatchSql(expected.combined[dialect]);
542
+ });
543
+ });
544
+
545
+ describe("MIN OVER: min ID per department", () => {
546
+ const db = createTestDb();
547
+ const def = db
548
+ .employee()
549
+ .select((e) => ({
550
+ id: e.id,
551
+ minId: expr.minOver(e.id, { partitionBy: [e.departmentId] }),
552
+ }))
553
+ .getSelectQueryDef();
554
+
555
+ it("Verify QueryDef", () => {
556
+ expect(def).toEqual({
557
+ type: "select",
558
+ as: "T1",
559
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
560
+ select: {
561
+ id: { type: "column", path: ["T1", "id"] },
562
+ minId: {
563
+ type: "window",
564
+ fn: {
565
+ type: "min",
566
+ column: { type: "column", path: ["T1", "id"] },
567
+ },
568
+ spec: {
569
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
570
+ },
571
+ },
572
+ },
573
+ });
574
+ });
575
+
576
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
577
+ const builder = createQueryBuilder(dialect);
578
+ expect(builder.build(def)).toMatchSql(expected.minOver[dialect]);
579
+ });
580
+ });
581
+
582
+ describe("MAX OVER: max ID per department", () => {
583
+ const db = createTestDb();
584
+ const def = db
585
+ .employee()
586
+ .select((e) => ({
587
+ id: e.id,
588
+ maxId: expr.maxOver(e.id, { partitionBy: [e.departmentId] }),
589
+ }))
590
+ .getSelectQueryDef();
591
+
592
+ it("Verify QueryDef", () => {
593
+ expect(def).toEqual({
594
+ type: "select",
595
+ as: "T1",
596
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
597
+ select: {
598
+ id: { type: "column", path: ["T1", "id"] },
599
+ maxId: {
600
+ type: "window",
601
+ fn: {
602
+ type: "max",
603
+ column: { type: "column", path: ["T1", "id"] },
604
+ },
605
+ spec: {
606
+ partitionBy: [{ type: "column", path: ["T1", "departmentId"] }],
607
+ },
608
+ },
609
+ },
610
+ });
611
+ });
612
+
613
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
614
+ const builder = createQueryBuilder(dialect);
615
+ expect(builder.build(def)).toMatchSql(expected.maxOver[dialect]);
616
+ });
617
+ });
618
+ });
@@ -0,0 +1,18 @@
1
+ import type { DbContextExecutor, ResultMeta } from "../../src/types/db";
2
+ import type { QueryDef } from "../../src/types/query-def";
3
+
4
+ export class MockExecutor implements DbContextExecutor {
5
+ // 연결 관리 (Mock - 아무것도 안 함)
6
+ async connect(): Promise<void> {}
7
+ async close(): Promise<void> {}
8
+
9
+ // 트랜잭션 (Mock - 아무것도 안 함)
10
+ async beginTransaction(): Promise<void> {}
11
+ async commitTransaction(): Promise<void> {}
12
+ async rollbackTransaction(): Promise<void> {}
13
+
14
+ // 쿼리 실행
15
+ executeDefs(_defs: QueryDef[], _resultMetas?: (ResultMeta | undefined)[]): Promise<any[][]> {
16
+ return Promise.resolve([[]]);
17
+ }
18
+ }
@@ -0,0 +1,59 @@
1
+ import "@simplysm/core-common";
2
+ import type { DbContextInstance } from "../../src/types/db-context-def";
3
+ import { defineDbContext } from "../../src/define-db-context";
4
+ import { createDbContext } from "../../src/create-db-context";
5
+ import { Post } from "./models/Post";
6
+ import { Company } from "./models/Company";
7
+ import { Sales } from "./models/Sales";
8
+ import { MonthlySales } from "./models/MonthlySales";
9
+ import { Employee } from "./models/Employee";
10
+ import { ActiveUsers } from "./views/ActiveUsers";
11
+ import { UserSummary } from "./views/UserSummary";
12
+ import { MockExecutor } from "./MockExecutor";
13
+ import { User } from "./models/User";
14
+ import { GetUserById } from "./procedure/GetUserById";
15
+ import { GetAllUsers } from "./procedure/GetAllUsers";
16
+
17
+ // Tables-only definition (used by view definitions to break circular reference)
18
+ // eslint-disable-next-line unused-imports/no-unused-vars -- used in typeof for TestDbTablesContext type
19
+ const TestDbTablesDef = defineDbContext({
20
+ tables: {
21
+ company: Company,
22
+ user: User,
23
+ post: Post,
24
+ sales: Sales,
25
+ monthlySales: MonthlySales,
26
+ employee: Employee,
27
+ },
28
+ });
29
+
30
+ /** Type for view definitions — references tables only, avoids circular dependency */
31
+ export type TestDbTablesContext = DbContextInstance<typeof TestDbTablesDef>;
32
+
33
+ export const TestDbDef = defineDbContext({
34
+ tables: {
35
+ company: Company,
36
+ user: User,
37
+ post: Post,
38
+ sales: Sales,
39
+ monthlySales: MonthlySales,
40
+ employee: Employee,
41
+ },
42
+ views: {
43
+ activeUsers: ActiveUsers,
44
+ userSummary: UserSummary,
45
+ },
46
+ procedures: {
47
+ getUserById: GetUserById,
48
+ getAllUsers: GetAllUsers,
49
+ },
50
+ });
51
+
52
+ export function createTestDb() {
53
+ return createDbContext(TestDbDef, new MockExecutor(), {
54
+ database: "TestDb",
55
+ schema: "TestSchema",
56
+ });
57
+ }
58
+
59
+ export type TestDbContext = ReturnType<typeof createTestDb>;
@@ -0,0 +1,13 @@
1
+ import { Table } from "../../../src/schema/table-builder";
2
+ import { User } from "./User";
3
+
4
+ export const Company = Table("Company")
5
+ .columns((c) => ({
6
+ id: c.bigint().autoIncrement(),
7
+ name: c.varchar(200),
8
+ foundedAt: c.date().nullable(),
9
+ }))
10
+ .primaryKey("id")
11
+ .relations((r) => ({
12
+ users: r.foreignKeyTarget(() => User, "company"),
13
+ }));