void sqlite3Fts5StructureRelease(void*);
int sqlite3Fts5StructureTest(Fts5Index*, void*);
+/*
+** Used by xInstToken() and xPhraseToken().
+*/
+int sqlite3Fts5IterToken(Fts5IndexIter*, int, int, const char**, int*);
/*
** Insert or remove data to or from the index. Each time a document is
int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *);
int sqlite3Fts5ExprQueryToken(Fts5Expr*, int, int, const char**, int*);
+int sqlite3Fts5ExprInstToken(Fts5Expr*, int, int, int, int, const char**, int*);
/*******************************************
** The fts5_expr.c API above this point is used by the other hand-written
return SQLITE_OK;
}
+int sqlite3Fts5ExprInstToken(
+ Fts5Expr *pExpr,
+ int iPhrase,
+ int iCol,
+ int iOff,
+ int iToken,
+ const char **ppOut,
+ int *pnOut
+){
+ Fts5ExprPhrase *pPhrase = 0;
+ Fts5IndexIter *pIter = 0;
+
+ if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){
+ return SQLITE_RANGE;
+ }
+ pPhrase = pExpr->apExprPhrase[iPhrase];
+ if( iToken<0 || iToken>=pPhrase->nTerm ){
+ return SQLITE_RANGE;
+ }
+ pIter = pPhrase->aTerm[iToken].pIter;
+
+ return sqlite3Fts5IterToken(pIter, iCol, iOff+iToken, ppOut, pnOut);
+}
+
typedef struct Fts5Structure Fts5Structure;
typedef struct Fts5StructureLevel Fts5StructureLevel;
typedef struct Fts5StructureSegment Fts5StructureSegment;
+typedef struct Fts5TokenMapEntry Fts5TokenMapEntry;
+typedef struct Fts5TokenMapToken Fts5TokenMapToken;
+typedef struct Fts5TokenMap Fts5TokenMap;
struct Fts5Data {
u8 *p; /* Pointer to buffer containing record */
int bRev; /* True to iterate in reverse order */
u8 bSkipEmpty; /* True to skip deleted entries */
+ Fts5TokenMap *pTokenMap;
i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */
Fts5CResult *aFirst; /* Current merge state (see above) */
Fts5SegIter aSeg[1]; /* Array of segment iterators */
};
+struct Fts5TokenMapEntry {
+ i64 iRowid;
+ u16 iCol;
+ int iOff;
+ int iTok; /* Offset into aToken[] + 1 */
+};
+
+struct Fts5TokenMapToken {
+ u8 *pTerm;
+ int nTerm;
+};
+
+struct Fts5TokenMap {
+ int nEntryAlloc;
+ int nEntry;
+ Fts5TokenMapEntry *aEntry;
+
+ int nTokenAlloc;
+ int nToken;
+ Fts5TokenMapToken *aToken;
+};
+
/*
** An instance of the following type is used to iterate through the contents
}while( p->rc==SQLITE_OK );
}
+/*
+** Free the Fts5TokenMap object passed as the only argument.
+*/
+static void fts5TokenMapFree(Fts5TokenMap *pMap){
+ if( pMap ){
+ int ii;
+ for(ii=0; ii<pMap->nToken; ii++){
+ sqlite3_free(pMap->aToken[ii].pTerm);
+ }
+ sqlite3_free(pMap->aToken);
+ sqlite3_free(pMap->aEntry);
+ sqlite3_free(pMap);
+ }
+}
/*
** Free the iterator object passed as the second argument.
fts5SegIterClear(&pIter->aSeg[i]);
}
fts5BufferFree(&pIter->poslist);
+ fts5TokenMapFree(pIter->pTokenMap);
sqlite3_free(pIter);
}
}
static void fts5MultiIterNew2(
Fts5Index *p, /* FTS5 backend to iterate within */
Fts5Data *pData, /* Doclist to iterate through */
+ Fts5TokenMap *pMap, /* Token-map, if any */
int bDesc, /* True for descending rowid order */
Fts5Iter **ppOut /* New object */
){
pNew = fts5MultiIterAlloc(p, 2);
if( pNew ){
Fts5SegIter *pIter = &pNew->aSeg[1];
-
+ pNew->pTokenMap = pMap;
+ pMap = 0;
pIter->flags = FTS5_SEGITER_ONETERM;
if( pData->szLeaf>0 ){
pIter->pLeaf = pData;
*ppOut = pNew;
}
+ fts5TokenMapFree(pMap);
fts5DataRelease(pData);
}
*p1 = out;
}
+static u8 *fts5IdxBufferDup(Fts5Index *p, const u8 *pDup, int nDup){
+ u8 *pRet = fts5IdxMalloc(p, nDup+1);
+ if( pRet ){
+ memcpy(pRet, pDup, nDup);
+ }
+ return pRet;
+}
+
+static void fts5TokenMapTerm(
+ Fts5Index *p,
+ Fts5TokenMap *pMap,
+ const u8 *pTerm,
+ int nTerm
+){
+ if( p->rc==SQLITE_OK ){
+ Fts5TokenMapToken *pToken = 0;
+ if( pMap->nToken==pMap->nTokenAlloc ){
+ i64 nNew = (pMap->nTokenAlloc ? pMap->nTokenAlloc * 2 : 32);
+ Fts5TokenMapToken *aNew = sqlite3_realloc64(
+ pMap->aToken, nNew*sizeof(Fts5TokenMapToken)
+ );
+ if( aNew==0 ){
+ p->rc = SQLITE_NOMEM;
+ return;
+ }
+ pMap->nTokenAlloc = nNew;
+ pMap->aToken = aNew;
+ }
+ pToken = &pMap->aToken[pMap->nToken++];
+ pToken->nTerm = nTerm;
+ pToken->pTerm = fts5IdxBufferDup(p, pTerm, nTerm);
+ }
+}
+
+
+static void fts5TokenMapPoslist(
+ Fts5Index *p,
+ Fts5TokenMap *pMap,
+ Fts5Iter *p1
+){
+ if( p->rc==SQLITE_OK ){
+ const u8 *a = p1->base.pData;
+ i64 iPos = 0;
+ int iOff = 0;
+
+ while( 0==sqlite3Fts5PoslistNext64(a, p1->base.nData, &iOff, &iPos) ){
+ Fts5TokenMapEntry *pEntry = 0;
+ int iCol = FTS5_POS2COLUMN(iPos);
+ int iTokOff = FTS5_POS2OFFSET(iPos);
+
+ if( pMap->nEntry==pMap->nEntryAlloc ){
+ i64 nNew = (pMap->nEntryAlloc ? pMap->nEntryAlloc * 2 : 32);
+ Fts5TokenMapEntry *aNew = sqlite3_realloc64(
+ pMap->aEntry, nNew*sizeof(Fts5TokenMapEntry)
+ );
+ if( aNew==0 ){
+ p->rc = SQLITE_NOMEM;
+ return;
+ }
+ pMap->nEntryAlloc = nNew;
+ pMap->aEntry = aNew;
+ }
+ pEntry = &pMap->aEntry[pMap->nEntry++];
+ pEntry->iRowid = p1->base.iRowid;
+ pEntry->iCol = iCol;
+ pEntry->iOff = iTokOff;
+ pEntry->iTok = pMap->nToken;
+ }
+ }
+}
+
+static int fts5TokenMapHash(i64 iRowid, int iCol, int iOff){
+ return iRowid + (iRowid << 3) + (iCol << 6) + (iOff << 9);
+}
+
+static void fts5TokenMapHashify(Fts5Index *p, Fts5TokenMap *pMap){
+ int nHash = pMap->nEntry*2;
+ Fts5TokenMapEntry *aHash = 0;
+
+ aHash = (Fts5TokenMapEntry*)fts5IdxMalloc(p, nHash*sizeof(Fts5TokenMapEntry));
+ if( aHash ){
+ int ii;
+ for(ii=0; ii<pMap->nEntry; ii++){
+ Fts5TokenMapEntry *pEntry = &pMap->aEntry[ii];
+ Fts5TokenMapEntry *pCopy = 0;
+ int iHash = fts5TokenMapHash(pEntry->iRowid, pEntry->iCol, pEntry->iOff);
+
+ while( aHash[iHash % nHash].iTok ){
+ iHash++;
+ }
+ pCopy = &aHash[iHash % nHash];
+ memcpy(pCopy, pEntry, sizeof(Fts5TokenMapEntry));
+ }
+
+ sqlite3_free(pMap->aEntry);
+ pMap->aEntry = aHash;
+ pMap->nEntry = pMap->nEntryAlloc = nHash;
+ }
+}
+
+static const u8 *fts5TokenMapLookup(
+ Fts5TokenMap *pMap,
+ i64 iRowid,
+ int iCol,
+ int iOff,
+ int *pnOut
+){
+ int iHash = fts5TokenMapHash(iRowid, iCol, iOff) % pMap->nEntry;
+
+ for(; pMap->aEntry[iHash].iTok!=0; iHash = (iHash+1)%pMap->nEntry){
+ Fts5TokenMapEntry *pEntry = &pMap->aEntry[iHash];
+ if( pEntry->iRowid==iRowid && pEntry->iCol==iCol && pEntry->iOff==iOff ){
+ *pnOut = pMap->aToken[pEntry->iTok-1].nTerm;
+ return pMap->aToken[pEntry->iTok-1].pTerm;
+ }
+ }
+
+ *pnOut = 0;
+ return 0;
+}
+
+
static void fts5SetupPrefixIter(
Fts5Index *p, /* Index to read from */
int bDesc, /* True for "ORDER BY rowid DESC" */
Fts5Buffer *aBuf;
int nBuf = 32;
int nMerge = 1;
+ Fts5TokenMap *pMap = 0;
void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*);
aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf);
pStruct = fts5StructureRead(p);
+ if( iIdx==0 ){
+ pMap = (Fts5TokenMap*)fts5IdxMalloc(p, sizeof(Fts5TokenMap));
+ }
+ assert( p->rc!=SQLITE_OK || (aBuf && pStruct) );
- if( aBuf && pStruct ){
+ if( p->rc==SQLITE_OK ){
const int flags = FTS5INDEX_QUERY_SCAN
| FTS5INDEX_QUERY_SKIPEMPTY
| FTS5INDEX_QUERY_NOOUTPUT;
** index contains all the doclists required, except for the one
** corresponding to the prefix itself. That one is extracted from the
** main term index here. */
+ assert( iIdx==0 || pMap==0 );
if( iIdx!=0 ){
int dummy = 0;
const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT;
const u8 *pTerm = pSeg->term.p;
p1->xSetOutputs(p1, pSeg);
+ if( pMap ){
+ if( bNewTerm ){
+ fts5TokenMapTerm(p, pMap, &pTerm[1], nTerm-1);
+ }
+ fts5TokenMapPoslist(p, pMap, p1);
+ }
+
assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 );
if( bNewTerm ){
if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break;
pData->p = (u8*)&pData[1];
pData->nn = pData->szLeaf = doclist.n;
if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n);
- fts5MultiIterNew2(p, pData, bDesc, ppIter);
+ if( pMap ) fts5TokenMapHashify(p, pMap);
+ fts5MultiIterNew2(p, pData, pMap, bDesc, ppIter);
}
fts5BufferFree(&doclist);
}
return (z ? &z[1] : 0);
}
+/*
+**
+*/
+int sqlite3Fts5IterToken(
+ Fts5IndexIter *pIndexIter,
+ int iCol,
+ int iOff,
+ const char **ppOut, int *pnOut
+){
+ Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
+ if( pIter->pTokenMap ){
+ *ppOut = fts5TokenMapLookup(
+ pIter->pTokenMap, pIndexIter->iRowid, iCol, iOff, pnOut
+ );
+ }else{
+ *ppOut = sqlite3Fts5IterTerm(pIndexIter, pnOut);
+ }
+ return SQLITE_OK;
+}
+
/*
** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery().
*/
){
if( iIdx<0 || iIdx>=pCsr->nInstCount ){
rc = SQLITE_RANGE;
-#if 0
- }else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){
- *piPhrase = pCsr->aInst[iIdx*3];
- *piCol = pCsr->aInst[iIdx*3 + 2];
- *piOff = -1;
-#endif
}else{
*piPhrase = pCsr->aInst[iIdx*3];
*piCol = pCsr->aInst[iIdx*3 + 1];
return rc;
}
+/*
+** xQueryToken() API implemenetation.
+*/
static int fts5ApiQueryToken(
Fts5Context* pCtx,
int iPhrase,
return sqlite3Fts5ExprQueryToken(pCsr->pExpr, iPhrase, iToken, ppOut, pnOut);
}
+/*
+** xInstToken() API implemenetation.
+*/
+static int fts5ApiInstToken(
+ Fts5Context *pCtx,
+ int iIdx,
+ int iToken,
+ const char **ppOut, int *pnOut
+){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ int rc = SQLITE_OK;
+ if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0
+ || SQLITE_OK==(rc = fts5CacheInstArray(pCsr))
+ ){
+ if( iIdx<0 || iIdx>=pCsr->nInstCount ){
+ rc = SQLITE_RANGE;
+ }else{
+ int iPhrase = pCsr->aInst[iIdx*3];
+ int iCol = pCsr->aInst[iIdx*3 + 1];
+ int iOff = pCsr->aInst[iIdx*3 + 2];
+
+ rc = sqlite3Fts5ExprInstToken(
+ pCsr->pExpr, iPhrase, iCol, iOff, iToken, ppOut, pnOut
+ );
+ }
+ }
+ return rc;
+}
+
+/*
+** xPhraseToken() API implemenetation.
+*/
+static int fts5ApiPhraseToken(
+ Fts5Context *pCtx,
+ Fts5PhraseIter *pIter,
+ int iToken,
+ const char **ppOut,
+ int *pnOut
+){
+ return SQLITE_OK;
+}
+
static int fts5ApiQueryPhrase(Fts5Context*, int, void*,
int(*)(const Fts5ExtensionApi*, Fts5Context*, void*)
fts5ApiPhraseFirstColumn,
fts5ApiPhraseNextColumn,
fts5ApiQueryToken,
- 0,
- 0
+ fts5ApiInstToken,
+ fts5ApiPhraseToken
};
/*
{ "xPhraseForeach", 4, "IPHRASE COLVAR OFFVAR SCRIPT" }, /* 16 */
{ "xPhraseColumnForeach", 3, "IPHRASE COLVAR SCRIPT" }, /* 17 */
- { "xQueryToken", 2, "IPHRASE ITERM" }, /* 19 */
+ { "xQueryToken", 2, "IPHRASE ITERM" }, /* 18 */
+ { "xInstToken", 2, "IDX ITERM" }, /* 19 */
{ 0, 0, 0}
};
break;
}
+ CASE(19, "xInstToken") {
+ const char *pTerm = 0;
+ int nTerm = 0;
+ int iIdx = 0;
+ int iTerm = 0;
+
+ if( Tcl_GetIntFromObj(interp, objv[2], &iIdx) ) return TCL_ERROR;
+ if( Tcl_GetIntFromObj(interp, objv[3], &iTerm) ) return TCL_ERROR;
+ rc = p->pApi->xInstToken(p->pFts, iIdx, iTerm, &pTerm, &nTerm);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(pTerm, nTerm));
+ }
+
+ break;
+ }
+
default:
assert( 0 );
break;
}
sqlite3_fts5_create_function db querytoken querytoken
-do_execsql_test 3.0 {
+do_execsql_test 4.0 {
CREATE VIRTUAL TABLE ft USING fts5(
x, tokenize='origintext unicode61', tokendata=1
);
INSERT INTO ft VALUES('one two three four');
}
-do_execsql_test 3.1 {
+do_execsql_test 4.1 {
SELECT rowid, querytoken(ft, 0, 0) FROM ft('TwO')
} {1 two.TwO}
-do_execsql_test 3.2 {
+do_execsql_test 4.2 {
SELECT rowid, querytoken(ft, 0, 0) FROM ft('one TWO ThreE')
} {1 one}
-do_execsql_test 3.3 {
+do_execsql_test 4.3 {
SELECT rowid, querytoken(ft, 1, 0) FROM ft('one TWO ThreE')
} {1 two.TWO}
-do_execsql_test 3.4 {
+do_execsql_test 4.4 {
SELECT rowid, querytoken(ft, 0, 2) FROM ft('"one TWO ThreE"')
} {1 three.ThreE}
-do_catchsql_test 3.5 {
+do_catchsql_test 4.5 {
SELECT rowid, querytoken(ft, 0, 3) FROM ft('"one TWO ThreE"')
} {1 SQLITE_RANGE}
-do_catchsql_test 3.6 {
+do_catchsql_test 4.6 {
SELECT rowid, querytoken(ft, 1, 0) FROM ft('"one TWO ThreE"')
} {1 SQLITE_RANGE}
-do_catchsql_test 3.7 {
+do_catchsql_test 4.7 {
SELECT rowid, querytoken(ft, -1, 0) FROM ft('"one TWO ThreE"')
} {1 SQLITE_RANGE}
+#-------------------------------------------------------------------------
+#
+reset_db
+sqlite3_fts5_register_origintext db
+proc insttoken {cmd iIdx iToken} {
+ set txt [$cmd xInstToken $iIdx $iToken]
+ string map [list "\0" "."] $txt
+}
+sqlite3_fts5_create_function db insttoken insttoken
+fts5_aux_test_functions db
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(
+ x, tokenize='origintext unicode61', tokendata=1
+ );
+ INSERT INTO ft VALUES('one ONE One oNe oNE one');
+}
+do_execsql_test 5.1 {
+ SELECT insttoken(ft, 0, 0),
+ insttoken(ft, 1, 0),
+ insttoken(ft, 2, 0),
+ insttoken(ft, 3, 0),
+ insttoken(ft, 4, 0),
+ insttoken(ft, 5, 0)
+ FROM ft('one');
+} {
+ one one.ONE one.One one.oNe one.oNE one
+}
+
+do_execsql_test 5.2 {
+ SELECT insttoken(ft, 1, 0) FROM ft('one');
+} {
+ one.ONE
+}
+
+do_execsql_test 5.3 {
+ SELECT fts5_test_poslist(ft) FROM ft('one');
+} {
+ {0.0.0 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5}
+}
finish_test
-C Add\snew\sfts5\sAPI\sxQueryToken().
-D 2023-11-13T14:29:12.382
+C Add\simplementation\sof\sxInstToken()\sAPI.
+D 2023-11-15T11:45:19.681
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb
F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0
F ext/fts5/fts5.h e27cdb10e38d87cb041dcb56cef97addf7d902aeab07e84e7102f5fc65d3357c
-F ext/fts5/fts5Int.h 19b198459a2791415919428d44ebf4c830b59b2da6f27f8faaffe39a876b7ecf
+F ext/fts5/fts5Int.h 88ab1ee1eefa6f98e4c7fd3c96c99ef76ed2819cc3058736c87bb01e4a301628
F ext/fts5/fts5_aux.c ee770eec0af8646db9e18fc01a0dad7345b5f5e8cbba236704cfae2d777022ad
F ext/fts5/fts5_buffer.c 3001fbabb585d6de52947b44b455235072b741038391f830d6b729225eeaf6a5
F ext/fts5/fts5_config.c 8072a207034b51ae9b7694121d1b5715c794e94b275e088f70ae532378ca5cdf
-F ext/fts5/fts5_expr.c 69c81af515ce1cedccf093c7c76f8b3b4f24bafbfb1d03a431af9f5c69a81834
+F ext/fts5/fts5_expr.c 4b50ed0c724cb160f086e20e964ed2d57b99d0d3c1cb1b029901c0300b11bd9f
F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1
-F ext/fts5/fts5_index.c 145723e22ffee28dbe2a24933e74ad998d32419223c0ddb8506a1f0c39b952c4
-F ext/fts5/fts5_main.c ddac85dbd28167af81f64e568bfe9020bf9708d650de207d1465ed19938316d1
+F ext/fts5/fts5_index.c 3b51c2f0554a665694e777c8f2765cb5b1283d4bc960dde350a604af3e5e5d98
+F ext/fts5/fts5_main.c f151eb2c6d27418d907c88cd623ad4508bdcf518a79d504e850270754c228b74
F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca56d6601426d
-F ext/fts5/fts5_tcl.c 71641a0c5693c64acfad9d10e64475ec92d9f464d06ba7fd350552de373586d8
+F ext/fts5/fts5_tcl.c cf0fd0dbe64ec272491b749e0d594f563cda03336aeb60900129e6d18b0aefb8
F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b
F ext/fts5/fts5_tokenize.c 83cfcede3898001cab84432a36ce1503e3080cf9b1c682b022ec82e267ea4c13
F ext/fts5/test/fts5optimize.test 36a752d24c818792032e4ff502936fc9cc5ef938721696396fdc79214b2717f1
F ext/fts5/test/fts5optimize2.test 93e742c36b487d8874621360af5b1ce4d39b04fb9e71ce9bc34015c5fc811785
F ext/fts5/test/fts5optimize3.test bf9c91bb927d0fb2b9a06318a217a0419183ac5913842e062c7e0b98ea5d0fca
-F ext/fts5/test/fts5origintext.test 8296984d268d1d20f85c9de316f422ffb6ebc12020d3f8a0a18144d6ca7b347f
+F ext/fts5/test/fts5origintext.test 908a1fb6b1106e4b6ed0f9cf683c2ad7f986cce1aea1e0a13b3309c6f568932b
F ext/fts5/test/fts5phrase.test 13e5d8e9083077b3d9c74315b3c92ec723cc6eb37c8155e0bfe1bba00559f07b
F ext/fts5/test/fts5plan.test b65cfcca9ddd6fdaa118c61e17aeec8e8433bc5b6bb307abd116514f79c49c5a
F ext/fts5/test/fts5porter.test 8d08010c28527db66bc3feebd2b8767504aaeb9b101a986342fa7833d49d0d15
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P b8a48cc18c94d15017f898c820fdd784efbaac20d7a45c4d97269333e8f2ec60
-R 177f59b3c48b3865f7f595e0ec855966
+P 828566392b3ea8db603cb1ae5eccbc8ac035efaa284bc7c15ba89874f634aec9
+R 7870d9470a55737470bd92d95fe480a9
U dan
-Z ac818dc8fd8dc9007ff77eae486bea2e
+Z d10d6cf5b22c051f4553454e4a3996a4
# Remove this line to create a well-formed Fossil manifest.
-828566392b3ea8db603cb1ae5eccbc8ac035efaa284bc7c15ba89874f634aec9
\ No newline at end of file
+a34b26fe7f60b74e7ae5cf64900920a3d352a20da2496401bcbc27041689cd07
\ No newline at end of file