From 42a98fbe616fb38efc57f93471e37886619edb31 Mon Sep 17 00:00:00 2001 From: "Christopher J. Brody" Date: Fri, 14 Apr 2017 02:12:55 +0200 Subject: [PATCH] selfTest simulate scenario in BUG litehelpers/Cordova-sqlite-storage#666 Includes string test and test of effects of location reload/change in this version branch --- CHANGES.md | 3 +- README.md | 3 +- SQLitePlugin.coffee.md | 120 +++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- plugin.xml | 2 +- www/SQLitePlugin.js | 111 +++++++++++++++++++++++++++++++++++--- 6 files changed, 226 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 74ecd0138..0f2f6bd46 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,8 @@ # Changes -### cordova-sqlite-legacy-express-core 1.0.0-pre1 +### cordova-sqlite-legacy-express-core 1.0.0-pre2 +- selfTest simulate scenario in BUG litehelpers/Cordova-sqlite-storage#666 (includes string test and test of effects of location reload/change in this version branch) - Drop engine constraints in package.json & plugin.xml (in this version branch) - Support macOS platform with builtin libsqlite3.dylib framework in this version branch diff --git a/README.md b/README.md index a6ec2d19b..6b88bd18c 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ Some other projects by [@brodybits](https://github.com/brodybits): - As described in [this posting](http://brodyspark.blogspot.com/2012/12/cordovaphonegap-sqlite-plugins-offer.html): - Keeps sqlite database in a user data location that is known; can be reconfigured (iOS/macOS platform version); and may be synchronized to iCloud (iOS version). - No 5MB maximum, more information at: http://www.sqlite.org/limits.html +- Also tested for multi-page applications with window location changes - This project is self-contained: no dependencies on other plugins such as cordova-plugin-file - Windows 10 UWP platform version available in [litehelpers / cordova-sqlite-ext](https://github.com/litehelpers/cordova-sqlite-ext) and Windows 8.1/Windows Phone 8.1/Windows 10 platform version available in [litehelpers / Cordova-sqlite-legacy](https://github.com/litehelpers/Cordova-sqlite-legacy) use the performant C++ [doo / SQLite3-WinRT](https://github.com/doo/SQLite3-WinRT) component. - [SQLCipher](https://www.zetetic.net/sqlcipher/) support for Android/iOS/macOS/Windows is available in: [litehelpers / Cordova-sqlcipher-adapter](https://github.com/litehelpers/Cordova-sqlcipher-adapter) @@ -124,6 +125,7 @@ Some other projects by [@brodybits](https://github.com/brodybits): ## Known issues +- Transaction problem after page change WITH POSSIBLE DATA LOSS ref: [litehelpers/Cordova-sqlite-storage#666](https://github.com/litehelpers/Cordova-sqlite-storage/issues/666) - iOS/macOS platform version does not support certain rapidly repeated open-and-close or open-and-delete test scenarios due to how the implementation handles background processing - As described below, auto-vacuum is NOT enabled by default. - INSERT statement that affects multiple rows (due to SELECT cause or using TRIGGER(s), for example) does not report proper rowsAffected on Android @@ -163,7 +165,6 @@ Some other projects by [@brodybits](https://github.com/brodybits): ## Further testing needed - Integration with PhoneGap developer app -- Multi-page apps - Use within [InAppBrowser](http://docs.phonegap.com/en/edge/cordova_inappbrowser_inappbrowser.md.html) - Use within an iframe (see [litehelpers/Cordova-sqlite-storage#368 (comment)](https://github.com/litehelpers/Cordova-sqlite-storage/issues/368#issuecomment-154046367)) - Actual behavior when using SAVEPOINT(s) diff --git a/SQLitePlugin.coffee.md b/SQLitePlugin.coffee.md index a5b187ffb..dc79cd172 100644 --- a/SQLitePlugin.coffee.md +++ b/SQLitePlugin.coffee.md @@ -27,6 +27,7 @@ # 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. + # NOTE: In case txLocks is renamed or replaced the selfTest has to be adapted as well. txLocks = {} ## utility functions: @@ -116,6 +117,7 @@ # Keep track of state of open db connections # XXX TBD this will be moved and renamed or # combined with txLocks. + # NOTE: In case txLocks is renamed or replaced the selfTest has to be adapted as well. SQLitePlugin::openDBs = {} SQLitePlugin::addTransaction = (t) -> @@ -690,11 +692,121 @@ start: (successcb, errorcb) -> SQLiteFactory.deleteDatabase {name: SelfTest.DBNAME, location: 'default'}, - (-> SelfTest.start2(successcb, errorcb)), - (-> SelfTest.start2(successcb, errorcb)) + (-> SelfTest.step1(successcb, errorcb)), + (-> SelfTest.step1(successcb, errorcb)) + return + + step1: (successcb, errorcb) -> + SQLiteFactory.openDatabase {name: SelfTest.DBNAME, location: 'default'}, (db) -> + check1 = false + db.transaction (tx) -> + tx.executeSql 'SELECT UPPER("Test") AS upperText', [], (ignored, resutSet) -> + if !resutSet.rows + return SelfTest.finishWithError errorcb, 'Missing resutSet.rows' + + if !resutSet.rows.length + return SelfTest.finishWithError errorcb, 'Missing resutSet.rows.length' + + if resutSet.rows.length isnt 1 + return SelfTest.finishWithError errorcb, + "Incorrect resutSet.rows.length value: #{resutSet.rows.length} (expected: 1)" + + if !resutSet.rows.item(0).upperText + return SelfTest.finishWithError errorcb, + 'Missing resutSet.rows.item(0).upperText' + + if resutSet.rows.item(0).upperText isnt 'TEST' + return SelfTest.finishWithError errorcb, + "Incorrect resutSet.rows.item(0).upperText value: #{resutSet.rows.item(0).upperText} (expected: 'TEST')" + + check1 = true + return + + , (ignored, tx_sql_err) -> + return SelfTest.finishWithError errorcb, "TX SQL error: #{tx_sql_err}" + + return + + , (tx_err) -> + return SelfTest.finishWithError errorcb, "TRANSACTION error: #{tx_err}" + + , () -> + # tx success: + if !check1 + return SelfTest.finishWithError errorcb, + 'Did not get expected upperText result data' + + # SIMULATE SCENARIO IN BUG litehelpers/Cordova-sqlite-storage#666: + db.executeSql 'BEGIN', null, (ignored) -> nextTick -> # (nextTick needed for Windows) + # DELETE INTERNAL STATE to simulate the effects of location refresh or change: + delete db.openDBs[SelfTest.DBNAME] + delete txLocks[SelfTest.DBNAME] + + # VERIFY INTERNAL STATE IS DELETED: + try + # EXPECTED TO THROW: + db.transaction (tx2) -> + tx2.executeSql 'SELECT 1' + return + # SHOULD NOT GET HERE: + return SelfTest.finishWithError errorcb, 'db.transaction DID NOT THROW after INTERNAL STATE IS DELETED' + catch e + # EXPECTED: + if !e + return SelfTest.finishWithError errorcb, 'Missing exception object' + SelfTest.step2 successcb, errorcb + return + return + return + + , (open_err) -> + SelfTest.finishWithError errorcb, "Open database error: #{open_err}" + return - start2: (successcb, errorcb) -> + step2: (successcb, errorcb) -> SQLiteFactory.openDatabase {name: SelfTest.DBNAME, location: 'default'}, (db) -> + db.transaction (tx) -> + tx.executeSql 'SELECT ? AS myResult', [null], (ignored, resutSet) -> + # Extra sql success callback ignored: + return + return + + , (txError) -> + # EXPECTED RESULT DUE TO BUG litehelpers/Cordova-sqlite-storage#666: + if !txError + return SelfTest.finishWithError errorcb, 'Missing txError object' + # second try should work: + db.transaction (tx2) -> + tx2.executeSql 'SELECT ? AS myResult', [null], (ignored, resutSet) -> + if !resutSet.rows + return SelfTest.finishWithError errorcb, 'Missing resutSet.rows' + if !resutSet.rows.length + return SelfTest.finishWithError errorcb, 'Missing resutSet.rows.length' + if resutSet.rows.length isnt 1 + return SelfTest.finishWithError errorcb, + SelfTest.step3 successcb, errorcb + return + return + , (tx2_err) -> + return SelfTest.finishWithError errorcb, "UNEXPECTED TRANSACTION ERROR: #{tx2_err}" + return + + , () -> + # TX SUCCESS POSSIBLE FOR Android (android.database) ONLY: + if /Android/.test(navigator.userAgent) and not /Windows /.test(navigator.userAgent) + return SelfTest.step3 successcb, errorcb + # OTHERWISE: + # TX SUCCESS NOT EXPECTED DUE TO BUG litehelpers/Cordova-sqlite-storage#666: + return SelfTest.finishWithError errorcb, 'UNEXPECTED SUCCESS ref: litehelpers/Cordova-sqlite-storage#666' + return + + , (open_err) -> + SelfTest.finishWithError errorcb, "Open database error: #{open_err}" + return + + step3: (successcb, errorcb) -> + SQLiteFactory.openDatabase {name: SelfTest.DBNAME, location: 'default'}, (db) -> + # FUTURE TBD TEST CRUD OPERATIONS (already fixed in a newer version branch) db.sqlBatch [ 'CREATE TABLE TestTable(TestColumn);' [ 'INSERT INTO TestTable (TestColumn) VALUES (?);', ['test-value'] ] @@ -764,11 +876,13 @@ , (open_err) -> SelfTest.finishWithError errorcb, "Open database error: #{open_err}" + return finishWithError: (errorcb, message) -> SQLiteFactory.deleteDatabase {name: SelfTest.DBNAME, location: 'default'}, -> errorcb newSQLError message , (err2)-> errorcb newSQLError "Cleanup error: #{err2} for error: #{message}" + return ## Exported API: diff --git a/package.json b/package.json index ca479ab82..be8847a4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-sqlite-legacy-express-core", - "version": "1.0.0-pre1", + "version": "1.0.0-pre2", "description": "Native interface to SQLite for PhoneGap/Cordova (legacy express core version)", "cordova": { "id": "cordova-sqlite-legacy-express-core", diff --git a/plugin.xml b/plugin.xml index e12377d61..e7b70ffd0 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="1.0.0-pre2"> Cordova sqlite storage plugin - express core version diff --git a/www/SQLitePlugin.js b/www/SQLitePlugin.js index 1431311b9..aafebbabd 100644 --- a/www/SQLitePlugin.js +++ b/www/SQLitePlugin.js @@ -315,7 +315,7 @@ }; SQLitePluginTransaction.prototype.start = function() { - var err, error1; + var err; try { this.fn(this); this.run(); @@ -402,7 +402,7 @@ tx = this; handlerFor = function(index, didSucceed) { return function(response) { - var err, error1; + var err; try { if (didSucceed) { tx.handleStatementSuccess(batchExecutes[index].success, response); @@ -606,17 +606,112 @@ SelfTest = { DBNAME: '___$$$___litehelpers___$$$___test___$$$___.db', start: function(successcb, errorcb) { - return SQLiteFactory.deleteDatabase({ + SQLiteFactory.deleteDatabase({ name: SelfTest.DBNAME, location: 'default' }, (function() { - return SelfTest.start2(successcb, errorcb); + return SelfTest.step1(successcb, errorcb); }), (function() { - return SelfTest.start2(successcb, errorcb); + return SelfTest.step1(successcb, errorcb); })); }, - start2: function(successcb, errorcb) { - return SQLiteFactory.openDatabase({ + step1: function(successcb, errorcb) { + SQLiteFactory.openDatabase({ + name: SelfTest.DBNAME, + location: 'default' + }, function(db) { + var check1; + check1 = false; + db.transaction(function(tx) { + tx.executeSql('SELECT UPPER("Test") AS upperText', [], function(ignored, resutSet) { + if (!resutSet.rows) { + return SelfTest.finishWithError(errorcb, 'Missing resutSet.rows'); + } + if (!resutSet.rows.length) { + return SelfTest.finishWithError(errorcb, 'Missing resutSet.rows.length'); + } + if (resutSet.rows.length !== 1) { + return SelfTest.finishWithError(errorcb, "Incorrect resutSet.rows.length value: " + resutSet.rows.length + " (expected: 1)"); + } + if (!resutSet.rows.item(0).upperText) { + return SelfTest.finishWithError(errorcb, 'Missing resutSet.rows.item(0).upperText'); + } + if (resutSet.rows.item(0).upperText !== 'TEST') { + return SelfTest.finishWithError(errorcb, "Incorrect resutSet.rows.item(0).upperText value: " + (resutSet.rows.item(0).upperText) + " (expected: 'TEST')"); + } + check1 = true; + }, function(ignored, tx_sql_err) { + return SelfTest.finishWithError(errorcb, "TX SQL error: " + tx_sql_err); + }); + }, function(tx_err) { + return SelfTest.finishWithError(errorcb, "TRANSACTION error: " + tx_err); + }, function() { + if (!check1) { + return SelfTest.finishWithError(errorcb, 'Did not get expected upperText result data'); + } + db.executeSql('BEGIN', null, function(ignored) { + return nextTick(function() { + var e; + delete db.openDBs[SelfTest.DBNAME]; + delete txLocks[SelfTest.DBNAME]; + try { + db.transaction(function(tx2) { + tx2.executeSql('SELECT 1'); + }); + return SelfTest.finishWithError(errorcb, 'db.transaction DID NOT THROW after INTERNAL STATE IS DELETED'); + } catch (error1) { + e = error1; + if (!e) { + return SelfTest.finishWithError(errorcb, 'Missing exception object'); + } + SelfTest.step2(successcb, errorcb); + } + }); + }); + }); + }, function(open_err) { + return SelfTest.finishWithError(errorcb, "Open database error: " + open_err); + }); + }, + step2: function(successcb, errorcb) { + SQLiteFactory.openDatabase({ + name: SelfTest.DBNAME, + location: 'default' + }, function(db) { + db.transaction(function(tx) { + tx.executeSql('SELECT ? AS myResult', [null], function(ignored, resutSet) {}); + }, function(txError) { + if (!txError) { + return SelfTest.finishWithError(errorcb, 'Missing txError object'); + } + db.transaction(function(tx2) { + tx2.executeSql('SELECT ? AS myResult', [null], function(ignored, resutSet) { + if (!resutSet.rows) { + return SelfTest.finishWithError(errorcb, 'Missing resutSet.rows'); + } + if (!resutSet.rows.length) { + return SelfTest.finishWithError(errorcb, 'Missing resutSet.rows.length'); + } + if (resutSet.rows.length !== 1) { + return SelfTest.finishWithError(errorcb); + } + SelfTest.step3(successcb, errorcb); + }); + }, function(tx2_err) { + return SelfTest.finishWithError(errorcb, "UNEXPECTED TRANSACTION ERROR: " + tx2_err); + }); + }, function() { + if (/Android/.test(navigator.userAgent) && !/Windows /.test(navigator.userAgent)) { + return SelfTest.step3(successcb, errorcb); + } + return SelfTest.finishWithError(errorcb, 'UNEXPECTED SUCCESS ref: litehelpers/Cordova-sqlite-storage#666'); + }); + }, function(open_err) { + return SelfTest.finishWithError(errorcb, "Open database error: " + open_err); + }); + }, + step3: function(successcb, errorcb) { + SQLiteFactory.openDatabase({ name: SelfTest.DBNAME, location: 'default' }, function(db) { @@ -691,7 +786,7 @@ }); }, finishWithError: function(errorcb, message) { - return SQLiteFactory.deleteDatabase({ + SQLiteFactory.deleteDatabase({ name: SelfTest.DBNAME, location: 'default' }, function() {