SQLite

Changes On Branch wasm-session-api
Login

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

Changes In Branch wasm-session-api Excluding Merge-Ins

This is equivalent to a diff from 8e3d4f62 to 64e03260

2022-12-25
20:25
Merge wasm-session-api branch into trunk, adding the session API to the JS/WASM components. (check-in: dfb8b651 user: stephan tags: trunk)
20:22
Add basic session API JS tests. (Closed-Leaf check-in: 64e03260 user: stephan tags: wasm-session-api)
20:05
Add sqlite3.wasm.irSizeof() and extend certain allocation functions to make use of it. (check-in: 1cbc7b18 user: stephan tags: wasm-session-api)
17:09
Replace the "manual" implementation of sqlite3.capi.sqlite3_exec() with a briefer "automated" one via the [7f9ace1b11a67] feature addition. Minor code-adjacent internal cleanups. (check-in: 4888957b user: stephan tags: trunk)
14:13
Merge trunk into wasm-session-api branch. (check-in: 7f8f1acd user: stephan tags: wasm-session-api)
14:04
Add missing sqlite3_context_db_handle() JS binding. Reimplement sqlite3_set_authorizer() and sqlite3_set_auxdata() JS bindings to take advantage of [7f9ace1b11a67]. Teach FuncPtrAdapter to emit a console.warn() message if it is invoked after the library is bootstrapped, the goal being to inform users that it's an internal API and should not be invoked from client-side code. (check-in: 8e3d4f62 user: stephan tags: trunk)
12:51
Enhance sqlite3.wasm.xWrap.FuncPtrAdapter to be able to handle sqlite3_create_function() and friends and reimplement those bindings to use this feature (this will also simplify certain session API bindings). Interal API changes only with no client-side breakage. (check-in: 7f9ace1b user: stephan tags: trunk)

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




1
2
3
4
5
6
7



_sqlite3_aggregate_context
_sqlite3_bind_blob
_sqlite3_bind_double
_sqlite3_bind_int
_sqlite3_bind_int64
_sqlite3_bind_null
_sqlite3_bind_parameter_count
>
>
>







1
2
3
4
5
6
7
8
9
10
_malloc
_free
_realloc
_sqlite3_aggregate_context
_sqlite3_bind_blob
_sqlite3_bind_double
_sqlite3_bind_int
_sqlite3_bind_int64
_sqlite3_bind_null
_sqlite3_bind_parameter_count
140
141
142
143
144
145
146













147
148
















149










_sqlite3_vtab_distinct
_sqlite3_vtab_in
_sqlite3_vtab_in_first
_sqlite3_vtab_in_next
_sqlite3_vtab_nochange
_sqlite3_vtab_on_conflict
_sqlite3_vtab_rhs_value













_malloc
_free
















_realloc

















>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
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
185
186
187
188
189
190
191
_sqlite3_vtab_distinct
_sqlite3_vtab_in
_sqlite3_vtab_in_first
_sqlite3_vtab_in_next
_sqlite3_vtab_nochange
_sqlite3_vtab_on_conflict
_sqlite3_vtab_rhs_value
_sqlite3changegroup_add
_sqlite3changegroup_add_strm
_sqlite3changegroup_delete
_sqlite3changegroup_new
_sqlite3changegroup_output
_sqlite3changegroup_output_strm
_sqlite3changeset_apply
_sqlite3changeset_apply_strm
_sqlite3changeset_apply_v2
_sqlite3changeset_apply_v2_strm
_sqlite3changeset_concat
_sqlite3changeset_concat_strm
_sqlite3changeset_conflict
_sqlite3changeset_finalize
_sqlite3changeset_fk_conflicts
_sqlite3changeset_invert
_sqlite3changeset_invert_strm
_sqlite3changeset_new
_sqlite3changeset_next
_sqlite3changeset_old
_sqlite3changeset_op
_sqlite3changeset_pk
_sqlite3changeset_start
_sqlite3changeset_start_strm
_sqlite3changeset_start_v2
_sqlite3changeset_start_v2_strm
_sqlite3session_attach
_sqlite3session_changeset
_sqlite3session_changeset_size
_sqlite3session_changeset_strm
_sqlite3session_config
_sqlite3session_create
_sqlite3session_delete
_sqlite3session_diff
_sqlite3session_enable
_sqlite3session_indirect
_sqlite3session_isempty
_sqlite3session_memory_used
_sqlite3session_object_config
_sqlite3session_patchset
_sqlite3session_patchset_strm
_sqlite3session_table_filter

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

310
311
312
313
314
315
316


















































































































































































317
318
319
320
321
322
323
    /*["sqlite3_vtab_config" is variadic and requires a hand-written
      proxy.] */
    ["sqlite3_vtab_nochange","int", "sqlite3_context*"],
    ["sqlite3_vtab_on_conflict","int", "sqlite3*"],
    ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"]
  ];



















































































































































































  /**
     Functions which are intended solely for API-internal use by the
     WASM components, not client code. These get installed into
     sqlite3.wasm. Some of them get exposed to clients via variants
     named sqlite3_js_...().
  */
  wasm.bindingSignatures.wasm = [







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







310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
    /*["sqlite3_vtab_config" is variadic and requires a hand-written
      proxy.] */
    ["sqlite3_vtab_nochange","int", "sqlite3_context*"],
    ["sqlite3_vtab_on_conflict","int", "sqlite3*"],
    ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"]
  ];

  // Add session/changeset APIs...
  if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){
    /* ACHTUNG: 2022-12-23: the session/changeset API bindings are
       COMPLETELY UNTESTED. */
    /**
       FuncPtrAdapter options for session-related callbacks with the
       native signature "i(ps)". This proxy converts the 2nd argument
       from a C string to a JS string before passing the arguments on
       to the client-provided JS callback.
    */
    const __ipsProxy = {
      signature: 'i(ps)',
      callProxy:(callback)=>{
        return (p,s)=>{
          try{return callback(p, wasm.cstrToJs(s)) | 0}
          catch(e){return e.resultCode || capi.SQLITE_ERROR}
        }
      }
    };

    wasm.bindingSignatures.int64.push(...[
      ['sqlite3changegroup_add', 'int', ['sqlite3_changegroup*', 'int', 'void*']],
      ['sqlite3changegroup_add_strm', 'int', [
        'sqlite3_changegroup*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3changegroup_delete', undefined, ['sqlite3_changegroup*']],
      ['sqlite3changegroup_new', 'int', ['**']],
      ['sqlite3changegroup_output', 'int', ['sqlite3_changegroup*', 'int*', '**']],
      ['sqlite3changegroup_output_strm', 'int', [
        'sqlite3_changegroup*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3changeset_apply', 'int', [
        'sqlite3*', 'int', 'void*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xFilter', bindScope: 'transient', ...__ipsProxy
        }),
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3changeset_apply_strm', 'int', [
        'sqlite3*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xFilter', bindScope: 'transient', ...__ipsProxy
        }),
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3changeset_apply_v2', 'int', [
        'sqlite3*', 'int', 'void*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xFilter', bindScope: 'transient', ...__ipsProxy
        }),
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
        }),
        'void*', '**', 'int*', 'int'

      ]],
      ['sqlite3changeset_apply_v2_strm', 'int', [
        'sqlite3*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xFilter', bindScope: 'transient', ...__ipsProxy
        }),
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xConflict', signature: 'i(pip)', bindScope: 'transient'
        }),
        'void*', '**', 'int*', 'int'
      ]],
      ['sqlite3changeset_concat', 'int', ['int','void*', 'int', 'void*', 'int*', '**']],
      ['sqlite3changeset_concat_strm', 'int', [
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInputA', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInputB', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3changeset_conflict', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
      ['sqlite3changeset_finalize', 'int', ['sqlite3_changeset_iter*']],
      ['sqlite3changeset_fk_conflicts', 'int', ['sqlite3_changeset_iter*', 'int*']],
      ['sqlite3changeset_invert', 'int', ['int', 'void*', 'int*', '**']],
      ['sqlite3changeset_invert_strm', 'int', [
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3changeset_new', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
      ['sqlite3changeset_next', 'int', ['sqlite3_changeset_iter*']],
      ['sqlite3changeset_old', 'int', ['sqlite3_changeset_iter*', 'int', '**']],
      ['sqlite3changeset_op', 'int', [
        'sqlite3_changeset_iter*', '**', 'int*', 'int*','int*'
      ]],
      ['sqlite3changeset_pk', 'int', ['sqlite3_changeset_iter*', '**', 'int*']],
      ['sqlite3changeset_start', 'int', ['**', 'int', '*']],
      ['sqlite3changeset_start_strm', 'int', [
        '**',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3changeset_start_v2', 'int', ['**', 'int', '*', 'int']],
      ['sqlite3changeset_start_v2_strm', 'int', [
        '**',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xInput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*', 'int'
      ]],
      ['sqlite3session_attach', 'int', ['sqlite3_session*', 'string']],
      ['sqlite3session_changeset', 'int', ['sqlite3_session*', 'int*', '**']],
      ['sqlite3session_changeset_size', 'i64', ['sqlite3_session*']],
      ['sqlite3session_changeset_strm', 'int', [
        'sqlite3_session*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3session_config', 'int', ['int', 'void*']],
      ['sqlite3session_create', 'int', ['sqlite3*', 'string', '**']],
      ['sqlite3session_delete', undefined, ['sqlite3_session*']],
      ['sqlite3session_diff', 'int', ['sqlite3_session*', 'string', 'string', '**']],
      ['sqlite3session_enable', 'int', ['sqlite3_session*', 'int']],
      ['sqlite3session_indirect', 'int', ['sqlite3_session*', 'int']],
      ['sqlite3session_isempty', 'int', ['sqlite3_session*']],
      ['sqlite3session_memory_used', 'i64', ['sqlite3_session*']],
      ['sqlite3session_object_config', 'int', ['sqlite3_session*', 'int', 'void*']],
      ['sqlite3session_patchset', 'int', ['sqlite3_session*', '*', '**']],
      ['sqlite3session_patchset_strm', 'int', [
        'sqlite3_session*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient'
        }),
        'void*'
      ]],
      ['sqlite3session_table_filter', undefined, [
        'sqlite3_session*',
        new wasm.xWrap.FuncPtrAdapter({
          name: 'xFilter', ...__ipsProxy,
          contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */]
        }),
        '*'
      ]]
    ]);
  }/*session/changeset APIs*/

  /**
     Functions which are intended solely for API-internal use by the
     WASM components, not client code. These get installed into
     sqlite3.wasm. Some of them get exposed to clients via variants
     named sqlite3_js_...().
  */
  wasm.bindingSignatures.wasm = [
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
      }.bind(Object.create(null))
    );
  }/* special-case string-type argument conversions */

  if(1){// wasm.xWrap() bindings...
    /**
       Add some descriptive xWrap() aliases for '*' intended to (A)
       initially improve readability/correctness of capi.signatures
       and (B) provide automatic conversion from higher-level
       representations, e.g. capi.sqlite3_vfs to `sqlite3_vfs*` via
       capi.sqlite3_vfs.pointer.
    */
    const aPtr = wasm.xWrap.argAdapter('*');
    const nilType = function(){};
    wasm.xWrap.argAdapter('sqlite3_filename', aPtr)
    ('sqlite3_context*', aPtr)
    ('sqlite3_value*', aPtr)
    ('void*', aPtr)




    ('sqlite3_stmt*', (v)=>
      aPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType))
           ? v.pointer : v))
    ('sqlite3*', (v)=>
      aPtr((v instanceof (sqlite3?.oo1?.DB || nilType))
           ? v.pointer : v))
    ('sqlite3_index_info*', (v)=>







|
|
|
|







>
>
>
>







557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
      }.bind(Object.create(null))
    );
  }/* special-case string-type argument conversions */

  if(1){// wasm.xWrap() bindings...
    /**
       Add some descriptive xWrap() aliases for '*' intended to (A)
       initially improve readability/correctness of
       wasm.bindingSignatures and (B) provide automatic conversion
       from higher-level representations, e.g. capi.sqlite3_vfs to
       `sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
    */
    const aPtr = wasm.xWrap.argAdapter('*');
    const nilType = function(){};
    wasm.xWrap.argAdapter('sqlite3_filename', aPtr)
    ('sqlite3_context*', aPtr)
    ('sqlite3_value*', aPtr)
    ('void*', aPtr)
    ('sqlite3_changegroup*', aPtr)
    ('sqlite3_changeset_iter*', aPtr)
    //('sqlite3_rebaser*', aPtr)
    ('sqlite3_session*', aPtr)
    ('sqlite3_stmt*', (v)=>
      aPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType))
           ? v.pointer : v))
    ('sqlite3*', (v)=>
      aPtr((v instanceof (sqlite3?.oo1?.DB || nilType))
           ? v.pointer : v))
    ('sqlite3_index_info*', (v)=>
1017
1018
1019
1020
1021
1022
1023
1024

1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
      toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
           "static buffer size!");
    }
    //console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
    wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
    // Groups of SQLITE_xyz macros...
    const defineGroups = ['access', 'authorizer',
                          'blobFinalizers', 'config', 'dataTypes',

                          'dbConfig', 'dbStatus',
                          'encodings', 'fcntl', 'flock', 'ioCap',
                          'limits', 'openFlags',
                          'prepareFlags', 'resultCodes',
                          'sqlite3Status',
                          'stmtStatus', 'syncFlags',
                          'trace', 'txnState', 'udfFlags',
                          'version' ];
    if(wasm.bigIntEnabled){
      defineGroups.push('serialize', 'vtab');
    }
    for(const t of defineGroups){
      for(const e of Object.entries(wasm.ctype[t])){
        // ^^^ [k,v] there triggers a buggy code transformation via
        // one of the Emscripten-driven optimizers.
        capi[e[0]] = e[1];
      }







|
>









|







1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
      toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
           "static buffer size!");
    }
    //console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
    wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
    // Groups of SQLITE_xyz macros...
    const defineGroups = ['access', 'authorizer',
                          'blobFinalizers', 'changeset',
                          'config', 'dataTypes',
                          'dbConfig', 'dbStatus',
                          'encodings', 'fcntl', 'flock', 'ioCap',
                          'limits', 'openFlags',
                          'prepareFlags', 'resultCodes',
                          'sqlite3Status',
                          'stmtStatus', 'syncFlags',
                          'trace', 'txnState', 'udfFlags',
                          'version' ];
    if(wasm.bigIntEnabled){
      defineGroups.push('serialize', 'session', 'vtab');
    }
    for(const t of defineGroups){
      for(const e of Object.entries(wasm.ctype[t])){
        // ^^^ [k,v] there triggers a buggy code transformation via
        // one of the Emscripten-driven optimizers.
        capi[e[0]] = e[1];
      }

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

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
     sqlite3_trace_v2() callback which gets installed by the DB ctor
     if its open-flags contain "t".
  */
  const __dbTraceToConsole =
        wasm.installFunction('i(ippp)', function(t,c,p,x){
          if(capi.SQLITE_TRACE_STMT===t){
            // x == SQL, p == sqlite3_stmt*
            console.log("SQL TRACE #"+(++this.counter),
                        wasm.cstrToJs(x));
          }
        }.bind({counter: 0}));

  /**
     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







|







68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
     sqlite3_trace_v2() callback which gets installed by the DB ctor
     if its open-flags contain "t".
  */
  const __dbTraceToConsole =
        wasm.installFunction('i(ippp)', function(t,c,p,x){
          if(capi.SQLITE_TRACE_STMT===t){
            // x == SQL, p == sqlite3_stmt*
            console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':',
                        wasm.cstrToJs(x));
          }
        }.bind({counter: 0}));

  /**
     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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
      const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
      let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
      pDb = wasm.peekPtr(pPtr);
      checkSqlite3Rc(pDb, rc);
      capi.sqlite3_extended_result_codes(pDb, 1);
      if(flagsStr.indexOf('t')>=0){
        capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
                              __dbTraceToConsole, 0);
      }
    }catch( e ){
      if( pDb ) capi.sqlite3_close_v2(pDb);
      throw e;
    }finally{
      wasm.pstack.restore(stack);
    }







|







157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
      const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
      let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
      pDb = wasm.peekPtr(pPtr);
      checkSqlite3Rc(pDb, rc);
      capi.sqlite3_extended_result_codes(pDb, 1);
      if(flagsStr.indexOf('t')>=0){
        capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
                              __dbTraceToConsole, pDb);
      }
    }catch( e ){
      if( pDb ) capi.sqlite3_close_v2(pDb);
      throw e;
    }finally{
      wasm.pstack.restore(stack);
    }

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

1015
1016
1017
1018
1019
1020
1021





1022
1023
1024
1025
1026
1027
1028



1029
1030
1031
1032
1033
1034
1035
1036
1037


1038
1039
1040
1041
1042
1043
1044
1045
1046



1047
1048
1049
1050
1051
1052
1053
    restore: wasm.exports.sqlite3_wasm_pstack_restore,
    /**
       Attempts to allocate the given number of bytes from the
       pstack. On success, it zeroes out a block of memory of the
       given size, adjusts the pstack pointer, and returns a pointer
       to the memory. On error, returns throws a WasmAllocError. The
       memory must eventually be released using restore().






       This method always adjusts the given value to be a multiple
       of 8 bytes because failing to do so can lead to incorrect
       results when reading and writing 64-bit values from/to the WASM
       heap. Similarly, the returned address is always 8-byte aligned.
    */
    alloc: (n)=>{



      return wasm.exports.sqlite3_wasm_pstack_alloc(n)
        || WasmAllocError.toss("Could not allocate",n,
                               "bytes from the pstack.");
    },
    /**
       alloc()'s n chunks, each sz bytes, as a single memory block and
       returns the addresses as an array of n element, each holding
       the address of one chunk.



       Throws a WasmAllocError if allocation fails.

       Example:

       ```
       const [p1, p2, p3] = wasm.pstack.allocChunks(3,4);
       ```
    */
    allocChunks: (n,sz)=>{



      const mem = wasm.pstack.alloc(n * sz);
      const rc = [];
      let i = 0, offset = 0;
      for(; i < n; offset = (sz * ++i)){
        rc.push(mem + offset);
      }
      return rc;







>
>
>
>
>






|
>
>
>









>
>








|
>
>
>







1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
    restore: wasm.exports.sqlite3_wasm_pstack_restore,
    /**
       Attempts to allocate the given number of bytes from the
       pstack. On success, it zeroes out a block of memory of the
       given size, adjusts the pstack pointer, and returns a pointer
       to the memory. On error, returns throws a WasmAllocError. The
       memory must eventually be released using restore().

       If n is a string, it must be a WASM "IR" value in the set
       accepted by wasm.irSizeof(), which is mapped to the size of
       that data type. If passed a string not in that set, it throws a
       WasmAllocError.

       This method always adjusts the given value to be a multiple
       of 8 bytes because failing to do so can lead to incorrect
       results when reading and writing 64-bit values from/to the WASM
       heap. Similarly, the returned address is always 8-byte aligned.
    */
    alloc: function(n){
      if('string'===typeof n && !(n = wasm.irSizeof(n))){
        WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")");
      }
      return wasm.exports.sqlite3_wasm_pstack_alloc(n)
        || WasmAllocError.toss("Could not allocate",n,
                               "bytes from the pstack.");
    },
    /**
       alloc()'s n chunks, each sz bytes, as a single memory block and
       returns the addresses as an array of n element, each holding
       the address of one chunk.

       sz may optionally be an IR string accepted by wasm.irSizeof().

       Throws a WasmAllocError if allocation fails.

       Example:

       ```
       const [p1, p2, p3] = wasm.pstack.allocChunks(3,4);
       ```
    */
    allocChunks: function(n,sz){
      if('string'===typeof sz && !(sz = wasm.irSizeof(sz))){
        WasmAllocError.toss("Invalid size value for allocChunks(",arguments[1],")");
      }
      const mem = wasm.pstack.alloc(n * sz);
      const rc = [];
      let i = 0, offset = 0;
      for(; i < n; offset = (sz * ++i)){
        rc.push(mem + offset);
      }
      return rc;

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

95
96
97
98
99
100
101
102
103
104
105
106
107






108
109
110



111
112
113
114
115
116
117
#endif
#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
#  define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
#endif
#ifndef SQLITE_ENABLE_FTS4
#  define SQLITE_ENABLE_FTS4 1
#endif
#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
#  define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
#endif
#ifndef SQLITE_ENABLE_MATH_FUNCTIONS
#  define SQLITE_ENABLE_MATH_FUNCTIONS 1
#endif






#ifndef SQLITE_ENABLE_RTREE
#  define SQLITE_ENABLE_RTREE 1
#endif



#ifndef SQLITE_ENABLE_STMTVTAB
#  define SQLITE_ENABLE_STMTVTAB 1
#endif
#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
#  define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
#endif








<
<
<



>
>
>
>
>
>



>
>
>







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
#endif
#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
#  define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
#endif
#ifndef SQLITE_ENABLE_FTS4
#  define SQLITE_ENABLE_FTS4 1
#endif



#ifndef SQLITE_ENABLE_MATH_FUNCTIONS
#  define SQLITE_ENABLE_MATH_FUNCTIONS 1
#endif
#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
#  define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
#endif
#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
#  define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/
#endif
#ifndef SQLITE_ENABLE_RTREE
#  define SQLITE_ENABLE_RTREE 1
#endif
#ifndef SQLITE_ENABLE_SESSION
#  define SQLITE_ENABLE_SESSION 1
#endif
#ifndef SQLITE_ENABLE_STMTVTAB
#  define SQLITE_ENABLE_STMTVTAB 1
#endif
#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
#  define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
#endif

455
456
457
458
459
460
461
















462
463
464
465
466
467
468
  } _DefGroup;

  DefGroup(blobFinalizers) {
    /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as
    ** integers to avoid casting-related warnings. */
    out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1");
  } _DefGroup;

















  DefGroup(config){
    DefInt(SQLITE_CONFIG_SINGLETHREAD);
    DefInt(SQLITE_CONFIG_MULTITHREAD);
    DefInt(SQLITE_CONFIG_SERIALIZED);
    DefInt(SQLITE_CONFIG_MALLOC);
    DefInt(SQLITE_CONFIG_GETMALLOC);







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







461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
  } _DefGroup;

  DefGroup(blobFinalizers) {
    /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as
    ** integers to avoid casting-related warnings. */
    out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1");
  } _DefGroup;

  DefGroup(changeset){
    DefInt(SQLITE_CHANGESETSTART_INVERT);
    DefInt(SQLITE_CHANGESETAPPLY_NOSAVEPOINT);
    DefInt(SQLITE_CHANGESETAPPLY_INVERT);

    DefInt(SQLITE_CHANGESET_DATA);
    DefInt(SQLITE_CHANGESET_NOTFOUND);
    DefInt(SQLITE_CHANGESET_CONFLICT);
    DefInt(SQLITE_CHANGESET_CONSTRAINT);
    DefInt(SQLITE_CHANGESET_FOREIGN_KEY);

    DefInt(SQLITE_CHANGESET_OMIT);
    DefInt(SQLITE_CHANGESET_REPLACE);
    DefInt(SQLITE_CHANGESET_ABORT);
  } _DefGroup;

  DefGroup(config){
    DefInt(SQLITE_CONFIG_SINGLETHREAD);
    DefInt(SQLITE_CONFIG_MULTITHREAD);
    DefInt(SQLITE_CONFIG_SERIALIZED);
    DefInt(SQLITE_CONFIG_MALLOC);
    DefInt(SQLITE_CONFIG_GETMALLOC);
791
792
793
794
795
796
797





798
799
800
801
802
803
804

  DefGroup(serialize){
    DefInt(SQLITE_SERIALIZE_NOCOPY);
    DefInt(SQLITE_DESERIALIZE_FREEONCLOSE);
    DefInt(SQLITE_DESERIALIZE_READONLY);
    DefInt(SQLITE_DESERIALIZE_RESIZEABLE);
  } _DefGroup;






  DefGroup(sqlite3Status){
    DefInt(SQLITE_STATUS_MEMORY_USED);
    DefInt(SQLITE_STATUS_PAGECACHE_USED);
    DefInt(SQLITE_STATUS_PAGECACHE_OVERFLOW);
    //DefInt(SQLITE_STATUS_SCRATCH_USED) /* NOT USED */;
    //DefInt(SQLITE_STATUS_SCRATCH_OVERFLOW) /* NOT USED */;







>
>
>
>
>







813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831

  DefGroup(serialize){
    DefInt(SQLITE_SERIALIZE_NOCOPY);
    DefInt(SQLITE_DESERIALIZE_FREEONCLOSE);
    DefInt(SQLITE_DESERIALIZE_READONLY);
    DefInt(SQLITE_DESERIALIZE_RESIZEABLE);
  } _DefGroup;

  DefGroup(session){
    DefInt(SQLITE_SESSION_CONFIG_STRMSIZE);
    DefInt(SQLITE_SESSION_OBJCONFIG_SIZE);
  } _DefGroup;

  DefGroup(sqlite3Status){
    DefInt(SQLITE_STATUS_MEMORY_USED);
    DefInt(SQLITE_STATUS_PAGECACHE_USED);
    DefInt(SQLITE_STATUS_PAGECACHE_OVERFLOW);
    //DefInt(SQLITE_STATUS_SCRATCH_USED) /* NOT USED */;
    //DefInt(SQLITE_STATUS_SCRATCH_OVERFLOW) /* NOT USED */;

Changes to ext/wasm/common/whwasmutil.js.

241
242
243
244
245
246
247



















248
249
250
251
252
253
254
  /**
     Used by scopedAlloc() and friends.
  */
  cache.scopedAlloc = [];

  cache.utf8Decoder = new TextDecoder();
  cache.utf8Encoder = new TextEncoder('utf-8');




















  /**
     If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if
     the heap has grown since the last call, updates cache.HEAPxyz.
     Returns the cache object.
  */
  const heapWrappers = function(){







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







241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
  /**
     Used by scopedAlloc() and friends.
  */
  cache.scopedAlloc = [];

  cache.utf8Decoder = new TextDecoder();
  cache.utf8Encoder = new TextEncoder('utf-8');

  /**
     For the given IR-like string in the set ('i8', 'i16', 'i32',
     'f32', 'float', 'i64', 'f64', 'double', '*'), or any string value
     ending in '*', returns the sizeof for that value
     (target.ptrSizeof in the latter case). For any other value, it
     returns the undefined value.
  */
  target.irSizeof = (n)=>{
    switch(n){
        case 'i8': return 1;
        case 'i16': return 2;
        case 'i32': case 'f32': case 'float': return 4;
        case 'i64': case 'f64': case 'double': return 8;
        case '*': return ptrSizeof;
        default:
          return (''+n).endsWith('*') ? ptrSizeof : undefined;
    }
  };

  /**
     If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if
     the heap has grown since the last call, updates cache.HEAPxyz.
     Returns the cache object.
  */
  const heapWrappers = function(){
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
        /** Returns the IR value for the given letter or throws
            if the letter is invalid. */
        letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x),
        /** Returns an object describing the result type and parameter
            type(s) of the given function signature, or throws if the
            signature is invalid. */
        /******** // only valid for use with the WebAssembly.Function ctor, which
                  // is not yet documented on MDN. 
        sigToWasm: function(sig){
          const rc = {parameters:[], results: []};
          if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0]));
          for(const x of f._.sigParams(sig)){
            rc.parameters.push(f._.typeCodes(x));
          }
          return rc;







|







462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
        /** Returns the IR value for the given letter or throws
            if the letter is invalid. */
        letterType: (x)=>f._.sigTypes[x] || toss("Invalid signature letter:",x),
        /** Returns an object describing the result type and parameter
            type(s) of the given function signature, or throws if the
            signature is invalid. */
        /******** // only valid for use with the WebAssembly.Function ctor, which
                  // is not yet documented on MDN.
        sigToWasm: function(sig){
          const rc = {parameters:[], results: []};
          if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0]));
          for(const x of f._.sigParams(sig)){
            rc.parameters.push(f._.typeCodes(x));
          }
          return rc;
1586
1587
1588
1589
1590
1591
1592







1593
1594
1595
1596



1597
1598
1599
1600
1601
1602
1603

     Yes, that ^^^ is quite awkward, but it's what we have.

     The constructor only saves the above state for later, and does
     not actually bind any functions. Its convertArg() method is
     called via xWrap() to perform any bindings.








     Shortcomings: function pointers which include C-string arguments
     may still need a level of hand-written wrappers around them,
     depending on how they're used, in order to provide the client
     with JS strings.



  */
  xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter {
    constructor(opt) {
      super(opt);
      if(xArg.FuncPtrAdapter.warnOnUse){
        console.warn('xArg.FuncPtrAdapter is an internal-only API',
                     'and is not intended to be invoked from',







>
>
>
>
>
>
>
|
|
|
|
>
>
>







1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632

     Yes, that ^^^ is quite awkward, but it's what we have.

     The constructor only saves the above state for later, and does
     not actually bind any functions. Its convertArg() method is
     called via xWrap() to perform any bindings.

     Shortcomings:

     - These "reverse" bindings, i.e. calling into a JS-defined
       function from a WASM-defined function (the generated proxy
       wrapper), lack all type conversion support. That means, for
       example, that...

     - Function pointers which include C-string arguments may still
       need a level of hand-written wrappers around them, depending on
       how they're used, in order to provide the client with JS
       strings. Alternately, clients will need to perform such conversions
       on their own, e.g. using cstrtojs(). Or maybe we can find a way
       to perform such conversions here, via addition of an xWrap()-style
       function signature to the options argument.
  */
  xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter {
    constructor(opt) {
      super(opt);
      if(xArg.FuncPtrAdapter.warnOnUse){
        console.warn('xArg.FuncPtrAdapter is an internal-only API',
                     'and is not intended to be invoked from',

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

1006
1007
1008
1009
1010
1011
1012
1013





1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
      const stack = P.pointer;
      T.assert(0===stack % 8 /* must be 8-byte aligned */);
      try{
        const remaining = P.remaining;
        T.assert(P.quota >= 4096)
          .assert(remaining === P.quota)
          .mustThrowMatching(()=>P.alloc(0), isAllocErr)
          .mustThrowMatching(()=>P.alloc(-1), isAllocErr);





        let p1 = P.alloc(12);
        T.assert(p1 === stack - 16/*8-byte aligned*/)
          .assert(P.pointer === p1);
        let p2 = P.alloc(7);
        T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
          .mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
          .assert(24 === stack - p2)
          .assert(P.pointer === p2);
        let n = remaining - (stack - p2);
        let p3 = P.alloc(n);
        T.assert(p3 === stack-remaining)
          .mustThrowMatching(()=>P.alloc(1), isAllocErr);
      }finally{
        P.restore(stack);
      }

      T.assert(P.pointer === stack);
      try {
        const [p1, p2, p3] = P.allocChunks(3,4);
        T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
          .assert(p2 === p1 + 4)
          .assert(p3 === p2 + 4);
        T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
                            (e)=>e instanceof sqlite3.WasmAllocError)
      }finally{
        P.restore(stack);







|
>
>
>
>
>


















|







1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
      const stack = P.pointer;
      T.assert(0===stack % 8 /* must be 8-byte aligned */);
      try{
        const remaining = P.remaining;
        T.assert(P.quota >= 4096)
          .assert(remaining === P.quota)
          .mustThrowMatching(()=>P.alloc(0), isAllocErr)
          .mustThrowMatching(()=>P.alloc(-1), isAllocErr)
          .mustThrowMatching(
            ()=>P.alloc('i33'),
            (e)=>e instanceof sqlite3.WasmAllocError
          );
        ;
        let p1 = P.alloc(12);
        T.assert(p1 === stack - 16/*8-byte aligned*/)
          .assert(P.pointer === p1);
        let p2 = P.alloc(7);
        T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
          .mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
          .assert(24 === stack - p2)
          .assert(P.pointer === p2);
        let n = remaining - (stack - p2);
        let p3 = P.alloc(n);
        T.assert(p3 === stack-remaining)
          .mustThrowMatching(()=>P.alloc(1), isAllocErr);
      }finally{
        P.restore(stack);
      }

      T.assert(P.pointer === stack);
      try {
        const [p1, p2, p3] = P.allocChunks(3,'i32');
        T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
          .assert(p2 === p1 + 4)
          .assert(p3 === p2 + 4);
        T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
                            (e)=>e instanceof sqlite3.WasmAllocError)
      }finally{
        P.restore(stack);
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
              v.dispose();
            }else if(v instanceof Function){
              try{v()} catch(e){/*ignored*/}
            }
          }
        }
      };
      
      T.assert(wasm.isPtr(db.pointer))
        .mustThrowMatching(()=>db.pointer=1, /read-only/)
        .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
        .assert('main'===db.dbName(0))
        .assert('string' === typeof db.dbVfsName())
        .assert(db.pointer === wasm.xWrap.testConvertArg('sqlite3*',db));
      // Custom db error message handling via sqlite3_prepare_v2/v3()







|







1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
              v.dispose();
            }else if(v instanceof Function){
              try{v()} catch(e){/*ignored*/}
            }
          }
        }
      };

      T.assert(wasm.isPtr(db.pointer))
        .mustThrowMatching(()=>db.pointer=1, /read-only/)
        .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
        .assert('main'===db.dbName(0))
        .assert('string' === typeof db.dbVfsName())
        .assert(db.pointer === wasm.xWrap.testConvertArg('sqlite3*',db));
      // Custom db error message handling via sqlite3_prepare_v2/v3()
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
      T.assert(0===rc);
      rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAX+1, 0);
      T.assert(capi.SQLITE_MISUSE === rc);
      rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAINDBNAME, "main");
      T.assert(0 === rc);
      const stack = wasm.pstack.pointer;
      try {
        const [pCur, pHi] = wasm.pstack.allocChunks(2,8);
        rc = capi.sqlite3_db_status(this.db, capi.SQLITE_DBSTATUS_LOOKASIDE_USED,
                                    pCur, pHi, 0);
        T.assert(0===rc);
        if(wasm.peek32(pCur)){
          warn("Cannot test db_config(SQLITE_DBCONFIG_LOOKASIDE)",
               "while lookaside memory is in use.");
        }else{







|







1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
      T.assert(0===rc);
      rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAX+1, 0);
      T.assert(capi.SQLITE_MISUSE === rc);
      rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_MAINDBNAME, "main");
      T.assert(0 === rc);
      const stack = wasm.pstack.pointer;
      try {
        const [pCur, pHi] = wasm.pstack.allocChunks(2,'i64');
        rc = capi.sqlite3_db_status(this.db, capi.SQLITE_DBSTATUS_LOOKASIDE_USED,
                                    pCur, pHi, 0);
        T.assert(0===rc);
        if(wasm.peek32(pCur)){
          warn("Cannot test db_config(SQLITE_DBCONFIG_LOOKASIDE)",
               "while lookaside memory is in use.");
        }else{
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
          xFinal: (pCtx)=>{
            const ac = sjac(pCtx, 0);
            capi.sqlite3_result_int( pCtx, ac ? wasm.peek32(ac) : 0 );
            // xFinal() may either return its value directly or call
            // sqlite3_result_xyz() and return undefined. Both are
            // functionally equivalent.
          }
        }); 
        T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
        T.mustThrowMatching(()=>{
          db.createFunction('nope',{
            xFunc: ()=>{}, xStep: ()=>{}
          });
        }, /scalar or aggregate\?/);
        T.mustThrowMatching(()=>{







|







1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
          xFinal: (pCtx)=>{
            const ac = sjac(pCtx, 0);
            capi.sqlite3_result_int( pCtx, ac ? wasm.peek32(ac) : 0 );
            // xFinal() may either return its value directly or call
            // sqlite3_result_xyz() and return undefined. Both are
            // functionally equivalent.
          }
        });
        T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
        T.mustThrowMatching(()=>{
          db.createFunction('nope',{
            xFunc: ()=>{}, xStep: ()=>{}
          });
        }, /scalar or aggregate\?/);
        T.mustThrowMatching(()=>{
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
        });
        T.assert(Array.isArray(rc))
          .assert(3===rc.length)
          .assert('select 1;' === rc[0])
          .assert('select 2;' === rc[1])
          .assert('-- empty\n; select 3' === rc[2]
                  /* Strange but true. */);
        
        T.mustThrowMatching(()=>{
          db.exec({sql:'', returnValue: 'nope'});
        }, /^Invalid returnValue/);

        db.exec("DROP TABLE twin");
      }
    }/*window UDFs*/)







<







1857
1858
1859
1860
1861
1862
1863

1864
1865
1866
1867
1868
1869
1870
        });
        T.assert(Array.isArray(rc))
          .assert(3===rc.length)
          .assert('select 1;' === rc[0])
          .assert('select 2;' === rc[1])
          .assert('-- empty\n; select 3' === rc[2]
                  /* Strange but true. */);

        T.mustThrowMatching(()=>{
          db.exec({sql:'', returnValue: 'nope'});
        }, /^Invalid returnValue/);

        db.exec("DROP TABLE twin");
      }
    }/*window UDFs*/)
2628
2629
2630
2631
2632
2633
2634








































































2635
2636
2637
2638
2639
2640
2641
          .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
          .assert(!(await opfs.entryExists(testDir)),
                  "entryExists(",testDir,") should have failed");
      }
    }/*OPFS util sanity checks*/)
  ;/* end OPFS tests */









































































  ////////////////////////////////////////////////////////////////////////
  log("Loading and initializing sqlite3 WASM module...");
  if(!self.sqlite3InitModule && !isUIThread()){
    /* Vanilla worker, as opposed to an ES6 module worker */
    /*
      If sqlite3.js is in a directory other than this script, in order
      to get sqlite3.js to resolve sqlite3.wasm properly, we have to







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







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
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
          .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
          .assert(!(await opfs.entryExists(testDir)),
                  "entryExists(",testDir,") should have failed");
      }
    }/*OPFS util sanity checks*/)
  ;/* end OPFS tests */

  T.g('Session API')
    .t({
      name: 'Session API sanity checks',
      predicate: ()=>!!capi.sqlite3changegroup_add,
      test: function(sqlite3){
        const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB();
        const sqlInit = [
          "create table t(rowid INTEGER PRIMARY KEY,a,b); ",
          "insert into t(rowid,a,b) values",
          "(1,'a1','b1'),",
          "(2,'a2','b2'),",
          "(3,'a3','b3');"
        ].join('');
        db1.exec(sqlInit);
        db2.exec(sqlInit);
        T.assert(3 === db1.selectValue("select count(*) from t"))
          .assert('b3' === db1.selectValue('select b from t where rowid=3'));
        const stackPtr = wasm.pstack.pointer;
        try{
          let ppOut = wasm.pstack.allocPtr();
          let rc = capi.sqlite3session_create(db1, "main", ppOut);
          T.assert(0===rc);
          let pSession = wasm.peekPtr(ppOut);
          T.assert(pSession && wasm.isPtr(pSession));
          if(1){
            capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{
              T.assert('t' === tbl).assert( 99 === pCtx );
              return 1;
            }, 99);
          }else{
            rc = capi.sqlite3session_attach(pSession, "t");
            T.assert( 0 === rc );
          }
          db1.exec([
            "update t set b='bTwo' where rowid=2;",
            "update t set a='aThree' where rowid=3;",
            "delete from t where rowid=1;",
            "insert into t(rowid,a,b) values(4,'a4','b4')"
          ]);
          T.assert('bTwo' === db1.selectValue("select b from t where rowid=2"))
            .assert(undefined === db1.selectValue('select a from t where rowid=1'))
            .assert('b4' === db1.selectValue('select b from t where rowid=4'));

          let pnChanges = wasm.pstack.alloc('i32'),
              ppChanges = wasm.pstack.allocPtr();
          rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges);
          T.assert( 0 === rc );
          capi.sqlite3session_delete(pSession);
          pSession = 0;
          const pChanges = wasm.peekPtr(ppChanges),
                nChanges = wasm.peek32(pnChanges);
          T.assert( pChanges && wasm.isPtr( pChanges ) ).assert( nChanges > 0 );
          pnChanges = ppChanges = 0;
          //log("pnChanges =", pnChanges, wasm.peek32(pnChanges), '@', pChanges);
          rc = capi.sqlite3changeset_apply(
            db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{
              return pCtx ? 1 : 0
            }, 1
          );
          wasm.dealloc( pChanges );
          T.assert( 0 === rc )
            .assert( 3 === db2.selectValue('select count(*) from t'))
            .assert( 'b4' === db2.selectValue('select b from t where rowid=4') );
        }finally{
          wasm.pstack.restore(stackPtr);
          db1.close();
          db2.close();
        }
      }
    })
  ;/*end of session API group*/;

  ////////////////////////////////////////////////////////////////////////
  log("Loading and initializing sqlite3 WASM module...");
  if(!self.sqlite3InitModule && !isUIThread()){
    /* Vanilla worker, as opposed to an ES6 module worker */
    /*
      If sqlite3.js is in a directory other than this script, in order
      to get sqlite3.js to resolve sqlite3.wasm properly, we have to