xref: /sqlite-3.40.0/ext/misc/amatch.c (revision 2b1c2aad)
18416fc7fSdrh /*
28416fc7fSdrh ** 2013-03-14
38416fc7fSdrh **
48416fc7fSdrh ** The author disclaims copyright to this source code.  In place of
58416fc7fSdrh ** a legal notice, here is a blessing:
68416fc7fSdrh **
78416fc7fSdrh **    May you do good and not evil.
88416fc7fSdrh **    May you find forgiveness for yourself and forgive others.
98416fc7fSdrh **    May you share freely, never taking more than you give.
108416fc7fSdrh **
118416fc7fSdrh *************************************************************************
128416fc7fSdrh **
138416fc7fSdrh ** This file contains code for a demonstration virtual table that finds
148416fc7fSdrh ** "approximate matches" - strings from a finite set that are nearly the
158416fc7fSdrh ** same as a single input string.  The virtual table is called "amatch".
168416fc7fSdrh **
178416fc7fSdrh ** A amatch virtual table is created like this:
188416fc7fSdrh **
198416fc7fSdrh **     CREATE VIRTUAL TABLE f USING approximate_match(
208416fc7fSdrh **        vocabulary_table=<tablename>,      -- V
218416fc7fSdrh **        vocabulary_word=<columnname>,      -- W
228416fc7fSdrh **        vocabulary_language=<columnname>,  -- L
238416fc7fSdrh **        edit_distances=<edit-cost-table>
248416fc7fSdrh **     );
258416fc7fSdrh **
268416fc7fSdrh ** When it is created, the new amatch table must be supplied with the
278416fc7fSdrh ** the name of a table V and columns V.W and V.L such that
288416fc7fSdrh **
298416fc7fSdrh **     SELECT W FROM V WHERE L=$language
308416fc7fSdrh **
318416fc7fSdrh ** returns the allowed vocabulary for the match.  If the "vocabulary_language"
328416fc7fSdrh ** or L columnname is left unspecified or is an empty string, then no
338416fc7fSdrh ** filtering of the vocabulary by language is performed.
348416fc7fSdrh **
358416fc7fSdrh ** For efficiency, it is essential that the vocabulary table be indexed:
368416fc7fSdrh **
378416fc7fSdrh **     CREATE vocab_index ON V(W)
388416fc7fSdrh **
398416fc7fSdrh ** A separate edit-cost-table provides scoring information that defines
408416fc7fSdrh ** what it means for one string to be "close" to another.
418416fc7fSdrh **
428416fc7fSdrh ** The edit-cost-table must contain exactly four columns (more precisely,
438416fc7fSdrh ** the statement "SELECT * FROM <edit-cost-table>" must return records
448416fc7fSdrh ** that consist of four columns). It does not matter what the columns are
458416fc7fSdrh ** named.
468416fc7fSdrh **
478416fc7fSdrh ** Each row in the edit-cost-table represents a single character
488416fc7fSdrh ** transformation going from user input to the vocabulary. The leftmost
498416fc7fSdrh ** column of the row (column 0) contains an integer identifier of the
508416fc7fSdrh ** language to which the transformation rule belongs (see "MULTIPLE LANGUAGES"
518416fc7fSdrh ** below). The second column of the row (column 1) contains the input
528416fc7fSdrh ** character or characters - the characters of user input. The third
538416fc7fSdrh ** column contains characters as they appear in the vocabulary table.
548416fc7fSdrh ** And the fourth column contains the integer cost of making the
558416fc7fSdrh ** transformation. For example:
568416fc7fSdrh **
578416fc7fSdrh **    CREATE TABLE f_data(iLang, cFrom, cTo, Cost);
588416fc7fSdrh **    INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '', 'a', 100);
598416fc7fSdrh **    INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'b', '', 87);
608416fc7fSdrh **    INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'o', 'oe', 38);
618416fc7fSdrh **    INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'oe', 'o', 40);
628416fc7fSdrh **
638416fc7fSdrh ** The first row inserted into the edit-cost-table by the SQL script
648416fc7fSdrh ** above indicates that the cost of having an extra 'a' in the vocabulary
658416fc7fSdrh ** table that is missing in the user input 100.  (All costs are integers.
668416fc7fSdrh ** Overall cost must not exceed 16777216.)  The second INSERT statement
678416fc7fSdrh ** creates a rule saying that the cost of having a single letter 'b' in
688416fc7fSdrh ** user input which is missing in the vocabulary table is 87.  The third
698416fc7fSdrh ** INSERT statement mean that the cost of matching an 'o' in user input
708416fc7fSdrh ** against an 'oe' in the vocabulary table is 38.  And so forth.
718416fc7fSdrh **
728416fc7fSdrh ** The following rules are special:
738416fc7fSdrh **
748416fc7fSdrh **    INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '?', '', 97);
758416fc7fSdrh **    INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '', '?', 98);
768416fc7fSdrh **    INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '?', '?', 99);
778416fc7fSdrh **
788416fc7fSdrh ** The '?' to '' rule is the cost of having any single character in the input
798416fc7fSdrh ** that is not found in the vocabular.  The '' to '?' rule is the cost of
808416fc7fSdrh ** having a character in the vocabulary table that is missing from input.
818416fc7fSdrh ** And the '?' to '?' rule is the cost of doing an arbitrary character
828416fc7fSdrh ** substitution.  These three generic rules apply across all languages.
838416fc7fSdrh ** In other words, the iLang field is ignored for the generic substitution
848416fc7fSdrh ** rules.  If more than one cost is given for a generic substitution rule,
858416fc7fSdrh ** then the lowest cost is used.
868416fc7fSdrh **
878416fc7fSdrh ** Once it has been created, the amatch virtual table can be queried
888416fc7fSdrh ** as follows:
898416fc7fSdrh **
908416fc7fSdrh **    SELECT word, distance FROM f
918416fc7fSdrh **     WHERE word MATCH 'abcdefg'
928416fc7fSdrh **       AND distance<200;
938416fc7fSdrh **
948416fc7fSdrh ** This query outputs the strings contained in the T(F) field that
958416fc7fSdrh ** are close to "abcdefg" and in order of increasing distance.  No string
968416fc7fSdrh ** is output more than once.  If there are multiple ways to transform the
978416fc7fSdrh ** target string ("abcdefg") into a string in the vocabulary table then
988416fc7fSdrh ** the lowest cost transform is the one that is returned.  In this example,
998416fc7fSdrh ** the search is limited to strings with a total distance of less than 200.
1008416fc7fSdrh **
1018416fc7fSdrh ** For efficiency, it is important to put tight bounds on the distance.
1028416fc7fSdrh ** The time and memory space needed to perform this query is exponential
1038416fc7fSdrh ** in the maximum distance.  A good rule of thumb is to limit the distance
1048416fc7fSdrh ** to no more than 1.5 or 2 times the maximum cost of any rule in the
1058416fc7fSdrh ** edit-cost-table.
1068416fc7fSdrh **
1078416fc7fSdrh ** The amatch is a read-only table.  Any attempt to DELETE, INSERT, or
1088416fc7fSdrh ** UPDATE on a amatch table will throw an error.
1098416fc7fSdrh **
1108416fc7fSdrh ** It is important to put some kind of a limit on the amatch output.  This
1118416fc7fSdrh ** can be either in the form of a LIMIT clause at the end of the query,
1128416fc7fSdrh ** or better, a "distance<NNN" constraint where NNN is some number.  The
1138416fc7fSdrh ** running time and memory requirement is exponential in the value of NNN
1148416fc7fSdrh ** so you want to make sure that NNN is not too big.  A value of NNN that
1158416fc7fSdrh ** is about twice the average transformation cost seems to give good results.
1168416fc7fSdrh **
1178416fc7fSdrh ** The amatch table can be useful for tasks such as spelling correction.
1188416fc7fSdrh ** Suppose all allowed words are in table vocabulary(w).  Then one would create
1198416fc7fSdrh ** an amatch virtual table like this:
1208416fc7fSdrh **
1218416fc7fSdrh **   CREATE VIRTUAL TABLE ex1 USING amatch(
1228416fc7fSdrh **       vocabtable=vocabulary,
1238416fc7fSdrh **       vocabcolumn=w,
1248416fc7fSdrh **       edit_distances=ec1
1258416fc7fSdrh **   );
1268416fc7fSdrh **
1278416fc7fSdrh ** Then given an input word $word, look up close spellings this way:
1288416fc7fSdrh **
1298416fc7fSdrh **   SELECT word, distance FROM ex1
1308416fc7fSdrh **    WHERE word MATCH $word AND distance<200;
1318416fc7fSdrh **
1328416fc7fSdrh ** MULTIPLE LANGUAGES
1338416fc7fSdrh **
1348416fc7fSdrh ** Normally, the "iLang" value associated with all character transformations
1358416fc7fSdrh ** in the edit-cost-table is zero. However, if required, the amatch
1368416fc7fSdrh ** virtual table allows multiple languages to be defined. Each query uses
1378416fc7fSdrh ** only a single iLang value.   This allows, for example, a single
1388416fc7fSdrh ** amatch table to support multiple languages.
1398416fc7fSdrh **
1408416fc7fSdrh ** By default, only the rules with iLang=0 are used. To specify an
1418416fc7fSdrh ** alternative language, a "language = ?" expression must be added to the
1428416fc7fSdrh ** WHERE clause of a SELECT, where ? is the integer identifier of the desired
1438416fc7fSdrh ** language. For example:
1448416fc7fSdrh **
1458416fc7fSdrh **   SELECT word, distance FROM ex1
1468416fc7fSdrh **    WHERE word MATCH $word
1478416fc7fSdrh **      AND distance<=200
1488416fc7fSdrh **      AND language=1 -- Specify use language 1 instead of 0
1498416fc7fSdrh **
1508416fc7fSdrh ** If no "language = ?" constraint is specified in the WHERE clause, language
1518416fc7fSdrh ** 0 is used.
1528416fc7fSdrh **
1538416fc7fSdrh ** LIMITS
1548416fc7fSdrh **
1558416fc7fSdrh ** The maximum language number is 2147483647.  The maximum length of either
1568416fc7fSdrh ** of the strings in the second or third column of the amatch data table
1578416fc7fSdrh ** is 50 bytes.  The maximum cost on a rule is 1000.
1588416fc7fSdrh */
1598416fc7fSdrh #include "sqlite3ext.h"
1608416fc7fSdrh SQLITE_EXTENSION_INIT1
1618416fc7fSdrh #include <stdlib.h>
1628416fc7fSdrh #include <string.h>
1638416fc7fSdrh #include <assert.h>
1648416fc7fSdrh #include <stdio.h>
1658416fc7fSdrh #include <ctype.h>
1668416fc7fSdrh 
16711f71d6aSdan #ifndef SQLITE_OMIT_VIRTUALTABLE
16811f71d6aSdan 
1698416fc7fSdrh /*
1708416fc7fSdrh ** Forward declaration of objects used by this implementation
1718416fc7fSdrh */
1728416fc7fSdrh typedef struct amatch_vtab amatch_vtab;
1738416fc7fSdrh typedef struct amatch_cursor amatch_cursor;
1748416fc7fSdrh typedef struct amatch_rule amatch_rule;
1758416fc7fSdrh typedef struct amatch_word amatch_word;
1768416fc7fSdrh typedef struct amatch_avl amatch_avl;
1778416fc7fSdrh 
1788416fc7fSdrh 
1798416fc7fSdrh /*****************************************************************************
1808416fc7fSdrh ** AVL Tree implementation
1818416fc7fSdrh */
1828416fc7fSdrh /*
1838416fc7fSdrh ** Objects that want to be members of the AVL tree should embedded an
1848416fc7fSdrh ** instance of this structure.
1858416fc7fSdrh */
1868416fc7fSdrh struct amatch_avl {
1878416fc7fSdrh   amatch_word *pWord;   /* Points to the object being stored in the tree */
1888416fc7fSdrh   char *zKey;           /* Key.  zero-terminated string.  Must be unique */
1898416fc7fSdrh   amatch_avl *pBefore;  /* Other elements less than zKey */
1908416fc7fSdrh   amatch_avl *pAfter;   /* Other elements greater than zKey */
1918416fc7fSdrh   amatch_avl *pUp;      /* Parent element */
1928416fc7fSdrh   short int height;     /* Height of this node.  Leaf==1 */
1938416fc7fSdrh   short int imbalance;  /* Height difference between pBefore and pAfter */
1948416fc7fSdrh };
1958416fc7fSdrh 
1968416fc7fSdrh /* Recompute the amatch_avl.height and amatch_avl.imbalance fields for p.
1978416fc7fSdrh ** Assume that the children of p have correct heights.
1988416fc7fSdrh */
amatchAvlRecomputeHeight(amatch_avl * p)1998416fc7fSdrh static void amatchAvlRecomputeHeight(amatch_avl *p){
2008416fc7fSdrh   short int hBefore = p->pBefore ? p->pBefore->height : 0;
2018416fc7fSdrh   short int hAfter = p->pAfter ? p->pAfter->height : 0;
2028416fc7fSdrh   p->imbalance = hBefore - hAfter;  /* -: pAfter higher.  +: pBefore higher */
2038416fc7fSdrh   p->height = (hBefore>hAfter ? hBefore : hAfter)+1;
2048416fc7fSdrh }
2058416fc7fSdrh 
2068416fc7fSdrh /*
2078416fc7fSdrh **     P                B
2088416fc7fSdrh **    / \              / \
2098416fc7fSdrh **   B   Z    ==>     X   P
2108416fc7fSdrh **  / \                  / \
2118416fc7fSdrh ** X   Y                Y   Z
2128416fc7fSdrh **
2138416fc7fSdrh */
amatchAvlRotateBefore(amatch_avl * pP)2148416fc7fSdrh static amatch_avl *amatchAvlRotateBefore(amatch_avl *pP){
2158416fc7fSdrh   amatch_avl *pB = pP->pBefore;
2168416fc7fSdrh   amatch_avl *pY = pB->pAfter;
2178416fc7fSdrh   pB->pUp = pP->pUp;
2188416fc7fSdrh   pB->pAfter = pP;
2198416fc7fSdrh   pP->pUp = pB;
2208416fc7fSdrh   pP->pBefore = pY;
2218416fc7fSdrh   if( pY ) pY->pUp = pP;
2228416fc7fSdrh   amatchAvlRecomputeHeight(pP);
2238416fc7fSdrh   amatchAvlRecomputeHeight(pB);
2248416fc7fSdrh   return pB;
2258416fc7fSdrh }
2268416fc7fSdrh 
2278416fc7fSdrh /*
2288416fc7fSdrh **     P                A
2298416fc7fSdrh **    / \              / \
2308416fc7fSdrh **   X   A    ==>     P   Z
2318416fc7fSdrh **      / \          / \
2328416fc7fSdrh **     Y   Z        X   Y
2338416fc7fSdrh **
2348416fc7fSdrh */
amatchAvlRotateAfter(amatch_avl * pP)2358416fc7fSdrh static amatch_avl *amatchAvlRotateAfter(amatch_avl *pP){
2368416fc7fSdrh   amatch_avl *pA = pP->pAfter;
2378416fc7fSdrh   amatch_avl *pY = pA->pBefore;
2388416fc7fSdrh   pA->pUp = pP->pUp;
2398416fc7fSdrh   pA->pBefore = pP;
2408416fc7fSdrh   pP->pUp = pA;
2418416fc7fSdrh   pP->pAfter = pY;
2428416fc7fSdrh   if( pY ) pY->pUp = pP;
2438416fc7fSdrh   amatchAvlRecomputeHeight(pP);
2448416fc7fSdrh   amatchAvlRecomputeHeight(pA);
2458416fc7fSdrh   return pA;
2468416fc7fSdrh }
2478416fc7fSdrh 
2488416fc7fSdrh /*
2498416fc7fSdrh ** Return a pointer to the pBefore or pAfter pointer in the parent
2508416fc7fSdrh ** of p that points to p.  Or if p is the root node, return pp.
2518416fc7fSdrh */
amatchAvlFromPtr(amatch_avl * p,amatch_avl ** pp)2528416fc7fSdrh static amatch_avl **amatchAvlFromPtr(amatch_avl *p, amatch_avl **pp){
2538416fc7fSdrh   amatch_avl *pUp = p->pUp;
2548416fc7fSdrh   if( pUp==0 ) return pp;
2558416fc7fSdrh   if( pUp->pAfter==p ) return &pUp->pAfter;
2568416fc7fSdrh   return &pUp->pBefore;
2578416fc7fSdrh }
2588416fc7fSdrh 
2598416fc7fSdrh /*
2608416fc7fSdrh ** Rebalance all nodes starting with p and working up to the root.
2618416fc7fSdrh ** Return the new root.
2628416fc7fSdrh */
amatchAvlBalance(amatch_avl * p)2638416fc7fSdrh static amatch_avl *amatchAvlBalance(amatch_avl *p){
2648416fc7fSdrh   amatch_avl *pTop = p;
2658416fc7fSdrh   amatch_avl **pp;
2668416fc7fSdrh   while( p ){
2678416fc7fSdrh     amatchAvlRecomputeHeight(p);
2688416fc7fSdrh     if( p->imbalance>=2 ){
2698416fc7fSdrh       amatch_avl *pB = p->pBefore;
2708416fc7fSdrh       if( pB->imbalance<0 ) p->pBefore = amatchAvlRotateAfter(pB);
2718416fc7fSdrh       pp = amatchAvlFromPtr(p,&p);
2728416fc7fSdrh       p = *pp = amatchAvlRotateBefore(p);
2738416fc7fSdrh     }else if( p->imbalance<=(-2) ){
2748416fc7fSdrh       amatch_avl *pA = p->pAfter;
2758416fc7fSdrh       if( pA->imbalance>0 ) p->pAfter = amatchAvlRotateBefore(pA);
2768416fc7fSdrh       pp = amatchAvlFromPtr(p,&p);
2778416fc7fSdrh       p = *pp = amatchAvlRotateAfter(p);
2788416fc7fSdrh     }
2798416fc7fSdrh     pTop = p;
2808416fc7fSdrh     p = p->pUp;
2818416fc7fSdrh   }
2828416fc7fSdrh   return pTop;
2838416fc7fSdrh }
2848416fc7fSdrh 
2858416fc7fSdrh /* Search the tree rooted at p for an entry with zKey.  Return a pointer
2868416fc7fSdrh ** to the entry or return NULL.
2878416fc7fSdrh */
amatchAvlSearch(amatch_avl * p,const char * zKey)2888416fc7fSdrh static amatch_avl *amatchAvlSearch(amatch_avl *p, const char *zKey){
2898416fc7fSdrh   int c;
2908416fc7fSdrh   while( p && (c = strcmp(zKey, p->zKey))!=0 ){
2918416fc7fSdrh     p = (c<0) ? p->pBefore : p->pAfter;
2928416fc7fSdrh   }
2938416fc7fSdrh   return p;
2948416fc7fSdrh }
2958416fc7fSdrh 
2968416fc7fSdrh /* Find the first node (the one with the smallest key).
2978416fc7fSdrh */
amatchAvlFirst(amatch_avl * p)2988416fc7fSdrh static amatch_avl *amatchAvlFirst(amatch_avl *p){
2998416fc7fSdrh   if( p ) while( p->pBefore ) p = p->pBefore;
3008416fc7fSdrh   return p;
3018416fc7fSdrh }
3028416fc7fSdrh 
3038416fc7fSdrh #if 0 /* NOT USED */
3048416fc7fSdrh /* Return the node with the next larger key after p.
3058416fc7fSdrh */
3068416fc7fSdrh static amatch_avl *amatchAvlNext(amatch_avl *p){
3078416fc7fSdrh   amatch_avl *pPrev = 0;
3088416fc7fSdrh   while( p && p->pAfter==pPrev ){
3098416fc7fSdrh     pPrev = p;
3108416fc7fSdrh     p = p->pUp;
3118416fc7fSdrh   }
3128416fc7fSdrh   if( p && pPrev==0 ){
3138416fc7fSdrh     p = amatchAvlFirst(p->pAfter);
3148416fc7fSdrh   }
3158416fc7fSdrh   return p;
3168416fc7fSdrh }
3178416fc7fSdrh #endif
3188416fc7fSdrh 
3198416fc7fSdrh #if 0 /* NOT USED */
3208416fc7fSdrh /* Verify AVL tree integrity
3218416fc7fSdrh */
3228416fc7fSdrh static int amatchAvlIntegrity(amatch_avl *pHead){
3238416fc7fSdrh   amatch_avl *p;
3248416fc7fSdrh   if( pHead==0 ) return 1;
3258416fc7fSdrh   if( (p = pHead->pBefore)!=0 ){
3268416fc7fSdrh     assert( p->pUp==pHead );
3278416fc7fSdrh     assert( amatchAvlIntegrity(p) );
3288416fc7fSdrh     assert( strcmp(p->zKey, pHead->zKey)<0 );
3298416fc7fSdrh     while( p->pAfter ) p = p->pAfter;
3308416fc7fSdrh     assert( strcmp(p->zKey, pHead->zKey)<0 );
3318416fc7fSdrh   }
3328416fc7fSdrh   if( (p = pHead->pAfter)!=0 ){
3338416fc7fSdrh     assert( p->pUp==pHead );
3348416fc7fSdrh     assert( amatchAvlIntegrity(p) );
3358416fc7fSdrh     assert( strcmp(p->zKey, pHead->zKey)>0 );
3368416fc7fSdrh     p = amatchAvlFirst(p);
3378416fc7fSdrh     assert( strcmp(p->zKey, pHead->zKey)>0 );
3388416fc7fSdrh   }
3398416fc7fSdrh   return 1;
3408416fc7fSdrh }
3418416fc7fSdrh static int amatchAvlIntegrity2(amatch_avl *pHead){
3428416fc7fSdrh   amatch_avl *p, *pNext;
3438416fc7fSdrh   for(p=amatchAvlFirst(pHead); p; p=pNext){
3448416fc7fSdrh     pNext = amatchAvlNext(p);
3458416fc7fSdrh     if( pNext==0 ) break;
3468416fc7fSdrh     assert( strcmp(p->zKey, pNext->zKey)<0 );
3478416fc7fSdrh   }
3488416fc7fSdrh   return 1;
3498416fc7fSdrh }
3508416fc7fSdrh #endif
3518416fc7fSdrh 
3528416fc7fSdrh /* Insert a new node pNew.  Return NULL on success.  If the key is not
3538416fc7fSdrh ** unique, then do not perform the insert but instead leave pNew unchanged
3548416fc7fSdrh ** and return a pointer to an existing node with the same key.
3558416fc7fSdrh */
amatchAvlInsert(amatch_avl ** ppHead,amatch_avl * pNew)3568416fc7fSdrh static amatch_avl *amatchAvlInsert(amatch_avl **ppHead, amatch_avl *pNew){
3578416fc7fSdrh   int c;
3588416fc7fSdrh   amatch_avl *p = *ppHead;
3598416fc7fSdrh   if( p==0 ){
3608416fc7fSdrh     p = pNew;
3618416fc7fSdrh     pNew->pUp = 0;
3628416fc7fSdrh   }else{
3638416fc7fSdrh     while( p ){
3648416fc7fSdrh       c = strcmp(pNew->zKey, p->zKey);
3658416fc7fSdrh       if( c<0 ){
3668416fc7fSdrh         if( p->pBefore ){
3678416fc7fSdrh           p = p->pBefore;
3688416fc7fSdrh         }else{
3698416fc7fSdrh           p->pBefore = pNew;
3708416fc7fSdrh           pNew->pUp = p;
3718416fc7fSdrh           break;
3728416fc7fSdrh         }
3738416fc7fSdrh       }else if( c>0 ){
3748416fc7fSdrh         if( p->pAfter ){
3758416fc7fSdrh           p = p->pAfter;
3768416fc7fSdrh         }else{
3778416fc7fSdrh           p->pAfter = pNew;
3788416fc7fSdrh           pNew->pUp = p;
3798416fc7fSdrh           break;
3808416fc7fSdrh         }
3818416fc7fSdrh       }else{
3828416fc7fSdrh         return p;
3838416fc7fSdrh       }
3848416fc7fSdrh     }
3858416fc7fSdrh   }
3868416fc7fSdrh   pNew->pBefore = 0;
3878416fc7fSdrh   pNew->pAfter = 0;
3888416fc7fSdrh   pNew->height = 1;
3898416fc7fSdrh   pNew->imbalance = 0;
3908416fc7fSdrh   *ppHead = amatchAvlBalance(p);
3918416fc7fSdrh   /* assert( amatchAvlIntegrity(*ppHead) ); */
3928416fc7fSdrh   /* assert( amatchAvlIntegrity2(*ppHead) ); */
3938416fc7fSdrh   return 0;
3948416fc7fSdrh }
3958416fc7fSdrh 
3968416fc7fSdrh /* Remove node pOld from the tree.  pOld must be an element of the tree or
3978416fc7fSdrh ** the AVL tree will become corrupt.
3988416fc7fSdrh */
amatchAvlRemove(amatch_avl ** ppHead,amatch_avl * pOld)3998416fc7fSdrh static void amatchAvlRemove(amatch_avl **ppHead, amatch_avl *pOld){
4008416fc7fSdrh   amatch_avl **ppParent;
4017bb22ac7Smistachkin   amatch_avl *pBalance = 0;
4028416fc7fSdrh   /* assert( amatchAvlSearch(*ppHead, pOld->zKey)==pOld ); */
4038416fc7fSdrh   ppParent = amatchAvlFromPtr(pOld, ppHead);
4048416fc7fSdrh   if( pOld->pBefore==0 && pOld->pAfter==0 ){
4058416fc7fSdrh     *ppParent = 0;
4068416fc7fSdrh     pBalance = pOld->pUp;
4078416fc7fSdrh   }else if( pOld->pBefore && pOld->pAfter ){
4088416fc7fSdrh     amatch_avl *pX, *pY;
4098416fc7fSdrh     pX = amatchAvlFirst(pOld->pAfter);
4108416fc7fSdrh     *amatchAvlFromPtr(pX, 0) = pX->pAfter;
4118416fc7fSdrh     if( pX->pAfter ) pX->pAfter->pUp = pX->pUp;
4128416fc7fSdrh     pBalance = pX->pUp;
4138416fc7fSdrh     pX->pAfter = pOld->pAfter;
4148416fc7fSdrh     if( pX->pAfter ){
4158416fc7fSdrh       pX->pAfter->pUp = pX;
4168416fc7fSdrh     }else{
4178416fc7fSdrh       assert( pBalance==pOld );
4188416fc7fSdrh       pBalance = pX;
4198416fc7fSdrh     }
4208416fc7fSdrh     pX->pBefore = pY = pOld->pBefore;
4218416fc7fSdrh     if( pY ) pY->pUp = pX;
4228416fc7fSdrh     pX->pUp = pOld->pUp;
4238416fc7fSdrh     *ppParent = pX;
4248416fc7fSdrh   }else if( pOld->pBefore==0 ){
4258416fc7fSdrh     *ppParent = pBalance = pOld->pAfter;
4268416fc7fSdrh     pBalance->pUp = pOld->pUp;
4278416fc7fSdrh   }else if( pOld->pAfter==0 ){
4288416fc7fSdrh     *ppParent = pBalance = pOld->pBefore;
4298416fc7fSdrh     pBalance->pUp = pOld->pUp;
4308416fc7fSdrh   }
4318416fc7fSdrh   *ppHead = amatchAvlBalance(pBalance);
4328416fc7fSdrh   pOld->pUp = 0;
4338416fc7fSdrh   pOld->pBefore = 0;
4348416fc7fSdrh   pOld->pAfter = 0;
4358416fc7fSdrh   /* assert( amatchAvlIntegrity(*ppHead) ); */
4368416fc7fSdrh   /* assert( amatchAvlIntegrity2(*ppHead) ); */
4378416fc7fSdrh }
4388416fc7fSdrh /*
4398416fc7fSdrh ** End of the AVL Tree implementation
4408416fc7fSdrh ******************************************************************************/
4418416fc7fSdrh 
4428416fc7fSdrh 
4438416fc7fSdrh /*
4448416fc7fSdrh ** Various types.
4458416fc7fSdrh **
4468416fc7fSdrh ** amatch_cost is the "cost" of an edit operation.
4478416fc7fSdrh **
4488416fc7fSdrh ** amatch_len is the length of a matching string.
4498416fc7fSdrh **
4508416fc7fSdrh ** amatch_langid is an ruleset identifier.
4518416fc7fSdrh */
4528416fc7fSdrh typedef int amatch_cost;
4538416fc7fSdrh typedef signed char amatch_len;
4548416fc7fSdrh typedef int amatch_langid;
4558416fc7fSdrh 
4568416fc7fSdrh /*
4578416fc7fSdrh ** Limits
4588416fc7fSdrh */
4598416fc7fSdrh #define AMATCH_MX_LENGTH          50  /* Maximum length of a rule string */
4608416fc7fSdrh #define AMATCH_MX_LANGID  2147483647  /* Maximum rule ID */
4618416fc7fSdrh #define AMATCH_MX_COST          1000  /* Maximum single-rule cost */
4628416fc7fSdrh 
4638416fc7fSdrh /*
4648416fc7fSdrh ** A match or partial match
4658416fc7fSdrh */
4668416fc7fSdrh struct amatch_word {
4678416fc7fSdrh   amatch_word *pNext;   /* Next on a list of all amatch_words */
4688416fc7fSdrh   amatch_avl sCost;     /* Linkage of this node into the cost tree */
4698416fc7fSdrh   amatch_avl sWord;     /* Linkage of this node into the word tree */
4708416fc7fSdrh   amatch_cost rCost;    /* Cost of the match so far */
4718416fc7fSdrh   int iSeq;             /* Sequence number */
4728416fc7fSdrh   char zCost[10];       /* Cost key (text rendering of rCost) */
4738416fc7fSdrh   short int nMatch;     /* Input characters matched */
4748416fc7fSdrh   char zWord[4];        /* Text of the word.  Extra space appended as needed */
4758416fc7fSdrh };
4768416fc7fSdrh 
4778416fc7fSdrh /*
4788416fc7fSdrh ** Each transformation rule is stored as an instance of this object.
4798416fc7fSdrh ** All rules are kept on a linked list sorted by rCost.
4808416fc7fSdrh */
4818416fc7fSdrh struct amatch_rule {
4828416fc7fSdrh   amatch_rule *pNext;      /* Next rule in order of increasing rCost */
4838416fc7fSdrh   char *zFrom;             /* Transform from (a string from user input) */
4848416fc7fSdrh   amatch_cost rCost;       /* Cost of this transformation */
4858416fc7fSdrh   amatch_langid iLang;     /* The langauge to which this rule belongs */
4868416fc7fSdrh   amatch_len nFrom, nTo;   /* Length of the zFrom and zTo strings */
4878416fc7fSdrh   char zTo[4];             /* Tranform to V.W value (extra space appended) */
4888416fc7fSdrh };
4898416fc7fSdrh 
4908416fc7fSdrh /*
4918416fc7fSdrh ** A amatch virtual-table object
4928416fc7fSdrh */
4938416fc7fSdrh struct amatch_vtab {
4948416fc7fSdrh   sqlite3_vtab base;         /* Base class - must be first */
4958416fc7fSdrh   char *zClassName;          /* Name of this class.  Default: "amatch" */
4968416fc7fSdrh   char *zDb;                 /* Name of database.  (ex: "main") */
4978416fc7fSdrh   char *zSelf;               /* Name of this virtual table */
4988416fc7fSdrh   char *zCostTab;            /* Name of edit-cost-table */
4998416fc7fSdrh   char *zVocabTab;           /* Name of vocabulary table */
5008416fc7fSdrh   char *zVocabWord;          /* Name of vocabulary table word column */
5018416fc7fSdrh   char *zVocabLang;          /* Name of vocabulary table language column */
5028416fc7fSdrh   amatch_rule *pRule;        /* All active rules in this amatch */
5038416fc7fSdrh   amatch_cost rIns;          /* Generic insertion cost  '' -> ? */
5048416fc7fSdrh   amatch_cost rDel;          /* Generic deletion cost  ? -> '' */
5058416fc7fSdrh   amatch_cost rSub;          /* Generic substitution cost ? -> ? */
5068416fc7fSdrh   sqlite3 *db;               /* The database connection */
5078416fc7fSdrh   sqlite3_stmt *pVCheck;     /* Query to check zVocabTab */
5088416fc7fSdrh   int nCursor;               /* Number of active cursors */
5098416fc7fSdrh };
5108416fc7fSdrh 
5118416fc7fSdrh /* A amatch cursor object */
5128416fc7fSdrh struct amatch_cursor {
5138416fc7fSdrh   sqlite3_vtab_cursor base;  /* Base class - must be first */
5148416fc7fSdrh   sqlite3_int64 iRowid;      /* The rowid of the current word */
5158416fc7fSdrh   amatch_langid iLang;       /* Use this language ID */
5168416fc7fSdrh   amatch_cost rLimit;        /* Maximum cost of any term */
5178416fc7fSdrh   int nBuf;                  /* Space allocated for zBuf */
5188416fc7fSdrh   int oomErr;                /* True following an OOM error */
5198416fc7fSdrh   int nWord;                 /* Number of amatch_word objects */
5208416fc7fSdrh   char *zBuf;                /* Temp-use buffer space */
5218416fc7fSdrh   char *zInput;              /* Input word to match against */
5228416fc7fSdrh   amatch_vtab *pVtab;        /* The virtual table this cursor belongs to */
5238416fc7fSdrh   amatch_word *pAllWords;    /* List of all amatch_word objects */
5248416fc7fSdrh   amatch_word *pCurrent;     /* Most recent solution */
5258416fc7fSdrh   amatch_avl *pCost;         /* amatch_word objects keyed by iCost */
5268416fc7fSdrh   amatch_avl *pWord;         /* amatch_word objects keyed by zWord */
5278416fc7fSdrh };
5288416fc7fSdrh 
5298416fc7fSdrh /*
5308416fc7fSdrh ** The two input rule lists are both sorted in order of increasing
5318416fc7fSdrh ** cost.  Merge them together into a single list, sorted by cost, and
5328416fc7fSdrh ** return a pointer to the head of that list.
5338416fc7fSdrh */
amatchMergeRules(amatch_rule * pA,amatch_rule * pB)5348416fc7fSdrh static amatch_rule *amatchMergeRules(amatch_rule *pA, amatch_rule *pB){
5358416fc7fSdrh   amatch_rule head;
5368416fc7fSdrh   amatch_rule *pTail;
5378416fc7fSdrh 
5388416fc7fSdrh   pTail =  &head;
5398416fc7fSdrh   while( pA && pB ){
5408416fc7fSdrh     if( pA->rCost<=pB->rCost ){
5418416fc7fSdrh       pTail->pNext = pA;
5428416fc7fSdrh       pTail = pA;
5438416fc7fSdrh       pA = pA->pNext;
5448416fc7fSdrh     }else{
5458416fc7fSdrh       pTail->pNext = pB;
5468416fc7fSdrh       pTail = pB;
5478416fc7fSdrh       pB = pB->pNext;
5488416fc7fSdrh     }
5498416fc7fSdrh   }
5508416fc7fSdrh   if( pA==0 ){
5518416fc7fSdrh     pTail->pNext = pB;
5528416fc7fSdrh   }else{
5538416fc7fSdrh     pTail->pNext = pA;
5548416fc7fSdrh   }
5558416fc7fSdrh   return head.pNext;
5568416fc7fSdrh }
5578416fc7fSdrh 
5588416fc7fSdrh /*
5598416fc7fSdrh ** Statement pStmt currently points to a row in the amatch data table. This
5608416fc7fSdrh ** function allocates and populates a amatch_rule structure according to
5618416fc7fSdrh ** the content of the row.
5628416fc7fSdrh **
5638416fc7fSdrh ** If successful, *ppRule is set to point to the new object and SQLITE_OK
5648416fc7fSdrh ** is returned. Otherwise, *ppRule is zeroed, *pzErr may be set to point
5658416fc7fSdrh ** to an error message and an SQLite error code returned.
5668416fc7fSdrh */
amatchLoadOneRule(amatch_vtab * p,sqlite3_stmt * pStmt,amatch_rule ** ppRule,char ** pzErr)5678416fc7fSdrh static int amatchLoadOneRule(
5688416fc7fSdrh   amatch_vtab *p,                 /* Fuzzer virtual table handle */
5698416fc7fSdrh   sqlite3_stmt *pStmt,            /* Base rule on statements current row */
5708416fc7fSdrh   amatch_rule **ppRule,           /* OUT: New rule object */
5718416fc7fSdrh   char **pzErr                    /* OUT: Error message */
5728416fc7fSdrh ){
5738416fc7fSdrh   sqlite3_int64 iLang = sqlite3_column_int64(pStmt, 0);
5748416fc7fSdrh   const char *zFrom = (const char *)sqlite3_column_text(pStmt, 1);
5758416fc7fSdrh   const char *zTo = (const char *)sqlite3_column_text(pStmt, 2);
5768416fc7fSdrh   amatch_cost rCost = sqlite3_column_int(pStmt, 3);
5778416fc7fSdrh 
5788416fc7fSdrh   int rc = SQLITE_OK;             /* Return code */
5798416fc7fSdrh   int nFrom;                      /* Size of string zFrom, in bytes */
5808416fc7fSdrh   int nTo;                        /* Size of string zTo, in bytes */
5818416fc7fSdrh   amatch_rule *pRule = 0;         /* New rule object to return */
5828416fc7fSdrh 
5838416fc7fSdrh   if( zFrom==0 ) zFrom = "";
5848416fc7fSdrh   if( zTo==0 ) zTo = "";
5858416fc7fSdrh   nFrom = (int)strlen(zFrom);
5868416fc7fSdrh   nTo = (int)strlen(zTo);
5878416fc7fSdrh 
5888416fc7fSdrh   /* Silently ignore null transformations */
5898416fc7fSdrh   if( strcmp(zFrom, zTo)==0 ){
5908416fc7fSdrh     if( zFrom[0]=='?' && zFrom[1]==0 ){
5918416fc7fSdrh       if( p->rSub==0 || p->rSub>rCost ) p->rSub = rCost;
5928416fc7fSdrh     }
5938416fc7fSdrh     *ppRule = 0;
5948416fc7fSdrh     return SQLITE_OK;
5958416fc7fSdrh   }
5968416fc7fSdrh 
5978416fc7fSdrh   if( rCost<=0 || rCost>AMATCH_MX_COST ){
5988416fc7fSdrh     *pzErr = sqlite3_mprintf("%s: cost must be between 1 and %d",
5998416fc7fSdrh         p->zClassName, AMATCH_MX_COST
6008416fc7fSdrh     );
6018416fc7fSdrh     rc = SQLITE_ERROR;
6028416fc7fSdrh   }else
6038416fc7fSdrh   if( nFrom>AMATCH_MX_LENGTH || nTo>AMATCH_MX_LENGTH ){
6048416fc7fSdrh     *pzErr = sqlite3_mprintf("%s: maximum string length is %d",
6058416fc7fSdrh         p->zClassName, AMATCH_MX_LENGTH
6068416fc7fSdrh     );
6078416fc7fSdrh     rc = SQLITE_ERROR;
6088416fc7fSdrh   }else
6098416fc7fSdrh   if( iLang<0 || iLang>AMATCH_MX_LANGID ){
6108416fc7fSdrh     *pzErr = sqlite3_mprintf("%s: iLang must be between 0 and %d",
6118416fc7fSdrh         p->zClassName, AMATCH_MX_LANGID
6128416fc7fSdrh     );
6138416fc7fSdrh     rc = SQLITE_ERROR;
6148416fc7fSdrh   }else
6158416fc7fSdrh   if( strcmp(zFrom,"")==0 && strcmp(zTo,"?")==0 ){
6168416fc7fSdrh     if( p->rIns==0 || p->rIns>rCost ) p->rIns = rCost;
6178416fc7fSdrh   }else
6188416fc7fSdrh   if( strcmp(zFrom,"?")==0 && strcmp(zTo,"")==0 ){
6198416fc7fSdrh     if( p->rDel==0 || p->rDel>rCost ) p->rDel = rCost;
6208416fc7fSdrh   }else
6218416fc7fSdrh   {
6222d77d80aSdrh     pRule = sqlite3_malloc64( sizeof(*pRule) + nFrom + nTo );
6238416fc7fSdrh     if( pRule==0 ){
6248416fc7fSdrh       rc = SQLITE_NOMEM;
6258416fc7fSdrh     }else{
6268416fc7fSdrh       memset(pRule, 0, sizeof(*pRule));
6278416fc7fSdrh       pRule->zFrom = &pRule->zTo[nTo+1];
62877fac879Smistachkin       pRule->nFrom = (amatch_len)nFrom;
6298416fc7fSdrh       memcpy(pRule->zFrom, zFrom, nFrom+1);
6308416fc7fSdrh       memcpy(pRule->zTo, zTo, nTo+1);
63177fac879Smistachkin       pRule->nTo = (amatch_len)nTo;
6328416fc7fSdrh       pRule->rCost = rCost;
6338416fc7fSdrh       pRule->iLang = (int)iLang;
6348416fc7fSdrh     }
6358416fc7fSdrh   }
6368416fc7fSdrh 
6378416fc7fSdrh   *ppRule = pRule;
6388416fc7fSdrh   return rc;
6398416fc7fSdrh }
6408416fc7fSdrh 
6418416fc7fSdrh /*
6428416fc7fSdrh ** Free all the content in the edit-cost-table
6438416fc7fSdrh */
amatchFreeRules(amatch_vtab * p)6448416fc7fSdrh static void amatchFreeRules(amatch_vtab *p){
6458416fc7fSdrh   while( p->pRule ){
6468416fc7fSdrh     amatch_rule *pRule = p->pRule;
6478416fc7fSdrh     p->pRule = pRule->pNext;
6488416fc7fSdrh     sqlite3_free(pRule);
6498416fc7fSdrh   }
6508416fc7fSdrh   p->pRule = 0;
6518416fc7fSdrh }
6528416fc7fSdrh 
6538416fc7fSdrh /*
6548416fc7fSdrh ** Load the content of the amatch data table into memory.
6558416fc7fSdrh */
amatchLoadRules(sqlite3 * db,amatch_vtab * p,char ** pzErr)6568416fc7fSdrh static int amatchLoadRules(
6578416fc7fSdrh   sqlite3 *db,                    /* Database handle */
6588416fc7fSdrh   amatch_vtab *p,                 /* Virtual amatch table to configure */
6598416fc7fSdrh   char **pzErr                    /* OUT: Error message */
6608416fc7fSdrh ){
6618416fc7fSdrh   int rc = SQLITE_OK;             /* Return code */
6628416fc7fSdrh   char *zSql;                     /* SELECT used to read from rules table */
6638416fc7fSdrh   amatch_rule *pHead = 0;
6648416fc7fSdrh 
6658416fc7fSdrh   zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", p->zDb, p->zCostTab);
6668416fc7fSdrh   if( zSql==0 ){
6678416fc7fSdrh     rc = SQLITE_NOMEM;
6688416fc7fSdrh   }else{
6698416fc7fSdrh     int rc2;                      /* finalize() return code */
6708416fc7fSdrh     sqlite3_stmt *pStmt = 0;
6718416fc7fSdrh     rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
6728416fc7fSdrh     if( rc!=SQLITE_OK ){
6738416fc7fSdrh       *pzErr = sqlite3_mprintf("%s: %s", p->zClassName, sqlite3_errmsg(db));
6748416fc7fSdrh     }else if( sqlite3_column_count(pStmt)!=4 ){
6758416fc7fSdrh       *pzErr = sqlite3_mprintf("%s: %s has %d columns, expected 4",
6768416fc7fSdrh           p->zClassName, p->zCostTab, sqlite3_column_count(pStmt)
6778416fc7fSdrh       );
6788416fc7fSdrh       rc = SQLITE_ERROR;
6798416fc7fSdrh     }else{
6808416fc7fSdrh       while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
6818416fc7fSdrh         amatch_rule *pRule = 0;
6828416fc7fSdrh         rc = amatchLoadOneRule(p, pStmt, &pRule, pzErr);
6838416fc7fSdrh         if( pRule ){
6848416fc7fSdrh           pRule->pNext = pHead;
6858416fc7fSdrh           pHead = pRule;
6868416fc7fSdrh         }
6878416fc7fSdrh       }
6888416fc7fSdrh     }
6898416fc7fSdrh     rc2 = sqlite3_finalize(pStmt);
6908416fc7fSdrh     if( rc==SQLITE_OK ) rc = rc2;
6918416fc7fSdrh   }
6928416fc7fSdrh   sqlite3_free(zSql);
6938416fc7fSdrh 
6948416fc7fSdrh   /* All rules are now in a singly linked list starting at pHead. This
6958416fc7fSdrh   ** block sorts them by cost and then sets amatch_vtab.pRule to point to
6968416fc7fSdrh   ** point to the head of the sorted list.
6978416fc7fSdrh   */
6988416fc7fSdrh   if( rc==SQLITE_OK ){
6998416fc7fSdrh     unsigned int i;
7008416fc7fSdrh     amatch_rule *pX;
7018416fc7fSdrh     amatch_rule *a[15];
7028416fc7fSdrh     for(i=0; i<sizeof(a)/sizeof(a[0]); i++) a[i] = 0;
7038416fc7fSdrh     while( (pX = pHead)!=0 ){
7048416fc7fSdrh       pHead = pX->pNext;
7058416fc7fSdrh       pX->pNext = 0;
7068416fc7fSdrh       for(i=0; a[i] && i<sizeof(a)/sizeof(a[0])-1; i++){
7078416fc7fSdrh         pX = amatchMergeRules(a[i], pX);
7088416fc7fSdrh         a[i] = 0;
7098416fc7fSdrh       }
7108416fc7fSdrh       a[i] = amatchMergeRules(a[i], pX);
7118416fc7fSdrh     }
7128416fc7fSdrh     for(pX=a[0], i=1; i<sizeof(a)/sizeof(a[0]); i++){
7138416fc7fSdrh       pX = amatchMergeRules(a[i], pX);
7148416fc7fSdrh     }
7158416fc7fSdrh     p->pRule = amatchMergeRules(p->pRule, pX);
7168416fc7fSdrh   }else{
7178416fc7fSdrh     /* An error has occurred. Setting p->pRule to point to the head of the
7188416fc7fSdrh     ** allocated list ensures that the list will be cleaned up in this case.
7198416fc7fSdrh     */
7208416fc7fSdrh     assert( p->pRule==0 );
7218416fc7fSdrh     p->pRule = pHead;
7228416fc7fSdrh   }
7238416fc7fSdrh 
7248416fc7fSdrh   return rc;
7258416fc7fSdrh }
7268416fc7fSdrh 
7278416fc7fSdrh /*
7288416fc7fSdrh ** This function converts an SQL quoted string into an unquoted string
7298416fc7fSdrh ** and returns a pointer to a buffer allocated using sqlite3_malloc()
7308416fc7fSdrh ** containing the result. The caller should eventually free this buffer
7318416fc7fSdrh ** using sqlite3_free.
7328416fc7fSdrh **
7338416fc7fSdrh ** Examples:
7348416fc7fSdrh **
7358416fc7fSdrh **     "abc"   becomes   abc
7368416fc7fSdrh **     'xyz'   becomes   xyz
7378416fc7fSdrh **     [pqr]   becomes   pqr
7388416fc7fSdrh **     `mno`   becomes   mno
7398416fc7fSdrh */
amatchDequote(const char * zIn)7408416fc7fSdrh static char *amatchDequote(const char *zIn){
7412d77d80aSdrh   sqlite3_int64 nIn;              /* Size of input string, in bytes */
7428416fc7fSdrh   char *zOut;                     /* Output (dequoted) string */
7438416fc7fSdrh 
7442d77d80aSdrh   nIn = strlen(zIn);
7452d77d80aSdrh   zOut = sqlite3_malloc64(nIn+1);
7468416fc7fSdrh   if( zOut ){
7478416fc7fSdrh     char q = zIn[0];              /* Quote character (if any ) */
7488416fc7fSdrh 
7498416fc7fSdrh     if( q!='[' && q!= '\'' && q!='"' && q!='`' ){
750065f3bf4Smistachkin       memcpy(zOut, zIn, (size_t)(nIn+1));
7518416fc7fSdrh     }else{
7528416fc7fSdrh       int iOut = 0;               /* Index of next byte to write to output */
7538416fc7fSdrh       int iIn;                    /* Index of next byte to read from input */
7548416fc7fSdrh 
7558416fc7fSdrh       if( q=='[' ) q = ']';
7568416fc7fSdrh       for(iIn=1; iIn<nIn; iIn++){
7578416fc7fSdrh         if( zIn[iIn]==q ) iIn++;
7588416fc7fSdrh         zOut[iOut++] = zIn[iIn];
7598416fc7fSdrh       }
7608416fc7fSdrh     }
7618416fc7fSdrh     assert( (int)strlen(zOut)<=nIn );
7628416fc7fSdrh   }
7638416fc7fSdrh   return zOut;
7648416fc7fSdrh }
7658416fc7fSdrh 
7668416fc7fSdrh /*
7678416fc7fSdrh ** Deallocate the pVCheck prepared statement.
7688416fc7fSdrh */
amatchVCheckClear(amatch_vtab * p)7698416fc7fSdrh static void amatchVCheckClear(amatch_vtab *p){
7708416fc7fSdrh   if( p->pVCheck ){
7718416fc7fSdrh     sqlite3_finalize(p->pVCheck);
7728416fc7fSdrh     p->pVCheck = 0;
7738416fc7fSdrh   }
7748416fc7fSdrh }
7758416fc7fSdrh 
7768416fc7fSdrh /*
7778416fc7fSdrh ** Deallocate an amatch_vtab object
7788416fc7fSdrh */
amatchFree(amatch_vtab * p)7798416fc7fSdrh static void amatchFree(amatch_vtab *p){
7808416fc7fSdrh   if( p ){
7818416fc7fSdrh     amatchFreeRules(p);
7828416fc7fSdrh     amatchVCheckClear(p);
7838416fc7fSdrh     sqlite3_free(p->zClassName);
7848416fc7fSdrh     sqlite3_free(p->zDb);
7858416fc7fSdrh     sqlite3_free(p->zCostTab);
7868416fc7fSdrh     sqlite3_free(p->zVocabTab);
7878416fc7fSdrh     sqlite3_free(p->zVocabWord);
7888416fc7fSdrh     sqlite3_free(p->zVocabLang);
78992054fefSdrh     sqlite3_free(p->zSelf);
7908416fc7fSdrh     memset(p, 0, sizeof(*p));
7918416fc7fSdrh     sqlite3_free(p);
7928416fc7fSdrh   }
7938416fc7fSdrh }
7948416fc7fSdrh 
7958416fc7fSdrh /*
7968416fc7fSdrh ** xDisconnect/xDestroy method for the amatch module.
7978416fc7fSdrh */
amatchDisconnect(sqlite3_vtab * pVtab)7988416fc7fSdrh static int amatchDisconnect(sqlite3_vtab *pVtab){
7998416fc7fSdrh   amatch_vtab *p = (amatch_vtab*)pVtab;
8008416fc7fSdrh   assert( p->nCursor==0 );
8018416fc7fSdrh   amatchFree(p);
8028416fc7fSdrh   return SQLITE_OK;
8038416fc7fSdrh }
8048416fc7fSdrh 
8058416fc7fSdrh /*
8068416fc7fSdrh ** Check to see if the argument is of the form:
8078416fc7fSdrh **
8088416fc7fSdrh **       KEY = VALUE
8098416fc7fSdrh **
8108416fc7fSdrh ** If it is, return a pointer to the first character of VALUE.
8118416fc7fSdrh ** If not, return NULL.  Spaces around the = are ignored.
8128416fc7fSdrh */
amatchValueOfKey(const char * zKey,const char * zStr)8138416fc7fSdrh static const char *amatchValueOfKey(const char *zKey, const char *zStr){
8148416fc7fSdrh   int nKey = (int)strlen(zKey);
8158416fc7fSdrh   int nStr = (int)strlen(zStr);
8168416fc7fSdrh   int i;
8178416fc7fSdrh   if( nStr<nKey+1 ) return 0;
8188416fc7fSdrh   if( memcmp(zStr, zKey, nKey)!=0 ) return 0;
819c56fac74Sdrh   for(i=nKey; isspace((unsigned char)zStr[i]); i++){}
8208416fc7fSdrh   if( zStr[i]!='=' ) return 0;
8218416fc7fSdrh   i++;
822c56fac74Sdrh   while( isspace((unsigned char)zStr[i]) ){ i++; }
8238416fc7fSdrh   return zStr+i;
8248416fc7fSdrh }
8258416fc7fSdrh 
8268416fc7fSdrh /*
8278416fc7fSdrh ** xConnect/xCreate method for the amatch module. Arguments are:
8288416fc7fSdrh **
8298416fc7fSdrh **   argv[0]    -> module name  ("approximate_match")
8308416fc7fSdrh **   argv[1]    -> database name
8318416fc7fSdrh **   argv[2]    -> table name
8328416fc7fSdrh **   argv[3...] -> arguments
8338416fc7fSdrh */
amatchConnect(sqlite3 * db,void * pAux,int argc,const char * const * argv,sqlite3_vtab ** ppVtab,char ** pzErr)8348416fc7fSdrh static int amatchConnect(
8358416fc7fSdrh   sqlite3 *db,
8368416fc7fSdrh   void *pAux,
8378416fc7fSdrh   int argc, const char *const*argv,
8388416fc7fSdrh   sqlite3_vtab **ppVtab,
8398416fc7fSdrh   char **pzErr
8408416fc7fSdrh ){
8418416fc7fSdrh   int rc = SQLITE_OK;             /* Return code */
8428416fc7fSdrh   amatch_vtab *pNew = 0;          /* New virtual table */
8438416fc7fSdrh   const char *zModule = argv[0];
8448416fc7fSdrh   const char *zDb = argv[1];
8458416fc7fSdrh   const char *zVal;
8468416fc7fSdrh   int i;
8478416fc7fSdrh 
8488416fc7fSdrh   (void)pAux;
8498416fc7fSdrh   *ppVtab = 0;
8508416fc7fSdrh   pNew = sqlite3_malloc( sizeof(*pNew) );
8518416fc7fSdrh   if( pNew==0 ) return SQLITE_NOMEM;
8528416fc7fSdrh   rc = SQLITE_NOMEM;
8538416fc7fSdrh   memset(pNew, 0, sizeof(*pNew));
8548416fc7fSdrh   pNew->db = db;
8558416fc7fSdrh   pNew->zClassName = sqlite3_mprintf("%s", zModule);
8568416fc7fSdrh   if( pNew->zClassName==0 ) goto amatchConnectError;
8578416fc7fSdrh   pNew->zDb = sqlite3_mprintf("%s", zDb);
8588416fc7fSdrh   if( pNew->zDb==0 ) goto amatchConnectError;
8598416fc7fSdrh   pNew->zSelf = sqlite3_mprintf("%s", argv[2]);
8608416fc7fSdrh   if( pNew->zSelf==0 ) goto amatchConnectError;
8618416fc7fSdrh   for(i=3; i<argc; i++){
8628416fc7fSdrh     zVal = amatchValueOfKey("vocabulary_table", argv[i]);
8638416fc7fSdrh     if( zVal ){
8648416fc7fSdrh       sqlite3_free(pNew->zVocabTab);
8658416fc7fSdrh       pNew->zVocabTab = amatchDequote(zVal);
8668416fc7fSdrh       if( pNew->zVocabTab==0 ) goto amatchConnectError;
8678416fc7fSdrh       continue;
8688416fc7fSdrh     }
8698416fc7fSdrh     zVal = amatchValueOfKey("vocabulary_word", argv[i]);
8708416fc7fSdrh     if( zVal ){
8718416fc7fSdrh       sqlite3_free(pNew->zVocabWord);
8728416fc7fSdrh       pNew->zVocabWord = amatchDequote(zVal);
8738416fc7fSdrh       if( pNew->zVocabWord==0 ) goto amatchConnectError;
8748416fc7fSdrh       continue;
8758416fc7fSdrh     }
8768416fc7fSdrh     zVal = amatchValueOfKey("vocabulary_language", argv[i]);
8778416fc7fSdrh     if( zVal ){
8788416fc7fSdrh       sqlite3_free(pNew->zVocabLang);
8798416fc7fSdrh       pNew->zVocabLang = amatchDequote(zVal);
8808416fc7fSdrh       if( pNew->zVocabLang==0 ) goto amatchConnectError;
8818416fc7fSdrh       continue;
8828416fc7fSdrh     }
8838416fc7fSdrh     zVal = amatchValueOfKey("edit_distances", argv[i]);
8848416fc7fSdrh     if( zVal ){
8858416fc7fSdrh       sqlite3_free(pNew->zCostTab);
8868416fc7fSdrh       pNew->zCostTab = amatchDequote(zVal);
8878416fc7fSdrh       if( pNew->zCostTab==0 ) goto amatchConnectError;
8888416fc7fSdrh       continue;
8898416fc7fSdrh     }
8908416fc7fSdrh     *pzErr = sqlite3_mprintf("unrecognized argument: [%s]\n", argv[i]);
8918416fc7fSdrh     amatchFree(pNew);
8928416fc7fSdrh     *ppVtab = 0;
8938416fc7fSdrh     return SQLITE_ERROR;
8948416fc7fSdrh   }
8958416fc7fSdrh   rc = SQLITE_OK;
8968416fc7fSdrh   if( pNew->zCostTab==0 ){
8978416fc7fSdrh     *pzErr = sqlite3_mprintf("no edit_distances table specified");
8988416fc7fSdrh     rc = SQLITE_ERROR;
8998416fc7fSdrh   }else{
9008416fc7fSdrh     rc = amatchLoadRules(db, pNew, pzErr);
9018416fc7fSdrh   }
9028416fc7fSdrh   if( rc==SQLITE_OK ){
903*2b1c2aadSdrh     sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
9048416fc7fSdrh     rc = sqlite3_declare_vtab(db,
9058416fc7fSdrh            "CREATE TABLE x(word,distance,language,"
9068416fc7fSdrh            "command HIDDEN,nword HIDDEN)"
9078416fc7fSdrh          );
9088416fc7fSdrh #define AMATCH_COL_WORD       0
9098416fc7fSdrh #define AMATCH_COL_DISTANCE   1
9108416fc7fSdrh #define AMATCH_COL_LANGUAGE   2
9118416fc7fSdrh #define AMATCH_COL_COMMAND    3
9128416fc7fSdrh #define AMATCH_COL_NWORD      4
9138416fc7fSdrh   }
9148416fc7fSdrh   if( rc!=SQLITE_OK ){
9158416fc7fSdrh     amatchFree(pNew);
9168416fc7fSdrh   }
9178416fc7fSdrh   *ppVtab = &pNew->base;
9188416fc7fSdrh   return rc;
9198416fc7fSdrh 
9208416fc7fSdrh amatchConnectError:
9218416fc7fSdrh   amatchFree(pNew);
9228416fc7fSdrh   return rc;
9238416fc7fSdrh }
9248416fc7fSdrh 
9258416fc7fSdrh /*
9268416fc7fSdrh ** Open a new amatch cursor.
9278416fc7fSdrh */
amatchOpen(sqlite3_vtab * pVTab,sqlite3_vtab_cursor ** ppCursor)9288416fc7fSdrh static int amatchOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
9298416fc7fSdrh   amatch_vtab *p = (amatch_vtab*)pVTab;
9308416fc7fSdrh   amatch_cursor *pCur;
9318416fc7fSdrh   pCur = sqlite3_malloc( sizeof(*pCur) );
9328416fc7fSdrh   if( pCur==0 ) return SQLITE_NOMEM;
9338416fc7fSdrh   memset(pCur, 0, sizeof(*pCur));
9348416fc7fSdrh   pCur->pVtab = p;
9358416fc7fSdrh   *ppCursor = &pCur->base;
9368416fc7fSdrh   p->nCursor++;
9378416fc7fSdrh   return SQLITE_OK;
9388416fc7fSdrh }
9398416fc7fSdrh 
9408416fc7fSdrh /*
9418416fc7fSdrh ** Free up all the memory allocated by a cursor.  Set it rLimit to 0
9428416fc7fSdrh ** to indicate that it is at EOF.
9438416fc7fSdrh */
amatchClearCursor(amatch_cursor * pCur)9448416fc7fSdrh static void amatchClearCursor(amatch_cursor *pCur){
9458416fc7fSdrh   amatch_word *pWord, *pNextWord;
9468416fc7fSdrh   for(pWord=pCur->pAllWords; pWord; pWord=pNextWord){
9478416fc7fSdrh     pNextWord = pWord->pNext;
9488416fc7fSdrh     sqlite3_free(pWord);
9498416fc7fSdrh   }
9508416fc7fSdrh   pCur->pAllWords = 0;
9518416fc7fSdrh   sqlite3_free(pCur->zInput);
9528416fc7fSdrh   pCur->zInput = 0;
95392054fefSdrh   sqlite3_free(pCur->zBuf);
95492054fefSdrh   pCur->zBuf = 0;
95592054fefSdrh   pCur->nBuf = 0;
9568416fc7fSdrh   pCur->pCost = 0;
9578416fc7fSdrh   pCur->pWord = 0;
9588416fc7fSdrh   pCur->pCurrent = 0;
9598416fc7fSdrh   pCur->rLimit = 1000000;
9608416fc7fSdrh   pCur->iLang = 0;
9618416fc7fSdrh   pCur->nWord = 0;
9628416fc7fSdrh }
9638416fc7fSdrh 
9648416fc7fSdrh /*
9658416fc7fSdrh ** Close a amatch cursor.
9668416fc7fSdrh */
amatchClose(sqlite3_vtab_cursor * cur)9678416fc7fSdrh static int amatchClose(sqlite3_vtab_cursor *cur){
9688416fc7fSdrh   amatch_cursor *pCur = (amatch_cursor *)cur;
9698416fc7fSdrh   amatchClearCursor(pCur);
9708416fc7fSdrh   pCur->pVtab->nCursor--;
9718416fc7fSdrh   sqlite3_free(pCur);
9728416fc7fSdrh   return SQLITE_OK;
9738416fc7fSdrh }
9748416fc7fSdrh 
9758416fc7fSdrh /*
9768416fc7fSdrh ** Render a 24-bit unsigned integer as a 4-byte base-64 number.
9778416fc7fSdrh */
amatchEncodeInt(int x,char * z)9788416fc7fSdrh static void amatchEncodeInt(int x, char *z){
9798416fc7fSdrh   static const char a[] =
9808416fc7fSdrh     "0123456789"
9818416fc7fSdrh     "ABCDEFGHIJ"
9828416fc7fSdrh     "KLMNOPQRST"
9838416fc7fSdrh     "UVWXYZ^abc"
9848416fc7fSdrh     "defghijklm"
9858416fc7fSdrh     "nopqrstuvw"
9868416fc7fSdrh     "xyz~";
9878416fc7fSdrh   z[0] = a[(x>>18)&0x3f];
9888416fc7fSdrh   z[1] = a[(x>>12)&0x3f];
9898416fc7fSdrh   z[2] = a[(x>>6)&0x3f];
9908416fc7fSdrh   z[3] = a[x&0x3f];
9918416fc7fSdrh }
9928416fc7fSdrh 
9938416fc7fSdrh /*
9948416fc7fSdrh ** Write the zCost[] field for a amatch_word object
9958416fc7fSdrh */
amatchWriteCost(amatch_word * pWord)9968416fc7fSdrh static void amatchWriteCost(amatch_word *pWord){
9978416fc7fSdrh   amatchEncodeInt(pWord->rCost, pWord->zCost);
9988416fc7fSdrh   amatchEncodeInt(pWord->iSeq, pWord->zCost+4);
9998416fc7fSdrh   pWord->zCost[8] = 0;
10008416fc7fSdrh }
10018416fc7fSdrh 
100265545b59Sdrh /* Circumvent compiler warnings about the use of strcpy() by supplying
100365545b59Sdrh ** our own implementation.
100465545b59Sdrh */
amatchStrcpy(char * dest,const char * src)100565545b59Sdrh static void amatchStrcpy(char *dest, const char *src){
100665545b59Sdrh   while( (*(dest++) = *(src++))!=0 ){}
100765545b59Sdrh }
amatchStrcat(char * dest,const char * src)100865545b59Sdrh static void amatchStrcat(char *dest, const char *src){
100965545b59Sdrh   while( *dest ) dest++;
101065545b59Sdrh   amatchStrcpy(dest, src);
101165545b59Sdrh }
101265545b59Sdrh 
10138416fc7fSdrh /*
10148416fc7fSdrh ** Add a new amatch_word object to the queue.
10158416fc7fSdrh **
10168416fc7fSdrh ** If a prior amatch_word object with the same zWord, and nMatch
10178416fc7fSdrh ** already exists, update its rCost (if the new rCost is less) but
10188416fc7fSdrh ** otherwise leave it unchanged.  Do not add a duplicate.
10198416fc7fSdrh **
10208416fc7fSdrh ** Do nothing if the cost exceeds threshold.
10218416fc7fSdrh */
amatchAddWord(amatch_cursor * pCur,amatch_cost rCost,int nMatch,const char * zWordBase,const char * zWordTail)10228416fc7fSdrh static void amatchAddWord(
10238416fc7fSdrh   amatch_cursor *pCur,
10248416fc7fSdrh   amatch_cost rCost,
10258416fc7fSdrh   int nMatch,
10268416fc7fSdrh   const char *zWordBase,
10278416fc7fSdrh   const char *zWordTail
10288416fc7fSdrh ){
10298416fc7fSdrh   amatch_word *pWord;
10308416fc7fSdrh   amatch_avl *pNode;
10318416fc7fSdrh   amatch_avl *pOther;
10328416fc7fSdrh   int nBase, nTail;
10338416fc7fSdrh   char zBuf[4];
10348416fc7fSdrh 
10358416fc7fSdrh   if( rCost>pCur->rLimit ){
10368416fc7fSdrh     return;
10378416fc7fSdrh   }
10388416fc7fSdrh   nBase = (int)strlen(zWordBase);
10398416fc7fSdrh   nTail = (int)strlen(zWordTail);
10408416fc7fSdrh   if( nBase+nTail+3>pCur->nBuf ){
10418416fc7fSdrh     pCur->nBuf = nBase+nTail+100;
10428416fc7fSdrh     pCur->zBuf = sqlite3_realloc(pCur->zBuf, pCur->nBuf);
10438416fc7fSdrh     if( pCur->zBuf==0 ){
10448416fc7fSdrh       pCur->nBuf = 0;
10458416fc7fSdrh       return;
10468416fc7fSdrh     }
10478416fc7fSdrh   }
10488416fc7fSdrh   amatchEncodeInt(nMatch, zBuf);
10498416fc7fSdrh   memcpy(pCur->zBuf, zBuf+2, 2);
10508416fc7fSdrh   memcpy(pCur->zBuf+2, zWordBase, nBase);
10518416fc7fSdrh   memcpy(pCur->zBuf+2+nBase, zWordTail, nTail+1);
10528416fc7fSdrh   pNode = amatchAvlSearch(pCur->pWord, pCur->zBuf);
10538416fc7fSdrh   if( pNode ){
10548416fc7fSdrh     pWord = pNode->pWord;
10558416fc7fSdrh     if( pWord->rCost>rCost ){
10568416fc7fSdrh #ifdef AMATCH_TRACE_1
10578416fc7fSdrh       printf("UPDATE [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
10588416fc7fSdrh              pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput,
10598416fc7fSdrh              pWord->rCost, pWord->zWord, pWord->zCost);
10608416fc7fSdrh #endif
10618416fc7fSdrh       amatchAvlRemove(&pCur->pCost, &pWord->sCost);
10628416fc7fSdrh       pWord->rCost = rCost;
10638416fc7fSdrh       amatchWriteCost(pWord);
10648416fc7fSdrh #ifdef AMATCH_TRACE_1
10658416fc7fSdrh       printf("  ---> %d (\"%s\" \"%s\")\n",
10668416fc7fSdrh              pWord->rCost, pWord->zWord, pWord->zCost);
10678416fc7fSdrh #endif
10688416fc7fSdrh       pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost);
10698416fc7fSdrh       assert( pOther==0 ); (void)pOther;
10708416fc7fSdrh     }
10718416fc7fSdrh     return;
10728416fc7fSdrh   }
10732d77d80aSdrh   pWord = sqlite3_malloc64( sizeof(*pWord) + nBase + nTail - 1 );
10748416fc7fSdrh   if( pWord==0 ) return;
10758416fc7fSdrh   memset(pWord, 0, sizeof(*pWord));
10768416fc7fSdrh   pWord->rCost = rCost;
10778416fc7fSdrh   pWord->iSeq = pCur->nWord++;
10788416fc7fSdrh   amatchWriteCost(pWord);
107977fac879Smistachkin   pWord->nMatch = (short)nMatch;
10808416fc7fSdrh   pWord->pNext = pCur->pAllWords;
10818416fc7fSdrh   pCur->pAllWords = pWord;
10828416fc7fSdrh   pWord->sCost.zKey = pWord->zCost;
10838416fc7fSdrh   pWord->sCost.pWord = pWord;
10848416fc7fSdrh   pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost);
10858416fc7fSdrh   assert( pOther==0 ); (void)pOther;
10868416fc7fSdrh   pWord->sWord.zKey = pWord->zWord;
10878416fc7fSdrh   pWord->sWord.pWord = pWord;
108865545b59Sdrh   amatchStrcpy(pWord->zWord, pCur->zBuf);
10898416fc7fSdrh   pOther = amatchAvlInsert(&pCur->pWord, &pWord->sWord);
10908416fc7fSdrh   assert( pOther==0 ); (void)pOther;
10918416fc7fSdrh #ifdef AMATCH_TRACE_1
10928416fc7fSdrh   printf("INSERT [%s][%.*s^%s] %d (\"%s\" \"%s\")\n", pWord->zWord+2,
10938416fc7fSdrh        pWord->nMatch, pCur->zInput, pCur->zInput+pWord->nMatch, rCost,
10948416fc7fSdrh        pWord->zWord, pWord->zCost);
10958416fc7fSdrh #endif
10968416fc7fSdrh }
10978416fc7fSdrh 
109865545b59Sdrh 
10998416fc7fSdrh /*
11008416fc7fSdrh ** Advance a cursor to its next row of output
11018416fc7fSdrh */
amatchNext(sqlite3_vtab_cursor * cur)11028416fc7fSdrh static int amatchNext(sqlite3_vtab_cursor *cur){
11038416fc7fSdrh   amatch_cursor *pCur = (amatch_cursor*)cur;
11048416fc7fSdrh   amatch_word *pWord = 0;
11058416fc7fSdrh   amatch_avl *pNode;
11068416fc7fSdrh   int isMatch = 0;
11078416fc7fSdrh   amatch_vtab *p = pCur->pVtab;
11088416fc7fSdrh   int nWord;
11098416fc7fSdrh   int rc;
11108416fc7fSdrh   int i;
11118416fc7fSdrh   const char *zW;
11128416fc7fSdrh   amatch_rule *pRule;
11138416fc7fSdrh   char *zBuf = 0;
11148416fc7fSdrh   char nBuf = 0;
11158416fc7fSdrh   char zNext[8];
11168416fc7fSdrh   char zNextIn[8];
11178416fc7fSdrh   int nNextIn;
11188416fc7fSdrh 
11198416fc7fSdrh   if( p->pVCheck==0 ){
11208416fc7fSdrh     char *zSql;
11218416fc7fSdrh     if( p->zVocabLang && p->zVocabLang[0] ){
11228416fc7fSdrh       zSql = sqlite3_mprintf(
112392054fefSdrh           "SELECT \"%w\" FROM \"%w\"",
11248416fc7fSdrh           " WHERE \"%w\">=?1 AND \"%w\"=?2"
11258416fc7fSdrh           " ORDER BY 1",
11268416fc7fSdrh           p->zVocabWord, p->zVocabTab,
11278416fc7fSdrh           p->zVocabWord, p->zVocabLang
11288416fc7fSdrh       );
11298416fc7fSdrh     }else{
11308416fc7fSdrh       zSql = sqlite3_mprintf(
113192054fefSdrh           "SELECT \"%w\" FROM \"%w\""
11328416fc7fSdrh           " WHERE \"%w\">=?1"
11338416fc7fSdrh           " ORDER BY 1",
11348416fc7fSdrh           p->zVocabWord, p->zVocabTab,
11358416fc7fSdrh           p->zVocabWord
11368416fc7fSdrh       );
11378416fc7fSdrh     }
11388416fc7fSdrh     rc = sqlite3_prepare_v2(p->db, zSql, -1, &p->pVCheck, 0);
11398416fc7fSdrh     sqlite3_free(zSql);
11408416fc7fSdrh     if( rc ) return rc;
11418416fc7fSdrh   }
11428416fc7fSdrh   sqlite3_bind_int(p->pVCheck, 2, pCur->iLang);
11438416fc7fSdrh 
11448416fc7fSdrh   do{
11458416fc7fSdrh     pNode = amatchAvlFirst(pCur->pCost);
11468416fc7fSdrh     if( pNode==0 ){
11478416fc7fSdrh       pWord = 0;
11488416fc7fSdrh       break;
11498416fc7fSdrh     }
11508416fc7fSdrh     pWord = pNode->pWord;
11518416fc7fSdrh     amatchAvlRemove(&pCur->pCost, &pWord->sCost);
11528416fc7fSdrh 
11538416fc7fSdrh #ifdef AMATCH_TRACE_1
11548416fc7fSdrh     printf("PROCESS [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
11558416fc7fSdrh        pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput+pWord->nMatch,
11568416fc7fSdrh        pWord->rCost, pWord->zWord, pWord->zCost);
11578416fc7fSdrh #endif
11588416fc7fSdrh     nWord = (int)strlen(pWord->zWord+2);
11598416fc7fSdrh     if( nWord+20>nBuf ){
116077fac879Smistachkin       nBuf = (char)(nWord+100);
11618416fc7fSdrh       zBuf = sqlite3_realloc(zBuf, nBuf);
11628416fc7fSdrh       if( zBuf==0 ) return SQLITE_NOMEM;
11638416fc7fSdrh     }
116465545b59Sdrh     amatchStrcpy(zBuf, pWord->zWord+2);
11658416fc7fSdrh     zNext[0] = 0;
11668416fc7fSdrh     zNextIn[0] = pCur->zInput[pWord->nMatch];
11678416fc7fSdrh     if( zNextIn[0] ){
11688416fc7fSdrh       for(i=1; i<=4 && (pCur->zInput[pWord->nMatch+i]&0xc0)==0x80; i++){
11698416fc7fSdrh         zNextIn[i] = pCur->zInput[pWord->nMatch+i];
11708416fc7fSdrh       }
11718416fc7fSdrh       zNextIn[i] = 0;
11728416fc7fSdrh       nNextIn = i;
11738416fc7fSdrh     }else{
11748416fc7fSdrh       nNextIn = 0;
11758416fc7fSdrh     }
11768416fc7fSdrh 
11778416fc7fSdrh     if( zNextIn[0] && zNextIn[0]!='*' ){
11788416fc7fSdrh       sqlite3_reset(p->pVCheck);
117965545b59Sdrh       amatchStrcat(zBuf, zNextIn);
11808416fc7fSdrh       sqlite3_bind_text(p->pVCheck, 1, zBuf, nWord+nNextIn, SQLITE_STATIC);
11818416fc7fSdrh       rc = sqlite3_step(p->pVCheck);
11828416fc7fSdrh       if( rc==SQLITE_ROW ){
11838416fc7fSdrh         zW = (const char*)sqlite3_column_text(p->pVCheck, 0);
11848416fc7fSdrh         if( strncmp(zBuf, zW, nWord+nNextIn)==0 ){
11858416fc7fSdrh           amatchAddWord(pCur, pWord->rCost, pWord->nMatch+nNextIn, zBuf, "");
11868416fc7fSdrh         }
11878416fc7fSdrh       }
11888416fc7fSdrh       zBuf[nWord] = 0;
11898416fc7fSdrh     }
11908416fc7fSdrh 
11918416fc7fSdrh     while( 1 ){
119265545b59Sdrh       amatchStrcpy(zBuf+nWord, zNext);
11938416fc7fSdrh       sqlite3_reset(p->pVCheck);
11948416fc7fSdrh       sqlite3_bind_text(p->pVCheck, 1, zBuf, -1, SQLITE_TRANSIENT);
11958416fc7fSdrh       rc = sqlite3_step(p->pVCheck);
11968416fc7fSdrh       if( rc!=SQLITE_ROW ) break;
11978416fc7fSdrh       zW = (const char*)sqlite3_column_text(p->pVCheck, 0);
119865545b59Sdrh       amatchStrcpy(zBuf+nWord, zNext);
11998416fc7fSdrh       if( strncmp(zW, zBuf, nWord)!=0 ) break;
12008416fc7fSdrh       if( (zNextIn[0]=='*' && zNextIn[1]==0)
12018416fc7fSdrh        || (zNextIn[0]==0 && zW[nWord]==0)
12028416fc7fSdrh       ){
12038416fc7fSdrh         isMatch = 1;
12048416fc7fSdrh         zNextIn[0] = 0;
12058416fc7fSdrh         nNextIn = 0;
12068416fc7fSdrh         break;
12078416fc7fSdrh       }
12088416fc7fSdrh       zNext[0] = zW[nWord];
12098416fc7fSdrh       for(i=1; i<=4 && (zW[nWord+i]&0xc0)==0x80; i++){
12108416fc7fSdrh         zNext[i] = zW[nWord+i];
12118416fc7fSdrh       }
12128416fc7fSdrh       zNext[i] = 0;
12138416fc7fSdrh       zBuf[nWord] = 0;
12148416fc7fSdrh       if( p->rIns>0 ){
12158416fc7fSdrh         amatchAddWord(pCur, pWord->rCost+p->rIns, pWord->nMatch,
12168416fc7fSdrh                       zBuf, zNext);
12178416fc7fSdrh       }
12188416fc7fSdrh       if( p->rSub>0 ){
12198416fc7fSdrh         amatchAddWord(pCur, pWord->rCost+p->rSub, pWord->nMatch+nNextIn,
12208416fc7fSdrh                       zBuf, zNext);
12218416fc7fSdrh       }
12228416fc7fSdrh       if( p->rIns<0 && p->rSub<0 ) break;
12238416fc7fSdrh       zNext[i-1]++;  /* FIX ME */
12248416fc7fSdrh     }
12258416fc7fSdrh     sqlite3_reset(p->pVCheck);
12268416fc7fSdrh 
12278416fc7fSdrh     if( p->rDel>0 ){
12288416fc7fSdrh       zBuf[nWord] = 0;
12298416fc7fSdrh       amatchAddWord(pCur, pWord->rCost+p->rDel, pWord->nMatch+nNextIn,
12308416fc7fSdrh                     zBuf, "");
12318416fc7fSdrh     }
12328416fc7fSdrh 
12338416fc7fSdrh     for(pRule=p->pRule; pRule; pRule=pRule->pNext){
12348416fc7fSdrh       if( pRule->iLang!=pCur->iLang ) continue;
12358416fc7fSdrh       if( strncmp(pRule->zFrom, pCur->zInput+pWord->nMatch, pRule->nFrom)==0 ){
12368416fc7fSdrh         amatchAddWord(pCur, pWord->rCost+pRule->rCost,
12378416fc7fSdrh                       pWord->nMatch+pRule->nFrom, pWord->zWord+2, pRule->zTo);
12388416fc7fSdrh       }
12398416fc7fSdrh     }
12408416fc7fSdrh   }while( !isMatch );
12418416fc7fSdrh   pCur->pCurrent = pWord;
12428416fc7fSdrh   sqlite3_free(zBuf);
12438416fc7fSdrh   return SQLITE_OK;
12448416fc7fSdrh }
12458416fc7fSdrh 
12468416fc7fSdrh /*
12478416fc7fSdrh ** Called to "rewind" a cursor back to the beginning so that
12488416fc7fSdrh ** it starts its output over again.  Always called at least once
12498416fc7fSdrh ** prior to any amatchColumn, amatchRowid, or amatchEof call.
12508416fc7fSdrh */
amatchFilter(sqlite3_vtab_cursor * pVtabCursor,int idxNum,const char * idxStr,int argc,sqlite3_value ** argv)12518416fc7fSdrh static int amatchFilter(
12528416fc7fSdrh   sqlite3_vtab_cursor *pVtabCursor,
12538416fc7fSdrh   int idxNum, const char *idxStr,
12548416fc7fSdrh   int argc, sqlite3_value **argv
12558416fc7fSdrh ){
12568416fc7fSdrh   amatch_cursor *pCur = (amatch_cursor *)pVtabCursor;
12578416fc7fSdrh   const char *zWord = "*";
12588416fc7fSdrh   int idx;
12598416fc7fSdrh 
12608416fc7fSdrh   amatchClearCursor(pCur);
12618416fc7fSdrh   idx = 0;
12628416fc7fSdrh   if( idxNum & 1 ){
12638416fc7fSdrh     zWord = (const char*)sqlite3_value_text(argv[0]);
12648416fc7fSdrh     idx++;
12658416fc7fSdrh   }
12668416fc7fSdrh   if( idxNum & 2 ){
12678416fc7fSdrh     pCur->rLimit = (amatch_cost)sqlite3_value_int(argv[idx]);
12688416fc7fSdrh     idx++;
12698416fc7fSdrh   }
12708416fc7fSdrh   if( idxNum & 4 ){
12718416fc7fSdrh     pCur->iLang = (amatch_cost)sqlite3_value_int(argv[idx]);
12728416fc7fSdrh     idx++;
12738416fc7fSdrh   }
12748416fc7fSdrh   pCur->zInput = sqlite3_mprintf("%s", zWord);
12758416fc7fSdrh   if( pCur->zInput==0 ) return SQLITE_NOMEM;
12768416fc7fSdrh   amatchAddWord(pCur, 0, 0, "", "");
12778416fc7fSdrh   amatchNext(pVtabCursor);
12788416fc7fSdrh 
12798416fc7fSdrh   return SQLITE_OK;
12808416fc7fSdrh }
12818416fc7fSdrh 
12828416fc7fSdrh /*
12838416fc7fSdrh ** Only the word and distance columns have values.  All other columns
12848416fc7fSdrh ** return NULL
12858416fc7fSdrh */
amatchColumn(sqlite3_vtab_cursor * cur,sqlite3_context * ctx,int i)12868416fc7fSdrh static int amatchColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
12878416fc7fSdrh   amatch_cursor *pCur = (amatch_cursor*)cur;
12888416fc7fSdrh   switch( i ){
12898416fc7fSdrh     case AMATCH_COL_WORD: {
12908416fc7fSdrh       sqlite3_result_text(ctx, pCur->pCurrent->zWord+2, -1, SQLITE_STATIC);
12918416fc7fSdrh       break;
12928416fc7fSdrh     }
12938416fc7fSdrh     case AMATCH_COL_DISTANCE: {
12948416fc7fSdrh       sqlite3_result_int(ctx, pCur->pCurrent->rCost);
12958416fc7fSdrh       break;
12968416fc7fSdrh     }
12978416fc7fSdrh     case AMATCH_COL_LANGUAGE: {
12988416fc7fSdrh       sqlite3_result_int(ctx, pCur->iLang);
12998416fc7fSdrh       break;
13008416fc7fSdrh     }
13018416fc7fSdrh     case AMATCH_COL_NWORD: {
13028416fc7fSdrh       sqlite3_result_int(ctx, pCur->nWord);
13038416fc7fSdrh       break;
13048416fc7fSdrh     }
13058416fc7fSdrh     default: {
13068416fc7fSdrh       sqlite3_result_null(ctx);
13078416fc7fSdrh       break;
13088416fc7fSdrh     }
13098416fc7fSdrh   }
13108416fc7fSdrh   return SQLITE_OK;
13118416fc7fSdrh }
13128416fc7fSdrh 
13138416fc7fSdrh /*
13148416fc7fSdrh ** The rowid.
13158416fc7fSdrh */
amatchRowid(sqlite3_vtab_cursor * cur,sqlite_int64 * pRowid)13168416fc7fSdrh static int amatchRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
13178416fc7fSdrh   amatch_cursor *pCur = (amatch_cursor*)cur;
13188416fc7fSdrh   *pRowid = pCur->iRowid;
13198416fc7fSdrh   return SQLITE_OK;
13208416fc7fSdrh }
13218416fc7fSdrh 
13228416fc7fSdrh /*
13238416fc7fSdrh ** EOF indicator
13248416fc7fSdrh */
amatchEof(sqlite3_vtab_cursor * cur)13258416fc7fSdrh static int amatchEof(sqlite3_vtab_cursor *cur){
13268416fc7fSdrh   amatch_cursor *pCur = (amatch_cursor*)cur;
13278416fc7fSdrh   return pCur->pCurrent==0;
13288416fc7fSdrh }
13298416fc7fSdrh 
13308416fc7fSdrh /*
13318416fc7fSdrh ** Search for terms of these forms:
13328416fc7fSdrh **
13338416fc7fSdrh **   (A)    word MATCH $str
13348416fc7fSdrh **   (B1)   distance < $value
13358416fc7fSdrh **   (B2)   distance <= $value
13368416fc7fSdrh **   (C)    language == $language
13378416fc7fSdrh **
13388416fc7fSdrh ** The distance< and distance<= are both treated as distance<=.
13398416fc7fSdrh ** The query plan number is a bit vector:
13408416fc7fSdrh **
13418416fc7fSdrh **   bit 1:   Term of the form (A) found
13428416fc7fSdrh **   bit 2:   Term like (B1) or (B2) found
13438416fc7fSdrh **   bit 3:   Term like (C) found
13448416fc7fSdrh **
13458416fc7fSdrh ** If bit-1 is set, $str is always in filter.argv[0].  If bit-2 is set
13468416fc7fSdrh ** then $value is in filter.argv[0] if bit-1 is clear and is in
13478416fc7fSdrh ** filter.argv[1] if bit-1 is set.  If bit-3 is set, then $ruleid is
13488416fc7fSdrh ** in filter.argv[0] if bit-1 and bit-2 are both zero, is in
13498416fc7fSdrh ** filter.argv[1] if exactly one of bit-1 and bit-2 are set, and is in
13508416fc7fSdrh ** filter.argv[2] if both bit-1 and bit-2 are set.
13518416fc7fSdrh */
amatchBestIndex(sqlite3_vtab * tab,sqlite3_index_info * pIdxInfo)13528416fc7fSdrh static int amatchBestIndex(
13538416fc7fSdrh   sqlite3_vtab *tab,
13548416fc7fSdrh   sqlite3_index_info *pIdxInfo
13558416fc7fSdrh ){
13568416fc7fSdrh   int iPlan = 0;
13578416fc7fSdrh   int iDistTerm = -1;
13588416fc7fSdrh   int iLangTerm = -1;
13598416fc7fSdrh   int i;
13608416fc7fSdrh   const struct sqlite3_index_constraint *pConstraint;
13618416fc7fSdrh 
13628416fc7fSdrh   (void)tab;
13638416fc7fSdrh   pConstraint = pIdxInfo->aConstraint;
13648416fc7fSdrh   for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
13658416fc7fSdrh     if( pConstraint->usable==0 ) continue;
13668416fc7fSdrh     if( (iPlan & 1)==0
13678416fc7fSdrh      && pConstraint->iColumn==0
13688416fc7fSdrh      && pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH
13698416fc7fSdrh     ){
13708416fc7fSdrh       iPlan |= 1;
13718416fc7fSdrh       pIdxInfo->aConstraintUsage[i].argvIndex = 1;
13728416fc7fSdrh       pIdxInfo->aConstraintUsage[i].omit = 1;
13738416fc7fSdrh     }
13748416fc7fSdrh     if( (iPlan & 2)==0
13758416fc7fSdrh      && pConstraint->iColumn==1
13768416fc7fSdrh      && (pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT
13778416fc7fSdrh            || pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE)
13788416fc7fSdrh     ){
13798416fc7fSdrh       iPlan |= 2;
13808416fc7fSdrh       iDistTerm = i;
13818416fc7fSdrh     }
13828416fc7fSdrh     if( (iPlan & 4)==0
13838416fc7fSdrh      && pConstraint->iColumn==2
13848416fc7fSdrh      && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ
13858416fc7fSdrh     ){
13868416fc7fSdrh       iPlan |= 4;
13878416fc7fSdrh       pIdxInfo->aConstraintUsage[i].omit = 1;
13888416fc7fSdrh       iLangTerm = i;
13898416fc7fSdrh     }
13908416fc7fSdrh   }
13918416fc7fSdrh   if( iPlan & 2 ){
13928416fc7fSdrh     pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 1+((iPlan&1)!=0);
13938416fc7fSdrh   }
13948416fc7fSdrh   if( iPlan & 4 ){
13958416fc7fSdrh     int idx = 1;
13968416fc7fSdrh     if( iPlan & 1 ) idx++;
13978416fc7fSdrh     if( iPlan & 2 ) idx++;
13988416fc7fSdrh     pIdxInfo->aConstraintUsage[iLangTerm].argvIndex = idx;
13998416fc7fSdrh   }
14008416fc7fSdrh   pIdxInfo->idxNum = iPlan;
14018416fc7fSdrh   if( pIdxInfo->nOrderBy==1
14028416fc7fSdrh    && pIdxInfo->aOrderBy[0].iColumn==1
14038416fc7fSdrh    && pIdxInfo->aOrderBy[0].desc==0
14048416fc7fSdrh   ){
14058416fc7fSdrh     pIdxInfo->orderByConsumed = 1;
14068416fc7fSdrh   }
14078416fc7fSdrh   pIdxInfo->estimatedCost = (double)10000;
14088416fc7fSdrh 
14098416fc7fSdrh   return SQLITE_OK;
14108416fc7fSdrh }
14118416fc7fSdrh 
14128416fc7fSdrh /*
14138416fc7fSdrh ** The xUpdate() method.
14148416fc7fSdrh **
14158416fc7fSdrh ** This implementation disallows DELETE and UPDATE.  The only thing
14168416fc7fSdrh ** allowed is INSERT into the "command" column.
14178416fc7fSdrh */
amatchUpdate(sqlite3_vtab * pVTab,int argc,sqlite3_value ** argv,sqlite_int64 * pRowid)14188416fc7fSdrh static int amatchUpdate(
14198416fc7fSdrh   sqlite3_vtab *pVTab,
14208416fc7fSdrh   int argc,
14218416fc7fSdrh   sqlite3_value **argv,
14228416fc7fSdrh   sqlite_int64 *pRowid
14238416fc7fSdrh ){
14248416fc7fSdrh   amatch_vtab *p = (amatch_vtab*)pVTab;
14258416fc7fSdrh   const unsigned char *zCmd;
14268416fc7fSdrh   (void)pRowid;
14278416fc7fSdrh   if( argc==1 ){
14288416fc7fSdrh     pVTab->zErrMsg = sqlite3_mprintf("DELETE from %s is not allowed",
14298416fc7fSdrh                                       p->zSelf);
14308416fc7fSdrh     return SQLITE_ERROR;
14318416fc7fSdrh   }
14328416fc7fSdrh   if( sqlite3_value_type(argv[0])!=SQLITE_NULL ){
14338416fc7fSdrh     pVTab->zErrMsg = sqlite3_mprintf("UPDATE of %s is not allowed",
14348416fc7fSdrh                                       p->zSelf);
14358416fc7fSdrh     return SQLITE_ERROR;
14368416fc7fSdrh   }
14378416fc7fSdrh   if( sqlite3_value_type(argv[2+AMATCH_COL_WORD])!=SQLITE_NULL
14388416fc7fSdrh    || sqlite3_value_type(argv[2+AMATCH_COL_DISTANCE])!=SQLITE_NULL
14398416fc7fSdrh    || sqlite3_value_type(argv[2+AMATCH_COL_LANGUAGE])!=SQLITE_NULL
14408416fc7fSdrh   ){
14418416fc7fSdrh     pVTab->zErrMsg = sqlite3_mprintf(
14428416fc7fSdrh             "INSERT INTO %s allowed for column [command] only", p->zSelf);
14438416fc7fSdrh     return SQLITE_ERROR;
14448416fc7fSdrh   }
14458416fc7fSdrh   zCmd = sqlite3_value_text(argv[2+AMATCH_COL_COMMAND]);
14468416fc7fSdrh   if( zCmd==0 ) return SQLITE_OK;
14478416fc7fSdrh 
14488416fc7fSdrh   return SQLITE_OK;
14498416fc7fSdrh }
14508416fc7fSdrh 
14518416fc7fSdrh /*
14528416fc7fSdrh ** A virtual table module that implements the "approximate_match".
14538416fc7fSdrh */
14548416fc7fSdrh static sqlite3_module amatchModule = {
14558416fc7fSdrh   0,                      /* iVersion */
14568416fc7fSdrh   amatchConnect,          /* xCreate */
14578416fc7fSdrh   amatchConnect,          /* xConnect */
14588416fc7fSdrh   amatchBestIndex,        /* xBestIndex */
14598416fc7fSdrh   amatchDisconnect,       /* xDisconnect */
14608416fc7fSdrh   amatchDisconnect,       /* xDestroy */
14618416fc7fSdrh   amatchOpen,             /* xOpen - open a cursor */
14628416fc7fSdrh   amatchClose,            /* xClose - close a cursor */
14638416fc7fSdrh   amatchFilter,           /* xFilter - configure scan constraints */
14648416fc7fSdrh   amatchNext,             /* xNext - advance a cursor */
14658416fc7fSdrh   amatchEof,              /* xEof - check for end of scan */
14668416fc7fSdrh   amatchColumn,           /* xColumn - read data */
14678416fc7fSdrh   amatchRowid,            /* xRowid - read data */
14688416fc7fSdrh   amatchUpdate,           /* xUpdate */
14698416fc7fSdrh   0,                      /* xBegin */
14708416fc7fSdrh   0,                      /* xSync */
14718416fc7fSdrh   0,                      /* xCommit */
14728416fc7fSdrh   0,                      /* xRollback */
14738416fc7fSdrh   0,                      /* xFindMethod */
14748416fc7fSdrh   0,                      /* xRename */
14758416fc7fSdrh   0,                      /* xSavepoint */
14768416fc7fSdrh   0,                      /* xRelease */
147784c501baSdrh   0,                      /* xRollbackTo */
147884c501baSdrh   0                       /* xShadowName */
14798416fc7fSdrh };
14808416fc7fSdrh 
148111f71d6aSdan #endif /* SQLITE_OMIT_VIRTUALTABLE */
148211f71d6aSdan 
14838416fc7fSdrh /*
14848416fc7fSdrh ** Register the amatch virtual table
14858416fc7fSdrh */
14868416fc7fSdrh #ifdef _WIN32
14878416fc7fSdrh __declspec(dllexport)
14888416fc7fSdrh #endif
sqlite3_amatch_init(sqlite3 * db,char ** pzErrMsg,const sqlite3_api_routines * pApi)14898416fc7fSdrh int sqlite3_amatch_init(
14908416fc7fSdrh   sqlite3 *db,
14918416fc7fSdrh   char **pzErrMsg,
14928416fc7fSdrh   const sqlite3_api_routines *pApi
14938416fc7fSdrh ){
14948416fc7fSdrh   int rc = SQLITE_OK;
14958416fc7fSdrh   SQLITE_EXTENSION_INIT2(pApi);
14968416fc7fSdrh   (void)pzErrMsg;  /* Not used */
149711f71d6aSdan #ifndef SQLITE_OMIT_VIRTUALTABLE
14988416fc7fSdrh   rc = sqlite3_create_module(db, "approximate_match", &amatchModule, 0);
149911f71d6aSdan #endif /* SQLITE_OMIT_VIRTUALTABLE */
15008416fc7fSdrh   return rc;
15018416fc7fSdrh }
1502