1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Add more JS tests. Flesh out the aggregate UDF tests to use sqlite3_aggregate_context() so that they can each be used multiple times in the same statement. Add sqlite3_js_aggregate_context() convenience helper.

FossilOrigin-Name: 9d034ef5e1bab7c9651c2450dc85765fa6365d3f1414c711550de858ff8b3ece
This commit is contained in:
stephan
2022-10-20 21:28:31 +00:00
parent 875b95d4f5
commit 96b6371d70
7 changed files with 155 additions and 53 deletions

View File

@ -149,9 +149,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE; if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY; if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
oflags |= capi.SQLITE_OPEN_EXRESCODE; oflags |= capi.SQLITE_OPEN_EXRESCODE;
const scope = wasm.scopedAllocPush(); const stack = wasm.pstack.pointer;
try { try {
const pPtr = wasm.allocPtr() /* output (sqlite3**) arg */; const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
const pVfsName = vfsName ? ( const pVfsName = vfsName ? (
('number'===typeof vfsName ? vfsName : wasm.scopedAllocCString(vfsName)) ('number'===typeof vfsName ? vfsName : wasm.scopedAllocCString(vfsName))
): 0; ): 0;
@ -163,21 +163,19 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
__dbTraceToConsole, 0); __dbTraceToConsole, 0);
} }
// Check for per-VFS post-open SQL... // Check for per-VFS post-open SQL...
wasm.setPtrValue(pPtr, 0); const pVfs = capi.sqlite3_js_db_vfs(pDb);
if(0===capi.sqlite3_file_control( //console.warn("Opened db",fn,"with vfs",vfsName,pVfs);
pDb, "main", capi.SQLITE_FCNTL_VFS_POINTER, pPtr if(!pVfs) toss3("Internal error: cannot get VFS for new db handle.");
)){ const postInitSql = __vfsPostOpenSql[pVfs];
const postInitSql = __vfsPostOpenSql[wasm.getPtrValue(pPtr)]; if(postInitSql){
if(postInitSql){ rc = capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0);
rc = capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0); checkSqlite3Rc(pDb, rc);
checkSqlite3Rc(pDb, rc);
}
} }
}catch( e ){ }catch( e ){
if( pDb ) capi.sqlite3_close_v2(pDb); if( pDb ) capi.sqlite3_close_v2(pDb);
throw e; throw e;
}finally{ }finally{
wasm.scopedAllocPop(scope); wasm.pstack.restore(stack);
} }
this.filename = fnJs; this.filename = fnJs;
__ptrMap.set(this, pDb); __ptrMap.set(this, pDb);

View File

@ -280,6 +280,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
this.name = 'WasmAllocError'; this.name = 'WasmAllocError';
} }
}; };
/**
Functionally equivalent to the WasmAllocError constructor but may
be used as part of an expression, e.g.:
```
return someAllocatingFunction(x) || WasmAllocError.toss(...);
```
*/
WasmAllocError.toss = (...args)=>{ WasmAllocError.toss = (...args)=>{
throw new WasmAllocError(args.join(' ')); throw new WasmAllocError(args.join(' '));
}; };
@ -508,6 +516,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
"flexible-string" argument converter. "flexible-string" argument converter.
*/ */
sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/,
/** /**
Various internal-use utilities are added here as needed. They Various internal-use utilities are added here as needed. They
are bound to an object only so that we have access to them in are bound to an object only so that we have access to them in
@ -1024,6 +1033,14 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
this.name = 'SQLite3Error'; this.name = 'SQLite3Error';
} }
}; };
/**
Functionally equivalent to the SQLite3Error constructor but may
be used as part of an expression, e.g.:
```
return someFunction(x) || SQLite3Error.toss(...);
```
*/
SQLite3Error.toss = (...args)=>{ SQLite3Error.toss = (...args)=>{
throw new SQLite3Error(args.join(' ')); throw new SQLite3Error(args.join(' '));
}; };
@ -1096,8 +1113,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
pointer. The 3rd argument specifies the database name of the pointer. The 3rd argument specifies the database name of the
given database connection to check, defaulting to the main db. given database connection to check, defaulting to the main db.
The 2nd and 3rd arguments may either be a JS string or a C-string The 2nd and 3rd arguments may either be a JS string or a WASM
allocated from the wasm environment. C-string. If the 2nd argument is a NULL WASM pointer, the default
VFS is assumed. If the 3rd is a NULL WASM pointer, "main" is
assumed.
The truthy value it returns is a pointer to the `sqlite3_vfs` The truthy value it returns is a pointer to the `sqlite3_vfs`
object. object.
@ -1112,17 +1131,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
const pK = capi.sqlite3_vfs_find(vfsName); const pK = capi.sqlite3_vfs_find(vfsName);
if(!pK) return false; if(!pK) return false;
else if(!pDb){ else if(!pDb){
return capi.sqlite3_vfs_find(0)===pK ? pK : false; return pK===capi.sqlite3_vfs_find(0) ? pK : false;
} }else{
const ppVfs = wasm.allocPtr(); return pK===capi.sqlite3_js_db_vfs(pDb) ? pK : false;
try{
return (
(0===capi.sqlite3_file_control(
pDb, dbName, capi.SQLITE_FCNTL_VFS_POINTER, ppVfs
)) && (wasm.getPtrValue(ppVfs) === pK)
) ? pK : false;
}finally{
wasm.dealloc(ppVfs);
} }
}catch(e){ }catch(e){
/* Ignore - probably bad args to a wasm-bound function. */ /* Ignore - probably bad args to a wasm-bound function. */
@ -1179,14 +1190,32 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
? wasm.heap8u().slice(pOut, pOut + Number(nOut)) ? wasm.heap8u().slice(pOut, pOut + Number(nOut))
: new Uint8Array(); : new Uint8Array();
return rc; return rc;
}catch(e){
console.error('internal error?',e);
throw w;
}finally{ }finally{
if(pOut) wasm.exports.sqlite3_free(pOut); if(pOut) wasm.exports.sqlite3_free(pOut);
wasm.pstack.restore(stack); wasm.pstack.restore(stack);
} }
}; };
/**
Given a `sqlite3*` and a database name (JS string or WASM
C-string pointer, which may be 0), returns a pointer to the
sqlite3_vfs responsible for it. If the given db name is null/0,
or not provided, then "main" is assumed.
*/
capi.sqlite3_js_db_vfs =
(dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName);
/**
A thin wrapper around capi.sqlite3_aggregate_context() which
behaves the same except that it throws a WasmAllocError if that
function returns 0.
*/
capi.sqlite3_js_aggregate_context = (pCtx, n)=>{
return capi.sqlite3_aggregate_context(pCtx, n)
|| WasmAllocError.toss(
"Cannot allocate",n,"bytes for sqlite3_aggregate_context()"
);
};
if( capi.util.isMainWindow() ){ if( capi.util.isMainWindow() ){
/* Features specific to the main window thread... */ /* Features specific to the main window thread... */

View File

@ -231,7 +231,7 @@
If the `dbId` does not refer to an opened ID, this is a no-op. If If the `dbId` does not refer to an opened ID, this is a no-op. If
the `args` object contains a truthy `unlink` value then the database the `args` object contains a truthy `unlink` value then the database
will unlinked (deleted) after closing it. The inability to close a will be unlinked (deleted) after closing it. The inability to close a
db (because it's not opened) or delete its file does not trigger an db (because it's not opened) or delete its file does not trigger an
error. error.

View File

@ -48,6 +48,7 @@
** want undefined. Please keep these alphabetized. ** want undefined. Please keep these alphabetized.
*/ */
#undef SQLITE_OMIT_DESERIALIZE #undef SQLITE_OMIT_DESERIALIZE
#undef SQLITE_OMIT_MEMORYDB
/* /*
** Define any SQLITE_... config defaults we want if they aren't ** Define any SQLITE_... config defaults we want if they aren't

View File

@ -1083,6 +1083,47 @@
.assert(0===this.db.openStatementCount()); .assert(0===this.db.openStatementCount());
}) })
////////////////////////////////////////////////////////////////////////
.t('sqlite3_js_...()', function(){
const db = this.db;
if(1){
const vfsList = capi.sqlite3_js_vfs_list();
T.assert(vfsList.length>1);
T.assert('string'===typeof vfsList[0]);
for(const v of vfsList){
T.assert('string' === typeof v)
.assert(capi.sqlite3_vfs_find(v) > 0);
}
}
/**
Trivia: the magic db name ":memory:" does not actually use the
"memdb" VFS unless "memdb" is _explicitly_ provided as the VFS
name. Instead, it uses the default VFS with an in-memory btree.
Thus this.db's VFS may not be memdb even though it's an in-memory
db.
*/
const pVfsMem = capi.sqlite3_vfs_find('memdb'),
pVfsDflt = capi.sqlite3_vfs_find(0),
pVfsDb = capi.sqlite3_js_db_vfs(db.pointer);
T.assert(pVfsMem > 0)
.assert(pVfsDflt > 0)
.assert(pVfsDb > 0)
.assert(pVfsMem !== pVfsDflt
/* memdb lives on top of the default vfs */)
.assert(':memory:' === db.filename)
.assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem)
;
/*const vMem = new capi.sqlite3_vfs(pVfsMem),
vDflt = new capi.sqlite3_vfs(pVfsDflt),
vDb = new capi.sqlite3_vfs(pVfsDb);*/
const duv = capi.sqlite3_js_db_uses_vfs;
T.assert(pVfsDflt === duv(db.pointer, 0)
|| pVfsMem === duv(db.pointer,0))
.assert(!duv(db.pointer, "foo"))
;
}/*sqlite3_js_...()*/)
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
.t('Table t', function(sqlite3){ .t('Table t', function(sqlite3){
const db = this.db; const db = this.db;
@ -1168,6 +1209,14 @@
.assert(0==e.message.indexOf('Cannot prepare empty')); .assert(0==e.message.indexOf('Cannot prepare empty'));
} }
}) })
////////////////////////////////////////////////////////////////////////
.t('sqlite3_js_db_export()', function(){
const db = this.db;
const xp = capi.sqlite3_js_db_export(db.pointer);
T.assert(xp instanceof Uint8Array)
.assert(xp.byteLength>0)
.assert(0 === xp.byteLength % 512);
}/*sqlite3_js_db_export()*/)
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
.t('Scalar UDFs', function(sqlite3){ .t('Scalar UDFs', function(sqlite3){
@ -1228,39 +1277,40 @@
name: 'Aggregate UDFs', name: 'Aggregate UDFs',
test: function(sqlite3){ test: function(sqlite3){
const db = this.db; const db = this.db;
const aggState = {summer: 0, summerN: 0}; const sjac = capi.sqlite3_js_aggregate_context;
db.createFunction({ db.createFunction({
name: 'summer', name: 'summer',
xStep: function(pCtx, n){ xStep: function(pCtx, n){
aggState.summer += n; const pAgg = sjac(pCtx, 4);
wasm.setMemValue(pAgg, wasm.getMemValue(pAgg,'i32') + Number(n), 'i32');
}, },
xFinal: function(pCtx){ xFinal: (pCtx)=>wasm.getMemValue(sjac(pCtx, 4),'i32')
const rc = aggState.summer;
aggState.summer = 0;
return rc;
}
}); });
let v = db.selectValue([ let v = db.selectValue([
"with cte(v) as (", "with cte(v) as (",
"select 3 union all select 5 union all select 7", "select 3 union all select 5 union all select 7",
") select summer(v) from cte" ") select summer(v), summer(v+1) from cte"
/* ------------------^^^^^^^^^^^ ensures that we're handling
sqlite3_aggregate_context() properly. */
]); ]);
T.assert(15===v); T.assert(15===v);
T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"), T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
/wrong number of arguments/); /wrong number of arguments/);
db.createFunction({ db.createFunction({
name: 'summerN', name: 'summerN',
arity: -1, arity: -1,
xStep: function(pCtx, ...args){ xStep: function(pCtx, ...args){
for(const v of args) aggState.summerN += v; const pAgg = sjac(pCtx, 4);
let sum = wasm.getMemValue(pAgg, 'i32');
for(const v of args) sum += Number(v);
wasm.setMemValue(pAgg, sum, 'i32');
}, },
xFinal: function(pCtx){ xFinal: function(pCtx){
const rc = aggState.summerN; return wasm.getMemValue(sjac(pCtx, 4),'i32')
aggState.summerN = 0;
return rc;
} }
}); });
T.assert(18===db.selectValue('select summerN(1,8,9)')); T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
T.mustThrowMatching(()=>{ T.mustThrowMatching(()=>{
db.createFunction('nope',{ db.createFunction('nope',{
xFunc: ()=>{}, xStep: ()=>{} xFunc: ()=>{}, xStep: ()=>{}
@ -1284,6 +1334,30 @@
} }
}/*aggregate UDFs*/) }/*aggregate UDFs*/)
////////////////////////////////////////////////////////////////////////
.t({
name: 'Aggregate UDFs (64-bit)',
predicate: ()=>wasm.bigIntEnabled,
test: function(sqlite3){
const db = this.db;
const sjac = capi.sqlite3_js_aggregate_context;
db.createFunction({
name: 'summer64',
xStep: function(pCtx, n){
const pAgg = sjac(pCtx, 8);
wasm.setMemValue(pAgg, wasm.getMemValue(pAgg,'i64') + BigInt(n), 'i64');
},
xFinal: (pCtx)=>wasm.getMemValue(sjac(pCtx, 8),'i64')
});
let v = db.selectValue([
"with cte(v) as (",
"select 3 union all select 5 union all select 7",
") select summer64(v*10), summer64(v+1) from cte"
]);
T.assert(150n===BigInt(v));
}
}/*aggregate UDFs*/)
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
.t({ .t({
name: 'Window UDFs (tests are TODO)', name: 'Window UDFs (tests are TODO)',

View File

@ -1,5 +1,5 @@
C Apply\smagic.txt\scorrection\sand\saddition\sreported\sin\s[forum\spost\s2d2366a04a0385|forum:2d2366a04a0385]. C Add\smore\sJS\stests.\sFlesh\sout\sthe\saggregate\sUDF\stests\sto\suse\ssqlite3_aggregate_context()\sso\sthat\sthey\scan\seach\sbe\sused\smultiple\stimes\sin\sthe\ssame\sstatement.\sAdd\ssqlite3_js_aggregate_context()\sconvenience\shelper.
D 2022-10-20T18:58:14.289 D 2022-10-20T21:28:31.440
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -485,14 +485,14 @@ F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1
F ext/wasm/api/pre-js.js 151e0616614a49f3db19ed544fa13b38c87c108959fbcd4029ea8399a562d94f F ext/wasm/api/pre-js.js 151e0616614a49f3db19ed544fa13b38c87c108959fbcd4029ea8399a562d94f
F ext/wasm/api/sqlite3-api-cleanup.js 4d07a7524dc9b7b050acfde57163e839243ad2383bd7ee0de0178b1b3e988588 F ext/wasm/api/sqlite3-api-cleanup.js 4d07a7524dc9b7b050acfde57163e839243ad2383bd7ee0de0178b1b3e988588
F ext/wasm/api/sqlite3-api-glue.js 0b5240bd325d2561f269cd0d82bf686336526e5e276251c2241adfbda802abf8 F ext/wasm/api/sqlite3-api-glue.js 0b5240bd325d2561f269cd0d82bf686336526e5e276251c2241adfbda802abf8
F ext/wasm/api/sqlite3-api-oo1.js dc9b6a61649ad32836044de388c5248790239d62ced4e1116023135fcb0fc68b F ext/wasm/api/sqlite3-api-oo1.js 0e278d131dad72e9eb348a3dda6a4ff734a9e08925b4ed7e6e5a688d2edaf525
F ext/wasm/api/sqlite3-api-opfs.js 22d60ba956e873b65e2e0591e239178082bd53a6d563c3c58db7dc03e562e8f7 F ext/wasm/api/sqlite3-api-opfs.js 22d60ba956e873b65e2e0591e239178082bd53a6d563c3c58db7dc03e562e8f7
F ext/wasm/api/sqlite3-api-prologue.js 1366d538a7b388c299a389f441a79cf0b18af50208343545bd318936b6232acd F ext/wasm/api/sqlite3-api-prologue.js bb7a98a8c62545bf07b5fdee831c0d40c86f98c0094b00d8497a9de8976a0544
F ext/wasm/api/sqlite3-api-worker1.js cb07b321164483524a27cf2207d4358b905703c410fcd8256e0acca5ab2fffb2 F ext/wasm/api/sqlite3-api-worker1.js a7f38f03275d6c27ab2aef3e83215d3c97ce09c43e6904df47c3764d9d4572b4
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 206ce6bbc3c30ad51a37d9c25e3a2712e70b586e0f9a2cf8cb0b9619017c2671 F ext/wasm/api/sqlite3-opfs-async-proxy.js 206ce6bbc3c30ad51a37d9c25e3a2712e70b586e0f9a2cf8cb0b9619017c2671
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c e1fcda97775dd149b4a2e0a4f16a22cede96daa5a91795ba214bdb3c2e680f4a F ext/wasm/api/sqlite3-wasm.c dde771a10e6a8d01ae516d32abc4cdd2d542cb3339e35c5b4be8ebbc1dc1ccc1
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
F ext/wasm/api/sqlite3-worker1.js dbe54b69c1520a2d25eae148cd2750ded2dd7f219ea4ee46f83e0a851dca5974 F ext/wasm/api/sqlite3-worker1.js dbe54b69c1520a2d25eae148cd2750ded2dd7f219ea4ee46f83e0a851dca5974
F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
@ -533,7 +533,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 48fc59110e8775bb43c9be25b6d634fc07ebadab7da8fbd44889e8129c6e2548 F ext/wasm/test-opfs-vfs.js 48fc59110e8775bb43c9be25b6d634fc07ebadab7da8fbd44889e8129c6e2548
F ext/wasm/tester1-worker.html 048c341f124fdb61ca14dfd1bd1f78742490f208aa3bb1e84399f83f1e7e6a74 F ext/wasm/tester1-worker.html 048c341f124fdb61ca14dfd1bd1f78742490f208aa3bb1e84399f83f1e7e6a74
F ext/wasm/tester1.html 37ccc958fa0d95074af2d72b7241c8e2d982bbec6dda4dc790241af3d933c3b6 F ext/wasm/tester1.html 37ccc958fa0d95074af2d72b7241c8e2d982bbec6dda4dc790241af3d933c3b6
F ext/wasm/tester1.js 44d71175e2941bf1d7c27afa0c395fe81c83cbd74cd10e34e0688dd833042f1e F ext/wasm/tester1.js 6e1c8e4d48add1d49fb7901dfee04fdf6cdd5bc39adcbf15c097f61b581a893c
F ext/wasm/version-info.c 5fa356d38859d71a0369b5c37e1935def7413fcc8a4e349a39d9052c1d0479f4 F ext/wasm/version-info.c 5fa356d38859d71a0369b5c37e1935def7413fcc8a4e349a39d9052c1d0479f4
F ext/wasm/wasmfs.make ee0004813e16c283ff633e08b482008d56adf9b7d42f6c5612f7ab002b924f69 F ext/wasm/wasmfs.make ee0004813e16c283ff633e08b482008d56adf9b7d42f6c5612f7ab002b924f69
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
@ -2036,8 +2036,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P ac9af71b72a749b0a040273a88480d56f49570b569389a4ea20cc055f494d8ff P 9bf26e2aa3579f354ed2d314e1bf3e3ef117cbd71500ef5f76caa1de5cce1edc
R b44d84c26c2f9b06f7e9b798ca72a049 R dbe97bbb80fe6071199461fb846ef1aa
U stephan U stephan
Z e01cdee717736d0716b4f5500835f6af Z 83bd13decfea9b8d2129079a8ff06725
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
9bf26e2aa3579f354ed2d314e1bf3e3ef117cbd71500ef5f76caa1de5cce1edc 9d034ef5e1bab7c9651c2450dc85765fa6365d3f1414c711550de858ff8b3ece