@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.
- package/README.md +54 -1447
- package/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +9 -9
- package/dist/create-db-context.js.map +1 -1
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +2 -2
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/table-ddl.d.ts +24 -24
- package/dist/ddl/table-ddl.js +4 -4
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +3 -3
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.d.ts +160 -160
- package/dist/exec/queryable.js +119 -119
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.d.ts +37 -37
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr.d.ts +257 -257
- package/dist/expr/expr.js +265 -265
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
- package/dist/query-builder/base/expr-renderer-base.js +2 -2
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
- package/dist/query-builder/base/query-builder-base.d.ts +26 -26
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +22 -22
- package/dist/query-builder/base/query-builder-base.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +18 -18
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +17 -17
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +17 -17
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +79 -79
- package/dist/schema/factory/column-builder.js +42 -42
- package/dist/schema/factory/index-builder.d.ts +39 -39
- package/dist/schema/factory/index-builder.js +26 -26
- package/dist/schema/factory/relation-builder.d.ts +99 -99
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +38 -38
- package/dist/schema/procedure-builder.d.ts +49 -49
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +33 -33
- package/dist/schema/table-builder.d.ts +59 -59
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +43 -43
- package/dist/schema/view-builder.d.ts +49 -49
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +32 -32
- package/dist/types/column.d.ts +22 -22
- package/dist/types/column.js +1 -1
- package/dist/types/column.js.map +1 -1
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +59 -59
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +44 -44
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/utils/result-parser.d.ts +11 -11
- package/dist/utils/result-parser.js +3 -3
- package/dist/utils/result-parser.js.map +1 -1
- package/package.json +5 -5
- package/src/create-db-context.ts +20 -20
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +259 -259
- package/src/ddl/relation-ddl.ts +89 -89
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +189 -189
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +2033 -2033
- package/src/exec/search-parser.ts +57 -57
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +2140 -2140
- package/src/query-builder/base/expr-renderer-base.ts +237 -237
- package/src/query-builder/base/query-builder-base.ts +213 -213
- package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
- package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
- package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
- package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
- package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
- package/src/query-builder/query-builder.ts +19 -19
- package/src/schema/factory/column-builder.ts +423 -423
- package/src/schema/factory/index-builder.ts +164 -164
- package/src/schema/factory/relation-builder.ts +453 -453
- package/src/schema/procedure-builder.ts +232 -232
- package/src/schema/table-builder.ts +319 -319
- package/src/schema/view-builder.ts +221 -221
- package/src/types/column.ts +188 -188
- package/src/types/db.ts +208 -208
- package/src/types/expr.ts +697 -697
- package/src/types/query-def.ts +513 -513
- package/src/utils/result-parser.ts +458 -458
- package/tests/db-context/create-db-context.spec.ts +224 -0
- package/tests/db-context/define-db-context.spec.ts +68 -0
- package/tests/ddl/basic.expected.ts +341 -0
- package/tests/ddl/basic.spec.ts +714 -0
- package/tests/ddl/column-builder.expected.ts +310 -0
- package/tests/ddl/column-builder.spec.ts +637 -0
- package/tests/ddl/index-builder.expected.ts +38 -0
- package/tests/ddl/index-builder.spec.ts +202 -0
- package/tests/ddl/procedure-builder.expected.ts +52 -0
- package/tests/ddl/procedure-builder.spec.ts +234 -0
- package/tests/ddl/relation-builder.expected.ts +36 -0
- package/tests/ddl/relation-builder.spec.ts +372 -0
- package/tests/ddl/table-builder.expected.ts +113 -0
- package/tests/ddl/table-builder.spec.ts +433 -0
- package/tests/ddl/view-builder.expected.ts +38 -0
- package/tests/ddl/view-builder.spec.ts +176 -0
- package/tests/dml/delete.expected.ts +96 -0
- package/tests/dml/delete.spec.ts +160 -0
- package/tests/dml/insert.expected.ts +192 -0
- package/tests/dml/insert.spec.ts +288 -0
- package/tests/dml/update.expected.ts +176 -0
- package/tests/dml/update.spec.ts +318 -0
- package/tests/dml/upsert.expected.ts +215 -0
- package/tests/dml/upsert.spec.ts +242 -0
- package/tests/errors/queryable-errors.spec.ts +177 -0
- package/tests/escape.spec.ts +100 -0
- package/tests/examples/pivot.expected.ts +211 -0
- package/tests/examples/pivot.spec.ts +533 -0
- package/tests/examples/sampling.expected.ts +69 -0
- package/tests/examples/sampling.spec.ts +105 -0
- package/tests/examples/unpivot.expected.ts +120 -0
- package/tests/examples/unpivot.spec.ts +226 -0
- package/tests/exec/search-parser.spec.ts +283 -0
- package/tests/executable/basic.expected.ts +18 -0
- package/tests/executable/basic.spec.ts +54 -0
- package/tests/expr/comparison.expected.ts +282 -0
- package/tests/expr/comparison.spec.ts +400 -0
- package/tests/expr/conditional.expected.ts +134 -0
- package/tests/expr/conditional.spec.ts +276 -0
- package/tests/expr/date.expected.ts +332 -0
- package/tests/expr/date.spec.ts +526 -0
- package/tests/expr/math.expected.ts +62 -0
- package/tests/expr/math.spec.ts +106 -0
- package/tests/expr/string.expected.ts +218 -0
- package/tests/expr/string.spec.ts +356 -0
- package/tests/expr/utility.expected.ts +147 -0
- package/tests/expr/utility.spec.ts +182 -0
- package/tests/select/basic.expected.ts +322 -0
- package/tests/select/basic.spec.ts +502 -0
- package/tests/select/filter.expected.ts +357 -0
- package/tests/select/filter.spec.ts +1068 -0
- package/tests/select/group.expected.ts +169 -0
- package/tests/select/group.spec.ts +244 -0
- package/tests/select/join.expected.ts +582 -0
- package/tests/select/join.spec.ts +805 -0
- package/tests/select/order.expected.ts +150 -0
- package/tests/select/order.spec.ts +189 -0
- package/tests/select/recursive-cte.expected.ts +244 -0
- package/tests/select/recursive-cte.spec.ts +514 -0
- package/tests/select/result-meta.spec.ts +270 -0
- package/tests/select/subquery.expected.ts +363 -0
- package/tests/select/subquery.spec.ts +537 -0
- package/tests/select/view.expected.ts +155 -0
- package/tests/select/view.spec.ts +235 -0
- package/tests/select/window.expected.ts +345 -0
- package/tests/select/window.spec.ts +618 -0
- package/tests/setup/MockExecutor.ts +18 -0
- package/tests/setup/TestDbContext.ts +59 -0
- package/tests/setup/models/Company.ts +13 -0
- package/tests/setup/models/Employee.ts +10 -0
- package/tests/setup/models/MonthlySales.ts +11 -0
- package/tests/setup/models/Post.ts +16 -0
- package/tests/setup/models/Sales.ts +10 -0
- package/tests/setup/models/User.ts +19 -0
- package/tests/setup/procedure/GetAllUsers.ts +9 -0
- package/tests/setup/procedure/GetUserById.ts +12 -0
- package/tests/setup/test-utils.ts +72 -0
- package/tests/setup/views/ActiveUsers.ts +8 -0
- package/tests/setup/views/UserSummary.ts +11 -0
- package/tests/types/nullable-queryable-record.spec.ts +145 -0
- package/tests/utils/result-parser-perf.spec.ts +210 -0
- package/tests/utils/result-parser.spec.ts +701 -0
- package/docs/expressions.md +0 -172
- package/docs/queries.md +0 -444
- 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
|
+
});
|