Fossil SCM
Incremental check-in for work on the indexed full-text search.
Commit
ec0e590191b10f23c8b3b975b1f1ca3d6149d02e
Parent
32d904e9cff95cf…
2 files changed
+7
-6
+193
-43
M
src/db.c
+7
-6
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1380,15 +1380,10 @@ | ||
| 1380 | 1380 | while( db.pAllStmt ){ |
| 1381 | 1381 | db_finalize(db.pAllStmt); |
| 1382 | 1382 | } |
| 1383 | 1383 | db_end_transaction(1); |
| 1384 | 1384 | pStmt = 0; |
| 1385 | - if( reportErrors ){ | |
| 1386 | - while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ | |
| 1387 | - fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); | |
| 1388 | - } | |
| 1389 | - } | |
| 1390 | 1385 | db_close_config(); |
| 1391 | 1386 | |
| 1392 | 1387 | /* If the localdb (the check-out database) is open and if it has |
| 1393 | 1388 | ** a lot of unused free space, then VACUUM it as we shut down. |
| 1394 | 1389 | */ |
| @@ -1399,12 +1394,18 @@ | ||
| 1399 | 1394 | db_multi_exec("VACUUM;"); |
| 1400 | 1395 | } |
| 1401 | 1396 | } |
| 1402 | 1397 | |
| 1403 | 1398 | if( g.db ){ |
| 1399 | + int rc; | |
| 1404 | 1400 | sqlite3_wal_checkpoint(g.db, 0); |
| 1405 | - sqlite3_close(g.db); | |
| 1401 | + rc = sqlite3_close(g.db); | |
| 1402 | + if( rc==SQLITE_BUSY && reportErrors ){ | |
| 1403 | + while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ | |
| 1404 | + fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); | |
| 1405 | + } | |
| 1406 | + } | |
| 1406 | 1407 | g.db = 0; |
| 1407 | 1408 | g.zMainDbType = 0; |
| 1408 | 1409 | } |
| 1409 | 1410 | g.repositoryOpen = 0; |
| 1410 | 1411 | g.localOpen = 0; |
| 1411 | 1412 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1380,15 +1380,10 @@ | |
| 1380 | while( db.pAllStmt ){ |
| 1381 | db_finalize(db.pAllStmt); |
| 1382 | } |
| 1383 | db_end_transaction(1); |
| 1384 | pStmt = 0; |
| 1385 | if( reportErrors ){ |
| 1386 | while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ |
| 1387 | fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); |
| 1388 | } |
| 1389 | } |
| 1390 | db_close_config(); |
| 1391 | |
| 1392 | /* If the localdb (the check-out database) is open and if it has |
| 1393 | ** a lot of unused free space, then VACUUM it as we shut down. |
| 1394 | */ |
| @@ -1399,12 +1394,18 @@ | |
| 1399 | db_multi_exec("VACUUM;"); |
| 1400 | } |
| 1401 | } |
| 1402 | |
| 1403 | if( g.db ){ |
| 1404 | sqlite3_wal_checkpoint(g.db, 0); |
| 1405 | sqlite3_close(g.db); |
| 1406 | g.db = 0; |
| 1407 | g.zMainDbType = 0; |
| 1408 | } |
| 1409 | g.repositoryOpen = 0; |
| 1410 | g.localOpen = 0; |
| 1411 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1380,15 +1380,10 @@ | |
| 1380 | while( db.pAllStmt ){ |
| 1381 | db_finalize(db.pAllStmt); |
| 1382 | } |
| 1383 | db_end_transaction(1); |
| 1384 | pStmt = 0; |
| 1385 | db_close_config(); |
| 1386 | |
| 1387 | /* If the localdb (the check-out database) is open and if it has |
| 1388 | ** a lot of unused free space, then VACUUM it as we shut down. |
| 1389 | */ |
| @@ -1399,12 +1394,18 @@ | |
| 1394 | db_multi_exec("VACUUM;"); |
| 1395 | } |
| 1396 | } |
| 1397 | |
| 1398 | if( g.db ){ |
| 1399 | int rc; |
| 1400 | sqlite3_wal_checkpoint(g.db, 0); |
| 1401 | rc = sqlite3_close(g.db); |
| 1402 | if( rc==SQLITE_BUSY && reportErrors ){ |
| 1403 | while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ |
| 1404 | fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); |
| 1405 | } |
| 1406 | } |
| 1407 | g.db = 0; |
| 1408 | g.zMainDbType = 0; |
| 1409 | } |
| 1410 | g.repositoryOpen = 0; |
| 1411 | g.localOpen = 0; |
| 1412 |
+193
-43
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -453,11 +453,11 @@ | ||
| 453 | 453 | void search_sql_setup(sqlite3 *db){ |
| 454 | 454 | static int once = 0; |
| 455 | 455 | if( once++ ) return; |
| 456 | 456 | sqlite3_create_function(db, "score", -1, SQLITE_UTF8, 0, |
| 457 | 457 | search_score_sqlfunc, 0, 0); |
| 458 | - sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, | |
| 458 | + sqlite3_create_function(db, "fsnippet", -1, SQLITE_UTF8, &gSearch, | |
| 459 | 459 | search_score_sqlfunc, 0, 0); |
| 460 | 460 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 461 | 461 | search_init_sqlfunc, 0, 0); |
| 462 | 462 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 463 | 463 | search_stext_sqlfunc, 0, 0); |
| @@ -553,64 +553,59 @@ | ||
| 553 | 553 | /* |
| 554 | 554 | ** Remove bits from srchFlags which are disallowed by either the |
| 555 | 555 | ** current server configuration or by user permissions. |
| 556 | 556 | */ |
| 557 | 557 | unsigned int search_restrict(unsigned int srchFlags){ |
| 558 | - if( (srchFlags & SRCH_CKIN)!=0 | |
| 559 | - && (g.perm.Read==0 || db_get_boolean("search-ci",0)==0) ){ | |
| 558 | + if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); | |
| 559 | + if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); | |
| 560 | + if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); | |
| 561 | + if( search_index_exists() ) return srchFlags; | |
| 562 | + if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){ | |
| 560 | 563 | srchFlags &= ~SRCH_CKIN; |
| 561 | 564 | } |
| 562 | - if( (srchFlags & SRCH_DOC)!=0 | |
| 563 | - && (g.perm.Read==0 || db_get_boolean("search-doc",0)==0) ){ | |
| 565 | + if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){ | |
| 564 | 566 | srchFlags &= ~SRCH_DOC; |
| 565 | 567 | } |
| 566 | - if( (srchFlags & SRCH_TKT)!=0 | |
| 567 | - && (g.perm.RdTkt==0 || db_get_boolean("search-tkt",0)==0) ){ | |
| 568 | + if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){ | |
| 568 | 569 | srchFlags &= ~SRCH_TKT; |
| 569 | 570 | } |
| 570 | - if( (srchFlags & SRCH_WIKI)!=0 | |
| 571 | - && (g.perm.RdWiki==0 || db_get_boolean("search-wiki",0)==0) ){ | |
| 571 | + if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){ | |
| 572 | 572 | srchFlags &= ~SRCH_WIKI; |
| 573 | 573 | } |
| 574 | 574 | return srchFlags; |
| 575 | 575 | } |
| 576 | 576 | |
| 577 | 577 | /* |
| 578 | -** This routine generates web-page output for a search operation. | |
| 579 | -** Other web-pages can invoke this routine to add search results | |
| 580 | -** in the middle of the page. | |
| 578 | +** When this routine is called, there already exists a table | |
| 579 | +** | |
| 580 | +** x(label,url,score,date,snip). | |
| 581 | +** | |
| 582 | +** And the srchFlags parameter has been validated. This routine | |
| 583 | +** fills the X table with search results using a full-text scan. | |
| 581 | 584 | ** |
| 582 | -** Return the number of rows. | |
| 585 | +** The companion indexed scan routine is search_indexed(). | |
| 583 | 586 | */ |
| 584 | -int search_run_and_output( | |
| 587 | +static void search_fullscan( | |
| 585 | 588 | const char *zPattern, /* The query pattern */ |
| 586 | 589 | unsigned int srchFlags /* What to search over */ |
| 587 | 590 | ){ |
| 588 | - Stmt q; | |
| 589 | - int nRow = 0; | |
| 590 | - | |
| 591 | - srchFlags = search_restrict(srchFlags); | |
| 592 | - if( srchFlags==0 ) return 0; | |
| 593 | - search_sql_setup(g.db); | |
| 594 | - add_content_sql_commands(g.db); | |
| 595 | 591 | search_init(zPattern, "<b>", "</b>", " ... ", |
| 596 | 592 | SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 597 | - db_multi_exec( | |
| 598 | - "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" | |
| 599 | - "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);" | |
| 600 | - ); | |
| 601 | 593 | if( (srchFlags & SRCH_DOC)!=0 ){ |
| 602 | 594 | char *zDocGlob = db_get("doc-glob",""); |
| 603 | 595 | char *zDocBr = db_get("doc-branch","trunk"); |
| 604 | 596 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 597 | + db_multi_exec( | |
| 598 | + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" | |
| 599 | + ); | |
| 605 | 600 | db_multi_exec( |
| 606 | 601 | "INSERT INTO x(label,url,date,snip)" |
| 607 | 602 | " SELECT printf('Document: %%s',foci.filename)," |
| 608 | 603 | " printf('%R/doc/%T/%%s',foci.filename)," |
| 609 | 604 | " (SELECT datetime(event.mtime) FROM event" |
| 610 | 605 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 611 | - " snippet(stext('d',blob.rid,foci.filename))" | |
| 606 | + " fsnippet(stext('d',blob.rid,foci.filename))" | |
| 612 | 607 | " FROM foci CROSS JOIN blob" |
| 613 | 608 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 614 | 609 | " AND blob.uuid=foci.uuid" |
| 615 | 610 | " AND %z", |
| 616 | 611 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| @@ -628,11 +623,11 @@ | ||
| 628 | 623 | ")" |
| 629 | 624 | "INSERT INTO x(label,url,date,snip)" |
| 630 | 625 | " SELECT printf('Wiki: %%s',name)," |
| 631 | 626 | " printf('%R/wiki?name=%%s',urlencode(name))," |
| 632 | 627 | " datetime(mtime)," |
| 633 | - " snippet(stext('w',rid,name))" | |
| 628 | + " fsnippet(stext('w',rid,name))" | |
| 634 | 629 | " FROM wiki;" |
| 635 | 630 | ); |
| 636 | 631 | } |
| 637 | 632 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 638 | 633 | db_multi_exec( |
| @@ -644,11 +639,11 @@ | ||
| 644 | 639 | ")" |
| 645 | 640 | "INSERT INTO x(label,url,date,snip)" |
| 646 | 641 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 647 | 642 | " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," |
| 648 | 643 | " datetime(mtime)," |
| 649 | - " snippet(stext('c',rid,NULL))" | |
| 644 | + " fsnippet(stext('c',rid,NULL))" | |
| 650 | 645 | " FROM ckin;" |
| 651 | 646 | ); |
| 652 | 647 | } |
| 653 | 648 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 654 | 649 | db_multi_exec( |
| @@ -655,17 +650,77 @@ | ||
| 655 | 650 | "INSERT INTO x(label,url,date,snip)" |
| 656 | 651 | " SELECT printf('Ticket [%%.17s] on %%s'," |
| 657 | 652 | "tkt_uuid,datetime(tkt_mtime))," |
| 658 | 653 | " printf('%R/tktview/%%.20s',tkt_uuid)," |
| 659 | 654 | " datetime(tkt_mtime)," |
| 660 | - " snippet(stext('t',tkt_id,NULL))" | |
| 655 | + " fsnippet(stext('t',tkt_id,NULL))" | |
| 661 | 656 | " FROM ticket;" |
| 662 | 657 | ); |
| 663 | 658 | } |
| 664 | - db_prepare(&q, "SELECT url, substr(snip,9), label" | |
| 665 | - " FROM x WHERE snip IS NOT NULL" | |
| 666 | - " ORDER BY substr(snip,1,8) DESC, date DESC;"); | |
| 659 | + db_multi_exec( | |
| 660 | + "UPDATE x SET score=substr(snip,1,8), snip=substr(snip,9)" | |
| 661 | + ); | |
| 662 | +} | |
| 663 | + | |
| 664 | +/* | |
| 665 | +** When this routine is called, there already exists a table | |
| 666 | +** | |
| 667 | +** x(label,url,score,date,snip). | |
| 668 | +** | |
| 669 | +** And the srchFlags parameter has been validated. This routine | |
| 670 | +** fills the X table with search results using a index scan. | |
| 671 | +** | |
| 672 | +** The companion full-text scan routine is search_fullscan(). | |
| 673 | +*/ | |
| 674 | +static void search_indexed( | |
| 675 | + const char *zPattern, /* The query pattern */ | |
| 676 | + unsigned int srchFlags /* What to search over */ | |
| 677 | +){ | |
| 678 | + db_multi_exec( | |
| 679 | + "INSERT INTO x(label,url,score,date,snip) " | |
| 680 | + " SELECT ftsdocs.label," | |
| 681 | + " ftsdocs.url," | |
| 682 | + " 1," /*FIX ME*/ | |
| 683 | + " datetime(ftsdocs.mtime)," | |
| 684 | + " fsnippet(ftsidx,'<b>','</b>',' ... ')" | |
| 685 | + " FROM ftsidx, ftsdocs" | |
| 686 | + " WHERE ftsidx MATCH %Q" | |
| 687 | + " AND ftsdocs.id=ftsidx.docid", | |
| 688 | + zPattern | |
| 689 | + ); | |
| 690 | +} | |
| 691 | + | |
| 692 | + | |
| 693 | +/* | |
| 694 | +** This routine generates web-page output for a search operation. | |
| 695 | +** Other web-pages can invoke this routine to add search results | |
| 696 | +** in the middle of the page. | |
| 697 | +** | |
| 698 | +** Return the number of rows. | |
| 699 | +*/ | |
| 700 | +int search_run_and_output( | |
| 701 | + const char *zPattern, /* The query pattern */ | |
| 702 | + unsigned int srchFlags /* What to search over */ | |
| 703 | +){ | |
| 704 | + Stmt q; | |
| 705 | + int nRow = 0; | |
| 706 | + | |
| 707 | + srchFlags = search_restrict(srchFlags); | |
| 708 | + if( srchFlags==0 ) return 0; | |
| 709 | + search_sql_setup(g.db); | |
| 710 | + add_content_sql_commands(g.db); | |
| 711 | + db_multi_exec( | |
| 712 | + "CREATE TEMP TABLE x(label,url,score,date,snip);" | |
| 713 | + ); | |
| 714 | + if( !search_index_exists() ){ | |
| 715 | + search_fullscan(zPattern, srchFlags); | |
| 716 | + }else{ | |
| 717 | + search_indexed(zPattern, srchFlags); | |
| 718 | + } | |
| 719 | + db_prepare(&q, "SELECT url, snip, label" | |
| 720 | + " FROM x" | |
| 721 | + " ORDER BY score DESC, date DESC;"); | |
| 667 | 722 | while( db_step(&q)==SQLITE_ROW ){ |
| 668 | 723 | const char *zUrl = db_column_text(&q, 0); |
| 669 | 724 | const char *zSnippet = db_column_text(&q, 1); |
| 670 | 725 | const char *zLabel = db_column_text(&q, 2); |
| 671 | 726 | if( nRow==0 ){ |
| @@ -926,21 +981,24 @@ | ||
| 926 | 981 | /* The schema for the full-text index |
| 927 | 982 | */ |
| 928 | 983 | static const char zFtsSchema[] = |
| 929 | 984 | @ -- One entry for each possible search result |
| 930 | 985 | @ CREATE TABLE IF NOT EXISTS "%w".ftsdocs( |
| 931 | -@ id INTEGER PRIMARY KEY, -- Maps to the ftsidx.rowid | |
| 986 | +@ id INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid | |
| 932 | 987 | @ type CHAR(1), -- Type of document |
| 933 | 988 | @ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document |
| 934 | 989 | @ name TEXT, -- Additional document description |
| 935 | 990 | @ idxed BOOLEAN, -- True if currently in the index |
| 991 | +@ label TEXT, -- Label to print on search results | |
| 992 | +@ url TEXT, -- URL to access this document | |
| 993 | +@ mtime DATE, -- Date when document created | |
| 936 | 994 | @ UNIQUE(type,rid) |
| 937 | 995 | @ ); |
| 938 | 996 | @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; |
| 939 | 997 | @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS |
| 940 | -@ SELECT id, type, rid, name, | |
| 941 | -@ stext(type,rid,name) AS stext | |
| 998 | +@ SELECT id, type, rid, name, idxed, label, url, mtime,fr 5 | |
| 999 | +@ stext(type,rid,name) AS 'stext' | |
| 942 | 1000 | @ FROM ftsdocs; |
| 943 | 1001 | @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx |
| 944 | 1002 | @ USING fts4(content="ftscontent", stext); |
| 945 | 1003 | ; |
| 946 | 1004 | static const char zFtsDrop[] = |
| @@ -1007,20 +1065,87 @@ | ||
| 1007 | 1065 | ** to the queue of documents that need to be indexed or reindexed. |
| 1008 | 1066 | */ |
| 1009 | 1067 | void search_doc_touch(char cType, int rid, const char *zName){ |
| 1010 | 1068 | if( search_index_exists() ){ |
| 1011 | 1069 | db_multi_exec( |
| 1012 | - "DELETE FROM ftsidx WHERE rowid IN" | |
| 1070 | + "DELETE FROM ftsidx WHERE docid IN" | |
| 1013 | 1071 | " (SELECT id FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", |
| 1014 | 1072 | cType, rid |
| 1015 | 1073 | ); |
| 1016 | 1074 | db_multi_exec( |
| 1017 | 1075 | "REPLACE INTO ftsdocs(type,rid,name,idxed)" |
| 1018 | 1076 | " VALUES(%Q,%d,%Q,0)", |
| 1019 | 1077 | cType, rid, zName |
| 1020 | 1078 | ); |
| 1021 | 1079 | } |
| 1080 | +} | |
| 1081 | + | |
| 1082 | +/* | |
| 1083 | +** If the doc-glob and doc-br settings are valid for document search | |
| 1084 | +** and if the latest check-in on doc-br is in the unindexed set of | |
| 1085 | +** check-ins, then update all 'd' entries in FTSDOCS that have | |
| 1086 | +** changed. | |
| 1087 | +*/ | |
| 1088 | +static void search_update_doc_index(void){ | |
| 1089 | + const char *zDocBr = db_get("doc-branch","trunk"); | |
| 1090 | + int ckid = zDocBr ? symbolic_name_to_rid(zDocBr,"ci") : 0; | |
| 1091 | + double rTime; | |
| 1092 | + char *zBrUuid; | |
| 1093 | + if( ckid==0 ) return; | |
| 1094 | + if( !db_exists("SELECT 1 FROM ftsdocs WHERE type='c' AND rid=%d" | |
| 1095 | + " AND NOT idxed", ckid) ) return; | |
| 1096 | + | |
| 1097 | + /* If we get this far, it means that changes to 'd' entries are | |
| 1098 | + ** required. */ | |
| 1099 | + rTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", ckid); | |
| 1100 | + zBrUuid = db_text("","SELECT substr(uuid,1,20) FROM blob WHERE rid=%d",ckid); | |
| 1101 | + db_multi_exec( | |
| 1102 | + "CREATE TEMP TABLE current_docs(rid INTEGER PRIMARY KEY, name);" | |
| 1103 | + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" | |
| 1104 | + "INSERT OR IGNORE INTO current_docs(rid, name)" | |
| 1105 | + " SELECT blob.rid, foci.filename FROM foci, blob" | |
| 1106 | + " WHERE foci.checkinID=%d AND blob.uuid=foci.uuid" | |
| 1107 | + " AND %z", | |
| 1108 | + ckid, glob_expr("foci.filename", db_get("doc-glob","")) | |
| 1109 | + ); | |
| 1110 | + db_multi_exec( | |
| 1111 | + "DELETE FROM ftsidx WHERE docid IN" | |
| 1112 | + " (SELECT id FROM ftsdocs WHERE type='d'" | |
| 1113 | + " AND rid NOT IN (SELECT rid FROM current_docs))" | |
| 1114 | + ); | |
| 1115 | + db_multi_exec( | |
| 1116 | + "DELETE FROM ftsdocs WHERE type='d'" | |
| 1117 | + " AND rid NOT IN (SELECT rid FROM current_docs)" | |
| 1118 | + ); | |
| 1119 | + db_multi_exec( | |
| 1120 | + "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)" | |
| 1121 | + " SELECT 'd', rid, name, 0," | |
| 1122 | + " printf('Document: %%s',name)," | |
| 1123 | + " printf('/doc/%q/%%s',urlencode(name))," | |
| 1124 | + " %.17g" | |
| 1125 | + " FROM current_docs", | |
| 1126 | + zBrUuid, rTime | |
| 1127 | + ); | |
| 1128 | + db_multi_exec( | |
| 1129 | + "INSERT INTO ftsidx(docid,stext)" | |
| 1130 | + " SELECT id, stext FROM ftscontent WHERE type='d' AND NOT idxed" | |
| 1131 | + ); | |
| 1132 | + db_multi_exec( | |
| 1133 | + "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed" | |
| 1134 | + ); | |
| 1135 | +} | |
| 1136 | + | |
| 1137 | +/* | |
| 1138 | +** Deal with all of the unindexed entries in the FTSDOCS table - that | |
| 1139 | +** is to say, all the entries with FTSDOCS.IDXED=0. Add them to the | |
| 1140 | +** index. | |
| 1141 | +*/ | |
| 1142 | +void search_update_index(void){ | |
| 1143 | + if( !search_index_exists() ) return; | |
| 1144 | + search_sql_setup(g.db); | |
| 1145 | + search_update_doc_index(); | |
| 1146 | + | |
| 1022 | 1147 | } |
| 1023 | 1148 | |
| 1024 | 1149 | /* |
| 1025 | 1150 | ** COMMAND: test-fts |
| 1026 | 1151 | */ |
| @@ -1030,12 +1155,14 @@ | ||
| 1030 | 1155 | static const struct { int iCmd; const char *z; } aCmd[] = { |
| 1031 | 1156 | { 1, "create" }, |
| 1032 | 1157 | { 2, "drop" }, |
| 1033 | 1158 | { 3, "exists" }, |
| 1034 | 1159 | { 4, "fill" }, |
| 1160 | + { 8, "refill" }, | |
| 1035 | 1161 | { 5, "pending" }, |
| 1036 | 1162 | { 6, "all" }, |
| 1163 | + { 7, "update" }, | |
| 1037 | 1164 | }; |
| 1038 | 1165 | db_find_and_open_repository(0, 0); |
| 1039 | 1166 | if( g.argc<3 ) usage("SUBCMD ..."); |
| 1040 | 1167 | zSubCmd = g.argv[2]; |
| 1041 | 1168 | n = (int)strlen(zSubCmd); |
| @@ -1064,23 +1191,41 @@ | ||
| 1064 | 1191 | break; |
| 1065 | 1192 | } |
| 1066 | 1193 | case 4: { assert( fossil_strncmp(zSubCmd, "fill", n)==0 ); |
| 1067 | 1194 | search_fill_index(); |
| 1068 | 1195 | break; |
| 1196 | + } | |
| 1197 | + case 8: { assert( fossil_strncmp(zSubCmd, "refill", n)==0 ); | |
| 1198 | + search_drop_index(); | |
| 1199 | + search_create_index(); | |
| 1200 | + search_fill_index(); | |
| 1201 | + break; | |
| 1069 | 1202 | } |
| 1070 | 1203 | case 5: { assert( fossil_strncmp(zSubCmd, "pending", n)==0 ); |
| 1071 | 1204 | Stmt q; |
| 1072 | 1205 | if( !search_index_exists() ) break; |
| 1073 | - db_prepare(&q, "SELECT id, type, rid, quote(name) FROM ftsdocs" | |
| 1206 | + db_prepare(&q, "SELECT id, type, rid, quote(label), url, date(mtime)" | |
| 1207 | + " FROM ftsdocs" | |
| 1074 | 1208 | " WHERE NOT idxed"); |
| 1075 | 1209 | while( db_step(&q)==SQLITE_ROW ){ |
| 1076 | - fossil_print("%6d: %s %6d %s\n", | |
| 1077 | - db_column_int(&q, 0), | |
| 1078 | - db_column_text(&q, 1), | |
| 1079 | - db_column_int(&q, 2), | |
| 1080 | - db_column_text(&q, 3) | |
| 1081 | - ); | |
| 1210 | + const char *zUrl = db_column_text(&q,4); | |
| 1211 | + if( zUrl && zUrl[0] ){ | |
| 1212 | + fossil_print("%6d: %s %6d %s %s\n %s\n", | |
| 1213 | + db_column_int(&q, 0), | |
| 1214 | + db_column_text(&q, 1), | |
| 1215 | + db_column_int(&q, 2), | |
| 1216 | + db_column_text(&q, 5), | |
| 1217 | + db_column_text(&q, 3), | |
| 1218 | + zUrl); | |
| 1219 | + }else{ | |
| 1220 | + fossil_print("%6d: %s %6d %s %s\n", | |
| 1221 | + db_column_int(&q, 0), | |
| 1222 | + db_column_text(&q, 1), | |
| 1223 | + db_column_int(&q, 2), | |
| 1224 | + db_column_text(&q, 5), | |
| 1225 | + db_column_text(&q, 3)); | |
| 1226 | + } | |
| 1082 | 1227 | } |
| 1083 | 1228 | db_finalize(&q); |
| 1084 | 1229 | break; |
| 1085 | 1230 | } |
| 1086 | 1231 | case 6: { assert( fossil_strncmp(zSubCmd, "all", n)==0 ); |
| @@ -1097,8 +1242,13 @@ | ||
| 1097 | 1242 | ); |
| 1098 | 1243 | } |
| 1099 | 1244 | db_finalize(&q); |
| 1100 | 1245 | break; |
| 1101 | 1246 | } |
| 1247 | + case 7: { assert( fossil_strncmp(zSubCmd, "update", n)==0 ); | |
| 1248 | + search_update_index(); | |
| 1249 | + break; | |
| 1250 | + } | |
| 1251 | + | |
| 1102 | 1252 | } |
| 1103 | 1253 | db_end_transaction(0); |
| 1104 | 1254 | } |
| 1105 | 1255 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -453,11 +453,11 @@ | |
| 453 | void search_sql_setup(sqlite3 *db){ |
| 454 | static int once = 0; |
| 455 | if( once++ ) return; |
| 456 | sqlite3_create_function(db, "score", -1, SQLITE_UTF8, 0, |
| 457 | search_score_sqlfunc, 0, 0); |
| 458 | sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, |
| 459 | search_score_sqlfunc, 0, 0); |
| 460 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 461 | search_init_sqlfunc, 0, 0); |
| 462 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 463 | search_stext_sqlfunc, 0, 0); |
| @@ -553,64 +553,59 @@ | |
| 553 | /* |
| 554 | ** Remove bits from srchFlags which are disallowed by either the |
| 555 | ** current server configuration or by user permissions. |
| 556 | */ |
| 557 | unsigned int search_restrict(unsigned int srchFlags){ |
| 558 | if( (srchFlags & SRCH_CKIN)!=0 |
| 559 | && (g.perm.Read==0 || db_get_boolean("search-ci",0)==0) ){ |
| 560 | srchFlags &= ~SRCH_CKIN; |
| 561 | } |
| 562 | if( (srchFlags & SRCH_DOC)!=0 |
| 563 | && (g.perm.Read==0 || db_get_boolean("search-doc",0)==0) ){ |
| 564 | srchFlags &= ~SRCH_DOC; |
| 565 | } |
| 566 | if( (srchFlags & SRCH_TKT)!=0 |
| 567 | && (g.perm.RdTkt==0 || db_get_boolean("search-tkt",0)==0) ){ |
| 568 | srchFlags &= ~SRCH_TKT; |
| 569 | } |
| 570 | if( (srchFlags & SRCH_WIKI)!=0 |
| 571 | && (g.perm.RdWiki==0 || db_get_boolean("search-wiki",0)==0) ){ |
| 572 | srchFlags &= ~SRCH_WIKI; |
| 573 | } |
| 574 | return srchFlags; |
| 575 | } |
| 576 | |
| 577 | /* |
| 578 | ** This routine generates web-page output for a search operation. |
| 579 | ** Other web-pages can invoke this routine to add search results |
| 580 | ** in the middle of the page. |
| 581 | ** |
| 582 | ** Return the number of rows. |
| 583 | */ |
| 584 | int search_run_and_output( |
| 585 | const char *zPattern, /* The query pattern */ |
| 586 | unsigned int srchFlags /* What to search over */ |
| 587 | ){ |
| 588 | Stmt q; |
| 589 | int nRow = 0; |
| 590 | |
| 591 | srchFlags = search_restrict(srchFlags); |
| 592 | if( srchFlags==0 ) return 0; |
| 593 | search_sql_setup(g.db); |
| 594 | add_content_sql_commands(g.db); |
| 595 | search_init(zPattern, "<b>", "</b>", " ... ", |
| 596 | SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 597 | db_multi_exec( |
| 598 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 599 | "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);" |
| 600 | ); |
| 601 | if( (srchFlags & SRCH_DOC)!=0 ){ |
| 602 | char *zDocGlob = db_get("doc-glob",""); |
| 603 | char *zDocBr = db_get("doc-branch","trunk"); |
| 604 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 605 | db_multi_exec( |
| 606 | "INSERT INTO x(label,url,date,snip)" |
| 607 | " SELECT printf('Document: %%s',foci.filename)," |
| 608 | " printf('%R/doc/%T/%%s',foci.filename)," |
| 609 | " (SELECT datetime(event.mtime) FROM event" |
| 610 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 611 | " snippet(stext('d',blob.rid,foci.filename))" |
| 612 | " FROM foci CROSS JOIN blob" |
| 613 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 614 | " AND blob.uuid=foci.uuid" |
| 615 | " AND %z", |
| 616 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| @@ -628,11 +623,11 @@ | |
| 628 | ")" |
| 629 | "INSERT INTO x(label,url,date,snip)" |
| 630 | " SELECT printf('Wiki: %%s',name)," |
| 631 | " printf('%R/wiki?name=%%s',urlencode(name))," |
| 632 | " datetime(mtime)," |
| 633 | " snippet(stext('w',rid,name))" |
| 634 | " FROM wiki;" |
| 635 | ); |
| 636 | } |
| 637 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 638 | db_multi_exec( |
| @@ -644,11 +639,11 @@ | |
| 644 | ")" |
| 645 | "INSERT INTO x(label,url,date,snip)" |
| 646 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 647 | " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," |
| 648 | " datetime(mtime)," |
| 649 | " snippet(stext('c',rid,NULL))" |
| 650 | " FROM ckin;" |
| 651 | ); |
| 652 | } |
| 653 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 654 | db_multi_exec( |
| @@ -655,17 +650,77 @@ | |
| 655 | "INSERT INTO x(label,url,date,snip)" |
| 656 | " SELECT printf('Ticket [%%.17s] on %%s'," |
| 657 | "tkt_uuid,datetime(tkt_mtime))," |
| 658 | " printf('%R/tktview/%%.20s',tkt_uuid)," |
| 659 | " datetime(tkt_mtime)," |
| 660 | " snippet(stext('t',tkt_id,NULL))" |
| 661 | " FROM ticket;" |
| 662 | ); |
| 663 | } |
| 664 | db_prepare(&q, "SELECT url, substr(snip,9), label" |
| 665 | " FROM x WHERE snip IS NOT NULL" |
| 666 | " ORDER BY substr(snip,1,8) DESC, date DESC;"); |
| 667 | while( db_step(&q)==SQLITE_ROW ){ |
| 668 | const char *zUrl = db_column_text(&q, 0); |
| 669 | const char *zSnippet = db_column_text(&q, 1); |
| 670 | const char *zLabel = db_column_text(&q, 2); |
| 671 | if( nRow==0 ){ |
| @@ -926,21 +981,24 @@ | |
| 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[] = |
| @@ -1007,20 +1065,87 @@ | |
| 1007 | ** to the queue of documents that need to be indexed or reindexed. |
| 1008 | */ |
| 1009 | void search_doc_touch(char cType, int rid, const char *zName){ |
| 1010 | if( search_index_exists() ){ |
| 1011 | db_multi_exec( |
| 1012 | "DELETE FROM ftsidx WHERE rowid IN" |
| 1013 | " (SELECT id FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", |
| 1014 | cType, rid |
| 1015 | ); |
| 1016 | db_multi_exec( |
| 1017 | "REPLACE INTO ftsdocs(type,rid,name,idxed)" |
| 1018 | " VALUES(%Q,%d,%Q,0)", |
| 1019 | cType, rid, zName |
| 1020 | ); |
| 1021 | } |
| 1022 | } |
| 1023 | |
| 1024 | /* |
| 1025 | ** COMMAND: test-fts |
| 1026 | */ |
| @@ -1030,12 +1155,14 @@ | |
| 1030 | static const struct { int iCmd; const char *z; } aCmd[] = { |
| 1031 | { 1, "create" }, |
| 1032 | { 2, "drop" }, |
| 1033 | { 3, "exists" }, |
| 1034 | { 4, "fill" }, |
| 1035 | { 5, "pending" }, |
| 1036 | { 6, "all" }, |
| 1037 | }; |
| 1038 | db_find_and_open_repository(0, 0); |
| 1039 | if( g.argc<3 ) usage("SUBCMD ..."); |
| 1040 | zSubCmd = g.argv[2]; |
| 1041 | n = (int)strlen(zSubCmd); |
| @@ -1064,23 +1191,41 @@ | |
| 1064 | break; |
| 1065 | } |
| 1066 | case 4: { assert( fossil_strncmp(zSubCmd, "fill", n)==0 ); |
| 1067 | search_fill_index(); |
| 1068 | break; |
| 1069 | } |
| 1070 | case 5: { assert( fossil_strncmp(zSubCmd, "pending", n)==0 ); |
| 1071 | Stmt q; |
| 1072 | if( !search_index_exists() ) break; |
| 1073 | db_prepare(&q, "SELECT id, type, rid, quote(name) FROM ftsdocs" |
| 1074 | " WHERE NOT idxed"); |
| 1075 | while( db_step(&q)==SQLITE_ROW ){ |
| 1076 | fossil_print("%6d: %s %6d %s\n", |
| 1077 | db_column_int(&q, 0), |
| 1078 | db_column_text(&q, 1), |
| 1079 | db_column_int(&q, 2), |
| 1080 | db_column_text(&q, 3) |
| 1081 | ); |
| 1082 | } |
| 1083 | db_finalize(&q); |
| 1084 | break; |
| 1085 | } |
| 1086 | case 6: { assert( fossil_strncmp(zSubCmd, "all", n)==0 ); |
| @@ -1097,8 +1242,13 @@ | |
| 1097 | ); |
| 1098 | } |
| 1099 | db_finalize(&q); |
| 1100 | break; |
| 1101 | } |
| 1102 | } |
| 1103 | db_end_transaction(0); |
| 1104 | } |
| 1105 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -453,11 +453,11 @@ | |
| 453 | void search_sql_setup(sqlite3 *db){ |
| 454 | static int once = 0; |
| 455 | if( once++ ) return; |
| 456 | sqlite3_create_function(db, "score", -1, SQLITE_UTF8, 0, |
| 457 | search_score_sqlfunc, 0, 0); |
| 458 | sqlite3_create_function(db, "fsnippet", -1, SQLITE_UTF8, &gSearch, |
| 459 | search_score_sqlfunc, 0, 0); |
| 460 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 461 | search_init_sqlfunc, 0, 0); |
| 462 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 463 | search_stext_sqlfunc, 0, 0); |
| @@ -553,64 +553,59 @@ | |
| 553 | /* |
| 554 | ** Remove bits from srchFlags which are disallowed by either the |
| 555 | ** current server configuration or by user permissions. |
| 556 | */ |
| 557 | unsigned int search_restrict(unsigned int srchFlags){ |
| 558 | if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); |
| 559 | if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); |
| 560 | if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); |
| 561 | if( search_index_exists() ) return srchFlags; |
| 562 | if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){ |
| 563 | srchFlags &= ~SRCH_CKIN; |
| 564 | } |
| 565 | if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){ |
| 566 | srchFlags &= ~SRCH_DOC; |
| 567 | } |
| 568 | if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){ |
| 569 | srchFlags &= ~SRCH_TKT; |
| 570 | } |
| 571 | if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){ |
| 572 | srchFlags &= ~SRCH_WIKI; |
| 573 | } |
| 574 | return srchFlags; |
| 575 | } |
| 576 | |
| 577 | /* |
| 578 | ** When this routine is called, there already exists a table |
| 579 | ** |
| 580 | ** x(label,url,score,date,snip). |
| 581 | ** |
| 582 | ** And the srchFlags parameter has been validated. This routine |
| 583 | ** fills the X table with search results using a full-text scan. |
| 584 | ** |
| 585 | ** The companion indexed scan routine is search_indexed(). |
| 586 | */ |
| 587 | static void search_fullscan( |
| 588 | const char *zPattern, /* The query pattern */ |
| 589 | unsigned int srchFlags /* What to search over */ |
| 590 | ){ |
| 591 | search_init(zPattern, "<b>", "</b>", " ... ", |
| 592 | SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 593 | if( (srchFlags & SRCH_DOC)!=0 ){ |
| 594 | char *zDocGlob = db_get("doc-glob",""); |
| 595 | char *zDocBr = db_get("doc-branch","trunk"); |
| 596 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 597 | db_multi_exec( |
| 598 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 599 | ); |
| 600 | db_multi_exec( |
| 601 | "INSERT INTO x(label,url,date,snip)" |
| 602 | " SELECT printf('Document: %%s',foci.filename)," |
| 603 | " printf('%R/doc/%T/%%s',foci.filename)," |
| 604 | " (SELECT datetime(event.mtime) FROM event" |
| 605 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 606 | " fsnippet(stext('d',blob.rid,foci.filename))" |
| 607 | " FROM foci CROSS JOIN blob" |
| 608 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 609 | " AND blob.uuid=foci.uuid" |
| 610 | " AND %z", |
| 611 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| @@ -628,11 +623,11 @@ | |
| 623 | ")" |
| 624 | "INSERT INTO x(label,url,date,snip)" |
| 625 | " SELECT printf('Wiki: %%s',name)," |
| 626 | " printf('%R/wiki?name=%%s',urlencode(name))," |
| 627 | " datetime(mtime)," |
| 628 | " fsnippet(stext('w',rid,name))" |
| 629 | " FROM wiki;" |
| 630 | ); |
| 631 | } |
| 632 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 633 | db_multi_exec( |
| @@ -644,11 +639,11 @@ | |
| 639 | ")" |
| 640 | "INSERT INTO x(label,url,date,snip)" |
| 641 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 642 | " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," |
| 643 | " datetime(mtime)," |
| 644 | " fsnippet(stext('c',rid,NULL))" |
| 645 | " FROM ckin;" |
| 646 | ); |
| 647 | } |
| 648 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 649 | db_multi_exec( |
| @@ -655,17 +650,77 @@ | |
| 650 | "INSERT INTO x(label,url,date,snip)" |
| 651 | " SELECT printf('Ticket [%%.17s] on %%s'," |
| 652 | "tkt_uuid,datetime(tkt_mtime))," |
| 653 | " printf('%R/tktview/%%.20s',tkt_uuid)," |
| 654 | " datetime(tkt_mtime)," |
| 655 | " fsnippet(stext('t',tkt_id,NULL))" |
| 656 | " FROM ticket;" |
| 657 | ); |
| 658 | } |
| 659 | db_multi_exec( |
| 660 | "UPDATE x SET score=substr(snip,1,8), snip=substr(snip,9)" |
| 661 | ); |
| 662 | } |
| 663 | |
| 664 | /* |
| 665 | ** When this routine is called, there already exists a table |
| 666 | ** |
| 667 | ** x(label,url,score,date,snip). |
| 668 | ** |
| 669 | ** And the srchFlags parameter has been validated. This routine |
| 670 | ** fills the X table with search results using a index scan. |
| 671 | ** |
| 672 | ** The companion full-text scan routine is search_fullscan(). |
| 673 | */ |
| 674 | static void search_indexed( |
| 675 | const char *zPattern, /* The query pattern */ |
| 676 | unsigned int srchFlags /* What to search over */ |
| 677 | ){ |
| 678 | db_multi_exec( |
| 679 | "INSERT INTO x(label,url,score,date,snip) " |
| 680 | " SELECT ftsdocs.label," |
| 681 | " ftsdocs.url," |
| 682 | " 1," /*FIX ME*/ |
| 683 | " datetime(ftsdocs.mtime)," |
| 684 | " fsnippet(ftsidx,'<b>','</b>',' ... ')" |
| 685 | " FROM ftsidx, ftsdocs" |
| 686 | " WHERE ftsidx MATCH %Q" |
| 687 | " AND ftsdocs.id=ftsidx.docid", |
| 688 | zPattern |
| 689 | ); |
| 690 | } |
| 691 | |
| 692 | |
| 693 | /* |
| 694 | ** This routine generates web-page output for a search operation. |
| 695 | ** Other web-pages can invoke this routine to add search results |
| 696 | ** in the middle of the page. |
| 697 | ** |
| 698 | ** Return the number of rows. |
| 699 | */ |
| 700 | int search_run_and_output( |
| 701 | const char *zPattern, /* The query pattern */ |
| 702 | unsigned int srchFlags /* What to search over */ |
| 703 | ){ |
| 704 | Stmt q; |
| 705 | int nRow = 0; |
| 706 | |
| 707 | srchFlags = search_restrict(srchFlags); |
| 708 | if( srchFlags==0 ) return 0; |
| 709 | search_sql_setup(g.db); |
| 710 | add_content_sql_commands(g.db); |
| 711 | db_multi_exec( |
| 712 | "CREATE TEMP TABLE x(label,url,score,date,snip);" |
| 713 | ); |
| 714 | if( !search_index_exists() ){ |
| 715 | search_fullscan(zPattern, srchFlags); |
| 716 | }else{ |
| 717 | search_indexed(zPattern, srchFlags); |
| 718 | } |
| 719 | db_prepare(&q, "SELECT url, snip, label" |
| 720 | " FROM x" |
| 721 | " ORDER BY score DESC, date DESC;"); |
| 722 | while( db_step(&q)==SQLITE_ROW ){ |
| 723 | const char *zUrl = db_column_text(&q, 0); |
| 724 | const char *zSnippet = db_column_text(&q, 1); |
| 725 | const char *zLabel = db_column_text(&q, 2); |
| 726 | if( nRow==0 ){ |
| @@ -926,21 +981,24 @@ | |
| 981 | /* The schema for the full-text index |
| 982 | */ |
| 983 | static const char zFtsSchema[] = |
| 984 | @ -- One entry for each possible search result |
| 985 | @ CREATE TABLE IF NOT EXISTS "%w".ftsdocs( |
| 986 | @ id INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid |
| 987 | @ type CHAR(1), -- Type of document |
| 988 | @ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document |
| 989 | @ name TEXT, -- Additional document description |
| 990 | @ idxed BOOLEAN, -- True if currently in the index |
| 991 | @ label TEXT, -- Label to print on search results |
| 992 | @ url TEXT, -- URL to access this document |
| 993 | @ mtime DATE, -- Date when document created |
| 994 | @ UNIQUE(type,rid) |
| 995 | @ ); |
| 996 | @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; |
| 997 | @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS |
| 998 | @ SELECT id, type, rid, name, idxed, label, url, mtime,fr 5 |
| 999 | @ stext(type,rid,name) AS 'stext' |
| 1000 | @ FROM ftsdocs; |
| 1001 | @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx |
| 1002 | @ USING fts4(content="ftscontent", stext); |
| 1003 | ; |
| 1004 | static const char zFtsDrop[] = |
| @@ -1007,20 +1065,87 @@ | |
| 1065 | ** to the queue of documents that need to be indexed or reindexed. |
| 1066 | */ |
| 1067 | void search_doc_touch(char cType, int rid, const char *zName){ |
| 1068 | if( search_index_exists() ){ |
| 1069 | db_multi_exec( |
| 1070 | "DELETE FROM ftsidx WHERE docid IN" |
| 1071 | " (SELECT id FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", |
| 1072 | cType, rid |
| 1073 | ); |
| 1074 | db_multi_exec( |
| 1075 | "REPLACE INTO ftsdocs(type,rid,name,idxed)" |
| 1076 | " VALUES(%Q,%d,%Q,0)", |
| 1077 | cType, rid, zName |
| 1078 | ); |
| 1079 | } |
| 1080 | } |
| 1081 | |
| 1082 | /* |
| 1083 | ** If the doc-glob and doc-br settings are valid for document search |
| 1084 | ** and if the latest check-in on doc-br is in the unindexed set of |
| 1085 | ** check-ins, then update all 'd' entries in FTSDOCS that have |
| 1086 | ** changed. |
| 1087 | */ |
| 1088 | static void search_update_doc_index(void){ |
| 1089 | const char *zDocBr = db_get("doc-branch","trunk"); |
| 1090 | int ckid = zDocBr ? symbolic_name_to_rid(zDocBr,"ci") : 0; |
| 1091 | double rTime; |
| 1092 | char *zBrUuid; |
| 1093 | if( ckid==0 ) return; |
| 1094 | if( !db_exists("SELECT 1 FROM ftsdocs WHERE type='c' AND rid=%d" |
| 1095 | " AND NOT idxed", ckid) ) return; |
| 1096 | |
| 1097 | /* If we get this far, it means that changes to 'd' entries are |
| 1098 | ** required. */ |
| 1099 | rTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", ckid); |
| 1100 | zBrUuid = db_text("","SELECT substr(uuid,1,20) FROM blob WHERE rid=%d",ckid); |
| 1101 | db_multi_exec( |
| 1102 | "CREATE TEMP TABLE current_docs(rid INTEGER PRIMARY KEY, name);" |
| 1103 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 1104 | "INSERT OR IGNORE INTO current_docs(rid, name)" |
| 1105 | " SELECT blob.rid, foci.filename FROM foci, blob" |
| 1106 | " WHERE foci.checkinID=%d AND blob.uuid=foci.uuid" |
| 1107 | " AND %z", |
| 1108 | ckid, glob_expr("foci.filename", db_get("doc-glob","")) |
| 1109 | ); |
| 1110 | db_multi_exec( |
| 1111 | "DELETE FROM ftsidx WHERE docid IN" |
| 1112 | " (SELECT id FROM ftsdocs WHERE type='d'" |
| 1113 | " AND rid NOT IN (SELECT rid FROM current_docs))" |
| 1114 | ); |
| 1115 | db_multi_exec( |
| 1116 | "DELETE FROM ftsdocs WHERE type='d'" |
| 1117 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1118 | ); |
| 1119 | db_multi_exec( |
| 1120 | "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)" |
| 1121 | " SELECT 'd', rid, name, 0," |
| 1122 | " printf('Document: %%s',name)," |
| 1123 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1124 | " %.17g" |
| 1125 | " FROM current_docs", |
| 1126 | zBrUuid, rTime |
| 1127 | ); |
| 1128 | db_multi_exec( |
| 1129 | "INSERT INTO ftsidx(docid,stext)" |
| 1130 | " SELECT id, stext FROM ftscontent WHERE type='d' AND NOT idxed" |
| 1131 | ); |
| 1132 | db_multi_exec( |
| 1133 | "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed" |
| 1134 | ); |
| 1135 | } |
| 1136 | |
| 1137 | /* |
| 1138 | ** Deal with all of the unindexed entries in the FTSDOCS table - that |
| 1139 | ** is to say, all the entries with FTSDOCS.IDXED=0. Add them to the |
| 1140 | ** index. |
| 1141 | */ |
| 1142 | void search_update_index(void){ |
| 1143 | if( !search_index_exists() ) return; |
| 1144 | search_sql_setup(g.db); |
| 1145 | search_update_doc_index(); |
| 1146 | |
| 1147 | } |
| 1148 | |
| 1149 | /* |
| 1150 | ** COMMAND: test-fts |
| 1151 | */ |
| @@ -1030,12 +1155,14 @@ | |
| 1155 | static const struct { int iCmd; const char *z; } aCmd[] = { |
| 1156 | { 1, "create" }, |
| 1157 | { 2, "drop" }, |
| 1158 | { 3, "exists" }, |
| 1159 | { 4, "fill" }, |
| 1160 | { 8, "refill" }, |
| 1161 | { 5, "pending" }, |
| 1162 | { 6, "all" }, |
| 1163 | { 7, "update" }, |
| 1164 | }; |
| 1165 | db_find_and_open_repository(0, 0); |
| 1166 | if( g.argc<3 ) usage("SUBCMD ..."); |
| 1167 | zSubCmd = g.argv[2]; |
| 1168 | n = (int)strlen(zSubCmd); |
| @@ -1064,23 +1191,41 @@ | |
| 1191 | break; |
| 1192 | } |
| 1193 | case 4: { assert( fossil_strncmp(zSubCmd, "fill", n)==0 ); |
| 1194 | search_fill_index(); |
| 1195 | break; |
| 1196 | } |
| 1197 | case 8: { assert( fossil_strncmp(zSubCmd, "refill", n)==0 ); |
| 1198 | search_drop_index(); |
| 1199 | search_create_index(); |
| 1200 | search_fill_index(); |
| 1201 | break; |
| 1202 | } |
| 1203 | case 5: { assert( fossil_strncmp(zSubCmd, "pending", n)==0 ); |
| 1204 | Stmt q; |
| 1205 | if( !search_index_exists() ) break; |
| 1206 | db_prepare(&q, "SELECT id, type, rid, quote(label), url, date(mtime)" |
| 1207 | " FROM ftsdocs" |
| 1208 | " WHERE NOT idxed"); |
| 1209 | while( db_step(&q)==SQLITE_ROW ){ |
| 1210 | const char *zUrl = db_column_text(&q,4); |
| 1211 | if( zUrl && zUrl[0] ){ |
| 1212 | fossil_print("%6d: %s %6d %s %s\n %s\n", |
| 1213 | db_column_int(&q, 0), |
| 1214 | db_column_text(&q, 1), |
| 1215 | db_column_int(&q, 2), |
| 1216 | db_column_text(&q, 5), |
| 1217 | db_column_text(&q, 3), |
| 1218 | zUrl); |
| 1219 | }else{ |
| 1220 | fossil_print("%6d: %s %6d %s %s\n", |
| 1221 | db_column_int(&q, 0), |
| 1222 | db_column_text(&q, 1), |
| 1223 | db_column_int(&q, 2), |
| 1224 | db_column_text(&q, 5), |
| 1225 | db_column_text(&q, 3)); |
| 1226 | } |
| 1227 | } |
| 1228 | db_finalize(&q); |
| 1229 | break; |
| 1230 | } |
| 1231 | case 6: { assert( fossil_strncmp(zSubCmd, "all", n)==0 ); |
| @@ -1097,8 +1242,13 @@ | |
| 1242 | ); |
| 1243 | } |
| 1244 | db_finalize(&q); |
| 1245 | break; |
| 1246 | } |
| 1247 | case 7: { assert( fossil_strncmp(zSubCmd, "update", n)==0 ); |
| 1248 | search_update_index(); |
| 1249 | break; |
| 1250 | } |
| 1251 | |
| 1252 | } |
| 1253 | db_end_transaction(0); |
| 1254 | } |
| 1255 |