|
3 | 3 | FS
|
4 | 4 | HEAP8
|
5 | 5 | Module
|
| 6 | + _malloc |
6 | 7 | _free
|
7 | 8 | addFunction
|
8 | 9 | allocate
|
|
14 | 15 | stackAlloc
|
15 | 16 | stackRestore
|
16 | 17 | stackSave
|
| 18 | + UTF8ToString |
| 19 | + stringToUTF8 |
| 20 | + lengthBytesUTF8 |
17 | 21 | */
|
18 | 22 |
|
19 | 23 | "use strict";
|
@@ -80,6 +84,12 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
|
80 | 84 | "number",
|
81 | 85 | ["number", "string", "number", "number", "number"]
|
82 | 86 | );
|
| 87 | + var sqlite3_sql = cwrap("sqlite3_sql", "string", ["number"]); |
| 88 | + var sqlite3_normalized_sql = cwrap( |
| 89 | + "sqlite3_normalized_sql", |
| 90 | + "string", |
| 91 | + ["number"] |
| 92 | + ); |
83 | 93 | var sqlite3_prepare_v2_sqlptr = cwrap(
|
84 | 94 | "sqlite3_prepare_v2",
|
85 | 95 | "number",
|
@@ -446,6 +456,29 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
|
446 | 456 | return rowObject;
|
447 | 457 | };
|
448 | 458 |
|
| 459 | + /** Get the SQL string used in preparing this statement. |
| 460 | + @return {string} The SQL string |
| 461 | + */ |
| 462 | + Statement.prototype["getSQL"] = function getSQL() { |
| 463 | + return sqlite3_sql(this.stmt); |
| 464 | + }; |
| 465 | + |
| 466 | + /** Get the SQLite's normalized version of the SQL string used in |
| 467 | + preparing this statement. The meaning of "normalized" is not |
| 468 | + well-defined: see {@link https://sqlite.org/c3ref/expanded_sql.html |
| 469 | + the SQLite documentation}. |
| 470 | +
|
| 471 | + @example |
| 472 | + db.run("create table test (x integer);"); |
| 473 | + stmt = db.prepare("select * from test where x = 42"); |
| 474 | + // returns "SELECT*FROM test WHERE x=?;" |
| 475 | +
|
| 476 | + @return {string} The normalized SQL string |
| 477 | + */ |
| 478 | + Statement.prototype["getNormalizedSQL"] = function getNormalizedSQL() { |
| 479 | + return sqlite3_normalized_sql(this.stmt); |
| 480 | + }; |
| 481 | + |
449 | 482 | /** Shorthand for bind + step + reset
|
450 | 483 | Bind the values, execute the statement, ignoring the rows it returns,
|
451 | 484 | and resets it
|
@@ -605,6 +638,138 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
|
605 | 638 | return res;
|
606 | 639 | };
|
607 | 640 |
|
| 641 | + /** |
| 642 | + * @classdesc |
| 643 | + * An iterator over multiple SQL statements in a string, |
| 644 | + * preparing and returning a Statement object for the next SQL |
| 645 | + * statement on each iteration. |
| 646 | + * |
| 647 | + * You can't instantiate this class directly, you have to use a |
| 648 | + * {@link Database} object in order to create a statement iterator |
| 649 | + * |
| 650 | + * {@see Database#iterateStatements} |
| 651 | + * |
| 652 | + * @example |
| 653 | + * // loop over and execute statements in string sql |
| 654 | + * for (let statement of db.iterateStatements(sql) { |
| 655 | + * statement.step(); |
| 656 | + * // get results, etc. |
| 657 | + * // do not call statement.free() manually, each statement is freed |
| 658 | + * // before the next one is parsed |
| 659 | + * } |
| 660 | + * |
| 661 | + * // capture any bad query exceptions with feedback |
| 662 | + * // on the bad sql |
| 663 | + * let it = db.iterateStatements(sql); |
| 664 | + * try { |
| 665 | + * for (let statement of it) { |
| 666 | + * statement.step(); |
| 667 | + * } |
| 668 | + * } catch(e) { |
| 669 | + * console.log( |
| 670 | + * `The SQL string "${it.getRemainingSQL()}" ` + |
| 671 | + * `contains the following error: ${e}` |
| 672 | + * ); |
| 673 | + * } |
| 674 | + * |
| 675 | + * @implements {Iterator<Statement>} |
| 676 | + * @implements {Iterable<Statement>} |
| 677 | + * @constructs StatementIterator |
| 678 | + * @memberof module:SqlJs |
| 679 | + * @param {string} sql A string containing multiple SQL statements |
| 680 | + * @param {Database} db The database from which this iterator was created |
| 681 | + */ |
| 682 | + function StatementIterator(sql, db) { |
| 683 | + this.db = db; |
| 684 | + var sz = lengthBytesUTF8(sql) + 1; |
| 685 | + this.sqlPtr = _malloc(sz); |
| 686 | + if (this.sqlPtr === null) { |
| 687 | + throw new Error("Unable to allocate memory for the SQL string"); |
| 688 | + } |
| 689 | + stringToUTF8(sql, this.sqlPtr, sz); |
| 690 | + this.nextSqlPtr = this.sqlPtr; |
| 691 | + this.nextSqlString = null; |
| 692 | + this.activeStatement = null; |
| 693 | + } |
| 694 | + |
| 695 | + /** |
| 696 | + * @typedef {{ done:true, value:undefined } | |
| 697 | + * { done:false, value:Statement}} |
| 698 | + * StatementIterator.StatementIteratorResult |
| 699 | + * @property {Statement} value the next available Statement |
| 700 | + * (as returned by {@link Database.prepare}) |
| 701 | + * @property {boolean} done true if there are no more available statements |
| 702 | + */ |
| 703 | + |
| 704 | + /** Prepare the next available SQL statement |
| 705 | + @return {StatementIterator.StatementIteratorResult} |
| 706 | + @throws {String} SQLite error or invalid iterator error |
| 707 | + */ |
| 708 | + StatementIterator.prototype["next"] = function next() { |
| 709 | + if (this.sqlPtr === null) { |
| 710 | + return { done: true }; |
| 711 | + } |
| 712 | + if (this.activeStatement !== null) { |
| 713 | + this.activeStatement["free"](); |
| 714 | + this.activeStatement = null; |
| 715 | + } |
| 716 | + if (!this.db.db) { |
| 717 | + this.finalize(); |
| 718 | + throw new Error("Database closed"); |
| 719 | + } |
| 720 | + var stack = stackSave(); |
| 721 | + var pzTail = stackAlloc(4); |
| 722 | + setValue(apiTemp, 0, "i32"); |
| 723 | + setValue(pzTail, 0, "i32"); |
| 724 | + try { |
| 725 | + this.db.handleError(sqlite3_prepare_v2_sqlptr( |
| 726 | + this.db.db, |
| 727 | + this.nextSqlPtr, |
| 728 | + -1, |
| 729 | + apiTemp, |
| 730 | + pzTail |
| 731 | + )); |
| 732 | + this.nextSqlPtr = getValue(pzTail, "i32"); |
| 733 | + var pStmt = getValue(apiTemp, "i32"); |
| 734 | + if (pStmt === NULL) { |
| 735 | + this.finalize(); |
| 736 | + return { done: true }; |
| 737 | + } |
| 738 | + this.activeStatement = new Statement(pStmt, this.db); |
| 739 | + this.db.statements[pStmt] = this.activeStatement; |
| 740 | + return { value: this.activeStatement, done: false }; |
| 741 | + } catch (e) { |
| 742 | + this.nextSqlString = UTF8ToString(this.nextSqlPtr); |
| 743 | + this.finalize(); |
| 744 | + throw e; |
| 745 | + } finally { |
| 746 | + stackRestore(stack); |
| 747 | + } |
| 748 | + }; |
| 749 | + |
| 750 | + StatementIterator.prototype.finalize = function finalize() { |
| 751 | + _free(this.sqlPtr); |
| 752 | + this.sqlPtr = null; |
| 753 | + }; |
| 754 | + |
| 755 | + /** Get any un-executed portions remaining of the original SQL string |
| 756 | + @return {String} |
| 757 | + */ |
| 758 | + StatementIterator.prototype["getRemainingSQL"] = function getRemainder() { |
| 759 | + // iff an exception occurred, we set the nextSqlString |
| 760 | + if (this.nextSqlString !== null) return this.nextSqlString; |
| 761 | + // otherwise, convert from nextSqlPtr |
| 762 | + return UTF8ToString(this.nextSqlPtr); |
| 763 | + }; |
| 764 | + |
| 765 | + /* implement Iterable interface */ |
| 766 | + |
| 767 | + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { |
| 768 | + StatementIterator.prototype[Symbol.iterator] = function iterator() { |
| 769 | + return this; |
| 770 | + }; |
| 771 | + } |
| 772 | + |
608 | 773 | /** @classdesc
|
609 | 774 | * Represents an SQLite database
|
610 | 775 | * @constructs Database
|
@@ -844,6 +1009,27 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
|
844 | 1009 | return stmt;
|
845 | 1010 | };
|
846 | 1011 |
|
| 1012 | + /** Iterate over multiple SQL statements in a SQL string. |
| 1013 | + * This function returns an iterator over {@link Statement} objects. |
| 1014 | + * You can use a for..of loop to execute the returned statements one by one. |
| 1015 | + * @param {string} sql a string of SQL that can contain multiple statements |
| 1016 | + * @return {StatementIterator} the resulting statement iterator |
| 1017 | + * @example <caption>Get the results of multiple SQL queries</caption> |
| 1018 | + * const sql_queries = "SELECT 1 AS x; SELECT '2' as y"; |
| 1019 | + * for (const statement of db.iterateStatements(sql_queries)) { |
| 1020 | + * statement.step(); // Execute the statement |
| 1021 | + * const sql = statement.getSQL(); // Get the SQL source |
| 1022 | + * const result = statement.getAsObject(); // Get the row of data |
| 1023 | + * console.log(sql, result); |
| 1024 | + * } |
| 1025 | + * // This will print: |
| 1026 | + * // 'SELECT 1 AS x;' { x: 1 } |
| 1027 | + * // " SELECT '2' as y" { y: '2' } |
| 1028 | + */ |
| 1029 | + Database.prototype["iterateStatements"] = function iterateStatements(sql) { |
| 1030 | + return new StatementIterator(sql, this); |
| 1031 | + }; |
| 1032 | + |
847 | 1033 | /** Exports the contents of the database to a binary array
|
848 | 1034 | @return {Uint8Array} An array of bytes of the SQLite3 database file
|
849 | 1035 | */
|
|
0 commit comments