| Index: third_party/sqlite/sqlite-src-3080704/ext/misc/amatch.c
|
| diff --git a/third_party/sqlite/sqlite-src-3080704/ext/misc/amatch.c b/third_party/sqlite/sqlite-src-3080704/ext/misc/amatch.c
|
| deleted file mode 100644
|
| index d869dbd8d1394af672f96a5508d9e5ab91a2934f..0000000000000000000000000000000000000000
|
| --- a/third_party/sqlite/sqlite-src-3080704/ext/misc/amatch.c
|
| +++ /dev/null
|
| @@ -1,1487 +0,0 @@
|
| -/*
|
| -** 2013-03-14
|
| -**
|
| -** The author disclaims copyright to this source code. In place of
|
| -** a legal notice, here is a blessing:
|
| -**
|
| -** May you do good and not evil.
|
| -** May you find forgiveness for yourself and forgive others.
|
| -** May you share freely, never taking more than you give.
|
| -**
|
| -*************************************************************************
|
| -**
|
| -** This file contains code for a demonstration virtual table that finds
|
| -** "approximate matches" - strings from a finite set that are nearly the
|
| -** same as a single input string. The virtual table is called "amatch".
|
| -**
|
| -** A amatch virtual table is created like this:
|
| -**
|
| -** CREATE VIRTUAL TABLE f USING approximate_match(
|
| -** vocabulary_table=<tablename>, -- V
|
| -** vocabulary_word=<columnname>, -- W
|
| -** vocabulary_language=<columnname>, -- L
|
| -** edit_distances=<edit-cost-table>
|
| -** );
|
| -**
|
| -** When it is created, the new amatch table must be supplied with the
|
| -** the name of a table V and columns V.W and V.L such that
|
| -**
|
| -** SELECT W FROM V WHERE L=$language
|
| -**
|
| -** returns the allowed vocabulary for the match. If the "vocabulary_language"
|
| -** or L columnname is left unspecified or is an empty string, then no
|
| -** filtering of the vocabulary by language is performed.
|
| -**
|
| -** For efficiency, it is essential that the vocabulary table be indexed:
|
| -**
|
| -** CREATE vocab_index ON V(W)
|
| -**
|
| -** A separate edit-cost-table provides scoring information that defines
|
| -** what it means for one string to be "close" to another.
|
| -**
|
| -** The edit-cost-table must contain exactly four columns (more precisely,
|
| -** the statement "SELECT * FROM <edit-cost-table>" must return records
|
| -** that consist of four columns). It does not matter what the columns are
|
| -** named.
|
| -**
|
| -** Each row in the edit-cost-table represents a single character
|
| -** transformation going from user input to the vocabulary. The leftmost
|
| -** column of the row (column 0) contains an integer identifier of the
|
| -** language to which the transformation rule belongs (see "MULTIPLE LANGUAGES"
|
| -** below). The second column of the row (column 1) contains the input
|
| -** character or characters - the characters of user input. The third
|
| -** column contains characters as they appear in the vocabulary table.
|
| -** And the fourth column contains the integer cost of making the
|
| -** transformation. For example:
|
| -**
|
| -** CREATE TABLE f_data(iLang, cFrom, cTo, Cost);
|
| -** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '', 'a', 100);
|
| -** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'b', '', 87);
|
| -** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'o', 'oe', 38);
|
| -** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'oe', 'o', 40);
|
| -**
|
| -** The first row inserted into the edit-cost-table by the SQL script
|
| -** above indicates that the cost of having an extra 'a' in the vocabulary
|
| -** table that is missing in the user input 100. (All costs are integers.
|
| -** Overall cost must not exceed 16777216.) The second INSERT statement
|
| -** creates a rule saying that the cost of having a single letter 'b' in
|
| -** user input which is missing in the vocabulary table is 87. The third
|
| -** INSERT statement mean that the cost of matching an 'o' in user input
|
| -** against an 'oe' in the vocabulary table is 38. And so forth.
|
| -**
|
| -** The following rules are special:
|
| -**
|
| -** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '?', '', 97);
|
| -** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '', '?', 98);
|
| -** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '?', '?', 99);
|
| -**
|
| -** The '?' to '' rule is the cost of having any single character in the input
|
| -** that is not found in the vocabular. The '' to '?' rule is the cost of
|
| -** having a character in the vocabulary table that is missing from input.
|
| -** And the '?' to '?' rule is the cost of doing an arbitrary character
|
| -** substitution. These three generic rules apply across all languages.
|
| -** In other words, the iLang field is ignored for the generic substitution
|
| -** rules. If more than one cost is given for a generic substitution rule,
|
| -** then the lowest cost is used.
|
| -**
|
| -** Once it has been created, the amatch virtual table can be queried
|
| -** as follows:
|
| -**
|
| -** SELECT word, distance FROM f
|
| -** WHERE word MATCH 'abcdefg'
|
| -** AND distance<200;
|
| -**
|
| -** This query outputs the strings contained in the T(F) field that
|
| -** are close to "abcdefg" and in order of increasing distance. No string
|
| -** is output more than once. If there are multiple ways to transform the
|
| -** target string ("abcdefg") into a string in the vocabulary table then
|
| -** the lowest cost transform is the one that is returned. In this example,
|
| -** the search is limited to strings with a total distance of less than 200.
|
| -**
|
| -** For efficiency, it is important to put tight bounds on the distance.
|
| -** The time and memory space needed to perform this query is exponential
|
| -** in the maximum distance. A good rule of thumb is to limit the distance
|
| -** to no more than 1.5 or 2 times the maximum cost of any rule in the
|
| -** edit-cost-table.
|
| -**
|
| -** The amatch is a read-only table. Any attempt to DELETE, INSERT, or
|
| -** UPDATE on a amatch table will throw an error.
|
| -**
|
| -** It is important to put some kind of a limit on the amatch output. This
|
| -** can be either in the form of a LIMIT clause at the end of the query,
|
| -** or better, a "distance<NNN" constraint where NNN is some number. The
|
| -** running time and memory requirement is exponential in the value of NNN
|
| -** so you want to make sure that NNN is not too big. A value of NNN that
|
| -** is about twice the average transformation cost seems to give good results.
|
| -**
|
| -** The amatch table can be useful for tasks such as spelling correction.
|
| -** Suppose all allowed words are in table vocabulary(w). Then one would create
|
| -** an amatch virtual table like this:
|
| -**
|
| -** CREATE VIRTUAL TABLE ex1 USING amatch(
|
| -** vocabtable=vocabulary,
|
| -** vocabcolumn=w,
|
| -** edit_distances=ec1
|
| -** );
|
| -**
|
| -** Then given an input word $word, look up close spellings this way:
|
| -**
|
| -** SELECT word, distance FROM ex1
|
| -** WHERE word MATCH $word AND distance<200;
|
| -**
|
| -** MULTIPLE LANGUAGES
|
| -**
|
| -** Normally, the "iLang" value associated with all character transformations
|
| -** in the edit-cost-table is zero. However, if required, the amatch
|
| -** virtual table allows multiple languages to be defined. Each query uses
|
| -** only a single iLang value. This allows, for example, a single
|
| -** amatch table to support multiple languages.
|
| -**
|
| -** By default, only the rules with iLang=0 are used. To specify an
|
| -** alternative language, a "language = ?" expression must be added to the
|
| -** WHERE clause of a SELECT, where ? is the integer identifier of the desired
|
| -** language. For example:
|
| -**
|
| -** SELECT word, distance FROM ex1
|
| -** WHERE word MATCH $word
|
| -** AND distance<=200
|
| -** AND language=1 -- Specify use language 1 instead of 0
|
| -**
|
| -** If no "language = ?" constraint is specified in the WHERE clause, language
|
| -** 0 is used.
|
| -**
|
| -** LIMITS
|
| -**
|
| -** The maximum language number is 2147483647. The maximum length of either
|
| -** of the strings in the second or third column of the amatch data table
|
| -** is 50 bytes. The maximum cost on a rule is 1000.
|
| -*/
|
| -#include "sqlite3ext.h"
|
| -SQLITE_EXTENSION_INIT1
|
| -#include <stdlib.h>
|
| -#include <string.h>
|
| -#include <assert.h>
|
| -#include <stdio.h>
|
| -#include <ctype.h>
|
| -
|
| -#ifndef SQLITE_OMIT_VIRTUALTABLE
|
| -
|
| -/*
|
| -** Forward declaration of objects used by this implementation
|
| -*/
|
| -typedef struct amatch_vtab amatch_vtab;
|
| -typedef struct amatch_cursor amatch_cursor;
|
| -typedef struct amatch_rule amatch_rule;
|
| -typedef struct amatch_word amatch_word;
|
| -typedef struct amatch_avl amatch_avl;
|
| -
|
| -
|
| -/*****************************************************************************
|
| -** AVL Tree implementation
|
| -*/
|
| -/*
|
| -** Objects that want to be members of the AVL tree should embedded an
|
| -** instance of this structure.
|
| -*/
|
| -struct amatch_avl {
|
| - amatch_word *pWord; /* Points to the object being stored in the tree */
|
| - char *zKey; /* Key. zero-terminated string. Must be unique */
|
| - amatch_avl *pBefore; /* Other elements less than zKey */
|
| - amatch_avl *pAfter; /* Other elements greater than zKey */
|
| - amatch_avl *pUp; /* Parent element */
|
| - short int height; /* Height of this node. Leaf==1 */
|
| - short int imbalance; /* Height difference between pBefore and pAfter */
|
| -};
|
| -
|
| -/* Recompute the amatch_avl.height and amatch_avl.imbalance fields for p.
|
| -** Assume that the children of p have correct heights.
|
| -*/
|
| -static void amatchAvlRecomputeHeight(amatch_avl *p){
|
| - short int hBefore = p->pBefore ? p->pBefore->height : 0;
|
| - short int hAfter = p->pAfter ? p->pAfter->height : 0;
|
| - p->imbalance = hBefore - hAfter; /* -: pAfter higher. +: pBefore higher */
|
| - p->height = (hBefore>hAfter ? hBefore : hAfter)+1;
|
| -}
|
| -
|
| -/*
|
| -** P B
|
| -** / \ / \
|
| -** B Z ==> X P
|
| -** / \ / \
|
| -** X Y Y Z
|
| -**
|
| -*/
|
| -static amatch_avl *amatchAvlRotateBefore(amatch_avl *pP){
|
| - amatch_avl *pB = pP->pBefore;
|
| - amatch_avl *pY = pB->pAfter;
|
| - pB->pUp = pP->pUp;
|
| - pB->pAfter = pP;
|
| - pP->pUp = pB;
|
| - pP->pBefore = pY;
|
| - if( pY ) pY->pUp = pP;
|
| - amatchAvlRecomputeHeight(pP);
|
| - amatchAvlRecomputeHeight(pB);
|
| - return pB;
|
| -}
|
| -
|
| -/*
|
| -** P A
|
| -** / \ / \
|
| -** X A ==> P Z
|
| -** / \ / \
|
| -** Y Z X Y
|
| -**
|
| -*/
|
| -static amatch_avl *amatchAvlRotateAfter(amatch_avl *pP){
|
| - amatch_avl *pA = pP->pAfter;
|
| - amatch_avl *pY = pA->pBefore;
|
| - pA->pUp = pP->pUp;
|
| - pA->pBefore = pP;
|
| - pP->pUp = pA;
|
| - pP->pAfter = pY;
|
| - if( pY ) pY->pUp = pP;
|
| - amatchAvlRecomputeHeight(pP);
|
| - amatchAvlRecomputeHeight(pA);
|
| - return pA;
|
| -}
|
| -
|
| -/*
|
| -** Return a pointer to the pBefore or pAfter pointer in the parent
|
| -** of p that points to p. Or if p is the root node, return pp.
|
| -*/
|
| -static amatch_avl **amatchAvlFromPtr(amatch_avl *p, amatch_avl **pp){
|
| - amatch_avl *pUp = p->pUp;
|
| - if( pUp==0 ) return pp;
|
| - if( pUp->pAfter==p ) return &pUp->pAfter;
|
| - return &pUp->pBefore;
|
| -}
|
| -
|
| -/*
|
| -** Rebalance all nodes starting with p and working up to the root.
|
| -** Return the new root.
|
| -*/
|
| -static amatch_avl *amatchAvlBalance(amatch_avl *p){
|
| - amatch_avl *pTop = p;
|
| - amatch_avl **pp;
|
| - while( p ){
|
| - amatchAvlRecomputeHeight(p);
|
| - if( p->imbalance>=2 ){
|
| - amatch_avl *pB = p->pBefore;
|
| - if( pB->imbalance<0 ) p->pBefore = amatchAvlRotateAfter(pB);
|
| - pp = amatchAvlFromPtr(p,&p);
|
| - p = *pp = amatchAvlRotateBefore(p);
|
| - }else if( p->imbalance<=(-2) ){
|
| - amatch_avl *pA = p->pAfter;
|
| - if( pA->imbalance>0 ) p->pAfter = amatchAvlRotateBefore(pA);
|
| - pp = amatchAvlFromPtr(p,&p);
|
| - p = *pp = amatchAvlRotateAfter(p);
|
| - }
|
| - pTop = p;
|
| - p = p->pUp;
|
| - }
|
| - return pTop;
|
| -}
|
| -
|
| -/* Search the tree rooted at p for an entry with zKey. Return a pointer
|
| -** to the entry or return NULL.
|
| -*/
|
| -static amatch_avl *amatchAvlSearch(amatch_avl *p, const char *zKey){
|
| - int c;
|
| - while( p && (c = strcmp(zKey, p->zKey))!=0 ){
|
| - p = (c<0) ? p->pBefore : p->pAfter;
|
| - }
|
| - return p;
|
| -}
|
| -
|
| -/* Find the first node (the one with the smallest key).
|
| -*/
|
| -static amatch_avl *amatchAvlFirst(amatch_avl *p){
|
| - if( p ) while( p->pBefore ) p = p->pBefore;
|
| - return p;
|
| -}
|
| -
|
| -#if 0 /* NOT USED */
|
| -/* Return the node with the next larger key after p.
|
| -*/
|
| -static amatch_avl *amatchAvlNext(amatch_avl *p){
|
| - amatch_avl *pPrev = 0;
|
| - while( p && p->pAfter==pPrev ){
|
| - pPrev = p;
|
| - p = p->pUp;
|
| - }
|
| - if( p && pPrev==0 ){
|
| - p = amatchAvlFirst(p->pAfter);
|
| - }
|
| - return p;
|
| -}
|
| -#endif
|
| -
|
| -#if 0 /* NOT USED */
|
| -/* Verify AVL tree integrity
|
| -*/
|
| -static int amatchAvlIntegrity(amatch_avl *pHead){
|
| - amatch_avl *p;
|
| - if( pHead==0 ) return 1;
|
| - if( (p = pHead->pBefore)!=0 ){
|
| - assert( p->pUp==pHead );
|
| - assert( amatchAvlIntegrity(p) );
|
| - assert( strcmp(p->zKey, pHead->zKey)<0 );
|
| - while( p->pAfter ) p = p->pAfter;
|
| - assert( strcmp(p->zKey, pHead->zKey)<0 );
|
| - }
|
| - if( (p = pHead->pAfter)!=0 ){
|
| - assert( p->pUp==pHead );
|
| - assert( amatchAvlIntegrity(p) );
|
| - assert( strcmp(p->zKey, pHead->zKey)>0 );
|
| - p = amatchAvlFirst(p);
|
| - assert( strcmp(p->zKey, pHead->zKey)>0 );
|
| - }
|
| - return 1;
|
| -}
|
| -static int amatchAvlIntegrity2(amatch_avl *pHead){
|
| - amatch_avl *p, *pNext;
|
| - for(p=amatchAvlFirst(pHead); p; p=pNext){
|
| - pNext = amatchAvlNext(p);
|
| - if( pNext==0 ) break;
|
| - assert( strcmp(p->zKey, pNext->zKey)<0 );
|
| - }
|
| - return 1;
|
| -}
|
| -#endif
|
| -
|
| -/* Insert a new node pNew. Return NULL on success. If the key is not
|
| -** unique, then do not perform the insert but instead leave pNew unchanged
|
| -** and return a pointer to an existing node with the same key.
|
| -*/
|
| -static amatch_avl *amatchAvlInsert(amatch_avl **ppHead, amatch_avl *pNew){
|
| - int c;
|
| - amatch_avl *p = *ppHead;
|
| - if( p==0 ){
|
| - p = pNew;
|
| - pNew->pUp = 0;
|
| - }else{
|
| - while( p ){
|
| - c = strcmp(pNew->zKey, p->zKey);
|
| - if( c<0 ){
|
| - if( p->pBefore ){
|
| - p = p->pBefore;
|
| - }else{
|
| - p->pBefore = pNew;
|
| - pNew->pUp = p;
|
| - break;
|
| - }
|
| - }else if( c>0 ){
|
| - if( p->pAfter ){
|
| - p = p->pAfter;
|
| - }else{
|
| - p->pAfter = pNew;
|
| - pNew->pUp = p;
|
| - break;
|
| - }
|
| - }else{
|
| - return p;
|
| - }
|
| - }
|
| - }
|
| - pNew->pBefore = 0;
|
| - pNew->pAfter = 0;
|
| - pNew->height = 1;
|
| - pNew->imbalance = 0;
|
| - *ppHead = amatchAvlBalance(p);
|
| - /* assert( amatchAvlIntegrity(*ppHead) ); */
|
| - /* assert( amatchAvlIntegrity2(*ppHead) ); */
|
| - return 0;
|
| -}
|
| -
|
| -/* Remove node pOld from the tree. pOld must be an element of the tree or
|
| -** the AVL tree will become corrupt.
|
| -*/
|
| -static void amatchAvlRemove(amatch_avl **ppHead, amatch_avl *pOld){
|
| - amatch_avl **ppParent;
|
| - amatch_avl *pBalance;
|
| - /* assert( amatchAvlSearch(*ppHead, pOld->zKey)==pOld ); */
|
| - ppParent = amatchAvlFromPtr(pOld, ppHead);
|
| - if( pOld->pBefore==0 && pOld->pAfter==0 ){
|
| - *ppParent = 0;
|
| - pBalance = pOld->pUp;
|
| - }else if( pOld->pBefore && pOld->pAfter ){
|
| - amatch_avl *pX, *pY;
|
| - pX = amatchAvlFirst(pOld->pAfter);
|
| - *amatchAvlFromPtr(pX, 0) = pX->pAfter;
|
| - if( pX->pAfter ) pX->pAfter->pUp = pX->pUp;
|
| - pBalance = pX->pUp;
|
| - pX->pAfter = pOld->pAfter;
|
| - if( pX->pAfter ){
|
| - pX->pAfter->pUp = pX;
|
| - }else{
|
| - assert( pBalance==pOld );
|
| - pBalance = pX;
|
| - }
|
| - pX->pBefore = pY = pOld->pBefore;
|
| - if( pY ) pY->pUp = pX;
|
| - pX->pUp = pOld->pUp;
|
| - *ppParent = pX;
|
| - }else if( pOld->pBefore==0 ){
|
| - *ppParent = pBalance = pOld->pAfter;
|
| - pBalance->pUp = pOld->pUp;
|
| - }else if( pOld->pAfter==0 ){
|
| - *ppParent = pBalance = pOld->pBefore;
|
| - pBalance->pUp = pOld->pUp;
|
| - }
|
| - *ppHead = amatchAvlBalance(pBalance);
|
| - pOld->pUp = 0;
|
| - pOld->pBefore = 0;
|
| - pOld->pAfter = 0;
|
| - /* assert( amatchAvlIntegrity(*ppHead) ); */
|
| - /* assert( amatchAvlIntegrity2(*ppHead) ); */
|
| -}
|
| -/*
|
| -** End of the AVL Tree implementation
|
| -******************************************************************************/
|
| -
|
| -
|
| -/*
|
| -** Various types.
|
| -**
|
| -** amatch_cost is the "cost" of an edit operation.
|
| -**
|
| -** amatch_len is the length of a matching string.
|
| -**
|
| -** amatch_langid is an ruleset identifier.
|
| -*/
|
| -typedef int amatch_cost;
|
| -typedef signed char amatch_len;
|
| -typedef int amatch_langid;
|
| -
|
| -/*
|
| -** Limits
|
| -*/
|
| -#define AMATCH_MX_LENGTH 50 /* Maximum length of a rule string */
|
| -#define AMATCH_MX_LANGID 2147483647 /* Maximum rule ID */
|
| -#define AMATCH_MX_COST 1000 /* Maximum single-rule cost */
|
| -
|
| -/*
|
| -** A match or partial match
|
| -*/
|
| -struct amatch_word {
|
| - amatch_word *pNext; /* Next on a list of all amatch_words */
|
| - amatch_avl sCost; /* Linkage of this node into the cost tree */
|
| - amatch_avl sWord; /* Linkage of this node into the word tree */
|
| - amatch_cost rCost; /* Cost of the match so far */
|
| - int iSeq; /* Sequence number */
|
| - char zCost[10]; /* Cost key (text rendering of rCost) */
|
| - short int nMatch; /* Input characters matched */
|
| - char zWord[4]; /* Text of the word. Extra space appended as needed */
|
| -};
|
| -
|
| -/*
|
| -** Each transformation rule is stored as an instance of this object.
|
| -** All rules are kept on a linked list sorted by rCost.
|
| -*/
|
| -struct amatch_rule {
|
| - amatch_rule *pNext; /* Next rule in order of increasing rCost */
|
| - char *zFrom; /* Transform from (a string from user input) */
|
| - amatch_cost rCost; /* Cost of this transformation */
|
| - amatch_langid iLang; /* The langauge to which this rule belongs */
|
| - amatch_len nFrom, nTo; /* Length of the zFrom and zTo strings */
|
| - char zTo[4]; /* Tranform to V.W value (extra space appended) */
|
| -};
|
| -
|
| -/*
|
| -** A amatch virtual-table object
|
| -*/
|
| -struct amatch_vtab {
|
| - sqlite3_vtab base; /* Base class - must be first */
|
| - char *zClassName; /* Name of this class. Default: "amatch" */
|
| - char *zDb; /* Name of database. (ex: "main") */
|
| - char *zSelf; /* Name of this virtual table */
|
| - char *zCostTab; /* Name of edit-cost-table */
|
| - char *zVocabTab; /* Name of vocabulary table */
|
| - char *zVocabWord; /* Name of vocabulary table word column */
|
| - char *zVocabLang; /* Name of vocabulary table language column */
|
| - amatch_rule *pRule; /* All active rules in this amatch */
|
| - amatch_cost rIns; /* Generic insertion cost '' -> ? */
|
| - amatch_cost rDel; /* Generic deletion cost ? -> '' */
|
| - amatch_cost rSub; /* Generic substitution cost ? -> ? */
|
| - sqlite3 *db; /* The database connection */
|
| - sqlite3_stmt *pVCheck; /* Query to check zVocabTab */
|
| - int nCursor; /* Number of active cursors */
|
| -};
|
| -
|
| -/* A amatch cursor object */
|
| -struct amatch_cursor {
|
| - sqlite3_vtab_cursor base; /* Base class - must be first */
|
| - sqlite3_int64 iRowid; /* The rowid of the current word */
|
| - amatch_langid iLang; /* Use this language ID */
|
| - amatch_cost rLimit; /* Maximum cost of any term */
|
| - int nBuf; /* Space allocated for zBuf */
|
| - int oomErr; /* True following an OOM error */
|
| - int nWord; /* Number of amatch_word objects */
|
| - char *zBuf; /* Temp-use buffer space */
|
| - char *zInput; /* Input word to match against */
|
| - amatch_vtab *pVtab; /* The virtual table this cursor belongs to */
|
| - amatch_word *pAllWords; /* List of all amatch_word objects */
|
| - amatch_word *pCurrent; /* Most recent solution */
|
| - amatch_avl *pCost; /* amatch_word objects keyed by iCost */
|
| - amatch_avl *pWord; /* amatch_word objects keyed by zWord */
|
| -};
|
| -
|
| -/*
|
| -** The two input rule lists are both sorted in order of increasing
|
| -** cost. Merge them together into a single list, sorted by cost, and
|
| -** return a pointer to the head of that list.
|
| -*/
|
| -static amatch_rule *amatchMergeRules(amatch_rule *pA, amatch_rule *pB){
|
| - amatch_rule head;
|
| - amatch_rule *pTail;
|
| -
|
| - pTail = &head;
|
| - while( pA && pB ){
|
| - if( pA->rCost<=pB->rCost ){
|
| - pTail->pNext = pA;
|
| - pTail = pA;
|
| - pA = pA->pNext;
|
| - }else{
|
| - pTail->pNext = pB;
|
| - pTail = pB;
|
| - pB = pB->pNext;
|
| - }
|
| - }
|
| - if( pA==0 ){
|
| - pTail->pNext = pB;
|
| - }else{
|
| - pTail->pNext = pA;
|
| - }
|
| - return head.pNext;
|
| -}
|
| -
|
| -/*
|
| -** Statement pStmt currently points to a row in the amatch data table. This
|
| -** function allocates and populates a amatch_rule structure according to
|
| -** the content of the row.
|
| -**
|
| -** If successful, *ppRule is set to point to the new object and SQLITE_OK
|
| -** is returned. Otherwise, *ppRule is zeroed, *pzErr may be set to point
|
| -** to an error message and an SQLite error code returned.
|
| -*/
|
| -static int amatchLoadOneRule(
|
| - amatch_vtab *p, /* Fuzzer virtual table handle */
|
| - sqlite3_stmt *pStmt, /* Base rule on statements current row */
|
| - amatch_rule **ppRule, /* OUT: New rule object */
|
| - char **pzErr /* OUT: Error message */
|
| -){
|
| - sqlite3_int64 iLang = sqlite3_column_int64(pStmt, 0);
|
| - const char *zFrom = (const char *)sqlite3_column_text(pStmt, 1);
|
| - const char *zTo = (const char *)sqlite3_column_text(pStmt, 2);
|
| - amatch_cost rCost = sqlite3_column_int(pStmt, 3);
|
| -
|
| - int rc = SQLITE_OK; /* Return code */
|
| - int nFrom; /* Size of string zFrom, in bytes */
|
| - int nTo; /* Size of string zTo, in bytes */
|
| - amatch_rule *pRule = 0; /* New rule object to return */
|
| -
|
| - if( zFrom==0 ) zFrom = "";
|
| - if( zTo==0 ) zTo = "";
|
| - nFrom = (int)strlen(zFrom);
|
| - nTo = (int)strlen(zTo);
|
| -
|
| - /* Silently ignore null transformations */
|
| - if( strcmp(zFrom, zTo)==0 ){
|
| - if( zFrom[0]=='?' && zFrom[1]==0 ){
|
| - if( p->rSub==0 || p->rSub>rCost ) p->rSub = rCost;
|
| - }
|
| - *ppRule = 0;
|
| - return SQLITE_OK;
|
| - }
|
| -
|
| - if( rCost<=0 || rCost>AMATCH_MX_COST ){
|
| - *pzErr = sqlite3_mprintf("%s: cost must be between 1 and %d",
|
| - p->zClassName, AMATCH_MX_COST
|
| - );
|
| - rc = SQLITE_ERROR;
|
| - }else
|
| - if( nFrom>AMATCH_MX_LENGTH || nTo>AMATCH_MX_LENGTH ){
|
| - *pzErr = sqlite3_mprintf("%s: maximum string length is %d",
|
| - p->zClassName, AMATCH_MX_LENGTH
|
| - );
|
| - rc = SQLITE_ERROR;
|
| - }else
|
| - if( iLang<0 || iLang>AMATCH_MX_LANGID ){
|
| - *pzErr = sqlite3_mprintf("%s: iLang must be between 0 and %d",
|
| - p->zClassName, AMATCH_MX_LANGID
|
| - );
|
| - rc = SQLITE_ERROR;
|
| - }else
|
| - if( strcmp(zFrom,"")==0 && strcmp(zTo,"?")==0 ){
|
| - if( p->rIns==0 || p->rIns>rCost ) p->rIns = rCost;
|
| - }else
|
| - if( strcmp(zFrom,"?")==0 && strcmp(zTo,"")==0 ){
|
| - if( p->rDel==0 || p->rDel>rCost ) p->rDel = rCost;
|
| - }else
|
| - {
|
| - pRule = sqlite3_malloc( sizeof(*pRule) + nFrom + nTo );
|
| - if( pRule==0 ){
|
| - rc = SQLITE_NOMEM;
|
| - }else{
|
| - memset(pRule, 0, sizeof(*pRule));
|
| - pRule->zFrom = &pRule->zTo[nTo+1];
|
| - pRule->nFrom = nFrom;
|
| - memcpy(pRule->zFrom, zFrom, nFrom+1);
|
| - memcpy(pRule->zTo, zTo, nTo+1);
|
| - pRule->nTo = nTo;
|
| - pRule->rCost = rCost;
|
| - pRule->iLang = (int)iLang;
|
| - }
|
| - }
|
| -
|
| - *ppRule = pRule;
|
| - return rc;
|
| -}
|
| -
|
| -/*
|
| -** Free all the content in the edit-cost-table
|
| -*/
|
| -static void amatchFreeRules(amatch_vtab *p){
|
| - while( p->pRule ){
|
| - amatch_rule *pRule = p->pRule;
|
| - p->pRule = pRule->pNext;
|
| - sqlite3_free(pRule);
|
| - }
|
| - p->pRule = 0;
|
| -}
|
| -
|
| -/*
|
| -** Load the content of the amatch data table into memory.
|
| -*/
|
| -static int amatchLoadRules(
|
| - sqlite3 *db, /* Database handle */
|
| - amatch_vtab *p, /* Virtual amatch table to configure */
|
| - char **pzErr /* OUT: Error message */
|
| -){
|
| - int rc = SQLITE_OK; /* Return code */
|
| - char *zSql; /* SELECT used to read from rules table */
|
| - amatch_rule *pHead = 0;
|
| -
|
| - zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", p->zDb, p->zCostTab);
|
| - if( zSql==0 ){
|
| - rc = SQLITE_NOMEM;
|
| - }else{
|
| - int rc2; /* finalize() return code */
|
| - sqlite3_stmt *pStmt = 0;
|
| - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
|
| - if( rc!=SQLITE_OK ){
|
| - *pzErr = sqlite3_mprintf("%s: %s", p->zClassName, sqlite3_errmsg(db));
|
| - }else if( sqlite3_column_count(pStmt)!=4 ){
|
| - *pzErr = sqlite3_mprintf("%s: %s has %d columns, expected 4",
|
| - p->zClassName, p->zCostTab, sqlite3_column_count(pStmt)
|
| - );
|
| - rc = SQLITE_ERROR;
|
| - }else{
|
| - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
| - amatch_rule *pRule = 0;
|
| - rc = amatchLoadOneRule(p, pStmt, &pRule, pzErr);
|
| - if( pRule ){
|
| - pRule->pNext = pHead;
|
| - pHead = pRule;
|
| - }
|
| - }
|
| - }
|
| - rc2 = sqlite3_finalize(pStmt);
|
| - if( rc==SQLITE_OK ) rc = rc2;
|
| - }
|
| - sqlite3_free(zSql);
|
| -
|
| - /* All rules are now in a singly linked list starting at pHead. This
|
| - ** block sorts them by cost and then sets amatch_vtab.pRule to point to
|
| - ** point to the head of the sorted list.
|
| - */
|
| - if( rc==SQLITE_OK ){
|
| - unsigned int i;
|
| - amatch_rule *pX;
|
| - amatch_rule *a[15];
|
| - for(i=0; i<sizeof(a)/sizeof(a[0]); i++) a[i] = 0;
|
| - while( (pX = pHead)!=0 ){
|
| - pHead = pX->pNext;
|
| - pX->pNext = 0;
|
| - for(i=0; a[i] && i<sizeof(a)/sizeof(a[0])-1; i++){
|
| - pX = amatchMergeRules(a[i], pX);
|
| - a[i] = 0;
|
| - }
|
| - a[i] = amatchMergeRules(a[i], pX);
|
| - }
|
| - for(pX=a[0], i=1; i<sizeof(a)/sizeof(a[0]); i++){
|
| - pX = amatchMergeRules(a[i], pX);
|
| - }
|
| - p->pRule = amatchMergeRules(p->pRule, pX);
|
| - }else{
|
| - /* An error has occurred. Setting p->pRule to point to the head of the
|
| - ** allocated list ensures that the list will be cleaned up in this case.
|
| - */
|
| - assert( p->pRule==0 );
|
| - p->pRule = pHead;
|
| - }
|
| -
|
| - return rc;
|
| -}
|
| -
|
| -/*
|
| -** This function converts an SQL quoted string into an unquoted string
|
| -** and returns a pointer to a buffer allocated using sqlite3_malloc()
|
| -** containing the result. The caller should eventually free this buffer
|
| -** using sqlite3_free.
|
| -**
|
| -** Examples:
|
| -**
|
| -** "abc" becomes abc
|
| -** 'xyz' becomes xyz
|
| -** [pqr] becomes pqr
|
| -** `mno` becomes mno
|
| -*/
|
| -static char *amatchDequote(const char *zIn){
|
| - int nIn; /* Size of input string, in bytes */
|
| - char *zOut; /* Output (dequoted) string */
|
| -
|
| - nIn = (int)strlen(zIn);
|
| - zOut = sqlite3_malloc(nIn+1);
|
| - if( zOut ){
|
| - char q = zIn[0]; /* Quote character (if any ) */
|
| -
|
| - if( q!='[' && q!= '\'' && q!='"' && q!='`' ){
|
| - memcpy(zOut, zIn, nIn+1);
|
| - }else{
|
| - int iOut = 0; /* Index of next byte to write to output */
|
| - int iIn; /* Index of next byte to read from input */
|
| -
|
| - if( q=='[' ) q = ']';
|
| - for(iIn=1; iIn<nIn; iIn++){
|
| - if( zIn[iIn]==q ) iIn++;
|
| - zOut[iOut++] = zIn[iIn];
|
| - }
|
| - }
|
| - assert( (int)strlen(zOut)<=nIn );
|
| - }
|
| - return zOut;
|
| -}
|
| -
|
| -/*
|
| -** Deallocate the pVCheck prepared statement.
|
| -*/
|
| -static void amatchVCheckClear(amatch_vtab *p){
|
| - if( p->pVCheck ){
|
| - sqlite3_finalize(p->pVCheck);
|
| - p->pVCheck = 0;
|
| - }
|
| -}
|
| -
|
| -/*
|
| -** Deallocate an amatch_vtab object
|
| -*/
|
| -static void amatchFree(amatch_vtab *p){
|
| - if( p ){
|
| - amatchFreeRules(p);
|
| - amatchVCheckClear(p);
|
| - sqlite3_free(p->zClassName);
|
| - sqlite3_free(p->zDb);
|
| - sqlite3_free(p->zCostTab);
|
| - sqlite3_free(p->zVocabTab);
|
| - sqlite3_free(p->zVocabWord);
|
| - sqlite3_free(p->zVocabLang);
|
| - sqlite3_free(p->zSelf);
|
| - memset(p, 0, sizeof(*p));
|
| - sqlite3_free(p);
|
| - }
|
| -}
|
| -
|
| -/*
|
| -** xDisconnect/xDestroy method for the amatch module.
|
| -*/
|
| -static int amatchDisconnect(sqlite3_vtab *pVtab){
|
| - amatch_vtab *p = (amatch_vtab*)pVtab;
|
| - assert( p->nCursor==0 );
|
| - amatchFree(p);
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** Check to see if the argument is of the form:
|
| -**
|
| -** KEY = VALUE
|
| -**
|
| -** If it is, return a pointer to the first character of VALUE.
|
| -** If not, return NULL. Spaces around the = are ignored.
|
| -*/
|
| -static const char *amatchValueOfKey(const char *zKey, const char *zStr){
|
| - int nKey = (int)strlen(zKey);
|
| - int nStr = (int)strlen(zStr);
|
| - int i;
|
| - if( nStr<nKey+1 ) return 0;
|
| - if( memcmp(zStr, zKey, nKey)!=0 ) return 0;
|
| - for(i=nKey; isspace(zStr[i]); i++){}
|
| - if( zStr[i]!='=' ) return 0;
|
| - i++;
|
| - while( isspace(zStr[i]) ){ i++; }
|
| - return zStr+i;
|
| -}
|
| -
|
| -/*
|
| -** xConnect/xCreate method for the amatch module. Arguments are:
|
| -**
|
| -** argv[0] -> module name ("approximate_match")
|
| -** argv[1] -> database name
|
| -** argv[2] -> table name
|
| -** argv[3...] -> arguments
|
| -*/
|
| -static int amatchConnect(
|
| - sqlite3 *db,
|
| - void *pAux,
|
| - int argc, const char *const*argv,
|
| - sqlite3_vtab **ppVtab,
|
| - char **pzErr
|
| -){
|
| - int rc = SQLITE_OK; /* Return code */
|
| - amatch_vtab *pNew = 0; /* New virtual table */
|
| - const char *zModule = argv[0];
|
| - const char *zDb = argv[1];
|
| - const char *zVal;
|
| - int i;
|
| -
|
| - (void)pAux;
|
| - *ppVtab = 0;
|
| - pNew = sqlite3_malloc( sizeof(*pNew) );
|
| - if( pNew==0 ) return SQLITE_NOMEM;
|
| - rc = SQLITE_NOMEM;
|
| - memset(pNew, 0, sizeof(*pNew));
|
| - pNew->db = db;
|
| - pNew->zClassName = sqlite3_mprintf("%s", zModule);
|
| - if( pNew->zClassName==0 ) goto amatchConnectError;
|
| - pNew->zDb = sqlite3_mprintf("%s", zDb);
|
| - if( pNew->zDb==0 ) goto amatchConnectError;
|
| - pNew->zSelf = sqlite3_mprintf("%s", argv[2]);
|
| - if( pNew->zSelf==0 ) goto amatchConnectError;
|
| - for(i=3; i<argc; i++){
|
| - zVal = amatchValueOfKey("vocabulary_table", argv[i]);
|
| - if( zVal ){
|
| - sqlite3_free(pNew->zVocabTab);
|
| - pNew->zVocabTab = amatchDequote(zVal);
|
| - if( pNew->zVocabTab==0 ) goto amatchConnectError;
|
| - continue;
|
| - }
|
| - zVal = amatchValueOfKey("vocabulary_word", argv[i]);
|
| - if( zVal ){
|
| - sqlite3_free(pNew->zVocabWord);
|
| - pNew->zVocabWord = amatchDequote(zVal);
|
| - if( pNew->zVocabWord==0 ) goto amatchConnectError;
|
| - continue;
|
| - }
|
| - zVal = amatchValueOfKey("vocabulary_language", argv[i]);
|
| - if( zVal ){
|
| - sqlite3_free(pNew->zVocabLang);
|
| - pNew->zVocabLang = amatchDequote(zVal);
|
| - if( pNew->zVocabLang==0 ) goto amatchConnectError;
|
| - continue;
|
| - }
|
| - zVal = amatchValueOfKey("edit_distances", argv[i]);
|
| - if( zVal ){
|
| - sqlite3_free(pNew->zCostTab);
|
| - pNew->zCostTab = amatchDequote(zVal);
|
| - if( pNew->zCostTab==0 ) goto amatchConnectError;
|
| - continue;
|
| - }
|
| - *pzErr = sqlite3_mprintf("unrecognized argument: [%s]\n", argv[i]);
|
| - amatchFree(pNew);
|
| - *ppVtab = 0;
|
| - return SQLITE_ERROR;
|
| - }
|
| - rc = SQLITE_OK;
|
| - if( pNew->zCostTab==0 ){
|
| - *pzErr = sqlite3_mprintf("no edit_distances table specified");
|
| - rc = SQLITE_ERROR;
|
| - }else{
|
| - rc = amatchLoadRules(db, pNew, pzErr);
|
| - }
|
| - if( rc==SQLITE_OK ){
|
| - rc = sqlite3_declare_vtab(db,
|
| - "CREATE TABLE x(word,distance,language,"
|
| - "command HIDDEN,nword HIDDEN)"
|
| - );
|
| -#define AMATCH_COL_WORD 0
|
| -#define AMATCH_COL_DISTANCE 1
|
| -#define AMATCH_COL_LANGUAGE 2
|
| -#define AMATCH_COL_COMMAND 3
|
| -#define AMATCH_COL_NWORD 4
|
| - }
|
| - if( rc!=SQLITE_OK ){
|
| - amatchFree(pNew);
|
| - }
|
| - *ppVtab = &pNew->base;
|
| - return rc;
|
| -
|
| -amatchConnectError:
|
| - amatchFree(pNew);
|
| - return rc;
|
| -}
|
| -
|
| -/*
|
| -** Open a new amatch cursor.
|
| -*/
|
| -static int amatchOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
| - amatch_vtab *p = (amatch_vtab*)pVTab;
|
| - amatch_cursor *pCur;
|
| - pCur = sqlite3_malloc( sizeof(*pCur) );
|
| - if( pCur==0 ) return SQLITE_NOMEM;
|
| - memset(pCur, 0, sizeof(*pCur));
|
| - pCur->pVtab = p;
|
| - *ppCursor = &pCur->base;
|
| - p->nCursor++;
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** Free up all the memory allocated by a cursor. Set it rLimit to 0
|
| -** to indicate that it is at EOF.
|
| -*/
|
| -static void amatchClearCursor(amatch_cursor *pCur){
|
| - amatch_word *pWord, *pNextWord;
|
| - for(pWord=pCur->pAllWords; pWord; pWord=pNextWord){
|
| - pNextWord = pWord->pNext;
|
| - sqlite3_free(pWord);
|
| - }
|
| - pCur->pAllWords = 0;
|
| - sqlite3_free(pCur->zInput);
|
| - pCur->zInput = 0;
|
| - sqlite3_free(pCur->zBuf);
|
| - pCur->zBuf = 0;
|
| - pCur->nBuf = 0;
|
| - pCur->pCost = 0;
|
| - pCur->pWord = 0;
|
| - pCur->pCurrent = 0;
|
| - pCur->rLimit = 1000000;
|
| - pCur->iLang = 0;
|
| - pCur->nWord = 0;
|
| -}
|
| -
|
| -/*
|
| -** Close a amatch cursor.
|
| -*/
|
| -static int amatchClose(sqlite3_vtab_cursor *cur){
|
| - amatch_cursor *pCur = (amatch_cursor *)cur;
|
| - amatchClearCursor(pCur);
|
| - pCur->pVtab->nCursor--;
|
| - sqlite3_free(pCur);
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** Render a 24-bit unsigned integer as a 4-byte base-64 number.
|
| -*/
|
| -static void amatchEncodeInt(int x, char *z){
|
| - static const char a[] =
|
| - "0123456789"
|
| - "ABCDEFGHIJ"
|
| - "KLMNOPQRST"
|
| - "UVWXYZ^abc"
|
| - "defghijklm"
|
| - "nopqrstuvw"
|
| - "xyz~";
|
| - z[0] = a[(x>>18)&0x3f];
|
| - z[1] = a[(x>>12)&0x3f];
|
| - z[2] = a[(x>>6)&0x3f];
|
| - z[3] = a[x&0x3f];
|
| -}
|
| -
|
| -/*
|
| -** Write the zCost[] field for a amatch_word object
|
| -*/
|
| -static void amatchWriteCost(amatch_word *pWord){
|
| - amatchEncodeInt(pWord->rCost, pWord->zCost);
|
| - amatchEncodeInt(pWord->iSeq, pWord->zCost+4);
|
| - pWord->zCost[8] = 0;
|
| -}
|
| -
|
| -/*
|
| -** Add a new amatch_word object to the queue.
|
| -**
|
| -** If a prior amatch_word object with the same zWord, and nMatch
|
| -** already exists, update its rCost (if the new rCost is less) but
|
| -** otherwise leave it unchanged. Do not add a duplicate.
|
| -**
|
| -** Do nothing if the cost exceeds threshold.
|
| -*/
|
| -static void amatchAddWord(
|
| - amatch_cursor *pCur,
|
| - amatch_cost rCost,
|
| - int nMatch,
|
| - const char *zWordBase,
|
| - const char *zWordTail
|
| -){
|
| - amatch_word *pWord;
|
| - amatch_avl *pNode;
|
| - amatch_avl *pOther;
|
| - int nBase, nTail;
|
| - char zBuf[4];
|
| -
|
| - if( rCost>pCur->rLimit ){
|
| - return;
|
| - }
|
| - nBase = (int)strlen(zWordBase);
|
| - nTail = (int)strlen(zWordTail);
|
| - if( nBase+nTail+3>pCur->nBuf ){
|
| - pCur->nBuf = nBase+nTail+100;
|
| - pCur->zBuf = sqlite3_realloc(pCur->zBuf, pCur->nBuf);
|
| - if( pCur->zBuf==0 ){
|
| - pCur->nBuf = 0;
|
| - return;
|
| - }
|
| - }
|
| - amatchEncodeInt(nMatch, zBuf);
|
| - memcpy(pCur->zBuf, zBuf+2, 2);
|
| - memcpy(pCur->zBuf+2, zWordBase, nBase);
|
| - memcpy(pCur->zBuf+2+nBase, zWordTail, nTail+1);
|
| - pNode = amatchAvlSearch(pCur->pWord, pCur->zBuf);
|
| - if( pNode ){
|
| - pWord = pNode->pWord;
|
| - if( pWord->rCost>rCost ){
|
| -#ifdef AMATCH_TRACE_1
|
| - printf("UPDATE [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
|
| - pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput,
|
| - pWord->rCost, pWord->zWord, pWord->zCost);
|
| -#endif
|
| - amatchAvlRemove(&pCur->pCost, &pWord->sCost);
|
| - pWord->rCost = rCost;
|
| - amatchWriteCost(pWord);
|
| -#ifdef AMATCH_TRACE_1
|
| - printf(" ---> %d (\"%s\" \"%s\")\n",
|
| - pWord->rCost, pWord->zWord, pWord->zCost);
|
| -#endif
|
| - pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost);
|
| - assert( pOther==0 ); (void)pOther;
|
| - }
|
| - return;
|
| - }
|
| - pWord = sqlite3_malloc( sizeof(*pWord) + nBase + nTail - 1 );
|
| - if( pWord==0 ) return;
|
| - memset(pWord, 0, sizeof(*pWord));
|
| - pWord->rCost = rCost;
|
| - pWord->iSeq = pCur->nWord++;
|
| - amatchWriteCost(pWord);
|
| - pWord->nMatch = nMatch;
|
| - pWord->pNext = pCur->pAllWords;
|
| - pCur->pAllWords = pWord;
|
| - pWord->sCost.zKey = pWord->zCost;
|
| - pWord->sCost.pWord = pWord;
|
| - pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost);
|
| - assert( pOther==0 ); (void)pOther;
|
| - pWord->sWord.zKey = pWord->zWord;
|
| - pWord->sWord.pWord = pWord;
|
| - strcpy(pWord->zWord, pCur->zBuf);
|
| - pOther = amatchAvlInsert(&pCur->pWord, &pWord->sWord);
|
| - assert( pOther==0 ); (void)pOther;
|
| -#ifdef AMATCH_TRACE_1
|
| - printf("INSERT [%s][%.*s^%s] %d (\"%s\" \"%s\")\n", pWord->zWord+2,
|
| - pWord->nMatch, pCur->zInput, pCur->zInput+pWord->nMatch, rCost,
|
| - pWord->zWord, pWord->zCost);
|
| -#endif
|
| -}
|
| -
|
| -/*
|
| -** Advance a cursor to its next row of output
|
| -*/
|
| -static int amatchNext(sqlite3_vtab_cursor *cur){
|
| - amatch_cursor *pCur = (amatch_cursor*)cur;
|
| - amatch_word *pWord = 0;
|
| - amatch_avl *pNode;
|
| - int isMatch = 0;
|
| - amatch_vtab *p = pCur->pVtab;
|
| - int nWord;
|
| - int rc;
|
| - int i;
|
| - const char *zW;
|
| - amatch_rule *pRule;
|
| - char *zBuf = 0;
|
| - char nBuf = 0;
|
| - char zNext[8];
|
| - char zNextIn[8];
|
| - int nNextIn;
|
| -
|
| - if( p->pVCheck==0 ){
|
| - char *zSql;
|
| - if( p->zVocabLang && p->zVocabLang[0] ){
|
| - zSql = sqlite3_mprintf(
|
| - "SELECT \"%w\" FROM \"%w\"",
|
| - " WHERE \"%w\">=?1 AND \"%w\"=?2"
|
| - " ORDER BY 1",
|
| - p->zVocabWord, p->zVocabTab,
|
| - p->zVocabWord, p->zVocabLang
|
| - );
|
| - }else{
|
| - zSql = sqlite3_mprintf(
|
| - "SELECT \"%w\" FROM \"%w\""
|
| - " WHERE \"%w\">=?1"
|
| - " ORDER BY 1",
|
| - p->zVocabWord, p->zVocabTab,
|
| - p->zVocabWord
|
| - );
|
| - }
|
| - rc = sqlite3_prepare_v2(p->db, zSql, -1, &p->pVCheck, 0);
|
| - sqlite3_free(zSql);
|
| - if( rc ) return rc;
|
| - }
|
| - sqlite3_bind_int(p->pVCheck, 2, pCur->iLang);
|
| -
|
| - do{
|
| - pNode = amatchAvlFirst(pCur->pCost);
|
| - if( pNode==0 ){
|
| - pWord = 0;
|
| - break;
|
| - }
|
| - pWord = pNode->pWord;
|
| - amatchAvlRemove(&pCur->pCost, &pWord->sCost);
|
| -
|
| -#ifdef AMATCH_TRACE_1
|
| - printf("PROCESS [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
|
| - pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput+pWord->nMatch,
|
| - pWord->rCost, pWord->zWord, pWord->zCost);
|
| -#endif
|
| - nWord = (int)strlen(pWord->zWord+2);
|
| - if( nWord+20>nBuf ){
|
| - nBuf = nWord+100;
|
| - zBuf = sqlite3_realloc(zBuf, nBuf);
|
| - if( zBuf==0 ) return SQLITE_NOMEM;
|
| - }
|
| - strcpy(zBuf, pWord->zWord+2);
|
| - zNext[0] = 0;
|
| - zNextIn[0] = pCur->zInput[pWord->nMatch];
|
| - if( zNextIn[0] ){
|
| - for(i=1; i<=4 && (pCur->zInput[pWord->nMatch+i]&0xc0)==0x80; i++){
|
| - zNextIn[i] = pCur->zInput[pWord->nMatch+i];
|
| - }
|
| - zNextIn[i] = 0;
|
| - nNextIn = i;
|
| - }else{
|
| - nNextIn = 0;
|
| - }
|
| -
|
| - if( zNextIn[0] && zNextIn[0]!='*' ){
|
| - sqlite3_reset(p->pVCheck);
|
| - strcat(zBuf, zNextIn);
|
| - sqlite3_bind_text(p->pVCheck, 1, zBuf, nWord+nNextIn, SQLITE_STATIC);
|
| - rc = sqlite3_step(p->pVCheck);
|
| - if( rc==SQLITE_ROW ){
|
| - zW = (const char*)sqlite3_column_text(p->pVCheck, 0);
|
| - if( strncmp(zBuf, zW, nWord+nNextIn)==0 ){
|
| - amatchAddWord(pCur, pWord->rCost, pWord->nMatch+nNextIn, zBuf, "");
|
| - }
|
| - }
|
| - zBuf[nWord] = 0;
|
| - }
|
| -
|
| - while( 1 ){
|
| - strcpy(zBuf+nWord, zNext);
|
| - sqlite3_reset(p->pVCheck);
|
| - sqlite3_bind_text(p->pVCheck, 1, zBuf, -1, SQLITE_TRANSIENT);
|
| - rc = sqlite3_step(p->pVCheck);
|
| - if( rc!=SQLITE_ROW ) break;
|
| - zW = (const char*)sqlite3_column_text(p->pVCheck, 0);
|
| - strcpy(zBuf+nWord, zNext);
|
| - if( strncmp(zW, zBuf, nWord)!=0 ) break;
|
| - if( (zNextIn[0]=='*' && zNextIn[1]==0)
|
| - || (zNextIn[0]==0 && zW[nWord]==0)
|
| - ){
|
| - isMatch = 1;
|
| - zNextIn[0] = 0;
|
| - nNextIn = 0;
|
| - break;
|
| - }
|
| - zNext[0] = zW[nWord];
|
| - for(i=1; i<=4 && (zW[nWord+i]&0xc0)==0x80; i++){
|
| - zNext[i] = zW[nWord+i];
|
| - }
|
| - zNext[i] = 0;
|
| - zBuf[nWord] = 0;
|
| - if( p->rIns>0 ){
|
| - amatchAddWord(pCur, pWord->rCost+p->rIns, pWord->nMatch,
|
| - zBuf, zNext);
|
| - }
|
| - if( p->rSub>0 ){
|
| - amatchAddWord(pCur, pWord->rCost+p->rSub, pWord->nMatch+nNextIn,
|
| - zBuf, zNext);
|
| - }
|
| - if( p->rIns<0 && p->rSub<0 ) break;
|
| - zNext[i-1]++; /* FIX ME */
|
| - }
|
| - sqlite3_reset(p->pVCheck);
|
| -
|
| - if( p->rDel>0 ){
|
| - zBuf[nWord] = 0;
|
| - amatchAddWord(pCur, pWord->rCost+p->rDel, pWord->nMatch+nNextIn,
|
| - zBuf, "");
|
| - }
|
| -
|
| - for(pRule=p->pRule; pRule; pRule=pRule->pNext){
|
| - if( pRule->iLang!=pCur->iLang ) continue;
|
| - if( strncmp(pRule->zFrom, pCur->zInput+pWord->nMatch, pRule->nFrom)==0 ){
|
| - amatchAddWord(pCur, pWord->rCost+pRule->rCost,
|
| - pWord->nMatch+pRule->nFrom, pWord->zWord+2, pRule->zTo);
|
| - }
|
| - }
|
| - }while( !isMatch );
|
| - pCur->pCurrent = pWord;
|
| - sqlite3_free(zBuf);
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** Called to "rewind" a cursor back to the beginning so that
|
| -** it starts its output over again. Always called at least once
|
| -** prior to any amatchColumn, amatchRowid, or amatchEof call.
|
| -*/
|
| -static int amatchFilter(
|
| - sqlite3_vtab_cursor *pVtabCursor,
|
| - int idxNum, const char *idxStr,
|
| - int argc, sqlite3_value **argv
|
| -){
|
| - amatch_cursor *pCur = (amatch_cursor *)pVtabCursor;
|
| - const char *zWord = "*";
|
| - int idx;
|
| -
|
| - amatchClearCursor(pCur);
|
| - idx = 0;
|
| - if( idxNum & 1 ){
|
| - zWord = (const char*)sqlite3_value_text(argv[0]);
|
| - idx++;
|
| - }
|
| - if( idxNum & 2 ){
|
| - pCur->rLimit = (amatch_cost)sqlite3_value_int(argv[idx]);
|
| - idx++;
|
| - }
|
| - if( idxNum & 4 ){
|
| - pCur->iLang = (amatch_cost)sqlite3_value_int(argv[idx]);
|
| - idx++;
|
| - }
|
| - pCur->zInput = sqlite3_mprintf("%s", zWord);
|
| - if( pCur->zInput==0 ) return SQLITE_NOMEM;
|
| - amatchAddWord(pCur, 0, 0, "", "");
|
| - amatchNext(pVtabCursor);
|
| -
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** Only the word and distance columns have values. All other columns
|
| -** return NULL
|
| -*/
|
| -static int amatchColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
|
| - amatch_cursor *pCur = (amatch_cursor*)cur;
|
| - switch( i ){
|
| - case AMATCH_COL_WORD: {
|
| - sqlite3_result_text(ctx, pCur->pCurrent->zWord+2, -1, SQLITE_STATIC);
|
| - break;
|
| - }
|
| - case AMATCH_COL_DISTANCE: {
|
| - sqlite3_result_int(ctx, pCur->pCurrent->rCost);
|
| - break;
|
| - }
|
| - case AMATCH_COL_LANGUAGE: {
|
| - sqlite3_result_int(ctx, pCur->iLang);
|
| - break;
|
| - }
|
| - case AMATCH_COL_NWORD: {
|
| - sqlite3_result_int(ctx, pCur->nWord);
|
| - break;
|
| - }
|
| - default: {
|
| - sqlite3_result_null(ctx);
|
| - break;
|
| - }
|
| - }
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** The rowid.
|
| -*/
|
| -static int amatchRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
| - amatch_cursor *pCur = (amatch_cursor*)cur;
|
| - *pRowid = pCur->iRowid;
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** EOF indicator
|
| -*/
|
| -static int amatchEof(sqlite3_vtab_cursor *cur){
|
| - amatch_cursor *pCur = (amatch_cursor*)cur;
|
| - return pCur->pCurrent==0;
|
| -}
|
| -
|
| -/*
|
| -** Search for terms of these forms:
|
| -**
|
| -** (A) word MATCH $str
|
| -** (B1) distance < $value
|
| -** (B2) distance <= $value
|
| -** (C) language == $language
|
| -**
|
| -** The distance< and distance<= are both treated as distance<=.
|
| -** The query plan number is a bit vector:
|
| -**
|
| -** bit 1: Term of the form (A) found
|
| -** bit 2: Term like (B1) or (B2) found
|
| -** bit 3: Term like (C) found
|
| -**
|
| -** If bit-1 is set, $str is always in filter.argv[0]. If bit-2 is set
|
| -** then $value is in filter.argv[0] if bit-1 is clear and is in
|
| -** filter.argv[1] if bit-1 is set. If bit-3 is set, then $ruleid is
|
| -** in filter.argv[0] if bit-1 and bit-2 are both zero, is in
|
| -** filter.argv[1] if exactly one of bit-1 and bit-2 are set, and is in
|
| -** filter.argv[2] if both bit-1 and bit-2 are set.
|
| -*/
|
| -static int amatchBestIndex(
|
| - sqlite3_vtab *tab,
|
| - sqlite3_index_info *pIdxInfo
|
| -){
|
| - int iPlan = 0;
|
| - int iDistTerm = -1;
|
| - int iLangTerm = -1;
|
| - int i;
|
| - const struct sqlite3_index_constraint *pConstraint;
|
| -
|
| - (void)tab;
|
| - pConstraint = pIdxInfo->aConstraint;
|
| - for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
| - if( pConstraint->usable==0 ) continue;
|
| - if( (iPlan & 1)==0
|
| - && pConstraint->iColumn==0
|
| - && pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH
|
| - ){
|
| - iPlan |= 1;
|
| - pIdxInfo->aConstraintUsage[i].argvIndex = 1;
|
| - pIdxInfo->aConstraintUsage[i].omit = 1;
|
| - }
|
| - if( (iPlan & 2)==0
|
| - && pConstraint->iColumn==1
|
| - && (pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT
|
| - || pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE)
|
| - ){
|
| - iPlan |= 2;
|
| - iDistTerm = i;
|
| - }
|
| - if( (iPlan & 4)==0
|
| - && pConstraint->iColumn==2
|
| - && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ
|
| - ){
|
| - iPlan |= 4;
|
| - pIdxInfo->aConstraintUsage[i].omit = 1;
|
| - iLangTerm = i;
|
| - }
|
| - }
|
| - if( iPlan & 2 ){
|
| - pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 1+((iPlan&1)!=0);
|
| - }
|
| - if( iPlan & 4 ){
|
| - int idx = 1;
|
| - if( iPlan & 1 ) idx++;
|
| - if( iPlan & 2 ) idx++;
|
| - pIdxInfo->aConstraintUsage[iLangTerm].argvIndex = idx;
|
| - }
|
| - pIdxInfo->idxNum = iPlan;
|
| - if( pIdxInfo->nOrderBy==1
|
| - && pIdxInfo->aOrderBy[0].iColumn==1
|
| - && pIdxInfo->aOrderBy[0].desc==0
|
| - ){
|
| - pIdxInfo->orderByConsumed = 1;
|
| - }
|
| - pIdxInfo->estimatedCost = (double)10000;
|
| -
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** The xUpdate() method.
|
| -**
|
| -** This implementation disallows DELETE and UPDATE. The only thing
|
| -** allowed is INSERT into the "command" column.
|
| -*/
|
| -static int amatchUpdate(
|
| - sqlite3_vtab *pVTab,
|
| - int argc,
|
| - sqlite3_value **argv,
|
| - sqlite_int64 *pRowid
|
| -){
|
| - amatch_vtab *p = (amatch_vtab*)pVTab;
|
| - const unsigned char *zCmd;
|
| - (void)pRowid;
|
| - if( argc==1 ){
|
| - pVTab->zErrMsg = sqlite3_mprintf("DELETE from %s is not allowed",
|
| - p->zSelf);
|
| - return SQLITE_ERROR;
|
| - }
|
| - if( sqlite3_value_type(argv[0])!=SQLITE_NULL ){
|
| - pVTab->zErrMsg = sqlite3_mprintf("UPDATE of %s is not allowed",
|
| - p->zSelf);
|
| - return SQLITE_ERROR;
|
| - }
|
| - if( sqlite3_value_type(argv[2+AMATCH_COL_WORD])!=SQLITE_NULL
|
| - || sqlite3_value_type(argv[2+AMATCH_COL_DISTANCE])!=SQLITE_NULL
|
| - || sqlite3_value_type(argv[2+AMATCH_COL_LANGUAGE])!=SQLITE_NULL
|
| - ){
|
| - pVTab->zErrMsg = sqlite3_mprintf(
|
| - "INSERT INTO %s allowed for column [command] only", p->zSelf);
|
| - return SQLITE_ERROR;
|
| - }
|
| - zCmd = sqlite3_value_text(argv[2+AMATCH_COL_COMMAND]);
|
| - if( zCmd==0 ) return SQLITE_OK;
|
| -
|
| - return SQLITE_OK;
|
| -}
|
| -
|
| -/*
|
| -** A virtual table module that implements the "approximate_match".
|
| -*/
|
| -static sqlite3_module amatchModule = {
|
| - 0, /* iVersion */
|
| - amatchConnect, /* xCreate */
|
| - amatchConnect, /* xConnect */
|
| - amatchBestIndex, /* xBestIndex */
|
| - amatchDisconnect, /* xDisconnect */
|
| - amatchDisconnect, /* xDestroy */
|
| - amatchOpen, /* xOpen - open a cursor */
|
| - amatchClose, /* xClose - close a cursor */
|
| - amatchFilter, /* xFilter - configure scan constraints */
|
| - amatchNext, /* xNext - advance a cursor */
|
| - amatchEof, /* xEof - check for end of scan */
|
| - amatchColumn, /* xColumn - read data */
|
| - amatchRowid, /* xRowid - read data */
|
| - amatchUpdate, /* xUpdate */
|
| - 0, /* xBegin */
|
| - 0, /* xSync */
|
| - 0, /* xCommit */
|
| - 0, /* xRollback */
|
| - 0, /* xFindMethod */
|
| - 0, /* xRename */
|
| - 0, /* xSavepoint */
|
| - 0, /* xRelease */
|
| - 0 /* xRollbackTo */
|
| -};
|
| -
|
| -#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
| -
|
| -/*
|
| -** Register the amatch virtual table
|
| -*/
|
| -#ifdef _WIN32
|
| -__declspec(dllexport)
|
| -#endif
|
| -int sqlite3_amatch_init(
|
| - sqlite3 *db,
|
| - char **pzErrMsg,
|
| - const sqlite3_api_routines *pApi
|
| -){
|
| - int rc = SQLITE_OK;
|
| - SQLITE_EXTENSION_INIT2(pApi);
|
| - (void)pzErrMsg; /* Not used */
|
| -#ifndef SQLITE_OMIT_VIRTUALTABLE
|
| - rc = sqlite3_create_module(db, "approximate_match", &amatchModule, 0);
|
| -#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
| - return rc;
|
| -}
|
|
|