Index: ext/fts5/fts5.c ================================================================== --- ext/fts5/fts5.c +++ ext/fts5/fts5.c @@ -72,10 +72,11 @@ ** There is one entry in the aIdx[] array for each phrase in the query, ** the value of which is the offset within aPoslist[] following the last ** byte of the position list for the corresponding phrase. */ struct Fts5Sorter { + int eStmt; sqlite3_stmt *pStmt; i64 iRowid; /* Current rowid */ const u8 *aPoslist; /* Position lists for current row */ int nIdx; /* Number of entries in aIdx[] */ int aIdx[0]; /* Offsets into aPoslist for current row */ @@ -374,13 +375,23 @@ if( pCsr->pStmt ){ int eStmt = fts5StmtType(pCsr->idxNum); sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt); } if( pCsr->pSorter ){ - Fts5Sorter *pSorter = pCsr->pSorter; - sqlite3_finalize(pSorter->pStmt); - sqlite3_free(pSorter); + Fts5Sorter *p = pCsr->pSorter; + + /* TODO: It would be better here to use sqlite3Fts5StorageStmtRelease() + ** so that the statement may be reused by subsequent queries. But that + ** is not possible as SQLite reference counts the virtual table objects. + ** And since pStmt reads from this very virtual table, saving it here + ** creates a circular reference. + ** + ** We wouldn't worry so much if SQLite had a built-in statement cache. + */ + /* sqlite3Fts5StorageStmtRelease(pTab->pStorage, p->eStmt, p->pStmt); */ + sqlite3_finalize(p->pStmt); + sqlite3_free(p); } if( pCsr->idxNum!=FTS5_PLAN_SOURCE ){ sqlite3Fts5ExprFree(pCsr->pExpr); } @@ -479,10 +490,11 @@ static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bAsc){ Fts5Config *pConfig = pTab->pConfig; Fts5Sorter *pSorter; int nPhrase; int nByte; + int eStmt; int rc = SQLITE_OK; char *zSql; nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr); nByte = sizeof(Fts5Sorter) + sizeof(int) * nPhrase; @@ -489,20 +501,15 @@ pSorter = (Fts5Sorter*)sqlite3_malloc(nByte); if( pSorter==0 ) return SQLITE_NOMEM; memset(pSorter, 0, nByte); pSorter->nIdx = nPhrase; - zSql = sqlite3_mprintf("SELECT rowid, %s FROM %Q.%Q ORDER BY +%s %s", - pConfig->zName, pConfig->zDb, pConfig->zName, FTS5_RANK_NAME, - bAsc ? "ASC" : "DESC" - ); - if( zSql==0 ){ - rc = SQLITE_NOMEM; - }else{ - rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pSorter->pStmt, 0); - sqlite3_free(zSql); - } + assert( FTS5_STMT_SORTER_ASC==1+FTS5_STMT_SORTER_DESC ); + assert( bAsc==0 || bAsc==1 ); + + pSorter->eStmt = FTS5_STMT_SORTER_DESC+bAsc; + rc = sqlite3Fts5StorageStmt(pTab->pStorage, pSorter->eStmt, &pSorter->pStmt); pCsr->pSorter = pSorter; if( rc==SQLITE_OK ){ assert( pTab->pSortCsr==0 ); pTab->pSortCsr = pCsr; Index: ext/fts5/fts5Int.h ================================================================== --- ext/fts5/fts5Int.h +++ ext/fts5/fts5Int.h @@ -280,13 +280,15 @@ /************************************************************************** ** Interface to code in fts5_storage.c. fts5_storage.c contains contains ** code to access the data stored in the %_content and %_docsize tables. */ -#define FTS5_STMT_SCAN_ASC 0 /* SELECT rowid, * FROM ... ORDER BY 1 ASC */ -#define FTS5_STMT_SCAN_DESC 1 /* SELECT rowid, * FROM ... ORDER BY 1 DESC */ -#define FTS5_STMT_LOOKUP 2 /* SELECT rowid, * FROM ... WHERE rowid=? */ +#define FTS5_STMT_SCAN_ASC 0 /* SELECT rowid, * FROM ... ORDER BY 1 ASC */ +#define FTS5_STMT_SCAN_DESC 1 /* SELECT rowid, * FROM ... ORDER BY 1 DESC */ +#define FTS5_STMT_LOOKUP 2 /* SELECT rowid, * FROM ... WHERE rowid=? */ +#define FTS5_STMT_SORTER_DESC 3 /* SELECT ... ORDER BY rank ASC */ +#define FTS5_STMT_SORTER_ASC 4 /* SELECT ... ORDER BY rank ASC */ typedef struct Fts5Storage Fts5Storage; int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**); int sqlite3Fts5StorageClose(Fts5Storage *p, int bDestroy); Index: ext/fts5/fts5_storage.c ================================================================== --- ext/fts5/fts5_storage.c +++ ext/fts5/fts5_storage.c @@ -17,11 +17,11 @@ struct Fts5Storage { Fts5Config *pConfig; Fts5Index *pIndex; i64 nTotalRow; /* Total number of rows in FTS table */ i64 *aTotalSize; /* Total sizes of each column */ - sqlite3_stmt *aStmt[9]; + sqlite3_stmt *aStmt[11]; }; #if FTS5_STMT_SCAN_ASC!=0 # error "FTS5_STMT_SCAN_ASC mismatch" @@ -30,19 +30,24 @@ # error "FTS5_STMT_SCAN_DESC mismatch" #endif #if FTS5_STMT_LOOKUP!=2 # error "FTS5_STMT_LOOKUP mismatch" #endif - -#define FTS5_STMT_INSERT_CONTENT 3 -#define FTS5_STMT_REPLACE_CONTENT 4 - -#define FTS5_STMT_DELETE_CONTENT 5 -#define FTS5_STMT_REPLACE_DOCSIZE 6 -#define FTS5_STMT_DELETE_DOCSIZE 7 - -#define FTS5_STMT_LOOKUP_DOCSIZE 8 +#if FTS5_STMT_SORTER_DESC!=3 +# error "FTS5_STMT_SORTER_DESC mismatch" +#endif +#if FTS5_STMT_SORTER_ASC!=4 +# error "FTS5_STMT_SORTER_ASC mismatch" +#endif + +#define FTS5_STMT_INSERT_CONTENT 5 +#define FTS5_STMT_REPLACE_CONTENT 6 + +#define FTS5_STMT_DELETE_CONTENT 7 +#define FTS5_STMT_REPLACE_DOCSIZE 8 +#define FTS5_STMT_DELETE_DOCSIZE 9 +#define FTS5_STMT_LOOKUP_DOCSIZE 10 /* ** Prepare the two insert statements - Fts5Storage.pInsertContent and ** Fts5Storage.pInsertDocsize - if they have not already been prepared. ** Return SQLITE_OK if successful, or an SQLite error code if an error @@ -60,10 +65,14 @@ const char *azStmt[] = { "SELECT * FROM %Q.'%q_content' ORDER BY id ASC", /* SCAN_ASC */ "SELECT * FROM %Q.'%q_content' ORDER BY id DESC", /* SCAN_DESC */ "SELECT * FROM %Q.'%q_content' WHERE rowid=?", /* LOOKUP */ + /* SORTER_DESC and SORTER_ASC: */ + "SELECT rowid, \"%s\" FROM %Q.%Q ORDER BY +" FTS5_RANK_NAME " DESC", + "SELECT rowid, \"%s\" FROM %Q.%Q ORDER BY +" FTS5_RANK_NAME " ASC", + "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */ "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */ @@ -86,10 +95,14 @@ } zBind[i*2-1] = '\0'; zSql = sqlite3_mprintf(azStmt[eStmt],pConfig->zDb,pConfig->zName,zBind); sqlite3_free(zBind); } + }else if( eStmt==FTS5_STMT_SORTER_ASC || eStmt==FTS5_STMT_SORTER_DESC ){ + zSql = sqlite3_mprintf(azStmt[eStmt], + pConfig->zName, pConfig->zDb, pConfig->zName + ); }else{ zSql = sqlite3_mprintf(azStmt[eStmt], pConfig->zDb, pConfig->zName); } if( zSql==0 ){ @@ -629,10 +642,12 @@ int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt **pp){ int rc; assert( eStmt==FTS5_STMT_SCAN_ASC || eStmt==FTS5_STMT_SCAN_DESC || eStmt==FTS5_STMT_LOOKUP + || eStmt==FTS5_STMT_SORTER_DESC + || eStmt==FTS5_STMT_SORTER_ASC ); rc = fts5StorageGetStmt(p, eStmt, pp); if( rc==SQLITE_OK ){ assert( p->aStmt[eStmt]==*pp ); p->aStmt[eStmt] = 0; @@ -651,10 +666,12 @@ sqlite3_stmt *pStmt ){ assert( eStmt==FTS5_STMT_SCAN_ASC || eStmt==FTS5_STMT_SCAN_DESC || eStmt==FTS5_STMT_LOOKUP + || eStmt==FTS5_STMT_SORTER_DESC + || eStmt==FTS5_STMT_SORTER_ASC ); if( p->aStmt[eStmt]==0 ){ sqlite3_reset(pStmt); p->aStmt[eStmt] = pStmt; }else{