From b316586fea9b9adca362636248e7889433957b06 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Wed, 18 Mar 2015 22:45:02 +0100 Subject: [PATCH] Use per-db state to properly handle transactions requested on a database that is being opened (#184). Document related txLocks & openDBs map objects. Mark other bugs to be fixed (#204/#209/#210). --- SQLitePlugin.coffee.md | 70 ++++++++++++++++++++++++++++++++++-------- www/SQLitePlugin.js | 52 +++++++++++++++++++------------ 2 files changed, 89 insertions(+), 33 deletions(-) diff --git a/SQLitePlugin.coffee.md b/SQLitePlugin.coffee.md index cec9d100d..e7994593a 100644 --- a/SQLitePlugin.coffee.md +++ b/SQLitePlugin.coffee.md @@ -14,8 +14,19 @@ READ_ONLY_REGEX = /^\s*(?:drop|delete|insert|update|create)\s/i + # per-db state + DB_STATE_INIT = "INIT" + DB_STATE_OPEN = "OPEN" + ## global(s): + # per-db map of locking and queueing + # XXX NOTE: This is NOT cleaned up when a db is closed and/or deleted. + # If the record is simply removed when a db is closed or deleted, + # it will cause some test failures and may break large-scale + # applications that repeatedly open and close the database. + # [BUG #210] TODO: better to abort and clean up the pending transaction state. + # XXX TBD this will be renamed and include some more per-db state. txLocks = {} ## utility functions: @@ -65,6 +76,8 @@ ## SQLite plugin db-connection handle +#### NOTE: there can be multipe SQLitePlugin db-connection handles per open db. + #### SQLite plugin db connection handle object is defined by a constructor function and prototype member functions: SQLitePlugin = (openargs, openSuccess, openError) -> @@ -95,17 +108,21 @@ return SQLitePlugin::databaseFeatures = isSQLitePluginDatabase: true + + # Keep track of state of open db connections + # XXX TBD this will be moved and renamed or + # combined with txLocks. SQLitePlugin::openDBs = {} SQLitePlugin::addTransaction = (t) -> - if !txLocks[@dbname] txLocks[@dbname] = { queue: [] inProgress: false } txLocks[@dbname].queue.push t - @startNextTransaction() + if @dbname of @openDBs && @openDBs[@dbname] isnt DB_STATE_INIT + @startNextTransaction() return SQLitePlugin::transaction = (fn, error, success) -> @@ -129,25 +146,46 @@ nextTick () -> txLock = txLocks[self.dbname] - if txLock.queue.length > 0 && !txLock.inProgress + if !txLock + # XXX TBD TODO (BUG #210/??): abort all pending transactions with error cb + return + + else if txLock.queue.length > 0 && !txLock.inProgress txLock.inProgress = true txLock.queue.shift().start() return + return SQLitePlugin::open = (success, error) -> - onSuccess = () => success this - if @dbname of @openDBs - ### - for a re-open run onSuccess async so that the openDatabase return value - can be used in the success handler as an alternative to the handler's - db argument - ### - nextTick () -> onSuccess() + # for a re-open run the success cb async so that the openDatabase return value + # can be used in the success handler as an alternative to the handler's + # db argument + nextTick => + success @ + return + else - @openDBs[@dbname] = true - cordova.exec onSuccess, error, "SQLitePlugin", "open", [ @openargs ] + opensuccesscb = => + # NOTE: the db state is NOT stored (in @openDBs) if the db was closed or deleted. + + # XXX TODO [BUG #210]: + #if !@openDBs[@dbname] then call open error cb, and abort pending tx if any + + if @dbname of @openDBs + @openDBs[@dbname] = DB_STATE_OPEN + success @ + + txLock = txLocks[@dbname] + if !!txLock && txLock.queue.length > 0 && !txLock.inProgress + @startNextTransaction() + return + + # store initial DB state: + @openDBs[@dbname] = DB_STATE_INIT + + cordova.exec opensuccesscb, error, "SQLitePlugin", "open", [ @openargs ] return @@ -157,8 +195,10 @@ error newSQLError 'database cannot be closed while a transaction is in progress' return + # XXX [BUG #209] closing one db handle disables other handles to same db delete @openDBs[@dbname] + # XXX [BUG #210] TODO: when closing or deleting a db, abort any pending transactions (with error callback) cordova.exec success, error, "SQLitePlugin", "close", [ { path: @dbname } ] else @@ -167,6 +207,9 @@ return SQLitePlugin::executeSql = (statement, params, success, error) -> + # XXX TODO: better to capture the result, and report it once + # the transaction has completely finished. + # This would fix BUG #204 (cannot close db in db.executeSql() callback). mysuccess = (t, r) -> if !!success then success r myerror = (t, e) -> if !!error then error e @@ -463,6 +506,7 @@ dblocation = if !!first.location then dblocations[first.location] else null args.dblocation = dblocation || dblocations[0] + # XXX [BUG #210] TODO: when closing or deleting a db, abort any pending transactions (with error callback) delete SQLitePlugin::openDBs[args.path] cordova.exec success, error, "SQLitePlugin", "delete", [ args ] diff --git a/www/SQLitePlugin.js b/www/SQLitePlugin.js index 6450634c3..3d92c0c40 100644 --- a/www/SQLitePlugin.js +++ b/www/SQLitePlugin.js @@ -1,10 +1,14 @@ (function() { - var READ_ONLY_REGEX, SQLiteFactory, SQLitePlugin, SQLitePluginTransaction, argsArray, dblocations, newSQLError, nextTick, root, txLocks; + var DB_STATE_INIT, DB_STATE_OPEN, READ_ONLY_REGEX, SQLiteFactory, SQLitePlugin, SQLitePluginTransaction, argsArray, dblocations, newSQLError, nextTick, root, txLocks; root = this; READ_ONLY_REGEX = /^\s*(?:drop|delete|insert|update|create)\s/i; + DB_STATE_INIT = "INIT"; + + DB_STATE_OPEN = "OPEN"; + txLocks = {}; newSQLError = function(error, code) { @@ -91,7 +95,9 @@ }; } txLocks[this.dbname].queue.push(t); - this.startNextTransaction(); + if (this.dbname in this.openDBs && this.openDBs[this.dbname] !== DB_STATE_INIT) { + this.startNextTransaction(); + } }; SQLitePlugin.prototype.transaction = function(fn, error, success) { @@ -116,7 +122,9 @@ nextTick(function() { var txLock; txLock = txLocks[self.dbname]; - if (txLock.queue.length > 0 && !txLock.inProgress) { + if (!txLock) { + return; + } else if (txLock.queue.length > 0 && !txLock.inProgress) { txLock.inProgress = true; txLock.queue.shift().start(); } @@ -124,25 +132,29 @@ }; SQLitePlugin.prototype.open = function(success, error) { - var onSuccess; - onSuccess = (function(_this) { - return function() { - return success(_this); - }; - })(this); + var opensuccesscb; if (this.dbname in this.openDBs) { - - /* - for a re-open run onSuccess async so that the openDatabase return value - can be used in the success handler as an alternative to the handler's - db argument - */ - nextTick(function() { - return onSuccess(); - }); + nextTick((function(_this) { + return function() { + success(_this); + }; + })(this)); } else { - this.openDBs[this.dbname] = true; - cordova.exec(onSuccess, error, "SQLitePlugin", "open", [this.openargs]); + opensuccesscb = (function(_this) { + return function() { + var txLock; + if (_this.dbname in _this.openDBs) { + _this.openDBs[_this.dbname] = DB_STATE_OPEN; + } + success(_this); + txLock = txLocks[_this.dbname]; + if (!!txLock && txLock.queue.length > 0 && !txLock.inProgress) { + _this.startNextTransaction(); + } + }; + })(this); + this.openDBs[this.dbname] = DB_STATE_INIT; + cordova.exec(opensuccesscb, error, "SQLitePlugin", "open", [this.openargs]); } };