Skip to content

Commit dae60d7

Browse files
kaizhu256kai.zhulovasoa
authored
Add a second parameter to Database.exec to bind parameters to the statements (#364)
* - allow "exec" commmand to pass dictionary-params to webworker - add test passing dictionary-params to webworker * - add documentation * Update README.md Co-Authored-By: Ophir LOJKINE <ophir.lojkine@auto-grid.com> * fix exec-command example-code to be consistent with example-result * - fix english-grammar error "an sql" to "a sql" * - revert grammar-change in c6aa888 * - resolve conversation - Statement.BindParams - resolve conversation - replace "several queries" with "several statements" - try to limit documentation to 80-column width Co-authored-by: kai.zhu <kai.zhu@windebt.com> Co-authored-by: Ophir LOJKINE <ophir.lojkine@auto-grid.com>
1 parent d1d66bd commit dae60d7

File tree

4 files changed

+89
-42
lines changed

4 files changed

+89
-42
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,15 +204,16 @@ Example:
204204
205205
worker.postMessage({
206206
id: 2,
207-
action: 'exec',
208-
sql: 'SELECT * FROM test'
207+
action: "exec",
208+
sql: "SELECT age,name FROM test WHERE id=$id",
209+
params: { "$id": 1 }
209210
});
210211
};
211212
212213
worker.onerror = e => console.log("Worker error: ", e);
213214
worker.postMessage({
214215
id:1,
215-
action:'open',
216+
action:"open",
216217
buffer:buf, /*Optional. An ArrayBuffer representing an SQLite Database file*/
217218
});
218219
</script>

src/api.js

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
188188
* **Warning**: When you close a database (using db.close()),
189189
* all its statements are closed too and become unusable.
190190
*
191-
* Statements can't be created by the API user directly, only by Database::prepare
191+
* Statements can't be created by the API user directly, only by
192+
* Database::prepare
192193
*
193194
* @see Database.html#prepare-dynamic
194195
* @see https://en.wikipedia.org/wiki/Prepared_statement
@@ -321,7 +322,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
321322

322323
/** Get one row of results of a statement.
323324
If the first parameter is not provided, step must have been called before.
324-
@param {Statement.BindParams} [params=[]] If set, the values will be bound
325+
@param {Statement.BindParams} [params] If set, the values will be bound
325326
to the statement before it is executed
326327
@return {Database.SqlValue[]} One row of result
327328
@@ -381,8 +382,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
381382
return results1;
382383
};
383384

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

522-
/** Bind names and values of an object to the named parameters of the statement
523+
/** Bind names and values of an object to the named parameters of the
524+
statement
523525
@param {Object<string, Database.SqlValue>} valuesObj
524526
@private
525527
@nodoc
@@ -589,8 +591,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
589591
* Represents an SQLite database
590592
* @constructs Database
591593
* @memberof module:SqlJs
592-
* Open a new database either by creating a new one or opening an existing one,
593-
* stored in the byte array passed in first argument
594+
* Open a new database either by creating a new one or opening an existing
595+
* one stored in the byte array passed in first argument
594596
* @param {number[]} data An array of bytes representing
595597
* an SQLite database file
596598
*/
@@ -611,10 +613,10 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
611613

612614
/** Execute an SQL query, ignoring the rows it returns.
613615
@param {string} sql a string containing some SQL text to execute
614-
@param {any[]} [params=[]] When the SQL statement contains placeholders,
615-
you can pass them in here. They will be bound to the statement
616-
before it is executed. If you use the params argument, you **cannot** provide an sql string
617-
that contains several queries (separated by `;`)
616+
@param {Statement.BindParams} [params] When the SQL statement contains
617+
placeholders, you can pass them in here. They will be bound to the statement
618+
before it is executed. If you use the params argument, you **cannot**
619+
provide an sql string that contains several statements (separated by `;`)
618620
619621
@example
620622
// Insert values in a table
@@ -644,48 +646,66 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
644646
* @typedef {{columns:string[], values:Database.SqlValue[][]}} Database.QueryExecResult
645647
* @property {string[]} columns the name of the columns of the result
646648
* (as returned by {@link Statement.getColumnNames})
647-
* @property {Database.SqlValue[][]} values one array per row, containing the column values
649+
* @property {Database.SqlValue[][]} values one array per row, containing
650+
* the column values
648651
*/
649652

650653
/** Execute an SQL query, and returns the result.
651654
*
652655
* This is a wrapper against
653656
* {@link Database.prepare},
657+
* {@link Statement.bind},
654658
* {@link Statement.step},
655659
* {@link Statement.get},
656660
* and {@link Statement.free}.
657661
*
658-
* The result is an array of result elements. There are as many result elements
659-
* as the number of statements in your sql string (statements are separated
660-
* by a semicolon)
662+
* The result is an array of result elements. There are as many result
663+
* elements as the number of statements in your sql string (statements are
664+
* separated by a semicolon)
661665
*
662666
* ## Example use
663-
* We have the following table, named *test* :
667+
* We will create the following table, named *test* and query it with a
668+
* multi-line statement using params:
664669
*
665670
* | id | age | name |
666671
* |:--:|:---:|:------:|
667672
* | 1 | 1 | Ling |
668673
* | 2 | 18 | Paul |
669-
* | 3 | 3 | Markus |
670674
*
671675
* We query it like that:
672676
* ```javascript
673677
* var db = new SQL.Database();
674-
* var res = db.exec("SELECT id FROM test; SELECT age,name FROM test;");
678+
* var res = db.exec(
679+
* "DROP TABLE IF EXISTS test;\n"
680+
* + "CREATE TABLE test (id INTEGER, age INTEGER, name TEXT);"
681+
* + "INSERT INTO test VALUES ($id1, :age1, @name1);"
682+
* + "INSERT INTO test VALUES ($id2, :age2, @name2);"
683+
* + "SELECT id FROM test;"
684+
* + "SELECT age,name FROM test WHERE id=$id1",
685+
* {
686+
* "$id1": 1, ":age1": 1, "@name1": "Ling",
687+
* "$id2": 2, ":age2": 18, "@name2": "Paul"
688+
* }
689+
* );
675690
* ```
676691
*
677692
* `res` is now :
678693
* ```javascript
679694
* [
680-
* {columns: ['id'], values:[[1],[2],[3]]},
681-
* {columns: ['age','name'], values:[[1,'Ling'],[18,'Paul'],[3,'Markus']]}
695+
* {"columns":["id"],"values":[[1],[2]]},
696+
* {"columns":["age","name"],"values":[[1,"Ling"]]}
682697
* ]
683698
* ```
684699
*
685-
* @param {string} sql a string containing some SQL text to execute
700+
@param {string} sql a string containing some SQL text to execute
701+
@param {Statement.BindParams} [params] When the SQL statement contains
702+
placeholders, you can pass them in here. They will be bound to the statement
703+
before it is executed. If you use the params argument as an array,
704+
you **cannot** provide an sql string that contains several statements
705+
(separated by `;`). This limitation does not apply to params as an object.
686706
* @return {Database.QueryExecResult[]} The results of each statement
687707
*/
688-
Database.prototype["exec"] = function exec(sql) {
708+
Database.prototype["exec"] = function exec(sql, params) {
689709
var curresult;
690710
var stmt;
691711
if (!this.db) {
@@ -713,6 +733,9 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
713733
if (pStmt !== NULL) {
714734
curresult = null;
715735
stmt = new Statement(pStmt, this);
736+
if (params != null) {
737+
stmt.bind(params);
738+
}
716739
while (stmt["step"]()) {
717740
if (curresult === null) {
718741
curresult = {
@@ -739,14 +762,16 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
739762

740763
/** Execute an sql statement, and call a callback for each row of result.
741764
742-
**Currently** this method is synchronous, it will not return until the callback
743-
has been called on every row of the result. But this might change.
765+
**Currently** this method is synchronous, it will not return until the
766+
callback has been called on every row of the result. But this might change.
744767
745768
@param {string} sql A string of SQL text. Can contain placeholders
746769
that will be bound to the parameters given as the second argument
747-
@param {Array<Database.SqlValue>} [params=[]] Parameters to bind to the query
748-
@param {function({Object}):void} callback A function that will be called on each row of result
749-
@param {function()} done A function that will be called when all rows have been retrieved
770+
@param {Array<Database.SqlValue>} [params] Parameters to bind to the query
771+
@param {function({Object}):void} callback A function that will be called on
772+
each row of result
773+
@param {function()} done A function that will be called when all rows have
774+
been retrieved
750775
751776
@return {Database} The database object. Useful for method chaining
752777

src/worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function onModuleReady(SQL) {
3131
}
3232
return postMessage({
3333
id: data["id"],
34-
results: db.exec(data["sql"])
34+
results: db.exec(data["sql"], data["params"])
3535
});
3636
case "each":
3737
if (db === null) {

test/test_worker.js

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// TODO: Instead of using tiny-worker, we could use the new Node 11 workers via
2-
// node --experimental-worker test/all.js
1+
// TODO: Instead of using puppeteer, we could use the new Node 11 workers via
2+
// node --experimental-worker test/all.js
33
// Then we could do this:
44
//const { Worker } = require('worker_threads');
5-
// But it turns out that the worker_threads interface is just different enough not to work.
5+
// But it turns out that the worker_threads interface is just different enough not to work.
66
var puppeteer = require("puppeteer");
77
var path = require("path");
88
var fs = require("fs");
@@ -40,7 +40,7 @@ exports.test = async function test(SQL, assert) {
4040
console.error("Skipping worker test for " + file + ". Not implemented yet");
4141
return;
4242
};
43-
// If we use tiny-worker, we need to pass in this new cwd as the root of the file being loaded:
43+
// If we use puppeteer, we need to pass in this new cwd as the root of the file being loaded:
4444
const filename = "../dist/worker." + file + ".js";
4545
var worker = await Worker.fromFile(path.join(__dirname, filename));
4646
var data = await worker.postMessage({ id: 1, action: 'open' });
@@ -50,20 +50,41 @@ exports.test = async function test(SQL, assert) {
5050
data = await worker.postMessage({
5151
id: 2,
5252
action: 'exec',
53+
params: {
54+
":num2": 2,
55+
"@str2": 'b',
56+
// test_worker.js has issue message-passing Uint8Array
57+
// but it works fine in real-world browser-usage
58+
// "$hex2": new Uint8Array([0x00, 0x42]),
59+
":num3": 3,
60+
"@str3": 'c'
61+
// "$hex3": new Uint8Array([0x00, 0x44])
62+
},
5363
sql: "CREATE TABLE test (num, str, hex);" +
5464
"INSERT INTO test VALUES (1, 'a', x'0042');" +
65+
"INSERT INTO test VALUES (:num2, @str2, x'0043');" +
66+
// test passing params split across multi-statement "exec"
67+
"INSERT INTO test VALUES (:num3, @str3, x'0044');" +
5568
"SELECT * FROM test;"
5669
});
5770
assert.strictEqual(data.id, 2, "Correct id");
71+
// debug error
72+
assert.strictEqual(data.error, undefined, data.error);
5873
var results = data.results;
5974
assert.ok(Array.isArray(results), 'Correct result type');
60-
assert.strictEqual(results.length, 1, 'Expected exactly 1 row');
61-
var row = results[0];
62-
assert.strictEqual(typeof row, 'object', 'Type of the returned row');
63-
assert.deepEqual(row.columns, ['num', 'str', 'hex'], 'Reading column names');
64-
assert.strictEqual(row.values[0][0], 1, 'Reading number');
65-
assert.strictEqual(row.values[0][1], 'a', 'Reading string');
66-
assert.deepEqual(obj2array(row.values[0][2]), [0x00, 0x42], 'Reading BLOB byte');
75+
assert.strictEqual(results.length, 1, 'Expected exactly 1 table');
76+
var table = results[0];
77+
assert.strictEqual(typeof table, 'object', 'Type of the returned table');
78+
assert.deepEqual(table.columns, ['num', 'str', 'hex'], 'Reading column names');
79+
assert.strictEqual(table.values[0][0], 1, 'Reading number');
80+
assert.strictEqual(table.values[0][1], 'a', 'Reading string');
81+
assert.deepEqual(obj2array(table.values[0][2]), [0x00, 0x42], 'Reading BLOB byte');
82+
assert.strictEqual(table.values[1][0], 2, 'Reading number');
83+
assert.strictEqual(table.values[1][1], 'b', 'Reading string');
84+
assert.deepEqual(obj2array(table.values[1][2]), [0x00, 0x43], 'Reading BLOB byte');
85+
assert.strictEqual(table.values[2][0], 3, 'Reading number');
86+
assert.strictEqual(table.values[2][1], 'c', 'Reading string');
87+
assert.deepEqual(obj2array(table.values[2][2]), [0x00, 0x44], 'Reading BLOB byte');
6788

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

0 commit comments

Comments
 (0)