ydn.db
Version:
Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.
550 lines (456 loc) • 13 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.
*/
goog.provide('ydn.db.core.req.AbstractCursor');
goog.require('goog.Disposable');
goog.require('ydn.debug.error.InternalError');
/**
* Open an index. This will resume depending on the cursor state.
* @param {ydn.db.base.Transaction} tx tx.
* @param {string} tx_no tx no.
* @param {ydn.db.schema.Store} store_schema schema.
* @param {ydn.db.base.QueryMethod=} opt_mth query method, default to
* values.
* @constructor
* @extends {goog.Disposable}
* @implements {ydn.db.core.req.ICursor}
* @struct
* @suppress {checkStructDictInheritance} suppress closure-library code.
*/
ydn.db.core.req.AbstractCursor = function(tx, tx_no, store_schema, opt_mth) {
goog.base(this);
/**
* @final
* @protected
* @type {ydn.db.schema.Store}
*/
this.store_schema = store_schema;
/**
* @final
* @protected
* @type {string}
*/
this.store_name = store_schema.getName();
/**
* @protected
*/
this.index_name = undefined;
/**
* @protected
*/
this.is_index = false;
/**
* @protected
*/
this.key_range = null;
this.tx = tx;
this.tx_no = tx_no;
this.count_ = 0;
/**
* @type {boolean}
* @private
*/
this.done_ = false;
/**
* @type {boolean}
* @private
*/
this.exited_ = false;
/**
* @protected
*/
this.query_method = opt_mth || ydn.db.base.QueryMethod.LIST_VALUE;
/**
* @type {IDBKey|undefined}
* @private
*/
this.key_ = undefined;
/**
* @type {IDBKey|undefined}
* @private
*/
this.primary_key_ = undefined;
/**
* @type {*}
* @private
*/
this.value_ = undefined;
/**
* @type {boolean|undefined}
* @protected
*/
this.reverse;
/**
* @type {boolean|undefined}
* @protected
*/
this.unique;
/**
* This method is overridden by cursor consumer.
* @param {IDBKey?=} opt_key effective key.
*/
this.onNext = function(opt_key) {
throw new ydn.debug.error.InternalError();
};
/**
* This method is overridden by cursor consumer.
* @param {Error|SQLError} e error.
*/
this.onFail = function(e) {
throw new ydn.debug.error.InternalError();
};
/**
* Invoke when cursor terminate.
* This method is overridden by iterator.
* @param {boolean} is_existed existed cursor.
* @param {IDBKey|undefined} key effective key.
* @param {IDBKey|undefined} primary_key primary key.
*/
this.onTerminated = function(is_existed, key, primary_key) {
};
};
goog.inherits(ydn.db.core.req.AbstractCursor, goog.Disposable);
/**
* Open an index. This will resume depending on the cursor state.
* @param {string} store_name the store name to open.
* @param {!Array.<string>|string|undefined} index_name index name.
* @param {IDBKeyRange} key_range key range.
* @param {ydn.db.base.Direction} direction cursor direction.
* @param {boolean} is_key_cursor mode.
*/
ydn.db.core.req.AbstractCursor.prototype.init = function(store_name,
index_name, key_range, direction, is_key_cursor) {
goog.asserts.assert(this.store_name == store_name, 'expect store name of ' +
this.store_name + ' but ' + store_name + ' found.');
if (goog.isDef(index_name)) {
this.index_name = this.store_schema.getIndexName(index_name);
goog.asserts.assertString(this.index_name, 'index "' +
index_name + '" not found in store "' + store_name + '"');
}
this.is_index = goog.isString(this.index_name);
this.key_range = key_range || null;
this.count_ = 0;
this.done_ = false;
this.exited_ = false;
this.reverse = direction == ydn.db.base.Direction.PREV ||
direction == ydn.db.base.Direction.PREV_UNIQUE;
this.unique = direction == ydn.db.base.Direction.NEXT_UNIQUE ||
direction == ydn.db.base.Direction.PREV_UNIQUE;
this.dir = direction;
this.is_key_cursor_ = is_key_cursor;
this.key_ = undefined;
this.primary_key_ = undefined;
this.value_ = undefined;
};
/**
* @protected
* @type {string|undefined}
*/
ydn.db.core.req.AbstractCursor.prototype.index_name;
/**
* @protected
* @type {boolean}
*/
ydn.db.core.req.AbstractCursor.prototype.is_index;
/**
* @protected
* @type {string}
*/
ydn.db.core.req.AbstractCursor.prototype.store_name;
/**
* @protected
* @type {string}
*/
ydn.db.core.req.AbstractCursor.prototype.dir = '';
/**
* @protected
* @type {IDBKeyRange}
*/
ydn.db.core.req.AbstractCursor.prototype.key_range = null;
/**
* @protected
* @type {boolean}
*/
ydn.db.core.req.AbstractCursor.prototype.unique = false;
/**
* @protected
* @type {boolean}
*/
ydn.db.core.req.AbstractCursor.prototype.reverse = false;
/**
* @private
* @type {boolean}
*/
ydn.db.core.req.AbstractCursor.prototype.is_key_cursor_ = true;
/**
* @protected
* @type {ydn.db.base.QueryMethod}
*/
ydn.db.core.req.AbstractCursor.prototype.query_method;
/**
* @protected
* @type {goog.debug.Logger} logger.
*/
ydn.db.core.req.AbstractCursor.prototype.logger =
goog.log.getLogger('ydn.db.core.req.AbstractCursor');
/**
*
* @return {boolean} true if transaction is active.
*/
ydn.db.core.req.AbstractCursor.prototype.isActive = function() {
return !!this.tx;
};
/**
*
* @return {boolean} return true if this is an index cursor.
*/
ydn.db.core.req.AbstractCursor.prototype.isIndexCursor = function() {
return this.is_index;
};
/**
*
* @return {boolean} return true if this is an index cursor.
*/
ydn.db.core.req.AbstractCursor.prototype.isPrimaryCursor = function() {
return !this.is_index;
};
/**
*
* @return {boolean} return true if this is an value cursor.
*/
ydn.db.core.req.AbstractCursor.prototype.isValueCursor = function() {
return !this.is_key_cursor_;
};
/**
* Callback on request error.
* @param {Error|SQLError} e error object.
*/
ydn.db.core.req.AbstractCursor.prototype.onError = function(e) {
this.onFail(e);
this.finalize_();
this.done_ = true;
};
/**
* Move cursor to a given position by primary key.
*
* This will iterate the cursor records until the primary key is found without
* changing index key. If index has change during iteration, this will invoke
* onNext callback with resulting value. If given primary key is in wrong
* direction, this will rewind and seek.
*
* Return value of:
* undefined : will invoke onNext
* null : don't do anything
* * : seek to given primary key value, not invoke onNext.
* true : continue next cursor position, not invoke onNext
* false : restart the cursor, not invoke onNext.
*
* @param {IDBKey=} opt_key
* @param {IDBKey=} opt_primary_key
* @param {*=} opt_value
*/
ydn.db.core.req.AbstractCursor.prototype.onSuccess = function(
opt_key, opt_primary_key, opt_value) {
// console.log(this.count_, opt_key, opt_primary_key, opt_value);
if (!goog.isDefAndNotNull(opt_key)) {
goog.log.finer(this.logger, this + ' finished.');
this.done_ = true;
}
this.key_ = opt_key;
this.primary_key_ = opt_primary_key;
this.value_ = opt_value;
this.count_++;
if (this.done_) {
goog.log.finest(this.logger, this + ' DONE.');
this.onNext();
this.finalize_();
} else {
var key_str = this.is_index ?
this.key_ + ', ' + this.primary_key_ : this.key_;
goog.log.finest(this.logger, this + ' new cursor position {' + key_str + '}');
this.onNext(this.key_);
}
};
/**
* @inheritDoc
*/
ydn.db.core.req.AbstractCursor.prototype.disposeInternal = function() {
this.tx = null;
};
if (goog.DEBUG) {
/**
* @inheritDoc
*/
ydn.db.core.req.AbstractCursor.prototype.toString = function() {
var index = goog.isDef(this.index_name) ? ':' + this.index_name : '';
var active = this.tx ? '' : '~';
return 'Cursor:' + this.store_name +
index + '[' + active + this.tx_no + ']';
};
}
/**
* Copy keys from cursors before browser GC them, as cursor lift-time expires.
* http://www.w3.org/TR/IndexedDB/#dfn-transaction-lifetime
* Keys are used to resume cursors position.
* @private
*/
ydn.db.core.req.AbstractCursor.prototype.finalize_ = function() {
// IndexedDB will GC array keys, so we clone it.
if (goog.isDefAndNotNull(this.primary_key_)) {
this.primary_key_ = ydn.db.Key.clone(this.primary_key_);
} else {
this.primary_key_ = undefined;
}
if (goog.isDefAndNotNull(this.key_)) {
this.key_ = ydn.db.Key.clone(this.key_);
} else {
this.key_ = undefined;
}
this.onTerminated(this.exited_, this.key_, this.primary_key_);
};
/**
* Make cursor opening request.
*
* This will seek to given initial position if given. If only ini_key (primary
* key) is given, this will rewind, if not found.
*
* @param {IDBKey=} opt_ini_key effective key to resume position.
* @param {IDBKey=} opt_ini_primary_key primary key to resume position.
*/
ydn.db.core.req.AbstractCursor.prototype.openCursor = goog.abstractMethod;
/**
* @param {ydn.db.base.Transaction} tx tx.
* @param {string} tx_no tx no.
* @param {IDBKey=} opt_ini_key effective key to resume position.
* @param {IDBKey=} opt_ini_primary_key primary key to resume position.
*/
ydn.db.core.req.AbstractCursor.prototype.open = function(tx, tx_no,
opt_ini_key, opt_ini_primary_key) {
this.tx = tx;
this.tx_no = tx_no;
this.exited_ = false;
this.done_ = false;
this.key_ = opt_ini_key;
this.primary_key_ = opt_ini_primary_key;
this.openCursor(this.key_, this.primary_key_);
};
/**
* Resume cursor.
* @param {ydn.db.base.Transaction} tx tx.
* @param {string} tx_no tx no.
* @final
*/
ydn.db.core.req.AbstractCursor.prototype.resume = function(tx, tx_no) {
if (this.done_) {
this.open(tx, tx_no);
} else {
this.open(tx, tx_no, this.key_, this.primary_key_);
}
};
/**
* Exit cursor
*/
ydn.db.core.req.AbstractCursor.prototype.exit = function() {
this.exited_ = true;
goog.log.finest(this.logger, this + ': exit');
this.finalize_();
};
/**
* @return {number} Number of steps iterated.
*/
ydn.db.core.req.AbstractCursor.prototype.getCount = function() {
return this.count_;
};
/**
* @return {IDBKey|undefined} effective key of cursor.
*/
ydn.db.core.req.AbstractCursor.prototype.getKey = function() {
return this.key_;
};
/**
* @return {IDBKey|undefined} primary key of cursor.
*/
ydn.db.core.req.AbstractCursor.prototype.getPrimaryKey = function() {
return this.isIndexCursor() ?
this.primary_key_ : this.key_;
};
/**
* @param {number=} opt_idx cursor index.
* @return {*} value.
*/
ydn.db.core.req.AbstractCursor.prototype.getValue = function(opt_idx) {
return this.isValueCursor() ?
this.value_ : this.getPrimaryKey();
};
/**
*
* @return {boolean} true if cursor gone.
*/
ydn.db.core.req.AbstractCursor.prototype.hasDone = function() {
return this.done_;
};
/**
*
* @return {boolean} true if iteration is existed.
*/
ydn.db.core.req.AbstractCursor.prototype.isExited = function() {
return this.exited_;
};
/**
* Move cursor position to the primary key while remaining on same index key.
* @param {IDBKey} primary_key primary key position to continue.
*/
ydn.db.core.req.AbstractCursor.prototype.continuePrimaryKey =
function(primary_key) {};
/**
* Move cursor position to the effective key.
* @param {IDBKey=} opt_effective_key effective key position to continue.
*/
ydn.db.core.req.AbstractCursor.prototype.continueEffectiveKey =
function(opt_effective_key) {};
/**
* Move cursor position to the effective key.
* @param {number} number_of_step
*/
ydn.db.core.req.AbstractCursor.prototype.advance = goog.abstractMethod;
/**
* @return {boolean}
*/
ydn.db.core.req.AbstractCursor.prototype.hasCursor = goog.abstractMethod;
/**
* @param {!Object} obj record value.
* @return {!goog.async.Deferred} value.
*/
ydn.db.core.req.AbstractCursor.prototype.update = goog.abstractMethod;
/**
* Clear record
* @return {!goog.async.Deferred} value.
*/
ydn.db.core.req.AbstractCursor.prototype.clear = goog.abstractMethod;
/**
* Restart the cursor. If previous cursor position is given,
* the position is skip.
* @param {IDBKey=} effective_key previous position.
* @param {IDBKey=} primary_key
* @final
*/
ydn.db.core.req.AbstractCursor.prototype.restart = function(
effective_key, primary_key) {
goog.log.finest(this.logger, this + ' restarting');
this.done_ = false;
this.exited_ = false;
this.openCursor(primary_key, effective_key);
};