diff --git a/SQLitePlugin.coffee.md b/SQLitePlugin.coffee.md index f4fc20774..7d923efe2 100644 --- a/SQLitePlugin.coffee.md +++ b/SQLitePlugin.coffee.md @@ -320,6 +320,17 @@ License for common Javascript: MIT or Apache SQLitePluginTransaction::executeSql = (sql, values, success, error) -> + if @finalized + throw {message: 'InvalidStateError: DOM Exception 11: This transaction is already finalized. Transactions are committed after its success or failure handlers are called. If you are using a Promise to handle callbacks, be aware that implementations following the A+ standard adhere to run-to-completion semantics and so Promise resolution occurs on a subsequent tick and therefore after the transaction commits.', code: 11} + return + + @_executeSqlInternal(sql, values, success, error) + return + + # This method performs the actual execute but does not check for + # finalization since it is used to execute COMMIT and ROLLBACK. + SQLitePluginTransaction::_executeSqlInternal = (sql, values, success, error) -> + if @readOnly && READ_ONLY_REGEX.test(sql) @handleStatementFailure(error, {message: 'invalid sql for a read-only transaction'}) return @@ -461,7 +472,7 @@ License for common Javascript: MIT or Apache @finalized = true if @txlock - @executeSql "ROLLBACK", [], succeeded, failed + @_executeSqlInternal "ROLLBACK", [], succeeded, failed @run() else succeeded(tx) @@ -487,7 +498,7 @@ License for common Javascript: MIT or Apache @finalized = true if @txlock - @executeSql "COMMIT", [], succeeded, failed + @_executeSqlInternal "COMMIT", [], succeeded, failed @run() else succeeded(tx) diff --git a/test-www/www/index.html b/test-www/www/index.html index 105e3182d..89456a6db 100755 --- a/test-www/www/index.html +++ b/test-www/www/index.html @@ -550,6 +550,38 @@ }); }); }); + + test(suiteName + "executeSql fails outside transaction", function() { + withTestTable(function(db) { + expect(5); + ok(!!db, "db ok"); + var txg; + stop(2); + db.transaction(function(tx) { + ok(!!tx, "tx ok"); + txg = tx; + tx.executeSql("insert into test_table (data, data_num) VALUES (?,?)", ['test', null], function(tx, res) { + equal(res.rowsAffected, 1, 'row inserted'); + }); + start(1); + }, function(err) { + ok(false, err); + start(1); + }, function() { + // this simulates what would happen if a Promise ran on the next tick + // and invoked an execute on the transaction + try { + txg.executeSql("select count(*) as cnt from test_table", [], null, null); + ok(false, "executeSql should have thrown but continued instead"); + } catch(err) { + ok(!!err.message, "error had valid message"); + ok(/InvalidStateError|SQLTransaction/.test(err.message), + "execute must throw InvalidStateError; actual error: " + err.message); + } + start(1); + }); + }); + }); test(suiteName + "all columns should be included in result set (including 'null' columns)", function() { withTestTable(function(db) {