#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#endif
#if 0#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;
int 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_realloc(pCur->zBuf, pCur->nBuf);
if( pCur->zBuf==0 ){
pCur->nBuf = 0;
return;
}
}
amatchEncodeInt(nMatch, zBuf);
memcpy(pCur->zBuf, zBuf+2, 2);
memcpy(pCur->zBuf+2, zWordBase, nBase);
memcpy(pCur->zBuf+2+nBase, zWordTail, nTail+1);
pNode = amatchAvlSearch(pCur->pWord, pCur->zBuf);
if( pNode ){
pWord = pNode->pWord;
if( pWord->rCost>rCost ){
#ifdef AMATCH_TRACE_1
printf("UPDATE [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput,
pWord->rCost, pWord->zWord, pWord->zCost);
#endif
amatchAvlRemove(&pCur->pCost, &pWord->sCost);
pWord->rCost = rCost;
amatchWriteCost(pWord);
#ifdef AMATCH_TRACE_1
printf(" ---> %d (\"%s\" \"%s\")\n",
pWord->rCost, pWord->zWord, pWord->zCost);
#endif
pOther = amatchAvlInsert(&pCur->pCost, &pWord->sCost);
assert( pOther==0 ); (void)pOther;
}
return;
}
pWord = sqlite3_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;
int nWord;
int rc;
int i;
const char *zW;
amatch_rule *pRule;
char *zBuf = 0;
char nBuf = 0;
char zNext[8];
char zNextIn[8];
int nNextIn;
if( p->pVCheck==0 ){
char *zSql;
if( p->zVocabLang && p->zVocabLang[0] ){
zSql = sqlite3_mprintf(
"SELECT \"%w\" FROM \"%w\"",
" WHERE \"%w\">=?1 AND \"%w\"=?2"
" ORDER BY 1",
p->zVocabWord, p->zVocabTab,
p->zVocabWord, p->zVocabLang
);
}else{
zSql = sqlite3_mprintf(
"SELECT \"%w\" FROM \"%w\""
" WHERE \"%w\">=?1"
" ORDER BY 1",
p->zVocabWord, p->zVocabTab,
p->zVocabWord
);
}
rc = sqlite3_prepare_v2(p->db, zSql, -1, &p->pVCheck, 0);
sqlite3_free(zSql);
if( rc ) return rc;
}
sqlite3_bind_int(p->pVCheck, 2, pCur->iLang);
do{
pNode = amatchAvlFirst(pCur->pCost);
if( pNode==0 ){
pWord = 0;
break;
}
pWord = pNode->pWord;
amatchAvlRemove(&pCur->pCost, &pWord->sCost);
#ifdef AMATCH_TRACE_1
printf("PROCESS [%s][%.*s^%s] %d (\"%s\" \"%s\")\n",
pWord->zWord+2, pWord->nMatch, pCur->zInput, pCur->zInput+pWord->nMatch,
pWord->rCost, pWord->zWord, pWord->zCost);
#endif
nWord = (int)strlen(pWord->zWord+2);
if( nWord+20>nBuf ){
nBuf = (char)(nWord+100);
zBuf = sqlite3_realloc(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
};
#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;
}