diff --git a/Makefile b/Makefile
index 4effbf04cbd2709aaaa15b42571ee828c4e6ed6c..90af380a12a464c1b04d47046c7f91b0215882c6 100644
--- a/Makefile
+++ b/Makefile
@@ -59,6 +59,11 @@ ${LINTDIR}/queries/query.js: ${SRCDIR}/queries/query.js
${JSLINT} ${LINTOPTS} $<
@cat $< > $@
+${LINTDIR}/indexeddb/indexeddb.js: ${SRCDIR}/indexeddb/indexeddb.js
+ @mkdir -p $(@D)
+ ${JSLINT} ${LINTOPTS} $<
+ @cat $< > $@
+
${LINTDIR}/${TESTDIR}/jio.storage/%.js: ${TESTDIR}/jio.storage/%.js
@mkdir -p $(@D)
${JSLINT} ${LINTOPTS} --predef QUnit --predef RSVP --predef jIO $<
@@ -104,6 +109,7 @@ lint: $(patsubst ${TESTDIR}/jio.storage/%.js, ${LINTDIR}/${TESTDIR}/jio.storage/
$(patsubst ${EXAMPLEDIR}/%.js, ${LINTDIR}/${EXAMPLEDIR}/%.js, $(wildcard ${EXAMPLEDIR}/*.js)) \
${LINTDIR}/queries/query.js \
${LINTDIR}/jio.date/jiodate.js \
+ ${LINTDIR}/indexeddb/indexeddb.js \
${LINTDIR}/jio.js \
${LINTDIR}/node/jio.js \
${LINTDIR}/${TESTDIR}/node.js \
@@ -129,6 +135,7 @@ ${JIOVERSION}: ${EXTERNALDIR}/URI.js \
${SRCDIR}/queries/parser-end.js \
${SRCDIR}/queries/query.js \
${SRCDIR}/jio.date/jiodate.js \
+ ${SRCDIR}/indexeddb/indexeddb.js \
${SRCDIR}/jio.js \
${EXTERNALDIR}/rusha.js \
${SRCDIR}/jio.storage/replicatestorage.js \
@@ -151,7 +158,10 @@ ${JIOVERSION}: ${EXTERNALDIR}/URI.js \
${SRCDIR}/jio.storage/indexeddbstorage.js \
${SRCDIR}/jio.storage/cryptstorage.js \
${SRCDIR}/jio.storage/fbstorage.js \
- ${SRCDIR}/jio.storage/cloudooostorage.js
+ ${SRCDIR}/jio.storage/cloudooostorage.js \
+ ${SRCDIR}/jio.storage/nocapacitystorage.js \
+ ${SRCDIR}/jio.storage/liststorage.js \
+ ${SRCDIR}/jio.storage/indexstorage2.js
@mkdir -p $(@D)
cat $^ > $@
diff --git a/examples/scenario.js b/examples/scenario.js
index a196e1362fbbc86beb1771634fb16e485a1de1d8..879f373c68e45ccb62d9a6c3056fab5b1e06cc50 100644
--- a/examples/scenario.js
+++ b/examples/scenario.js
@@ -18,7 +18,7 @@
* See https://www.nexedi.com/licensing for rationale and options.
*/
-/*global console, btoa, Blob*/
+/*global console, btoa, Blob, indexedDB*/
/*jslint nomen: true, maxlen: 200*/
(function (window, QUnit, jIO, rJS) {
"use strict";
@@ -28,7 +28,9 @@
ok = QUnit.ok,
stop = QUnit.stop,
start = QUnit.start,
- deepEqual = QUnit.deepEqual;
+ deepEqual = QUnit.deepEqual,
+ test_signature_database = 'test_signature_storage_scenario';
+
rJS(window)
@@ -60,12 +62,22 @@
return g.run({
type: "query",
sub_storage: {
- type: "uuid",
+ type: "list",
+ signature_storage: {
+ type: "indexeddb",
+ database: test_signature_database
+ },
sub_storage: {
- type: "union",
- storage_list: [{
- type: "memory"
- }]
+ type: "nocapacity",
+ sub_storage: {
+ type: "uuid",
+ sub_storage: {
+ type: "union",
+ storage_list: [{
+ type: "memory"
+ }]
+ }
+ }
}
}
});
@@ -362,6 +374,9 @@
.then(function () {
return jio.repair();
})
+ .then(function () {
+ return indexedDB.deleteDatabase('jio:' + test_signature_database);
+ })
.fail(function (error) {
console.error("---");
diff --git a/src/indexeddb/indexeddb.js b/src/indexeddb/indexeddb.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef56d17c2d220b0ab554707f79323b05cce6587f
--- /dev/null
+++ b/src/indexeddb/indexeddb.js
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2019, Nexedi SA
+ *
+ * This program is free software: you can Use, Study, Modify and Redistribute
+ * it under the terms of the GNU General Public License version 3, or (at your
+ * option) any later version, as published by the Free Software Foundation.
+ *
+ * You can also Link and Combine this program with other software covered by
+ * the terms of any of the Free Software licenses or any of the Open Source
+ * Initiative approved licenses and Convey the resulting work. Corresponding
+ * source of such a combination shall include the source code for all other
+ * software used.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See COPYING file for full licensing terms.
+ * See https://www.nexedi.com/licensing for rationale and options.
+ */
+/*jslint nomen: true */
+/*global indexedDB, RSVP, IDBOpenDBRequest, DOMError, Event, Set, DOMException,
+ window*/
+
+(function (indexedDB, RSVP, IDBOpenDBRequest, DOMError, DOMException) {
+ "use strict";
+
+ function waitForIDBRequest(request) {
+ return new RSVP.Promise(function (resolve, reject) {
+ request.onerror = reject;
+ request.onsuccess = resolve;
+ });
+ }
+
+ function waitForAllSynchronousCursor(request, callback) {
+ var force_cancellation = false;
+
+ function canceller() {
+ force_cancellation = true;
+ }
+
+ function resolver(resolve, reject) {
+ request.onerror = reject;
+ request.onsuccess = function (evt) {
+ var cursor = evt.target.result;
+ if (cursor && !force_cancellation) {
+ try {
+ callback(cursor);
+ } catch (error) {
+ reject(error);
+ }
+ // continue to next iteration
+ cursor["continue"]();
+ } else {
+ resolve();
+ }
+ };
+ }
+ return new RSVP.Promise(resolver, canceller);
+ }
+
+ function waitForOpenIndexedDB(db_name, version, upgrade_handler, callback) {
+ var request;
+
+ function canceller() {
+ if ((request !== undefined) && (request.result !== undefined)) {
+ request.result.close();
+ }
+ }
+
+ function resolver(resolve, reject) {
+ // Open DB //
+ request = indexedDB.open(db_name, version);
+ request.onerror = function (error) {
+ canceller();
+ if ((error !== undefined) &&
+ (error.target instanceof IDBOpenDBRequest) &&
+ ((error.target.error instanceof DOMError) ||
+ (error.target.error instanceof DOMException))) {
+ reject("Connection to: " + db_name + " failed: " +
+ error.target.error.message);
+ } else {
+ reject(error);
+ }
+ };
+
+ request.onabort = function () {
+ canceller();
+ reject("Aborting connection to: " + db_name);
+ };
+
+ request.ontimeout = function () {
+ reject("Connection to: " + db_name + " timeout");
+ };
+
+ request.onblocked = function () {
+ canceller();
+ reject("Connection to: " + db_name + " was blocked");
+ };
+
+ // Create DB if necessary //
+ request.onupgradeneeded = upgrade_handler;
+
+ request.onversionchange = function () {
+ canceller();
+ reject(db_name + " was upgraded");
+ };
+
+ request.onsuccess = function () {
+ var result;
+ try {
+ result = callback(request.result);
+ } catch (error) {
+ reject(error);
+ }
+ return new RSVP.Queue()
+ .push(function () {
+ return result;
+ })
+ .push(function (final_result) {
+ canceller();
+ resolve(final_result);
+ }, function (error) {
+ canceller();
+ reject(error);
+ });
+ };
+ }
+
+ return new RSVP.Promise(resolver, canceller);
+ }
+
+ function waitForTransaction(db, stores, flag, callback) {
+ var tx = db.transaction(stores, flag);
+ function canceller() {
+ try {
+ tx.abort();
+ } catch (unused) {
+ // Transaction already finished
+ return;
+ }
+ }
+ function resolver(resolve, reject) {
+ var result;
+ try {
+ result = callback(tx);
+ } catch (error) {
+ reject(error);
+ }
+ tx.oncomplete = function () {
+ return new RSVP.Queue()
+ .push(function () {
+ return result;
+ })
+ .push(resolve, function (error) {
+ canceller();
+ reject(error);
+ });
+ };
+ tx.onerror = function (error) {
+ canceller();
+ reject(error);
+ };
+ tx.onabort = function (evt) {
+ reject(evt.target);
+ };
+ return tx;
+ }
+ return new RSVP.Promise(resolver, canceller);
+ }
+
+ window.waitForTransaction = waitForTransaction;
+ window.waitForOpenIndexedDB = waitForOpenIndexedDB;
+ window.waitForIDBRequest = waitForIDBRequest;
+ window.waitForAllSynchronousCursor = waitForAllSynchronousCursor;
+
+}(indexedDB, RSVP, IDBOpenDBRequest, DOMError, DOMException));
\ No newline at end of file
diff --git a/src/jio.storage/indexeddbstorage.js b/src/jio.storage/indexeddbstorage.js
index c7b89bdf50d0733e3d035fa532adea6e96aacf13..aae4221b7c6a75e1f70f8270c959bba6c962fc9f 100644
--- a/src/jio.storage/indexeddbstorage.js
+++ b/src/jio.storage/indexeddbstorage.js
@@ -42,11 +42,11 @@
*/
/*jslint nomen: true */
-/*global indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest,
- DOMError, Event*/
+/*global jIO, RSVP, Blob, Math, IDBKeyRange, Event, waitForOpenIndexedDB,
+ waitForIDBRequest, waitForAllSynchronousCursor, waitForTransaction*/
-(function (indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest,
- DOMError) {
+(function (jIO, RSVP, Blob, Math, IDBKeyRange, waitForOpenIndexedDB,
+ waitForIDBRequest, waitForAllSynchronousCursor, waitForTransaction) {
"use strict";
// Read only as changing it can lead to data corruption
@@ -96,143 +96,6 @@
store.createIndex("_id", "_id", {unique: false});
}
- function waitForOpenIndexedDB(db_name, callback) {
- var request;
-
- function canceller() {
- if ((request !== undefined) && (request.result !== undefined)) {
- request.result.close();
- }
- }
-
- function resolver(resolve, reject) {
- // Open DB //
- request = indexedDB.open(db_name);
- request.onerror = function (error) {
- canceller();
- if ((error !== undefined) &&
- (error.target instanceof IDBOpenDBRequest) &&
- (error.target.error instanceof DOMError)) {
- reject("Connection to: " + db_name + " failed: " +
- error.target.error.message);
- } else {
- reject(error);
- }
- };
-
- request.onabort = function () {
- canceller();
- reject("Aborting connection to: " + db_name);
- };
-
- request.ontimeout = function () {
- reject("Connection to: " + db_name + " timeout");
- };
-
- request.onblocked = function () {
- canceller();
- reject("Connection to: " + db_name + " was blocked");
- };
-
- // Create DB if necessary //
- request.onupgradeneeded = handleUpgradeNeeded;
-
- request.onversionchange = function () {
- canceller();
- reject(db_name + " was upgraded");
- };
-
- request.onsuccess = function () {
- var result;
- try {
- result = callback(request.result);
- } catch (error) {
- reject(error);
- }
- return new RSVP.Queue()
- .push(function () {
- return result;
- })
- .push(function (final_result) {
- canceller();
- resolve(final_result);
- }, function (error) {
- canceller();
- reject(error);
- });
- };
- }
-
- return new RSVP.Promise(resolver, canceller);
- }
-
- function waitForTransaction(db, stores, flag, callback) {
- var tx = db.transaction(stores, flag);
- function canceller() {
- try {
- tx.abort();
- } catch (unused) {
- // Transaction already finished
- return;
- }
- }
- function resolver(resolve, reject) {
- var result;
- try {
- result = callback(tx);
- } catch (error) {
- reject(error);
- }
- tx.oncomplete = function () {
- return new RSVP.Queue()
- .push(function () {
- return result;
- })
- .push(resolve, function (error) {
- canceller();
- reject(error);
- });
- };
- tx.onerror = reject;
- tx.onabort = reject;
- }
- return new RSVP.Promise(resolver, canceller);
- }
-
- function waitForIDBRequest(request) {
- return new RSVP.Promise(function (resolve, reject) {
- request.onerror = reject;
- request.onsuccess = resolve;
- });
- }
-
- function waitForAllSynchronousCursor(request, callback) {
- var force_cancellation = false;
-
- function canceller() {
- force_cancellation = true;
- }
-
- function resolver(resolve, reject) {
- request.onerror = reject;
- request.onsuccess = function (evt) {
- var cursor = evt.target.result;
- if (cursor && !force_cancellation) {
- try {
- callback(cursor);
- } catch (error) {
- reject(error);
- }
- // continue to next iteration
- cursor["continue"]();
- } else {
- resolve();
- }
- };
- }
- return new RSVP.Promise(resolver, canceller);
- }
-
IndexedDBStorage.prototype.buildQuery = function (options) {
var result_list = [],
context = this;
@@ -254,21 +117,22 @@
return new RSVP.Queue()
.push(function () {
- return waitForOpenIndexedDB(context._database_name, function (db) {
- return waitForTransaction(db, ["metadata"], "readonly",
+ return waitForOpenIndexedDB(context._database_name, undefined,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["metadata"], "readonly",
function (tx) {
- if (options.include_docs === true) {
+ if (options.include_docs === true) {
+ return waitForAllSynchronousCursor(
+ tx.objectStore("metadata").index("_id").openCursor(),
+ pushIncludedMetadata
+ );
+ }
return waitForAllSynchronousCursor(
- tx.objectStore("metadata").index("_id").openCursor(),
- pushIncludedMetadata
+ tx.objectStore("metadata").index("_id").openKeyCursor(),
+ pushMetadata
);
- }
- return waitForAllSynchronousCursor(
- tx.objectStore("metadata").index("_id").openKeyCursor(),
- pushMetadata
- );
- });
- });
+ });
+ });
})
.push(function () {
return result_list;
@@ -279,12 +143,13 @@
var context = this;
return new RSVP.Queue()
.push(function () {
- return waitForOpenIndexedDB(context._database_name, function (db) {
- return waitForTransaction(db, ["metadata"], "readonly",
- function (tx) {
- return waitForIDBRequest(tx.objectStore("metadata").get(id));
- });
- });
+ return waitForOpenIndexedDB(context._database_name, undefined,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["metadata"], "readonly",
+ function (tx) {
+ return waitForIDBRequest(tx.objectStore("metadata").get(id));
+ });
+ });
})
.push(function (evt) {
if (evt.target.result) {
@@ -307,19 +172,20 @@
return new RSVP.Queue()
.push(function () {
- return waitForOpenIndexedDB(context._database_name, function (db) {
- return waitForTransaction(db, ["metadata", "attachment"], "readonly",
- function (tx) {
- return RSVP.all([
- waitForIDBRequest(tx.objectStore("metadata").get(id)),
- waitForAllSynchronousCursor(
- tx.objectStore("attachment").index("_id")
- .openKeyCursor(IDBKeyRange.only(id)),
- addEntry
- )
- ]);
- });
- });
+ return waitForOpenIndexedDB(context._database_name, undefined,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["metadata", "attachment"],
+ "readonly", function (tx) {
+ return RSVP.all([
+ waitForIDBRequest(tx.objectStore("metadata").get(id)),
+ waitForAllSynchronousCursor(
+ tx.objectStore("attachment").index("_id")
+ .openKeyCursor(IDBKeyRange.only(id)),
+ addEntry
+ )
+ ]);
+ });
+ });
})
.push(function (result_list) {
var evt = result_list[0];
@@ -336,56 +202,58 @@
};
IndexedDBStorage.prototype.put = function (id, metadata) {
- return waitForOpenIndexedDB(this._database_name, function (db) {
- return waitForTransaction(db, ["metadata"], "readwrite",
- function (tx) {
- return waitForIDBRequest(tx.objectStore("metadata").put({
- "_id": id,
- "doc": metadata
- }));
- });
- });
+ return waitForOpenIndexedDB(this._database_name, undefined,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["metadata"], "readwrite",
+ function (tx) {
+ return waitForIDBRequest(tx.objectStore("metadata").put({
+ "_id": id,
+ "doc": metadata
+ }));
+ });
+ });
};
IndexedDBStorage.prototype.remove = function (id) {
- return waitForOpenIndexedDB(this._database_name, function (db) {
- return waitForTransaction(db, ["metadata", "attachment", "blob"],
- "readwrite", function (tx) {
-
- var promise_list = [],
- metadata_store = tx.objectStore("metadata"),
- attachment_store = tx.objectStore("attachment"),
- blob_store = tx.objectStore("blob");
-
- function deleteAttachment(cursor) {
- promise_list.push(
- waitForIDBRequest(attachment_store.delete(cursor.primaryKey))
- );
- }
- function deleteBlob(cursor) {
- promise_list.push(
- waitForIDBRequest(blob_store.delete(cursor.primaryKey))
- );
- }
+ return waitForOpenIndexedDB(this._database_name, undefined,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["metadata", "attachment", "blob"],
+ "readwrite", function (tx) {
+
+ var promise_list = [],
+ metadata_store = tx.objectStore("metadata"),
+ attachment_store = tx.objectStore("attachment"),
+ blob_store = tx.objectStore("blob");
- return RSVP.all([
- waitForIDBRequest(metadata_store.delete(id)),
- waitForAllSynchronousCursor(
- attachment_store.index("_id")
- .openKeyCursor(IDBKeyRange.only(id)),
- deleteAttachment
- ),
- waitForAllSynchronousCursor(
- blob_store.index("_id")
- .openKeyCursor(IDBKeyRange.only(id)),
- deleteBlob
- ),
- ])
- .then(function () {
- return RSVP.all(promise_list);
- });
- });
- });
+ function deleteAttachment(cursor) {
+ promise_list.push(
+ waitForIDBRequest(attachment_store.delete(cursor.primaryKey))
+ );
+ }
+ function deleteBlob(cursor) {
+ promise_list.push(
+ waitForIDBRequest(blob_store.delete(cursor.primaryKey))
+ );
+ }
+
+ return RSVP.all([
+ waitForIDBRequest(metadata_store.delete(id)),
+ waitForAllSynchronousCursor(
+ attachment_store.index("_id")
+ .openKeyCursor(IDBKeyRange.only(id)),
+ deleteAttachment
+ ),
+ waitForAllSynchronousCursor(
+ blob_store.index("_id")
+ .openKeyCursor(IDBKeyRange.only(id)),
+ deleteBlob
+ ),
+ ])
+ .then(function () {
+ return RSVP.all(promise_list);
+ });
+ });
+ });
};
IndexedDBStorage.prototype.getAttachment = function (id, name, options) {
@@ -416,65 +284,66 @@
return new RSVP.Queue()
.push(function () {
- return waitForOpenIndexedDB(db_name, function (db) {
- return waitForTransaction(db, ["blob"], "readonly",
- function (tx) {
- var key_path = buildKeyPath([id, name]),
- blob_store = tx.objectStore("blob"),
- start_index,
- end_index,
- promise_list = [];
-
-
- start_index = Math.floor(start / UNITE);
- if (end !== undefined) {
- end_index = Math.floor(end / UNITE);
- if (end % UNITE === 0) {
- end_index -= 1;
- }
- }
-
- function getBlobKey(cursor) {
- var index = parseInt(
- cursor.primaryKey.slice(key_path.length + 1),
- 10
- ),
- i;
-
- if ((start !== 0) && (index < start_index)) {
- // No need to fetch blobs at the start
- return;
- }
- if ((end !== undefined) && (index > end_index)) {
- // No need to fetch blobs at the end
- return;
+ return waitForOpenIndexedDB(db_name, undefined,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["blob"], "readonly",
+ function (tx) {
+ var key_path = buildKeyPath([id, name]),
+ blob_store = tx.objectStore("blob"),
+ start_index,
+ end_index,
+ promise_list = [];
+
+
+ start_index = Math.floor(start / UNITE);
+ if (end !== undefined) {
+ end_index = Math.floor(end / UNITE);
+ if (end % UNITE === 0) {
+ end_index -= 1;
+ }
}
- i = index - start_index;
- // Extend array size
- while (i > promise_list.length) {
- promise_list.push(null);
- i -= 1;
+ function getBlobKey(cursor) {
+ var index = parseInt(
+ cursor.primaryKey.slice(key_path.length + 1),
+ 10
+ ),
+ i;
+
+ if ((start !== 0) && (index < start_index)) {
+ // No need to fetch blobs at the start
+ return;
+ }
+ if ((end !== undefined) && (index > end_index)) {
+ // No need to fetch blobs at the end
+ return;
+ }
+
+ i = index - start_index;
+ // Extend array size
+ while (i > promise_list.length) {
+ promise_list.push(null);
+ i -= 1;
+ }
+ // Sort the blob by their index
+ promise_list.splice(
+ index - start_index,
+ 0,
+ waitForIDBRequest(blob_store.get(cursor.primaryKey))
+ );
}
- // Sort the blob by their index
- promise_list.splice(
- index - start_index,
- 0,
- waitForIDBRequest(blob_store.get(cursor.primaryKey))
- );
- }
- // Get all blob keys to check if they must be fetched
- return waitForAllSynchronousCursor(
- blob_store.index("_id_attachment")
- .openKeyCursor(IDBKeyRange.only([id, name])),
- getBlobKey
- )
- .then(function () {
- return RSVP.all(promise_list);
- });
- });
- });
+ // Get all blob keys to check if they must be fetched
+ return waitForAllSynchronousCursor(
+ blob_store.index("_id_attachment")
+ .openKeyCursor(IDBKeyRange.only([id, name])),
+ getBlobKey
+ )
+ .then(function () {
+ return RSVP.all(promise_list);
+ });
+ });
+ });
})
.push(function (result_list) {
// No need to keep the IDB open
@@ -501,46 +370,47 @@
// Request the full blob
return new RSVP.Queue()
.push(function () {
- return waitForOpenIndexedDB(db_name, function (db) {
- return waitForTransaction(db, ["attachment", "blob"], "readonly",
- function (tx) {
- var key_path = buildKeyPath([id, name]),
- attachment_store = tx.objectStore("attachment"),
- blob_store = tx.objectStore("blob");
+ return waitForOpenIndexedDB(db_name, undefined, handleUpgradeNeeded,
+ function (db) {
+ return waitForTransaction(db, ["attachment", "blob"], "readonly",
+ function (tx) {
+ var key_path = buildKeyPath([id, name]),
+ attachment_store = tx.objectStore("attachment"),
+ blob_store = tx.objectStore("blob");
- function getBlob(cursor) {
- var index = parseInt(
- cursor.primaryKey.slice(key_path.length + 1),
- 10
- ),
- i = index;
- // Extend array size
- while (i > array_buffer_list.length) {
- array_buffer_list.push(null);
- i -= 1;
+ function getBlob(cursor) {
+ var index = parseInt(
+ cursor.primaryKey.slice(key_path.length + 1),
+ 10
+ ),
+ i = index;
+ // Extend array size
+ while (i > array_buffer_list.length) {
+ array_buffer_list.push(null);
+ i -= 1;
+ }
+ // Sort the blob by their index
+ array_buffer_list.splice(
+ index,
+ 0,
+ cursor.value.blob
+ );
}
- // Sort the blob by their index
- array_buffer_list.splice(
- index,
- 0,
- cursor.value.blob
- );
- }
-
- return RSVP.all([
- // Get the attachment info (mime type)
- waitForIDBRequest(attachment_store.get(
- key_path
- )),
- // Get all needed blobs
- waitForAllSynchronousCursor(
- blob_store.index("_id_attachment")
- .openCursor(IDBKeyRange.only([id, name])),
- getBlob
- )
- ]);
- });
- });
+
+ return RSVP.all([
+ // Get the attachment info (mime type)
+ waitForIDBRequest(attachment_store.get(
+ key_path
+ )),
+ // Get all needed blobs
+ waitForAllSynchronousCursor(
+ blob_store.index("_id_attachment")
+ .openCursor(IDBKeyRange.only([id, name])),
+ getBlob
+ )
+ ]);
+ });
+ });
})
.push(function (result_list) {
@@ -591,103 +461,106 @@
handled_size += UNITE;
}
- return waitForOpenIndexedDB(db_name, function (db) {
- return waitForTransaction(db, ["attachment", "blob"], "readwrite",
- function (tx) {
- var blob_store,
- promise_list,
- delete_promise_list = [],
- key_path = buildKeyPath([id, name]),
- i;
- // First write the attachment info on top of previous
- promise_list = [
- waitForIDBRequest(tx.objectStore("attachment").put({
- "_key_path": key_path,
- "_id": id,
- "_attachment": name,
- "info": {
- "content_type": blob.type,
- "length": blob.size
- }
- }))
- ];
- // Then, write all blob parts on top of previous
- blob_store = tx.objectStore("blob");
- for (i = 0; i < blob_part.length; i += 1) {
- promise_list.push(
- waitForIDBRequest(blob_store.put({
- "_key_path": buildKeyPath([id, name, i]),
- "_id" : id,
- "_attachment" : name,
- "_part" : i,
- "blob": blob_part[i]
+ return waitForOpenIndexedDB(db_name, undefined, handleUpgradeNeeded,
+ function (db) {
+ return waitForTransaction(db, ["attachment", "blob"], "readwrite",
+ function (tx) {
+ var blob_store,
+ promise_list,
+ delete_promise_list = [],
+ key_path = buildKeyPath([id, name]),
+ i;
+ // First write the attachment info on top of previous
+ promise_list = [
+ waitForIDBRequest(tx.objectStore("attachment").put({
+ "_key_path": key_path,
+ "_id": id,
+ "_attachment": name,
+ "info": {
+ "content_type": blob.type,
+ "length": blob.size
+ }
}))
- );
- }
+ ];
+ // Then, write all blob parts on top of previous
+ blob_store = tx.objectStore("blob");
+ for (i = 0; i < blob_part.length; i += 1) {
+ promise_list.push(
+ waitForIDBRequest(blob_store.put({
+ "_key_path": buildKeyPath([id, name, i]),
+ "_id" : id,
+ "_attachment" : name,
+ "_part" : i,
+ "blob": blob_part[i]
+ }))
+ );
+ }
- function deleteEntry(cursor) {
- var index = parseInt(
- cursor.primaryKey.slice(key_path.length + 1),
- 10
- );
- if (index >= blob_part.length) {
- delete_promise_list.push(
- waitForIDBRequest(blob_store.delete(cursor.primaryKey))
+ function deleteEntry(cursor) {
+ var index = parseInt(
+ cursor.primaryKey.slice(key_path.length + 1),
+ 10
);
+ if (index >= blob_part.length) {
+ delete_promise_list.push(
+ waitForIDBRequest(blob_store.delete(cursor.primaryKey))
+ );
+ }
}
- }
- // Finally, remove all remaining blobs
- promise_list.push(
- waitForAllSynchronousCursor(
- blob_store.index("_id_attachment")
- .openKeyCursor(IDBKeyRange.only([id, name])),
- deleteEntry
- )
- );
+ // Finally, remove all remaining blobs
+ promise_list.push(
+ waitForAllSynchronousCursor(
+ blob_store.index("_id_attachment")
+ .openKeyCursor(IDBKeyRange.only([id, name])),
+ deleteEntry
+ )
+ );
- return RSVP.all(promise_list)
- .then(function () {
- if (delete_promise_list.length) {
- return RSVP.all(delete_promise_list);
- }
- });
- });
- });
+ return RSVP.all(promise_list)
+ .then(function () {
+ if (delete_promise_list.length) {
+ return RSVP.all(delete_promise_list);
+ }
+ });
+ });
+ });
});
};
IndexedDBStorage.prototype.removeAttachment = function (id, name) {
- return waitForOpenIndexedDB(this._database_name, function (db) {
- return waitForTransaction(db, ["attachment", "blob"], "readwrite",
- function (tx) {
- var promise_list = [],
- attachment_store = tx.objectStore("attachment"),
- blob_store = tx.objectStore("blob");
-
- function deleteEntry(cursor) {
- promise_list.push(
- waitForIDBRequest(blob_store.delete(cursor.primaryKey))
- );
- }
+ return waitForOpenIndexedDB(this._database_name, undefined,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["attachment", "blob"], "readwrite",
+ function (tx) {
+ var promise_list = [],
+ attachment_store = tx.objectStore("attachment"),
+ blob_store = tx.objectStore("blob");
- return RSVP.all([
- waitForIDBRequest(
- attachment_store.delete(buildKeyPath([id, name]))
- ),
- waitForAllSynchronousCursor(
- blob_store.index("_id_attachment")
- .openKeyCursor(IDBKeyRange.only([id, name])),
- deleteEntry
- )
- ])
- .then(function () {
- return RSVP.all(promise_list);
- });
+ function deleteEntry(cursor) {
+ promise_list.push(
+ waitForIDBRequest(blob_store.delete(cursor.primaryKey))
+ );
+ }
+
+ return RSVP.all([
+ waitForIDBRequest(
+ attachment_store.delete(buildKeyPath([id, name]))
+ ),
+ waitForAllSynchronousCursor(
+ blob_store.index("_id_attachment")
+ .openKeyCursor(IDBKeyRange.only([id, name])),
+ deleteEntry
+ )
+ ])
+ .then(function () {
+ return RSVP.all(promise_list);
+ });
- });
- });
+ });
+ });
};
jIO.addStorage("indexeddb", IndexedDBStorage);
-}(indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest, DOMError));
+}(jIO, RSVP, Blob, Math, IDBKeyRange, waitForOpenIndexedDB, waitForIDBRequest,
+ waitForAllSynchronousCursor, waitForTransaction));
diff --git a/src/jio.storage/indexstorage2.js b/src/jio.storage/indexstorage2.js
new file mode 100644
index 0000000000000000000000000000000000000000..aed84b93037c9b12fdf56d3f95bbdcf37401821d
--- /dev/null
+++ b/src/jio.storage/indexstorage2.js
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2019, Nexedi SA
+ *
+ * This program is free software: you can Use, Study, Modify and Redistribute
+ * it under the terms of the GNU General Public License version 3, or (at your
+ * option) any later version, as published by the Free Software Foundation.
+ *
+ * You can also Link and Combine this program with other software covered by
+ * the terms of any of the Free Software licenses or any of the Open Source
+ * Initiative approved licenses and Convey the resulting work. Corresponding
+ * source of such a combination shall include the source code for all other
+ * software used.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See COPYING file for full licensing terms.
+ * See https://www.nexedi.com/licensing for rationale and options.
+ */
+/*jslint nomen: true */
+/*global indexedDB, jIO, RSVP, Event, parseStringToObject, Set,
+ waitForTransaction, waitForAllSynchronousCursor, waitForIDBRequest,
+ waitForOpenIndexedDB*/
+
+(function (indexedDB, jIO, RSVP, parseStringToObject, waitForTransaction,
+ waitForAllSynchronousCursor, waitForIDBRequest, waitForOpenIndexedDB) {
+ "use strict";
+
+ function IndexStorage2(description) {
+ if (typeof description.database !== "string" ||
+ description.database === "") {
+ throw new TypeError("IndexStorage2 'database' description property " +
+ "must be a non-empty string");
+ }
+ if (description.index_keys && !(description.index_keys instanceof Array)) {
+ throw new TypeError("IndexStorage2 'index_keys' description property " +
+ "must be an Array");
+ }
+ if (description.version && (typeof description.version !== "number")) {
+ throw new TypeError("IndexStorage2 'version' description property " +
+ "must be a number");
+ }
+ this._sub_storage_description = description.sub_storage;
+ this._sub_storage = jIO.createJIO(description.sub_storage);
+ this._database_name = "jio:" + description.database;
+ this._index_keys = description.index_keys || [];
+ this._version = description.version;
+ this._signature_storage_name = description.database + "_signatures";
+ }
+
+ IndexStorage2.prototype.hasCapacity = function (name) {
+ return (name === "query") || (name === "limit") || (name === "list") ||
+ this._sub_storage.hasCapacity(name);
+ };
+
+ function isSubset(set1, set2) {
+ var i, values;
+ values = Array.from(set2);
+ for (i = 0; i < values.length; i += 1) {
+ if (!set1.has(values[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function filterDocValues(doc, keys) {
+ var filtered_doc = {}, i;
+ if (keys) {
+ for (i = 0; i < keys.length; i += 1) {
+ filtered_doc[keys[i]] = doc[keys[i]];
+ }
+ return filtered_doc;
+ }
+ return doc;
+ }
+
+ function getCursorResult(cursor, limit) {
+ var result = [], count = 0;
+ function pushLimitedMetadata(cursor) {
+ if (count >= limit[0] && count < limit[1]) {
+ result.push({id: cursor.primaryKey, value: {}});
+ }
+ count += 1;
+ }
+ return waitForAllSynchronousCursor(cursor, pushLimitedMetadata)
+ .then(function () {
+ return result;
+ });
+ }
+
+ function waitForRepairableOpenIndexedDB(db_name, version, index_keys,
+ sub_storage_description, signature_storage_name, upgrade_handler,
+ callback) {
+ var handleUpgradeNeeded = function (evt) {
+ return upgrade_handler(evt, index_keys, sub_storage_description,
+ signature_storage_name);
+ };
+ return waitForOpenIndexedDB(db_name, version, handleUpgradeNeeded,
+ callback);
+ }
+
+ function VirtualIDB(description) {
+ this._operations = description.operations;
+ }
+
+ function virtualOperation(type, context, function_arguments) {
+ var cancel_callback, operation = {}, promise;
+ function resolver(resolve, reject) {
+ cancel_callback = reject;
+ operation.type = type;
+ operation.arguments = function_arguments;
+ operation.onsuccess = resolve;
+ operation.onerror = reject;
+ }
+ function canceller() {
+ cancel_callback();
+ }
+ promise = new RSVP.Promise(resolver, canceller);
+ operation.promise = promise;
+ context._operations.push(operation);
+ return promise;
+ }
+
+ VirtualIDB.prototype.hasCapacity = function (name) {
+ return (name === "list");
+ };
+
+ VirtualIDB.prototype.put = function () {
+ return virtualOperation("put", this, arguments);
+ };
+
+ VirtualIDB.prototype.remove = function () {
+ return virtualOperation("remove", this, arguments);
+ };
+
+ VirtualIDB.prototype.get = function () {
+ return virtualOperation("get", this, arguments);
+ };
+
+ VirtualIDB.prototype.buildQuery = function () {
+ return virtualOperation("buildQuery", this, arguments);
+ };
+
+ VirtualIDB.prototype.allAttachments = function () {
+ return {};
+ };
+
+ jIO.addStorage("virtualidb", VirtualIDB);
+
+ function getRepairStorage(operations, sub_storage_description,
+ signature_storage_name) {
+ return jIO.createJIO({
+ type: "replicate",
+ local_sub_storage: sub_storage_description,
+ remote_sub_storage: {
+ type: "virtualidb",
+ operations: operations
+ },
+ signature_sub_storage: {
+ type: "query",
+ sub_storage: {
+ type: "indexeddb",
+ database: signature_storage_name
+ }
+ },
+ check_remote_modification: false,
+ check_remote_creation: false,
+ check_remote_deletion: false,
+ conflict_handling: 1,
+ parallel_operation_amount: 16
+ });
+ }
+
+ function handleVirtualGetSuccess(id, onsuccess, onerror) {
+ return function (result) {
+ if (result.target.result === undefined) {
+ return onerror(new jIO.util.jIOError("Cannot find document: " +
+ id, 404));
+ }
+ return onsuccess(result.target.result.doc);
+ };
+ }
+
+ function processVirtualOperation(operation, store, index_keys, disable_get) {
+ var request, get_success_handler;
+ if (operation.promise.isRejected) {
+ return;
+ }
+ if (operation.type === "put") {
+ request = store.put({
+ id: operation.arguments[0],
+ doc: filterDocValues(operation.arguments[1], index_keys),
+ });
+ request.onerror = operation.onerror;
+ return {request: request, onsuccess: operation.onsuccess};
+ }
+ if (operation.type === "get") {
+ // if storage was cleared, get can return without checking the database
+ if (disable_get) {
+ operation.onerror(new jIO.util.jIOError("Cannot find document: " +
+ operation.arguments[0], 404));
+ } else {
+ get_success_handler = handleVirtualGetSuccess(operation.arguments[0],
+ operation.onsuccess, operation.onerror);
+ request = store.get(operation.arguments[0]);
+ request.onerror = operation.onerror;
+ return {request: request, onsuccess: get_success_handler};
+ }
+ }
+ if (operation.type === "buildQuery") {
+ request = store.getAllKeys();
+ request.oerror = operation.onerror;
+ return {request: request, onsuccess: operation.onsuccess};
+ }
+ if (operation.type === "remove") {
+ request = store.delete(operation.arguments[0]);
+ request.onerror = operation.onerror;
+ return {request: request, onsuccess: operation.onsuccess};
+ }
+ }
+
+ function repairInTransaction(sub_storage_description, transaction,
+ index_keys, signature_storage_name, clear_storage) {
+ var repair_promise, repeatUntilPromiseFulfilled, store,
+ operations = [];
+ if (clear_storage) {
+ indexedDB.deleteDatabase("jio:" + signature_storage_name);
+ }
+ store = transaction.objectStore("index-store");
+ repair_promise = getRepairStorage(operations,
+ sub_storage_description, signature_storage_name).repair();
+ repeatUntilPromiseFulfilled = function repeatUntilPromiseFulfilled(
+ continuation_request,
+ continuation_resolve
+ ) {
+ var operation_result, next_continuation_request,
+ next_continuation_resolve;
+ continuation_request.onsuccess = function () {
+ if (continuation_resolve) {
+ continuation_resolve.apply(null, arguments);
+ }
+ while (operations.length !== 0) {
+ operation_result = processVirtualOperation(operations.shift(), store,
+ index_keys, clear_storage);
+ // use the current request to continue the repeat loop if possible
+ if (next_continuation_request && operation_result) {
+ operation_result.request.onsuccess = operation_result.onsuccess;
+ } else if (operation_result) {
+ next_continuation_request = operation_result.request;
+ next_continuation_resolve = operation_result.onsuccess;
+ }
+ }
+ if (repair_promise.isRejected) {
+ transaction.abort();
+ return;
+ }
+ if (repair_promise.isFulfilled) {
+ return;
+ }
+ return repeatUntilPromiseFulfilled(next_continuation_request ||
+ store.get("inexistent"), next_continuation_resolve);
+ };
+ };
+ return repeatUntilPromiseFulfilled(store.get("inexistent"));
+ }
+
+ function handleUpgradeNeeded(evt, index_keys, sub_storage_description,
+ signature_storage_name) {
+ var db = evt.target.result, store, i, current_indices, required_indices;
+ required_indices = new Set(index_keys.map(function (name) {
+ return "Index-" + name;
+ }));
+ if (db.objectStoreNames[0] === "index-store") {
+ store = evt.target.transaction.objectStore("index-store");
+ }
+
+ current_indices = new Set(store ? store.indexNames : []);
+ if (isSubset(current_indices, required_indices)) {
+ if (store) {
+ for (i = 0; i < store.indexNames.length; i += 1) {
+ if (!required_indices.has(store.indexNames[i])) {
+ store.deleteIndex(store.indexNames[i]);
+ }
+ }
+ }
+ } else {
+ if (store) {
+ db.deleteObjectStore("index-store");
+ current_indices.clear();
+ }
+ store = db.createObjectStore("index-store", {
+ keyPath: "id",
+ autoIncrement: false
+ });
+ for (i = 0; i < index_keys.length; i += 1) {
+ store.createIndex("Index-" + index_keys[i],
+ "doc." + index_keys[i], { unique: false });
+ }
+ return repairInTransaction(sub_storage_description,
+ evt.target.transaction, index_keys, signature_storage_name, true);
+ }
+ }
+
+ IndexStorage2.prototype._runQuery = function (key, value, limit) {
+ var context = this;
+
+ return RSVP.Queue()
+ .push(function () {
+ return waitForRepairableOpenIndexedDB(context._database_name,
+ context._version, context._index_keys,
+ context._sub_storage_description, context._signature_storage_name,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["index-store"], "readonly",
+ function (tx) {
+ if (limit) {
+ return getCursorResult(tx.objectStore("index-store")
+ .index("Index-" + key).openCursor(value), limit);
+ }
+ return waitForIDBRequest(tx.objectStore("index-store")
+ .index("Index-" + key).getAllKeys(value));
+ });
+ });
+ })
+ .push(function (result) {
+ if (limit) {
+ return result;
+ }
+ return result.target.result.map(function (item) {
+ return {id: item, value: {}};
+ });
+ });
+ };
+
+ IndexStorage2.prototype.buildQuery = function (options) {
+ var context = this, query;
+ if (options.query && !options.include_docs && !options.sort_on &&
+ !options.select_list) {
+ query = parseStringToObject(options.query);
+ if (query.type === "simple") {
+ if (context._index_keys.indexOf(query.key) !== -1) {
+ return context._runQuery(query.key, query.value, options.limit)
+ .then(function (result) {
+ return result;
+ });
+ }
+ }
+ }
+ return context._sub_storage.allDocs(options)
+ .push(function (result) {
+ return result.data.rows;
+ });
+ };
+
+ IndexStorage2.prototype.get = function () {
+ return this._sub_storage.get.apply(this._sub_storage, arguments);
+ };
+
+ IndexStorage2.prototype._put = function (id, value) {
+ var context = this;
+ if (context._index_keys.length === 0) {
+ return;
+ }
+ return waitForRepairableOpenIndexedDB(context._database_name,
+ context._version, context._index_keys, context._sub_storage_description,
+ context._signature_storage_name, handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["index-store"], "readwrite",
+ function (tx) {
+ return waitForIDBRequest(tx.objectStore("index-store").put({
+ "id": id,
+ "doc": filterDocValues(value, context._index_keys)
+ }));
+ });
+ });
+ };
+
+ IndexStorage2.prototype.put = function (id, value) {
+ var context = this;
+ return context._sub_storage.put(id, value)
+ .push(function () {
+ return context._put(id, value);
+ });
+ };
+
+ IndexStorage2.prototype.post = function (value) {
+ var context = this;
+ return context._sub_storage.post(value)
+ .push(function (id) {
+ return context._put(id, value)
+ .then(function () {
+ return id;
+ });
+ });
+ };
+
+ IndexStorage2.prototype.remove = function (id) {
+ var context = this;
+ return context._sub_storage.remove(id)
+ .push(function () {
+ return waitForRepairableOpenIndexedDB(context._database_name,
+ context._version, context._index_keys,
+ context._sub_storage_description, context._signature_storage_name,
+ handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["index-store"], "readwrite",
+ function (tx) {
+ return waitForIDBRequest(tx.objectStore("index-store")
+ .delete(id));
+ });
+ });
+ });
+ };
+
+ IndexStorage2.prototype.repair = function () {
+ var context = this;
+ return waitForRepairableOpenIndexedDB(context._database_name,
+ context._version, context._index_keys, context._sub_storage_description,
+ context._signature_storage_name, handleUpgradeNeeded, function (db) {
+ return waitForTransaction(db, ["index-store"], "readwrite",
+ function (tx) {
+ return repairInTransaction(context._sub_storage_description, tx,
+ context._index_keys, context._signature_storage_name);
+ });
+ });
+ };
+
+ IndexStorage2.prototype.getAttachment = function () {
+ return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
+ };
+
+ IndexStorage2.prototype.putAttachment = function () {
+ return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+ };
+
+ IndexStorage2.prototype.removeAttachment = function () {
+ return this._sub_storage.removeAttachment.apply(this._sub_storage,
+ arguments);
+ };
+
+ jIO.addStorage("index2", IndexStorage2);
+}(indexedDB, jIO, RSVP, parseStringToObject, waitForTransaction,
+ waitForAllSynchronousCursor, waitForIDBRequest, waitForOpenIndexedDB));
\ No newline at end of file
diff --git a/src/jio.storage/liststorage.js b/src/jio.storage/liststorage.js
new file mode 100644
index 0000000000000000000000000000000000000000..dd5311a8762fa2fd9c6f3c3e6bbd29cb453ca613
--- /dev/null
+++ b/src/jio.storage/liststorage.js
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2019, Nexedi SA
+ *
+ * This program is free software: you can Use, Study, Modify and Redistribute
+ * it under the terms of the GNU General Public License version 3, or (at your
+ * option) any later version, as published by the Free Software Foundation.
+ *
+ * You can also Link and Combine this program with other software covered by
+ * the terms of any of the Free Software licenses or any of the Open Source
+ * Initiative approved licenses and Convey the resulting work. Corresponding
+ * source of such a combination shall include the source code for all other
+ * software used.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See COPYING file for full licensing terms.
+ * See https://www.nexedi.com/licensing for rationale and options.
+ */
+/*jslint nomen: true*/
+/*global Set*/
+(function (jIO) {
+ "use strict";
+
+ function ListStorage(spec) {
+ this._sub_storage = jIO.createJIO(spec.sub_storage);
+ this._signature_storage = jIO.createJIO(spec.signature_storage);
+ }
+
+ ListStorage.prototype.get = function () {
+ return this._sub_storage.get.apply(this._sub_storage, arguments);
+ };
+ ListStorage.prototype.allAttachments = function () {
+ return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
+ };
+ ListStorage.prototype.post = function (value) {
+ var gadget = this;
+ return gadget._sub_storage.post(value)
+ .push(function (id) {
+ return gadget._signature_storage.put(id, {})
+ .push(function () {
+ return id;
+ });
+ });
+ };
+ ListStorage.prototype.put = function (id, value) {
+ var gadget = this;
+ return gadget._sub_storage.put(id, value)
+ .push(function (result) {
+ return gadget._signature_storage.put(id, {})
+ .push(function () {
+ return result;
+ });
+ });
+ };
+ ListStorage.prototype.remove = function (id) {
+ var gadget = this;
+ return gadget._sub_storage.remove(id)
+ .push(function (result) {
+ return gadget._signature_storage.remove(id)
+ .push(function () {
+ return result;
+ });
+ });
+ };
+ ListStorage.prototype.getAttachment = function () {
+ return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
+ };
+ ListStorage.prototype.putAttachment = function () {
+ return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+ };
+ ListStorage.prototype.removeAttachment = function () {
+ return this._sub_storage.removeAttachment.apply(this._sub_storage,
+ arguments);
+ };
+ ListStorage.prototype.repair = function () {
+ return this._sub_storage.repair.apply(this._sub_storage, arguments);
+ };
+ ListStorage.prototype.hasCapacity = function (name) {
+ if (name === "list") {
+ return this._signature_storage.hasCapacity('list');
+ }
+ return false;
+ };
+ ListStorage.prototype.buildQuery = function () {
+ return this._signature_storage.buildQuery();
+ };
+
+ jIO.addStorage('list', ListStorage);
+
+}(jIO));
\ No newline at end of file
diff --git a/src/jio.storage/nocapacitystorage.js b/src/jio.storage/nocapacitystorage.js
new file mode 100644
index 0000000000000000000000000000000000000000..f746b8838d90e1a42e9b8c303baa83fabe807626
--- /dev/null
+++ b/src/jio.storage/nocapacitystorage.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019, Nexedi SA
+ *
+ * This program is free software: you can Use, Study, Modify and Redistribute
+ * it under the terms of the GNU General Public License version 3, or (at your
+ * option) any later version, as published by the Free Software Foundation.
+ *
+ * You can also Link and Combine this program with other software covered by
+ * the terms of any of the Free Software licenses or any of the Open Source
+ * Initiative approved licenses and Convey the resulting work. Corresponding
+ * source of such a combination shall include the source code for all other
+ * software used.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See COPYING file for full licensing terms.
+ * See https://www.nexedi.com/licensing for rationale and options.
+ */
+/*jslint nomen: true*/
+(function (jIO) {
+ "use strict";
+
+ function NoCapacityStorage(spec) {
+ this._sub_storage = jIO.createJIO(spec.sub_storage);
+ }
+
+ NoCapacityStorage.prototype.get = function () {
+ return this._sub_storage.get.apply(this._sub_storage, arguments);
+ };
+ NoCapacityStorage.prototype.allAttachments = function () {
+ return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
+ };
+ NoCapacityStorage.prototype.post = function () {
+ return this._sub_storage.post.apply(this._sub_storage, arguments);
+ };
+ NoCapacityStorage.prototype.put = function () {
+ return this._sub_storage.put.apply(this._sub_storage, arguments);
+ };
+ NoCapacityStorage.prototype.remove = function () {
+ return this._sub_storage.remove.apply(this._sub_storage, arguments);
+ };
+ NoCapacityStorage.prototype.getAttachment = function () {
+ return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
+ };
+ NoCapacityStorage.prototype.putAttachment = function () {
+ return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+ };
+ NoCapacityStorage.prototype.removeAttachment = function () {
+ return this._sub_storage.removeAttachment.apply(this._sub_storage,
+ arguments);
+ };
+ NoCapacityStorage.prototype.repair = function () {
+ return this._sub_storage.repair.apply(this._sub_storage, arguments);
+ };
+
+ jIO.addStorage('nocapacity', NoCapacityStorage);
+
+}(jIO));
\ No newline at end of file
diff --git a/test/jio.storage/indexstorage2.tests.js b/test/jio.storage/indexstorage2.tests.js
new file mode 100644
index 0000000000000000000000000000000000000000..50391daffe31c9d27f64432e16377eed865cc953
--- /dev/null
+++ b/test/jio.storage/indexstorage2.tests.js
@@ -0,0 +1,1331 @@
+/*
+ * Copyright 2019, Nexedi SA
+ *
+ * This program is free software: you can Use, Study, Modify and Redistribute
+ * it under the terms of the GNU General Public License version 3, or (at your
+ * option) any later version, as published by the Free Software Foundation.
+ *
+ * You can also Link and Combine this program with other software covered by
+ * the terms of any of the Free Software licenses or any of the Open Source
+ * Initiative approved licenses and Convey the resulting work. Corresponding
+ * source of such a combination shall include the source code for all other
+ * software used.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See COPYING file for full licensing terms.
+ * See https://www.nexedi.com/licensing for rationale and options.
+ */
+/*jslint nomen: true */
+/*global indexedDB, Blob*/
+(function (jIO, QUnit, indexedDB, Blob) {
+ "use strict";
+ var test = QUnit.test,
+ stop = QUnit.stop,
+ start = QUnit.start,
+ ok = QUnit.ok,
+ expect = QUnit.expect,
+ deepEqual = QUnit.deepEqual,
+ equal = QUnit.equal,
+ module = QUnit.module,
+ throws = QUnit.throws;
+
+ function deleteIndexStorage2(storage) {
+ return new RSVP.Promise(function resolver(resolve, reject) {
+ var storage_deletion_request = indexedDB.deleteDatabase(
+ storage.__storage._database_name
+ ), signature_deletion_request = indexedDB.deleteDatabase(
+ "jio:" + storage.__storage._signature_storage_name
+ );
+ storage_deletion_request.onerror = reject;
+ storage_deletion_request.onblocked = reject;
+ storage_deletion_request.onsuccess = resolve;
+ signature_deletion_request.onerror = reject;
+ signature_deletion_request.onblocked = reject;
+ signature_deletion_request.onsuccess = resolve;
+ });
+ }
+
+ function idCompare(value1, value2) {
+ if (value1.id > value2.id) {
+ return 1;
+ }
+ if (value1.id < value2.id) {
+ return -1;
+ }
+ return 0;
+ }
+
+ /////////////////////////////////////////////////////////////////
+ // Custom test substorage definition
+ /////////////////////////////////////////////////////////////////
+ function DummyStorage3() {
+ return this;
+ }
+ jIO.addStorage("dummystorage3", DummyStorage3);
+
+ /////////////////////////////////////////////////////////////////
+ // indexStorage2.constructor
+ /////////////////////////////////////////////////////////////////
+ module("indexStorage2.constructor", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("Constructor without index_keys", function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+
+ equal(this.jio.__type, "index2");
+ equal(this.jio.__storage._sub_storage.__type, "dummystorage3");
+ equal(this.jio.__storage._database_name, "jio:index2_test");
+ equal(this.jio.__storage._signature_storage_name, "index2_test_signatures");
+ deepEqual(this.jio.__storage._index_keys, []);
+ });
+
+ test("Constructor incorrect description values", function () {
+
+ throws(
+ function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: 44,
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ },
+ function (error) {
+ ok(error instanceof TypeError);
+ equal(error.message, "IndexStorage2 'database' description property " +
+ "must be a non-empty string");
+ return true;
+ }
+ );
+
+ throws(
+ function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: "",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ },
+ function (error) {
+ ok(error instanceof TypeError);
+ equal(error.message, "IndexStorage2 'database' description property " +
+ "must be a non-empty string");
+ return true;
+ }
+ );
+
+ throws(
+ function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: "index_key",
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ },
+ function (error) {
+ ok(error instanceof TypeError);
+ equal(error.message, "IndexStorage2 'index_keys' description property" +
+ " must be an Array");
+ return true;
+ }
+ );
+ throws(
+ function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ version: "1",
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ },
+ function (error) {
+ ok(error instanceof TypeError);
+ equal(error.message, "IndexStorage2 'version' description property" +
+ " must be a number");
+ return true;
+ }
+ );
+ });
+
+ test("Constructor with index_keys and version", function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ version: 4,
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+
+ equal(this.jio.__type, "index2");
+ equal(this.jio.__storage._sub_storage.__type, "dummystorage3");
+ equal(this.jio.__storage._database_name, "jio:index2_test");
+ equal(this.jio.__storage._version, 4);
+ equal(this.jio.__storage._signature_storage_name, "index2_test_signatures");
+ deepEqual(this.jio.__storage._sub_storage_description,
+ {type: "dummystorage3"});
+ deepEqual(this.jio.__storage._index_keys, ["a", "b"]);
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // indexStorage2.hasCapacity
+ /////////////////////////////////////////////////////////////////
+ module("indexStorage2.hasCapacity", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("Test various capacities", function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "sort");
+ };
+
+ throws(
+ function () {
+ this.jio.hasCapacity("non");
+ },
+ function (error) {
+ ok(error instanceof jIO.util.jIOError);
+ equal(error.status_code, 501);
+ equal(error.message,
+ "Capacity 'non' is not implemented on 'dummystorage3'");
+ return true;
+ }
+ );
+
+ ok(this.jio.hasCapacity("query"));
+ ok(this.jio.hasCapacity("limit"));
+ ok(this.jio.hasCapacity("sort"));
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // indexStorage2.get
+ /////////////////////////////////////////////////////////////////
+ module("indexStorage2.get", {
+ setup: function () {
+ this.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: [],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ },
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("Get calls substorage", function () {
+ var context = this;
+ stop();
+ expect(2);
+
+ DummyStorage3.prototype.get = function (id) {
+ equal(id, "32");
+ return {"a": 3, "b": 2, "c": 8};
+ };
+
+ context.jio.get("32")
+ .then(function (result) {
+ deepEqual(result, {"a": 3, "b": 2, "c": 8});
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // indexStorage2.buildQuery
+ /////////////////////////////////////////////////////////////////
+ module("indexStorage2.buildQuery", {
+ setup: function () {
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "list") || (name === "include") || (name === "select");
+ };
+ DummyStorage3.prototype.buildQuery = function () {
+ return [];
+ };
+ },
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+
+ test("Sub-storage handles empty options", function () {
+ var context = this, call_count = 0;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(2);
+
+ DummyStorage3.prototype.put = function (id) {
+ return id;
+ };
+
+ DummyStorage3.prototype.buildQuery = function (options) {
+ call_count += 1;
+ if (call_count === 1) {
+ return [];
+ }
+ if (options.include_docs !== true) {
+ return [
+ {id: "2", value: {}},
+ {id: "32", value: {}},
+ {id: "16", value: {}},
+ {id: "21", value: {}}
+ ];
+ }
+ return [];
+ };
+
+ RSVP.all([
+ context.jio.put("2", {"a": "close", "b": 45}),
+ context.jio.put("16", {"a": "value", "b": 5}),
+ context.jio.put("21", {"a": "advice", "b": 12}),
+ context.jio.put("32", {"a": "recieve", "b": 76})
+ ])
+ .then(function () {
+ return context.jio.allDocs();
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 4);
+ deepEqual(result.data.rows.sort(idCompare), [{id: "16", value: {}},
+ {id: "2", value: {}}, {id: "21", value: {}}, {id: "32", value: {}}]);
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Sub storage capacities are used by default", function () {
+ var context = this, kemp = 0;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b", "c"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(3);
+
+ DummyStorage3.prototype.put = function (id) {
+ return id;
+ };
+
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "list") || (name === "include") || (name === "select")
+ || (name === "sort") || (name === "limit");
+ };
+
+ DummyStorage3.prototype.buildQuery = function (options) {
+ kemp += 1;
+ if (kemp === 1) {
+ return [];
+ }
+ if (options.include_docs === true) {
+ return [];
+ }
+ deepEqual(options, {include_docs: false, select_list: ["a", "c"],
+ limit: 3, sort_on: [["a", "descending"], ["b", "ascending"]]});
+ return [
+ {"id": "21", "value": {"a": "value", "c": "4"}},
+ {"id": "16", "value": {"a": "value", "c": "54"}},
+ {"id": "1", "value": {"a": "exhalt", "c": "28"}}
+ ];
+ };
+
+ RSVP.all([
+ context.jio.put("2", {"a": "close", "b": 45, "c": "78"}),
+ context.jio.put("16", {"a": "value", "b": 5, "c": "54"}),
+ context.jio.put("21", {"a": "value", "b": 12, "c": "4"}),
+ context.jio.put("7", {"a": "device", "b": 83, "c": "26"}),
+ context.jio.put("1", {"a": "exhalt", "b": 68, "c": "28"})
+ ])
+ .then(function () {
+ return context.jio.allDocs({select_list: ["a", "c"], limit: 3,
+ sort_on: [["a", "descending"], ["b", "ascending"]],
+ include_docs: false, });
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 3);
+ deepEqual(result.data.rows, [
+ {"id": "21", "value": {"a": "value", "c": "4"}},
+ {"id": "16", "value": {"a": "value", "c": "54"}},
+ {"id": "1", "value": {"a": "exhalt", "c": "28"}}
+ ]);
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Simple query matching single object", function () {
+ var context = this;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(4);
+
+ DummyStorage3.prototype.put = function (id, value) {
+ equal(id, "32");
+ deepEqual(value, {a: "3", b: "2"});
+ return id;
+ };
+
+ context.jio.put("32", {"a": "3", "b": "2"})
+ .then(function () {
+ return context.jio.allDocs({query: 'a: "3"'});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 1);
+ deepEqual(result.data.rows, [{"id": "32", "value": {}}]);
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Simple query matching multiple objects", function () {
+ var context = this;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(5);
+
+ DummyStorage3.prototype.put = function (id, value) {
+ if (id === "32") {
+ deepEqual(value, {a: "3", b: "1"});
+ }
+ if (id === "21") {
+ deepEqual(value, {a: "8", b: "1"});
+ }
+ if (id === "3") {
+ deepEqual(value, {a: "5", b: "1"});
+ }
+ return id;
+ };
+
+ RSVP.all([
+ context.jio.put("32", {a: "3", b: "1"}),
+ context.jio.put("21", {a: "8", b: "1"}),
+ context.jio.put("3", {a: "5", b: "1"})
+ ])
+ .then(function () {
+ return context.jio.allDocs({query: 'b: "1"'});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 3);
+ deepEqual(result.data.rows.sort(idCompare),
+ [
+ {"id": "32", "value": {}},
+ {"id": "21", "value": {}},
+ {"id": "3", "value": {}}
+ ].sort(idCompare));
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Querying with key without an index", function () {
+ var context = this;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(4);
+
+ DummyStorage3.prototype.put = function (id, value) {
+ equal(id, "32");
+ deepEqual(value, {"a": "3", "b": "2"});
+ return id;
+ };
+
+ context.jio.put("32", {"a": "3", "b": "2"})
+ .then(function () {
+ return context.jio.allDocs({query: 'b:"2"'});
+ })
+ .fail(function (error) {
+ equal(error.status_code, 501);
+ equal(error.message,
+ "Capacity 'query' is not implemented on 'dummystorage3'");
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Use existing data to build Index", function () {
+ var context = this, fake_data;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "c"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(2);
+
+ fake_data = {
+ "1": {a: "id54", b: 2, c: "night"},
+ "4": {a: "vn92", b: 7, c: "matter"},
+ "9": {a: "ru23", b: 3, c: "control"}
+ };
+
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "list") || (name === "select");
+ };
+ DummyStorage3.prototype.put = function (id, value) {
+ fake_data[id] = value;
+ return id;
+ };
+ DummyStorage3.prototype.get = function (id) {
+ return fake_data[id];
+ };
+ DummyStorage3.prototype.buildQuery = function () {
+ var keys = Object.keys(fake_data);
+ return keys.map(function (v) { return {id: v, value: {id: v}}; });
+ };
+
+ RSVP.all([
+ context.jio.put("3", {a: "ab43", b: 9, c: "absorb"}),
+ context.jio.put("5", {a: "gu31", b: 5, c: "control"}),
+ context.jio.put("7", {a: "zf76", b: 3, c: "rules"}),
+ context.jio.put("6", {a: "cc92", b: 6, c: "afraid"})
+ ])
+ .then(function () {
+ return context.jio.allDocs({query: 'c:"control"'});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 2);
+ deepEqual(result.data.rows.sort(idCompare), [{id: "5", value: {}},
+ {id: "9", value: {}}]);
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("No index keys provided but substorage supports querying", function () {
+ var context = this;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: [],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(3);
+
+ DummyStorage3.prototype.put = function (id) {
+ return id;
+ };
+ DummyStorage3.prototype.hasCapacity = function (capacity) {
+ return (capacity === "list") || (capacity === "query");
+ };
+ DummyStorage3.prototype.buildQuery = function (options) {
+ equal(options.query, 'a: "5"');
+ return [{id: "3", value: {}}];
+ };
+
+ RSVP.all([
+ context.jio.put("32", {a: "3", b: "1"}),
+ context.jio.put("21", {a: "8", b: "1"}),
+ context.jio.put("3", {a: "5", b: "1"})
+ ])
+ .then(function () {
+ return context.jio.allDocs({query: 'a: "5"'});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 1);
+ deepEqual(result.data.rows, [{"id": "3", "value": {}}]);
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Limit with query", function () {
+ var context = this;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(1);
+
+ DummyStorage3.prototype.put = function (id) {
+ return id;
+ };
+
+ RSVP.all([
+ context.jio.put("1", {"a": "55", "b": "2"}),
+ context.jio.put("2", {"a": "98", "b": "2"}),
+ context.jio.put("3", {"a": "75", "b": "2"}),
+ context.jio.put("8", {"a": "43", "b": "3"}),
+ context.jio.put("6", {"a": "21", "b": "2"}),
+ context.jio.put("16", {"a": "39", "b": "2"}),
+ context.jio.put("11", {"a": "16", "b": "2"})
+ ])
+ .then(function () {
+ return context.jio.allDocs({limit: [1, 3], query: "b:2"});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 2);
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Index keys are modified", function () {
+ var context = this, dummy_data;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ version: 1,
+ index_keys: ["a"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(8);
+
+ dummy_data = {};
+
+ DummyStorage3.prototype.put = function (id, value) {
+ dummy_data[id] = {id: id, doc: value, value: value};
+ return id;
+ };
+ DummyStorage3.prototype.get = function (id) {
+ if (dummy_data[id]) {
+ return dummy_data[id].doc;
+ }
+ throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+ };
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "list") || (name === "include") || (name === "select");
+ };
+ DummyStorage3.prototype.buildQuery = function () {
+ return Object.values(dummy_data);
+ };
+
+ RSVP.all([
+ context.jio.put("32", {"a": "3", "b": "2", "c": "inverse"}),
+ context.jio.put("5", {"a": "6", "b": "2", "c": "strong"}),
+ context.jio.put("14", {"a": "67", "b": "3", "c": "disolve"})
+ ])
+ .then(function () {
+ return context.jio.allDocs({query: 'a: "67"'});
+ })
+ .then(function (result) {
+ deepEqual(result.data.rows, [{"id": "14", "value": {}}]);
+ })
+ .then(function () {
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ version: 2,
+ index_keys: ["a", "b", "c"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ })
+ .then(function () {
+ return RSVP.all([
+ context.jio.put("18", {"a": "2", "b": "3", "c": "align"}),
+ context.jio.put("62", {"a": "3", "b": "2", "c": "disolve"})
+ ]);
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'b: "3"'});
+ })
+ .then(function (result) {
+ deepEqual(result.data.rows, [{"id": "14", "value": {}},
+ {"id": "18", "value": {}}]);
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'c: "disolve"'});
+ })
+ .then(function (result) {
+ deepEqual(result.data.rows, [{"id": "14", "value": {}},
+ {"id": "62", "value": {}}]);
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'a: "3"'});
+ })
+ .then(function (result) {
+ deepEqual(result.data.rows, [{"id": "32", "value": {}},
+ {"id": "62", "value": {}}]);
+ })
+ .then(function () {
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "c"],
+ version: 3,
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ })
+ .then(function () {
+ return context.jio.put("192", {"a": "3", "b": "3", "c": "disolve"});
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'a: "3"'});
+ })
+ .then(function (result) {
+ deepEqual(result.data.rows.sort(idCompare), [{"id": "192", "value": {}},
+ {"id": "32", "value": {}}, {"id": "62", "value": {}}]);
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'c: "disolve"'});
+ })
+ .then(function (result) {
+ deepEqual(result.data.rows.sort(idCompare), [{"id": "14", "value": {}},
+ {"id": "192", "value": {}}, {"id": "62", "value": {}}]);
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'b: "3"'});
+ })
+ .fail(function (error) {
+ equal(error.status_code, 501);
+ equal(error.message,
+ "Capacity 'query' is not implemented on 'dummystorage3'");
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("Manual repair", function () {
+ var context = this, fake_data;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "c"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(15);
+
+ fake_data = {
+ "1": {a: "id54", b: "9", c: "night"},
+ "4": {a: "vn92", b: "7", c: "matter"},
+ "9": {a: "ru23", b: "3", c: "control"},
+ "42": {a: "k422", b: "100", c: "grape"}
+ };
+
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "list") || (name === "select");
+ };
+ DummyStorage3.prototype.put = function (id, value) {
+ fake_data[id] = value;
+ return id;
+ };
+ DummyStorage3.prototype.get = function (id) {
+ if (fake_data[id]) {
+ return fake_data[id];
+ }
+ throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+ };
+ DummyStorage3.prototype.remove = function (id) {
+ delete fake_data[id];
+ return id;
+ };
+ DummyStorage3.prototype.buildQuery = function () {
+ var keys = Object.keys(fake_data);
+ return keys.map(function (v) { return {id: v, value: {}}; });
+ };
+
+ context.jio.allDocs({query: 'c: "control"'})
+ .then(function (result) {
+ equal(result.data.total_rows, 1);
+ deepEqual(result.data.rows, [{id: "9", value: {}}]);
+ })
+ .then(function () {
+ fake_data["2"] = {a: "zu64", b: "1", c: "matter"};
+ fake_data["13"] = {a: "tk32", b: "9", c: "matter"};
+ return context.jio.repair();
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'c: "matter"'});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 3);
+ deepEqual(result.data.rows.sort(idCompare), [{id: "13", value: {}},
+ {id: "2", value: {}}, {id: "4", value: {}}]);
+ })
+ .then(function () {
+ fake_data["2"] = {a: "zu64", b: "1", c: "observe"};
+ return context.jio.repair();
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'c: "observe"'});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 1);
+ deepEqual(result.data.rows, [{id: "2", value: {}}]);
+ })
+ .then(function () {
+ delete fake_data["2"];
+ return context.jio.repair();
+ })
+ .then(function () {
+ return context.jio.allDocs({query: 'c: "observe"'});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 0);
+ deepEqual(result.data.rows, []);
+ })
+ .then(function () {
+ return RSVP.all([
+ context.jio.put("43", {a: "t345", b: "101", c: "pear"}),
+ context.jio.put("44", {a: "j939", b: "121", c: "grape"}),
+ context.jio.put("45", {a: "q423", b: "131", c: "grape"}),
+ context.jio.remove("42")
+ ]);
+ })
+ .then(function () {
+ return context.jio.repair();
+ })
+ .then(function () {
+ return context.jio.allDocs({query: "c:grape"});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 2);
+ deepEqual(result.data.rows.sort(idCompare), [{id: "44", value: {}},
+ {id: "45", value: {}}]);
+ })
+ .then(function () {
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "c"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ delete fake_data["13"];
+ fake_data["3"] = {a: "gg38", b: "4", c: "matter"};
+ fake_data["9"] = {a: "tk32", b: "9", c: "matter"};
+ return context.jio.repair();
+ })
+ .then(function () {
+ return context.jio.allDocs({query: "c:matter"});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 3);
+ deepEqual(result.data.rows.sort(idCompare), [{id: "3", value: {}},
+ {id: "4", value: {}}, {id: "9", value: {}}]);
+ })
+ .then(function () {
+ delete fake_data["9"];
+ fake_data["3"] = {a: "xu76", b: "9", c: "night"};
+ fake_data["7"] = {a: "bn02", b: "9", c: "matter"};
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["b"],
+ version: 2,
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ return context.jio.allDocs({query: "b:9"});
+ })
+ .then(function (result) {
+ equal(result.data.total_rows, 3);
+ deepEqual(result.data.rows.sort(idCompare), [{id: "1", value: {}},
+ {id: "3", value: {}}, {id: "7", value: {}}]);
+ })
+ .then(function () {
+ return context.jio.allDocs({query: "c:matter"});
+ })
+ .fail(function (error) {
+ equal(error.message,
+ "Capacity 'query' is not implemented on 'dummystorage3'");
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // IndexStorage2.getAttachment
+ /////////////////////////////////////////////////////////////////
+ module("IndexStorage2.getAttachment", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("getAttachment called substorage getAttachment", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["name", "user"],
+ sub_storage: {
+ type: "dummystorage3"
+ },
+ }),
+ blob = new Blob([""]);
+
+ DummyStorage3.prototype.getAttachment = function (id, name) {
+ equal(id, "1");
+ equal(name, "test_name");
+ return blob;
+ };
+
+ jio.getAttachment("1", "test_name")
+ .then(function (result) {
+ equal(result, blob);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // IndexStorage2.putAttachment
+ /////////////////////////////////////////////////////////////////
+ module("IndexStorage2.putAttachment", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("putAttachment called substorage putAttachment", function () {
+ stop();
+ expect(4);
+
+ var jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["name", "user"],
+ sub_storage: {
+ type: "dummystorage3"
+ },
+ }),
+ blob = new Blob([""]);
+
+ DummyStorage3.prototype.putAttachment = function (id, name, blob2) {
+ equal(id, "1");
+ equal(name, "test_name");
+ deepEqual(blob2, blob);
+ return "OK";
+ };
+
+ jio.putAttachment("1", "test_name", blob)
+ .then(function (result) {
+ equal(result, "OK");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // IndexStorage2.removeAttachment
+ /////////////////////////////////////////////////////////////////
+ module("IndexStorage2.removeAttachment", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("removeAttachment called substorage removeAttachment", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["name", "user"],
+ sub_storage: {
+ type: "dummystorage3"
+ },
+ });
+
+ DummyStorage3.prototype.removeAttachment = function (id, name) {
+ equal(id, "1");
+ equal(name, "test_name");
+ return "removed";
+ };
+
+ jio.removeAttachment("1", "test_name")
+ .then(function (result) {
+ equal(result, "removed");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // indexStorage2.put
+ /////////////////////////////////////////////////////////////////
+ module("indexStorage2.put", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("Put creates index", function () {
+ var context = this, request, store, records;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(12);
+
+ DummyStorage3.prototype.put = function (id) {
+ return id;
+ };
+
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "list") || (name === "include") || (name === "select");
+ };
+
+ DummyStorage3.prototype.buildQuery = function () {
+ return [];
+ };
+
+ RSVP.all([
+ context.jio.put("32", {"a": "894", "b": "inversion", "c": 2}),
+ context.jio.put("33", {"a": "65", "b": "division", "c": 4}),
+ context.jio.put("34", {"a": "65", "b": "prolong", "c": 8})
+ ])
+ .then(function () {
+ return new RSVP.Promise(function (resolve) {
+ request = indexedDB.open("jio:index2_test");
+ request.onsuccess = function () {
+ resolve(request.result);
+ };
+ });
+ })
+ .then(function (result) {
+ equal(result.version, 1);
+ equal(result.name, "jio:index2_test");
+ equal(result.objectStoreNames.length, 1);
+ equal(result.objectStoreNames[0], "index-store");
+ store = result.transaction("index-store").objectStore("index-store");
+ equal(store.indexNames.length, 2);
+ equal(store.keyPath, "id");
+ deepEqual(Array.from(store.indexNames).sort(), ["Index-a", "Index-b"]);
+ equal(store.index("Index-a").keyPath, "doc.a");
+ equal(store.index("Index-b").keyPath, "doc.b");
+ equal(store.index("Index-a").unique, false);
+ equal(store.index("Index-b").unique, false);
+ return new RSVP.Promise(function (resolve) {
+ records = store.getAll();
+ records.onsuccess = function () {
+ resolve(records);
+ };
+ });
+ })
+ .then(function (values) {
+ deepEqual(values.result.sort(idCompare), [
+ {"id": "32", "doc": {"a": "894", "b": "inversion"}},
+ {"id": "33", "doc": {"a": "65", "b": "division"}},
+ {"id": "34", "doc": {"a": "65", "b": "prolong"}}
+ ]);
+ })
+ .then(function () {
+ request.result.close();
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // indexStorage2.post
+ /////////////////////////////////////////////////////////////////
+ module("indexStorage2.post", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("Post creates index", function () {
+ var context = this, request, store, records;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(12);
+
+ DummyStorage3.prototype.post = function (value) {
+ if (value.a === "5") {
+ return "1";
+ }
+ if (value.a === "62") {
+ return "2";
+ }
+ if (value.a === "37") {
+ return "3";
+ }
+ };
+
+ DummyStorage3.prototype.hasCapacity = function (name) {
+ return (name === "list") || (name === "include") || (name === "select");
+ };
+
+ DummyStorage3.prototype.buildQuery = function () {
+ return [];
+ };
+
+ RSVP.all([
+ context.jio.post({"a": "5", "b": "inversion", "c": 2}),
+ context.jio.post({"a": "62", "b": "division", "c": 4}),
+ context.jio.post({"a": "37", "b": "prolong", "c": 8})
+ ])
+ .then(function () {
+ return new RSVP.Promise(function (resolve) {
+ request = indexedDB.open("jio:index2_test");
+ request.onsuccess = function () {
+ resolve(request.result);
+ };
+ });
+ })
+ .then(function (result) {
+ equal(result.version, 1);
+ equal(result.name, "jio:index2_test");
+ equal(result.objectStoreNames.length, 1);
+ equal(result.objectStoreNames[0], "index-store");
+ store = result.transaction("index-store").objectStore("index-store");
+ equal(store.indexNames.length, 2);
+ equal(store.keyPath, "id");
+ deepEqual(Array.from(store.indexNames).sort(), ["Index-a", "Index-b"]);
+ equal(store.index("Index-a").keyPath, "doc.a");
+ equal(store.index("Index-b").keyPath, "doc.b");
+ equal(store.index("Index-a").unique, false);
+ equal(store.index("Index-b").unique, false);
+ return new RSVP.Promise(function (resolve) {
+ records = store.getAll();
+ records.onsuccess = function () {
+ resolve(records);
+ };
+ });
+ })
+ .then(function (values) {
+ deepEqual(values.result.sort(idCompare), [
+ {"id": "1", "doc": {"a": "5", "b": "inversion"}},
+ {"id": "2", "doc": {"a": "62", "b": "division"}},
+ {"id": "3", "doc": {"a": "37", "b": "prolong"}}
+ ]);
+ })
+ .then(function () {
+ request.result.close();
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // indexStorage2.remove
+ /////////////////////////////////////////////////////////////////
+ module("indexStorage2.remove", {
+ teardown: function () {
+ deleteIndexStorage2(this.jio);
+ }
+ });
+ test("Remove values", function () {
+ var context = this, request, store, records;
+ context.jio = jIO.createJIO({
+ type: "index2",
+ database: "index2_test",
+ index_keys: ["a", "b"],
+ sub_storage: {
+ type: "dummystorage3"
+ }
+ });
+ stop();
+ expect(3);
+
+ DummyStorage3.prototype.put = function (id) {
+ return id;
+ };
+
+ DummyStorage3.prototype.remove = function (id) {
+ equal(id, "33");
+ };
+
+ RSVP.all([
+ context.jio.put("32", {"a": "894", "b": "inversion", "c": 2}),
+ context.jio.put("33", {"a": "65", "b": "division", "c": 4}),
+ context.jio.put("34", {"a": "65", "b": "prolong", "c": 8})
+ ])
+ .then(function () {
+ return new RSVP.Promise(function (resolve) {
+ request = indexedDB.open("jio:index2_test");
+ request.onsuccess = function () {
+ resolve(request.result);
+ };
+ });
+ })
+ .then(function (result) {
+ store = result.transaction("index-store").objectStore("index-store");
+ return new RSVP.Promise(function (resolve) {
+ records = store.getAll();
+ records.onsuccess = function () {
+ resolve(records);
+ };
+ });
+ })
+ .then(function (values) {
+ deepEqual(values.result.sort(idCompare), [
+ {"id": "32", "doc": {"a": "894", "b": "inversion"}},
+ {"id": "33", "doc": {"a": "65", "b": "division"}},
+ {"id": "34", "doc": {"a": "65", "b": "prolong"}}
+ ]);
+ })
+ .then(function () {
+ return context.jio.remove("33");
+ })
+ .then(function () {
+ store = request.result.transaction("index-store")
+ .objectStore("index-store");
+ return new RSVP.Promise(function (resolve) {
+ records = store.getAll();
+ records.onsuccess = function () {
+ resolve(records);
+ };
+ });
+ })
+ .then(function (values) {
+ deepEqual(values.result.sort(idCompare), [
+ {"id": "32", "doc": {"a": "894", "b": "inversion"}},
+ {"id": "34", "doc": {"a": "65", "b": "prolong"}}
+ ]);
+ })
+ .then(function () {
+ request.result.close();
+ })
+ .fail(function (error) {
+ console.log(error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+}(jIO, QUnit, indexedDB, Blob));
\ No newline at end of file
diff --git a/test/jio.storage/liststorage.tests.js b/test/jio.storage/liststorage.tests.js
new file mode 100644
index 0000000000000000000000000000000000000000..873f9f778c2cf413e115fd81eaff60e5ede54273
--- /dev/null
+++ b/test/jio.storage/liststorage.tests.js
@@ -0,0 +1,432 @@
+/*
+ * Copyright 2019, Nexedi SA
+ *
+ * This program is free software: you can Use, Study, Modify and Redistribute
+ * it under the terms of the GNU General Public License version 3, or (at your
+ * option) any later version, as published by the Free Software Foundation.
+ *
+ * You can also Link and Combine this program with other software covered by
+ * the terms of any of the Free Software licenses or any of the Open Source
+ * Initiative approved licenses and Convey the resulting work. Corresponding
+ * source of such a combination shall include the source code for all other
+ * software used.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See COPYING file for full licensing terms.
+ * See https://www.nexedi.com/licensing for rationale and options.
+ */
+/*jslint nomen: true */
+/*global Blob*/
+(function (jIO, QUnit) {
+ "use strict";
+ var test = QUnit.test,
+ stop = QUnit.stop,
+ expect = QUnit.expect,
+ deepEqual = QUnit.deepEqual,
+ ok = QUnit.ok,
+ start = QUnit.start,
+ equal = QUnit.equal,
+ module = QUnit.module;
+
+ /////////////////////////////////////////////////////////////////
+ // Custom test substorage definition
+ /////////////////////////////////////////////////////////////////
+ function DummyStorage1() {
+ return this;
+ }
+ function DummyStorage2() {
+ return this;
+ }
+ jIO.addStorage('dummystorage1', DummyStorage1);
+ jIO.addStorage('dummystorage2', DummyStorage2);
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage constructor
+ /////////////////////////////////////////////////////////////////
+
+
+ module("ListStorage.constructor");
+
+ test("create storage", function () {
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+ equal(jio.__type, "list");
+ equal(jio.__storage._sub_storage.__type, "dummystorage1");
+ equal(jio.__storage._signature_storage.__type, "dummystorage2");
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.get
+ /////////////////////////////////////////////////////////////////
+
+ module("ListStorage.get");
+
+ test("get called substorage get", function () {
+ stop();
+ expect(2);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+
+ DummyStorage1.prototype.get = function (id) {
+ equal(id, "1");
+ return {"name": "test_name"};
+ };
+
+ jio.get("1")
+ .then(function (result) {
+ deepEqual(result, {"name": "test_name"});
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.allAttachments
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.allAttachments");
+ test("allAttachments called substorage allAttachments", function () {
+ stop();
+ expect(2);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+
+ DummyStorage1.prototype.allAttachments = function (id) {
+ equal(id, "1");
+ return {attachmentname: {}};
+ };
+
+ jio.allAttachments("1")
+ .then(function (result) {
+ deepEqual(result, {
+ attachmentname: {}
+ });
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.post
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.post");
+ test("post called substorage post", function () {
+ stop();
+ expect(4);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+
+ DummyStorage1.prototype.post = function (param) {
+ deepEqual(param, {"name": "test_name"});
+ return "posted";
+ };
+ DummyStorage2.prototype.put = function (id, value) {
+ equal(id, 'posted');
+ deepEqual(value, {});
+ return id;
+ };
+
+ jio.post({"name": "test_name"})
+ .then(function (result) {
+ equal(result, "posted");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.put
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.put");
+ test("put called substorage put", function () {
+ stop();
+ expect(5);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+ DummyStorage1.prototype.put = function (id, param) {
+ equal(id, "1");
+ deepEqual(param, {"name": "test_name"});
+ return id;
+ };
+ DummyStorage2.prototype.put = function (id, param) {
+ equal(id, "1");
+ deepEqual(param, {});
+ return id;
+ };
+
+ jio.put("1", {"name": "test_name"})
+ .then(function (result) {
+ equal(result, "1");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.remove
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.remove");
+ test("remove called substorage remove", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+ DummyStorage1.prototype.remove = function (id) {
+ equal(id, "1");
+ return id;
+ };
+ DummyStorage2.prototype.remove = function (id) {
+ equal(id, "1");
+ return id;
+ };
+
+ jio.remove("1")
+ .then(function (result) {
+ equal(result, "1");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.getAttachment
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.getAttachment");
+ test("getAttachment called substorage getAttachment", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ }),
+ blob = new Blob([""]);
+
+ DummyStorage1.prototype.getAttachment = function (id, name) {
+ equal(id, "1");
+ equal(name, "test_name");
+ return blob;
+ };
+
+ jio.getAttachment("1", "test_name")
+ .then(function (result) {
+ equal(result, blob);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.putAttachment
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.putAttachment");
+ test("putAttachment called substorage putAttachment", function () {
+ stop();
+ expect(4);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ }),
+ blob = new Blob([""]);
+
+ DummyStorage1.prototype.putAttachment = function (id, name, blob2) {
+ equal(id, "1");
+ equal(name, "test_name");
+ deepEqual(blob2, blob);
+ return "OK";
+ };
+
+ jio.putAttachment("1", "test_name", blob)
+ .then(function (result) {
+ equal(result, "OK");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.removeAttachment
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.removeAttachment");
+ test("removeAttachment called substorage removeAttachment", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+
+ DummyStorage1.prototype.removeAttachment = function (id, name) {
+ equal(id, "1");
+ equal(name, "test_name");
+ return "removed";
+ };
+
+ jio.removeAttachment("1", "test_name")
+ .then(function (result) {
+ equal(result, "removed");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.hasCapacity
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.hasCapacity");
+ test("list capacity is implemented", function () {
+ expect(2);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+
+ DummyStorage1.prototype.hasCapacity = function () {
+ return false;
+ };
+ DummyStorage2.prototype.hasCapacity = function (capacity) {
+ equal(capacity, 'list');
+ return true;
+ };
+
+ ok(jio.hasCapacity("list"));
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // ListStorage.buildQuery
+ /////////////////////////////////////////////////////////////////
+ module("ListStorage.buildQuery");
+ test("buildQuery calls substorage buildQuery", function () {
+ stop();
+ expect(1);
+
+ var jio = jIO.createJIO({
+ type: "list",
+ sub_storage: {
+ type: "dummystorage1"
+ },
+ signature_storage: {
+ type: "dummystorage2",
+ }
+ });
+
+ DummyStorage2.prototype.buildQuery = function () {
+ return [{"id": "1"}, {"id": "2"}];
+ };
+
+ jio.buildQuery()
+ .then(function (result) {
+ deepEqual(result, [{"id": "1"}, {"id": "2"}]);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+}(jIO, QUnit));
\ No newline at end of file
diff --git a/test/jio.storage/nocapacitystorage.tests.js b/test/jio.storage/nocapacitystorage.tests.js
new file mode 100644
index 0000000000000000000000000000000000000000..d942fa985c8dd2781d57a3b94d1da24851f6d61f
--- /dev/null
+++ b/test/jio.storage/nocapacitystorage.tests.js
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2019, Nexedi SA
+ *
+ * This program is free software: you can Use, Study, Modify and Redistribute
+ * it under the terms of the GNU General Public License version 3, or (at your
+ * option) any later version, as published by the Free Software Foundation.
+ *
+ * You can also Link and Combine this program with other software covered by
+ * the terms of any of the Free Software licenses or any of the Open Source
+ * Initiative approved licenses and Convey the resulting work. Corresponding
+ * source of such a combination shall include the source code for all other
+ * software used.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See COPYING file for full licensing terms.
+ * See https://www.nexedi.com/licensing for rationale and options.
+ */
+/*jslint nomen: true */
+/*global Blob*/
+(function (jIO, QUnit) {
+ "use strict";
+ var test = QUnit.test,
+ stop = QUnit.stop,
+ expect = QUnit.expect,
+ deepEqual = QUnit.deepEqual,
+ ok = QUnit.ok,
+ start = QUnit.start,
+ equal = QUnit.equal,
+ module = QUnit.module,
+ throws = QUnit.throws;
+
+ /////////////////////////////////////////////////////////////////
+ // Custom test substorage definition
+ /////////////////////////////////////////////////////////////////
+ function DummyStorage() {
+ return this;
+ }
+ jIO.addStorage('dummystorage', DummyStorage);
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage constructor
+ /////////////////////////////////////////////////////////////////
+
+ module("NoCapacityStorage.constructor");
+
+ test("create storage", function () {
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+ equal(jio.__type, "nocapacity");
+ equal(jio.__storage._sub_storage.__type, "dummystorage");
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.get
+ /////////////////////////////////////////////////////////////////
+
+ module("NoCapacityStorage.get");
+
+ test("get called substorage get", function () {
+ stop();
+ expect(2);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+
+ DummyStorage.prototype.get = function (id) {
+ equal(id, "1");
+ return {"name": "test_name"};
+ };
+
+ jio.get("1")
+ .then(function (result) {
+ deepEqual(result, {
+ "name": "test_name"
+ });
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.allAttachments
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.allAttachments");
+ test("allAttachments called substorage allAttachments", function () {
+ stop();
+ expect(2);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+
+ DummyStorage.prototype.allAttachments = function (id) {
+ equal(id, "1");
+ return {attachmentname: {}};
+ };
+
+ jio.allAttachments("1")
+ .then(function (result) {
+ deepEqual(result, {
+ attachmentname: {}
+ });
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.post
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.post");
+ test("post called substorage post", function () {
+ stop();
+ expect(2);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+
+ DummyStorage.prototype.post = function (param) {
+ deepEqual(param, {"name": "test_name"});
+ return "posted";
+ };
+
+ jio.post({"name": "test_name"})
+ .then(function (result) {
+ equal(result, "posted");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.put
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.put");
+ test("put called substorage put", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+ DummyStorage.prototype.put = function (id, param) {
+ equal(id, "1");
+ deepEqual(param, {"name": "test_name"});
+ return id;
+ };
+
+ jio.put("1", {"name": "test_name"})
+ .then(function (result) {
+ equal(result, "1");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.remove
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.remove");
+ test("remove called substorage remove", function () {
+ stop();
+ expect(2);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+ DummyStorage.prototype.remove = function (id) {
+ deepEqual(id, "1");
+ return id;
+ };
+
+ jio.remove("1")
+ .then(function (result) {
+ equal(result, "1");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.getAttachment
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.getAttachment");
+ test("getAttachment called substorage getAttachment", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ }),
+ blob = new Blob([""]);
+
+ DummyStorage.prototype.getAttachment = function (id, name) {
+ equal(id, "1");
+ equal(name, "test_name");
+ return blob;
+ };
+
+ jio.getAttachment("1", "test_name")
+ .then(function (result) {
+ equal(result, blob);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.putAttachment
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.putAttachment");
+ test("putAttachment called substorage putAttachment", function () {
+ stop();
+ expect(4);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ }),
+ blob = new Blob([""]);
+
+ DummyStorage.prototype.putAttachment = function (id, name, blob2) {
+ equal(id, "1");
+ equal(name, "test_name");
+ deepEqual(blob2, blob);
+ return "OK";
+ };
+
+ jio.putAttachment("1", "test_name", blob)
+ .then(function (result) {
+ equal(result, "OK");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacity.removeAttachment
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.removeAttachment");
+ test("removeAttachment called substorage removeAttachment", function () {
+ stop();
+ expect(3);
+
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+
+ DummyStorage.prototype.removeAttachment = function (id, name) {
+ equal(id, "1");
+ equal(name, "test_name");
+ return "removed";
+ };
+
+ jio.removeAttachment("1", "test_name")
+ .then(function (result) {
+ equal(result, "removed");
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // NoCapacityStorage.hasCapacity
+ /////////////////////////////////////////////////////////////////
+ module("NoCapacityStorage.hasCapacity");
+ test("list capacity is not implemented", function () {
+ DummyStorage.prototype.hasCapacity = function () {
+ return true;
+ };
+ var jio = jIO.createJIO({
+ type: "nocapacity",
+ sub_storage: {
+ type: "dummystorage"
+ }
+ });
+ throws(
+ function () {
+ jio.hasCapacity("list");
+ },
+ function (error) {
+ equal(error.message,
+ "Capacity 'list' is not implemented on 'nocapacity'");
+ return true;
+ }
+ );
+ });
+
+}(jIO, QUnit));
\ No newline at end of file
diff --git a/test/node.js b/test/node.js
index edc60c41d58b5b472242745b7130a5a2acab89a3..f5db7eeda5382d366169c20c90c80178ec951dba 100644
--- a/test/node.js
+++ b/test/node.js
@@ -64,7 +64,10 @@
'test/jio.storage/erp5storage.tests.js',
'test/jio.storage/fbstorage.tests.js',
'test/jio.storage/gdrivestorage.tests.js',
+ 'test/jio.storage/indexstorage2.tests.js',
+ 'test/jio.storage/liststorage.tests.js',
'test/jio.storage/memorystorage.tests.js',
+ 'test/jio.storage/nocapacitystorage.tests.js',
'test/jio.storage/querystorage.tests.js',
'test/jio.storage/replicatestorage.tests.js',
'test/jio.storage/replicatestorage_fastrepair.tests.js',
diff --git a/test/tests.html b/test/tests.html
index a32229e759ab1b9049b5464254d83216a62ba970..df48945f10544ddadee6097f9c28d17a0b0f674f 100644
--- a/test/tests.html
+++ b/test/tests.html
@@ -77,6 +77,9 @@ See https://www.nexedi.com/licensing for rationale and options.
+
+
+