4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 *************************************************************************
13 ** This file contains code for a demonstration virtual table that finds
14 ** "approximate matches" - strings from a finite set that are nearly the
15 ** same as a single input string. The virtual table is called "amatch".
17 ** A amatch virtual table is created like this:
19 ** CREATE VIRTUAL TABLE f USING approximate_match(
20 ** vocabulary_table=<tablename>, -- V
21 ** vocabulary_word=<columnname>, -- W
22 ** vocabulary_language=<columnname>, -- L
23 ** edit_distances=<edit-cost-table>
26 ** When it is created, the new amatch table must be supplied with the
27 ** the name of a table V and columns V.W and V.L such that
29 ** SELECT W FROM V WHERE L=$language
31 ** returns the allowed vocabulary for the match. If the "vocabulary_language"
32 ** or L columnname is left unspecified or is an empty string, then no
33 ** filtering of the vocabulary by language is performed.
35 ** For efficiency, it is essential that the vocabulary table be indexed:
37 ** CREATE vocab_index ON V(W)
39 ** A separate edit-cost-table provides scoring information that defines
40 ** what it means for one string to be "close" to another.
42 ** The edit-cost-table must contain exactly four columns (more precisely,
43 ** the statement "SELECT * FROM <edit-cost-table>" must return records
44 ** that consist of four columns). It does not matter what the columns are
47 ** Each row in the edit-cost-table represents a single character
48 ** transformation going from user input to the vocabulary. The leftmost
49 ** column of the row (column 0) contains an integer identifier of the
50 ** language to which the transformation rule belongs (see "MULTIPLE LANGUAGES"
51 ** below). The second column of the row (column 1) contains the input
52 ** character or characters - the characters of user input. The third
53 ** column contains characters as they appear in the vocabulary table.
54 ** And the fourth column contains the integer cost of making the
55 ** transformation. For example:
57 ** CREATE TABLE f_data(iLang, cFrom, cTo, Cost);
58 ** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '', 'a', 100);
59 ** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'b', '', 87);
60 ** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'o', 'oe', 38);
61 ** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, 'oe', 'o', 40);
63 ** The first row inserted into the edit-cost-table by the SQL script
64 ** above indicates that the cost of having an extra 'a' in the vocabulary
65 ** table that is missing in the user input 100. (All costs are integers.
66 ** Overall cost must not exceed 16777216.) The second INSERT statement
67 ** creates a rule saying that the cost of having a single letter 'b' in
68 ** user input which is missing in the vocabulary table is 87. The third
69 ** INSERT statement mean that the cost of matching an 'o' in user input
70 ** against an 'oe' in the vocabulary table is 38. And so forth.
72 ** The following rules are special:
74 ** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '?', '', 97);
75 ** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '', '?', 98);
76 ** INSERT INTO f_data(iLang, cFrom, cTo, Cost) VALUES(0, '?', '?', 99);
78 ** The '?' to '' rule is the cost of having any single character in the input
79 ** that is not found in the vocabular. The '' to '?' rule is the cost of
80 ** having a character in the vocabulary table that is missing from input.
81 ** And the '?' to '?' rule is the cost of doing an arbitrary character
82 ** substitution. These three generic rules apply across all languages.
83 ** In other words, the iLang field is ignored for the generic substitution
84 ** rules. If more than one cost is given for a generic substitution rule,
85 ** then the lowest cost is used.
87 ** Once it has been created, the amatch virtual table can be queried
90 ** SELECT word, distance FROM f
91 ** WHERE word MATCH 'abcdefg'
94 ** This query outputs the strings contained in the T(F) field that
95 ** are close to "abcdefg" and in order of increasing distance. No string
96 ** is output more than once. If there are multiple ways to transform the
97 ** target string ("abcdefg") into a string in the vocabulary table then
98 ** the lowest cost transform is the one that is returned. In this example,
99 ** the search is limited to strings with a total distance of less than 200.
101 ** For efficiency, it is important to put tight bounds on the distance.
102 ** The time and memory space needed to perform this query is exponential
103 ** in the maximum distance. A good rule of thumb is to limit the distance
104 ** to no more than 1.5 or 2 times the maximum cost of any rule in the
107 ** The amatch is a read-only table. Any attempt to DELETE, INSERT, or
108 ** UPDATE on a amatch table will throw an error.
110 ** It is important to put some kind of a limit on the amatch output. This
111 ** can be either in the form of a LIMIT clause at the end of the query,
112 ** or better, a "distance<NNN" constraint where NNN is some number. The
113 ** running time and memory requirement is exponential in the value of NNN
114 ** so you want to make sure that NNN is not too big. A value of NNN that
115 ** is about twice the average transformation cost seems to give good results.
117 ** The amatch table can be useful for tasks such as spelling correction.
118 ** Suppose all allowed words are in table vocabulary(w). Then one would create
119 ** an amatch virtual table like this:
121 ** CREATE VIRTUAL TABLE ex1 USING amatch(
122 ** vocabtable=vocabulary,
124 ** edit_distances=ec1
127 ** Then given an input word $word, look up close spellings this way:
129 ** SELECT word, distance FROM ex1
130 ** WHERE word MATCH $word AND distance<200;
132 ** MULTIPLE LANGUAGES
134 ** Normally, the "iLang" value associated with all character transformations
135 ** in the edit-cost-table is zero. However, if required, the amatch
136 ** virtual table allows multiple languages to be defined. Each query uses
137 ** only a single iLang value. This allows, for example, a single
138 ** amatch table to support multiple languages.
140 ** By default, only the rules with iLang=0 are used. To specify an
141 ** alternative language, a "language = ?" expression must be added to the
142 ** WHERE clause of a SELECT, where ? is the integer identifier of the desired
143 ** language. For example:
145 ** SELECT word, distance FROM ex1
146 ** WHERE word MATCH $word
148 ** AND language=1 -- Specify use language 1 instead of 0
150 ** If no "language = ?" constraint is specified in the WHERE clause, language
155 ** The maximum language number is 2147483647. The maximum length of either
156 ** of the strings in the second or third column of the amatch data table
157 ** is 50 bytes. The maximum cost on a rule is 1000.
159 #include "sqlite3ext.h"
160 SQLITE_EXTENSION_INIT1
167 #ifndef SQLITE_OMIT_VIRTUALTABLE
170 ** Forward declaration of objects used by this implementation
172 typedef struct amatch_vtab amatch_vtab
;
173 typedef struct amatch_cursor amatch_cursor
;
174 typedef struct amatch_rule amatch_rule
;
175 typedef struct amatch_word amatch_word
;
176 typedef struct amatch_avl amatch_avl
;
179 /*****************************************************************************
180 ** AVL Tree implementation
183 ** Objects that want to be members of the AVL tree should embedded an
184 ** instance of this structure.
187 amatch_word
*pWord
; /* Points to the object being stored in the tree */
188 char *zKey
; /* Key. zero-terminated string. Must be unique */
189 amatch_avl
*pBefore
; /* Other elements less than zKey */
190 amatch_avl
*pAfter
; /* Other elements greater than zKey */
191 amatch_avl
*pUp
; /* Parent element */
192 short int height
; /* Height of this node. Leaf==1 */
193 short int imbalance
; /* Height difference between pBefore and pAfter */
196 /* Recompute the amatch_avl.height and amatch_avl.imbalance fields for p.
197 ** Assume that the children of p have correct heights.
199 static void amatchAvlRecomputeHeight(amatch_avl
*p
){
200 short int hBefore
= p
->pBefore
? p
->pBefore
->height
: 0;
201 short int hAfter
= p
->pAfter
? p
->pAfter
->height
: 0;
202 p
->imbalance
= hBefore
- hAfter
; /* -: pAfter higher. +: pBefore higher */
203 p
->height
= (hBefore
>hAfter
? hBefore
: hAfter
)+1;
214 static amatch_avl
*amatchAvlRotateBefore(amatch_avl
*pP
){
215 amatch_avl
*pB
= pP
->pBefore
;
216 amatch_avl
*pY
= pB
->pAfter
;
221 if( pY
) pY
->pUp
= pP
;
222 amatchAvlRecomputeHeight(pP
);
223 amatchAvlRecomputeHeight(pB
);
235 static amatch_avl
*amatchAvlRotateAfter(amatch_avl
*pP
){
236 amatch_avl
*pA
= pP
->pAfter
;
237 amatch_avl
*pY
= pA
->pBefore
;
242 if( pY
) pY
->pUp
= pP
;
243 amatchAvlRecomputeHeight(pP
);
244 amatchAvlRecomputeHeight(pA
);
249 ** Return a pointer to the pBefore or pAfter pointer in the parent
250 ** of p that points to p. Or if p is the root node, return pp.
252 static amatch_avl
**amatchAvlFromPtr(amatch_avl
*p
, amatch_avl
**pp
){
253 amatch_avl
*pUp
= p
->pUp
;
254 if( pUp
==0 ) return pp
;
255 if( pUp
->pAfter
==p
) return &pUp
->pAfter
;
256 return &pUp
->pBefore
;
260 ** Rebalance all nodes starting with p and working up to the root.
261 ** Return the new root.
263 static amatch_avl
*amatchAvlBalance(amatch_avl
*p
){
264 amatch_avl
*pTop
= p
;
267 amatchAvlRecomputeHeight(p
);
268 if( p
->imbalance
>=2 ){
269 amatch_avl
*pB
= p
->pBefore
;
270 if( pB
->imbalance
<0 ) p
->pBefore
= amatchAvlRotateAfter(pB
);
271 pp
= amatchAvlFromPtr(p
,&p
);
272 p
= *pp
= amatchAvlRotateBefore(p
);
273 }else if( p
->imbalance
<=(-2) ){
274 amatch_avl
*pA
= p
->pAfter
;
275 if( pA
->imbalance
>0 ) p
->pAfter
= amatchAvlRotateBefore(pA
);
276 pp
= amatchAvlFromPtr(p
,&p
);
277 p
= *pp
= amatchAvlRotateAfter(p
);
285 /* Search the tree rooted at p for an entry with zKey. Return a pointer
286 ** to the entry or return NULL.
288 static amatch_avl
*amatchAvlSearch(amatch_avl
*p
, const char *zKey
){
290 while( p
&& (c
= strcmp(zKey
, p
->zKey
))!=0 ){
291 p
= (c
<0) ? p
->pBefore
: p
->pAfter
;
296 /* Find the first node (the one with the smallest key).
298 static amatch_avl
*amatchAvlFirst(amatch_avl
*p
){
299 if( p
) while( p
->pBefore
) p
= p
->pBefore
;
304 /* Return the node with the next larger key after p.
306 static amatch_avl
*amatchAvlNext(amatch_avl
*p
){
307 amatch_avl
*pPrev
= 0;
308 while( p
&& p
->pAfter
==pPrev
){
313 p
= amatchAvlFirst(p
->pAfter
);
320 /* Verify AVL tree integrity
322 static int amatchAvlIntegrity(amatch_avl
*pHead
){
324 if( pHead
==0 ) return 1;
325 if( (p
= pHead
->pBefore
)!=0 ){
326 assert( p
->pUp
==pHead
);
327 assert( amatchAvlIntegrity(p
) );
328 assert( strcmp(p
->zKey
, pHead
->zKey
)<0 );
329 while( p
->pAfter
) p
= p
->pAfter
;
330 assert( strcmp(p
->zKey
, pHead
->zKey
)<0 );
332 if( (p
= pHead
->pAfter
)!=0 ){
333 assert( p
->pUp
==pHead
);
334 assert( amatchAvlIntegrity(p
) );
335 assert( strcmp(p
->zKey
, pHead
->zKey
)>0 );
336 p
= amatchAvlFirst(p
);
337 assert( strcmp(p
->zKey
, pHead
->zKey
)>0 );
341 static int amatchAvlIntegrity2(amatch_avl
*pHead
){
342 amatch_avl
*p
, *pNext
;
343 for(p
=amatchAvlFirst(pHead
); p
; p
=pNext
){
344 pNext
= amatchAvlNext(p
);
345 if( pNext
==0 ) break;
346 assert( strcmp(p
->zKey
, pNext
->zKey
)<0 );
352 /* Insert a new node pNew. Return NULL on success. If the key is not
353 ** unique, then do not perform the insert but instead leave pNew unchanged
354 ** and return a pointer to an existing node with the same key.
356 static amatch_avl
*amatchAvlInsert(amatch_avl
**ppHead
, amatch_avl
*pNew
){
358 amatch_avl
*p
= *ppHead
;
364 c
= strcmp(pNew
->zKey
, p
->zKey
);
390 *ppHead
= amatchAvlBalance(p
);
391 /* assert( amatchAvlIntegrity(*ppHead) ); */
392 /* assert( amatchAvlIntegrity2(*ppHead) ); */
396 /* Remove node pOld from the tree. pOld must be an element of the tree or
397 ** the AVL tree will become corrupt.
399 static void amatchAvlRemove(amatch_avl
**ppHead
, amatch_avl
*pOld
){
400 amatch_avl
**ppParent
;
401 amatch_avl
*pBalance
;
402 /* assert( amatchAvlSearch(*ppHead, pOld->zKey)==pOld ); */
403 ppParent
= amatchAvlFromPtr(pOld
, ppHead
);
404 if( pOld
->pBefore
==0 && pOld
->pAfter
==0 ){
406 pBalance
= pOld
->pUp
;
407 }else if( pOld
->pBefore
&& pOld
->pAfter
){
409 pX
= amatchAvlFirst(pOld
->pAfter
);
410 *amatchAvlFromPtr(pX
, 0) = pX
->pAfter
;
411 if( pX
->pAfter
) pX
->pAfter
->pUp
= pX
->pUp
;
413 pX
->pAfter
= pOld
->pAfter
;
415 pX
->pAfter
->pUp
= pX
;
417 assert( pBalance
==pOld
);
420 pX
->pBefore
= pY
= pOld
->pBefore
;
421 if( pY
) pY
->pUp
= pX
;
424 }else if( pOld
->pBefore
==0 ){
425 *ppParent
= pBalance
= pOld
->pAfter
;
426 pBalance
->pUp
= pOld
->pUp
;
427 }else if( pOld
->pAfter
==0 ){
428 *ppParent
= pBalance
= pOld
->pBefore
;
429 pBalance
->pUp
= pOld
->pUp
;
431 *ppHead
= amatchAvlBalance(pBalance
);
435 /* assert( amatchAvlIntegrity(*ppHead) ); */
436 /* assert( amatchAvlIntegrity2(*ppHead) ); */
439 ** End of the AVL Tree implementation
440 ******************************************************************************/
446 ** amatch_cost is the "cost" of an edit operation.
448 ** amatch_len is the length of a matching string.
450 ** amatch_langid is an ruleset identifier.
452 typedef int amatch_cost
;
453 typedef signed char amatch_len
;
454 typedef int amatch_langid
;
459 #define AMATCH_MX_LENGTH 50 /* Maximum length of a rule string */
460 #define AMATCH_MX_LANGID 2147483647 /* Maximum rule ID */
461 #define AMATCH_MX_COST 1000 /* Maximum single-rule cost */
464 ** A match or partial match
467 amatch_word
*pNext
; /* Next on a list of all amatch_words */
468 amatch_avl sCost
; /* Linkage of this node into the cost tree */
469 amatch_avl sWord
; /* Linkage of this node into the word tree */
470 amatch_cost rCost
; /* Cost of the match so far */
471 int iSeq
; /* Sequence number */
472 char zCost
[10]; /* Cost key (text rendering of rCost) */
473 short int nMatch
; /* Input characters matched */
474 char zWord
[4]; /* Text of the word. Extra space appended as needed */
478 ** Each transformation rule is stored as an instance of this object.
479 ** All rules are kept on a linked list sorted by rCost.
482 amatch_rule
*pNext
; /* Next rule in order of increasing rCost */
483 char *zFrom
; /* Transform from (a string from user input) */
484 amatch_cost rCost
; /* Cost of this transformation */
485 amatch_langid iLang
; /* The langauge to which this rule belongs */
486 amatch_len nFrom
, nTo
; /* Length of the zFrom and zTo strings */
487 char zTo
[4]; /* Tranform to V.W value (extra space appended) */
491 ** A amatch virtual-table object
494 sqlite3_vtab base
; /* Base class - must be first */
495 char *zClassName
; /* Name of this class. Default: "amatch" */
496 char *zDb
; /* Name of database. (ex: "main") */
497 char *zSelf
; /* Name of this virtual table */
498 char *zCostTab
; /* Name of edit-cost-table */
499 char *zVocabTab
; /* Name of vocabulary table */
500 char *zVocabWord
; /* Name of vocabulary table word column */
501 char *zVocabLang
; /* Name of vocabulary table language column */
502 amatch_rule
*pRule
; /* All active rules in this amatch */
503 amatch_cost rIns
; /* Generic insertion cost '' -> ? */
504 amatch_cost rDel
; /* Generic deletion cost ? -> '' */
505 amatch_cost rSub
; /* Generic substitution cost ? -> ? */
506 sqlite3
*db
; /* The database connection */
507 sqlite3_stmt
*pVCheck
; /* Query to check zVocabTab */
508 int nCursor
; /* Number of active cursors */
511 /* A amatch cursor object */
512 struct amatch_cursor
{
513 sqlite3_vtab_cursor base
; /* Base class - must be first */
514 sqlite3_int64 iRowid
; /* The rowid of the current word */
515 amatch_langid iLang
; /* Use this language ID */
516 amatch_cost rLimit
; /* Maximum cost of any term */
517 int nBuf
; /* Space allocated for zBuf */
518 int oomErr
; /* True following an OOM error */
519 int nWord
; /* Number of amatch_word objects */
520 char *zBuf
; /* Temp-use buffer space */
521 char *zInput
; /* Input word to match against */
522 amatch_vtab
*pVtab
; /* The virtual table this cursor belongs to */
523 amatch_word
*pAllWords
; /* List of all amatch_word objects */
524 amatch_word
*pCurrent
; /* Most recent solution */
525 amatch_avl
*pCost
; /* amatch_word objects keyed by iCost */
526 amatch_avl
*pWord
; /* amatch_word objects keyed by zWord */
530 ** The two input rule lists are both sorted in order of increasing
531 ** cost. Merge them together into a single list, sorted by cost, and
532 ** return a pointer to the head of that list.
534 static amatch_rule
*amatchMergeRules(amatch_rule
*pA
, amatch_rule
*pB
){
540 if( pA
->rCost
<=pB
->rCost
){
559 ** Statement pStmt currently points to a row in the amatch data table. This
560 ** function allocates and populates a amatch_rule structure according to
561 ** the content of the row.
563 ** If successful, *ppRule is set to point to the new object and SQLITE_OK
564 ** is returned. Otherwise, *ppRule is zeroed, *pzErr may be set to point
565 ** to an error message and an SQLite error code returned.
567 static int amatchLoadOneRule(
568 amatch_vtab
*p
, /* Fuzzer virtual table handle */
569 sqlite3_stmt
*pStmt
, /* Base rule on statements current row */
570 amatch_rule
**ppRule
, /* OUT: New rule object */
571 char **pzErr
/* OUT: Error message */
573 sqlite3_int64 iLang
= sqlite3_column_int64(pStmt
, 0);
574 const char *zFrom
= (const char *)sqlite3_column_text(pStmt
, 1);
575 const char *zTo
= (const char *)sqlite3_column_text(pStmt
, 2);
576 amatch_cost rCost
= sqlite3_column_int(pStmt
, 3);
578 int rc
= SQLITE_OK
; /* Return code */
579 int nFrom
; /* Size of string zFrom, in bytes */
580 int nTo
; /* Size of string zTo, in bytes */
581 amatch_rule
*pRule
= 0; /* New rule object to return */
583 if( zFrom
==0 ) zFrom
= "";
584 if( zTo
==0 ) zTo
= "";
585 nFrom
= (int)strlen(zFrom
);
586 nTo
= (int)strlen(zTo
);
588 /* Silently ignore null transformations */
589 if( strcmp(zFrom
, zTo
)==0 ){
590 if( zFrom
[0]=='?' && zFrom
[1]==0 ){
591 if( p
->rSub
==0 || p
->rSub
>rCost
) p
->rSub
= rCost
;
597 if( rCost
<=0 || rCost
>AMATCH_MX_COST
){
598 *pzErr
= sqlite3_mprintf("%s: cost must be between 1 and %d",
599 p
->zClassName
, AMATCH_MX_COST
603 if( nFrom
>AMATCH_MX_LENGTH
|| nTo
>AMATCH_MX_LENGTH
){
604 *pzErr
= sqlite3_mprintf("%s: maximum string length is %d",
605 p
->zClassName
, AMATCH_MX_LENGTH
609 if( iLang
<0 || iLang
>AMATCH_MX_LANGID
){
610 *pzErr
= sqlite3_mprintf("%s: iLang must be between 0 and %d",
611 p
->zClassName
, AMATCH_MX_LANGID
615 if( strcmp(zFrom
,"")==0 && strcmp(zTo
,"?")==0 ){
616 if( p
->rIns
==0 || p
->rIns
>rCost
) p
->rIns
= rCost
;
618 if( strcmp(zFrom
,"?")==0 && strcmp(zTo
,"")==0 ){
619 if( p
->rDel
==0 || p
->rDel
>rCost
) p
->rDel
= rCost
;
622 pRule
= sqlite3_malloc( sizeof(*pRule
) + nFrom
+ nTo
);
626 memset(pRule
, 0, sizeof(*pRule
));
627 pRule
->zFrom
= &pRule
->zTo
[nTo
+1];
628 pRule
->nFrom
= nFrom
;
629 memcpy(pRule
->zFrom
, zFrom
, nFrom
+1);
630 memcpy(pRule
->zTo
, zTo
, nTo
+1);
632 pRule
->rCost
= rCost
;
633 pRule
->iLang
= (int)iLang
;
642 ** Free all the content in the edit-cost-table
644 static void amatchFreeRules(amatch_vtab
*p
){
646 amatch_rule
*pRule
= p
->pRule
;
647 p
->pRule
= pRule
->pNext
;
654 ** Load the content of the amatch data table into memory.
656 static int amatchLoadRules(
657 sqlite3
*db
, /* Database handle */
658 amatch_vtab
*p
, /* Virtual amatch table to configure */
659 char **pzErr
/* OUT: Error message */
661 int rc
= SQLITE_OK
; /* Return code */
662 char *zSql
; /* SELECT used to read from rules table */
663 amatch_rule
*pHead
= 0;
665 zSql
= sqlite3_mprintf("SELECT * FROM %Q.%Q", p
->zDb
, p
->zCostTab
);
669 int rc2
; /* finalize() return code */
670 sqlite3_stmt
*pStmt
= 0;
671 rc
= sqlite3_prepare_v2(db
, zSql
, -1, &pStmt
, 0);
673 *pzErr
= sqlite3_mprintf("%s: %s", p
->zClassName
, sqlite3_errmsg(db
));
674 }else if( sqlite3_column_count(pStmt
)!=4 ){
675 *pzErr
= sqlite3_mprintf("%s: %s has %d columns, expected 4",
676 p
->zClassName
, p
->zCostTab
, sqlite3_column_count(pStmt
)
680 while( rc
==SQLITE_OK
&& SQLITE_ROW
==sqlite3_step(pStmt
) ){
681 amatch_rule
*pRule
= 0;
682 rc
= amatchLoadOneRule(p
, pStmt
, &pRule
, pzErr
);
684 pRule
->pNext
= pHead
;
689 rc2
= sqlite3_finalize(pStmt
);
690 if( rc
==SQLITE_OK
) rc
= rc2
;
694 /* All rules are now in a singly linked list starting at pHead. This
695 ** block sorts them by cost and then sets amatch_vtab.pRule to point to
696 ** point to the head of the sorted list.
702 for(i
=0; i
<sizeof(a
)/sizeof(a
[0]); i
++) a
[i
] = 0;
703 while( (pX
= pHead
)!=0 ){
706 for(i
=0; a
[i
] && i
<sizeof(a
)/sizeof(a
[0])-1; i
++){
707 pX
= amatchMergeRules(a
[i
], pX
);
710 a
[i
] = amatchMergeRules(a
[i
], pX
);
712 for(pX
=a
[0], i
=1; i
<sizeof(a
)/sizeof(a
[0]); i
++){
713 pX
= amatchMergeRules(a
[i
], pX
);
715 p
->pRule
= amatchMergeRules(p
->pRule
, pX
);
717 /* An error has occurred. Setting p->pRule to point to the head of the
718 ** allocated list ensures that the list will be cleaned up in this case.
720 assert( p
->pRule
==0 );
728 ** This function converts an SQL quoted string into an unquoted string
729 ** and returns a pointer to a buffer allocated using sqlite3_malloc()
730 ** containing the result. The caller should eventually free this buffer
731 ** using sqlite3_free.
740 static char *amatchDequote(const char *zIn
){
741 int nIn
; /* Size of input string, in bytes */
742 char *zOut
; /* Output (dequoted) string */
744 nIn
= (int)strlen(zIn
);
745 zOut
= sqlite3_malloc(nIn
+1);
747 char q
= zIn
[0]; /* Quote character (if any ) */
749 if( q
!='[' && q
!= '\'' && q
!='"' && q
!='`' ){
750 memcpy(zOut
, zIn
, nIn
+1);
752 int iOut
= 0; /* Index of next byte to write to output */
753 int iIn
; /* Index of next byte to read from input */
755 if( q
=='[' ) q
= ']';
756 for(iIn
=1; iIn
<nIn
; iIn
++){
757 if( zIn
[iIn
]==q
) iIn
++;
758 zOut
[iOut
++] = zIn
[iIn
];
761 assert( (int)strlen(zOut
)<=nIn
);
767 ** Deallocate the pVCheck prepared statement.
769 static void amatchVCheckClear(amatch_vtab
*p
){
771 sqlite3_finalize(p
->pVCheck
);
777 ** Deallocate an amatch_vtab object
779 static void amatchFree(amatch_vtab
*p
){
782 amatchVCheckClear(p
);
783 sqlite3_free(p
->zClassName
);
784 sqlite3_free(p
->zDb
);
785 sqlite3_free(p
->zCostTab
);
786 sqlite3_free(p
->zVocabTab
);
787 sqlite3_free(p
->zVocabWord
);
788 sqlite3_free(p
->zVocabLang
);
789 sqlite3_free(p
->zSelf
);
790 memset(p
, 0, sizeof(*p
));
796 ** xDisconnect/xDestroy method for the amatch module.
798 static int amatchDisconnect(sqlite3_vtab
*pVtab
){
799 amatch_vtab
*p
= (amatch_vtab
*)pVtab
;
800 assert( p
->nCursor
==0 );
806 ** Check to see if the argument is of the form:
810 ** If it is, return a pointer to the first character of VALUE.
811 ** If not, return NULL. Spaces around the = are ignored.
813 static const char *amatchValueOfKey(const char *zKey
, const char *zStr
){
814 int nKey
= (int)strlen(zKey
);
815 int nStr
= (int)strlen(zStr
);
817 if( nStr
<nKey
+1 ) return 0;
818 if( memcmp(zStr
, zKey
, nKey
)!=0 ) return 0;
819 for(i
=nKey
; isspace(zStr
[i
]); i
++){}
820 if( zStr
[i
]!='=' ) return 0;
822 while( isspace(zStr
[i
]) ){ i
++; }
827 ** xConnect/xCreate method for the amatch module. Arguments are:
829 ** argv[0] -> module name ("approximate_match")
830 ** argv[1] -> database name
831 ** argv[2] -> table name
832 ** argv[3...] -> arguments
834 static int amatchConnect(
837 int argc
, const char *const*argv
,
838 sqlite3_vtab
**ppVtab
,
841 int rc
= SQLITE_OK
; /* Return code */
842 amatch_vtab
*pNew
= 0; /* New virtual table */
843 const char *zModule
= argv
[0];
844 const char *zDb
= argv
[1];
850 pNew
= sqlite3_malloc( sizeof(*pNew
) );
851 if( pNew
==0 ) return SQLITE_NOMEM
;
853 memset(pNew
, 0, sizeof(*pNew
));
855 pNew
->zClassName
= sqlite3_mprintf("%s", zModule
);
856 if( pNew
->zClassName
==0 ) goto amatchConnectError
;
857 pNew
->zDb
= sqlite3_mprintf("%s", zDb
);
858 if( pNew
->zDb
==0 ) goto amatchConnectError
;
859 pNew
->zSelf
= sqlite3_mprintf("%s", argv
[2]);
860 if( pNew
->zSelf
==0 ) goto amatchConnectError
;
861 for(i
=3; i
<argc
; i
++){
862 zVal
= amatchValueOfKey("vocabulary_table", argv
[i
]);
864 sqlite3_free(pNew
->zVocabTab
);
865 pNew
->zVocabTab
= amatchDequote(zVal
);
866 if( pNew
->zVocabTab
==0 ) goto amatchConnectError
;
869 zVal
= amatchValueOfKey("vocabulary_word", argv
[i
]);
871 sqlite3_free(pNew
->zVocabWord
);
872 pNew
->zVocabWord
= amatchDequote(zVal
);
873 if( pNew
->zVocabWord
==0 ) goto amatchConnectError
;
876 zVal
= amatchValueOfKey("vocabulary_language", argv
[i
]);
878 sqlite3_free(pNew
->zVocabLang
);
879 pNew
->zVocabLang
= amatchDequote(zVal
);
880 if( pNew
->zVocabLang
==0 ) goto amatchConnectError
;
883 zVal
= amatchValueOfKey("edit_distances", argv
[i
]);
885 sqlite3_free(pNew
->zCostTab
);
886 pNew
->zCostTab
= amatchDequote(zVal
);
887 if( pNew
->zCostTab
==0 ) goto amatchConnectError
;
890 *pzErr
= sqlite3_mprintf("unrecognized argument: [%s]\n", argv
[i
]);
896 if( pNew
->zCostTab
==0 ){
897 *pzErr
= sqlite3_mprintf("no edit_distances table specified");
900 rc
= amatchLoadRules(db
, pNew
, pzErr
);
903 rc
= sqlite3_declare_vtab(db
,
904 "CREATE TABLE x(word,distance,language,"
905 "command HIDDEN,nword HIDDEN)"
907 #define AMATCH_COL_WORD 0
908 #define AMATCH_COL_DISTANCE 1
909 #define AMATCH_COL_LANGUAGE 2
910 #define AMATCH_COL_COMMAND 3
911 #define AMATCH_COL_NWORD 4
916 *ppVtab
= &pNew
->base
;
925 ** Open a new amatch cursor.
927 static int amatchOpen(sqlite3_vtab
*pVTab
, sqlite3_vtab_cursor
**ppCursor
){
928 amatch_vtab
*p
= (amatch_vtab
*)pVTab
;
930 pCur
= sqlite3_malloc( sizeof(*pCur
) );
931 if( pCur
==0 ) return SQLITE_NOMEM
;
932 memset(pCur
, 0, sizeof(*pCur
));
934 *ppCursor
= &pCur
->base
;
940 ** Free up all the memory allocated by a cursor. Set it rLimit to 0
941 ** to indicate that it is at EOF.
943 static void amatchClearCursor(amatch_cursor
*pCur
){
944 amatch_word
*pWord
, *pNextWord
;
945 for(pWord
=pCur
->pAllWords
; pWord
; pWord
=pNextWord
){
946 pNextWord
= pWord
->pNext
;
950 sqlite3_free(pCur
->zInput
);
952 sqlite3_free(pCur
->zBuf
);
958 pCur
->rLimit
= 1000000;
964 ** Close a amatch cursor.
966 static int amatchClose(sqlite3_vtab_cursor
*cur
){
967 amatch_cursor
*pCur
= (amatch_cursor
*)cur
;
968 amatchClearCursor(pCur
);
969 pCur
->pVtab
->nCursor
--;
975 ** Render a 24-bit unsigned integer as a 4-byte base-64 number.
977 static void amatchEncodeInt(int x
, char *z
){
978 static const char a
[] =
986 z
[0] = a
[(x
>>18)&0x3f];
987 z
[1] = a
[(x
>>12)&0x3f];
988 z
[2] = a
[(x
>>6)&0x3f];
993 ** Write the zCost[] field for a amatch_word object
995 static void amatchWriteCost(amatch_word
*pWord
){
996 amatchEncodeInt(pWord
->rCost
, pWord
->zCost
);
997 amatchEncodeInt(pWord
->iSeq
, pWord
->zCost
+4);
1002 ** Add a new amatch_word object to the queue.
1004 ** If a prior amatch_word object with the same zWord, and nMatch
1005 ** already exists, update its rCost (if the new rCost is less) but
1006 ** otherwise leave it unchanged. Do not add a duplicate.
1008 ** Do nothing if the cost exceeds threshold.
1010 static void amatchAddWord(
1011 amatch_cursor
*pCur
,
1014 const char *zWordBase
,
1015 const char *zWordTail
1023 if( rCost
>pCur
->rLimit
){
1026 nBase
= (int)strlen(zWordBase
);
1027 nTail
= (int)strlen(zWordTail
);
1028 if( nBase
+nTail
+3>pCur
->nBuf
){
1029 pCur
->nBuf
= nBase
+nTail
+100;
1030 pCur
->zBuf
= sqlite3_realloc(pCur
->zBuf
, pCur
->nBuf
);
1031 if( pCur
->zBuf
==0 ){
1036 amatchEncodeInt(nMatch
, zBuf
);
1037 memcpy(pCur
->zBuf
, zBuf
+2, 2);
1038 memcpy(pCur
->zBuf
+2, zWordBase
, nBase
);
1039 memcpy(pCur
->zBuf
+2+nBase
, zWordTail
, nTail
+1);
1040 pNode
= amatchAvlSearch(pCur
->pWord
, pCur
->zBuf
);
1042 pWord
= pNode
->pWord
;
1043 if( pWord
->rCost
>rCost
){
1044 #ifdef AMATCH_TRACE_1
1045 printf("UPDATE [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
1046 pWord
->zWord
+2, pWord
->nMatch
, pCur
->zInput
, pCur
->zInput
,
1047 pWord
->rCost
, pWord
->zWord
, pWord
->zCost
);
1049 amatchAvlRemove(&pCur
->pCost
, &pWord
->sCost
);
1050 pWord
->rCost
= rCost
;
1051 amatchWriteCost(pWord
);
1052 #ifdef AMATCH_TRACE_1
1053 printf(" ---> %d (\"%s\" \"%s\")\n",
1054 pWord
->rCost
, pWord
->zWord
, pWord
->zCost
);
1056 pOther
= amatchAvlInsert(&pCur
->pCost
, &pWord
->sCost
);
1057 assert( pOther
==0 ); (void)pOther
;
1061 pWord
= sqlite3_malloc( sizeof(*pWord
) + nBase
+ nTail
- 1 );
1062 if( pWord
==0 ) return;
1063 memset(pWord
, 0, sizeof(*pWord
));
1064 pWord
->rCost
= rCost
;
1065 pWord
->iSeq
= pCur
->nWord
++;
1066 amatchWriteCost(pWord
);
1067 pWord
->nMatch
= nMatch
;
1068 pWord
->pNext
= pCur
->pAllWords
;
1069 pCur
->pAllWords
= pWord
;
1070 pWord
->sCost
.zKey
= pWord
->zCost
;
1071 pWord
->sCost
.pWord
= pWord
;
1072 pOther
= amatchAvlInsert(&pCur
->pCost
, &pWord
->sCost
);
1073 assert( pOther
==0 ); (void)pOther
;
1074 pWord
->sWord
.zKey
= pWord
->zWord
;
1075 pWord
->sWord
.pWord
= pWord
;
1076 strcpy(pWord
->zWord
, pCur
->zBuf
);
1077 pOther
= amatchAvlInsert(&pCur
->pWord
, &pWord
->sWord
);
1078 assert( pOther
==0 ); (void)pOther
;
1079 #ifdef AMATCH_TRACE_1
1080 printf("INSERT [%s][%.*s^%s] %d (\"%s\" \"%s\")\n", pWord
->zWord
+2,
1081 pWord
->nMatch
, pCur
->zInput
, pCur
->zInput
+pWord
->nMatch
, rCost
,
1082 pWord
->zWord
, pWord
->zCost
);
1087 ** Advance a cursor to its next row of output
1089 static int amatchNext(sqlite3_vtab_cursor
*cur
){
1090 amatch_cursor
*pCur
= (amatch_cursor
*)cur
;
1091 amatch_word
*pWord
= 0;
1094 amatch_vtab
*p
= pCur
->pVtab
;
1106 if( p
->pVCheck
==0 ){
1108 if( p
->zVocabLang
&& p
->zVocabLang
[0] ){
1109 zSql
= sqlite3_mprintf(
1110 "SELECT \"%w\" FROM \"%w\"",
1111 " WHERE \"%w\">=?1 AND \"%w\"=?2"
1113 p
->zVocabWord
, p
->zVocabTab
,
1114 p
->zVocabWord
, p
->zVocabLang
1117 zSql
= sqlite3_mprintf(
1118 "SELECT \"%w\" FROM \"%w\""
1121 p
->zVocabWord
, p
->zVocabTab
,
1125 rc
= sqlite3_prepare_v2(p
->db
, zSql
, -1, &p
->pVCheck
, 0);
1129 sqlite3_bind_int(p
->pVCheck
, 2, pCur
->iLang
);
1132 pNode
= amatchAvlFirst(pCur
->pCost
);
1137 pWord
= pNode
->pWord
;
1138 amatchAvlRemove(&pCur
->pCost
, &pWord
->sCost
);
1140 #ifdef AMATCH_TRACE_1
1141 printf("PROCESS [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
1142 pWord
->zWord
+2, pWord
->nMatch
, pCur
->zInput
, pCur
->zInput
+pWord
->nMatch
,
1143 pWord
->rCost
, pWord
->zWord
, pWord
->zCost
);
1145 nWord
= (int)strlen(pWord
->zWord
+2);
1146 if( nWord
+20>nBuf
){
1148 zBuf
= sqlite3_realloc(zBuf
, nBuf
);
1149 if( zBuf
==0 ) return SQLITE_NOMEM
;
1151 strcpy(zBuf
, pWord
->zWord
+2);
1153 zNextIn
[0] = pCur
->zInput
[pWord
->nMatch
];
1155 for(i
=1; i
<=4 && (pCur
->zInput
[pWord
->nMatch
+i
]&0xc0)==0x80; i
++){
1156 zNextIn
[i
] = pCur
->zInput
[pWord
->nMatch
+i
];
1164 if( zNextIn
[0] && zNextIn
[0]!='*' ){
1165 sqlite3_reset(p
->pVCheck
);
1166 strcat(zBuf
, zNextIn
);
1167 sqlite3_bind_text(p
->pVCheck
, 1, zBuf
, nWord
+nNextIn
, SQLITE_STATIC
);
1168 rc
= sqlite3_step(p
->pVCheck
);
1169 if( rc
==SQLITE_ROW
){
1170 zW
= (const char*)sqlite3_column_text(p
->pVCheck
, 0);
1171 if( strncmp(zBuf
, zW
, nWord
+nNextIn
)==0 ){
1172 amatchAddWord(pCur
, pWord
->rCost
, pWord
->nMatch
+nNextIn
, zBuf
, "");
1179 strcpy(zBuf
+nWord
, zNext
);
1180 sqlite3_reset(p
->pVCheck
);
1181 sqlite3_bind_text(p
->pVCheck
, 1, zBuf
, -1, SQLITE_TRANSIENT
);
1182 rc
= sqlite3_step(p
->pVCheck
);
1183 if( rc
!=SQLITE_ROW
) break;
1184 zW
= (const char*)sqlite3_column_text(p
->pVCheck
, 0);
1185 strcpy(zBuf
+nWord
, zNext
);
1186 if( strncmp(zW
, zBuf
, nWord
)!=0 ) break;
1187 if( (zNextIn
[0]=='*' && zNextIn
[1]==0)
1188 || (zNextIn
[0]==0 && zW
[nWord
]==0)
1195 zNext
[0] = zW
[nWord
];
1196 for(i
=1; i
<=4 && (zW
[nWord
+i
]&0xc0)==0x80; i
++){
1197 zNext
[i
] = zW
[nWord
+i
];
1202 amatchAddWord(pCur
, pWord
->rCost
+p
->rIns
, pWord
->nMatch
,
1206 amatchAddWord(pCur
, pWord
->rCost
+p
->rSub
, pWord
->nMatch
+nNextIn
,
1209 if( p
->rIns
<0 && p
->rSub
<0 ) break;
1210 zNext
[i
-1]++; /* FIX ME */
1212 sqlite3_reset(p
->pVCheck
);
1216 amatchAddWord(pCur
, pWord
->rCost
+p
->rDel
, pWord
->nMatch
+nNextIn
,
1220 for(pRule
=p
->pRule
; pRule
; pRule
=pRule
->pNext
){
1221 if( pRule
->iLang
!=pCur
->iLang
) continue;
1222 if( strncmp(pRule
->zFrom
, pCur
->zInput
+pWord
->nMatch
, pRule
->nFrom
)==0 ){
1223 amatchAddWord(pCur
, pWord
->rCost
+pRule
->rCost
,
1224 pWord
->nMatch
+pRule
->nFrom
, pWord
->zWord
+2, pRule
->zTo
);
1228 pCur
->pCurrent
= pWord
;
1234 ** Called to "rewind" a cursor back to the beginning so that
1235 ** it starts its output over again. Always called at least once
1236 ** prior to any amatchColumn, amatchRowid, or amatchEof call.
1238 static int amatchFilter(
1239 sqlite3_vtab_cursor
*pVtabCursor
,
1240 int idxNum
, const char *idxStr
,
1241 int argc
, sqlite3_value
**argv
1243 amatch_cursor
*pCur
= (amatch_cursor
*)pVtabCursor
;
1244 const char *zWord
= "*";
1247 amatchClearCursor(pCur
);
1250 zWord
= (const char*)sqlite3_value_text(argv
[0]);
1254 pCur
->rLimit
= (amatch_cost
)sqlite3_value_int(argv
[idx
]);
1258 pCur
->iLang
= (amatch_cost
)sqlite3_value_int(argv
[idx
]);
1261 pCur
->zInput
= sqlite3_mprintf("%s", zWord
);
1262 if( pCur
->zInput
==0 ) return SQLITE_NOMEM
;
1263 amatchAddWord(pCur
, 0, 0, "", "");
1264 amatchNext(pVtabCursor
);
1270 ** Only the word and distance columns have values. All other columns
1273 static int amatchColumn(sqlite3_vtab_cursor
*cur
, sqlite3_context
*ctx
, int i
){
1274 amatch_cursor
*pCur
= (amatch_cursor
*)cur
;
1276 case AMATCH_COL_WORD
: {
1277 sqlite3_result_text(ctx
, pCur
->pCurrent
->zWord
+2, -1, SQLITE_STATIC
);
1280 case AMATCH_COL_DISTANCE
: {
1281 sqlite3_result_int(ctx
, pCur
->pCurrent
->rCost
);
1284 case AMATCH_COL_LANGUAGE
: {
1285 sqlite3_result_int(ctx
, pCur
->iLang
);
1288 case AMATCH_COL_NWORD
: {
1289 sqlite3_result_int(ctx
, pCur
->nWord
);
1293 sqlite3_result_null(ctx
);
1303 static int amatchRowid(sqlite3_vtab_cursor
*cur
, sqlite_int64
*pRowid
){
1304 amatch_cursor
*pCur
= (amatch_cursor
*)cur
;
1305 *pRowid
= pCur
->iRowid
;
1312 static int amatchEof(sqlite3_vtab_cursor
*cur
){
1313 amatch_cursor
*pCur
= (amatch_cursor
*)cur
;
1314 return pCur
->pCurrent
==0;
1318 ** Search for terms of these forms:
1320 ** (A) word MATCH $str
1321 ** (B1) distance < $value
1322 ** (B2) distance <= $value
1323 ** (C) language == $language
1325 ** The distance< and distance<= are both treated as distance<=.
1326 ** The query plan number is a bit vector:
1328 ** bit 1: Term of the form (A) found
1329 ** bit 2: Term like (B1) or (B2) found
1330 ** bit 3: Term like (C) found
1332 ** If bit-1 is set, $str is always in filter.argv[0]. If bit-2 is set
1333 ** then $value is in filter.argv[0] if bit-1 is clear and is in
1334 ** filter.argv[1] if bit-1 is set. If bit-3 is set, then $ruleid is
1335 ** in filter.argv[0] if bit-1 and bit-2 are both zero, is in
1336 ** filter.argv[1] if exactly one of bit-1 and bit-2 are set, and is in
1337 ** filter.argv[2] if both bit-1 and bit-2 are set.
1339 static int amatchBestIndex(
1341 sqlite3_index_info
*pIdxInfo
1347 const struct sqlite3_index_constraint
*pConstraint
;
1350 pConstraint
= pIdxInfo
->aConstraint
;
1351 for(i
=0; i
<pIdxInfo
->nConstraint
; i
++, pConstraint
++){
1352 if( pConstraint
->usable
==0 ) continue;
1354 && pConstraint
->iColumn
==0
1355 && pConstraint
->op
==SQLITE_INDEX_CONSTRAINT_MATCH
1358 pIdxInfo
->aConstraintUsage
[i
].argvIndex
= 1;
1359 pIdxInfo
->aConstraintUsage
[i
].omit
= 1;
1362 && pConstraint
->iColumn
==1
1363 && (pConstraint
->op
==SQLITE_INDEX_CONSTRAINT_LT
1364 || pConstraint
->op
==SQLITE_INDEX_CONSTRAINT_LE
)
1370 && pConstraint
->iColumn
==2
1371 && pConstraint
->op
==SQLITE_INDEX_CONSTRAINT_EQ
1374 pIdxInfo
->aConstraintUsage
[i
].omit
= 1;
1379 pIdxInfo
->aConstraintUsage
[iDistTerm
].argvIndex
= 1+((iPlan
&1)!=0);
1383 if( iPlan
& 1 ) idx
++;
1384 if( iPlan
& 2 ) idx
++;
1385 pIdxInfo
->aConstraintUsage
[iLangTerm
].argvIndex
= idx
;
1387 pIdxInfo
->idxNum
= iPlan
;
1388 if( pIdxInfo
->nOrderBy
==1
1389 && pIdxInfo
->aOrderBy
[0].iColumn
==1
1390 && pIdxInfo
->aOrderBy
[0].desc
==0
1392 pIdxInfo
->orderByConsumed
= 1;
1394 pIdxInfo
->estimatedCost
= (double)10000;
1400 ** The xUpdate() method.
1402 ** This implementation disallows DELETE and UPDATE. The only thing
1403 ** allowed is INSERT into the "command" column.
1405 static int amatchUpdate(
1406 sqlite3_vtab
*pVTab
,
1408 sqlite3_value
**argv
,
1409 sqlite_int64
*pRowid
1411 amatch_vtab
*p
= (amatch_vtab
*)pVTab
;
1412 const unsigned char *zCmd
;
1415 pVTab
->zErrMsg
= sqlite3_mprintf("DELETE from %s is not allowed",
1417 return SQLITE_ERROR
;
1419 if( sqlite3_value_type(argv
[0])!=SQLITE_NULL
){
1420 pVTab
->zErrMsg
= sqlite3_mprintf("UPDATE of %s is not allowed",
1422 return SQLITE_ERROR
;
1424 if( sqlite3_value_type(argv
[2+AMATCH_COL_WORD
])!=SQLITE_NULL
1425 || sqlite3_value_type(argv
[2+AMATCH_COL_DISTANCE
])!=SQLITE_NULL
1426 || sqlite3_value_type(argv
[2+AMATCH_COL_LANGUAGE
])!=SQLITE_NULL
1428 pVTab
->zErrMsg
= sqlite3_mprintf(
1429 "INSERT INTO %s allowed for column [command] only", p
->zSelf
);
1430 return SQLITE_ERROR
;
1432 zCmd
= sqlite3_value_text(argv
[2+AMATCH_COL_COMMAND
]);
1433 if( zCmd
==0 ) return SQLITE_OK
;
1439 ** A virtual table module that implements the "approximate_match".
1441 static sqlite3_module amatchModule
= {
1443 amatchConnect
, /* xCreate */
1444 amatchConnect
, /* xConnect */
1445 amatchBestIndex
, /* xBestIndex */
1446 amatchDisconnect
, /* xDisconnect */
1447 amatchDisconnect
, /* xDestroy */
1448 amatchOpen
, /* xOpen - open a cursor */
1449 amatchClose
, /* xClose - close a cursor */
1450 amatchFilter
, /* xFilter - configure scan constraints */
1451 amatchNext
, /* xNext - advance a cursor */
1452 amatchEof
, /* xEof - check for end of scan */
1453 amatchColumn
, /* xColumn - read data */
1454 amatchRowid
, /* xRowid - read data */
1455 amatchUpdate
, /* xUpdate */
1460 0, /* xFindMethod */
1467 #endif /* SQLITE_OMIT_VIRTUALTABLE */
1470 ** Register the amatch virtual table
1473 __declspec(dllexport
)
1475 int sqlite3_amatch_init(
1478 const sqlite3_api_routines
*pApi
1481 SQLITE_EXTENSION_INIT2(pApi
);
1482 (void)pzErrMsg
; /* Not used */
1483 #ifndef SQLITE_OMIT_VIRTUALTABLE
1484 rc
= sqlite3_create_module(db
, "approximate_match", &amatchModule
, 0);
1485 #endif /* SQLITE_OMIT_VIRTUALTABLE */