diff --git a/README.md b/README.md index 61f4ea29c..2558024ef 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ License for Android and Windows versions: MIT or Apache 2.0 License for iOS version: MIT only -|Android Circle-CI (**full** suite)|iOS Travis-CI (*very* limited suite)| +|Android Circle-CI (**full** suite)|iOS Travis-CI (_partial_ suite)| |-----------------------|----------------------| |[![Circle CI](https://circleci.com/gh/litehelpers/Cordova-sqlite-storage.svg?style=svg)](https://circleci.com/gh/litehelpers/Cordova-sqlite-storage)|[![Build Status](https://travis-ci.org/litehelpers/Cordova-sqlite-storage.svg)](https://travis-ci.org/litehelpers/Cordova-sqlite-storage)| @@ -124,6 +124,8 @@ See the [Sample section](#sample) for a sample with a more detailed explanation. - Issue with UNICODE `\u0000` character (same as `\0`) - No background processing - INCORRECT error code (0) and INCONSISTENT error message (missing actual error info) in error callbacks ref: [litehelpers/Cordova-sqlite-storage#539](https://github.com/litehelpers/Cordova-sqlite-storage/issues/539) + - Issue with emojis and other 4-octet UTF-8 characters (apparently not stored correctly) + - Not possible to read BLOB column values - It is **not** possible to use this plugin with the default "Any CPU" target. A specific target CPU type **must** be specified when building an app with this plugin. - FTS3, FTS4, and R-Tree support is tested working OK in this version (for all target platforms in this version branch Android/iOS/Windows) - Android is supported back to SDK 10 (a.k.a. Gingerbread, Android 2.3.3); support for older versions is available upon request. @@ -317,7 +319,7 @@ As "strongly recommended" by [Web SQL Database API 8.5 SQL injection](https://ww - This plugin does *not* support the synchronous Web SQL interfaces. - 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) - It is possible to insert multiple rows like: `transaction.executeSql('INSERT INTO MyTable VALUES (?,?),(?,?)', ['Alice', 101, 'Betty', 102]);` which was not supported by SQLite 3.6.19 as referenced by [Web SQL API section 5](https://www.w3.org/TR/webdatabase/#web-sql). The iOS WebKit Web SQL implementation seems to support this as well. -- Unlike the HTML5/[Web SQL API](http://www.w3.org/TR/webdatabase/) this plugin handles executeSql calls with too few parameters without error reporting. +- Unlike the HTML5/[Web SQL API](http://www.w3.org/TR/webdatabase/) this plugin handles executeSql calls with too few parameters without error reporting. In case of too many parameters this plugin reports error code 0 (SQLError.UNKNOWN_ERR) while Android/iOS (WebKit) Web SQL correctly reports error code 5 (SQLError.SYNTAX_ERR) ref: https://www.w3.org/TR/webdatabase/#dom-sqlexception-code-syntax - Known issue(s) with handling of `Infinity` values (positive or negative) and certain ASCII/UNICODE characters as described below. - Boolean `true` and `false` values are handled by converting them to the "true" and "false" TEXT string values, same as WebKit Web SQL on Android and iOS. This does not seem to be 100% correct as discussed in: [litehelpers/Cordova-sqlite-storage#545](https://github.com/litehelpers/Cordova-sqlite-storage/issues/545) - Certain errors such as CREATE VIRTUAL TABLE USING bogus module are reported with error code 5 (SQLError.SYNTAX_ERR) on Android and iOS. This happens to be the case for WebKit Web SQL (Android/iOS). @@ -325,6 +327,7 @@ As "strongly recommended" by [Web SQL Database API 8.5 SQL injection](https://ww - Issues with error code on Windows as well as Android with the `androidDatabaseImplementation: 2` setting described below. - In case of an issue that causes an API function to throw an exception (Android/iOS WebKit) Web SQL includes includes a code member with value of 0 (SQLError.UNKNOWN_ERR) in the exception while the plugin includes no such code member. - This plugin supports some non-standard features as documented below. +- Results of SELECT with BLOB data such as `SELECT LOWER(X'40414243') AS myresult`, `SELECT X'40414243' AS myresult`, or reading data stored by `INSERT INTO MyTable VALUES (X'40414243')` are not consistent on Android in case the built-in Android database is used (using the `androidDatabaseImplementation: 2` setting in `window.sqlitePlugin.openDatabase`) or Windows. (These work with Android/iOS WebKit Web SQL and have been supported by SQLite for a number of years.) ### Security of deleted data @@ -343,6 +346,7 @@ See **Security of sensitive data** in the [Security](#security) section above. - iOS 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. +- The Android and Windows versions do not always handle four-byte UTF-8 characters emoji characters such as `\u1F603` (SMILING FACE, MOUTH OPEN) correctly ref: [litehelpers/Cordova-sqlite-storage#564](https://github.com/litehelpers/Cordova-sqlite-storage/issues/564). It is sometimes possible to store and retrieve such characters but certain operations hex conversions do not work properly with the default [Android-sqlite-connector](https://github.com/liteglue/Android-sqlite-connector) database implementation. It is suspected that such characters would be stored incorrectly in the Android and Windows versions. This is not an issue in case the built-in Android database is used (using the `androidDatabaseImplementation: 2` setting in `window.sqlitePlugin.openDatabase`) - INSERT statement that affects multiple rows (due to SELECT cause or using TRIGGER(s), for example) does not report proper rowsAffected on Android in case the built-in Android database used (using the `androidDatabaseImplementation` option in `window.sqlitePlugin.openDatabase`) - Memory issue observed when adding a large number of records due to the JSON implementation which is improved in [litehelpers / Cordova-sqlite-evcore-extbuild-free](https://github.com/litehelpers/Cordova-sqlite-evcore-extbuild-free) (available with GPL or commercial license options) - Infinity (positive or negative) values are not supported (known to be broken on Android, may cause a crash on iOS ref: [litehelpers/Cordova-sqlite-storage#405](https://github.com/litehelpers/Cordova-sqlite-storage/issues/405)) @@ -402,7 +406,7 @@ Some more limitations are tracked in the [open Cordova-sqlite-storage documentat - Delete an open database inside a statement or transaction callback. - WITH clause (not supported by some older sqlite3 versions) - _Handling of invalid transaction and transaction.executeSql arguments_ -- _Emojis and other 4-octet UTF-8 characters_ +- More emojis and other 4-octet UTF-8 characters diff --git a/spec/www/spec/db-tx-string-test.js b/spec/www/spec/db-tx-string-test.js index 052eddb5d..b6c08fe1a 100755 --- a/spec/www/spec/db-tx-string-test.js +++ b/spec/www/spec/db-tx-string-test.js @@ -7,8 +7,6 @@ var DEFAULT_SIZE = 5000000; // max to avoid popup in safari/ios var isWP8 = /IEMobile/.test(navigator.userAgent); // Matches WP(7/8/8.1) var isWindows = /Windows /.test(navigator.userAgent); // Windows (8.1) var isAndroid = !isWindows && /Android/.test(navigator.userAgent); -var isIE = isWindows || isWP8; -var isWebKit = !isIE; // TBD [Android or iOS] // NOTE: In the common storage-master branch there is no difference between the // default implementation and implementation #2. But the test will also apply @@ -49,8 +47,10 @@ var mytests = function() { } } - it(suiteName + 'US-ASCII String manipulation test', function(done) { - var db = openDatabase("ASCII-string-test.db", "1.0", "Demo", DEFAULT_SIZE); + describe(suiteName + 'basic string binding/manipulation tests', function() { + + it(suiteName + 'Inline US-ASCII String manipulation test with empty ([]) parameter list', function(done) { + var db = openDatabase("Inline-US-ASCII-string-test-with-empty-parameter-list.db", "1.0", "Demo", DEFAULT_SIZE); expect(db).toBeDefined(); @@ -66,6 +66,23 @@ var mytests = function() { }); }, MYTIMEOUT); + it(suiteName + 'Inline US-ASCII String manipulation test with null parameter list', function(done) { + var db = openDatabase("Inline-US-ASCII-string-test-with-null-parameter-list.db", "1.0", "Demo", DEFAULT_SIZE); + + expect(db).toBeDefined(); + + db.transaction(function(tx) { + expect(tx).toBeDefined(); + + tx.executeSql("SELECT UPPER('Some US-ASCII text') AS uppertext", null, function(tx, res) { + expect(res.rows.item(0).uppertext).toBe("SOME US-ASCII TEXT"); + + // Close (plugin only) & finish: + (isWebSql) ? done() : db.close(done, done); + }); + }); + }, MYTIMEOUT); + it(suiteName + 'US-ASCII String binding test', function(done) { var db = openDatabase("ASCII-string-binding-test.db", "1.0", "Demo", DEFAULT_SIZE); @@ -95,9 +112,9 @@ var mytests = function() { }, MYTIMEOUT); it(suiteName + 'String encoding test with UNICODE \\u0000', function (done) { - if (isWindows) pending('BROKEN for Windows'); // XXX if (isWP8) pending('BROKEN for WP(8)'); // [BUG #202] UNICODE characters not working with WP(8) - if (isAndroid && !isWebSql && !isImpl2) pending('BROKEN for Android (default sqlite-connector version)'); // XXX + if (isWindows) pending('BROKEN for Windows'); // [FUTURE TBD, already documented] + if (!isWebSql && isAndroid && !isImpl2) pending('BROKEN for Android (default sqlite-connector version)'); // [FUTURE TBD (documented)] var dbName = "Unicode-hex-test"; var db = openDatabase(dbName, "1.0", "Demo", DEFAULT_SIZE); @@ -231,6 +248,92 @@ var mytests = function() { }); }, MYTIMEOUT); + // NOTE On Windows [using a customized version of the SQLite3-WinRT component] and + // default Android-sqlite-connector implementations it is possible to manipulate, + // store, and retrieve a text string with 4-octet UTF-8 characters such as emojis. + // However HEX manipulations do not work the same as Android/iOS WebKit Web SQL, + // iOS plugin, or Android plugin with androidDatabaseImplementation : 2 setting. + // This linkely indicates that such characters are stored differently + // due to UTF-8 string handling limitations of Android-sqlite-connector + // and Windows (SQLite3-WinRT) versions. ref: litehelpers/Cordova-sqlite-storage#564 + + it(suiteName + 'Inline emoji string manipulation test: SELECT UPPER("a\\uD83D\\uDE03.") [\\u1F603 SMILING FACE (MOUTH OPEN)]', function(done) { + var db = openDatabase("Inline-emoji-hex-test.db", "1.0", "Demo", DEFAULT_SIZE); + expect(db).toBeDefined(); + + db.transaction(function(tx) { + expect(tx).toBeDefined(); + + tx.executeSql('SELECT UPPER("a\uD83D\uDE03.") AS uppertext', [], function(tx, res) { + expect(res.rows.item(0).uppertext).toBe('A\uD83D\uDE03.'); + + // Close (plugin only) & finish: + (isWebSql) ? done() : db.close(done, done); + }); + }); + }, MYTIMEOUT); + + it(suiteName + 'Inline emoji HEX test: SELECT HEX("@\\uD83D\\uDE03!") [\\u1F603 SMILING FACE (MOUTH OPEN)]', function(done) { + if (isWP8) pending('BROKEN for WP8'); + if (isAndroid && !isWebSql && !isImpl2) pending('BROKEN for Android (default sqlite-connector version)'); + if (isWindows) pending('BROKEN for Windows'); + + var db = openDatabase("Inline-emoji-hex-test.db", "1.0", "Demo", DEFAULT_SIZE); + expect(db).toBeDefined(); + + db.transaction(function(tx) { + expect(tx).toBeDefined(); + + tx.executeSql('SELECT HEX("@\uD83D\uDE03!") AS hexvalue', [], function(tx, res) { + expect(res.rows.item(0).hexvalue).toBe('40F09F988321'); + + // Close (plugin only) & finish: + (isWebSql) ? done() : db.close(done, done); + }); + }); + }, MYTIMEOUT); + + it(suiteName + "Inline BLOB with emoji string manipulation test: SELECT LOWER(X'41F09F9883') [A\uD83D\uDE03] [\\u1F603 SMILING FACE (MOUTH OPEN)]", function(done) { + if (isWP8) pending('BROKEN for WP8'); // [CRASH with uncaught exception] + if (isAndroid && !isWebSql && !isImpl2) pending('BROKEN for Android (default sqlite-connector version)'); + if (isWindows) pending('BROKEN for Windows'); + + var db = openDatabase("Inline-emoji-select-lower-result-test.db", "1.0", "Demo", DEFAULT_SIZE); + expect(db).toBeDefined(); + + db.transaction(function(tx) { + expect(tx).toBeDefined(); + + tx.executeSql("SELECT LOWER(X'41F09F9883') AS lowertext", [], function(ignored, res) { + expect(res).toBeDefined(); + expect(res.rows.item(0).lowertext).toBe('a\uD83D\uDE03'); + + // Close (plugin only) & finish: + (isWebSql) ? done() : db.close(done, done); + }); + }); + }, MYTIMEOUT); + + it(suiteName + 'emoji SELECT HEX(?) parameter value test: "@\\uD83D\\uDE03!" [\\u1F603 SMILING FACE (MOUTH OPEN)]', function(done) { + if (isWP8) pending('BROKEN for WP8'); + if (isAndroid && !isWebSql && !isImpl2) pending('BROKEN for Android (default sqlite-connector version)'); + if (isWindows) pending('BROKEN for Windows'); + + var db = openDatabase("String-emoji-parameter-value-test.db", "1.0", "Demo", DEFAULT_SIZE); + expect(db).toBeDefined(); + + db.transaction(function(tx) { + expect(tx).toBeDefined(); + + tx.executeSql('SELECT HEX(?) AS hexvalue', ['@\uD83D\uDE03!'], function(tx, res) { + expect(res.rows.item(0).hexvalue).toBe('40F09F988321'); + + // Close (plugin only) & finish: + (isWebSql) ? done() : db.close(done, done); + }); + }); + }, MYTIMEOUT); + // NOTE: the next two tests show that for iOS: // - UNICODE \u2028 line separator from Javascript to Objective-C is working ok // - UNICODE \u2028 line separator from Objective-C to Javascript is BROKEN @@ -257,9 +360,8 @@ var mytests = function() { }, MYTIMEOUT); it(suiteName + ' handles UNICODE \\u2028 line separator correctly [string test]', function (done) { - if (isWP8) pending('BROKEN for WP(8)'); // [BUG #202] UNICODE characters not working with WP(8) - if (!(isWebSql || isAndroid || isIE)) pending('BROKEN for iOS'); // XXX [BUG #147] (no callback received) + if (!isWebSql && !isAndroid && !isWindows && !isWP8) pending('BROKEN for iOS plugin'); // [BUG #147] (no callback received) // NOTE: since the above test shows the UNICODE line separator (\u2028) // is seen by the sqlite implementation OK, it is now concluded that @@ -286,7 +388,7 @@ var mytests = function() { // - UNICODE \u2029 line separator from Javascript to Objective-C is working ok // - UNICODE \u2029 line separator from Objective-C to Javascript is BROKEN // ref: litehelpers/Cordova-sqlite-storage#147 - it(suiteName + "UNICODE \\u2029 line separator string length", function(done) { + it(suiteName + "UNICODE \\u2029 paragraph separator string length", function(done) { if (isWP8) pending('BROKEN for WP(8)'); // [BUG #202] Certain UNICODE characters not working with WP(8) // NOTE: this test verifies that the UNICODE paragraph separator (\u2029) @@ -308,10 +410,9 @@ var mytests = function() { }); }, MYTIMEOUT); - it(suiteName + ' handles UNICODE \\u2029 line separator correctly [string test]', function (done) { - + it(suiteName + ' handles UNICODE \\u2029 paragraph separator correctly [string test]', function (done) { if (isWP8) pending('BROKEN for WP(8)'); // [BUG #202] UNICODE characters not working with WP(8) - if (!(isWebSql || isAndroid || isIE)) pending('BROKEN for iOS'); // XXX [BUG #147] (no callback received) + if (!isWebSql && !isAndroid && !isWindows && !isWP8) pending('BROKEN for iOS plugin'); // [BUG #147] (no callback received) // NOTE: since the above test shows the UNICODE paragraph separator (\u2029) // is seen by the sqlite implementation OK, it is now concluded that @@ -423,6 +524,10 @@ var mytests = function() { }); }, MYTIMEOUT); + }); + + describe(suiteName + 'string test with non-primitive parameter values', function() { + it(suiteName + 'String test with new String object', function(done) { var db = openDatabase("String-object-string-test.db", "1.0", "Demo", DEFAULT_SIZE); @@ -437,13 +542,13 @@ var mytests = function() { }); }, MYTIMEOUT); - it(suiteName + 'String test with custom object', function(done) { - // MyCustomObject "class": - function MyCustomObject() {}; - MyCustomObject.prototype.toString = function() {return 'toString result';}; - MyCustomObject.prototype.valueOf = function() {return 'valueOf result';}; + it(suiteName + 'String test with custom object parameter value', function(done) { + // MyCustomParameterObject "class": + function MyCustomParameterObject() {}; + MyCustomParameterObject.prototype.toString = function() {return 'toString result';}; + MyCustomParameterObject.prototype.valueOf = function() {return 'valueOf result';}; - var myObject = new MyCustomObject(); + var myObject = new MyCustomParameterObject(); // Check myObject: expect(myObject.toString()).toBe('toString result'); expect(myObject.valueOf()).toBe('valueOf result'); @@ -460,8 +565,40 @@ var mytests = function() { }); }); }, MYTIMEOUT); + }); + + describe(suiteName + 'string test with [non-primitive] values for SQL', function() { + + it(suiteName + 'String test with new String for SQL', function(done) { + var myNewString = new String("SELECT UPPER('Alice') as u1"); + + var db = openDatabase("New-string-for-sql-test.db", "1.0", "Demo", DEFAULT_SIZE); + + db.transaction(function(tx) { + tx.executeSql(myNewString, [], function(tx_ignored, resultSet) { + // EXPECTED RESULT: + expect(true).toBe(true); + expect(resultSet).toBeDefined(); + expect(resultSet.rows).toBeDefined(); + expect(resultSet.rows.length).toBe(1); + expect(resultSet.rows.item(0)).toBeDefined(); + expect(resultSet.rows.item(0).u1).toBeDefined(); + expect(resultSet.rows.item(0).u1).toBe('ALICE'); + // Close (plugin only) & finish: + (isWebSql) ? done() : db.close(done, done); + + }, function(tx_ignored, error) { + // NOT EXPECTED: + expect(false).toBe(true); + expect(error.message).toBe('--'); + // Close (plugin only) & finish: + (isWebSql) ? done() : db.close(done, done); + }); + + }); + }, MYTIMEOUT); - it(suiteName + 'String test with custom object in place of sql', function(done) { + it(suiteName + 'String test with custom object for SQL', function(done) { // MyCustomObject "class": function MyCustomObject() {}; MyCustomObject.prototype.toString = function() {return "SELECT UPPER('Alice') as u1";}; @@ -472,9 +609,7 @@ var mytests = function() { expect(myObject.toString()).toBe("SELECT UPPER('Alice') as u1"); expect(myObject.valueOf()).toBe("SELECT UPPER('Betty') as u1"); - var db = openDatabase("Custom-sql-object-test.db", "1.0", "Demo", DEFAULT_SIZE); - - var check1 = false; + var db = openDatabase("Custom-object-for-sql-test.db", "1.0", "Demo", DEFAULT_SIZE); db.transaction(function(tx) { tx.executeSql(myObject, [], function(tx_ignored, resultSet) { @@ -500,16 +635,20 @@ var mytests = function() { }); }, MYTIMEOUT); + }); + + describe(suiteName + 'BLOB string test(s)', function() { + it(suiteName + "SELECT HEX(X'010203') [BLOB value test]", function(done) { var db = openDatabase("SELECT-HEX-BLOB-test.db", "1.0", "Demo", DEFAULT_SIZE); db.transaction(function(tx) { - tx.executeSql("SELECT HEX(X'010203') AS hex_value", [], function(ignored, rs) { + tx.executeSql("SELECT HEX(X'010203') AS hexvalue", [], function(ignored, rs) { expect(rs).toBeDefined(); expect(rs.rows).toBeDefined(); expect(rs.rows.length).toBe(1); - expect(rs.rows.item(0).hex_value).toBe('010203'); + expect(rs.rows.item(0).hexvalue).toBe('010203'); // Close (plugin only) & finish: (isWebSql) ? done() : db.close(done, done); @@ -517,6 +656,8 @@ var mytests = function() { }); }, MYTIMEOUT); + }); + }); } diff --git a/spec/www/spec/db-tx-value-bindings-test.js b/spec/www/spec/db-tx-value-bindings-test.js index d8dc01535..7bd14c215 100755 --- a/spec/www/spec/db-tx-value-bindings-test.js +++ b/spec/www/spec/db-tx-value-bindings-test.js @@ -51,15 +51,15 @@ var mytests = function() { for (var i=0; i