ydn.db
Version:
Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.
374 lines (325 loc) • 10.3 kB
JavaScript
// Copyright 2012 YDN 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 Cursor for IndexedDB.
*/
goog.provide('ydn.db.core.req.IDBCursor');
goog.require('ydn.db.core.req.AbstractCursor');
goog.require('ydn.debug.error.InternalError');
/**
* Open an index. This will resume depending on the cursor state.
* @param {ydn.db.base.Transaction} tx
* @param {string} tx_no tx no.
* @param {ydn.db.schema.Store} store_schema schema.
* @param {ydn.db.base.QueryMethod=} opt_mth true for keys query method.
* @extends {ydn.db.core.req.AbstractCursor}
* @constructor
* @struct
*/
ydn.db.core.req.IDBCursor = function(tx, tx_no, store_schema, opt_mth) {
goog.base(this, tx, tx_no, store_schema, opt_mth);
/**
* @type {IDBRequest} cursor request object.
* @private
*/
this.request_ = null;
};
goog.inherits(ydn.db.core.req.IDBCursor, ydn.db.core.req.AbstractCursor);
/**
* @define {boolean} debug flag.
*/
ydn.db.core.req.IDBCursor.DEBUG = false;
/**
* @protected
* @type {goog.debug.Logger} logger.
*/
ydn.db.core.req.IDBCursor.prototype.logger =
goog.log.getLogger('ydn.db.core.req.IDBCursor');
/**
*
* @param {Event} ev event.
*/
ydn.db.core.req.IDBCursor.prototype.defaultOnSuccess = function(ev) {
// console.log(this, this.request_);
var cursor = ev.target.result;
if (ydn.db.core.req.IDBCursor.DEBUG) {
goog.global.console.log(this + ' onSuccess ' + (cursor ?
cursor.key + ', ' + cursor.primaryKey : ''));
}
if (cursor) {
// var p_key = this.isIndexCursor() ? cursor.primaryKey : undefined;
this.onSuccess(cursor.key, cursor.primaryKey, cursor.value);
} else {
this.onSuccess();
}
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.openCursor = function(key, primary_key) {
var msg = this + ' opening ';
if (goog.isDefAndNotNull(key)) {
msg += '{' + key;
if (goog.isDefAndNotNull(primary_key)) {
msg += ';' + primary_key + '}';
} else {
msg += '}';
}
}
goog.log.finest(this.logger, msg);
var key_range = this.key_range;
var obj_store = this.tx.objectStore(this.store_name);
var index = goog.isString(this.index_name) ?
obj_store.index(this.index_name) : null;
if (goog.isDef(key)) {
var open = index ? !(goog.isDef(primary_key)) : true;
var s_key = /** @type {IDBKey} */ (key);
var lower = /** @type {IDBKey|undefined} */ (key_range ?
key_range.lower : undefined);
var upper = /** @type {IDBKey|undefined} */ (key_range ?
key_range.upper : undefined);
var lowerOpen = key_range ? !!key_range.lowerOpen : false;
var upperOpen = key_range ? !!key_range.upperOpen : false;
var kr = this.reverse ?
new ydn.db.KeyRange(lower, s_key, lowerOpen, open) :
new ydn.db.KeyRange(s_key, upper, open, upperOpen);
key_range = kr.toIDBKeyRange();
}
var request;
if (!this.isValueCursor()) {
if (index) {
if (goog.isDefAndNotNull(this.dir)) {
request = index.openKeyCursor(key_range, this.dir);
} else if (goog.isDefAndNotNull(key_range)) {
request = index.openKeyCursor(key_range);
} else {
request = index.openKeyCursor();
}
} else {
//throw new ydn.error.InvalidOperationException(
// 'object store cannot open for key cursor');
// IDB v1 spec do not have openKeyCursor, hopefully next version does
// http://lists.w3.org/Archives/Public/public-webapps/2012OctDec/0466.html
// however, lazy serailization used at least in FF.
if (goog.isDefAndNotNull(this.dir)) {
request = obj_store.openCursor(key_range, this.dir);
} else if (goog.isDefAndNotNull(key_range)) {
request = obj_store.openCursor(key_range);
// some browser have problem with null, even though spec said OK.
} else {
request = obj_store.openCursor();
}
}
} else {
if (index) {
if (goog.isDefAndNotNull(this.dir)) {
request = index.openCursor(key_range, this.dir);
} else if (goog.isDefAndNotNull(key_range)) {
request = index.openCursor(key_range);
} else {
request = index.openCursor();
}
} else {
if (goog.isDefAndNotNull(this.dir)) {
request = obj_store.openCursor(key_range, this.dir);
} else if (goog.isDefAndNotNull(key_range)) {
request = obj_store.openCursor(key_range);
// some browser have problem with null, even though spec said OK.
} else {
request = obj_store.openCursor();
}
}
}
var me = this;
request.onerror = function(ev) {
var err = request.error;
ev.preventDefault(); // not abort the transaction.
me.onError(err);
};
/**
*
* @param {IDBKey=} key
* @param {IDBKey=} primaryKey
* @param {*=} value
*/
var requestReady = function(key, primaryKey, value) {
if (ydn.db.core.req.IDBCursor.DEBUG) {
goog.global.console.log(me + ' requestReady ' + key);
}
me.request_ = request;
me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
var p_key = me.isIndexCursor() ? primaryKey : undefined;
me.onSuccess(key, p_key, value);
request = null;
};
if (goog.isDefAndNotNull(key)) {
// start position is given, cursor must open after this position.
if (ydn.db.core.req.IDBCursor.DEBUG) {
goog.global.console.log(me + ' seeking start position, now ' + key);
}
request.onsuccess = function(ev) {
var cursor = ev.target.result;
if (cursor) {
var cmp = ydn.db.base.indexedDb.cmp(cursor.key, key);
var dir = me.reverse ? -1 : 1;
if (cmp == dir) {
requestReady(cursor.key, cursor.primaryKey, cursor.value);
} else if (cmp == (-dir)) {
cursor['continue'](key);
} else {
if (goog.isDefAndNotNull(primary_key)) {
var cmp2 = ydn.db.base.indexedDb.cmp(
cursor.primaryKey, primary_key);
if (cmp2 == dir) {
requestReady(cursor.key, cursor.primaryKey, cursor.value);
} else {
cursor['continue']();
}
} else {
cursor['continue']();
}
}
} else {
requestReady();
}
};
} else {
me.request_ = request;
if (ydn.db.core.req.IDBCursor.DEBUG) {
goog.global.console.log(me + ' ready ', me.request_);
}
me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
}
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.hasCursor = function() {
return !!this.request_;
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.update = function(record) {
var cursor = this.request_.result;
if (cursor) {
var df = new goog.async.Deferred();
var req = cursor.update(record);
req.onsuccess = function(x) {
df.callback(x.target.result);
};
req.onerror = function(e) {
e.preventDefault();
df.errback(e);
};
return df;
} else {
throw new ydn.db.InvalidAccessError('cursor gone');
}
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.clear = function() {
var cursor = this.request_.result;
if (cursor) {
var df = new goog.async.Deferred();
var req = cursor['delete']();
req.onsuccess = function(x) {
df.callback(1);
};
req.onerror = function(e) {
e.preventDefault();
df.errback(e);
};
return df;
} else {
throw new ydn.db.InvalidAccessError('cursor gone');
}
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.advance = function(step) {
var cursor = this.request_.result;
if (step == 1) {
//some browser, like mobile chrome does not implement cursor advance method.
cursor['continue']();
} else {
cursor.advance(step);
}
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.continuePrimaryKey = function(key) {
var cursor = this.request_.result;
if (goog.DEBUG) {
var cmp = ydn.db.base.indexedDb.cmp(key, cursor.primaryKey);
var exp_cmp = this.reverse ? -1 : 1;
if (cmp != exp_cmp) { // key must higher than primary key
throw new ydn.debug.error.InternalError('continuing primary key "' + key +
'" must ' + (this.reverse ? 'lower' : 'higher') +
' than current primary key "' + cursor.primaryKey + '"');
}
}
var me = this;
this.request_.onsuccess = function(ev) {
cursor = ev.target.result;
if (cursor) {
cmp = ydn.db.base.indexedDb.cmp(cursor.primaryKey, key);
if (cmp == 0 ||
(cmp == 1 && !me.reverse) ||
(cmp == -1 && me.reverse)) {
me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
var p_key = me.isIndexCursor() ? cursor.primaryKey : undefined;
me.onSuccess(cursor.key, p_key, cursor.value);
} else {
cursor['continue'](); // take another step.
}
} else {
me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
me.onSuccess();
}
};
cursor['continue'](); // take one step.
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.continueEffectiveKey = function(key) {
var cursor = this.request_.result;
if (goog.isDefAndNotNull(key)) {
// it is an DataError for undefined or null key.
// this behaviour is reasonable, somehow it is not.
cursor['continue'](key);
} else {
cursor['continue']();
}
};
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.request_ = null;
};
if (goog.DEBUG) {
/**
* @inheritDoc
*/
ydn.db.core.req.IDBCursor.prototype.toString = function() {
return 'IDB' + goog.base(this, 'toString');
};
}