| | @@ -633,15 +633,16 @@ |
| 633 | 633 | db_finalize(&q); |
| 634 | 634 | } |
| 635 | 635 | |
| 636 | 636 | #if INTERFACE |
| 637 | 637 | /* What to search for */ |
| 638 | | -#define SRCH_CKIN 0x0001 /* Search over check-in comments */ |
| 639 | | -#define SRCH_DOC 0x0002 /* Search over embedded documents */ |
| 640 | | -#define SRCH_TKT 0x0004 /* Search over tickets */ |
| 641 | | -#define SRCH_WIKI 0x0008 /* Search over wiki */ |
| 642 | | -#define SRCH_ALL 0x000f /* Search over everything */ |
| 638 | +#define SRCH_CKIN 0x0001 /* Search over check-in comments */ |
| 639 | +#define SRCH_DOC 0x0002 /* Search over embedded documents */ |
| 640 | +#define SRCH_TKT 0x0004 /* Search over tickets */ |
| 641 | +#define SRCH_WIKI 0x0008 /* Search over wiki */ |
| 642 | +#define SRCH_TECHNOTE 0x0010 /* Search over tech notes */ |
| 643 | +#define SRCH_ALL 0x001f /* Search over everything */ |
| 643 | 644 | #endif |
| 644 | 645 | |
| 645 | 646 | /* |
| 646 | 647 | ** Remove bits from srchFlags which are disallowed by either the |
| 647 | 648 | ** current server configuration or by user permissions. |
| | @@ -648,17 +649,18 @@ |
| 648 | 649 | */ |
| 649 | 650 | unsigned int search_restrict(unsigned int srchFlags){ |
| 650 | 651 | static unsigned int knownGood = 0; |
| 651 | 652 | static unsigned int knownBad = 0; |
| 652 | 653 | static const struct { unsigned m; const char *zKey; } aSetng[] = { |
| 653 | | - { SRCH_CKIN, "search-ci" }, |
| 654 | | - { SRCH_DOC, "search-doc" }, |
| 655 | | - { SRCH_TKT, "search-tkt" }, |
| 656 | | - { SRCH_WIKI, "search-wiki" }, |
| 654 | + { SRCH_CKIN, "search-ci" }, |
| 655 | + { SRCH_DOC, "search-doc" }, |
| 656 | + { SRCH_TKT, "search-tkt" }, |
| 657 | + { SRCH_WIKI, "search-wiki" }, |
| 658 | + { SRCH_TECHNOTE, "search-technote" }, |
| 657 | 659 | }; |
| 658 | 660 | int i; |
| 659 | | - if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); |
| 661 | + if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC|SRCH_TECHNOTE); |
| 660 | 662 | if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); |
| 661 | 663 | if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); |
| 662 | 664 | for(i=0; i<count(aSetng); i++){ |
| 663 | 665 | unsigned int m = aSetng[i].m; |
| 664 | 666 | if( (srchFlags & m)==0 ) continue; |
| | @@ -770,10 +772,30 @@ |
| 770 | 772 | " datetime(tkt_mtime)," |
| 771 | 773 | " search_snippet()" |
| 772 | 774 | " FROM ticket" |
| 773 | 775 | " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" |
| 774 | 776 | ); |
| 777 | + } |
| 778 | + if( (srchFlags & SRCH_TECHNOTE)!=0 ){ |
| 779 | + db_multi_exec( |
| 780 | + "WITH technote(uuid,rid,mtime) AS (" |
| 781 | + " SELECT substr(tagname,7), tagxref.rid, max(tagxref.mtime)" |
| 782 | + " FROM tag, tagxref" |
| 783 | + " WHERE tag.tagname GLOB 'event-*'" |
| 784 | + " AND tagxref.tagid=tag.tagid" |
| 785 | + " GROUP BY 1" |
| 786 | + ")" |
| 787 | + "INSERT INTO x(label,url,score,id,date,snip)" |
| 788 | + " SELECT printf('Tech Note: %%s',uuid)," |
| 789 | + " printf('/technote/%%s',uuid)," |
| 790 | + " search_score()," |
| 791 | + " 'e'||rid," |
| 792 | + " datetime(mtime)," |
| 793 | + " search_snippet()" |
| 794 | + " FROM technote" |
| 795 | + " WHERE search_match('',body('e',rid,NULL));" |
| 796 | + ); |
| 775 | 797 | } |
| 776 | 798 | } |
| 777 | 799 | |
| 778 | 800 | /* |
| 779 | 801 | ** Number of significant bits in a u32 |
| | @@ -886,14 +908,15 @@ |
| 886 | 908 | zPattern |
| 887 | 909 | ); |
| 888 | 910 | if( srchFlags!=SRCH_ALL ){ |
| 889 | 911 | const char *zSep = " AND ("; |
| 890 | 912 | static const struct { unsigned m; char c; } aMask[] = { |
| 891 | | - { SRCH_CKIN, 'c' }, |
| 892 | | - { SRCH_DOC, 'd' }, |
| 893 | | - { SRCH_TKT, 't' }, |
| 894 | | - { SRCH_WIKI, 'w' }, |
| 913 | + { SRCH_CKIN, 'c' }, |
| 914 | + { SRCH_DOC, 'd' }, |
| 915 | + { SRCH_TKT, 't' }, |
| 916 | + { SRCH_WIKI, 'w' }, |
| 917 | + { SRCH_TECHNOTE, 'e' }, |
| 895 | 918 | }; |
| 896 | 919 | int i; |
| 897 | 920 | for(i=0; i<count(aMask); i++){ |
| 898 | 921 | if( srchFlags & aMask[i].m ){ |
| 899 | 922 | blob_appendf(&sql, "%sftsdocs.type='%c'", zSep, aMask[i].c); |
| | @@ -1037,14 +1060,15 @@ |
| 1037 | 1060 | const char *zDisable2; |
| 1038 | 1061 | const char *zPattern; |
| 1039 | 1062 | int fDebug = PB("debug"); |
| 1040 | 1063 | srchFlags = search_restrict(srchFlags); |
| 1041 | 1064 | switch( srchFlags ){ |
| 1042 | | - case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 1043 | | - case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 1044 | | - case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| 1045 | | - case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break; |
| 1065 | + case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 1066 | + case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 1067 | + case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| 1068 | + case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break; |
| 1069 | + case SRCH_TECHNOTE: zType = " Tech Notes"; zClass = "Note"; break; |
| 1046 | 1070 | } |
| 1047 | 1071 | if( srchFlags==0 ){ |
| 1048 | 1072 | zDisable1 = " disabled"; |
| 1049 | 1073 | zDisable2 = " disabled"; |
| 1050 | 1074 | zPattern = ""; |
| | @@ -1060,15 +1084,16 @@ |
| 1060 | 1084 | @ <div class='searchForm'> |
| 1061 | 1085 | } |
| 1062 | 1086 | @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)> |
| 1063 | 1087 | if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){ |
| 1064 | 1088 | static const struct { char *z; char *zNm; unsigned m; } aY[] = { |
| 1065 | | - { "all", "All", SRCH_ALL }, |
| 1066 | | - { "c", "Check-ins", SRCH_CKIN }, |
| 1067 | | - { "d", "Docs", SRCH_DOC }, |
| 1068 | | - { "t", "Tickets", SRCH_TKT }, |
| 1069 | | - { "w", "Wiki", SRCH_WIKI }, |
| 1089 | + { "all", "All", SRCH_ALL }, |
| 1090 | + { "c", "Check-ins", SRCH_CKIN }, |
| 1091 | + { "d", "Docs", SRCH_DOC }, |
| 1092 | + { "t", "Tickets", SRCH_TKT }, |
| 1093 | + { "w", "Wiki", SRCH_WIKI }, |
| 1094 | + { "e", "Tech Notes", SRCH_TECHNOTE }, |
| 1070 | 1095 | }; |
| 1071 | 1096 | const char *zY = PD("y","all"); |
| 1072 | 1097 | unsigned newFlags = srchFlags; |
| 1073 | 1098 | int i; |
| 1074 | 1099 | @ <select size='1' name='y'> |
| | @@ -1116,10 +1141,11 @@ |
| 1116 | 1141 | ** y=TYPE What to search. |
| 1117 | 1142 | ** c -> check-ins |
| 1118 | 1143 | ** d -> documentation |
| 1119 | 1144 | ** t -> tickets |
| 1120 | 1145 | ** w -> wiki |
| 1146 | +** e -> tech notes |
| 1121 | 1147 | ** all -> everything |
| 1122 | 1148 | */ |
| 1123 | 1149 | void search_page(void){ |
| 1124 | 1150 | login_check_credentials(); |
| 1125 | 1151 | style_header("Search"); |
| | @@ -1222,10 +1248,11 @@ |
| 1222 | 1248 | ** |
| 1223 | 1249 | ** cType: d Embedded documentation |
| 1224 | 1250 | ** w Wiki page |
| 1225 | 1251 | ** c Check-in comment |
| 1226 | 1252 | ** t Ticket text |
| 1253 | +** e Tech note |
| 1227 | 1254 | ** |
| 1228 | 1255 | ** rid The RID of an artifact that defines the object |
| 1229 | 1256 | ** being searched. |
| 1230 | 1257 | ** |
| 1231 | 1258 | ** zName Name of the object being searched. This is used |
| | @@ -1247,12 +1274,14 @@ |
| 1247 | 1274 | blob_to_utf8_no_bom(&doc, 0); |
| 1248 | 1275 | get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut); |
| 1249 | 1276 | blob_reset(&doc); |
| 1250 | 1277 | break; |
| 1251 | 1278 | } |
| 1279 | + case 'e': /* Tech Notes */ |
| 1252 | 1280 | case 'w': { /* Wiki */ |
| 1253 | | - Manifest *pWiki = manifest_get(rid, CFTYPE_WIKI,0); |
| 1281 | + Manifest *pWiki = manifest_get(rid, |
| 1282 | + cType == 'e' ? CFTYPE_EVENT : CFTYPE_WIKI, 0); |
| 1254 | 1283 | Blob wiki; |
| 1255 | 1284 | if( pWiki==0 ) break; |
| 1256 | 1285 | blob_init(&wiki, pWiki->zWiki, -1); |
| 1257 | 1286 | get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype), |
| 1258 | 1287 | pOut); |
| | @@ -1367,11 +1396,11 @@ |
| 1367 | 1396 | ** COMMAND: test-search-stext |
| 1368 | 1397 | ** |
| 1369 | 1398 | ** Usage: fossil test-search-stext TYPE RID NAME |
| 1370 | 1399 | ** |
| 1371 | 1400 | ** Compute the search text for document TYPE-RID whose name is NAME. |
| 1372 | | -** The TYPE is one of "c", "d", "t", or "w". The RID is the document |
| 1401 | +** The TYPE is one of "c", "d", "t", "w", or "e". The RID is the document |
| 1373 | 1402 | ** ID. The NAME is used to figure out a mimetype to use for formatting |
| 1374 | 1403 | ** the raw document text. |
| 1375 | 1404 | */ |
| 1376 | 1405 | void test_search_stext(void){ |
| 1377 | 1406 | Blob out; |
| | @@ -1484,10 +1513,14 @@ |
| 1484 | 1513 | ); |
| 1485 | 1514 | db_multi_exec( |
| 1486 | 1515 | "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" |
| 1487 | 1516 | " SELECT 't', tkt_id, 0 FROM ticket;" |
| 1488 | 1517 | ); |
| 1518 | + db_multi_exec( |
| 1519 | + "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)" |
| 1520 | + " SELECT 'e', objid, comment, 0 FROM event WHERE type='e';" |
| 1521 | + ); |
| 1489 | 1522 | } |
| 1490 | 1523 | |
| 1491 | 1524 | /* |
| 1492 | 1525 | ** The document described by cType,rid,zName is about to be added or |
| 1493 | 1526 | ** updated. If the document has already been indexed, then unindex it |
| | @@ -1508,19 +1541,19 @@ |
| 1508 | 1541 | db_multi_exec( |
| 1509 | 1542 | "REPLACE INTO ftsdocs(type,rid,name,idxed)" |
| 1510 | 1543 | " VALUES(%Q,%d,%Q,0)", |
| 1511 | 1544 | zType, rid, zName |
| 1512 | 1545 | ); |
| 1513 | | - if( cType=='w' ){ |
| 1546 | + if( cType=='w' || cType=='e' ){ |
| 1514 | 1547 | db_multi_exec( |
| 1515 | 1548 | "DELETE FROM ftsidx WHERE docid IN" |
| 1516 | | - " (SELECT rowid FROM ftsdocs WHERE type='w' AND name=%Q AND idxed)", |
| 1517 | | - zName |
| 1549 | + " (SELECT rowid FROM ftsdocs WHERE type='%c' AND name=%Q AND idxed)", |
| 1550 | + cType, zName |
| 1518 | 1551 | ); |
| 1519 | 1552 | db_multi_exec( |
| 1520 | | - "DELETE FROM ftsdocs WHERE type='w' AND name=%Q AND rid!=%d", |
| 1521 | | - zName, rid |
| 1553 | + "DELETE FROM ftsdocs WHERE type='%c' AND name=%Q AND rid!=%d", |
| 1554 | + cType, zName, rid |
| 1522 | 1555 | ); |
| 1523 | 1556 | } |
| 1524 | 1557 | } |
| 1525 | 1558 | } |
| 1526 | 1559 | |
| | @@ -1647,10 +1680,34 @@ |
| 1647 | 1680 | " tagxref.mtime" |
| 1648 | 1681 | " FROM tagxref WHERE tagxref.rid=ftsdocs.rid)" |
| 1649 | 1682 | " WHERE ftsdocs.type='w' AND NOT ftsdocs.idxed" |
| 1650 | 1683 | ); |
| 1651 | 1684 | } |
| 1685 | + |
| 1686 | +/* |
| 1687 | +** Deal with all of the unindexed 'e' terms in FTSDOCS |
| 1688 | +*/ |
| 1689 | +static void search_update_technote_index(void){ |
| 1690 | + db_multi_exec( |
| 1691 | + "INSERT INTO ftsidx(docid,title,body)" |
| 1692 | + " SELECT rowid, title('e',rid,NULL),body('e',rid,NULL) FROM ftsdocs" |
| 1693 | + " WHERE type='e' AND NOT idxed;" |
| 1694 | + ); |
| 1695 | + if( db_changes()==0 ) return; |
| 1696 | + db_multi_exec( |
| 1697 | + "UPDATE ftsdocs SET idxed=1," |
| 1698 | + " (name,label,url,mtime) = " |
| 1699 | + " (SELECT ftsdocs.name," |
| 1700 | + " 'Tech Note: '||ftsdocs.name," |
| 1701 | + " '/technote/'||substr(tag.tagname,7)," |
| 1702 | + " tagxref.mtime" |
| 1703 | + " FROM tagxref, tag USING (tagid)" |
| 1704 | + " WHERE tagxref.rid=ftsdocs.rid" |
| 1705 | + " AND tagname GLOB 'event-*')" |
| 1706 | + " WHERE ftsdocs.type='e' AND NOT ftsdocs.idxed" |
| 1707 | + ); |
| 1708 | +} |
| 1652 | 1709 | |
| 1653 | 1710 | /* |
| 1654 | 1711 | ** Deal with all of the unindexed entries in the FTSDOCS table - that |
| 1655 | 1712 | ** is to say, all the entries with FTSDOCS.IDXED=0. Add them to the |
| 1656 | 1713 | ** index. |
| | @@ -1667,10 +1724,13 @@ |
| 1667 | 1724 | search_update_ticket_index(); |
| 1668 | 1725 | } |
| 1669 | 1726 | if( srchFlags & SRCH_WIKI ){ |
| 1670 | 1727 | search_update_wiki_index(); |
| 1671 | 1728 | } |
| 1729 | + if( srchFlags & SRCH_TECHNOTE ){ |
| 1730 | + search_update_technote_index(); |
| 1731 | + } |
| 1672 | 1732 | } |
| 1673 | 1733 | |
| 1674 | 1734 | /* |
| 1675 | 1735 | ** Construct, prepopulate, and then update the full-text index. |
| 1676 | 1736 | */ |
| | @@ -1694,14 +1754,14 @@ |
| 1694 | 1754 | ** reindex Rebuild the search index. This is a no-op if |
| 1695 | 1755 | ** index search is disabled |
| 1696 | 1756 | ** |
| 1697 | 1757 | ** index (on|off) Turn the search index on or off |
| 1698 | 1758 | ** |
| 1699 | | -** enable cdtw Enable various kinds of search. c=Check-ins, |
| 1700 | | -** d=Documents, t=Tickets, w=Wiki. |
| 1759 | +** enable cdtwe Enable various kinds of search. c=Check-ins, |
| 1760 | +** d=Documents, t=Tickets, w=Wiki, e=Tech Notes. |
| 1701 | 1761 | ** |
| 1702 | | -** disable cdtw Disable various kinds of search |
| 1762 | +** disable cdtwe Disable various kinds of search |
| 1703 | 1763 | ** |
| 1704 | 1764 | ** stemmer (on|off) Turn the Porter stemmer on or off for indexed |
| 1705 | 1765 | ** search. (Unindexed search is never stemmed.) |
| 1706 | 1766 | ** |
| 1707 | 1767 | ** The current search settings are displayed after any changes are applied. |
| | @@ -1714,14 +1774,15 @@ |
| 1714 | 1774 | { 3, "disable" }, |
| 1715 | 1775 | { 4, "enable" }, |
| 1716 | 1776 | { 5, "stemmer" }, |
| 1717 | 1777 | }; |
| 1718 | 1778 | static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { |
| 1719 | | - { "search-ckin", "check-in search:", "c" }, |
| 1720 | | - { "search-doc", "document search:", "d" }, |
| 1721 | | - { "search-tkt", "ticket search:", "t" }, |
| 1722 | | - { "search-wiki", "wiki search:", "w" }, |
| 1779 | + { "search-ckin", "check-in search:", "c" }, |
| 1780 | + { "search-doc", "document search:", "d" }, |
| 1781 | + { "search-tkt", "ticket search:", "t" }, |
| 1782 | + { "search-wiki", "wiki search:", "w" }, |
| 1783 | + { "search-technote", "tech note search:", "e" }, |
| 1723 | 1784 | }; |
| 1724 | 1785 | char *zSubCmd = 0; |
| 1725 | 1786 | int i, j, n; |
| 1726 | 1787 | int iCmd = 0; |
| 1727 | 1788 | int iAction = 0; |
| | @@ -1779,21 +1840,21 @@ |
| 1779 | 1840 | search_rebuild_index(); |
| 1780 | 1841 | } |
| 1781 | 1842 | |
| 1782 | 1843 | /* Always show the status before ending */ |
| 1783 | 1844 | for(i=0; i<count(aSetng); i++){ |
| 1784 | | - fossil_print("%-16s %s\n", aSetng[i].zName, |
| 1845 | + fossil_print("%-17s %s\n", aSetng[i].zName, |
| 1785 | 1846 | db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off"); |
| 1786 | 1847 | } |
| 1787 | | - fossil_print("%-16s %s\n", "Porter stemmer:", |
| 1848 | + fossil_print("%-17s %s\n", "Porter stemmer:", |
| 1788 | 1849 | db_get_boolean("search-stemmer",0) ? "on" : "off"); |
| 1789 | 1850 | if( search_index_exists() ){ |
| 1790 | | - fossil_print("%-16s enabled\n", "full-text index:"); |
| 1791 | | - fossil_print("%-16s %d\n", "documents:", |
| 1851 | + fossil_print("%-17s enabled\n", "full-text index:"); |
| 1852 | + fossil_print("%-17s %d\n", "documents:", |
| 1792 | 1853 | db_int(0, "SELECT count(*) FROM ftsdocs")); |
| 1793 | 1854 | }else{ |
| 1794 | | - fossil_print("%-16s disabled\n", "full-text index:"); |
| 1855 | + fossil_print("%-17s disabled\n", "full-text index:"); |
| 1795 | 1856 | } |
| 1796 | 1857 | db_end_transaction(0); |
| 1797 | 1858 | } |
| 1798 | 1859 | |
| 1799 | 1860 | /* |
| 1800 | 1861 | |