@simplysm/orm-common 13.0.69 → 13.0.71

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 +105 -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,242 @@
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 "./upsert.expected";
8
+
9
+ describe("UPSERT - Basic", () => {
10
+ describe("simple UPSERT (WHERE condition)", () => {
11
+ const db = createTestDb();
12
+ const def = db
13
+ .employee()
14
+ .where((e) => [expr.eq(e.id, 1)])
15
+ .getUpsertQueryDef(
16
+ () => ({ name: expr.val("string", "new name") }),
17
+ (upd) => ({ name: upd.name, departmentId: expr.val("number", 1) }),
18
+ );
19
+
20
+ it("should validate QueryDef", () => {
21
+ expect(def).toEqual({
22
+ type: "upsert",
23
+ table: { database: "TestDb", schema: "TestSchema", name: "Employee" },
24
+ existsSelectQuery: {
25
+ type: "select",
26
+ as: "T1",
27
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
28
+ where: [
29
+ {
30
+ type: "eq",
31
+ source: { type: "column", path: ["T1", "id"] },
32
+ target: { type: "value", value: 1 },
33
+ },
34
+ ],
35
+ },
36
+ updateRecord: {
37
+ name: { type: "value", value: "new name" },
38
+ },
39
+ insertRecord: {
40
+ name: { type: "value", value: "new name" },
41
+ departmentId: { type: "value", value: 1 },
42
+ },
43
+ });
44
+ });
45
+
46
+ it.each(dialects)("[%s] should validate SQL", (dialect) => {
47
+ const builder = createQueryBuilder(dialect);
48
+ expect(builder.build(def)).toMatchSql(expected.upsertSimple[dialect]);
49
+ });
50
+ });
51
+
52
+ describe("INSERT reusing UPDATE values", () => {
53
+ const db = createTestDb();
54
+ const def = db
55
+ .employee()
56
+ .where((e) => [expr.eq(e.id, 1)])
57
+ .getUpsertQueryDef(
58
+ () => ({
59
+ name: expr.val("string", "Gildong Hong"),
60
+ departmentId: expr.val("number", 2),
61
+ }),
62
+ (upd) => ({
63
+ ...upd,
64
+ managerId: expr.val("number", 100),
65
+ }),
66
+ );
67
+
68
+ it("Verify QueryDef", () => {
69
+ expect(def).toEqual({
70
+ type: "upsert",
71
+ table: { database: "TestDb", schema: "TestSchema", name: "Employee" },
72
+ existsSelectQuery: {
73
+ type: "select",
74
+ as: "T1",
75
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
76
+ where: [
77
+ {
78
+ type: "eq",
79
+ source: { type: "column", path: ["T1", "id"] },
80
+ target: { type: "value", value: 1 },
81
+ },
82
+ ],
83
+ },
84
+ updateRecord: {
85
+ name: { type: "value", value: "Gildong Hong" },
86
+ departmentId: { type: "value", value: 2 },
87
+ },
88
+ insertRecord: {
89
+ name: { type: "value", value: "Gildong Hong" },
90
+ departmentId: { type: "value", value: 2 },
91
+ managerId: { type: "value", value: 100 },
92
+ },
93
+ });
94
+ });
95
+
96
+ it.each(dialects)("[%s] should validate SQL", (dialect) => {
97
+ const builder = createQueryBuilder(dialect);
98
+ expect(builder.build(def)).toMatchSql(expected.upsertReuse[dialect]);
99
+ });
100
+ });
101
+
102
+ describe("Specify output column", () => {
103
+ const db = createTestDb();
104
+ const def = db
105
+ .employee()
106
+ .where((e) => [expr.eq(e.id, 1)])
107
+ .getUpsertQueryDef(
108
+ () => ({ name: expr.val("string", "New Name") }),
109
+ (upd) => ({ name: upd.name }),
110
+ ["id", "name"],
111
+ );
112
+
113
+ it("Verify QueryDef", () => {
114
+ expect(def).toEqual({
115
+ type: "upsert",
116
+ table: { database: "TestDb", schema: "TestSchema", name: "Employee" },
117
+ existsSelectQuery: {
118
+ type: "select",
119
+ as: "T1",
120
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
121
+ where: [
122
+ {
123
+ type: "eq",
124
+ source: { type: "column", path: ["T1", "id"] },
125
+ target: { type: "value", value: 1 },
126
+ },
127
+ ],
128
+ },
129
+ updateRecord: {
130
+ name: { type: "value", value: "New Name" },
131
+ },
132
+ insertRecord: {
133
+ name: { type: "value", value: "New Name" },
134
+ },
135
+ output: {
136
+ columns: ["id", "name"],
137
+ pkColNames: ["id"],
138
+ aiColName: "id",
139
+ },
140
+ });
141
+ });
142
+
143
+ it.each(dialects)("[%s] should validate SQL", (dialect) => {
144
+ const builder = createQueryBuilder(dialect);
145
+ expect(builder.build(def)).toMatchSql(expected.upsertWithOutput[dialect]);
146
+ });
147
+ });
148
+
149
+ describe("Complex WHERE condition", () => {
150
+ const db = createTestDb();
151
+ const def = db
152
+ .employee()
153
+ .where((e) => [expr.eq(e.name, "Gildong Hong"), expr.eq(e.departmentId, 1)])
154
+ .getUpsertQueryDef(
155
+ () => ({ managerId: expr.val("number", 10) }),
156
+ (upd) => ({
157
+ name: expr.val("string", "Gildong Hong"),
158
+ departmentId: expr.val("number", 1),
159
+ managerId: upd.managerId,
160
+ }),
161
+ );
162
+
163
+ it("Verify QueryDef", () => {
164
+ expect(def).toEqual({
165
+ type: "upsert",
166
+ table: { database: "TestDb", schema: "TestSchema", name: "Employee" },
167
+ existsSelectQuery: {
168
+ type: "select",
169
+ as: "T1",
170
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
171
+ where: [
172
+ {
173
+ type: "eq",
174
+ source: { type: "column", path: ["T1", "name"] },
175
+ target: { type: "value", value: "Gildong Hong" },
176
+ },
177
+ {
178
+ type: "eq",
179
+ source: { type: "column", path: ["T1", "departmentId"] },
180
+ target: { type: "value", value: 1 },
181
+ },
182
+ ],
183
+ },
184
+ updateRecord: {
185
+ managerId: { type: "value", value: 10 },
186
+ },
187
+ insertRecord: {
188
+ name: { type: "value", value: "Gildong Hong" },
189
+ departmentId: { type: "value", value: 1 },
190
+ managerId: { type: "value", value: 10 },
191
+ },
192
+ });
193
+ });
194
+
195
+ it.each(dialects)("[%s] should validate SQL", (dialect) => {
196
+ const builder = createQueryBuilder(dialect);
197
+ expect(builder.build(def)).toMatchSql(expected.upsertMultiWhere[dialect]);
198
+ });
199
+ });
200
+
201
+ describe("UPSERT with literal values (without expr.val)", () => {
202
+ const db = createTestDb();
203
+ const def = db
204
+ .employee()
205
+ .where((e) => [expr.eq(e.id, 1)])
206
+ .getUpsertQueryDef(
207
+ () => ({ name: "New Name" }),
208
+ (upd) => ({ name: upd.name, departmentId: 1 }),
209
+ );
210
+
211
+ it("Verify QueryDef", () => {
212
+ expect(def).toEqual({
213
+ type: "upsert",
214
+ table: { database: "TestDb", schema: "TestSchema", name: "Employee" },
215
+ existsSelectQuery: {
216
+ type: "select",
217
+ as: "T1",
218
+ from: { database: "TestDb", schema: "TestSchema", name: "Employee" },
219
+ where: [
220
+ {
221
+ type: "eq",
222
+ source: { type: "column", path: ["T1", "id"] },
223
+ target: { type: "value", value: 1 },
224
+ },
225
+ ],
226
+ },
227
+ updateRecord: {
228
+ name: { type: "value", value: "New Name" },
229
+ },
230
+ insertRecord: {
231
+ name: { type: "value", value: "New Name" },
232
+ departmentId: { type: "value", value: 1 },
233
+ },
234
+ });
235
+ });
236
+
237
+ it.each(dialects)("[%s] should validate SQL", (dialect) => {
238
+ const builder = createQueryBuilder(dialect);
239
+ expect(builder.build(def)).toMatchSql(expected.upsertPlainValues[dialect]);
240
+ });
241
+ });
242
+ });
@@ -0,0 +1,177 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createTestDb } from "../setup/TestDbContext";
3
+ import { Queryable } from "../../src/exec/queryable";
4
+ import { createQueryBuilder } from "../../src/query-builder/query-builder";
5
+ import { expr } from "../../src/expr/expr";
6
+
7
+ describe("Queryable error cases", () => {
8
+ describe("expr.and()/expr.or() errors", () => {
9
+ it("ArgumentError when calling and() with empty array", () => {
10
+ expect(() => expr.and([])).toThrow("empty arrays are not allowed");
11
+ });
12
+
13
+ it("ArgumentError when calling or() with empty array", () => {
14
+ expect(() => expr.or([])).toThrow("empty arrays are not allowed");
15
+ });
16
+ });
17
+
18
+ describe("executable errors", () => {
19
+ it("Error when passing parameters to procedure without parameters", () => {
20
+ const db = createTestDb();
21
+ expect(() => {
22
+ // @ts-expect-error - test passing parameters to a procedure that expects none
23
+ db.getAllUsers().getExecProcQueryDef({ unexpectedParam: 1 });
24
+ }).toThrow("has no parameters.");
25
+ });
26
+ });
27
+
28
+ describe("include() errors", () => {
29
+ it("Error when including non-existent relation", () => {
30
+ const db = createTestDb();
31
+
32
+ expect(() => {
33
+ // @ts-expect-error - non-existent relation test
34
+ db.user().include((item) => item.nonExistentRelation);
35
+ }).toThrow("Relation 'nonExistentRelation' not found.");
36
+ });
37
+
38
+ it("Error when calling include on ViewBuilder-based queryable", () => {
39
+ const db = createTestDb();
40
+
41
+ expect(() => {
42
+ // @ts-expect-error - ViewBuilder has no relations, include not supported
43
+ db.activeUsers().include((item) => item.someRelation);
44
+ }).toThrow("include() can only be used on TableBuilder-based queryables.");
45
+ });
46
+ });
47
+
48
+ describe("union() errors", () => {
49
+ it("Error when calling union with a single queryable", () => {
50
+ const db = createTestDb();
51
+
52
+ expect(() => {
53
+ Queryable.union(db.user());
54
+ }).toThrow("union requires at least 2 queryables.");
55
+ });
56
+ });
57
+
58
+ describe("limit() errors", () => {
59
+ it("Error when calling limit without ORDER BY", () => {
60
+ const db = createTestDb();
61
+
62
+ expect(() => {
63
+ db.user().limit(0, 10);
64
+ }).toThrow("limit() requires ORDER BY clause.");
65
+ });
66
+ });
67
+
68
+ describe("regexp() errors", () => {
69
+ it("Error when using regexp in MSSQL", () => {
70
+ const db = createTestDb();
71
+ const def = db
72
+ .user()
73
+ .where((item) => [expr.regexp(item.name, "^test.*")])
74
+ .getSelectQueryDef();
75
+
76
+ const builder = createQueryBuilder("mssql");
77
+ expect(() => {
78
+ builder.build(def);
79
+ }).toThrow("MSSQL does not natively support REGEXP.");
80
+ });
81
+ });
82
+
83
+ describe("inQuery() errors", () => {
84
+ it("Error when using multi-column subquery", () => {
85
+ const db = createTestDb();
86
+
87
+ expect(() => {
88
+ db.user()
89
+ .where((u) => [
90
+ expr.inQuery(
91
+ u.id,
92
+ // @ts-expect-error - multi-column subquery
93
+ db.post().select((p) => ({ userId: p.userId, title: p.title })),
94
+ ),
95
+ ])
96
+ .getSelectQueryDef();
97
+ }).toThrow("inQuery subquery must SELECT only a single column.");
98
+ });
99
+
100
+ it("Error when using subquery without column specification", () => {
101
+ const db = createTestDb();
102
+
103
+ expect(() => {
104
+ db.user()
105
+ .where((u) => [
106
+ expr.inQuery(
107
+ u.id,
108
+ // @ts-expect-error - subquery without SELECT
109
+ db.post(),
110
+ ),
111
+ ])
112
+ .getSelectQueryDef();
113
+ }).toThrow("inQuery subquery must SELECT only a single column.");
114
+ });
115
+ });
116
+
117
+ describe("countAsync() errors", () => {
118
+ it("Error when calling directly after distinct()", async () => {
119
+ const db = createTestDb();
120
+
121
+ await expect(
122
+ db
123
+ .user()
124
+ .select((u) => ({ name: u.name }))
125
+ .distinct()
126
+ .count(),
127
+ ).rejects.toThrow(
128
+ "Cannot use count() after distinct(). Use wrap() first.",
129
+ );
130
+ });
131
+
132
+ it("Error when calling directly after groupBy()", async () => {
133
+ const db = createTestDb();
134
+
135
+ await expect(
136
+ db
137
+ .user()
138
+ .groupBy((u) => [u.name])
139
+ .count(),
140
+ ).rejects.toThrow("Cannot use count() after groupBy(). Use wrap() first.");
141
+ });
142
+ });
143
+
144
+ describe("RecursiveQueryable.union() errors", () => {
145
+ it("Error when calling union with a single queryable", () => {
146
+ const db = createTestDb();
147
+
148
+ expect(() => {
149
+ db.employee()
150
+ .where((e) => [expr.null(e.managerId)])
151
+ .recursive((cte) => cte.union(db.employee()));
152
+ }).toThrow("union requires at least 2 queryables.");
153
+ });
154
+
155
+ it("Error when calling union with zero queryables", () => {
156
+ const db = createTestDb();
157
+
158
+ expect(() => {
159
+ db.employee()
160
+ .where((e) => [expr.null(e.managerId)])
161
+ .recursive((cte) => cte.union());
162
+ }).toThrow("union requires at least 2 queryables.");
163
+ });
164
+ });
165
+
166
+ describe("JoinQueryable.union() errors", () => {
167
+ it("Error when calling union with a single queryable inside join", () => {
168
+ const db = createTestDb();
169
+
170
+ expect(() => {
171
+ db.user().join("posts", (j, u) =>
172
+ j.union(db.post().where((p) => [expr.eq(p.userId, u.id)])),
173
+ );
174
+ }).toThrow("union requires at least 2 queryables.");
175
+ });
176
+ });
177
+ });
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { MysqlExprRenderer } from "../src/query-builder/mysql/mysql-expr-renderer";
3
+
4
+ describe("MysqlExprRenderer.escapeString", () => {
5
+ const renderer = new MysqlExprRenderer(() => "");
6
+
7
+ //#region ========== Basic Escaping ==========
8
+
9
+ it("should escape quotes", () => {
10
+ const result = renderer.escapeString("O'Reilly");
11
+ expect(result).toBe("O''Reilly");
12
+ });
13
+
14
+ it("should escape backslashes", () => {
15
+ const result = renderer.escapeString("C:\\path");
16
+ expect(result).toBe("C:\\\\path");
17
+ });
18
+
19
+ it("should escape NULL bytes", () => {
20
+ const result = renderer.escapeString("admin\0--");
21
+ expect(result).toBe("admin\\0--");
22
+ });
23
+
24
+ //#endregion
25
+
26
+ //#region ========== Control Character Escaping ==========
27
+
28
+ it("should escape newlines", () => {
29
+ const result = renderer.escapeString("line1\nline2");
30
+ expect(result).toBe("line1\\nline2");
31
+ });
32
+
33
+ it("should escape carriage returns", () => {
34
+ const result = renderer.escapeString("line1\rline2");
35
+ expect(result).toBe("line1\\rline2");
36
+ });
37
+
38
+ it("should escape tabs", () => {
39
+ const result = renderer.escapeString("col1\tcol2");
40
+ expect(result).toBe("col1\\tcol2");
41
+ });
42
+
43
+ //#endregion
44
+
45
+ //#region ========== Combined Attack Test ==========
46
+
47
+ it("should defend against SQL injection attempts", () => {
48
+ const malicious = "'; DROP TABLE users; --";
49
+ const result = renderer.escapeString(malicious);
50
+ expect(result).toBe("''; DROP TABLE users; --");
51
+ });
52
+
53
+ it("should defend against backslash + quote combination", () => {
54
+ const malicious = "\\'";
55
+ const result = renderer.escapeString(malicious);
56
+ expect(result).toBe("\\\\''");
57
+ });
58
+
59
+ it("should defend against NULL byte + SQL comment combination", () => {
60
+ const malicious = "admin\0-- ";
61
+ const result = renderer.escapeString(malicious);
62
+ expect(result).toBe("admin\\0-- ");
63
+ });
64
+
65
+ //#endregion
66
+ });
67
+
68
+ describe("MysqlExprRenderer.escapeValue", () => {
69
+ const renderer = new MysqlExprRenderer(() => "");
70
+
71
+ it("escapes string with escapeString() and wraps in quotes", () => {
72
+ const result = renderer.escapeValue("O'Reilly");
73
+ expect(result).toBe("'O''Reilly'");
74
+ });
75
+
76
+ it("correctly escapes string containing backslashes", () => {
77
+ const result = renderer.escapeValue("C:\\path");
78
+ expect(result).toBe("'C:\\\\path'");
79
+ });
80
+
81
+ it("defends against SQL injection attempts", () => {
82
+ const result = renderer.escapeValue("'; DROP TABLE users; --");
83
+ expect(result).toBe("'''; DROP TABLE users; --'");
84
+ });
85
+
86
+ it("returns 'NULL' string for null", () => {
87
+ const result = renderer.escapeValue(null);
88
+ expect(result).toBe("NULL");
89
+ });
90
+
91
+ it("converts number to string", () => {
92
+ const result = renderer.escapeValue(123);
93
+ expect(result).toBe("123");
94
+ });
95
+
96
+ it("converts boolean to TRUE/FALSE", () => {
97
+ expect(renderer.escapeValue(true)).toBe("TRUE");
98
+ expect(renderer.escapeValue(false)).toBe("FALSE");
99
+ });
100
+ });