spice-js 2.7.31 → 2.7.32
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.
|
@@ -712,17 +712,44 @@ class SpiceModel {
|
|
|
712
712
|
}
|
|
713
713
|
})();
|
|
714
714
|
}
|
|
715
|
-
query(query, scope) {
|
|
715
|
+
query(query, scope, options) {
|
|
716
716
|
var _this5 = this;
|
|
717
717
|
return _asyncToGenerator(function* () {
|
|
718
718
|
try {
|
|
719
|
-
var results = yield _this5.database.query(query, scope);
|
|
719
|
+
var results = yield _this5.database.query(query, scope, options);
|
|
720
720
|
return results;
|
|
721
721
|
} catch (error) {
|
|
722
722
|
throw error;
|
|
723
723
|
}
|
|
724
724
|
})();
|
|
725
725
|
}
|
|
726
|
+
getQueryOptions(args) {
|
|
727
|
+
if (args === void 0) {
|
|
728
|
+
args = {};
|
|
729
|
+
}
|
|
730
|
+
var scanConsistency = args.scan_consistency;
|
|
731
|
+
|
|
732
|
+
// Backward compatibility for callers that already use statement_consistent.
|
|
733
|
+
// The adapter accepts the SDK-style value as well as these snake_case keys.
|
|
734
|
+
if (scanConsistency === undefined && args.statement_consistent !== undefined) {
|
|
735
|
+
var legacyValue = args.statement_consistent;
|
|
736
|
+
var isRequestPlus = legacyValue === true || typeof legacyValue === "string" && ["true", "request_plus"].includes(legacyValue.toLowerCase());
|
|
737
|
+
scanConsistency = isRequestPlus ? "request_plus" : "not_bounded";
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
scan_consistency: scanConsistency,
|
|
741
|
+
consistent_with: args.consistent_with,
|
|
742
|
+
scan_wait: args.scan_wait
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
requiresFreshQuery(args) {
|
|
746
|
+
if (args === void 0) {
|
|
747
|
+
args = {};
|
|
748
|
+
}
|
|
749
|
+
var options = this.getQueryOptions(args);
|
|
750
|
+
var scanConsistency = options.scan_consistency;
|
|
751
|
+
return options.consistent_with !== undefined || scanConsistency === true || typeof scanConsistency === "string" && ["true", "request_plus"].includes(scanConsistency.toLowerCase());
|
|
752
|
+
}
|
|
726
753
|
getMulti(args) {
|
|
727
754
|
var _this6 = this;
|
|
728
755
|
return _asyncToGenerator(function* () {
|
|
@@ -1409,9 +1436,9 @@ class SpiceModel {
|
|
|
1409
1436
|
if (args.skip_hooks !== true) {
|
|
1410
1437
|
yield _this10.run_hook(_this10, "list", "before");
|
|
1411
1438
|
}
|
|
1412
|
-
var cacheKey = "list::" + _this10.type + "::" + args._join + "::" + query + "::" + args.limit + "::" + args.offset + "::" + args.sort + "::" + args.do_count + "::" + args.statement_consistent + "::" + args.columns + "::" + args.is_full_text + "::" + args.is_custom_query;
|
|
1439
|
+
var cacheKey = "list::" + _this10.type + "::" + args._join + "::" + query + "::" + args.limit + "::" + args.offset + "::" + args.sort + "::" + args.do_count + "::" + args.query_scope + "::" + args.scan_consistency + "::" + args.statement_consistent + "::" + args.scan_wait + "::" + args.columns + "::" + args.is_full_text + "::" + args.is_custom_query;
|
|
1413
1440
|
var results;
|
|
1414
|
-
if (_this10.shouldUseCache(_this10.type)) {
|
|
1441
|
+
if (_this10.shouldUseCache(_this10.type) && !_this10.requiresFreshQuery(args)) {
|
|
1415
1442
|
var cached = yield _this10.getCacheProviderObject(_this10.type).get(cacheKey);
|
|
1416
1443
|
results = cached == null ? void 0 : cached.value;
|
|
1417
1444
|
var isEmpty = (cached == null ? void 0 : cached.value) === undefined;
|
|
@@ -1494,14 +1521,15 @@ class SpiceModel {
|
|
|
1494
1521
|
var p = (_this11$_ctx = _this11[_ctx]) == null ? void 0 : _this11$_ctx.profiler;
|
|
1495
1522
|
var doFetch = /*#__PURE__*/function () {
|
|
1496
1523
|
var _ref17 = _asyncToGenerator(function* () {
|
|
1524
|
+
var queryOptions = _this11.getQueryOptions(args);
|
|
1497
1525
|
if (args.is_custom_query === "true" && args.ids.length > 0) {
|
|
1498
1526
|
console.log("Fetching results from database", _this11.type, args, query, mappedNestings);
|
|
1499
|
-
return yield _this11.database.query(query);
|
|
1527
|
+
return yield _this11.database.query(query, args.query_scope, queryOptions);
|
|
1500
1528
|
} else if (args.is_full_text === "true") {
|
|
1501
1529
|
return yield _this11.database.full_text_search(_this11.type, query || "", args.limit, args.offset, args._join);
|
|
1502
1530
|
} else {
|
|
1503
1531
|
var correctedColumns = _this11.correctColumns(args.columns, mappedNestings);
|
|
1504
|
-
var result = yield _this11.database.search(_this11.type, correctedColumns || args.columns || "", query || "", args.limit, args.offset, args.sort, args.do_count, args.
|
|
1532
|
+
var result = yield _this11.database.search(_this11.type, correctedColumns || args.columns || "", query || "", args.limit, args.offset, args.sort, args.do_count, args.query_scope, args._join, queryOptions);
|
|
1505
1533
|
return result;
|
|
1506
1534
|
}
|
|
1507
1535
|
});
|
package/package.json
CHANGED
package/src/models/SpiceModel.js
CHANGED
|
@@ -783,15 +783,47 @@ export default class SpiceModel {
|
|
|
783
783
|
}
|
|
784
784
|
}
|
|
785
785
|
|
|
786
|
-
async query(query, scope) {
|
|
786
|
+
async query(query, scope, options) {
|
|
787
787
|
try {
|
|
788
|
-
let results = await this.database.query(query, scope);
|
|
788
|
+
let results = await this.database.query(query, scope, options);
|
|
789
789
|
return results;
|
|
790
790
|
} catch (error) {
|
|
791
791
|
throw error;
|
|
792
792
|
}
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
+
getQueryOptions(args = {}) {
|
|
796
|
+
let scanConsistency = args.scan_consistency;
|
|
797
|
+
|
|
798
|
+
// Backward compatibility for callers that already use statement_consistent.
|
|
799
|
+
// The adapter accepts the SDK-style value as well as these snake_case keys.
|
|
800
|
+
if (scanConsistency === undefined && args.statement_consistent !== undefined) {
|
|
801
|
+
const legacyValue = args.statement_consistent;
|
|
802
|
+
const isRequestPlus =
|
|
803
|
+
legacyValue === true ||
|
|
804
|
+
(typeof legacyValue === "string" &&
|
|
805
|
+
["true", "request_plus"].includes(legacyValue.toLowerCase()));
|
|
806
|
+
scanConsistency = isRequestPlus ? "request_plus" : "not_bounded";
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
scan_consistency: scanConsistency,
|
|
811
|
+
consistent_with: args.consistent_with,
|
|
812
|
+
scan_wait: args.scan_wait,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
requiresFreshQuery(args = {}) {
|
|
817
|
+
const options = this.getQueryOptions(args);
|
|
818
|
+
const scanConsistency = options.scan_consistency;
|
|
819
|
+
return (
|
|
820
|
+
options.consistent_with !== undefined ||
|
|
821
|
+
scanConsistency === true ||
|
|
822
|
+
(typeof scanConsistency === "string" &&
|
|
823
|
+
["true", "request_plus"].includes(scanConsistency.toLowerCase()))
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
795
827
|
async getMulti(args) {
|
|
796
828
|
// ⚡ Profiling: use track() for proper async context forking
|
|
797
829
|
const p = this[_ctx]?.profiler;
|
|
@@ -1569,10 +1601,10 @@ export default class SpiceModel {
|
|
|
1569
1601
|
await this.run_hook(this, "list", "before");
|
|
1570
1602
|
}
|
|
1571
1603
|
|
|
1572
|
-
const cacheKey = `list::${this.type}::${args._join}::${query}::${args.limit}::${args.offset}::${args.sort}::${args.do_count}::${args.statement_consistent}::${args.columns}::${args.is_full_text}::${args.is_custom_query}`;
|
|
1604
|
+
const cacheKey = `list::${this.type}::${args._join}::${query}::${args.limit}::${args.offset}::${args.sort}::${args.do_count}::${args.query_scope}::${args.scan_consistency}::${args.statement_consistent}::${args.scan_wait}::${args.columns}::${args.is_full_text}::${args.is_custom_query}`;
|
|
1573
1605
|
let results;
|
|
1574
1606
|
|
|
1575
|
-
if (this.shouldUseCache(this.type)) {
|
|
1607
|
+
if (this.shouldUseCache(this.type) && !this.requiresFreshQuery(args)) {
|
|
1576
1608
|
const cached = await this.getCacheProviderObject(this.type).get(
|
|
1577
1609
|
cacheKey,
|
|
1578
1610
|
);
|
|
@@ -1671,6 +1703,7 @@ export default class SpiceModel {
|
|
|
1671
1703
|
const p = this[_ctx]?.profiler;
|
|
1672
1704
|
|
|
1673
1705
|
const doFetch = async () => {
|
|
1706
|
+
const queryOptions = this.getQueryOptions(args);
|
|
1674
1707
|
if (args.is_custom_query === "true" && args.ids.length > 0) {
|
|
1675
1708
|
console.log(
|
|
1676
1709
|
"Fetching results from database",
|
|
@@ -1679,7 +1712,7 @@ export default class SpiceModel {
|
|
|
1679
1712
|
query,
|
|
1680
1713
|
mappedNestings,
|
|
1681
1714
|
);
|
|
1682
|
-
return await this.database.query(query);
|
|
1715
|
+
return await this.database.query(query, args.query_scope, queryOptions);
|
|
1683
1716
|
} else if (args.is_full_text === "true") {
|
|
1684
1717
|
return await this.database.full_text_search(
|
|
1685
1718
|
this.type,
|
|
@@ -1701,8 +1734,9 @@ export default class SpiceModel {
|
|
|
1701
1734
|
args.offset,
|
|
1702
1735
|
args.sort,
|
|
1703
1736
|
args.do_count,
|
|
1704
|
-
args.
|
|
1737
|
+
args.query_scope,
|
|
1705
1738
|
args._join,
|
|
1739
|
+
queryOptions,
|
|
1706
1740
|
);
|
|
1707
1741
|
return result;
|
|
1708
1742
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const { createTestModel } = require("../helpers/modelFactory");
|
|
2
|
+
|
|
3
|
+
describe("SpiceModel query consistency", () => {
|
|
4
|
+
test("keeps query scope separate from query consistency", async () => {
|
|
5
|
+
const model = createTestModel({ type: "application" });
|
|
6
|
+
model.database.search = jest.fn(async () => ({ data: [], total: null }));
|
|
7
|
+
|
|
8
|
+
await model.fetchResults(
|
|
9
|
+
{
|
|
10
|
+
ids: [],
|
|
11
|
+
query_scope: "cluster",
|
|
12
|
+
scan_consistency: "request_plus",
|
|
13
|
+
scan_wait: 2500,
|
|
14
|
+
_join: "",
|
|
15
|
+
},
|
|
16
|
+
"",
|
|
17
|
+
[],
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(model.database.search).toHaveBeenCalledWith(
|
|
21
|
+
"application",
|
|
22
|
+
"",
|
|
23
|
+
"",
|
|
24
|
+
undefined,
|
|
25
|
+
undefined,
|
|
26
|
+
undefined,
|
|
27
|
+
undefined,
|
|
28
|
+
"cluster",
|
|
29
|
+
"",
|
|
30
|
+
{
|
|
31
|
+
scan_consistency: "request_plus",
|
|
32
|
+
consistent_with: undefined,
|
|
33
|
+
scan_wait: 2500,
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test.each([true, "true", "request_plus"])(
|
|
39
|
+
"maps legacy statement_consistent=%s to request_plus",
|
|
40
|
+
(legacyValue) => {
|
|
41
|
+
const model = createTestModel({ type: "application" });
|
|
42
|
+
|
|
43
|
+
expect(
|
|
44
|
+
model.getQueryOptions({ statement_consistent: legacyValue }),
|
|
45
|
+
).toEqual(
|
|
46
|
+
expect.objectContaining({ scan_consistency: "request_plus" }),
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
test.each([false, "false"])(
|
|
52
|
+
"maps legacy statement_consistent=%s to not_bounded",
|
|
53
|
+
(legacyValue) => {
|
|
54
|
+
const model = createTestModel({ type: "application" });
|
|
55
|
+
|
|
56
|
+
expect(
|
|
57
|
+
model.getQueryOptions({ statement_consistent: legacyValue }),
|
|
58
|
+
).toEqual(
|
|
59
|
+
expect.objectContaining({ scan_consistency: "not_bounded" }),
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
test("explicit scan_consistency takes precedence over the legacy flag", () => {
|
|
65
|
+
const model = createTestModel({ type: "application" });
|
|
66
|
+
|
|
67
|
+
expect(
|
|
68
|
+
model.getQueryOptions({
|
|
69
|
+
scan_consistency: "not_bounded",
|
|
70
|
+
statement_consistent: true,
|
|
71
|
+
}),
|
|
72
|
+
).toEqual(expect.objectContaining({ scan_consistency: "not_bounded" }));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("strongly consistent reads bypass the model query cache", () => {
|
|
76
|
+
const model = createTestModel({ type: "application" });
|
|
77
|
+
|
|
78
|
+
expect(model.requiresFreshQuery({ scan_consistency: "request_plus" })).toBe(
|
|
79
|
+
true,
|
|
80
|
+
);
|
|
81
|
+
expect(model.requiresFreshQuery({ statement_consistent: true })).toBe(true);
|
|
82
|
+
expect(model.requiresFreshQuery({ consistent_with: {} })).toBe(true);
|
|
83
|
+
expect(model.requiresFreshQuery({ scan_consistency: "not_bounded" })).toBe(
|
|
84
|
+
false,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("custom queries receive the same query options", async () => {
|
|
89
|
+
const model = createTestModel({ type: "application" });
|
|
90
|
+
model.database.query = jest.fn(async () => ({ rows: [] }));
|
|
91
|
+
const mutationState = { token: "test-token" };
|
|
92
|
+
|
|
93
|
+
await model.fetchResults(
|
|
94
|
+
{
|
|
95
|
+
ids: ["application-1"],
|
|
96
|
+
is_custom_query: "true",
|
|
97
|
+
query_scope: "scope",
|
|
98
|
+
consistent_with: mutationState,
|
|
99
|
+
},
|
|
100
|
+
"SELECT 1",
|
|
101
|
+
[],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(model.database.query).toHaveBeenCalledWith(
|
|
105
|
+
"SELECT 1",
|
|
106
|
+
"scope",
|
|
107
|
+
{
|
|
108
|
+
scan_consistency: undefined,
|
|
109
|
+
consistent_with: mutationState,
|
|
110
|
+
scan_wait: undefined,
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
});
|