]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Begin adding interface for auxiliary functions.
authordan <dan@noemail.net>
Wed, 16 Jul 2014 19:15:57 +0000 (19:15 +0000)
committerdan <dan@noemail.net>
Wed, 16 Jul 2014 19:15:57 +0000 (19:15 +0000)
FossilOrigin-Name: 1e2a7ba0889093416455f488fca893eaeb195d45

ext/fts5/fts5.c
ext/fts5/fts5.h [new file with mode: 0644]
ext/fts5/fts5Int.h
ext/fts5/fts5_aux.c [new file with mode: 0644]
ext/fts5/fts5_buffer.c
ext/fts5/fts5_expr.c
ext/fts5/fts5parse.y [new file with mode: 0644]
main.mk
manifest
manifest.uuid
test/fts5ac.test

index ba9117c527c8fb81380a0fb452aa77d6e801842b..1278ab11f773a950f792ea232156628a1d909f10 100644 (file)
 
 typedef struct Fts5Table Fts5Table;
 typedef struct Fts5Cursor Fts5Cursor;
+typedef struct Fts5Global Fts5Global;
+typedef struct Fts5Auxiliary Fts5Auxiliary;
+
+/*
+** A single object of this type is allocated when the FTS5 module is 
+** registered with a database handle. It is used to store pointers to
+** all registered FTS5 extensions - tokenizers and auxiliary functions.
+*/
+struct Fts5Global {
+  sqlite3 *db;                    /* Associated database connection */ 
+  i64 iNextId;                    /* Used to allocate unique cursor ids */
+  Fts5Auxiliary *pAux;            /* First in list of all aux. functions */
+  Fts5Cursor *pCsr;               /* First in list of all open cursors */
+};
+
+/*
+** Each auxiliary function registered with the FTS5 module is represented
+** by an object of the following type. All such objects are stored as part
+** of the Fts5Global.pAux list.
+*/
+struct Fts5Auxiliary {
+  Fts5Global *pGlobal;            /* Global context for this function */
+  char *zFunc;                    /* Function name (nul-terminated) */
+  void *pUserData;                /* User-data pointer */
+  fts5_extension_function xFunc;  /* Callback function */
+  void (*xDestroy)(void*);        /* Destructor function */
+  Fts5Auxiliary *pNext;           /* Next registered auxiliary function */
+};
 
 /*
 ** Virtual-table object.
@@ -26,6 +54,12 @@ struct Fts5Table {
   Fts5Config *pConfig;            /* Virtual table configuration */
   Fts5Index *pIndex;              /* Full-text index */
   Fts5Storage *pStorage;          /* Document store */
+  Fts5Global *pGlobal;            /* Global (connection wide) data */
+};
+
+struct Fts5MatchPhrase {
+  Fts5Buffer *pPoslist;           /* Pointer to current poslist */
+  int nTerm;                      /* Size of phrase in terms */
 };
 
 /*
@@ -37,7 +71,12 @@ struct Fts5Cursor {
   sqlite3_stmt *pStmt;            /* Statement used to read %_content */
   int bEof;                       /* True at EOF */
   Fts5Expr *pExpr;                /* Expression for MATCH queries */
-  int bSeekRequired;
+  int bSeekRequired;              /* True if seek is required */
+  Fts5Cursor *pNext;              /* Next cursor in Fts5Cursor.pCsr list */
+
+  /* Variables used by auxiliary functions */
+  i64 iCsrId;                     /* Cursor id */
+  Fts5Auxiliary *pAux;            /* Currently executing function */
 };
 
 /*
@@ -108,6 +147,7 @@ static int fts5InitVtab(
     }else{
       memset(pTab, 0, sizeof(Fts5Table));
       pTab->pConfig = pConfig;
+      pTab->pGlobal = (Fts5Global*)pAux;
     }
   }
 
@@ -234,11 +274,17 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
 ** Implementation of xOpen method.
 */
 static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
-  Fts5Cursor *pCsr;
-  int rc = SQLITE_OK;
+  Fts5Table *pTab = (Fts5Table*)pVTab;
+  Fts5Cursor *pCsr;               /* New cursor object */
+  int rc = SQLITE_OK;             /* Return code */
+
   pCsr = (Fts5Cursor*)sqlite3_malloc(sizeof(Fts5Cursor));
   if( pCsr ){
+    Fts5Global *pGlobal = pTab->pGlobal;
     memset(pCsr, 0, sizeof(Fts5Cursor));
+    pCsr->pNext = pGlobal->pCsr;
+    pGlobal->pCsr = pCsr;
+    pCsr->iCsrId = ++pGlobal->iNextId;
   }else{
     rc = SQLITE_NOMEM;
   }
@@ -260,11 +306,17 @@ static int fts5StmtType(int idxNum){
 static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){
   Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
   Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+  Fts5Cursor **pp;
   if( pCsr->pStmt ){
     int eStmt = fts5StmtType(pCsr->idxNum);
     sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt);
   }
   sqlite3Fts5ExprFree(pCsr->pExpr);
+
+  /* Remove the cursor from the Fts5Global.pCsr list */
+  for(pp=&pTab->pGlobal->pCsr; (*pp)!=pCsr; pp=&(*pp)->pNext);
+  *pp = pCsr->pNext;
+
   sqlite3_free(pCsr);
   return SQLITE_OK;
 }
@@ -373,22 +425,14 @@ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
   return SQLITE_OK;
 }
 
-/* 
-** This is the xColumn method, called by SQLite to request a value from
-** the row that the supplied cursor currently points to.
+/*
+** If the cursor requires seeking (bSeekRequired flag is set), seek it.
+** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise.
 */
-static int fts5ColumnMethod(
-  sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
-  sqlite3_context *pCtx,          /* Context for sqlite3_result_xxx() calls */
-  int iCol                        /* Index of column to read value from */
-){
-  Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
-  int ePlan = FTS5_PLAN(pCsr->idxNum);
+static int fts5SeekCursor(Fts5Cursor *pCsr){
   int rc = SQLITE_OK;
-  
-  assert( pCsr->bEof==0 );
   if( pCsr->bSeekRequired ){
-    assert( ePlan==FTS5_PLAN_MATCH && pCsr->pExpr );
+    assert( pCsr->pExpr );
     sqlite3_reset(pCsr->pStmt);
     sqlite3_bind_int64(pCsr->pStmt, 1, sqlite3Fts5ExprRowid(pCsr->pExpr));
     rc = sqlite3_step(pCsr->pStmt);
@@ -401,9 +445,35 @@ static int fts5ColumnMethod(
       }
     }
   }
+  return rc;
+}
 
-  if( rc==SQLITE_OK ){
-    sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
+/* 
+** This is the xColumn method, called by SQLite to request a value from
+** the row that the supplied cursor currently points to.
+*/
+static int fts5ColumnMethod(
+  sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
+  sqlite3_context *pCtx,          /* Context for sqlite3_result_xxx() calls */
+  int iCol                        /* Index of column to read value from */
+){
+  Fts5Config *pConfig = ((Fts5Table*)(pCursor->pVtab))->pConfig;
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+  int rc = SQLITE_OK;
+  
+  assert( pCsr->bEof==0 );
+
+  if( iCol==pConfig->nCol ){
+    /* User is requesting the value of the special column with the same name
+    ** as the table. Return the cursor integer id number. This value is only
+    ** useful in that it may be passed as the first argument to an FTS5
+    ** auxiliary function.  */
+    sqlite3_result_int64(pCtx, pCsr->iCsrId);
+  }else{
+    rc = fts5SeekCursor(pCsr);
+    if( rc==SQLITE_OK ){
+      sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
+    }
   }
   return rc;
 }
@@ -513,6 +583,121 @@ static int fts5RollbackMethod(sqlite3_vtab *pVtab){
   return rc;
 }
 
+static void *fts5ApiUserData(Fts5Context *pCtx){
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+  return pCsr->pAux->pUserData;
+}
+
+static int fts5ApiColumnCount(Fts5Context *pCtx){
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+  return ((Fts5Table*)(pCsr->base.pVtab))->pConfig->nCol;
+}
+
+static int fts5ApiColumnAvgSize(Fts5Context *pCtx, int iCol, int *pnToken){
+  assert( 0 );
+  return 0;
+}
+
+static int fts5ApiTokenize(
+  Fts5Context *pCtx, 
+  const char *pText, int nText, 
+  void *pUserData,
+  int (*xToken)(void*, const char*, int, int, int, int)
+){
+  assert( 0 );
+  return SQLITE_OK;
+}
+
+static int fts5ApiPhraseCount(Fts5Context *pCtx){
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+  return sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+}
+
+static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+  return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase);
+}
+
+static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+  return sqlite3Fts5ExprRowid(pCsr->pExpr);
+}
+
+static int fts5ApiColumnText(
+  Fts5Context *pCtx, 
+  int iCol, 
+  const char **pz, 
+  int *pn
+){
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+  int rc = fts5SeekCursor(pCsr);
+  if( rc==SQLITE_OK ){
+    *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol);
+    *pn = sqlite3_column_bytes(pCsr->pStmt, iCol);
+  }
+  return rc;
+}
+
+static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
+  assert( 0 );
+  return 0;
+}
+
+static int fts5ApiPoslist(
+  Fts5Context *pCtx, 
+  int iPhrase, 
+  int *pi, 
+  int *piCol, 
+  int *piOff
+){
+  Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+  const u8 *a; int n;             /* Poslist for phrase iPhrase */
+  n = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, &a);
+  return sqlite3Fts5PoslistNext(a, n, pi, piCol, piOff);
+}
+
+static void fts5ApiCallback(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  static const Fts5ExtensionApi sApi = {
+    1,                            /* iVersion */
+    fts5ApiUserData,
+    fts5ApiColumnCount,
+    fts5ApiColumnAvgSize,
+    fts5ApiTokenize,
+    fts5ApiPhraseCount,
+    fts5ApiPhraseSize,
+    fts5ApiRowid,
+    fts5ApiColumnText,
+    fts5ApiColumnSize,
+    fts5ApiPoslist,
+  };
+
+  Fts5Auxiliary *pAux;
+  Fts5Cursor *pCsr;
+  i64 iCsrId;
+
+  assert( argc>=1 );
+  pAux = (Fts5Auxiliary*)sqlite3_user_data(context);
+  iCsrId = sqlite3_value_int64(argv[0]);
+
+  for(pCsr=pAux->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){
+    if( pCsr->iCsrId==iCsrId ) break;
+  }
+  if( pCsr==0 ){
+    char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId);
+    sqlite3_result_error(context, zErr, -1);
+  }else{
+    assert( pCsr->pAux==0 );
+    pCsr->pAux = pAux;
+    pAux->xFunc(&sApi, (Fts5Context*)pCsr, context, argc-1, &argv[1]);
+    pCsr->pAux = 0;
+  }
+}
+
+
 /*
 ** This routine implements the xFindFunction method for the FTS3
 ** virtual table.
@@ -522,8 +707,19 @@ static int fts5FindFunctionMethod(
   int nArg,                       /* Number of SQL function arguments */
   const char *zName,              /* Name of SQL function */
   void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
-  void **ppArg                    /* Unused */
+  void **ppArg                    /* OUT: User data for *pxFunc */
 ){
+  Fts5Table *pTab = (Fts5Table*)pVtab;
+  Fts5Auxiliary *pAux;
+
+  for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){
+    if( sqlite3_stricmp(zName, pAux->zFunc)==0 ){
+      *pxFunc = fts5ApiCallback;
+      *ppArg = (void*)pAux;
+      return 1;
+    }
+  }
+
   /* No function of the specified name was found. Return 0. */
   return 0;
 }
@@ -567,37 +763,99 @@ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
   return SQLITE_OK;
 }
 
-static const sqlite3_module fts5Module = {
-  /* iVersion      */ 2,
-  /* xCreate       */ fts5CreateMethod,
-  /* xConnect      */ fts5ConnectMethod,
-  /* xBestIndex    */ fts5BestIndexMethod,
-  /* xDisconnect   */ fts5DisconnectMethod,
-  /* xDestroy      */ fts5DestroyMethod,
-  /* xOpen         */ fts5OpenMethod,
-  /* xClose        */ fts5CloseMethod,
-  /* xFilter       */ fts5FilterMethod,
-  /* xNext         */ fts5NextMethod,
-  /* xEof          */ fts5EofMethod,
-  /* xColumn       */ fts5ColumnMethod,
-  /* xRowid        */ fts5RowidMethod,
-  /* xUpdate       */ fts5UpdateMethod,
-  /* xBegin        */ fts5BeginMethod,
-  /* xSync         */ fts5SyncMethod,
-  /* xCommit       */ fts5CommitMethod,
-  /* xRollback     */ fts5RollbackMethod,
-  /* xFindFunction */ fts5FindFunctionMethod,
-  /* xRename       */ fts5RenameMethod,
-  /* xSavepoint    */ fts5SavepointMethod,
-  /* xRelease      */ fts5ReleaseMethod,
-  /* xRollbackTo   */ fts5RollbackToMethod,
-};
+/*
+** Register a new auxiliary function with global context pGlobal.
+*/
+int sqlite3Fts5CreateAux(
+  Fts5Global *pGlobal,            /* Global context (one per db handle) */
+  const char *zName,              /* Name of new function */
+  void *pUserData,                /* User data for aux. function */
+  fts5_extension_function xFunc,  /* Aux. function implementation */
+  void(*xDestroy)(void*)          /* Destructor for pUserData */
+){
+  int rc = sqlite3_overload_function(pGlobal->db, zName, -1);
+  if( rc==SQLITE_OK ){
+    Fts5Auxiliary *pAux;
+    int nByte;                      /* Bytes of space to allocate */
+
+    nByte = sizeof(Fts5Auxiliary) + strlen(zName) + 1;
+    pAux = (Fts5Auxiliary*)sqlite3_malloc(nByte);
+    if( pAux ){
+      memset(pAux, 0, nByte);
+      pAux->zFunc = (char*)&pAux[1];
+      strcpy(pAux->zFunc, zName);
+      pAux->pGlobal = pGlobal;
+      pAux->pUserData = pUserData;
+      pAux->xFunc = xFunc;
+      pAux->xDestroy = xDestroy;
+      pAux->pNext = pGlobal->pAux;
+      pGlobal->pAux = pAux;
+    }else{
+      rc = SQLITE_NOMEM;
+    }
+  }
+
+  return rc;
+}
+
+static void fts5ModuleDestroy(void *pCtx){
+  Fts5Auxiliary *pAux;
+  Fts5Auxiliary *pNext;
+  Fts5Global *pGlobal = (Fts5Global*)pCtx;
+  for(pAux=pGlobal->pAux; pAux; pAux=pNext){
+    pNext = pAux->pNext;
+    if( pAux->xDestroy ){
+      pAux->xDestroy(pAux->pUserData);
+    }
+    sqlite3_free(pAux);
+  }
+  sqlite3_free(pGlobal);
+}
+
 
 int sqlite3Fts5Init(sqlite3 *db){
+  static const sqlite3_module fts5Mod = {
+    /* iVersion      */ 2,
+    /* xCreate       */ fts5CreateMethod,
+    /* xConnect      */ fts5ConnectMethod,
+    /* xBestIndex    */ fts5BestIndexMethod,
+    /* xDisconnect   */ fts5DisconnectMethod,
+    /* xDestroy      */ fts5DestroyMethod,
+    /* xOpen         */ fts5OpenMethod,
+    /* xClose        */ fts5CloseMethod,
+    /* xFilter       */ fts5FilterMethod,
+    /* xNext         */ fts5NextMethod,
+    /* xEof          */ fts5EofMethod,
+    /* xColumn       */ fts5ColumnMethod,
+    /* xRowid        */ fts5RowidMethod,
+    /* xUpdate       */ fts5UpdateMethod,
+    /* xBegin        */ fts5BeginMethod,
+    /* xSync         */ fts5SyncMethod,
+    /* xCommit       */ fts5CommitMethod,
+    /* xRollback     */ fts5RollbackMethod,
+    /* xFindFunction */ fts5FindFunctionMethod,
+    /* xRename       */ fts5RenameMethod,
+    /* xSavepoint    */ fts5SavepointMethod,
+    /* xRelease      */ fts5ReleaseMethod,
+    /* xRollbackTo   */ fts5RollbackToMethod,
+  };
+
   int rc;
-  rc = sqlite3_create_module_v2(db, "fts5", &fts5Module, 0, 0);
-  if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
-  if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(db);
+  Fts5Global *pGlobal = 0;
+  pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global));
+
+  if( pGlobal==0 ){
+    rc = SQLITE_NOMEM;
+  }else{
+    void *p = (void*)pGlobal;
+    memset(pGlobal, 0, sizeof(Fts5Global));
+    pGlobal->db = db;
+    rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
+    if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
+    if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(db);
+    if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(pGlobal);
+  }
   return rc;
 }
 
+
diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h
new file mode 100644 (file)
index 0000000..2e73630
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+**
+** Interfaces to extend FTS5. Using the interfaces defined in this file, 
+** FTS5 may be extended with:
+**
+**     * custom tokenizers, and
+**     * custom auxiliary functions.
+*/
+
+
+#ifndef _FTS5_H
+#define _FTS5_H
+
+#include "sqlite3.h"
+
+/*************************************************************************
+** CUSTOM AUXILIARY FUNCTIONS
+**
+** Virtual table implemenations may overload SQL functions by implementing
+** the sqlite3_module.xFindFunction() method.
+*/
+
+typedef struct Fts5ExtensionApi Fts5ExtensionApi;
+typedef struct Fts5Context Fts5Context;
+
+typedef void (*fts5_extension_function)(
+  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 */
+);
+
+/*
+** xColumnCount:
+**   Returns the number of columns in the FTS5 table.
+**
+** xPhraseCount:
+**   Returns the number of phrases in the current query expression.
+**
+** xPhraseSize:
+**   Returns the number of tokens in phrase iPhrase of the query. Phrases
+**   are numbered starting from zero.
+**
+** xRowid:
+**   Returns the rowid of the current row.
+**
+** xPoslist:
+**   Iterate through instances of phrase iPhrase in the current row. 
+*/
+struct Fts5ExtensionApi {
+  int iVersion;                   /* Currently always set to 1 */
+
+  void *(*xUserData)(Fts5Context*);
+
+  int (*xColumnCount)(Fts5Context*);
+  int (*xColumnAvgSize)(Fts5Context*, int iCol, int *pnToken);
+  int (*xTokenize)(Fts5Context*, 
+    const char *pText, int nText, /* Text to tokenize */
+    void *pCtx,                   /* Context passed to xToken() */
+    int (*xToken)(void*, const char*, int, int, int, int)    /* Callback */
+  );
+
+  int (*xPhraseCount)(Fts5Context*);
+  int (*xPhraseSize)(Fts5Context*, int iPhrase);
+
+  sqlite3_int64 (*xRowid)(Fts5Context*);
+  int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
+  int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
+  int (*xPoslist)(Fts5Context*, int iPhrase, int *pi, int *piCol, int *piOff);
+};
+
+/* 
+** CUSTOM AUXILIARY FUNCTIONS
+*************************************************************************/
+#endif /* _FTS5_H */
+
index 94206a849fceec404879bac5ccb3b3d57c115427..c7392b06e63cb8575be85ab497412d5d4c5c26ed 100644 (file)
@@ -14,6 +14,7 @@
 #ifndef _FTS5INT_H
 #define _FTS5INT_H
 
+#include "fts5.h"
 #include "sqliteInt.h"
 #include "fts3_tokenizer.h"
 
@@ -122,6 +123,12 @@ struct Fts5PoslistWriter {
 };
 int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
 
+int sqlite3Fts5PoslistNext(
+  const u8 *a, int n,             /* Buffer containing poslist */
+  int *pi,                        /* IN/OUT: Offset within a[] */
+  int *piCol,                     /* IN/OUT: Current column */
+  int *piOff                      /* IN/OUT: Current token offset */
+);
 
 /*
 ** End of interface to code in fts5_buffer.c.
@@ -331,6 +338,10 @@ void sqlite3Fts5ExprFree(Fts5Expr*);
 /* Called during startup to register a UDF with SQLite */
 int sqlite3Fts5ExprInit(sqlite3*);
 
+int sqlite3Fts5ExprPhraseCount(Fts5Expr*);
+int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase);
+int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **);
+
 /*******************************************
 ** The fts5_expr.c API above this point is used by the other hand-written
 ** C code in this module. The interfaces below this point are called by
@@ -373,4 +384,31 @@ void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
 ** End of interface to code in fts5_expr.c.
 **************************************************************************/
 
+
+/**************************************************************************
+** Interface to code in fts5.c. 
+*/
+typedef struct Fts5Global Fts5Global;
+
+int sqlite3Fts5CreateAux(
+    Fts5Global*, 
+    const char*, 
+    void*, 
+    fts5_extension_function, 
+    void(*)(void*)
+);
+/*
+** End of interface to code in fts5.c.
+**************************************************************************/
+
+
+/**************************************************************************
+** Interface to code in fts5_aux.c. 
+*/
+
+int sqlite3Fts5AuxInit(Fts5Global*);
+/*
+** End of interface to code in fts5_expr.c.
+**************************************************************************/
+
 #endif
diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c
new file mode 100644 (file)
index 0000000..3eea3c6
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+*/
+
+#include "fts5Int.h"
+
+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 */
+){
+  assert( 0 );
+}
+
+static void fts5TestFunction(
+  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 */
+){
+  Fts5Buffer s;                   /* Build up text to return here */
+  int nCol;                       /* Number of columns in table */
+  int nPhrase;                    /* Number of phrases in query */
+  i64 iRowid;                     /* Rowid of current row */
+  const char *zReq = 0;
+  int rc = SQLITE_OK;
+  int i;
+
+  if( nVal>=1 ){
+    zReq = (const char*)sqlite3_value_text(apVal[0]);
+  }
+
+  memset(&s, 0, sizeof(Fts5Buffer));
+
+  if( zReq==0 ){
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, "columncount ");
+  }
+  if( 0==zReq || 0==sqlite3_stricmp(zReq, "columncount") ){
+    nCol = pApi->xColumnCount(pFts);
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, "%d", nCol);
+  }
+
+  if( zReq==0 ){
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, " phrasecount ");
+  }
+  nPhrase = pApi->xPhraseCount(pFts);
+  if( 0==zReq || 0==sqlite3_stricmp(zReq, "phrasecount") ){
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, "%d", nPhrase);
+  }
+
+  if( zReq==0 ){
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, " phrasesize ");
+  }
+  if( 0==zReq || 0==sqlite3_stricmp(zReq, "phrasesize") ){
+    if( nPhrase==1 ){
+      int nSize = pApi->xPhraseSize(pFts, 0);
+      sqlite3Fts5BufferAppendPrintf(&rc, &s, "%d", nSize);
+    }else{
+      sqlite3Fts5BufferAppendPrintf(&rc, &s, "{");
+      for(i=0; i<nPhrase; i++){
+        int nSize = pApi->xPhraseSize(pFts, i);
+        sqlite3Fts5BufferAppendPrintf(&rc, &s, "%s%d", (i==0?"":" "), nSize);
+      }
+      sqlite3Fts5BufferAppendPrintf(&rc, &s, "}");
+    }
+  }
+
+  if( zReq==0 ){
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, " poslist ");
+  }
+  if( 0==zReq || 0==sqlite3_stricmp(zReq, "poslist") ){
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, "{");
+    for(i=0; i<nPhrase; i++){
+      int j = 0;
+      int iOff = 0;
+      int iCol = 0;
+      int bFirst = 1;
+      sqlite3Fts5BufferAppendPrintf(&rc, &s, "%s{", (i==0?"":" "));
+      while( 0==pApi->xPoslist(pFts, i, &j, &iCol, &iOff) ){
+        sqlite3Fts5BufferAppendPrintf(
+            &rc, &s, "%s%d.%d", (bFirst?"":" "), iCol, iOff
+        );
+        bFirst = 0;
+      }
+      sqlite3Fts5BufferAppendPrintf(&rc, &s, "}");
+    }
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, "}");
+  }
+
+  if( zReq==0 ){
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, " rowid ");
+  }
+  if( 0==zReq || 0==sqlite3_stricmp(zReq, "rowid") ){
+    iRowid = pApi->xRowid(pFts);
+    sqlite3Fts5BufferAppendPrintf(&rc, &s, "%lld", iRowid);
+  }
+
+  if( rc==SQLITE_OK ){
+    sqlite3_result_text(pCtx, (const char*)s.p, -1, SQLITE_TRANSIENT);
+  }else{
+    sqlite3_result_error_code(pCtx, rc);
+  }
+  sqlite3Fts5BufferFree(&s);
+}
+
+int sqlite3Fts5AuxInit(Fts5Global *pGlobal){
+  struct Builtin {
+    const char *zFunc;            /* Function name (nul-terminated) */
+    void *pUserData;              /* User-data pointer */
+    fts5_extension_function xFunc;/* Callback function */
+    void (*xDestroy)(void*);      /* Destructor function */
+  } aBuiltin [] = {
+    { "snippet", 0, fts5SnippetFunction, 0 },
+    { "fts5_test", 0, fts5TestFunction, 0 },
+  };
+
+  int rc = SQLITE_OK;             /* Return code */
+  int i;                          /* To iterate through builtin functions */
+
+  for(i=0; rc==SQLITE_OK && i<sizeof(aBuiltin)/sizeof(aBuiltin[0]); i++){
+    rc = sqlite3Fts5CreateAux(pGlobal, 
+        aBuiltin[i].zFunc,
+        aBuiltin[i].pUserData,
+        aBuiltin[i].xFunc,
+        aBuiltin[i].xDestroy
+    );
+  }
+
+  return rc;
+}
+
+
index 0f09b5925578a81d2fbab181ecf14374367b3ece..2afce9502068c8459931a28cc97cdf8672a105a0 100644 (file)
@@ -197,3 +197,29 @@ int sqlite3Fts5PoslistWriterAppend(
 
   return rc;
 }
+
+int sqlite3Fts5PoslistNext(
+  const u8 *a, int n,             /* Buffer containing poslist */
+  int *pi,                        /* IN/OUT: Offset within a[] */
+  int *piCol,                     /* IN/OUT: Current column */
+  int *piOff                      /* IN/OUT: Current token offset */
+){
+  int i = *pi;
+  int iVal;
+  if( i>=n ){
+    /* EOF */
+    return 1;  
+  }
+  i += getVarint32(&a[i], iVal);
+  if( iVal==1 ){
+    i += getVarint32(&a[i], iVal);
+    *piCol = iVal;
+    *piOff = 0;
+    i += getVarint32(&a[i], iVal);
+  }
+  *piOff += (iVal-2);
+  *pi = i;
+  return 0;
+}
+
+
index 4618e7d491e4176f974cb7765e4169bc3bc54e53..bcbc3745e7836045dc49f5bddb95122940543f54 100644 (file)
@@ -33,6 +33,8 @@ struct Fts5Expr {
   Fts5Index *pIndex;
   Fts5ExprNode *pRoot;
   int bAsc;
+  int nPhrase;                    /* Number of phrases in expression */
+  Fts5ExprPhrase **apPhrase;      /* Pointers to phrase objects */
 };
 
 /*
@@ -92,6 +94,8 @@ struct Fts5Parse {
   Fts5Config *pConfig;
   char *zErr;
   int rc;
+  int nPhrase;                    /* Size of apPhrase array */
+  Fts5ExprPhrase **apPhrase;      /* Array of all phrases */
   Fts5ExprNode *pExpr;            /* Result of a successful parse */
 };
 
@@ -211,9 +215,13 @@ int sqlite3Fts5ExprNew(
     }else{
       pNew->pRoot = sParse.pExpr;
       pNew->pIndex = 0;
+      pNew->apPhrase = sParse.apPhrase;
+      pNew->nPhrase = sParse.nPhrase;
+      sParse.apPhrase = 0;
     }
   }
 
+  sqlite3_free(sParse.apPhrase);
   *pzErr = sParse.zErr;
   return sParse.rc;
 }
@@ -236,6 +244,7 @@ void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){
 void sqlite3Fts5ExprFree(Fts5Expr *p){
   if( p ){
     sqlite3Fts5ParseNodeFree(p->pRoot);
+    sqlite3_free(p->apPhrase);
     sqlite3_free(p);
   }
 }
@@ -959,6 +968,17 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
   int rc;                         /* Tokenize return code */
   char *z = 0;
 
+  if( pPhrase==0 ){
+    if( (pParse->nPhrase % 8)==0 ){
+      int nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
+      Fts5ExprPhrase **apNew;
+      apNew = (Fts5ExprPhrase**)sqlite3_realloc(pParse->apPhrase, nByte);
+      if( apNew==0 ) return 0;
+      pParse->apPhrase = apNew;
+    }
+    pParse->nPhrase++;
+  }
+
   pParse->rc = fts5ParseStringFromToken(pToken, &z);
   if( z==0 ) return 0;
   sqlite3Fts5Dequote(z);
@@ -974,6 +994,8 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
     sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
   }
 
+
+  pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase;
   sqlite3_free(z);
   return sCtx.pPhrase;
 }
@@ -1354,3 +1376,33 @@ int sqlite3Fts5ExprInit(sqlite3 *db){
   return rc;
 }
 
+/*
+** Return the number of phrases in expression pExpr.
+*/
+int sqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){
+  return pExpr->nPhrase;
+}
+
+/*
+** Return the number of terms in the iPhrase'th phrase in pExpr.
+*/
+int sqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){
+  if( iPhrase<0 || iPhrase>=pExpr->nPhrase ) return 0;
+  return pExpr->apPhrase[iPhrase]->nTerm;
+}
+
+/*
+** This function is used to access the current position list for phrase
+** iPhrase.
+*/
+int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){
+  if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){
+    *pa = 0;
+    return 0;
+  }else{
+    Fts5ExprPhrase *pPhrase = pExpr->apPhrase[iPhrase];
+    *pa = pPhrase->poslist.p;
+    return pPhrase->poslist.n;
+  }
+}
+
diff --git a/ext/fts5/fts5parse.y b/ext/fts5/fts5parse.y
new file mode 100644 (file)
index 0000000..ec52bdb
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+**
+*/
+
+
+// All token codes are small integers with #defines that begin with "TK_"
+%token_prefix FTS5_
+
+// The type of the data attached to each token is Token.  This is also the
+// default type for non-terminals.
+//
+%token_type {Fts5Token}
+%default_type {Fts5Token}
+
+// The generated parser function takes a 4th argument as follows:
+%extra_argument {Fts5Parse *pParse}
+
+// This code runs whenever there is a syntax error
+//
+%syntax_error {
+  sqlite3Fts5ParseError(
+    pParse, "fts5: syntax error near \"%.*s\"",TOKEN.n,TOKEN.p
+  );
+}
+%stack_overflow {
+  assert( 0 );
+}
+
+// The name of the generated procedure that implements the parser
+// is as follows:
+%name sqlite3Fts5Parser
+
+// The following text is included near the beginning of the C source
+// code file that implements the parser.
+//
+%include {
+#include "fts5Int.h"
+#include "fts5parse.h"
+
+/*
+** Disable all error recovery processing in the parser push-down
+** automaton.
+*/
+#define YYNOERRORRECOVERY 1
+
+/*
+** Make yytestcase() the same as testcase()
+*/
+#define yytestcase(X) testcase(X)
+
+} // end %include
+
+%left OR.
+%left AND.
+%left NOT.
+%left COLON.
+
+input ::= expr(X). { sqlite3Fts5ParseFinished(pParse, X); }
+
+%type cnearset    {Fts5ExprNode*}
+%type expr        {Fts5ExprNode*}
+%type exprlist    {Fts5ExprNode*}
+%destructor cnearset { sqlite3Fts5ParseNodeFree($$); }
+%destructor expr     { sqlite3Fts5ParseNodeFree($$); }
+%destructor exprlist { sqlite3Fts5ParseNodeFree($$); }
+
+expr(A) ::= expr(X) AND expr(Y). {
+  A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
+}
+expr(A) ::= expr(X) OR expr(Y). {
+  A = sqlite3Fts5ParseNode(pParse, FTS5_OR, X, Y, 0);
+}
+expr(A) ::= expr(X) NOT expr(Y). {
+  A = sqlite3Fts5ParseNode(pParse, FTS5_NOT, X, Y, 0);
+}
+
+expr(A) ::= LP expr(X) RP. {A = X;}
+expr(A) ::= exprlist(X).   {A = X;}
+
+exprlist(A) ::= cnearset(X). {A = X;}
+exprlist(A) ::= exprlist(X) cnearset(Y). {
+  A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
+}
+
+cnearset(A) ::= nearset(X). { 
+  A = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, X); 
+}
+cnearset(A) ::= STRING(X) COLON nearset(Y). { 
+  sqlite3Fts5ParseSetColumn(pParse, Y, &X);
+  A = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, Y); 
+}
+
+%type nearset     {Fts5ExprNearset*}
+%type nearphrases {Fts5ExprNearset*}
+%destructor nearset { sqlite3Fts5ParseNearsetFree($$); }
+%destructor nearphrases { sqlite3Fts5ParseNearsetFree($$); }
+
+nearset(A) ::= phrase(X). { A = sqlite3Fts5ParseNearset(pParse, 0, X); }
+nearset(A) ::= STRING(X) LP nearphrases(Y) neardist_opt(Z) RP. {
+  sqlite3Fts5ParseNear(pParse, &X);
+  sqlite3Fts5ParseSetDistance(pParse, Y, &Z);
+  A = Y;
+}
+
+nearphrases(A) ::= phrase(X). { 
+  A = sqlite3Fts5ParseNearset(pParse, 0, X); 
+}
+nearphrases(A) ::= nearphrases(X) phrase(Y). {
+  A = sqlite3Fts5ParseNearset(pParse, X, Y);
+}
+
+/*
+** The optional ", <integer>" at the end of the NEAR() arguments.
+*/
+neardist_opt(A) ::= . { A.p = 0; A.n = 0; }
+neardist_opt(A) ::= COMMA STRING(X). { A = X; }
+
+/*
+** A phrase. A set of primitives connected by "+" operators. Examples:
+**
+**     "the" + "quick brown" + fo *
+**     "the quick brown fo" *
+**     the+quick+brown+fo*
+*/
+%type phrase {Fts5ExprPhrase*}
+%destructor phrase { sqlite3Fts5ParsePhraseFree($$); }
+
+phrase(A) ::= phrase(X) PLUS STRING(Y) star_opt(Z). { 
+  A = sqlite3Fts5ParseTerm(pParse, X, &Y, Z);
+}
+phrase(A) ::= STRING(Y) star_opt(Z). { 
+  A = sqlite3Fts5ParseTerm(pParse, 0, &Y, Z);
+}
+
+/*
+** Optional "*" character.
+*/
+%type star_opt {int}
+
+star_opt(A) ::= STAR. { A = 1; }
+star_opt(A) ::= . { A = 0; }
+
+
+
+
diff --git a/main.mk b/main.mk
index 953d63e39f7f7b05fd371e151147d8a259852a0e..a1582fb6dcf2513e0678257d8e8d56599c9594c3 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -73,6 +73,7 @@ LIBOBJ+= vdbe.o parse.o \
         vdbetrace.o wal.o walker.o where.o utf.o vtab.o
 
 LIBOBJ += fts5.o
+LIBOBJ += fts5_aux.o
 LIBOBJ += fts5_buffer.o
 LIBOBJ += fts5_config.o
 LIBOBJ += fts5_expr.o
@@ -385,7 +386,8 @@ EXTHDR += \
 EXTHDR += \
   $(TOP)/ext/icu/sqliteicu.h
 EXTHDR += \
-  $(TOP)/ext/fts5/fts5Int.h
+  $(TOP)/ext/fts5/fts5Int.h  \
+  $(TOP)/ext/fts5/fts5.h 
 
 # This is the default Makefile target.  The objects listed here
 # are what get build when you type just "make" with no arguments.
@@ -573,6 +575,9 @@ rtree.o:    $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
 
 # FTS5 things
 #
+fts5_aux.o:    $(TOP)/ext/fts5/fts5_aux.c $(HDR) $(EXTHDR)
+       $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_aux.c
+
 fts5_buffer.o: $(TOP)/ext/fts5/fts5_buffer.c $(HDR) $(EXTHDR)
        $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_buffer.c
 
index f9998628d2769c2648b90c632aa13b530989f205..16bcb4aad6e048df001069c4c34dd5ef8ba1589a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Support\s"ORDER\sBY\srowid\sASC".
-D 2014-07-10T20:21:12.482
+C Begin\sadding\sinterface\sfor\sauxiliary\sfunctions.
+D 2014-07-16T19:15:57.212
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in b03432313a3aad96c706f8164fb9f5307eaf19f5
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -103,13 +103,16 @@ F ext/fts3/tool/fts3view.c 6cfc5b67a5f0e09c0d698f9fd012c784bfaa9197
 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 1af3184dd9c0e5c1686f71202d6b6cac8f225f05
-F ext/fts5/fts5Int.h bb716a6e6a376a7c8211e55e5577c6c020d176c2
-F ext/fts5/fts5_buffer.c 83b463a179ad4348fa87796fce78b0e4ef6b898a
+F ext/fts5/fts5.c 20bcb1e10756c72b550947236960edf96929ca2f
+F ext/fts5/fts5.h cda3b9d73e6ffa6d0cd35b7da6b808bf3a1ada32
+F ext/fts5/fts5Int.h 2d4c1e1ebdf18278776fcd8a64233ff3c04ea51f
+F ext/fts5/fts5_aux.c 53ab338c6a469dc67e7a6bd8685ce727beee8403
+F ext/fts5/fts5_buffer.c b7aa6cdf4a63642fcc12359cedc4be748ca400cc
 F ext/fts5/fts5_config.c 94f1b4cb4de6a7cd5780c14adb0198e289df8cef
-F ext/fts5/fts5_expr.c 0dc31b06d444cad097bec05699797590729d2638
+F ext/fts5/fts5_expr.c e4e4e6d32beff1ab0d076f8fbf5cf3b2241d4dbc
 F ext/fts5/fts5_index.c 9ff3008e903aa9077b0a7a7aa76ab6080eb07a36
 F ext/fts5/fts5_storage.c 7848d8f8528d798bba159900ea310a6d4a279da8
+F ext/fts5/fts5parse.y 777da8e5819f75c217982c79c29d014c293acac9
 F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
 F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
 F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
@@ -153,7 +156,7 @@ F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk c5524f888196af43a9b5dfae878205044f549dbf
+F main.mk cffc02a30f1af82d35410674f70a0286587add81
 F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
 F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5
 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@ -594,7 +597,7 @@ F test/fts4noti.test 524807f0c36d49deea7920cdd4cd687408b58849
 F test/fts4unicode.test 01ec3fe2a7c3cfff3b4c0581b83caa11b33efa36
 F test/fts5aa.test c8d3b9694f6b2864161c7437408464a535d19343
 F test/fts5ab.test dc04ed48cf93ca957d174406e6c192f2ff4f3397
-F test/fts5ac.test 28203ba2334030514d7a6271c5fb1ba3cbc219b1
+F test/fts5ac.test 398a2d8d9576e0579a0f0955fabd8410ace969e4
 F test/fts5ad.test 2ed38bbc865678cb2905247120d02ebba7f20e07
 F test/fts5ea.test ff43b40f8879ba50b82def70f2ab67c195d1a1d4
 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d
@@ -1191,7 +1194,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 75ebd3cd5904a4f89f7f3a9b25d32b2a42a31310
-R 5a76d2f2fc0d7fcaa9a60fabc7fdb146
+P b96b5e166990e4ec363b24f66e04cfa5f00f6342
+R ff6cbab233811678a295f9640beec5d4
 U dan
-Z 870004bd588f44c77d8063239acbea69
+Z 5e7398b52fb14b2e0bc342aa9223ff97
index e8c5df20558ef1ddcea79c48c7fea02566148874..7b1ee2441d9fe625ef109d932f902c84b8de3994 100644 (file)
@@ -1 +1 @@
-b96b5e166990e4ec363b24f66e04cfa5f00f6342
\ No newline at end of file
+1e2a7ba0889093416455f488fca893eaeb195d45
\ No newline at end of file
index ddd27481a13e4ee451b2ee7785590238fd3466b4..66cce1342bf07e11059dff5a5e0a7d27c57d355a 100644 (file)
@@ -138,9 +138,7 @@ do_test 1.1 {
 } {}
 
 proc phrasematch {phrase value} {
-  if {[string first $phrase $value]>=0} {
-    return 1
-  }
+  if {[string first $phrase $value]>=0} { return 1 }
   return 0
 }
 
@@ -177,9 +175,9 @@ proc nearmatch {nNear phraselist value} {
 
 # Usage:
 #
-#   nearset aCol ?-near N? ?-col C? -- phrase1 phrase2...
+#   poslist aCol ?-near N? ?-col C? -- phrase1 phrase2...
 #
-proc nearset {aCol args} {
+proc poslist {aCol args} {
   set O(-near) 10
   set O(-col)  -1
 
@@ -191,44 +189,121 @@ proc nearset {aCol args} {
     set O($k) $v
   }
 
+  # Set phraselist to be a list of phrases. nPhrase its length.
   set phraselist [lrange $args [expr $nOpt+1] end]
+  set nPhrase [llength $phraselist]
+
+  for {set j 0} {$j < [llength $aCol]} {incr j} {
+    for {set i 0} {$i < $nPhrase} {incr i} { 
+      set A($j,$i) [list]
+    }
+  }
 
-  set bMatch 0
   set iCol -1
   foreach col $aCol {
     incr iCol
     if {$O(-col)>=0 && $O(-col)!=$iCol} continue
 
-    if {[nearmatch $O(-near) $phraselist $col]} {
-      set bMatch 1
-      break
+    set nToken [llength $col]
+
+    set iFL [expr $O(-near) >= $nToken ? $nToken - 1 : $O(-near)]
+    for { } {$iFL < $nToken} {incr iFL} {
+      for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+        set B($iPhrase) [list]
+      }
+      
+      for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+        set p [lindex $phraselist $iPhrase]
+        set nPm1 [expr {[llength $p] - 1}]
+        set iFirst [expr $iFL - $O(-near) - [llength $p]]
+
+        for {set i $iFirst} {$i <= $iFL} {incr i} {
+          if {[lrange $col $i [expr $i+$nPm1]] == $p} { lappend B($iPhrase) $i }
+        }
+        if {[llength $B($iPhrase)] == 0} break
+      }
+
+      if {$iPhrase==$nPhrase} {
+        for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+          set A($iCol,$iPhrase) [concat $A($iCol,$iPhrase) $B($iPhrase)]
+          set A($iCol,$iPhrase) [lsort -integer -uniq $A($iCol,$iPhrase)]
+        }
+      }
     }
   }
 
-  return $bMatch
+  set res [list]
+  for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+    set plist [list]
+    for {set iCol 0} {$iCol < [llength $aCol]} {incr iCol} {
+      foreach a $A($iCol,$iPhrase) {
+        lappend plist "$iCol.$a"
+      }
+    }
+    lappend res $plist
+  }
+
+  return $res
 }
 
-proc matchdata {expr {bAsc 0}} {
+# Usage:
+#
+#   nearset aCol ?-near N? ?-col C? -- phrase1 phrase2...
+#
+proc nearset {args} {
+  set plist [poslist {*}$args]
+  return [expr [llength [lindex $plist 0]]>0]
+}
+
+# Argument $expr is an FTS5 match expression designed to be executed against
+# an FTS5 table with the following schema:
+# 
+#   CREATE VIRTUAL TABLE xy USING fts5(x, y);
+#
+# Assuming the table contains the same records as stored int the global 
+# $::data array (see above), this function returns a list containing one
+# element for each match in the dataset. The elements are themselves lists
+# formatted as follows:
+#
+#   <rowid> {<phrase 0 matches> <phrase 1 matches>...}
+#
+# where each <phrase X matches> element is a list of phrase matches in the
+# same form as returned by auxiliary scalar function fts5_test().
+#
+proc matchdata {bPos expr {bAsc 0}} {
+
   set tclexpr [db one {SELECT fts5_expr_tcl($expr, 'nearset $cols', 'x', 'y')}]
   set res [list]
+
+  #puts $tclexpr
   foreach {id x y} $::data {
     set cols [list $x $y]
     if $tclexpr {
-      lappend res $id
+      if {$bPos} {
+        set N [regexp -all -inline {\[nearset [^\]]*\]} $tclexpr]
+        set rowres [list]
+        foreach phrase $N {
+          set cmd "poslist [string range $phrase 9 end-1]"
+          lappend rowres [eval $cmd]
+        }
+        if {[string first "\{" $rowres]<0} { set rowres "{{$rowres}}" }
+        lappend res [list $id $rowres]
+      } else {
+        lappend res $id
+      }
     }
   }
 
-  # puts $tclexpr
-
   if {$bAsc} {
-    set res [lsort -integer -increasing $res]
+    set res [lsort -integer -increasing -index 0 $res]
   } else {
-    set res [lsort -integer -decreasing $res]
+    set res [lsort -integer -decreasing -index 0 $res]
   }
 
-  return $res
+  return [concat {*}$res]
 }
 
+
 foreach {tn phrase} {
   1 "o"
   2 "b q"
@@ -243,10 +318,10 @@ foreach {tn phrase} {
 } {
 
   set expr "\"$phrase\""
-  set res [matchdata $expr]
+  set res [matchdata $expr]
 
   do_execsql_test 1.2.$tn.[llength $res] { 
-    SELECT rowid FROM xx WHERE xx match $expr
+    SELECT rowid, fts5_test(xx, 'poslist') FROM xx WHERE xx match $expr
   } $res
 }
 
@@ -261,6 +336,10 @@ do_test 2.5 { nearmatch 400 {a b} {a x x b} } 1
 do_test 2.6 { nearmatch 0 {a} {a x x b} } 1
 do_test 2.7 { nearmatch 0 {b} {a x x b} } 1
 
+do_test 2.8  { poslist {{a b c}} -- a } {0.0}
+do_test 2.9  { poslist {{a b c}} -- c } {0.2}
+
+
 foreach {tn expr tclexpr} {
   1 {a b} {[N $x -- {a}] && [N $x -- {b}]}
 } {
@@ -275,7 +354,6 @@ foreach {bAsc sql} {
 } {
   foreach {tn expr} {
     0.1 x
-
     1 { NEAR(r c) }
     2 { NEAR(r c, 5) }
     3 { NEAR(r c, 3) }
@@ -297,12 +375,10 @@ foreach {bAsc sql} {
     18 { c NOT (b OR a) }
     19 { c NOT b OR a AND d }
   } {
-    set res [matchdata $expr $bAsc]
+    set res [matchdata $expr $bAsc]
     do_execsql_test 4.$bAsc.$tn.[llength $res] $sql $res
   }
 }
 
-
-
 finish_test