ydn.db
Version:
Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.
787 lines (666 loc) • 20.6 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 range iterator iterates cursor of an index or an
* object store.
*
* @author kyawtun@yathit.com (Kyaw Tun)
*/
goog.provide('ydn.db.IndexIterator');
goog.provide('ydn.db.IndexValueIterator');
goog.provide('ydn.db.Iterator');
goog.provide('ydn.db.Iterator.State');
goog.provide('ydn.db.KeyIterator');
goog.provide('ydn.db.ValueIterator');
goog.require('goog.log');
goog.require('goog.functions');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Where');
goog.require('ydn.db.base');
goog.require('ydn.db.core.AbstractIterator');
goog.require('ydn.db.core.req.ICursor');
goog.require('ydn.debug.error.ArgumentException');
/**
* Create an iterator object.
* @param {!string} store store name.
* @param {string=} opt_index store field, where key query is preformed.
* @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
* @param {boolean=} opt_reverse reverse.
* @param {boolean=} opt_unique unique.
* @param {boolean=} opt_key_only true for key only iterator. Default value is
* true if index is specified, false if not defined.
* @param {(!Array.<string>|string)=} opt_index_key_path index key path. If
* key path is specified, key path is used to lookup the index instead of
* index name.
* @constructor
* @extends {ydn.db.core.AbstractIterator}
* @struct
*/
ydn.db.Iterator = function(store, opt_index, opt_key_range, opt_reverse,
opt_unique, opt_key_only, opt_index_key_path) {
// Note for V8 optimization, declare all properties in constructor.
if (goog.DEBUG && !goog.isString(store)) {
throw new TypeError('store name must be a string, but ' + store +
' found.');
}
/**
* Store name.
* @final
* @private
*/
this.store_name_ = store;
/**
* Indexed field.
* @final
* @private
*/
this.index_name_ = opt_index;
/**
* @final
* @private
*/
this.index_key_path_ = opt_index_key_path;
/**
* @final
* @private
*/
this.is_index_iterator_ = !!this.index_name_;
if (goog.DEBUG) {
if (goog.isDef(opt_reverse) && !goog.isBoolean(opt_reverse)) {
throw new ydn.debug.error.ArgumentException('reverse value must be' +
' a boolean, but ' + typeof opt_reverse + ' found');
}
if (goog.isDef(opt_unique) && !goog.isBoolean(opt_unique)) {
throw new ydn.debug.error.ArgumentException('unique value must be' +
' a boolean, but ' + typeof opt_unique + ' found');
}
if (goog.isDef(opt_key_only) && !goog.isBoolean(opt_key_only)) {
throw new ydn.debug.error.ArgumentException('key_only value must be' +
' a boolean, but ' + typeof opt_key_only + ' found');
}
}
/**
* @final
* @private
* @type {boolean}
*/
this.is_key_iterator_ = goog.isDef(opt_key_only) ?
opt_key_only : !!(goog.isString(this.index_name_));
var direction = ydn.db.base.Direction.NEXT;
if (opt_reverse && opt_unique) {
direction = ydn.db.base.Direction.PREV_UNIQUE;
} else if (opt_reverse) {
direction = ydn.db.base.Direction.PREV;
} else if (opt_unique) {
direction = ydn.db.base.Direction.NEXT_UNIQUE;
}
/**
* @final
* @private
* @type {ydn.db.base.Direction}
*/
this.direction_ = direction;
if (goog.DEBUG) {
var msg = ydn.db.KeyRange.validate(opt_key_range);
if (msg) {
throw new ydn.debug.error.ArgumentException('Invalid key range: ' + msg);
}
}
/**
* @final
* @private
* @type {?IDBKeyRange}
*/
this.key_range_ = ydn.db.KeyRange.parseIDBKeyRange(opt_key_range);
/**
* cursor state
* @type {ydn.db.Iterator.State}
* @private
*/
this.state_ = ydn.db.Iterator.State.INITIAL;
/**
* current effective key.
* @type {IDBKey|undefined}
* @private
*/
this.i_key_;
/**
* current primary key.
* @type {IDBKey|undefined}
* @private
*/
this.i_primary_key_;
/**
* Index of index key path that seperate prefix and postfix. This is used in
* join algorithms.
* @type {number}
*/
this.prefix_index = NaN;
};
goog.inherits(ydn.db.Iterator, ydn.db.core.AbstractIterator);
/**
* @define {boolean} to debug this file.
*/
ydn.db.Iterator.DEBUG = false;
/**
* @type {!string}
* @private
*/
ydn.db.Iterator.prototype.store_name_;
/**
* @type {string|undefined}
* @private
*/
ydn.db.Iterator.prototype.index_name_;
/**
* Composite index iterator build by using restrict do not have index name,
* instead index has to be lookup by this index key path. If this is defined,
* #index_name_ will be undefined and vise versa.
* @type {!Array.<string>|string|undefined}
* @private
*/
ydn.db.Iterator.prototype.index_key_path_;
/**
* @type {boolean}
* @private
*/
ydn.db.Iterator.prototype.is_index_iterator_;
/**
*
* @private
* @type {IDBKeyRange}
*/
ydn.db.Iterator.prototype.key_range_;
/**
*
* @type {boolean}
* @private
*/
ydn.db.Iterator.prototype.is_key_iterator_ = true;
/**
* Cursor direction.
* @type {(ydn.db.base.Direction)}
* @private
*/
ydn.db.Iterator.prototype.direction_;
/**
* Create an iterator object.
* @param {string} store store name.
* @param {(!KeyRangeJson|ydn.db.KeyRange)=} opt_key_range key range.
* @param {boolean=} opt_reverse reverse.
* @constructor
* @extends {ydn.db.Iterator}
*/
ydn.db.KeyIterator = function(store, opt_key_range, opt_reverse) {
if (arguments.length > 3) {
throw new ydn.debug.error.ArgumentException('too many argument');
}
goog.base(this, store, undefined, opt_key_range, opt_reverse,
undefined, true);
};
goog.inherits(ydn.db.KeyIterator, ydn.db.Iterator);
/**
* Create a new key cursor iterator.
* @param {string} store_name store name.
* @param {string} op where operator.
* @param {IDBKey} value rvalue to compare.
* @param {string=} opt_op2 second operator.
* @param {IDBKey=} opt_value2 second rvalue to compare.
* @return {!ydn.db.KeyIterator} newly created iterator.
*/
ydn.db.KeyIterator.where = function(store_name, op, value, opt_op2, opt_value2) {
return new ydn.db.KeyIterator(store_name,
ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};
/**
* Create an iterator object.
* @param {string} store store name.
* @param {string} index index name.
* @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
* @param {boolean=} opt_reverse reverse.
* @param {boolean=} opt_unique unique.
* @constructor
* @extends {ydn.db.Iterator}
*/
ydn.db.IndexIterator = function(store, index, opt_key_range, opt_reverse,
opt_unique) {
if (!goog.isString(index)) {
throw new ydn.debug.error.ArgumentException('index name must be string');
}
goog.base(this, store, index, opt_key_range, opt_reverse, opt_unique, true);
};
goog.inherits(ydn.db.IndexIterator, ydn.db.Iterator);
/**
* Create an iterator object.
* @param {string} store_name store name.
* @param {string} index index name.
* @param {string} op where operator.
* @param {IDBKey} value rvalue to compare.
* @param {string=} opt_op2 second operator.
* @param {IDBKey=} opt_value2 second rvalue to compare.
* @return {!ydn.db.IndexIterator}
*/
ydn.db.IndexIterator.where = function(store_name, index, op, value, opt_op2,
opt_value2) {
return new ydn.db.IndexIterator(store_name, index,
ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};
/**
* Create an iterator object.
* @param {!string} store store name.
* @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
* @param {boolean=} opt_reverse reverse.
* @constructor
* @extends {ydn.db.Iterator}
*/
ydn.db.ValueIterator = function(store, opt_key_range, opt_reverse) {
if (arguments.length > 3) {
throw new ydn.debug.error.ArgumentException('too many argument');
}
goog.base(this, store, undefined, opt_key_range, opt_reverse, undefined,
false);
};
goog.inherits(ydn.db.ValueIterator, ydn.db.Iterator);
/**
* Create a new value cursor range iterator using where clause condition.
* @param {string} store_name store name.
* @param {string} op where operator.
* @param {IDBKey} value rvalue to compare.
* @param {string=} opt_op2 second operator.
* @param {IDBKey=} opt_value2 second rvalue to compare.
* @return {!ydn.db.ValueIterator} newly craeted cursor.
*/
ydn.db.ValueIterator.where = function(store_name, op, value, opt_op2,
opt_value2) {
return new ydn.db.ValueIterator(store_name,
ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};
/**
* Create an iterator object.
* @param {!string} store store name.
* @param {string} index index name.
* @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
* @param {boolean=} opt_reverse reverse.
* @param {boolean=} opt_unique unique.
* @constructor
* @extends {ydn.db.Iterator}
*/
ydn.db.IndexValueIterator = function(store, index, opt_key_range, opt_reverse,
opt_unique) {
if (!goog.isString(index)) {
throw new ydn.debug.error.ArgumentException('index name must be string');
}
goog.base(this, store, index, opt_key_range, opt_reverse, opt_unique, false);
};
goog.inherits(ydn.db.IndexValueIterator, ydn.db.Iterator);
/**
*
* @param {string} store_name store name.
* @param {string} index index name.
* @param {string} op where operator.
* @param {IDBKey} value rvalue to compare.
* @param {string=} opt_op2 second operator.
* @param {IDBKey=} opt_value2 second rvalue to compare.
* @return {!ydn.db.IndexValueIterator}
*/
ydn.db.IndexValueIterator.where = function(store_name, index, op, value,
opt_op2, opt_value2) {
return new ydn.db.IndexValueIterator(store_name, index,
ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};
/**
* Iterator state.
* @enum {string}
*/
ydn.db.Iterator.State = {
INITIAL: 'init',
WORKING: 'busy',
RESTING: 'rest',
COMPLETED: 'done'
};
/**
* @protected
* @type {goog.debug.Logger} logger.
*/
ydn.db.Iterator.prototype.logger =
goog.log.getLogger('ydn.db.Iterator');
/**
*
* @return {!string} return store name.
*/
ydn.db.Iterator.prototype.getStoreName = function() {
return this.store_name_;
};
/**
*
* @return {string|undefined} return store name.
*/
ydn.db.Iterator.prototype.getIndexName = function() {
return this.index_name_;
};
/**
*
* @return {!Array.<string>|string|undefined} return index key path.
*/
ydn.db.Iterator.prototype.getIndexKeyPath = function() {
return this.index_key_path_;
};
/**
*
* @return {ydn.db.base.Direction} return store name.
*/
ydn.db.Iterator.prototype.getDirection = function() {
return this.direction_;
};
/**
* This is for friendly module use only, that does not mutate the key range.
* otherwise use @see #getKeyRange
* @return {ydn.db.IDBKeyRange} return key range instance.
*/
ydn.db.Iterator.prototype.keyRange = function() {
return this.key_range_;
};
/**
*
* @return {boolean} true if this iterator has key range restriction.
*/
ydn.db.Iterator.prototype.hasKeyRange = function() {
return !!this.key_range_;
};
/**
*
* @return {IDBKey|undefined} get lower value of key range.
*/
ydn.db.Iterator.prototype.getLower = function() {
return this.key_range_ ? undefined : this.key_range_.lower;
};
/**
*
* @return {boolean|undefined} get lower value of key range.
*/
ydn.db.Iterator.prototype.getLowerOpen = function() {
return this.key_range_ ? undefined : this.key_range_.lowerOpen;
};
/**
*
* @return {IDBKey|undefined} get upper value of key range.
*/
ydn.db.Iterator.prototype.getUpper = function() {
return this.key_range_ ? undefined : this.key_range_.upper;
};
/**
*
* @return {boolean|undefined} get upper value of key range.
*/
ydn.db.Iterator.prototype.getUpperOpen = function() {
return this.key_range_ ? undefined : this.key_range_.upperOpen;
};
/**
* @return {ydn.db.IDBKeyRange} return a clone of key range.
*/
ydn.db.Iterator.prototype.getKeyRange = function() {
if (this.key_range_) {
if (this.key_range_ instanceof ydn.db.IDBKeyRange) {
return this.key_range_; // none mutable key range.
} else {
return ydn.db.IDBKeyRange.bound(this.key_range_.lower,
this.key_range_.upper, this.key_range_.lowerOpen,
this.key_range_.upperOpen);
}
} else {
return null;
}
};
/**
* @return {boolean} <code>true</code> if key iterator, <code>false</code>
* if value iterator.
*/
ydn.db.Iterator.prototype.isKeyIterator = function() {
return this.is_key_iterator_;
};
/**
*
* @return {boolean} true if index iterator.
*/
ydn.db.Iterator.prototype.isIndexIterator = function() {
return this.is_index_iterator_;
};
/**
* @return {boolean} true for primary iterator, which use primary key as
* effective key.
* @deprecated use isIndexIterator instead.
*/
ydn.db.Iterator.prototype.isPrimaryIterator = function() {
return !this.is_index_iterator_;
};
/**
* Copy this iterator with internal state.
* @see copy
* @return {!ydn.db.Iterator}
*/
ydn.db.Iterator.prototype.clone = function() {
var iter = new ydn.db.Iterator(this.store_name_, this.index_name_,
this.key_range_, this.isReversed(), this.isUnique(), this.isKeyIterator(),
this.index_key_path_);
iter.prefix_index = this.prefix_index;
return iter;
};
/**
* Copy this iterator with setting key only to true.
* @see copy
* @return {!ydn.db.Iterator}
*/
ydn.db.Iterator.prototype.asKeyIterator = function() {
return new ydn.db.Iterator(this.store_name_, this.index_name_,
this.key_range_, this.isReversed(), this.isUnique(), true,
this.index_key_path_);
};
/**
* Copy this iterator with setting key only to false.
* @see copy
* @return {!ydn.db.Iterator}
*/
ydn.db.Iterator.prototype.asValueIterator = function() {
return new ydn.db.Iterator(this.store_name_, this.index_name_,
this.key_range_, this.isReversed(), this.isUnique(), false,
this.index_key_path_);
};
/**
* Set unique state.
* @param {boolean} val
* @return {!ydn.db.Iterator} newly created iterator.
*/
ydn.db.Iterator.prototype.unique = function(val) {
return new ydn.db.Iterator(this.store_name_, this.index_name_,
this.key_range_, this.isReversed(), val, this.isKeyIterator(),
this.index_key_path_);
};
/**
* Copy this iterator.
* @see clone
* @return {!ydn.db.Iterator}
*/
ydn.db.Iterator.prototype.copy = function() {
return new ydn.db.Iterator(this.store_name_, this.index_name_,
this.key_range_, this.isReversed(), this.isUnique(), this.isKeyIterator(),
this.index_key_path_);
};
if (goog.DEBUG) {
/**
* @inheritDoc
*/
ydn.db.Iterator.prototype.toJSON = function() {
return {
'store': this.store_name_,
'index': this.index_name_,
'keyRange': this.key_range_ ?
ydn.db.KeyRange.toJSON(this.key_range_) : null,
'direction': this.direction_
};
};
/**
* @override
*/
ydn.db.Iterator.prototype.toString = function() {
var str = goog.isDef(this.index_key_path_) ?
':' + this.index_key_path_.join(',') :
goog.isDef(this.index_name_) ? ':' + this.index_name_ : '';
str += ydn.db.KeyRange.toString(this.key_range_);
if (this.state_ != ydn.db.Iterator.State.INITIAL) {
str += this.state_ + '{' + this.i_key_;
if (this.isIndexIterator()) {
str += ', ' + this.i_primary_key_;
}
str += '}';
}
var s = this.isIndexIterator() ? 'Index' : '';
s += this.isKeyIterator() ? 'Key' : 'Value';
return s + 'Iterator:' + this.store_name_ + str;
};
}
/**
* Resume from a saved position.
* @param {IDBKey} key effective key as start position.
* @param {IDBKey=} opt_primary_key primary key as start position for index
* iterator.
* @return {!ydn.db.Iterator}
*/
ydn.db.Iterator.prototype.resume = function(key, opt_primary_key) {
var iter = new ydn.db.Iterator(this.store_name_, this.index_name_,
this.key_range_, this.isReversed(), this.isUnique(),
this.is_key_iterator_, this.index_key_path_);
iter.i_key_ = key;
iter.i_primary_key_ = opt_primary_key;
iter.state_ = ydn.db.Iterator.State.RESTING;
return iter;
};
/**
* Resume from a saved position.
* @param {IDBKey=} opt_key effective key as start position.
* @param {IDBKey=} opt_primary_key primary key as start position for index
* iterator.
* @return {!ydn.db.Iterator}
*/
ydn.db.Iterator.prototype.reverse = function(opt_key, opt_primary_key) {
var iter = new ydn.db.Iterator(this.store_name_, this.index_name_,
this.key_range_, !this.isReversed(), this.isUnique(),
this.is_key_iterator_, this.index_key_path_);
//if (this.cursor_) {
//console.log('rev cur')
//iter.cursor_ = this.cursor_.clone(true);
//}
return iter;
};
/**
*
* @return {boolean} true if iteration direction is reverse.
*/
ydn.db.Iterator.prototype.isReversed = function() {
return this.direction_ === ydn.db.base.Direction.PREV ||
this.direction_ === ydn.db.base.Direction.PREV_UNIQUE;
};
/**
*
* @return {boolean} true if cursor is iterator unique key.
*/
ydn.db.Iterator.prototype.isUnique = function() {
return this.direction_ === ydn.db.base.Direction.NEXT_UNIQUE ||
this.direction_ === ydn.db.base.Direction.PREV_UNIQUE;
};
/**
* Return next key range.
* @return {IDBKeyRange}
*/
ydn.db.Iterator.prototype.getNextKeyRange = function() {
if (this.state_ == ydn.db.Iterator.State.COMPLETED ||
this.state_ == ydn.db.Iterator.State.INITIAL ||
!goog.isDefAndNotNull(this.i_key_)) {
return this.key_range_;
} else {
return this.key_range_;
}
};
/**
*
* @return {ydn.db.Iterator.State} iterator state.
*/
ydn.db.Iterator.prototype.getState = function() {
return this.state_;
};
/**
* @inheritDoc
*/
ydn.db.Iterator.prototype.load = function(cursors) {
var cursor = cursors[0];
cursor.init(this.store_name_,
this.index_key_path_ || this.index_name_,
this.key_range_, this.direction_, this.is_key_iterator_);
this.state_ = ydn.db.Iterator.State.WORKING;
var me = this;
cursor.onTerminated = function(is_existed, key, primary_key) {
me.i_key_ = key;
me.i_primary_key_ = primary_key;
me.state_ = is_existed ? ydn.db.Iterator.State.RESTING :
ydn.db.Iterator.State.COMPLETED;
};
cursor.openCursor(this.i_key_, this.i_primary_key_);
return cursor;
};
/**
* @return {boolean} return true if iterator is fresh state, i.e., iteration
* will start without starting point.
*/
ydn.db.Iterator.prototype.isFreshState = function() {
return this.state_ == ydn.db.Iterator.State.INITIAL ||
this.state_ == ydn.db.Iterator.State.COMPLETED;
};
/**
*
* @return {IDBKey|undefined} Current cursor key.
*/
ydn.db.Iterator.prototype.getKey = function() {
return this.i_key_;
};
/**
*
* @return {IDBKey|undefined} Current cursor index key.
*/
ydn.db.Iterator.prototype.getPrimaryKey = function() {
return this.i_primary_key_;
};
/**
* Reset the state.
* @param {ydn.db.Iterator.State=} opt_state reset state, default to INITIAL.
* @param {IDBKey=} opt_key effective key for setting RESTING state.
* @param {IDBKey=} opt_primary_key effective key for setting RESTING state.
*/
ydn.db.Iterator.prototype.reset = function(opt_state,
opt_key, opt_primary_key) {
var state = opt_state || ydn.db.Iterator.State.INITIAL;
if (this.getState() == ydn.db.Iterator.State.WORKING) {
goog.log.warning(this.logger, this + ': resetting state to ' + state +
' ignore during iteration');
} else {
this.i_key_ = opt_key;
this.i_primary_key_ = opt_primary_key;
this.state_ = state;
}
};
/**
*
* @return {!Array.<string>} list of stores.
*/
ydn.db.Iterator.prototype.stores = function() {
return [this.store_name_];
};