diff --git a/CHANGES.md b/CHANGES.md index 10c9f5b1a..2d161f58d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,8 @@ # Changes -### cordova-sqlite-legacy-express-core 1.0.0-pre2 +### cordova-sqlite-legacy-express-core 1.0.0-pre3 +- selfTest simulate scenario in BUG litehelpers/Cordova-sqlite-storage#666 (also includes string test and test of effects of location reload/change in this version branch, along with another internal check) - Drop engine constraints in package.json & plugin.xml (in this version branch) - Remove Lawnchair adapter from this version branch - Support macOS platform with builtin libsqlite3.dylib framework in this version branch diff --git a/README.md b/README.md index 205fde612..60afe5332 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Use the `location` or `iosDatabaseLocation` option in `sqlitePlugin.openDatabase - 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 platform 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) @@ -118,6 +119,7 @@ Use the `location` or `iosDatabaseLocation` option in `sqlitePlugin.openDatabase ## 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. - It is possible to request a SQL statement list such as "SELECT 1; SELECT 2" within a single SQL statement string, however the plugin will only execute the first statement and silently ignore the others ref: [litehelpers/Cordova-sqlite-storage#551](https://github.com/litehelpers/Cordova-sqlite-storage/issues/551) @@ -174,7 +176,6 @@ Issues fixed in some newer version branches: ## 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..4ff8e9694 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,127 @@ 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] + nextTick -> + # VERIFY INTERNAL STATE IS DELETED: + db.transaction (tx2) -> + tx2.executeSql 'SELECT 1' + return + , (tx_err) -> + # EXPECTED RESULT: + if !tx_err + return SelfTest.finishWithError errorcb, 'Missing error object' + SelfTest.step2 successcb, errorcb + return + , () -> + # NOT EXPECTED: + return SelfTest.finishWithError errorcb, 'Missing error object' + return + return + + return + return - start2: (successcb, errorcb) -> + , (open_err) -> + SelfTest.finishWithError errorcb, "Open database error: #{open_err}" + return + + step2: (successcb, errorcb) -> + SQLiteFactory.openDatabase {name: SelfTest.DBNAME, location: 'default'}, (db) -> + # TX FAILURE EXPECTED DUE TO BUG litehelpers/Cordova-sqlite-storage#666: + 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 + # NOTE: Windows 10 (UWP) mobile platform also shows "Android" in navigator.userAgent, + # filtered out here. + # FUTURE TBD android.database implementation should be fixed to report error in this case. + 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'] ] @@ -751,9 +869,19 @@ # CLEANUP & FINISH: db.close () -> SQLiteFactory.deleteDatabase {name: SelfTest.DBNAME, location: 'default'}, successcb, (cleanup_err)-> + # TBD IGNORE THIS ERROR on Windows (and WP8): + if /Windows /.test(navigator.userAgent) or /IEMobile/.test(navigator.userAgent) + console.log "IGNORE CLEANUP (DELETE) ERROR: #{JSON.stringify cleanup_err} (Windows/WP8)" + successcb() + return SelfTest.finishWithError errorcb, "Cleanup error: #{cleanup_err}" , (close_err) -> + # TBD IGNORE THIS ERROR on Windows (and WP8): + if /Windows /.test(navigator.userAgent) or /IEMobile/.test(navigator.userAgent) + console.log "IGNORE close ERROR: #{JSON.stringify close_err} (Windows/WP8)" + SQLiteFactory.deleteDatabase {name: SelfTest.DBNAME, location: 'default'}, successcb, successcb + return SelfTest.finishWithError errorcb, "close error: #{close_err}" , (select_err) -> @@ -764,11 +892,16 @@ , (open_err) -> SelfTest.finishWithError errorcb, "Open database error: #{open_err}" + return finishWithError: (errorcb, message) -> + console.log "selfTest ERROR with message: #{message}" SQLiteFactory.deleteDatabase {name: SelfTest.DBNAME, location: 'default'}, -> errorcb newSQLError message + # FUTURE TODO: return + # FUTURE TODO log err2 , (err2)-> errorcb newSQLError "Cleanup error: #{err2} for error: #{message}" + return ## Exported API: diff --git a/package.json b/package.json index be8847a4b..04e2417d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-sqlite-legacy-express-core", - "version": "1.0.0-pre2", + "version": "1.0.0-pre3", "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 5e027b26e..386d19d13 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="1.0.0-pre3"> Cordova sqlite storage plugin - legacy express core version diff --git a/www/SQLitePlugin.js b/www/SQLitePlugin.js index 1431311b9..1b6c7da23 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,111 @@ 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() { + delete db.openDBs[SelfTest.DBNAME]; + delete txLocks[SelfTest.DBNAME]; + nextTick(function() { + db.transaction(function(tx2) { + tx2.executeSql('SELECT 1'); + }, function(tx_err) { + if (!tx_err) { + return SelfTest.finishWithError(errorcb, 'Missing error object'); + } + SelfTest.step2(successcb, errorcb); + }, function() { + return SelfTest.finishWithError(errorcb, 'Missing error object'); + }); + }); + }); + }); + }); + }, 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) { @@ -673,9 +767,22 @@ name: SelfTest.DBNAME, location: 'default' }, successcb, function(cleanup_err) { + if (/Windows /.test(navigator.userAgent) || /IEMobile/.test(navigator.userAgent)) { + console.log("IGNORE CLEANUP (DELETE) ERROR: " + (JSON.stringify(cleanup_err)) + " (Windows/WP8)"); + successcb(); + return; + } return SelfTest.finishWithError(errorcb, "Cleanup error: " + cleanup_err); }); }, function(close_err) { + if (/Windows /.test(navigator.userAgent) || /IEMobile/.test(navigator.userAgent)) { + console.log("IGNORE close ERROR: " + (JSON.stringify(close_err)) + " (Windows/WP8)"); + SQLiteFactory.deleteDatabase({ + name: SelfTest.DBNAME, + location: 'default' + }, successcb, successcb); + return; + } return SelfTest.finishWithError(errorcb, "close error: " + close_err); }); }); @@ -691,7 +798,8 @@ }); }, finishWithError: function(errorcb, message) { - return SQLiteFactory.deleteDatabase({ + console.log("selfTest ERROR with message: " + message); + SQLiteFactory.deleteDatabase({ name: SelfTest.DBNAME, location: 'default' }, function() {