| | @@ -449,10 +449,12 @@ |
| 449 | 449 | ** Register the "score()" SQL function to score its input text |
| 450 | 450 | ** using the given Search object. Once this function is registered, |
| 451 | 451 | ** do not delete the Search object. |
| 452 | 452 | */ |
| 453 | 453 | void search_sql_setup(sqlite3 *db){ |
| 454 | + static int once = 0; |
| 455 | + if( once++ ) return; |
| 454 | 456 | sqlite3_create_function(db, "score", -1, SQLITE_UTF8, 0, |
| 455 | 457 | search_score_sqlfunc, 0, 0); |
| 456 | 458 | sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, |
| 457 | 459 | search_score_sqlfunc, 0, 0); |
| 458 | 460 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| | @@ -789,18 +791,18 @@ |
| 789 | 791 | ** zName Name of the object being searched. |
| 790 | 792 | */ |
| 791 | 793 | void search_stext( |
| 792 | 794 | char cType, /* Type of document */ |
| 793 | 795 | int rid, /* BLOB.RID or TAG.TAGID value for document */ |
| 794 | | - const char *zName, /* Name of the document */ |
| 796 | + const char *zName, /* Auxiliary information */ |
| 795 | 797 | Blob *pOut /* OUT: Initialize to the search text */ |
| 796 | 798 | ){ |
| 797 | 799 | blob_init(pOut, 0, 0); |
| 798 | 800 | switch( cType ){ |
| 799 | 801 | case 'd': { /* Documents */ |
| 800 | 802 | Blob doc; |
| 801 | | - content_get(rid, &doc); |
| 803 | + content_get(rid, &doc); |
| 802 | 804 | blob_to_utf8_no_bom(&doc, 0); |
| 803 | 805 | get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut); |
| 804 | 806 | blob_reset(&doc); |
| 805 | 807 | break; |
| 806 | 808 | } |
| | @@ -918,5 +920,97 @@ |
| 918 | 920 | search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); |
| 919 | 921 | zUrl = search_url(g.argv[2][0], atoi(g.argv[3]), g.argv[4]); |
| 920 | 922 | fossil_print("%s\n%z\n",blob_str(&out),zUrl); |
| 921 | 923 | blob_reset(&out); |
| 922 | 924 | } |
| 925 | + |
| 926 | +/* The schema for the full-text index |
| 927 | +*/ |
| 928 | +static const char zFtsSchema[] = |
| 929 | +@ -- One entry for each possible search result |
| 930 | +@ CREATE TABLE IF NOT EXISTS "%w".ftsdocs( |
| 931 | +@ id INTEGER PRIMARY KEY, -- Maps to the ftsidx.rowid |
| 932 | +@ type CHAR(1), -- Type of document |
| 933 | +@ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document |
| 934 | +@ name TEXT, -- Additional document description |
| 935 | +@ idxed BOOLEAN, -- True if currently in the index |
| 936 | +@ UNIQUE(type,rid) |
| 937 | +@ ); |
| 938 | +@ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; |
| 939 | +@ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS |
| 940 | +@ SELECT id, type, rid, name, |
| 941 | +@ stext(type,rid,name) AS stext |
| 942 | +@ FROM ftsdocs; |
| 943 | +@ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx |
| 944 | +@ USING fts4(content="ftscontent", stext); |
| 945 | +; |
| 946 | +static const char zFtsDrop[] = |
| 947 | +@ DROP TABLE IF EXISTS "%w".ftsidx; |
| 948 | +@ DROP VIEW IF EXISTS "%w".ftscontent; |
| 949 | +@ DROP TABLE IF EXISTS "%w".ftsdocs; |
| 950 | +; |
| 951 | + |
| 952 | +/* |
| 953 | +** Create or drop the tables associated with a full-text index. |
| 954 | +*/ |
| 955 | +void search_create_index(void){ |
| 956 | + const char *zDb = db_name("repository"); |
| 957 | + search_sql_setup(g.db); |
| 958 | + db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w"*/, zDb, zDb, zDb, zDb); |
| 959 | +} |
| 960 | +void search_drop_index(void){ |
| 961 | + const char *zDb = db_name("repository"); |
| 962 | + db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb); |
| 963 | +} |
| 964 | + |
| 965 | +/* |
| 966 | +** Return true if the full-text search index exists |
| 967 | +*/ |
| 968 | +int search_index_exists(void){ |
| 969 | + static int fExists = -1; |
| 970 | + if( fExists<0 ) fExists = db_table_exists("repository","ftsdocs"); |
| 971 | + return fExists; |
| 972 | +} |
| 973 | + |
| 974 | +/* |
| 975 | +** The document described by cType,rid,zName is about to be added or |
| 976 | +** updated. If the document has already been indexed, then unindex it |
| 977 | +** now while we still have access to the old content. Add the document |
| 978 | +** to the queue of documents that need to be indexed or reindexed. |
| 979 | +*/ |
| 980 | +void search_doc_touch(char cType, int rid, const char *zName){ |
| 981 | + if( search_index_exists() ){ |
| 982 | + db_multi_exec( |
| 983 | + "DELETE FROM ftsidx WHERE rowid IN" |
| 984 | + " (SELECT id FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", |
| 985 | + cType, rid |
| 986 | + ); |
| 987 | + db_multi_exec( |
| 988 | + "REPLACE INTO ftsdocs(type,rid,name,idxed)" |
| 989 | + " VALUES(%Q,%d,%Q,0)", |
| 990 | + cType, rid, zName |
| 991 | + ); |
| 992 | + } |
| 993 | +} |
| 994 | + |
| 995 | +/* |
| 996 | +** COMMAND: test-fts |
| 997 | +*/ |
| 998 | +void test_fts_cmd(void){ |
| 999 | + char *zSubCmd; |
| 1000 | + int n; |
| 1001 | + db_find_and_open_repository(0, 0); |
| 1002 | + if( g.argc<3 ) usage("SUBCMD ..."); |
| 1003 | + zSubCmd = g.argv[2]; |
| 1004 | + n = (int)strlen(zSubCmd); |
| 1005 | + db_begin_transaction(); |
| 1006 | + if( fossil_strncmp(zSubCmd, "create", n)==0 ){ |
| 1007 | + search_create_index(); |
| 1008 | + }else if( fossil_strncmp(zSubCmd, "drop",n)==0 ){ |
| 1009 | + search_drop_index(); |
| 1010 | + }else if( fossil_strncmp(zSubCmd, "exists",n)==0 ){ |
| 1011 | + fossil_print("search_index_exists() = %d\n", search_index_exists()); |
| 1012 | + }else{ |
| 1013 | + fossil_fatal("unknown subcommand \"%s\"", zSubCmd); |
| 1014 | + } |
| 1015 | + db_end_transaction(0); |
| 1016 | +} |
| 923 | 1017 | |