blob: dbaae378ebf218b9f2fc606094930291ce4e8bf8 [file] [log] [blame]
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for an IndexedDB object store.
*
*/
goog.provide('goog.db.ObjectStore');
goog.require('goog.async.Deferred');
goog.require('goog.db.Cursor');
goog.require('goog.db.Error');
goog.require('goog.db.Index');
goog.require('goog.debug');
goog.require('goog.events');
/**
* Creates an IDBObjectStore wrapper object. Object stores have methods for
* storing and retrieving records, and are accessed through a transaction
* object. They also have methods for creating indexes associated with the
* object store. They can only be created when setting the version of the
* database. Should not be created directly, access object stores through
* transactions.
* @see goog.db.IndexedDb#setVersion
* @see goog.db.Transaction#objectStore
*
* @param {!IDBObjectStore} store The backing IndexedDb object.
* @constructor
*
* TODO(arthurhsu): revisit msg in exception and errors in this class. In newer
* Chrome (v22+) the error/request come with a DOM error string that is
* already very descriptive.
* @final
*/
goog.db.ObjectStore = function(store) {
/**
* Underlying IndexedDB object store object.
*
* @type {!IDBObjectStore}
* @private
*/
this.store_ = store;
};
/**
* @return {string} The name of the object store.
*/
goog.db.ObjectStore.prototype.getName = function() {
return this.store_.name;
};
/**
* Helper function for put and add.
*
* @param {string} fn Function name to call on the object store.
* @param {string} msg Message to give to the error.
* @param {*} value Value to insert into the object store.
* @param {IDBKeyType=} opt_key The key to use.
* @return {!goog.async.Deferred} The resulting deferred request.
* @private
*/
goog.db.ObjectStore.prototype.insert_ = function(fn, msg, value, opt_key) {
// TODO(user): refactor wrapping an IndexedDB request in a Deferred by
// creating a higher-level abstraction for it (mostly affects here and
// goog.db.Index)
var d = new goog.async.Deferred();
var request;
try {
// put or add with (value, undefined) throws an error, so we need to check
// for undefined ourselves
if (opt_key) {
request = this.store_[fn](value, opt_key);
} else {
request = this.store_[fn](value);
}
} catch (ex) {
msg += goog.debug.deepExpose(value);
if (opt_key) {
msg += ', with key ' + goog.debug.deepExpose(opt_key);
}
d.errback(goog.db.Error.fromException(ex, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
msg += goog.debug.deepExpose(value);
if (opt_key) {
msg += ', with key ' + goog.debug.deepExpose(opt_key);
}
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Adds an object to the object store. Replaces existing objects with the
* same key.
*
* @param {*} value The value to put.
* @param {IDBKeyType=} opt_key The key to use. Cannot be used if the
* keyPath was specified for the object store. If the keyPath was not
* specified but autoIncrement was not enabled, it must be used.
* @return {!goog.async.Deferred} The deferred put request.
*/
goog.db.ObjectStore.prototype.put = function(value, opt_key) {
return this.insert_(
'put',
'putting into ' + this.getName() + ' with value',
value,
opt_key);
};
/**
* Adds an object to the object store. Requires that there is no object with
* the same key already present.
*
* @param {*} value The value to add.
* @param {IDBKeyType=} opt_key The key to use. Cannot be used if the
* keyPath was specified for the object store. If the keyPath was not
* specified but autoIncrement was not enabled, it must be used.
* @return {!goog.async.Deferred} The deferred add request.
*/
goog.db.ObjectStore.prototype.add = function(value, opt_key) {
return this.insert_(
'add',
'adding into ' + this.getName() + ' with value ',
value,
opt_key);
};
/**
* Removes an object from the store. No-op if there is no object present with
* the given key.
*
* @param {IDBKeyType} key The key to remove objects under.
* @return {!goog.async.Deferred} The deferred remove request.
*/
goog.db.ObjectStore.prototype.remove = function(key) {
var d = new goog.async.Deferred();
var request;
try {
request = this.store_['delete'](key);
} catch (err) {
var msg = 'removing from ' + this.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
var self = this;
request.onerror = function(ev) {
var msg = 'removing from ' + self.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Gets an object from the store. If no object is present with that key
* the result is {@code undefined}.
*
* @param {IDBKeyType} key The key to look up.
* @return {!goog.async.Deferred} The deferred get request.
*/
goog.db.ObjectStore.prototype.get = function(key) {
var d = new goog.async.Deferred();
var request;
try {
request = this.store_.get(key);
} catch (err) {
var msg = 'getting from ' + this.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback(ev.target.result);
};
var self = this;
request.onerror = function(ev) {
var msg = 'getting from ' + self.getName() + ' with key ' +
goog.debug.deepExpose(key);
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Gets all objects from the store and returns them as an array.
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
* over the whole object store.
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
* moves in a forward direction with duplicates.
* @return {!goog.async.Deferred} The deferred getAll request.
*/
goog.db.ObjectStore.prototype.getAll = function(opt_range, opt_direction) {
var d = new goog.async.Deferred();
var cursor;
try {
cursor = this.openCursor(opt_range, opt_direction);
} catch (err) {
d.errback(err);
return d;
}
var result = [];
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
result.push(cursor.getValue());
cursor.next();
});
goog.events.listenOnce(cursor, [
goog.db.Cursor.EventType.ERROR,
goog.db.Cursor.EventType.COMPLETE
], function(evt) {
cursor.dispose();
if (evt.type == goog.db.Cursor.EventType.COMPLETE) {
d.callback(result);
} else {
d.errback();
}
});
return d;
};
/**
* Opens a cursor over the specified key range. Returns a cursor object which is
* able to iterate over the given range.
*
* Example usage:
*
* <code>
* var cursor = objectStore.openCursor(goog.db.Range.bound('a', 'c'));
*
* var key = goog.events.listen(
* cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
* // Do something with data.
* cursor.next();
* });
*
* goog.events.listenOnce(
* cursor, goog.db.Cursor.EventType.COMPLETE, function() {
* // Clean up listener, and perform a finishing operation on the data.
* goog.events.unlistenByKey(key);
* });
* </code>
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
* over the whole object store.
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
* moves in a forward direction with duplicates.
* @return {!goog.db.Cursor} The cursor.
* @throws {goog.db.Error} If there was a problem opening the cursor.
*/
goog.db.ObjectStore.prototype.openCursor = function(opt_range, opt_direction) {
return goog.db.Cursor.openCursor(this.store_, opt_range, opt_direction);
};
/**
* Deletes all objects from the store.
*
* @return {!goog.async.Deferred} The deferred clear request.
*/
goog.db.ObjectStore.prototype.clear = function() {
var msg = 'clearing store ' + this.getName();
var d = new goog.async.Deferred();
var request;
try {
request = this.store_.clear();
} catch (err) {
d.errback(goog.db.Error.fromException(err, msg));
return d;
}
request.onsuccess = function(ev) {
d.callback();
};
request.onerror = function(ev) {
d.errback(goog.db.Error.fromRequest(ev.target, msg));
};
return d;
};
/**
* Creates an index in this object store. Can only be called inside the callback
* for the Deferred returned from goog.db.IndexedDb#setVersion.
*
* @param {string} name Name of the index to create.
* @param {string} keyPath Attribute to index on.
* @param {!Object=} opt_parameters Optional parameters object. The only
* available option is unique, which defaults to false. If unique is true,
* the index will enforce that there is only ever one object in the object
* store for each unique value it indexes on.
* @return {!goog.db.Index} The newly created, wrapped index.
* @throws {goog.db.Error} In case of an error creating the index.
*/
goog.db.ObjectStore.prototype.createIndex = function(
name, keyPath, opt_parameters) {
try {
return new goog.db.Index(this.store_.createIndex(
name, keyPath, opt_parameters));
} catch (ex) {
var msg = 'creating new index ' + name + ' with key path ' + keyPath;
throw goog.db.Error.fromException(ex, msg);
}
};
/**
* Gets an index.
*
* @param {string} name Name of the index to fetch.
* @return {!goog.db.Index} The requested wrapped index.
* @throws {goog.db.Error} In case of an error getting the index.
*/
goog.db.ObjectStore.prototype.getIndex = function(name) {
try {
return new goog.db.Index(this.store_.index(name));
} catch (ex) {
var msg = 'getting index ' + name;
throw goog.db.Error.fromException(ex, msg);
}
};
/**
* Deletes an index from the object store. Can only be called inside the
* callback for the Deferred returned from goog.db.IndexedDb#setVersion.
*
* @param {string} name Name of the index to delete.
* @throws {goog.db.Error} In case of an error deleting the index.
*/
goog.db.ObjectStore.prototype.deleteIndex = function(name) {
try {
this.store_.deleteIndex(name);
} catch (ex) {
var msg = 'deleting index ' + name;
throw goog.db.Error.fromException(ex, msg);
}
};
/**
* Gets number of records within a key range.
*
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined, this will
* count all records in the object store.
* @return {!goog.async.Deferred} The deferred number of records.
*/
goog.db.ObjectStore.prototype.count = function(opt_range) {
var d = new goog.async.Deferred();
try {
var range = opt_range ? opt_range.range() : null;
var request = this.store_.count(range);
request.onsuccess = function(ev) {
d.callback(ev.target.result);
};
var self = this;
request.onerror = function(ev) {
d.errback(goog.db.Error.fromRequest(ev.target, self.getName()));
};
} catch (ex) {
d.errback(goog.db.Error.fromException(ex, this.getName()));
}
return d;
};