#include "fts5Int.h"
+typedef struct SnippetPhrase SnippetPhrase;
+typedef struct SnippetIter SnippetIter;
+typedef struct SnippetCtx SnippetCtx;
+
+struct SnippetPhrase {
+ u64 mask; /* Current mask */
+ int nToken; /* Tokens in this phrase */
+ int i; /* Current offset in phrase poslist */
+ i64 iPos; /* Next position in phrase (-ve -> EOF) */
+};
+
+struct SnippetIter {
+ i64 iLast; /* Last token position of current snippet */
+ int nScore; /* Score of current snippet */
+
+ const Fts5ExtensionApi *pApi;
+ Fts5Context *pFts;
+ u64 szmask; /* Mask used to on SnippetPhrase.mask */
+ int nPhrase; /* Number of phrases */
+ SnippetPhrase aPhrase[0]; /* Array of size nPhrase */
+};
+
+struct SnippetCtx {
+ int iFirst; /* Offset of first token to record */
+ int nToken; /* Size of aiStart[] and aiEnd[] arrays */
+ int iSeen; /* Set to largest offset seen */
+ int *aiStart;
+ int *aiEnd;
+};
+
+static int fts5SnippetCallback(
+ void *pContext, /* Pointer to Fts5Buffer object */
+ const char *pToken, /* Buffer containing token */
+ int nToken, /* Size of token in bytes */
+ int iStart, /* Start offset of token */
+ int iEnd, /* End offset of token */
+ int iPos /* Position offset of token */
+){
+ int rc = SQLITE_OK;
+ SnippetCtx *pCtx = (SnippetCtx*)pContext;
+ int iOff = iPos - pCtx->iFirst;
+
+ if( iOff>=0 ){
+ if( iOff < pCtx->nToken ){
+ pCtx->aiStart[iOff] = iStart;
+ pCtx->aiEnd[iOff] = iEnd;
+ }
+ pCtx->iSeen = iPos;
+ if( iOff>=pCtx->nToken ) rc = SQLITE_DONE;
+ }
+
+ return rc;
+}
+
+/*
+** Set pIter->nScore to the score for the current entry.
+*/
+static void fts5SnippetCalculateScore(SnippetIter *pIter){
+ int i;
+ int nScore = 0;
+ assert( pIter->iLast>=0 );
+
+ for(i=0; i<pIter->nPhrase; i++){
+ SnippetPhrase *p = &pIter->aPhrase[i];
+ u64 mask = p->mask;
+ if( mask ){
+ u64 j;
+ nScore += 1000;
+ for(j=1; j & pIter->szmask; j<<=1){
+ if( mask & j ) nScore++;
+ }
+ }
+ }
+
+ pIter->nScore = nScore;
+}
+
+/*
+** Allocate a new snippet iter.
+*/
+static int fts5SnippetIterNew(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ int nToken, /* Number of tokens in snippets */
+ SnippetIter **ppIter /* OUT: New object */
+){
+ int i; /* Counter variable */
+ SnippetIter *pIter; /* New iterator object */
+ int nByte; /* Bytes of space to allocate */
+ int nPhrase; /* Number of phrases in query */
+
+ *ppIter = 0;
+ nPhrase = pApi->xPhraseCount(pFts);
+ nByte = sizeof(SnippetIter) + nPhrase * sizeof(SnippetPhrase);
+ pIter = (SnippetIter*)sqlite3_malloc(nByte);
+ if( pIter==0 ) return SQLITE_NOMEM;
+ memset(pIter, 0, nByte);
+
+ pIter->nPhrase = nPhrase;
+ pIter->pApi = pApi;
+ pIter->pFts = pFts;
+ pIter->szmask = ((u64)1 << nToken) - 1;
+ assert( nToken<=63 );
+
+ for(i=0; i<nPhrase; i++){
+ pIter->aPhrase[i].nToken = pApi->xPhraseSize(pFts, i);
+ }
+
+ *ppIter = pIter;
+ return SQLITE_OK;
+}
+
+/*
+** Set the iterator to point to the first candidate snippet.
+*/
+static void fts5SnippetIterFirst(SnippetIter *pIter){
+ const Fts5ExtensionApi *pApi = pIter->pApi;
+ Fts5Context *pFts = pIter->pFts;
+ int i; /* Used to iterate through phrases */
+ SnippetPhrase *pMin = 0; /* Phrase with first match */
+
+ memset(pIter->aPhrase, 0, sizeof(SnippetPhrase) * pIter->nPhrase);
+
+ for(i=0; i<pIter->nPhrase; i++){
+ SnippetPhrase *p = &pIter->aPhrase[i];
+ p->nToken = pApi->xPhraseSize(pFts, i);
+ pApi->xPoslist(pFts, i, &p->i, &p->iPos);
+ if( p->iPos>=0 && (pMin==0 || p->iPos<pMin->iPos) ){
+ pMin = p;
+ }
+ }
+ assert( pMin );
+
+ pIter->iLast = pMin->iPos + pMin->nToken - 1;
+ pMin->mask = 0x01;
+ pApi->xPoslist(pFts, pMin - pIter->aPhrase, &pMin->i, &pMin->iPos);
+ fts5SnippetCalculateScore(pIter);
+}
+
+/*
+** Advance the snippet iterator to the next candidate snippet.
+*/
+static void fts5SnippetIterNext(SnippetIter *pIter){
+ const Fts5ExtensionApi *pApi = pIter->pApi;
+ Fts5Context *pFts = pIter->pFts;
+ int nPhrase = pIter->nPhrase;
+ int i; /* Used to iterate through phrases */
+ SnippetPhrase *pMin = 0;
+
+ for(i=0; i<nPhrase; i++){
+ SnippetPhrase *p = &pIter->aPhrase[i];
+ if( p->iPos>=0 && (pMin==0 || p->iPos<pMin->iPos) ) pMin = p;
+ }
+
+ if( pMin==0 ){
+ /* pMin==0 indicates that the SnippetIter is at EOF. */
+ pIter->iLast = -1;
+ }else{
+ i64 nShift = pMin->iPos - pIter->iLast;
+ assert( nShift>=0 );
+ for(i=0; i<nPhrase; i++){
+ SnippetPhrase *p = &pIter->aPhrase[i];
+ if( nShift>=63 ){
+ p->mask = 0;
+ }else{
+ p->mask = p->mask << (int)nShift;
+ p->mask &= pIter->szmask;
+ }
+ }
+
+ pIter->iLast = pMin->iPos;
+ pMin->mask |= 0x01;
+ fts5SnippetCalculateScore(pIter);
+ pApi->xPoslist(pFts, pMin - pIter->aPhrase, &pMin->i, &pMin->iPos);
+ }
+}
+
+static void fts5SnippetIterFree(SnippetIter *pIter){
+ if( pIter ){
+ sqlite3_free(pIter);
+ }
+}
+
+static int fts5SnippetText(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ SnippetIter *pIter, /* Snippet to write to buffer */
+ int nToken, /* Size of desired snippet in tokens */
+ const char *zStart,
+ const char *zFinal,
+ const char *zEllip,
+ Fts5Buffer *pBuf /* Write output to this buffer */
+){
+ SnippetCtx ctx;
+ int i;
+ u64 all = 0;
+ const char *zCol; /* Column text to extract snippet from */
+ int nCol; /* Size of column text in bytes */
+ int rc;
+ int nShift;
+
+ rc = pApi->xColumnText(pFts, FTS5_POS2COLUMN(pIter->iLast), &zCol, &nCol);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* At this point pIter->iLast is the offset of the last token in the
+ ** proposed snippet. However, in all cases pIter->iLast contains the
+ ** final token of one of the phrases. This makes the snippet look
+ ** unbalanced. For example:
+ **
+ ** "...x x x x x <b>term</b>..."
+ **
+ ** It is better to increase iLast a little so that the snippet looks
+ ** more like:
+ **
+ ** "...x x x <b>term</b> y y..."
+ **
+ ** The problem is that there is no easy way to discover whether or not
+ ** how many tokens are present in the column following "term".
+ */
+
+ /* Set variable nShift to the number of tokens by which the snippet
+ ** should be shifted, assuming there are sufficient tokens to the right
+ ** of iLast in the column value. */
+ for(i=0; i<pIter->nPhrase; i++){
+ int iToken;
+ for(iToken=0; iToken<pIter->aPhrase[i].nToken; iToken++){
+ all |= (pIter->aPhrase[i].mask << iToken);
+ }
+ }
+ for(i=nToken-1; i>=0; i--){
+ if( all & ((u64)1 << i) ) break;
+ }
+ assert( i>=0 );
+ nShift = (nToken - i) / 2;
+
+ memset(&ctx, 0, sizeof(SnippetCtx));
+ ctx.nToken = nToken + nShift;
+ ctx.iFirst = FTS5_POS2OFFSET(pIter->iLast) - nToken + 1;
+ if( ctx.iFirst<0 ){
+ nShift += ctx.iFirst;
+ if( nShift<0 ) nShift = 0;
+ ctx.iFirst = 0;
+ }
+ ctx.aiStart = (int*)sqlite3_malloc(sizeof(int) * ctx.nToken * 2);
+ if( ctx.aiStart==0 ) return SQLITE_NOMEM;
+ ctx.aiEnd = &ctx.aiStart[ctx.nToken];
+
+ rc = pApi->xTokenize(pFts, zCol, nCol, (void*)&ctx, fts5SnippetCallback);
+ if( rc==SQLITE_OK ){
+ int i1; /* First token from input to include */
+ int i2; /* Last token from input to include */
+
+ int iPrint;
+ int iMatchto;
+ int iBit0;
+ int iLast;
+
+ int *aiStart = ctx.aiStart - ctx.iFirst;
+ int *aiEnd = ctx.aiEnd - ctx.iFirst;
+
+ /* Ideally we want to start the snippet with token (ctx.iFirst + nShift).
+ ** However, this is only possible if there are sufficient tokens within
+ ** the column. This block sets variables i1 and i2 to the first and last
+ ** input tokens to include in the snippet. */
+ if( (ctx.iFirst + nShift + nToken)<=ctx.iSeen ){
+ i1 = ctx.iFirst + nShift;
+ i2 = i1 + nToken - 1;
+ }else{
+ i2 = ctx.iSeen;
+ i1 = ctx.iSeen - nToken + 1;
+ assert( i1>=0 || ctx.iFirst==0 );
+ if( i1<0 ) i1 = 0;
+ }
+
+ /* If required, append the preceding ellipsis. */
+ if( i1>0 ) sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%s", zEllip);
+
+ iLast = FTS5_POS2OFFSET(pIter->iLast);
+ iPrint = i1;
+ iMatchto = -1;
+
+ for(i=i1; i<=i2; i++){
+
+ /* Check if this is the first token of any phrase match. */
+ int ip;
+ for(ip=0; ip<pIter->nPhrase; ip++){
+ SnippetPhrase *pPhrase = &pIter->aPhrase[ip];
+ u64 m = (1 << (iLast - i - pPhrase->nToken + 1));
+
+ if( i<=iLast && (pPhrase->mask & m) ){
+ if( iMatchto<0 ){
+ sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s%s",
+ aiStart[i] - aiStart[iPrint],
+ &zCol[aiStart[iPrint]],
+ zStart
+ );
+ iPrint = i;
+ }
+ if( i>iMatchto ) iMatchto = i + pPhrase->nToken - 1;
+ }
+ }
+
+ if( i==iMatchto ){
+ sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s%s",
+ aiEnd[i] - aiStart[iPrint],
+ &zCol[aiStart[iPrint]],
+ zFinal
+ );
+ iMatchto = -1;
+ iPrint = i+1;
+
+ if( i<i2 ){
+ sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s",
+ aiStart[i+1] - aiEnd[i],
+ &zCol[aiEnd[i]]
+ );
+ }
+ }
+ }
+
+ if( iPrint<=i2 ){
+ sqlite3Fts5BufferAppendPrintf(&rc, pBuf, "%.*s",
+ aiEnd[i2] - aiStart[iPrint],
+ &zCol[aiStart[iPrint]]
+ );
+ if( iMatchto>=0 ){
+ sqlite3Fts5BufferAppendString(&rc, pBuf, zFinal);
+ }
+ }
+
+ /* If required, append the trailing ellipsis. */
+ if( i2<ctx.iSeen ) sqlite3Fts5BufferAppendString(&rc, pBuf, zEllip);
+ }
+
+ sqlite3_free(ctx.aiStart);
+ return rc;
+}
+
+/*
+** A default snippet() implementation. This is compatible with the FTS3
+** snippet() function.
+*/
static void fts5SnippetFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
sqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
sqlite3_value **apVal /* Array of trailing arguments */
+){
+ const char *zStart = "<b>";
+ const char *zFinal = "</b>";
+ const char *zEllip = "<b>...</b>";
+ int nToken = -15;
+ int nAbs;
+ int nFrag; /* Number of fragments to return */
+ int rc;
+ SnippetIter *pIter = 0;
+
+ if( nVal>=1 ) zStart = (const char*)sqlite3_value_text(apVal[0]);
+ if( nVal>=2 ) zFinal = (const char*)sqlite3_value_text(apVal[1]);
+ if( nVal>=3 ) zEllip = (const char*)sqlite3_value_text(apVal[2]);
+ if( nVal>=4 ){
+ nToken = sqlite3_value_int(apVal[3]);
+ if( nToken==0 ) nToken = -15;
+ }
+ nAbs = nToken * (nToken<0 ? -1 : 1);
+
+ rc = fts5SnippetIterNew(pApi, pFts, nAbs, &pIter);
+ if( rc==SQLITE_OK ){
+ Fts5Buffer buf; /* Result buffer */
+ int nBestScore = 0; /* Score of best snippet found */
+ int n; /* Size of column snippet is from in bytes */
+ int i; /* Used to iterate through phrases */
+
+ for(fts5SnippetIterFirst(pIter);
+ pIter->iLast>=0;
+ fts5SnippetIterNext(pIter)
+ ){
+ if( pIter->nScore>nBestScore ) nBestScore = pIter->nScore;
+ }
+ for(fts5SnippetIterFirst(pIter);
+ pIter->iLast>=0;
+ fts5SnippetIterNext(pIter)
+ ){
+ if( pIter->nScore==nBestScore ) break;
+ }
+
+ memset(&buf, 0, sizeof(Fts5Buffer));
+ rc = fts5SnippetText(pApi, pFts, pIter, nAbs, zStart, zFinal, zEllip, &buf);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_text(pCtx, (const char*)buf.p, buf.n, SQLITE_TRANSIENT);
+ }
+ sqlite3_free(buf.p);
+ }
+
+ fts5SnippetIterFree(pIter);
+ if( rc!=SQLITE_OK ){
+ sqlite3_result_error_code(pCtx, rc);
+ }
+}
+
+static void fts5Bm25Function(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ sqlite3_context *pCtx, /* Context for returning result/error */
+ int nVal, /* Number of values in apVal[] array */
+ sqlite3_value **apVal /* Array of trailing arguments */
){
assert( 0 );
}
for(i=0; i<nPhrase; i++){
Fts5Buffer s2; /* List of positions for phrase/column */
int j = 0;
- int iOff = 0;
- int iCol = 0;
+ i64 iPos = 0;
int nElem = 0;
memset(&s2, 0, sizeof(s2));
- while( 0==pApi->xPoslist(pFts, i, &j, &iCol, &iOff) ){
+ while( 0==pApi->xPoslist(pFts, i, &j, &iPos) ){
+ int iOff = FTS5_POS2OFFSET(iPos);
+ int iCol = FTS5_POS2COLUMN(iPos);
if( nElem!=0 ) sqlite3Fts5BufferAppendPrintf(&rc, &s2, " ");
sqlite3Fts5BufferAppendPrintf(&rc, &s2, "%d.%d", iCol, iOff);
nElem++;
return rc;
}
+static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){
+ Fts5Config *pConfig = p->pConfig;
+ char *zSql;
+ int rc;
+
+ zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'",
+ pConfig->zDb, pConfig->zName, zSuffix
+ );
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3_stmt *pCnt = 0;
+ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0);
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCnt) ){
+ *pnRow = sqlite3_column_int64(pCnt, 0);
+ }
+ rc = sqlite3_finalize(pCnt);
+ }
+
+ sqlite3_free(zSql);
+ return rc;
+}
+
/*
** Context object used by sqlite3Fts5StorageIntegrity().
*/
struct Fts5IntegrityCtx {
i64 iRowid;
int iCol;
+ int szCol;
u64 cksum;
Fts5Config *pConfig;
};
pCtx->cksum ^= sqlite3Fts5IndexCksum(
pCtx->pConfig, pCtx->iRowid, pCtx->iCol, iPos, pToken, nToken
);
+ pCtx->szCol = iPos+1;
return SQLITE_OK;
}
int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
Fts5Config *pConfig = p->pConfig;
int rc; /* Return code */
+ int *aColSize; /* Array of size pConfig->nCol */
+ i64 *aTotalSize; /* Array of size pConfig->nCol */
Fts5IntegrityCtx ctx;
sqlite3_stmt *pScan;
memset(&ctx, 0, sizeof(Fts5IntegrityCtx));
ctx.pConfig = p->pConfig;
+ aTotalSize = (i64*)sqlite3_malloc(pConfig->nCol * (sizeof(int)+sizeof(i64)));
+ if( !aTotalSize ) return SQLITE_NOMEM;
+ aColSize = (int*)&aTotalSize[pConfig->nCol];
+ memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol);
/* Generate the expected index checksum based on the contents of the
** %_content table. This block stores the checksum in ctx.cksum. */
while( SQLITE_ROW==sqlite3_step(pScan) ){
int i;
ctx.iRowid = sqlite3_column_int64(pScan, 0);
+ ctx.szCol = 0;
+ rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
ctx.iCol = i;
rc = sqlite3Fts5Tokenize(
(void*)&ctx,
fts5StorageIntegrityCallback
);
+ if( ctx.szCol!=aColSize[i] ) rc = SQLITE_CORRUPT_VTAB;
+ aTotalSize[i] += ctx.szCol;
}
+ if( rc!=SQLITE_OK ) break;
}
rc2 = sqlite3_reset(pScan);
if( rc==SQLITE_OK ) rc = rc2;
}
+ /* Test that the "totals" (sometimes called "averages") record looks Ok */
+ if( rc==SQLITE_OK ){
+ int i;
+ rc = fts5StorageLoadTotals(p);
+ for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
+ if( p->aTotalSize[i]!=aTotalSize[i] ) rc = SQLITE_CORRUPT_VTAB;
+ }
+ }
+
+ /* Check that the %_docsize and %_content tables contain the expected
+ ** number of rows. */
+ if( rc==SQLITE_OK ){
+ i64 nRow;
+ rc = fts5StorageCount(p, "content", &nRow);
+ if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = SQLITE_CORRUPT_VTAB;
+ }
+ if( rc==SQLITE_OK ){
+ i64 nRow;
+ rc = fts5StorageCount(p, "docsize", &nRow);
+ if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = SQLITE_CORRUPT_VTAB;
+ }
+
/* Pass the expected checksum down to the FTS index module. It will
** verify, amongst other things, that it matches the checksum generated by
** inspecting the index itself. */
rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
}
+ sqlite3_free(aTotalSize);
return rc;
}
-C Fix\sDELETE\sand\sUPDATE\soperations\son\sfts5\stables.
-D 2014-07-21T15:45:26.584
+C Add\sa\ssnippet()\sfunction\sto\sfts5.
+D 2014-07-23T19:31:56.454
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in b03432313a3aad96c706f8164fb9f5307eaf19f5
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
F ext/fts3/unicode/mkunicode.tcl dc6f268eb526710e2c6e496c372471d773d0c368
-F ext/fts5/fts5.c 35124fe8a49868808604c6a5264bf4f23587ac99
-F ext/fts5/fts5.h c77b6a4a56d80f70fc4f0444030c88724397ed10
+F ext/fts5/fts5.c 6f859d444eb8be46cb3f7aba3aaae369c5b26809
+F ext/fts5/fts5.h 57325b418b26dcd60be5bc8aab05b33767d81590
F ext/fts5/fts5Int.h 12d03496152b716e63a5380e396b776fbefa2065
-F ext/fts5/fts5_aux.c 14961135231dd50e6c17894e649c3bbc8c042829
-F ext/fts5/fts5_buffer.c 00361d4a70040ebd2c32bc349ab708ff613a1749
+F ext/fts5/fts5_aux.c cba929fb13931c9b8be7d572991e648b98f14cf2
+F ext/fts5/fts5_buffer.c 248c61ac9fec001602efc72a45704f3b8d367c00
F ext/fts5/fts5_config.c 94f1b4cb4de6a7cd5780c14adb0198e289df8cef
-F ext/fts5/fts5_expr.c 288b3e016253eab69ea8cefbff346a4697b44291
+F ext/fts5/fts5_expr.c 2911813db174afa28b69ccc7031b6dd80293b241
F ext/fts5/fts5_index.c 68d2d41b5c6d2f8838c3d6ebdc8b242718b8e997
-F ext/fts5/fts5_storage.c 7bb34138d134841cbe0a809467070d07013d8d7d
+F ext/fts5/fts5_storage.c 9a2744f492413395a0e75f20c19b797c801a7308
F ext/fts5/fts5parse.y 777da8e5819f75c217982c79c29d014c293acac9
F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
F test/fts5ac.test 9be418d037763f4cc5d86f4239db41fc86bb4f85
F test/fts5ad.test 2ed38bbc865678cb2905247120d02ebba7f20e07
F test/fts5ae.test fe9db78201bbb87c6f82b72a14b946d0f7fc3026
+F test/fts5af.test a2980528a04b67ac4690e6c02ebe9455f45c9a35
F test/fts5ea.test ff43b40f8879ba50b82def70f2ab67c195d1a1d4
F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d
F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef
F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d
F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025
F test/percentile.test b98fc868d71eb5619d42a1702e9ab91718cbed54
-F test/permutations.test c3eb62a88337d9a5046c509dd90ba6d43debc76d
+F test/permutations.test e6e951c816199693676d8c3d22d6bf54bcd719fc
F test/pragma.test adb21a90875bc54a880fa939c4d7c46598905aa0
F test/pragma2.test aea7b3d82c76034a2df2b38a13745172ddc0bc13
F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 8c6b0aff3443fae4b7f0b9adcbf1514992b70653
-R ceadf07a7e21667d721f7d8d3cf93a59
+P d44d3a8518ff7a1a3e2c0ab97493aa590676ee8c
+R 638d6826a594d773b5778bd6943c3d96
U dan
-Z 2d0ad058324af420450f942c4237ae4f
+Z 30db824dafb73f9c4c6895383aa25ed9
--- /dev/null
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+# More specifically, the tests in this file focus on the built-in
+# snippet() function.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix fts5af
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts3 {
+ finish_test
+ return
+}
+
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y);
+}
+
+
+foreach {tn doc res} {
+
+ 1.1 {X o o o o o o} {[X] o o o o o o}
+ 1.2 {o X o o o o o} {o [X] o o o o o}
+ 1.3 {o o X o o o o} {o o [X] o o o o}
+ 1.4 {o o o X o o o} {o o o [X] o o o}
+ 1.5 {o o o o X o o} {o o o o [X] o o}
+ 1.6 {o o o o o X o} {o o o o o [X] o}
+ 1.7 {o o o o o o X} {o o o o o o [X]}
+
+ 2.1 {X o o o o o o o} {[X] o o o o o o...}
+ 2.2 {o X o o o o o o} {o [X] o o o o o...}
+ 2.3 {o o X o o o o o} {o o [X] o o o o...}
+ 2.4 {o o o X o o o o} {o o o [X] o o o...}
+ 2.5 {o o o o X o o o} {...o o o [X] o o o}
+ 2.6 {o o o o o X o o} {...o o o o [X] o o}
+ 2.7 {o o o o o o X o} {...o o o o o [X] o}
+ 2.8 {o o o o o o o X} {...o o o o o o [X]}
+
+ 3.1 {X o o o o o o o o} {[X] o o o o o o...}
+ 3.2 {o X o o o o o o o} {o [X] o o o o o...}
+ 3.3 {o o X o o o o o o} {o o [X] o o o o...}
+ 3.4 {o o o X o o o o o} {o o o [X] o o o...}
+ 3.5 {o o o o X o o o o} {...o o o [X] o o o...}
+ 3.6 {o o o o o X o o o} {...o o o [X] o o o}
+ 3.7 {o o o o o o X o o} {...o o o o [X] o o}
+ 3.8 {o o o o o o o X o} {...o o o o o [X] o}
+ 3.9 {o o o o o o o o X} {...o o o o o o [X]}
+
+ 4.1 {X o o o o o X o o} {[X] o o o o o [X]...}
+ 4.2 {o X o o o o o X o} {...[X] o o o o o [X]...}
+ 4.3 {o o X o o o o o X} {...[X] o o o o o [X]}
+
+ 5.1 {X o o o o X o o o} {[X] o o o o [X] o...}
+ 5.2 {o X o o o o X o o} {...[X] o o o o [X] o...}
+ 5.3 {o o X o o o o X o} {...[X] o o o o [X] o}
+ 5.4 {o o o X o o o o X} {...o [X] o o o o [X]}
+
+ 6.1 {X o o o X o o o} {[X] o o o [X] o o...}
+ 6.2 {o X o o o X o o o} {o [X] o o o [X] o...}
+ 6.3 {o o X o o o X o o} {...o [X] o o o [X] o...}
+ 6.4 {o o o X o o o X o} {...o [X] o o o [X] o}
+ 6.5 {o o o o X o o o X} {...o o [X] o o o [X]}
+
+ 7.1 {X o o X o o o o o} {[X] o o [X] o o o...}
+ 7.2 {o X o o X o o o o} {o [X] o o [X] o o...}
+ 7.3 {o o X o o X o o o} {...o [X] o o [X] o o...}
+ 7.4 {o o o X o o X o o} {...o [X] o o [X] o o}
+ 7.5 {o o o o X o o X o} {...o o [X] o o [X] o}
+ 7.6 {o o o o o X o o X} {...o o o [X] o o [X]}
+} {
+ do_execsql_test 1.$tn.1 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES($doc, NULL);
+ SELECT snippet(t1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH 'X';
+ } [list $res]
+
+ do_execsql_test 1.$tn.2 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES(NULL, $doc);
+ SELECT snippet(t1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH 'X';
+ } [list $res]
+}
+
+foreach {tn doc res} {
+ 1.1 {X Y o o o o o} {[X Y] o o o o o}
+ 1.2 {o X Y o o o o} {o [X Y] o o o o}
+ 1.3 {o o X Y o o o} {o o [X Y] o o o}
+ 1.4 {o o o X Y o o} {o o o [X Y] o o}
+ 1.5 {o o o o X Y o} {o o o o [X Y] o}
+ 1.6 {o o o o o X Y} {o o o o o [X Y]}
+
+ 2.1 {X Y o o o o o o} {[X Y] o o o o o...}
+ 2.2 {o X Y o o o o o} {o [X Y] o o o o...}
+ 2.3 {o o X Y o o o o} {o o [X Y] o o o...}
+ 2.4 {o o o X Y o o o} {...o o [X Y] o o o}
+ 2.5 {o o o o X Y o o} {...o o o [X Y] o o}
+ 2.6 {o o o o o X Y o} {...o o o o [X Y] o}
+ 2.7 {o o o o o o X Y} {...o o o o o [X Y]}
+
+ 3.1 {X Y o o o o o o o} {[X Y] o o o o o...}
+ 3.2 {o X Y o o o o o o} {o [X Y] o o o o...}
+ 3.3 {o o X Y o o o o o} {o o [X Y] o o o...}
+ 3.4 {o o o X Y o o o o} {...o o [X Y] o o o...}
+ 3.5 {o o o o X Y o o o} {...o o [X Y] o o o}
+ 3.6 {o o o o o X Y o o} {...o o o [X Y] o o}
+ 3.7 {o o o o o o X Y o} {...o o o o [X Y] o}
+ 3.8 {o o o o o o o X Y} {...o o o o o [X Y]}
+
+} {
+ do_execsql_test 2.$tn.1 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES($doc, NULL);
+ SELECT snippet(t1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH 'X+Y';
+ } [list $res]
+
+ do_execsql_test 2.$tn.2 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES(NULL, $doc);
+ SELECT snippet(t1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH 'X+Y';
+ } [list $res]
+}
+
+finish_test
+