]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
The Worker-specific variants of the most significant DB-class JS bindings are impleme...
authorstephan <stephan@noemail.net>
Wed, 1 Jun 2022 11:20:07 +0000 (11:20 +0000)
committerstephan <stephan@noemail.net>
Wed, 1 Jun 2022 11:20:07 +0000 (11:20 +0000)
FossilOrigin-Name: 371d6f7497016ca9bf82c8524b4d701ddf1e614b3fb4e69ff63935da0d51ed05

ext/fiddle/fiddle-worker.js
ext/fiddle/sqlite3-api.js
ext/fiddle/sqlite3-worker.js
ext/fiddle/testing2.js
manifest
manifest.uuid

index e71955a8a8dd2c52a1c8c200d5eb8b826f13f973..ca562323cee1f4d64ea978a14feb3880fe858dfa 100644 (file)
                 fiddleModule.FS.createDataFile("/", fn, buffer, true, true);
                 const oldName = Sqlite3Shell.dbFilename();
                 Sqlite3Shell.exec('.open "/'+fn+'"');
-                if(oldName !== fn){
-                    fiddleModule.FS.unlink(oldName);
+                if(oldName && oldName !== fn){
+                    try{fiddleModule.FS.unlink(oldName);}
+                    catch(e){/*ignored*/}
                 }
                 stdout("Replaced DB with",fn+".");
                 return;
index 853f16e07bc39a9d9cc8df2aec3bc0a67681c9ec..943f1bc8e0fece1e3d5835f3ea5812eecab4bd74 100644 (file)
@@ -278,7 +278,8 @@ Module.postRun.push(function(namespace/*the module object, the target for
        image.
 
        If the filename is provided, only the last component of the
-       path is used - any path prefix is stripped. If no name is
+       path is used - any path prefix is stripped and certain
+       "special" characters are replaced with `_`. If no name is
        provided, a random name is generated. The resulting filename is
        the one used for accessing the db file within root directory of
        the emscripten-supplied virtual filesystem, and is set (with no
@@ -300,7 +301,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
             arg = undefined;
         }else if(arguments.length){ /*(filename[,buffer])*/
             if('string'===typeof arg){
-                const p = arg.split('/').pop().replace(':','');
+                const p = arg.split('/').pop().replace(':','_');
                 if(p) fn = p;
                 if(arguments.length>1){
                     buffer = arguments[1];
@@ -406,7 +407,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
         if('string'!==typeof out.sql) toss("Missing SQL argument.");
         if(out.opt.callback || out.opt.resultRows){
             switch((undefined===out.opt.rowMode)
-                    ? 'stmt' : out.opt.rowMode) {
+                   ? 'stmt' : out.opt.rowMode) {
                 case 'object': out.cbArg = (stmt)=>stmt.get({}); break;
                 case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
                 case 'stmt': out.cbArg = (stmt)=>stmt; break;
@@ -440,9 +441,12 @@ Module.postRun.push(function(namespace/*the module object, the target for
         /**
            Finalizes all open statements and closes this database
            connection. This is a no-op if the db has already been
-           closed.
+           closed. If the db is open and alsoUnlink is truthy then the
+           this.filename entry in the pseudo-filesystem will also be
+           removed (and any error in that attempt is silently
+           ignored).
         */
-        close: function(){
+        close: function(alsoUnlink){
             if(this._pDb){
                 let s;
                 const that = this;
@@ -453,9 +457,15 @@ Module.postRun.push(function(namespace/*the module object, the target for
                 Object.values(this._udfs).forEach(SQM.removeFunction);
                 delete this._udfs;
                 delete this._statements;
-                delete this.filename;
                 api.sqlite3_close_v2(this._pDb);
                 delete this._pDb;
+                if(this.filename){
+                    if(alsoUnlink){
+                        try{SQM.FS.unlink(this.filename);}
+                        catch(e){/*ignored*/}
+                    }
+                    delete this.filename;
+                }
             }
         },
         /**
@@ -491,7 +501,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
            one.
 
            This function supports the following additional options not
-           used by execMulti():
+           supported by execMulti():
 
            - .multi: if true, this function acts as a proxy for
              execMulti() and behaves identically to that function.
@@ -499,10 +509,11 @@ Module.postRun.push(function(namespace/*the module object, the target for
            - .resultRows: if this is an array, each row of the result
              set (if any) is appended to it in the format specified
              for the `rowMode` property, with the exception that the
-             `rowMode` property _must_ be one of 'array' or 'object'
-             if this is set (else an exception is throws). It is legal
+             only legal values for `rowMode` in this case are 'array'
+             or 'object', neither of which is the default. It is legal
              to use both `resultRows` and `callback`, but `resultRows`
-             is likely much simpler to use for small data sets.
+             is likely much simpler to use for small data sets and can
+             be used over a WebWorker-style message interface.
 
            - .columnNames: if this is an array and the query has
              result columns, the array is passed to
@@ -528,8 +539,8 @@ Module.postRun.push(function(namespace/*the module object, the target for
             try {
                 if(Array.isArray(opt.resultRows)){
                     if(opt.rowMode!=='array' && opt.rowMode!=='object'){
-                        throw new Error("Invalid rowMode for resultRows array: must "+
-                                        "be one of 'array' or 'object'.");
+                        toss("Invalid rowMode for resultRows array: must",
+                             "be one of 'array' or 'object'.");
                     }
                     rowTarget = opt.resultRows;
                 }
@@ -1316,6 +1327,13 @@ Module.postRun.push(function(namespace/*the module object, the target for
                     const ptr = api.sqlite3_column_blob(this._pStmt, ndx);
                     const rc = new Uint8Array(n);
                     for(let i = 0; i < n; ++i) rc[i] = HEAP8[ptr + i];
+                    if(n && this.db._blobXfer instanceof Array){
+                        /* This is an optimization soley for the
+                           Worker-based API. These values will be
+                           transfered to the main thread directly
+                           instead of being copied. */
+                        this.db._blobXfer.push(rc.buffer);
+                    }
                     return rc;
                 }
                 default: toss("Don't know how to translate",
@@ -1462,13 +1480,9 @@ Module.postRun.push(function(namespace/*the module object, the target for
         SQLite3
     };
 
-    const postApiLoaded = function(){
-        setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0);
-    };
-
     if(self === self.window){
         /* This is running in the main window thread, so we're done. */
-        postApiLoaded();
+        setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0);
         return;
     }
     /******************************************************************
@@ -1506,18 +1520,170 @@ Module.postRun.push(function(namespace/*the module object, the target for
         engine's memory.
     */
 
+    /**
+       Helper for managing Worker-level state.
+    */
     const wState = {
         db: undefined,
         open: function(arg){
             if(!arg && this.db) return this.db;
             else if(this.db) this.db.close();
             return this.db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg));
+        },
+        close: function(){
+            if(this.db){
+                this.db.close();
+                this.db = undefined;
+            }
+        },
+        affirmOpen: function(){
+            return this.db || toss("DB is not opened.");
+        },
+        post: function(type,data,xferList){
+            if(xferList){
+                self.postMessage({type, data},xferList);
+                xferList.length = 0;
+            }else{
+                self.postMessage({type, data});
+            }
         }
     };
-    const wMsg = (type,data)=>self.postMessage({type, data});
 
     /**
-       UNDER CONSTRUCTION:
+       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 response
+       state, which the dispatcher may amend. All methods must throw
+       on error.
+    */
+    const wMsgHandler = {
+        xfer: [/*Temp holder for "transferable" postMessage() state.*/],
+        /**
+           Proxy for DB.exec() which expects a single argument of type
+           string (SQL to execute) or an options object in the form
+           expected by exec(). The notable differences from exec()
+           include:
+
+           - The default value for options.rowMode is 'array' because
+           the normal default cannot cross the window/Worker boundary.
+
+           - A function-type options.callback property cannot cross
+           the window/Worker boundary, so is not useful here. If
+           options.callback is a string then it is assumed to be a
+           message type key, in which case a callback function will be
+           applied which posts each row result via:
+
+           postMessage({type: thatKeyType, data: theRow})
+
+           And, at the end of the result set (whether or not any
+           result rows were produced), it will post an identical
+           message with data:null to alert the caller than the result
+           set is completed.
+
+           The callback proxy must not recurse into this interface, or
+           results are undefined. (It hypothetically cannot recurse
+           because an exec() call will be tying up the Worker thread,
+           causing any recursion attempt to wait until the first
+           exec() is completed.)
+
+           The response is the input options object (or a synthesized
+           one if passed only a string), noting that
+           options.resultRows and options.columnNames may be populated
+           by the call to exec().
+
+           This opens/creates the Worker's db if needed.
+        */
+        exec: function(ev){
+            const opt = (
+                'string'===typeof ev.data
+            ) ? {sql: ev.data} : (ev.data || {});
+            if(!opt.rowMode){
+                /* Since the default rowMode of 'stmt' is not useful
+                   for the Worker interface, we'll default to
+                   something else. */
+                opt.rowMode = 'array';
+            }else if('stmt'===opt.rowMode){
+                toss("Invalid rowMode for exec(): stmt mode",
+                     "does not work in the Worker API.");
+            }
+            const db = wState.open();
+            if(opt.callback || opt.resultRows instanceof Array){
+                // Part of a copy-avoidance optimization for blobs
+                db._blobXfer = this.xfer;
+            }
+            const callbackMsgType = opt.callback;
+            if('string' === typeof callbackMsgType){
+                const that = this;
+                opt.callback =
+                    (row)=>wState.post(callbackMsgType,row,this.xfer);
+            }
+            try {
+                db.exec(opt);
+                if(opt.callback instanceof Function){
+                    opt.callback = callbackMsgType;
+                    wState.post(callbackMsgType, null);
+                }
+            }finally{
+                delete db._blobXfer;
+                if('string'===typeof callbackMsgType){
+                    opt.callback = callbackMsgType;
+                }
+            }
+            return opt;
+        }/*exec()*/,
+        /**
+           Proxy for DB.exportBinaryImage(). Throws if the db has not
+           been opened. Response is an object:
+
+           {
+             buffer: Uint8Array (db file contents),
+             filename: the current db filename,
+             mimetype: string
+           }
+        */
+        export: function(ev){
+            const db = wState.affirmOpen();
+            const response = {
+                buffer: db.exportBinaryImage(),
+                filename: db.filename,
+                mimetype: 'application/x-sqlite3'
+            };
+            this.xfer.push(response.buffer.buffer);
+            return response;
+        }/*export()*/,
+        /**
+           Proxy for the DB constructor. Expects to be passed a single
+           object or a falsy value to use defaults. The object may
+           have a filename property to name the db file (see the DB
+           constructor for peculiarities and transformations) and/or a
+           buffer property (a Uint8Array holding a complete database
+           file's contents). The response is an object:
+
+           {
+             filename: db filename (possibly differing from the input)
+           }
+
+           If the Worker's db is currently opened, this call closes it
+           before proceeding.
+        */
+        open: function(ev){
+            wState.close(/*true???*/);
+            const args = [], data = (ev.data || {});
+            if(data.filename) args.push(data.filename);
+            if(data.buffer){
+                args.push(data.buffer);
+                this.xfer.push(data.buffer.buffer);
+            }
+            const db = wState.open(args);
+            return {filename: db.filename};
+        }
+    }/*wMsgHandler*/;
+    
+    /**
+       UNDER CONSTRUCTION!
 
        A subset of the DB API is accessible via Worker messages in the form:
 
@@ -1534,51 +1700,39 @@ Module.postRun.push(function(namespace/*the module object, the target for
        during processing result in an `error`-type event with a
        payload in the form:
 
-       {message: error string,
-        errorClass: class name of the error type,
-        [, messageId: if set in the inbound message]}
+       {
+         message: error string,
+         errorClass: class name of the error type,
+         input: ev.data,
+         [messageId: if set in the inbound message]
+       }
 
-       The individual APIs will be documented as they are fleshed out.
+       The individual APIs are documented in the wMsgHandler object.
     */
     self.onmessage = function(ev){
         ev = ev.data;
-        let response = {}, evType = ev.type;
+        let response, evType = ev.type;
         try {
-            switch(evType){
-                case 'open': {
-                    const args = [], data = (ev.data || {});
-                    if(data.filename) args.push(data.filename);
-                    if(data.buffer) args.push(data.buffer);
-                    const d = wState.open(args);
-                    response.filename = d.filename;
-                    response.messageId = data.messageId;
-                    break;
-                }
-                case 'exec': {
-                    const opt = (
-                        'string'===typeof ev.data
-                    ) ? {sql: ev.data} : (ev.data || {});
-                    if(!opt.rowMode) opt.rowMode = 'array';
-                    wState.open().exec(opt);
-                    response = opt;
-                    break;
-                }
-                default:
-                    throw new Error("Unknown db worker message type: "+ev.type);
+            if(wMsgHandler.hasOwnProperty(evType) &&
+               wMsgHandler[evType] instanceof Function){
+                response = wMsgHandler[evType](ev);
+            }else{
+                toss("Unknown db worker message type:",ev.type);
             }
         }catch(err){
             evType = 'error';
             response = {
                 message: err.message,
-                errorClass: err.name
+                errorClass: err.name,
+                input: ev
             };
         }
         if(!response.messageId && ev.data
            && 'object'===typeof ev.data && ev.data.messageId){
             response.messageId = ev.data.messageId;
         }
-        wMsg(evType, response);
+        wState.post(evType, response, wMsgHandler.xfer);
     };
 
-    postApiLoaded();
+    setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0);
 });
index 6c1e9ca672a8405271243a1df21dbe6f5be124a7..fe7423f4c4aceb9199ce36f3becd1d5c4873db53 100644 (file)
 
   ***********************************************************************
 
-  UNDER CONSTRUCTION
-
   This is a JS Worker file for the main sqlite3 api. It loads
-  sqlite3.js and offers access to the db via the Worker
-  message-passing interface.
-*/
+  sqlite3.js, initializes the module, and postMessage()'s a message
+  after the module is initialized:
 
+  {type: 'sqlite3-api', data: 'ready'}
+
+  This seemingly superfluous level of indirection is necessary when
+  loading sqlite3.js via a Worker. Loading sqlite3.js from the main
+  window thread elides the Worker-specific API. Instantiating a worker
+  with new Worker("sqlite.js") will not (cannot) call
+  initSqlite3Module() to initialize the module due to a
+  timing/order-of-operations conflict (and that symbol is not exported
+  in a way that a Worker loading it that way can see it).  Thus JS
+  code wanting to load the sqlite3 Worker-specific API needs to pass
+  _this_ file (or equivalent) to the Worker constructor and then
+  listen for an event in the form shown above in order to know when
+  the module has completed initialization. sqlite3.js will fire a
+  similar event, with data:'loaded' as the final step in its loading
+  process. Whether or not we _really_ need both 'loaded' and 'ready'
+  events is unclear, but they are currently separate events primarily
+  for the sake of clarity in the timing of when it's okay to use the
+  loaded module. At the time the 'loaded' event is fired, it's
+  possible (but unknown and unknowable) that the emscripten-generated
+  module-setup infrastructure still has work to do. Thus it is
+  hypothesized that client code is better off waiting for the 'ready'
+  even before using the API.
+*/
 "use strict";
-(function(){
-    /** Posts a worker message as {type:type, data:data}. */
-    const wMsg = (type,data)=>self.postMessage({type, data});
-    self.onmessage = function(ev){
-        /*ev = ev.data;
-        switch(ev.type){
-            default: break;
-        };*/
-        console.warn("Unknown sqlite3-worker message type:",ev);
-    };
-    importScripts('sqlite3.js');
-    initSqlite3Module().then(function(){
-        wMsg('sqlite3-api','ready');
-    });
-})();
+importScripts('sqlite3.js');
+initSqlite3Module().then(function(){
+    setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'ready'}), 0);
+});
index de9c92564852d49ada97fcf81b93ef4839e4e13e..4ec9faae6abfeb2e85cf25919dc5cb46424a39cc 100644 (file)
         }
     };
 
+    const testCount = ()=>log("Total test count:",T.counter);
+
     const runOneTest = function(eventType, eventData, callback){
         T.assert(eventData && 'object'===typeof eventData);
+        /* ^^^ that is for the testing and messageId-related code, not
+           a hard requirement of all of the Worker-exposed APIs. */
         eventData.messageId = MsgHandlerQueue.push(eventType,function(ev){
             log("runOneTest",eventType,"result",ev.data);
-            callback(ev);
+            if(callback instanceof Function){
+                callback(ev);
+                testCount();
+            }
         });
         wMsg(eventType, eventData);
     };
 
-    const testCount = ()=>log("Total test count:",T.counter);
-
+    /** Methods which map directly to onmessage() event.type keys.
+        They get passed the inbound event. */
+    const dbMsgHandler = {
+        open: function(ev){
+            log("open result",ev.data);
+        },
+        exec: function(ev){
+            log("exec result",ev.data);
+        },
+        export: function(ev){
+            log("exec result",ev.data);
+        },
+        error: function(ev){
+            error("ERROR from the worker:",ev.data);
+        },
+        resultRowTest1: function f(ev){
+            if(undefined === f.counter) f.counter = 0;
+            if(ev.data) ++f.counter;
+            //log("exec() result row:",ev.data);
+            T.assert(null===ev.data || 'number' === typeof ev.data.b);
+        }
+    };
+    
     const runTests = function(){
         /**
            "The problem" now is that the test results are async. We
             //log("open result",ev);
             T.assert('testing2.sqlite3'===ev.data.filename)
                 .assert(ev.data.messageId);
-            testCount();
         });
         runOneTest('exec',{
             sql: ["create table t(a,b)",
                   "insert into t(a,b) values(1,2),(3,4),(5,6)"
                  ].join(';'),
             multi: true,
-            resultRows: [],
-            columnNames: []
+            resultRows: [], columnNames: []
         }, function(ev){
             ev = ev.data;
             T.assert(0===ev.resultRows.length)
                 .assert(0===ev.columnNames.length);
-            testCount();
         });
         runOneTest('exec',{
             sql: 'select a a, b b from t order by a',
-            resultRows: [], columnNames: []
+            resultRows: [], columnNames: [],
         }, function(ev){
             ev = ev.data;
             T.assert(3===ev.resultRows.length)
                 .assert(6===ev.resultRows[2][1])
                 .assert(2===ev.columnNames.length)
                 .assert('b'===ev.columnNames[1]);
-            testCount();
         });
-        runOneTest('exec',{sql:'select 1 from intentional_error'}, function(){
+        runOneTest('exec',{
+            sql: 'select a a, b b from t order by a',
+            resultRows: [], columnNames: [],
+            rowMode: 'object'
+        }, function(ev){
+            ev = ev.data;
+            T.assert(3===ev.resultRows.length)
+                .assert(1===ev.resultRows[0].a)
+                .assert(6===ev.resultRows[2].b)
+        });
+        runOneTest('exec',{sql:'intentional_error'}, function(){
             throw new Error("This is not supposed to be reached.");
         });
         // Ensure that the message-handler queue survives ^^^ that error...
         runOneTest('exec',{
             sql:'select 1',
             resultRows: [],
-            rowMode: 'array',
+            //rowMode: 'array', // array is the default in the Worker interface
         }, function(ev){
             ev = ev.data;
             T.assert(1 === ev.resultRows.length)
                 .assert(1 === ev.resultRows[0][0]);
-            testCount();
         });
-    };
-
-    const dbMsgHandler = {
-        open: function(ev){
-            log("open result",ev.data);
-        },
-        exec: function(ev){
-            log("exec result",ev.data);
-        },
-        error: function(ev){
-            error("ERROR from the worker:",ev.data);
-        }
+        runOneTest('exec',{
+            sql: 'select a a, b b from t order by a',
+            callback: 'resultRowTest1',
+            rowMode: 'object'
+        }, function(ev){
+            T.assert(3===dbMsgHandler.resultRowTest1.counter);
+            dbMsgHandler.resultRowTest1.counter = 0;
+        });
+        runOneTest('exec',{sql: 'delete from t where a>3'});
+        runOneTest('exec',{
+            sql: 'select count(a) from t',
+            resultRows: []
+        },function(ev){
+            ev = ev.data;
+            T.assert(1===ev.resultRows.length)
+                .assert(2===ev.resultRows[0][0]);
+        });
+        runOneTest('export',{}, function(ev){
+            ev = ev.data;
+            T.assert('string' === typeof ev.filename)
+                .assert(ev.buffer instanceof Uint8Array)
+                .assert(ev.buffer.length > 1024)
+                .assert('application/x-sqlite3' === ev.mimetype);
+        });
     };
 
     SW.onmessage = function(ev){
         }
     };
 
-    log("Init complete, but async bits may still be running.");
+    log("Init complete, but async init bits may still be running.");
 })();
index 3a61f64d249b6cd8ed116a761f31deb4a4ab015a..bba794ebe2982d181dbdfbe181d025988b91543a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Move\sthe\ssqlite_offset()\sfunction\simplementation\sto\sbe\san\sin-line\sfunction,\nthereby\savoiding\sspecial\scase\scode\sand\sfreeing\sup\sa\sbit\sin\sthe\nFuncDef.flags\sfield.
-D 2022-06-01T11:05:59.088
+C The\sWorker-specific\svariants\sof\sthe\smost\ssignificant\sDB-class\sJS\sbindings\sare\simplemented,\smost\snotably\svarious\suses\sof\sDB.exec().
+D 2022-06-01T11:20:07.553
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -61,17 +61,17 @@ F ext/fiddle/EXPORTED_RUNTIME_METHODS b831017ba67ba993b34a27400cef2f6095bd6789c0
 F ext/fiddle/Makefile e25d34a0e1324f771d64c09c592601b97219282011587e6ce410fa8acdedb913
 F ext/fiddle/SqliteTestUtil.js 559731c3e8e0de330ec7d292e6c1846566408caee6637acc8a119ac338a8781c
 F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
-F ext/fiddle/fiddle-worker.js 3a19253dc026d1ad9064ee853f3c4da3385223ce4434dab1838837525d817371
+F ext/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba833865ab64a1b4ae
 F ext/fiddle/fiddle.html 724f1cd4126616bc87f5871f78d3f7aaaf41e45c9728724627baab87e6af35f0
 F ext/fiddle/fiddle.js 5b456ed7085830cda2fc75a0801476174a978521949335f24bc4154d076dcd4d
 F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
-F ext/fiddle/sqlite3-api.js a2c0fa1a30e564a16650e3224a23a60cc8636325028223183933630669ebec8d
-F ext/fiddle/sqlite3-worker.js c137daed6529b5f527ed61eb358cb0d23f90e04784442479cd15ac684eccdf7a
+F ext/fiddle/sqlite3-api.js 2d7b0e63affa1dcad97de6c5bfef2eb1fb2ecdde44b8991299cd01b988ce9994
+F ext/fiddle/sqlite3-worker.js a9c2b614beca187dbdd8c053ec2770cc61ec1ac9c0ec6398ceb49a79f705a421
 F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a
 F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a
 F ext/fiddle/testing1.js b5bf7e33b35f02f4208e4d68eaa41e5ed42eaefd57e0a1131e87cba96d4808dc
 F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4
-F ext/fiddle/testing2.js 15e53ded82e78a5360daa4af109124c81b52eba79be2de241bef6558697931b7
+F ext/fiddle/testing2.js afb3f79c5731b50148201797150a64e210a16730a34a72965599f6985a4f83c6
 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
 F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
 F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@@ -1974,8 +1974,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P d9efe3e92d1c95aee6f5ae37a8ba28d8cf4891d746744ce4aa2464f766821a0b
-R 52cfcb84f807d4bfd299e794f93f33ab
-U drh
-Z 74e345d13b420413b02c8b365815c584
+P 1c9812c458bd229c862efe5df1b64fae333da9871c8756b5ae4605a81bcda4b5
+R b028ebbd2fb0859e58130b8e01609e9c
+U stephan
+Z b5b92e7ff660a052d41c589f495d31e9
 # Remove this line to create a well-formed Fossil manifest.
index f369ffc8a4b3176998e5d9b9cab6c85d8df694c9..43587cf9de33a43d6e7b6f680d4f4a98b0d272e9 100644 (file)
@@ -1 +1 @@
-1c9812c458bd229c862efe5df1b64fae333da9871c8756b5ae4605a81bcda4b5
\ No newline at end of file
+371d6f7497016ca9bf82c8524b4d701ddf1e614b3fb4e69ff63935da0d51ed05
\ No newline at end of file