SQLite

Check-in [8fbda563]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Extend the JS/WASM SEE build support by (A) filtering SEE-related bits out of the JS when not building with SEE and (B) accepting an optional key/textkey/hexkey option to the sqlite3.oo1.DB and subclass constructors to create/open SEE-encrypted databases with. Demonstrate SEE in the test app using the kvvfs. This obviates the changes made in [5c505ee8a7].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 8fbda563d2f56f8dd3f695a5711e4356de79035f332270db45d4b33ed52fdfd2
User & Date: stephan 2024-04-22 16:46:37
References
2024-04-22
17:03
Minor cleanups to [8fbda563d2f5]. (check-in: 5ee2594b user: stephan tags: trunk)
Context
2024-04-22
17:03
Minor cleanups to [8fbda563d2f5]. (check-in: 5ee2594b user: stephan tags: trunk)
16:46
Extend the JS/WASM SEE build support by (A) filtering SEE-related bits out of the JS when not building with SEE and (B) accepting an optional key/textkey/hexkey option to the sqlite3.oo1.DB and subclass constructors to create/open SEE-encrypted databases with. Demonstrate SEE in the test app using the kvvfs. This obviates the changes made in [5c505ee8a7]. (check-in: 8fbda563 user: stephan tags: trunk)
13:31
Extra robustness in the code that causes cursors to return NULL when they are participating in an OUTER JOIN. (check-in: 672c2869 user: drh tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/wasm/GNUmakefile.

331
332
333
334
335
336
337



338
339
340
341
342
343




344
345
346
347
348
349
350
351
352
353
354
355
356
357
#
# c-pp.c was written specifically for the sqlite project's JavaScript
# builds but is maintained as a standalone project:
# https://fossil.wanderinghorse.net/r/c-pp
#
# Note that the SQLITE_... build flags used here have NO EFFECT on the
# JS/WASM build. They are solely for use with $(bin.c-pp) itself.



bin.c-pp := ./c-pp
$(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE)
	$(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \
		-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \
		-DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \
		-DSQLITE_TEMP_STORE=3




define C-PP.FILTER
# Create $2 from $1 using $(bin.c-pp)
# $1 = Input file: c-pp -f $(1).js
# $2 = Output file: c-pp -o $(2).js
# $3 = optional c-pp -D... flags
$(2): $(1) $$(MAKEFILE) $$(bin.c-pp)
	$$(bin.c-pp) -f $(1) -o $$@ $(3)
CLEAN_FILES += $(2)
endef
# /end C-PP.FILTER
########################################################################

# cflags.common = C compiler flags for all builds
cflags.common :=  -I. -I$(dir $(sqlite3.c))







>
>
>






>
>
>
>






|







331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
#
# c-pp.c was written specifically for the sqlite project's JavaScript
# builds but is maintained as a standalone project:
# https://fossil.wanderinghorse.net/r/c-pp
#
# Note that the SQLITE_... build flags used here have NO EFFECT on the
# JS/WASM build. They are solely for use with $(bin.c-pp) itself.
#
# -D... flags which should be included in all invocations should be
# appended to $(C-PP.FILTER.global).
bin.c-pp := ./c-pp
$(bin.c-pp): c-pp.c $(sqlite3.c) $(MAKEFILE)
	$(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \
		-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \
		-DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_WAL -DSQLITE_THREADSAFE=0 \
		-DSQLITE_TEMP_STORE=3
C-PP.FILTER.global ?=
ifeq (1,$(SQLITE_C_IS_SEE))
  C-PP.FILTER.global += -Denable-see
endif
define C-PP.FILTER
# Create $2 from $1 using $(bin.c-pp)
# $1 = Input file: c-pp -f $(1).js
# $2 = Output file: c-pp -o $(2).js
# $3 = optional c-pp -D... flags
$(2): $(1) $$(MAKEFILE) $$(bin.c-pp)
	$$(bin.c-pp) -f $(1) -o $$@ $(3) $(C-PP.FILTER.global)
CLEAN_FILES += $(2)
endef
# /end C-PP.FILTER
########################################################################

# cflags.common = C compiler flags for all builds
cflags.common :=  -I. -I$(dir $(sqlite3.c))

Changes to ext/wasm/api/sqlite3-api-glue.js.

325
326
327
328
329
330
331

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348


349
350
351
352
353
354
355
  if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
    /* ^^^ "the problem" is that this is an option feature and the
       build-time function-export list does not currently take
       optional features into account. */
    wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]);
  }


  if(wasm.exports.sqlite3_activate_see instanceof Function){
    /**
       This code is capable of using an SEE build but note that an SEE
       WASM build is generally incompatible with SEE's license
       conditions. It is permitted for use internally in organizations
       which have licensed SEE, but not for public sites because
       exposing an SEE build of sqlite3.wasm effectively provides all
       clients with a working copy of the commercial SEE code.
    */
    wasm.bindingSignatures.push(
      ["sqlite3_key", "int", "sqlite3*", "string", "int"],
      ["sqlite3_key_v2","int","sqlite3*","string","*","int"],
      ["sqlite3_rekey", "int", "sqlite3*", "string", "int"],
      ["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"],
      ["sqlite3_activate_see", undefined, "string"]
    );
  }


  /**
     Functions which require BigInt (int64) support are separated from
     the others because we need to conditionally bind them or apply
     dummy impls, depending on the capabilities of the environment.
     (That said: we never actually build without BigInt support,
     and such builds are untested.)








>
|
















>
>







325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
  if(false && wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
    /* ^^^ "the problem" is that this is an option feature and the
       build-time function-export list does not currently take
       optional features into account. */
    wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]);
  }

//#if enable-see
  if(wasm.exports.sqlite3_key_v2 instanceof Function){
    /**
       This code is capable of using an SEE build but note that an SEE
       WASM build is generally incompatible with SEE's license
       conditions. It is permitted for use internally in organizations
       which have licensed SEE, but not for public sites because
       exposing an SEE build of sqlite3.wasm effectively provides all
       clients with a working copy of the commercial SEE code.
    */
    wasm.bindingSignatures.push(
      ["sqlite3_key", "int", "sqlite3*", "string", "int"],
      ["sqlite3_key_v2","int","sqlite3*","string","*","int"],
      ["sqlite3_rekey", "int", "sqlite3*", "string", "int"],
      ["sqlite3_rekey_v2", "int", "sqlite3*", "string", "*", "int"],
      ["sqlite3_activate_see", undefined, "string"]
    );
  }
//#endif enable-see

  /**
     Functions which require BigInt (int64) support are separated from
     the others because we need to conditionally bind them or apply
     dummy impls, depending on the capabilities of the environment.
     (That said: we never actually build without BigInt support,
     and such builds are untested.)

623
624
625
626
627
628
629
630

631
632
633
634
635
636
637
  */
  wasm.bindingSignatures.wasmInternal = [
    ["sqlite3__wasm_db_reset", "int", "sqlite3*"],
    ["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
    ["sqlite3__wasm_vfs_create_file", "int",
     "sqlite3_vfs*","string","*", "int"],
    ["sqlite3__wasm_posix_create_file", "int", "string","*", "int"],
    ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]

  ];

  /**
     Install JS<->C struct bindings for the non-opaque struct types we
     need... */
  sqlite3.StructBinder = globalThis.Jaccwabyt({
    heap: 0 ? wasm.memory : wasm.heap8u,







|
>







626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
  */
  wasm.bindingSignatures.wasmInternal = [
    ["sqlite3__wasm_db_reset", "int", "sqlite3*"],
    ["sqlite3__wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
    ["sqlite3__wasm_vfs_create_file", "int",
     "sqlite3_vfs*","string","*", "int"],
    ["sqlite3__wasm_posix_create_file", "int", "string","*", "int"],
    ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"],
    ["sqlite3__wasm_qfmt_token","string:dealloc", "string","int"]
  ];

  /**
     Install JS<->C struct bindings for the non-opaque struct types we
     need... */
  sqlite3.StructBinder = globalThis.Jaccwabyt({
    heap: 0 ? wasm.memory : wasm.heap8u,

Changes to ext/wasm/api/sqlite3-api-oo1.js.

83
84
85
86
87
88
89
























































































90
91
92
93
94
95
96
     A map of sqlite3_vfs pointers to SQL code or a callback function
     to run when the DB constructor opens a database with the given
     VFS. In the latter case, the call signature is (theDbObject,sqlite3Namespace)
     and the callback is expected to throw on error.
  */
  const __vfsPostOpenSql = Object.create(null);

























































































  /**
     A proxy for DB class constructors. It must be called with the
     being-construct DB object as its "this". See the DB constructor
     for the argument docs. This is split into a separate function
     in order to enable simple creation of special-case DB constructors,
     e.g. JsStorageDb and OpfsDb.








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
     A map of sqlite3_vfs pointers to SQL code or a callback function
     to run when the DB constructor opens a database with the given
     VFS. In the latter case, the call signature is (theDbObject,sqlite3Namespace)
     and the callback is expected to throw on error.
  */
  const __vfsPostOpenSql = Object.create(null);

  /**
     Converts ArrayBuffer or Uint8Array ba into a string of hex
     digits.
  */
  const byteArrayToHex = function(ba){
    if( ba instanceof ArrayBuffer ){
      ba = new Uint8Array(ba);
    }
    const li = [];
    const digits = "0123456789abcdef";
    for( const d of ba ){
      li.push( digits[(d & 0xf0) >> 4], digits[d & 0x0f] );
    }
    return li.join('');
  };

//#if enable-see
  /**
     Internal helper to apply an SEE key to a just-opened
     database. Requires that db be-a DB object which has just been
     opened, opt be the options object processed by its ctor, and opt
     must have either the key, hexkey, or textkey properties, either
     as a string, an ArrayBuffer, or a Uint8Array.

     This is a no-op in non-SEE builds. It throws on error and returns
     without side effects if its key/textkey options are not of valid
     types.

     Returns true if it applies the key, else a falsy value.
  */
  const dbCtorApplySEEKey = function(db,opt){
    if( !capi.sqlite3_key_v2 ) return;
    let keytype;
    let key;
    const check = (opt.key ? 1 : 0) + (opt.hexkey ? 1 : 0) + (opt.textkey ? 1 : 0);
    if( !check ) return;
    else if( check>1 ) toss3("Only ONE of (key, hexkey, textkey) may be provided.");
    if( opt.key ){
      /* It is not legal to bind an argument to PRAGMA key=?, so we
         convert it to a hexkey... */
      keytype = 'key';
      key = opt.key;
      if('string'===typeof key){
        key = new TextEncoder('utf-8').encode(key);
      }
      if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
        key = byteArrayToHex(key);
        keytype = 'hexkey';
      }else{
        toss3("Invalid value for the 'key' option. Expecting a string, ArrayBuffer, or Uint8Array.");
        return;
      }
    }else if( opt.textkey ){
      /* For textkey we need it to be in string form, so convert it to
         a string if it's a byte array... */
      keytype = 'textkey';
      key = opt.textkey;
      if(key instanceof ArrayBuffer){
        key = new Uint8Array(key);
      }
      if(key instanceof Uint8Array){
        key = new TextDecoder('utf-8').decode(key);
      }else if('string'!==typeof key){
        toss3("Invalid value for the 'textkey' option. Expecting a string, ArrayBuffer, or Uint8Array.");
      }
    }else if( opt.hexkey ){
      keytype = 'hexkey';
      key = opt.hexkey;
      if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
        key = byteArrayToHex(key);
      }else if('string'!==typeof key){
        toss3("Invalid value for the 'hexkey' option. Expecting a string, ArrayBuffer, or Uint8Array.");
      }
      /* else assume it's valid hex codes */;
    }else{
      return;
    }
    let stmt;
    try{
      stmt = db.prepare("PRAGMA "+keytype+"="+util.sqlite3__wasm_qfmt_token(key, 1));
      stmt.step();
    }finally{
      if(stmt) stmt.finalize();
    }
    return true;
  };
//#endif enable-see

  /**
     A proxy for DB class constructors. It must be called with the
     being-construct DB object as its "this". See the DB constructor
     for the argument docs. This is split into a separate function
     in order to enable simple creation of special-case DB constructors,
     e.g. JsStorageDb and OpfsDb.

171
172
173
174
175
176
177



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200
201
202
203
204
205
206
    }finally{
      wasm.pstack.restore(stack);
    }
    this.filename = fnJs;
    __ptrMap.set(this, pDb);
    __stmtMap.set(this, Object.create(null));
    try{



      // Check for per-VFS post-open SQL/callback...
      const pVfs = capi.sqlite3_js_db_vfs(pDb);
      if(!pVfs) toss3("Internal error: cannot get VFS for new db handle.");
      const postInitSql = __vfsPostOpenSql[pVfs];
      if(postInitSql){
        if(capi.sqlite3_activate_see){
          /**
             In SEE-capable builds we have to avoid running any db
             code before the client has an opportunity to apply their
             decryption key. If we first run any db code, e.g. pragma
             journal_mode=..., then it will fail with SQLITE_NOTADB
             and the db handle will be left in an unusuable
             state. Note that at this point we do not actually know
             whether the db is encrypted, but if a client has gone out
             of their way to create an SEE build, it seems safe to

             assume that they are using the encryption.
          */
          sqlite3.config.warn(
            "Disabling execution of on-open() db code "+
            "because this is an SEE build. DB: "+fnJs
          );
        }else if(postInitSql instanceof Function){
          postInitSql(this, sqlite3);
        }else{
          checkSqlite3Rc(
            pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
          );
        }
      }







>
>
>

|
|


<
|
<
|
<
|
<
<
|
<
>
|
|
<
<
<
<
|







259
260
261
262
263
264
265
266
267
268
269
270
271
272
273

274

275

276


277

278
279
280




281
282
283
284
285
286
287
288
    }finally{
      wasm.pstack.restore(stack);
    }
    this.filename = fnJs;
    __ptrMap.set(this, pDb);
    __stmtMap.set(this, Object.create(null));
    try{
//#if enable-see
      dbCtorApplySEEKey(this,opt);
//#endif
      // Check for per-VFS post-open SQL/callback...
      const pVfs = capi.sqlite3_js_db_vfs(pDb)
            || toss3("Internal error: cannot get VFS for new db handle.");
      const postInitSql = __vfsPostOpenSql[pVfs];
      if(postInitSql){

        /**

           Reminder: if this db is encrypted and the client did _not_ pass

           in the key, any init code will fail, causing the ctor to throw.


           We don't actually know whether the db is encrypted, so we cannot

           sensibly apply any heuristics which skip the init code only for
           encrypted databases for which no key has yet been supplied.
        */




        if(postInitSql instanceof Function){
          postInitSql(this, sqlite3);
        }else{
          checkSqlite3Rc(
            pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
          );
        }
      }
294
295
296
297
298
299
300














301
302
303
304
305
306
307
     in the form of a single configuration object with the following
     properties:

     - `filename`: database file name
     - `flags`: open-mode flags
     - `vfs`: the VFS fname















     The `filename` and `vfs` arguments may be either JS strings or
     C-strings allocated via WASM. `flags` is required to be a JS
     string (because it's specific to this API, which is specific
     to JS).

     For purposes of passing a DB instance to C-style sqlite3
     functions, the DB object's read-only `pointer` property holds its







>
>
>
>
>
>
>
>
>
>
>
>
>
>







376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
     in the form of a single configuration object with the following
     properties:

     - `filename`: database file name
     - `flags`: open-mode flags
     - `vfs`: the VFS fname

//#if enable-see
     And, for SEE-capable builds, optionally ONE of the following:

     - `key`, `hexkey`, or `textkey`: encryption key as a string,
       ArrayBuffer, or Uint8Array. These flags function as documented
       for the SEE pragmas of the same names.

     In non-SEE builds, these options are ignored. In SEE builds,
     `PRAGMA key/textkey/hexkey=X` is executed immediately after
     opening the db. If more than one of the options is provided,
     or any option has an invalid argument type, an exception is
     thrown.
//#endif enable-see

     The `filename` and `vfs` arguments may be either JS strings or
     C-strings allocated via WASM. `flags` is required to be a JS
     string (because it's specific to this API, which is specific
     to JS).

     For purposes of passing a DB instance to C-style sqlite3
     functions, the DB object's read-only `pointer` property holds its
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
         property when binding an array/object (see below) is treated
         the same as null.

       - Numbers are bound as either doubles or integers: doubles if
         they are larger than 32 bits, else double or int32, depending
         on whether they have a fractional part. Booleans are bound as
         integer 0 or 1. It is not expected the distinction of binding
         doubles which have no fractional parts is integers is
         significant for the majority of clients due to sqlite3's data
         typing model. If [BigInt] support is enabled then this
         routine will bind BigInt values as 64-bit integers if they'll
         fit in 64 bits. If that support disabled, it will store the
         BigInt as an int32 or a double if it can do so without loss
         of precision. If the BigInt is _too BigInt_ then it will
         throw.







|







1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
         property when binding an array/object (see below) is treated
         the same as null.

       - Numbers are bound as either doubles or integers: doubles if
         they are larger than 32 bits, else double or int32, depending
         on whether they have a fractional part. Booleans are bound as
         integer 0 or 1. It is not expected the distinction of binding
         doubles which have no fractional parts and integers is
         significant for the majority of clients due to sqlite3's data
         typing model. If [BigInt] support is enabled then this
         routine will bind BigInt values as 64-bit integers if they'll
         fit in 64 bits. If that support disabled, it will store the
         BigInt as an int32 or a double if it can do so without loss
         of precision. If the BigInt is _too BigInt_ then it will
         throw.
1942
1943
1944
1945
1946
1947
1948











1949
1950


1951
1952
1953

1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
  }/*oo1 object*/;

  if(util.isUIThread()){
    /**
       Functionally equivalent to DB(storageName,'c','kvvfs') except
       that it throws if the given storage name is not one of 'local'
       or 'session'.











    */
    sqlite3.oo1.JsStorageDb = function(storageName='session'){


      if('session'!==storageName && 'local'!==storageName){
        toss3("JsStorageDb db name must be one of 'session' or 'local'.");
      }

      dbCtorHelper.call(this, {
        filename: storageName,
        flags: 'c',
        vfs: "kvvfs"
      });
    };
    const jdb = sqlite3.oo1.JsStorageDb;
    jdb.prototype = Object.create(DB.prototype);
    /** Equivalent to sqlite3_js_kvvfs_clear(). */
    jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
    /**
       Clears this database instance's storage or throws if this







>
>
>
>
>
>
>
>
>
>
>


>
>



>
|
<
<
<
<







2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064




2065
2066
2067
2068
2069
2070
2071
  }/*oo1 object*/;

  if(util.isUIThread()){
    /**
       Functionally equivalent to DB(storageName,'c','kvvfs') except
       that it throws if the given storage name is not one of 'local'
       or 'session'.

       As of version 3.46, the argument may optionally be an options
       object in the form:

       {
         filename: 'session'|'local',
         ... etc. (all options supported by the DB ctor)
       }

       noting that the 'vfs' option supported by main DB
       constructor is ignored here: the vfs is always 'kvvfs'.
    */
    sqlite3.oo1.JsStorageDb = function(storageName='session'){
      const opt = dbCtorHelper.normalizeArgs(...arguments);
      storageName = opt.filename;
      if('session'!==storageName && 'local'!==storageName){
        toss3("JsStorageDb db name must be one of 'session' or 'local'.");
      }
      opt.vfs = 'kvvfs';
      dbCtorHelper.call(this, opt);




    };
    const jdb = sqlite3.oo1.JsStorageDb;
    jdb.prototype = Object.create(DB.prototype);
    /** Equivalent to sqlite3_js_kvvfs_clear(). */
    jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
    /**
       Clears this database instance's storage or throws if this

Changes to ext/wasm/api/sqlite3-wasm.c.

1673
1674
1675
1676
1677
1678
1679



















1680
1681
1682
1683
1684
1685
1686
** Binding for combinations of sqlite3_config() arguments which take
** a single i64 argument.
*/
SQLITE_WASM_EXPORT
int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
  return sqlite3_config(op, arg);
}




















#if 0
// Pending removal after verification of a workaround discussed in the
// forum post linked to below.
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
** Binding for combinations of sqlite3_config() arguments which take
** a single i64 argument.
*/
SQLITE_WASM_EXPORT
int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
  return sqlite3_config(op, arg);
}

/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** If z is not NULL, returns the result of passing z to
** sqlite3_mprintf()'s %Q modifier (if addQuotes is true) or %q (if
** addQuotes is 0). Returns NULL if z is NULL or on OOM.
*/
SQLITE_WASM_EXPORT
char * sqlite3__wasm_qfmt_token(char *z, int addQuotes){
  char * rc = 0;
  if( z ){
    rc = addQuotes
      ? sqlite3_mprintf("%Q", z)
      : sqlite3_mprintf("%q", z);
  }
  return rc;
}

#if 0
// Pending removal after verification of a workaround discussed in the
// forum post linked to below.
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.

Changes to ext/wasm/tester1.c-pp.js.

1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
        T.assert(0===st.columnCount);
        const ndx = st.getParamIndex(':b');
        T.assert(1===ndx);
        st.bindAsBlob(ndx, "ima blob")
          /*step() skipped intentionally*/.reset(true);
      } finally {
        T.assert(0===st.finalize())
          .assert(undefined===st.finalize());        
      }

      try {
        db.prepare("/*empty SQL*/");
        toss("Must not be reached.");
      }catch(e){
        T.assert(e instanceof sqlite3.SQLite3Error)







|







1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
        T.assert(0===st.columnCount);
        const ndx = st.getParamIndex(':b');
        T.assert(1===ndx);
        st.bindAsBlob(ndx, "ima blob")
          /*step() skipped intentionally*/.reset(true);
      } finally {
        T.assert(0===st.finalize())
          .assert(undefined===st.finalize());
      }

      try {
        db.prepare("/*empty SQL*/");
        toss("Must not be reached.");
      }catch(e){
        T.assert(e instanceof sqlite3.SQLite3Error)
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
















































2608
2609
2610
2611
2612
2613
2614
      predicate: ()=>(isUIThread()
                      || "local/sessionStorage are unavailable in a Worker"),
      test: function(sqlite3){
        const filename = this.kvvfsDbFile = 'session';
        const pVfs = capi.sqlite3_vfs_find('kvvfs');
        T.assert(pVfs);
        const JDb = this.JDb = sqlite3.oo1.JsStorageDb;
        const unlink = this.kvvfsUnlink = ()=>{JDb.clearStorage(filename)};
        unlink();
        let db = new JDb(filename);
        try {
          db.exec([
            'create table kvvfs(a);',
            'insert into kvvfs(a) values(1),(2),(3)'
          ]);
          T.assert(3 === db.selectValue('select count(*) from kvvfs'));
          db.close();
          db = new JDb(filename);
          db.exec('insert into kvvfs(a) values(4),(5),(6)');
          T.assert(6 === db.selectValue('select count(*) from kvvfs'));
        }finally{
          db.close();
        }
      }
    }/*kvvfs sanity checks*/)
















































  ;/* end kvvfs tests */

  ////////////////////////////////////////////////////////////////////////
  T.g('Hook APIs')
    .t({
      name: "sqlite3_commit/rollback/update_hook()",
      predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64",







|

















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
      predicate: ()=>(isUIThread()
                      || "local/sessionStorage are unavailable in a Worker"),
      test: function(sqlite3){
        const filename = this.kvvfsDbFile = 'session';
        const pVfs = capi.sqlite3_vfs_find('kvvfs');
        T.assert(pVfs);
        const JDb = this.JDb = sqlite3.oo1.JsStorageDb;
        const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(this.kvvfsDbFile);
        unlink();
        let db = new JDb(filename);
        try {
          db.exec([
            'create table kvvfs(a);',
            'insert into kvvfs(a) values(1),(2),(3)'
          ]);
          T.assert(3 === db.selectValue('select count(*) from kvvfs'));
          db.close();
          db = new JDb(filename);
          db.exec('insert into kvvfs(a) values(4),(5),(6)');
          T.assert(6 === db.selectValue('select count(*) from kvvfs'));
        }finally{
          db.close();
        }
      }
    }/*kvvfs sanity checks*/)
//#if enable-see
    .t({
      name: 'kvvfs with SEE encryption',
      predicate: ()=>(isUIThread()
                      || "Only available in main thread."),
      test: function(sqlite3){
        this.kvvfsUnlink();
        let db;
        try{
          db = new this.JDb({
            filename: this.kvvfsDbFile,
            key: 'foo'
          });
          db.exec([
            "create table t(a,b);",
            "insert into t(a,b) values(1,2),(3,4)"
          ]);
          db.close();
          let err;
          try{
            db = new this.JDb({
              filename: this.kvvfsDbFile,
              flags: 'ct'
            });
            T.assert(db) /* opening is fine, but... */;
            db.exec("select 1 from sqlite_schema");
            console.warn("sessionStorage =",sessionStorage);
          }catch(e){
            err = e;
          }finally{
            db.close();
          }
          T.assert(err,"Expecting an exception")
            .assert(sqlite3.capi.SQLITE_NOTADB==err.resultCode,
                    "Expecting NOTADB");
          db = new sqlite3.oo1.DB({
            filename: this.kvvfsDbFile,
            vfs: 'kvvfs',
            hexkey: new Uint8Array([0x66,0x6f,0x6f]) // equivalent: '666f6f'
          });
          T.assert( 4===db.selectValue('select sum(a) from t') );
        }finally{
          if( db ) db.close();
          this.kvvfsUnlink();
        }
      }
    })/*kvvfs with SEE*/
//#endif enable-see
  ;/* end kvvfs tests */

  ////////////////////////////////////////////////////////////////////////
  T.g('Hook APIs')
    .t({
      name: "sqlite3_commit/rollback/update_hook()",
      predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64",