Annotation of embedaddon/sqlite3/ext/fts3/fts3_aux.c, revision 1.1

1.1     ! misho       1: /*
        !             2: ** 2011 Jan 27
        !             3: **
        !             4: ** The author disclaims copyright to this source code.  In place of
        !             5: ** a legal notice, here is a blessing:
        !             6: **
        !             7: **    May you do good and not evil.
        !             8: **    May you find forgiveness for yourself and forgive others.
        !             9: **    May you share freely, never taking more than you give.
        !            10: **
        !            11: ******************************************************************************
        !            12: **
        !            13: */
        !            14: #include "fts3Int.h"
        !            15: #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
        !            16: 
        !            17: #include <string.h>
        !            18: #include <assert.h>
        !            19: 
        !            20: typedef struct Fts3auxTable Fts3auxTable;
        !            21: typedef struct Fts3auxCursor Fts3auxCursor;
        !            22: 
        !            23: struct Fts3auxTable {
        !            24:   sqlite3_vtab base;              /* Base class used by SQLite core */
        !            25:   Fts3Table *pFts3Tab;
        !            26: };
        !            27: 
        !            28: struct Fts3auxCursor {
        !            29:   sqlite3_vtab_cursor base;       /* Base class used by SQLite core */
        !            30:   Fts3MultiSegReader csr;        /* Must be right after "base" */
        !            31:   Fts3SegFilter filter;
        !            32:   char *zStop;
        !            33:   int nStop;                      /* Byte-length of string zStop */
        !            34:   int isEof;                      /* True if cursor is at EOF */
        !            35:   sqlite3_int64 iRowid;           /* Current rowid */
        !            36: 
        !            37:   int iCol;                       /* Current value of 'col' column */
        !            38:   int nStat;                      /* Size of aStat[] array */
        !            39:   struct Fts3auxColstats {
        !            40:     sqlite3_int64 nDoc;           /* 'documents' values for current csr row */
        !            41:     sqlite3_int64 nOcc;           /* 'occurrences' values for current csr row */
        !            42:   } *aStat;
        !            43: };
        !            44: 
        !            45: /*
        !            46: ** Schema of the terms table.
        !            47: */
        !            48: #define FTS3_TERMS_SCHEMA "CREATE TABLE x(term, col, documents, occurrences)"
        !            49: 
        !            50: /*
        !            51: ** This function does all the work for both the xConnect and xCreate methods.
        !            52: ** These tables have no persistent representation of their own, so xConnect
        !            53: ** and xCreate are identical operations.
        !            54: */
        !            55: static int fts3auxConnectMethod(
        !            56:   sqlite3 *db,                    /* Database connection */
        !            57:   void *pUnused,                  /* Unused */
        !            58:   int argc,                       /* Number of elements in argv array */
        !            59:   const char * const *argv,       /* xCreate/xConnect argument array */
        !            60:   sqlite3_vtab **ppVtab,          /* OUT: New sqlite3_vtab object */
        !            61:   char **pzErr                    /* OUT: sqlite3_malloc'd error message */
        !            62: ){
        !            63:   char const *zDb;                /* Name of database (e.g. "main") */
        !            64:   char const *zFts3;              /* Name of fts3 table */
        !            65:   int nDb;                        /* Result of strlen(zDb) */
        !            66:   int nFts3;                      /* Result of strlen(zFts3) */
        !            67:   int nByte;                      /* Bytes of space to allocate here */
        !            68:   int rc;                         /* value returned by declare_vtab() */
        !            69:   Fts3auxTable *p;                /* Virtual table object to return */
        !            70: 
        !            71:   UNUSED_PARAMETER(pUnused);
        !            72: 
        !            73:   /* The user should specify a single argument - the name of an fts3 table. */
        !            74:   if( argc!=4 ){
        !            75:     *pzErr = sqlite3_mprintf(
        !            76:         "wrong number of arguments to fts4aux constructor"
        !            77:     );
        !            78:     return SQLITE_ERROR;
        !            79:   }
        !            80: 
        !            81:   zDb = argv[1]; 
        !            82:   nDb = strlen(zDb);
        !            83:   zFts3 = argv[3];
        !            84:   nFts3 = strlen(zFts3);
        !            85: 
        !            86:   rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA);
        !            87:   if( rc!=SQLITE_OK ) return rc;
        !            88: 
        !            89:   nByte = sizeof(Fts3auxTable) + sizeof(Fts3Table) + nDb + nFts3 + 2;
        !            90:   p = (Fts3auxTable *)sqlite3_malloc(nByte);
        !            91:   if( !p ) return SQLITE_NOMEM;
        !            92:   memset(p, 0, nByte);
        !            93: 
        !            94:   p->pFts3Tab = (Fts3Table *)&p[1];
        !            95:   p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
        !            96:   p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
        !            97:   p->pFts3Tab->db = db;
        !            98:   p->pFts3Tab->nIndex = 1;
        !            99: 
        !           100:   memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
        !           101:   memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
        !           102:   sqlite3Fts3Dequote((char *)p->pFts3Tab->zName);
        !           103: 
        !           104:   *ppVtab = (sqlite3_vtab *)p;
        !           105:   return SQLITE_OK;
        !           106: }
        !           107: 
        !           108: /*
        !           109: ** This function does the work for both the xDisconnect and xDestroy methods.
        !           110: ** These tables have no persistent representation of their own, so xDisconnect
        !           111: ** and xDestroy are identical operations.
        !           112: */
        !           113: static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){
        !           114:   Fts3auxTable *p = (Fts3auxTable *)pVtab;
        !           115:   Fts3Table *pFts3 = p->pFts3Tab;
        !           116:   int i;
        !           117: 
        !           118:   /* Free any prepared statements held */
        !           119:   for(i=0; i<SizeofArray(pFts3->aStmt); i++){
        !           120:     sqlite3_finalize(pFts3->aStmt[i]);
        !           121:   }
        !           122:   sqlite3_free(pFts3->zSegmentsTbl);
        !           123:   sqlite3_free(p);
        !           124:   return SQLITE_OK;
        !           125: }
        !           126: 
        !           127: #define FTS4AUX_EQ_CONSTRAINT 1
        !           128: #define FTS4AUX_GE_CONSTRAINT 2
        !           129: #define FTS4AUX_LE_CONSTRAINT 4
        !           130: 
        !           131: /*
        !           132: ** xBestIndex - Analyze a WHERE and ORDER BY clause.
        !           133: */
        !           134: static int fts3auxBestIndexMethod(
        !           135:   sqlite3_vtab *pVTab, 
        !           136:   sqlite3_index_info *pInfo
        !           137: ){
        !           138:   int i;
        !           139:   int iEq = -1;
        !           140:   int iGe = -1;
        !           141:   int iLe = -1;
        !           142: 
        !           143:   UNUSED_PARAMETER(pVTab);
        !           144: 
        !           145:   /* This vtab delivers always results in "ORDER BY term ASC" order. */
        !           146:   if( pInfo->nOrderBy==1 
        !           147:    && pInfo->aOrderBy[0].iColumn==0 
        !           148:    && pInfo->aOrderBy[0].desc==0
        !           149:   ){
        !           150:     pInfo->orderByConsumed = 1;
        !           151:   }
        !           152: 
        !           153:   /* Search for equality and range constraints on the "term" column. */
        !           154:   for(i=0; i<pInfo->nConstraint; i++){
        !           155:     if( pInfo->aConstraint[i].usable && pInfo->aConstraint[i].iColumn==0 ){
        !           156:       int op = pInfo->aConstraint[i].op;
        !           157:       if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i;
        !           158:       if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i;
        !           159:       if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i;
        !           160:       if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i;
        !           161:       if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i;
        !           162:     }
        !           163:   }
        !           164: 
        !           165:   if( iEq>=0 ){
        !           166:     pInfo->idxNum = FTS4AUX_EQ_CONSTRAINT;
        !           167:     pInfo->aConstraintUsage[iEq].argvIndex = 1;
        !           168:     pInfo->estimatedCost = 5;
        !           169:   }else{
        !           170:     pInfo->idxNum = 0;
        !           171:     pInfo->estimatedCost = 20000;
        !           172:     if( iGe>=0 ){
        !           173:       pInfo->idxNum += FTS4AUX_GE_CONSTRAINT;
        !           174:       pInfo->aConstraintUsage[iGe].argvIndex = 1;
        !           175:       pInfo->estimatedCost /= 2;
        !           176:     }
        !           177:     if( iLe>=0 ){
        !           178:       pInfo->idxNum += FTS4AUX_LE_CONSTRAINT;
        !           179:       pInfo->aConstraintUsage[iLe].argvIndex = 1 + (iGe>=0);
        !           180:       pInfo->estimatedCost /= 2;
        !           181:     }
        !           182:   }
        !           183: 
        !           184:   return SQLITE_OK;
        !           185: }
        !           186: 
        !           187: /*
        !           188: ** xOpen - Open a cursor.
        !           189: */
        !           190: static int fts3auxOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
        !           191:   Fts3auxCursor *pCsr;            /* Pointer to cursor object to return */
        !           192: 
        !           193:   UNUSED_PARAMETER(pVTab);
        !           194: 
        !           195:   pCsr = (Fts3auxCursor *)sqlite3_malloc(sizeof(Fts3auxCursor));
        !           196:   if( !pCsr ) return SQLITE_NOMEM;
        !           197:   memset(pCsr, 0, sizeof(Fts3auxCursor));
        !           198: 
        !           199:   *ppCsr = (sqlite3_vtab_cursor *)pCsr;
        !           200:   return SQLITE_OK;
        !           201: }
        !           202: 
        !           203: /*
        !           204: ** xClose - Close a cursor.
        !           205: */
        !           206: static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){
        !           207:   Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
        !           208:   Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
        !           209: 
        !           210:   sqlite3Fts3SegmentsClose(pFts3);
        !           211:   sqlite3Fts3SegReaderFinish(&pCsr->csr);
        !           212:   sqlite3_free((void *)pCsr->filter.zTerm);
        !           213:   sqlite3_free(pCsr->zStop);
        !           214:   sqlite3_free(pCsr->aStat);
        !           215:   sqlite3_free(pCsr);
        !           216:   return SQLITE_OK;
        !           217: }
        !           218: 
        !           219: static int fts3auxGrowStatArray(Fts3auxCursor *pCsr, int nSize){
        !           220:   if( nSize>pCsr->nStat ){
        !           221:     struct Fts3auxColstats *aNew;
        !           222:     aNew = (struct Fts3auxColstats *)sqlite3_realloc(pCsr->aStat, 
        !           223:         sizeof(struct Fts3auxColstats) * nSize
        !           224:     );
        !           225:     if( aNew==0 ) return SQLITE_NOMEM;
        !           226:     memset(&aNew[pCsr->nStat], 0, 
        !           227:         sizeof(struct Fts3auxColstats) * (nSize - pCsr->nStat)
        !           228:     );
        !           229:     pCsr->aStat = aNew;
        !           230:     pCsr->nStat = nSize;
        !           231:   }
        !           232:   return SQLITE_OK;
        !           233: }
        !           234: 
        !           235: /*
        !           236: ** xNext - Advance the cursor to the next row, if any.
        !           237: */
        !           238: static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
        !           239:   Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
        !           240:   Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
        !           241:   int rc;
        !           242: 
        !           243:   /* Increment our pretend rowid value. */
        !           244:   pCsr->iRowid++;
        !           245: 
        !           246:   for(pCsr->iCol++; pCsr->iCol<pCsr->nStat; pCsr->iCol++){
        !           247:     if( pCsr->aStat[pCsr->iCol].nDoc>0 ) return SQLITE_OK;
        !           248:   }
        !           249: 
        !           250:   rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
        !           251:   if( rc==SQLITE_ROW ){
        !           252:     int i = 0;
        !           253:     int nDoclist = pCsr->csr.nDoclist;
        !           254:     char *aDoclist = pCsr->csr.aDoclist;
        !           255:     int iCol;
        !           256: 
        !           257:     int eState = 0;
        !           258: 
        !           259:     if( pCsr->zStop ){
        !           260:       int n = (pCsr->nStop<pCsr->csr.nTerm) ? pCsr->nStop : pCsr->csr.nTerm;
        !           261:       int mc = memcmp(pCsr->zStop, pCsr->csr.zTerm, n);
        !           262:       if( mc<0 || (mc==0 && pCsr->csr.nTerm>pCsr->nStop) ){
        !           263:         pCsr->isEof = 1;
        !           264:         return SQLITE_OK;
        !           265:       }
        !           266:     }
        !           267: 
        !           268:     if( fts3auxGrowStatArray(pCsr, 2) ) return SQLITE_NOMEM;
        !           269:     memset(pCsr->aStat, 0, sizeof(struct Fts3auxColstats) * pCsr->nStat);
        !           270:     iCol = 0;
        !           271: 
        !           272:     while( i<nDoclist ){
        !           273:       sqlite3_int64 v = 0;
        !           274: 
        !           275:       i += sqlite3Fts3GetVarint(&aDoclist[i], &v);
        !           276:       switch( eState ){
        !           277:         /* State 0. In this state the integer just read was a docid. */
        !           278:         case 0:
        !           279:           pCsr->aStat[0].nDoc++;
        !           280:           eState = 1;
        !           281:           iCol = 0;
        !           282:           break;
        !           283: 
        !           284:         /* State 1. In this state we are expecting either a 1, indicating
        !           285:         ** that the following integer will be a column number, or the
        !           286:         ** start of a position list for column 0.  
        !           287:         ** 
        !           288:         ** The only difference between state 1 and state 2 is that if the
        !           289:         ** integer encountered in state 1 is not 0 or 1, then we need to
        !           290:         ** increment the column 0 "nDoc" count for this term.
        !           291:         */
        !           292:         case 1:
        !           293:           assert( iCol==0 );
        !           294:           if( v>1 ){
        !           295:             pCsr->aStat[1].nDoc++;
        !           296:           }
        !           297:           eState = 2;
        !           298:           /* fall through */
        !           299: 
        !           300:         case 2:
        !           301:           if( v==0 ){       /* 0x00. Next integer will be a docid. */
        !           302:             eState = 0;
        !           303:           }else if( v==1 ){ /* 0x01. Next integer will be a column number. */
        !           304:             eState = 3;
        !           305:           }else{            /* 2 or greater. A position. */
        !           306:             pCsr->aStat[iCol+1].nOcc++;
        !           307:             pCsr->aStat[0].nOcc++;
        !           308:           }
        !           309:           break;
        !           310: 
        !           311:         /* State 3. The integer just read is a column number. */
        !           312:         default: assert( eState==3 );
        !           313:           iCol = (int)v;
        !           314:           if( fts3auxGrowStatArray(pCsr, iCol+2) ) return SQLITE_NOMEM;
        !           315:           pCsr->aStat[iCol+1].nDoc++;
        !           316:           eState = 2;
        !           317:           break;
        !           318:       }
        !           319:     }
        !           320: 
        !           321:     pCsr->iCol = 0;
        !           322:     rc = SQLITE_OK;
        !           323:   }else{
        !           324:     pCsr->isEof = 1;
        !           325:   }
        !           326:   return rc;
        !           327: }
        !           328: 
        !           329: /*
        !           330: ** xFilter - Initialize a cursor to point at the start of its data.
        !           331: */
        !           332: static int fts3auxFilterMethod(
        !           333:   sqlite3_vtab_cursor *pCursor,   /* The cursor used for this query */
        !           334:   int idxNum,                     /* Strategy index */
        !           335:   const char *idxStr,             /* Unused */
        !           336:   int nVal,                       /* Number of elements in apVal */
        !           337:   sqlite3_value **apVal           /* Arguments for the indexing scheme */
        !           338: ){
        !           339:   Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
        !           340:   Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
        !           341:   int rc;
        !           342:   int isScan;
        !           343: 
        !           344:   UNUSED_PARAMETER(nVal);
        !           345:   UNUSED_PARAMETER(idxStr);
        !           346: 
        !           347:   assert( idxStr==0 );
        !           348:   assert( idxNum==FTS4AUX_EQ_CONSTRAINT || idxNum==0
        !           349:        || idxNum==FTS4AUX_LE_CONSTRAINT || idxNum==FTS4AUX_GE_CONSTRAINT
        !           350:        || idxNum==(FTS4AUX_LE_CONSTRAINT|FTS4AUX_GE_CONSTRAINT)
        !           351:   );
        !           352:   isScan = (idxNum!=FTS4AUX_EQ_CONSTRAINT);
        !           353: 
        !           354:   /* In case this cursor is being reused, close and zero it. */
        !           355:   testcase(pCsr->filter.zTerm);
        !           356:   sqlite3Fts3SegReaderFinish(&pCsr->csr);
        !           357:   sqlite3_free((void *)pCsr->filter.zTerm);
        !           358:   sqlite3_free(pCsr->aStat);
        !           359:   memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
        !           360: 
        !           361:   pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
        !           362:   if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
        !           363: 
        !           364:   if( idxNum&(FTS4AUX_EQ_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) ){
        !           365:     const unsigned char *zStr = sqlite3_value_text(apVal[0]);
        !           366:     if( zStr ){
        !           367:       pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr);
        !           368:       pCsr->filter.nTerm = sqlite3_value_bytes(apVal[0]);
        !           369:       if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM;
        !           370:     }
        !           371:   }
        !           372:   if( idxNum&FTS4AUX_LE_CONSTRAINT ){
        !           373:     int iIdx = (idxNum&FTS4AUX_GE_CONSTRAINT) ? 1 : 0;
        !           374:     pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iIdx]));
        !           375:     pCsr->nStop = sqlite3_value_bytes(apVal[iIdx]);
        !           376:     if( pCsr->zStop==0 ) return SQLITE_NOMEM;
        !           377:   }
        !           378: 
        !           379:   rc = sqlite3Fts3SegReaderCursor(pFts3, 0, FTS3_SEGCURSOR_ALL,
        !           380:       pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr
        !           381:   );
        !           382:   if( rc==SQLITE_OK ){
        !           383:     rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
        !           384:   }
        !           385: 
        !           386:   if( rc==SQLITE_OK ) rc = fts3auxNextMethod(pCursor);
        !           387:   return rc;
        !           388: }
        !           389: 
        !           390: /*
        !           391: ** xEof - Return true if the cursor is at EOF, or false otherwise.
        !           392: */
        !           393: static int fts3auxEofMethod(sqlite3_vtab_cursor *pCursor){
        !           394:   Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
        !           395:   return pCsr->isEof;
        !           396: }
        !           397: 
        !           398: /*
        !           399: ** xColumn - Return a column value.
        !           400: */
        !           401: static int fts3auxColumnMethod(
        !           402:   sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
        !           403:   sqlite3_context *pContext,      /* Context for sqlite3_result_xxx() calls */
        !           404:   int iCol                        /* Index of column to read value from */
        !           405: ){
        !           406:   Fts3auxCursor *p = (Fts3auxCursor *)pCursor;
        !           407: 
        !           408:   assert( p->isEof==0 );
        !           409:   if( iCol==0 ){        /* Column "term" */
        !           410:     sqlite3_result_text(pContext, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
        !           411:   }else if( iCol==1 ){  /* Column "col" */
        !           412:     if( p->iCol ){
        !           413:       sqlite3_result_int(pContext, p->iCol-1);
        !           414:     }else{
        !           415:       sqlite3_result_text(pContext, "*", -1, SQLITE_STATIC);
        !           416:     }
        !           417:   }else if( iCol==2 ){  /* Column "documents" */
        !           418:     sqlite3_result_int64(pContext, p->aStat[p->iCol].nDoc);
        !           419:   }else{                /* Column "occurrences" */
        !           420:     sqlite3_result_int64(pContext, p->aStat[p->iCol].nOcc);
        !           421:   }
        !           422: 
        !           423:   return SQLITE_OK;
        !           424: }
        !           425: 
        !           426: /*
        !           427: ** xRowid - Return the current rowid for the cursor.
        !           428: */
        !           429: static int fts3auxRowidMethod(
        !           430:   sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
        !           431:   sqlite_int64 *pRowid            /* OUT: Rowid value */
        !           432: ){
        !           433:   Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
        !           434:   *pRowid = pCsr->iRowid;
        !           435:   return SQLITE_OK;
        !           436: }
        !           437: 
        !           438: /*
        !           439: ** Register the fts3aux module with database connection db. Return SQLITE_OK
        !           440: ** if successful or an error code if sqlite3_create_module() fails.
        !           441: */
        !           442: int sqlite3Fts3InitAux(sqlite3 *db){
        !           443:   static const sqlite3_module fts3aux_module = {
        !           444:      0,                           /* iVersion      */
        !           445:      fts3auxConnectMethod,        /* xCreate       */
        !           446:      fts3auxConnectMethod,        /* xConnect      */
        !           447:      fts3auxBestIndexMethod,      /* xBestIndex    */
        !           448:      fts3auxDisconnectMethod,     /* xDisconnect   */
        !           449:      fts3auxDisconnectMethod,     /* xDestroy      */
        !           450:      fts3auxOpenMethod,           /* xOpen         */
        !           451:      fts3auxCloseMethod,          /* xClose        */
        !           452:      fts3auxFilterMethod,         /* xFilter       */
        !           453:      fts3auxNextMethod,           /* xNext         */
        !           454:      fts3auxEofMethod,            /* xEof          */
        !           455:      fts3auxColumnMethod,         /* xColumn       */
        !           456:      fts3auxRowidMethod,          /* xRowid        */
        !           457:      0,                           /* xUpdate       */
        !           458:      0,                           /* xBegin        */
        !           459:      0,                           /* xSync         */
        !           460:      0,                           /* xCommit       */
        !           461:      0,                           /* xRollback     */
        !           462:      0,                           /* xFindFunction */
        !           463:      0,                           /* xRename       */
        !           464:      0,                           /* xSavepoint    */
        !           465:      0,                           /* xRelease      */
        !           466:      0                            /* xRollbackTo   */
        !           467:   };
        !           468:   int rc;                         /* Return code */
        !           469: 
        !           470:   rc = sqlite3_create_module(db, "fts4aux", &fts3aux_module, 0);
        !           471:   return rc;
        !           472: }
        !           473: 
        !           474: #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>