From 2e307e11d53689f44c38e486aaeea505470e6c16 Mon Sep 17 00:00:00 2001 From: cpainter Date: Sun, 9 Aug 2020 21:28:06 -0600 Subject: [PATCH 01/10] Fixed issue where getColumnNames() returned an empty array if SELECT query returned 0 rows. --- src/api.js | 3 ++- src/exported_functions.json | 1 + test/test_statement.js | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/api.js b/src/api.js index 7113c312..a3467be4 100644 --- a/src/api.js +++ b/src/api.js @@ -112,6 +112,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { ); var sqlite3_step = cwrap("sqlite3_step", "number", ["number"]); var sqlite3_errmsg = cwrap("sqlite3_errmsg", "string", ["number"]); + var sqlite3_column_count = cwrap("sqlite3_column_count", "number", ["number"]); var sqlite3_data_count = cwrap("sqlite3_data_count", "number", ["number"]); var sqlite3_column_double = cwrap( "sqlite3_column_double", @@ -423,7 +424,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { var results1; results1 = []; i = 0; - ref = sqlite3_data_count(this.stmt); + ref = sqlite3_column_count(this.stmt); while (i < ref) { results1.push(sqlite3_column_name(this.stmt, i)); i += 1; diff --git a/src/exported_functions.json b/src/exported_functions.json index af5085c7..b6882dfe 100644 --- a/src/exported_functions.json +++ b/src/exported_functions.json @@ -13,6 +13,7 @@ "_sqlite3_bind_int", "_sqlite3_bind_parameter_index", "_sqlite3_step", +"_sqlite3_column_count", "_sqlite3_data_count", "_sqlite3_column_double", "_sqlite3_column_text", diff --git a/test/test_statement.js b/test/test_statement.js index 8ac9ef4f..f8a88186 100644 --- a/test/test_statement.js +++ b/test/test_statement.js @@ -37,6 +37,10 @@ exports.test = function(sql, assert){ assert.deepEqual(res, {nbr:5, str:'粵語😄', no_value:null}, "Statement.getAsObject()"); stmt.free(); + // getColumnNames() should work even if query returns no data + stmt = db.prepare("SELECT * FROM data WHERE nbr = -1"); + assert.deepEqual(stmt.getColumnNames(), ['nbr','str','no_value'], 'Statement.GetColumnNames()'); + stmt.free(); stmt = db.prepare("SELECT str FROM data WHERE str=?"); assert.deepEqual(stmt.getAsObject(['粵語😄']), {'str':'粵語😄'}, "UTF8 support in prepared statements"); From e4b815bda8865cae822f10205ef68def9e877bd8 Mon Sep 17 00:00:00 2001 From: cpainter Date: Sun, 9 Aug 2020 21:42:38 -0600 Subject: [PATCH 02/10] Fixed line length issue with last commit. --- src/api.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api.js b/src/api.js index a3467be4..b03cf006 100644 --- a/src/api.js +++ b/src/api.js @@ -112,7 +112,11 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { ); var sqlite3_step = cwrap("sqlite3_step", "number", ["number"]); var sqlite3_errmsg = cwrap("sqlite3_errmsg", "string", ["number"]); - var sqlite3_column_count = cwrap("sqlite3_column_count", "number", ["number"]); + var sqlite3_column_count = cwrap( + "sqlite3_column_count", + "number", + ["number"] + ); var sqlite3_data_count = cwrap("sqlite3_data_count", "number", ["number"]); var sqlite3_column_double = cwrap( "sqlite3_column_double", From 6ff7383f77b99c0fa52417ed4b3d80c99c45420b Mon Sep 17 00:00:00 2001 From: cpainter Date: Sun, 9 Aug 2020 21:52:08 -0600 Subject: [PATCH 03/10] More eslint fixes --- src/api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api.js b/src/api.js index b03cf006..212fbc30 100644 --- a/src/api.js +++ b/src/api.js @@ -113,8 +113,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { var sqlite3_step = cwrap("sqlite3_step", "number", ["number"]); var sqlite3_errmsg = cwrap("sqlite3_errmsg", "string", ["number"]); var sqlite3_column_count = cwrap( - "sqlite3_column_count", - "number", + "sqlite3_column_count", + "number", ["number"] ); var sqlite3_data_count = cwrap("sqlite3_data_count", "number", ["number"]); From da2a49df254c61519ece89b844d0d5c4ce3ee3cd Mon Sep 17 00:00:00 2001 From: cpainter Date: Thu, 13 Aug 2020 15:10:07 -0600 Subject: [PATCH 04/10] Added Database.prepareMany() function to prepare and split SQL containing multiple queries. --- examples/simple.html | 12 +++++++- src/api.js | 48 +++++++++++++++++++++++++++++++ src/exported_functions.json | 1 + src/exported_runtime_methods.json | 3 +- test/test_database.js | 14 +++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/examples/simple.html b/examples/simple.html index af841ea6..38ae3a87 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -28,6 +28,16 @@ var row = stmt.getAsObject(); console.log('Here is a row: ' + JSON.stringify(row)); } + + // try new functionality + var thisSql; + var nextSql; + [stmt, thisSql, nextSql] = db.prepareMany("create table foo (x text); insert into foo values ('hello'), ('bonjour'); select * from foo;"); + console.log(thisSql); + console.log(nextSql); + + stmt.step(); + console.log(db.getRowsModified()); }); @@ -35,4 +45,4 @@ Output is in Javscript console - \ No newline at end of file + diff --git a/src/api.js b/src/api.js index 212fbc30..5e29787e 100644 --- a/src/api.js +++ b/src/api.js @@ -85,6 +85,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { "number", ["number", "number", "number", "number", "number"] ); + var sqlite3_sql = cwrap("sqlite3_sql", "string", ["number"]); var sqlite3_bind_text = cwrap( "sqlite3_bind_text", "number", @@ -885,6 +886,53 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return stmt; }; + /** Prepare the first statement in a string containing multiple SQL + statements + @param {string} sql a string of SQL, with possibly multiple SQL statements + (separated by ;), without parameters + @return {[Statement, string, string]} an array containing the prepared + statement, the portion of the SQL prepared, and the remaining SQL + @throws {String} SQLite error + */ + Database.prototype["prepareMany"] = function prepareMany(sql) { + var pStmt; + var pzTail; + var stmt; + var remainder; + var firstStatement; + var stack = stackSave(); + + try { + setValue(apiTemp, 0, "i32"); + pzTail = stackAlloc(4); + + this.handleError(sqlite3_prepare_v2( + this.db, + sql, + -1, + apiTemp, + pzTail + )); + + // pointer to a statement, or null + pStmt = getValue(apiTemp, "i32"); + + if (pStmt === NULL) { + stackRestore(stack); + return [NULL, sql, NULL]; + } + + // unused portion of the SQL or null + remainder = UTF8ToString(getValue(pzTail, "i32")); + stmt = new Statement(pStmt, this); + firstStatement = sqlite3_sql(pStmt); + this.statements[pStmt] = stmt; + return [stmt, firstStatement, remainder]; + } finally { + stackRestore(stack); + } + }; + /** Exports the contents of the database to a binary array @return {Uint8Array} An array of bytes of the SQLite3 database file */ diff --git a/src/exported_functions.json b/src/exported_functions.json index b6882dfe..46dedbf2 100644 --- a/src/exported_functions.json +++ b/src/exported_functions.json @@ -7,6 +7,7 @@ "_sqlite3_errmsg", "_sqlite3_changes", "_sqlite3_prepare_v2", +"_sqlite3_sql", "_sqlite3_bind_text", "_sqlite3_bind_blob", "_sqlite3_bind_double", diff --git a/src/exported_runtime_methods.json b/src/exported_runtime_methods.json index 644fd3ea..13a8efb8 100644 --- a/src/exported_runtime_methods.json +++ b/src/exported_runtime_methods.json @@ -2,5 +2,6 @@ "cwrap", "stackAlloc", "stackSave", -"stackRestore" +"stackRestore", +"UTF8ToString" ] diff --git a/test/test_database.js b/test/test_database.js index 1759749b..245f6316 100644 --- a/test/test_database.js +++ b/test/test_database.js @@ -61,6 +61,20 @@ exports.test = function(SQL, assert, done) { done(); } }, 3000); + + // Prepare first of many statements + var thisSql; + var nextSql; + var sqlstr = "CREATE TABLE test (x integer); \n"; + sqlstr = sqlstr + "INSERT INTO test VALUES (42), (77);"; + sqlstr = sqlstr + "SELECT * FROM test"; + [stmt, thisSql, nextSql] = db.prepareMany(sqlstr); + assert.strictEqual(thisSql, "CREATE TABLE test (x integer);", + "db.prepareMany extracts first statement"); + assert.strictEqual(nextSql.trim(), "INSERT INTO test VALUES (42), (77);SELECT * FROM test", + "db.prepareMany extracts remaining statements"); + [stmt, thisSql, nextSql] = db.prepareMany(" -- just a comment "); + assert.strictEqual(stmt, NULL, "db.prepareMany returns null Statement when no statement"); }; if (module == require.main) { From 27584b409747824b4b1d29e59dd9b05f7425eb31 Mon Sep 17 00:00:00 2001 From: cpainter Date: Thu, 13 Aug 2020 23:41:56 -0600 Subject: [PATCH 05/10] New function Database.execMany() that executes many (non-parameterized) SQL statements and provides detailed results on each. Also, removed Database.prepareMany() function; it ended up not being useful for what I really wanted to do. --- Makefile | 3 +- src/api.js | 244 ++++++++++++++++++++++++++++-------- src/exported_functions.json | 1 + 3 files changed, 197 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index 14e0e719..13d91b25 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,8 @@ CFLAGS = \ -DSQLITE_DISABLE_LFS \ -DSQLITE_ENABLE_FTS3 \ -DSQLITE_ENABLE_FTS3_PARENTHESIS \ - -DSQLITE_THREADSAFE=0 + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_ENABLE_NORMALIZE # When compiling to WASM, enabling memory-growth is not expected to make much of an impact, so we enable it for all builds # Since tihs is a library and not a standalone executable, we don't want to catch unhandled Node process exceptions diff --git a/src/api.js b/src/api.js index 5e29787e..3c7e41ef 100644 --- a/src/api.js +++ b/src/api.js @@ -86,6 +86,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { ["number", "number", "number", "number", "number"] ); var sqlite3_sql = cwrap("sqlite3_sql", "string", ["number"]); + var sqlite3_normalized_sql = cwrap("sqlite3_normalized_sql", "string", ["number"]); var sqlite3_bind_text = cwrap( "sqlite3_bind_text", "number", @@ -720,8 +721,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { * and {@link Statement.free}. * * The result is an array of result elements. There are as many result - * elements as the number of statements in your sql string (statements are - * separated by a semicolon) + * elements as the number of statements which return at least one row + * in your sql string (statements are separated by a semicolon) * * ## Example use * We will create the following table, named *test* and query it with a @@ -732,7 +733,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { * | 1 | 1 | Ling | * | 2 | 18 | Paul | * - * We query it like that: + * We query it like this: * ```javascript * var db = new SQL.Database(); * var res = db.exec( @@ -820,6 +821,196 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { } }; + /** Execute multiple queries, with detailed results for each. + * Queries may not use parameters. + * + * The result is an array of objects of varying structure. There are as + * many result elements as the number of statements in your sql string + * (statements are separated by a semicolon). + * + * Result objects may contain the following members: + * - success: true/false (all queries) + * - error: error message if success is false + * - columns: column headers, if query returns a result + * (even if empty) + * - values: query results, if query returns a + * result (even if empty) + * - rowsModified: rows affected, if query is INSERT, UPDATE, or DELETE + * - sql: the SQL executed for this result (if no error) + * + * ## Example use + * We will create the following table, named *test* and query it with a + * multi-line statement: + * + * | id | age | name | + * |:--:|:---:|:------:| + * | 1 | 1 | Ling | + * | 2 | 18 | Paul | + * + * We query it like this: + * ```javascript + * var db = new SQL.Database(); + * var res = db.execMany( + * "DROP TABLE IF EXISTS test;" + * + "CREATE TABLE test (id INTEGER, age INTEGER, name TEXT);" + * + "INSERT INTO test VALUES (1, 1, 'Ling');" + * + "INSERT INTO test VALUES (2, 18, 'Paul');" + * + "SELECT id FROM test;" + * + "SELECT age, name FROM test WHERE id = 1;" + * + "SELECT age, name FROM test WHERE id = 4;" + * + "INSERT INTO test (blah, foo) VALUES ('hello', 42);" + * + "DELETE FROM test;" + * ); + * ``` + * + * `res` is now : + * ```javascript + * [ + * {"success": true, "sql": "DROP TABLE IF EXISTS test;"}, + * { + * "success": true, + * "rowsModified": 1, + * "sql": + * "CREATE TABLE test (id INTEGER, age INTEGER, name TEXT);" + * }, + * { + * "success": true, + * "rowsModified": 1 + * "sql": "INSERT INTO test VALUES (1, 1, 'Ling');" + * }, + * { + * "success": true, + * "rowsModified": 1 + * "sql": "INSERT INTO test VALUES (2, 18, 'Paul');" + * }, + * { + * "success": true, + * "columns": ["id"], + * "values": [[1],[2]]}, + * "sql": "SELECT id FROM test;" + * }, + * { + * "success": true, + * "columns": ["age","name"], + * "values" :[[1,"Ling"]]}, + * "sql": "SELECT age, name FROM test WHERE id = 1;" + * }, + * { + * "success": true, + * "columns": ["age","name"], + * "values":[] + * "sql": "SELECT age, name FROM test WHERE id = 4;" + * }, + * { + * "success": false, + * "error": "table test has no column named blah", + * "sql": "INSERT INTO test (blah, foo) VALUES ('hello', 42);" + * }, + * {"success": true, "rowsModified": 2, "sql": "DELETE FROM test;"} + * ] + * ``` + * + @param {string} sql a string containing some SQL text to execute + @param {boolean} exitOnError whether or not to continue after error + @return {Object[]} as described above + */ + Database.prototype["execMany"] = function execMany(sql, exitOnError) { + var answer = []; + var result; + var data; + var columns; + var stack; + var lastSql; + var nextSql; + var thisSql; + var normalizedSql; + var sqlType; + var stmt; + var pStmt; + var pzTail; + var returnCode; + var errorMessage; + + if (!this.db) { + throw "Database closed"; + } + + stack = stackSave(); + + nextSql = sql; + pzTail = stackAlloc(4); + + while (true) { + setValue(apiTemp, 0, "i32"); + setValue(pzTail, 0, "i32"); + returnCode = sqlite3_prepare_v2(this.db, nextSql, -1, apiTemp, pzTail); + lastSql = nextSql; + nextSql = UTF8ToString(getValue(pzTail, "i32")); + if (returnCode !== SQLITE_OK) { + errorMessage = sqlite3_errmsg(this.db); + + // there's no valid statement pointer, so we have to do a hack to + // discover the most recent SQL statement: + thisSql = lastSql.substr(0, lastSql.length - nextSql.length); + + answer.push({ success: false, error: errorMessage, sql: thisSql }); + + if (exitOnError) { + stackRestore(stack); + return answer; + } + continue; + } + + // pointer to a statement, or NULL if nothing there + pStmt = getValue(apiTemp, "i32"); + if (pStmt === NULL) break; + + // get most recent sql statement + thisSql = sqlite3_sql(pStmt); + + // wrap in a Statement so we can use existing methods + stmt = new Statement(pStmt, this); + + // get column headers, if any + columns = stmt.getColumnNames(); + data = []; + try { + while (stmt.step()) { + data.push(stmt.get()); + } + } catch (e) { + answer.push({ success: false, error: e.toString(), sql: thisSql }); + if (exitOnError) { + stackRestore(stack); + return answer; + } + continue; + } + + result = { success: true, sql: thisSql }; + + if (columns.length > 0) { + result.columns = columns; + result.values = data; + } else { + // bit of a kludge: determine if last query was modification query + normalizedSql = sqlite3_normalized_sql(pStmt); + console.log(normalizedSql); + sqlType = normalizedSql.trim().substr(0,6).toLowerCase(); + if (sqlType === 'insert' || sqlType === 'update' || sqlType === 'delete') { + result.rowsModified = this.getRowsModified(); + } + } + answer.push(result); + + // clean up + stmt.free(); + } + stackRestore(stack); + return answer; + }; + /** Execute an sql statement, and call a callback for each row of result. Currently this method is synchronous, it will not return until the callback @@ -886,53 +1077,6 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return stmt; }; - /** Prepare the first statement in a string containing multiple SQL - statements - @param {string} sql a string of SQL, with possibly multiple SQL statements - (separated by ;), without parameters - @return {[Statement, string, string]} an array containing the prepared - statement, the portion of the SQL prepared, and the remaining SQL - @throws {String} SQLite error - */ - Database.prototype["prepareMany"] = function prepareMany(sql) { - var pStmt; - var pzTail; - var stmt; - var remainder; - var firstStatement; - var stack = stackSave(); - - try { - setValue(apiTemp, 0, "i32"); - pzTail = stackAlloc(4); - - this.handleError(sqlite3_prepare_v2( - this.db, - sql, - -1, - apiTemp, - pzTail - )); - - // pointer to a statement, or null - pStmt = getValue(apiTemp, "i32"); - - if (pStmt === NULL) { - stackRestore(stack); - return [NULL, sql, NULL]; - } - - // unused portion of the SQL or null - remainder = UTF8ToString(getValue(pzTail, "i32")); - stmt = new Statement(pStmt, this); - firstStatement = sqlite3_sql(pStmt); - this.statements[pStmt] = stmt; - return [stmt, firstStatement, remainder]; - } finally { - stackRestore(stack); - } - }; - /** Exports the contents of the database to a binary array @return {Uint8Array} An array of bytes of the SQLite3 database file */ diff --git a/src/exported_functions.json b/src/exported_functions.json index 46dedbf2..b93b07d2 100644 --- a/src/exported_functions.json +++ b/src/exported_functions.json @@ -8,6 +8,7 @@ "_sqlite3_changes", "_sqlite3_prepare_v2", "_sqlite3_sql", +"_sqlite3_normalized_sql", "_sqlite3_bind_text", "_sqlite3_bind_blob", "_sqlite3_bind_double", From 2ef3af8731e54909e682c2d135115cf993f4ccd9 Mon Sep 17 00:00:00 2001 From: cpainter Date: Sat, 22 Aug 2020 01:03:09 -0600 Subject: [PATCH 06/10] Fixed eslint issues. Created test script for execMany(). --- src/api.js | 49 +++++++---- test/test_exec_many.js | 192 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 17 deletions(-) create mode 100644 test/test_exec_many.js diff --git a/src/api.js b/src/api.js index 3c7e41ef..1418dab9 100644 --- a/src/api.js +++ b/src/api.js @@ -869,7 +869,6 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { * {"success": true, "sql": "DROP TABLE IF EXISTS test;"}, * { * "success": true, - * "rowsModified": 1, * "sql": * "CREATE TABLE test (id INTEGER, age INTEGER, name TEXT);" * }, @@ -943,17 +942,27 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { while (true) { setValue(apiTemp, 0, "i32"); setValue(pzTail, 0, "i32"); - returnCode = sqlite3_prepare_v2(this.db, nextSql, -1, apiTemp, pzTail); + returnCode = sqlite3_prepare_v2( + this.db, + nextSql, + -1, + apiTemp, + pzTail) + ; lastSql = nextSql; nextSql = UTF8ToString(getValue(pzTail, "i32")); if (returnCode !== SQLITE_OK) { errorMessage = sqlite3_errmsg(this.db); - // there's no valid statement pointer, so we have to do a hack to - // discover the most recent SQL statement: + // there's no valid statement pointer, so we have to do + // a hack to discover the most recent SQL statement: thisSql = lastSql.substr(0, lastSql.length - nextSql.length); - answer.push({ success: false, error: errorMessage, sql: thisSql }); + answer.push({ + "success": false, + "error": errorMessage, + "sql": thisSql + }); if (exitOnError) { stackRestore(stack); @@ -973,14 +982,18 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { stmt = new Statement(pStmt, this); // get column headers, if any - columns = stmt.getColumnNames(); + columns = stmt["getColumnNames"]() data = []; try { - while (stmt.step()) { - data.push(stmt.get()); + while (stmt["step"]()) { + data.push(stmt["get"]()); } } catch (e) { - answer.push({ success: false, error: e.toString(), sql: thisSql }); + answer.push({ + "success": false, + "error": e.toString(), + "sql": thisSql + }); if (exitOnError) { stackRestore(stack); return answer; @@ -988,24 +1001,26 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { continue; } - result = { success: true, sql: thisSql }; + result = { "success": true, "sql": thisSql }; if (columns.length > 0) { - result.columns = columns; - result.values = data; + result["columns"] = columns; + result["values"] = data; } else { - // bit of a kludge: determine if last query was modification query + // bit of a kludge: determine if last + // query was modification query normalizedSql = sqlite3_normalized_sql(pStmt); - console.log(normalizedSql); sqlType = normalizedSql.trim().substr(0,6).toLowerCase(); - if (sqlType === 'insert' || sqlType === 'update' || sqlType === 'delete') { - result.rowsModified = this.getRowsModified(); + if (sqlType === "insert" + || sqlType === "update" + || sqlType === "delete") { + result["rowsModified"] = this["getRowsModified"](); } } answer.push(result); // clean up - stmt.free(); + stmt["free"](); } stackRestore(stack); return answer; diff --git a/test/test_exec_many.js b/test/test_exec_many.js new file mode 100644 index 00000000..143bf435 --- /dev/null +++ b/test/test_exec_many.js @@ -0,0 +1,192 @@ +exports.test = function(sql, assert){ + // Create a database + var db = new sql.Database(); + + // test DDL + var result = db.execMany("DROP TABLE IF EXISTS test;"); + assert.deepEqual( + result, + [{ + "success": true, + "sql": "DROP TABLE IF EXISTS test;" + }], + "DDL: DROP" + ); + + result = db.execMany("CREATE TABLE test (id INTEGER, age INTEGER, name TEXT)"); + assert.deepEqual( + result, + [{ + "success": true, + "sql": "CREATE TABLE test (id INTEGER, age INTEGER, name TEXT)" + }], + "DDL: CREATE" + ); + + // test modification queries + result = db.execMany("INSERT INTO test VALUES (1, 1, 'Ling')"); + assert.deepEqual( + result, + [{ + "success": true, + "rowsModified": 1, + "sql": "INSERT INTO test VALUES (1, 1, 'Ling')" + }], + "INSERT 1" + ); + + result = db.execMany("INSERT INTO test VALUES (2, 10, 'Wendy'), (3, 7, 'Jeff')"); + assert.deepEqual( + result, + [{ + "success": true, + "rowsModified": 2, + "sql": "INSERT INTO test VALUES (2, 10, 'Wendy'), (3, 7, 'Jeff')" + }], + "INSERT many" + ); + + result = db.execMany("UPDATE test SET age = age + 1"); + assert.deepEqual( + result, + [{ + "success": true, + "rowsModified": 3, + "sql": "UPDATE test SET age = age + 1" + }], + "UPDATE" + ); + + result = db.execMany("DELETE FROM TEST WHERE name = 'Priya'"); + assert.deepEqual( + result, + [{ + "success": true, + "rowsModified": 0, + "sql": "DELETE FROM TEST WHERE name = 'Priya'" + }], + "DELETE" + ); + + // SELECT queries + result = db.execMany("SELECT * FROM test"); + assert.deepEqual( + result, + [{ + "success": true, + "columns": ["id", "age", "name"], + "values": [[1, 2, "Ling"], [2, 11, "Wendy"], [3, 8, "Jeff"]], + "sql": "SELECT * FROM test" + }], + "SELECT many" + ); + + result = db.execMany("SELECT * FROM test WHERE name = 'Priya'"); + assert.deepEqual( + result, + [{ + "success": true, + "columns": ["id", "age", "name"], + "values": [], + "sql": "SELECT * FROM test WHERE name = 'Priya'" + }], + "SELECT 0" + ); + + // test errors in query + result = db.execMany("INSERT INTO test (blah, foo) VALUES ('hello', 42);"); + assert.deepEqual( + result, + [{ + "success": false, + "error": "table test has no column named blah", + "sql": "INSERT INTO test (blah, foo) VALUES ('hello', 42);" + }], + "Error result: INSERT" + ); + + result = db.execMany("SELECT blah FROM test"); + assert.deepEqual( + result, + [{ + "success": false, + "error": "no such column: blah", + "sql": "SELECT blah FROM test" + }], + "Error result: SELECT" + ); + + // test multiple statement and stop/no stop on error + + result = db.execMany( + "INSERT INTO test VALUES (4, 17, 'Priya');" + + "SELECT blah FROM test;" + + "DELETE FROM test", + true + ); + assert.deepEqual( + result, + [{ + "success": true, + "rowsModified": 1, + "sql": "INSERT INTO test VALUES (4, 17, 'Priya');" + }, + { + "success": false, + "error": "no such column: blah", + "sql": "SELECT blah FROM test;" + }], + "Multiple statements with stop on error" + ); + + result = db.execMany( + "INSERT INTO test VALUES (5, 9, 'Azam');" + + "SELECT blah FROM test;" + + "DELETE FROM test;" + + "SELECT id FROM test" + ); + assert.deepEqual( + result, + [{ + "success": true, + "rowsModified": 1, + "sql": "INSERT INTO test VALUES (5, 9, 'Azam');" + }, + { + "success": false, + "error": "no such column: blah", + "sql": "SELECT blah FROM test;" + }, + { + "success": true, + "rowsModified": 5, + "sql": "DELETE FROM test;" + }, + { + "success": true, + "columns": ["id"], + "values": [], + "sql": "SELECT id FROM test" + }], + "Multiple statements with no stop on error" + ); + + // Close the database + db.close(); +}; + +if (module == require.main) { + const target_file = process.argv[2]; + const sql_loader = require('./load_sql_lib'); + sql_loader(target_file).then((sql)=>{ + require('test').run({ + 'test statement': function(assert){ + exports.test(sql, assert); + } + }); + }) + .catch((e)=>{ + console.error(e); + assert.fail(e); + }); +} From 01805771b4f63e8a535c7432d03293c88d9ff085 Mon Sep 17 00:00:00 2001 From: cpainter Date: Sat, 22 Aug 2020 01:10:13 -0600 Subject: [PATCH 07/10] Rolled back accidental changes to simple.html. --- examples/simple.html | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/simple.html b/examples/simple.html index 38ae3a87..3d175b43 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -28,16 +28,6 @@ var row = stmt.getAsObject(); console.log('Here is a row: ' + JSON.stringify(row)); } - - // try new functionality - var thisSql; - var nextSql; - [stmt, thisSql, nextSql] = db.prepareMany("create table foo (x text); insert into foo values ('hello'), ('bonjour'); select * from foo;"); - console.log(thisSql); - console.log(nextSql); - - stmt.step(); - console.log(db.getRowsModified()); }); From 709ea8beadea79b99accceed30ebb594fcde7bee Mon Sep 17 00:00:00 2001 From: cpainter Date: Sat, 22 Aug 2020 01:13:59 -0600 Subject: [PATCH 08/10] Rolled back changes to test_database.js for discarded functionality. --- test/test_database.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/test_database.js b/test/test_database.js index 245f6316..1759749b 100644 --- a/test/test_database.js +++ b/test/test_database.js @@ -61,20 +61,6 @@ exports.test = function(SQL, assert, done) { done(); } }, 3000); - - // Prepare first of many statements - var thisSql; - var nextSql; - var sqlstr = "CREATE TABLE test (x integer); \n"; - sqlstr = sqlstr + "INSERT INTO test VALUES (42), (77);"; - sqlstr = sqlstr + "SELECT * FROM test"; - [stmt, thisSql, nextSql] = db.prepareMany(sqlstr); - assert.strictEqual(thisSql, "CREATE TABLE test (x integer);", - "db.prepareMany extracts first statement"); - assert.strictEqual(nextSql.trim(), "INSERT INTO test VALUES (42), (77);SELECT * FROM test", - "db.prepareMany extracts remaining statements"); - [stmt, thisSql, nextSql] = db.prepareMany(" -- just a comment "); - assert.strictEqual(stmt, NULL, "db.prepareMany returns null Statement when no statement"); }; if (module == require.main) { From 45c645867bf8b927e1d53f3b16e97877a53e5a31 Mon Sep 17 00:00:00 2001 From: cpainter Date: Sat, 22 Aug 2020 01:41:23 -0600 Subject: [PATCH 09/10] More eslint fixes. --- src/api.js | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/api.js b/src/api.js index 1418dab9..f378b1d0 100644 --- a/src/api.js +++ b/src/api.js @@ -86,7 +86,11 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { ["number", "number", "number", "number", "number"] ); var sqlite3_sql = cwrap("sqlite3_sql", "string", ["number"]); - var sqlite3_normalized_sql = cwrap("sqlite3_normalized_sql", "string", ["number"]); + var sqlite3_normalized_sql = cwrap( + "sqlite3_normalized_sql", + "string", + ["number"] + ); var sqlite3_bind_text = cwrap( "sqlite3_bind_text", "number", @@ -929,6 +933,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { var pzTail; var returnCode; var errorMessage; + var errorAnswer; if (!this.db) { throw "Database closed"; @@ -939,6 +944,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { nextSql = sql; pzTail = stackAlloc(4); + // eslint-disable-next-line no-constant-condition while (true) { setValue(apiTemp, 0, "i32"); setValue(pzTail, 0, "i32"); @@ -947,8 +953,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { nextSql, -1, apiTemp, - pzTail) - ; + pzTail + ); lastSql = nextSql; nextSql = UTF8ToString(getValue(pzTail, "i32")); if (returnCode !== SQLITE_OK) { @@ -958,16 +964,17 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { // a hack to discover the most recent SQL statement: thisSql = lastSql.substr(0, lastSql.length - nextSql.length); - answer.push({ - "success": false, - "error": errorMessage, - "sql": thisSql - }); + errorAnswer = {}; + errorAnswer["success"] = false; + errorAnswer["error"] = errorMessage; + errorAnswer["sql"] = thisSql; + answer.push(errorAnswer); if (exitOnError) { stackRestore(stack); return answer; } + // eslint-disable-next-line no-continue continue; } @@ -982,26 +989,29 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { stmt = new Statement(pStmt, this); // get column headers, if any - columns = stmt["getColumnNames"]() + columns = stmt["getColumnNames"](); data = []; try { while (stmt["step"]()) { data.push(stmt["get"]()); } } catch (e) { - answer.push({ - "success": false, - "error": e.toString(), - "sql": thisSql - }); + errorAnswer = {}; + errorAnswer["success"] = false; + errorAnswer["error"] = e.toString(); + errorAnswer["sql"] = thisSql; + answer.push(errorAnswer); if (exitOnError) { stackRestore(stack); return answer; } + // eslint-disable-next-line no-continue continue; } - result = { "success": true, "sql": thisSql }; + result = {}; + result["success"] = true; + result["sql"] = thisSql; if (columns.length > 0) { result["columns"] = columns; @@ -1010,7 +1020,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { // bit of a kludge: determine if last // query was modification query normalizedSql = sqlite3_normalized_sql(pStmt); - sqlType = normalizedSql.trim().substr(0,6).toLowerCase(); + sqlType = normalizedSql.trim().substr(0, 6).toLowerCase(); if (sqlType === "insert" || sqlType === "update" || sqlType === "delete") { From c88c208063f5681cb7ae186d841d261c0e861950 Mon Sep 17 00:00:00 2001 From: cpainter Date: Sat, 22 Aug 2020 01:50:34 -0600 Subject: [PATCH 10/10] More eslint fixes 2. --- src/api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api.js b/src/api.js index f378b1d0..690c4b70 100644 --- a/src/api.js +++ b/src/api.js @@ -14,6 +14,7 @@ stackAlloc stackRestore stackSave + UTF8ToString */ "use strict";