Repository: dexie/Dexie.js (language: javascript)

Issue:
Object queries (where({...})) cannot reuse virtual indexes if two or more props are specified
A object query such as:

```js
await db.chaps.where({name: "Mambo", number: 5}).toArray();
```
returns empty array if there's a compound index covering 3 props with these 2 as the leading ones.

Schema has a compound index `[name+number+shoeSize]` that should be able to reuse parts of but the bug is that the queries only works if the object query either contains a single property (`name`) or all properties (`name`, `number`, `shoeSize`) but not when containing partials.

```js
db.version(x).stores({
    chaps: "++id,[name+number+shoeSize]",
});
```

Also, instead of failing, it returns empty result.

Object queries (where({...})) cannot reuse virtual indexes if two or more props are specified
A object query such as:

```js
await db.chaps.where({name: "Mambo", number: 5}).toArray();
```
returns empty array if there's a compound index covering 3 props with these 2 as the leading ones.

Schema has a compound index `[name+number+shoeSize]` that should be able to reuse parts of but the bug is that the queries only works if the object query either contains a single property (`name`) or all properties (`name`, `number`, `shoeSize`) but not when containing partials.

```js
db.version(x).stores({
    chaps: "++id,[name+number+shoeSize]",
});
```

Also, instead of failing, it returns empty result.



Discussion / hints:

Fix patch:
diff --git a/src/classes/table/table.ts b/src/classes/table/table.ts
index 4ca180831..e4d37de6c 100644
--- a/src/classes/table/table.ts
+++ b/src/classes/table/table.ts
@@ -103,17 +103,26 @@ export class Table implements ITable<any, IndexableType> {
     // Multiple criterias.
     // Let's try finding a compound index that matches all keyPaths in
     // arbritary order:
-    const compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(ix =>
-      ix.compound &&
-      keyPaths.every(keyPath => ix.keyPath.indexOf(keyPath) >= 0) &&
-      (ix.keyPath as string[]).every(keyPath => keyPaths.indexOf(keyPath) >= 0))[0];
-
-    if (compoundIndex && this.db._maxKey !== maxString)
+    const compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(ix => {
+      if (
+        ix.compound &&
+        keyPaths.every(keyPath => ix.keyPath.indexOf(keyPath) >= 0)) {
+          for (let i=0; i<keyPaths.length; ++i) {
+            if (keyPaths.indexOf(ix.keyPath[i]) === -1) return false;
+          }
+          return true;
+        }
+        return false;
+      }).sort((a,b) => a.keyPath.length - b.keyPath.length)[0];
+            
+    if (compoundIndex && this.db._maxKey !== maxString) {
       // Cool! We found such compound index
       // and this browser supports compound indexes (maxKey !== maxString)!
+      const keyPathsInValidOrder = (compoundIndex.keyPath as string[]).slice(0, keyPaths.length);
       return this
-        .where(compoundIndex.name)
-        .equals((compoundIndex.keyPath as string[]).map(kp => indexOrCrit[kp]));
+        .where(keyPathsInValidOrder)
+        .equals(keyPathsInValidOrder.map(kp => indexOrCrit[kp]));
+    }
 
     if (!compoundIndex && debug) console.warn(
       `The query ${JSON.stringify(indexOrCrit)} on ${this.name} would benefit of a ` +


Test patch:
diff --git a/test/tests-whereclause.js b/test/tests-whereclause.js
index e9ea1b1be..a065299bd 100644
--- a/test/tests-whereclause.js
+++ b/test/tests-whereclause.js
@@ -5,13 +5,13 @@ import {resetDatabase, supports, spawnedTest, promisedTest} from './dexie-unitte
 const async = Dexie.async;
 
 var db = new Dexie("TestDBWhereClause");
-db.version(1).stores({
+db.version(2).stores({
     folders: "++id,&path",
     files: "++id,filename,extension,[filename+extension],folderId",
     people: "[name+number],name,number",
     friends: "++id,name,age",
     chart: '[patno+row+col], patno',
-    chaps: "++id,[name+number]",
+    chaps: "++id,[name+number+age]",
     multiMulti: "id,*tags,*categories"
 });
 
@@ -753,7 +753,7 @@ asyncTest("startsWithAnyOfIgnoreCase()", function () {
     }).finally(start);
 });
 
-promisedTest("where({key: value})", async ()=>{
+promisedTest("where({key: value})", async () => {
     let readme = await db.files.where({filename: "README"}).first();
     ok (readme, 'Should get a result for db.files.get({filename: "README"});');
     equal (readme.extension, ".TXT", "Should get README.TXT");
@@ -782,6 +782,26 @@ promisedTest("where({key: value})", async ()=>{
     });
 });
 
+promisedTest("where({notIndexed: value}) should not return emtpy result", async () => {
+    await db.chaps.add({name: "Chaplin", number: 1, age: 134, notIndexed: 'foo'});
+    try {
+        let chaplin = await db.chaps.where({name: "Chaplin", notIndexed: "foo"}).first();
+        if (!chaplin) {
+            ok(false, "Got empty result when data exists matching the query. If we're not using an index we should not get an empty result but an error.");
+        } else {
+            ok(chaplin,
+                "Ok, so we got a result even though 'notIndexed' is not indexed at all. "+
+                "This is not expected but ok if the result would be valid"
+            );
+            equal(chaplin.name, "Chaplin", "Got the correct data Chaplin!");
+        }
+    } catch (e) {
+        ok(true, `Got error: "${e}" because 'notIndexed' is not indexed at all. This is also ok.`);
+    }
+});
+
+
+
 promisedTest("orderBy(['idx1','idx2'])", async () => {
     if (!supports("compound")) {
         ok(true, "Browser does not support compound indexes. Ignoring test.");
@@ -818,21 +838,27 @@ promisedTest("Virtual Index", async () => {
 
     await db.chaps.bulkAdd([{
         name: "David",
-        number: 2
+        number: 2,
+        age: 29
     },{
         name: "David",
-        number: 3
+        number: 3,
+        age: 39
     },{
         name: "David",
-        number: 1
+        number: 1,
+        age: 49
     },{
         name: "Mambo",
-        number: 5
+        number: 5,
+        age: 55
     }]);
 
     // Verify that Dexie can use the [name+number] index to query name only:
     const davids = await db.chaps.where({name: "David"}).toArray();
     equal(davids.length, 3, "There should be 3 Davids in the result");
+    const mambosNumber5 = await db.chaps.where({name: "Mambo", number: 5}).toArray();
+    equal(mambosNumber5.length, 1, "There should be 1 Mambo no 5 in the result");
     // Verify that equalsIgnoreCase also works:
     const daves = await db.chaps.where('name').equalsIgnoreCase('david').toArray();
     equal(JSON.stringify(daves.map(({name, number}) => ({name, number})), null, 2),