ydn.db
Version:
Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.
1,019 lines (857 loc) • 31.2 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 WebSQL database connector.
*
* @see http://www.w3.org/TR/webdatabase/
*
* @author kyawtun@yathit.com (Kyaw Tun)
*/
goog.provide('ydn.db.con.WebSql');
goog.require('goog.async.Deferred');
goog.require('goog.log');
goog.require('goog.functions');
goog.require('ydn.db.SecurityError');
goog.require('ydn.db.base');
goog.require('ydn.db.con.IDatabase');
goog.require('ydn.debug.error.NotImplementedException');
goog.require('ydn.json');
goog.require('ydn.string');
/**
* Construct a WebSql database connector.
* Note: Version is ignored, since it does work well.
* @param {number=} opt_size estimated database size. Default to 5 MB.
* @param {ydn.db.base.Mechanisms=} opt_type either WEBSQL or SQLITE
* @implements {ydn.db.con.IDatabase}
* @constructor
* @struct
*/
ydn.db.con.WebSql = function(opt_size, opt_type) {
// Safari default limit is slightly over 4 MB, so we ask the largest storage
// size but, still not don't bother to user.
// Opera don't ask user even request for 1 GB.
/**
* @private
* @final
* @type {number}
*/
this.size_ = goog.isDef(opt_size) ? opt_size : 4 * 1024 * 1024; // 5 MB
this.type_ = opt_type || ydn.db.base.Mechanisms.WEBSQL;
};
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.connect = function(dbname, schema) {
var description = dbname;
/**
* @type {ydn.db.con.WebSql}
*/
var me = this;
var old_version = NaN;
var init_migrated = false;
var df = new goog.async.Deferred();
/**
*
* @param {Database} db database.
* @param {Error=} opt_err error object only in case of error.
*/
var setDb = function(db, opt_err) {
if (goog.isDef(opt_err)) {
me.sql_db_ = null;
df.errback(opt_err);
} else {
me.sql_db_ = db;
df.callback(parseFloat(old_version));
}
};
/**
* Migrate from current version to the new version.
* @private
* @param {Database} db database.
* @param {ydn.db.schema.Database} schema schema.
* @param {boolean} is_version_change version change or not.
*/
var doVersionChange_ = function(db, schema, is_version_change) {
var action = is_version_change ? 'changing version' : 'setting version';
var current_version = db.version ? parseInt(db.version, 10) : 0;
var new_version = schema.isAutoVersion() ?
is_version_change ? isNaN(current_version) ?
1 : (current_version + 1) : current_version : schema.version;
goog.log.fine(me.logger, dbname + ': ' + action + ' from ' +
db.version + ' to ' + new_version);
var executed = false;
var updated_count = 0;
/**
* SQLTransactionCallback
* @param {!SQLTransaction} tx transaction object.
*/
var transaction_callback = function(tx) {
// sniff current table info in the database.
me.getSchema(function(existing_schema) {
executed = true;
for (var i = 0; i < schema.count(); i++) {
var counter = function(ok) {
if (ok) {
updated_count++;
}
};
var table_info = existing_schema.getStore(schema.store(i).getName());
// hint to sniffed schema, so that some lost info are recovered.
var hinted_store_schema = table_info ?
table_info.hintForWebSql(schema.store(i)) : null;
me.update_store_with_info_(tx, schema.store(i), counter,
hinted_store_schema);
}
for (var j = 0; j < existing_schema.count(); j++) {
var info_store = existing_schema.store(j);
if (!schema.hasStore(info_store.getName())) {
if (schema instanceof ydn.db.schema.EditableDatabase) {
var edited_schema = schema;
edited_schema.addStore(info_store);
} else {
var sql = 'DROP TABLE ' + info_store.getQuotedName();
goog.log.finer(me.logger, sql);
tx.executeSql(sql, [],
function(tr) {
// ok
}, function(tx, e) {
throw e;
});
}
}
}
}, tx, db);
};
/**
* SQLVoidCallback
*/
var success_callback = function() {
var has_created = updated_count == schema.stores.length;
if (!executed) {
// success callback without actually executing
goog.log.warning(me.logger, dbname + ': ' + action + ' voided.');
//if (!me.df_sql_db_.hasFired()) { // FIXME: why need to check ?
// this checking is necessary when browser prompt user,
// this migration function run two times: one creating table
// and one without creating table. How annoying ?
// testing is in /test/test_multi_storage.html page.
} else {
var msg = '.';
if (updated_count != schema.stores.length) {
msg = ' but unexpected stores exists.';
}
goog.log.finest(me.logger, dbname + ':' + db.version + ' ready' + msg);
setDb(db);
}
};
/**
* SQLTransactionErrorCallback
* @param {SQLError} e error.
*/
var error_callback = function(e) {
goog.log.error(me.logger, 'SQLError ' + e + ' ' + e.code + '(' + e.message + ') ' +
'while changing version from ' + db.version + ' to ' + new_version +
' on ' + dbname);
if (ydn.db.con.WebSql.DEBUG) {
goog.global.console.log(e);
}
throw e;
};
// db.transaction(transaction_callback, error_callback, success_callback);
db.changeVersion(db.version, new_version + '', transaction_callback,
error_callback, success_callback);
};
/**
* @type {Database}
*/
var db = null;
var creationCallback = function(e) {
var msg = init_migrated ?
' and already migrated, but migrating again.' : ', migrating.';
goog.log.finest(me.logger, 'receiving creation callback ' + msg);
// the standard state that we should call VERSION_CHANGE request on
// this callback.
// http://www.w3.org/TR/webdatabase/#dom-opendatabase
var use_version_change_request = true;
//if (!init_migrated) {
// yeah, to make sure.
doVersionChange_(db, schema, use_version_change_request);
//}
};
try {
/**
* http://www.w3.org/TR/webdatabase/#dom-opendatabase
*
* Opening robust web database is tricky. Mainly due to the fact that
* an empty database is created even if user deny to create the database.
*/
var version = schema.isAutoVersion() ? '' : schema.version + '';
// From the W3C description:
// <snap>
// If the database version provided is not the empty string, and there is
// already a database with the given name from the origin origin, but the
// database has a different version than the version provided, then throw
// an INVALID_STATE_ERR exception and abort these steps.
// </snap>
//
// Since we have no way of knowing, the database with different version
// already exist in user browser, opening a version database with specific
// version is unwise.
//
// Interestingly chrome and (Safari on OS X) do not emmit INVALID_STATE_ERR
// even if the database already exist. It simply invokes creationCallback,
// as it should.
//
// Hence, always open with empty string database version.
if (this.type_ == ydn.db.base.Mechanisms.SQLITE) {
// use sqlitePlugin
if (goog.global['sqlitePlugin']) {
db = goog.global['sqlitePlugin'].openDatabase(dbname, '', description, this.size_);
if (!db.readTransaction) {
db.readTransaction = db.transaction;
}
db.changeVersion = function(old_ver, new_ver, transaction_callback, error_callback, success_callback) {
db.transaction(transaction_callback, error_callback, success_callback);
};
} else {
goog.log.warning(this.logger, 'sqlitePlugin not found.');
db = null;
this.last_error_ = new Error('sqlitePlugin not found.');
}
} else {
db = goog.global.openDatabase(dbname, '', description, this.size_);
}
} catch (e) {
if (e.name == 'SECURITY_ERR') {
goog.log.warning(this.logger, 'SECURITY_ERR for opening ' + dbname);
db = null; // this will purge the tx queue
// throw new ydn.db.SecurityError(e);
// don't throw now, so that web app can handle without using
// database.
this.last_error_ = new ydn.db.SecurityError(e);
} else {
// this should never happen.
throw e;
}
}
if (!db) {
setDb(null, this.last_error_);
} else {
// Even if db version are the same, we cannot assume schema are as expected.
// Sometimes database is just empty with given version.
// in case previous database fail, but user granted in next refresh.
// In this case, empty database of the request version exist,
// but no tables.
// WebSQL return limbo database connection,
// if user haven't decieted whether to allow to deny the storage.
// the limbo database connection do not execute transaction.
// version change concept in WebSQL is broken.
// db.transaction request can alter or create table, which suppose to
// be done only with db.changeVersion request.
// the approach we taking here is, we still honour visioning of database
// but, we do not assume, opening right version will have correct
// schema as expected. If not correct, we will correct to the schema,
// without increasing database version.
old_version = db.version || ''; // sqlite does not have version attribute.
var db_info = 'database ' + dbname +
(old_version.length == 0 ? '' : ' version ' + db.version);
if (goog.isDefAndNotNull(schema.version) && schema.version == db.version) {
goog.log.fine(me.logger, 'Existing ' + db_info + ' opened as requested.');
setDb(db);
} else {
// require upgrade check
this.getSchema(function(existing_schema) {
var msg = schema.difference(existing_schema, true, false);
if (msg) {
if (old_version == 0) {
goog.log.fine(me.logger, 'New ' + db_info + ' created.');
doVersionChange_(db, schema, true);
} else if (!schema.isAutoVersion()) {
goog.log.fine(me.logger, 'Existing ' + db_info + ' opened and ' +
' schema change to version ' + schema.version + ' for ' + msg);
doVersionChange_(db, schema, true);
} else {
goog.log.fine(me.logger, 'Existing ' + db_info + ' opened and ' +
'schema change for ' + msg);
doVersionChange_(db, schema, true);
}
} else {
// same schema.
goog.log.fine(me.logger, 'Existing ' + db_info + ' with same schema opened.');
setDb(db);
}
}, null, db);
}
}
return df;
};
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.getType = function() {
return this.type_;
};
/**
*
* @type {Error} error.
* @private
*/
ydn.db.con.WebSql.prototype.last_error_ = null;
/**
* @type {Database} database instance.
* @private
*/
ydn.db.con.WebSql.prototype.sql_db_ = null;
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.getDbInstance = function() {
return this.sql_db_ || null;
};
/**
*
* @return {boolean} true if supported.
*/
ydn.db.con.WebSql.isSupported = function() {
return goog.isFunction(goog.global.openDatabase);
};
/**
*
* @return {boolean} true if sqlite is supported on cordova enviroment.
*/
ydn.db.con.WebSql.isSqliteSupported = function() {
return !!goog.global['sqlitePlugin'];
};
/**
* @const
* @type {boolean} debug flag.
*/
ydn.db.con.WebSql.DEBUG = false;
/**
* @protected
* @type {goog.log.Logger} logger.
*/
ydn.db.con.WebSql.prototype.logger =
goog.log.getLogger('ydn.db.con.WebSql');
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.onFail = function(e) {};
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.onError = function(e) {};
/**
* @define {boolean} option to create index.
*/
ydn.db.con.WebSql.CREATE_INDEX = false;
/**
* Initialize variable to the schema and prepare SQL statement for creating
* the table.
* @private
* @param {ydn.db.schema.Store} table table schema.
* @return {!Array.<string>} SQL statement for creating the table.
*/
ydn.db.con.WebSql.prototype.prepareCreateTable_ = function(table) {
// prepare schema
var primary_type = table.getSqlType();
var insert_statement = 'CREATE TABLE IF NOT EXISTS ';
var sql = insert_statement + table.getQuotedName() + ' (';
var q_primary_column = table.getSQLKeyColumnNameQuoted();
sql += q_primary_column + ' ' + primary_type +
' PRIMARY KEY ';
if (table.autoIncrement) {
sql += ' AUTOINCREMENT ';
}
// table must has a default field to store schemaless fields, unless
// fixed table schema is used.
if (!table.isFixed() ||
// note: when even when using fixed schema, blob data are store in
// default column when store is out-of-line non-indexing
(!table.usedInlineKey()) && table.countIndex() == 0) {
sql += ' ,' + ydn.db.base.DEFAULT_BLOB_COLUMN + ' ' +
ydn.db.schema.DataType.BLOB;
}
var sqls = [];
var sep = ', ';
var column_names = [q_primary_column];
for (var i = 0, n = table.countIndex(); i < n; i++) {
/**
* @type {ydn.db.schema.Index}
*/
var index = table.index(i);
var unique = '';
if (index.isMultiEntry()) {
// create separate table for multiEntry
var idx_name = ydn.db.base.PREFIX_MULTIENTRY +
table.getName() + ':' + index.getName();
var idx_unique = index.isUnique() ? ' UNIQUE ' : '';
var multi_entry_sql = insert_statement +
goog.string.quote(idx_name) + ' (' +
q_primary_column + ' ' + primary_type + ', ' +
index.getSQLIndexColumnNameQuoted() + ' ' + index.getSqlType() +
idx_unique + ')';
sqls.push(multi_entry_sql);
continue;
} else if (index.isUnique()) {
unique = ' UNIQUE ';
}
// http://sqlite.org/lang_createindex.html
// http://www.sqlite.org/lang_createtable.html
// Indexing just the column seems like counter productive. ?
/*
INTEGER PRIMARY KEY columns aside, both UNIQUE and PRIMARY KEY constraints
are implemented by creating an index in the database (in the same way as a
"CREATE UNIQUE INDEX" statement would). Such an index is used like any
other index in the database to optimize queries. As a result, there often
no advantage (but significant overhead) in creating an index on a set of
columns that are already collectively subject to a UNIQUE or PRIMARY KEY
constraint.
*/
var key_path = index.getKeyPath();
if (ydn.db.con.WebSql.CREATE_INDEX && index.type != ydn.db.schema.DataType.BLOB && goog.isString(key_path)) {
var idx_sql = 'CREATE ' + unique + ' INDEX IF NOT EXISTS ' +
// table name is suffix to index name to satisfy unique index name
// requirement within a database.
goog.string.quote(table.getName() + '-' + index.getName()) +
' ON ' + table.getQuotedName() +
' (' + index.getSQLIndexColumnNameQuoted() + ')';
sqls.push(idx_sql);
}
var index_key_path = index.getSQLIndexColumnNameQuoted();
if (column_names.indexOf(index_key_path) == -1) {
// store keyPath can also be indexed in IndexedDB spec
sql += sep + index_key_path + ' ' + index.getSqlType() +
unique;
column_names.push(index_key_path);
}
}
sql += ')';
sqls.unshift(sql);
return sqls;
};
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.getVersion = function() {
return this.sql_db_ ? parseFloat(this.sql_db_.version) : undefined;
};
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.getSchema = function(callback, trans, db) {
var me = this;
db = db || this.sql_db_;
var version = (db && db.version) ?
parseFloat(db.version) : undefined;
version = isNaN(version) ? undefined : version;
/**
* @final
* @type {!Array.<ydn.db.schema.Store>}
*/
var stores = [];
/**
* @param {SQLTransaction} transaction transaction.
* @param {SQLResultSet} results results.
*/
var success_callback = function(transaction, results) {
if (!results || !results.rows) {
return;
}
for (var i = 0; i < results.rows.length; i++) {
var info = /** @type {SqliteTableInfo} */ (results.rows.item(i));
// console.log(info);
// name: "st1"
// rootpage: 5
// sql: "CREATE TABLE "st1" ("id" TEXT UNIQUE PRIMARY KEY ,
// _default_ undefined )"
// tbl_name: "st1"
// type: "table"
// name: "sqlite_autoindex_st1_1"
// rootpage: 6
// sql: null
// tbl_name: "st1"
// type: "index"
if (info.name == '__WebKitDatabaseInfoTable__') {
continue;
}
if (info.name == 'sqlite_sequence') {
// internal table used by Sqlite
// http://www.sqlite.org/fileformat2.html#seqtab
continue;
}
if (info.type == 'table') {
var sql = goog.object.get(info, 'sql');
goog.log.finest(me.logger, 'Parsing table schema from SQL: ' + sql);
var str = sql.substr(sql.indexOf('('), sql.lastIndexOf(')'));
var column_infos = ydn.string.split_comma_seperated(str);
var store_key_path = undefined;
var key_type;
var indexes = [];
var autoIncrement = false;
var has_default_blob_column = false;
for (var j = 0; j < column_infos.length; j++) {
var fields = ydn.string.split_space_seperated(column_infos[j]);
var upper_fields = goog.array.map(fields, function(x) {
return x.toUpperCase();
});
var name = goog.string.stripQuotes(fields[0], '"');
var type = ydn.db.schema.Index.toType(upper_fields[1]);
// console.log([fields[1], type]);
if (upper_fields.indexOf('PRIMARY') != -1 &&
upper_fields.indexOf('KEY') != -1) {
key_type = type;
if (goog.isString(name) && !goog.string.isEmpty(name) &&
name != ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME) {
// console.log('PRIMARY ' + name + ' on ' + info.name);
// Array key path is denoted by comma separated list.
var arr_path = name.split(',');
store_key_path = name;
if (arr_path.length > 1) {
store_key_path = arr_path;
key_type = undefined;
}
}
if (upper_fields.indexOf('AUTOINCREMENT') != -1) {
autoIncrement = true;
}
} else if (name == ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME) {
// pass, multi entry store use it as non-unique index key.
} else if (name == ydn.db.base.DEFAULT_BLOB_COLUMN) {
has_default_blob_column = true;
} else {
var unique = upper_fields[2] == 'UNIQUE';
if (goog.string.startsWith(name, info.tbl_name + '-')) {
name = name.substr(info.tbl_name.length + 1);
}
var index = new ydn.db.schema.Index(name, type, unique);
// console.log(index);
indexes.push(index);
}
}
// multiEntry store, which store in separated table
if (goog.string.startsWith(info.name,
ydn.db.base.PREFIX_MULTIENTRY)) {
var names = info.name.split(':');
if (names.length >= 3) {
var st_name = names[1];
var multi_index = new ydn.db.schema.Index(names[2], type,
unique, true);
var ex_index = goog.array.findIndex(indexes, function(x) {
return x.getName() == names[2];
});
if (ex_index >= 0) {
indexes[ex_index] = multi_index;
} else {
indexes.push(multi_index);
}
var store_index = goog.array.findIndex(stores, function(x) {
return x.getName() === st_name;
});
if (store_index >= 0) { // main table exist, add this index
var ex_store = stores[store_index];
stores[store_index] = new ydn.db.schema.Store(ex_store.getName(),
ex_store.getKeyPath(), autoIncrement,
key_type, indexes, undefined, !has_default_blob_column);
} else { // main table don't exist, create a temporary table
stores.push(new ydn.db.schema.Store(st_name, undefined, false,
undefined, [multi_index]));
}
goog.log.finest(me.logger, 'multi entry index "' + multi_index.getName() +
'" found in ' + st_name + (store_index == -1 ? '*' : ''));
} else {
goog.log.warning(me.logger, 'Invalid multiEntry store name "' + info.name +
'"');
}
} else {
var i_store = goog.array.findIndex(stores, function(x) {
return x.getName() === info.name;
});
if (i_store >= 0) {
var ex_index = stores[i_store].index(0);
goog.asserts.assertInstanceof(ex_index, ydn.db.schema.Index);
indexes.push(ex_index);
stores[i_store] = new ydn.db.schema.Store(info.name, store_key_path,
autoIncrement, key_type, indexes, undefined,
!has_default_blob_column);
} else {
var store = new ydn.db.schema.Store(info.name, store_key_path,
autoIncrement, key_type, indexes, undefined,
!has_default_blob_column);
stores.push(store);
}
}
//console.log([info, store]);
}
}
var out = new ydn.db.schema.Database(version, stores);
// console.log(out.toJSON());
callback(out);
};
/**
* @param {SQLTransaction} tr transaction.
* @param {SQLError} error error.
*/
var error_callback = function(tr, error) {
if (ydn.db.con.WebSql.DEBUG) {
goog.global.console.log([tr, error]);
}
throw error;
};
if (!trans) {
var tx_error_callback = function(e) {
goog.log.error(me.logger, 'opening tx: ' + e.message);
throw e;
};
db.readTransaction(function(tx) {
me.getSchema(callback, tx, db);
}, tx_error_callback, success_callback);
return;
}
// var sql = 'PRAGMA table_info(' + goog.string.quote(table_name) + ')';
// Invoking this will result error of:
// "could not prepare statement (23 not authorized)"
var sql = 'SELECT * FROM sqlite_master';
trans.executeSql(sql, [], success_callback, error_callback);
};
/**
*
* @param {SQLTransaction} trans transaction.
* @param {ydn.db.schema.Store} store_schema schema.
* @param {function(boolean)} callback callback on finished.
* @private
*/
ydn.db.con.WebSql.prototype.update_store_ = function(trans, store_schema,
callback) {
var me = this;
this.getSchema(function(table_infos) {
var table_info = table_infos.getStore(store_schema.getName());
me.update_store_with_info_(trans, store_schema,
callback, table_info);
}, trans);
};
/**
* Alter or create table with given table schema.
* @param {SQLTransaction} trans transaction.
* @param {ydn.db.schema.Store} table_schema table schema to be upgrade.
* @param {function(boolean)?} callback callback on finished. return true
* if table is updated.
* @param {ydn.db.schema.Store|undefined} existing_table_schema table
* information in the existing database.
* @private
*/
ydn.db.con.WebSql.prototype.update_store_with_info_ = function(trans,
table_schema, callback, existing_table_schema) {
var me = this;
var count = 0;
var exe_sql = function(sql) {
/**
* @param {SQLTransaction} transaction transaction.
* @param {SQLResultSet} results results.
*/
var success_callback = function(transaction, results) {
count++;
if (count == sqls.length) {
callback(true);
callback = null; // must call only once.
}
};
/**
* @param {SQLTransaction} tr transaction.
* @param {SQLError} error error.
*/
var error_callback = function(tr, error) {
if (ydn.db.con.WebSql.DEBUG) {
goog.global.console.log([tr, error]);
}
count++;
if (count == sqls.length) {
callback(false); // false for no change
callback = null; // must call only once.
}
var msg = goog.DEBUG ? 'SQLError creating table: ' +
table_schema.getName() + ' ' + error.message + ' for executing "' +
sql : '"';
throw new ydn.db.SQLError(error, msg);
};
trans.executeSql(sql, [], success_callback, error_callback);
};
var sqls = this.prepareCreateTable_(table_schema);
var action = 'Create';
if (existing_table_schema) {
// table already exists.
var msg = table_schema.difference(existing_table_schema);
if (msg.length == 0) {
goog.log.finest(me.logger, 'same table ' + table_schema.getName() + ' exists.');
callback(true);
callback = null;
return;
} else {
action = 'Modify';
// ALTER TABLE cannot run in WebSQL
goog.log.warning(this.logger,
'table: ' + table_schema.getName() + ' has changed by ' + msg +
' ALTER TABLE cannot run in WebSql, dropping old table.');
sqls.unshift('DROP TABLE IF EXISTS ' +
goog.string.quote(table_schema.getName()));
}
}
if (ydn.db.con.WebSql.DEBUG) {
goog.global.console.log([sqls, existing_table_schema]);
}
goog.log.finest(me.logger, action + ' table: ' + table_schema.getName() + ': ' +
sqls.join(';'));
for (var i = 0; i < sqls.length; i++) {
exe_sql(sqls[i]);
}
};
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.isReady = function() {
return !!this.sql_db_;
};
/**
* @final
*/
ydn.db.con.WebSql.prototype.close = function() {
// WebSQl API do not have close method.
this.sql_db_ = null;
};
/**
* @inheritDoc
* @protected
*/
ydn.db.con.WebSql.prototype.doTransaction = function(trFn, scopes, mode,
completed_event_handler) {
var me = this;
/**
* SQLTransactionCallback
* @param {!SQLTransaction} tx transaction.
*/
var transaction_callback = function(tx) {
trFn(tx);
};
/**
* SQLVoidCallback
*/
var success_callback = function() {
completed_event_handler(ydn.db.base.TxEventTypes.COMPLETE,
{'type': ydn.db.base.TxEventTypes.COMPLETE});
};
/**
* SQLTransactionErrorCallback
* @param {SQLError} e error.
*/
var error_callback = function(e) {
goog.log.finest(me.logger, me + ': Tx ' + mode + ' request cause error.');
// NOTE: we have to call ABORT, instead of ERROR, here.
// IndexedDB API use COMPLETE or ABORT as promise callbacks.
// ERROR is just an event.
completed_event_handler(ydn.db.base.TxEventTypes.ABORT, e);
};
if (goog.isNull(this.sql_db_)) {
// this happen on SECURITY_ERR
trFn(null);
// NOTE: we have to call ABORT, instead of ERROR, here. See above.
completed_event_handler(ydn.db.base.TxEventTypes.ABORT,
this.last_error_);
}
if (mode == ydn.db.base.TransactionMode.READ_ONLY) {
this.sql_db_.readTransaction(transaction_callback,
error_callback, success_callback);
} else if (mode == ydn.db.base.TransactionMode.VERSION_CHANGE) {
var next_version = this.sql_db_.version + 1;
this.sql_db_.changeVersion(this.sql_db_.version, next_version + '',
transaction_callback, error_callback, success_callback);
} else {
this.sql_db_.transaction(transaction_callback,
error_callback, success_callback);
}
};
/**
*
* @param {string} db_name database name to be deleted.
* @param {string=} opt_type delete only specific types.
*/
ydn.db.con.WebSql.deleteDatabase = function(db_name, opt_type) {
if (!ydn.db.con.WebSql.isSupported() ||
(!!opt_type && opt_type != ydn.db.base.Mechanisms.WEBSQL)) {
return;
}
// WebSQL API does not expose deleting database.
// Dropping all tables indeed delete the database.
var db = new ydn.db.con.WebSql();
var schema = new ydn.db.schema.EditableDatabase();
goog.log.finer(db.logger, 'deleting websql database: ' + db_name);
var df = db.connect(db_name, schema);
var on_completed = function(t, e) {
goog.log.info(db.logger, 'all tables in ' + db_name + ' deleted.');
};
df.addCallback(function() {
db.doTransaction(function delete_tables(tx) {
/**
* @param {SQLTransaction} transaction transaction.
* @param {SQLResultSet} results results.
*/
var success_callback = function(transaction, results) {
if (!results || !results.rows) {
return;
}
var n = results.rows.length;
var del = 0;
for (var i = 0; i < n; i++) {
var info = /** @type {SqliteTableInfo} */ (results.rows.item(i));
if (info.name == '__WebKitDatabaseInfoTable__' ||
info.name == 'sqlite_sequence') {
continue;
}
del++;
goog.log.finest(db.logger, 'deleting table: ' + info.name);
tx.executeSql('DROP TABLE ' + info.name);
}
goog.log.finer(db.logger, del + ' tables deleted from "' + db_name + '"');
};
/**
* @param {SQLTransaction} tr transaction.
* @param {SQLError} error error.
*/
var error_callback = function(tr, error) {
if (ydn.db.con.WebSql.DEBUG) {
goog.global.console.log([tr, error]);
}
throw error;
};
var sql = 'SELECT * FROM sqlite_master WHERE type = "table"';
tx.executeSql(sql, [], success_callback, error_callback);
}, [], ydn.db.base.TransactionMode.READ_WRITE, on_completed);
});
df.addErrback(function() {
goog.log.warning(db.logger, 'Connecting ' + db_name + ' failed.');
});
};
ydn.db.databaseDeletors.push(ydn.db.con.WebSql.deleteDatabase);
/**
* @inheritDoc
*/
ydn.db.con.WebSql.prototype.onVersionChange = function(e) {};
if (goog.DEBUG) {
/**
* @override
*/
ydn.db.con.WebSql.prototype.toString = function() {
var s = this.sql_db_ ? ':' + this.sql_db_.version : '';
return 'WebSql:' + s;
};
}