UNPKG

ydn.db

Version:

Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.

313 lines (268 loc) 7.92 kB
// 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 Iterator builder. * * Provide iterator. * @author kyawtun@yathit.com (Kyaw Tun) */ goog.provide('ydn.db.query.Iterator'); goog.require('ydn.db.ConstraintError'); goog.require('ydn.db.Iterator'); /** * Create an Query Iterator. * @param {ydn.db.schema.Store} store store name. * @param {ydn.db.KeyRange=} opt_key_range key range. * @param {boolean=} opt_reverse reverse. * @param {boolean=} opt_unique unique. * @constructor * @struct */ ydn.db.query.Iterator = function(store, opt_key_range, opt_reverse, opt_unique) { /** * @protected * @type {ydn.db.schema.Store} */ this.store = store; /** * @protected * @type {ydn.db.KeyRange} */ this.key_range = opt_key_range || null; /** * @type {boolean} */ this.is_reverse = !!opt_reverse; /** * @type {boolean} */ this.is_unique = !!opt_unique; /** * @protected * @type {Array.<string>} */ this.prefix = []; /** * Postfix or order. * @protected * @type {Array.<string>} */ this.postfix = []; }; /** * Set key path prefix. * @param {Array.<string>} prefix * @return {string?} error message return, if require index not exist. */ ydn.db.query.Iterator.prototype.setPrefix = function(prefix) { this.prefix = prefix; return null; // ok }; /** * Set key path prefix or ordering. * @param {Array.<string>} postfix * @return {string?} error message return, if require index not exist. */ ydn.db.query.Iterator.prototype.setOrder = function(postfix) { // remove ordering as in prefix var n = postfix.length; for (var i = n - 1; i >= 0; i--) { if (postfix[i] == this.prefix[this.prefix.length - 1]) { postfix = postfix.slice(0, i); } else { break; } } this.postfix = postfix; if (this.key_range && !!this.prefix[0]) { if (this.key_range.lower != this.key_range.upper) { throw new ydn.db.ConstraintError('Ordering no allowed with range query'); } this.key_range = ydn.db.KeyRange.starts([this.key_range.lower]); } return null; }; /** * Get iterable iterator. * @param {boolean=} opt_value_iterator if true value itrator is return. * @return {!ydn.db.Iterator} iterator for this. */ ydn.db.query.Iterator.prototype.getIterator = function(opt_value_iterator) { if (!this.hasValidIndex()) { throw new ydn.db.ConstraintError('Require index "' + this.prefix.concat(this.postfix).join(', ') + '" not found in store "' + this.store.getName() + '"'); } var iter = new ydn.db.Iterator(this.store.getName(), this.getIndexName(), this.key_range, this.is_reverse, this.is_unique, !!opt_value_iterator); iter.prefix_index = this.prefix.length; return iter; }; /** * @return {string} */ ydn.db.query.Iterator.prototype.getStoreName = function() { return this.store.getName(); }; /** * @return {boolean} */ ydn.db.query.Iterator.prototype.hasPrefix = function() { return this.prefix.length > 0; }; /** * Clone this. * @return {!ydn.db.query.Iterator} */ ydn.db.query.Iterator.prototype.clone = function() { var iter = new ydn.db.query.Iterator(this.store, this.key_range, this.is_reverse, this.is_unique); iter.postfix = this.postfix.slice(); iter.prefix = this.prefix.slice(); return iter; }; /** * @return {ydn.db.KeyRange} */ ydn.db.query.Iterator.prototype.getKeyRange = function() { return this.key_range; }; /** * @return {ydn.db.schema.Index} */ ydn.db.query.Iterator.prototype.getIndex = function() { var indexes = this.prefix.concat(this.postfix); var index = this.store.getIndexByKeyPath(indexes); if (index) { return index; } else if (indexes[indexes.length - 1] == this.store.getKeyPath()) { index = this.store.getIndexByKeyPath(indexes.slice(0, indexes.length - 1)); if (index) { return index; } } return null; }; /** * @return {string|undefined} */ ydn.db.query.Iterator.prototype.getIndexName = function() { var index = this.getIndex(); return index ? index.getName() : undefined; }; /** * @return {boolean} */ ydn.db.query.Iterator.prototype.hasValidIndex = function() { if (this.prefix.length == 0 && this.postfix.length == 0) { return true; } if (!this.usedIndex()) { return true; } return !!this.getIndex(); // there must be an index }; /** * @return {!ydn.db.query.Iterator} */ ydn.db.query.Iterator.prototype.reverse = function() { var iter = this.clone(); iter.is_reverse = !this.is_reverse; return iter; }; /** * @param {boolean} val unique value. * @return {!ydn.db.query.Iterator} */ ydn.db.query.Iterator.prototype.unique = function(val) { var iter = this.clone(); iter.is_unique = !!val; return iter; }; /** * @return {boolean} */ ydn.db.query.Iterator.prototype.isUnique = function() { return this.is_unique; }; /** * @return {boolean} */ ydn.db.query.Iterator.prototype.isReverse = function() { return this.is_reverse; }; /** * Test this iterator used index. * @return {boolean} True if there is postfix or prefix (different from * primary key. */ ydn.db.query.Iterator.prototype.usedIndex = function() { if (this.prefix.length > 0) { return true; } if (this.postfix.length == 1) { return this.postfix[0] != this.store.getKeyPath(); } else if (this.postfix.length > 1) { return true; } return false; }; /** * Add where clause condition. * @param {string|Array.<string>} index_name index name or index key path. * @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 {string?} return error message. */ ydn.db.query.Iterator.prototype.where = function(index_name, op, value, opt_op2, opt_value2) { var key_range = ydn.db.KeyRange.where(op, value, opt_op2, opt_value2); if (this.prefix.length > 0) { // only possible with equal key range if (this.key_range) { if (goog.isDefAndNotNull(this.key_range.lower) && goog.isDefAndNotNull(this.key_range.upper) && ydn.db.cmp(this.key_range.lower, this.key_range.upper) == 0) { var lower = goog.isArray(this.key_range.lower) ? this.key_range.lower.slice().push(op) : [this.key_range.lower, op]; var op2 = goog.isDefAndNotNull(opt_op2) ? opt_op2 : '\uffff'; var upper = goog.isArray(this.key_range.upper) ? this.key_range.upper.slice().push(op2) : [this.key_range.upper, op2]; this.key_range = ydn.db.KeyRange.where(op, lower, op2, upper); } else if ((this.prefix.length == 1 && this.prefix[0] == index_name) || goog.isArray(index_name) && goog.array.equals(this.prefix, index_name)) { this.key_range = this.key_range.and(key_range); } else { return 'cannot use where clause with existing filter'; } } else { return 'cannot use where clause with existing filter'; } } else { this.prefix = goog.isArray(index_name) ? index_name : [index_name]; if (this.key_range) { this.key_range = this.key_range.and(key_range); } else { this.key_range = key_range; } } return null; }; /** * Get ordering. * @return {Array.<string>} */ ydn.db.query.Iterator.prototype.getPostFix = function() { return this.postfix.slice(); };