@simplysm/orm-common 13.0.69 → 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.
- 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 +104 -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 - 파라미터 없는 프로시저에 파라미터 전달 테스트
|
|
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("백슬래시와 따옴표 조합을 방어해야 함", () => {
|
|
54
|
+
const malicious = "\\'";
|
|
55
|
+
const result = renderer.escapeString(malicious);
|
|
56
|
+
expect(result).toBe("\\\\''");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("NULL 바이트와 SQL 주석 조합을 방어해야 함", () => {
|
|
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("문자열을 escapeString()으로 이스케이프하고 따옴표로 감싸야 함", () => {
|
|
72
|
+
const result = renderer.escapeValue("O'Reilly");
|
|
73
|
+
expect(result).toBe("'O''Reilly'");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("백슬래시가 포함된 문자열을 올바르게 이스케이프해야 함", () => {
|
|
77
|
+
const result = renderer.escapeValue("C:\\path");
|
|
78
|
+
expect(result).toBe("'C:\\\\path'");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("SQL 인젝션 시도를 방어해야 함", () => {
|
|
82
|
+
const result = renderer.escapeValue("'; DROP TABLE users; --");
|
|
83
|
+
expect(result).toBe("'''; DROP TABLE users; --'");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("NULL을 'NULL' 문자열로 반환해야 함", () => {
|
|
87
|
+
const result = renderer.escapeValue(null);
|
|
88
|
+
expect(result).toBe("NULL");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("Number를 문자열로 conversion해야 함", () => {
|
|
92
|
+
const result = renderer.escapeValue(123);
|
|
93
|
+
expect(result).toBe("123");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("불리언을 TRUE/FALSE로 conversion해야 함", () => {
|
|
97
|
+
expect(renderer.escapeValue(true)).toBe("TRUE");
|
|
98
|
+
expect(renderer.escapeValue(false)).toBe("FALSE");
|
|
99
|
+
});
|
|
100
|
+
});
|