SQLite

Check-in [c7750c10]
Login

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

Overview
Comment:Worker1: use a list, rather than a single slot, to manage the default db link so that we can keep the least-recently-opened db as the default. Re-introduce worker1.export() but do not yet expose it until an equivalent import() (or open() option) is implemented.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: c7750c101d9b7baa31496731bc34ea0a9b2bad0c11e2b3e92a8c7b327135c1bb
User & Date: stephan 2022-10-31 13:50:05
Context
2022-10-31
18:01
Make the UPDATE method of the sqlite_dbpage virtual table rebust against OOM errors. Forum post bbcf0dd6ca. Test case in TH3. (check-in: d15c9a4a user: drh tags: trunk)
13:50
Worker1: use a list, rather than a single slot, to manage the default db link so that we can keep the least-recently-opened db as the default. Re-introduce worker1.export() but do not yet expose it until an equivalent import() (or open() option) is implemented. (check-in: c7750c10 user: stephan tags: trunk)
11:53
Internal tweaks to the OPFS VFS result codes. (check-in: 32e1a2d2 user: stephan tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

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

630
631
632
633
634
635
636
637






638
639
640
641
642
643
644
      const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
            pstack = wasm.pstack,
            pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc;

      const kvvfsStorage = (zClass)=>
            ((115/*=='s'*/===wasm.getMemValue(zClass))
             ? sessionStorage : localStorage);
      






      const kvvfsImpls = {
        xRead: (zClass, zKey, zBuf, nBuf)=>{
          const stack = pstack.pointer,
                astack = wasm.scopedAllocPush();
          try {
            const zXKey = kvvfsMakeKey(zClass,zKey);
            if(!zXKey) return -3/*OOM*/;







|
>
>
>
>
>
>







630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
      const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
            pstack = wasm.pstack,
            pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc;

      const kvvfsStorage = (zClass)=>
            ((115/*=='s'*/===wasm.getMemValue(zClass))
             ? sessionStorage : localStorage);

      /**
         Implementations for members of the object referred to by
         sqlite3_wasm_kvvfs_methods(). We swap out the native
         implementations with these, which use localStorage or
         sessionStorage for their backing store.
      */
      const kvvfsImpls = {
        xRead: (zClass, zKey, zBuf, nBuf)=>{
          const stack = pstack.pointer,
                astack = wasm.scopedAllocPush();
          try {
            const zXKey = kvvfsMakeKey(zClass,zKey);
            if(!zXKey) return -3/*OOM*/;
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
            console.error("kvstorageDelete()",e);
            return capi.SQLITE_IOERR;
          }finally{
            pstack.restore(stack);
          }
        }
      }/*kvvfsImpls*/;
      for(let k of Object.keys(kvvfsImpls)){
        kvvfsMethods[kvvfsMethods.memberKey(k)] =
          wasm.installFunction(
            kvvfsMethods.memberSignature(k),
            kvvfsImpls[k]
          );
      }
    }else{







|







698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
            console.error("kvstorageDelete()",e);
            return capi.SQLITE_IOERR;
          }finally{
            pstack.restore(stack);
          }
        }
      }/*kvvfsImpls*/;
      for(const k of Object.keys(kvvfsImpls)){
        kvvfsMethods[kvvfsMethods.memberKey(k)] =
          wasm.installFunction(
            kvvfsMethods.memberSignature(k),
            kvvfsImpls[k]
          );
      }
    }else{

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

200
201
202
203
204
205
206
207
208
209

210
211
212
213
214
215
216
    messageId: ...as above...,
    result: {
      filename: db filename, possibly differing from the input.

      dbId: an opaque ID value which must be passed in the message
      envelope to other calls in this API to tell them which db to
      use. If it is not provided to future calls, they will default to
      operating on the first-opened db. This property is, for API
      consistency's sake, also part of the containing message envelope.
      Only the `open` operation includes it in the `result` property.


      persistent: true if the given filename resides in the
      known-persistent storage, else false.

   }
  }
  ```







|
|
|
>







200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    messageId: ...as above...,
    result: {
      filename: db filename, possibly differing from the input.

      dbId: an opaque ID value which must be passed in the message
      envelope to other calls in this API to tell them which db to
      use. If it is not provided to future calls, they will default to
      operating on the least-recently-opened db. This property is, for
      API consistency's sake, also part of the containing message
      envelope.  Only the `open` operation includes it in the `result`
      property.

      persistent: true if the given filename resides in the
      known-persistent storage, else false.

   }
  }
  ```
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
    return id;
  };

  /**
     Internal helper for managing Worker-level state.
  */
  const wState = {


    /** First-opened db is the default for future operations when no
        dbId is provided by the client. */

    defaultDb: undefined,
    /** Sequence number of dbId generation. */
    idSeq: 0,
    /** Map of DB instances to dbId. */
    idMap: new WeakMap,
    /** Temp holder for "transferable" postMessage() state. */
    xfer: [],
    open: function(opt){
      const db = new DB(opt.filename);
      this.dbs[getDbId(db)] = db;
      if(!this.defaultDb) this.defaultDb = db;
      return db;
    },
    close: function(db,alsoUnlink){
      if(db){
        delete this.dbs[getDbId(db)];
        const filename = db.filename;
        const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0);
        db.close();

        if(db===this.defaultDb) this.defaultDb = undefined;
        if(alsoUnlink && filename && pVfs){
          sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename);
        }
      }
    },
    /**
       Posts the given worker message value. If xferList is provided,







>
>
|
|
>
|









|








>
|







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
    return id;
  };

  /**
     Internal helper for managing Worker-level state.
  */
  const wState = {
    /**
       Each opened DB is added to this.dbList, and the first entry in
       that list is the default db. As each db is closed, its entry is
       removed from the list.
    */
    dbList: [],
    /** Sequence number of dbId generation. */
    idSeq: 0,
    /** Map of DB instances to dbId. */
    idMap: new WeakMap,
    /** Temp holder for "transferable" postMessage() state. */
    xfer: [],
    open: function(opt){
      const db = new DB(opt.filename);
      this.dbs[getDbId(db)] = db;
      if(this.dbList.indexOf(db)<0) this.dbList.push(db);
      return db;
    },
    close: function(db,alsoUnlink){
      if(db){
        delete this.dbs[getDbId(db)];
        const filename = db.filename;
        const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0);
        db.close();
        const ddNdx = this.dbList.indexOf(db);
        if(ddNdx>=0) this.dbList.splice(ddNdx, 1);
        if(alsoUnlink && filename && pVfs){
          sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename);
        }
      }
    },
    /**
       Posts the given worker message value. If xferList is provided,
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
        id is not valid, else return the db or undefined. */
    getDb: function(id,require=true){
      return this.dbs[id]
        || (require ? toss("Unknown (or closed) DB ID:",id) : undefined);
    }
  };

  /** Throws if the given db is falsy or not opened. */

  const affirmDbOpen = function(db = wState.defaultDb){
    return (db && db.pointer) ? db : toss("DB is not opened.");
  };

  /** Extract dbId from the given message payload. */
  const getMsgDb = function(msgData,affirmExists=true){
    const db = wState.getDb(msgData.dbId,false) || wState.defaultDb;
    return affirmExists ? affirmDbOpen(db) : db;
  };

  const getDefaultDbId = function(){
    return wState.defaultDb && getDbId(wState.defaultDb);
  };

  /**
     A level of "organizational abstraction" for the Worker
     API. Each method in this object must map directly to a Worker
     message type key. The onmessage() dispatcher attempts to
     dispatch all inbound messages to a method of this object,
     passing it the event.data part of the inbound event object. All
     methods must return a plain Object containing any result
     state, which the dispatcher may amend. All methods must throw
     on error.
  */







|
>
|





|




|



|
|







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
        id is not valid, else return the db or undefined. */
    getDb: function(id,require=true){
      return this.dbs[id]
        || (require ? toss("Unknown (or closed) DB ID:",id) : undefined);
    }
  };

  /** Throws if the given db is falsy or not opened, else returns its
      argument. */
  const affirmDbOpen = function(db = wState.dbList[0]){
    return (db && db.pointer) ? db : toss("DB is not opened.");
  };

  /** Extract dbId from the given message payload. */
  const getMsgDb = function(msgData,affirmExists=true){
    const db = wState.getDb(msgData.dbId,false) || wState.dbList[0];
    return affirmExists ? affirmDbOpen(db) : db;
  };

  const getDefaultDbId = function(){
    return wState.dbList[0] && getDbId(wState.dbList[0]);
  };

  /**
     A level of "organizational abstraction" for the Worker1
     API. Each method in this object must map directly to a Worker1
     message type key. The onmessage() dispatcher attempts to
     dispatch all inbound messages to a method of this object,
     passing it the event.data part of the inbound event object. All
     methods must return a plain Object containing any result
     state, which the dispatcher may amend. All methods must throw
     on error.
  */
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
        oargs.filename = args.filename || '';
      }else{
        oargs.filename = args.filename;
      }
      const db = wState.open(oargs);
      rc.filename = db.filename;
      rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/'))
        || sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs");
      rc.dbId = getDbId(db);
      return rc;
    },

    close: function(ev){
      const db = getMsgDb(ev,false);
      const response = {







|







442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
        oargs.filename = args.filename || '';
      }else{
        oargs.filename = args.filename;
      }
      const db = wState.open(oargs);
      rc.filename = db.filename;
      rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/'))
        || !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs");
      rc.dbId = getDbId(db);
      return rc;
    },

    close: function(ev){
      const db = getMsgDb(ev,false);
      const response = {
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
      rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir();
      rc.version = sqlite3.version;
      rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list();
      return rc;
    },

    /**
       TO(RE)DO, once we can abstract away access to the
       JS environment's virtual filesystem. Currently this
       always throws.

       Response is (should be) an object:

       {
         buffer: Uint8Array (db file contents),
         filename: the current db filename,
         mimetype: 'application/x-sqlite3'
       }

       2022-09-30: we have shell.c:fiddle_export_db() which works fine
       for disk-based databases (even if it's a virtual disk like an
       Emscripten VFS). sqlite3_serialize() can return this for
       :memory: and temp databases.
    */
    export: function(ev){
      toss("export() requires reimplementing for portability reasons.");
      /**
         We need to reimplement this to use the Emscripten FS
         interface. That part used to be in the OO#1 API but that
         dependency was removed from that level of the API.
      */
      /**const db = getMsgDb(ev);
      const response = {
        buffer: db.exportBinaryImage(),
        filename: db.filename,
        mimetype: 'application/x-sqlite3'
      };
      wState.xfer.push(response.buffer.buffer);
      return response;**/
    }/*export()*/,

    toss: function(ev){
      toss("Testing worker exception");
    }
  }/*wMsgHandler*/;








<
<
<
|
|


|



<
<
<
<
<


<





|

|



|
|







528
529
530
531
532
533
534



535
536
537
538
539
540
541
542





543
544

545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
      rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir();
      rc.version = sqlite3.version;
      rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list();
      return rc;
    },

    /**



       Exports the database to a byte array, as per
       sqlite3_serialize(). Response is an object:

       {
         bytearray: Uint8Array (db file contents),
         filename: the current db filename,
         mimetype: 'application/x-sqlite3'
       }





    */
    export: function(ev){

      /**
         We need to reimplement this to use the Emscripten FS
         interface. That part used to be in the OO#1 API but that
         dependency was removed from that level of the API.
      */
      const db = getMsgDb(ev);
      const response = {
        bytearray: sqlite3.capi.sqlite3_js_db_export(db.pointer),
        filename: db.filename,
        mimetype: 'application/x-sqlite3'
      };
      wState.xfer.push(response.bytearray.buffer);
      return response;
    }/*export()*/,

    toss: function(ev){
      toss("Testing worker exception");
    }
  }/*wMsgHandler*/;