-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New db function: execMany() - execute many statements with detailed results #413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2e307e1
e4b815b
6ff7383
da2a49d
27584b4
2ef3af8
0180577
709ea8b
45c6458
c88c208
9982150
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,4 +35,4 @@ | |
Output is in Javscript console | ||
</body> | ||
|
||
</html> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
stackAlloc | ||
stackRestore | ||
stackSave | ||
UTF8ToString | ||
*/ | ||
|
||
"use strict"; | ||
|
@@ -85,6 +86,12 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { | |
"number", | ||
["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", | ||
|
@@ -683,8 +690,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 | ||
|
@@ -695,7 +702,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( | ||
|
@@ -780,6 +787,217 @@ 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 | ||
Comment on lines
+798
to
+799
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need two fields here. Let's just have an |
||
* - columns: column headers, if query returns a result | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to specify the type. And can't we always return it ? Just set it to the empty array when a query doesn't return results. |
||
* (even if empty) | ||
* - values: query results, if query returns a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will need to specify in what form the query results come. |
||
* result (even if empty) | ||
* - rowsModified: rows affected, if query is INSERT, UPDATE, or DELETE | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can simply always include it, and set it to |
||
* - 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, | ||
* "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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will need a better type definition here. |
||
*/ | ||
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; | ||
var errorAnswer; | ||
Comment on lines
+883
to
+898
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know a lot of the code had this style after the automatic coffee to js translation, but we are now trying to have the variables declared at their first use site. |
||
|
||
if (!this.db) { | ||
throw "Database closed"; | ||
} | ||
|
||
stack = stackSave(); | ||
|
||
nextSql = sql; | ||
pzTail = stackAlloc(4); | ||
|
||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
Comment on lines
+909
to
+910
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They advise to use But this is a code smell anyway. Isn't there a way to reorganise the code to make the stopping condition explicit ? |
||
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); | ||
|
||
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; | ||
Comment on lines
+939
to
+940
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we have a simple if-else instead of disabling the lint ? |
||
} | ||
|
||
// 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) { | ||
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; | ||
Comment on lines
+970
to
+971
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can avoid this continue too |
||
} | ||
|
||
result = {}; | ||
result["success"] = true; | ||
result["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); | ||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,6 @@ | |
"cwrap", | ||
"stackAlloc", | ||
"stackSave", | ||
"stackRestore" | ||
"stackRestore", | ||
"UTF8ToString" | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may contain is not very clear. We should have a clear specification for what the function returns.