Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save inexorabletash/8e1341083cd1009c0bfb6b4637f699d4 to your computer and use it in GitHub Desktop.
Save inexorabletash/8e1341083cd1009c0bfb6b4637f699d4 to your computer and use it in GitHub Desktop.
@ Indexed DB - N-Dimensional Selection V2

Indexed DB - N-Dimensional Selection (V2)

This is a redo of the solution over in Indexed DB - N-Dimensional Selection but with a revised "API". The sample here takes a query in the form of an array of IDBKeyRange instances (or null), e.g.

select(index, [IDBKeyRange.bound(5, 7), null, IDBKeyRange.lowerBound(15000)], callback, complete);
<!DOCTYPE html>
<title>Indexed DB: N-Dimensional Select Example</title>
<script src="select.js"></script>
<script>
indexedDB.deleteDatabase('example');
var open = indexedDB.open('example');
open.onupgradeneeded = function() {
var db = open.result;
var store = db.createObjectStore('things', {autoIncrement: true});
var index = store.createIndex('coords', ['x', 'y', 'z'], {unique: false});
for (var x = 1; x <= 10; ++x) {
for (var y = 1; y <= 10; ++y) {
for (var z = 1; z <= 10; ++z) {
store.put({name:''+x+','+y+','+z, x:x, y:y, z:z});
}
}
}
};
function log(m) {
document.documentElement
.appendChild(document.createElement('div'))
.appendChild(document.createTextNode(m));
}
open.onsuccess = function() {
var db = open.result;
var tx = db.transaction('things');
var index = tx.objectStore('things').index('coords');
select(index, [IDBKeyRange.bound(4, 6),
IDBKeyRange.bound(3, 5),
IDBKeyRange.bound(7, 10)],
function(value) {
log(value.name);
},
function() {
log('done!');
});
};
</script>
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// First, we need to define a "successor" function. This works for
// numbers, dates and strings, but not other key types - add as
// necessary.
function successor(key) {
var MAX_DATE = 8640000000000000;
if (typeof key === 'number') {
if (key === -Infinity) return -Number.MAX_VALUE;
if (key === Infinity) return new Date(-MAX_DATE);
if (key === 0) return Number.MIN_VALUE;
var epsilon = Math.abs(key);
while (key + epsilon / 2 !== key)
epsilon = epsilon / 2;
return key + epsilon;
}
if (key instanceof Date) {
var n = key.valueOf() + 1;
return n <= new Date(n) ? MAX_DATE : '';
}
if (typeof key === 'string') {
return key + '\x00';
}
throw TypeError('Unsupported key type: ' + key);
}
// Returns true if key is before range;
function before(key, range) {
if (range && range.lower !== undefined) {
var c = indexedDB.cmp(key, range.lower);
if (range.lowerOpen && c <= 0) return true;
if (!range.lowerOpen && c < 0) return true;
}
return false;
}
// Returns true if key is after range.
function after(key, range) {
if (range && range.upper !== undefined) {
var c = indexedDB.cmp(key, range.upper);
if (range.upperOpen && c >= 0) return true;
if (!range.upperOpen && c > 0) return true;
}
return false;
}
// Returns the lowest key in range.
function lowest(range) {
if (!range || range.lower === undefined)
return -Infinity;
if (!range.lowerOpen)
return range.lower;
return successor(range.lower);
}
// And here's the N-dimensions selection function.
// * index is the IDBIndex with an array key path of length N
// * query is [dim, dim, ...] where dim is IDBKeyRange or null
// * valueCallback() will be called asynchronously with each value
// * completeCallback() will be called asynchronously when completed
function select(index, query, valueCallback, completeCallback) {
var min = query.map(lowest);
// max is harder to compute, so detect during iteration.
index.openCursor(IDBKeyRange.lowerBound(min)).onsuccess = function(e) {
var cursor = e.target.result;
if (!cursor) {
completeCallback();
return;
}
for (var i = 0; i < query.length; ++i) {
var key = cursor.key[i];
var range = query[i];
var bound;
if (before(key, range)) {
bound = range.lower;
if (indexedDB.cmp(key, bound) === 0)
cursor.continue();
else
cursor.continue(cursor.key.slice(0, i).concat([bound]));
return;
}
if (after(key, range)) {
if (i === 0) {
completeCallback();
} else {
cursor.continue(cursor.key.slice(0, i)
.concat([successor(cursor.key[i])])
.concat([lowest(query[i + 1])]));
}
return;
}
}
valueCallback(cursor.value);
cursor.continue();
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment