spice-couchbase 2.0.45 → 2.0.46
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.js +73 -17
- package/build/utility.js +352 -0
- package/build/utility.test.js +784 -0
- package/package.json +2 -2
- package/src/index.js +79 -30
- package/src/utility.js +368 -0
- package/src/utility.test.js +986 -0
package/build/index.js
CHANGED
|
@@ -757,40 +757,96 @@ module.exports = class Database {
|
|
|
757
757
|
return response.rows;
|
|
758
758
|
})();
|
|
759
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Recursively checks if the query plan contains a Fetch operator
|
|
762
|
+
* @param {Object} plan - The EXPLAIN query plan
|
|
763
|
+
* @returns {boolean} - True if Fetch operator is found
|
|
764
|
+
*/
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
hasFetchOperator(plan) {
|
|
768
|
+
if (!plan || typeof plan !== "object") {
|
|
769
|
+
return false;
|
|
770
|
+
} // Check if this node is a Fetch operator
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
if (plan["#operator"] === "Fetch") {
|
|
774
|
+
return true;
|
|
775
|
+
} // Recursively check all child properties
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
for (var key of Object.keys(plan)) {
|
|
779
|
+
var value = plan[key];
|
|
780
|
+
|
|
781
|
+
if (Array.isArray(value)) {
|
|
782
|
+
for (var item of value) {
|
|
783
|
+
if (this.hasFetchOperator(item)) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
} else if (typeof value === "object" && value !== null) {
|
|
788
|
+
if (this.hasFetchOperator(value)) {
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
760
796
|
|
|
761
797
|
optimize(query, exec_scope) {
|
|
798
|
+
var _this18 = this;
|
|
799
|
+
|
|
762
800
|
return _asyncToGenerator(function* () {
|
|
763
801
|
try {
|
|
764
|
-
var
|
|
802
|
+
var _explainResponse$rows;
|
|
765
803
|
|
|
766
|
-
var
|
|
767
|
-
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
var
|
|
804
|
+
var {
|
|
805
|
+
buildCoveringIndex
|
|
806
|
+
} = require("./utility"); // 1. Run EXPLAIN to get the query plan
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
var explainQuery = "EXPLAIN ".concat(query);
|
|
810
|
+
var explainResponse = yield exec_scope.query(explainQuery); // 2. Check if query plan contains Fetch operator (not covered)
|
|
811
|
+
|
|
812
|
+
var plan = explainResponse === null || explainResponse === void 0 ? void 0 : (_explainResponse$rows = explainResponse.rows) === null || _explainResponse$rows === void 0 ? void 0 : _explainResponse$rows[0];
|
|
813
|
+
|
|
814
|
+
if (!_this18.hasFetchOperator(plan)) {
|
|
815
|
+
// Query is already covered, no need to create index
|
|
816
|
+
return;
|
|
817
|
+
} // 3. Build covering index from the query
|
|
772
818
|
|
|
773
|
-
|
|
774
|
-
|
|
819
|
+
|
|
820
|
+
var indexStatement = buildCoveringIndex(query, {
|
|
821
|
+
bucket: _this18.bucket,
|
|
822
|
+
scope: _this18.scope_name,
|
|
823
|
+
collection: _this18.collection_name
|
|
824
|
+
}); // 4. Execute the index creation if we got a valid statement
|
|
825
|
+
|
|
826
|
+
if (indexStatement) {
|
|
827
|
+
console.log("Executing index creation:", indexStatement);
|
|
828
|
+
var response = yield exec_scope.query(indexStatement);
|
|
829
|
+
console.log("Index creation response:", response);
|
|
775
830
|
}
|
|
776
|
-
} catch (e) {
|
|
831
|
+
} catch (e) {// Silently handle errors (index may already exist, etc.)
|
|
832
|
+
}
|
|
777
833
|
})();
|
|
778
834
|
}
|
|
779
835
|
|
|
780
836
|
query(query) {
|
|
781
|
-
var
|
|
837
|
+
var _this19 = this;
|
|
782
838
|
|
|
783
839
|
var query_scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "cluster";
|
|
784
840
|
return _asyncToGenerator(function* () {
|
|
785
|
-
var scope = query_scope ||
|
|
841
|
+
var scope = query_scope || _this19.query_scope;
|
|
786
842
|
var sanitizedQuery = query; //sanitization step to be implemented
|
|
787
843
|
|
|
788
844
|
var exec_scope = null;
|
|
789
845
|
|
|
790
846
|
if (scope === "cluster") {
|
|
791
|
-
exec_scope =
|
|
847
|
+
exec_scope = _this19.cluster;
|
|
792
848
|
} else {
|
|
793
|
-
exec_scope =
|
|
849
|
+
exec_scope = _this19.scope;
|
|
794
850
|
} // Start time
|
|
795
851
|
|
|
796
852
|
|
|
@@ -803,10 +859,10 @@ module.exports = class Database {
|
|
|
803
859
|
var endTime = process.hrtime.bigint(); // Calculate duration in microseconds
|
|
804
860
|
|
|
805
861
|
var durationInMilliseconds = (endTime - startTime) / BigInt(1000000); // optimize if response time is greater than 1 second
|
|
862
|
+
//if (durationInMilliseconds > 2000) {
|
|
863
|
+
|
|
864
|
+
_this19.optimize(query, exec_scope); //}
|
|
806
865
|
|
|
807
|
-
if (durationInMilliseconds > 2000) {
|
|
808
|
-
_this18.optimize(query, exec_scope);
|
|
809
|
-
}
|
|
810
866
|
|
|
811
867
|
return response;
|
|
812
868
|
})();
|
package/build/utility.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var crypto = require("crypto");
|
|
4
|
+
/**
|
|
5
|
+
* Generates a short hash from a string for unique index naming
|
|
6
|
+
* @param {string} str - The string to hash
|
|
7
|
+
* @returns {string} - A short hash string
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function generateHash(str) {
|
|
12
|
+
return crypto.createHash("md5").update(str).digest("hex").substring(0, 8);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Extracts field names from WHERE clause of a query
|
|
16
|
+
* @param {string} query - The N1QL query
|
|
17
|
+
* @returns {string[]} - Array of field names found in WHERE clause
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
function extractWhereFields(query) {
|
|
22
|
+
var fields = new Set(); // Find the WHERE clause
|
|
23
|
+
|
|
24
|
+
var whereMatch = query.match(/\bWHERE\b([\s\S]+?)(?:\bORDER\b|\bLIMIT\b|\bOFFSET\b|\bGROUP\b|\bHAVING\b|$)/i);
|
|
25
|
+
|
|
26
|
+
if (!whereMatch) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var whereClause = whereMatch[1]; // Pattern to match field names in comparisons
|
|
31
|
+
// Matches: field = value, field > value, field < value, field >= value, field <= value
|
|
32
|
+
// Also matches: field IN (...), field LIKE ..., field IS NULL, field IS MISSING, field IS NOT NULL
|
|
33
|
+
// Handles backtick-quoted fields like `field_name`
|
|
34
|
+
|
|
35
|
+
var fieldPattern = /`?(\w+)`?\s*(?:=|!=|<>|>=|<=|>|<|\bIN\b|\bLIKE\b|\bIS\b|\bBETWEEN\b)/gi;
|
|
36
|
+
var match;
|
|
37
|
+
|
|
38
|
+
while ((match = fieldPattern.exec(whereClause)) !== null) {
|
|
39
|
+
var fieldName = match[1].toLowerCase(); // Skip common SQL keywords that might be matched
|
|
40
|
+
|
|
41
|
+
if (!["and", "or", "not", "null", "missing", "true", "false"].includes(fieldName)) {
|
|
42
|
+
fields.add(match[1]); // Keep original case
|
|
43
|
+
}
|
|
44
|
+
} // Also look for fields in function calls like LOWER(field), UPPER(field)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
var functionPattern = /(?:LOWER|UPPER|TRIM|LENGTH|SUBSTR)\s*\(\s*`?(\w+)`?/gi;
|
|
48
|
+
|
|
49
|
+
while ((match = functionPattern.exec(whereClause)) !== null) {
|
|
50
|
+
fields.add(match[1]);
|
|
51
|
+
} // Extract array fields from ANY/EVERY ... IN array_field SATISFIES ... END
|
|
52
|
+
// Pattern: ANY/EVERY variable IN array_field SATISFIES ... END
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
var anyEveryPattern = /\b(?:ANY|EVERY)\s+\w+\s+IN\s+`?(\w+)`?\s+SATISFIES/gi;
|
|
56
|
+
|
|
57
|
+
while ((match = anyEveryPattern.exec(whereClause)) !== null) {
|
|
58
|
+
fields.add(match[1]);
|
|
59
|
+
} // Also extract nested property access within SATISFIES: v.property = value
|
|
60
|
+
// This captures fields accessed through array iteration variables
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
var satisfiesPattern = /SATISFIES\s+(.+?)\s+END/gi;
|
|
64
|
+
|
|
65
|
+
while ((match = satisfiesPattern.exec(whereClause)) !== null) {
|
|
66
|
+
var satisfiesClause = match[1]; // Look for comparisons like: v.property = value, v.field > 10
|
|
67
|
+
|
|
68
|
+
var nestedFieldPattern = /\w+\.`?(\w+)`?\s*(?:=|!=|<>|>=|<=|>|<|\bIN\b|\bLIKE\b|\bIS\b)/gi;
|
|
69
|
+
var nestedMatch = void 0;
|
|
70
|
+
|
|
71
|
+
while ((nestedMatch = nestedFieldPattern.exec(satisfiesClause)) !== null) {// These are nested properties, we might want to track them separately
|
|
72
|
+
// For now, we'll note them but the array field is what matters for indexing
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return Array.from(fields);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extracts field names from SELECT clause of a query
|
|
80
|
+
* @param {string} query - The N1QL query
|
|
81
|
+
* @returns {string[]|null} - Array of field names, or null if SELECT *
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
function extractSelectFields(query) {
|
|
86
|
+
// Find the SELECT clause (between SELECT and FROM)
|
|
87
|
+
var selectMatch = query.match(/\bSELECT\b([\s\S]+?)\bFROM\b/i);
|
|
88
|
+
|
|
89
|
+
if (!selectMatch) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
var selectClause = selectMatch[1].trim(); // Check for SELECT * patterns
|
|
94
|
+
|
|
95
|
+
if (/^\s*\*\s*$/.test(selectClause) || /\(\s*\w+\s*\)\s*\.\s*\*/.test(selectClause)) {
|
|
96
|
+
return null; // Cannot determine fields for SELECT *
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
var fields = new Set(); // Split by comma, handling nested parentheses
|
|
100
|
+
|
|
101
|
+
var parts = splitSelectClause(selectClause);
|
|
102
|
+
|
|
103
|
+
for (var part of parts) {
|
|
104
|
+
var trimmedPart = part.trim(); // Skip if it's a * pattern
|
|
105
|
+
|
|
106
|
+
if (trimmedPart === "*" || trimmedPart.endsWith(".*")) {
|
|
107
|
+
return null;
|
|
108
|
+
} // Handle AS alias - get the source field
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
var asMatch = trimmedPart.match(/^(.+?)\s+AS\s+\w+$/i);
|
|
112
|
+
var fieldExpr = asMatch ? asMatch[1].trim() : trimmedPart; // Extract field from META().id pattern - skip these
|
|
113
|
+
|
|
114
|
+
if (/META\s*\(/i.test(fieldExpr)) {
|
|
115
|
+
continue;
|
|
116
|
+
} // Extract field from function calls like COUNT(field), SUM(field)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
var funcMatch = fieldExpr.match(/^\w+\s*\(\s*`?(\w+)`?\s*\)$/);
|
|
120
|
+
|
|
121
|
+
if (funcMatch && funcMatch[1].toLowerCase() !== "id") {
|
|
122
|
+
fields.add(funcMatch[1]);
|
|
123
|
+
continue;
|
|
124
|
+
} // Handle simple field reference: `field` or collection.field or `collection`.`field`
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
var simpleFieldMatch = fieldExpr.match(/(?:`?\w+`?\s*\.\s*)?`?(\w+)`?$/);
|
|
128
|
+
|
|
129
|
+
if (simpleFieldMatch) {
|
|
130
|
+
var fieldName = simpleFieldMatch[1];
|
|
131
|
+
|
|
132
|
+
if (fieldName.toLowerCase() !== "id") {
|
|
133
|
+
fields.add(fieldName);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return Array.from(fields);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extracts field names from GROUP BY clause of a query
|
|
142
|
+
* @param {string} query - The N1QL query
|
|
143
|
+
* @returns {string[]} - Array of field names found in GROUP BY clause
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
function extractGroupByFields(query) {
|
|
148
|
+
var fields = []; // Find the GROUP BY clause
|
|
149
|
+
|
|
150
|
+
var groupByMatch = query.match(/\bGROUP[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+BY\b([\s\S]+?)(?:\bHAVING\b|\bORDER\b|\bLIMIT\b|\bOFFSET\b|$)/i);
|
|
151
|
+
|
|
152
|
+
if (!groupByMatch) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
var groupByClause = groupByMatch[1]; // Split by comma and extract field names
|
|
157
|
+
|
|
158
|
+
var parts = groupByClause.split(",");
|
|
159
|
+
|
|
160
|
+
for (var part of parts) {
|
|
161
|
+
var trimmedPart = part.trim(); // Handle collection.field or `collection`.`field` syntax
|
|
162
|
+
|
|
163
|
+
var fieldMatch = trimmedPart.match(/(?:`?\w+`?\s*\.\s*)?`?(\w+)`?$/);
|
|
164
|
+
|
|
165
|
+
if (fieldMatch) {
|
|
166
|
+
var fieldName = fieldMatch[1]; // Skip if it's a keyword or id
|
|
167
|
+
|
|
168
|
+
if (!["id"].includes(fieldName.toLowerCase())) {
|
|
169
|
+
fields.push(fieldName);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return fields;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Extracts field names from ORDER BY clause of a query
|
|
178
|
+
* @param {string} query - The N1QL query
|
|
179
|
+
* @returns {string[]} - Array of field names found in ORDER BY clause
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
function extractOrderByFields(query) {
|
|
184
|
+
var fields = []; // Find the ORDER BY clause
|
|
185
|
+
|
|
186
|
+
var orderByMatch = query.match(/\bORDER[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+BY\b([\s\S]+?)(?:\bLIMIT\b|\bOFFSET\b|$)/i);
|
|
187
|
+
|
|
188
|
+
if (!orderByMatch) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var orderByClause = orderByMatch[1]; // Split by comma and extract field names
|
|
193
|
+
|
|
194
|
+
var parts = orderByClause.split(",");
|
|
195
|
+
|
|
196
|
+
for (var part of parts) {
|
|
197
|
+
var trimmedPart = part.trim(); // Remove ASC/DESC suffix
|
|
198
|
+
|
|
199
|
+
var withoutDirection = trimmedPart.replace(/\s+(ASC|DESC)\s*$/i, "").trim(); // Handle collection.field or `collection`.`field` syntax
|
|
200
|
+
|
|
201
|
+
var fieldMatch = withoutDirection.match(/(?:`?\w+`?\s*\.\s*)?`?(\w+)`?$/);
|
|
202
|
+
|
|
203
|
+
if (fieldMatch) {
|
|
204
|
+
var fieldName = fieldMatch[1]; // Skip if it's a keyword or id
|
|
205
|
+
|
|
206
|
+
if (!["id", "asc", "desc"].includes(fieldName.toLowerCase())) {
|
|
207
|
+
fields.push(fieldName);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return fields;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Splits SELECT clause by commas, respecting parentheses
|
|
216
|
+
* @param {string} clause - The SELECT clause content
|
|
217
|
+
* @returns {string[]} - Array of individual select expressions
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
function splitSelectClause(clause) {
|
|
222
|
+
var parts = [];
|
|
223
|
+
var current = "";
|
|
224
|
+
var depth = 0;
|
|
225
|
+
|
|
226
|
+
for (var char of clause) {
|
|
227
|
+
if (char === "(") {
|
|
228
|
+
depth++;
|
|
229
|
+
current += char;
|
|
230
|
+
} else if (char === ")") {
|
|
231
|
+
depth--;
|
|
232
|
+
current += char;
|
|
233
|
+
} else if (char === "," && depth === 0) {
|
|
234
|
+
parts.push(current);
|
|
235
|
+
current = "";
|
|
236
|
+
} else {
|
|
237
|
+
current += char;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (current.trim()) {
|
|
242
|
+
parts.push(current);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return parts;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Builds a covering index CREATE statement from a query
|
|
249
|
+
* @param {string} query - The N1QL query to analyze
|
|
250
|
+
* @param {Object} options - Configuration options
|
|
251
|
+
* @param {string} options.bucket - The bucket name
|
|
252
|
+
* @param {string} options.scope - The scope name
|
|
253
|
+
* @param {string} options.collection - The collection name
|
|
254
|
+
* @returns {string|null} - The CREATE INDEX statement, or null if cannot build
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
function buildCoveringIndex(query, options) {
|
|
259
|
+
var {
|
|
260
|
+
bucket,
|
|
261
|
+
scope,
|
|
262
|
+
collection
|
|
263
|
+
} = options;
|
|
264
|
+
|
|
265
|
+
if (!bucket || !scope || !collection) {
|
|
266
|
+
return null;
|
|
267
|
+
} // Extract fields from SELECT clause
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
var selectFields = extractSelectFields(query);
|
|
271
|
+
|
|
272
|
+
if (selectFields === null) {
|
|
273
|
+
// SELECT * query - cannot create covering index
|
|
274
|
+
return null;
|
|
275
|
+
} // Extract fields from WHERE clause
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
var whereFields = extractWhereFields(query); // Extract fields from GROUP BY clause
|
|
279
|
+
|
|
280
|
+
var groupByFields = extractGroupByFields(query); // Extract fields from ORDER BY clause
|
|
281
|
+
|
|
282
|
+
var orderByFields = extractOrderByFields(query); // Remove 'deleted' from whereFields as it will be added first with INCLUDE MISSING
|
|
283
|
+
|
|
284
|
+
var filteredWhereFields = whereFields.filter(f => f.toLowerCase() !== "deleted"); // Track fields already added to avoid duplicates
|
|
285
|
+
|
|
286
|
+
var addedFields = new Set(filteredWhereFields.map(f => f.toLowerCase())); // Filter GROUP BY fields (remove duplicates from WHERE, remove deleted)
|
|
287
|
+
|
|
288
|
+
var filteredGroupByFields = groupByFields.filter(f => {
|
|
289
|
+
var lower = f.toLowerCase();
|
|
290
|
+
|
|
291
|
+
if (addedFields.has(lower) || lower === "deleted") {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
addedFields.add(lower);
|
|
296
|
+
return true;
|
|
297
|
+
}); // Filter ORDER BY fields (remove duplicates from WHERE and GROUP BY, remove deleted)
|
|
298
|
+
|
|
299
|
+
var filteredOrderByFields = orderByFields.filter(f => {
|
|
300
|
+
var lower = f.toLowerCase();
|
|
301
|
+
|
|
302
|
+
if (addedFields.has(lower) || lower === "deleted") {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
addedFields.add(lower);
|
|
307
|
+
return true;
|
|
308
|
+
}); // Filter SELECT fields (remove duplicates from WHERE, GROUP BY, and ORDER BY, remove deleted)
|
|
309
|
+
|
|
310
|
+
var selectFieldsFiltered = selectFields.filter(f => {
|
|
311
|
+
var lower = f.toLowerCase();
|
|
312
|
+
|
|
313
|
+
if (addedFields.has(lower) || lower === "deleted") {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
addedFields.add(lower);
|
|
318
|
+
return true;
|
|
319
|
+
}); // All fields for the index: WHERE -> GROUP BY -> ORDER BY -> SELECT
|
|
320
|
+
|
|
321
|
+
var allFields = [...filteredWhereFields, ...filteredGroupByFields, ...filteredOrderByFields, ...selectFieldsFiltered];
|
|
322
|
+
|
|
323
|
+
if (allFields.length === 0) {
|
|
324
|
+
// No fields to index besides deleted
|
|
325
|
+
return null;
|
|
326
|
+
} // Generate unique index name based on query hash
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
var hash = generateHash(query);
|
|
330
|
+
var indexName = "adv_covered_".concat(hash); // Build the field list with deleted INCLUDE MISSING first
|
|
331
|
+
|
|
332
|
+
var fieldList = ["`deleted` INCLUDE MISSING", ...allFields.map(f => "`".concat(f, "`"))].join(", "); // Build the CREATE INDEX statement
|
|
333
|
+
|
|
334
|
+
var indexStatement = "CREATE INDEX `".concat(indexName, "` ON `default`:`").concat(bucket, "`.`").concat(scope, "`.`").concat(collection, "`(").concat(fieldList, ")"); // Debug logs
|
|
335
|
+
|
|
336
|
+
console.log("[DEBUG buildCoveringIndex] Original query:", query);
|
|
337
|
+
console.log("[DEBUG buildCoveringIndex] WHERE fields:", filteredWhereFields);
|
|
338
|
+
console.log("[DEBUG buildCoveringIndex] GROUP BY fields:", filteredGroupByFields);
|
|
339
|
+
console.log("[DEBUG buildCoveringIndex] ORDER BY fields:", filteredOrderByFields);
|
|
340
|
+
console.log("[DEBUG buildCoveringIndex] SELECT fields:", selectFieldsFiltered);
|
|
341
|
+
console.log("[DEBUG buildCoveringIndex] Index statement:", indexStatement);
|
|
342
|
+
return indexStatement;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = {
|
|
346
|
+
buildCoveringIndex,
|
|
347
|
+
extractWhereFields,
|
|
348
|
+
extractSelectFields,
|
|
349
|
+
extractGroupByFields,
|
|
350
|
+
extractOrderByFields,
|
|
351
|
+
generateHash
|
|
352
|
+
};
|