📄 fts1.c
字号:
if( rc!=SQLITE_OK ) return rc; rc = sql_step_statement(v, CONTENT_SELECT_STMT, &s); if( rc!=SQLITE_ROW ) return rc; values = (const char **) malloc(v->nColumn * sizeof(const char *)); for(i=0; i<v->nColumn; ++i){ if( sqlite3_column_type(s, i)==SQLITE_NULL ){ values[i] = NULL; }else{ values[i] = string_dup((char*)sqlite3_column_text(s, i)); } } /* We expect only one row. We must execute another sqlite3_step() * to complete the iteration; otherwise the table will remain locked. */ rc = sqlite3_step(s); if( rc==SQLITE_DONE ){ *pValues = values; return SQLITE_OK; } freeStringArray(v->nColumn, values); return rc;}/* delete from %_content where rowid = [iRow ] */static int content_delete(fulltext_vtab *v, sqlite_int64 iRow){ sqlite3_stmt *s; int rc = sql_get_statement(v, CONTENT_DELETE_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iRow); if( rc!=SQLITE_OK ) return rc; return sql_single_step_statement(v, CONTENT_DELETE_STMT, &s);}/* select rowid, doclist from %_term * where term = [pTerm] and segment = [iSegment] * If found, returns SQLITE_ROW; the caller must free the * returned doclist. If no rows found, returns SQLITE_DONE. */static int term_select(fulltext_vtab *v, const char *pTerm, int nTerm, int iSegment, sqlite_int64 *rowid, DocList *out){ sqlite3_stmt *s; int rc = sql_get_statement(v, TERM_SELECT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_text(s, 1, pTerm, nTerm, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int(s, 2, iSegment); if( rc!=SQLITE_OK ) return rc; rc = sql_step_statement(v, TERM_SELECT_STMT, &s); if( rc!=SQLITE_ROW ) return rc; *rowid = sqlite3_column_int64(s, 0); docListInit(out, DL_DEFAULT, sqlite3_column_blob(s, 1), sqlite3_column_bytes(s, 1)); /* We expect only one row. We must execute another sqlite3_step() * to complete the iteration; otherwise the table will remain locked. */ rc = sqlite3_step(s); return rc==SQLITE_DONE ? SQLITE_ROW : rc;}/* Load the segment doclists for term pTerm and merge them in** appropriate order into out. Returns SQLITE_OK if successful. If** there are no segments for pTerm, successfully returns an empty** doclist in out.**** Each document consists of 1 or more "columns". The number of** columns is v->nColumn. If iColumn==v->nColumn, then return** position information about all columns. If iColumn<v->nColumn,** then only return position information about the iColumn-th column** (where the first column is 0).*/static int term_select_all( fulltext_vtab *v, /* The fulltext index we are querying against */ int iColumn, /* If <nColumn, only look at the iColumn-th column */ const char *pTerm, /* The term whose posting lists we want */ int nTerm, /* Number of bytes in pTerm */ DocList *out /* Write the resulting doclist here */){ DocList doclist; sqlite3_stmt *s; int rc = sql_get_statement(v, TERM_SELECT_ALL_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_text(s, 1, pTerm, nTerm, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; docListInit(&doclist, DL_DEFAULT, 0, 0); /* TODO(shess) Handle schema and busy errors. */ while( (rc=sql_step_statement(v, TERM_SELECT_ALL_STMT, &s))==SQLITE_ROW ){ DocList old; /* TODO(shess) If we processed doclists from oldest to newest, we ** could skip the malloc() involved with the following call. For ** now, I'd rather keep this logic similar to index_insert_term(). ** We could additionally drop elements when we see deletes, but ** that would require a distinct version of docListAccumulate(). */ docListInit(&old, DL_DEFAULT, sqlite3_column_blob(s, 0), sqlite3_column_bytes(s, 0)); if( iColumn<v->nColumn ){ /* querying a single column */ docListRestrictColumn(&old, iColumn); } /* doclist contains the newer data, so write it over old. Then ** steal accumulated result for doclist. */ docListAccumulate(&old, &doclist); docListDestroy(&doclist); doclist = old; } if( rc!=SQLITE_DONE ){ docListDestroy(&doclist); return rc; } docListDiscardEmpty(&doclist); *out = doclist; return SQLITE_OK;}/* insert into %_term (rowid, term, segment, doclist) values ([piRowid], [pTerm], [iSegment], [doclist])** Lets sqlite select rowid if piRowid is NULL, else uses *piRowid.**** NOTE(shess) piRowid is IN, with values of "space of int64" plus** null, it is not used to pass data back to the caller.*/static int term_insert(fulltext_vtab *v, sqlite_int64 *piRowid, const char *pTerm, int nTerm, int iSegment, DocList *doclist){ sqlite3_stmt *s; int rc = sql_get_statement(v, TERM_INSERT_STMT, &s); if( rc!=SQLITE_OK ) return rc; if( piRowid==NULL ){ rc = sqlite3_bind_null(s, 1); }else{ rc = sqlite3_bind_int64(s, 1, *piRowid); } if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_text(s, 2, pTerm, nTerm, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int(s, 3, iSegment); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_blob(s, 4, doclist->pData, doclist->nData, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; return sql_single_step_statement(v, TERM_INSERT_STMT, &s);}/* update %_term set doclist = [doclist] where rowid = [rowid] */static int term_update(fulltext_vtab *v, sqlite_int64 rowid, DocList *doclist){ sqlite3_stmt *s; int rc = sql_get_statement(v, TERM_UPDATE_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_blob(s, 1, doclist->pData, doclist->nData, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 2, rowid); if( rc!=SQLITE_OK ) return rc; return sql_single_step_statement(v, TERM_UPDATE_STMT, &s);}static int term_delete(fulltext_vtab *v, sqlite_int64 rowid){ sqlite3_stmt *s; int rc = sql_get_statement(v, TERM_DELETE_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, rowid); if( rc!=SQLITE_OK ) return rc; return sql_single_step_statement(v, TERM_DELETE_STMT, &s);}/*** Free the memory used to contain a fulltext_vtab structure.*/static void fulltext_vtab_destroy(fulltext_vtab *v){ int iStmt, i; TRACE(("FTS1 Destroy %p\n", v)); for( iStmt=0; iStmt<MAX_STMT; iStmt++ ){ if( v->pFulltextStatements[iStmt]!=NULL ){ sqlite3_finalize(v->pFulltextStatements[iStmt]); v->pFulltextStatements[iStmt] = NULL; } } if( v->pTokenizer!=NULL ){ v->pTokenizer->pModule->xDestroy(v->pTokenizer); v->pTokenizer = NULL; } free(v->azColumn); for(i = 0; i < v->nColumn; ++i) { sqlite3_free(v->azContentColumn[i]); } free(v->azContentColumn); free(v);}/*** Token types for parsing the arguments to xConnect or xCreate.*/#define TOKEN_EOF 0 /* End of file */#define TOKEN_SPACE 1 /* Any kind of whitespace */#define TOKEN_ID 2 /* An identifier */#define TOKEN_STRING 3 /* A string literal */#define TOKEN_PUNCT 4 /* A single punctuation character *//*** If X is a character that can be used in an identifier then** IdChar(X) will be true. Otherwise it is false.**** For ASCII, any character with the high-order bit set is** allowed in an identifier. For 7-bit characters, ** sqlite3IsIdChar[X] must be 1.**** Ticket #1066. the SQL standard does not allow '$' in the** middle of identfiers. But many SQL implementations do. ** SQLite will allow '$' in identifiers for compatibility.** But the feature is undocumented.*/static const char isIdChar[] = {/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */};#define IdChar(C) (((c=C)&0x80)!=0 || (c>0x1f && isIdChar[c-0x20]))/*** Return the length of the token that begins at z[0]. ** Store the token type in *tokenType before returning.*/static int getToken(const char *z, int *tokenType){ int i, c; switch( *z ){ case 0: { *tokenType = TOKEN_EOF; return 0; } case ' ': case '\t': case '\n': case '\f': case '\r': { for(i=1; safe_isspace(z[i]); i++){} *tokenType = TOKEN_SPACE; return i; } case '`': case '\'': case '"': { int delim = z[0]; for(i=1; (c=z[i])!=0; i++){ if( c==delim ){ if( z[i+1]==delim ){ i++; }else{ break; } } } *tokenType = TOKEN_STRING; return i + (c!=0); } case '[': { for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){} *tokenType = TOKEN_ID; return i; } default: { if( !IdChar(*z) ){ break; } for(i=1; IdChar(z[i]); i++){} *tokenType = TOKEN_ID; return i; } } *tokenType = TOKEN_PUNCT; return 1;}/*** A token extracted from a string is an instance of the following** structure.*/typedef struct Token { const char *z; /* Pointer to token text. Not '\000' terminated */ short int n; /* Length of the token text in bytes. */} Token;/*** Given a input string (which is really one of the argv[] parameters** passed into xConnect or xCreate) split the string up into tokens.** Return an array of pointers to '\000' terminated strings, one string** for each non-whitespace token.**** The returned array is terminated by a single NULL pointer.**** Space to hold the returned array is obtained from a single** malloc and should be freed by passing the return value to free().** The individual strings within the token list are all a part of** the single memory allocation and will all be freed at once.*/static char **tokenizeString(const char *z, int *pnToken){ int nToken = 0; Token *aToken = malloc( strlen(z) * sizeof(aToken[0]) ); int n = 1; int e, i; int totalSize = 0; char **azToken; char *zCopy; while( n>0 ){ n = getToken(z, &e); if( e!=TOKEN_SPACE ){ aToken[nToken].z = z; aToken[nToken].n = n; nToken++; totalSize += n+1; } z += n; } azToken = (char**)malloc( nToken*sizeof(char*) + totalSize ); zCopy = (char*)&azToken[nToken]; nToken--; for(i=0; i<nToken; i++){ azToken[i] = zCopy; n = aToken[i].n; memcpy(zCopy, aToken[i].z, n); zCopy[n] = 0; zCopy += n+1; } azToken[nToken] = 0; free(aToken); *pnToken = nToken; return azToken;}/*** Convert an SQL-style quoted string into a normal string by removing** the quote characters. The conversion is done in-place. If the** input does not begin with a quote character, then this routine** is a no-op.**** Examples:**** "abc" becomes abc** 'xyz' becomes xyz** [pqr] becomes pqr** `mno` becomes mno*/static void dequoteString(char *z){ int quote; int i, j; if( z==0 ) return; quote = z[0]; switch( quote ){ case '\'': break; case '"': break; case '`': break; /* For MySQL compatibility */ case '[': quote = ']'; break; /* For MS SqlServer compatibility */ default: return; } for(i=1, j=0; z[i]; i++){ if( z[i]==quote ){ if( z[i+1]==quote ){ z[j++] = quote; i++; }else{ z[j++] = 0; break; } }else{ z[j++] = z[i]; } }}/*** The input azIn is a NULL-terminated list of tokens. Remove the first** token and all punctuation tokens. Remove the quotes from** around string literal tokens.**** Example:**** input: tokenize chinese ( 'simplifed' , 'mixed' )** output: chinese simplifed mixed**** Another example:**** input: delimiters ( '[' , ']' , '...' )** output: [ ] ...*/static void tokenListToIdList(char **azIn){ int i, j; if( azIn ){ for(i=0, j=-1; azIn[i]; i++){ if( safe_isalnum(azIn[i][0]) || azIn[i][1] ){ dequoteString(azIn[i]); if( j>=0 ){ azIn[j] = azIn[i]; } j++; } } azIn[j] = 0; }}/*** Find the first alphanumeric token in the string zIn. Null-terminate** this token. Remove any quotation marks. And return a pointer to** the result.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -