diff --git a/CHANGES.md b/CHANGES.md index 787512689..377e4ace7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,16 @@ # Changes +### cordova-sqlite-storage 3.2.0-OS3 +- Update cordova-sqlite-storage-dependencies dependency to version 2.0.0-OS1 [RNMT-4515](https://outsystemsrd.atlassian.net/browse/RNMT-4515) +- Revert hotfix that was applied to ensure the support for Android 11 [RNMT-4515](https://outsystemsrd.atlassian.net/browse/RNMT-4515) + +### cordova-sqlite-storage 3.2.0-OS2 +- An hotfix was applied to ensure the support for Android 11 [RNMT-4515](https://outsystemsrd.atlassian.net/browse/RNMT-4515) + +### cordova-sqlite-storage 3.2.0-OS1 +- Added support for async calls + +### cordova-sqlite-storage 3.2.0-OS +- Added support for multiple connections to the same database (enable some concurrency) #### cordova-sqlite-storage 3.2.1-dev diff --git a/README.md b/README.md index d75b7e2c3..42f1ec1d3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +# OutSystems' version of SQLite cordova adapter + +SQLite cordova interface based on *storage-master* branch of [litehelpers / Cordova-sqlite-storage](https://github.com/litehelpers/Cordova-sqlite-storage) customized to provide: + +- Support for multiple transactions running concurrently (using multiple connections) + +Below is an updated version of the source branch's README for further documentation, as of the last merge. + +# Original README information + # Cross-platform SQLite storage plugin for Cordova/PhoneGap - cordova-sqlite-storage plugin version Native SQLite component with API based on HTML5/[Web SQL (DRAFT) API](http://www.w3.org/TR/webdatabase/) for the following platforms: diff --git a/package.json b/package.json index c712acce1..5561ec85e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-sqlite-storage", - "version": "3.2.1-dev", + "version": "3.2.0", "description": "Native interface to SQLite for PhoneGap/Cordova - cordova-sqlite-storage plugin version", "cordova": { "id": "cordova-sqlite-storage", @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/xpbrew/cordova-sqlite-storage", "dependencies": { - "cordova-sqlite-storage-dependencies": "2.0.0" + "cordova-sqlite-storage-dependencies": "https://github.com/OutSystems/cordova-sqlite-storage-dependencies#2.0.0-OS1" }, "scripts": { "clean-spec": "rm -rf spec/[mnp]* && git cl spec/config.xml && git st", diff --git a/plugin.xml b/plugin.xml index c0978ed1b..2969ad6d1 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="3.2.0"> Cordova sqlite storage plugin - cordova-sqlite-storage plugin version diff --git a/src/android/io/sqlc/SQLitePlugin.java b/src/android/io/sqlc/SQLitePlugin.java index 69d4afa6b..215f3e80a 100755 --- a/src/android/io/sqlc/SQLitePlugin.java +++ b/src/android/io/sqlc/SQLitePlugin.java @@ -28,7 +28,7 @@ public class SQLitePlugin extends CordovaPlugin { /** - * Concurrent database runner map. + * Multiple database runner map (static). * * NOTE: no public static accessor to db (runner) map since it is not * expected to work properly with db threading. @@ -40,9 +40,9 @@ public class SQLitePlugin extends CordovaPlugin { * https://gist.github.com/AlainODea/1375759b8720a3f9f094 * * THANKS to @NeoLSN (Jason Yang/楊朝傑) for giving the pointer in: - * https://github.com/litehelpers/Cordova-sqlite-storage/issues/727 + * https://github.com/litehelpers/Cordova-sqlite-storage/issues/727 */ - private Map dbrmap = new ConcurrentHashMap(); + static Map dbrmap = new ConcurrentHashMap(); /** * NOTE: Using default constructor, no explicit constructor. @@ -101,9 +101,10 @@ private boolean executeAndPossiblyThrow(Action action, JSONArray args, CallbackC case close: o = args.getJSONObject(0); - dbname = o.getString("path"); + dbname = o.getString("dbname"); + // put request in the q to close the db - this.closeDatabase(dbname, cbc); + this.closeDatabase(getDBConnectionName(dbname, o), cbc); break; case delete: @@ -136,7 +137,7 @@ private boolean executeAndPossiblyThrow(Action action, JSONArray args, CallbackC // put db query in the queue to be executed in the db thread: DBQuery q = new DBQuery(queries, jsonparams, cbc); - DBRunner r = dbrmap.get(dbname); + DBRunner r = dbrmap.get(getDBConnectionName(dbname, dbargs)); if (r != null) { try { r.q.put(q); @@ -160,18 +161,18 @@ private boolean executeAndPossiblyThrow(Action action, JSONArray args, CallbackC @Override public void onDestroy() { while (!dbrmap.isEmpty()) { - String dbname = dbrmap.keySet().iterator().next(); + String dbConnectionName = dbrmap.keySet().iterator().next(); - this.closeDatabaseNow(dbname); + this.closeDatabaseNow(dbConnectionName); - DBRunner r = dbrmap.get(dbname); + DBRunner r = dbrmap.get(dbConnectionName); try { // stop the db runner thread: r.q.put(new DBQuery()); } catch(Exception e) { Log.e(SQLitePlugin.class.getSimpleName(), "INTERNAL PLUGIN CLEANUP ERROR: could not stop db thread due to exception", e); } - dbrmap.remove(dbname); + dbrmap.remove(dbConnectionName); } } @@ -179,15 +180,19 @@ public void onDestroy() { // LOCAL METHODS // -------------------------------------------------------------------------- - private void startDatabase(String dbname, JSONObject options, CallbackContext cbc) { - DBRunner r = dbrmap.get(dbname); + private void startDatabase(String dbname, JSONObject options, CallbackContext cbc) throws JSONException { + String dbConnectionName = getDBConnectionName(dbname, options); + + // TODO: is it an issue that we can orphan an existing thread? What should we do here? + // If we re-use the existing DBRunner it might be in the process of closing... + DBRunner r = dbrmap.get(dbConnectionName); if (r != null) { // NO LONGER EXPECTED due to BUG 666 workaround solution: cbc.error("INTERNAL ERROR: database already open for db name: " + dbname); } else { - r = new DBRunner(dbname, options, cbc); - dbrmap.put(dbname, r); + r = new DBRunner(dbname, dbConnectionName, options, cbc); + dbrmap.put(dbConnectionName, r); this.cordova.getThreadPool().execute(r); } } @@ -226,10 +231,10 @@ private SQLiteAndroidDatabase openDatabase(String dbname, CallbackContext cbc, b /** * Close a database (in another thread). * - * @param dbName The name of the database file + * @param dbConnectionName The name of the database connection runner */ - private void closeDatabase(String dbname, CallbackContext cbc) { - DBRunner r = dbrmap.get(dbname); + private void closeDatabase(String dbConnectionName, CallbackContext cbc) { + DBRunner r = dbrmap.get(dbConnectionName); if (r != null) { try { r.q.put(new DBQuery(false, cbc)); @@ -249,10 +254,10 @@ private void closeDatabase(String dbname, CallbackContext cbc) { /** * Close a database (in the current thread). * - * @param dbname The name of the database file + * @param dbConnectionName The name of the database connection runner */ - private void closeDatabaseNow(String dbname) { - DBRunner r = dbrmap.get(dbname); + private void closeDatabaseNow(String dbConnectionName) { + DBRunner r = dbrmap.get(dbConnectionName); if (r != null) { SQLiteAndroidDatabase mydb = r.mydb; @@ -263,7 +268,7 @@ private void closeDatabaseNow(String dbname) { } private void deleteDatabase(String dbname, CallbackContext cbc) { - DBRunner r = dbrmap.get(dbname); + DBRunner r = getRunnerForDb(dbname); if (r != null) { try { r.q.put(new DBQuery(true, cbc)); @@ -300,9 +305,19 @@ private boolean deleteDatabaseNow(String dbname) { return false; } } + + private static String getDBConnectionName(String dbname, JSONObject options) { + String connectionName = options.optString("connectionName"); + if (connectionName != null) { + return dbname + "_" + connectionName; + } else { + return dbname; + } + } private class DBRunner implements Runnable { final String dbname; + final String dbConnectionName; private boolean oldImpl; private boolean bugWorkaround; @@ -311,8 +326,9 @@ private class DBRunner implements Runnable { SQLiteAndroidDatabase mydb; - DBRunner(final String dbname, JSONObject options, CallbackContext cbc) { + DBRunner(final String dbname, final String dbConnectionName, JSONObject options, CallbackContext cbc) { this.dbname = dbname; + this.dbConnectionName = dbConnectionName; this.oldImpl = options.has("androidOldDatabaseImplementation"); Log.v(SQLitePlugin.class.getSimpleName(), "Android db implementation: built-in android.database.sqlite package"); this.bugWorkaround = this.oldImpl && options.has("androidBugWorkaround"); @@ -328,7 +344,7 @@ public void run() { this.mydb = openDatabase(dbname, this.openCbc, this.oldImpl); } catch (Exception e) { Log.e(SQLitePlugin.class.getSimpleName(), "unexpected error, stopping db thread", e); - dbrmap.remove(dbname); + dbrmap.remove(dbConnectionName); return; } @@ -351,24 +367,14 @@ public void run() { if (dbq != null && dbq.close) { try { - closeDatabaseNow(dbname); + closeDatabaseNow(dbConnectionName); - dbrmap.remove(dbname); // (should) remove ourself + dbrmap.remove(dbConnectionName); // (should) remove ourself if (!dbq.delete) { dbq.cbc.success(); } else { - try { - boolean deleteResult = deleteDatabaseNow(dbname); - if (deleteResult) { - dbq.cbc.success(); - } else { - dbq.cbc.error("couldn't delete database"); - } - } catch (Exception e) { - Log.e(SQLitePlugin.class.getSimpleName(), "couldn't delete database", e); - dbq.cbc.error("couldn't delete database: " + e); - } + deleteDatabase(dbname, dbq.cbc); } } catch (Exception e) { Log.e(SQLitePlugin.class.getSimpleName(), "couldn't close database", e); @@ -380,6 +386,15 @@ public void run() { } } + private DBRunner getRunnerForDb(String dbName) { + for (DBRunner runner : dbrmap.values()) { + if (runner.dbname.equals(dbName)) { + return runner; + } + } + return null; + } + private final class DBQuery { // XXX TODO replace with DBRunner action enum: final boolean stop; diff --git a/src/ios/SQLitePlugin.m b/src/ios/SQLitePlugin.m index dbe4e9580..13d2bff6f 100755 --- a/src/ios/SQLitePlugin.m +++ b/src/ios/SQLitePlugin.m @@ -129,7 +129,8 @@ -(void)openNow: (CDVInvokedUrlCommand*)command [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId]; return; } else { - NSValue *dbPointer = [openDBs objectForKey:dbfilename]; + NSString *dbConnectionName = [SQLitePlugin getDbConnectionNameForDb: dbfilename withOptions: options]; + NSValue *dbPointer = [openDBs objectForKey: dbConnectionName]; if (dbPointer != NULL) { // NO LONGER EXPECTED due to BUG 666 workaround solution: @@ -162,7 +163,7 @@ -(void)openNow: (CDVInvokedUrlCommand*)command // Attempt to read the SQLite master table [to support SQLCipher version]: if(sqlite3_exec(db, (const char*)"SELECT count(*) FROM sqlite_master;", NULL, NULL, NULL) == SQLITE_OK) { dbPointer = [NSValue valueWithPointer:db]; - [openDBs setObject: dbPointer forKey: dbfilename]; + [openDBs setObject: dbPointer forKey: dbConnectionName]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Database opened"]; } else { pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Unable to open DB with key"]; @@ -189,14 +190,15 @@ -(void)closeNow: (CDVInvokedUrlCommand*)command CDVPluginResult* pluginResult = nil; NSMutableDictionary *options = [command.arguments objectAtIndex:0]; - NSString *dbFileName = [options objectForKey:@"path"]; + NSString *dbFileName = [options objectForKey:@"dbname"]; if (dbFileName == NULL) { // Should not happen: DLog(@"No db name specified for close"); pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: You must specify database path"]; } else { - NSValue *val = [openDBs objectForKey:dbFileName]; + NSString *dbConnectionName = [SQLitePlugin getDbConnectionNameForDb: dbFileName withOptions: options]; + NSValue *val = [openDBs objectForKey:dbConnectionName]; sqlite3 *db = [val pointerValue]; if (db == NULL) { @@ -207,7 +209,7 @@ -(void)closeNow: (CDVInvokedUrlCommand*)command else { DLog(@"close db name: %@", dbFileName); sqlite3_close (db); - [openDBs removeObjectForKey:dbFileName]; + [openDBs removeObjectForKey:dbConnectionName]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"DB closed"]; } } @@ -242,7 +244,16 @@ -(void)deleteNow: (CDVInvokedUrlCommand*)command if ([[NSFileManager defaultManager]fileExistsAtPath:dbPath]) { DLog(@"delete full db path: %@", dbPath); [[NSFileManager defaultManager]removeItemAtPath:dbPath error:nil]; - [openDBs removeObjectForKey:dbFileName]; + + // Remove all connections associated to this DB + for (NSString *key in [openDBs allKeys]) { + NSValue *dbPointer = [openDBs objectForKey:key]; + const char *obtainedPath = sqlite3_db_filename([dbPointer pointerValue], [@"main" UTF8String]); + + if ([dbPath isEqualToString:[NSString stringWithUTF8String:obtainedPath]]) { + [openDBs removeObjectForKey:key]; + } + } pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"DB deleted"]; } else { DLog(@"delete: db was not found: %@", dbPath); @@ -314,7 +325,8 @@ -(CDVPluginResult*) executeSqlWithDict: (NSMutableDictionary*)options andArgs: ( NSMutableArray *params = [options objectForKey:@"params"]; // optional - NSValue *dbPointer = [openDBs objectForKey:dbFileName]; + NSString *dbConnectionName = [SQLitePlugin getDbConnectionNameForDb: dbFileName withOptions: dbargs]; + NSValue *dbPointer = [openDBs objectForKey: dbConnectionName]; if (dbPointer == NULL) { return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: No such database, you must open it first"]; } @@ -527,4 +539,28 @@ +(int)mapSQLiteErrorCode:(int)code } } ++(NSString*)getDbConnectionNameForDb:(NSString*)dbName + withOptions:(NSMutableDictionary*)options +{ + NSString *connectionName = [options objectForKey:@"connectionName"]; + if (connectionName == NULL) { + return dbName; + } else { + return [[dbName stringByAppendingString:@"_"] stringByAppendingString:connectionName]; + } +} + +#ifdef READ_BLOB_AS_BASE64 ++(NSString*)getBlobAsBase64String:(const char*)blob_chars + withLength:(int)blob_length +{ + // THANKS for guidance: http://stackoverflow.com/a/8354941/1283667 + NSData * data = [NSData dataWithBytes: (const void *)blob_chars length: blob_length]; + + // THANKS for guidance: + // https://github.com/apache/cordova-ios/blob/master/guides/API%20changes%20in%204.0.md#nsdatabase64h-removed + return [data base64EncodedStringWithOptions:0]; +} +#endif + @end /* vim: set expandtab : */ diff --git a/www/SQLitePlugin.js b/www/SQLitePlugin.js index 8b48865d8..69989aed7 100644 --- a/www/SQLitePlugin.js +++ b/www/SQLitePlugin.js @@ -85,7 +85,8 @@ }; SQLitePlugin.prototype.databaseFeatures = { - isSQLitePluginDatabase: true + isSQLitePluginDatabase: true, + hasPromiseSupport: true }; SQLitePlugin.prototype.openDBs = {}; @@ -94,11 +95,12 @@ if (!txLocks[this.dbname]) { txLocks[this.dbname] = { queue: [], + availableConnections: this.openDBs[this.dbname].connections, inProgress: false }; } txLocks[this.dbname].queue.push(t); - if (this.dbname in this.openDBs && this.openDBs[this.dbname] !== DB_STATE_INIT) { + if (this.dbname in this.openDBs && this.openDBs[this.dbname].state !== DB_STATE_INIT) { this.startNextTransaction(); } else { if (this.dbname in this.openDBs) { @@ -126,25 +128,36 @@ }; SQLitePlugin.prototype.startNextTransaction = function() { - var self; - self = this; - nextTick((function(_this) { - return function() { + var _this = this; + nextTick(function() { var txLock; - if (!(_this.dbname in _this.openDBs) || _this.openDBs[_this.dbname] !== DB_STATE_OPEN) { + if (!(_this.dbname in _this.openDBs) || _this.openDBs[_this.dbname].state !== DB_STATE_OPEN) { console.log('cannot start next transaction: database not open'); return; } - txLock = txLocks[self.dbname]; + txLock = txLocks[_this.dbname]; if (!txLock) { console.log('cannot start next transaction: database connection is lost'); return; - } else if (txLock.queue.length > 0 && !txLock.inProgress) { - txLock.inProgress = true; - txLock.queue.shift().start(); + } else { + var i = 0; + while (i < txLock.queue.length && txLock.availableConnections.length > 0) { + if (txLock.queue[i].readOnly) { + // if it's read-only, start away + var connectionName = txLock.availableConnections.shift(); + txLock.queue.splice(i, 1)[0].start(connectionName); + } else if (!txLock.inProgress) { + // if it's writeable, only start if no writeable started + var connectionName = txLock.availableConnections.shift(); + txLock.inProgress = true; + txLock.queue.splice(i, 1)[0].start(connectionName); + } else { + // otherwise don't start the transaction yet + i++; + } + } } - }; - })(this)); + }); }; SQLitePlugin.prototype.abortAllPendingTransactions = function() { @@ -162,56 +175,112 @@ }; SQLitePlugin.prototype.open = function(success, error) { - var openerrorcb, opensuccesscb, step2; + var openerrorcb, opensuccesscb; + var _this = this; + if (this.dbname in this.openDBs) { console.log('database already open: ' + this.dbname); - nextTick((function(_this) { - return function() { + nextTick(function() { success(_this); - }; - })(this)); + }); } else { + + var maxConnections = 4, okConnections = 0; + var failed = false; + var successOnce = function(sqlitePlugin) { + okConnections++; + if (!failed && okConnections == maxConnections) { + success(sqlitePlugin); + } + } + console.log('OPEN database: ' + this.dbname); - opensuccesscb = (function(_this) { - return function() { - var txLock; - console.log('OPEN database: ' + _this.dbname + ' - OK'); - if (!_this.openDBs[_this.dbname]) { - console.log('database was closed during open operation'); - } - if (_this.dbname in _this.openDBs) { - _this.openDBs[_this.dbname] = DB_STATE_OPEN; - } - if (!!success) { - success(_this); - } - txLock = txLocks[_this.dbname]; - if (!!txLock && txLock.queue.length > 0 && !txLock.inProgress) { - _this.startNextTransaction(); - } + opensuccesscb = function(connectionName) { + return function(continueCb) { + var txLock; + console.log('OPEN database: ' + _this.dbname + ', connection: ' + connectionName + ' - OK'); + if (!_this.openDBs[_this.dbname]) { + console.log('database was closed during open operation'); + } + if (_this.dbname in _this.openDBs) { + _this.openDBs[_this.dbname].state = DB_STATE_OPEN; + _this.openDBs[_this.dbname].connections.push(connectionName); + } + + var callSuccessAndStartTransaction = function () { + continueCb(); + if (!!success) { + successOnce(_this); + } + txLock = txLocks[_this.dbname]; + if (!!txLock && txLock.queue.length > 0) { + _this.startNextTransaction(); + } + }; + + if (maxConnections > 1) { + // Otherwise readers will be blocked by writers + _this.executeSql('PRAGMA journal_mode = WAL;', [], callSuccessAndStartTransaction); + } else { + callSuccessAndStartTransaction(); + } + + }; }; - })(this); - openerrorcb = (function(_this) { - return function() { + openerrorcb = function() { console.log('OPEN database: ' + _this.dbname + ' FAILED, aborting any pending transactions'); - if (!!error) { + if (!!error && !failed) { error(newSQLError('Could not open database')); } + failed = true; delete _this.openDBs[_this.dbname]; _this.abortAllPendingTransactions(); }; - })(this); - this.openDBs[this.dbname] = DB_STATE_INIT; - step2 = (function(_this) { - return function() { - cordova.exec(opensuccesscb, openerrorcb, "SQLitePlugin", "open", [_this.openargs]); - }; - })(this); - cordova.exec(step2, step2, 'SQLitePlugin', 'close', [ - { - path: this.dbname + this.openDBs[this.dbname] = { + state: DB_STATE_INIT, + connections: [] + }; + + var copyOptions = function() { + var options = {}; + var optionNames = Object.keys(_this.openargs); + for (var i = 0; i < optionNames.length; i++) { + options[optionNames[i]] = _this.openargs[optionNames[i]]; } - ]); + return options; + } + + var openNext = function openNext(connNumber) { + + connNumber++; + + var options = copyOptions(); + var connectionName = 'connection' + connNumber; + var onSuccess = opensuccesscb(connectionName); + options['connectionName'] = connectionName; + + // close each connection before attempting to open it + var step2 = (function(_this) { + return function() { + cordova.exec(function() { + onSuccess(function() { + if (connNumber < maxConnections) { + openNext(connNumber); + } + }); + }, openerrorcb, "SQLitePlugin", "open", [options]); + }; + })(_this); + + cordova.exec(step2, step2, 'SQLitePlugin', 'close', [ + { + dbname: _this.dbname, + connectionName: connectionName + } + ]); + }; + + openNext(0); } }; @@ -222,18 +291,42 @@ error(newSQLError('database cannot be closed while a transaction is in progress')); return; } + + var connections = this.openDBs[this.dbname].connections; + console.log('CLOSE database: ' + this.dbname); delete this.openDBs[this.dbname]; + if (txLocks[this.dbname]) { console.log('closing db with transaction queue length: ' + txLocks[this.dbname].queue.length); } else { console.log('closing db with no transaction lock state'); } - cordova.exec(success, error, "SQLitePlugin", "close", [ - { - path: this.dbname + + var closedConnections = 0; + var closeConnectionSuccessCb = function () { + closedConnections++; + if (closedConnections == connections.length) { + return success(); } - ]); + }; + + var closeErrorOccurred = false; + var closeConnectionErrorCb = function (e) { + if (!closeErrorOccurred) { + closeErrorOccurred = true; + return error(e); + } + }; + + for (var i = 0; i < connections.length; i++) { + cordova.exec(closeConnectionSuccessCb, closeConnectionErrorCb, "SQLitePlugin", "close", [ + { + dbname: this.dbname, + connectionName: connections[i] + } + ]); + } } else { console.log('cannot close: database is not open'); if (error) { @@ -324,21 +417,33 @@ } }; - SQLitePluginTransaction.prototype.start = function() { + SQLitePluginTransaction.prototype.start = function(connectionName) { var err; try { + this.connectionName = connectionName; this.fn(this); this.run(); } catch (error1) { err = error1; - txLocks[this.db.dbname].inProgress = false; - this.db.startNextTransaction(); + this.releaseConnection(); if (this.error) { this.error(newSQLError(err)); } } }; + SQLitePluginTransaction.prototype.releaseConnection = function() { + var txLock = txLocks[this.db.dbname]; + if (!this.readOnly) { + txLock.inProgress = false; + } + + txLock.availableConnections.push(this.connectionName); + this.connectionName = null; + + this.db.startNextTransaction(); + }; + SQLitePluginTransaction.prototype.executeSql = function(sql, values, success, error) { if (this.finalized) { throw { @@ -426,16 +531,18 @@ txFailure = newSQLError(err); } } - if (--waiting === 0) { - if (txFailure) { - tx.executes = []; - tx.abort(txFailure); - } else if (tx.executes.length > 0) { - tx.run(); - } else { - tx.finish(); + setTimeout(function() { + if (--waiting === 0) { + if (txFailure) { + tx.executes = []; + tx.abort(txFailure); + } else if (tx.executes.length > 0) { + tx.run(); + } else { + tx.finish(); + } } - } + }, 0); }; }; mycbmap = {}; @@ -469,7 +576,8 @@ cordova.exec(mycb, null, "SQLitePlugin", "backgroundExecuteSqlBatch", [ { dbargs: { - dbname: this.db.dbname + dbname: this.db.dbname, + connectionName: this.connectionName }, executes: tropts } @@ -483,15 +591,13 @@ } tx = this; succeeded = function(tx) { - txLocks[tx.db.dbname].inProgress = false; - tx.db.startNextTransaction(); + tx.releaseConnection(); if (tx.error && typeof tx.error === 'function') { tx.error(txFailure); } }; failed = function(tx, err) { - txLocks[tx.db.dbname].inProgress = false; - tx.db.startNextTransaction(); + tx.releaseConnection(); if (tx.error && typeof tx.error === 'function') { tx.error(newSQLError('error while trying to roll back: ' + err.message, err.code)); } @@ -512,15 +618,13 @@ } tx = this; succeeded = function(tx) { - txLocks[tx.db.dbname].inProgress = false; - tx.db.startNextTransaction(); + tx.releaseConnection(); if (tx.success && typeof tx.success === 'function') { tx.success(); } }; failed = function(tx, err) { - txLocks[tx.db.dbname].inProgress = false; - tx.db.startNextTransaction(); + tx.releaseConnection(); if (tx.error && typeof tx.error === 'function') { tx.error(newSQLError('error while trying to commit: ' + err.message, err.code)); }