spice-couchbase 2.0.60 → 2.0.62
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/build/index.auto-optimize.test.js +578 -0
- package/build/index.js +153 -13
- package/build/utility.js +156 -48
- package/build/utility.test.js +53 -5
- package/package.json +2 -1
- package/src/index.auto-optimize.test.js +393 -0
- package/src/index.js +165 -8
- package/src/utility.js +165 -62
- package/src/utility.test.js +73 -4
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
4
|
+
|
|
5
|
+
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
6
|
+
|
|
7
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
|
8
|
+
|
|
9
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
10
|
+
|
|
11
|
+
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
12
|
+
|
|
13
|
+
var assert = require("node:assert/strict");
|
|
14
|
+
|
|
15
|
+
var fs = require("node:fs");
|
|
16
|
+
|
|
17
|
+
var path = require("node:path");
|
|
18
|
+
|
|
19
|
+
var vm = require("node:vm");
|
|
20
|
+
|
|
21
|
+
var {
|
|
22
|
+
test
|
|
23
|
+
} = require("node:test");
|
|
24
|
+
|
|
25
|
+
var {
|
|
26
|
+
buildCoveringIndex,
|
|
27
|
+
buildCoveringIndexDefinition
|
|
28
|
+
} = require("./utility");
|
|
29
|
+
|
|
30
|
+
var keyspace = {
|
|
31
|
+
bucket: "saturn",
|
|
32
|
+
scope: "test",
|
|
33
|
+
collection: "users"
|
|
34
|
+
};
|
|
35
|
+
var lookupQuery = "SELECT name, email FROM users WHERE status = 'active'";
|
|
36
|
+
|
|
37
|
+
function existingIndex(index_key) {
|
|
38
|
+
var overrides = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
39
|
+
return _objectSpread({
|
|
40
|
+
name: "legacy_covering_index",
|
|
41
|
+
using: "gsi",
|
|
42
|
+
state: "online",
|
|
43
|
+
index_key
|
|
44
|
+
}, overrides);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function executionScope() {
|
|
48
|
+
var indexes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
49
|
+
var {
|
|
50
|
+
inventoryError,
|
|
51
|
+
inventoryResponse,
|
|
52
|
+
plan
|
|
53
|
+
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
54
|
+
var calls = [];
|
|
55
|
+
return {
|
|
56
|
+
calls,
|
|
57
|
+
|
|
58
|
+
query(statement, options) {
|
|
59
|
+
return _asyncToGenerator(function* () {
|
|
60
|
+
calls.push({
|
|
61
|
+
statement,
|
|
62
|
+
options
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (statement.startsWith("EXPLAIN ")) {
|
|
66
|
+
return {
|
|
67
|
+
rows: [plan || {
|
|
68
|
+
"#operator": "Fetch"
|
|
69
|
+
}]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (statement.includes("FROM system:indexes")) {
|
|
74
|
+
if (inventoryError) throw inventoryError;
|
|
75
|
+
return inventoryResponse === undefined ? {
|
|
76
|
+
rows: indexes
|
|
77
|
+
} : inventoryResponse;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
rows: []
|
|
82
|
+
};
|
|
83
|
+
})();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createStatements(scope) {
|
|
90
|
+
return scope.calls.filter((_ref) => {
|
|
91
|
+
var {
|
|
92
|
+
statement
|
|
93
|
+
} = _ref;
|
|
94
|
+
return statement.startsWith("CREATE INDEX ");
|
|
95
|
+
});
|
|
96
|
+
} // Isolate optimizer state and time without connecting to Couchbase or sleeping.
|
|
97
|
+
// Each load is a fresh module: its own seen-set, cooldown and policy.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
function loadDriver(durationMs) {
|
|
101
|
+
var env = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
102
|
+
var tick = BigInt(0);
|
|
103
|
+
var module = {
|
|
104
|
+
exports: {}
|
|
105
|
+
};
|
|
106
|
+
vm.runInNewContext(fs.readFileSync(path.join(__dirname, "index.js"), "utf8"), {
|
|
107
|
+
require,
|
|
108
|
+
module,
|
|
109
|
+
console,
|
|
110
|
+
process: {
|
|
111
|
+
env,
|
|
112
|
+
hrtime: {
|
|
113
|
+
bigint: () => {
|
|
114
|
+
var current = tick;
|
|
115
|
+
tick += BigInt(durationMs) * BigInt(1000000);
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return module.exports;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function instanceOf(Database) {
|
|
125
|
+
var db = Object.create(Database.prototype);
|
|
126
|
+
db.bucket = "saturn";
|
|
127
|
+
db.scope_name = "test";
|
|
128
|
+
db.collection_name = "users";
|
|
129
|
+
db.cluster = {
|
|
130
|
+
query: function () {
|
|
131
|
+
var _query = _asyncToGenerator(function* () {
|
|
132
|
+
return {
|
|
133
|
+
rows: []
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
function query() {
|
|
138
|
+
return _query.apply(this, arguments);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return query;
|
|
142
|
+
}()
|
|
143
|
+
};
|
|
144
|
+
return db;
|
|
145
|
+
} // Opted in wholesale: per-resource resolution has its own tests further down.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
function createDatabase(durationMs) {
|
|
149
|
+
var env = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
150
|
+
var db = instanceOf(loadDriver(durationMs, env));
|
|
151
|
+
db.auto_optimize = true;
|
|
152
|
+
return db;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
test("auto-optimization only starts above the default 3-second threshold",
|
|
156
|
+
/*#__PURE__*/
|
|
157
|
+
_asyncToGenerator(function* () {
|
|
158
|
+
for (var duration of [1001, 2999, 3000, 3001]) {
|
|
159
|
+
var db = createDatabase(duration);
|
|
160
|
+
var optimizations = 0;
|
|
161
|
+
db.optimize =
|
|
162
|
+
/*#__PURE__*/
|
|
163
|
+
_asyncToGenerator(function* () {
|
|
164
|
+
optimizations += 1;
|
|
165
|
+
});
|
|
166
|
+
yield db.query("SELECT name FROM users WHERE status = 'active'");
|
|
167
|
+
assert.equal(optimizations, duration > 3000 ? 1 : 0, "".concat(duration, "ms"));
|
|
168
|
+
}
|
|
169
|
+
}));
|
|
170
|
+
test("the explicit slow-query threshold override remains supported",
|
|
171
|
+
/*#__PURE__*/
|
|
172
|
+
_asyncToGenerator(function* () {
|
|
173
|
+
var db = createDatabase(5001, {
|
|
174
|
+
SPICE_COUCHBASE_SLOW_QUERY_MS: "5000"
|
|
175
|
+
});
|
|
176
|
+
var optimizations = 0;
|
|
177
|
+
db.optimize =
|
|
178
|
+
/*#__PURE__*/
|
|
179
|
+
_asyncToGenerator(function* () {
|
|
180
|
+
optimizations += 1;
|
|
181
|
+
});
|
|
182
|
+
yield db.query("SELECT name FROM users WHERE status = 'active'");
|
|
183
|
+
assert.equal(optimizations, 1);
|
|
184
|
+
}));
|
|
185
|
+
test("Fetch plans for full-document projections never create indexes",
|
|
186
|
+
/*#__PURE__*/
|
|
187
|
+
_asyncToGenerator(function* () {
|
|
188
|
+
var _loop = function* _loop(projection) {
|
|
189
|
+
var db = createDatabase(0);
|
|
190
|
+
var statements = [];
|
|
191
|
+
var query = "SELECT ".concat(projection, " FROM users WHERE status = 'active' ORDER BY created_at DESC");
|
|
192
|
+
yield db.optimize(query, {
|
|
193
|
+
query: function () {
|
|
194
|
+
var _query2 = _asyncToGenerator(function* (statement) {
|
|
195
|
+
statements.push(statement);
|
|
196
|
+
return {
|
|
197
|
+
rows: [{
|
|
198
|
+
"#operator": "Fetch"
|
|
199
|
+
}]
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
function query(_x) {
|
|
204
|
+
return _query2.apply(this, arguments);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return query;
|
|
208
|
+
}()
|
|
209
|
+
});
|
|
210
|
+
assert.deepEqual(statements, ["EXPLAIN ".concat(query)]);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
for (var projection of ["*", "users.*", "`users`.*", "(`users`).*, META(`users`).id AS id"]) {
|
|
214
|
+
yield* _loop(projection);
|
|
215
|
+
}
|
|
216
|
+
}));
|
|
217
|
+
test("Fetch plans for explicit fields still create a covering index",
|
|
218
|
+
/*#__PURE__*/
|
|
219
|
+
_asyncToGenerator(function* () {
|
|
220
|
+
var db = createDatabase(0);
|
|
221
|
+
var scope = executionScope();
|
|
222
|
+
yield db.optimize("SELECT name FROM users WHERE status = 'active'", scope);
|
|
223
|
+
assert.equal(scope.calls.length, 3);
|
|
224
|
+
assert.match(scope.calls[1].statement, /FROM system:indexes/);
|
|
225
|
+
assert.equal(JSON.stringify(scope.calls[1].options.parameters), JSON.stringify(keyspace));
|
|
226
|
+
assert.match(scope.calls[2].statement, /^CREATE INDEX `adv_covered_/);
|
|
227
|
+
assert.match(scope.calls[2].statement, /`status`, `name`/);
|
|
228
|
+
}));
|
|
229
|
+
test("reordered selected columns and changed literals generate identical DDL", () => {
|
|
230
|
+
var original = buildCoveringIndex(lookupQuery, keyspace);
|
|
231
|
+
var reordered = buildCoveringIndex("SELECT email, name FROM users WHERE status = 'pending'", keyspace);
|
|
232
|
+
assert.equal(original, reordered);
|
|
233
|
+
});
|
|
234
|
+
test("adding explicit ascending order cannot rename an identical definition", () => {
|
|
235
|
+
var base = "SELECT status, created_at FROM users WHERE status = 'pending'";
|
|
236
|
+
assert.equal(buildCoveringIndex(base, keyspace), buildCoveringIndex("".concat(base, " ORDER BY created_at ASC"), keyspace));
|
|
237
|
+
});
|
|
238
|
+
test("genuinely different access-key order and direction keep distinct definitions", () => {
|
|
239
|
+
var base = "SELECT name, email FROM users WHERE status = 'active'";
|
|
240
|
+
var asc = buildCoveringIndex("".concat(base, " ORDER BY name, email"), keyspace);
|
|
241
|
+
var desc = buildCoveringIndex("".concat(base, " ORDER BY name DESC, email"), keyspace);
|
|
242
|
+
var reordered = buildCoveringIndex("".concat(base, " ORDER BY email, name"), keyspace);
|
|
243
|
+
assert.notEqual(asc, desc);
|
|
244
|
+
assert.notEqual(asc, reordered);
|
|
245
|
+
assert.notEqual(asc.match(/adv_covered_\w+/)[0], reordered.match(/adv_covered_\w+/)[0]);
|
|
246
|
+
});
|
|
247
|
+
test("case-distinct document fields are not collapsed", () => {
|
|
248
|
+
var statement = buildCoveringIndex("SELECT Name, name, Status, status FROM users ORDER BY Name DESC", keyspace);
|
|
249
|
+
assert.match(statement, /`Name` DESC/);
|
|
250
|
+
assert.match(statement, /`name`/);
|
|
251
|
+
assert.match(statement, /`Status`/);
|
|
252
|
+
assert.match(statement, /`status`/);
|
|
253
|
+
assert.doesNotMatch(statement, /`name` DESC/);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
var _loop2 = function _loop2(state) {
|
|
257
|
+
test("an existing ".concat(state, " index with reordered projection keys prevents creation"),
|
|
258
|
+
/*#__PURE__*/
|
|
259
|
+
_asyncToGenerator(function* () {
|
|
260
|
+
var scope = executionScope([existingIndex(["`deleted` INCLUDE MISSING", "`status`", "`name`", "`email`"], {
|
|
261
|
+
state
|
|
262
|
+
})]);
|
|
263
|
+
yield createDatabase(0).optimize(lookupQuery, scope);
|
|
264
|
+
assert.equal(scope.calls.length, 2);
|
|
265
|
+
assert.equal(createStatements(scope).length, 0);
|
|
266
|
+
}));
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
for (var state of ["online", "deferred", "pending", "building", "offline"]) {
|
|
270
|
+
_loop2(state);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
test("a wider existing index covers a projection subset without another index",
|
|
274
|
+
/*#__PURE__*/
|
|
275
|
+
_asyncToGenerator(function* () {
|
|
276
|
+
var scope = executionScope([existingIndex(["`deleted` INCLUDE MISSING", "`status`", "`name`", "`phone`", "`email` DESC"])]);
|
|
277
|
+
yield createDatabase(0).optimize(lookupQuery, scope);
|
|
278
|
+
assert.equal(createStatements(scope).length, 0);
|
|
279
|
+
}));
|
|
280
|
+
test("catalog key formatting, explicit ASC and attribute order are normalized",
|
|
281
|
+
/*#__PURE__*/
|
|
282
|
+
_asyncToGenerator(function* () {
|
|
283
|
+
for (var leadingKey of ["((`deleted`)) ASC INCLUDE MISSING", "deleted INCLUDE MISSING ASC"]) {
|
|
284
|
+
var scope = executionScope([existingIndex([leadingKey, "(status) ASC", "`name`", "email ASC"])]);
|
|
285
|
+
yield createDatabase(0).optimize(lookupQuery, scope);
|
|
286
|
+
assert.equal(createStatements(scope).length, 0, leadingKey);
|
|
287
|
+
}
|
|
288
|
+
}));
|
|
289
|
+
var incompatibleIndexes = [["missing a projection field", existingIndex(["`deleted` INCLUDE MISSING", "`status`", "`name`"])], ["different access prefix", existingIndex(["`deleted` INCLUDE MISSING", "`name`", "`status`", "`email`"])], ["missing-leading-key exclusion", existingIndex(["`deleted`", "`status`", "`name`", "`email`"])], ["case-distinct field", existingIndex(["`deleted` INCLUDE MISSING", "`status`", "`Name`", "`email`"])], ["expression instead of the field", existingIndex(["`deleted` INCLUDE MISSING", "`status`", "LOWER(`name`)", "`email`"])], ["array key", existingIndex(["`deleted` INCLUDE MISSING", "`status`", "`name`", "`email`", "DISTINCT ARRAY v FOR v IN tags END"])], ["partial index", existingIndex(["`deleted` INCLUDE MISSING", "`status`", "`name`", "`email`"], {
|
|
290
|
+
condition: "(`status` = 'pending')"
|
|
291
|
+
})], ["primary index", existingIndex([], {
|
|
292
|
+
is_primary: true
|
|
293
|
+
})]];
|
|
294
|
+
|
|
295
|
+
var _loop3 = function _loop3(reason, index) {
|
|
296
|
+
test("does not mistake ".concat(reason, " for a compatible covering index"),
|
|
297
|
+
/*#__PURE__*/
|
|
298
|
+
_asyncToGenerator(function* () {
|
|
299
|
+
var scope = executionScope([index]);
|
|
300
|
+
yield createDatabase(0).optimize(lookupQuery, scope);
|
|
301
|
+
assert.equal(createStatements(scope).length, 1);
|
|
302
|
+
}));
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
for (var [reason, index] of incompatibleIndexes) {
|
|
306
|
+
_loop3(reason, index);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
test("existing opposite sort direction does not satisfy the requested sort",
|
|
310
|
+
/*#__PURE__*/
|
|
311
|
+
_asyncToGenerator(function* () {
|
|
312
|
+
var query = "".concat(lookupQuery, " ORDER BY created_at DESC");
|
|
313
|
+
var scope = executionScope([existingIndex(["`deleted` INCLUDE MISSING", "`status`", "`created_at`", "`name`", "`email`"])]);
|
|
314
|
+
yield createDatabase(0).optimize(query, scope);
|
|
315
|
+
assert.equal(createStatements(scope).length, 1);
|
|
316
|
+
assert.match(createStatements(scope)[0].statement, /`created_at` DESC/);
|
|
317
|
+
}));
|
|
318
|
+
test("same generated name is never recreated even if its keys are unavailable",
|
|
319
|
+
/*#__PURE__*/
|
|
320
|
+
_asyncToGenerator(function* () {
|
|
321
|
+
var candidate = buildCoveringIndexDefinition(lookupQuery, keyspace);
|
|
322
|
+
var scope = executionScope([{
|
|
323
|
+
name: candidate.name,
|
|
324
|
+
state: "building"
|
|
325
|
+
}]);
|
|
326
|
+
yield createDatabase(0).optimize(lookupQuery, scope);
|
|
327
|
+
assert.equal(createStatements(scope).length, 0);
|
|
328
|
+
}));
|
|
329
|
+
test("catalog failure or incomplete results prevent creation and trigger cooldown",
|
|
330
|
+
/*#__PURE__*/
|
|
331
|
+
_asyncToGenerator(function* () {
|
|
332
|
+
var _loop4 = function* _loop4(options) {
|
|
333
|
+
var db = createDatabase(3001);
|
|
334
|
+
var scope = executionScope([], options);
|
|
335
|
+
yield db.optimize(lookupQuery, scope);
|
|
336
|
+
assert.equal(createStatements(scope).length, 0);
|
|
337
|
+
var retries = 0;
|
|
338
|
+
db.optimize =
|
|
339
|
+
/*#__PURE__*/
|
|
340
|
+
_asyncToGenerator(function* () {
|
|
341
|
+
retries++;
|
|
342
|
+
});
|
|
343
|
+
yield db.query("SELECT email FROM users WHERE status = 'pending'");
|
|
344
|
+
assert.equal(retries, 0);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
for (var options of [{
|
|
348
|
+
inventoryError: new Error("query service unavailable")
|
|
349
|
+
}, {
|
|
350
|
+
inventoryResponse: {}
|
|
351
|
+
}, {
|
|
352
|
+
inventoryResponse: {
|
|
353
|
+
rows: [],
|
|
354
|
+
meta: {
|
|
355
|
+
warnings: [{
|
|
356
|
+
message: "partial inventory"
|
|
357
|
+
}]
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}]) {
|
|
361
|
+
yield* _loop4(options);
|
|
362
|
+
}
|
|
363
|
+
}));
|
|
364
|
+
test("already-covered plans need neither an inventory lookup nor creation",
|
|
365
|
+
/*#__PURE__*/
|
|
366
|
+
_asyncToGenerator(function* () {
|
|
367
|
+
var scope = executionScope([], {
|
|
368
|
+
plan: {
|
|
369
|
+
"#operator": "IndexScan3"
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
yield createDatabase(0).optimize(lookupQuery, scope);
|
|
373
|
+
assert.equal(scope.calls.length, 1);
|
|
374
|
+
}));
|
|
375
|
+
test("a default collection uses default names in its inventory lookup",
|
|
376
|
+
/*#__PURE__*/
|
|
377
|
+
_asyncToGenerator(function* () {
|
|
378
|
+
var db = createDatabase(0);
|
|
379
|
+
db.scope_name = undefined;
|
|
380
|
+
db.collection_name = undefined;
|
|
381
|
+
var scope = executionScope();
|
|
382
|
+
yield db.optimize("SELECT name FROM saturn WHERE status = 'active'", scope);
|
|
383
|
+
assert.equal(JSON.stringify(scope.calls[1].options.parameters), JSON.stringify({
|
|
384
|
+
bucket: "saturn",
|
|
385
|
+
scope: "_default",
|
|
386
|
+
collection: "_default"
|
|
387
|
+
}));
|
|
388
|
+
assert.match(createStatements(scope)[0].statement, /`saturn`\.`_default`\.`_default`/);
|
|
389
|
+
})); // ---- Per-resource opt-in for auto cover indexing ----
|
|
390
|
+
// Nothing decided at instance level: resolution falls through to the call,
|
|
391
|
+
// the application policy and the static lists.
|
|
392
|
+
|
|
393
|
+
function createUnconfiguredDatabase() {
|
|
394
|
+
var env = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
395
|
+
return instanceOf(loadDriver(3001, env));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function optimizationsFor(_x2, _x3) {
|
|
399
|
+
return _optimizationsFor.apply(this, arguments);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function _optimizationsFor() {
|
|
403
|
+
_optimizationsFor = _asyncToGenerator(function* (db, statement) {
|
|
404
|
+
var optimizations = 0;
|
|
405
|
+
db.optimize =
|
|
406
|
+
/*#__PURE__*/
|
|
407
|
+
_asyncToGenerator(function* () {
|
|
408
|
+
optimizations += 1;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
for (var _len = arguments.length, queryArgs = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
|
|
412
|
+
queryArgs[_key - 2] = arguments[_key];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
yield db.query(statement, ...queryArgs);
|
|
416
|
+
return optimizations;
|
|
417
|
+
});
|
|
418
|
+
return _optimizationsFor.apply(this, arguments);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
var slowLookup = "SELECT name FROM users WHERE status = 'active'";
|
|
422
|
+
var EVERY_RESOURCE = {
|
|
423
|
+
SPICE_COUCHBASE_OPTIMIZE_RESOURCES: "*"
|
|
424
|
+
};
|
|
425
|
+
test("no resource is auto-indexed unless something opts it in",
|
|
426
|
+
/*#__PURE__*/
|
|
427
|
+
_asyncToGenerator(function* () {
|
|
428
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase(), slowLookup)), 0);
|
|
429
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase({
|
|
430
|
+
SPICE_COUCHBASE_OPTIMIZE_RESOURCES: ""
|
|
431
|
+
}), slowLookup)), 0);
|
|
432
|
+
}));
|
|
433
|
+
test("SPICE_COUCHBASE_OPTIMIZE_RESOURCES enables the instance's collection, case-insensitively",
|
|
434
|
+
/*#__PURE__*/
|
|
435
|
+
_asyncToGenerator(function* () {
|
|
436
|
+
var env = {
|
|
437
|
+
SPICE_COUCHBASE_OPTIMIZE_RESOURCES: " Users ,audit_log,, `sessions` "
|
|
438
|
+
};
|
|
439
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase(env), slowLookup)), 1);
|
|
440
|
+
var sessions = createUnconfiguredDatabase(env);
|
|
441
|
+
sessions.collection_name = "sessions";
|
|
442
|
+
assert.equal((yield optimizationsFor(sessions, "SELECT id FROM sessions")), 1);
|
|
443
|
+
var orders = createUnconfiguredDatabase(env);
|
|
444
|
+
orders.collection_name = "orders";
|
|
445
|
+
assert.equal((yield optimizationsFor(orders, "SELECT total FROM orders WHERE status = 'paid'")), 0);
|
|
446
|
+
}));
|
|
447
|
+
test("a wildcard restores auto indexing for every resource",
|
|
448
|
+
/*#__PURE__*/
|
|
449
|
+
_asyncToGenerator(function* () {
|
|
450
|
+
var db = createUnconfiguredDatabase(EVERY_RESOURCE);
|
|
451
|
+
db.collection_name = "anything";
|
|
452
|
+
assert.equal((yield optimizationsFor(db, "SELECT a FROM anything WHERE b = 1")), 1);
|
|
453
|
+
}));
|
|
454
|
+
test("legacy bucket mode resolves the resource from the type literal the driver adds",
|
|
455
|
+
/*#__PURE__*/
|
|
456
|
+
_asyncToGenerator(function* () {
|
|
457
|
+
var env = {
|
|
458
|
+
SPICE_COUCHBASE_OPTIMIZE_RESOURCES: "audit_log"
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
for (var [type, expected] of [["audit_log", 1], ["users", 0]]) {
|
|
462
|
+
var db = createUnconfiguredDatabase(env);
|
|
463
|
+
db.collection_name = "_default";
|
|
464
|
+
db.scope_name = "_default";
|
|
465
|
+
assert.equal((yield optimizationsFor(db, "SELECT actor FROM `saturn` WHERE type = '".concat(type, "' AND deleted = false"))), expected, type);
|
|
466
|
+
}
|
|
467
|
+
}));
|
|
468
|
+
test("a fully qualified or aliased FROM keyspace matches on its last segment",
|
|
469
|
+
/*#__PURE__*/
|
|
470
|
+
_asyncToGenerator(function* () {
|
|
471
|
+
var env = {
|
|
472
|
+
SPICE_COUCHBASE_OPTIMIZE_RESOURCES: "logs"
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
for (var statement of ["SELECT level FROM `default`:`saturn`.`app`.`logs` WHERE level = 'error'", "SELECT l.level FROM saturn.app.logs AS l WHERE l.level = 'error'", "SELECT u.name FROM users AS u JOIN `logs` AS l ON l.user_id = META(u).id"]) {
|
|
476
|
+
var db = createUnconfiguredDatabase(env);
|
|
477
|
+
db.collection_name = "_default";
|
|
478
|
+
assert.equal((yield optimizationsFor(db, statement)), 1, statement);
|
|
479
|
+
}
|
|
480
|
+
}));
|
|
481
|
+
test("connection or constructor lists work without the environment variable",
|
|
482
|
+
/*#__PURE__*/
|
|
483
|
+
_asyncToGenerator(function* () {
|
|
484
|
+
var db = createUnconfiguredDatabase();
|
|
485
|
+
db.enabled_resources = new Set(["users"]);
|
|
486
|
+
assert.equal((yield optimizationsFor(db, slowLookup)), 1);
|
|
487
|
+
}));
|
|
488
|
+
test("an explicit instance setting overrides every resource list",
|
|
489
|
+
/*#__PURE__*/
|
|
490
|
+
_asyncToGenerator(function* () {
|
|
491
|
+
var on = createUnconfiguredDatabase();
|
|
492
|
+
on.auto_optimize = true;
|
|
493
|
+
assert.equal((yield optimizationsFor(on, slowLookup)), 1);
|
|
494
|
+
var off = createUnconfiguredDatabase(EVERY_RESOURCE);
|
|
495
|
+
off.auto_optimize = false;
|
|
496
|
+
assert.equal((yield optimizationsFor(off, slowLookup)), 0);
|
|
497
|
+
}));
|
|
498
|
+
test("a single call can opt in or out through query options",
|
|
499
|
+
/*#__PURE__*/
|
|
500
|
+
_asyncToGenerator(function* () {
|
|
501
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase(), slowLookup, {
|
|
502
|
+
auto_optimize: true
|
|
503
|
+
})), 1);
|
|
504
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase(EVERY_RESOURCE), slowLookup, {
|
|
505
|
+
auto_optimize: false
|
|
506
|
+
})), 0);
|
|
507
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase(EVERY_RESOURCE), slowLookup, "cluster", {
|
|
508
|
+
optimize: false
|
|
509
|
+
})), 0); // The scan-consistency shorthand is not an opt-in or opt-out.
|
|
510
|
+
|
|
511
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase(EVERY_RESOURCE), slowLookup, "cluster", true)), 1);
|
|
512
|
+
assert.equal((yield optimizationsFor(createUnconfiguredDatabase(), slowLookup, "cluster", true)), 0);
|
|
513
|
+
}));
|
|
514
|
+
test("an application policy decides per resource and sees the statement context",
|
|
515
|
+
/*#__PURE__*/
|
|
516
|
+
_asyncToGenerator(function* () {
|
|
517
|
+
var Database = loadDriver(3001);
|
|
518
|
+
var seen = [];
|
|
519
|
+
Database.setAutoOptimizePolicy((resource, context) => {
|
|
520
|
+
seen.push([resource, context]);
|
|
521
|
+
return resource === "users";
|
|
522
|
+
});
|
|
523
|
+
assert.equal(Database.getAutoOptimizePolicy().length, 2);
|
|
524
|
+
var users = instanceOf(Database);
|
|
525
|
+
users.connection_name = "default";
|
|
526
|
+
assert.equal((yield optimizationsFor(users, slowLookup)), 1); // Objects cross the vm boundary, so compare by value rather than prototype.
|
|
527
|
+
|
|
528
|
+
assert.equal(JSON.stringify(seen[0]), JSON.stringify(["users", {
|
|
529
|
+
query: slowLookup,
|
|
530
|
+
connection: "default",
|
|
531
|
+
collection: "users"
|
|
532
|
+
}]));
|
|
533
|
+
var orders = instanceOf(Database);
|
|
534
|
+
orders.collection_name = "orders";
|
|
535
|
+
assert.equal((yield optimizationsFor(orders, "SELECT total FROM orders WHERE status = 'paid'")), 0);
|
|
536
|
+
}));
|
|
537
|
+
test("a policy that returns undefined defers to the static lists",
|
|
538
|
+
/*#__PURE__*/
|
|
539
|
+
_asyncToGenerator(function* () {
|
|
540
|
+
for (var [env, expected] of [[{
|
|
541
|
+
SPICE_COUCHBASE_OPTIMIZE_RESOURCES: "users"
|
|
542
|
+
}, 1], [{}, 0]]) {
|
|
543
|
+
var Database = loadDriver(3001, env);
|
|
544
|
+
Database.setAutoOptimizePolicy(() => undefined);
|
|
545
|
+
assert.equal((yield optimizationsFor(instanceOf(Database), slowLookup)), expected);
|
|
546
|
+
}
|
|
547
|
+
}));
|
|
548
|
+
test("an explicit policy refusal wins over the wildcard, and a throwing policy counts as a refusal",
|
|
549
|
+
/*#__PURE__*/
|
|
550
|
+
_asyncToGenerator(function* () {
|
|
551
|
+
for (var policy of [() => false, () => {
|
|
552
|
+
throw new Error("settings unavailable");
|
|
553
|
+
}]) {
|
|
554
|
+
var Database = loadDriver(3001, EVERY_RESOURCE);
|
|
555
|
+
Database.setAutoOptimizePolicy(policy);
|
|
556
|
+
assert.equal((yield optimizationsFor(instanceOf(Database), slowLookup)), 0);
|
|
557
|
+
}
|
|
558
|
+
}));
|
|
559
|
+
test("the policy can be cleared and must be a function",
|
|
560
|
+
/*#__PURE__*/
|
|
561
|
+
_asyncToGenerator(function* () {
|
|
562
|
+
var Database = loadDriver(3001, EVERY_RESOURCE);
|
|
563
|
+
Database.setAutoOptimizePolicy(() => false);
|
|
564
|
+
Database.setAutoOptimizePolicy(null);
|
|
565
|
+
assert.equal(Database.getAutoOptimizePolicy(), null);
|
|
566
|
+
assert.equal((yield optimizationsFor(instanceOf(Database), slowLookup)), 1);
|
|
567
|
+
assert.throws(() => Database.setAutoOptimizePolicy("users"), {
|
|
568
|
+
name: "TypeError"
|
|
569
|
+
});
|
|
570
|
+
}));
|
|
571
|
+
test("a declined statement is not remembered as already optimised",
|
|
572
|
+
/*#__PURE__*/
|
|
573
|
+
_asyncToGenerator(function* () {
|
|
574
|
+
var db = createUnconfiguredDatabase();
|
|
575
|
+
assert.equal((yield optimizationsFor(db, slowLookup)), 0);
|
|
576
|
+
db.auto_optimize = true;
|
|
577
|
+
assert.equal((yield optimizationsFor(db, slowLookup)), 1);
|
|
578
|
+
}));
|