UNPKG

ydn.db

Version:

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

452 lines (402 loc) 14 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 Query builder class. * * @author kyawtun@yathit.com (Kyaw Tun) */ goog.provide('ydn.db.Query'); goog.require('ydn.db.core.Storage'); goog.require('ydn.db.query.Base'); goog.require('ydn.db.query.ConjQuery'); goog.require('ydn.db.query.Iterator'); goog.require('ydn.debug.error.ArgumentException'); /** * Query builder class. * @param {ydn.db.core.DbOperator} db * @param {ydn.db.schema.Database} schema * @param {ydn.db.base.QueryMethod?} type query type. Defaults to values. * @param {ydn.db.query.Iterator} iter index name. * @constructor * @extends {ydn.db.query.Base} * @struct */ ydn.db.Query = function(db, schema, type, iter) { goog.base(this, db, schema, type); /** * @final * @protected * @type {ydn.db.query.Iterator} */ this.iter = iter; }; goog.inherits(ydn.db.Query, ydn.db.query.Base); /** * @define {boolean} debug flag. */ ydn.db.Query.DEBUG = false; /** * @return {!ydn.db.Query} */ ydn.db.Query.prototype.copy = function() { return new ydn.db.Query(this.db, this.schema, this.type, this.iter.clone()); }; /** * @return {!ydn.db.Query} return a new query. */ ydn.db.Query.prototype.reverse = function() { var iter = this.iter.reverse(); return new ydn.db.Query(this.db, this.schema, this.type, iter); }; /** * Set query's unique requirement. * @param {boolean} val * @return {!ydn.db.Query} */ ydn.db.Query.prototype.unique = function(val) { if (!goog.isBoolean(val)) { throw new ydn.debug.error.ArgumentException('unique value must be' + ' a boolean, but ' + typeof val + ' found'); } var iter = this.iter.unique(val); return new ydn.db.Query(this.db, this.schema, this.type, iter); }; /** * Specify query order. * @param {string|Array.<string>} order * @return {!ydn.db.Query} return a new query. */ ydn.db.Query.prototype.order = function(order) { var orders = goog.isString(order) ? [order] : order; var iter = this.iter.clone(); var msg = iter.setOrder(orders); if (msg) { throw new Error(msg); } else { return new ydn.db.Query(this.db, this.schema, this.type, iter); } }; /** * Create a new value cursor range iterator using where clause condition. * @param {string} index_name 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.query.Base} return this for chaining. */ ydn.db.Query.prototype.where = function(index_name, op, value, opt_op2, opt_value2) { if (!this.iter.getIndexName() || this.iter.getIndexName() == index_name) { if (!this.iter.getIndexName()) { var store = this.schema.getStore(this.iter.getStoreName()); if (!store.hasIndex(index_name)) { throw new ydn.debug.error.ArgumentException('index "' + index_name + '" not exists in ' + this.iter.getStoreName()); } } var iter = this.iter.clone(); var msg = iter.where(index_name, op, value, opt_op2, opt_value2); if (msg) { throw new ydn.debug.error.ArgumentException(msg); } return new ydn.db.Query(this.db, this.schema, this.type, iter); } else { var kr = ydn.db.KeyRange.where(op, value, opt_op2, opt_value2); var iter = new ydn.db.query.Iterator(this.getStore(), kr, this.iter.isReverse(), this.iter.isUnique()); var q = new ydn.db.Query(this.db, this.schema, this.type, iter); return this.and(q); } }; /** * @return {ydn.db.schema.Store} */ ydn.db.Query.prototype.getStore = function() { return this.schema.getStore(this.iter.getStoreName()); }; /** * Select query result. * @param {string|!Array.<string>} field_name_s select field name(s). * @return {!ydn.db.Query} */ ydn.db.Query.prototype.select = function(field_name_s) { var store = this.getStore(); var fields = goog.isString(field_name_s) ? [field_name_s] : field_name_s; var type = this.type; var iter = this.iter.clone(); var index = this.iter.getIndexName(); if (fields.length == 1) { var field = fields[0]; if (field == ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME || field == store.getKeyPath()) { type = ydn.db.base.QueryMethod.LIST_PRIMARY_KEY; } else if (!field || field == '*') { type = ydn.db.base.QueryMethod.LIST_VALUE; } else if (store.hasIndex(field)) { var msg = iter.setOrder(fields); if (msg) { throw new ydn.debug.error.ArgumentException(msg); } type = ydn.db.base.QueryMethod.LIST_KEY; } else { throw new ydn.debug.error.ArgumentException('Invalid select "' + field + '", index not found in store "' + store.getName() + '"'); } } else if (fields.length == 2) { if (!index) { throw new ydn.debug.error.ArgumentException('Only primary key can be ' + 'selected for this query.'); } for (var i = 0; i < 2; i++) { var is_primary = fields[i] == ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME || store.isKeyPath(fields[i]); if (!is_primary) { if (fields[i] != index) { throw new ydn.debug.error.ArgumentException('select field name ' + 'must be "' + index + '", but "' + fields[i] + '" found.'); } } } type = ydn.db.base.QueryMethod.LIST_KEYS; } else { throw new ydn.debug.error.ArgumentException('Selecting more than 2 field' + ' names is not supported, but ' + fields.length + ' fields selected.'); } return new ydn.db.Query(this.db, this.schema, type, iter); }; /** * Execute query and collect results as an array. This method forces query execution. * @param {number=} opt_limit * @return {!ydn.db.Request} */ ydn.db.Query.prototype.list = function(opt_limit) { var offset = 0; var limit = opt_limit || ydn.db.base.DEFAULT_RESULT_LIMIT; var mth = ydn.db.base.QueryMethod.LIST_VALUE; var iter = this.getIterator(); if (this.marker && this.marker[0]) { // console.log('starting from ' + this.marker[0]); iter = iter.resume(this.marker[0], this.marker[1]); } if (this.type == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY || this.type == ydn.db.base.QueryMethod.LIST_KEYS || this.type == ydn.db.base.QueryMethod.LIST_KEY) { mth = this.type; } var req = this.db.listIter(mth, iter, limit, offset); req.addCallback(function(x) { if (iter.getState() == ydn.db.Iterator.State.RESTING) { // iteration not finished // console.log('end in ' + iter.getKey()); this.marker = [iter.getKey(), iter.getPrimaryKey()]; } }, this); return req; }; /** * @inheritDoc */ ydn.db.Query.prototype.getIterators = function() { return [this.iter.clone()]; }; /** * Get iterator. * @param {boolean=} opt_key_only return key-only iterator. * @return {!ydn.db.Iterator} */ ydn.db.Query.prototype.getIterator = function(opt_key_only) { var is_key_only = !!opt_key_only || (this.type == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY || this.type == ydn.db.base.QueryMethod.LIST_KEYS || this.type == ydn.db.base.QueryMethod.LIST_KEY); return this.iter.getIterator(!is_key_only); }; /** * @return {Array.<string>} */ ydn.db.Query.prototype.getOrder = function() { return this.iter.getPostFix(); }; /** * Patch object. * @param {!Object|string|!Array.<string>} arg1 Patch object, field name or * field names. * @param {*=} opt_arg2 field value or field values. * @return {!ydn.db.Request} */ ydn.db.Query.prototype.patch = function(arg1, opt_arg2) { var iter = this.getIterator(); if (iter.isKeyIterator()) { iter = iter.asValueIterator(); } if (goog.DEBUG) { if (arguments.length < 1) { throw new ydn.debug.error.ArgumentException('too few arguments'); } else if (arguments.length == 2) { if (goog.isString(arg1)) { // any value is OK. } else if (goog.isArray(arg1)) { if (!goog.isArray(opt_arg2)) { throw new ydn.debug.error.ArgumentException('an array is expected ' + 'for second argument but, ' + ydn.json.toShortString(opt_arg2) + ' of type ' + typeof opt_arg2 + ' found'); } else if (arg1.length != opt_arg2.length) { throw new ydn.debug.error.ArgumentException('length of two input ' + 'arguments must be equal but, ' + arg1.length + ' and ' + opt_arg2.length + ' found'); } } } else if (arguments.length == 1) { if (!goog.isObject(arg1)) { throw new ydn.debug.error.ArgumentException('an object is expected ' + 'but, ' + ydn.json.toShortString(arg1) + ' of type ' + typeof arg1 + ' found'); } } else { throw new ydn.debug.error.ArgumentException('too many arguments'); } } var req = this.db.open(function(cursor) { var val = /** @type {!Object} */ (cursor.getValue()); if (goog.isString(arg1)) { ydn.db.utils.setValueByKeys(val, arg1, opt_arg2); } else if (goog.isArray(arg1)) { for (var i = 0; i < arg1.length; i++) { ydn.db.utils.setValueByKeys(val, arg1[i], opt_arg2[i]); } } else if (goog.isObject(arg1)) { for (var k in arg1) { if (arg1.hasOwnProperty(k)) { val[k] = arg1[k]; } } } req.awaitDeferred(cursor.update(val)); }, iter, ydn.db.base.TransactionMode.READ_WRITE, this); return req; }; /** * Execute query and collect results as an array. This method forces query execution. * @param {function(this: T, !ydn.db.core.req.ICursor)} cb * @param {T=} opt_scope * @return {!ydn.db.Request} * @template T */ ydn.db.Query.prototype.open = function(cb, opt_scope) { var req = this.db.open(cb, this.getIterator(), ydn.db.base.TransactionMode.READ_WRITE, opt_scope); return req; }; /** * Count results of query. This method forces query execution. * @return {!ydn.db.Request} */ ydn.db.Query.prototype.count = function() { var req; if (this.iter.usedIndex()) { if (this.iter.isUnique()) { req = this.db.count(this.iter.getIterator()); } else { req = this.db.count(this.iter.getStoreName(), this.iter.getIndexName(), this.iter.getKeyRange()); } } else { req = this.db.count(this.iter.getStoreName(), this.iter.getKeyRange()); } return req; }; /** * Count results of query. This method forces query execution. * @return {!ydn.db.Request} */ ydn.db.Query.prototype.clear = function() { var req = this.iter.usedIndex() ? this.db.clear(this.iter.getStoreName(), this.iter.getIndexName(), this.iter.getKeyRange()) : this.db.clear(this.iter.getStoreName(), this.iter.getKeyRange()); return req; }; /** * Create AND query. * @param {ydn.db.query.Base} q * @return {!ydn.db.query.ConjQuery} */ ydn.db.Query.prototype.and = function(q) { var iters = q.getIterators().concat(this.getIterators()); return new ydn.db.query.ConjQuery(this.db, this.schema, this.type, iters); }; /** * Create a new query. * @param {string} store_name * @param {string=} opt_op1 where operator. * @param {IDBKey=} opt_value1 rvalue to compare. * @param {string=} opt_op2 second operator. * @param {IDBKey=} opt_value2 second rvalue to compare. * @return {!ydn.db.Query} */ ydn.db.core.Storage.prototype.from = function(store_name, opt_op1, opt_value1, opt_op2, opt_value2) { if (goog.DEBUG && !goog.isString(store_name)) { throw new TypeError('store name "' + store_name + '"'); } if (!this.schema.hasStore(store_name)) { throw new ydn.debug.error.ArgumentException('Store "' + store_name + '" not found.'); } var range = null; if (goog.isDef(opt_op1)) { if (!goog.isDef(opt_value1)) { throw new ydn.debug.error.ArgumentException('boundary value ' + 'must be defined.'); } range = ydn.db.KeyRange.where(opt_op1, opt_value1, opt_op2, opt_value2); } else if (goog.isDef(opt_op2)) { throw new ydn.debug.error.ArgumentException('second boundary must not be' + ' defined.'); } var iter = new ydn.db.query.Iterator(this.schema.getStore(store_name), range); return new ydn.db.Query(this.getIndexOperator(), this.schema, null, iter); }; /** * Create a new query. * @param {string} store_name * @param {string=} opt_op1 where operator. * @param {IDBKey=} opt_value1 rvalue to compare. * @param {string=} opt_op2 second operator. * @param {IDBKey=} opt_value2 second rvalue to compare. * @return {!ydn.db.Query} */ ydn.db.core.DbOperator.prototype.from = function(store_name, opt_op1, opt_value1, opt_op2, opt_value2) { if (goog.DEBUG && !goog.isString(store_name)) { throw new TypeError('store name "' + store_name + '"'); } if (!this.schema.hasStore(store_name)) { throw new ydn.debug.error.ArgumentException('Store "' + store_name + '" not found.'); } var range = null; if (goog.isDef(opt_op1)) { if (!goog.isDef(opt_value1)) { throw new ydn.debug.error.ArgumentException('boundary value ' + 'must be defined.'); } range = ydn.db.KeyRange.where(opt_op1, opt_value1, opt_op2, opt_value2); } else if (goog.isDef(opt_op2)) { throw new ydn.debug.error.ArgumentException('second boundary must not be' + ' defined.'); } var iter = new ydn.db.query.Iterator(this.schema.getStore(store_name), range); return new ydn.db.Query(this, this.schema, null, iter); };