]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
WASM: added bindings for sqlite3_compileoption_get/used(), moved OO #1 into sqlite3...
authorstephan <stephan@noemail.net>
Sun, 22 May 2022 16:25:43 +0000 (16:25 +0000)
committerstephan <stephan@noemail.net>
Sun, 22 May 2022 16:25:43 +0000 (16:25 +0000)
FossilOrigin-Name: f3bc0328c87cac7d50513b0f13576d8fe7b411396f19c08fbe7e7c657b33cfbf

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

index 09f6e8ba79c21c1e542cbaaa51cdd6b981bfe79c..e6b57420c2ae0fd7eec726a88eae0f61a582d35a 100644 (file)
@@ -3,6 +3,7 @@ _sqlite3_bind_double
 _sqlite3_bind_int
 _sqlite3_bind_int64
 _sqlite3_bind_null
+_sqlite3_bind_parameter_count
 _sqlite3_bind_parameter_index
 _sqlite3_bind_text
 _sqlite3_changes
@@ -16,6 +17,8 @@ _sqlite3_column_double
 _sqlite3_column_name
 _sqlite3_column_text
 _sqlite3_column_type
+_sqlite3_compileoption_get
+_sqlite3_compileoption_used
 _sqlite3_create_function_v2
 _sqlite3_data_count
 _sqlite3_db_filename
index 83e46bbcaf45fed0a425a1296c74baea5b549f4c..0c24085784e93ee5895832e6092562d06d90242b 100644 (file)
 
   ***********************************************************************
 
-  This file is intended to be loaded after loading
-  sqlite3-module.wasm. It sets one of any number of potential
-  bindings using that API, this one as closely matching the C-native
-  API as is feasible.
+  This file is intended to be loaded after loading sqlite3.wasm. It
+  sets one of any number of potential bindings using that API, this
+  one as closely matching the C-native API as is feasible.
 
   Note that this file is not named sqlite3.js because that file gets
   generated by emscripten as the JS-glue counterpart of sqlite3.wasm.
 
   The API gets installed as self.sqlite3, where self is expected to be
-  either the global window or Worker object.
+  either the global window or Worker object. In addition, a higher-level
+  OO API is installed as self.SQLite3.
 
-  Because using this API properly requires some degree of WASM-related
-  magic, it is not recommended that this API be used as-is in
-  client-level code, but instead is intended to be used as a basis for
-  APIs more appropriate for high-level client code.
+  Potential TODO: instead of exporting 2 symbols, export only SQLite3
+  as {api: sqlite3, oo1: SQLite3}. The way we export this module is
+  not _really_ modern-JS-friendly because it exports global symbols
+  (which is admittedly poor form). Exporting it "cleanly" requires
+  using a module loader in all client code. As there are several
+  different approaches, none of which this developer is currently
+  truly familiar with, the current approach will have to do for the
+  time being.
+
+  Because using the low-level API properly requires some degree of
+  WASM-related magic, it is not recommended that that API be used
+  as-is in client-level code. Rather, client code should use the
+  higher-level OO API or write such a wrapper on top of the
+  lower-level API.
 
   This file installs namespace.sqlite3, where namespace is `self`,
   meaning either the global window or worker, depending on where this
         /* It is important that the following integer values match
            those from the C code. Ideally we could fetch them from the
            C API, e.g., in the form of a JSON object, but getting that
-           JSON string constructed within our current confised is
-           currently not worth the effort. */
+           JSON string constructed within our current confines is
+           currently not worth the effort.
+
+           Reminder to self: we could probably do so by adding the
+           proverbial level of indirection, calling in to C to get it,
+           and having that C func call an
+           emscripten-installed/JS-implemented library function which
+           builds the result object:
+
+           const obj = {};
+           sqlite3__get_enum(function(key,val){
+               obj[key] = val;
+           });
+
+           but whether or not we can pass a function that way, via a
+           (void*) is as yet unknown.
+        */
         /* Minimum subset of sqlite result codes we'll need. */
         SQLITE_OK: 0,
         SQLITE_ROW: 100,
         ["sqlite3_bind_int","number",["number", "number", "number"]],
         ["sqlite3_bind_int64","number",["number", "number", "number"]],
         ["sqlite3_bind_null","void",["number"]],
+        ["sqlite3_bind_parameter_count", "number", ["number"]],
         ["sqlite3_bind_parameter_index","number",["number", "string"]],
         ["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]],
         ["sqlite3_changes", "number", ["number"]],
         ["sqlite3_column_name","string",["number", "number"]],
         ["sqlite3_column_text","string",["number", "number"]],
         ["sqlite3_column_type","number",["number", "number"]],
+        ["sqlite3_compileoption_get", "string", ["number"]],
+        ["sqlite3_compileoption_used", "number", ["string"]],
         ["sqlite3_create_function_v2", "number",
          ["number", "string", "number", "number","number",
           "number", "number", "number", "number"]],
         api[k] = cwrap.apply(this, a);
     });
     //console.debug("libversion =",api.sqlite3_libversion());
-    namespace.sqlite3 = api;
+
+    /* 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
+       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 */
+    const pPtrArg = stackAlloc(4);
+    const toss = function(){
+        throw new Error(Array.prototype.join.call(arguments, ' '));
+    };
+
+    const sqlite3/*canonical name*/ = S/*convenience alias*/ = api;
+    
+    /**
+       The DB class wraps a sqlite3 db handle.
+    */
+    const DB = function(name/*TODO: openMode flags*/){
+        if(!name) name = ':memory:';
+        else if('string'!==typeof name){
+            toss("TODO: support blob image of db here.");
+        }
+        this.checkRc(S.sqlite3_open(name, pPtrArg));
+        this.pDb = getValue(pPtrArg, "i32");
+        this.filename = name;
+        this._statements = {/*map of open Stmt _pointers_*/};
+    };
+
+    /**
+       Internal-use enum for mapping JS types to DB-bindable types.
+       These do not (and need not) line up with the SQLITE_type
+       values. All values in this enum must be truthy and distinct
+       but they need not be numbers.
+    */
+    const BindTypes = {
+        null: 1,
+        number: 2,
+        string: 3,
+        boolean: 4,
+        blob: 5
+    };
+    BindTypes['undefined'] == BindTypes.null;
+
+    /**
+       This class wraps sqlite3_stmt. Calling this constructor
+       directly will trigger an exception. Use DB.prepare() to create
+       new instances.
+    */
+    const Stmt = function(){
+        if(BindTypes!=arguments[2]){
+            toss("Do not call the Stmt constructor directly. Use DB.prepare().");
+        }
+        this.db = arguments[0];
+        this.pStmt = arguments[1];
+        this.columnCount = S.sqlite3_column_count(this.pStmt);
+        this.parameterCount = S.sqlite3_bind_parameter_count(this.pStmt);
+        this._allocs = [/*list of alloc'd memory blocks for bind() values*/]
+    };
+
+    /** Throws if the given DB has been closed, else it is returned. */
+    const affirmDbOpen = function(db){
+        if(!db.pDb) toss("DB has been closed.");
+        return db;
+    };
+
+    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.
+        */
+        checkRc: function(sqliteResultCode){
+            if(!sqliteResultCode) return this;
+            toss(S.sqlite3_errmsg(this.pDb) || "Unknown db error.");
+        },
+        /**
+           Finalizes all open statements and closes this database
+           connection. This is a no-op if the db has already been
+           closed.
+        */
+        close: function(){
+            if(this.pDb){
+                let s;
+                const that = this;
+                Object.keys(this._statements).forEach(function(k,s){
+                    delete that._statements[k];
+                    if(s && s.pStmt) s.finalize();
+                });
+                S.sqlite3_close_v2(this.pDb);
+                delete this.pDb;
+            }
+        },
+        /**
+           Similar to this.filename but will return NULL for
+           special names like ":memory:". Not of much use until
+           we have filesystem support. Throws if the DB has
+           been closed. If passed an argument it then it will return
+           the filename of the ATTACHEd db with that name, else it assumes
+           a name of `main`.
+        */
+        fileName: function(dbName){
+            return S.sqlite3_db_filename(affirmDbOpen(this).pDb, dbName||"main");
+        },
+        /**
+           Compiles the given SQL and returns a prepared Stmt. This is
+           the only way to create new Stmt objects. Throws on error.
+        */
+        prepare: function(sql){
+            affirmDbOpen(this);
+            setValue(pPtrArg,0,"i32");
+            this.checkRc(S.sqlite3_prepare_v2(this.pDb, sql, -1, pPtrArg, null));
+            const pStmt = getValue(pPtrArg, "i32");
+            if(!pStmt) toss("Empty SQL is not permitted.");
+            const stmt = new Stmt(this, pStmt, BindTypes);
+            this._statements[pStmt] = stmt;
+            return stmt;
+        }
+    };
+
+    /** Returns an opaque truthy value from the BindTypes
+        enum if v's type is a valid bindable type, else
+        returns a falsy value. */
+    const isSupportedBindType = function(v){
+        let t = BindTypes[null===v ? 'null' : typeof v];
+        if(t) return t;
+        // TODO: handle buffer/blob types.
+        return undefined;
+    }
+
+    /**
+       If isSupportedBindType(v) returns a truthy value, this
+       function returns that value, else it throws.
+    */
+    const affirmSupportedBindType = function(v){
+        const t = isSupportedBindType(v);
+        if(t) return t;
+        toss("Unsupport bind() argument type.");
+    };
+
+    /**
+       If key is a number and within range of stmt's bound parameter
+       count, key is returned.
+
+       If key is not a number then it is checked against named
+       parameters. If a match is found, its index is returned.
+
+       Else it throws.
+    */
+    const indexOfParam = function(stmt,key){
+        const n = ('number'===typeof key)
+              ? key : S.sqlite3_bind_parameter_index(stmt.pStmt, key);
+        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.");
+        return n;
+    };
+
+    /**
+       Binds a single bound parameter value on the given stmt at the
+       given index (numeric or named) using the given bindType (see
+       the BindTypes enum) and value. Throws on error. Returns stmt on
+       success.
+    */
+    const bindOne = function(stmt,ndx,bindType,val){
+        affirmSupportedBindType(val);
+        ndx = indexOfParam(stmt,ndx);
+        let rc = 0;
+        switch(bindType){
+            case BindType.null:
+                rc = S.sqlite3_bind_null(stmt.pStmt, ndx);
+                break;
+            case BindType.string:{
+                const bytes = intArrayFromString(string,false);
+                const pStr = allocate(bytes, ALLOC_NORMAL);
+                stmt._allocs.push(pStr);
+                rc = S.sqlite3_bind_text(stmt.pStmt, ndx, pStr,
+                                         bytes.length, 0);
+                break;
+            }
+            case BindType.number: {
+                const m = ((val === (val|0))
+                           ? (val>0xefffffff
+                              ? S.sqlite3_bind_int64
+                              : S.sqlite3_bind_int)
+                           : S.sqlite3_bind_double);
+                rc = m(stmt.pStmt, ndx, val);
+                break;
+            }
+            case BindType.boolean:
+                rc = S.sqlite3_bind_int(stmt.pStmt, ndx, val ? 1 : 0);
+                break;
+            case BindType.blob:
+            default: toss("Unsupported bind() argument type.");
+        }
+        if(rc) stmt.db.checkRc(rc);
+        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){
+        let m;
+        while(undefined !== (m = stmt._allocs.pop())){
+            _free(m);
+        }
+        return stmt;
+    };
+    
+    Stmt.prototype = {
+        /**
+           "Finalizes" this statement. This is a no-op if the
+           statement has already been finalizes. Returns
+           undefined. Most methods in this class will throw if called
+           after this is.
+        */
+        finalize: function(){
+            if(this.pStmt){
+                freeBindMemory(this);
+                delete this.db._statements[this.pStmt];
+                S.sqlite3_finalize(this.pStmt);
+                delete this.pStmt;
+                delete this.db;
+            }
+        },
+        /** Clears all bound values. Returns this object.
+            Throws if this statement has been finalized. */
+        clearBindings: function(){
+            freeBindMemory(affirmStmtOpen(this));
+            S.sqlite3_clear_bindings(this.pStmt);
+            return this;
+        },
+        /**
+           Resets this statement so that it may be step()ed again
+           from the beginning. Returns this object. Throws if this
+           statement has been finalized.
+
+           If passed a truthy argument then this.clearBindings() is
+           also called, otherwise any existing bindings, along with
+           any memory allocated for them, are retained.
+        */
+        reset: function(alsoClearBinds){
+            if(alsoClearBinds) this.clearBindings();
+            S.sqlite3_reset(affirmStmtOpen(this).pStmt);
+            return this;
+        },
+        /**
+           Binds one or more values to its bindable parameters. It
+           accepts 1 or 2 arguments:
+
+           If passed a single argument, it must be either an array, an
+           object, or a value of a bindable type (see below).
+
+           If passed 2 arguments, the first one is the 1-based bind
+           index or bindable parameter name and the second one must be
+           a value of a bindable type.
+
+           Bindable value types:
+
+           - null or undefined is bound as NULL.
+
+           - Numbers are bound as either doubles or integers: int64 if
+             they are larger than 0xEFFFFFFF, else int32. Booleans are
+             bound as integer 0 or 1. Note that doubles with no
+             fractional part are bound as integers. It is not expected
+             that that distinction is significant for the majority of
+             clients due to sqlite3's data typing model. This API does
+             not currently support the BigInt type.
+
+           - Strings are bound as strings (use bindAsBlob() to force
+             blob binding).
+
+           - buffers (blobs) are currently TODO but will be bound as
+             blobs.
+
+           If passed an array, each element of the array is bound at
+           the parameter index equal to the array index plus 1
+           (because arrays are 0-based but binding is 1-based).
+
+           If passed an object, each object key is treated as a
+           bindable parameter name. The object keys _must_ match any
+           bindable parameter names, including any `$`, `@`, or `:`
+           prefix. Because `$` is a legal identifier chararacter in
+           JavaScript, that is the suggested prefix for bindable
+           parameters.
+
+           It returns this object on success and throws on
+           error. Errors include:
+
+           - Any bind index is out of range, a named bind parameter
+             does not match, or this statement has no bindable
+             parameters.
+
+           - Any value to bind is of an unsupported type.
+
+           - Passed no arguments or more than two.
+
+           - The statement has been finalized.
+        */
+        bind: function(/*[ndx,] value*/){
+            if(!this.parameterCount){
+                toss("This statement has no bindable parameters.");
+            }
+            let ndx, arg;
+            switch(arguments.length){
+                case 1: ndx = 1; arg = arguments[0]; break;
+                case 2: ndx = arguments[0]; arg = arguments[1]; break;
+                default: toss("Invalid bind() arguments.");
+            }
+            affirmStmtOpen(this);
+            if(null===arg || undefined===arg){
+                /* bind NULL */
+                return bindOne(this, ndx, BindType.null, arg);
+            }
+            else if(Array.isArray(arg)){
+                /* bind each entry by index */
+                if(1!==arguments.length){
+                    toss("When binding an array, an index argument is not permitted.");
+                }
+                arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
+                return this;
+            }
+            else if('object'===typeof arg/*null was checked above*/){
+                /* bind by name */
+                if(1!==arguments.length){
+                    toss("When binding an object, an index argument is not permitted.");
+                }
+                Object.keys(arg)
+                    .forEach(k=>bindOne(this, k,
+                                        affirmSupportedBindType(arg[k]),
+                                        arg[k]));
+                return this;
+            }else{
+                return bindOne(this, ndx,
+                               affirmSupportedBindType(arg), arg);
+            }
+            toss("Should not reach this point.");
+        },
+        /**
+           Special case of bind() which binds the given value
+           using the BLOB binding mechanism instead of the default
+           selected one for the value. The ndx may be a numbered
+           or named bind index. The value must be of type string,
+           buffer, or null/undefined (both treated as null).
+
+            If passed a single argument, a bind index of 1 is assumed.
+        */
+        bindAsBlob: function(ndx,arg){
+            affirmStmtOpen(this);
+            if(1===arguments.length){
+                ndx = 1;
+                arg = arguments[0];
+            }
+            const t = affirmSupportedBindType(arg);
+            if(BindTypes.string !== t && BindTypes.blob !== t
+               && BindTypes.null !== t){
+                toss("Invalid value type for bindAsBlob()");
+            }
+            return bindOne(this, ndx, BindType.blob, arg);
+        }
+    };
+
+    /** OO binding's namespace. */
+    const SQLite3 = {
+        version: {
+            lib: sqlite3.sqlite3_libversion(),
+            ooApi: "0.0.1"
+        },
+        DB,
+        Stmt,
+        /**
+           Reports whether a given compile-time option, named by the
+           given argument.
+
+           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.
+
+           If optName is an object, its keys are expected to be
+           compilation options and this function sets each entry to
+           true or false. That object is returned.
+
+           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.
+
+           In all other cases it returns true if the 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,
+           the prefix is elided.
+        */
+        compileOptionUsed: function f(optName){
+            if(!arguments.length){
+                if(!f._opt){
+                    f._rx = /^([^=]+)=(.+)/;
+                    f._rxInt = /^-?\d+/;
+                    f._opt = function(opt, rv){
+                        const m = f._rx.exec(opt);
+                        rv[0] = (m ? m[1] : opt);
+                        rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
+                    };                    
+                }
+                const rc = {}, ov = [0,0];
+                let i = 0;
+                while((k = S.sqlite3_compileoption_get(i++))){
+                    f._opt(k,ov);
+                    rc[ov[0]] = ov[1];
+                }
+                return rc;
+            }
+            else if(Array.isArray(optName)){
+                const rc = {};
+                optName.forEach((v)=>{
+                    rc[v] = S.sqlite3_compileoption_used(v);
+                });
+                return rc;
+            }
+            else if('object' === typeof optName){
+                Object.keys(optName).forEach((k)=> {
+                    optName[k] = S.sqlite3_compileoption_used(k);
+                });
+                return optName;
+            }
+            return (
+                'string'===typeof optName
+            ) ? !!S.sqlite3_compileoption_used(optName) : false;
+        }
+    };
+    
+    namespace.sqlite3 = sqlite3;
+    namespace.SQLite3 = SQLite3;
 })(self/*worker or window*/);
index 2fe9a860ba97d2536f58fe1c3489773e4e2f27cd..762ce58668fa4390155ea0a0f04073e632476007 100644 (file)
@@ -28,7 +28,6 @@
     const statusElement = E('#module-status');
     const progressElement = E('#module-progress');
     const spinnerElement = E('#module-spinner');
-
     self.Module = {
         /* ^^^ cannot declare that const because fiddle-module.js
            (auto-generated) includes a decl for it and runs in this scope. */
                            : 'All downloads complete.');
         },
         /* Loads sqlite3-api.js and calls the given callback (if
-           provided), passing it the sqlite3 module. Whether this is
-           synchronous or async depends on whether it's run in the
-           main thread or a worker.*/
+           provided), passing it an object which contains the sqlite3
+           and SQLite3 modules. Whether this is synchronous or async
+           depends on whether it's run in the main thread or a
+           worker.*/
         loadSqliteAPI: function(callback){
             const theScript = 'sqlite3-api.js';
             if(self.importScripts){/*worker*/
                     script.async = true;
                     script.src = theScript;
                 }).then(() => {
-                    if(callback) callback(self.sqlite3);
+                    if(callback) callback({sqlite3:self.sqlite3,
+                                           SQLite3:self.SQLite3});
                 });
             }
         }
     };
+
+    const _expr = function(expr){
+        return (expr instanceof Function) ? expr() : !!expr;
+    };
+    
+    /**
+       Helpers for writing sqlite3-specific tests.
+    */
+    self.SqliteTester = {
+        /** Running total of the number of tests run via
+            this API. */
+        counter: 0,
+        /** abort() if expr is false. If expr is a function, it
+            is called and its result is evaluated.
+        */
+        assert: function(expr, msg){
+            ++this.counter;
+            if(!_expr(expr)) abort(msg || "Assertion failed.");
+            return this;
+        },
+        /** Identical to assert() but throws instead of calling
+            abort(). */
+        affirm: function(expr, msg){
+            ++this.counter;
+            if(!_expr(expr)) throw new Error(msg || "Affirmation failed.");
+            return this;
+        },
+        /** Calls f() and squelches any exception it throws. If it
+            does not throw, this function throws. */
+        mustThrow: function(f, msg){
+            ++this.counter;
+            let err;
+            try{ f(); } catch(e){err=e;}
+            if(!err) throw new Error(msg || "Expected exception.");
+            return this;
+        },
+        /** Throws if expr is truthy or expr is a function and expr()
+            returns truthy. */
+        throwIf: function(expr, msg){
+            ++this.counter;
+            if(_expr(expr)) throw new Error(msg || "throwIf() failed");
+            return this;
+        },
+        /** Throws if expr is falsy or expr is a function and expr()
+            returns falsy. */
+        throwUnless: function(expr, msg){
+            ++this.counter;
+            if(!_expr(expr)) throw new Error(msg || "throwUnless() failed");
+            return this;
+        }
+    };
+
 })(self/*window or worker*/);
index a24d63f1053c3327bafd878edce52ec7876a2528..6e79be0257ade8dfaf03e81cd35662c8f84eacee 100644 (file)
 
   A basic test script for sqlite3-api.js.
 */
-const setupAPI = function(S/*sqlite3 module*/){
 
-    /* memory for use in some pointer-passing routines */
-    const pPtrArg = stackAlloc(4);
-    const dummyArg = {/*for restricting Stmt constructor to internal use*/};
-    const toss = function(){
-        throw new Error(Array.prototype.join.apply(arguments, ' '));
-    };
-
-    /**
-       The DB class wraps a sqlite3 db handle.
-    */
-    const DB = function(name/*TODO: openMode flags*/){
-        if(!name) name = ':memory:';
-        else if('string'!==typeof name){
-            toss("TODO: support blob image of db here.");
-        }
-        this.checkRc(S.sqlite3_open(name, pPtrArg));
-        this.pDb = getValue(pPtrArg, "i32");
-        this.filename = name;
-        this._statements = {/*array of open Stmt _pointers_*/};
-    };
-
-    /**
-       This class wraps sqlite3_stmt. Calling this constructor
-       directly will trigger an exception. Use DB.prepare() to create
-       new instances.
-    */
-    const Stmt = function(){
-        if(dummyArg!=arguments[2]){
-            toss("Do not call the Stmt constructor directly. Use DB.prepare().");
-        }
-        this.db = arguments[0];
-        this.pStmt = arguments[1];
-        this.columnCount = S.sqlite3_column_count(this.pStmt);
-        this._allocs = [/*list of alloc'd memory blocks for bind() values*/]
-    };
-
-
-    /** Throws if the given DB has been closed, else it is returned. */
-    const affirmDbOpen = function(db){
-        if(!db.pDb) toss("DB has been closed.");
-        return db;
-    };
-    
-    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.
-        */
-        checkRc: function(sqliteResultCode){
-            if(!sqliteResultCode) return this;
-            toss(S.sqlite3_errmsg(this.pDb) || "Unknown db error.");
-        },
-        /**
-           Finalizes all open statements and closes this database
-           connection. This is a no-op if the db has already been
-           closed.
-        */
-        close: function(){
-            if(this.pDb){
-                let s;
-                while(undefined!==(s = this._statements.pop())){
-                    if(s.pStmt) s.finalize();
-                }
-                S.sqlite3_close_v2(this.pDb);
-                delete this.pDb;
-            }
-        },
-        /**
-           Similar to this.filename but will return NULL for
-           special names like ":memory:". Not of much use until
-           we have filesystem support. Throws if the DB has
-           been closed. If passed an argument it then it will return
-           the filename of the ATTACHEd db with that name, else it assumes
-           a name of `main`.
-        */
-        fileName: function(dbName){
-            return S.sqlite3_db_filename(affirmDbOpen(this).pDb, dbName||"main");
-        },
-
-        /**
-           Compiles the given SQL and returns a prepared Stmt. This is
-           the only way to create new Stmt objects. Throws on error.
-        */
-        prepare: function(sql){
-            affirmDbOpen(this);
-            setValue(pPtrArg,0,"i32");
-            this.checkRc(S.sqlite3_prepare_v2(this.pDb, sql, -1, pPtrArg, null));
-            const pStmt = getValue(pPtrArg, "i32");
-            if(!pStmt) toss("Empty SQL is not permitted.");
-            const stmt = new Stmt(this, pStmt, dummyArg);
-            this._statements[pStmt] = stmt;
-            return stmt;
-        }
-    };
-
-    /**
-       Internal-use enum for mapping JS types to DB-bindable types.
-       These do not (and need not) line up with the SQLITE_type
-       values. All values in this enum must be truthy and distinct
-       but they need not be numbers.
-    */
-    const BindTypes = {
-        null: 1,
-        number: 2,
-        string: 3,
-        boolean: 4,
-        blob: 5
-    };
-    BindTypes['undefined'] == BindTypes.null;
-
-    /** Returns an opaque truthy value from the BindTypes
-        enum if v's type is a valid bindable type, else
-        returns a falsy value. */
-    const isSupportedBindType = function(v){
-        let t = BindTypes[null===v ? 'null' : typeof v];
-        if(t) return t;
-        // TODO: handle buffer/blob types.
-        return undefined;
-    }
-
-    /**
-       If isSupportedBindType(v) returns a truthy value, this
-       function returns that value, else it throws.
-    */
-    const affirmSupportedBindType = function(v){
-        const t = isSupportedBindType(v);
-        if(t) return t;
-        toss("Unsupport bind() argument type.");
-    };
-
-    /**
-       If key is a number and within range of stmt's bound parameter
-       count, key is returned.
-
-       If key is not a number then it is checked against named
-       parameters. If a match is found, its index is returned.
-
-       Else it throws.
-    */
-    const indexOfParam = function(stmt,key){
-        const n = ('number'===typeof key)
-              ? key : S.sqlite3_bind_parameter_index(stmt.pStmt, key);
-        if(0===n || (n===key && (n!==(n|0)/*floating point*/))){
-            toss("Invalid bind() parameter name: "+key);
-        }
-        else if(n>=stmt.columnCount) toss("Bind index",key,"is out of range.");
-        return n;
-    };
-
-    /**
-       Binds a single bound parameter value on the given stmt at the
-       given index (numeric or named) using the given bindType (see
-       the BindTypes enum) and value. Throws on error. Returns stmt on
-       success.
-    */
-    const bindOne = function(stmt,ndx,bindType,val){
-        affirmSupportedBindType(val);
-        ndx = indexOfParam(stmt,ndx);
-        let rc = 0;
-        switch(bindType){
-            case BindType.null:
-                rc = S.sqlite3_bind_null(stmt.pStmt, ndx);
-                break;
-            case BindType.string:{
-                const bytes = intArrayFromString(string,false);
-                const pStr = allocate(bytes, ALLOC_NORMAL);
-                stmt._allocs.push(pStr);
-                rc = S.sqlite3_bind_text(stmt.pStmt, ndx, pStr,
-                                         bytes.length, 0);
-                break;
-            }
-            case BindType.number: {
-                const m = ((val === (val|0))
-                           ? (val>0xefffffff
-                              ? S.sqlite3_bind_int64
-                              : S.sqlite3_bind_int)
-                           : S.sqlite3_bind_double);
-                rc = m(stmt.pStmt, ndx, val);
-                break;
-            }
-            case BindType.boolean:
-                rc = S.sqlite3_bind_int(stmt.pStmt, ndx, val ? 1 : 0);
-                break;
-            case BindType.blob:
-            default: toss("Unsupported bind() argument type.");
-        }
-        if(rc) stmt.db.checkRc(rc);
-        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){
-        let m;
-        while(undefined !== (m = stmt._allocs.pop())){
-            _free(m);
-        }
-        return stmt;
-    };
-    
-    Stmt.prototype = {
-        /**
-           "Finalizes" this statement. This is a no-op if the
-           statement has already been finalizes. Returns
-           undefined. Most methods in this class will throw if called
-           after this is.
-        */
-        finalize: function(){
-            if(this.pStmt){
-                freeBindMemory(this);
-                S.sqlite3_finalize(this.pStmt);
-                delete this.pStmt;
-                delete this.db;
-            }
-        },
-        /** Clears all bound values. Returns this object.
-            Throws if this statement has been finalized. */
-        clearBindings: function(){
-            freeBindMemory(affirmStmtOpen(this));
-            S.sqlite3_clear_bindings(this.pStmt);
-            return this;
-        },
-        /**
-           Resets this statement so that it may be step()ed again
-           from the beginning. Returns this object. Throws if this
-           statement has been finalized.
-
-           If passed a truthy argument then this.clearBindings() is
-           also called, otherwise any existing bindings, along with
-           any memory allocated for them, are retained.
-        */
-        reset: function(alsoClearBinds){
-            if(alsoClearBinds) this.clearBindings();
-            S.sqlite3_reset(affirmStmtOpen(this).pStmt);
-            return this;
-        },
-        /**
-           Binds one or more values to its bindable parameters. It
-           accepts 1 or 2 arguments:
-
-           If passed a single argument, it must be either an array, an
-           object, or a value of a bindable type (see below).
-
-           If passed 2 arguments, the first one is the 1-based bind
-           index or bindable parameter name and the second one must be
-           a value of a bindable type.
-
-           Bindable value types:
-
-           - null or undefined is bound as NULL.
-
-           - Numbers are bound as either doubles or integers: int64 if
-             they are larger than 0xEFFFFFFF, else int32. Booleans are
-             bound as integer 0 or 1. Note that doubles with no
-             fractional part are bound as integers. It is not expected
-             that that distinction is significant for the majority of
-             clients due to sqlite3's data typing model. This API does
-             not currently support the BigInt type.
-
-           - Strings are bound as strings (use bindAsBlob() to force
-             blob binding).
-
-           - buffers (blobs) are currently TODO but will be bound as
-             blobs.
-
-           If passed an array, each element of the array is bound at
-           the parameter index equal to the array index plus 1
-           (because arrays are 0-based but binding is 1-based).
-
-           If passed an object, each object key is treated as a
-           bindable parameter name. The object keys _must_ match any
-           bindable parameter names, including any `$`, `@`, or `:`
-           prefix. Because `$` is a legal identifier chararacter in
-           JavaScript, that is the suggested prefix for bindable
-           parameters.
-
-           It returns this object on success and throws on
-           error. Errors include:
-
-           - Any bind index is out of range or a named bind parameter
-             does not match.
-
-           - Any value to bind is of an unsupported type.
-
-           - Passed no arguments or more than two.
-
-           - The statement has been finalized.
-        */
-        bind: function(/*[ndx,] value*/){
-            let ndx, arg;
-            switch(arguments.length){
-                case 1: ndx = 1; arg = arguments[0]; break;
-                case 2: ndx = arguments[0]; arg = arguments[1]; break;
-                default: toss("Invalid bind() arguments.");
-            }
-            affirmStmtOpen(this);
-            if(null===arg || undefined===arg){
-                /* bind NULL */
-                return bindOne(this, ndx, BindType.null, arg);
-            }
-            else if(Array.isArray(arg)){
-                /* bind each entry by index */
-                if(1!==arguments.length){
-                    toss("When binding an array, an index argument is not permitted.");
-                }
-                arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
-                return this;
-            }
-            else if('object'===typeof arg/*null was checked above*/){
-                /* bind by name */
-                if(1!==arguments.length){
-                    toss("When binding an object, an index argument is not permitted.");
-                }
-                Object.keys(arg)
-                    .forEach(k=>bindOne(this, k,
-                                        affirmSupportedBindType(arg[k]),
-                                        arg[k]));
-                return this;
-            }else{
-                return bindOne(this, ndx,
-                               affirmSupportedBindType(arg), arg);
-            }
-            toss("Should not reach this point.");
-        },
-        /**
-           Special case of bind() which binds the given value
-           using the BLOB binding mechanism instead of the default
-           selected one for the value. The ndx may be a numbered
-           or named bind index. The value must be of type string,
-           buffer, or null/undefined (both treated as null).
-
-            If passed a single argument, a bind index of 1 is assumed.
-        */
-        bindAsBlob: function(ndx,arg){
-            affirmStmtOpen(this);
-            if(1===arguments.length){
-                ndx = 1;
-                arg = arguments[0];
-            }
-            const t = affirmSupportedBindType(arg);
-            if(BindTypes.string !== t && BindTypes.blob !== t
-               && BindTypes.null !== t){
-                toss("Invalid value type for bindAsBlob()");
-            }
-            return bindOne(this, ndx, BindType.blob, arg);
-        }
-    };    
-
-    const SQLite3 = {
-        version: {
-            lib: S.sqlite3_libversion(),
-            ooApi: "0.0.1"
-        },
-        DB
-    };
-    return SQLite3;
-};
-
-const mainTest1 = function(S/*sqlite3 module*/){
+const mainTest1 = function(namespace){
+    const S = namespace.sqlite3;
+    const oo = namespace.SQLite3;
+    const T = self.SqliteTester;
     console.log("Loaded module:",S.sqlite3_libversion(),
                 S.sqlite3_sourceid());
-    const oo = setupAPI(S);
-
     const db = new oo.DB();
-    console.log("DB:",db.filename);
+    const log = console.log.bind(console);
+    T.assert(db.pDb);
+    log("DB:",db.filename);
+    log("Build options:",oo.compileOptionUsed());
+
+    let st = db.prepare("select 1");
+    T.assert(st.pStmt);
+    log("statement =",st);
+    T.assert(st === db._statements[st.pStmt])
+        .assert(1===st.columnCount)
+        .assert(0===st.parameterCount)
+        .mustThrow(()=>st.bind(1,null));
+
+    let pId = st.pStmt;
+    st.finalize();
+    T.assert(!st.pStmt)
+        .assert(!db._statements[pId]);
+    log("Test count:",T.counter);
 };
 
 self/*window or worker*/.Module.onRuntimeInitialized = function(){
index 54f4e5cb516388e26df7689e5efa83af1aad0bbc..36dce9f18d7e2f2e55a1bd9aebbb84f7cae6ef5c 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C WASM\sOO\swrapper\s#1:\sprepare()\sand\sbind()\sAPIs\sare\sin\splace\sbut\sare\suntested,\spending\sfetch/get\sAPIs.
-D 2022-05-22T14:07:44.577
+C WASM:\sadded\sbindings\sfor\ssqlite3_compileoption_get/used(),\smoved\sOO\s#1\sinto\ssqlite3-api.js\ssince\sit\scan\sonly\sbe\sused\sfrom\sthe\ssame\sthread\sas\sthat\sAPI\sand\sseparating\sthem\scomplicates\sclient-side\suse.\sStarted\sadding\stest\sutilities\sand\stests\sfor\sthe\sOO1\sAPI.
+D 2022-05-22T16:25:43.076
 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 a3a2862941270ae5e2633d21cbf44979901c4b75efa42a452c15ef879b47ad2b
+F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 00553766051a038b1acd3992d04e50540d1284c3ea78bd11daa521383e57d653
 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 5f256e3dc78ed0ac4f8556c0c77860812f9baf542b7a73b19b2abb72a6e13146
-F ext/fiddle/testing-common.js 37b014758db7e5e74278e37dc712ced2fc9b40d0617f5ed0b8b64a6bd9c0a45d
+F ext/fiddle/sqlite3-api.js ab7e7ded7b3079ee7de43e8290f1942e757d90ebb47ae4654cfe03c980cd0cad
+F ext/fiddle/testing-common.js 2b2826a1e7c8ca3e610dfa4255ff1077438b6570e08096cc139c226811e60dbb
 F ext/fiddle/testing1.html 68cec1b1c8646a071717e5979f22e4268e6d36d96ba13ad68333351acdbcf1d1
-F ext/fiddle/testing1.js 2e9aa40a17c97ab8e90a8ba942725ebf590ae5db3f0329583d7431e4524f5b11
+F ext/fiddle/testing1.js 6a314a10efc954bcd854af89d53ab768f48a42d3dcb80773b297f4ba0ac0236d
 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 dea098b64eb95c395b346ebcae687afe42b7d21df48833527808c02226300a66
-R d6777acddc9dd48a02f29eb36eccd673
+P 84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39
+R d3343607f5b0d8f2cee2f7485f266550
 U stephan
-Z ec1032561dde0c5db030c66f88c029fe
+Z 4ea03d4418e5522f96fc7a3cf30bf26a
 # Remove this line to create a well-formed Fossil manifest.
index 8691610d23288074af7ee35979996d5cf6773874..47aa878ffb6df9fa16cf2844ad902866691429d6 100644 (file)
@@ -1 +1 @@
-84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39
\ No newline at end of file
+f3bc0328c87cac7d50513b0f13576d8fe7b411396f19c08fbe7e7c657b33cfbf
\ No newline at end of file