Skip to content

- allow "exec" commmand to pass dictionary-params to webworker (with testcase) #364

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

Merged
merged 7 commits into from
Mar 8, 2020
Merged
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,15 +204,16 @@ Example:

worker.postMessage({
id: 2,
action: 'exec',
sql: 'SELECT * FROM test'
action: "exec",
sql: "SELECT age,name FROM test WHERE id=$id",
params: { "$id": 1 }
});
};

worker.onerror = e => console.log("Worker error: ", e);
worker.postMessage({
id:1,
action:'open',
action:"open",
buffer:buf, /*Optional. An ArrayBuffer representing an SQLite Database file*/
});
</script>
Expand Down
79 changes: 52 additions & 27 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
* **Warning**: When you close a database (using db.close()),
* all its statements are closed too and become unusable.
*
* Statements can't be created by the API user directly, only by Database::prepare
* Statements can't be created by the API user directly, only by
* Database::prepare
*
* @see Database.html#prepare-dynamic
* @see https://en.wikipedia.org/wiki/Prepared_statement
Expand Down Expand Up @@ -321,7 +322,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {

/** Get one row of results of a statement.
If the first parameter is not provided, step must have been called before.
@param {Statement.BindParams} [params=[]] If set, the values will be bound
@param {Statement.BindParams} [params] If set, the values will be bound
to the statement before it is executed
@return {Database.SqlValue[]} One row of result

Expand Down Expand Up @@ -381,8 +382,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
return results1;
};

/** Get one row of result as a javascript object, associating column names with
their value in the current row.
/** Get one row of result as a javascript object, associating column names
with their value in the current row.
@param {Statement.BindParams} [params] If set, the values will be bound
to the statement, and it will be executed
@return {Object<string, Database.SqlValue>} The row of result
Expand Down Expand Up @@ -519,7 +520,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
);
};

/** Bind names and values of an object to the named parameters of the statement
/** Bind names and values of an object to the named parameters of the
statement
@param {Object<string, Database.SqlValue>} valuesObj
@private
@nodoc
Expand Down Expand Up @@ -589,8 +591,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
* Represents an SQLite database
* @constructs Database
* @memberof module:SqlJs
* Open a new database either by creating a new one or opening an existing one,
* stored in the byte array passed in first argument
* Open a new database either by creating a new one or opening an existing
* one stored in the byte array passed in first argument
* @param {number[]} data An array of bytes representing
* an SQLite database file
*/
Expand All @@ -611,10 +613,10 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {

/** Execute an SQL query, ignoring the rows it returns.
@param {string} sql a string containing some SQL text to execute
@param {any[]} [params=[]] When the SQL statement contains placeholders,
you can pass them in here. They will be bound to the statement
before it is executed. If you use the params argument, you **cannot** provide an sql string
that contains several queries (separated by `;`)
@param {Statement.BindParams} [params] When the SQL statement contains
placeholders, you can pass them in here. They will be bound to the statement
before it is executed. If you use the params argument, you **cannot**
provide an sql string that contains several statements (separated by `;`)

@example
// Insert values in a table
Expand Down Expand Up @@ -644,48 +646,66 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
* @typedef {{columns:string[], values:Database.SqlValue[][]}} Database.QueryExecResult
* @property {string[]} columns the name of the columns of the result
* (as returned by {@link Statement.getColumnNames})
* @property {Database.SqlValue[][]} values one array per row, containing the column values
* @property {Database.SqlValue[][]} values one array per row, containing
* the column values
*/

/** Execute an SQL query, and returns the result.
*
* This is a wrapper against
* {@link Database.prepare},
* {@link Statement.bind},
* {@link Statement.step},
* {@link Statement.get},
* 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)
* 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)
*
* ## Example use
* We have the following table, named *test* :
* We will create the following table, named *test* and query it with a
* multi-line statement using params:
*
* | id | age | name |
* |:--:|:---:|:------:|
* | 1 | 1 | Ling |
* | 2 | 18 | Paul |
* | 3 | 3 | Markus |
*
* We query it like that:
* ```javascript
* var db = new SQL.Database();
* var res = db.exec("SELECT id FROM test; SELECT age,name FROM test;");
* var res = db.exec(
Copy link
Member Author

Choose a reason for hiding this comment

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

re-opening this convesation, since it was clobbered by last commit. is example-code ok the way it is, or still too verbose? verified example works.

image

* "DROP TABLE IF EXISTS test;\n"
* + "CREATE TABLE test (id INTEGER, age INTEGER, name TEXT);"
* + "INSERT INTO test VALUES ($id1, :age1, @name1);"
* + "INSERT INTO test VALUES ($id2, :age2, @name2);"
* + "SELECT id FROM test;"
* + "SELECT age,name FROM test WHERE id=$id1",
* {
* "$id1": 1, ":age1": 1, "@name1": "Ling",
* "$id2": 2, ":age2": 18, "@name2": "Paul"
* }
* );
* ```
*
* `res` is now :
* ```javascript
* [
* {columns: ['id'], values:[[1],[2],[3]]},
* {columns: ['age','name'], values:[[1,'Ling'],[18,'Paul'],[3,'Markus']]}
* {"columns":["id"],"values":[[1],[2]]},
* {"columns":["age","name"],"values":[[1,"Ling"]]}
* ]
* ```
*
* @param {string} sql a string containing some SQL text to execute
@param {string} sql a string containing some SQL text to execute
@param {Statement.BindParams} [params] When the SQL statement contains
placeholders, you can pass them in here. They will be bound to the statement
before it is executed. If you use the params argument as an array,
you **cannot** provide an sql string that contains several statements
(separated by `;`). This limitation does not apply to params as an object.
* @return {Database.QueryExecResult[]} The results of each statement
*/
Database.prototype["exec"] = function exec(sql) {
Database.prototype["exec"] = function exec(sql, params) {
var curresult;
var stmt;
if (!this.db) {
Expand Down Expand Up @@ -713,6 +733,9 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
if (pStmt !== NULL) {
curresult = null;
stmt = new Statement(pStmt, this);
if (params != null) {
stmt.bind(params);
}
while (stmt["step"]()) {
if (curresult === null) {
curresult = {
Expand All @@ -739,14 +762,16 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {

/** 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
has been called on every row of the result. But this might change.
**Currently** this method is synchronous, it will not return until the
callback has been called on every row of the result. But this might change.

@param {string} sql A string of SQL text. Can contain placeholders
that will be bound to the parameters given as the second argument
@param {Array<Database.SqlValue>} [params=[]] Parameters to bind to the query
@param {function({Object}):void} callback A function that will be called on each row of result
@param {function()} done A function that will be called when all rows have been retrieved
@param {Array<Database.SqlValue>} [params] Parameters to bind to the query
@param {function({Object}):void} callback A function that will be called on
each row of result
@param {function()} done A function that will be called when all rows have
been retrieved

@return {Database} The database object. Useful for method chaining

Expand Down
2 changes: 1 addition & 1 deletion src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function onModuleReady(SQL) {
}
return postMessage({
id: data["id"],
results: db.exec(data["sql"])
results: db.exec(data["sql"], data["params"])
});
case "each":
if (db === null) {
Expand Down
43 changes: 32 additions & 11 deletions test/test_worker.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// TODO: Instead of using tiny-worker, we could use the new Node 11 workers via
// node --experimental-worker test/all.js
// TODO: Instead of using puppeteer, we could use the new Node 11 workers via
// node --experimental-worker test/all.js
// Then we could do this:
//const { Worker } = require('worker_threads');
// But it turns out that the worker_threads interface is just different enough not to work.
// But it turns out that the worker_threads interface is just different enough not to work.
var puppeteer = require("puppeteer");
var path = require("path");
var fs = require("fs");
Expand Down Expand Up @@ -40,7 +40,7 @@ exports.test = async function test(SQL, assert) {
console.error("Skipping worker test for " + file + ". Not implemented yet");
return;
};
// If we use tiny-worker, we need to pass in this new cwd as the root of the file being loaded:
// If we use puppeteer, we need to pass in this new cwd as the root of the file being loaded:
const filename = "../dist/worker." + file + ".js";
var worker = await Worker.fromFile(path.join(__dirname, filename));
var data = await worker.postMessage({ id: 1, action: 'open' });
Expand All @@ -50,20 +50,41 @@ exports.test = async function test(SQL, assert) {
data = await worker.postMessage({
id: 2,
action: 'exec',
params: {
":num2": 2,
"@str2": 'b',
// test_worker.js has issue message-passing Uint8Array
// but it works fine in real-world browser-usage
// "$hex2": new Uint8Array([0x00, 0x42]),
":num3": 3,
"@str3": 'c'
// "$hex3": new Uint8Array([0x00, 0x44])
},
sql: "CREATE TABLE test (num, str, hex);" +
"INSERT INTO test VALUES (1, 'a', x'0042');" +
"INSERT INTO test VALUES (:num2, @str2, x'0043');" +
// test passing params split across multi-statement "exec"
"INSERT INTO test VALUES (:num3, @str3, x'0044');" +
"SELECT * FROM test;"
});
assert.strictEqual(data.id, 2, "Correct id");
// debug error
assert.strictEqual(data.error, undefined, data.error);
var results = data.results;
assert.ok(Array.isArray(results), 'Correct result type');
assert.strictEqual(results.length, 1, 'Expected exactly 1 row');
var row = results[0];
assert.strictEqual(typeof row, 'object', 'Type of the returned row');
assert.deepEqual(row.columns, ['num', 'str', 'hex'], 'Reading column names');
assert.strictEqual(row.values[0][0], 1, 'Reading number');
assert.strictEqual(row.values[0][1], 'a', 'Reading string');
assert.deepEqual(obj2array(row.values[0][2]), [0x00, 0x42], 'Reading BLOB byte');
assert.strictEqual(results.length, 1, 'Expected exactly 1 table');
var table = results[0];
assert.strictEqual(typeof table, 'object', 'Type of the returned table');
assert.deepEqual(table.columns, ['num', 'str', 'hex'], 'Reading column names');
assert.strictEqual(table.values[0][0], 1, 'Reading number');
assert.strictEqual(table.values[0][1], 'a', 'Reading string');
assert.deepEqual(obj2array(table.values[0][2]), [0x00, 0x42], 'Reading BLOB byte');
assert.strictEqual(table.values[1][0], 2, 'Reading number');
assert.strictEqual(table.values[1][1], 'b', 'Reading string');
assert.deepEqual(obj2array(table.values[1][2]), [0x00, 0x43], 'Reading BLOB byte');
assert.strictEqual(table.values[2][0], 3, 'Reading number');
assert.strictEqual(table.values[2][1], 'c', 'Reading string');
assert.deepEqual(obj2array(table.values[2][2]), [0x00, 0x44], 'Reading BLOB byte');

data = await worker.postMessage({ action: 'export' });
var header = "SQLite format 3\0";
Expand Down