@simplysm/orm-common 13.0.70 → 13.0.72
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/package.json +2 -2
- package/tests/ddl/column-builder.expected.ts +2 -2
- package/tests/dml/update.expected.ts +1 -1
- package/tests/dml/update.spec.ts +1 -1
- package/tests/errors/queryable-errors.spec.ts +1 -1
- package/tests/escape.spec.ts +8 -8
- package/tests/examples/pivot.expected.ts +2 -2
- package/tests/examples/sampling.expected.ts +1 -1
- package/tests/examples/sampling.spec.ts +4 -3
- package/tests/expr/comparison.expected.ts +3 -3
- package/tests/expr/comparison.spec.ts +2 -2
- package/tests/expr/conditional.spec.ts +2 -2
- package/tests/expr/utility.expected.ts +1 -1
- package/tests/select/basic.expected.ts +3 -3
- package/tests/select/filter.expected.ts +3 -3
- package/tests/select/filter.spec.ts +15 -15
- package/tests/select/join.spec.ts +3 -3
- package/tests/select/order.expected.ts +3 -3
- package/tests/select/order.spec.ts +3 -3
- package/tests/select/recursive-cte.expected.ts +2 -2
- package/tests/select/subquery.expected.ts +2 -2
- package/tests/select/view.expected.ts +1 -1
- package/tests/setup/MockExecutor.ts +3 -3
- package/tests/setup/models/Employee.ts +1 -1
- package/tests/setup/views/ActiveUsers.ts +1 -1
- package/tests/setup/views/UserSummary.ts +1 -1
- package/tests/utils/result-parser-perf.spec.ts +19 -19
- package/tests/utils/result-parser.spec.ts +34 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/orm-common",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.72",
|
|
4
4
|
"description": "Simplysm Package - ORM Module (common)",
|
|
5
5
|
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -19,6 +19,6 @@
|
|
|
19
19
|
],
|
|
20
20
|
"sideEffects": false,
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@simplysm/core-common": "13.0.
|
|
22
|
+
"@simplysm/core-common": "13.0.72"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mysql, pgsql, tsql } from "@simplysm/core-common";
|
|
2
2
|
import type { ExpectedSql } from "../setup/test-utils";
|
|
3
3
|
|
|
4
|
-
//#region ==========
|
|
4
|
+
//#region ========== Data Type Basic Tests ==========
|
|
5
5
|
|
|
6
6
|
export const intType: ExpectedSql = {
|
|
7
7
|
mysql: mysql`
|
|
@@ -215,7 +215,7 @@ export const uuidType: ExpectedSql = {
|
|
|
215
215
|
|
|
216
216
|
//#endregion
|
|
217
217
|
|
|
218
|
-
//#region ==========
|
|
218
|
+
//#region ========== Method Combination Tests ==========
|
|
219
219
|
|
|
220
220
|
export const nullableColumn: ExpectedSql = {
|
|
221
221
|
mysql: mysql`
|
package/tests/dml/update.spec.ts
CHANGED
|
@@ -160,7 +160,7 @@ describe("UPDATE - Basic", () => {
|
|
|
160
160
|
.employee()
|
|
161
161
|
.where((e) => [expr.eq(e.id, 1)])
|
|
162
162
|
.getUpdateQueryDef((e) => ({
|
|
163
|
-
// managerId = managerId + 1 (
|
|
163
|
+
// managerId = managerId + 1 (example)
|
|
164
164
|
managerId: expr.raw("number")`${e.managerId} + 1`,
|
|
165
165
|
}));
|
|
166
166
|
|
|
@@ -19,7 +19,7 @@ describe("Queryable error cases", () => {
|
|
|
19
19
|
it("Error when passing parameters to procedure without parameters", () => {
|
|
20
20
|
const db = createTestDb();
|
|
21
21
|
expect(() => {
|
|
22
|
-
// @ts-expect-error -
|
|
22
|
+
// @ts-expect-error - test passing parameters to a procedure that expects none
|
|
23
23
|
db.getAllUsers().getExecProcQueryDef({ unexpectedParam: 1 });
|
|
24
24
|
}).toThrow("has no parameters.");
|
|
25
25
|
});
|
package/tests/escape.spec.ts
CHANGED
|
@@ -50,13 +50,13 @@ describe("MysqlExprRenderer.escapeString", () => {
|
|
|
50
50
|
expect(result).toBe("''; DROP TABLE users; --");
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
it("
|
|
53
|
+
it("should defend against backslash + quote combination", () => {
|
|
54
54
|
const malicious = "\\'";
|
|
55
55
|
const result = renderer.escapeString(malicious);
|
|
56
56
|
expect(result).toBe("\\\\''");
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it("NULL
|
|
59
|
+
it("should defend against NULL byte + SQL comment combination", () => {
|
|
60
60
|
const malicious = "admin\0-- ";
|
|
61
61
|
const result = renderer.escapeString(malicious);
|
|
62
62
|
expect(result).toBe("admin\\0-- ");
|
|
@@ -68,32 +68,32 @@ describe("MysqlExprRenderer.escapeString", () => {
|
|
|
68
68
|
describe("MysqlExprRenderer.escapeValue", () => {
|
|
69
69
|
const renderer = new MysqlExprRenderer(() => "");
|
|
70
70
|
|
|
71
|
-
it("
|
|
71
|
+
it("escapes string with escapeString() and wraps in quotes", () => {
|
|
72
72
|
const result = renderer.escapeValue("O'Reilly");
|
|
73
73
|
expect(result).toBe("'O''Reilly'");
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
it("
|
|
76
|
+
it("correctly escapes string containing backslashes", () => {
|
|
77
77
|
const result = renderer.escapeValue("C:\\path");
|
|
78
78
|
expect(result).toBe("'C:\\\\path'");
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
it("
|
|
81
|
+
it("defends against SQL injection attempts", () => {
|
|
82
82
|
const result = renderer.escapeValue("'; DROP TABLE users; --");
|
|
83
83
|
expect(result).toBe("'''; DROP TABLE users; --'");
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
it("
|
|
86
|
+
it("returns 'NULL' string for null", () => {
|
|
87
87
|
const result = renderer.escapeValue(null);
|
|
88
88
|
expect(result).toBe("NULL");
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
it("
|
|
91
|
+
it("converts number to string", () => {
|
|
92
92
|
const result = renderer.escapeValue(123);
|
|
93
93
|
expect(result).toBe("123");
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
it("
|
|
96
|
+
it("converts boolean to TRUE/FALSE", () => {
|
|
97
97
|
expect(renderer.escapeValue(true)).toBe("TRUE");
|
|
98
98
|
expect(renderer.escapeValue(false)).toBe("FALSE");
|
|
99
99
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mysql, pgsql, tsql } from "@simplysm/core-common";
|
|
2
2
|
import type { ExpectedSql } from "../setup/test-utils";
|
|
3
3
|
|
|
4
|
-
//#region ========== PIVOT - Basic
|
|
4
|
+
//#region ========== PIVOT - Basic Aggregate Functions ==========
|
|
5
5
|
|
|
6
6
|
export const pivotSum: ExpectedSql = {
|
|
7
7
|
mysql: mysql`
|
|
@@ -125,7 +125,7 @@ GROUP BY "T1"."id", "T1"."category"
|
|
|
125
125
|
|
|
126
126
|
//#endregion
|
|
127
127
|
|
|
128
|
-
//#region ========== PIVOT -
|
|
128
|
+
//#region ========== PIVOT - Multiple Pivot Values ==========
|
|
129
129
|
|
|
130
130
|
export const pivotMultipleYears: ExpectedSql = {
|
|
131
131
|
mysql: mysql`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mysql, pgsql, tsql } from "@simplysm/core-common";
|
|
2
2
|
import type { ExpectedSql } from "../setup/test-utils";
|
|
3
3
|
|
|
4
|
-
//#region ==========
|
|
4
|
+
//#region ========== Random Sampling ==========
|
|
5
5
|
|
|
6
6
|
export const samplingBasic: ExpectedSql = {
|
|
7
7
|
mysql: mysql`
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Random sampling example
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Implements random sampling using expr.random() combined with orderBy/top
|
|
5
|
+
* instead of a dedicated sample() method.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* Usage: db.user().orderBy(() => expr.random()).top(5)
|
|
7
8
|
*/
|
|
8
9
|
import { describe, expect, it } from "vitest";
|
|
9
10
|
import { createTestDb } from "../setup/TestDbContext";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mysql, pgsql, tsql, DateTime, DateOnly, Time, Uuid } from "@simplysm/core-common";
|
|
2
2
|
import type { ExpectedSql } from "../setup/test-utils";
|
|
3
3
|
|
|
4
|
-
// DateTime
|
|
4
|
+
// For DateTime value tests
|
|
5
5
|
export const testDateTime = new DateTime(2024, 1, 15, 10, 30, 0);
|
|
6
6
|
export const testDateOnly = new DateOnly(2024, 1, 15);
|
|
7
7
|
export const testTime = new Time(10, 30, 0);
|
|
@@ -237,7 +237,7 @@ export const inEmpty: ExpectedSql = {
|
|
|
237
237
|
`,
|
|
238
238
|
};
|
|
239
239
|
|
|
240
|
-
//#region ========== DateTime/DateOnly/Time
|
|
240
|
+
//#region ========== DateTime/DateOnly/Time Value Tests ==========
|
|
241
241
|
|
|
242
242
|
export const eqDateTime: ExpectedSql = {
|
|
243
243
|
mysql: mysql`
|
|
@@ -259,7 +259,7 @@ export const eqDateTime: ExpectedSql = {
|
|
|
259
259
|
|
|
260
260
|
//#endregion
|
|
261
261
|
|
|
262
|
-
//#region ========== inQuery
|
|
262
|
+
//#region ========== inQuery Tests ==========
|
|
263
263
|
|
|
264
264
|
export const inQuery: ExpectedSql = {
|
|
265
265
|
mysql: mysql`
|
|
@@ -340,7 +340,7 @@ describe("Expr - Comparison operators (null-safe)", () => {
|
|
|
340
340
|
});
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
-
//#region ========== DateTime
|
|
343
|
+
//#region ========== DateTime value tests ==========
|
|
344
344
|
|
|
345
345
|
describe("eq - DateTime value comparison", () => {
|
|
346
346
|
const db = createTestDb();
|
|
@@ -358,7 +358,7 @@ describe("Expr - Comparison operators (null-safe)", () => {
|
|
|
358
358
|
|
|
359
359
|
//#endregion
|
|
360
360
|
|
|
361
|
-
//#region ========== inQuery
|
|
361
|
+
//#region ========== inQuery tests ==========
|
|
362
362
|
|
|
363
363
|
describe("inQuery - subquery IN condition", () => {
|
|
364
364
|
const db = createTestDb();
|
|
@@ -218,7 +218,7 @@ describe("Expr - Conditional functions", () => {
|
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
describe("switch - all case/default are undefined", () => {
|
|
221
|
-
it("
|
|
221
|
+
it("throws an error", () => {
|
|
222
222
|
const db = createTestDb();
|
|
223
223
|
expect(() => {
|
|
224
224
|
db.user()
|
|
@@ -234,7 +234,7 @@ describe("Expr - Conditional functions", () => {
|
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
describe("if - both then and else are undefined", () => {
|
|
237
|
-
it("
|
|
237
|
+
it("throws an error", () => {
|
|
238
238
|
const db = createTestDb();
|
|
239
239
|
expect(() => {
|
|
240
240
|
db.user()
|
|
@@ -203,7 +203,7 @@ export const selectGreatest: ExpectedSql = {
|
|
|
203
203
|
|
|
204
204
|
//#endregion
|
|
205
205
|
|
|
206
|
-
//#region ==========
|
|
206
|
+
//#region ========== Options ==========
|
|
207
207
|
|
|
208
208
|
export const selectDistinct: ExpectedSql = {
|
|
209
209
|
mysql: mysql`
|
|
@@ -256,7 +256,7 @@ export const selectDistinctLock: ExpectedSql = {
|
|
|
256
256
|
|
|
257
257
|
//#endregion
|
|
258
258
|
|
|
259
|
-
//#region ==========
|
|
259
|
+
//#region ========== Limit ==========
|
|
260
260
|
|
|
261
261
|
export const selectTop: ExpectedSql = {
|
|
262
262
|
mysql: mysql`
|
|
@@ -317,6 +317,6 @@ export const selectLimitOffset: ExpectedSql = {
|
|
|
317
317
|
`,
|
|
318
318
|
};
|
|
319
319
|
|
|
320
|
-
//
|
|
320
|
+
// See examples/sampling.spec.ts for random sampling
|
|
321
321
|
|
|
322
322
|
//#endregion
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { mysql, pgsql, tsql } from "@simplysm/core-common";
|
|
5
5
|
import type { ExpectedSql } from "../setup/test-utils";
|
|
6
6
|
|
|
7
|
-
//#region ==========
|
|
7
|
+
//#region ========== Comparison Operators ==========
|
|
8
8
|
|
|
9
9
|
export const whereEq: ExpectedSql = {
|
|
10
10
|
mysql: mysql`
|
|
@@ -116,7 +116,7 @@ export const whereLte: ExpectedSql = {
|
|
|
116
116
|
|
|
117
117
|
//#endregion
|
|
118
118
|
|
|
119
|
-
//#region ========== NULL
|
|
119
|
+
//#region ========== NULL Check ==========
|
|
120
120
|
|
|
121
121
|
export const whereNull: ExpectedSql = {
|
|
122
122
|
mysql: mysql`
|
|
@@ -236,7 +236,7 @@ export const whereNotLike: ExpectedSql = {
|
|
|
236
236
|
|
|
237
237
|
//#endregion
|
|
238
238
|
|
|
239
|
-
//#region ==========
|
|
239
|
+
//#region ========== Logical Operators ==========
|
|
240
240
|
|
|
241
241
|
export const whereMultipleAnd: ExpectedSql = {
|
|
242
242
|
mysql: mysql`
|
|
@@ -6,7 +6,7 @@ import { dialects } from "../setup/test-utils";
|
|
|
6
6
|
import "../setup/test-utils"; // toMatchSql matcher
|
|
7
7
|
import * as expected from "./filter.expected";
|
|
8
8
|
|
|
9
|
-
//#region ==========
|
|
9
|
+
//#region ========== comparison operations ==========
|
|
10
10
|
|
|
11
11
|
describe("SELECT - WHERE - comparison operations", () => {
|
|
12
12
|
describe("eq (equal)", () => {
|
|
@@ -183,7 +183,7 @@ describe("SELECT - WHERE - comparison operations", () => {
|
|
|
183
183
|
|
|
184
184
|
//#endregion
|
|
185
185
|
|
|
186
|
-
//#region ========== NULL
|
|
186
|
+
//#region ========== NULL check ==========
|
|
187
187
|
|
|
188
188
|
describe("SELECT - WHERE - NULL check", () => {
|
|
189
189
|
describe("null", () => {
|
|
@@ -383,10 +383,10 @@ describe("SELECT - WHERE - LIKE", () => {
|
|
|
383
383
|
|
|
384
384
|
//#endregion
|
|
385
385
|
|
|
386
|
-
//#region ==========
|
|
386
|
+
//#region ========== logical operations ==========
|
|
387
387
|
|
|
388
388
|
describe("SELECT - WHERE - logical operations", () => {
|
|
389
|
-
describe("
|
|
389
|
+
describe("multiple conditions (AND)", () => {
|
|
390
390
|
const db = createTestDb();
|
|
391
391
|
const def = db
|
|
392
392
|
.user()
|
|
@@ -495,7 +495,7 @@ describe("SELECT - WHERE - logical operations", () => {
|
|
|
495
495
|
});
|
|
496
496
|
});
|
|
497
497
|
|
|
498
|
-
it("
|
|
498
|
+
it("chained where (AND combination)", () => {
|
|
499
499
|
const db = createTestDb();
|
|
500
500
|
const def = db
|
|
501
501
|
.user()
|
|
@@ -601,7 +601,7 @@ describe("SELECT - WHERE - EXISTS / IN subquery", () => {
|
|
|
601
601
|
});
|
|
602
602
|
});
|
|
603
603
|
|
|
604
|
-
it("not exists - QueryDef
|
|
604
|
+
it("not exists - verifies QueryDef", () => {
|
|
605
605
|
const db = createTestDb();
|
|
606
606
|
const def = db
|
|
607
607
|
.user()
|
|
@@ -683,7 +683,7 @@ describe("SELECT - WHERE - EXISTS / IN subquery", () => {
|
|
|
683
683
|
//#region ========== SEARCH ==========
|
|
684
684
|
|
|
685
685
|
describe("SELECT - SEARCH", () => {
|
|
686
|
-
it("
|
|
686
|
+
it("single keyword - single column", () => {
|
|
687
687
|
const db = createTestDb();
|
|
688
688
|
const def = db
|
|
689
689
|
.post()
|
|
@@ -717,7 +717,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
717
717
|
});
|
|
718
718
|
});
|
|
719
719
|
|
|
720
|
-
it("
|
|
720
|
+
it("multiple keywords (OR)", () => {
|
|
721
721
|
const db = createTestDb();
|
|
722
722
|
const def = db
|
|
723
723
|
.post()
|
|
@@ -769,7 +769,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
769
769
|
});
|
|
770
770
|
});
|
|
771
771
|
|
|
772
|
-
it("
|
|
772
|
+
it("phrase search (quotes)", () => {
|
|
773
773
|
const db = createTestDb();
|
|
774
774
|
const def = db
|
|
775
775
|
.post()
|
|
@@ -803,7 +803,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
803
803
|
});
|
|
804
804
|
});
|
|
805
805
|
|
|
806
|
-
it("
|
|
806
|
+
it("wildcard (*)", () => {
|
|
807
807
|
const db = createTestDb();
|
|
808
808
|
const def = db
|
|
809
809
|
.post()
|
|
@@ -837,7 +837,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
837
837
|
});
|
|
838
838
|
});
|
|
839
839
|
|
|
840
|
-
it("
|
|
840
|
+
it("escape (\\*)", () => {
|
|
841
841
|
const db = createTestDb();
|
|
842
842
|
const def = db
|
|
843
843
|
.post()
|
|
@@ -871,7 +871,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
871
871
|
});
|
|
872
872
|
});
|
|
873
873
|
|
|
874
|
-
it("
|
|
874
|
+
it("multiple column search (OR)", () => {
|
|
875
875
|
const db = createTestDb();
|
|
876
876
|
const def = db
|
|
877
877
|
.post()
|
|
@@ -913,7 +913,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
913
913
|
});
|
|
914
914
|
});
|
|
915
915
|
|
|
916
|
-
it("
|
|
916
|
+
it("exclusion search (-)", () => {
|
|
917
917
|
const db = createTestDb();
|
|
918
918
|
const def = db
|
|
919
919
|
.post()
|
|
@@ -963,7 +963,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
963
963
|
});
|
|
964
964
|
});
|
|
965
965
|
|
|
966
|
-
it("
|
|
966
|
+
it("complex search (include, exclude, phrase)", () => {
|
|
967
967
|
const db = createTestDb();
|
|
968
968
|
const def = db
|
|
969
969
|
.post()
|
|
@@ -1050,7 +1050,7 @@ describe("SELECT - SEARCH", () => {
|
|
|
1050
1050
|
});
|
|
1051
1051
|
});
|
|
1052
1052
|
|
|
1053
|
-
it("
|
|
1053
|
+
it("empty search term", () => {
|
|
1054
1054
|
const db = createTestDb();
|
|
1055
1055
|
const def = db
|
|
1056
1056
|
.post()
|
|
@@ -213,7 +213,7 @@ describe("SELECT - JOIN", () => {
|
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
215
|
|
|
216
|
-
describe("
|
|
216
|
+
describe("multi-level joinSingle", () => {
|
|
217
217
|
const db = createTestDb();
|
|
218
218
|
const def = db
|
|
219
219
|
.post()
|
|
@@ -563,7 +563,7 @@ describe("SELECT - INCLUDE", () => {
|
|
|
563
563
|
.include((item) => item.user.company)
|
|
564
564
|
.getSelectQueryDef();
|
|
565
565
|
|
|
566
|
-
//
|
|
566
|
+
// duplicate includes are automatically deduplicated (user once, company once)
|
|
567
567
|
expect(def).toEqual({
|
|
568
568
|
type: "select",
|
|
569
569
|
as: "T1",
|
|
@@ -707,7 +707,7 @@ describe("SELECT - INCLUDE", () => {
|
|
|
707
707
|
});
|
|
708
708
|
|
|
709
709
|
describe("3 depth include (FK -> FKT -> FK)", () => {
|
|
710
|
-
// Post
|
|
710
|
+
// Post -> user(FK) -> posts(FKT) -> user(FK)
|
|
711
711
|
const db = createTestDb();
|
|
712
712
|
const def = db
|
|
713
713
|
.post()
|
|
@@ -62,7 +62,7 @@ export const orderDesc: ExpectedSql = {
|
|
|
62
62
|
|
|
63
63
|
//#endregion
|
|
64
64
|
|
|
65
|
-
//#region ==========
|
|
65
|
+
//#region ========== Multiple Sort ==========
|
|
66
66
|
|
|
67
67
|
export const orderMultiple: ExpectedSql = {
|
|
68
68
|
mysql: mysql`
|
|
@@ -84,7 +84,7 @@ export const orderMultiple: ExpectedSql = {
|
|
|
84
84
|
|
|
85
85
|
//#endregion
|
|
86
86
|
|
|
87
|
-
//#region ==========
|
|
87
|
+
//#region ========== Expression Sort ==========
|
|
88
88
|
|
|
89
89
|
export const orderExpression: ExpectedSql = {
|
|
90
90
|
mysql: mysql`
|
|
@@ -106,7 +106,7 @@ export const orderExpression: ExpectedSql = {
|
|
|
106
106
|
|
|
107
107
|
//#endregion
|
|
108
108
|
|
|
109
|
-
//#region ==========
|
|
109
|
+
//#region ========== Combination ==========
|
|
110
110
|
|
|
111
111
|
export const orderSelectCombo: ExpectedSql = {
|
|
112
112
|
mysql: mysql`
|
|
@@ -76,7 +76,7 @@ describe("SELECT - ORDER BY", () => {
|
|
|
76
76
|
|
|
77
77
|
//#endregion
|
|
78
78
|
|
|
79
|
-
//#region ==========
|
|
79
|
+
//#region ========== multiple sorting ==========
|
|
80
80
|
|
|
81
81
|
describe("multiple sorting", () => {
|
|
82
82
|
const db = createTestDb();
|
|
@@ -106,7 +106,7 @@ describe("SELECT - ORDER BY", () => {
|
|
|
106
106
|
|
|
107
107
|
//#endregion
|
|
108
108
|
|
|
109
|
-
//#region ==========
|
|
109
|
+
//#region ========== expression sorting ==========
|
|
110
110
|
|
|
111
111
|
describe("sort by expressions", () => {
|
|
112
112
|
const db = createTestDb();
|
|
@@ -132,7 +132,7 @@ describe("SELECT - ORDER BY", () => {
|
|
|
132
132
|
|
|
133
133
|
//#endregion
|
|
134
134
|
|
|
135
|
-
//#region ==========
|
|
135
|
+
//#region ========== combinations ==========
|
|
136
136
|
|
|
137
137
|
describe("SELECT + ORDER BY combination", () => {
|
|
138
138
|
const db = createTestDb();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { mysql, pgsql, tsql } from "@simplysm/core-common";
|
|
5
5
|
import type { ExpectedSql } from "../setup/test-utils";
|
|
6
6
|
|
|
7
|
-
//#region ========== Basic
|
|
7
|
+
//#region ========== Basic Recursive CTE ==========
|
|
8
8
|
|
|
9
9
|
export const basicSubordinates: ExpectedSql = {
|
|
10
10
|
mysql: mysql`
|
|
@@ -143,7 +143,7 @@ export const upwardManagers: ExpectedSql = {
|
|
|
143
143
|
|
|
144
144
|
//#endregion
|
|
145
145
|
|
|
146
|
-
//#region ========== CTE +
|
|
146
|
+
//#region ========== CTE + Post Processing ==========
|
|
147
147
|
|
|
148
148
|
export const cteWithOrderBy: ExpectedSql = {
|
|
149
149
|
mysql: mysql`
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SELECT -
|
|
2
|
+
* SELECT - Subquery/WRAP/UNION test expected SQL
|
|
3
3
|
*/
|
|
4
4
|
import { mysql, pgsql, tsql } from "@simplysm/core-common";
|
|
5
5
|
import type { ExpectedSql } from "../setup/test-utils";
|
|
6
6
|
|
|
7
|
-
//#region ========== WRAP (
|
|
7
|
+
//#region ========== WRAP (Subquery) ==========
|
|
8
8
|
|
|
9
9
|
export const wrapBasic: ExpectedSql = {
|
|
10
10
|
mysql: mysql`
|
|
@@ -80,7 +80,7 @@ export const viewSelectOrderByLimit: ExpectedSql = {
|
|
|
80
80
|
|
|
81
81
|
//#endregion
|
|
82
82
|
|
|
83
|
-
//#region ========== View -
|
|
83
|
+
//#region ========== View - View With SELECT ==========
|
|
84
84
|
|
|
85
85
|
export const userSummarySelect: ExpectedSql = {
|
|
86
86
|
mysql: mysql`SELECT * FROM \`TestDb\`.\`UserSummary\` AS \`T1\``,
|
|
@@ -2,16 +2,16 @@ import type { DbContextExecutor, ResultMeta } from "../../src/types/db";
|
|
|
2
2
|
import type { QueryDef } from "../../src/types/query-def";
|
|
3
3
|
|
|
4
4
|
export class MockExecutor implements DbContextExecutor {
|
|
5
|
-
//
|
|
5
|
+
// connection management (Mock - no-op)
|
|
6
6
|
async connect(): Promise<void> {}
|
|
7
7
|
async close(): Promise<void> {}
|
|
8
8
|
|
|
9
|
-
//
|
|
9
|
+
// transactions (Mock - no-op)
|
|
10
10
|
async beginTransaction(): Promise<void> {}
|
|
11
11
|
async commitTransaction(): Promise<void> {}
|
|
12
12
|
async rollbackTransaction(): Promise<void> {}
|
|
13
13
|
|
|
14
|
-
//
|
|
14
|
+
// query execution
|
|
15
15
|
executeDefs(_defs: QueryDef[], _resultMetas?: (ResultMeta | undefined)[]): Promise<any[][]> {
|
|
16
16
|
return Promise.resolve([[]]);
|
|
17
17
|
}
|
|
@@ -4,7 +4,7 @@ export const Employee = Table("Employee")
|
|
|
4
4
|
.columns((c) => ({
|
|
5
5
|
id: c.bigint().autoIncrement(),
|
|
6
6
|
name: c.varchar(100),
|
|
7
|
-
managerId: c.bigint().nullable(), //
|
|
7
|
+
managerId: c.bigint().nullable(), // self-referencing (parent manager)
|
|
8
8
|
departmentId: c.bigint().nullable(),
|
|
9
9
|
}))
|
|
10
10
|
.primaryKey("id");
|
|
@@ -2,7 +2,7 @@ import { View } from "../../../src/schema/view-builder";
|
|
|
2
2
|
import { expr } from "../../../src/expr/expr";
|
|
3
3
|
import type { TestDbTablesContext } from "../TestDbContext";
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// view showing only active users
|
|
6
6
|
export const ActiveUsers = View("ActiveUsers").query((db: TestDbTablesContext) =>
|
|
7
7
|
db.user().where((u) => [expr.eq(u.isActive, true)]),
|
|
8
8
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { View } from "../../../src/schema/view-builder";
|
|
2
2
|
import type { TestDbTablesContext } from "../TestDbContext";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// view for user summary info (includes select)
|
|
5
5
|
export const UserSummary = View("UserSummary").query((db: TestDbTablesContext) =>
|
|
6
6
|
db.user().select((u) => ({
|
|
7
7
|
id: u.id,
|
|
@@ -3,16 +3,16 @@ import { parseQueryResult } from "../../src/utils/result-parser";
|
|
|
3
3
|
import type { ResultMeta } from "../../src/types/db";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Performance benchmark tests
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
8
|
+
* Purpose: measure optimization effect of removing objClone
|
|
9
|
+
* Metrics:
|
|
10
|
+
* - processing time for large simple records
|
|
11
|
+
* - processing time for large JOIN records
|
|
12
|
+
* - processing time for nested JOIN records
|
|
13
13
|
*/
|
|
14
14
|
describe("result-parser performance", () => {
|
|
15
|
-
//
|
|
15
|
+
// test data generation helpers
|
|
16
16
|
function generateSimpleRecords(count: number): Record<string, unknown>[] {
|
|
17
17
|
return Array.from({ length: count }, (_, i) => ({
|
|
18
18
|
id: String(i + 1),
|
|
@@ -63,7 +63,7 @@ describe("result-parser performance", () => {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
describe("simple record processing", () => {
|
|
66
|
-
it("10,000
|
|
66
|
+
it("10,000 records processing - within 500ms", async () => {
|
|
67
67
|
const raw = generateSimpleRecords(10_000);
|
|
68
68
|
const meta: ResultMeta = {
|
|
69
69
|
columns: {
|
|
@@ -82,10 +82,10 @@ describe("result-parser performance", () => {
|
|
|
82
82
|
|
|
83
83
|
expect(result).toHaveLength(10_000);
|
|
84
84
|
expect(elapsed).toBeLessThan(500);
|
|
85
|
-
console.log(`
|
|
85
|
+
console.log(` simple 10,000: ${elapsed.toFixed(2)}ms`);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
it("50,000
|
|
88
|
+
it("50,000 records processing - within 3000ms", async () => {
|
|
89
89
|
const raw = generateSimpleRecords(50_000);
|
|
90
90
|
const meta: ResultMeta = {
|
|
91
91
|
columns: {
|
|
@@ -104,12 +104,12 @@ describe("result-parser performance", () => {
|
|
|
104
104
|
|
|
105
105
|
expect(result).toHaveLength(50_000);
|
|
106
106
|
expect(elapsed).toBeLessThan(3000);
|
|
107
|
-
console.log(`
|
|
107
|
+
console.log(` simple 50,000: ${elapsed.toFixed(2)}ms`);
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
describe("1-level JOIN processing", () => {
|
|
112
|
-
it("1,000 users × 10 posts = 10,000
|
|
112
|
+
it("1,000 users × 10 posts = 10,000 records - within 600ms", async () => {
|
|
113
113
|
const raw = generateJoinRecords(1_000, 10);
|
|
114
114
|
const meta: ResultMeta = {
|
|
115
115
|
columns: {
|
|
@@ -128,10 +128,10 @@ describe("result-parser performance", () => {
|
|
|
128
128
|
|
|
129
129
|
expect(result).toHaveLength(1_000);
|
|
130
130
|
expect(elapsed).toBeLessThan(600);
|
|
131
|
-
console.log(` JOIN 10,000
|
|
131
|
+
console.log(` JOIN 10,000 (1000×10): ${elapsed.toFixed(2)}ms`);
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
it("5,000 users × 10 posts = 50,000
|
|
134
|
+
it("5,000 users × 10 posts = 50,000 records - within 3000ms", async () => {
|
|
135
135
|
const raw = generateJoinRecords(5_000, 10);
|
|
136
136
|
const meta: ResultMeta = {
|
|
137
137
|
columns: {
|
|
@@ -150,12 +150,12 @@ describe("result-parser performance", () => {
|
|
|
150
150
|
|
|
151
151
|
expect(result).toHaveLength(5_000);
|
|
152
152
|
expect(elapsed).toBeLessThan(3000);
|
|
153
|
-
console.log(` JOIN 50,000
|
|
153
|
+
console.log(` JOIN 50,000 (5000×10): ${elapsed.toFixed(2)}ms`);
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
describe("2-level nested JOIN processing", () => {
|
|
158
|
-
it("100 users × 10 posts × 5 comments = 5,000
|
|
158
|
+
it("100 users × 10 posts × 5 comments = 5,000 records - within 500ms", async () => {
|
|
159
159
|
const raw = generateNestedJoinRecords(100, 10, 5);
|
|
160
160
|
const meta: ResultMeta = {
|
|
161
161
|
columns: {
|
|
@@ -178,10 +178,10 @@ describe("result-parser performance", () => {
|
|
|
178
178
|
|
|
179
179
|
expect(result).toHaveLength(100);
|
|
180
180
|
expect(elapsed).toBeLessThan(500);
|
|
181
|
-
console.log(`
|
|
181
|
+
console.log(` nested JOIN 5,000 (100×10×5): ${elapsed.toFixed(2)}ms`);
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
-
it("500 users × 10 posts × 5 comments = 25,000
|
|
184
|
+
it("500 users × 10 posts × 5 comments = 25,000 records - within 2000ms", async () => {
|
|
185
185
|
const raw = generateNestedJoinRecords(500, 10, 5);
|
|
186
186
|
const meta: ResultMeta = {
|
|
187
187
|
columns: {
|
|
@@ -204,7 +204,7 @@ describe("result-parser performance", () => {
|
|
|
204
204
|
|
|
205
205
|
expect(result).toHaveLength(500);
|
|
206
206
|
expect(elapsed).toBeLessThan(2000);
|
|
207
|
-
console.log(`
|
|
207
|
+
console.log(` nested JOIN 25,000 (500×10×5): ${elapsed.toFixed(2)}ms`);
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
210
|
});
|
|
@@ -4,7 +4,7 @@ import { parseQueryResult } from "../../src/utils/result-parser";
|
|
|
4
4
|
import type { ResultMeta } from "../../src/types/db";
|
|
5
5
|
|
|
6
6
|
describe("result-parser", () => {
|
|
7
|
-
//#region ==========
|
|
7
|
+
//#region ========== type parsing ==========
|
|
8
8
|
|
|
9
9
|
describe("type parsing", () => {
|
|
10
10
|
it("number conversion", async () => {
|
|
@@ -51,7 +51,7 @@ describe("result-parser", () => {
|
|
|
51
51
|
expect(result).toEqual([{ active: true, deleted: false }]);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
it('boolean conversion - "0"/"1"
|
|
54
|
+
it('boolean conversion - "0"/"1" strings', async () => {
|
|
55
55
|
const raw = [{ active: "1", deleted: "0" }];
|
|
56
56
|
const meta: ResultMeta = {
|
|
57
57
|
columns: { active: "boolean", deleted: "boolean" },
|
|
@@ -107,7 +107,7 @@ describe("result-parser", () => {
|
|
|
107
107
|
expect(result![0].startTime.second).toBe(0);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
it("Uuid conversion -
|
|
110
|
+
it("Uuid conversion - string", async () => {
|
|
111
111
|
const uuidStr = "550e8400-e29b-41d4-a716-446655440000";
|
|
112
112
|
const raw = [{ id: uuidStr }];
|
|
113
113
|
const meta: ResultMeta = {
|
|
@@ -136,7 +136,7 @@ describe("result-parser", () => {
|
|
|
136
136
|
expect(result![0].id.toString()).toBe(uuidStr);
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
it("Bytes conversion - Uint8Array
|
|
139
|
+
it("Bytes conversion - Uint8Array passthrough", async () => {
|
|
140
140
|
const bytes = new Uint8Array([0x01, 0x02, 0x03]);
|
|
141
141
|
const raw = [{ data: bytes }];
|
|
142
142
|
const meta: ResultMeta = {
|
|
@@ -150,8 +150,8 @@ describe("result-parser", () => {
|
|
|
150
150
|
expect(Array.from(result![0].data)).toEqual([0x01, 0x02, 0x03]);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
it("Bytes conversion - hex
|
|
154
|
-
// [0x01, 0x02, 0x03]
|
|
153
|
+
it("Bytes conversion - hex string", async () => {
|
|
154
|
+
// hex representation of [0x01, 0x02, 0x03]
|
|
155
155
|
const hexStr = "010203";
|
|
156
156
|
const raw = [{ data: hexStr }];
|
|
157
157
|
const meta: ResultMeta = {
|
|
@@ -171,7 +171,7 @@ describe("result-parser", () => {
|
|
|
171
171
|
//#region ========== null/undefined processing ==========
|
|
172
172
|
|
|
173
173
|
describe("null/undefined processing", () => {
|
|
174
|
-
it("null
|
|
174
|
+
it("null value removes the key", async () => {
|
|
175
175
|
const raw = [{ id: 1, name: null }];
|
|
176
176
|
const meta: ResultMeta = {
|
|
177
177
|
columns: { id: "number", name: "string" },
|
|
@@ -183,7 +183,7 @@ describe("result-parser", () => {
|
|
|
183
183
|
expect(result![0]).not.toHaveProperty("name");
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
-
it("undefined
|
|
186
|
+
it("undefined value removes the key", async () => {
|
|
187
187
|
const raw = [{ id: 1, name: undefined }];
|
|
188
188
|
const meta: ResultMeta = {
|
|
189
189
|
columns: { id: "number", name: "string" },
|
|
@@ -195,7 +195,7 @@ describe("result-parser", () => {
|
|
|
195
195
|
expect(result![0]).not.toHaveProperty("name");
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
-
it("
|
|
198
|
+
it("record with all null values is excluded", async () => {
|
|
199
199
|
const raw = [{ id: null, name: null }];
|
|
200
200
|
const meta: ResultMeta = {
|
|
201
201
|
columns: { id: "number", name: "string" },
|
|
@@ -206,7 +206,7 @@ describe("result-parser", () => {
|
|
|
206
206
|
expect(result).toBeUndefined();
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
it("
|
|
209
|
+
it("empty array returns undefined", async () => {
|
|
210
210
|
const raw: Record<string, unknown>[] = [];
|
|
211
211
|
const meta: ResultMeta = {
|
|
212
212
|
columns: { id: "number" },
|
|
@@ -217,8 +217,8 @@ describe("result-parser", () => {
|
|
|
217
217
|
expect(result).toBeUndefined();
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
-
it("columns
|
|
221
|
-
const raw = [{ id: 1, name: "Gildong Hong", extra: "
|
|
220
|
+
it("columns not in meta are ignored", async () => {
|
|
221
|
+
const raw = [{ id: 1, name: "Gildong Hong", extra: "ignored" }];
|
|
222
222
|
const meta: ResultMeta = {
|
|
223
223
|
columns: { id: "number", name: "string" },
|
|
224
224
|
joins: {},
|
|
@@ -232,7 +232,7 @@ describe("result-parser", () => {
|
|
|
232
232
|
|
|
233
233
|
//#endregion
|
|
234
234
|
|
|
235
|
-
//#region ==========
|
|
235
|
+
//#region ========== error processing ==========
|
|
236
236
|
|
|
237
237
|
describe("error processing", () => {
|
|
238
238
|
it("number throw if parsing fails", async () => {
|
|
@@ -266,7 +266,7 @@ describe("result-parser", () => {
|
|
|
266
266
|
});
|
|
267
267
|
|
|
268
268
|
it("Bytes throw if parsing fails", async () => {
|
|
269
|
-
const raw = [{ data: 12345 }]; // Number
|
|
269
|
+
const raw = [{ data: 12345 }]; // Number cannot be converted to Bytes
|
|
270
270
|
const meta: ResultMeta = {
|
|
271
271
|
columns: { data: "Bytes" },
|
|
272
272
|
joins: {},
|
|
@@ -278,10 +278,10 @@ describe("result-parser", () => {
|
|
|
278
278
|
|
|
279
279
|
//#endregion
|
|
280
280
|
|
|
281
|
-
//#region ========== 1
|
|
281
|
+
//#region ========== 1-level JOIN ==========
|
|
282
282
|
|
|
283
283
|
describe("1-level JOIN", () => {
|
|
284
|
-
it("isSingle: true -
|
|
284
|
+
it("isSingle: true - single object", async () => {
|
|
285
285
|
const raw = [{ "id": 1, "name": "User1", "company.id": 100, "company.name": "Corp" }];
|
|
286
286
|
const meta: ResultMeta = {
|
|
287
287
|
columns: {
|
|
@@ -303,7 +303,7 @@ describe("result-parser", () => {
|
|
|
303
303
|
]);
|
|
304
304
|
});
|
|
305
305
|
|
|
306
|
-
it("isSingle: false -
|
|
306
|
+
it("isSingle: false - array", async () => {
|
|
307
307
|
const raw = [
|
|
308
308
|
{ "id": 1, "name": "User1", "posts.id": 10, "posts.title": "Post1" },
|
|
309
309
|
{ "id": 1, "name": "User1", "posts.id": 11, "posts.title": "Post2" },
|
|
@@ -331,7 +331,7 @@ describe("result-parser", () => {
|
|
|
331
331
|
]);
|
|
332
332
|
});
|
|
333
333
|
|
|
334
|
-
it("isSingle: true -
|
|
334
|
+
it("isSingle: true - throws when multiple different results exist", async () => {
|
|
335
335
|
const raw = [
|
|
336
336
|
{ "id": 1, "name": "User1", "company.id": 100, "company.name": "Corp1" },
|
|
337
337
|
{ "id": 1, "name": "User1", "company.id": 200, "company.name": "Corp2" },
|
|
@@ -351,7 +351,7 @@ describe("result-parser", () => {
|
|
|
351
351
|
);
|
|
352
352
|
});
|
|
353
353
|
|
|
354
|
-
it("
|
|
354
|
+
it("empty JOIN result omits the key", async () => {
|
|
355
355
|
const raw = [{ "id": 1, "name": "User1", "company.id": null, "company.name": null }];
|
|
356
356
|
const meta: ResultMeta = {
|
|
357
357
|
columns: {
|
|
@@ -368,7 +368,7 @@ describe("result-parser", () => {
|
|
|
368
368
|
expect(result![0]).not.toHaveProperty("company");
|
|
369
369
|
});
|
|
370
370
|
|
|
371
|
-
it("
|
|
371
|
+
it("multiple root records with JOIN", async () => {
|
|
372
372
|
const raw = [
|
|
373
373
|
{ "id": 1, "name": "User1", "posts.id": 10, "posts.title": "Post1" },
|
|
374
374
|
{ "id": 2, "name": "User2", "posts.id": 20, "posts.title": "Post2" },
|
|
@@ -405,10 +405,10 @@ describe("result-parser", () => {
|
|
|
405
405
|
|
|
406
406
|
//#endregion
|
|
407
407
|
|
|
408
|
-
//#region ==========
|
|
408
|
+
//#region ========== multi-level JOIN ==========
|
|
409
409
|
|
|
410
410
|
describe("multi-level JOIN", () => {
|
|
411
|
-
it("2
|
|
411
|
+
it("2-level nesting", async () => {
|
|
412
412
|
const raw = [
|
|
413
413
|
{
|
|
414
414
|
"id": 1,
|
|
@@ -461,7 +461,7 @@ describe("result-parser", () => {
|
|
|
461
461
|
]);
|
|
462
462
|
});
|
|
463
463
|
|
|
464
|
-
it("3
|
|
464
|
+
it("3-level nesting", async () => {
|
|
465
465
|
const raw = [
|
|
466
466
|
{
|
|
467
467
|
"id": 1,
|
|
@@ -527,7 +527,7 @@ describe("result-parser", () => {
|
|
|
527
527
|
]);
|
|
528
528
|
});
|
|
529
529
|
|
|
530
|
-
it("
|
|
530
|
+
it("multiple JOINs with mixed isSingle", async () => {
|
|
531
531
|
const raw = [
|
|
532
532
|
{
|
|
533
533
|
"id": 1,
|
|
@@ -575,7 +575,7 @@ describe("result-parser", () => {
|
|
|
575
575
|
]);
|
|
576
576
|
});
|
|
577
577
|
|
|
578
|
-
it("
|
|
578
|
+
it("partial NULL among multiple JOINs - company present, posts absent", async () => {
|
|
579
579
|
const raw = [
|
|
580
580
|
{
|
|
581
581
|
"id": 1,
|
|
@@ -615,10 +615,10 @@ describe("result-parser", () => {
|
|
|
615
615
|
|
|
616
616
|
//#endregion
|
|
617
617
|
|
|
618
|
-
//#region ==========
|
|
618
|
+
//#region ========== edge cases ==========
|
|
619
619
|
|
|
620
620
|
describe("edge cases", () => {
|
|
621
|
-
it("joins
|
|
621
|
+
it("joins is an empty object", async () => {
|
|
622
622
|
const raw = [{ id: 1, name: "User1" }];
|
|
623
623
|
const meta: ResultMeta = {
|
|
624
624
|
columns: { id: "number", name: "string" },
|
|
@@ -629,10 +629,10 @@ describe("result-parser", () => {
|
|
|
629
629
|
expect(result).toEqual([{ id: 1, name: "User1" }]);
|
|
630
630
|
});
|
|
631
631
|
|
|
632
|
-
it("
|
|
632
|
+
it("deduplicates duplicate data", async () => {
|
|
633
633
|
const raw = [
|
|
634
634
|
{ "id": 1, "name": "User1", "posts.id": 10, "posts.title": "Post1" },
|
|
635
|
-
{ "id": 1, "name": "User1", "posts.id": 10, "posts.title": "Post1" }, //
|
|
635
|
+
{ "id": 1, "name": "User1", "posts.id": 10, "posts.title": "Post1" }, // duplicate
|
|
636
636
|
];
|
|
637
637
|
const meta: ResultMeta = {
|
|
638
638
|
columns: {
|
|
@@ -649,15 +649,15 @@ describe("result-parser", () => {
|
|
|
649
649
|
{
|
|
650
650
|
id: 1,
|
|
651
651
|
name: "User1",
|
|
652
|
-
posts: [{ id: 10, title: "Post1" }], //
|
|
652
|
+
posts: [{ id: 10, title: "Post1" }], // deduplicated
|
|
653
653
|
},
|
|
654
654
|
]);
|
|
655
655
|
});
|
|
656
656
|
|
|
657
|
-
it("isSingle: true -
|
|
657
|
+
it("isSingle: true - identical data does not throw", async () => {
|
|
658
658
|
const raw = [
|
|
659
659
|
{ "id": 1, "name": "User1", "company.id": 100, "company.name": "Corp" },
|
|
660
|
-
{ "id": 1, "name": "User1", "company.id": 100, "company.name": "Corp" }, //
|
|
660
|
+
{ "id": 1, "name": "User1", "company.id": 100, "company.name": "Corp" }, // identical
|
|
661
661
|
];
|
|
662
662
|
const meta: ResultMeta = {
|
|
663
663
|
columns: {
|
|
@@ -679,8 +679,8 @@ describe("result-parser", () => {
|
|
|
679
679
|
]);
|
|
680
680
|
});
|
|
681
681
|
|
|
682
|
-
it("
|
|
683
|
-
// 250
|
|
682
|
+
it("large dataset yield processing", async () => {
|
|
683
|
+
// generates 250 records (yield fires twice: at i=100, i=200)
|
|
684
684
|
const raw = Array.from({ length: 250 }, (_, i) => ({
|
|
685
685
|
id: String(i + 1),
|
|
686
686
|
name: `User${i + 1}`,
|