Is it possible to abort an IndexedDB upgrade event? - javascript

I'm trying to implement an IndexedDB interface that allows a user to migrate data from a previous database (DB) version (oldVersion detected in onupgradeneeded handler, event.target.oldVersion) to a recent version (currentVersion about to be open), meaning I need to:
cancel or abort the currentVersion open-operation
open the previous DB (oldVersion detected in onupgradeneeded)
read its data
close the DB
(continue with the normal process of opening a currentVersion DB)
I'm having trouble when opening oldVersion because I cannot abort (Exception 11) the currentVersion upgrade event (it is also not cancelable).
Exception 11: An operation was called on an object on which it is not allowed or at a time when it is not allowed.
Is it possible to somehow cancel or abort the upgrade event for currentVersion, in order for me to open the oldVersion...?
Note: ...If not, is there anyother way to migrate data from older versions of a DB that I'm missing?

Is it possible to somehow cancel or abort the upgrade event for currentVersion, in order for me to open the oldVersion...?
var rq = indexedDB.open(name, ver);
rq.onupgradeneeded = function(e) {
rq.transaction.abort();
};
rq.onsuccess = function(e) { console.log('THIS SHOULD NOT RUN'); };
rq.onerror = function(e) { console.log('This should run'); };
is there anyother way to migrate data from older versions of a DB that I'm missing?
Usually this is the whole purpose of upgradeneeded - during the verionchange transaction provided to you, you migrate the data and schema from the old version.
var rq = indexedDB.open(name, 2);
rq.onupgradeneeded = function(e) {
var db = rq.result;
if (e.oldVersion < 1) {
// database didn't exist at all, create new schema
db.createObjectStore('store2');
} else if (e.oldVersion < 2) {
// do the migration - assumes v1 had 'store1'
var store1 = rq.transaction.objectStore('store1');
var store2 = db.createObjectStore('store2');
var r = store1.openCursor();
r.onsuccess = function() {
var cursor = r.result;
if (cursor) {
store2.put(cursor.value, cursor.key);
cursor.continue();
} else {
// migration done, delete old store
db.deleteObjectStore('store1');
}
};
}
};

Related

Uncaught DOMException: Failed to execute 'put' on 'IDBObjectStore': Evaluating the object store's key path did not yield a value at request.onsuccess

I'm trying to Store some application data using indexedDB
Here is my code
function _getLocalApplicationCache(_, payload) {
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.shimIndexedDB;
if (!indexedDB) {
if (__DEV__) {
console.error("IndexedDB could not found in this browser.");
}
}
const request = indexedDB.open("ApplicationCache", 1);
request.onerror = event => {
if (__DEV__) {
console.error("An error occurred with IndexedDB.");
console.error(event);
}
return;
};
request.onupgradeneeded = function () {
const db = request.result;
const store = db.createObjectStore("swimlane", {keyPath: "id", autoIncrement: true});
store.createIndex("keyData", ["name"], {unique: false});
};
request.onsuccess = () => {
// creating the transition
const db = request.result;
const transition = db.transaction("swimlane", "readwrite");
// Reference to our object store that holds the swimlane data;
const store = transition.objectStore("swimlane");
const swimlaneData = store.index("keyData");
payload = JSON.parse(JSON.stringify(payload));
store.put(payload);
const Query = swimlaneData.getAll(["keyData"]);
Query.onsuccess = () => {
if (__DEV__) {
console.log("Application Cache is loaded", Query.result);
}
};
transition.oncomplete = () => {
db.close();
};
};
}
If I do use different version then 1 here --> indexedDB.open("ApplicationCache", 1);
I'm getting a error like they keyPath is already exist. And other than than for version 1 I'm getting this error.
Can someone please help me where i'm doing wrong.
Review the introductory materials on using indexedDB.
If you did something like connect and create a database without a schema, or created an object store without an explicit key path, and then you stored some objects, and then you edited the upgradeneeded callback to specify the keypath, and then never triggered the upgradeneeded callback to run because you continue to use current version number instead of a newer version number, it would be one possible explanation for this error.
The upgradeneeded callback needs to have logic that checks for whether the object stores and indices already exist, and only create them if they do not exist. If the store does not exist, create it and its indices. If the store exists and the indices do not, add indices to the store. If the store exists and the indices exist, do nothing.
You need to trigger the upgradeneeded callback to run after changing your database schema by connecting with a higher version number. If you do not connect with a higher version number, the callback never runs, so you will end up connecting to the older version where your schema changes have not taken place.

how fire upgradeneeded event with out upgrade version of the indedexdb

i have a question about the event "upgradeneeded".
i need to check the data base every time the user reload the page, but how to fire it with out upgrade the version of the indexeddb, or it's the unique solution ?
request.addEventListener('upgradeneeded', event => {
var db = event.target.result;
var planningObjectStore = db.transaction("planningSave", "read").objectStore("planningSave");
});
"upgradeneeded" is only fired when you need to change the schema, which you signal by changing the version number. If you're not modifying the schema - e.g. you're just reading/writing to existing object stores - use the "success" event instead. Also, there's an implicit transaction within the "upgradeneeded" event, so no need to call transaction() there.
var request = indexedDB.open("mydb", 1); // version 1
// only fires for newly created databases, before "success"
request.addEventListener("upgradeneeded", event => {
var db = event.target.result;
var planningObjectStore = db.createObjectStore("planningSave");
// write initial data into the store
});
// fires after any successful open of the database
request.addEventListener("success", event => {
var db = event.target.result;
var tx = db.transaction("planningSave");
var planningObjectStore = tx.objectStore("planningSave");
// read data within the new transaction
});

Safari: IndexedDB: Cannot create object stores in separate transactions

It appears that the Safari and iPhone web browsers are incapable of allowing the user to create different object stores from separate transactions. This is even the case when the user closes the database, increments the version number and then uses createObjectStore() within the onupgradedneeded callback.
Is there a workaround?
For example, visit http://bl.ocks.org/redgeoff/1dea140c52397d963377 in Safari and you'll get an alert with the "AbortError" when Safari attempts to create the 2nd object store.
For convenience, here is the same snippet of code:
var idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB
|| window.msIndexedDB;
// Generate a unique db name as IndexedDB is very delicate and we want our test
// to focus on a new DB
var dbName = 'mydb' + '_' + (new Date()).getTime() + '_'
+ Math.round(1000000*Math.random());
var db = null;
var version = 1;
var open = function (version, onSuccess, onUpgradeNeeded) {
var request = null;
if (version) {
request = idb.open(dbName, version);
} else { // 1st time opening?
request = idb.open(dbName);
}
request.onupgradeneeded = function () {
if (onUpgradeNeeded) {
onUpgradeNeeded(request);
}
};
request.onsuccess = function () {
db = request.result;
if (onSuccess) {
onSuccess(request);
}
};
request.onerror = function () {
console.log('error=', request.error);
alert('error=' + JSON.stringify(request.error));
};
};
var createObjectStore = function (name, callback) {
db.close(); // synchronous
version++; // increment version to trigger onupgradeneeded
open(version, callback, function (request) {
request.result.createObjectStore(name, {
keyPath: 'id'
});
});
};
// NOTE: we could create the first store when opening the DB for the first time, but we'll keep
// things simple and reuse our createObjectStore code for both object stores
open(null, function () {
createObjectStore('store1', function () {
createObjectStore('store2', function () {
console.log('done creating both stores');
});
});
});
I tried using a sleep of 2 secs after the DB is closed and reopened and that doesn't appear to work. If there is no workaround then this essentially means that you cannot use the IndexedDB implementation in Safari to dynamically create object stores, which means that you need to know all your object stores before creating a DB.
Unless I am mistaken and someone has a workaround, the best way to dynamically add object stores is to implement a db-per-object-store design. In other words, you should create a new database whenever you need to create a new object store.
Another good option is to use https://github.com/axemclion/IndexedDBShim to emulate IndexedDB with WebSQL.

InvalidStateError while opening IndexedDB in Firefox

In Firefox 17.0.1 when I try to open the IndexedDB database, Firebug console shows me an InvalidStateError exception. Also request.onerror event is raised, but event.target.errorCode is undefined.
if (window.indexedDB) {
var request = window.indexedDB.open('demo', 1);
request.onsuccess = function(event) {
// not raised
};
request.onupgradeneeded = function(event) {
// not raised
};
request.onerror = function(event) {
// raised with InvalidStateError
};
}
Does anyone have experience with IndexedDB in Firefox?
Update
Firefox 18.0.1 has the same behavior. Comlete source.
I answer because the problem still exists (in Firefox 54). This happens if you:
use Firefox in private mode
or switch between different Firefox versions (https://bugzilla.mozilla.org/show_bug.cgi?id=1236557, https://bugzilla.mozilla.org/show_bug.cgi?id=1331103)
To prevent the InvalidStateError a try catch isn't working (but useful for other errors, e.g. disabled cookies), instead you need event.preventDefault(). Yes I know, too easy to be true. :)
if (window.indexedDB) {
var request = window.indexedDB.open('demo', 1);
request.onsuccess = function(event) {
// not raised
};
request.onupgradeneeded = function(event) {
// not raised
};
request.onerror = function(event) {
// raised with no InvalidStateError
if (request.error && request.error.name === 'InvalidStateError') {
event.preventDefault();
}
};
}
Kudos go to https://bugzilla.mozilla.org/show_bug.cgi?id=1331103#c3.
I am pretty sure the error you get is a version error, meaning the current version of the database is higher then the version you are opening the database with. If you take a look in event.target.error you will see that the name will contain "VersionError".
An other possibility is that you will see "AbortError", that would mean that the VERSION_CHANGE transaction was aborted. Meaning there was an error in the onupgradeneeded event that caused an abort. You could get this if you are creating an object store that already exists.
I see no other possibilities than these too, if not provide some more info about the error you get.
You need to create the object store in a separate transaction, you're lumping both the open database and create object store transaction into the same event.
Also you can't have both autoincrement and a path as options to your object store. You have to pick one or the other.
Here's the code that will get your example going:
function initDB() {
if (window.indexedDB) {
var request = window.indexedDB.open('demo', 1);
request.onsuccess = function(event) {
db = event.target.result;
createObjectStore();
};
request.onupgradeneeded = function(event) {
db = event.target.result;
$('#messages').prepend('blah blah<br/>');
};
request.onerror = function(event) {
$('#messages').prepend('Chyba databáze #' + event.target.errorCode + '<br/>');
};
}
}
function createObjectStore() {
db.close();
var request = window.indexedDB.open('demo', 2);
request.onsuccess = function(event) {
db = event.target.result;
showDB();
};
request.onupgradeneeded = function(event) {
db = event.target.result;
$('#messages').prepend('yeah yeah yeah<br/>');
var store = db.createObjectStore('StoreName', { keyPath: 'id' });
store.createIndex('IndexName', 'id', { unique: true });
};
request.onerror = function(event) {
$('#messages').prepend('Chyba databáze #' + event.target.errorCode + '<br/>');
};
}
If you start getting stuck you can take a look at some indexeddb code I wrote for the Firefox addon-sdk. The code is more complicated than what you need but you'll be able to see all the events, errors, and order of transactions that need to happen. https://github.com/clarkbw/indexed-db-storage
Good luck!
FireFox will also throw an "InvalidStateError" when using IndexedDB, if the browser is set to "Do not store history" in the privacy tab of the FireFox settings.
I believe FireFox basically runs in incognito mode when that setting is set.
IndexedDB is not available when running FireFox in private mode.

How do I update data in indexedDB?

I have tried to get some information from W3C regarding the update of an objectStore item in a indexedDB database, but with not so much susccess.
I found here a way to do it, but it doesn't really work for me.
My implementation is something like this
DBM.activitati.edit = function(id, obj, callback){
var transaction = DBM.db.transaction(["activitati"], IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("activitati");
var keyRange = IDBKeyRange.only(id);
objCursor = objectStore.openCursor(keyRange);
objCursor.onsuccess = function(e){
var cursor = e.target.result;
console.log(obj);
var request = cursor.update(obj);
request.onsuccess = function(){
callback();
}
request.onerror = function(e){
conosole.log("DBM.activitati.edit -> error " + e);
}
}
objCursor.onerror = function(e){
conosole.log("DBM.activitati.edit -> error " + e);
}
}
I have all DBM.activitati.(add | remove | getAll | getById | getByIndex) methods working, but I can not resolve this.
If you know how I can manage it, please, do tell!
Thank you!
Check out this jsfiddle for some examples on how to update IDB records. I worked on that with another StackOverflower -- it's a pretty decent standalone example of IndexedDB that uses indexes and does updates.
The method you seem to be looking for is put, which will either insert or update a record if there are unique indexes. In that example fiddle, it's used like this:
phodaDB.indexedDB.addUser = function(userObject){
//console.log('adding entry: '+entryTxt);
var db = phodaDB.indexedDB.db;
var trans = db.transaction(["userData"],IDBTransaction.READ_WRITE);
var store = trans.objectStore("userData");
var request = store.put(userObject);
request.onsuccess = function(e){
phodaDB.indexedDB.getAllEntries();
};
request.onerror = function(e){
console.log('Error adding: '+e);
};
};
For what it's worth, you've got some possible syntax errors, misspelling "console" in console.log as "conosole".
A bit late for an answer, but possible it helps others. I still stumbled -as i guess- over the same problem, but it's very simple:
If you want to INSERT or UPDATE records you use objectStore.put(object) (help)
If you only want to INSERT records you use objectStore.add(object) (help)
So if you use add(object), and a record key still exists in DB, it will not overwritten and fires error 0 "ConstraintError: Key already exists in the object store".
If you use put(object), it will be overwritten.
this is case of update infos of an user object
var transaction = db.transaction(["tab_user"], "readwrite");
var store = transaction.objectStore("tab_user");
var req = store.openCursor();
req.onerror = function(event) {
console.log("case if have an error");
};
req.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor){
if(cursor.value.idUser == users.idUser){//we find by id an user we want to update
var user = {};
user.idUser = users.idUser ;
user.nom = users.nom ;
var res = cursor.update(user);
res.onsuccess = function(e){
console.log("update success!!");
}
res.onerror = function(e){
console.log("update failed!!");
}
}
cursor.continue();
}
else{
console.log("fin mise a jour");
}
}
I'm a couple of years late, but thought it'd be nice to add my two cents in.
First, check out BakedGoods if you don't want to deal with the complex IndexedDB API.
It's a library which establishes a uniform interface that can be used to conduct storage operations in all native, and some non-native client storage facilities. It also maintains the flexibility and options afforded to the user by each. Oh, and it's maintained by yours truly :) .
With it, placing one or more data items in an object store can be as simple as:
bakedGoods.set({
data: [{key: "key1", value: "value1"}, {key: "key2", value: "value2"}),
storageTypes: ["indexedDB"],
complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj){}
});
Now to answer the actual question...
Lets begin by aggregating the valuable information spread across the existing answers:
IDBObjectStore.put() adds a new record to the store, or updates an existing one
IDBObjectStore.add() adds a new record to the store
IDBCursor.update() updates the record at the current position of the cursor
As one can see, OP is using an appropriate method to update a record. There are, however, several things in his/her code, unrelated to the method, that are incorrect (with respect to the API today at least). I've identified and corrected them below:
var cursorRequest = objectStore.openCursor(keyRange); //Correctly define result as request
cursorRequest.onsuccess = function(e){ //Correctly set onsuccess for request
var objCursor = cursorRequest.result; //Get cursor from request
var obj = objCursor.value; //Get value from existing cursor ref
console.log(obj);
var request = objCursor.update(obj);
request.onsuccess = function(){
callback();
}
request.onerror = function(e){
console.log("DBM.activitati.edit -> error " + e); //Use "console" to log :)
}
}
cursorRequest.onerror = function(e){ //Correctly set onerror for request
console.log("DBM.activitati.edit -> error " + e); //Use "console" to log :)
}

Categories

Resources