spice-couchbase 2.0.56 → 2.0.59

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.
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ /**
3
+ * Regression tests for the WHERE clause shared by the data and count queries in `search()`.
4
+ *
5
+ * The count query used to be built from a `where_clause` that held the bare literal `" WHERE "`,
6
+ * while the conditions were appended only to the data query. Every `do_count` request therefore
7
+ * emitted `... FROM \`bucket\` WHERE` and Couchbase answered
8
+ * "syntax error - at end of input" (code 3000) with a 400 — on every resource, for every caller.
9
+ *
10
+ * It survived the existing suite because nothing exercised the count statement.
11
+ */
12
+
13
+ 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); } }
14
+
15
+ 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); }); }; }
16
+
17
+ var assert = require("assert");
18
+
19
+ var Database = require("./index");
20
+
21
+ var passed = 0;
22
+ var failed = 0;
23
+ /** Captures the two statements `search()` produces without touching a cluster. */
24
+
25
+ function createSearchDb() {
26
+ var statements = {
27
+ data: null,
28
+ count: null
29
+ };
30
+ var db = Object.create(Database.prototype);
31
+ db.use_bucket_name = false;
32
+ db.bucket = "test";
33
+ db.scope_name = "scope";
34
+ db.collection_name = "platformtask";
35
+
36
+ db.getData =
37
+ /*#__PURE__*/
38
+ function () {
39
+ var _ref = _asyncToGenerator(function* (statement) {
40
+ statements.data = statement;
41
+ return [];
42
+ });
43
+
44
+ return function (_x) {
45
+ return _ref.apply(this, arguments);
46
+ };
47
+ }();
48
+
49
+ db.getCount =
50
+ /*#__PURE__*/
51
+ function () {
52
+ var _ref2 = _asyncToGenerator(function* (statement) {
53
+ statements.count = statement;
54
+ return 0;
55
+ });
56
+
57
+ return function (_x2) {
58
+ return _ref2.apply(this, arguments);
59
+ };
60
+ }();
61
+
62
+ db.optimize = () => {};
63
+
64
+ return {
65
+ db,
66
+ statements
67
+ };
68
+ }
69
+
70
+ function test(_x3, _x4) {
71
+ return _test.apply(this, arguments);
72
+ }
73
+
74
+ function _test() {
75
+ _test = _asyncToGenerator(function* (name, fn) {
76
+ try {
77
+ yield fn();
78
+ console.log("\u2713 ".concat(name));
79
+ passed += 1;
80
+ } catch (error) {
81
+ console.log("\u2717 ".concat(name));
82
+ console.log(" Error: ".concat(error.stack || error.message));
83
+ failed += 1;
84
+ }
85
+ });
86
+ return _test.apply(this, arguments);
87
+ }
88
+
89
+ function run() {
90
+ return _run.apply(this, arguments);
91
+ }
92
+
93
+ function _run() {
94
+ _run = _asyncToGenerator(function* () {
95
+ console.log("\n--- count query tests ---");
96
+ yield test("the count query carries the predicates, not a dangling WHERE",
97
+ /*#__PURE__*/
98
+ _asyncToGenerator(function* () {
99
+ var {
100
+ db,
101
+ statements
102
+ } = createSearchDb();
103
+ yield db.search("platformtask", "id", "status = \"pending\"", 1, 0, null, "true");
104
+ assert.ok(statements.count.includes("status = \"pending\""), "count query lost its predicate: ".concat(statements.count));
105
+ assert.ok(!/WHERE\s*$/.test(statements.count.trim()), "count query ends in a dangling WHERE: ".concat(statements.count));
106
+ }));
107
+ yield test("multiple conditions are joined with AND in the count query",
108
+ /*#__PURE__*/
109
+ _asyncToGenerator(function* () {
110
+ var {
111
+ db,
112
+ statements
113
+ } = createSearchDb();
114
+ yield db.search("platformtask", "id", "status = \"pending\" AND assignee_type = \"user\"", 1, 0, null, "true");
115
+ assert.ok(statements.count.includes("status = \"pending\""));
116
+ assert.ok(statements.count.includes("assignee_type = \"user\""));
117
+ }));
118
+ yield test("an unfiltered count emits no WHERE at all",
119
+ /*#__PURE__*/
120
+ _asyncToGenerator(function* () {
121
+ var {
122
+ db,
123
+ statements
124
+ } = createSearchDb();
125
+ yield db.search("platformtask", "id", "", 1, 0, null, "true");
126
+ assert.ok(!/WHERE/i.test(statements.count), "unfiltered count should have no WHERE: ".concat(statements.count));
127
+ }));
128
+ yield test("the data and count queries agree on the WHERE clause",
129
+ /*#__PURE__*/
130
+ _asyncToGenerator(function* () {
131
+ var {
132
+ db,
133
+ statements
134
+ } = createSearchDb();
135
+ yield db.search("platformtask", "id", "status = \"blocked\"", 1, 0, null, "true");
136
+
137
+ var whereOf = sql => {
138
+ var m = sql.match(/WHERE[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+([\s\S]*?)(?:[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+ORDER BY|[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+LIMIT|[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+OFFSET|$)/i);
139
+ return m ? m[1].trim() : null;
140
+ };
141
+
142
+ assert.strictEqual(whereOf(statements.data), whereOf(statements.count));
143
+ }));
144
+ yield test("no count statement is produced when do_count is off",
145
+ /*#__PURE__*/
146
+ _asyncToGenerator(function* () {
147
+ var {
148
+ db,
149
+ statements
150
+ } = createSearchDb();
151
+ yield db.search("platformtask", "id", "status = \"pending\"", 1, 0, null, "false");
152
+ assert.strictEqual(statements.count, null);
153
+ assert.ok(statements.data.includes("status = \"pending\""));
154
+ }));
155
+ console.log("\n========================================");
156
+ console.log("Tests: ".concat(passed + failed, " | Passed: ").concat(passed, " | Failed: ").concat(failed));
157
+ console.log("========================================");
158
+ if (failed > 0) process.exit(1);
159
+ });
160
+ return _run.apply(this, arguments);
161
+ }
162
+
163
+ run();
package/build/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ var _process$env$SPICE_CO;
4
+
3
5
  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; }
4
6
 
5
7
  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; }
@@ -33,7 +35,38 @@ if (!Promise.allSettled) {
33
35
  } // Couchbase lookupIn has a limit of 16 operations per call
34
36
 
35
37
 
36
- var LOOKUP_IN_MAX_SPECS = 16;
38
+ var LOOKUP_IN_MAX_SPECS = 16; // Auto-optimisation guard rails. `query()` fires `optimize()` (an EXPLAIN plus
39
+ // a possible CREATE INDEX) for any statement slower than SLOW_QUERY_MS. When
40
+ // the cluster is degraded every statement is slow, so without limits each
41
+ // query spawned two more heavy ones and deepened the outage. These caps make it
42
+ // a bounded, once-per-statement background task that backs off on failure.
43
+
44
+ var SLOW_QUERY_MS = Number(process.env.SPICE_COUCHBASE_SLOW_QUERY_MS || 1000);
45
+ var AUTO_OPTIMIZE_ENABLED = !["0", "false", "no", "off"].includes(String((_process$env$SPICE_CO = process.env.SPICE_COUCHBASE_AUTO_OPTIMIZE) !== null && _process$env$SPICE_CO !== void 0 ? _process$env$SPICE_CO : "true").trim().toLowerCase());
46
+ var OPTIMIZE_MAX_INFLIGHT = Number(process.env.SPICE_COUCHBASE_OPTIMIZE_MAX_INFLIGHT || 1);
47
+ var OPTIMIZE_FAILURE_COOLDOWN_MS = Number(process.env.SPICE_COUCHBASE_OPTIMIZE_COOLDOWN_MS || 5 * 60 * 1000); // Statements already optimised in this process. Bounded so it cannot grow
48
+ // without limit on a workload with many distinct literals.
49
+
50
+ var OPTIMIZE_SEEN_LIMIT = 1000;
51
+ var optimizeState = {
52
+ inFlight: 0,
53
+ seen: new Set(),
54
+ cooldownUntil: 0
55
+ };
56
+
57
+ function shouldOptimize(query) {
58
+ if (!AUTO_OPTIMIZE_ENABLED) return false;
59
+ if (optimizeState.inFlight >= OPTIMIZE_MAX_INFLIGHT) return false;
60
+ if (Date.now() < optimizeState.cooldownUntil) return false;
61
+ if (optimizeState.seen.has(query)) return false;
62
+
63
+ if (optimizeState.seen.size >= OPTIMIZE_SEEN_LIMIT) {
64
+ optimizeState.seen.delete(optimizeState.seen.values().next().value);
65
+ }
66
+
67
+ optimizeState.seen.add(query);
68
+ return true;
69
+ }
37
70
 
38
71
  function normalizeScanConsistency(value) {
39
72
  if (value === true || value === QueryScanConsistency.RequestPlus || typeof value === "string" && ["true", "request_plus"].includes(value.toLowerCase())) {
@@ -704,10 +737,15 @@ module.exports = class Database {
704
737
 
705
738
  if (query) {
706
739
  conditions.push(query);
707
- }
740
+ } // The conditions are folded into `where_clause` itself so that both queries below are built
741
+ // from one string. Previously this held the bare literal " WHERE " and only the data query
742
+ // appended `conditions.join(" AND ")`, which left the count query emitting a dangling
743
+ // `WHERE` — Couchbase answered "syntax error - at end of input" and every `do_count=true`
744
+ // request failed with a 400, on every resource.
708
745
 
709
- var where_clause = conditions.length ? " WHERE " : "";
710
- query = front_query + where_clause + conditions.join(" AND ");
746
+
747
+ var where_clause = conditions.length ? " WHERE ".concat(conditions.join(" AND ")) : "";
748
+ query = front_query + where_clause;
711
749
  var count_query = "SELECT count(meta(".concat(bucket, ").id) as count FROM ").concat(bucket, " ").concat(join, " ").concat(where_clause); //console.log("Cound Query::", count_query);
712
750
 
713
751
  var order_by_clause = order_by ? " ORDER BY ".concat(order_by) : "";
@@ -863,6 +901,8 @@ module.exports = class Database {
863
901
  var _this19 = this;
864
902
 
865
903
  return _asyncToGenerator(function* () {
904
+ optimizeState.inFlight += 1;
905
+
866
906
  try {
867
907
  var _explainResponse$rows;
868
908
 
@@ -889,9 +929,15 @@ module.exports = class Database {
889
929
  }); // 4. Execute the index creation if we got a valid statement
890
930
 
891
931
  if (indexStatement) {
892
- var response = yield exec_scope.query(indexStatement);
932
+ yield exec_scope.query(indexStatement);
893
933
  }
894
- } catch (e) {// Silently handle errors (index may already exist, etc.)
934
+ } catch (e) {
935
+ // A failure here (timeout, index already exists, cluster busy) almost
936
+ // always means the cluster is struggling. Stop adding EXPLAIN and
937
+ // CREATE INDEX load for a while rather than piling on.
938
+ optimizeState.cooldownUntil = Date.now() + OPTIMIZE_FAILURE_COOLDOWN_MS;
939
+ } finally {
940
+ optimizeState.inFlight -= 1;
895
941
  }
896
942
  })();
897
943
  }
@@ -924,10 +970,11 @@ module.exports = class Database {
924
970
 
925
971
  var endTime = process.hrtime.bigint(); // Calculate duration in microseconds
926
972
 
927
- var durationInMilliseconds = (endTime - startTime) / BigInt(1000000); // optimize if response time is greater than 1 second
973
+ var durationInMilliseconds = (endTime - startTime) / BigInt(1000000); // Optimise slow statements in the background, subject to the guard rails
974
+ // declared at the top of this file. Never awaited, never allowed to reject.
928
975
 
929
- if (durationInMilliseconds > 1000) {
930
- _this20.optimize(query, exec_scope);
976
+ if (durationInMilliseconds > SLOW_QUERY_MS && shouldOptimize(sanitizedQuery)) {
977
+ _this20.optimize(sanitizedQuery, exec_scope).catch(() => {});
931
978
  }
932
979
 
933
980
  return response;
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "spice-couchbase",
3
- "version": "2.0.56",
3
+ "version": "2.0.59",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
7
7
  "test": "node src/utility.test.js",
8
- "test:query-consistency": "node src/index.query-consistency.test.js"
8
+ "test:query-consistency": "node src/index.query-consistency.test.js",
9
+ "test:count-query": "node src/index.count-query.test.js"
9
10
  },
10
11
  "author": "Chad Fraser <chad@sonover.com> (http://sonover.com)",
11
12
  "license": "ISC",
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Regression tests for the WHERE clause shared by the data and count queries in `search()`.
5
+ *
6
+ * The count query used to be built from a `where_clause` that held the bare literal `" WHERE "`,
7
+ * while the conditions were appended only to the data query. Every `do_count` request therefore
8
+ * emitted `... FROM \`bucket\` WHERE` and Couchbase answered
9
+ * "syntax error - at end of input" (code 3000) with a 400 — on every resource, for every caller.
10
+ *
11
+ * It survived the existing suite because nothing exercised the count statement.
12
+ */
13
+
14
+ const assert = require("assert");
15
+ const Database = require("./index");
16
+
17
+ let passed = 0;
18
+ let failed = 0;
19
+
20
+ /** Captures the two statements `search()` produces without touching a cluster. */
21
+ function createSearchDb() {
22
+ const statements = { data: null, count: null };
23
+ const db = Object.create(Database.prototype);
24
+
25
+ db.use_bucket_name = false;
26
+ db.bucket = "test";
27
+ db.scope_name = "scope";
28
+ db.collection_name = "platformtask";
29
+
30
+ db.getData = async (statement) => {
31
+ statements.data = statement;
32
+ return [];
33
+ };
34
+ db.getCount = async (statement) => {
35
+ statements.count = statement;
36
+ return 0;
37
+ };
38
+ db.optimize = () => {};
39
+
40
+ return { db, statements };
41
+ }
42
+
43
+ async function test(name, fn) {
44
+ try {
45
+ await fn();
46
+ console.log(`✓ ${name}`);
47
+ passed += 1;
48
+ } catch (error) {
49
+ console.log(`✗ ${name}`);
50
+ console.log(` Error: ${error.stack || error.message}`);
51
+ failed += 1;
52
+ }
53
+ }
54
+
55
+ async function run() {
56
+ console.log("\n--- count query tests ---");
57
+
58
+ await test("the count query carries the predicates, not a dangling WHERE", async () => {
59
+ const { db, statements } = createSearchDb();
60
+
61
+ await db.search("platformtask", "id", `status = "pending"`, 1, 0, null, "true");
62
+
63
+ assert.ok(
64
+ statements.count.includes(`status = "pending"`),
65
+ `count query lost its predicate: ${statements.count}`
66
+ );
67
+ assert.ok(
68
+ !/WHERE\s*$/.test(statements.count.trim()),
69
+ `count query ends in a dangling WHERE: ${statements.count}`
70
+ );
71
+ });
72
+
73
+ await test("multiple conditions are joined with AND in the count query", async () => {
74
+ const { db, statements } = createSearchDb();
75
+
76
+ await db.search(
77
+ "platformtask",
78
+ "id",
79
+ `status = "pending" AND assignee_type = "user"`,
80
+ 1,
81
+ 0,
82
+ null,
83
+ "true"
84
+ );
85
+
86
+ assert.ok(statements.count.includes(`status = "pending"`));
87
+ assert.ok(statements.count.includes(`assignee_type = "user"`));
88
+ });
89
+
90
+ await test("an unfiltered count emits no WHERE at all", async () => {
91
+ const { db, statements } = createSearchDb();
92
+
93
+ await db.search("platformtask", "id", "", 1, 0, null, "true");
94
+
95
+ assert.ok(
96
+ !/WHERE/i.test(statements.count),
97
+ `unfiltered count should have no WHERE: ${statements.count}`
98
+ );
99
+ });
100
+
101
+ await test("the data and count queries agree on the WHERE clause", async () => {
102
+ const { db, statements } = createSearchDb();
103
+
104
+ await db.search("platformtask", "id", `status = "blocked"`, 1, 0, null, "true");
105
+
106
+ const whereOf = (sql) => {
107
+ const m = sql.match(/WHERE\s+(.*?)(?:\s+ORDER BY|\s+LIMIT|\s+OFFSET|$)/is);
108
+ return m ? m[1].trim() : null;
109
+ };
110
+ assert.strictEqual(whereOf(statements.data), whereOf(statements.count));
111
+ });
112
+
113
+ await test("no count statement is produced when do_count is off", async () => {
114
+ const { db, statements } = createSearchDb();
115
+
116
+ await db.search("platformtask", "id", `status = "pending"`, 1, 0, null, "false");
117
+
118
+ assert.strictEqual(statements.count, null);
119
+ assert.ok(statements.data.includes(`status = "pending"`));
120
+ });
121
+
122
+ console.log("\n========================================");
123
+ console.log(`Tests: ${passed + failed} | Passed: ${passed} | Failed: ${failed}`);
124
+ console.log("========================================");
125
+
126
+ if (failed > 0) process.exit(1);
127
+ }
128
+
129
+ run();
package/src/index.js CHANGED
@@ -25,6 +25,44 @@ if (!Promise.allSettled) {
25
25
  // Couchbase lookupIn has a limit of 16 operations per call
26
26
  const LOOKUP_IN_MAX_SPECS = 16;
27
27
 
28
+ // Auto-optimisation guard rails. `query()` fires `optimize()` (an EXPLAIN plus
29
+ // a possible CREATE INDEX) for any statement slower than SLOW_QUERY_MS. When
30
+ // the cluster is degraded every statement is slow, so without limits each
31
+ // query spawned two more heavy ones and deepened the outage. These caps make it
32
+ // a bounded, once-per-statement background task that backs off on failure.
33
+ const SLOW_QUERY_MS = Number(process.env.SPICE_COUCHBASE_SLOW_QUERY_MS || 1000);
34
+ const AUTO_OPTIMIZE_ENABLED = !["0", "false", "no", "off"].includes(
35
+ String(process.env.SPICE_COUCHBASE_AUTO_OPTIMIZE ?? "true")
36
+ .trim()
37
+ .toLowerCase(),
38
+ );
39
+ const OPTIMIZE_MAX_INFLIGHT = Number(
40
+ process.env.SPICE_COUCHBASE_OPTIMIZE_MAX_INFLIGHT || 1,
41
+ );
42
+ const OPTIMIZE_FAILURE_COOLDOWN_MS = Number(
43
+ process.env.SPICE_COUCHBASE_OPTIMIZE_COOLDOWN_MS || 5 * 60 * 1000,
44
+ );
45
+ // Statements already optimised in this process. Bounded so it cannot grow
46
+ // without limit on a workload with many distinct literals.
47
+ const OPTIMIZE_SEEN_LIMIT = 1000;
48
+ const optimizeState = {
49
+ inFlight: 0,
50
+ seen: new Set(),
51
+ cooldownUntil: 0,
52
+ };
53
+
54
+ function shouldOptimize(query) {
55
+ if (!AUTO_OPTIMIZE_ENABLED) return false;
56
+ if (optimizeState.inFlight >= OPTIMIZE_MAX_INFLIGHT) return false;
57
+ if (Date.now() < optimizeState.cooldownUntil) return false;
58
+ if (optimizeState.seen.has(query)) return false;
59
+ if (optimizeState.seen.size >= OPTIMIZE_SEEN_LIMIT) {
60
+ optimizeState.seen.delete(optimizeState.seen.values().next().value);
61
+ }
62
+ optimizeState.seen.add(query);
63
+ return true;
64
+ }
65
+
28
66
  function normalizeScanConsistency(value) {
29
67
  if (
30
68
  value === true ||
@@ -671,8 +709,15 @@ module.exports = class Database {
671
709
  conditions.push(query);
672
710
  }
673
711
 
674
- const where_clause = conditions.length ? " WHERE " : "";
675
- query = front_query + where_clause + conditions.join(" AND ");
712
+ // The conditions are folded into `where_clause` itself so that both queries below are built
713
+ // from one string. Previously this held the bare literal " WHERE " and only the data query
714
+ // appended `conditions.join(" AND ")`, which left the count query emitting a dangling
715
+ // `WHERE` — Couchbase answered "syntax error - at end of input" and every `do_count=true`
716
+ // request failed with a 400, on every resource.
717
+ const where_clause = conditions.length
718
+ ? ` WHERE ${conditions.join(" AND ")}`
719
+ : "";
720
+ query = front_query + where_clause;
676
721
 
677
722
  const count_query = `SELECT count(meta(${bucket}).id) as count FROM ${bucket} ${join} ${where_clause}`;
678
723
  //console.log("Cound Query::", count_query);
@@ -809,6 +854,7 @@ module.exports = class Database {
809
854
  }
810
855
 
811
856
  async optimize(query, exec_scope) {
857
+ optimizeState.inFlight += 1;
812
858
  try {
813
859
  const { buildCoveringIndex } = require("./utility");
814
860
 
@@ -832,10 +878,15 @@ module.exports = class Database {
832
878
 
833
879
  // 4. Execute the index creation if we got a valid statement
834
880
  if (indexStatement) {
835
- const response = await exec_scope.query(indexStatement);
881
+ await exec_scope.query(indexStatement);
836
882
  }
837
883
  } catch (e) {
838
- // Silently handle errors (index may already exist, etc.)
884
+ // A failure here (timeout, index already exists, cluster busy) almost
885
+ // always means the cluster is struggling. Stop adding EXPLAIN and
886
+ // CREATE INDEX load for a while rather than piling on.
887
+ optimizeState.cooldownUntil = Date.now() + OPTIMIZE_FAILURE_COOLDOWN_MS;
888
+ } finally {
889
+ optimizeState.inFlight -= 1;
839
890
  }
840
891
  }
841
892
 
@@ -866,9 +917,13 @@ module.exports = class Database {
866
917
  let endTime = process.hrtime.bigint();
867
918
  // Calculate duration in microseconds
868
919
  let durationInMilliseconds = (endTime - startTime) / BigInt(1000000);
869
- // optimize if response time is greater than 1 second
870
- if (durationInMilliseconds > 1000) {
871
- this.optimize(query, exec_scope);
920
+ // Optimise slow statements in the background, subject to the guard rails
921
+ // declared at the top of this file. Never awaited, never allowed to reject.
922
+ if (
923
+ durationInMilliseconds > SLOW_QUERY_MS &&
924
+ shouldOptimize(sanitizedQuery)
925
+ ) {
926
+ this.optimize(sanitizedQuery, exec_scope).catch(() => {});
872
927
  }
873
928
  return response;
874
929
  }