OLD | NEW |
| (Empty) |
1 /* | |
2 ** 2013-03-14 | |
3 ** | |
4 ** The author disclaims copyright to this source code. In place of | |
5 ** a legal notice, here is a blessing: | |
6 ** | |
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. | |
10 ** | |
11 ************************************************************************* | |
12 ** | |
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". | |
16 ** | |
17 ** A amatch virtual table is created like this: | |
18 ** | |
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> | |
24 ** ); | |
25 ** | |
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 | |
28 ** | |
29 ** SELECT W FROM V WHERE L=$language | |
30 ** | |
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. | |
34 ** | |
35 ** For efficiency, it is essential that the vocabulary table be indexed: | |
36 ** | |
37 ** CREATE vocab_index ON V(W) | |
38 ** | |
39 ** A separate edit-cost-table provides scoring information that defines | |
40 ** what it means for one string to be "close" to another. | |
41 ** | |
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 | |
45 ** named. | |
46 ** | |
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: | |
56 ** | |
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); | |
62 ** | |
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. | |
71 ** | |
72 ** The following rules are special: | |
73 ** | |
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); | |
77 ** | |
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. | |
86 ** | |
87 ** Once it has been created, the amatch virtual table can be queried | |
88 ** as follows: | |
89 ** | |
90 ** SELECT word, distance FROM f | |
91 ** WHERE word MATCH 'abcdefg' | |
92 ** AND distance<200; | |
93 ** | |
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. | |
100 ** | |
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 | |
105 ** edit-cost-table. | |
106 ** | |
107 ** The amatch is a read-only table. Any attempt to DELETE, INSERT, or | |
108 ** UPDATE on a amatch table will throw an error. | |
109 ** | |
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. | |
116 ** | |
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: | |
120 ** | |
121 ** CREATE VIRTUAL TABLE ex1 USING amatch( | |
122 ** vocabtable=vocabulary, | |
123 ** vocabcolumn=w, | |
124 ** edit_distances=ec1 | |
125 ** ); | |
126 ** | |
127 ** Then given an input word $word, look up close spellings this way: | |
128 ** | |
129 ** SELECT word, distance FROM ex1 | |
130 ** WHERE word MATCH $word AND distance<200; | |
131 ** | |
132 ** MULTIPLE LANGUAGES | |
133 ** | |
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. | |
139 ** | |
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: | |
144 ** | |
145 ** SELECT word, distance FROM ex1 | |
146 ** WHERE word MATCH $word | |
147 ** AND distance<=200 | |
148 ** AND language=1 -- Specify use language 1 instead of 0 | |
149 ** | |
150 ** If no "language = ?" constraint is specified in the WHERE clause, language | |
151 ** 0 is used. | |
152 ** | |
153 ** LIMITS | |
154 ** | |
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. | |
158 */ | |
159 #include "sqlite3ext.h" | |
160 SQLITE_EXTENSION_INIT1 | |
161 #include <stdlib.h> | |
162 #include <string.h> | |
163 #include <assert.h> | |
164 #include <stdio.h> | |
165 #include <ctype.h> | |
166 | |
167 #ifndef SQLITE_OMIT_VIRTUALTABLE | |
168 | |
169 /* | |
170 ** Forward declaration of objects used by this implementation | |
171 */ | |
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; | |
177 | |
178 | |
179 /***************************************************************************** | |
180 ** AVL Tree implementation | |
181 */ | |
182 /* | |
183 ** Objects that want to be members of the AVL tree should embedded an | |
184 ** instance of this structure. | |
185 */ | |
186 struct amatch_avl { | |
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 */ | |
194 }; | |
195 | |
196 /* Recompute the amatch_avl.height and amatch_avl.imbalance fields for p. | |
197 ** Assume that the children of p have correct heights. | |
198 */ | |
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; | |
204 } | |
205 | |
206 /* | |
207 ** P B | |
208 ** / \ / \ | |
209 ** B Z ==> X P | |
210 ** / \ / \ | |
211 ** X Y Y Z | |
212 ** | |
213 */ | |
214 static amatch_avl *amatchAvlRotateBefore(amatch_avl *pP){ | |
215 amatch_avl *pB = pP->pBefore; | |
216 amatch_avl *pY = pB->pAfter; | |
217 pB->pUp = pP->pUp; | |
218 pB->pAfter = pP; | |
219 pP->pUp = pB; | |
220 pP->pBefore = pY; | |
221 if( pY ) pY->pUp = pP; | |
222 amatchAvlRecomputeHeight(pP); | |
223 amatchAvlRecomputeHeight(pB); | |
224 return pB; | |
225 } | |
226 | |
227 /* | |
228 ** P A | |
229 ** / \ / \ | |
230 ** X A ==> P Z | |
231 ** / \ / \ | |
232 ** Y Z X Y | |
233 ** | |
234 */ | |
235 static amatch_avl *amatchAvlRotateAfter(amatch_avl *pP){ | |
236 amatch_avl *pA = pP->pAfter; | |
237 amatch_avl *pY = pA->pBefore; | |
238 pA->pUp = pP->pUp; | |
239 pA->pBefore = pP; | |
240 pP->pUp = pA; | |
241 pP->pAfter = pY; | |
242 if( pY ) pY->pUp = pP; | |
243 amatchAvlRecomputeHeight(pP); | |
244 amatchAvlRecomputeHeight(pA); | |
245 return pA; | |
246 } | |
247 | |
248 /* | |
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. | |
251 */ | |
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; | |
257 } | |
258 | |
259 /* | |
260 ** Rebalance all nodes starting with p and working up to the root. | |
261 ** Return the new root. | |
262 */ | |
263 static amatch_avl *amatchAvlBalance(amatch_avl *p){ | |
264 amatch_avl *pTop = p; | |
265 amatch_avl **pp; | |
266 while( 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); | |
278 } | |
279 pTop = p; | |
280 p = p->pUp; | |
281 } | |
282 return pTop; | |
283 } | |
284 | |
285 /* Search the tree rooted at p for an entry with zKey. Return a pointer | |
286 ** to the entry or return NULL. | |
287 */ | |
288 static amatch_avl *amatchAvlSearch(amatch_avl *p, const char *zKey){ | |
289 int c; | |
290 while( p && (c = strcmp(zKey, p->zKey))!=0 ){ | |
291 p = (c<0) ? p->pBefore : p->pAfter; | |
292 } | |
293 return p; | |
294 } | |
295 | |
296 /* Find the first node (the one with the smallest key). | |
297 */ | |
298 static amatch_avl *amatchAvlFirst(amatch_avl *p){ | |
299 if( p ) while( p->pBefore ) p = p->pBefore; | |
300 return p; | |
301 } | |
302 | |
303 #if 0 /* NOT USED */ | |
304 /* Return the node with the next larger key after p. | |
305 */ | |
306 static amatch_avl *amatchAvlNext(amatch_avl *p){ | |
307 amatch_avl *pPrev = 0; | |
308 while( p && p->pAfter==pPrev ){ | |
309 pPrev = p; | |
310 p = p->pUp; | |
311 } | |
312 if( p && pPrev==0 ){ | |
313 p = amatchAvlFirst(p->pAfter); | |
314 } | |
315 return p; | |
316 } | |
317 #endif | |
318 | |
319 #if 0 /* NOT USED */ | |
320 /* Verify AVL tree integrity | |
321 */ | |
322 static int amatchAvlIntegrity(amatch_avl *pHead){ | |
323 amatch_avl *p; | |
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 ); | |
331 } | |
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 ); | |
338 } | |
339 return 1; | |
340 } | |
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 ); | |
347 } | |
348 return 1; | |
349 } | |
350 #endif | |
351 | |
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. | |
355 */ | |
356 static amatch_avl *amatchAvlInsert(amatch_avl **ppHead, amatch_avl *pNew){ | |
357 int c; | |
358 amatch_avl *p = *ppHead; | |
359 if( p==0 ){ | |
360 p = pNew; | |
361 pNew->pUp = 0; | |
362 }else{ | |
363 while( p ){ | |
364 c = strcmp(pNew->zKey, p->zKey); | |
365 if( c<0 ){ | |
366 if( p->pBefore ){ | |
367 p = p->pBefore; | |
368 }else{ | |
369 p->pBefore = pNew; | |
370 pNew->pUp = p; | |
371 break; | |
372 } | |
373 }else if( c>0 ){ | |
374 if( p->pAfter ){ | |
375 p = p->pAfter; | |
376 }else{ | |
377 p->pAfter = pNew; | |
378 pNew->pUp = p; | |
379 break; | |
380 } | |
381 }else{ | |
382 return p; | |
383 } | |
384 } | |
385 } | |
386 pNew->pBefore = 0; | |
387 pNew->pAfter = 0; | |
388 pNew->height = 1; | |
389 pNew->imbalance = 0; | |
390 *ppHead = amatchAvlBalance(p); | |
391 /* assert( amatchAvlIntegrity(*ppHead) ); */ | |
392 /* assert( amatchAvlIntegrity2(*ppHead) ); */ | |
393 return 0; | |
394 } | |
395 | |
396 /* Remove node pOld from the tree. pOld must be an element of the tree or | |
397 ** the AVL tree will become corrupt. | |
398 */ | |
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 ){ | |
405 *ppParent = 0; | |
406 pBalance = pOld->pUp; | |
407 }else if( pOld->pBefore && pOld->pAfter ){ | |
408 amatch_avl *pX, *pY; | |
409 pX = amatchAvlFirst(pOld->pAfter); | |
410 *amatchAvlFromPtr(pX, 0) = pX->pAfter; | |
411 if( pX->pAfter ) pX->pAfter->pUp = pX->pUp; | |
412 pBalance = pX->pUp; | |
413 pX->pAfter = pOld->pAfter; | |
414 if( pX->pAfter ){ | |
415 pX->pAfter->pUp = pX; | |
416 }else{ | |
417 assert( pBalance==pOld ); | |
418 pBalance = pX; | |
419 } | |
420 pX->pBefore = pY = pOld->pBefore; | |
421 if( pY ) pY->pUp = pX; | |
422 pX->pUp = pOld->pUp; | |
423 *ppParent = 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; | |
430 } | |
431 *ppHead = amatchAvlBalance(pBalance); | |
432 pOld->pUp = 0; | |
433 pOld->pBefore = 0; | |
434 pOld->pAfter = 0; | |
435 /* assert( amatchAvlIntegrity(*ppHead) ); */ | |
436 /* assert( amatchAvlIntegrity2(*ppHead) ); */ | |
437 } | |
438 /* | |
439 ** End of the AVL Tree implementation | |
440 ******************************************************************************/ | |
441 | |
442 | |
443 /* | |
444 ** Various types. | |
445 ** | |
446 ** amatch_cost is the "cost" of an edit operation. | |
447 ** | |
448 ** amatch_len is the length of a matching string. | |
449 ** | |
450 ** amatch_langid is an ruleset identifier. | |
451 */ | |
452 typedef int amatch_cost; | |
453 typedef signed char amatch_len; | |
454 typedef int amatch_langid; | |
455 | |
456 /* | |
457 ** Limits | |
458 */ | |
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 */ | |
462 | |
463 /* | |
464 ** A match or partial match | |
465 */ | |
466 struct amatch_word { | |
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 */ | |
475 }; | |
476 | |
477 /* | |
478 ** Each transformation rule is stored as an instance of this object. | |
479 ** All rules are kept on a linked list sorted by rCost. | |
480 */ | |
481 struct amatch_rule { | |
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) */ | |
488 }; | |
489 | |
490 /* | |
491 ** A amatch virtual-table object | |
492 */ | |
493 struct amatch_vtab { | |
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 */ | |
509 }; | |
510 | |
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 */ | |
527 }; | |
528 | |
529 /* | |
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. | |
533 */ | |
534 static amatch_rule *amatchMergeRules(amatch_rule *pA, amatch_rule *pB){ | |
535 amatch_rule head; | |
536 amatch_rule *pTail; | |
537 | |
538 pTail = &head; | |
539 while( pA && pB ){ | |
540 if( pA->rCost<=pB->rCost ){ | |
541 pTail->pNext = pA; | |
542 pTail = pA; | |
543 pA = pA->pNext; | |
544 }else{ | |
545 pTail->pNext = pB; | |
546 pTail = pB; | |
547 pB = pB->pNext; | |
548 } | |
549 } | |
550 if( pA==0 ){ | |
551 pTail->pNext = pB; | |
552 }else{ | |
553 pTail->pNext = pA; | |
554 } | |
555 return head.pNext; | |
556 } | |
557 | |
558 /* | |
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. | |
562 ** | |
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. | |
566 */ | |
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 */ | |
572 ){ | |
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); | |
577 | |
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 */ | |
582 | |
583 if( zFrom==0 ) zFrom = ""; | |
584 if( zTo==0 ) zTo = ""; | |
585 nFrom = (int)strlen(zFrom); | |
586 nTo = (int)strlen(zTo); | |
587 | |
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; | |
592 } | |
593 *ppRule = 0; | |
594 return SQLITE_OK; | |
595 } | |
596 | |
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 | |
600 ); | |
601 rc = SQLITE_ERROR; | |
602 }else | |
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 | |
606 ); | |
607 rc = SQLITE_ERROR; | |
608 }else | |
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 | |
612 ); | |
613 rc = SQLITE_ERROR; | |
614 }else | |
615 if( strcmp(zFrom,"")==0 && strcmp(zTo,"?")==0 ){ | |
616 if( p->rIns==0 || p->rIns>rCost ) p->rIns = rCost; | |
617 }else | |
618 if( strcmp(zFrom,"?")==0 && strcmp(zTo,"")==0 ){ | |
619 if( p->rDel==0 || p->rDel>rCost ) p->rDel = rCost; | |
620 }else | |
621 { | |
622 pRule = sqlite3_malloc( sizeof(*pRule) + nFrom + nTo ); | |
623 if( pRule==0 ){ | |
624 rc = SQLITE_NOMEM; | |
625 }else{ | |
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); | |
631 pRule->nTo = nTo; | |
632 pRule->rCost = rCost; | |
633 pRule->iLang = (int)iLang; | |
634 } | |
635 } | |
636 | |
637 *ppRule = pRule; | |
638 return rc; | |
639 } | |
640 | |
641 /* | |
642 ** Free all the content in the edit-cost-table | |
643 */ | |
644 static void amatchFreeRules(amatch_vtab *p){ | |
645 while( p->pRule ){ | |
646 amatch_rule *pRule = p->pRule; | |
647 p->pRule = pRule->pNext; | |
648 sqlite3_free(pRule); | |
649 } | |
650 p->pRule = 0; | |
651 } | |
652 | |
653 /* | |
654 ** Load the content of the amatch data table into memory. | |
655 */ | |
656 static int amatchLoadRules( | |
657 sqlite3 *db, /* Database handle */ | |
658 amatch_vtab *p, /* Virtual amatch table to configure */ | |
659 char **pzErr /* OUT: Error message */ | |
660 ){ | |
661 int rc = SQLITE_OK; /* Return code */ | |
662 char *zSql; /* SELECT used to read from rules table */ | |
663 amatch_rule *pHead = 0; | |
664 | |
665 zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", p->zDb, p->zCostTab); | |
666 if( zSql==0 ){ | |
667 rc = SQLITE_NOMEM; | |
668 }else{ | |
669 int rc2; /* finalize() return code */ | |
670 sqlite3_stmt *pStmt = 0; | |
671 rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); | |
672 if( rc!=SQLITE_OK ){ | |
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) | |
677 ); | |
678 rc = SQLITE_ERROR; | |
679 }else{ | |
680 while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ | |
681 amatch_rule *pRule = 0; | |
682 rc = amatchLoadOneRule(p, pStmt, &pRule, pzErr); | |
683 if( pRule ){ | |
684 pRule->pNext = pHead; | |
685 pHead = pRule; | |
686 } | |
687 } | |
688 } | |
689 rc2 = sqlite3_finalize(pStmt); | |
690 if( rc==SQLITE_OK ) rc = rc2; | |
691 } | |
692 sqlite3_free(zSql); | |
693 | |
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. | |
697 */ | |
698 if( rc==SQLITE_OK ){ | |
699 unsigned int i; | |
700 amatch_rule *pX; | |
701 amatch_rule *a[15]; | |
702 for(i=0; i<sizeof(a)/sizeof(a[0]); i++) a[i] = 0; | |
703 while( (pX = pHead)!=0 ){ | |
704 pHead = pX->pNext; | |
705 pX->pNext = 0; | |
706 for(i=0; a[i] && i<sizeof(a)/sizeof(a[0])-1; i++){ | |
707 pX = amatchMergeRules(a[i], pX); | |
708 a[i] = 0; | |
709 } | |
710 a[i] = amatchMergeRules(a[i], pX); | |
711 } | |
712 for(pX=a[0], i=1; i<sizeof(a)/sizeof(a[0]); i++){ | |
713 pX = amatchMergeRules(a[i], pX); | |
714 } | |
715 p->pRule = amatchMergeRules(p->pRule, pX); | |
716 }else{ | |
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. | |
719 */ | |
720 assert( p->pRule==0 ); | |
721 p->pRule = pHead; | |
722 } | |
723 | |
724 return rc; | |
725 } | |
726 | |
727 /* | |
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. | |
732 ** | |
733 ** Examples: | |
734 ** | |
735 ** "abc" becomes abc | |
736 ** 'xyz' becomes xyz | |
737 ** [pqr] becomes pqr | |
738 ** `mno` becomes mno | |
739 */ | |
740 static char *amatchDequote(const char *zIn){ | |
741 int nIn; /* Size of input string, in bytes */ | |
742 char *zOut; /* Output (dequoted) string */ | |
743 | |
744 nIn = (int)strlen(zIn); | |
745 zOut = sqlite3_malloc(nIn+1); | |
746 if( zOut ){ | |
747 char q = zIn[0]; /* Quote character (if any ) */ | |
748 | |
749 if( q!='[' && q!= '\'' && q!='"' && q!='`' ){ | |
750 memcpy(zOut, zIn, nIn+1); | |
751 }else{ | |
752 int iOut = 0; /* Index of next byte to write to output */ | |
753 int iIn; /* Index of next byte to read from input */ | |
754 | |
755 if( q=='[' ) q = ']'; | |
756 for(iIn=1; iIn<nIn; iIn++){ | |
757 if( zIn[iIn]==q ) iIn++; | |
758 zOut[iOut++] = zIn[iIn]; | |
759 } | |
760 } | |
761 assert( (int)strlen(zOut)<=nIn ); | |
762 } | |
763 return zOut; | |
764 } | |
765 | |
766 /* | |
767 ** Deallocate the pVCheck prepared statement. | |
768 */ | |
769 static void amatchVCheckClear(amatch_vtab *p){ | |
770 if( p->pVCheck ){ | |
771 sqlite3_finalize(p->pVCheck); | |
772 p->pVCheck = 0; | |
773 } | |
774 } | |
775 | |
776 /* | |
777 ** Deallocate an amatch_vtab object | |
778 */ | |
779 static void amatchFree(amatch_vtab *p){ | |
780 if( p ){ | |
781 amatchFreeRules(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)); | |
791 sqlite3_free(p); | |
792 } | |
793 } | |
794 | |
795 /* | |
796 ** xDisconnect/xDestroy method for the amatch module. | |
797 */ | |
798 static int amatchDisconnect(sqlite3_vtab *pVtab){ | |
799 amatch_vtab *p = (amatch_vtab*)pVtab; | |
800 assert( p->nCursor==0 ); | |
801 amatchFree(p); | |
802 return SQLITE_OK; | |
803 } | |
804 | |
805 /* | |
806 ** Check to see if the argument is of the form: | |
807 ** | |
808 ** KEY = VALUE | |
809 ** | |
810 ** If it is, return a pointer to the first character of VALUE. | |
811 ** If not, return NULL. Spaces around the = are ignored. | |
812 */ | |
813 static const char *amatchValueOfKey(const char *zKey, const char *zStr){ | |
814 int nKey = (int)strlen(zKey); | |
815 int nStr = (int)strlen(zStr); | |
816 int i; | |
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; | |
821 i++; | |
822 while( isspace(zStr[i]) ){ i++; } | |
823 return zStr+i; | |
824 } | |
825 | |
826 /* | |
827 ** xConnect/xCreate method for the amatch module. Arguments are: | |
828 ** | |
829 ** argv[0] -> module name ("approximate_match") | |
830 ** argv[1] -> database name | |
831 ** argv[2] -> table name | |
832 ** argv[3...] -> arguments | |
833 */ | |
834 static int amatchConnect( | |
835 sqlite3 *db, | |
836 void *pAux, | |
837 int argc, const char *const*argv, | |
838 sqlite3_vtab **ppVtab, | |
839 char **pzErr | |
840 ){ | |
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]; | |
845 const char *zVal; | |
846 int i; | |
847 | |
848 (void)pAux; | |
849 *ppVtab = 0; | |
850 pNew = sqlite3_malloc( sizeof(*pNew) ); | |
851 if( pNew==0 ) return SQLITE_NOMEM; | |
852 rc = SQLITE_NOMEM; | |
853 memset(pNew, 0, sizeof(*pNew)); | |
854 pNew->db = db; | |
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]); | |
863 if( zVal ){ | |
864 sqlite3_free(pNew->zVocabTab); | |
865 pNew->zVocabTab = amatchDequote(zVal); | |
866 if( pNew->zVocabTab==0 ) goto amatchConnectError; | |
867 continue; | |
868 } | |
869 zVal = amatchValueOfKey("vocabulary_word", argv[i]); | |
870 if( zVal ){ | |
871 sqlite3_free(pNew->zVocabWord); | |
872 pNew->zVocabWord = amatchDequote(zVal); | |
873 if( pNew->zVocabWord==0 ) goto amatchConnectError; | |
874 continue; | |
875 } | |
876 zVal = amatchValueOfKey("vocabulary_language", argv[i]); | |
877 if( zVal ){ | |
878 sqlite3_free(pNew->zVocabLang); | |
879 pNew->zVocabLang = amatchDequote(zVal); | |
880 if( pNew->zVocabLang==0 ) goto amatchConnectError; | |
881 continue; | |
882 } | |
883 zVal = amatchValueOfKey("edit_distances", argv[i]); | |
884 if( zVal ){ | |
885 sqlite3_free(pNew->zCostTab); | |
886 pNew->zCostTab = amatchDequote(zVal); | |
887 if( pNew->zCostTab==0 ) goto amatchConnectError; | |
888 continue; | |
889 } | |
890 *pzErr = sqlite3_mprintf("unrecognized argument: [%s]\n", argv[i]); | |
891 amatchFree(pNew); | |
892 *ppVtab = 0; | |
893 return SQLITE_ERROR; | |
894 } | |
895 rc = SQLITE_OK; | |
896 if( pNew->zCostTab==0 ){ | |
897 *pzErr = sqlite3_mprintf("no edit_distances table specified"); | |
898 rc = SQLITE_ERROR; | |
899 }else{ | |
900 rc = amatchLoadRules(db, pNew, pzErr); | |
901 } | |
902 if( rc==SQLITE_OK ){ | |
903 rc = sqlite3_declare_vtab(db, | |
904 "CREATE TABLE x(word,distance,language," | |
905 "command HIDDEN,nword HIDDEN)" | |
906 ); | |
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 | |
912 } | |
913 if( rc!=SQLITE_OK ){ | |
914 amatchFree(pNew); | |
915 } | |
916 *ppVtab = &pNew->base; | |
917 return rc; | |
918 | |
919 amatchConnectError: | |
920 amatchFree(pNew); | |
921 return rc; | |
922 } | |
923 | |
924 /* | |
925 ** Open a new amatch cursor. | |
926 */ | |
927 static int amatchOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ | |
928 amatch_vtab *p = (amatch_vtab*)pVTab; | |
929 amatch_cursor *pCur; | |
930 pCur = sqlite3_malloc( sizeof(*pCur) ); | |
931 if( pCur==0 ) return SQLITE_NOMEM; | |
932 memset(pCur, 0, sizeof(*pCur)); | |
933 pCur->pVtab = p; | |
934 *ppCursor = &pCur->base; | |
935 p->nCursor++; | |
936 return SQLITE_OK; | |
937 } | |
938 | |
939 /* | |
940 ** Free up all the memory allocated by a cursor. Set it rLimit to 0 | |
941 ** to indicate that it is at EOF. | |
942 */ | |
943 static void amatchClearCursor(amatch_cursor *pCur){ | |
944 amatch_word *pWord, *pNextWord; | |
945 for(pWord=pCur->pAllWords; pWord; pWord=pNextWord){ | |
946 pNextWord = pWord->pNext; | |
947 sqlite3_free(pWord); | |
948 } | |
949 pCur->pAllWords = 0; | |
950 sqlite3_free(pCur->zInput); | |
951 pCur->zInput = 0; | |
952 sqlite3_free(pCur->zBuf); | |
953 pCur->zBuf = 0; | |
954 pCur->nBuf = 0; | |
955 pCur->pCost = 0; | |
956 pCur->pWord = 0; | |
957 pCur->pCurrent = 0; | |
958 pCur->rLimit = 1000000; | |
959 pCur->iLang = 0; | |
960 pCur->nWord = 0; | |
961 } | |
962 | |
963 /* | |
964 ** Close a amatch cursor. | |
965 */ | |
966 static int amatchClose(sqlite3_vtab_cursor *cur){ | |
967 amatch_cursor *pCur = (amatch_cursor *)cur; | |
968 amatchClearCursor(pCur); | |
969 pCur->pVtab->nCursor--; | |
970 sqlite3_free(pCur); | |
971 return SQLITE_OK; | |
972 } | |
973 | |
974 /* | |
975 ** Render a 24-bit unsigned integer as a 4-byte base-64 number. | |
976 */ | |
977 static void amatchEncodeInt(int x, char *z){ | |
978 static const char a[] = | |
979 "0123456789" | |
980 "ABCDEFGHIJ" | |
981 "KLMNOPQRST" | |
982 "UVWXYZ^abc" | |
983 "defghijklm" | |
984 "nopqrstuvw" | |
985 "xyz~"; | |
986 z[0] = a[(x>>18)&0x3f]; | |
987 z[1] = a[(x>>12)&0x3f]; | |
988 z[2] = a[(x>>6)&0x3f]; | |
989 z[3] = a[x&0x3f]; | |
990 } | |
991 | |
992 /* | |
993 ** Write the zCost[] field for a amatch_word object | |
994 */ | |
995 static void amatchWriteCost(amatch_word *pWord){ | |
996 amatchEncodeInt(pWord->rCost, pWord->zCost); | |
997 amatchEncodeInt(pWord->iSeq, pWord->zCost+4); | |
998 pWord->zCost[8] = 0; | |
999 } | |
1000 | |
1001 /* | |
1002 ** Add a new amatch_word object to the queue. | |
1003 ** | |
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. | |
1007 ** | |
1008 ** Do nothing if the cost exceeds threshold. | |
1009 */ | |
1010 static void amatchAddWord( | |
1011 amatch_cursor *pCur, | |
1012 amatch_cost rCost, | |
1013 int nMatch, | |
1014 const char *zWordBase, | |
1015 const char *zWordTail | |
1016 ){ | |
1017 amatch_word *pWord; | |
1018 amatch_avl *pNode; | |
1019 amatch_avl *pOther; | |
1020 int nBase, nTail; | |
1021 char zBuf[4]; | |
1022 | |
1023 if( rCost>pCur->rLimit ){ | |
1024 return; | |
1025 } | |
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 ){ | |
1032 pCur->nBuf = 0; | |
1033 return; | |
1034 } | |
1035 } | |
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); | |
1041 if( pNode ){ | |
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); | |
1048 #endif | |
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); | |
1055 #endif | |
1056 pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost); | |
1057 assert( pOther==0 ); (void)pOther; | |
1058 } | |
1059 return; | |
1060 } | |
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); | |
1083 #endif | |
1084 } | |
1085 | |
1086 /* | |
1087 ** Advance a cursor to its next row of output | |
1088 */ | |
1089 static int amatchNext(sqlite3_vtab_cursor *cur){ | |
1090 amatch_cursor *pCur = (amatch_cursor*)cur; | |
1091 amatch_word *pWord = 0; | |
1092 amatch_avl *pNode; | |
1093 int isMatch = 0; | |
1094 amatch_vtab *p = pCur->pVtab; | |
1095 int nWord; | |
1096 int rc; | |
1097 int i; | |
1098 const char *zW; | |
1099 amatch_rule *pRule; | |
1100 char *zBuf = 0; | |
1101 char nBuf = 0; | |
1102 char zNext[8]; | |
1103 char zNextIn[8]; | |
1104 int nNextIn; | |
1105 | |
1106 if( p->pVCheck==0 ){ | |
1107 char *zSql; | |
1108 if( p->zVocabLang && p->zVocabLang[0] ){ | |
1109 zSql = sqlite3_mprintf( | |
1110 "SELECT \"%w\" FROM \"%w\"", | |
1111 " WHERE \"%w\">=?1 AND \"%w\"=?2" | |
1112 " ORDER BY 1", | |
1113 p->zVocabWord, p->zVocabTab, | |
1114 p->zVocabWord, p->zVocabLang | |
1115 ); | |
1116 }else{ | |
1117 zSql = sqlite3_mprintf( | |
1118 "SELECT \"%w\" FROM \"%w\"" | |
1119 " WHERE \"%w\">=?1" | |
1120 " ORDER BY 1", | |
1121 p->zVocabWord, p->zVocabTab, | |
1122 p->zVocabWord | |
1123 ); | |
1124 } | |
1125 rc = sqlite3_prepare_v2(p->db, zSql, -1, &p->pVCheck, 0); | |
1126 sqlite3_free(zSql); | |
1127 if( rc ) return rc; | |
1128 } | |
1129 sqlite3_bind_int(p->pVCheck, 2, pCur->iLang); | |
1130 | |
1131 do{ | |
1132 pNode = amatchAvlFirst(pCur->pCost); | |
1133 if( pNode==0 ){ | |
1134 pWord = 0; | |
1135 break; | |
1136 } | |
1137 pWord = pNode->pWord; | |
1138 amatchAvlRemove(&pCur->pCost, &pWord->sCost); | |
1139 | |
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); | |
1144 #endif | |
1145 nWord = (int)strlen(pWord->zWord+2); | |
1146 if( nWord+20>nBuf ){ | |
1147 nBuf = nWord+100; | |
1148 zBuf = sqlite3_realloc(zBuf, nBuf); | |
1149 if( zBuf==0 ) return SQLITE_NOMEM; | |
1150 } | |
1151 strcpy(zBuf, pWord->zWord+2); | |
1152 zNext[0] = 0; | |
1153 zNextIn[0] = pCur->zInput[pWord->nMatch]; | |
1154 if( zNextIn[0] ){ | |
1155 for(i=1; i<=4 && (pCur->zInput[pWord->nMatch+i]&0xc0)==0x80; i++){ | |
1156 zNextIn[i] = pCur->zInput[pWord->nMatch+i]; | |
1157 } | |
1158 zNextIn[i] = 0; | |
1159 nNextIn = i; | |
1160 }else{ | |
1161 nNextIn = 0; | |
1162 } | |
1163 | |
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, ""); | |
1173 } | |
1174 } | |
1175 zBuf[nWord] = 0; | |
1176 } | |
1177 | |
1178 while( 1 ){ | |
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) | |
1189 ){ | |
1190 isMatch = 1; | |
1191 zNextIn[0] = 0; | |
1192 nNextIn = 0; | |
1193 break; | |
1194 } | |
1195 zNext[0] = zW[nWord]; | |
1196 for(i=1; i<=4 && (zW[nWord+i]&0xc0)==0x80; i++){ | |
1197 zNext[i] = zW[nWord+i]; | |
1198 } | |
1199 zNext[i] = 0; | |
1200 zBuf[nWord] = 0; | |
1201 if( p->rIns>0 ){ | |
1202 amatchAddWord(pCur, pWord->rCost+p->rIns, pWord->nMatch, | |
1203 zBuf, zNext); | |
1204 } | |
1205 if( p->rSub>0 ){ | |
1206 amatchAddWord(pCur, pWord->rCost+p->rSub, pWord->nMatch+nNextIn, | |
1207 zBuf, zNext); | |
1208 } | |
1209 if( p->rIns<0 && p->rSub<0 ) break; | |
1210 zNext[i-1]++; /* FIX ME */ | |
1211 } | |
1212 sqlite3_reset(p->pVCheck); | |
1213 | |
1214 if( p->rDel>0 ){ | |
1215 zBuf[nWord] = 0; | |
1216 amatchAddWord(pCur, pWord->rCost+p->rDel, pWord->nMatch+nNextIn, | |
1217 zBuf, ""); | |
1218 } | |
1219 | |
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); | |
1225 } | |
1226 } | |
1227 }while( !isMatch ); | |
1228 pCur->pCurrent = pWord; | |
1229 sqlite3_free(zBuf); | |
1230 return SQLITE_OK; | |
1231 } | |
1232 | |
1233 /* | |
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. | |
1237 */ | |
1238 static int amatchFilter( | |
1239 sqlite3_vtab_cursor *pVtabCursor, | |
1240 int idxNum, const char *idxStr, | |
1241 int argc, sqlite3_value **argv | |
1242 ){ | |
1243 amatch_cursor *pCur = (amatch_cursor *)pVtabCursor; | |
1244 const char *zWord = "*"; | |
1245 int idx; | |
1246 | |
1247 amatchClearCursor(pCur); | |
1248 idx = 0; | |
1249 if( idxNum & 1 ){ | |
1250 zWord = (const char*)sqlite3_value_text(argv[0]); | |
1251 idx++; | |
1252 } | |
1253 if( idxNum & 2 ){ | |
1254 pCur->rLimit = (amatch_cost)sqlite3_value_int(argv[idx]); | |
1255 idx++; | |
1256 } | |
1257 if( idxNum & 4 ){ | |
1258 pCur->iLang = (amatch_cost)sqlite3_value_int(argv[idx]); | |
1259 idx++; | |
1260 } | |
1261 pCur->zInput = sqlite3_mprintf("%s", zWord); | |
1262 if( pCur->zInput==0 ) return SQLITE_NOMEM; | |
1263 amatchAddWord(pCur, 0, 0, "", ""); | |
1264 amatchNext(pVtabCursor); | |
1265 | |
1266 return SQLITE_OK; | |
1267 } | |
1268 | |
1269 /* | |
1270 ** Only the word and distance columns have values. All other columns | |
1271 ** return NULL | |
1272 */ | |
1273 static int amatchColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ | |
1274 amatch_cursor *pCur = (amatch_cursor*)cur; | |
1275 switch( i ){ | |
1276 case AMATCH_COL_WORD: { | |
1277 sqlite3_result_text(ctx, pCur->pCurrent->zWord+2, -1, SQLITE_STATIC); | |
1278 break; | |
1279 } | |
1280 case AMATCH_COL_DISTANCE: { | |
1281 sqlite3_result_int(ctx, pCur->pCurrent->rCost); | |
1282 break; | |
1283 } | |
1284 case AMATCH_COL_LANGUAGE: { | |
1285 sqlite3_result_int(ctx, pCur->iLang); | |
1286 break; | |
1287 } | |
1288 case AMATCH_COL_NWORD: { | |
1289 sqlite3_result_int(ctx, pCur->nWord); | |
1290 break; | |
1291 } | |
1292 default: { | |
1293 sqlite3_result_null(ctx); | |
1294 break; | |
1295 } | |
1296 } | |
1297 return SQLITE_OK; | |
1298 } | |
1299 | |
1300 /* | |
1301 ** The rowid. | |
1302 */ | |
1303 static int amatchRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ | |
1304 amatch_cursor *pCur = (amatch_cursor*)cur; | |
1305 *pRowid = pCur->iRowid; | |
1306 return SQLITE_OK; | |
1307 } | |
1308 | |
1309 /* | |
1310 ** EOF indicator | |
1311 */ | |
1312 static int amatchEof(sqlite3_vtab_cursor *cur){ | |
1313 amatch_cursor *pCur = (amatch_cursor*)cur; | |
1314 return pCur->pCurrent==0; | |
1315 } | |
1316 | |
1317 /* | |
1318 ** Search for terms of these forms: | |
1319 ** | |
1320 ** (A) word MATCH $str | |
1321 ** (B1) distance < $value | |
1322 ** (B2) distance <= $value | |
1323 ** (C) language == $language | |
1324 ** | |
1325 ** The distance< and distance<= are both treated as distance<=. | |
1326 ** The query plan number is a bit vector: | |
1327 ** | |
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 | |
1331 ** | |
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. | |
1338 */ | |
1339 static int amatchBestIndex( | |
1340 sqlite3_vtab *tab, | |
1341 sqlite3_index_info *pIdxInfo | |
1342 ){ | |
1343 int iPlan = 0; | |
1344 int iDistTerm = -1; | |
1345 int iLangTerm = -1; | |
1346 int i; | |
1347 const struct sqlite3_index_constraint *pConstraint; | |
1348 | |
1349 (void)tab; | |
1350 pConstraint = pIdxInfo->aConstraint; | |
1351 for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ | |
1352 if( pConstraint->usable==0 ) continue; | |
1353 if( (iPlan & 1)==0 | |
1354 && pConstraint->iColumn==0 | |
1355 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH | |
1356 ){ | |
1357 iPlan |= 1; | |
1358 pIdxInfo->aConstraintUsage[i].argvIndex = 1; | |
1359 pIdxInfo->aConstraintUsage[i].omit = 1; | |
1360 } | |
1361 if( (iPlan & 2)==0 | |
1362 && pConstraint->iColumn==1 | |
1363 && (pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT | |
1364 || pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE) | |
1365 ){ | |
1366 iPlan |= 2; | |
1367 iDistTerm = i; | |
1368 } | |
1369 if( (iPlan & 4)==0 | |
1370 && pConstraint->iColumn==2 | |
1371 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ | |
1372 ){ | |
1373 iPlan |= 4; | |
1374 pIdxInfo->aConstraintUsage[i].omit = 1; | |
1375 iLangTerm = i; | |
1376 } | |
1377 } | |
1378 if( iPlan & 2 ){ | |
1379 pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 1+((iPlan&1)!=0); | |
1380 } | |
1381 if( iPlan & 4 ){ | |
1382 int idx = 1; | |
1383 if( iPlan & 1 ) idx++; | |
1384 if( iPlan & 2 ) idx++; | |
1385 pIdxInfo->aConstraintUsage[iLangTerm].argvIndex = idx; | |
1386 } | |
1387 pIdxInfo->idxNum = iPlan; | |
1388 if( pIdxInfo->nOrderBy==1 | |
1389 && pIdxInfo->aOrderBy[0].iColumn==1 | |
1390 && pIdxInfo->aOrderBy[0].desc==0 | |
1391 ){ | |
1392 pIdxInfo->orderByConsumed = 1; | |
1393 } | |
1394 pIdxInfo->estimatedCost = (double)10000; | |
1395 | |
1396 return SQLITE_OK; | |
1397 } | |
1398 | |
1399 /* | |
1400 ** The xUpdate() method. | |
1401 ** | |
1402 ** This implementation disallows DELETE and UPDATE. The only thing | |
1403 ** allowed is INSERT into the "command" column. | |
1404 */ | |
1405 static int amatchUpdate( | |
1406 sqlite3_vtab *pVTab, | |
1407 int argc, | |
1408 sqlite3_value **argv, | |
1409 sqlite_int64 *pRowid | |
1410 ){ | |
1411 amatch_vtab *p = (amatch_vtab*)pVTab; | |
1412 const unsigned char *zCmd; | |
1413 (void)pRowid; | |
1414 if( argc==1 ){ | |
1415 pVTab->zErrMsg = sqlite3_mprintf("DELETE from %s is not allowed", | |
1416 p->zSelf); | |
1417 return SQLITE_ERROR; | |
1418 } | |
1419 if( sqlite3_value_type(argv[0])!=SQLITE_NULL ){ | |
1420 pVTab->zErrMsg = sqlite3_mprintf("UPDATE of %s is not allowed", | |
1421 p->zSelf); | |
1422 return SQLITE_ERROR; | |
1423 } | |
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 | |
1427 ){ | |
1428 pVTab->zErrMsg = sqlite3_mprintf( | |
1429 "INSERT INTO %s allowed for column [command] only", p->zSelf); | |
1430 return SQLITE_ERROR; | |
1431 } | |
1432 zCmd = sqlite3_value_text(argv[2+AMATCH_COL_COMMAND]); | |
1433 if( zCmd==0 ) return SQLITE_OK; | |
1434 | |
1435 return SQLITE_OK; | |
1436 } | |
1437 | |
1438 /* | |
1439 ** A virtual table module that implements the "approximate_match". | |
1440 */ | |
1441 static sqlite3_module amatchModule = { | |
1442 0, /* iVersion */ | |
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 */ | |
1456 0, /* xBegin */ | |
1457 0, /* xSync */ | |
1458 0, /* xCommit */ | |
1459 0, /* xRollback */ | |
1460 0, /* xFindMethod */ | |
1461 0, /* xRename */ | |
1462 0, /* xSavepoint */ | |
1463 0, /* xRelease */ | |
1464 0 /* xRollbackTo */ | |
1465 }; | |
1466 | |
1467 #endif /* SQLITE_OMIT_VIRTUALTABLE */ | |
1468 | |
1469 /* | |
1470 ** Register the amatch virtual table | |
1471 */ | |
1472 #ifdef _WIN32 | |
1473 __declspec(dllexport) | |
1474 #endif | |
1475 int sqlite3_amatch_init( | |
1476 sqlite3 *db, | |
1477 char **pzErrMsg, | |
1478 const sqlite3_api_routines *pApi | |
1479 ){ | |
1480 int rc = SQLITE_OK; | |
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 */ | |
1486 return rc; | |
1487 } | |
OLD | NEW |