spice-couchbase 2.0.48 → 2.0.49
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/utility.js +52 -7
- package/build/utility.test.js +30 -3
- package/package.json +1 -1
- package/src/utility.js +50 -6
- package/src/utility.test.js +41 -2
package/build/utility.js
CHANGED
|
@@ -181,6 +181,16 @@ function extractGroupByFields(query) {
|
|
|
181
181
|
|
|
182
182
|
|
|
183
183
|
function extractOrderByFields(query) {
|
|
184
|
+
return extractOrderByIndexFields(query).map(field => field.name);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Extracts ORDER BY fields with sort direction for index key generation
|
|
188
|
+
* @param {string} query - The N1QL query
|
|
189
|
+
* @returns {{name: string, direction: string}[]} - Array of fields with normalized direction
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
function extractOrderByIndexFields(query) {
|
|
184
194
|
var fields = []; // Find the ORDER BY clause
|
|
185
195
|
|
|
186
196
|
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);
|
|
@@ -194,8 +204,10 @@ function extractOrderByFields(query) {
|
|
|
194
204
|
var parts = orderByClause.split(",");
|
|
195
205
|
|
|
196
206
|
for (var part of parts) {
|
|
197
|
-
var trimmedPart = part.trim(); // Remove ASC/DESC suffix
|
|
207
|
+
var trimmedPart = part.trim(); // Remove ASC/DESC suffix while preserving DESC for index key generation.
|
|
198
208
|
|
|
209
|
+
var directionMatch = trimmedPart.match(/\s+(ASC|DESC)\s*$/i);
|
|
210
|
+
var direction = directionMatch && directionMatch[1].toUpperCase() === "DESC" ? "DESC" : "";
|
|
199
211
|
var withoutDirection = trimmedPart.replace(/\s+(ASC|DESC)\s*$/i, "").trim(); // Handle collection.field or `collection`.`field` syntax
|
|
200
212
|
|
|
201
213
|
var fieldMatch = withoutDirection.match(/(?:`?\w+`?\s*\.\s*)?`?(\w+)`?$/);
|
|
@@ -204,13 +216,41 @@ function extractOrderByFields(query) {
|
|
|
204
216
|
var fieldName = fieldMatch[1]; // Skip if it's a keyword or id
|
|
205
217
|
|
|
206
218
|
if (!["id", "asc", "desc"].includes(fieldName.toLowerCase())) {
|
|
207
|
-
fields.push(
|
|
219
|
+
fields.push({
|
|
220
|
+
name: fieldName,
|
|
221
|
+
direction
|
|
222
|
+
});
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
return fields;
|
|
213
228
|
}
|
|
229
|
+
|
|
230
|
+
function buildOrderDirectionMap(orderByIndexFields) {
|
|
231
|
+
var directionMap = new Map();
|
|
232
|
+
|
|
233
|
+
for (var {
|
|
234
|
+
name,
|
|
235
|
+
direction
|
|
236
|
+
} of orderByIndexFields) {
|
|
237
|
+
if (direction === "DESC") {
|
|
238
|
+
directionMap.set(name.toLowerCase(), direction);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return directionMap;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function formatIndexField(field, orderDirectionMap) {
|
|
246
|
+
var direction = orderDirectionMap.get(field.toLowerCase());
|
|
247
|
+
return "`".concat(field, "`").concat(direction ? " ".concat(direction) : "");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function formatIndexKey(field, orderDirectionMap) {
|
|
251
|
+
var direction = orderDirectionMap.get(field.toLowerCase());
|
|
252
|
+
return "".concat(field).concat(direction ? " ".concat(direction) : "");
|
|
253
|
+
}
|
|
214
254
|
/**
|
|
215
255
|
* Splits SELECT clause by commas, respecting parentheses
|
|
216
256
|
* @param {string} clause - The SELECT clause content
|
|
@@ -277,9 +317,11 @@ function buildCoveringIndex(query, options) {
|
|
|
277
317
|
|
|
278
318
|
var whereFields = extractWhereFields(query); // Extract fields from GROUP BY clause
|
|
279
319
|
|
|
280
|
-
var groupByFields = extractGroupByFields(query); // Extract fields from ORDER BY clause
|
|
320
|
+
var groupByFields = extractGroupByFields(query); // Extract fields from ORDER BY clause, preserving DESC sort direction
|
|
281
321
|
|
|
282
|
-
var
|
|
322
|
+
var orderByIndexFields = extractOrderByIndexFields(query);
|
|
323
|
+
var orderDirectionMap = buildOrderDirectionMap(orderByIndexFields);
|
|
324
|
+
var orderByFields = orderByIndexFields.map(field => field.name); // Remove 'deleted' from whereFields as it will be added first with INCLUDE MISSING
|
|
283
325
|
|
|
284
326
|
var filteredWhereFields = whereFields.filter(f => f.toLowerCase() !== "deleted"); // Track fields already added to avoid duplicates
|
|
285
327
|
|
|
@@ -326,10 +368,12 @@ function buildCoveringIndex(query, options) {
|
|
|
326
368
|
} // Build the field list with deleted INCLUDE MISSING first
|
|
327
369
|
|
|
328
370
|
|
|
329
|
-
var fieldList = ["`deleted` INCLUDE MISSING", ...allFields.map(f =>
|
|
371
|
+
var fieldList = ["`deleted` INCLUDE MISSING", ...allFields.map(f => formatIndexField(f, orderDirectionMap))].join(", "); // Generate unique index name based on bucket, scope, collection, and fields
|
|
330
372
|
// This ensures multiple queries covered by the same index get the same hash
|
|
331
373
|
|
|
332
|
-
var
|
|
374
|
+
var indexFieldsKey = allFields.map(field => formatIndexKey(field, orderDirectionMap)).sort().join(",");
|
|
375
|
+
var orderFieldsKey = orderByIndexFields.map(field => formatIndexKey(field.name, orderDirectionMap)).join(",");
|
|
376
|
+
var indexKey = "".concat(bucket, "|").concat(scope, "|").concat(collection, "|fields:").concat(indexFieldsKey, "|order:").concat(orderFieldsKey);
|
|
333
377
|
var hash = generateHash(indexKey);
|
|
334
378
|
var indexName = "adv_covered_".concat(hash); // Build the CREATE INDEX statement
|
|
335
379
|
|
|
@@ -343,5 +387,6 @@ module.exports = {
|
|
|
343
387
|
extractSelectFields,
|
|
344
388
|
extractGroupByFields,
|
|
345
389
|
extractOrderByFields,
|
|
390
|
+
extractOrderByIndexFields,
|
|
346
391
|
generateHash
|
|
347
|
-
};
|
|
392
|
+
};
|
package/build/utility.test.js
CHANGED
|
@@ -598,6 +598,24 @@ test("same index name for different queries with same fields", () => {
|
|
|
598
598
|
|
|
599
599
|
assert.strictEqual(name1, name2);
|
|
600
600
|
});
|
|
601
|
+
test("different index name for different ORDER BY direction", () => {
|
|
602
|
+
var query1 = "SELECT name FROM users WHERE status = 'active' ORDER BY created_at";
|
|
603
|
+
var query2 = "SELECT name FROM users WHERE status = 'active' ORDER BY created_at DESC";
|
|
604
|
+
var result1 = buildCoveringIndex(query1, defaultOptions);
|
|
605
|
+
var result2 = buildCoveringIndex(query2, defaultOptions);
|
|
606
|
+
var name1 = result1.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
607
|
+
var name2 = result2.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
608
|
+
assert.notStrictEqual(name1, name2);
|
|
609
|
+
});
|
|
610
|
+
test("different index name for different multiple ORDER BY field order", () => {
|
|
611
|
+
var query1 = "SELECT email FROM users WHERE status = 'active' ORDER BY last_name ASC, created_at DESC";
|
|
612
|
+
var query2 = "SELECT email FROM users WHERE status = 'active' ORDER BY created_at DESC, last_name ASC";
|
|
613
|
+
var result1 = buildCoveringIndex(query1, defaultOptions);
|
|
614
|
+
var result2 = buildCoveringIndex(query2, defaultOptions);
|
|
615
|
+
var name1 = result1.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
616
|
+
var name2 = result2.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
617
|
+
assert.notStrictEqual(name1, name2);
|
|
618
|
+
});
|
|
601
619
|
test("different index name for queries with different fields", () => {
|
|
602
620
|
var query1 = "SELECT name FROM users WHERE status = 'active'";
|
|
603
621
|
var query2 = "SELECT email FROM users WHERE type = 'admin'";
|
|
@@ -659,7 +677,7 @@ test("includes ORDER BY fields in index", () => {
|
|
|
659
677
|
assert.ok(result !== null);
|
|
660
678
|
assert.ok(result.includes("`deleted` INCLUDE MISSING"));
|
|
661
679
|
assert.ok(result.includes("`status`"));
|
|
662
|
-
assert.ok(result.includes("`created_at`"));
|
|
680
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
663
681
|
assert.ok(result.includes("`name`"));
|
|
664
682
|
assert.ok(result.includes("`email`"));
|
|
665
683
|
});
|
|
@@ -681,6 +699,7 @@ test("deduplicates ORDER BY field if already in WHERE", () => {
|
|
|
681
699
|
var firstCreatedAt = result.indexOf("`created_at`");
|
|
682
700
|
var lastCreatedAt = result.lastIndexOf("`created_at`");
|
|
683
701
|
assert.strictEqual(firstCreatedAt, lastCreatedAt);
|
|
702
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
684
703
|
});
|
|
685
704
|
test("deduplicates ORDER BY field if already in SELECT", () => {
|
|
686
705
|
var query = "SELECT name, created_at FROM users WHERE status = 'active' ORDER BY created_at DESC";
|
|
@@ -688,6 +707,7 @@ test("deduplicates ORDER BY field if already in SELECT", () => {
|
|
|
688
707
|
|
|
689
708
|
var matches = result.match(/`created_at`/g);
|
|
690
709
|
assert.strictEqual(matches.length, 1);
|
|
710
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
691
711
|
});
|
|
692
712
|
test("handles multiple ORDER BY fields", () => {
|
|
693
713
|
var query = "SELECT email FROM users WHERE status = 'active' ORDER BY last_name ASC, first_name ASC";
|
|
@@ -701,13 +721,20 @@ test("handles multiple ORDER BY fields", () => {
|
|
|
701
721
|
assert.ok(lastNameIndex < emailIndex);
|
|
702
722
|
assert.ok(firstNameIndex < emailIndex);
|
|
703
723
|
});
|
|
724
|
+
test("handles mixed ORDER BY directions in index", () => {
|
|
725
|
+
var query = "SELECT email FROM users WHERE status = 'active' ORDER BY last_name ASC, created_at DESC";
|
|
726
|
+
var result = buildCoveringIndex(query, defaultOptions);
|
|
727
|
+
assert.ok(result.includes("`last_name`"));
|
|
728
|
+
assert.ok(!result.includes("`last_name` ASC"));
|
|
729
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
730
|
+
});
|
|
704
731
|
test("handles realistic query with ORDER BY", () => {
|
|
705
732
|
var query = "SELECT first_name, last_name, email, status \n FROM users \n WHERE (deleted = false OR deleted IS MISSING) AND status = 'active' \n ORDER BY created_at DESC \n LIMIT 50 OFFSET 0";
|
|
706
733
|
var result = buildCoveringIndex(query, defaultOptions);
|
|
707
734
|
assert.ok(result !== null);
|
|
708
735
|
assert.ok(result.includes("`deleted` INCLUDE MISSING"));
|
|
709
736
|
assert.ok(result.includes("`status`"));
|
|
710
|
-
assert.ok(result.includes("`created_at`"));
|
|
737
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
711
738
|
assert.ok(result.includes("`first_name`"));
|
|
712
739
|
assert.ok(result.includes("`last_name`"));
|
|
713
740
|
assert.ok(result.includes("`email`")); // Verify order: status (WHERE) -> created_at (ORDER BY) -> SELECT fields
|
|
@@ -804,4 +831,4 @@ test("field order: WHERE -> GROUP BY -> ORDER BY -> SELECT", () => {
|
|
|
804
831
|
console.log("\n========================================");
|
|
805
832
|
console.log("Tests: ".concat(passed + failed, " | Passed: ").concat(passed, " | Failed: ").concat(failed));
|
|
806
833
|
console.log("========================================\n");
|
|
807
|
-
process.exit(failed > 0 ? 1 : 0);
|
|
834
|
+
process.exit(failed > 0 ? 1 : 0);
|
package/package.json
CHANGED
package/src/utility.js
CHANGED
|
@@ -190,6 +190,15 @@ function extractGroupByFields(query) {
|
|
|
190
190
|
* @returns {string[]} - Array of field names found in ORDER BY clause
|
|
191
191
|
*/
|
|
192
192
|
function extractOrderByFields(query) {
|
|
193
|
+
return extractOrderByIndexFields(query).map((field) => field.name);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Extracts ORDER BY fields with sort direction for index key generation
|
|
198
|
+
* @param {string} query - The N1QL query
|
|
199
|
+
* @returns {{name: string, direction: string}[]} - Array of fields with normalized direction
|
|
200
|
+
*/
|
|
201
|
+
function extractOrderByIndexFields(query) {
|
|
193
202
|
const fields = [];
|
|
194
203
|
|
|
195
204
|
// Find the ORDER BY clause
|
|
@@ -208,7 +217,12 @@ function extractOrderByFields(query) {
|
|
|
208
217
|
for (const part of parts) {
|
|
209
218
|
const trimmedPart = part.trim();
|
|
210
219
|
|
|
211
|
-
// Remove ASC/DESC suffix
|
|
220
|
+
// Remove ASC/DESC suffix while preserving DESC for index key generation.
|
|
221
|
+
const directionMatch = trimmedPart.match(/\s+(ASC|DESC)\s*$/i);
|
|
222
|
+
const direction =
|
|
223
|
+
directionMatch && directionMatch[1].toUpperCase() === "DESC" ?
|
|
224
|
+
"DESC"
|
|
225
|
+
: "";
|
|
212
226
|
const withoutDirection = trimmedPart
|
|
213
227
|
.replace(/\s+(ASC|DESC)\s*$/i, "")
|
|
214
228
|
.trim();
|
|
@@ -219,7 +233,7 @@ function extractOrderByFields(query) {
|
|
|
219
233
|
const fieldName = fieldMatch[1];
|
|
220
234
|
// Skip if it's a keyword or id
|
|
221
235
|
if (!["id", "asc", "desc"].includes(fieldName.toLowerCase())) {
|
|
222
|
-
fields.push(fieldName);
|
|
236
|
+
fields.push({ name: fieldName, direction });
|
|
223
237
|
}
|
|
224
238
|
}
|
|
225
239
|
}
|
|
@@ -227,6 +241,26 @@ function extractOrderByFields(query) {
|
|
|
227
241
|
return fields;
|
|
228
242
|
}
|
|
229
243
|
|
|
244
|
+
function buildOrderDirectionMap(orderByIndexFields) {
|
|
245
|
+
const directionMap = new Map();
|
|
246
|
+
for (const { name, direction } of orderByIndexFields) {
|
|
247
|
+
if (direction === "DESC") {
|
|
248
|
+
directionMap.set(name.toLowerCase(), direction);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return directionMap;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function formatIndexField(field, orderDirectionMap) {
|
|
255
|
+
const direction = orderDirectionMap.get(field.toLowerCase());
|
|
256
|
+
return `\`${field}\`${direction ? ` ${direction}` : ""}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function formatIndexKey(field, orderDirectionMap) {
|
|
260
|
+
const direction = orderDirectionMap.get(field.toLowerCase());
|
|
261
|
+
return `${field}${direction ? ` ${direction}` : ""}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
230
264
|
/**
|
|
231
265
|
* Splits SELECT clause by commas, respecting parentheses
|
|
232
266
|
* @param {string} clause - The SELECT clause content
|
|
@@ -288,8 +322,10 @@ function buildCoveringIndex(query, options) {
|
|
|
288
322
|
// Extract fields from GROUP BY clause
|
|
289
323
|
const groupByFields = extractGroupByFields(query);
|
|
290
324
|
|
|
291
|
-
// Extract fields from ORDER BY clause
|
|
292
|
-
const
|
|
325
|
+
// Extract fields from ORDER BY clause, preserving DESC sort direction
|
|
326
|
+
const orderByIndexFields = extractOrderByIndexFields(query);
|
|
327
|
+
const orderDirectionMap = buildOrderDirectionMap(orderByIndexFields);
|
|
328
|
+
const orderByFields = orderByIndexFields.map((field) => field.name);
|
|
293
329
|
|
|
294
330
|
// Remove 'deleted' from whereFields as it will be added first with INCLUDE MISSING
|
|
295
331
|
const filteredWhereFields = whereFields.filter(
|
|
@@ -345,12 +381,19 @@ function buildCoveringIndex(query, options) {
|
|
|
345
381
|
// Build the field list with deleted INCLUDE MISSING first
|
|
346
382
|
const fieldList = [
|
|
347
383
|
"`deleted` INCLUDE MISSING",
|
|
348
|
-
...allFields.map((f) =>
|
|
384
|
+
...allFields.map((f) => formatIndexField(f, orderDirectionMap)),
|
|
349
385
|
].join(", ");
|
|
350
386
|
|
|
351
387
|
// Generate unique index name based on bucket, scope, collection, and fields
|
|
352
388
|
// This ensures multiple queries covered by the same index get the same hash
|
|
353
|
-
const
|
|
389
|
+
const indexFieldsKey = allFields
|
|
390
|
+
.map((field) => formatIndexKey(field, orderDirectionMap))
|
|
391
|
+
.sort()
|
|
392
|
+
.join(",");
|
|
393
|
+
const orderFieldsKey = orderByIndexFields
|
|
394
|
+
.map((field) => formatIndexKey(field.name, orderDirectionMap))
|
|
395
|
+
.join(",");
|
|
396
|
+
const indexKey = `${bucket}|${scope}|${collection}|fields:${indexFieldsKey}|order:${orderFieldsKey}`;
|
|
354
397
|
const hash = generateHash(indexKey);
|
|
355
398
|
const indexName = `adv_covered_${hash}`;
|
|
356
399
|
|
|
@@ -366,5 +409,6 @@ module.exports = {
|
|
|
366
409
|
extractSelectFields,
|
|
367
410
|
extractGroupByFields,
|
|
368
411
|
extractOrderByFields,
|
|
412
|
+
extractOrderByIndexFields,
|
|
369
413
|
generateHash,
|
|
370
414
|
};
|
package/src/utility.test.js
CHANGED
|
@@ -722,6 +722,34 @@ test("same index name for different queries with same fields", () => {
|
|
|
722
722
|
assert.strictEqual(name1, name2);
|
|
723
723
|
});
|
|
724
724
|
|
|
725
|
+
test("different index name for different ORDER BY direction", () => {
|
|
726
|
+
const query1 = "SELECT name FROM users WHERE status = 'active' ORDER BY created_at";
|
|
727
|
+
const query2 = "SELECT name FROM users WHERE status = 'active' ORDER BY created_at DESC";
|
|
728
|
+
|
|
729
|
+
const result1 = buildCoveringIndex(query1, defaultOptions);
|
|
730
|
+
const result2 = buildCoveringIndex(query2, defaultOptions);
|
|
731
|
+
|
|
732
|
+
const name1 = result1.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
733
|
+
const name2 = result2.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
734
|
+
|
|
735
|
+
assert.notStrictEqual(name1, name2);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("different index name for different multiple ORDER BY field order", () => {
|
|
739
|
+
const query1 =
|
|
740
|
+
"SELECT email FROM users WHERE status = 'active' ORDER BY last_name ASC, created_at DESC";
|
|
741
|
+
const query2 =
|
|
742
|
+
"SELECT email FROM users WHERE status = 'active' ORDER BY created_at DESC, last_name ASC";
|
|
743
|
+
|
|
744
|
+
const result1 = buildCoveringIndex(query1, defaultOptions);
|
|
745
|
+
const result2 = buildCoveringIndex(query2, defaultOptions);
|
|
746
|
+
|
|
747
|
+
const name1 = result1.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
748
|
+
const name2 = result2.match(/CREATE INDEX `(adv_covered_\w+)`/)[1];
|
|
749
|
+
|
|
750
|
+
assert.notStrictEqual(name1, name2);
|
|
751
|
+
});
|
|
752
|
+
|
|
725
753
|
test("different index name for queries with different fields", () => {
|
|
726
754
|
const query1 = "SELECT name FROM users WHERE status = 'active'";
|
|
727
755
|
const query2 = "SELECT email FROM users WHERE type = 'admin'";
|
|
@@ -814,7 +842,7 @@ test("includes ORDER BY fields in index", () => {
|
|
|
814
842
|
assert.ok(result !== null);
|
|
815
843
|
assert.ok(result.includes("`deleted` INCLUDE MISSING"));
|
|
816
844
|
assert.ok(result.includes("`status`"));
|
|
817
|
-
assert.ok(result.includes("`created_at`"));
|
|
845
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
818
846
|
assert.ok(result.includes("`name`"));
|
|
819
847
|
assert.ok(result.includes("`email`"));
|
|
820
848
|
});
|
|
@@ -841,6 +869,7 @@ test("deduplicates ORDER BY field if already in WHERE", () => {
|
|
|
841
869
|
const firstCreatedAt = result.indexOf("`created_at`");
|
|
842
870
|
const lastCreatedAt = result.lastIndexOf("`created_at`");
|
|
843
871
|
assert.strictEqual(firstCreatedAt, lastCreatedAt);
|
|
872
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
844
873
|
});
|
|
845
874
|
|
|
846
875
|
test("deduplicates ORDER BY field if already in SELECT", () => {
|
|
@@ -850,6 +879,7 @@ test("deduplicates ORDER BY field if already in SELECT", () => {
|
|
|
850
879
|
// created_at should only appear once (in ORDER BY position, before SELECT fields)
|
|
851
880
|
const matches = result.match(/`created_at`/g);
|
|
852
881
|
assert.strictEqual(matches.length, 1);
|
|
882
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
853
883
|
});
|
|
854
884
|
|
|
855
885
|
test("handles multiple ORDER BY fields", () => {
|
|
@@ -868,6 +898,15 @@ test("handles multiple ORDER BY fields", () => {
|
|
|
868
898
|
assert.ok(firstNameIndex < emailIndex);
|
|
869
899
|
});
|
|
870
900
|
|
|
901
|
+
test("handles mixed ORDER BY directions in index", () => {
|
|
902
|
+
const query = "SELECT email FROM users WHERE status = 'active' ORDER BY last_name ASC, created_at DESC";
|
|
903
|
+
const result = buildCoveringIndex(query, defaultOptions);
|
|
904
|
+
|
|
905
|
+
assert.ok(result.includes("`last_name`"));
|
|
906
|
+
assert.ok(!result.includes("`last_name` ASC"));
|
|
907
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
908
|
+
});
|
|
909
|
+
|
|
871
910
|
test("handles realistic query with ORDER BY", () => {
|
|
872
911
|
const query = `SELECT first_name, last_name, email, status
|
|
873
912
|
FROM users
|
|
@@ -879,7 +918,7 @@ test("handles realistic query with ORDER BY", () => {
|
|
|
879
918
|
assert.ok(result !== null);
|
|
880
919
|
assert.ok(result.includes("`deleted` INCLUDE MISSING"));
|
|
881
920
|
assert.ok(result.includes("`status`"));
|
|
882
|
-
assert.ok(result.includes("`created_at`"));
|
|
921
|
+
assert.ok(result.includes("`created_at` DESC"));
|
|
883
922
|
assert.ok(result.includes("`first_name`"));
|
|
884
923
|
assert.ok(result.includes("`last_name`"));
|
|
885
924
|
assert.ok(result.includes("`email`"));
|