| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include "sqlite3ext.h" |
| SQLITE_EXTENSION_INIT1 |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <stdio.h> |
| #include <ctype.h> |
|
|
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
|
|
| |
| |
| |
| typedef struct amatch_vtab amatch_vtab; |
| typedef struct amatch_cursor amatch_cursor; |
| typedef struct amatch_rule amatch_rule; |
| typedef struct amatch_word amatch_word; |
| typedef struct amatch_avl amatch_avl; |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| struct amatch_avl { |
| amatch_word *pWord; |
| char *zKey; |
| amatch_avl *pBefore; |
| amatch_avl *pAfter; |
| amatch_avl *pUp; |
| short int height; |
| short int imbalance; |
| }; |
|
|
| |
| |
| |
| static void amatchAvlRecomputeHeight(amatch_avl *p){ |
| short int hBefore = p->pBefore ? p->pBefore->height : 0; |
| short int hAfter = p->pAfter ? p->pAfter->height : 0; |
| p->imbalance = hBefore - hAfter; |
| p->height = (hBefore>hAfter ? hBefore : hAfter)+1; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static amatch_avl *amatchAvlRotateBefore(amatch_avl *pP){ |
| amatch_avl *pB = pP->pBefore; |
| amatch_avl *pY = pB->pAfter; |
| pB->pUp = pP->pUp; |
| pB->pAfter = pP; |
| pP->pUp = pB; |
| pP->pBefore = pY; |
| if( pY ) pY->pUp = pP; |
| amatchAvlRecomputeHeight(pP); |
| amatchAvlRecomputeHeight(pB); |
| return pB; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static amatch_avl *amatchAvlRotateAfter(amatch_avl *pP){ |
| amatch_avl *pA = pP->pAfter; |
| amatch_avl *pY = pA->pBefore; |
| pA->pUp = pP->pUp; |
| pA->pBefore = pP; |
| pP->pUp = pA; |
| pP->pAfter = pY; |
| if( pY ) pY->pUp = pP; |
| amatchAvlRecomputeHeight(pP); |
| amatchAvlRecomputeHeight(pA); |
| return pA; |
| } |
|
|
| |
| |
| |
| |
| static amatch_avl **amatchAvlFromPtr(amatch_avl *p, amatch_avl **pp){ |
| amatch_avl *pUp = p->pUp; |
| if( pUp==0 ) return pp; |
| if( pUp->pAfter==p ) return &pUp->pAfter; |
| return &pUp->pBefore; |
| } |
|
|
| |
| |
| |
| |
| static amatch_avl *amatchAvlBalance(amatch_avl *p){ |
| amatch_avl *pTop = p; |
| amatch_avl **pp; |
| while( p ){ |
| amatchAvlRecomputeHeight(p); |
| if( p->imbalance>=2 ){ |
| amatch_avl *pB = p->pBefore; |
| if( pB->imbalance<0 ) p->pBefore = amatchAvlRotateAfter(pB); |
| pp = amatchAvlFromPtr(p,&p); |
| p = *pp = amatchAvlRotateBefore(p); |
| }else if( p->imbalance<=(-2) ){ |
| amatch_avl *pA = p->pAfter; |
| if( pA->imbalance>0 ) p->pAfter = amatchAvlRotateBefore(pA); |
| pp = amatchAvlFromPtr(p,&p); |
| p = *pp = amatchAvlRotateAfter(p); |
| } |
| pTop = p; |
| p = p->pUp; |
| } |
| return pTop; |
| } |
|
|
| |
| |
| |
| static amatch_avl *amatchAvlSearch(amatch_avl *p, const char *zKey){ |
| int c; |
| while( p && (c = strcmp(zKey, p->zKey))!=0 ){ |
| p = (c<0) ? p->pBefore : p->pAfter; |
| } |
| return p; |
| } |
|
|
| |
| |
| static amatch_avl *amatchAvlFirst(amatch_avl *p){ |
| if( p ) while( p->pBefore ) p = p->pBefore; |
| return p; |
| } |
|
|
| #if 0 |
| |
| |
| static amatch_avl *amatchAvlNext(amatch_avl *p){ |
| amatch_avl *pPrev = 0; |
| while( p && p->pAfter==pPrev ){ |
| pPrev = p; |
| p = p->pUp; |
| } |
| if( p && pPrev==0 ){ |
| p = amatchAvlFirst(p->pAfter); |
| } |
| return p; |
| } |
| #endif |
|
|
| #if 0 |
| |
| |
| static int amatchAvlIntegrity(amatch_avl *pHead){ |
| amatch_avl *p; |
| if( pHead==0 ) return 1; |
| if( (p = pHead->pBefore)!=0 ){ |
| assert( p->pUp==pHead ); |
| assert( amatchAvlIntegrity(p) ); |
| assert( strcmp(p->zKey, pHead->zKey)<0 ); |
| while( p->pAfter ) p = p->pAfter; |
| assert( strcmp(p->zKey, pHead->zKey)<0 ); |
| } |
| if( (p = pHead->pAfter)!=0 ){ |
| assert( p->pUp==pHead ); |
| assert( amatchAvlIntegrity(p) ); |
| assert( strcmp(p->zKey, pHead->zKey)>0 ); |
| p = amatchAvlFirst(p); |
| assert( strcmp(p->zKey, pHead->zKey)>0 ); |
| } |
| return 1; |
| } |
| static int amatchAvlIntegrity2(amatch_avl *pHead){ |
| amatch_avl *p, *pNext; |
| for(p=amatchAvlFirst(pHead); p; p=pNext){ |
| pNext = amatchAvlNext(p); |
| if( pNext==0 ) break; |
| assert( strcmp(p->zKey, pNext->zKey)<0 ); |
| } |
| return 1; |
| } |
| #endif |
|
|
| |
| |
| |
| |
| static amatch_avl *amatchAvlInsert(amatch_avl **ppHead, amatch_avl *pNew){ |
| int c; |
| amatch_avl *p = *ppHead; |
| if( p==0 ){ |
| p = pNew; |
| pNew->pUp = 0; |
| }else{ |
| while( p ){ |
| c = strcmp(pNew->zKey, p->zKey); |
| if( c<0 ){ |
| if( p->pBefore ){ |
| p = p->pBefore; |
| }else{ |
| p->pBefore = pNew; |
| pNew->pUp = p; |
| break; |
| } |
| }else if( c>0 ){ |
| if( p->pAfter ){ |
| p = p->pAfter; |
| }else{ |
| p->pAfter = pNew; |
| pNew->pUp = p; |
| break; |
| } |
| }else{ |
| return p; |
| } |
| } |
| } |
| pNew->pBefore = 0; |
| pNew->pAfter = 0; |
| pNew->height = 1; |
| pNew->imbalance = 0; |
| *ppHead = amatchAvlBalance(p); |
| |
| |
| return 0; |
| } |
|
|
| |
| |
| |
| static void amatchAvlRemove(amatch_avl **ppHead, amatch_avl *pOld){ |
| amatch_avl **ppParent; |
| amatch_avl *pBalance = 0; |
| |
| ppParent = amatchAvlFromPtr(pOld, ppHead); |
| if( pOld->pBefore==0 && pOld->pAfter==0 ){ |
| *ppParent = 0; |
| pBalance = pOld->pUp; |
| }else if( pOld->pBefore && pOld->pAfter ){ |
| amatch_avl *pX, *pY; |
| pX = amatchAvlFirst(pOld->pAfter); |
| *amatchAvlFromPtr(pX, 0) = pX->pAfter; |
| if( pX->pAfter ) pX->pAfter->pUp = pX->pUp; |
| pBalance = pX->pUp; |
| pX->pAfter = pOld->pAfter; |
| if( pX->pAfter ){ |
| pX->pAfter->pUp = pX; |
| }else{ |
| assert( pBalance==pOld ); |
| pBalance = pX; |
| } |
| pX->pBefore = pY = pOld->pBefore; |
| if( pY ) pY->pUp = pX; |
| pX->pUp = pOld->pUp; |
| *ppParent = pX; |
| }else if( pOld->pBefore==0 ){ |
| *ppParent = pBalance = pOld->pAfter; |
| pBalance->pUp = pOld->pUp; |
| }else if( pOld->pAfter==0 ){ |
| *ppParent = pBalance = pOld->pBefore; |
| pBalance->pUp = pOld->pUp; |
| } |
| *ppHead = amatchAvlBalance(pBalance); |
| pOld->pUp = 0; |
| pOld->pBefore = 0; |
| pOld->pAfter = 0; |
| |
| |
| } |
| |
| |
| |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| typedef int amatch_cost; |
| typedef signed char amatch_len; |
| typedef int amatch_langid; |
|
|
| |
| |
| |
| #define AMATCH_MX_LENGTH 50 |
| #define AMATCH_MX_LANGID 2147483647 |
| #define AMATCH_MX_COST 1000 |
|
|
| |
| |
| |
| struct amatch_word { |
| amatch_word *pNext; |
| amatch_avl sCost; |
| amatch_avl sWord; |
| amatch_cost rCost; |
| int iSeq; |
| char zCost[10]; |
| short int nMatch; |
| char zWord[4]; |
| }; |
|
|
| |
| |
| |
| |
| struct amatch_rule { |
| amatch_rule *pNext; |
| char *zFrom; |
| amatch_cost rCost; |
| amatch_langid iLang; |
| amatch_len nFrom, nTo; |
| char zTo[4]; |
| }; |
|
|
| |
| |
| |
| struct amatch_vtab { |
| sqlite3_vtab base; |
| char *zClassName; |
| char *zDb; |
| char *zSelf; |
| char *zCostTab; |
| char *zVocabTab; |
| char *zVocabWord; |
| char *zVocabLang; |
| amatch_rule *pRule; |
| amatch_cost rIns; |
| amatch_cost rDel; |
| amatch_cost rSub; |
| sqlite3 *db; |
| sqlite3_stmt *pVCheck; |
| int nCursor; |
| }; |
|
|
| |
| struct amatch_cursor { |
| sqlite3_vtab_cursor base; |
| sqlite3_int64 iRowid; |
| amatch_langid iLang; |
| amatch_cost rLimit; |
| sqlite3_int64 nBuf; |
| int oomErr; |
| int nWord; |
| char *zBuf; |
| char *zInput; |
| amatch_vtab *pVtab; |
| amatch_word *pAllWords; |
| amatch_word *pCurrent; |
| amatch_avl *pCost; |
| amatch_avl *pWord; |
| }; |
|
|
| |
| |
| |
| |
| |
| static amatch_rule *amatchMergeRules(amatch_rule *pA, amatch_rule *pB){ |
| amatch_rule head; |
| amatch_rule *pTail; |
|
|
| pTail = &head; |
| while( pA && pB ){ |
| if( pA->rCost<=pB->rCost ){ |
| pTail->pNext = pA; |
| pTail = pA; |
| pA = pA->pNext; |
| }else{ |
| pTail->pNext = pB; |
| pTail = pB; |
| pB = pB->pNext; |
| } |
| } |
| if( pA==0 ){ |
| pTail->pNext = pB; |
| }else{ |
| pTail->pNext = pA; |
| } |
| return head.pNext; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int amatchLoadOneRule( |
| amatch_vtab *p, |
| sqlite3_stmt *pStmt, |
| amatch_rule **ppRule, |
| char **pzErr |
| ){ |
| sqlite3_int64 iLang = sqlite3_column_int64(pStmt, 0); |
| const char *zFrom = (const char *)sqlite3_column_text(pStmt, 1); |
| const char *zTo = (const char *)sqlite3_column_text(pStmt, 2); |
| amatch_cost rCost = sqlite3_column_int(pStmt, 3); |
|
|
| int rc = SQLITE_OK; |
| int nFrom; |
| int nTo; |
| amatch_rule *pRule = 0; |
|
|
| if( zFrom==0 ) zFrom = ""; |
| if( zTo==0 ) zTo = ""; |
| nFrom = (int)strlen(zFrom); |
| nTo = (int)strlen(zTo); |
|
|
| |
| if( strcmp(zFrom, zTo)==0 ){ |
| if( zFrom[0]=='?' && zFrom[1]==0 ){ |
| if( p->rSub==0 || p->rSub>rCost ) p->rSub = rCost; |
| } |
| *ppRule = 0; |
| return SQLITE_OK; |
| } |
|
|
| if( rCost<=0 || rCost>AMATCH_MX_COST ){ |
| *pzErr = sqlite3_mprintf("%s: cost must be between 1 and %d", |
| p->zClassName, AMATCH_MX_COST |
| ); |
| rc = SQLITE_ERROR; |
| }else |
| if( nFrom>AMATCH_MX_LENGTH || nTo>AMATCH_MX_LENGTH ){ |
| *pzErr = sqlite3_mprintf("%s: maximum string length is %d", |
| p->zClassName, AMATCH_MX_LENGTH |
| ); |
| rc = SQLITE_ERROR; |
| }else |
| if( iLang<0 || iLang>AMATCH_MX_LANGID ){ |
| *pzErr = sqlite3_mprintf("%s: iLang must be between 0 and %d", |
| p->zClassName, AMATCH_MX_LANGID |
| ); |
| rc = SQLITE_ERROR; |
| }else |
| if( strcmp(zFrom,"")==0 && strcmp(zTo,"?")==0 ){ |
| if( p->rIns==0 || p->rIns>rCost ) p->rIns = rCost; |
| }else |
| if( strcmp(zFrom,"?")==0 && strcmp(zTo,"")==0 ){ |
| if( p->rDel==0 || p->rDel>rCost ) p->rDel = rCost; |
| }else |
| { |
| pRule = sqlite3_malloc64( sizeof(*pRule) + nFrom + nTo ); |
| if( pRule==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| memset(pRule, 0, sizeof(*pRule)); |
| pRule->zFrom = &pRule->zTo[nTo+1]; |
| pRule->nFrom = (amatch_len)nFrom; |
| memcpy(pRule->zFrom, zFrom, nFrom+1); |
| memcpy(pRule->zTo, zTo, nTo+1); |
| pRule->nTo = (amatch_len)nTo; |
| pRule->rCost = rCost; |
| pRule->iLang = (int)iLang; |
| } |
| } |
|
|
| *ppRule = pRule; |
| return rc; |
| } |
|
|
| |
| |
| |
| static void amatchFreeRules(amatch_vtab *p){ |
| while( p->pRule ){ |
| amatch_rule *pRule = p->pRule; |
| p->pRule = pRule->pNext; |
| sqlite3_free(pRule); |
| } |
| p->pRule = 0; |
| } |
|
|
| |
| |
| |
| static int amatchLoadRules( |
| sqlite3 *db, |
| amatch_vtab *p, |
| char **pzErr |
| ){ |
| int rc = SQLITE_OK; |
| char *zSql; |
| amatch_rule *pHead = 0; |
|
|
| zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", p->zDb, p->zCostTab); |
| if( zSql==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| int rc2; |
| sqlite3_stmt *pStmt = 0; |
| rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
| if( rc!=SQLITE_OK ){ |
| *pzErr = sqlite3_mprintf("%s: %s", p->zClassName, sqlite3_errmsg(db)); |
| }else if( sqlite3_column_count(pStmt)!=4 ){ |
| *pzErr = sqlite3_mprintf("%s: %s has %d columns, expected 4", |
| p->zClassName, p->zCostTab, sqlite3_column_count(pStmt) |
| ); |
| rc = SQLITE_ERROR; |
| }else{ |
| while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ |
| amatch_rule *pRule = 0; |
| rc = amatchLoadOneRule(p, pStmt, &pRule, pzErr); |
| if( pRule ){ |
| pRule->pNext = pHead; |
| pHead = pRule; |
| } |
| } |
| } |
| rc2 = sqlite3_finalize(pStmt); |
| if( rc==SQLITE_OK ) rc = rc2; |
| } |
| sqlite3_free(zSql); |
|
|
| |
| |
| |
| |
| if( rc==SQLITE_OK ){ |
| unsigned int i; |
| amatch_rule *pX; |
| amatch_rule *a[15]; |
| for(i=0; i<sizeof(a)/sizeof(a[0]); i++) a[i] = 0; |
| while( (pX = pHead)!=0 ){ |
| pHead = pX->pNext; |
| pX->pNext = 0; |
| for(i=0; a[i] && i<sizeof(a)/sizeof(a[0])-1; i++){ |
| pX = amatchMergeRules(a[i], pX); |
| a[i] = 0; |
| } |
| a[i] = amatchMergeRules(a[i], pX); |
| } |
| for(pX=a[0], i=1; i<sizeof(a)/sizeof(a[0]); i++){ |
| pX = amatchMergeRules(a[i], pX); |
| } |
| p->pRule = amatchMergeRules(p->pRule, pX); |
| }else{ |
| |
| |
| |
| assert( p->pRule==0 ); |
| p->pRule = pHead; |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static char *amatchDequote(const char *zIn){ |
| sqlite3_int64 nIn; |
| char *zOut; |
|
|
| nIn = strlen(zIn); |
| zOut = sqlite3_malloc64(nIn+1); |
| if( zOut ){ |
| char q = zIn[0]; |
|
|
| if( q!='[' && q!= '\'' && q!='"' && q!='`' ){ |
| memcpy(zOut, zIn, (size_t)(nIn+1)); |
| }else{ |
| int iOut = 0; |
| int iIn; |
|
|
| if( q=='[' ) q = ']'; |
| for(iIn=1; iIn<nIn; iIn++){ |
| if( zIn[iIn]==q ) iIn++; |
| zOut[iOut++] = zIn[iIn]; |
| } |
| } |
| assert( (int)strlen(zOut)<=nIn ); |
| } |
| return zOut; |
| } |
|
|
| |
| |
| |
| static void amatchVCheckClear(amatch_vtab *p){ |
| if( p->pVCheck ){ |
| sqlite3_finalize(p->pVCheck); |
| p->pVCheck = 0; |
| } |
| } |
|
|
| |
| |
| |
| static void amatchFree(amatch_vtab *p){ |
| if( p ){ |
| amatchFreeRules(p); |
| amatchVCheckClear(p); |
| sqlite3_free(p->zClassName); |
| sqlite3_free(p->zDb); |
| sqlite3_free(p->zCostTab); |
| sqlite3_free(p->zVocabTab); |
| sqlite3_free(p->zVocabWord); |
| sqlite3_free(p->zVocabLang); |
| sqlite3_free(p->zSelf); |
| memset(p, 0, sizeof(*p)); |
| sqlite3_free(p); |
| } |
| } |
|
|
| |
| |
| |
| static int amatchDisconnect(sqlite3_vtab *pVtab){ |
| amatch_vtab *p = (amatch_vtab*)pVtab; |
| assert( p->nCursor==0 ); |
| amatchFree(p); |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static const char *amatchValueOfKey(const char *zKey, const char *zStr){ |
| int nKey = (int)strlen(zKey); |
| int nStr = (int)strlen(zStr); |
| int i; |
| if( nStr<nKey+1 ) return 0; |
| if( memcmp(zStr, zKey, nKey)!=0 ) return 0; |
| for(i=nKey; isspace((unsigned char)zStr[i]); i++){} |
| if( zStr[i]!='=' ) return 0; |
| i++; |
| while( isspace((unsigned char)zStr[i]) ){ i++; } |
| return zStr+i; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static int amatchConnect( |
| sqlite3 *db, |
| void *pAux, |
| int argc, const char *const*argv, |
| sqlite3_vtab **ppVtab, |
| char **pzErr |
| ){ |
| int rc = SQLITE_OK; |
| amatch_vtab *pNew = 0; |
| const char *zModule = argv[0]; |
| const char *zDb = argv[1]; |
| const char *zVal; |
| int i; |
|
|
| (void)pAux; |
| *ppVtab = 0; |
| pNew = sqlite3_malloc( sizeof(*pNew) ); |
| if( pNew==0 ) return SQLITE_NOMEM; |
| rc = SQLITE_NOMEM; |
| memset(pNew, 0, sizeof(*pNew)); |
| pNew->db = db; |
| pNew->zClassName = sqlite3_mprintf("%s", zModule); |
| if( pNew->zClassName==0 ) goto amatchConnectError; |
| pNew->zDb = sqlite3_mprintf("%s", zDb); |
| if( pNew->zDb==0 ) goto amatchConnectError; |
| pNew->zSelf = sqlite3_mprintf("%s", argv[2]); |
| if( pNew->zSelf==0 ) goto amatchConnectError; |
| for(i=3; i<argc; i++){ |
| zVal = amatchValueOfKey("vocabulary_table", argv[i]); |
| if( zVal ){ |
| sqlite3_free(pNew->zVocabTab); |
| pNew->zVocabTab = amatchDequote(zVal); |
| if( pNew->zVocabTab==0 ) goto amatchConnectError; |
| continue; |
| } |
| zVal = amatchValueOfKey("vocabulary_word", argv[i]); |
| if( zVal ){ |
| sqlite3_free(pNew->zVocabWord); |
| pNew->zVocabWord = amatchDequote(zVal); |
| if( pNew->zVocabWord==0 ) goto amatchConnectError; |
| continue; |
| } |
| zVal = amatchValueOfKey("vocabulary_language", argv[i]); |
| if( zVal ){ |
| sqlite3_free(pNew->zVocabLang); |
| pNew->zVocabLang = amatchDequote(zVal); |
| if( pNew->zVocabLang==0 ) goto amatchConnectError; |
| continue; |
| } |
| zVal = amatchValueOfKey("edit_distances", argv[i]); |
| if( zVal ){ |
| sqlite3_free(pNew->zCostTab); |
| pNew->zCostTab = amatchDequote(zVal); |
| if( pNew->zCostTab==0 ) goto amatchConnectError; |
| continue; |
| } |
| *pzErr = sqlite3_mprintf("unrecognized argument: [%s]\n", argv[i]); |
| amatchFree(pNew); |
| *ppVtab = 0; |
| return SQLITE_ERROR; |
| } |
| rc = SQLITE_OK; |
| if( pNew->zCostTab==0 ){ |
| *pzErr = sqlite3_mprintf("no edit_distances table specified"); |
| rc = SQLITE_ERROR; |
| }else{ |
| rc = amatchLoadRules(db, pNew, pzErr); |
| } |
| if( rc==SQLITE_OK ){ |
| sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); |
| rc = sqlite3_declare_vtab(db, |
| "CREATE TABLE x(word,distance,language," |
| "command HIDDEN,nword HIDDEN)" |
| ); |
| #define AMATCH_COL_WORD 0 |
| #define AMATCH_COL_DISTANCE 1 |
| #define AMATCH_COL_LANGUAGE 2 |
| #define AMATCH_COL_COMMAND 3 |
| #define AMATCH_COL_NWORD 4 |
| } |
| if( rc!=SQLITE_OK ){ |
| amatchFree(pNew); |
| } |
| *ppVtab = &pNew->base; |
| return rc; |
|
|
| amatchConnectError: |
| amatchFree(pNew); |
| return rc; |
| } |
|
|
| |
| |
| |
| static int amatchOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ |
| amatch_vtab *p = (amatch_vtab*)pVTab; |
| amatch_cursor *pCur; |
| pCur = sqlite3_malloc( sizeof(*pCur) ); |
| if( pCur==0 ) return SQLITE_NOMEM; |
| memset(pCur, 0, sizeof(*pCur)); |
| pCur->pVtab = p; |
| *ppCursor = &pCur->base; |
| p->nCursor++; |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| |
| static void amatchClearCursor(amatch_cursor *pCur){ |
| amatch_word *pWord, *pNextWord; |
| for(pWord=pCur->pAllWords; pWord; pWord=pNextWord){ |
| pNextWord = pWord->pNext; |
| sqlite3_free(pWord); |
| } |
| pCur->pAllWords = 0; |
| sqlite3_free(pCur->zInput); |
| pCur->zInput = 0; |
| sqlite3_free(pCur->zBuf); |
| pCur->zBuf = 0; |
| pCur->nBuf = 0; |
| pCur->pCost = 0; |
| pCur->pWord = 0; |
| pCur->pCurrent = 0; |
| pCur->rLimit = 1000000; |
| pCur->iLang = 0; |
| pCur->nWord = 0; |
| } |
|
|
| |
| |
| |
| static int amatchClose(sqlite3_vtab_cursor *cur){ |
| amatch_cursor *pCur = (amatch_cursor *)cur; |
| amatchClearCursor(pCur); |
| pCur->pVtab->nCursor--; |
| sqlite3_free(pCur); |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| static void amatchEncodeInt(int x, char *z){ |
| static const char a[] = |
| "0123456789" |
| "ABCDEFGHIJ" |
| "KLMNOPQRST" |
| "UVWXYZ^abc" |
| "defghijklm" |
| "nopqrstuvw" |
| "xyz~"; |
| z[0] = a[(x>>18)&0x3f]; |
| z[1] = a[(x>>12)&0x3f]; |
| z[2] = a[(x>>6)&0x3f]; |
| z[3] = a[x&0x3f]; |
| } |
|
|
| |
| |
| |
| static void amatchWriteCost(amatch_word *pWord){ |
| amatchEncodeInt(pWord->rCost, pWord->zCost); |
| amatchEncodeInt(pWord->iSeq, pWord->zCost+4); |
| pWord->zCost[8] = 0; |
| } |
|
|
| |
| |
| |
| static void amatchStrcpy(char *dest, const char *src){ |
| while( (*(dest++) = *(src++))!=0 ){} |
| } |
| static void amatchStrcat(char *dest, const char *src){ |
| while( *dest ) dest++; |
| amatchStrcpy(dest, src); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static void amatchAddWord( |
| amatch_cursor *pCur, |
| amatch_cost rCost, |
| int nMatch, |
| const char *zWordBase, |
| const char *zWordTail |
| ){ |
| amatch_word *pWord; |
| amatch_avl *pNode; |
| amatch_avl *pOther; |
| int nBase, nTail; |
| char zBuf[4]; |
| |
| if( rCost>pCur->rLimit ){ |
| return; |
| } |
| nBase = (int)strlen(zWordBase); |
| nTail = (int)strlen(zWordTail); |
| if( nBase+nTail+3>pCur->nBuf ){ |
| pCur->nBuf = nBase+nTail+100; |
| pCur->zBuf = sqlite3_realloc64(pCur->zBuf, pCur->nBuf); |
| if( pCur->zBuf==0 ){ |
| pCur->nBuf = 0; |
| return; |
| } |
| } |
| amatchEncodeInt(nMatch, zBuf); |
| memcpy(pCur->zBuf, zBuf+2, 2); |
| memcpy(pCur->zBuf+2, zWordBase, nBase); |
| memcpy(pCur->zBuf+2+nBase, zWordTail, nTail+1); |
| pNode = amatchAvlSearch(pCur->pWord, pCur->zBuf); |
| if( pNode ){ |
| pWord = pNode->pWord; |
| if( pWord->rCost>rCost ){ |
| #ifdef AMATCH_TRACE_1 |
| printf("UPDATE [%s][%.*s^%s] %d (\"%s\" \"%s\")\n", |
| pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput, |
| pWord->rCost, pWord->zWord, pWord->zCost); |
| #endif |
| amatchAvlRemove(&pCur->pCost, &pWord->sCost); |
| pWord->rCost = rCost; |
| amatchWriteCost(pWord); |
| #ifdef AMATCH_TRACE_1 |
| printf(" ---> %d (\"%s\" \"%s\")\n", |
| pWord->rCost, pWord->zWord, pWord->zCost); |
| #endif |
| pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost); |
| assert( pOther==0 ); (void)pOther; |
| } |
| return; |
| } |
| pWord = sqlite3_malloc64( sizeof(*pWord) + nBase + nTail - 1 ); |
| if( pWord==0 ) return; |
| memset(pWord, 0, sizeof(*pWord)); |
| pWord->rCost = rCost; |
| pWord->iSeq = pCur->nWord++; |
| amatchWriteCost(pWord); |
| pWord->nMatch = (short)nMatch; |
| pWord->pNext = pCur->pAllWords; |
| pCur->pAllWords = pWord; |
| pWord->sCost.zKey = pWord->zCost; |
| pWord->sCost.pWord = pWord; |
| pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost); |
| assert( pOther==0 ); (void)pOther; |
| pWord->sWord.zKey = pWord->zWord; |
| pWord->sWord.pWord = pWord; |
| amatchStrcpy(pWord->zWord, pCur->zBuf); |
| pOther = amatchAvlInsert(&pCur->pWord, &pWord->sWord); |
| assert( pOther==0 ); (void)pOther; |
| #ifdef AMATCH_TRACE_1 |
| printf("INSERT [%s][%.*s^%s] %d (\"%s\" \"%s\")\n", pWord->zWord+2, |
| pWord->nMatch, pCur->zInput, pCur->zInput+pWord->nMatch, rCost, |
| pWord->zWord, pWord->zCost); |
| #endif |
| } |
|
|
|
|
| |
| |
| |
| static int amatchNext(sqlite3_vtab_cursor *cur){ |
| amatch_cursor *pCur = (amatch_cursor*)cur; |
| amatch_word *pWord = 0; |
| amatch_avl *pNode; |
| int isMatch = 0; |
| amatch_vtab *p = pCur->pVtab; |
| sqlite3_int64 nWord; |
| int rc; |
| int i; |
| const char *zW; |
| amatch_rule *pRule; |
| char *zBuf = 0; |
| sqlite3_int64 nBuf = 0; |
| char zNext[8]; |
| char zNextIn[8]; |
| int nNextIn; |
|
|
| if( p->pVCheck==0 ){ |
| char *zSql; |
| if( p->zVocabLang && p->zVocabLang[0] ){ |
| zSql = sqlite3_mprintf( |
| "SELECT \"%w\" FROM \"%w\"", |
| " WHERE \"%w\">=?1 AND \"%w\"=?2" |
| " ORDER BY 1", |
| p->zVocabWord, p->zVocabTab, |
| p->zVocabWord, p->zVocabLang |
| ); |
| }else{ |
| zSql = sqlite3_mprintf( |
| "SELECT \"%w\" FROM \"%w\"" |
| " WHERE \"%w\">=?1" |
| " ORDER BY 1", |
| p->zVocabWord, p->zVocabTab, |
| p->zVocabWord |
| ); |
| } |
| rc = sqlite3_prepare_v2(p->db, zSql, -1, &p->pVCheck, 0); |
| sqlite3_free(zSql); |
| if( rc ) return rc; |
| } |
| sqlite3_bind_int(p->pVCheck, 2, pCur->iLang); |
|
|
| do{ |
| pNode = amatchAvlFirst(pCur->pCost); |
| if( pNode==0 ){ |
| pWord = 0; |
| break; |
| } |
| pWord = pNode->pWord; |
| amatchAvlRemove(&pCur->pCost, &pWord->sCost); |
|
|
| #ifdef AMATCH_TRACE_1 |
| printf("PROCESS [%s][%.*s^%s] %d (\"%s\" \"%s\")\n", |
| pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput+pWord->nMatch, |
| pWord->rCost, pWord->zWord, pWord->zCost); |
| #endif |
| nWord = (int)strlen(pWord->zWord+2); |
| if( nWord+20>nBuf ){ |
| nBuf = (char)(nWord+100); |
| zBuf = sqlite3_realloc64(zBuf, nBuf); |
| if( zBuf==0 ) return SQLITE_NOMEM; |
| } |
| amatchStrcpy(zBuf, pWord->zWord+2); |
| zNext[0] = 0; |
| zNextIn[0] = pCur->zInput[pWord->nMatch]; |
| if( zNextIn[0] ){ |
| for(i=1; i<=4 && (pCur->zInput[pWord->nMatch+i]&0xc0)==0x80; i++){ |
| zNextIn[i] = pCur->zInput[pWord->nMatch+i]; |
| } |
| zNextIn[i] = 0; |
| nNextIn = i; |
| }else{ |
| nNextIn = 0; |
| } |
|
|
| if( zNextIn[0] && zNextIn[0]!='*' ){ |
| sqlite3_reset(p->pVCheck); |
| amatchStrcat(zBuf, zNextIn); |
| sqlite3_bind_text(p->pVCheck, 1, zBuf, nWord+nNextIn, SQLITE_STATIC); |
| rc = sqlite3_step(p->pVCheck); |
| if( rc==SQLITE_ROW ){ |
| zW = (const char*)sqlite3_column_text(p->pVCheck, 0); |
| if( strncmp(zBuf, zW, nWord+nNextIn)==0 ){ |
| amatchAddWord(pCur, pWord->rCost, pWord->nMatch+nNextIn, zBuf, ""); |
| } |
| } |
| zBuf[nWord] = 0; |
| } |
|
|
| while( 1 ){ |
| amatchStrcpy(zBuf+nWord, zNext); |
| sqlite3_reset(p->pVCheck); |
| sqlite3_bind_text(p->pVCheck, 1, zBuf, -1, SQLITE_TRANSIENT); |
| rc = sqlite3_step(p->pVCheck); |
| if( rc!=SQLITE_ROW ) break; |
| zW = (const char*)sqlite3_column_text(p->pVCheck, 0); |
| amatchStrcpy(zBuf+nWord, zNext); |
| if( strncmp(zW, zBuf, nWord)!=0 ) break; |
| if( (zNextIn[0]=='*' && zNextIn[1]==0) |
| || (zNextIn[0]==0 && zW[nWord]==0) |
| ){ |
| isMatch = 1; |
| zNextIn[0] = 0; |
| nNextIn = 0; |
| break; |
| } |
| zNext[0] = zW[nWord]; |
| for(i=1; i<=4 && (zW[nWord+i]&0xc0)==0x80; i++){ |
| zNext[i] = zW[nWord+i]; |
| } |
| zNext[i] = 0; |
| zBuf[nWord] = 0; |
| if( p->rIns>0 ){ |
| amatchAddWord(pCur, pWord->rCost+p->rIns, pWord->nMatch, |
| zBuf, zNext); |
| } |
| if( p->rSub>0 ){ |
| amatchAddWord(pCur, pWord->rCost+p->rSub, pWord->nMatch+nNextIn, |
| zBuf, zNext); |
| } |
| if( p->rIns<0 && p->rSub<0 ) break; |
| zNext[i-1]++; |
| } |
| sqlite3_reset(p->pVCheck); |
|
|
| if( p->rDel>0 ){ |
| zBuf[nWord] = 0; |
| amatchAddWord(pCur, pWord->rCost+p->rDel, pWord->nMatch+nNextIn, |
| zBuf, ""); |
| } |
|
|
| for(pRule=p->pRule; pRule; pRule=pRule->pNext){ |
| if( pRule->iLang!=pCur->iLang ) continue; |
| if( strncmp(pRule->zFrom, pCur->zInput+pWord->nMatch, pRule->nFrom)==0 ){ |
| amatchAddWord(pCur, pWord->rCost+pRule->rCost, |
| pWord->nMatch+pRule->nFrom, pWord->zWord+2, pRule->zTo); |
| } |
| } |
| }while( !isMatch ); |
| pCur->pCurrent = pWord; |
| sqlite3_free(zBuf); |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| |
| |
| static int amatchFilter( |
| sqlite3_vtab_cursor *pVtabCursor, |
| int idxNum, const char *idxStr, |
| int argc, sqlite3_value **argv |
| ){ |
| amatch_cursor *pCur = (amatch_cursor *)pVtabCursor; |
| const char *zWord = "*"; |
| int idx; |
|
|
| amatchClearCursor(pCur); |
| idx = 0; |
| if( idxNum & 1 ){ |
| zWord = (const char*)sqlite3_value_text(argv[0]); |
| idx++; |
| } |
| if( idxNum & 2 ){ |
| pCur->rLimit = (amatch_cost)sqlite3_value_int(argv[idx]); |
| idx++; |
| } |
| if( idxNum & 4 ){ |
| pCur->iLang = (amatch_cost)sqlite3_value_int(argv[idx]); |
| idx++; |
| } |
| pCur->zInput = sqlite3_mprintf("%s", zWord); |
| if( pCur->zInput==0 ) return SQLITE_NOMEM; |
| amatchAddWord(pCur, 0, 0, "", ""); |
| amatchNext(pVtabCursor); |
|
|
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| |
| static int amatchColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ |
| amatch_cursor *pCur = (amatch_cursor*)cur; |
| switch( i ){ |
| case AMATCH_COL_WORD: { |
| sqlite3_result_text(ctx, pCur->pCurrent->zWord+2, -1, SQLITE_STATIC); |
| break; |
| } |
| case AMATCH_COL_DISTANCE: { |
| sqlite3_result_int(ctx, pCur->pCurrent->rCost); |
| break; |
| } |
| case AMATCH_COL_LANGUAGE: { |
| sqlite3_result_int(ctx, pCur->iLang); |
| break; |
| } |
| case AMATCH_COL_NWORD: { |
| sqlite3_result_int(ctx, pCur->nWord); |
| break; |
| } |
| default: { |
| sqlite3_result_null(ctx); |
| break; |
| } |
| } |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| static int amatchRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| amatch_cursor *pCur = (amatch_cursor*)cur; |
| *pRowid = pCur->iRowid; |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| static int amatchEof(sqlite3_vtab_cursor *cur){ |
| amatch_cursor *pCur = (amatch_cursor*)cur; |
| return pCur->pCurrent==0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int amatchBestIndex( |
| sqlite3_vtab *tab, |
| sqlite3_index_info *pIdxInfo |
| ){ |
| int iPlan = 0; |
| int iDistTerm = -1; |
| int iLangTerm = -1; |
| int i; |
| const struct sqlite3_index_constraint *pConstraint; |
|
|
| (void)tab; |
| pConstraint = pIdxInfo->aConstraint; |
| for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ |
| if( pConstraint->usable==0 ) continue; |
| if( (iPlan & 1)==0 |
| && pConstraint->iColumn==0 |
| && pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH |
| ){ |
| iPlan |= 1; |
| pIdxInfo->aConstraintUsage[i].argvIndex = 1; |
| pIdxInfo->aConstraintUsage[i].omit = 1; |
| } |
| if( (iPlan & 2)==0 |
| && pConstraint->iColumn==1 |
| && (pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT |
| || pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE) |
| ){ |
| iPlan |= 2; |
| iDistTerm = i; |
| } |
| if( (iPlan & 4)==0 |
| && pConstraint->iColumn==2 |
| && pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ |
| ){ |
| iPlan |= 4; |
| pIdxInfo->aConstraintUsage[i].omit = 1; |
| iLangTerm = i; |
| } |
| } |
| if( iPlan & 2 ){ |
| pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 1+((iPlan&1)!=0); |
| } |
| if( iPlan & 4 ){ |
| int idx = 1; |
| if( iPlan & 1 ) idx++; |
| if( iPlan & 2 ) idx++; |
| pIdxInfo->aConstraintUsage[iLangTerm].argvIndex = idx; |
| } |
| pIdxInfo->idxNum = iPlan; |
| if( pIdxInfo->nOrderBy==1 |
| && pIdxInfo->aOrderBy[0].iColumn==1 |
| && pIdxInfo->aOrderBy[0].desc==0 |
| ){ |
| pIdxInfo->orderByConsumed = 1; |
| } |
| pIdxInfo->estimatedCost = (double)10000; |
| |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static int amatchUpdate( |
| sqlite3_vtab *pVTab, |
| int argc, |
| sqlite3_value **argv, |
| sqlite_int64 *pRowid |
| ){ |
| amatch_vtab *p = (amatch_vtab*)pVTab; |
| const unsigned char *zCmd; |
| (void)pRowid; |
| if( argc==1 ){ |
| pVTab->zErrMsg = sqlite3_mprintf("DELETE from %s is not allowed", |
| p->zSelf); |
| return SQLITE_ERROR; |
| } |
| if( sqlite3_value_type(argv[0])!=SQLITE_NULL ){ |
| pVTab->zErrMsg = sqlite3_mprintf("UPDATE of %s is not allowed", |
| p->zSelf); |
| return SQLITE_ERROR; |
| } |
| if( sqlite3_value_type(argv[2+AMATCH_COL_WORD])!=SQLITE_NULL |
| || sqlite3_value_type(argv[2+AMATCH_COL_DISTANCE])!=SQLITE_NULL |
| || sqlite3_value_type(argv[2+AMATCH_COL_LANGUAGE])!=SQLITE_NULL |
| ){ |
| pVTab->zErrMsg = sqlite3_mprintf( |
| "INSERT INTO %s allowed for column [command] only", p->zSelf); |
| return SQLITE_ERROR; |
| } |
| zCmd = sqlite3_value_text(argv[2+AMATCH_COL_COMMAND]); |
| if( zCmd==0 ) return SQLITE_OK; |
| |
| return SQLITE_OK; |
| } |
|
|
| |
| |
| |
| static sqlite3_module amatchModule = { |
| 0, |
| amatchConnect, |
| amatchConnect, |
| amatchBestIndex, |
| amatchDisconnect, |
| amatchDisconnect, |
| amatchOpen, |
| amatchClose, |
| amatchFilter, |
| amatchNext, |
| amatchEof, |
| amatchColumn, |
| amatchRowid, |
| amatchUpdate, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0 |
| }; |
|
|
| #endif |
|
|
| |
| |
| |
| #ifdef _WIN32 |
| __declspec(dllexport) |
| #endif |
| int sqlite3_amatch_init( |
| sqlite3 *db, |
| char **pzErrMsg, |
| const sqlite3_api_routines *pApi |
| ){ |
| int rc = SQLITE_OK; |
| SQLITE_EXTENSION_INIT2(pApi); |
| (void)pzErrMsg; |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| rc = sqlite3_create_module(db, "approximate_match", &amatchModule, 0); |
| #endif |
| return rc; |
| } |
|
|