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

Ease-of-use/legibility improvements in the virtual table JS helpers.

FossilOrigin-Name: 54c7ad7e08bdb87579398ade366605bfa2e2538a94aabcc6e4cda8e173649760
This commit is contained in:
stephan
2022-12-08 17:07:27 +00:00
parent 4bfacc4b40
commit 500fa7d518
4 changed files with 199 additions and 133 deletions

View File

@ -308,12 +308,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
vt.sqlite3ValuesToJs = capi.sqlite3_create_function_v2.udfConvertArgs;
/**
Factory function for xAbc() impls.
Internal factory function for xVtab and xCursor impls.
*/
const __xWrapFactory = function(methodName,structType){
const __xWrapFactory = function(methodName,StructType){
return function(ptr,removeMapping=false){
if(0===arguments.length) ptr = new structType;
if(ptr instanceof structType){
if(0===arguments.length) ptr = new StructType;
if(ptr instanceof StructType){
//T.assert(!this.has(ptr.pointer));
this.set(ptr.pointer, ptr);
return ptr;
@ -325,52 +325,99 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc;
}.bind(new Map);
};
/**
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
Has 3 distinct uses:
- wrapVtab() instantiates a new capi.sqlite3_vtab instance, maps
its pointer for later by-pointer lookup, and returns that
object. This is intended to be called from
sqlite3_module::xConnect() or xCreate() implementations.
- wrapVtab(pVtab) accepts a WASM pointer to a C-level
(sqlite3_vtab*) instance and returns the capi.sqlite3_vtab
object created by the first form of this function, or undefined
if that form has not been used. This is intended to be called
from sqlite3_module methods which take a (sqlite3_vtab*)
pointer _except_ for xDestroy() (if there is a distinct
xCreate()) or xDisconnect() (if xCreate() is 0 or is the same
as xConnect()), in which case use...
- wrapVtab(pVtab,true) as for the previous form, but removes the
pointer-to-object mapping before returning. The caller must
call dispose() on the returned object. This is intended to be
called from sqlite3_module::xDisconnect() implementations or
in error handling of a failed xCreate() or xConnect().
*/
vt.xVtab = __xWrapFactory('xVtab',capi.sqlite3_vtab);
/**
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
Internal helper for implementing xVtab and xCursor.
The first argument must be the logical name of the
handler ('xVtab' or 'xCursor') and the second must be
the capi.XYZ struct-type value, e.g. capi.sqlite3_vtab
or capi.sqlite3_vtab_cursor.
*/
const __xLifetimeManager = function(name, StructType){
const __xWrap = __xWrapFactory(name,StructType);
/**
This object houses a small API for managing mappings of (`T*`)
to StructType<T> objects, specifically within the lifetime
requirements of sqlite3_module methods.
*/
return Object.assign(Object.create(null),{
/** The StructType object for this object's API. */
StructType,
/**
Creates a new StructType object, writes its `pointer`
value to the given output pointer, and returns that
object. Its intended usage depends on StructType:
Works identically to wrapVtab() except that it deals with
sqlite3_cursor objects and pointers instead of sqlite3_vtab.
sqlite3_vtab: to be called from sqlite3_module::xConnect()
or xCreate() implementations.
- wrapCursor() is intended to be called from sqlite3_module::xOpen()
sqlite3_vtab_cursor: to be called from xOpen().
- wrapCursor(pCursor) is intended to be called from all sqlite3_module
methods which take a (sqlite3_vtab_cursor*) _except_ for
xClose(), in which case use...
This will throw if allocation of the StructType instance
fails or if ppOut is not a pointer-type value.
*/
create: (ppOut)=>{
const rc = __xWrap();
wasm.setPtrValue(ppOut, rc.pointer);
return rc;
},
/**
Returns the StructType object previously mapped to the
given pointer using create(). Its intended usage depends
on StructType:
- wrapCursor(pCursor, true) will remove the mapping of pCursor to a
capi.sqlite3_vtab_cursor object and return that object. The
caller must call dispose() on the returned object. This is
intended to be called from xClose() or in error handling of a
failed xOpen().
*/
vt.xCursor = __xWrapFactory('xCursor',capi.sqlite3_vtab_cursor);
sqlite3_vtab: to be called from sqlite3_module methods which
take a (sqlite3_vtab*) pointer _except_ for
xDestroy()/xDisconnect(), in which case unget() or dispose().
sqlite3_vtab_cursor: to be called from any sqlite3_module methods
which take a `sqlite3_vtab_cursor*` argument except xClose(),
in which case use unget() or dispose().
*/
get: (pCObj)=>__xWrap(pCObj),
/**
Identical to get() but also disconnects the mapping between the
given pointer and the returned StructType object, such that
future calls to this function or get() with the same pointer
will return the undefined value. Its intended usage depends
on StructType:
sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or
xDestroy() implementations or in error handling of a failed
xCreate() or xConnect().
sqlite3_vtab_cursor: to be called from xClose() or during
cleanup in a failed xOpen().
*/
unget: (pCObj)=>__xWrap(pCObj,true),
/**
Works like unget() plus it calls dispose() on the
StructType object.
*/
dispose: (pCObj)=>{
const o = __xWrap(pCObj,true);
if(o) o.dispose();
}
});
};
/**
A lifetime-management object for mapping `sqlite3_vtab*`
instances in sqlite3_module methods to capi.sqlite3_vtab
objects.
The API docs are in the API-internal __xLifetimeManager().
*/
vt.xVtab = __xLifetimeManager('xVtab', capi.sqlite3_vtab);
/**
A lifetime-management object for mapping `sqlite3_vtab_cursor*`
instances in sqlite3_module methods to capi.sqlite3_vtab_cursor
objects.
The API docs are in the API-internal __xLifetimeManager().
*/
vt.xCursor = __xLifetimeManager('xCursor', capi.sqlite3_vtab_cursor);
/**
Convenience form of creating an sqlite3_index_info wrapper,
@ -466,16 +513,28 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
A helper for sqlite3_vtab::xRowid() implementations. It must be
passed that function's 2nd argument and the value for that
pointer. Returns the same as wasm.setMemValue() and will throw
passed that function's 2nd argument (an output pointer to an
int64 row ID) and the value to store at the output pointer's
address. Returns the same as wasm.setMemValue() and will throw
if the 1st or 2nd arguments are invalid for that function.
Example xRowid impl:
```
const xRowid = (pCursor, ppRowid64)=>{
const c = VtabHelper.xCursor(pCursor);
VtabHelper.xRowid(ppRowid64, c.myRowId);
return 0;
};
```
*/
vt.xRowid = (ppRowid64, value)=>wasm.setMemValue(ppRowid64, value, 'i64');
/**
Sets up an sqlite3_module() object for later installation into
individual databases using sqlite3_create_module(). Requires an
object with the following properties:
A helper to initialize and set up an sqlite3_module() object for
later installation into individual databases using
sqlite3_create_module(). Requires an object with the following
properties:
- `methods`: an object containing a mapping of properties with
the C-side names of the sqlite3_module methods, e.g. xCreate,
@ -506,16 +565,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
behavior. (VtabHelper.xError() is intended to assist in reporting
such exceptions.)
If `methods.xConnect` is `true` then the value of
`methods.xCreate` is used in its place, and vice versa. This is
to facilitate creation of those methods inline in the passed-in
object without requiring the client to explicitly get a reference
to one of them in order to assign it to the other one. Note that
sqlite treats those two functions specially if they are exactly
the same function (same pointer value). The
`catchExceptions`-installed handlers will account for identical
references to those two functions and will install the same
wrapper function for both.
Certain methods may refer to the same implementation. To simplify
the definition of such methods:
- If `methods.xConnect` is `true` then the value of
`methods.xCreate` is used in its place, and vice versa. sqlite
treats xConnect/xCreate functions specially if they are exactly
the same function (same pointer value).
- If `methods.xDisconnect` is true then the value of
`methods.xDestroy` is used in its place, and vice versa.
This is to facilitate creation of those methods inline in the
passed-in object without requiring the client to explicitly get a
reference to one of them in order to assign it to the other
one.
The `catchExceptions`-installed handlers will account for
identical references to the above functions and will install the
same wrapper function for both.
The given methods are expected to return integer values, as
expected by the C API. If `catchExceptions` is truthy, the return
@ -525,7 +593,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
active, the method implementations must explicitly return integer
values.
Throws on error. Returns the sqlite3_module object on success.
Throws on error. Returns the opt.struct sqlite3_module object on
success.
*/
vt.setupModule = function(opt){
const mod = opt.struct || new capi.sqlite3_module();
@ -533,6 +602,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const methods = opt.methods || toss("Missing 'methods' object.");
if(true===methods.xConnect) methods.xConnect = methods.xCreate;
else if(true===methods.xCreate) methods.xCreate = methods.xConnect;
if(true===methods.xDisconnect) methods.xDisconnect = methods.xDestroy;
else if(true===methods.xDestroy) methods.xDestroy = methods.xDisconnect;
if(opt.catchExceptions){
const fwrap = function(methodName, func){
if(['xConnect','xCreate'].indexOf(methodName) >= 0){
@ -577,6 +648,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
this.installMethods(mod, remethods, false);
}else{
// No automatic exception handling. Trust the client
// to not throw.
this.installMethods(
mod, methods, !!opt.applyArgcCheck/*undocumented option*/
);

View File

@ -999,7 +999,12 @@ self.sqlite3InitModule = sqlite3InitModule;
const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c');
db.onclose = {
disposeAfter: [],
disposeBefore: [],
disposeBefore: [
(db)=>{
//console.debug("db.onclose.before dropping modules");
//sqlite3.capi.sqlite3_drop_modules(db.pointer, 0);
}
],
before: function(db){
while(this.disposeBefore.length){
const v = this.disposeBefore.shift();
@ -1013,8 +1018,6 @@ self.sqlite3InitModule = sqlite3InitModule;
}
}
}
console.debug("db.onclose.before dropping modules");
sqlite3.capi.sqlite3_drop_modules(db.pointer, 0);
},
after: function(){
while(this.disposeAfter.length){
@ -1669,7 +1672,7 @@ self.sqlite3InitModule = sqlite3InitModule;
////////////////////////////////////////////////////////////////////////
.t({
name: 'virtual table #1',
name: 'virtual table #1: eponymous w/ manual exception handling',
predicate: ()=>!!capi.sqlite3_index_info,
test: function(sqlite3){
warn("The vtab/module JS bindings are experimental and subject to change.");
@ -1694,9 +1697,8 @@ self.sqlite3InitModule = sqlite3InitModule;
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
const t = vth.xVtab();
wasm.setPtrValue(ppVtab, t.pointer);
T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
const t = vth.xVtab.create(ppVtab);
T.assert(t === vth.xVtab.get(wasm.getPtrValue(ppVtab)));
}
return rc;
}catch(e){
@ -1709,8 +1711,7 @@ self.sqlite3InitModule = sqlite3InitModule;
},
xDisconnect: function(pVtab){
try {
const t = vth.xVtab(pVtab, true);
t.dispose();
vth.xVtab.unget(pVtab).dispose();
return 0;
}catch(e){
return vth.xError('xDisconnect',e);
@ -1718,10 +1719,10 @@ self.sqlite3InitModule = sqlite3InitModule;
},
xOpen: function(pVtab, ppCursor){
try{
const t = vth.xVtab(pVtab), c = vth.xCursor();
const t = vth.xVtab.get(pVtab),
c = vth.xCursor.create(ppCursor);
T.assert(t instanceof capi.sqlite3_vtab)
.assert(c instanceof capi.sqlite3_vtab_cursor);
wasm.setPtrValue(ppCursor, c.pointer);
c._rowId = 0;
return 0;
}catch(e){
@ -1730,9 +1731,9 @@ self.sqlite3InitModule = sqlite3InitModule;
},
xClose: function(pCursor){
try{
const c = vth.xCursor(pCursor,true);
const c = vth.xCursor.unget(pCursor);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
.assert(!vth.xCursor(pCursor));
.assert(!vth.xCursor.get(pCursor));
c.dispose();
return 0;
}catch(e){
@ -1741,17 +1742,16 @@ self.sqlite3InitModule = sqlite3InitModule;
},
xNext: function(pCursor){
try{
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
++c._rowId;
return 0;
}catch(e){
return vth.xError('xNext',e);
}
},
xColumn: function(pCursor, pCtx, iCol){
try{
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
switch(iCol){
case tmplCols.A:
capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
@ -1768,7 +1768,7 @@ self.sqlite3InitModule = sqlite3InitModule;
},
xRowid: function(pCursor, ppRowid64){
try{
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
vth.xRowid(ppRowid64, c._rowId);
return 0;
}catch(e){
@ -1776,7 +1776,7 @@ self.sqlite3InitModule = sqlite3InitModule;
}
},
xEof: function(pCursor){
const c = vth.xCursor(pCursor),
const c = vth.xCursor.get(pCursor),
rc = c._rowId>=10;
c.dispose();
return rc;
@ -1784,7 +1784,7 @@ self.sqlite3InitModule = sqlite3InitModule;
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
try{
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
c._rowId = 0;
const list = vth.sqlite3ValuesToJs(argc, argv);
T.assert(argc === list.length);
@ -1797,7 +1797,7 @@ self.sqlite3InitModule = sqlite3InitModule;
},
xBestIndex: function(pVtab, pIdxInfo){
try{
//const t = vth.xVtab(pVtab);
//const t = vth.xVtab.get(pVtab);
const sii = capi.sqlite3_index_info;
const pii = new sii(pIdxInfo);
pii.$estimatedRows = 10;
@ -1886,7 +1886,7 @@ self.sqlite3InitModule = sqlite3InitModule;
////////////////////////////////////////////////////////////////////////
.t({
name: 'virtual table #2 (non-eponymous w/ automated exception wrapping)',
name: 'virtual table #2: non-eponymous w/ automated exception wrapping',
predicate: ()=>!!capi.sqlite3_index_info,
test: function(sqlite3){
warn("The vtab/module JS bindings are experimental and subject to change.");
@ -1926,55 +1926,41 @@ self.sqlite3InitModule = sqlite3InitModule;
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
const t = vth.xVtab();
wasm.setPtrValue(ppVtab, t.pointer);
T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
const t = vth.xVtab.create(ppVtab);
T.assert(t === vth.xVtab.get(wasm.getPtrValue(ppVtab)));
vtabTrace("xCreate",...arguments," ppVtab =",t.pointer);
}
return rc;
},
xConnect: true,
xDestroy: function(pVtab){
vtabTrace("sqlite3_xDestroy",pVtab);
const t = vth.xVtab(pVtab, true);
T.assert(t);
t.dispose();
vtabTrace("xDestroy/xDisconnect",pVtab);
vth.xVtab.dispose(pVtab);
},
/*xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
vtabTrace("xConnect",...arguments);
const t = vth.xVtab();
wasm.setPtrValue(ppVtab, t.pointer);
T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
vtabTrace("xConnect",...arguments,"ppVtab =",t.pointer);
},
xDisconnect: function(pVtab){
vtabTrace("xDisconnect",pVtab);
const t = vth.xVtab(pVtab, true);
T.assert(t);
t.dispose();
},*/
xDisconnect: true,
xOpen: function(pVtab, ppCursor){
const t = vth.xVtab(pVtab), c = vth.xCursor();
const t = vth.xVtab.get(pVtab),
c = vth.xCursor.create(ppCursor);
T.assert(t instanceof capi.sqlite3_vtab)
.assert(c instanceof capi.sqlite3_vtab_cursor);
wasm.setPtrValue(ppCursor, c.pointer);
vtabTrace("xOpen",...arguments," cursor =",c.pointer);
c._rowId = 0;
},
xClose: function(pCursor){
vtabTrace("xClose",...arguments);
const c = vth.xCursor(pCursor,true);
const c = vth.xCursor.unget(pCursor);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
.assert(!vth.xCursor(pCursor));
.assert(!vth.xCursor.get(pCursor));
c.dispose();
},
xNext: function(pCursor){
vtabTrace("xNext",...arguments);
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
++c._rowId;
},
xColumn: function(pCursor, pCtx, iCol){
vtabTrace("xColumn",...arguments);
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
switch(iCol){
case tmplCols.A:
capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
@ -1987,24 +1973,24 @@ self.sqlite3InitModule = sqlite3InitModule;
},
xRowid: function(pCursor, ppRowid64){
vtabTrace("xRowid",...arguments);
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
vth.xRowid(ppRowid64, c._rowId);
},
xEof: function(pCursor){
vtabTrace("xEof",...arguments);
return vth.xCursor(pCursor)._rowId>=10;
return vth.xCursor.get(pCursor)._rowId>=10;
},
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
vtabTrace("xFilter",...arguments);
const c = vth.xCursor(pCursor);
const c = vth.xCursor.get(pCursor);
c._rowId = 0;
const list = vth.sqlite3ValuesToJs(argc, argv);
T.assert(argc === list.length);
},
xBestIndex: function(pVtab, pIdxInfo){
vtabTrace("xBestIndex",...arguments);
//const t = vth.xVtab(pVtab);
//const t = vth.xVtab.get(pVtab);
const pii = vth.xIndexInfo(pIdxInfo);
pii.$estimatedRows = 10;
pii.$estimatedCost = 10.0;
@ -2024,19 +2010,18 @@ self.sqlite3InitModule = sqlite3InitModule;
modConfig.name,
"(arg1 blah, arg2 bloop)"
]);
this.db.onclose.disposeBefore.push(function(db){
console.debug("testvtab2 disposeBefore handler...");
db.exec(
/**
DROP TABLE is the only way to get xDestroy() to be called.
If we DROP TABLE at the end of the containing
test function, xDestroy() is called. If we instead
delay it until db.onclose.before(), we're getting
"no such table"?
*/
"DROP TABLE testvtab2"
);
});
if(0){
/* If we DROP TABLE then xDestroy() is called. If the
vtab is instead destroyed when the db is closed,
xDisconnect() is called. */
this.db.onclose.disposeBefore.push(function(db){
console.debug("Explicitly dropping testvtab2 via disposeBefore handler...");
db.exec(
/** DROP TABLE is the only way to get xDestroy() to be called. */
"DROP TABLE testvtab2"
);
});
}
let list = this.db.selectArrays(
"SELECT a,b FROM testvtab2 where a<9999 and b>1 order by a, b"
/* Query is shaped so that it will ensure that some
@ -2052,6 +2037,15 @@ self.sqlite3InitModule = sqlite3InitModule;
T.assert(5===list.length)
.assert(1000===list[0][0])
.assert(2004===list[list.length-1][1]);
// Call it as a table-valued function...
list = this.db.selectArrays([
"SELECT a,b FROM ", modConfig.name,
" where a<9999 and b>1 order by b, a limit 1"
]);
T.assert(1===list.length)
.assert(1000===list[0][0])
.assert(2000===list[0][1]);
}
})/*custom vtab #2*/

View File

@ -1,5 +1,5 @@
C Initial\ssupport\sfor\svirtual\stables\simplemented\sin\sJavaScript.
D 2022-12-08T15:00:53.415
C Ease-of-use/legibility\simprovements\sin\sthe\svirtual\stable\sJS\shelpers.
D 2022-12-08T17:07:27.475
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -509,7 +509,7 @@ F ext/wasm/api/sqlite3-api-prologue.js 1380e933325c11786b2afc93fc8ff88c2fd1ffeac
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 7795b84b66a7a8dedc791340709b310bb497c3c72a80bef364fa2a58e2ddae3f
F ext/wasm/api/sqlite3-v-helper.js ec03a222ad3551764626f14f38de1b1081bda509e098849502b498c041993a0f w ext/wasm/api/sqlite3-vfs-helper.js
F ext/wasm/api/sqlite3-v-helper.js d0d647110f8595d26836951cfc5a47f15282f20e72584decc0b00e2a3f0d169c
F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 8ec510fee735c646fb18a3b99f0ca5ca461f9e066c43cdc404d7144f12ae6ed6
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 97034ab4f40ec1fac71ccfaf3afffdca6b1ea2dcd95b7871527bad0f34e152b0
@ -555,7 +555,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
F ext/wasm/tester1-worker.html d43f3c131d88f10d00aff3e328fed13c858d674ea2ff1ff90225506137f85aa9
F ext/wasm/tester1.c-pp.html d34bef3d48e5cbc1c7c06882ad240fec49bf88f5f65696cc2c72c416933aa406
F ext/wasm/tester1.c-pp.js ecd0ecd6c9f0c5bce7f39a3ccb28151c7ae7b1042379a0c855e930661579f2e8
F ext/wasm/tester1.c-pp.js ff147d87a1b6b827484da0bedf6f9f03ae027d6d4aec8ab9b22b22801c6df474
F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70
F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@ -2067,9 +2067,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 53dcb81b029e4422bdc0b5cf694183854b997195427d437b6154ed4dad7ad0ba 51e3c3b569dce2097063d39dc484f44b2f98cad0a902ef66765ca4cdc3e06f47
R 8761e73b7826ed273f62994737b011e2
T +closed 51e3c3b569dce2097063d39dc484f44b2f98cad0a902ef66765ca4cdc3e06f47 Closed\sby\sintegrate-merge.
P a1454744c770a30a32a6d7b7fc59ef7be48cf67348b238540592850d7c2c7757
R 17cb094a6db22f136920052decf90bf5
U stephan
Z b6d983dab7ab65c0caebb5e5bbac6324
Z fce05bcff7bc40d31cdbdf756e15ecfc
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
a1454744c770a30a32a6d7b7fc59ef7be48cf67348b238540592850d7c2c7757
54c7ad7e08bdb87579398ade366605bfa2e2538a94aabcc6e4cda8e173649760