From d60b7275c3a8734ab8ebeeb5277b2fbb49f49484 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 22 May 2022 14:07:44 +0000 Subject: [PATCH] WASM OO wrapper #1: prepare() and bind() APIs are in place but are untested, pending fetch/get APIs. FossilOrigin-Name: 84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39 --- ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 | 4 + ext/fiddle/sqlite3-api.js | 81 +++--- ext/fiddle/testing1.js | 391 +++++++++++++++++++++++++- manifest | 18 +- manifest.uuid | 2 +- 5 files changed, 437 insertions(+), 59 deletions(-) diff --git a/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 b/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 index 6bb557e99d..09f6e8ba79 100644 --- a/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 +++ b/ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 @@ -1,6 +1,8 @@ _sqlite3_bind_blob _sqlite3_bind_double _sqlite3_bind_int +_sqlite3_bind_int64 +_sqlite3_bind_null _sqlite3_bind_parameter_index _sqlite3_bind_text _sqlite3_changes @@ -16,9 +18,11 @@ _sqlite3_column_text _sqlite3_column_type _sqlite3_create_function_v2 _sqlite3_data_count +_sqlite3_db_filename _sqlite3_errmsg _sqlite3_exec _sqlite3_finalize +_sqlite3_interrupt _sqlite3_libversion _sqlite3_open _sqlite3_prepare_v2 diff --git a/ext/fiddle/sqlite3-api.js b/ext/fiddle/sqlite3-api.js index 65e6b00f61..83e46bbcaf 100644 --- a/ext/fiddle/sqlite3-api.js +++ b/ext/fiddle/sqlite3-api.js @@ -85,6 +85,7 @@ SQLITE_FLOAT: 2, SQLITE_TEXT: 3, SQLITE_BLOB: 4, + SQLITE_NULL: 5, /* sqlite encodings, used for creating UDFs, noting that we will only support UTF8. */ SQLITE_UTF8: 1 @@ -102,61 +103,61 @@ use for the JS-side binding. That's required when overloading a binding for two different uses. */ - ["sqlite3_open", "number", ["string", "number"]], - ["sqlite3_close_v2", "number", ["number"]], - ["sqlite3_exec", "number", - ["number", "string", "number", "number", "number"]], + ["sqlite3_bind_blob","number",["number", "number", "number", "number", "number"]], + ["sqlite3_bind_double","number",["number", "number", "number"]], + ["sqlite3_bind_int","number",["number", "number", "number"]], + ["sqlite3_bind_int64","number",["number", "number", "number"]], + ["sqlite3_bind_null","void",["number"]], + ["sqlite3_bind_parameter_index","number",["number", "string"]], + ["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]], ["sqlite3_changes", "number", ["number"]], + ["sqlite3_clear_bindings","number",["number"]], + ["sqlite3_close_v2", "number", ["number"]], + ["sqlite3_column_blob","number", ["number", "number"]], + ["sqlite3_column_bytes","number",["number", "number"]], + ["sqlite3_column_count", "number", ["number"]], + ["sqlite3_column_count","number",["number"]], + ["sqlite3_column_double","number",["number", "number"]], + ["sqlite3_column_name","string",["number", "number"]], + ["sqlite3_column_text","string",["number", "number"]], + ["sqlite3_column_type","number",["number", "number"]], + ["sqlite3_create_function_v2", "number", + ["number", "string", "number", "number","number", + "number", "number", "number", "number"]], + ["sqlite3_data_count", "number", ["number"]], + ["sqlite3_db_filename", "string", ["number", "string"]], + ["sqlite3_errmsg", "string", ["number"]], + ["sqlite3_exec", "number", ["number", "string", "number", "number", "number"]], + ["sqlite3_finalize", "number", ["number"]], + ["sqlite3_interrupt", "void", ["number"]], + ["sqlite3_libversion", "string", []], + ["sqlite3_open", "number", ["string", "number"]], ["sqlite3_prepare_v2", "number", ["number", "string", "number", "number", "number"]], - ["sqlite3_prepare_v2_sqlptr", + ["sqlite3_prepare_v2_sqlptr", "sqlite3_prepare_v2", /* Impl which requires that the 2nd argument be a pointer to the SQL, instead of a string. This is used for cases where we require a non-NULL value for the final argument. We may or may not need this, depending on how our higher-level API shapes up, but this code's spiritual guide (sql.js) uses it we we'll include it. */ - "sqlite3_prepare_v2", "number", ["number", "number", "number", "number", "number"]], - ["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]], - ["sqlite3_bind_blob","number",["number", "number", "number", "number", "number"]], - ["sqlite3_bind_double","number",["number", "number", "number"]], - ["sqlite3_bind_int","number",["number", "number", "number"]], - ["sqlite3_bind_parameter_index","number",["number", "string"]], - ["sqlite3_step", "number", ["number"]], - ["sqlite3_errmsg", "string", ["number"]], - ["sqlite3_column_count","number",["number"]], - ["sqlite3_data_count", "number", ["number"]], - ["sqlite3_column_count", "number", ["number"]], - ["sqlite3_column_double","number",["number", "number"]], - ["sqlite3_column_text","string",["number", "number"]], - ["sqlite3_column_blob","number", ["number", "number"]], - ["sqlite3_column_bytes","number",["number", "number"]], - ["sqlite3_column_type","number",["number", "number"]], - ["sqlite3_column_name","string",["number", "number"]], ["sqlite3_reset", "number", ["number"]], - ["sqlite3_clear_bindings","number",["number"]], - ["sqlite3_finalize", "number", ["number"]], - ["sqlite3_create_function_v2", "number", - ["number", "string", "number", "number", - "number", "number", "number", "number", - "number"]], - ["sqlite3_value_type", "number", ["number"]], - ["sqlite3_value_bytes","number",["number"]], - ["sqlite3_value_text", "string", ["number"]], - ["sqlite3_value_blob", "number", ["number"]], - ["sqlite3_value_double","number",["number"]], + ["sqlite3_result_blob",null,["number", "number", "number", "number"]], ["sqlite3_result_double",null,["number", "number"]], + ["sqlite3_result_error",null,["number", "string", "number"]], + ["sqlite3_result_int",null,["number", "number"]], ["sqlite3_result_null",null,["number"]], ["sqlite3_result_text",null,["number", "string", "number", "number"]], - ["sqlite3_result_blob",null,["number", "number", "number", "number"]], - ["sqlite3_result_int",null,["number", "number"]], - ["sqlite3_result_error",null,["number", "string", "number"]], - ["sqlite3_libversion", "string", []], - ["sqlite3_sourceid", "string", []] + ["sqlite3_sourceid", "string", []], + ["sqlite3_step", "number", ["number"]], + ["sqlite3_value_blob", "number", ["number"]], + ["sqlite3_value_bytes","number",["number"]], + ["sqlite3_value_double","number",["number"]], + ["sqlite3_value_text", "string", ["number"]], + ["sqlite3_value_type", "number", ["number"]] //["sqlite3_sql", "string", ["number"]], //["sqlite3_normalized_sql", "string", ["number"]] - ].forEach(function(e){ - const a = Array.prototype.slice.call(e); + ].forEach(function(a){ const k = (4==a.length) ? a.shift() : a[0]; api[k] = cwrap.apply(this, a); }); diff --git a/ext/fiddle/testing1.js b/ext/fiddle/testing1.js index d1b15e2810..a24d63f105 100644 --- a/ext/fiddle/testing1.js +++ b/ext/fiddle/testing1.js @@ -12,12 +12,385 @@ A basic test script for sqlite3-api.js. */ -(function(){ - self.Module.onRuntimeInitialized = function(){ - console.log("Loading sqlite3-api.js..."); - self.Module.loadSqliteAPI(function(S){ - console.log("Loaded module:",S.sqlite3_libversion(), - S.sqlite3_sourceid()); - }); - }; -})(self/*window or worker*/); +const setupAPI = function(S/*sqlite3 module*/){ + + /* memory for use in some pointer-passing routines */ + const pPtrArg = stackAlloc(4); + const dummyArg = {/*for restricting Stmt constructor to internal use*/}; + const toss = function(){ + throw new Error(Array.prototype.join.apply(arguments, ' ')); + }; + + /** + The DB class wraps a sqlite3 db handle. + */ + const DB = function(name/*TODO: openMode flags*/){ + if(!name) name = ':memory:'; + else if('string'!==typeof name){ + toss("TODO: support blob image of db here."); + } + this.checkRc(S.sqlite3_open(name, pPtrArg)); + this.pDb = getValue(pPtrArg, "i32"); + this.filename = name; + this._statements = {/*array of open Stmt _pointers_*/}; + }; + + /** + This class wraps sqlite3_stmt. Calling this constructor + directly will trigger an exception. Use DB.prepare() to create + new instances. + */ + const Stmt = function(){ + if(dummyArg!=arguments[2]){ + toss("Do not call the Stmt constructor directly. Use DB.prepare()."); + } + this.db = arguments[0]; + this.pStmt = arguments[1]; + this.columnCount = S.sqlite3_column_count(this.pStmt); + this._allocs = [/*list of alloc'd memory blocks for bind() values*/] + }; + + + /** Throws if the given DB has been closed, else it is returned. */ + const affirmDbOpen = function(db){ + if(!db.pDb) toss("DB has been closed."); + return db; + }; + + DB.prototype = { + /** + Expects to be given an sqlite3 API result code. If it is + falsy, this function returns this object, else it throws an + exception with an error message from sqlite3_errmsg(), + using this object's db handle. + */ + checkRc: function(sqliteResultCode){ + if(!sqliteResultCode) return this; + toss(S.sqlite3_errmsg(this.pDb) || "Unknown db error."); + }, + /** + Finalizes all open statements and closes this database + connection. This is a no-op if the db has already been + closed. + */ + close: function(){ + if(this.pDb){ + let s; + while(undefined!==(s = this._statements.pop())){ + if(s.pStmt) s.finalize(); + } + S.sqlite3_close_v2(this.pDb); + delete this.pDb; + } + }, + /** + Similar to this.filename but will return NULL for + special names like ":memory:". Not of much use until + we have filesystem support. Throws if the DB has + been closed. If passed an argument it then it will return + the filename of the ATTACHEd db with that name, else it assumes + a name of `main`. + */ + fileName: function(dbName){ + return S.sqlite3_db_filename(affirmDbOpen(this).pDb, dbName||"main"); + }, + + /** + Compiles the given SQL and returns a prepared Stmt. This is + the only way to create new Stmt objects. Throws on error. + */ + prepare: function(sql){ + affirmDbOpen(this); + setValue(pPtrArg,0,"i32"); + this.checkRc(S.sqlite3_prepare_v2(this.pDb, sql, -1, pPtrArg, null)); + const pStmt = getValue(pPtrArg, "i32"); + if(!pStmt) toss("Empty SQL is not permitted."); + const stmt = new Stmt(this, pStmt, dummyArg); + this._statements[pStmt] = stmt; + return stmt; + } + }; + + /** + Internal-use enum for mapping JS types to DB-bindable types. + These do not (and need not) line up with the SQLITE_type + values. All values in this enum must be truthy and distinct + but they need not be numbers. + */ + const BindTypes = { + null: 1, + number: 2, + string: 3, + boolean: 4, + blob: 5 + }; + BindTypes['undefined'] == BindTypes.null; + + /** Returns an opaque truthy value from the BindTypes + enum if v's type is a valid bindable type, else + returns a falsy value. */ + const isSupportedBindType = function(v){ + let t = BindTypes[null===v ? 'null' : typeof v]; + if(t) return t; + // TODO: handle buffer/blob types. + return undefined; + } + + /** + If isSupportedBindType(v) returns a truthy value, this + function returns that value, else it throws. + */ + const affirmSupportedBindType = function(v){ + const t = isSupportedBindType(v); + if(t) return t; + toss("Unsupport bind() argument type."); + }; + + /** + If key is a number and within range of stmt's bound parameter + count, key is returned. + + If key is not a number then it is checked against named + parameters. If a match is found, its index is returned. + + Else it throws. + */ + const indexOfParam = function(stmt,key){ + const n = ('number'===typeof key) + ? key : S.sqlite3_bind_parameter_index(stmt.pStmt, key); + if(0===n || (n===key && (n!==(n|0)/*floating point*/))){ + toss("Invalid bind() parameter name: "+key); + } + else if(n>=stmt.columnCount) toss("Bind index",key,"is out of range."); + return n; + }; + + /** + Binds a single bound parameter value on the given stmt at the + given index (numeric or named) using the given bindType (see + the BindTypes enum) and value. Throws on error. Returns stmt on + success. + */ + const bindOne = function(stmt,ndx,bindType,val){ + affirmSupportedBindType(val); + ndx = indexOfParam(stmt,ndx); + let rc = 0; + switch(bindType){ + case BindType.null: + rc = S.sqlite3_bind_null(stmt.pStmt, ndx); + break; + case BindType.string:{ + const bytes = intArrayFromString(string,false); + const pStr = allocate(bytes, ALLOC_NORMAL); + stmt._allocs.push(pStr); + rc = S.sqlite3_bind_text(stmt.pStmt, ndx, pStr, + bytes.length, 0); + break; + } + case BindType.number: { + const m = ((val === (val|0)) + ? (val>0xefffffff + ? S.sqlite3_bind_int64 + : S.sqlite3_bind_int) + : S.sqlite3_bind_double); + rc = m(stmt.pStmt, ndx, val); + break; + } + case BindType.boolean: + rc = S.sqlite3_bind_int(stmt.pStmt, ndx, val ? 1 : 0); + break; + case BindType.blob: + default: toss("Unsupported bind() argument type."); + } + if(rc) stmt.db.checkRc(rc); + return stmt; + }; + + /** Throws if the given Stmt has been finalized, else + it is returned. */ + const affirmStmtOpen = function(stmt){ + if(!stmt.pStmt) toss("Stmt has been closed."); + return stmt; + }; + + /** Frees any memory explicitly allocated for the given + Stmt object. Returns stmt. */ + const freeBindMemory = function(stmt){ + let m; + while(undefined !== (m = stmt._allocs.pop())){ + _free(m); + } + return stmt; + }; + + Stmt.prototype = { + /** + "Finalizes" this statement. This is a no-op if the + statement has already been finalizes. Returns + undefined. Most methods in this class will throw if called + after this is. + */ + finalize: function(){ + if(this.pStmt){ + freeBindMemory(this); + S.sqlite3_finalize(this.pStmt); + delete this.pStmt; + delete this.db; + } + }, + /** Clears all bound values. Returns this object. + Throws if this statement has been finalized. */ + clearBindings: function(){ + freeBindMemory(affirmStmtOpen(this)); + S.sqlite3_clear_bindings(this.pStmt); + return this; + }, + /** + Resets this statement so that it may be step()ed again + from the beginning. Returns this object. Throws if this + statement has been finalized. + + If passed a truthy argument then this.clearBindings() is + also called, otherwise any existing bindings, along with + any memory allocated for them, are retained. + */ + reset: function(alsoClearBinds){ + if(alsoClearBinds) this.clearBindings(); + S.sqlite3_reset(affirmStmtOpen(this).pStmt); + return this; + }, + /** + Binds one or more values to its bindable parameters. It + accepts 1 or 2 arguments: + + If passed a single argument, it must be either an array, an + object, or a value of a bindable type (see below). + + If passed 2 arguments, the first one is the 1-based bind + index or bindable parameter name and the second one must be + a value of a bindable type. + + Bindable value types: + + - null or undefined is bound as NULL. + + - Numbers are bound as either doubles or integers: int64 if + they are larger than 0xEFFFFFFF, else int32. Booleans are + bound as integer 0 or 1. Note that doubles with no + fractional part are bound as integers. It is not expected + that that distinction is significant for the majority of + clients due to sqlite3's data typing model. This API does + not currently support the BigInt type. + + - Strings are bound as strings (use bindAsBlob() to force + blob binding). + + - buffers (blobs) are currently TODO but will be bound as + blobs. + + If passed an array, each element of the array is bound at + the parameter index equal to the array index plus 1 + (because arrays are 0-based but binding is 1-based). + + If passed an object, each object key is treated as a + bindable parameter name. The object keys _must_ match any + bindable parameter names, including any `$`, `@`, or `:` + prefix. Because `$` is a legal identifier chararacter in + JavaScript, that is the suggested prefix for bindable + parameters. + + It returns this object on success and throws on + error. Errors include: + + - Any bind index is out of range or a named bind parameter + does not match. + + - Any value to bind is of an unsupported type. + + - Passed no arguments or more than two. + + - The statement has been finalized. + */ + bind: function(/*[ndx,] value*/){ + let ndx, arg; + switch(arguments.length){ + case 1: ndx = 1; arg = arguments[0]; break; + case 2: ndx = arguments[0]; arg = arguments[1]; break; + default: toss("Invalid bind() arguments."); + } + affirmStmtOpen(this); + if(null===arg || undefined===arg){ + /* bind NULL */ + return bindOne(this, ndx, BindType.null, arg); + } + else if(Array.isArray(arg)){ + /* bind each entry by index */ + if(1!==arguments.length){ + toss("When binding an array, an index argument is not permitted."); + } + arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); + return this; + } + else if('object'===typeof arg/*null was checked above*/){ + /* bind by name */ + if(1!==arguments.length){ + toss("When binding an object, an index argument is not permitted."); + } + Object.keys(arg) + .forEach(k=>bindOne(this, k, + affirmSupportedBindType(arg[k]), + arg[k])); + return this; + }else{ + return bindOne(this, ndx, + affirmSupportedBindType(arg), arg); + } + toss("Should not reach this point."); + }, + /** + Special case of bind() which binds the given value + using the BLOB binding mechanism instead of the default + selected one for the value. The ndx may be a numbered + or named bind index. The value must be of type string, + buffer, or null/undefined (both treated as null). + + If passed a single argument, a bind index of 1 is assumed. + */ + bindAsBlob: function(ndx,arg){ + affirmStmtOpen(this); + if(1===arguments.length){ + ndx = 1; + arg = arguments[0]; + } + const t = affirmSupportedBindType(arg); + if(BindTypes.string !== t && BindTypes.blob !== t + && BindTypes.null !== t){ + toss("Invalid value type for bindAsBlob()"); + } + return bindOne(this, ndx, BindType.blob, arg); + } + }; + + const SQLite3 = { + version: { + lib: S.sqlite3_libversion(), + ooApi: "0.0.1" + }, + DB + }; + return SQLite3; +}; + +const mainTest1 = function(S/*sqlite3 module*/){ + console.log("Loaded module:",S.sqlite3_libversion(), + S.sqlite3_sourceid()); + const oo = setupAPI(S); + + const db = new oo.DB(); + console.log("DB:",db.filename); +}; + +self/*window or worker*/.Module.onRuntimeInitialized = function(){ + console.log("Loading sqlite3-api.js..."); + self.Module.loadSqliteAPI(mainTest1); +}; diff --git a/manifest b/manifest index 34fda5bf97..54f4e5cb51 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Build\srefactoring\sfor\sthe\sfiddle/wasm\sbits.\sSet\sup\swasm\sbinding\sof\sa\schunk\sof\sthe\score\sC\sAPI\sand\sadded\ssome\sinfastructure\sfor\screating\stest\spages\sfor\sit. -D 2022-05-22T00:27:19.296 +C WASM\sOO\swrapper\s#1:\sprepare()\sand\sbind()\sAPIs\sare\sin\splace\sbut\sare\suntested,\spending\sfetch/get\sAPIs. +D 2022-05-22T14:07:44.577 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -55,8 +55,8 @@ F ext/expert/expert1.test 3c642a4e7bbb14f21ddab595436fb465a4733f47a0fe5b2855e1d5 F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0ff5d9cdfac204 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 -F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f w ext/fiddle/EXPORTED_FUNCTIONS -F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 4b06e6c3ce8c8389274079ffb6b441ffff1a55e32a448cf21ce1da45a16c8a01 +F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f +F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 a3a2862941270ae5e2633d21cbf44979901c4b75efa42a452c15ef879b47ad2b F ext/fiddle/EXPORTED_RUNTIME_METHODS 91d5dcb0168ee056fa1a340cb8ab3c23d922622f8dad39d28919dd8af2b3ade0 F ext/fiddle/Makefile 9277c73e208b9c8093659256c9f07409c877e366480c7c22ec545ee345451d95 F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f @@ -64,10 +64,10 @@ F ext/fiddle/fiddle-worker.js c22557b641b47fa1473d3465a4e69fe06b8b09b924955805a4 F ext/fiddle/fiddle.html 657c6c3f860c322fba3c69fa4f7a1209e2d2ce44b4bc65a3e154e3a97c047a7c F ext/fiddle/fiddle.js f9c79164428e96a5909532f18a8bc8f8c8ec4f738bfc09ad3d2a532c2400f9f0 F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf -F ext/fiddle/sqlite3-api.js d3c6da99850e146e50dc42039ac027e5d9b08b9f24eb22b31d1982c49930ee7c +F ext/fiddle/sqlite3-api.js 5f256e3dc78ed0ac4f8556c0c77860812f9baf542b7a73b19b2abb72a6e13146 F ext/fiddle/testing-common.js 37b014758db7e5e74278e37dc712ced2fc9b40d0617f5ed0b8b64a6bd9c0a45d F ext/fiddle/testing1.html 68cec1b1c8646a071717e5979f22e4268e6d36d96ba13ad68333351acdbcf1d1 -F ext/fiddle/testing1.js 0fb900c768b06c2ec3922ab522f721a68b0756d200e3c66602461f45910bcd39 +F ext/fiddle/testing1.js 2e9aa40a17c97ab8e90a8ba942725ebf590ae5db3f0329583d7431e4524f5b11 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea @@ -1967,8 +1967,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c7cfdd4c3682659352642461d3307bf8180703b121ec1802ba5881f8e1ef9809 -R d89e623e1614875df0e3a579152a2be9 +P dea098b64eb95c395b346ebcae687afe42b7d21df48833527808c02226300a66 +R d6777acddc9dd48a02f29eb36eccd673 U stephan -Z fd89698a5fe01334529b2fb407d595a1 +Z ec1032561dde0c5db030c66f88c029fe # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a0a10eb4c2..8691610d23 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dea098b64eb95c395b346ebcae687afe42b7d21df48833527808c02226300a66 \ No newline at end of file +84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39 \ No newline at end of file -- 2.47.2