if( pExpr->eType==FTSQUERY_PHRASE ){
int iCol = pExpr->pPhrase->iColumn;
int i;
- pExpr->bDeferred = 1;
for(i=0; rc==SQLITE_OK && i<pExpr->pPhrase->nToken; i++){
Fts3PhraseToken *pToken = &pExpr->pPhrase->aToken[i];
if( pToken->pDeferred==0 ){
int iCol = pPhrase->iColumn;
int isTermPos = (pPhrase->nToken>1 || isReqPos);
Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
+ int isFirst = 1;
int iPrevTok = 0;
int nDoc = 0;
/* If this is an xFilter() evaluation, create a segment-reader for each
- ** phrase token. Or, if this is an xNest() or snippet/offsets/matchinfo
+ ** phrase token. Or, if this is an xNext() or snippet/offsets/matchinfo
** evaluation, only create segment-readers if there are no Fts3DeferredToken
** objects attached to the phrase-tokens.
*/
for(ii=0; ii<pPhrase->nToken; ii++){
Fts3PhraseToken *pTok = &pPhrase->aToken[ii];
- if( pTok->pArray==0 && (pCsr->doDeferred==0 || pTok->pDeferred==0) ){
- rc = fts3TermSegReaderArray(
- pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pArray
- );
- if( rc!=SQLITE_OK ) return rc;
+ if( pTok->pArray==0 ){
+ if( (pCsr->eEvalmode==FTS3_EVAL_FILTER)
+ || (pCsr->eEvalmode==FTS3_EVAL_NEXT && pCsr->pDeferred==0)
+ || (pCsr->eEvalmode==FTS3_EVAL_MATCHINFO && pTok->bFulltext)
+ ){
+ rc = fts3TermSegReaderArray(
+ pCsr, pTok->z, pTok->n, pTok->isPrefix, &pTok->pArray
+ );
+ if( rc!=SQLITE_OK ) return rc;
+ }
}
}
** are processed in order from least to most costly. Otherwise, tokens
** are processed in the order in which they occur in the phrase.
*/
- if( pCsr->doDeferred || isReqPos ){
+ if( pCsr->eEvalmode==FTS3_EVAL_MATCHINFO ){
+ assert( isReqPos );
+ iTok = ii;
+ pTok = &pPhrase->aToken[iTok];
+ if( pTok->bFulltext==0 ) continue;
+ }else if( pCsr->eEvalmode==FTS3_EVAL_NEXT || isReqPos ){
iTok = ii;
pTok = &pPhrase->aToken[iTok];
}else{
}
}
- if( pCsr->doDeferred && pTok->pDeferred ){
+ if( pCsr->eEvalmode==FTS3_EVAL_NEXT && pTok->pDeferred ){
rc = fts3DeferredTermSelect(pTok->pDeferred, isTermPos, &nList, &pList);
}else{
assert( pTok->pArray );
rc = fts3TermSelect(p, pTok, iCol, isTermPos, &nList, &pList);
+ pTok->bFulltext = 1;
}
- assert( rc!=SQLITE_OK || pCsr->doDeferred || pTok->pArray==0 );
+ assert( rc!=SQLITE_OK || pCsr->eEvalmode || pTok->pArray==0 );
if( rc!=SQLITE_OK ) break;
- if( ii==0 ){
+ if( isFirst ){
pOut = pList;
nOut = nList;
- if( pCsr->doDeferred==0 && pPhrase->nToken>1 ){
+ if( pCsr->eEvalmode==FTS3_EVAL_FILTER && pPhrase->nToken>1 ){
nDoc = fts3DoclistCountDocids(1, pOut, nOut);
}
+ isFirst = 0;
}else{
/* Merge the new term list and the current output. */
char *aLeft, *aRight;
}
if( rc==SQLITE_OK ){
- if( ii!=pPhrase->nToken ){
- assert( pCsr->doDeferred==0 && isReqPos==0 );
+ if( ii!=pPhrase->nToken && pCsr->eEvalmode==FTS3_EVAL_FILTER ){
+ assert( pCsr->eEvalmode==FTS3_EVAL_FILTER && isReqPos==0 );
fts3DoclistStripPositions(pOut, &nOut);
}
*paOut = pOut;
){
int rc = SQLITE_OK; /* Return code */
- if( pCsr->doDeferred ) return SQLITE_OK;
+ assert( pCsr->eEvalmode!=FTS3_EVAL_MATCHINFO );
+ if( pCsr->eEvalmode==FTS3_EVAL_NEXT ) return SQLITE_OK;
if( pnExpr && pExpr->eType!=FTSQUERY_AND ){
(*pnExpr)++;
pnExpr = 0;
paOut, pnOut
);
fts3ExprFreeSegReaders(pExpr);
- }else if( p->doDeferred==0 && pExpr->eType==FTSQUERY_AND ){
+ }else if( p->eEvalmode==FTS3_EVAL_FILTER && pExpr->eType==FTSQUERY_AND ){
ExprAndCost *aExpr = 0; /* Array of AND'd expressions and costs */
int nExpr = 0; /* Size of aExpr[] */
char *aRet = 0; /* Doclist to return to caller */
assert( pExpr->eType==FTSQUERY_NEAR
|| pExpr->eType==FTSQUERY_OR
|| pExpr->eType==FTSQUERY_NOT
- || (pExpr->eType==FTSQUERY_AND && p->doDeferred)
+ || (pExpr->eType==FTSQUERY_AND && p->eEvalmode==FTS3_EVAL_NEXT)
);
if( 0==(rc = fts3EvalExpr(p, pExpr->pRight, &aRight, &nRight, isReqPos))
if( rc==SQLITE_OK ){
char *a = 0;
int n = 0;
- pCsr->doDeferred = 1;
rc = fts3EvalExpr(pCsr, pCsr->pExpr, &a, &n, 0);
- pCsr->doDeferred = 0;
assert( n>=0 );
*pbRes = (n>0);
sqlite3_free(a);
int rc = SQLITE_OK; /* Return code */
Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
+ pCsr->eEvalmode = FTS3_EVAL_NEXT;
do {
if( pCsr->aDoclist==0 ){
if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){
*/
int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *pCsr, Fts3Expr *pExpr){
int rc;
- pCsr->doDeferred = 1;
+ assert( pExpr->eType==FTSQUERY_PHRASE && pExpr->pPhrase );
+ assert( pCsr->eEvalmode==FTS3_EVAL_NEXT );
rc = fts3EvalExpr(pCsr, pExpr, &pExpr->aDoclist, &pExpr->nDoclist, 1);
- pCsr->doDeferred = 0;
+ return rc;
+}
+
+int sqlite3Fts3ExprLoadFtDoclist(
+ Fts3Cursor *pCsr,
+ Fts3Expr *pExpr,
+ char **paDoclist,
+ int *pnDoclist
+){
+ int rc;
+ assert( pCsr->eEvalmode==FTS3_EVAL_NEXT );
+ assert( pExpr->eType==FTSQUERY_PHRASE && pExpr->pPhrase );
+ pCsr->eEvalmode = FTS3_EVAL_MATCHINFO;
+ rc = fts3EvalExpr(pCsr, pExpr, paDoclist, pnDoclist, 1);
+ pCsr->eEvalmode = FTS3_EVAL_NEXT;
return rc;
}
int nDoclist; /* Size of buffer at aDoclist */
int isMatchinfoNeeded; /* True when aMatchinfo[] needs filling in */
u32 *aMatchinfo; /* Information about most recent match */
- int doDeferred;
+ int eEvalmode; /* An FTS3_EVAL_XX constant */
int nRowAvg; /* Average size of database rows, in pages */
};
+#define FTS3_EVAL_FILTER 0
+#define FTS3_EVAL_NEXT 1
+#define FTS3_EVAL_MATCHINFO 2
+
/*
** The Fts3Cursor.eSearch member is always set to one of the following.
** Actualy, Fts3Cursor.eSearch can be greater than or equal to
** sequence. A single token is the base case and the most common case.
** For a sequence of tokens contained in double-quotes (i.e. "one two three")
** nToken will be the number of tokens in the string.
+**
+** The nDocMatch and nMatch variables contain data that may be used by the
+** matchinfo() function. They are populated when the full-text index is
+** queried for hits on the phrase. If one or more tokens in the phrase
+** are deferred, the nDocMatch and nMatch variables are populated based
+** on the assumption that the
*/
struct Fts3PhraseToken {
char *z; /* Text of the token */
int n; /* Number of bytes in buffer z */
int isPrefix; /* True if token ends with a "*" character */
+ int bFulltext; /* True if full-text index was used */
Fts3SegReaderArray *pArray; /* Segment-reader for this token */
Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
};
struct Fts3Phrase {
+ /* Variables populated by fts3_expr.c when parsing a MATCH expression */
int nToken; /* Number of tokens in the phrase */
int iColumn; /* Index of column this phrase must match */
int isNot; /* Phrase prefixed by unary not (-) operator */
Fts3Expr *pRight; /* Right operand */
Fts3Phrase *pPhrase; /* Valid if eType==FTSQUERY_PHRASE */
- int bDeferred;
-
int isLoaded; /* True if aDoclist/nDoclist are initialized. */
char *aDoclist; /* Buffer containing doclist */
int nDoclist; /* Size of aDoclist in bytes */
char *sqlite3Fts3FindPositions(Fts3Expr *, sqlite3_int64, int);
int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *);
+int sqlite3Fts3ExprLoadFtDoclist(Fts3Cursor *, Fts3Expr *, char **, int *);
int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
/* fts3_tokenizer.c */
return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f';
}
+/*
+** Allocate nByte bytes of memory using sqlite3_malloc(). If successful,
+** zero the memory before returning a pointer to it. If unsuccessful,
+** return NULL.
+*/
+static void *fts3MallocZero(int nByte){
+ void *pRet = sqlite3_malloc(nByte);
+ if( pRet ) memset(pRet, 0, nByte);
+ return pRet;
+}
+
+
/*
** Extract the next token from buffer z (length n) using the tokenizer
** and other information (column names etc.) in pParse. Create an Fts3Expr
if( rc==SQLITE_OK ){
nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken;
- pRet = (Fts3Expr *)sqlite3_malloc(nByte);
+ pRet = (Fts3Expr *)fts3MallocZero(nByte);
if( !pRet ){
rc = SQLITE_NOMEM;
}else{
- memset(pRet, 0, nByte);
pRet->eType = FTSQUERY_PHRASE;
pRet->pPhrase = (Fts3Phrase *)&pRet[1];
pRet->pPhrase->nToken = 1;
p->pPhrase = (Fts3Phrase *)&p[1];
}
p->pPhrase = (Fts3Phrase *)&p[1];
+ memset(&p->pPhrase->aToken[ii], 0, sizeof(Fts3PhraseToken));
p->pPhrase->nToken = ii+1;
p->pPhrase->aToken[ii].n = nToken;
- p->pPhrase->aToken[ii].pDeferred = 0;
- p->pPhrase->aToken[ii].pArray = 0;
memcpy(&zTemp[nTemp], zToken, nToken);
nTemp += nToken;
if( iEnd<nInput && zInput[iEnd]=='*' ){
if( fts3isspace(cNext)
|| cNext=='"' || cNext=='(' || cNext==')' || cNext==0
){
- pRet = (Fts3Expr *)sqlite3_malloc(sizeof(Fts3Expr));
+ pRet = (Fts3Expr *)fts3MallocZero(sizeof(Fts3Expr));
if( !pRet ){
return SQLITE_NOMEM;
}
- memset(pRet, 0, sizeof(Fts3Expr));
pRet->eType = pKey->eType;
pRet->nNear = nNear;
*ppExpr = pRet;
&& p->eType==FTSQUERY_PHRASE && p->pPhrase->isNot
){
/* Create an implicit NOT operator. */
- Fts3Expr *pNot = sqlite3_malloc(sizeof(Fts3Expr));
+ Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr));
if( !pNot ){
sqlite3Fts3ExprFree(p);
rc = SQLITE_NOMEM;
goto exprparse_out;
}
- memset(pNot, 0, sizeof(Fts3Expr));
pNot->eType = FTSQUERY_NOT;
pNot->pRight = p;
if( pNotBranch ){
/* Insert an implicit AND operator. */
Fts3Expr *pAnd;
assert( pRet && pPrev );
- pAnd = sqlite3_malloc(sizeof(Fts3Expr));
+ pAnd = fts3MallocZero(sizeof(Fts3Expr));
if( !pAnd ){
sqlite3Fts3ExprFree(p);
rc = SQLITE_NOMEM;
goto exprparse_out;
}
- memset(pAnd, 0, sizeof(Fts3Expr));
pAnd->eType = FTSQUERY_AND;
insertBinaryOperator(&pRet, pPrev, pAnd);
pPrev = pAnd;
}
if( pnPhrase ) *pnPhrase = sCtx.nPhrase;
if( pnToken ) *pnToken = sCtx.nToken;
- sqlite3Fts3SegmentsClose((Fts3Table *)pCsr->base.pVtab);
return rc;
}
void *pCtx /* Pointer to MatchInfo structure */
){
MatchInfo *p = (MatchInfo *)pCtx;
- char *pCsr;
+ Fts3Cursor *pCsr = p->pCursor;
+ char *pIter;
char *pEnd;
+ char *pFree = 0;
const int iStart = 2 + (iPhrase * p->nCol * 3) + 1;
assert( pExpr->isLoaded );
+ assert( pExpr->eType==FTSQUERY_PHRASE );
+
+ if( pCsr->pDeferred ){
+ Fts3Phrase *pPhrase = pExpr->pPhrase;
+ int ii;
+ for(ii=0; ii<pPhrase->nToken; ii++){
+ if( pPhrase->aToken[ii].bFulltext ) break;
+ }
+ if( ii<pPhrase->nToken ){
+ int nFree = 0;
+ int rc = sqlite3Fts3ExprLoadFtDoclist(pCsr, pExpr, &pFree, &nFree);
+ if( rc!=SQLITE_OK ) return rc;
+ pIter = pFree;
+ pEnd = &pFree[nFree];
+ }else{
+ int nDoc = p->aMatchinfo[2 + 3*p->nCol*p->aMatchinfo[0]];
+ for(ii=0; ii<p->nCol; ii++){
+ p->aMatchinfo[iStart + ii*3] = nDoc;
+ p->aMatchinfo[iStart + ii*3 + 1] = nDoc;
+ }
+ return SQLITE_OK;
+ }
+ }else{
+ pIter = pExpr->aDoclist;
+ pEnd = &pExpr->aDoclist[pExpr->nDoclist];
+ }
/* Fill in the global hit count matrix row for this phrase. */
- pCsr = pExpr->aDoclist;
- pEnd = &pExpr->aDoclist[pExpr->nDoclist];
- while( pCsr<pEnd ){
- while( *pCsr++ & 0x80 ); /* Skip past docid. */
- fts3LoadColumnlistCounts(&pCsr, &p->aMatchinfo[iStart], 1);
+ while( pIter<pEnd ){
+ while( *pIter++ & 0x80 ); /* Skip past docid. */
+ fts3LoadColumnlistCounts(&pIter, &p->aMatchinfo[iStart], 1);
}
+ sqlite3_free(pFree);
return SQLITE_OK;
}
}
memset(sInfo.aMatchinfo, 0, sizeof(u32)*nMatchinfo);
-
/* First element of match-info is the number of phrases in the query */
sInfo.aMatchinfo[0] = nPhrase;
sInfo.aMatchinfo[1] = sInfo.nCol;
- (void)fts3ExprIterate(pCsr->pExpr, fts3ExprGlobalMatchinfoCb,(void*)&sInfo);
if( pTab->bHasDocsize ){
int ofst = 2 + 3*sInfo.aMatchinfo[0]*sInfo.aMatchinfo[1];
rc = sqlite3Fts3MatchinfoDocsizeGlobal(pCsr, &sInfo.aMatchinfo[ofst]);
}
+ (void)fts3ExprIterate(pCsr->pExpr, fts3ExprGlobalMatchinfoCb,(void*)&sInfo);
pCsr->aMatchinfo = sInfo.aMatchinfo;
pCsr->isMatchinfoNeeded = 1;
}
}
snippet_out:
+ sqlite3Fts3SegmentsClose(pTab);
if( rc!=SQLITE_OK ){
sqlite3_result_error_code(pCtx, rc);
sqlite3_free(res.z);
offsets_out:
sqlite3_free(sCtx.aTerm);
assert( rc!=SQLITE_DONE );
+ sqlite3Fts3SegmentsClose(pTab);
if( rc!=SQLITE_OK ){
sqlite3_result_error_code(pCtx, rc);
sqlite3_free(res.z);
return;
}
rc = fts3GetMatchinfo(pCsr);
+ sqlite3Fts3SegmentsClose((Fts3Table *)pCsr->base.pVtab );
if( rc!=SQLITE_OK ){
sqlite3_result_error_code(pContext, rc);
}else{
if( pExpr ){
fts3DeferredDoclistClear(pExpr->pLeft);
fts3DeferredDoclistClear(pExpr->pRight);
- if( pExpr->bDeferred && pExpr->isLoaded ){
+ if( pExpr->isLoaded ){
sqlite3_free(pExpr->aDoclist);
pExpr->isLoaded = 0;
pExpr->aDoclist = 0;
sqlite3_free(pDef->pList);
pDef->pList = 0;
}
- fts3DeferredDoclistClear(pCsr->pExpr);
+ if( pCsr->pDeferred ){
+ fts3DeferredDoclistClear(pCsr->pExpr);
+ }
}
/*
-C Add\snew\stest\sfile\sfts3defer2.test.
-D 2010-10-22T19:03:34
+C Fixes\sfor\sthe\smatchinfo()\sfunction\srelated\sto\sFTS4\scommon\stoken\shandling.
+D 2010-10-23T19:07:30
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 2c8cefd962eca0147132c7cf9eaa4bb24c656f3f
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c e2f031ea6b213371a31cc5bf181c2177fef86aad
+F ext/fts3/fts3.c b7442369abecdda3cb58dc0ed482e7d1e852028f
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h 068d80157cc7a4bf674d2df817f3b427001ad94a
-F ext/fts3/fts3_expr.c a5aee50edde20e5c9116199bd58be869a3a22c9f
+F ext/fts3/fts3Int.h 11fa12ce041bacbc6ac53db127eb0bcc0538af51
+F ext/fts3/fts3_expr.c ee48b9278b8b2432a05a03320fbcacba151dbaa5
F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
F ext/fts3/fts3_icu.c ac494aed69835008185299315403044664bda295
F ext/fts3/fts3_porter.c 8df6f6efcc4e9e31f8bf73a4007c2e9abca1dfba
-F ext/fts3/fts3_snippet.c ca60a2a47de5e7abb22a804ccd1a743f81c2fe3e
+F ext/fts3/fts3_snippet.c 300c12b7f0a2a6ae0491bb2d00e2d5ff9c28f685
F ext/fts3/fts3_tokenizer.c b4f2d01c24573852755bc92864816785dae39318
F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
-F ext/fts3/fts3_write.c 54ddeed7323f62af6e55162f0d4102822b991684
+F ext/fts3/fts3_write.c 1b9211904f8157ebca8e17034e1150f3653e6c3f
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
F test/fts3cov.test 54cf1f98c72abee246447cd688590898c9ecbaf7
F test/fts3d.test 95fb3c862cbc4297c93fceb9a635543744e9ef52
F test/fts3defer.test cf66bf69afcc2fb8373d3aed31c55399409e83f2
-F test/fts3defer2.test 98259e5d7fa16b2d275d9b6a6c0c972e285fa18c
+F test/fts3defer2.test acd2fdd4db0a3b7f0ce6b1b3154c9521cb62d27d
F test/fts3e.test 1f6c6ac9cc8b772ca256e6b22aaeed50c9350851
F test/fts3expr.test 5e745b2b6348499d9ef8d59015de3182072c564c
F test/fts3expr2.test 18da930352e5693eaa163a3eacf96233b7290d1a
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 1c9c70fec3c88319f7b2efe5316694a6ce0ab1a5
-R d4195a622cd830429b9e32aeb4872a48
+P 5a4d5bfcaeb78a81713f138b01c0ea45a15c4d6c
+R eb3b2e3c665e63f01c554e4a5a259aeb
U dan
-Z 7c9af859e7e36df7363e90eaf6cd38eb
+Z f37bf8cb2dd9e3f74310b44a4142ec1c
-5a4d5bfcaeb78a81713f138b01c0ea45a15c4d6c
\ No newline at end of file
+deb80eac9112d21835dfd3cee08ed8f09d975bf7
\ No newline at end of file
} [list \
{a b c d [e] [f] [a] x y} \
{0 1 8 1 0 0 10 1 0 2 12 1} \
- [list 3 1 1 1 1 1 1 1 1 1 1 3 13336 9]
+ [list 3 1 1 1 1 1 3 3 1 3 3 3 13336 9]
]
do_execsql_test 1.2.3 {
} [list \
{[a] b c d [e] [f] [a] x y} \
{0 2 0 1 0 1 8 1 0 0 10 1 0 2 12 1} \
- [list 3 1 1 1 1 1 1 1 2 2 1 3 13336 9]
+ [list 3 1 1 1 1 1 3 3 2 3 3 3 13336 9]
]
do_execsql_test 1.3.1 { DROP TABLE t1 }
do_execsql_test 2.1.1 {
CREATE VIRTUAL TABLE t2 USING fts4;
}
-do_execsql_test 2.1.2 "INSERT INTO t2 VALUES('[string repeat {a } 20000]')"
-do_execsql_test 2.1.3 "INSERT INTO t2 VALUES('[string repeat {z } 20000]')"
-do_execsql_test 2.1.4 {
+do_execsql_test 2.1.2 "INSERT INTO t2 VALUES('[string repeat {a } 10000]')"
+do_execsql_test 2.1.3 "INSERT INTO t2 VALUES('b [string repeat {z } 10000]')"
+do_execsql_test 2.1.4 [string repeat "INSERT INTO t2 VALUES('x');" 50]
+do_execsql_test 2.1.5 {
INSERT INTO t2 VALUES('a b c d e f g');
INSERT INTO t2 VALUES('a b c d e f g');
}
WHERE length(block)>10000;
}
} {
-if {$tn==3} break
execsql $sql
do_execsql_test 2.2.$tn.1 {
SELECT mit(matchinfo(t2)) FROM t2 WHERE t2 MATCH 'a b';
} [list \
- [list 2 1 1 20002 3 1 2 2 4 10004 7] \
- [list 2 1 1 20002 3 1 2 2 4 10004 7] \
+ [list 2 1 1 54 54 1 3 3 54 372 7] \
+ [list 2 1 1 54 54 1 3 3 54 372 7] \
]
}