]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
WASM: added exec(), execMulti(), and several getters. Various touchups and fixes.
authorstephan <stephan@noemail.net>
Sun, 22 May 2022 22:00:39 +0000 (22:00 +0000)
committerstephan <stephan@noemail.net>
Sun, 22 May 2022 22:00:39 +0000 (22:00 +0000)
FossilOrigin-Name: b790c91b85e9cf8eecce86ac1717e8ccd2c3b6b98a1ad6a5d64eefc94ee86f9d

ext/fiddle/EXPORTED_FUNCTIONS.sqlite3
ext/fiddle/sqlite3-api.js
ext/fiddle/testing1.js
manifest
manifest.uuid

index ae34ccdb19ad7a5a7f0eb18f3218908c0a2da5ee..da14513415b6ff07bb9cfa462cc9362c9b348af2 100644 (file)
@@ -40,6 +40,7 @@ _sqlite3_result_int
 _sqlite3_result_null
 _sqlite3_result_text
 _sqlite3_sourceid
+_sqlite3_sql
 _sqlite3_step
 _sqlite3_value_blob
 _sqlite3_value_bytes
index d4423a2175dd3169c5dd6f961182d279823fed7e..52ea1f982ceeac43f98c2040cd296ca6740f4205 100644 (file)
   This file installs namespace.sqlite3, where namespace is `self`,
   meaning either the global window or worker, depending on where this
   is loaded from.
+
+  # Goals and Non-goals of this API
+
+  Goals:
+
+  - Except where noted in the non-goals, provide a more-or-less
+    complete wrapper to the sqlite3 C API, insofar as WASM feature
+    parity with C allows for.
+
+  Non-goals:
+
+  - As WASM is a web-based technology and UTF-8 is the King of
+    Encodings in that realm, there are no plans to support the
+    UTF16-related APIs will not be. They would add a complication to
+    the bindings for no appreciable benefit.
+
 */
 (function(namespace){
     /* For reference: sql.js does essentially everything we want and
         ["sqlite3_result_null",null,["number"]],
         ["sqlite3_result_text",null,["number", "string", "number", "number"]],
         ["sqlite3_sourceid", "string", []],
+        ["sqlite3_sql", "string", ["number"]],
         ["sqlite3_step", "number", ["number"]],
         ["sqlite3_value_blob", "number", ["number"]],
         ["sqlite3_value_bytes","number",["number"]],
         ["sqlite3_value_double","number",["number"]],
         ["sqlite3_value_text", "string", ["number"]],
         ["sqlite3_value_type", "number", ["number"]]
-        //["sqlite3_sql", "string", ["number"]],
         //["sqlite3_normalized_sql", "string", ["number"]]
     ].forEach(function(a){
         const k = (4==a.length) ? a.shift() : a[0];
     /* What follows is colloquially known as "OO API #1". It is a
        binding of the sqlite3 API which is designed to be run within
        the same thread (main or worker) as the one in which the
-       sqlite3 WASM binding was initialized.  This wrapper cannot use
+       sqlite3 WASM binding was initialized. This wrapper cannot use
        the sqlite3 binding if, e.g., the wrapper is in the main thread
        and the sqlite3 API is in a worker. */
-    /* memory for use in some pointer-passing routines */
+
+    /** Memory for use in some pointer-to-pointer-passing routines. */
     const pPtrArg = stackAlloc(4);
     /** Throws a new error, concatenating all args with a space between
         each. */
         throw new Error(Array.prototype.join.call(arguments, ' '));
     };
 
-    const sqlite3/*canonical name*/ = S/*convenience alias*/ = api;
-    
+    const S/*convenience alias*/ = api;
+
     /**
        The DB class wraps a sqlite3 db handle.
     */
         else if('string'!==typeof name){
             toss("TODO: support blob image of db here.");
         }
+        setValue(pPtrArg, 0, "i32");
         this.checkRc(S.sqlite3_open(name, pPtrArg));
         this._pDb = getValue(pPtrArg, "i32");
         this.filename = name;
        new instances.
     */
     const Stmt = function(){
-        if(BindTypes!=arguments[2]){
+        if(BindTypes!==arguments[2]){
             toss("Do not call the Stmt constructor directly. Use DB.prepare().");
         }
         this.db = arguments[0];
         return db;
     };
 
+    /**
+       Expects to be passed (arguments) from DB.exec() and
+       DB.execMulti(). Does the argument processing/validation, throws
+       on error, and returns a new object on success:
+
+       { sql: the SQL, obt: optionsObj, cbArg: function}
+
+       cbArg is only set if the opt.callback is set, in which case
+       it's a function which expects to be passed the current Stmt
+       and returns the callback argument of the type indicated by
+       the input arguments.
+    */
+    const parseExecArgs = function(args){
+        const out = {};
+        switch(args.length){
+            case 1:
+                if('string'===typeof args[0]){
+                    out.sql = args[0];
+                    out.opt = {};
+                }else if(args[0] && 'object'===typeof args[0]){
+                    out.opt = args[0];
+                    out.sql = out.opt.sql;
+                }
+            break;
+            case 2:
+                out.sql = args[0];
+                out.opt = args[1];
+            break;
+            default: toss("Invalid argument count for exec().");
+        };
+        if('string'!==typeof out.sql) toss("Missing SQL argument.");
+        if(out.opt.callback){
+            switch((undefined===out.opt.rowMode)
+                    ? 'stmt' : out.opt.rowMode) {
+                case 'object': out.cbArg = (stmt)=>stmt.get({}); break;
+                case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
+                case 'stmt': out.cbArg = (stmt)=>stmt; break;
+                default: toss("Invalid rowMode:",out.opt.rowMode);
+            }
+        }
+        return out;
+    };
+
     DB.prototype = {
         /**
            Expects to be given an sqlite3 API result code. If it is
            falsy, this function returns this object, else it throws an
            exception with an error message from sqlite3_errmsg(),
-           using this object's db handle.
+           using this object's db handle. Note that if it's passed a
+           non-error code like SQLITE_ROW or SQLITE_DONE, it will
+           still throw but the error string might be "Not an error."
+           The various non-0 non-error codes need to be checked for in
+           client code where they are expected.
         */
         checkRc: function(sqliteResultCode){
             if(!sqliteResultCode) return this;
-            toss(S.sqlite3_errmsg(this._pDb) || "Unknown db error.");
+            toss("sqlite result code",sqliteResultCode+":",
+                 S.sqlite3_errmsg(this._pDb) || "Unknown db error.");
         },
         /**
            Finalizes all open statements and closes this database
             const stmt = new Stmt(this, pStmt, BindTypes);
             this._statements[pStmt] = stmt;
             return stmt;
-        }
+        },
+        /**
+           This function works like execMulti(), and takes the same
+           arguments, but is more efficient (performs much less work)
+           when the input SQL is only a single statement. If passed a
+           multi-statement SQL, it only processes the first one.
+
+           This function supports one additional option not used by
+           execMulti():
+
+           - .multi: if true, this function acts as a proxy for
+             execMulti().
+        */
+        exec: function(/*(sql [,optionsObj]) or (optionsObj)*/){
+            affirmDbOpen(this);
+            const arg = parseExecArgs(arguments);
+            if(!arg.sql) return this;
+            else if(arg.multi){
+                return this.execMulti(arg, undefined, BindTypes);
+            }
+            const opt = arg.opt;
+            let stmt;
+            try {
+                stmt = this.prepare(arg.sql);
+                if(opt.bind) stmt.bind(bind);
+                if(opt.callback){
+                    while(stmt.step()){
+                        stmt._isLocked = true;
+                        opt.callback(arg.cbArg(stmt), stmt);
+                        stmt._isLocked = false;
+                    }
+                }else{
+                    stmt.step();
+                }
+            }finally{
+                if(stmt){
+                    delete stmt._isLocked;
+                    stmt.finalize();
+                }
+            }
+            return this;
+
+        }/*exec()*/,
+        /**
+           Executes one or more SQL statements. Its arguments
+           must be either (sql,optionsObject) or (optionsObject).
+           In the latter case, optionsObject.sql must contain the
+           SQL to execute. Returns this object. Throws on error.
+
+           If no SQL is provided, or a non-string is provided, an
+           exception is triggered. Empty SQL, on the other hand, is
+           simply a no-op.
+
+           The optional options object may contain any of the following
+           properties:
+
+           - .sql = the SQL to run (unless it's provided as the first
+             argument).
+
+           - .bind = a single value valid as an argument for
+             Stmt.bind(). This is ONLY applied to the FIRST non-empty
+             statement in the SQL which has any bindable
+             parameters. (Empty statements are skipped entirely.)
+
+           - .callback = a function which gets called for each row of
+             the FIRST statement in the SQL (if it has any result
+             rows). The second argument passed to the callback is
+             always the current Stmt object (so that the caller
+             may collect column names, or similar). The first
+             argument passed to the callback defaults to the current
+             Stmt object but may be changed with ...
+
+           - .rowMode = a string describing what type of argument
+             should be passed as the first argument to the callback. A
+             value of 'object' causes the results of `stmt.get({})` to
+             be passed to the object. A value of 'array' causes the
+             results of `stmt.get([])` to be passed to the callback.
+             A value of 'stmt' is equivalent to the default, passing
+             the current Stmt to the callback (noting that it's always
+             passed as the 2nd argument). Any other value triggers an
+             exception.
+
+           - saveSql = an optional array. If set, the SQL of each
+             executed statement is appended to this array before the
+             statement is executed (but after it is prepared - we
+             don't have the string until after that). Empty SQL
+             statements are elided.
+
+           ACHTUNG #1: The callback MUST NOT modify the Stmt
+           object. Calling any of the Stmt.get() variants,
+           Stmt.getColumnName(), or simililar, is legal, but calling
+           step() or finalize() is not. Routines which are illegal
+           in this context will trigger an exception.
+
+           ACHTUNG #2: The semantics of the `bind` and `callback`
+           options may well change or those options may be removed
+           altogether for this function (but retained for exec()).
+        */
+        execMulti: function(/*(sql [,obj]) || (obj)*/){
+            affirmDbOpen(this);
+            const arg = (BindTypes===arguments[2]
+                         /* ^^^ Being passed on from exec() */
+                         ? arguments[0] : parseExecArgs(arguments));
+            if(!arg.sql) return this;
+            const opt = arg.opt;
+            const stack = stackSave();
+            let stmt;
+            let bind = opt.bind;
+            let rowMode = (
+                (opt.callback && opt.rowMode)
+                    ? opt.rowMode : false);
+            try{
+                let pSql = allocateUTF8OnStack(arg.sql)
+                const pzTail = stackAlloc(4);
+                while(getValue(pSql, "i8")){
+                    setValue(pPtrArg, 0, "i32");
+                    setValue(pzTail, 0, "i32");
+                    this.checkRc(S.sqlite3_prepare_v2_sqlptr(
+                        this._pDb, pSql, -1, pPtrArg, pzTail
+                    ));
+                    const pStmt = getValue(pPtrArg, "i32");
+                    pSql = getValue(pzTail, "i32");
+                    if(!pStmt) continue;
+                    if(opt.saveSql){
+                        opt.saveSql.push(S.sqlite3_sql(pStmt).trim());
+                    }
+                    stmt = new Stmt(this, pStmt, BindTypes);
+                    if(bind && stmt.parameterCount){
+                        stmt.bind(bind);
+                        bind = null;
+                    }
+                    if(opt.callback && null!==rowMode){
+                        while(stmt.step()){
+                            stmt._isLocked = true;
+                            callback(arg.cbArg(stmt), stmt);
+                            stmt._isLocked = false;
+                        }
+                        rowMode = null;
+                    }else{
+                        // Do we need to while(stmt.step()){} here?
+                        stmt.step();
+                    }
+                    stmt.finalize();
+                    stmt = null;
+                }
+            }finally{
+                if(stmt){
+                    delete stmt._isLocked;
+                    stmt.finalize();
+                }
+                stackRestore(stack);
+            }
+            return this;
+        }/*execMulti()*/
+    }/*DB.prototype*/;
+
+
+    /** Throws if the given Stmt has been finalized, else stmt is
+        returned. */
+    const affirmStmtOpen = function(stmt){
+        if(!stmt._pStmt) toss("Stmt has been closed.");
+        return stmt;
     };
 
     /** Returns an opaque truthy value from the BindTypes
         if(0===n || (n===key && (n!==(n|0)/*floating point*/))){
             toss("Invalid bind() parameter name: "+key);
         }
-        else if(n<1 || n>=stmt.parameterCount) toss("Bind index",key,"is out of range.");
+        else if(n<1 || n>stmt.parameterCount) toss("Bind index",key,"is out of range.");
         return n;
     };
 
     /** Throws if ndx is not an integer or if it is out of range
-        for stmt.columnCount, else returns stmt. */
+        for stmt.columnCount, else returns stmt.
+
+        Reminder: this will also fail after the statement is finalized
+        but the resulting error will be about an out-of-bounds column
+        index.
+    */
     const affirmColIndex = function(stmt,ndx){
         if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){
             toss("Column index",ndx,"is out of range.");
         }
         return stmt;
     };
-    
+
+    /**
+       If stmt._isLocked is truthy, this throws an exception
+       complaining that the 2nd argument (an operation name,
+       e.g. "bind()") is not legal while the statement is "locked".
+       Locking happens before an exec()-like callback is passed a
+       statement, to ensure that the callback does not mutate or
+       finalize the statement. If it does not throw, it returns stmt.
+    */
+    const affirmUnlocked = function(stmt,currentOpName){
+        if(stmt._isLocked){
+            toss("Operation is illegal when statement is locked:",currentOpName);
+        }
+        return stmt;
+    };
+
     /**
        Binds a single bound parameter value on the given stmt at the
        given index (numeric or named) using the given bindType (see
        success.
     */
     const bindOne = function f(stmt,ndx,bindType,val){
+        affirmUnlocked(stmt, 'bind()');
         if(!f._){
             f._ = {
                 string: function(stmt, ndx, val, asBlob){
         ndx = affirmParamIndex(stmt,ndx);
         let rc = 0;
         switch((null===val || undefined===val) ? BindTypes.null : bindType){
-            case BindType.null:
+            case BindTypes.null:
                 rc = S.sqlite3_bind_null(stmt._pStmt, ndx);
                 break;
-            case BindType.string:{
+            case BindTypes.string:{
                 rc = f._.string(stmt, ndx, val, false);
                 break;
             }
-            case BindType.number: {
+            case BindTypes.number: {
                 const m = ((val === (val|0))
                            ? ((val & 0x00000000/*>32 bits*/)
                               ? S.sqlite3_bind_int64
                 rc = m(stmt._pStmt, ndx, val);
                 break;
             }
-            case BindType.boolean:
+            case BindTypes.boolean:
                 rc = S.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0);
                 break;
-            case BindType.blob: {
+            case BindTypes.blob: {
                 if('string'===typeof val){
                     rc = f._.string(stmt, ndx, val, true);
                 }else{
         return stmt;
     };
 
-    /** Throws if the given Stmt has been finalized, else
-        it is returned. */
-    const affirmStmtOpen = function(stmt){
-        if(!stmt._pStmt) toss("Stmt has been closed.");
-        return stmt;
-    };
-
     /** Frees any memory explicitly allocated for the given
         Stmt object. Returns stmt. */
     const freeBindMemory = function(stmt){
         */
         finalize: function(){
             if(this._pStmt){
+                affirmUnlocked(this,'finalize()');
                 freeBindMemory(this);
                 delete this.db._statements[this._pStmt];
                 S.sqlite3_finalize(this._pStmt);
                 delete this.parameterCount;
                 delete this._pStmt;
                 delete this.db;
+                delete this._isLocked;
             }
         },
         /** Clears all bound values. Returns this object.
             Throws if this statement has been finalized. */
         clearBindings: function(){
-            freeBindMemory(affirmStmtOpen(this));
+            freeBindMemory(
+                affirmUnlocked(affirmStmtOpen(this), 'clearBindings()')
+            );
             S.sqlite3_clear_bindings(this._pStmt);
             this._mayGet = false;
             return this;
            any memory allocated for them, are retained.
         */
         reset: function(alsoClearBinds){
+            affirmUnlocked(this,'reset()');
             if(alsoClearBinds) this.clearBindings();
             S.sqlite3_reset(affirmStmtOpen(this)._pStmt);
             this._mayGet = false;
             }
             if(null===arg || undefined===arg){
                 /* bind NULL */
-                return bindOne(this, ndx, BindType.null, arg);
+                return bindOne(this, ndx, BindTypes.null, arg);
             }
             else if(Array.isArray(arg)){
                 /* bind each entry by index */
                && BindTypes.null !== t){
                 toss("Invalid value type for bindAsBlob()");
             }
-            return bindOne(this, ndx, BindType.blob, arg);
+            return bindOne(this, ndx, BindTypes.blob, arg);
         },
         /**
            Steps the statement one time. If the result indicates that
            data is available, false is returned.  Throws on error.
         */
         step: function(){
+            affirmUnlocked(this, 'step()');
             const rc = S.sqlite3_step(affirmStmtOpen(this)._pStmt);
             this._mayGet = false;
             switch(rc){
                 case S.SQLITE_DONE: return false;
                 case S.SQLITE_ROW: return this._mayGet = true;
-                default: this.db.checkRc(rc);
+                default:
+                    console.warn("sqlite3_step() rc=",rc,"SQL =",
+                                 S.sqlite3_sql(this._pStmt));
+                    this.db.checkRc(rc);
             };
         },
         /**
            A convenience wrapper around get() which fetches the value
            as a string and then, if it is not null, passes it to
            JSON.parse(), returning that result. Throws if parsing
-           fails.
+           fails. If the result is null, null is returned. An empty
+           string, on the other hand, will trigger an exception.
         */
         getJSON: function(ndx){
             const s = this.get(ndx, S.SQLITE_STRING);
            finalized.
         */
         getColumnName: function(ndx){
-            return S.sqlite3_column_name(affirmColIndex(this,ndx)._pStmt, ndx);
+            return S.sqlite3_column_name(
+                affirmColIndex(affirmStmtOpen(this),ndx)._pStmt, ndx
+            );
+        },
+        /**
+           If this statement potentially has result columns, this
+           function returns an array of all such names. If passed an
+           array, it is used as the target and all names are appended
+           to it. Returns the target array. Throws if this statement
+           cannot have result columns. This object's columnCount member
+           holds the number of columns.
+        */
+        getColumnNames: function(tgt){
+            affirmColIndex(affirmStmtOpen(this),0);
+            if(!tgt) tgt = [];
+            for(let i = 0; i < this.columnCount; ++i){
+                tgt.push(S.sqlite3_column_name(this._pStmt, i));
+            }
+            return tgt;
+        },
+        /**
+           If this statement has named bindable parameters and the
+           given name matches one, its 1-based bind index is
+           returned. If no match is found, 0 is returned. If it has no
+           bindable parameters, the undefined value is returned.
+        */
+        getParamIndex: function(name){
+            return (affirmStmtOpen(this).parameterCount
+                    ? S.sqlite3_bind_parameter_index(this._pStmt, name)
+                    : undefined);
         }
-    };
+    }/*Stmt.prototype*/;
 
     /** OO binding's namespace. */
     const SQLite3 = {
         version: {
-            lib: sqlite3.sqlite3_libversion(),
+            lib: S.sqlite3_libversion(),
             ooApi: "0.0.1"
         },
         DB,
         Stmt,
         /**
            Reports whether a given compile-time option, named by the
-           given argument.
+           given argument. It has several distinct uses:
 
            If optName is an array then it is expected to be a list of
            compilation options and this function returns an object
-           which maps each such option to true or false. That object
-           is returned.
+           which maps each such option to true or false, indicating
+           whether or not the given option was included in this
+           build. That object is returned.
 
            If optName is an object, its keys are expected to be
            compilation options and this function sets each entry to
 
            If passed no arguments then it returns an object mapping
            all known compilation options to their compile-time values,
-           or true if the are defined with no value.
+           or boolean true if they are defined with no value.
 
-           In all other cases it returns true if the option was active
-           when when compiling the sqlite3 module, else false.
+           In all other cases it returns true if the given option was
+           active when when compiling the sqlite3 module, else false.
 
            Compile-time option names may optionally include their
            "SQLITE_" prefix. When it returns an object of all options,
             ) ? !!S.sqlite3_compileoption_used(optName) : false;
         }
     };
-    
+
     namespace.sqlite3 = {
-        api:sqlite3,
+        api: api,
         SQLite3
     };
 })(self/*worker or window*/);
index d667a480c53c30dcbc6b29d26995e0c27e96bb55..b7dcfe6b007f4c011c357c10e2eea6f8a7fdb0f9 100644 (file)
@@ -20,43 +20,77 @@ const mainTest1 = function(namespace){
     console.log("Loaded module:",S.sqlite3_libversion(),
                 S.sqlite3_sourceid());
     const db = new oo.DB();
-    const log = console.log.bind(console);
-    T.assert(db._pDb);
-    log("DB:",db.filename);
-    log("Build options:",oo.compileOptionUsed());
-    let st = db.prepare("select 3 as a");
-    log("statement =",st);
-    T.assert(st._pStmt)
-        .assert(!st._mayGet)
-        .assert('a' === st.getColumnName(0))
-        .assert(st === db._statements[st._pStmt])
-        .assert(1===st.columnCount)
-        .assert(0===st.parameterCount)
-        .mustThrow(()=>st.bind(1,null))
-        .assert(true===st.step())
-        .assert(3 === st.get(0))
-        .mustThrow(()=>st.get(1))
-        .mustThrow(()=>st.get(0,~S.SQLITE_INTEGER))
-        .assert(3 === st.get(0,S.SQLITE_INTEGER))
-        .assert(3 === st.getInt(0))
-        .assert('3' === st.get(0,S.SQLITE_TEXT))
-        .assert('3' === st.getString(0))
-        .assert(3.0 === st.get(0,S.SQLITE_FLOAT))
-        .assert(3.0 === st.getFloat(0))
-        .assert(st.get(0,S.SQLITE_BLOB) instanceof Uint8Array)
-        .assert(st.getBlob(0) instanceof Uint8Array)
-        .assert(3 === st.get([])[0])
-        .assert(3 === st.get({}).a)
-        .assert(3 === st.getJSON(0))
-        .assert(st._mayGet)
-        .assert(false===st.step())
-        .assert(!st._mayGet)
-    ;
-    let pId = st._pStmt;
-    st.finalize();
-    T.assert(!st._pStmt)
-        .assert(!db._statements[pId]);
-    log("Test count:",T.counter);
+    try {
+        const log = console.log.bind(console);
+        T.assert(db._pDb);
+        log("DB:",db.filename);
+        log("Build options:",oo.compileOptionUsed());
+        let st = db.prepare("select 3 as a");
+        log("statement =",st);
+        T.assert(st._pStmt)
+            .assert(!st._mayGet)
+            .assert('a' === st.getColumnName(0))
+            .assert(st === db._statements[st._pStmt])
+            .assert(1===st.columnCount)
+            .assert(0===st.parameterCount)
+            .mustThrow(()=>st.bind(1,null))
+            .assert(true===st.step())
+            .assert(3 === st.get(0))
+            .mustThrow(()=>st.get(1))
+            .mustThrow(()=>st.get(0,~S.SQLITE_INTEGER))
+            .assert(3 === st.get(0,S.SQLITE_INTEGER))
+            .assert(3 === st.getInt(0))
+            .assert('3' === st.get(0,S.SQLITE_TEXT))
+            .assert('3' === st.getString(0))
+            .assert(3.0 === st.get(0,S.SQLITE_FLOAT))
+            .assert(3.0 === st.getFloat(0))
+            .assert(st.get(0,S.SQLITE_BLOB) instanceof Uint8Array)
+            .assert(st.getBlob(0) instanceof Uint8Array)
+            .assert(3 === st.get([])[0])
+            .assert(3 === st.get({}).a)
+            .assert(3 === st.getJSON(0))
+            .assert(st._mayGet)
+            .assert(false===st.step())
+            .assert(!st._mayGet)
+        ;
+        let pId = st._pStmt;
+        st.finalize();
+        T.assert(!st._pStmt)
+            .assert(!db._statements[pId]);
+
+        let list = [];
+        db.execMulti({
+            sql:`CREATE TABLE t(a,b);
+INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
+            saveSql: list,
+            bind: [5,6]
+        });
+        log("Exec'd SQL:", list);
+        let counter = 0, colNames = [];
+        db.exec("SELECT a a, b b FROM t",{
+            rowMode: 'object',
+            callback: function(row,stmt){
+                if(!counter) stmt.getColumnNames(colNames);
+                ++counter;
+                T.assert(row.a%2 && row.a<6);
+            }
+        });
+        assert(2 === colNames.length);
+        assert('a' === colNames[0]);
+        T.assert(3 === counter);
+        db.exec("SELECT a a, b b FROM t",{
+            rowMode: 'array',
+            callback: function(row,stmt){
+                ++counter;
+                assert(Array.isArray(row));
+                T.assert(0===row[1]%2 && row[1]<7);
+            }
+        });
+        T.assert(6 === counter);
+        log("Test count:",T.counter);
+    }finally{
+        db.close();
+    }
 };
 
 self/*window or worker*/.Module.onRuntimeInitialized = function(){
index da28b2bacd3907799f29fde9451c16f67e8a3565..885ccb9c860bad26838c994dcc0f46fedb4efddb 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Implemented\sStmt.get()\sand\sfriends\sfor\sWASM\sOO\s#1\swrapper.\sAdded\sbasic\stests\sfor\sprepare/step/get.\sRestructured\smodule\sinit\soutput\sto\sintroduce\sonly\s1\sglobal-scope\ssymbol\sinstead\sof\s2.
-D 2022-05-22T19:09:59.198
+C WASM:\sadded\sexec(),\sexecMulti(),\sand\sseveral\sgetters.\sVarious\stouchups\sand\sfixes.
+D 2022-05-22T22:00:39.271
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -56,7 +56,7 @@ F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0
 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
 F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
 F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f
-F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 bba07bb2b81ce667df7916d4c942f0bfe6de6c77f5fe769d479f01250f92ca01
+F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 5816adc4d4715b410a9df971c70f55fca610d3a240bd85d2ec34e75483cb54bb
 F ext/fiddle/EXPORTED_RUNTIME_METHODS 91d5dcb0168ee056fa1a340cb8ab3c23d922622f8dad39d28919dd8af2b3ade0
 F ext/fiddle/Makefile 9277c73e208b9c8093659256c9f07409c877e366480c7c22ec545ee345451d95
 F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
@@ -64,10 +64,10 @@ F ext/fiddle/fiddle-worker.js c22557b641b47fa1473d3465a4e69fe06b8b09b924955805a4
 F ext/fiddle/fiddle.html 657c6c3f860c322fba3c69fa4f7a1209e2d2ce44b4bc65a3e154e3a97c047a7c
 F ext/fiddle/fiddle.js f9c79164428e96a5909532f18a8bc8f8c8ec4f738bfc09ad3d2a532c2400f9f0
 F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
-F ext/fiddle/sqlite3-api.js e205ccb758678bab7261f184e816d809a5031e1b4babd7738bed98de534787dd
+F ext/fiddle/sqlite3-api.js 6d088e075fa2910dcad2584d2a518de33c7dd5ce4d94408691c0cdb1c9c4394b
 F ext/fiddle/testing-common.js 53284264504821314f052017b54fa75ab065dcd9cbb754cc8060930498faeee8
 F ext/fiddle/testing1.html 68cec1b1c8646a071717e5979f22e4268e6d36d96ba13ad68333351acdbcf1d1
-F ext/fiddle/testing1.js 8849eaee6d7b31a195b29f9532c16a87a03e1be780a48cbdec54760c39ebf66c
+F ext/fiddle/testing1.js 5e46c8850f826821cb24b13a21e4dabee8dac9ce76845149dac599ab643784ab
 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
 F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
 F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@@ -1967,8 +1967,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P f3bc0328c87cac7d50513b0f13576d8fe7b411396f19c08fbe7e7c657b33cfbf
-R e0afacc1df156a218ff63194b2d94770
+P 601dc3fa29c2ce2ede5a8320c79050305f3774b6d7bc759247c5021f3b74aaec
+R e2842421067cb498614f974216c18719
 U stephan
-Z 8276de9ecbbfd692683c1010f2d924a3
+Z 9b9e03e3e05307544edc47fc1b8505b5
 # Remove this line to create a well-formed Fossil manifest.
index 826df84f448f72f6ff5d081131555bf7fd961075..e601bd2321fe466a830336baae71901e31533fc5 100644 (file)
@@ -1 +1 @@
-601dc3fa29c2ce2ede5a8320c79050305f3774b6d7bc759247c5021f3b74aaec
\ No newline at end of file
+b790c91b85e9cf8eecce86ac1717e8ccd2c3b6b98a1ad6a5d64eefc94ee86f9d
\ No newline at end of file