Fossil SCM
Improvements to the ranking function. Add the undocumented "debug" query parameter to /search.
Commit
9f67861aed7d59fbfd53836d140f22799010dbec
Parent
71295a98b7bce92…
1 file changed
+61
-25
+61
-25
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -651,14 +651,15 @@ | ||
| 651 | 651 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 652 | 652 | db_multi_exec( |
| 653 | 653 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 654 | 654 | ); |
| 655 | 655 | db_multi_exec( |
| 656 | - "INSERT INTO x(label,url,score,date,snip)" | |
| 656 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 657 | 657 | " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," |
| 658 | 658 | " printf('/doc/%T/%%s',foci.filename)," |
| 659 | 659 | " search_score()," |
| 660 | + " 'd'||blob.rid," | |
| 660 | 661 | " (SELECT datetime(event.mtime) FROM event" |
| 661 | 662 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 662 | 663 | " search_snippet()" |
| 663 | 664 | " FROM foci CROSS JOIN blob" |
| 664 | 665 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| @@ -677,14 +678,15 @@ | ||
| 677 | 678 | " FROM tag, tagxref" |
| 678 | 679 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 679 | 680 | " AND tagxref.tagid=tag.tagid" |
| 680 | 681 | " GROUP BY 1" |
| 681 | 682 | ")" |
| 682 | - "INSERT INTO x(label,url,score,date,snip)" | |
| 683 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 683 | 684 | " SELECT printf('Wiki: %%s',name)," |
| 684 | 685 | " printf('/wiki?name=%%s',urlencode(name))," |
| 685 | 686 | " search_score()," |
| 687 | + " 'w'||rid," | |
| 686 | 688 | " datetime(mtime)," |
| 687 | 689 | " search_snippet()" |
| 688 | 690 | " FROM wiki" |
| 689 | 691 | " WHERE search_match(title('w',rid,name),body('w',rid,name));" |
| 690 | 692 | ); |
| @@ -695,34 +697,45 @@ | ||
| 695 | 697 | " SELECT blob.uuid, event.objid, event.mtime" |
| 696 | 698 | " FROM event, blob" |
| 697 | 699 | " WHERE event.type='ci'" |
| 698 | 700 | " AND blob.rid=event.objid" |
| 699 | 701 | ")" |
| 700 | - "INSERT INTO x(label,url,score,date,snip)" | |
| 702 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 701 | 703 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 702 | 704 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 703 | 705 | " search_score()," |
| 706 | + " 'c'||rid," | |
| 704 | 707 | " datetime(mtime)," |
| 705 | 708 | " search_snippet()" |
| 706 | 709 | " FROM ckin" |
| 707 | 710 | " WHERE search_match('',body('c',rid,NULL));" |
| 708 | 711 | ); |
| 709 | 712 | } |
| 710 | 713 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 711 | 714 | db_multi_exec( |
| 712 | - "INSERT INTO x(label,url,score, date,snip)" | |
| 715 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 713 | 716 | " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," |
| 714 | 717 | "datetime(tkt_mtime))," |
| 715 | 718 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 716 | 719 | " search_score()," |
| 720 | + " 't'||tkt_id," | |
| 717 | 721 | " datetime(tkt_mtime)," |
| 718 | 722 | " search_snippet()" |
| 719 | 723 | " FROM ticket" |
| 720 | 724 | " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" |
| 721 | 725 | ); |
| 722 | 726 | } |
| 723 | 727 | } |
| 728 | + | |
| 729 | +/* | |
| 730 | +** Number of significant bits in a u32 | |
| 731 | +*/ | |
| 732 | +static int nbits(u32 x){ | |
| 733 | + int n = 0; | |
| 734 | + while( x ){ n++; x >>= 1; } | |
| 735 | + return n; | |
| 736 | +} | |
| 724 | 737 | |
| 725 | 738 | /* |
| 726 | 739 | ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). |
| 727 | 740 | */ |
| 728 | 741 | static void search_rank_sqlfunc( |
| @@ -733,29 +746,42 @@ | ||
| 733 | 746 | const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); |
| 734 | 747 | int nVal = sqlite3_value_bytes(argv[0])/4; |
| 735 | 748 | int nCol; /* Number of columns in the index */ |
| 736 | 749 | int nTerm; /* Number of search terms in the query */ |
| 737 | 750 | int i, j; /* Loop counter */ |
| 738 | - double r = 1.0; /* Score */ | |
| 751 | + double r = 0.0; /* Score */ | |
| 739 | 752 | const unsigned *aX, *aS; |
| 740 | 753 | |
| 741 | 754 | if( nVal<2 ) return; |
| 742 | 755 | nTerm = aVal[0]; |
| 743 | 756 | nCol = aVal[1]; |
| 744 | - if( nVal<2+3*nCol*nTerm+4*nCol ) return; | |
| 757 | + if( nVal<2+3*nCol*nTerm+nCol ) return; | |
| 745 | 758 | aS = aVal+2; |
| 746 | 759 | aX = aS+nCol; |
| 747 | 760 | for(j=0; j<nCol; j++){ |
| 748 | - r *= 1<<((30*(aS[j]-1))/nTerm); | |
| 749 | - for(i=0; i<nTerm; i++){ | |
| 750 | - int hits_this_row = aX[j + i*nCol]; | |
| 751 | - int hits_all_rows = aX[j + i*nCol + 1]; | |
| 752 | - int rows_with_hit = aX[j + i*nCol + 2]; | |
| 753 | - double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit; | |
| 754 | - r *= hits_this_row/avg_hits_per_row; | |
| 755 | - } | |
| 756 | - r *= 2.0; | |
| 761 | + double x; | |
| 762 | + if( aS[j]>0 ){ | |
| 763 | + x = 0.0; | |
| 764 | + for(i=0; i<nTerm; i++){ | |
| 765 | + int hits_this_row; | |
| 766 | + int hits_all_rows; | |
| 767 | + int rows_with_hit; | |
| 768 | + double avg_hits_per_row; | |
| 769 | + | |
| 770 | + hits_this_row = aX[j + i*nCol*3]; | |
| 771 | + if( hits_this_row==0 )continue; | |
| 772 | + hits_all_rows = aX[j + i*nCol*3 + 1]; | |
| 773 | + rows_with_hit = aX[j + i*nCol*3 + 2]; | |
| 774 | + if( rows_with_hit==0 ) continue; | |
| 775 | + avg_hits_per_row = hits_all_rows/(double)rows_with_hit; | |
| 776 | + x += hits_this_row/(avg_hits_per_row*nbits(rows_with_hit)); | |
| 777 | + } | |
| 778 | + x *= (1<<((30*(aS[j]-1))/nTerm)); | |
| 779 | + }else{ | |
| 780 | + x = 0.0; | |
| 781 | + } | |
| 782 | + r = r*10.0 + x; | |
| 757 | 783 | } |
| 758 | 784 | #define SEARCH_DEBUG_RANK 0 |
| 759 | 785 | #if SEARCH_DEBUG_RANK |
| 760 | 786 | { |
| 761 | 787 | Blob x; |
| @@ -790,14 +816,15 @@ | ||
| 790 | 816 | if( srchFlags==0 ) return; |
| 791 | 817 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, |
| 792 | 818 | search_rank_sqlfunc, 0, 0); |
| 793 | 819 | blob_init(&sql, 0, 0); |
| 794 | 820 | blob_appendf(&sql, |
| 795 | - "INSERT INTO x(label,url,score,date,snip) " | |
| 821 | + "INSERT INTO x(label,url,score,id,date,snip) " | |
| 796 | 822 | " SELECT ftsdocs.label," |
| 797 | 823 | " ftsdocs.url," |
| 798 | 824 | " rank(matchinfo(ftsidx,'pcsx'))," |
| 825 | + " ftsdocs.type || ftsdocs.rid," | |
| 799 | 826 | " datetime(ftsdocs.mtime)," |
| 800 | 827 | " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)" |
| 801 | 828 | " FROM ftsidx CROSS JOIN ftsdocs" |
| 802 | 829 | " WHERE ftsidx MATCH %Q" |
| 803 | 830 | " AND ftsdocs.rowid=ftsidx.docid", |
| @@ -882,29 +909,30 @@ | ||
| 882 | 909 | ** |
| 883 | 910 | ** Return the number of rows. |
| 884 | 911 | */ |
| 885 | 912 | int search_run_and_output( |
| 886 | 913 | const char *zPattern, /* The query pattern */ |
| 887 | - unsigned int srchFlags /* What to search over */ | |
| 914 | + unsigned int srchFlags, /* What to search over */ | |
| 915 | + int fDebug /* Extra debugging output */ | |
| 888 | 916 | ){ |
| 889 | 917 | Stmt q; |
| 890 | 918 | int nRow = 0; |
| 891 | 919 | |
| 892 | 920 | srchFlags = search_restrict(srchFlags); |
| 893 | 921 | if( srchFlags==0 ) return 0; |
| 894 | 922 | search_sql_setup(g.db); |
| 895 | 923 | add_content_sql_commands(g.db); |
| 896 | 924 | db_multi_exec( |
| 897 | - "CREATE TEMP TABLE x(label,url,score,date,snip);" | |
| 925 | + "CREATE TEMP TABLE x(label,url,score,id,date,snip);" | |
| 898 | 926 | ); |
| 899 | 927 | if( !search_index_exists() ){ |
| 900 | 928 | search_fullscan(zPattern, srchFlags); |
| 901 | 929 | }else{ |
| 902 | 930 | search_update_index(srchFlags); |
| 903 | 931 | search_indexed(zPattern, srchFlags); |
| 904 | 932 | } |
| 905 | - db_prepare(&q, "SELECT url, snip, label" | |
| 933 | + db_prepare(&q, "SELECT url, snip, label, score, id" | |
| 906 | 934 | " FROM x" |
| 907 | 935 | " ORDER BY score DESC, date DESC;"); |
| 908 | 936 | while( db_step(&q)==SQLITE_ROW ){ |
| 909 | 937 | const char *zUrl = db_column_text(&q, 0); |
| 910 | 938 | const char *zSnippet = db_column_text(&q, 1); |
| @@ -911,12 +939,15 @@ | ||
| 911 | 939 | const char *zLabel = db_column_text(&q, 2); |
| 912 | 940 | if( nRow==0 ){ |
| 913 | 941 | @ <ol> |
| 914 | 942 | } |
| 915 | 943 | nRow++; |
| 916 | - @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br> | |
| 917 | - @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> | |
| 944 | + @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a> | |
| 945 | + if( fDebug ){ | |
| 946 | + @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4))) | |
| 947 | + } | |
| 948 | + @ <br><span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> | |
| 918 | 949 | } |
| 919 | 950 | db_finalize(&q); |
| 920 | 951 | if( nRow ){ |
| 921 | 952 | @ </ol> |
| 922 | 953 | } |
| @@ -944,10 +975,11 @@ | ||
| 944 | 975 | const char *zType = 0; |
| 945 | 976 | const char *zClass = 0; |
| 946 | 977 | const char *zDisable1; |
| 947 | 978 | const char *zDisable2; |
| 948 | 979 | const char *zPattern; |
| 980 | + int fDebug = PB("debug"); | |
| 949 | 981 | srchFlags = search_restrict(srchFlags); |
| 950 | 982 | switch( srchFlags ){ |
| 951 | 983 | case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 952 | 984 | case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 953 | 985 | case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| @@ -991,10 +1023,13 @@ | ||
| 991 | 1023 | cgi_printf(">%s</option>\n", aY[i].zNm); |
| 992 | 1024 | } |
| 993 | 1025 | @ </select> |
| 994 | 1026 | srchFlags = newFlags; |
| 995 | 1027 | } |
| 1028 | + if( fDebug ){ | |
| 1029 | + @ <input type="hidden" name="debug" value="1"> | |
| 1030 | + } | |
| 996 | 1031 | @ <input type="submit" value="Search%s(zType)"%s(zDisable2)> |
| 997 | 1032 | if( srchFlags==0 ){ |
| 998 | 1033 | @ <p class="generalError">Search is disabled</p> |
| 999 | 1034 | } |
| 1000 | 1035 | @ </div></form> |
| @@ -1003,11 +1038,11 @@ | ||
| 1003 | 1038 | if( zClass ){ |
| 1004 | 1039 | @ <div class='searchResult searchResult%s(zClass)'> |
| 1005 | 1040 | }else{ |
| 1006 | 1041 | @ <div class='searchResult'> |
| 1007 | 1042 | } |
| 1008 | - if( search_run_and_output(zPattern, srchFlags)==0 ){ | |
| 1043 | + if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){ | |
| 1009 | 1044 | @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> |
| 1010 | 1045 | } |
| 1011 | 1046 | @ </div> |
| 1012 | 1047 | } |
| 1013 | 1048 | } |
| @@ -1454,25 +1489,26 @@ | ||
| 1454 | 1489 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1455 | 1490 | ); |
| 1456 | 1491 | db_multi_exec( |
| 1457 | 1492 | "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)" |
| 1458 | 1493 | " SELECT 'd', rid, name, 0," |
| 1459 | - " 'Document: '||title('d',rid,name)," | |
| 1494 | + " title('d',rid,name)," | |
| 1460 | 1495 | " body('d',rid,name)," |
| 1461 | 1496 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1462 | 1497 | " %.17g" |
| 1463 | 1498 | " FROM current_docs", |
| 1464 | 1499 | zBrUuid, rTime |
| 1465 | 1500 | ); |
| 1466 | 1501 | db_multi_exec( |
| 1467 | 1502 | "INSERT INTO ftsidx(docid,title,body)" |
| 1468 | - " SELECT rowid, name, bx FROM ftsdocs WHERE type='d' AND NOT idxed" | |
| 1503 | + " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed" | |
| 1469 | 1504 | ); |
| 1470 | 1505 | db_multi_exec( |
| 1471 | 1506 | "UPDATE ftsdocs SET" |
| 1472 | 1507 | " idxed=1," |
| 1473 | - " bx=NULL" | |
| 1508 | + " bx=NULL," | |
| 1509 | + " label='Document: '||label" | |
| 1474 | 1510 | " WHERE type='d' AND NOT idxed" |
| 1475 | 1511 | ); |
| 1476 | 1512 | } |
| 1477 | 1513 | |
| 1478 | 1514 | /* |
| 1479 | 1515 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -651,14 +651,15 @@ | |
| 651 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 652 | db_multi_exec( |
| 653 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 654 | ); |
| 655 | db_multi_exec( |
| 656 | "INSERT INTO x(label,url,score,date,snip)" |
| 657 | " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," |
| 658 | " printf('/doc/%T/%%s',foci.filename)," |
| 659 | " search_score()," |
| 660 | " (SELECT datetime(event.mtime) FROM event" |
| 661 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 662 | " search_snippet()" |
| 663 | " FROM foci CROSS JOIN blob" |
| 664 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| @@ -677,14 +678,15 @@ | |
| 677 | " FROM tag, tagxref" |
| 678 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 679 | " AND tagxref.tagid=tag.tagid" |
| 680 | " GROUP BY 1" |
| 681 | ")" |
| 682 | "INSERT INTO x(label,url,score,date,snip)" |
| 683 | " SELECT printf('Wiki: %%s',name)," |
| 684 | " printf('/wiki?name=%%s',urlencode(name))," |
| 685 | " search_score()," |
| 686 | " datetime(mtime)," |
| 687 | " search_snippet()" |
| 688 | " FROM wiki" |
| 689 | " WHERE search_match(title('w',rid,name),body('w',rid,name));" |
| 690 | ); |
| @@ -695,34 +697,45 @@ | |
| 695 | " SELECT blob.uuid, event.objid, event.mtime" |
| 696 | " FROM event, blob" |
| 697 | " WHERE event.type='ci'" |
| 698 | " AND blob.rid=event.objid" |
| 699 | ")" |
| 700 | "INSERT INTO x(label,url,score,date,snip)" |
| 701 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 702 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 703 | " search_score()," |
| 704 | " datetime(mtime)," |
| 705 | " search_snippet()" |
| 706 | " FROM ckin" |
| 707 | " WHERE search_match('',body('c',rid,NULL));" |
| 708 | ); |
| 709 | } |
| 710 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 711 | db_multi_exec( |
| 712 | "INSERT INTO x(label,url,score, date,snip)" |
| 713 | " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," |
| 714 | "datetime(tkt_mtime))," |
| 715 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 716 | " search_score()," |
| 717 | " datetime(tkt_mtime)," |
| 718 | " search_snippet()" |
| 719 | " FROM ticket" |
| 720 | " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" |
| 721 | ); |
| 722 | } |
| 723 | } |
| 724 | |
| 725 | /* |
| 726 | ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). |
| 727 | */ |
| 728 | static void search_rank_sqlfunc( |
| @@ -733,29 +746,42 @@ | |
| 733 | const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); |
| 734 | int nVal = sqlite3_value_bytes(argv[0])/4; |
| 735 | int nCol; /* Number of columns in the index */ |
| 736 | int nTerm; /* Number of search terms in the query */ |
| 737 | int i, j; /* Loop counter */ |
| 738 | double r = 1.0; /* Score */ |
| 739 | const unsigned *aX, *aS; |
| 740 | |
| 741 | if( nVal<2 ) return; |
| 742 | nTerm = aVal[0]; |
| 743 | nCol = aVal[1]; |
| 744 | if( nVal<2+3*nCol*nTerm+4*nCol ) return; |
| 745 | aS = aVal+2; |
| 746 | aX = aS+nCol; |
| 747 | for(j=0; j<nCol; j++){ |
| 748 | r *= 1<<((30*(aS[j]-1))/nTerm); |
| 749 | for(i=0; i<nTerm; i++){ |
| 750 | int hits_this_row = aX[j + i*nCol]; |
| 751 | int hits_all_rows = aX[j + i*nCol + 1]; |
| 752 | int rows_with_hit = aX[j + i*nCol + 2]; |
| 753 | double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit; |
| 754 | r *= hits_this_row/avg_hits_per_row; |
| 755 | } |
| 756 | r *= 2.0; |
| 757 | } |
| 758 | #define SEARCH_DEBUG_RANK 0 |
| 759 | #if SEARCH_DEBUG_RANK |
| 760 | { |
| 761 | Blob x; |
| @@ -790,14 +816,15 @@ | |
| 790 | if( srchFlags==0 ) return; |
| 791 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, |
| 792 | search_rank_sqlfunc, 0, 0); |
| 793 | blob_init(&sql, 0, 0); |
| 794 | blob_appendf(&sql, |
| 795 | "INSERT INTO x(label,url,score,date,snip) " |
| 796 | " SELECT ftsdocs.label," |
| 797 | " ftsdocs.url," |
| 798 | " rank(matchinfo(ftsidx,'pcsx'))," |
| 799 | " datetime(ftsdocs.mtime)," |
| 800 | " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)" |
| 801 | " FROM ftsidx CROSS JOIN ftsdocs" |
| 802 | " WHERE ftsidx MATCH %Q" |
| 803 | " AND ftsdocs.rowid=ftsidx.docid", |
| @@ -882,29 +909,30 @@ | |
| 882 | ** |
| 883 | ** Return the number of rows. |
| 884 | */ |
| 885 | int search_run_and_output( |
| 886 | const char *zPattern, /* The query pattern */ |
| 887 | unsigned int srchFlags /* What to search over */ |
| 888 | ){ |
| 889 | Stmt q; |
| 890 | int nRow = 0; |
| 891 | |
| 892 | srchFlags = search_restrict(srchFlags); |
| 893 | if( srchFlags==0 ) return 0; |
| 894 | search_sql_setup(g.db); |
| 895 | add_content_sql_commands(g.db); |
| 896 | db_multi_exec( |
| 897 | "CREATE TEMP TABLE x(label,url,score,date,snip);" |
| 898 | ); |
| 899 | if( !search_index_exists() ){ |
| 900 | search_fullscan(zPattern, srchFlags); |
| 901 | }else{ |
| 902 | search_update_index(srchFlags); |
| 903 | search_indexed(zPattern, srchFlags); |
| 904 | } |
| 905 | db_prepare(&q, "SELECT url, snip, label" |
| 906 | " FROM x" |
| 907 | " ORDER BY score DESC, date DESC;"); |
| 908 | while( db_step(&q)==SQLITE_ROW ){ |
| 909 | const char *zUrl = db_column_text(&q, 0); |
| 910 | const char *zSnippet = db_column_text(&q, 1); |
| @@ -911,12 +939,15 @@ | |
| 911 | const char *zLabel = db_column_text(&q, 2); |
| 912 | if( nRow==0 ){ |
| 913 | @ <ol> |
| 914 | } |
| 915 | nRow++; |
| 916 | @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br> |
| 917 | @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> |
| 918 | } |
| 919 | db_finalize(&q); |
| 920 | if( nRow ){ |
| 921 | @ </ol> |
| 922 | } |
| @@ -944,10 +975,11 @@ | |
| 944 | const char *zType = 0; |
| 945 | const char *zClass = 0; |
| 946 | const char *zDisable1; |
| 947 | const char *zDisable2; |
| 948 | const char *zPattern; |
| 949 | srchFlags = search_restrict(srchFlags); |
| 950 | switch( srchFlags ){ |
| 951 | case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 952 | case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 953 | case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| @@ -991,10 +1023,13 @@ | |
| 991 | cgi_printf(">%s</option>\n", aY[i].zNm); |
| 992 | } |
| 993 | @ </select> |
| 994 | srchFlags = newFlags; |
| 995 | } |
| 996 | @ <input type="submit" value="Search%s(zType)"%s(zDisable2)> |
| 997 | if( srchFlags==0 ){ |
| 998 | @ <p class="generalError">Search is disabled</p> |
| 999 | } |
| 1000 | @ </div></form> |
| @@ -1003,11 +1038,11 @@ | |
| 1003 | if( zClass ){ |
| 1004 | @ <div class='searchResult searchResult%s(zClass)'> |
| 1005 | }else{ |
| 1006 | @ <div class='searchResult'> |
| 1007 | } |
| 1008 | if( search_run_and_output(zPattern, srchFlags)==0 ){ |
| 1009 | @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> |
| 1010 | } |
| 1011 | @ </div> |
| 1012 | } |
| 1013 | } |
| @@ -1454,25 +1489,26 @@ | |
| 1454 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1455 | ); |
| 1456 | db_multi_exec( |
| 1457 | "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)" |
| 1458 | " SELECT 'd', rid, name, 0," |
| 1459 | " 'Document: '||title('d',rid,name)," |
| 1460 | " body('d',rid,name)," |
| 1461 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1462 | " %.17g" |
| 1463 | " FROM current_docs", |
| 1464 | zBrUuid, rTime |
| 1465 | ); |
| 1466 | db_multi_exec( |
| 1467 | "INSERT INTO ftsidx(docid,title,body)" |
| 1468 | " SELECT rowid, name, bx FROM ftsdocs WHERE type='d' AND NOT idxed" |
| 1469 | ); |
| 1470 | db_multi_exec( |
| 1471 | "UPDATE ftsdocs SET" |
| 1472 | " idxed=1," |
| 1473 | " bx=NULL" |
| 1474 | " WHERE type='d' AND NOT idxed" |
| 1475 | ); |
| 1476 | } |
| 1477 | |
| 1478 | /* |
| 1479 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -651,14 +651,15 @@ | |
| 651 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 652 | db_multi_exec( |
| 653 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 654 | ); |
| 655 | db_multi_exec( |
| 656 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 657 | " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," |
| 658 | " printf('/doc/%T/%%s',foci.filename)," |
| 659 | " search_score()," |
| 660 | " 'd'||blob.rid," |
| 661 | " (SELECT datetime(event.mtime) FROM event" |
| 662 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 663 | " search_snippet()" |
| 664 | " FROM foci CROSS JOIN blob" |
| 665 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| @@ -677,14 +678,15 @@ | |
| 678 | " FROM tag, tagxref" |
| 679 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 680 | " AND tagxref.tagid=tag.tagid" |
| 681 | " GROUP BY 1" |
| 682 | ")" |
| 683 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 684 | " SELECT printf('Wiki: %%s',name)," |
| 685 | " printf('/wiki?name=%%s',urlencode(name))," |
| 686 | " search_score()," |
| 687 | " 'w'||rid," |
| 688 | " datetime(mtime)," |
| 689 | " search_snippet()" |
| 690 | " FROM wiki" |
| 691 | " WHERE search_match(title('w',rid,name),body('w',rid,name));" |
| 692 | ); |
| @@ -695,34 +697,45 @@ | |
| 697 | " SELECT blob.uuid, event.objid, event.mtime" |
| 698 | " FROM event, blob" |
| 699 | " WHERE event.type='ci'" |
| 700 | " AND blob.rid=event.objid" |
| 701 | ")" |
| 702 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 703 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 704 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 705 | " search_score()," |
| 706 | " 'c'||rid," |
| 707 | " datetime(mtime)," |
| 708 | " search_snippet()" |
| 709 | " FROM ckin" |
| 710 | " WHERE search_match('',body('c',rid,NULL));" |
| 711 | ); |
| 712 | } |
| 713 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 714 | db_multi_exec( |
| 715 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 716 | " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," |
| 717 | "datetime(tkt_mtime))," |
| 718 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 719 | " search_score()," |
| 720 | " 't'||tkt_id," |
| 721 | " datetime(tkt_mtime)," |
| 722 | " search_snippet()" |
| 723 | " FROM ticket" |
| 724 | " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" |
| 725 | ); |
| 726 | } |
| 727 | } |
| 728 | |
| 729 | /* |
| 730 | ** Number of significant bits in a u32 |
| 731 | */ |
| 732 | static int nbits(u32 x){ |
| 733 | int n = 0; |
| 734 | while( x ){ n++; x >>= 1; } |
| 735 | return n; |
| 736 | } |
| 737 | |
| 738 | /* |
| 739 | ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). |
| 740 | */ |
| 741 | static void search_rank_sqlfunc( |
| @@ -733,29 +746,42 @@ | |
| 746 | const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); |
| 747 | int nVal = sqlite3_value_bytes(argv[0])/4; |
| 748 | int nCol; /* Number of columns in the index */ |
| 749 | int nTerm; /* Number of search terms in the query */ |
| 750 | int i, j; /* Loop counter */ |
| 751 | double r = 0.0; /* Score */ |
| 752 | const unsigned *aX, *aS; |
| 753 | |
| 754 | if( nVal<2 ) return; |
| 755 | nTerm = aVal[0]; |
| 756 | nCol = aVal[1]; |
| 757 | if( nVal<2+3*nCol*nTerm+nCol ) return; |
| 758 | aS = aVal+2; |
| 759 | aX = aS+nCol; |
| 760 | for(j=0; j<nCol; j++){ |
| 761 | double x; |
| 762 | if( aS[j]>0 ){ |
| 763 | x = 0.0; |
| 764 | for(i=0; i<nTerm; i++){ |
| 765 | int hits_this_row; |
| 766 | int hits_all_rows; |
| 767 | int rows_with_hit; |
| 768 | double avg_hits_per_row; |
| 769 | |
| 770 | hits_this_row = aX[j + i*nCol*3]; |
| 771 | if( hits_this_row==0 )continue; |
| 772 | hits_all_rows = aX[j + i*nCol*3 + 1]; |
| 773 | rows_with_hit = aX[j + i*nCol*3 + 2]; |
| 774 | if( rows_with_hit==0 ) continue; |
| 775 | avg_hits_per_row = hits_all_rows/(double)rows_with_hit; |
| 776 | x += hits_this_row/(avg_hits_per_row*nbits(rows_with_hit)); |
| 777 | } |
| 778 | x *= (1<<((30*(aS[j]-1))/nTerm)); |
| 779 | }else{ |
| 780 | x = 0.0; |
| 781 | } |
| 782 | r = r*10.0 + x; |
| 783 | } |
| 784 | #define SEARCH_DEBUG_RANK 0 |
| 785 | #if SEARCH_DEBUG_RANK |
| 786 | { |
| 787 | Blob x; |
| @@ -790,14 +816,15 @@ | |
| 816 | if( srchFlags==0 ) return; |
| 817 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, |
| 818 | search_rank_sqlfunc, 0, 0); |
| 819 | blob_init(&sql, 0, 0); |
| 820 | blob_appendf(&sql, |
| 821 | "INSERT INTO x(label,url,score,id,date,snip) " |
| 822 | " SELECT ftsdocs.label," |
| 823 | " ftsdocs.url," |
| 824 | " rank(matchinfo(ftsidx,'pcsx'))," |
| 825 | " ftsdocs.type || ftsdocs.rid," |
| 826 | " datetime(ftsdocs.mtime)," |
| 827 | " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)" |
| 828 | " FROM ftsidx CROSS JOIN ftsdocs" |
| 829 | " WHERE ftsidx MATCH %Q" |
| 830 | " AND ftsdocs.rowid=ftsidx.docid", |
| @@ -882,29 +909,30 @@ | |
| 909 | ** |
| 910 | ** Return the number of rows. |
| 911 | */ |
| 912 | int search_run_and_output( |
| 913 | const char *zPattern, /* The query pattern */ |
| 914 | unsigned int srchFlags, /* What to search over */ |
| 915 | int fDebug /* Extra debugging output */ |
| 916 | ){ |
| 917 | Stmt q; |
| 918 | int nRow = 0; |
| 919 | |
| 920 | srchFlags = search_restrict(srchFlags); |
| 921 | if( srchFlags==0 ) return 0; |
| 922 | search_sql_setup(g.db); |
| 923 | add_content_sql_commands(g.db); |
| 924 | db_multi_exec( |
| 925 | "CREATE TEMP TABLE x(label,url,score,id,date,snip);" |
| 926 | ); |
| 927 | if( !search_index_exists() ){ |
| 928 | search_fullscan(zPattern, srchFlags); |
| 929 | }else{ |
| 930 | search_update_index(srchFlags); |
| 931 | search_indexed(zPattern, srchFlags); |
| 932 | } |
| 933 | db_prepare(&q, "SELECT url, snip, label, score, id" |
| 934 | " FROM x" |
| 935 | " ORDER BY score DESC, date DESC;"); |
| 936 | while( db_step(&q)==SQLITE_ROW ){ |
| 937 | const char *zUrl = db_column_text(&q, 0); |
| 938 | const char *zSnippet = db_column_text(&q, 1); |
| @@ -911,12 +939,15 @@ | |
| 939 | const char *zLabel = db_column_text(&q, 2); |
| 940 | if( nRow==0 ){ |
| 941 | @ <ol> |
| 942 | } |
| 943 | nRow++; |
| 944 | @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a> |
| 945 | if( fDebug ){ |
| 946 | @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4))) |
| 947 | } |
| 948 | @ <br><span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> |
| 949 | } |
| 950 | db_finalize(&q); |
| 951 | if( nRow ){ |
| 952 | @ </ol> |
| 953 | } |
| @@ -944,10 +975,11 @@ | |
| 975 | const char *zType = 0; |
| 976 | const char *zClass = 0; |
| 977 | const char *zDisable1; |
| 978 | const char *zDisable2; |
| 979 | const char *zPattern; |
| 980 | int fDebug = PB("debug"); |
| 981 | srchFlags = search_restrict(srchFlags); |
| 982 | switch( srchFlags ){ |
| 983 | case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 984 | case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 985 | case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| @@ -991,10 +1023,13 @@ | |
| 1023 | cgi_printf(">%s</option>\n", aY[i].zNm); |
| 1024 | } |
| 1025 | @ </select> |
| 1026 | srchFlags = newFlags; |
| 1027 | } |
| 1028 | if( fDebug ){ |
| 1029 | @ <input type="hidden" name="debug" value="1"> |
| 1030 | } |
| 1031 | @ <input type="submit" value="Search%s(zType)"%s(zDisable2)> |
| 1032 | if( srchFlags==0 ){ |
| 1033 | @ <p class="generalError">Search is disabled</p> |
| 1034 | } |
| 1035 | @ </div></form> |
| @@ -1003,11 +1038,11 @@ | |
| 1038 | if( zClass ){ |
| 1039 | @ <div class='searchResult searchResult%s(zClass)'> |
| 1040 | }else{ |
| 1041 | @ <div class='searchResult'> |
| 1042 | } |
| 1043 | if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){ |
| 1044 | @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> |
| 1045 | } |
| 1046 | @ </div> |
| 1047 | } |
| 1048 | } |
| @@ -1454,25 +1489,26 @@ | |
| 1489 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1490 | ); |
| 1491 | db_multi_exec( |
| 1492 | "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)" |
| 1493 | " SELECT 'd', rid, name, 0," |
| 1494 | " title('d',rid,name)," |
| 1495 | " body('d',rid,name)," |
| 1496 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1497 | " %.17g" |
| 1498 | " FROM current_docs", |
| 1499 | zBrUuid, rTime |
| 1500 | ); |
| 1501 | db_multi_exec( |
| 1502 | "INSERT INTO ftsidx(docid,title,body)" |
| 1503 | " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed" |
| 1504 | ); |
| 1505 | db_multi_exec( |
| 1506 | "UPDATE ftsdocs SET" |
| 1507 | " idxed=1," |
| 1508 | " bx=NULL," |
| 1509 | " label='Document: '||label" |
| 1510 | " WHERE type='d' AND NOT idxed" |
| 1511 | ); |
| 1512 | } |
| 1513 | |
| 1514 | /* |
| 1515 |