UNPKG

ydn.db

Version:

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

1,247 lines (1,069 loc) 35.2 kB
// 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 WebSQL executor. * * @see http://www.w3.org/TR/webdatabase/ * * @author kyawtun@yathit.com (Kyaw Tun) */ goog.provide('ydn.db.crud.req.WebSql'); goog.require('goog.async.Deferred'); goog.require('goog.log'); goog.require('ydn.db.Where'); goog.require('ydn.db.crud.req.IRequestExecutor'); goog.require('ydn.db.crud.req.RequestExecutor'); goog.require('ydn.json'); /** * @extends {ydn.db.crud.req.RequestExecutor} * @param {string} dbname database name. * @param {!ydn.db.schema.Database} schema schema. * @constructor * @implements {ydn.db.crud.req.IRequestExecutor} * @struct */ ydn.db.crud.req.WebSql = function(dbname, schema) { goog.base(this, dbname, schema); }; goog.inherits(ydn.db.crud.req.WebSql, ydn.db.crud.req.RequestExecutor); /** * @const * @type {boolean} debug flag. */ ydn.db.crud.req.WebSql.DEBUG = false; /** * Maximum number of readonly requests created per transaction. * Common implementation in WebSQL library is sending massive requests * to the transaction and use setTimeout to prevent breaking the system. * To get optimal performance, we send limited number of request per * transaction. * Sending more request will not help much because JS is just parsing and * pushing to result array data which is faster than SQL processing. * Smaller number also help SQLite engine to give * other transaction to perform parallel requests. * @const * @type {number} Maximum number of readonly requests created per transaction. */ ydn.db.crud.req.WebSql.REQ_PER_TX = 10; /** * Maximum number of read-write requests created per transaction. * Since SQLite locks all stores during read write request, it is better * to give this number smaller. Larger number will not help to get faster * because it bottleneck is in SQL engine, not from JS side. * @const * @type {number} Maximum number of read-write requests created per transaction. */ ydn.db.crud.req.WebSql.RW_REQ_PER_TX = 2; /** * @protected * @type {goog.log.Logger} logger. */ ydn.db.crud.req.WebSql.prototype.logger = goog.log.getLogger('ydn.db.crud.req.WebSql'); /** * Parse resulting object of a row into original object as it 'put' into the * database. * @final * @param {!Object} row row. * @param {ydn.db.schema.Store} store store schema. * @return {!Object} parse value. */ ydn.db.crud.req.WebSql.parseRow = function(row, store) { if (store.isFixed() && !store.usedInlineKey() && store.countIndex() == 0 && row[ydn.db.base.DEFAULT_BLOB_COLUMN]) { // check for blob or file var s = row[ydn.db.base.DEFAULT_BLOB_COLUMN]; var BASE64_MARKER = ';base64,'; if (s.indexOf(BASE64_MARKER) == -1) { return ydn.json.parse(s); } else { if (s.charAt(0) == '"' && s.charAt(s.length - 1) == '"') { s = s.substr(1, s.length - 2); } var parts = s.split(BASE64_MARKER); var contentType = parts[0].split(':')[1]; var raw = window.atob(parts[1]); var rawLength = raw.length; var uInt8Array = new Uint8Array(rawLength); for (var i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array.buffer], {type: contentType}); } } var value = row[ydn.db.base.DEFAULT_BLOB_COLUMN] ? ydn.json.parse(row[ydn.db.base.DEFAULT_BLOB_COLUMN]) : {}; if (goog.isDefAndNotNull(store.keyPath)) { var key = ydn.db.schema.Index.sql2js(row[store.keyPath], store.getType()); if (goog.isDefAndNotNull(key)) { store.setKeyValue(value, key); } } for (var j = 0; j < store.countIndex(); j++) { var index = store.index(j); var column_name = index.getSQLIndexColumnName(); if (column_name == ydn.db.base.DEFAULT_BLOB_COLUMN || index.isComposite() || index.isMultiEntry()) { continue; } if (index.getType() == ydn.db.schema.DataType.DATE || store.isFixed()) { // fixed schema does not stored data in default blob // in JSON serialization, date lost type. var x = row[column_name]; var v = ydn.db.schema.Index.sql2js(x, index.getType()); if (goog.isDef(v)) { index.applyValue(value, v); } } } return value; }; /** * Extract key from row result. * @final * @protected * @param {ydn.db.schema.Store} table table of concern. * @param {!Object} row row. * @return {!Object} parse value. */ ydn.db.crud.req.WebSql.prototype.getKeyFromRow = function(table, row) { return row[table.keyPath || ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME]; }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.putByKeys = goog.abstractMethod; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.putData = function(tx, tx_no, df, store_name, data, delimiter) { throw new ydn.debug.error.NotImplementedException('putData'); }; /** * @param {ydn.db.Request} req tx. * @param {boolean} is_replace true if `put`, otherwise `add`. * @param {boolean} single false for array input. * @param {string} store_name table name. * @param {!Array.<!Object>} objects object to put. * @param {!Array.<(!Array|string|number)>=} opt_keys optional out-of-line keys. * @protected */ ydn.db.crud.req.WebSql.prototype.insertObjects = function( req, is_replace, single, store_name, objects, opt_keys) { var create = !is_replace; var table = this.schema.getStore(store_name); var insert_statement = create ? 'INSERT INTO ' : 'INSERT OR REPLACE INTO '; var tx = req.getTx(); var me = this; var result_keys = []; var result_count = 0; var msg = req.getLabel() + ' inserting ' + objects.length + ' objects.'; var has_error = false; /** * Put and item at i. This ydn.db.con.Storage will invoke callback to df if * all objects * have been put, otherwise recursive call to itself at next i+1 item. * @param {number} i index. * @param {SQLTransaction} tx transaction. */ var put = function(i, tx) { if (!goog.isDefAndNotNull(objects[i])) { goog.log.finest(me.logger, 'empty object at ' + i + ' of ' + objects.length); result_count++; if (result_count == objects.length) { goog.log.finer(me.logger, msg + ' success ' + msg); // console.log(msg, result_keys); req.setDbValue(result_keys, has_error); } else { var next = i + ydn.db.crud.req.WebSql.RW_REQ_PER_TX; if (next < objects.length) { put(next, tx); } } } var out; if (goog.isDef(opt_keys)) { out = table.sqlNamesValues(objects[i], opt_keys[i]); } else { out = table.sqlNamesValues(objects[i]); } //console.log([obj, JSON.stringify(obj)]); var sql = insert_statement + table.getQuotedName() + ' (' + out.columns.join(', ') + ') ' + 'VALUES (' + out.slots.join(', ') + ');'; var i_msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + out.values + ' REQ: ' + i + ' of ' + objects.length; /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var success_callback = function(transaction, results) { result_count++; var key = goog.isDef(out.key) ? out.key : results.insertId; if (results.rowsAffected < 1) { // catch for no-op // assuming index constraint no op has_error = true; key = new ydn.db.ConstraintError(key + ' no-op'); } /** * Insert a row for each multi entry index. * @param {ydn.db.schema.Index} index multi entry index. * @param {number} value index at. */ var insertMultiEntryIndex = function(index, value) { var idx_name = ydn.db.base.PREFIX_MULTIENTRY + table.getName() + ':' + index.getName(); var idx_sql = insert_statement + goog.string.quote(idx_name) + ' (' + table.getSQLKeyColumnNameQuoted() + ', ' + index.getSQLIndexColumnNameQuoted() + ') VALUES (?, ?)'; var idx_params = [ydn.db.schema.Index.js2sql(key, table.getType()), ydn.db.schema.Index.js2sql(value, index.getType())]; /** * @param {SQLTransaction} tx transaction. * @param {SQLResultSet} rs results. */ var idx_success = function(tx, rs) { }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var idx_error = function(tr, error) { goog.log.warning(me.logger, 'multiEntry index insert error: ' + error.message); return false; }; goog.log.finest(me.logger, req.getLabel() + ' multiEntry ' + idx_sql + ' ' + idx_params); tx.executeSql(idx_sql, idx_params, idx_success, idx_error); }; for (var j = 0, nj = table.countIndex(); j < nj; j++) { var idx = table.index(j); if (idx.isMultiEntry()) { var index_values = ydn.db.utils.getValueByKeys(objects[i], idx.getKeyPath()); var n = (!index_values ? 0 : index_values.length) || 0; for (var k = 0; k < n; k++) { insertMultiEntryIndex(idx, index_values[k]); } } } if (single) { // console.log(msg, key); req.setDbValue(key); } else { result_keys[i] = key; if (result_count == objects.length) { // console.log(msg, result_keys); req.setDbValue(result_keys, has_error); } else { var next = i + ydn.db.crud.req.WebSql.RW_REQ_PER_TX; if (next < objects.length) { put(next, transaction); } } } }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([sql, out, tr, error]); } result_count++; has_error = true; if (error.code == 6) { // constraint failed error.name = 'ConstraintError'; } else { goog.log.warning(me.logger, 'error: ' + error.message + ' ' + msg); } if (single) { req.setDbValue(error, true); } else { result_keys[i] = error; if (result_count == objects.length) { goog.log.finest(me.logger, 'success ' + msg); // still success message ? req.setDbValue(result_keys, has_error); } else { var next = i + ydn.db.crud.req.WebSql.RW_REQ_PER_TX; if (next < objects.length) { put(next, tr); } } } return false; // continue, not rollback }; // console.log([sql, out.values]); goog.log.finest(me.logger, i_msg); if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log(sql, out.values); } tx.executeSql(sql, out.values, success_callback, error_callback); }; if (objects.length > 0) { // send parallel requests for (var i = 0; i < ydn.db.crud.req.WebSql.RW_REQ_PER_TX && i < objects.length; i++) { put(i, /** @type {SQLTransaction} */ (tx)); } } else { goog.log.finer(this.logger, 'success'); req.setDbValue([]); } }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.putByKeys = function(rq, objs, keys) { if (keys.length == 0) { rq.setDbValue([]); return; } var tx = rq.getTx(); var results = []; var count = 0; var total = 0; var me = this; /** * * @param {string} store_name * @param {!Array.<number>} idx */ var execute_on_store = function(store_name, idx) { var idx_objs = []; goog.log.finest(me.logger, 'put ' + idx.length + ' objects to ' + store_name); var store = me.schema.getStore(store_name); var inline = store.usedInlineKey(); var idx_keys = inline ? undefined : []; for (var i = 0; i < idx.length; i++) { idx_objs.push(objs[idx[i]]); if (!inline) { idx_keys.push(keys[idx[i]].getId()); } } var i_rq = rq.copy(); i_rq.addCallbacks(function(xs) { for (var i = 0; i < idx.length; i++) { results[idx[i]] = xs[i]; } count++; if (count == total) { rq.setDbValue(results); } }, function(e) { count++; if (count == total) { rq.setDbValue(results, true); } }); me.insertObjects(i_rq, false, false, store_name, idx_objs, idx_keys); }; var store_name = ''; var store; var idx = []; var ids = []; for (var i = 0; i < keys.length; i++) { var name = keys[i].getStoreName(); var id = keys[i].getId(); if (name != store_name) { total++; if (idx.length > 0) { execute_on_store(store_name, idx); } idx = [i]; ids = [id]; store_name = name; } else { idx.push(i); ids.push(id); } } if (idx.length > 0) { execute_on_store(store_name, idx); } }; /** * * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.getById = function(req, table_name, id) { var tx = req.getTx(); var table = this.schema.getStore(table_name); goog.asserts.assertInstanceof(table, ydn.db.schema.Store, table_name + ' not found.'); var me = this; var column_name = table.getSQLKeyColumnNameQuoted(); var params = [ydn.db.schema.Index.js2sql(id, table.getType())]; var sql = 'SELECT * FROM ' + table.getQuotedName() + ' WHERE ' + column_name + ' = ?'; var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + params; /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { if (results.rows.length > 0) { var row = results.rows.item(0); if (goog.isDefAndNotNull(row)) { var value = ydn.db.crud.req.WebSql.parseRow(row, table); req.setDbValue(value); } else { goog.log.finer(me.logger, 'success no result: ' + msg); req.setDbValue(undefined); } } else { goog.log.finer(me.logger, 'success no result: ' + msg); req.setDbValue(undefined); } }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } goog.log.warning(me.logger, 'error: ' + msg + ' ' + error.message); req.setDbValue(error, true); return false; }; //goog.global.console.log(['getById', sql, params]); goog.log.finest(this.logger, msg); tx.executeSql(sql, params, callback, error_callback); }; /** * * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.listByIds = function(req, table_name, ids) { var tx = req.getTx(); var me = this; var objects = []; var result_count = 0; var table = this.schema.getStore(table_name); /** * Get fetch the given id of i position and put to results array in * i position. If req_done are all true, df will be invoked, if not * it recursively call itself to next sequence. * @param {number} i the index of ids. * @param {SQLTransaction} tx tx. */ var get = function(i, tx) { /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { result_count++; if (results.rows.length > 0) { var row = results.rows.item(0); if (goog.isDefAndNotNull(row)) { objects[i] = ydn.db.crud.req.WebSql.parseRow(row, table); } // this is get function, we take only one result. } else { objects[i] = undefined; // not necessary. } if (result_count == ids.length) { req.setDbValue(objects); } else { var next = i + ydn.db.crud.req.WebSql.REQ_PER_TX; if (next < ids.length) { get(next, transaction); } } }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { result_count++; if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } goog.log.warning(me.logger, 'error: ' + sql + ' ' + error.message); // t.abort(); there is no abort if (result_count == ids.length) { req.setDbValue(objects); } else { var next = i + ydn.db.crud.req.WebSql.REQ_PER_TX; if (next < ids.length) { get(next, tr); } } return false; }; var id = ids[i]; var column_name = table.getSQLKeyColumnNameQuoted(); var params = [ydn.db.schema.Index.js2sql(id, table.getType())]; var sql = 'SELECT * FROM ' + table.getQuotedName() + ' WHERE ' + column_name + ' = ?'; goog.log.finest(me.logger, 'SQL: ' + sql + ' PARAMS: ' + params); tx.executeSql(sql, params, callback, error_callback); }; if (ids.length > 0) { // send parallel requests for (var i = 0; i < ydn.db.crud.req.WebSql.REQ_PER_TX && i < ids.length; i++) { get(i, /** @type {SQLTransaction} */ (tx)); } } else { goog.log.finer(me.logger, 'success'); req.setDbValue([]); } }; /** * * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.listByKeys = function(req, keys) { var tx = req.getTx(); var me = this; var objects = []; var result_count = 0; var get = function(i, tx) { var key = keys[i]; var table_name = key.getStoreName(); var table = me.schema.getStore(table_name); /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { result_count++; if (results.rows.length > 0) { var row = results.rows.item(0); if (goog.isDefAndNotNull(row)) { objects[i] = ydn.db.crud.req.WebSql.parseRow(row, table); } // this is get function, we take only one result. } else { objects[i] = undefined; // not necessary. } if (result_count == keys.length) { goog.log.finest(me.logger, 'success ' + sql); req.setDbValue(objects); } else { var next = i + ydn.db.crud.req.WebSql.REQ_PER_TX; if (next < keys.length) { get(next, transaction); } } }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } req.setDbValue(error, true); return false; }; var id = key.getNormalizedId(); var column_name = table.getSQLKeyColumnNameQuoted(); var params = [ydn.db.schema.Index.js2sql(id, table.getType())]; var sql = 'SELECT * FROM ' + table.getQuotedName() + ' WHERE ' + column_name + ' = ?'; goog.log.finest(me.logger, 'SQL: ' + sql + ' PARAMS: ' + params); tx.executeSql(sql, params, callback, error_callback); }; if (keys.length > 0) { // send parallel requests for (var i = 0; i < ydn.db.crud.req.WebSql.REQ_PER_TX && i < keys.length; i++) { get(i, tx); } } else { goog.log.finest(this.logger, 'success'); req.setDbValue([]); } }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.clearByStores = function(req, store_names) { var tx = req.getTx(); var me = this; var deleteStore = function(i, tx) { var store = me.schema.getStore(store_names[i]); var sql = 'DELETE FROM ' + store.getQuotedName(); /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { if (i == store_names.length - 1) { goog.log.finest(me.logger, 'success ' + sql); req.setDbValue(store_names.length); } else { deleteStore(i + 1, transaction); } }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var errback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } req.setDbValue(error, true); return false; }; goog.log.finest(me.logger, 'SQL: ' + sql + ' PARAMS: []'); tx.executeSql(sql, [], callback, errback); /** * * @param {ydn.db.schema.Index} index */ var deleteMultiEntryIndex = function(index) { var idx_name = ydn.db.base.PREFIX_MULTIENTRY + store.getName() + ':' + index.getName(); var idx_sql = 'DELETE FROM ' + goog.string.quote(idx_name); goog.log.finest(me.logger, 'SQL: ' + idx_sql); tx.executeSql(idx_sql, []); }; for (var j = 0, n = store.countIndex(); j < n; j++) { var index = store.index(j); if (index.isMultiEntry()) { deleteMultiEntryIndex(index); } } }; if (store_names.length > 0) { deleteStore(0, tx); } else { goog.log.finest(this.logger, 'success'); req.setDbValue(0); } }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.removeByKeys = function(req, keys) { var tx = req.getTx(); var me = this; var count = 0; var has_failed = false; var store_name, store, key; var msg = req.getLabel() + ' removeByKeys: ' + keys.length + ' keys'; goog.log.finest(this.logger, msg); var removeAt = function(i) { if (i >= keys.length) { req.setDbValue(count, has_failed); return; } var store = me.schema.getStore(keys[i].getStoreName()); var key = ydn.db.schema.Index.js2sql(keys[i].getId(), store.getType()); /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var success_callback = function(transaction, results) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log(results); } count++; removeAt(i); }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } goog.log.warning(me.logger, 'error: ' + i_msg + error.message); has_failed = true; removeAt(i); return false; }; var where = ' WHERE ' + store.getSQLKeyColumnNameQuoted() + ' = ?'; var sql = 'DELETE FROM ' + store.getQuotedName() + where; //console.log([sql, out.values]) var i_msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + [key]; if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log(i_msg); } tx.executeSql(sql, [key], success_callback, error_callback); i++; /** * * @param {ydn.db.schema.Index} index */ var deleteMultiEntryIndex = function(index) { var idx_name = ydn.db.base.PREFIX_MULTIENTRY + store.getName() + ':' + index.getName(); var idx_sql = 'DELETE FROM ' + goog.string.quote(idx_name) + where; goog.log.finest(me.logger, req.getLabel() + + ' SQL: ' + idx_sql); tx.executeSql(idx_sql, [key]); }; for (var j = 0, n = store.countIndex(); j < n; j++) { var index = store.index(j); if (index.isMultiEntry()) { deleteMultiEntryIndex(index); } } }; removeAt(0); }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.removeById = function(req, table, id) { var tx = req.getTx(); var store = this.schema.getStore(table); var key = ydn.db.schema.Index.js2sql(id, store.getType()); var me = this; /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var success_callback = function(transaction, results) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log(results); } req.setDbValue(results.rowsAffected); }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } req.setDbValue(error, true); return false; // not rollback yet. }; var where = ' WHERE ' + store.getSQLKeyColumnNameQuoted() + ' = ?'; var sql = 'DELETE FROM ' + store.getQuotedName() + where; //console.log([sql, out.values]) var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + [key]; goog.log.finest(this.logger, msg); tx.executeSql(sql, [key], success_callback, error_callback); /** * * @param {ydn.db.schema.Index} index */ var deleteMultiEntryIndex = function(index) { var idx_name = ydn.db.base.PREFIX_MULTIENTRY + store.getName() + ':' + index.getName(); var idx_sql = 'DELETE FROM ' + goog.string.quote(idx_name) + where; goog.log.finest(me.logger, req.getLabel() + + ' SQL: ' + idx_sql); tx.executeSql(idx_sql, [key]); }; for (var j = 0, n = store.countIndex(); j < n; j++) { var index = store.index(j); if (index.isMultiEntry()) { deleteMultiEntryIndex(index); } } }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.clearByKeyRange = function(req, store_name, key_range) { this.clear_by_key_range_(req, store_name, undefined, key_range); }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.removeByKeyRange = function(req, store_name, key_range) { this.clear_by_key_range_(req, store_name, undefined, key_range); }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.removeByIndexKeyRange = function(req, store_name, index_name, key_range) { this.clear_by_key_range_(req, store_name, index_name, key_range); }; /** * Retrieve primary keys or value from a store in a given key range. * @param {ydn.db.Request} req request. * @param {string} store_name table name. * @param {string|undefined} column_name name. * @param {IDBKeyRange} key_range to retrieve. * @private */ ydn.db.crud.req.WebSql.prototype.clear_by_key_range_ = function(req, store_name, column_name, key_range) { var tx = req.getTx(); var me = this; var arr = []; var store = this.schema.getStore(store_name); var sql = 'DELETE FROM ' + store.getQuotedName(); var params = []; var where_params = []; var where = ''; if (goog.isDefAndNotNull(key_range)) { if (goog.isDef(column_name)) { var index = store.getIndex(column_name); ydn.db.KeyRange.toSql(index.getSQLIndexColumnNameQuoted(), index.getType(), key_range, where_params, params); } else { ydn.db.KeyRange.toSql(store.getSQLKeyColumnNameQuoted(), store.getType(), key_range, where_params, params); } where = ' WHERE ' + where_params.join(' AND '); } sql += where; /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { goog.log.finest(me.logger, 'success ' + msg); req.setDbValue(results.rowsAffected); }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } goog.log.warning(me.logger, 'error: ' + msg + error.message); req.setDbValue(error, true); return false; }; //console.log([sql, params]) var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + params; goog.log.finest(this.logger, msg); tx.executeSql(sql, params, callback, error_callback); /** * * @param {ydn.db.schema.Index} index */ var deleteMultiEntryIndex = function(index) { var idx_name = ydn.db.base.PREFIX_MULTIENTRY + store.getName() + ':' + index.getName(); var idx_sql = 'DELETE FROM ' + goog.string.quote(idx_name) + where; goog.log.finest(me.logger, req.getLabel() + + ' SQL: ' + idx_sql); tx.executeSql(idx_sql, where_params); }; for (var j = 0, n = store.countIndex(); j < n; j++) { var j_index = store.index(j); if (j_index.isMultiEntry()) { deleteMultiEntryIndex(j_index); } } }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.countStores = function(req, tables) { var tx = req.getTx(); var me = this; var out = []; /** * * @param {number} i */ var count = function(i) { var table = tables[i]; var sql = 'SELECT COUNT(*) FROM ' + goog.string.quote(table); /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { var row = results.rows.item(0); // console.log(['row ', row , results]); out[i] = parseInt(row['COUNT(*)'], 10); i++; if (i == tables.length) { req.setDbValue(out); } else { count(i); } }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } req.setDbValue(error, true); return false; }; goog.log.finest(me.logger, 'SQL: ' + sql + ' PARAMS: []'); tx.executeSql(sql, [], callback, error_callback); }; if (tables.length == 0) { goog.log.finest(this.logger, 'success'); req.setDbValue(0); } else { count(0); } }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.countKeyRange = function(req, table, key_range, index_name, unique) { var me = this; var params = []; var store = this.schema.getStore(table); var sql = store.toSql(params, ydn.db.base.QueryMethod.COUNT, index_name, key_range, false, unique); /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([sql, results]); } var row = results.rows.item(0); // console.log(['row ', row , results]); req.setDbValue(ydn.object.takeFirst(row)); // usually row['COUNT(*)'] // , but may be row['COUNT("id")'] }; /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([sql, error]); } req.setDbValue(error, true); return false; }; var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + params; goog.log.finest(this.logger, msg); req.getTx().executeSql(sql, params, callback, error_callback); }; /** * @inheritDoc */ ydn.db.crud.req.WebSql.prototype.list = function(req, mth, store_name, index_column, key_range, limit, offset, reverse, distinct, opt_position) { var me = this; var arr = []; var store = this.schema.getStore(store_name); var key_column = store.getSQLKeyColumnName(); var primary_type = store.getType(); var effective_type = primary_type; var index = goog.isDefAndNotNull(index_column) && (index_column !== key_column) ? store.getIndex(index_column) : null; var effective_column = index_column || key_column; if (index) { effective_type = index.getType(); } var params = []; var sql; if (!!opt_position && goog.isDef(opt_position[0])) { var e_key = /** @type {IDBKey} */ (opt_position[0]); if (index && goog.isDef(opt_position[1])) { var p_key = /** @type {IDBKey} */ (opt_position[1]); sql = store.sqlContinueIndexEffectiveKey(mth, params, index.getName(), key_range, e_key, true, p_key, reverse, distinct); } else { sql = store.sqlContinueEffectiveKey(mth, params, index_column, key_range, reverse, distinct, e_key, true); } } else { sql = store.toSql(params, mth, effective_column, key_range, reverse, distinct); } if (goog.isNumber(limit)) { sql += ' LIMIT ' + limit; } if (goog.isNumber(offset)) { sql += ' OFFSET ' + offset; } /** * @param {SQLTransaction} transaction transaction. * @param {SQLResultSet} results results. */ var callback = function(transaction, results) { var n = results.rows.length; if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log(results); } var row; for (var i = 0; i < n; i++) { row = results.rows.item(i); if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log(row); } if (mth == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) { arr[i] = ydn.db.schema.Index.sql2js(row[key_column], primary_type); } else if (mth == ydn.db.base.QueryMethod.LIST_KEY) { arr[i] = ydn.db.schema.Index.sql2js(row[effective_column], effective_type); } else if (mth == ydn.db.base.QueryMethod.LIST_KEYS) { arr[i] = [ ydn.db.schema.Index.sql2js(row[effective_column], effective_type), ydn.db.schema.Index.sql2js(row[key_column], primary_type)]; } else if (goog.isDefAndNotNull(row)) { // LIST_VALUE arr[i] = ydn.db.crud.req.WebSql.parseRow(row, store); } } goog.log.finer(me.logger, 'success ' + req); if (opt_position && row) { opt_position[0] = ydn.db.schema.Index.sql2js(row[effective_column], effective_type); opt_position[1] = ydn.db.schema.Index.sql2js(row[key_column], primary_type); } req.setDbValue(arr); }; var msg = req + ' SQL: ' + sql + ' ;params= ' + ydn.json.stringify(params); /** * @param {SQLTransaction} tr transaction. * @param {SQLError} error error. * @return {boolean} true to roll back. */ var error_callback = function(tr, error) { if (ydn.db.crud.req.WebSql.DEBUG) { goog.global.console.log([tr, error]); } goog.log.warning(me.logger, 'error: ' + msg + error.message); req.setDbValue(error, true); return false; }; goog.log.finest(this.logger, msg); req.getTx().executeSql(sql, params, callback, error_callback); };