/ Check-in [b939a37a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Updates to snippet() and offsets() functions of FTS3 so that they work sanely following an OOM fault.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: b939a37a8ce296785a300e79ab9d3d87ad91343f
User & Date: drh 2009-11-28 21:33:21
Context
2009-11-30
08:55
Add test cases for examples recently added to documentation file fts3.html. check-in: 498922cc user: dan tags: trunk
2009-11-28
21:33
Updates to snippet() and offsets() functions of FTS3 so that they work sanely following an OOM fault. check-in: b939a37a user: drh tags: trunk
17:23
Change FTS3 to detect when the RHS of the MATCH opertor encounters an OOM during string format conversion and report back an SQLITE_NOMEM error. check-in: 31eed4f8 user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/fts3/fts3_snippet.c.

    42     42   };
    43     43   
    44     44   
    45     45   /* It is not safe to call isspace(), tolower(), or isalnum() on
    46     46   ** hi-bit-set characters.  This is the same solution used in the
    47     47   ** tokenizer.
    48     48   */
    49         -/* TODO(shess) The snippet-generation code should be using the
    50         -** tokenizer-generated tokens rather than doing its own local
    51         -** tokenization.
    52         -*/
    53         -/* TODO(shess) Is __isascii() a portable version of (c&0x80)==0? */
    54         -static int safe_isspace(char c){
           49  +static int fts3snippetIsspace(char c){
    55     50     return (c&0x80)==0 ? isspace(c) : 0;
    56     51   }
    57         -static int safe_isalnum(char c){
    58         -  return (c&0x80)==0 ? isalnum(c) : 0;
    59         -}
    60         -
    61         -/*******************************************************************/
    62         -/* DataBuffer is used to collect data into a buffer in piecemeal
    63         -** fashion.  It implements the usual distinction between amount of
    64         -** data currently stored (nData) and buffer capacity (nCapacity).
    65         -**
    66         -** dataBufferInit - create a buffer with given initial capacity.
    67         -** dataBufferReset - forget buffer's data, retaining capacity.
    68         -** dataBufferSwap - swap contents of two buffers.
    69         -** dataBufferExpand - expand capacity without adding data.
    70         -** dataBufferAppend - append data.
    71         -** dataBufferAppend2 - append two pieces of data at once.
    72         -** dataBufferReplace - replace buffer's data.
    73         -*/
    74         -typedef struct DataBuffer {
    75         -  char *pData;          /* Pointer to malloc'ed buffer. */
    76         -  int nCapacity;        /* Size of pData buffer. */
    77         -  int nData;            /* End of data loaded into pData. */
    78         -} DataBuffer;
    79         -
    80         -static void dataBufferInit(DataBuffer *pBuffer, int nCapacity){
    81         -  assert( nCapacity>=0 );
    82         -  pBuffer->nData = 0;
    83         -  pBuffer->nCapacity = nCapacity;
    84         -  pBuffer->pData = nCapacity==0 ? NULL : sqlite3_malloc(nCapacity);
    85         -}
    86         -static void dataBufferReset(DataBuffer *pBuffer){
    87         -  pBuffer->nData = 0;
    88         -}
    89         -static void dataBufferExpand(DataBuffer *pBuffer, int nAddCapacity){
    90         -  assert( nAddCapacity>0 );
    91         -  /* TODO(shess) Consider expanding more aggressively.  Note that the
    92         -  ** underlying malloc implementation may take care of such things for
    93         -  ** us already.
    94         -  */
    95         -  if( pBuffer->nData+nAddCapacity>pBuffer->nCapacity ){
    96         -    pBuffer->nCapacity = pBuffer->nData+nAddCapacity;
    97         -    pBuffer->pData = sqlite3_realloc(pBuffer->pData, pBuffer->nCapacity);
    98         -  }
    99         -}
   100         -static void dataBufferAppend(DataBuffer *pBuffer,
   101         -                             const char *pSource, int nSource){
   102         -  assert( nSource>0 && pSource!=NULL );
   103         -  dataBufferExpand(pBuffer, nSource);
   104         -  memcpy(pBuffer->pData+pBuffer->nData, pSource, nSource);
   105         -  pBuffer->nData += nSource;
   106         -}
   107         -static void dataBufferAppend2(DataBuffer *pBuffer,
   108         -                              const char *pSource1, int nSource1,
   109         -                              const char *pSource2, int nSource2){
   110         -  assert( nSource1>0 && pSource1!=NULL );
   111         -  assert( nSource2>0 && pSource2!=NULL );
   112         -  dataBufferExpand(pBuffer, nSource1+nSource2);
   113         -  memcpy(pBuffer->pData+pBuffer->nData, pSource1, nSource1);
   114         -  memcpy(pBuffer->pData+pBuffer->nData+nSource1, pSource2, nSource2);
   115         -  pBuffer->nData += nSource1+nSource2;
   116         -}
   117         -static void dataBufferReplace(DataBuffer *pBuffer,
   118         -                              const char *pSource, int nSource){
   119         -  dataBufferReset(pBuffer);
   120         -  dataBufferAppend(pBuffer, pSource, nSource);
   121         -}
   122         -
   123         -
   124         -/* StringBuffer is a null-terminated version of DataBuffer. */
           52  +
           53  +
           54  +/*
           55  +** A StringBuffer object holds a zero-terminated string that grows
           56  +** arbitrarily by appending.  Space to hold the string is obtained
           57  +** from sqlite3_malloc().  After any memory allocation failure, 
           58  +** StringBuffer.z is set to NULL and no further allocation is attempted.
           59  +*/
   125     60   typedef struct StringBuffer {
   126         -  DataBuffer b;            /* Includes null terminator. */
           61  +  char *z;         /* Text of the string.  Space from malloc. */
           62  +  int nUsed;       /* Number bytes of z[] used, not counting \000 terminator */
           63  +  int nAlloc;      /* Bytes allocated for z[] */
   127     64   } StringBuffer;
   128     65   
   129         -static void initStringBuffer(StringBuffer *sb){
   130         -  dataBufferInit(&sb->b, 100);
   131         -  dataBufferReplace(&sb->b, "", 1);
   132         -}
   133         -static int stringBufferLength(StringBuffer *sb){
   134         -  return sb->b.nData-1;
   135         -}
   136         -static char *stringBufferData(StringBuffer *sb){
   137         -  return sb->b.pData;
   138         -}
   139         -
   140         -static void nappend(StringBuffer *sb, const char *zFrom, int nFrom){
   141         -  assert( sb->b.nData>0 );
   142         -  if( nFrom>0 ){
   143         -    sb->b.nData--;
   144         -    dataBufferAppend2(&sb->b, zFrom, nFrom, "", 1);
   145         -  }
   146         -}
   147         -static void append(StringBuffer *sb, const char *zFrom){
   148         -  nappend(sb, zFrom, strlen(zFrom));
   149         -}
   150         -
   151         -static int endsInWhiteSpace(StringBuffer *p){
   152         -  return stringBufferLength(p)>0 &&
   153         -    safe_isspace(stringBufferData(p)[stringBufferLength(p)-1]);
           66  +
           67  +/*
           68  +** Initialize a new StringBuffer.
           69  +*/
           70  +static void fts3SnippetSbInit(StringBuffer *p){
           71  +  p->nAlloc = 100;
           72  +  p->nUsed = 0;
           73  +  p->z = sqlite3_malloc( p->nAlloc );
           74  +}
           75  +
           76  +/*
           77  +** Append text to the string buffer.
           78  +*/
           79  +static void fts3SnippetAppend(StringBuffer *p, const char *zNew, int nNew){
           80  +  if( p->z==0 ) return;
           81  +  if( nNew<0 ) nNew = strlen(zNew);
           82  +  if( p->nUsed + nNew >= p->nAlloc ){
           83  +    int nAlloc;
           84  +    char *zNew;
           85  +
           86  +    nAlloc = p->nUsed + nNew + p->nAlloc;
           87  +    zNew = sqlite3_realloc(p->z, nAlloc);
           88  +    if( zNew==0 ){
           89  +      sqlite3_free(p->z);
           90  +      p->z = 0;
           91  +      return;
           92  +    }
           93  +    p->z = zNew;
           94  +    p->nAlloc = nAlloc;
           95  +  }
           96  +  memcpy(&p->z[p->nUsed], zNew, nNew);
           97  +  p->nUsed += nNew;
           98  +  p->z[p->nUsed] = 0;
   154     99   }
   155    100   
   156    101   /* If the StringBuffer ends in something other than white space, add a
   157    102   ** single space character to the end.
   158    103   */
   159         -static void appendWhiteSpace(StringBuffer *p){
   160         -  if( stringBufferLength(p)==0 ) return;
   161         -  if( !endsInWhiteSpace(p) ) append(p, " ");
          104  +static void fts3SnippetAppendWhiteSpace(StringBuffer *p){
          105  +  if( p->z && p->nUsed && !fts3snippetIsspace(p->z[p->nUsed-1]) ){
          106  +    fts3SnippetAppend(p, " ", 1);
          107  +  }
   162    108   }
   163    109   
   164    110   /* Remove white space from the end of the StringBuffer */
   165         -static void trimWhiteSpace(StringBuffer *p){
   166         -  while( endsInWhiteSpace(p) ){
   167         -    p->b.pData[--p->b.nData-1] = '\0';
          111  +static void fts3SnippetTrimWhiteSpace(StringBuffer *p){
          112  +  if( p->z ){
          113  +    while( p->nUsed && fts3snippetIsspace(p->z[p->nUsed-1]) ){
          114  +      p->nUsed--;
          115  +    }
          116  +    p->z[p->nUsed] = 0;
   168    117     }
   169    118   }
   170         -
   171    119   
   172    120   /* 
   173    121   ** Release all memory associated with the Snippet structure passed as
   174    122   ** an argument.
   175    123   */
   176    124   static void fts3SnippetFree(Snippet *p){
   177         -  sqlite3_free(p->aMatch);
   178         -  sqlite3_free(p->zOffset);
   179         -  sqlite3_free(p->zSnippet);
   180         -  sqlite3_free(p);
          125  +  if( p ){
          126  +    sqlite3_free(p->aMatch);
          127  +    sqlite3_free(p->zOffset);
          128  +    sqlite3_free(p->zSnippet);
          129  +    sqlite3_free(p);
          130  +  }
   181    131   }
   182    132   
   183    133   /*
   184    134   ** Append a single entry to the p->aMatch[] log.
   185    135   */
   186         -static void snippetAppendMatch(
          136  +static int snippetAppendMatch(
   187    137     Snippet *p,               /* Append the entry to this snippet */
   188    138     int iCol, int iTerm,      /* The column and query term */
   189    139     int iToken,               /* Matching token in document */
   190    140     int iStart, int nByte     /* Offset and size of the match */
   191    141   ){
   192    142     int i;
   193    143     struct snippetMatch *pMatch;
   194    144     if( p->nMatch+1>=p->nAlloc ){
          145  +    struct snippetMatch *pNew;
   195    146       p->nAlloc = p->nAlloc*2 + 10;
   196         -    p->aMatch = sqlite3_realloc(p->aMatch, p->nAlloc*sizeof(p->aMatch[0]) );
   197         -    if( p->aMatch==0 ){
          147  +    pNew = sqlite3_realloc(p->aMatch, p->nAlloc*sizeof(p->aMatch[0]) );
          148  +    if( pNew==0 ){
          149  +      p->aMatch = 0;
   198    150         p->nMatch = 0;
   199    151         p->nAlloc = 0;
   200         -      return;
          152  +      return SQLITE_NOMEM;
   201    153       }
          154  +    p->aMatch = pNew;
   202    155     }
   203    156     i = p->nMatch++;
   204    157     pMatch = &p->aMatch[i];
   205    158     pMatch->iCol = iCol;
   206    159     pMatch->iTerm = iTerm;
   207    160     pMatch->iToken = iToken;
   208    161     pMatch->iStart = iStart;
   209    162     pMatch->nByte = nByte;
          163  +  return SQLITE_OK;
   210    164   }
   211    165   
   212    166   /*
   213    167   ** Sizing information for the circular buffer used in snippetOffsetsOfColumn()
   214    168   */
   215    169   #define FTS3_ROTOR_SZ   (32)
   216    170   #define FTS3_ROTOR_MASK (FTS3_ROTOR_SZ-1)
................................................................................
   276    230     return 0;
   277    231   }
   278    232   
   279    233   /*
   280    234   ** Add entries to pSnippet->aMatch[] for every match that occurs against
   281    235   ** document zDoc[0..nDoc-1] which is stored in column iColumn.
   282    236   */
   283         -static void snippetOffsetsOfColumn(
          237  +static int snippetOffsetsOfColumn(
   284    238     Fts3Cursor *pCur,         /* The fulltest search cursor */
   285    239     Snippet *pSnippet,             /* The Snippet object to be filled in */
   286    240     int iColumn,                   /* Index of fulltext table column */
   287    241     const char *zDoc,              /* Text of the fulltext table column */
   288    242     int nDoc                       /* Length of zDoc in bytes */
   289    243   ){
   290    244     const sqlite3_tokenizer_module *pTModule;  /* The tokenizer module */
................................................................................
   306    260     int iRotorLen[FTS3_ROTOR_SZ];        /* Length of token */
   307    261   
   308    262     pVtab =  (Fts3Table *)pCur->base.pVtab;
   309    263     nColumn = pVtab->nColumn;
   310    264     pTokenizer = pVtab->pTokenizer;
   311    265     pTModule = pTokenizer->pModule;
   312    266     rc = pTModule->xOpen(pTokenizer, zDoc, nDoc, &pTCursor);
   313         -  if( rc ) return;
          267  +  if( rc ) return rc;
   314    268     pTCursor->pTokenizer = pTokenizer;
   315    269   
   316    270     prevMatch = 0;
   317         -  while( !pTModule->xNext(pTCursor, &zToken, &nToken, &iBegin, &iEnd, &iPos) ){
          271  +  while( (rc = pTModule->xNext(pTCursor, &zToken, &nToken,
          272  +                               &iBegin, &iEnd, &iPos))==SQLITE_OK ){
   318    273       Fts3Expr *pIter = pCur->pExpr;
   319    274       int iIter = -1;
   320    275       iRotorBegin[iRotor&FTS3_ROTOR_MASK] = iBegin;
   321    276       iRotorLen[iRotor&FTS3_ROTOR_MASK] = iEnd-iBegin;
   322    277       match = 0;
   323    278       for(i=0; i<(FTS3_ROTOR_SZ-1) && fts3NextExprToken(&pIter, &iIter); i++){
   324    279         int nPhrase;                    /* Number of tokens in current phrase */
................................................................................
   335    290         assert( pToken->n<=nToken );
   336    291         if( memcmp(pToken->z, zToken, pToken->n) ) continue;
   337    292         if( iIter>0 && (prevMatch & (1<<i))==0 ) continue;
   338    293         match |= 1<<i;
   339    294         if( i==(FTS3_ROTOR_SZ-2) || nPhrase==iIter+1 ){
   340    295           for(j=nPhrase-1; j>=0; j--){
   341    296             int k = (iRotor-j) & FTS3_ROTOR_MASK;
   342         -          snippetAppendMatch(pSnippet, iColumn, i-j, iPos-j,
   343         -                iRotorBegin[k], iRotorLen[k]);
          297  +          rc = snippetAppendMatch(pSnippet, iColumn, i-j, iPos-j,
          298  +                                  iRotorBegin[k], iRotorLen[k]);
          299  +          if( rc ) goto end_offsets_of_column;
   344    300           }
   345    301         }
   346    302       }
   347    303       prevMatch = match<<1;
   348    304       iRotor++;
   349    305     }
          306  +end_offsets_of_column:
   350    307     pTModule->xClose(pTCursor);  
          308  +  return rc==SQLITE_DONE ? SQLITE_OK : rc;
   351    309   }
   352    310   
   353    311   /*
   354    312   ** Remove entries from the pSnippet structure to account for the NEAR
   355    313   ** operator. When this is called, pSnippet contains the list of token 
   356    314   ** offsets produced by treating all NEAR operators as AND operators.
   357    315   ** This function removes any entries that should not be present after
................................................................................
   485    443   static int snippetAllOffsets(Fts3Cursor *pCsr, Snippet **ppSnippet){
   486    444     Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
   487    445     int nColumn;
   488    446     int iColumn, i;
   489    447     int iFirst, iLast;
   490    448     int iTerm = 0;
   491    449     Snippet *pSnippet;
          450  +  int rc = SQLITE_OK;
   492    451   
   493    452     if( pCsr->pExpr==0 ){
   494    453       return SQLITE_OK;
   495    454     }
   496    455   
   497    456     pSnippet = (Snippet *)sqlite3_malloc(sizeof(Snippet));
   498    457     *ppSnippet = pSnippet;
................................................................................
   508    467       iFirst = 0;
   509    468       iLast = nColumn-1;
   510    469     }else{
   511    470       /* Look for matches in the iColumn-th column of the index only */
   512    471       iFirst = iColumn;
   513    472       iLast = iColumn;
   514    473     }
   515         -  for(i=iFirst; i<=iLast; i++){
          474  +  for(i=iFirst; rc==SQLITE_OK && i<=iLast; i++){
   516    475       const char *zDoc;
   517    476       int nDoc;
   518    477       zDoc = (const char*)sqlite3_column_text(pCsr->pStmt, i+1);
   519    478       nDoc = sqlite3_column_bytes(pCsr->pStmt, i+1);
   520         -    snippetOffsetsOfColumn(pCsr, pSnippet, i, zDoc, nDoc);
          479  +    if( zDoc==0 && sqlite3_column_type(pCsr->pStmt, i+1)!=SQLITE_NULL ){
          480  +      rc = SQLITE_NOMEM;
          481  +    }else{
          482  +      rc = snippetOffsetsOfColumn(pCsr, pSnippet, i, zDoc, nDoc);
          483  +    }
   521    484     }
   522    485   
   523    486     while( trimSnippetOffsets(pCsr->pExpr, pSnippet, &iTerm) ){
   524    487       iTerm = 0;
   525    488     }
   526    489   
   527         -  return SQLITE_OK;
          490  +  return rc;
   528    491   }
   529    492   
   530    493   /*
   531    494   ** Convert the information in the aMatch[] array of the snippet
   532    495   ** into the string zOffset[0..nOffset-1]. This string is used as
   533    496   ** the return of the SQL offsets() function.
   534    497   */
   535    498   static void snippetOffsetText(Snippet *p){
   536    499     int i;
   537    500     int cnt = 0;
   538    501     StringBuffer sb;
   539    502     char zBuf[200];
   540    503     if( p->zOffset ) return;
   541         -  initStringBuffer(&sb);
          504  +  fts3SnippetSbInit(&sb);
   542    505     for(i=0; i<p->nMatch; i++){
   543    506       struct snippetMatch *pMatch = &p->aMatch[i];
   544    507       if( pMatch->iTerm>=0 ){
   545    508         /* If snippetMatch.iTerm is less than 0, then the match was 
   546    509         ** discarded as part of processing the NEAR operator (see the 
   547    510         ** trimSnippetOffsetsForNear() function for details). Ignore 
   548    511         ** it in this case
   549    512         */
   550    513         zBuf[0] = ' ';
   551    514         sqlite3_snprintf(sizeof(zBuf)-1, &zBuf[cnt>0], "%d %d %d %d",
   552    515             pMatch->iCol, pMatch->iTerm, pMatch->iStart, pMatch->nByte);
   553         -      append(&sb, zBuf);
          516  +      fts3SnippetAppend(&sb, zBuf, -1);
   554    517         cnt++;
   555    518       }
   556    519     }
   557         -  p->zOffset = stringBufferData(&sb);
   558         -  p->nOffset = stringBufferLength(&sb);
          520  +  p->zOffset = sb.z;
          521  +  p->nOffset = sb.z ? sb.nUsed : 0;
   559    522   }
   560    523   
   561    524   /*
   562    525   ** zDoc[0..nDoc-1] is phrase of text.  aMatch[0..nMatch-1] are a set
   563    526   ** of matching words some of which might be in zDoc.  zDoc is column
   564    527   ** number iCol.
   565    528   **
................................................................................
   589    552         return aMatch[i].iStart;
   590    553       }
   591    554       if( i>0 && aMatch[i-1].iStart+aMatch[i-1].nByte>=iBreak ){
   592    555         return aMatch[i-1].iStart;
   593    556       }
   594    557     }
   595    558     for(i=1; i<=10; i++){
   596         -    if( safe_isspace(zDoc[iBreak-i]) ){
          559  +    if( fts3snippetIsspace(zDoc[iBreak-i]) ){
   597    560         return iBreak - i + 1;
   598    561       }
   599         -    if( safe_isspace(zDoc[iBreak+i]) ){
          562  +    if( fts3snippetIsspace(zDoc[iBreak+i]) ){
   600    563         return iBreak + i + 1;
   601    564       }
   602    565     }
   603    566     return iBreak;
   604    567   }
   605    568   
   606    569   
................................................................................
   636    599     int iMatch;
   637    600     
   638    601   
   639    602     sqlite3_free(pSnippet->zSnippet);
   640    603     pSnippet->zSnippet = 0;
   641    604     aMatch = pSnippet->aMatch;
   642    605     nMatch = pSnippet->nMatch;
   643         -  initStringBuffer(&sb);
          606  +  fts3SnippetSbInit(&sb);
   644    607   
   645    608     for(i=0; i<nMatch; i++){
   646    609       aMatch[i].snStatus = SNIPPET_IGNORE;
   647    610     }
   648    611     nDesired = 0;
   649    612     for(i=0; i<FTS3_ROTOR_SZ; i++){
   650    613       for(j=0; j<nMatch; j++){
................................................................................
   670    633       if( iStart<=10 ){
   671    634         iStart = 0;
   672    635       }
   673    636       if( iCol==tailCol && iStart<=tailOffset+20 ){
   674    637         iStart = tailOffset;
   675    638       }
   676    639       if( (iCol!=tailCol && tailCol>=0) || iStart!=tailOffset ){
   677         -      trimWhiteSpace(&sb);
   678         -      appendWhiteSpace(&sb);
   679         -      append(&sb, zEllipsis);
   680         -      appendWhiteSpace(&sb);
          640  +      fts3SnippetTrimWhiteSpace(&sb);
          641  +      fts3SnippetAppendWhiteSpace(&sb);
          642  +      fts3SnippetAppend(&sb, zEllipsis, -1);
          643  +      fts3SnippetAppendWhiteSpace(&sb);
   681    644       }
   682    645       iEnd = aMatch[i].iStart + aMatch[i].nByte + 40;
   683    646       iEnd = wordBoundary(iEnd, zDoc, nDoc, aMatch, nMatch, iCol);
   684    647       if( iEnd>=nDoc-10 ){
   685    648         iEnd = nDoc;
   686    649         tailEllipsis = 0;
   687    650       }else{
................................................................................
   691    654       while( iStart<iEnd ){
   692    655         while( iMatch<nMatch && aMatch[iMatch].iStart<iStart
   693    656                && aMatch[iMatch].iCol<=iCol ){
   694    657           iMatch++;
   695    658         }
   696    659         if( iMatch<nMatch && aMatch[iMatch].iStart<iEnd
   697    660                && aMatch[iMatch].iCol==iCol ){
   698         -        nappend(&sb, &zDoc[iStart], aMatch[iMatch].iStart - iStart);
          661  +        fts3SnippetAppend(&sb, &zDoc[iStart], aMatch[iMatch].iStart - iStart);
   699    662           iStart = aMatch[iMatch].iStart;
   700         -        append(&sb, zStartMark);
   701         -        nappend(&sb, &zDoc[iStart], aMatch[iMatch].nByte);
   702         -        append(&sb, zEndMark);
          663  +        fts3SnippetAppend(&sb, zStartMark, -1);
          664  +        fts3SnippetAppend(&sb, &zDoc[iStart], aMatch[iMatch].nByte);
          665  +        fts3SnippetAppend(&sb, zEndMark, -1);
   703    666           iStart += aMatch[iMatch].nByte;
   704    667           for(j=iMatch+1; j<nMatch; j++){
   705    668             if( aMatch[j].iTerm==aMatch[iMatch].iTerm
   706    669                 && aMatch[j].snStatus==SNIPPET_DESIRED ){
   707    670               nDesired--;
   708    671               aMatch[j].snStatus = SNIPPET_IGNORE;
   709    672             }
   710    673           }
   711    674         }else{
   712         -        nappend(&sb, &zDoc[iStart], iEnd - iStart);
          675  +        fts3SnippetAppend(&sb, &zDoc[iStart], iEnd - iStart);
   713    676           iStart = iEnd;
   714    677         }
   715    678       }
   716    679       tailCol = iCol;
   717    680       tailOffset = iEnd;
   718    681     }
   719         -  trimWhiteSpace(&sb);
          682  +  fts3SnippetTrimWhiteSpace(&sb);
   720    683     if( tailEllipsis ){
   721         -    appendWhiteSpace(&sb);
   722         -    append(&sb, zEllipsis);
          684  +    fts3SnippetAppendWhiteSpace(&sb);
          685  +    fts3SnippetAppend(&sb, zEllipsis, -1);
   723    686     }
   724         -  pSnippet->zSnippet = stringBufferData(&sb);
   725         -  pSnippet->nSnippet = stringBufferLength(&sb);
          687  +  pSnippet->zSnippet = sb.z;
          688  +  pSnippet->nSnippet = sb.z ? sb.nUsed : 0;
   726    689   }
   727    690   
   728    691   void sqlite3Fts3Offsets(
   729    692     sqlite3_context *pCtx,          /* SQLite function call context */
   730    693     Fts3Cursor *pCsr                /* Cursor object */
   731    694   ){
   732    695     Snippet *p;                     /* Snippet structure */
   733    696     int rc = snippetAllOffsets(pCsr, &p);
   734         -  snippetOffsetText(p);
   735         -  sqlite3_result_text(pCtx, p->zOffset, p->nOffset, SQLITE_TRANSIENT);
          697  +  if( rc==SQLITE_OK ){
          698  +    snippetOffsetText(p);
          699  +    if( p->zOffset ){
          700  +      sqlite3_result_text(pCtx, p->zOffset, p->nOffset, SQLITE_TRANSIENT);
          701  +    }else{
          702  +      sqlite3_result_error_nomem(pCtx);
          703  +    }
          704  +  }else{
          705  +    sqlite3_result_error_nomem(pCtx);
          706  +  }
   736    707     fts3SnippetFree(p);
   737    708   }
   738    709   
   739    710   void sqlite3Fts3Snippet(
   740    711     sqlite3_context *pCtx,          /* SQLite function call context */
   741    712     Fts3Cursor *pCsr,               /* Cursor object */
   742    713     const char *zStart,             /* Snippet start text - "<b>" */
   743    714     const char *zEnd,               /* Snippet end text - "</b>" */
   744    715     const char *zEllipsis           /* Snippet ellipsis text - "<b>...</b>" */
   745    716   ){
   746    717     Snippet *p;                     /* Snippet structure */
   747    718     int rc = snippetAllOffsets(pCsr, &p);
   748         -  snippetText(pCsr, p, zStart, zEnd, zEllipsis);
   749         -  sqlite3_result_text(pCtx, p->zSnippet, p->nSnippet, SQLITE_TRANSIENT);
          719  +  if( rc==SQLITE_OK ){
          720  +    snippetText(pCsr, p, zStart, zEnd, zEllipsis);
          721  +    if( p->zSnippet ){
          722  +      sqlite3_result_text(pCtx, p->zSnippet, p->nSnippet, SQLITE_TRANSIENT);
          723  +    }else{
          724  +      sqlite3_result_error_nomem(pCtx);
          725  +    }
          726  +  }else{
          727  +    sqlite3_result_error_nomem(pCtx);
          728  +  }
   750    729     fts3SnippetFree(p);
   751    730   }
   752    731   
   753    732   #endif

Changes to ext/fts3/fts3_write.c.

   642    642           rc = fts3PendingTermsAdd(p, zText, -1);
   643    643           if( rc!=SQLITE_OK ){
   644    644             sqlite3_reset(pSelect);
   645    645             return rc;
   646    646           }
   647    647         }
   648    648       }
          649  +    rc = sqlite3_reset(pSelect);
          650  +  }else{
          651  +    sqlite3_reset(pSelect);
   649    652     }
   650         -
   651         -  return sqlite3_reset(pSelect);
          653  +  return rc;
   652    654   }
   653    655   
   654    656   /*
   655    657   ** Forward declaration to account for the circular dependency between
   656    658   ** functions fts3SegmentMerge() and fts3AllocateSegdirIdx().
   657    659   */
   658    660   static int fts3SegmentMerge(Fts3Table *, int);