Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@
Output is in Javscript console
</body>

</html>
</html>
224 changes: 221 additions & 3 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
stackAlloc
stackRestore
stackSave
UTF8ToString
*/

"use strict";
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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:
Copy link
Member

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.

* - success: true/false (all queries)
* - error: error message if success is false
Comment on lines +798 to +799
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need two fields here. Let's just have an error field with type string|null

* - columns: column headers, if query returns a result
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can simply always include it, and set it to 0 if there is 0 modified rows.

* - 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
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They advise to use for (;;) for that purpose, in the documentation: https://eslint.org/docs/rules/no-constant-condition

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
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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
Expand Down
2 changes: 2 additions & 0 deletions src/exported_functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"_sqlite3_errmsg",
"_sqlite3_changes",
"_sqlite3_prepare_v2",
"_sqlite3_sql",
"_sqlite3_normalized_sql",
"_sqlite3_bind_text",
"_sqlite3_bind_blob",
"_sqlite3_bind_double",
Expand Down
3 changes: 2 additions & 1 deletion src/exported_runtime_methods.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"cwrap",
"stackAlloc",
"stackSave",
"stackRestore"
"stackRestore",
"UTF8ToString"
]
Loading