Fossil SCM
Show document, ticket, and wiki titles on the result page of unindexed search.
Commit
0e77f1fbc08a5f1f8acf758a7b882e85c4cde584
Parent
b552f55b1f58405…
1 file changed
+94
-18
+94
-18
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -213,11 +213,11 @@ | ||
| 213 | 213 | aiLastDoc[j] = iDoc; |
| 214 | 214 | aiLastOfst[j] = i; |
| 215 | 215 | for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} |
| 216 | 216 | for(ii=0; ii<k; ii++){ |
| 217 | 217 | if( anMatch[j-ii]<k ){ |
| 218 | - anMatch[j-ii] = k; | |
| 218 | + anMatch[j-ii] = k*(nDoc-iDoc); | |
| 219 | 219 | aiBestDoc[j-ii] = aiLastDoc[j-ii]; |
| 220 | 220 | aiBestOfst[j-ii] = aiLastOfst[j-ii]; |
| 221 | 221 | } |
| 222 | 222 | } |
| 223 | 223 | break; |
| @@ -396,14 +396,18 @@ | ||
| 396 | 396 | static void search_match_sqlfunc( |
| 397 | 397 | sqlite3_context *context, |
| 398 | 398 | int argc, |
| 399 | 399 | sqlite3_value **argv |
| 400 | 400 | ){ |
| 401 | - const char *zSText = (const char*)sqlite3_value_text(argv[0]); | |
| 401 | + const char *azDoc[5]; | |
| 402 | + int nDoc; | |
| 402 | 403 | int rc; |
| 403 | - if( zSText==0 ) return; | |
| 404 | - rc = search_match(&gSearch, 1, &zSText); | |
| 404 | + for(nDoc=0; nDoc<ArraySize(azDoc) && nDoc<argc; nDoc++){ | |
| 405 | + azDoc[nDoc] = (const char*)sqlite3_value_text(argv[nDoc]); | |
| 406 | + if( azDoc[nDoc]==0 ) azDoc[nDoc] = ""; | |
| 407 | + } | |
| 408 | + rc = search_match(&gSearch, nDoc, azDoc); | |
| 405 | 409 | sqlite3_result_int(context, rc); |
| 406 | 410 | } |
| 407 | 411 | |
| 408 | 412 | /* |
| 409 | 413 | ** These SQL functions return the results of the last |
| @@ -435,16 +439,43 @@ | ||
| 435 | 439 | static void search_stext_sqlfunc( |
| 436 | 440 | sqlite3_context *context, |
| 437 | 441 | int argc, |
| 438 | 442 | sqlite3_value **argv |
| 439 | 443 | ){ |
| 440 | - Blob txt; | |
| 444 | + const char *zType = (const char*)sqlite3_value_text(argv[0]); | |
| 445 | + int rid = sqlite3_value_int(argv[1]); | |
| 446 | + const char *zName = (const char*)sqlite3_value_text(argv[2]); | |
| 447 | + sqlite3_result_text(context, search_stext_cached(zType[0],rid,zName,0), -1, | |
| 448 | + SQLITE_TRANSIENT); | |
| 449 | +} | |
| 450 | +static void search_title_sqlfunc( | |
| 451 | + sqlite3_context *context, | |
| 452 | + int argc, | |
| 453 | + sqlite3_value **argv | |
| 454 | +){ | |
| 455 | + const char *zType = (const char*)sqlite3_value_text(argv[0]); | |
| 456 | + int rid = sqlite3_value_int(argv[1]); | |
| 457 | + const char *zName = (const char*)sqlite3_value_text(argv[2]); | |
| 458 | + int nHdr; | |
| 459 | + char *z = search_stext_cached(zType[0], rid, zName, &nHdr); | |
| 460 | + if( nHdr || zType[0]!='d' ){ | |
| 461 | + sqlite3_result_text(context, z, nHdr, SQLITE_TRANSIENT); | |
| 462 | + }else{ | |
| 463 | + sqlite3_result_value(context, argv[2]); | |
| 464 | + } | |
| 465 | +} | |
| 466 | +static void search_body_sqlfunc( | |
| 467 | + sqlite3_context *context, | |
| 468 | + int argc, | |
| 469 | + sqlite3_value **argv | |
| 470 | +){ | |
| 441 | 471 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 442 | 472 | int rid = sqlite3_value_int(argv[1]); |
| 443 | 473 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 444 | - search_stext(zType[0], rid, zName, &txt); | |
| 445 | - sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free); | |
| 474 | + int nHdr; | |
| 475 | + char *z = search_stext_cached(zType[0], rid, zName, &nHdr); | |
| 476 | + sqlite3_result_text(context, z+nHdr+1, -1, SQLITE_TRANSIENT); | |
| 446 | 477 | } |
| 447 | 478 | |
| 448 | 479 | /* |
| 449 | 480 | ** Encode a string for use as a query parameter in a URL |
| 450 | 481 | */ |
| @@ -463,20 +494,24 @@ | ||
| 463 | 494 | ** do not delete the Search object. |
| 464 | 495 | */ |
| 465 | 496 | void search_sql_setup(sqlite3 *db){ |
| 466 | 497 | static int once = 0; |
| 467 | 498 | if( once++ ) return; |
| 468 | - sqlite3_create_function(db, "search_match", 1, SQLITE_UTF8, 0, | |
| 499 | + sqlite3_create_function(db, "search_match", -1, SQLITE_UTF8, 0, | |
| 469 | 500 | search_match_sqlfunc, 0, 0); |
| 470 | 501 | sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, |
| 471 | 502 | search_score_sqlfunc, 0, 0); |
| 472 | 503 | sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, |
| 473 | 504 | search_snippet_sqlfunc, 0, 0); |
| 474 | 505 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 475 | 506 | search_init_sqlfunc, 0, 0); |
| 476 | 507 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 477 | 508 | search_stext_sqlfunc, 0, 0); |
| 509 | + sqlite3_create_function(db, "title", 3, SQLITE_UTF8, 0, | |
| 510 | + search_title_sqlfunc, 0, 0); | |
| 511 | + sqlite3_create_function(db, "body", 3, SQLITE_UTF8, 0, | |
| 512 | + search_body_sqlfunc, 0, 0); | |
| 478 | 513 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 479 | 514 | search_urlencode_sqlfunc, 0, 0); |
| 480 | 515 | } |
| 481 | 516 | |
| 482 | 517 | /* |
| @@ -617,20 +652,21 @@ | ||
| 617 | 652 | db_multi_exec( |
| 618 | 653 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 619 | 654 | ); |
| 620 | 655 | db_multi_exec( |
| 621 | 656 | "INSERT INTO x(label,url,score,date,snip)" |
| 622 | - " SELECT printf('Document: %%s',foci.filename)," | |
| 657 | + " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," | |
| 623 | 658 | " printf('/doc/%T/%%s',foci.filename)," |
| 624 | 659 | " search_score()," |
| 625 | 660 | " (SELECT datetime(event.mtime) FROM event" |
| 626 | 661 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 627 | 662 | " search_snippet()" |
| 628 | 663 | " FROM foci CROSS JOIN blob" |
| 629 | 664 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 630 | 665 | " AND blob.uuid=foci.uuid" |
| 631 | - " AND search_match(stext('d',blob.rid,foci.filename))" | |
| 666 | + " AND search_match(title('d',blob.rid,foci.filename)," | |
| 667 | + " body('d',blob.rid,foci.filename))" | |
| 632 | 668 | " AND %z", |
| 633 | 669 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| 634 | 670 | ); |
| 635 | 671 | } |
| 636 | 672 | } |
| @@ -648,11 +684,11 @@ | ||
| 648 | 684 | " printf('/wiki?name=%%s',urlencode(name))," |
| 649 | 685 | " search_score()," |
| 650 | 686 | " datetime(mtime)," |
| 651 | 687 | " search_snippet()" |
| 652 | 688 | " FROM wiki" |
| 653 | - " WHERE search_match(stext('w',rid,name));" | |
| 689 | + " WHERE search_match(title('w',rid,name),body('w',rid,name));" | |
| 654 | 690 | ); |
| 655 | 691 | } |
| 656 | 692 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 657 | 693 | db_multi_exec( |
| 658 | 694 | "WITH ckin(uuid,rid,mtime) AS (" |
| @@ -666,24 +702,24 @@ | ||
| 666 | 702 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 667 | 703 | " search_score()," |
| 668 | 704 | " datetime(mtime)," |
| 669 | 705 | " search_snippet()" |
| 670 | 706 | " FROM ckin" |
| 671 | - " WHERE search_match(stext('c',rid,NULL));" | |
| 707 | + " WHERE search_match('',body('c',rid,NULL));" | |
| 672 | 708 | ); |
| 673 | 709 | } |
| 674 | 710 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 675 | 711 | db_multi_exec( |
| 676 | 712 | "INSERT INTO x(label,url,score, date,snip)" |
| 677 | - " SELECT printf('Ticket [%%.17s] on %%s'," | |
| 678 | - "tkt_uuid,datetime(tkt_mtime))," | |
| 713 | + " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," | |
| 714 | + "datetime(tkt_mtime))," | |
| 679 | 715 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 680 | 716 | " search_score()," |
| 681 | 717 | " datetime(tkt_mtime)," |
| 682 | 718 | " search_snippet()" |
| 683 | 719 | " FROM ticket" |
| 684 | - " WHERE search_match(stext('t',tkt_id,NULL));" | |
| 720 | + " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" | |
| 685 | 721 | ); |
| 686 | 722 | } |
| 687 | 723 | } |
| 688 | 724 | |
| 689 | 725 | /* |
| @@ -1023,12 +1059,11 @@ | ||
| 1023 | 1059 | if( doc_is_embedded_html(pIn, &title) ){ |
| 1024 | 1060 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1025 | 1061 | } |
| 1026 | 1062 | html_to_plaintext(blob_str(pIn), pOut); |
| 1027 | 1063 | }else{ |
| 1028 | - *pOut = *pIn; | |
| 1029 | - blob_init(pIn, 0, 0); | |
| 1064 | + blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); | |
| 1030 | 1065 | } |
| 1031 | 1066 | blob_reset(&html); |
| 1032 | 1067 | blob_reset(&title); |
| 1033 | 1068 | } |
| 1034 | 1069 | |
| @@ -1038,11 +1073,11 @@ | ||
| 1038 | 1073 | */ |
| 1039 | 1074 | static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){ |
| 1040 | 1075 | int n = db_column_count(pQuery); |
| 1041 | 1076 | int i; |
| 1042 | 1077 | const char *zMime = 0; |
| 1043 | - if( iTitle>=0 ){ | |
| 1078 | + if( iTitle>=0 && iTitle<n ){ | |
| 1044 | 1079 | if( db_column_type(pQuery,iTitle)==SQLITE_TEXT ){ |
| 1045 | 1080 | blob_append(pAccum, db_column_text(pQuery,iTitle), -1); |
| 1046 | 1081 | } |
| 1047 | 1082 | blob_append(pAccum, "\n", 1); |
| 1048 | 1083 | } |
| @@ -1170,10 +1205,51 @@ | ||
| 1170 | 1205 | } |
| 1171 | 1206 | break; |
| 1172 | 1207 | } |
| 1173 | 1208 | } |
| 1174 | 1209 | } |
| 1210 | + | |
| 1211 | +/* | |
| 1212 | +** This routine is a wrapper around search_stext(). | |
| 1213 | +** | |
| 1214 | +** This routine looks up the search text, stores it in an internal | |
| 1215 | +** buffer, and returns a pointer to the text. Subsequent requests | |
| 1216 | +** for the same document return the same pointer. The returned pointer | |
| 1217 | +** is valid until the next invocation of this routine. Call this routine | |
| 1218 | +** with an eType of 0 to clear the cache. | |
| 1219 | +*/ | |
| 1220 | +char *search_stext_cached( | |
| 1221 | + char cType, /* Type of document */ | |
| 1222 | + int rid, /* BLOB.RID or TAG.TAGID value for document */ | |
| 1223 | + const char *zName, /* Auxiliary information */ | |
| 1224 | + int *pnTitle /* OUT: length of title in bytes excluding \n */ | |
| 1225 | +){ | |
| 1226 | + static struct { | |
| 1227 | + Blob stext; /* Cached search text */ | |
| 1228 | + char cType; /* The type */ | |
| 1229 | + int rid; /* The RID */ | |
| 1230 | + int nTitle; /* Number of bytes in title */ | |
| 1231 | + } cache; | |
| 1232 | + int i; | |
| 1233 | + char *z; | |
| 1234 | + if( cType!=cache.cType || rid!=cache.rid ){ | |
| 1235 | + if( cache.rid>0 ){ | |
| 1236 | + blob_reset(&cache.stext); | |
| 1237 | + }else{ | |
| 1238 | + blob_init(&cache.stext,0,0); | |
| 1239 | + } | |
| 1240 | + cache.cType = cType; | |
| 1241 | + cache.rid = rid; | |
| 1242 | + if( cType==0 ) return 0; | |
| 1243 | + search_stext(cType, rid, zName, &cache.stext); | |
| 1244 | + z = blob_str(&cache.stext); | |
| 1245 | + for(i=0; z[i] && z[i]!='\n'; i++){} | |
| 1246 | + cache.nTitle = i; | |
| 1247 | + } | |
| 1248 | + if( pnTitle ) *pnTitle = cache.nTitle; | |
| 1249 | + return blob_str(&cache.stext); | |
| 1250 | +} | |
| 1175 | 1251 | |
| 1176 | 1252 | /* |
| 1177 | 1253 | ** COMMAND: test-search-stext |
| 1178 | 1254 | ** |
| 1179 | 1255 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| 1180 | 1256 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -213,11 +213,11 @@ | |
| 213 | aiLastDoc[j] = iDoc; |
| 214 | aiLastOfst[j] = i; |
| 215 | for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} |
| 216 | for(ii=0; ii<k; ii++){ |
| 217 | if( anMatch[j-ii]<k ){ |
| 218 | anMatch[j-ii] = k; |
| 219 | aiBestDoc[j-ii] = aiLastDoc[j-ii]; |
| 220 | aiBestOfst[j-ii] = aiLastOfst[j-ii]; |
| 221 | } |
| 222 | } |
| 223 | break; |
| @@ -396,14 +396,18 @@ | |
| 396 | static void search_match_sqlfunc( |
| 397 | sqlite3_context *context, |
| 398 | int argc, |
| 399 | sqlite3_value **argv |
| 400 | ){ |
| 401 | const char *zSText = (const char*)sqlite3_value_text(argv[0]); |
| 402 | int rc; |
| 403 | if( zSText==0 ) return; |
| 404 | rc = search_match(&gSearch, 1, &zSText); |
| 405 | sqlite3_result_int(context, rc); |
| 406 | } |
| 407 | |
| 408 | /* |
| 409 | ** These SQL functions return the results of the last |
| @@ -435,16 +439,43 @@ | |
| 435 | static void search_stext_sqlfunc( |
| 436 | sqlite3_context *context, |
| 437 | int argc, |
| 438 | sqlite3_value **argv |
| 439 | ){ |
| 440 | Blob txt; |
| 441 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 442 | int rid = sqlite3_value_int(argv[1]); |
| 443 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 444 | search_stext(zType[0], rid, zName, &txt); |
| 445 | sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free); |
| 446 | } |
| 447 | |
| 448 | /* |
| 449 | ** Encode a string for use as a query parameter in a URL |
| 450 | */ |
| @@ -463,20 +494,24 @@ | |
| 463 | ** do not delete the Search object. |
| 464 | */ |
| 465 | void search_sql_setup(sqlite3 *db){ |
| 466 | static int once = 0; |
| 467 | if( once++ ) return; |
| 468 | sqlite3_create_function(db, "search_match", 1, SQLITE_UTF8, 0, |
| 469 | search_match_sqlfunc, 0, 0); |
| 470 | sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, |
| 471 | search_score_sqlfunc, 0, 0); |
| 472 | sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, |
| 473 | search_snippet_sqlfunc, 0, 0); |
| 474 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 475 | search_init_sqlfunc, 0, 0); |
| 476 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 477 | search_stext_sqlfunc, 0, 0); |
| 478 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 479 | search_urlencode_sqlfunc, 0, 0); |
| 480 | } |
| 481 | |
| 482 | /* |
| @@ -617,20 +652,21 @@ | |
| 617 | db_multi_exec( |
| 618 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 619 | ); |
| 620 | db_multi_exec( |
| 621 | "INSERT INTO x(label,url,score,date,snip)" |
| 622 | " SELECT printf('Document: %%s',foci.filename)," |
| 623 | " printf('/doc/%T/%%s',foci.filename)," |
| 624 | " search_score()," |
| 625 | " (SELECT datetime(event.mtime) FROM event" |
| 626 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 627 | " search_snippet()" |
| 628 | " FROM foci CROSS JOIN blob" |
| 629 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 630 | " AND blob.uuid=foci.uuid" |
| 631 | " AND search_match(stext('d',blob.rid,foci.filename))" |
| 632 | " AND %z", |
| 633 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| 634 | ); |
| 635 | } |
| 636 | } |
| @@ -648,11 +684,11 @@ | |
| 648 | " printf('/wiki?name=%%s',urlencode(name))," |
| 649 | " search_score()," |
| 650 | " datetime(mtime)," |
| 651 | " search_snippet()" |
| 652 | " FROM wiki" |
| 653 | " WHERE search_match(stext('w',rid,name));" |
| 654 | ); |
| 655 | } |
| 656 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 657 | db_multi_exec( |
| 658 | "WITH ckin(uuid,rid,mtime) AS (" |
| @@ -666,24 +702,24 @@ | |
| 666 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 667 | " search_score()," |
| 668 | " datetime(mtime)," |
| 669 | " search_snippet()" |
| 670 | " FROM ckin" |
| 671 | " WHERE search_match(stext('c',rid,NULL));" |
| 672 | ); |
| 673 | } |
| 674 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 675 | db_multi_exec( |
| 676 | "INSERT INTO x(label,url,score, date,snip)" |
| 677 | " SELECT printf('Ticket [%%.17s] on %%s'," |
| 678 | "tkt_uuid,datetime(tkt_mtime))," |
| 679 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 680 | " search_score()," |
| 681 | " datetime(tkt_mtime)," |
| 682 | " search_snippet()" |
| 683 | " FROM ticket" |
| 684 | " WHERE search_match(stext('t',tkt_id,NULL));" |
| 685 | ); |
| 686 | } |
| 687 | } |
| 688 | |
| 689 | /* |
| @@ -1023,12 +1059,11 @@ | |
| 1023 | if( doc_is_embedded_html(pIn, &title) ){ |
| 1024 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1025 | } |
| 1026 | html_to_plaintext(blob_str(pIn), pOut); |
| 1027 | }else{ |
| 1028 | *pOut = *pIn; |
| 1029 | blob_init(pIn, 0, 0); |
| 1030 | } |
| 1031 | blob_reset(&html); |
| 1032 | blob_reset(&title); |
| 1033 | } |
| 1034 | |
| @@ -1038,11 +1073,11 @@ | |
| 1038 | */ |
| 1039 | static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){ |
| 1040 | int n = db_column_count(pQuery); |
| 1041 | int i; |
| 1042 | const char *zMime = 0; |
| 1043 | if( iTitle>=0 ){ |
| 1044 | if( db_column_type(pQuery,iTitle)==SQLITE_TEXT ){ |
| 1045 | blob_append(pAccum, db_column_text(pQuery,iTitle), -1); |
| 1046 | } |
| 1047 | blob_append(pAccum, "\n", 1); |
| 1048 | } |
| @@ -1170,10 +1205,51 @@ | |
| 1170 | } |
| 1171 | break; |
| 1172 | } |
| 1173 | } |
| 1174 | } |
| 1175 | |
| 1176 | /* |
| 1177 | ** COMMAND: test-search-stext |
| 1178 | ** |
| 1179 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| 1180 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -213,11 +213,11 @@ | |
| 213 | aiLastDoc[j] = iDoc; |
| 214 | aiLastOfst[j] = i; |
| 215 | for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} |
| 216 | for(ii=0; ii<k; ii++){ |
| 217 | if( anMatch[j-ii]<k ){ |
| 218 | anMatch[j-ii] = k*(nDoc-iDoc); |
| 219 | aiBestDoc[j-ii] = aiLastDoc[j-ii]; |
| 220 | aiBestOfst[j-ii] = aiLastOfst[j-ii]; |
| 221 | } |
| 222 | } |
| 223 | break; |
| @@ -396,14 +396,18 @@ | |
| 396 | static void search_match_sqlfunc( |
| 397 | sqlite3_context *context, |
| 398 | int argc, |
| 399 | sqlite3_value **argv |
| 400 | ){ |
| 401 | const char *azDoc[5]; |
| 402 | int nDoc; |
| 403 | int rc; |
| 404 | for(nDoc=0; nDoc<ArraySize(azDoc) && nDoc<argc; nDoc++){ |
| 405 | azDoc[nDoc] = (const char*)sqlite3_value_text(argv[nDoc]); |
| 406 | if( azDoc[nDoc]==0 ) azDoc[nDoc] = ""; |
| 407 | } |
| 408 | rc = search_match(&gSearch, nDoc, azDoc); |
| 409 | sqlite3_result_int(context, rc); |
| 410 | } |
| 411 | |
| 412 | /* |
| 413 | ** These SQL functions return the results of the last |
| @@ -435,16 +439,43 @@ | |
| 439 | static void search_stext_sqlfunc( |
| 440 | sqlite3_context *context, |
| 441 | int argc, |
| 442 | sqlite3_value **argv |
| 443 | ){ |
| 444 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 445 | int rid = sqlite3_value_int(argv[1]); |
| 446 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 447 | sqlite3_result_text(context, search_stext_cached(zType[0],rid,zName,0), -1, |
| 448 | SQLITE_TRANSIENT); |
| 449 | } |
| 450 | static void search_title_sqlfunc( |
| 451 | sqlite3_context *context, |
| 452 | int argc, |
| 453 | sqlite3_value **argv |
| 454 | ){ |
| 455 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 456 | int rid = sqlite3_value_int(argv[1]); |
| 457 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 458 | int nHdr; |
| 459 | char *z = search_stext_cached(zType[0], rid, zName, &nHdr); |
| 460 | if( nHdr || zType[0]!='d' ){ |
| 461 | sqlite3_result_text(context, z, nHdr, SQLITE_TRANSIENT); |
| 462 | }else{ |
| 463 | sqlite3_result_value(context, argv[2]); |
| 464 | } |
| 465 | } |
| 466 | static void search_body_sqlfunc( |
| 467 | sqlite3_context *context, |
| 468 | int argc, |
| 469 | sqlite3_value **argv |
| 470 | ){ |
| 471 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 472 | int rid = sqlite3_value_int(argv[1]); |
| 473 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 474 | int nHdr; |
| 475 | char *z = search_stext_cached(zType[0], rid, zName, &nHdr); |
| 476 | sqlite3_result_text(context, z+nHdr+1, -1, SQLITE_TRANSIENT); |
| 477 | } |
| 478 | |
| 479 | /* |
| 480 | ** Encode a string for use as a query parameter in a URL |
| 481 | */ |
| @@ -463,20 +494,24 @@ | |
| 494 | ** do not delete the Search object. |
| 495 | */ |
| 496 | void search_sql_setup(sqlite3 *db){ |
| 497 | static int once = 0; |
| 498 | if( once++ ) return; |
| 499 | sqlite3_create_function(db, "search_match", -1, SQLITE_UTF8, 0, |
| 500 | search_match_sqlfunc, 0, 0); |
| 501 | sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, |
| 502 | search_score_sqlfunc, 0, 0); |
| 503 | sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, |
| 504 | search_snippet_sqlfunc, 0, 0); |
| 505 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 506 | search_init_sqlfunc, 0, 0); |
| 507 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 508 | search_stext_sqlfunc, 0, 0); |
| 509 | sqlite3_create_function(db, "title", 3, SQLITE_UTF8, 0, |
| 510 | search_title_sqlfunc, 0, 0); |
| 511 | sqlite3_create_function(db, "body", 3, SQLITE_UTF8, 0, |
| 512 | search_body_sqlfunc, 0, 0); |
| 513 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 514 | search_urlencode_sqlfunc, 0, 0); |
| 515 | } |
| 516 | |
| 517 | /* |
| @@ -617,20 +652,21 @@ | |
| 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')" |
| 665 | " AND blob.uuid=foci.uuid" |
| 666 | " AND search_match(title('d',blob.rid,foci.filename)," |
| 667 | " body('d',blob.rid,foci.filename))" |
| 668 | " AND %z", |
| 669 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| 670 | ); |
| 671 | } |
| 672 | } |
| @@ -648,11 +684,11 @@ | |
| 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 | ); |
| 691 | } |
| 692 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 693 | db_multi_exec( |
| 694 | "WITH ckin(uuid,rid,mtime) AS (" |
| @@ -666,24 +702,24 @@ | |
| 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 | /* |
| @@ -1023,12 +1059,11 @@ | |
| 1059 | if( doc_is_embedded_html(pIn, &title) ){ |
| 1060 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1061 | } |
| 1062 | html_to_plaintext(blob_str(pIn), pOut); |
| 1063 | }else{ |
| 1064 | blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); |
| 1065 | } |
| 1066 | blob_reset(&html); |
| 1067 | blob_reset(&title); |
| 1068 | } |
| 1069 | |
| @@ -1038,11 +1073,11 @@ | |
| 1073 | */ |
| 1074 | static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){ |
| 1075 | int n = db_column_count(pQuery); |
| 1076 | int i; |
| 1077 | const char *zMime = 0; |
| 1078 | if( iTitle>=0 && iTitle<n ){ |
| 1079 | if( db_column_type(pQuery,iTitle)==SQLITE_TEXT ){ |
| 1080 | blob_append(pAccum, db_column_text(pQuery,iTitle), -1); |
| 1081 | } |
| 1082 | blob_append(pAccum, "\n", 1); |
| 1083 | } |
| @@ -1170,10 +1205,51 @@ | |
| 1205 | } |
| 1206 | break; |
| 1207 | } |
| 1208 | } |
| 1209 | } |
| 1210 | |
| 1211 | /* |
| 1212 | ** This routine is a wrapper around search_stext(). |
| 1213 | ** |
| 1214 | ** This routine looks up the search text, stores it in an internal |
| 1215 | ** buffer, and returns a pointer to the text. Subsequent requests |
| 1216 | ** for the same document return the same pointer. The returned pointer |
| 1217 | ** is valid until the next invocation of this routine. Call this routine |
| 1218 | ** with an eType of 0 to clear the cache. |
| 1219 | */ |
| 1220 | char *search_stext_cached( |
| 1221 | char cType, /* Type of document */ |
| 1222 | int rid, /* BLOB.RID or TAG.TAGID value for document */ |
| 1223 | const char *zName, /* Auxiliary information */ |
| 1224 | int *pnTitle /* OUT: length of title in bytes excluding \n */ |
| 1225 | ){ |
| 1226 | static struct { |
| 1227 | Blob stext; /* Cached search text */ |
| 1228 | char cType; /* The type */ |
| 1229 | int rid; /* The RID */ |
| 1230 | int nTitle; /* Number of bytes in title */ |
| 1231 | } cache; |
| 1232 | int i; |
| 1233 | char *z; |
| 1234 | if( cType!=cache.cType || rid!=cache.rid ){ |
| 1235 | if( cache.rid>0 ){ |
| 1236 | blob_reset(&cache.stext); |
| 1237 | }else{ |
| 1238 | blob_init(&cache.stext,0,0); |
| 1239 | } |
| 1240 | cache.cType = cType; |
| 1241 | cache.rid = rid; |
| 1242 | if( cType==0 ) return 0; |
| 1243 | search_stext(cType, rid, zName, &cache.stext); |
| 1244 | z = blob_str(&cache.stext); |
| 1245 | for(i=0; z[i] && z[i]!='\n'; i++){} |
| 1246 | cache.nTitle = i; |
| 1247 | } |
| 1248 | if( pnTitle ) *pnTitle = cache.nTitle; |
| 1249 | return blob_str(&cache.stext); |
| 1250 | } |
| 1251 | |
| 1252 | /* |
| 1253 | ** COMMAND: test-search-stext |
| 1254 | ** |
| 1255 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| 1256 |