Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IndexedDB “and” and “or” queries

I’m storing the following content in an IndexedDB:

[
  {
    name: 'pizza',
    ingredients: [ 'flour', 'water', 'yeast', 'salt', 'oil' ]
  }, {
    name: 'pasta',
    ingredients: [ 'semolina', 'salt', 'water' ]
  }, …
]

I have a multiEntry index on the ingredients:

store.createIndex('ingredientIdx', 'ingredients', { multiEntry: true });

Now, I’d like to get:

  1. Meals which contain any of the given ingredients, e.g. flour or water

  2. Meals which contain all of the given ingredients, e.g. semolina, salt, and water

My naive approach currently performs several queries for each given ingredient, and then filters and deduplicates the results. But this feels not very “database-like” at all.

Does IndexedDB support some kind of “or” or “and” queries? Doing something like this obviously does not work:

const tx = db.transaction('meals');
const ingredientIdx = tx.objectStore('meals').index('ingredientsIdx');
const request = ingredientIdx.getAll([ 'flour' , 'water' ]);
// …
like image 481
qqilihq Avatar asked Oct 18 '25 06:10

qqilihq


1 Answers

You have setup the stores correctly and are using the index correctly and are querying one at a time correctly. There is nothing wrong with the way you have set it all up. However, unfortunately, indexedDB does not make it easy to efficiently query that data in the way you want, e.g. by matching multiple objects using a single cursor/getAll.

The first issue is regarding the use of OR. You cannot do this in a single request. You will need to perform multiple requests. Note that you can at least perform these requests concurrently. Loop over the operands and get each one from the multi-entry index, appending each result into a single array shared by each of the requests, taking into account duplicates. You need to do this in a way such that each subsequent request is not waiting for the prior request to complete before starting.

The second issue is regarding the use of AND. You can only do AND-style queries over a range of values, e.g. like all numbers between this lower bound and upper bound. So instead you need to query one at a time, again, like you already are.

Regarding AND, you can consider getting a bit clever and structuring the data differently. For example, create boolean (using 0 and 1) properties for each of the ingredients. E.g. if pizza has flour then the pizza object has a property named ingredient_flour with a value of 1, and if pizza does not have flour it either has no property or has the property ingrediant_flour with a value of 0. Then you can create various indices on the different combinations of ingredients. E.g. you can create an index on flour and water, then query that index for IDBKeyRange.only([1,1]).

Concurrent OR example (something like this):

function any(db, values) {
  return new Promise((resolve, reject) => {
    const objects = [];
    const transaction = db.transaction('meals');

    transaction.oncomplete = function(event) {
      resolve(objects);
    };

    transaction.onerror = function(event) {
      reject(event.target.error);
    };

    const store = transaction.objectStore('meals');
    const index = store.index('ingredients');

    function onsuccess(event) {
      const cursor = event.target.result;
      if (cursor) {
        const value = cursor.value;

        let exists = false;
        for (const object of objects) {
          if (object.name === value.name) {
            exists = true;
            break;
          }
        }

        if (!exists) {
          objects.push(value);
        }

        cursor.continue();
      }
    }

    for (const value of values) {
       // IDBKeyRange.only is implied in this next line
       const request = index.get(value);
       request.onsuccess = onsuccess;
    }

  });
}
like image 182
Josh Avatar answered Oct 19 '25 21:10

Josh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!