| | @@ -60,11 +60,11 @@ |
| 60 | 60 | if( zDocType[0]==0 || zDocType[1]==0 ) return 0; |
| 61 | 61 | zDocId = zDocType + 2; |
| 62 | 62 | } |
| 63 | 63 | id = atoi(zDocId); |
| 64 | 64 | switch( zDocType[0] ){ |
| 65 | | - case 'c': { /* A check-in comment. zDocId is the UUID */ |
| 65 | + case 'c': { /* A check-in comment. zDocId is the RID */ |
| 66 | 66 | zRes = db_text(0, |
| 67 | 67 | "SELECT coalesce(ecomment,comment) || char(10) ||" |
| 68 | 68 | " 'user: ' || coalesce(euser,user) || char(10) ||" |
| 69 | 69 | " 'branch: ' || coalesce((SELECT value FROM tagxref" |
| 70 | 70 | " WHERE tagid=%d AND tagtype>0" |
| | @@ -72,38 +72,59 @@ |
| 72 | 72 | " FROM event" |
| 73 | 73 | " WHERE event.objid=%d" |
| 74 | 74 | " AND event.type GLOB 'c*'", |
| 75 | 75 | TAG_BRANCH, id, id); |
| 76 | 76 | break; |
| 77 | + } |
| 78 | + case 'f': { /* A file with zDocId as the filename.fnid */ |
| 79 | + zRes = db_text(0, |
| 80 | + "SELECT content(mlink.fid)" |
| 81 | + " FROM filename, mlink, event" |
| 82 | + " WHERE filename.fnid=%d" |
| 83 | + " AND mlink.fnid=filename.fnid" |
| 84 | + " AND event.objid=mlink.mid" |
| 85 | + " ORDER BY event.mtime DESC LIMIT 1", |
| 86 | + id |
| 87 | + ); |
| 88 | + break; |
| 77 | 89 | } |
| 78 | 90 | default: { |
| 79 | 91 | /* No-op */ |
| 80 | 92 | } |
| 81 | 93 | } |
| 82 | 94 | return zRes; |
| 83 | 95 | } |
| 84 | 96 | |
| 85 | 97 | /* Return a human-readable description for the document described by |
| 86 | | -** the arguments. The returned text is in the Wiki format and contains |
| 87 | | -** links to the document in question. |
| 98 | +** the arguments. |
| 88 | 99 | ** |
| 89 | 100 | ** See ftsearch_content() for further information |
| 90 | 101 | */ |
| 91 | | -char *ftsearch_description(const char *zDocType, const char *zDocId){ |
| 102 | +char *ftsearch_description( |
| 103 | + const char *zDocType, |
| 104 | + const char *zDocId, |
| 105 | + int bLink /* Provide hyperlink in text if true */ |
| 106 | +){ |
| 92 | 107 | char *zRes = 0; /* The result to be returned */ |
| 93 | 108 | int id; |
| 94 | 109 | if( zDocId==0 ){ |
| 95 | 110 | if( zDocType[0]==0 || zDocType[1]==0 ) return 0; |
| 96 | 111 | zDocId = zDocType + 2; |
| 97 | 112 | } |
| 98 | 113 | id = atoi(zDocId); |
| 99 | 114 | switch( zDocType[0] ){ |
| 100 | | - case 'c': { /* A check-in comment. zDocId is the UUID */ |
| 115 | + case 'c': { /* A check-in comment. zDocId is the RID */ |
| 101 | 116 | char *zUuid = db_text("","SELECT uuid FROM blob WHERE rid=%d", id); |
| 102 | 117 | zRes = mprintf("Check-in [%S]", zUuid); |
| 103 | 118 | fossil_free(zUuid); |
| 104 | 119 | break; |
| 120 | + } |
| 121 | + case 'f': { /* A file. zDocId is the FNID */ |
| 122 | + char *zName = db_text("","SELECT name FROM filename WHERE fnid=%d",id); |
| 123 | + zRes = mprintf("File %s", zName); |
| 124 | + fossil_free(zName); |
| 125 | + break; |
| 105 | 126 | } |
| 106 | 127 | default: { |
| 107 | 128 | /* No-op */ |
| 108 | 129 | } |
| 109 | 130 | } |
| | @@ -125,11 +146,11 @@ |
| 125 | 146 | db_find_and_open_repository(0, 0); |
| 126 | 147 | verify_all_options(); |
| 127 | 148 | if( g.argc!=3 ) usage("DOCUMENTCODE"); |
| 128 | 149 | if( strlen(g.argv[2])>3 ){ |
| 129 | 150 | zContent = ftsearch_content(g.argv[2],0); |
| 130 | | - zDesc = ftsearch_description(g.argv[2],0); |
| 151 | + zDesc = ftsearch_description(g.argv[2],0,0); |
| 131 | 152 | } |
| 132 | 153 | if( zDesc ){ |
| 133 | 154 | fossil_print("Description: %s\n", zDesc); |
| 134 | 155 | fossil_free(zDesc); |
| 135 | 156 | } |
| | @@ -210,12 +231,19 @@ |
| 210 | 231 | |
| 211 | 232 | /* |
| 212 | 233 | ** Completely rebuild the ftsearch indexes from scratch |
| 213 | 234 | */ |
| 214 | 235 | void ftsearch_rebuild_all(void){ |
| 236 | + const char *zEnables; |
| 215 | 237 | db_begin_transaction(); |
| 216 | 238 | ftsearch_disable_all(); |
| 239 | + zEnables = db_get("ftsearch-index-type", "cdeftw"); |
| 240 | + |
| 241 | + /* If none of the search categories are enabled, then do not |
| 242 | + ** bother constructing the search tables |
| 243 | + */ |
| 244 | + if( sqlite3_strglob("*[cdeftw]*", zEnables) ) return; |
| 217 | 245 | |
| 218 | 246 | /* The FTSSEARCHXREF table provides a mapping between the integer |
| 219 | 247 | ** document-ids in FTS4 to the "document codes" that describe a |
| 220 | 248 | ** referenced object |
| 221 | 249 | */ |
| | @@ -242,18 +270,35 @@ |
| 242 | 270 | "CREATE VIRTUAL TABLE %s.ftsearch" |
| 243 | 271 | " USING fts4(content='ftsearchbody',body);", |
| 244 | 272 | db_name("repository") |
| 245 | 273 | ); |
| 246 | 274 | |
| 247 | | - /* Populate the FTSEARCHXREF table with references to all check-in |
| 248 | | - ** comments currently in the event table |
| 249 | | - */ |
| 250 | | - db_multi_exec( |
| 251 | | - "INSERT INTO ftsearchxref(ftsid,mtime)" |
| 252 | | - " SELECT 'c-' || objid, mtime FROM event" |
| 253 | | - " WHERE type='ci';" |
| 254 | | - ); |
| 275 | + if( strchr(zEnables, 'c')!=0 ){ |
| 276 | + /* Populate the FTSEARCHXREF table with references to all check-in |
| 277 | + ** comments currently in the event table |
| 278 | + */ |
| 279 | + db_multi_exec( |
| 280 | + "INSERT INTO ftsearchxref(ftsid,mtime)" |
| 281 | + " SELECT 'c-' || objid, mtime FROM event" |
| 282 | + " WHERE type='ci';" |
| 283 | + ); |
| 284 | + } |
| 285 | + |
| 286 | + if( strchr(zEnables, 'f')!=0 ){ |
| 287 | + /* Populate the FTSEARCHXREF table with references to all files |
| 288 | + */ |
| 289 | + db_multi_exec( |
| 290 | + "INSERT INTO ftsearchxref(ftsid,mtime)" |
| 291 | + " SELECT 'f-' || filename.fnid, max(event.mtime)" |
| 292 | + " FROM filename, mlink, event" |
| 293 | + " WHERE mlink.fnid=filename.fnid" |
| 294 | + " AND event.objid=mlink.mid" |
| 295 | + " AND %s" |
| 296 | + " GROUP BY 1", |
| 297 | + glob_expr("filename.name", db_get("search-file-glob","*")) |
| 298 | + ); |
| 299 | + } |
| 255 | 300 | |
| 256 | 301 | /* Index every document mentioned in the FTSEARCHXREF table */ |
| 257 | 302 | db_multi_exec( |
| 258 | 303 | "INSERT INTO ftsearch(docid,body)" |
| 259 | 304 | " SELECT docid, ftsearch_content(ftsid) FROM ftsearchxref;" |
| | @@ -272,11 +317,23 @@ |
| 272 | 317 | ** enable searching. |
| 273 | 318 | ** |
| 274 | 319 | ** The "search-config" is used to setup the search feature of the repository. |
| 275 | 320 | ** Subcommands are: |
| 276 | 321 | ** |
| 277 | | -** fossil search-config setting ?NAME? ?VALUE? |
| 322 | +** fossil search-config doclist |
| 323 | +** |
| 324 | +** List all the documents currently indexed |
| 325 | +** |
| 326 | +** fossil search-config rebuild |
| 327 | +** |
| 328 | +** Completely rebuild the search index. |
| 329 | +** |
| 330 | +** fossil search-config reset |
| 331 | +** |
| 332 | +** Disable search and remove the search indexes from the repository. |
| 333 | +** |
| 334 | +** fossil search-config setting NAME ?VALUE? |
| 278 | 335 | ** |
| 279 | 336 | ** Set or query a search setting. NAMES are: |
| 280 | 337 | ** file-glob Comma-separated list of GLOBs for file search |
| 281 | 338 | ** ticket-expr SQL expression to render TICKET content |
| 282 | 339 | ** ticketchng-expr SQL expression to render TICKETCHNG content |
| | @@ -293,20 +350,18 @@ |
| 293 | 350 | ** w: wiki pages |
| 294 | 351 | ** |
| 295 | 352 | ** It is necessary to run "fossil search-config rebuild" after making |
| 296 | 353 | ** setting changes in order to reconstruct the search index |
| 297 | 354 | ** |
| 298 | | -** fossil search-config rebuild |
| 299 | | -** fossil search-config optimize |
| 300 | | -** |
| 301 | | -** Completely rebuild the search index, or optimize the search index. |
| 302 | | -** |
| 303 | | -** fossil search-config reset |
| 304 | | -** |
| 305 | | -** Disable search and remove the search indexes from the repository. |
| 355 | +** fossil search-config status |
| 356 | +** |
| 357 | +** Report on the status of the search configuration. |
| 306 | 358 | */ |
| 307 | 359 | void ftsearch_cmd(void){ |
| 360 | + static const char *azSettings[] = { |
| 361 | + "file-glob", "index-type", "ticket-expr", "ticketchng-expr" |
| 362 | + }; |
| 308 | 363 | const char *zSubCmd; |
| 309 | 364 | int nSubCmd; |
| 310 | 365 | db_find_and_open_repository(0, 0); |
| 311 | 366 | verify_all_options(); |
| 312 | 367 | if( g.argc<3 ) usage("search PATTERN"); |
| | @@ -328,31 +383,90 @@ |
| 328 | 383 | fossil_fatal("search is disabled - see \"fossil help search\"" |
| 329 | 384 | " for more information"); |
| 330 | 385 | } |
| 331 | 386 | db_prepare(&q, "SELECT " |
| 332 | 387 | " snippet(ftsearch,%Q,%Q,'...')," |
| 333 | | - " ftsearchxref.ftsid" |
| 388 | + " ftsearchxref.ftsid," |
| 389 | + " date(ftsearchxref.mtime)" |
| 334 | 390 | " FROM ftsearch, ftsearchxref" |
| 335 | 391 | " WHERE ftsearch.body MATCH %Q" |
| 336 | 392 | " AND ftsearchxref.docid=ftsearch.docid" |
| 337 | | - " ORDER BY ftsearchxref.mtime DESC;", |
| 393 | + " ORDER BY ftsearchxref.mtime DESC LIMIT 50;", |
| 338 | 394 | zMark1, zMark2, zSubCmd); |
| 339 | 395 | while( db_step(&q)==SQLITE_ROW ){ |
| 340 | 396 | const char *zSnippet = db_column_text(&q,0); |
| 341 | | - char *zDesc = ftsearch_description(db_column_text(&q,1),0); |
| 397 | + char *zDesc = ftsearch_description(db_column_text(&q,1),0,0); |
| 398 | + const char *zDate = db_column_text(&q,2); |
| 342 | 399 | if( i++ > 0 ){ |
| 343 | 400 | fossil_print("----------------------------------------------------\n"); |
| 344 | 401 | } |
| 345 | | - fossil_print("%s\n%s\n", zDesc, zSnippet); |
| 402 | + fossil_print("%s (%s)\n%s\n", zDesc, zDate, zSnippet); |
| 346 | 403 | fossil_free(zDesc); |
| 347 | 404 | } |
| 348 | 405 | db_finalize(&q); |
| 349 | | - }else if( strncmp(zSubCmd, "settings", nSubCmd)==0 ){ |
| 350 | | - |
| 406 | + }else if( strncmp(zSubCmd, "doclist", nSubCmd)==0 ){ |
| 407 | + if( db_table_exists("repository","ftsearch") ){ |
| 408 | + Stmt q; |
| 409 | + db_prepare(&q, "SELECT ftsid, date(mtime) FROM ftsearchxref" |
| 410 | + " ORDER BY mtime DESC"); |
| 411 | + while( db_step(&q)==SQLITE_ROW ){ |
| 412 | + const char *zDate = db_column_text(&q,1); |
| 413 | + const char *zFtsid = db_column_text(&q,0); |
| 414 | + char *zDesc = ftsearch_description(zFtsid,0,0); |
| 415 | + fossil_print("%s (%s)\n", zDesc, zDate); |
| 416 | + fossil_free(zDesc); |
| 417 | + } |
| 418 | + db_finalize(&q); |
| 419 | + } |
| 351 | 420 | }else if( strncmp(zSubCmd, "rebuild", nSubCmd)==0 ){ |
| 352 | 421 | ftsearch_rebuild_all(); |
| 353 | 422 | }else if( strncmp(zSubCmd, "reset", nSubCmd)==0 ){ |
| 354 | 423 | ftsearch_disable_all(); |
| 355 | | - }else if( strncmp(zSubCmd, "optimize", nSubCmd)==0 ){ |
| 424 | + }else if( strncmp(zSubCmd, "settings", nSubCmd)==0 ){ |
| 425 | + const char *zName = g.argv[3]; |
| 426 | + const char *zValue = g.argc>=5 ? g.argv[4] : 0; |
| 427 | + char *zFullname; |
| 428 | + int i; |
| 429 | + if( g.argc<4 ) usage("setting NAME ?VALUE?"); |
| 430 | + for(i=0; i<count(azSettings); i++){ |
| 431 | + if( strcmp(zName, azSettings[i])==0 ) break; |
| 432 | + } |
| 433 | + if( i>=count(azSettings) ){ |
| 434 | + Blob x; |
| 435 | + blob_init(&x,0,0); |
| 436 | + for(i=0; i<count(azSettings); i++) blob_appendf(&x," %s", azSettings[i]); |
| 437 | + fossil_fatal("unknown setting \"%s\" - should be one of:%s", |
| 438 | + zName, blob_str(&x)); |
| 439 | + } |
| 440 | + zFullname = mprintf("search-%s", zName); |
| 441 | + if( zValue==0 ){ |
| 442 | + zValue = db_get(zFullname, 0); |
| 443 | + }else{ |
| 444 | + db_set(zFullname, zValue, 0); |
| 445 | + } |
| 446 | + if( zValue==0 ){ |
| 447 | + fossil_print("%s is not defined\n", zName); |
| 448 | + }else{ |
| 449 | + fossil_print("%s: %s\n", zName, zValue); |
| 450 | + } |
| 451 | + }else if( strncmp(zSubCmd, "status", nSubCmd)==0 ){ |
| 452 | + int i; |
| 453 | + fossil_print("search settings:\n"); |
| 454 | + for(i=0; i<count(azSettings); i++){ |
| 455 | + char *zFullname = mprintf("search-%s", azSettings[i]); |
| 456 | + char *zValue = db_get(zFullname, 0); |
| 457 | + if( zValue==0 ){ |
| 458 | + fossil_print(" %s is undefined\n", azSettings[i]); |
| 459 | + }else{ |
| 460 | + fossil_print(" %s: %s\n", azSettings[i], zValue); |
| 461 | + } |
| 462 | + fossil_free(zFullname); |
| 463 | + } |
| 464 | + if( db_table_exists("repository","ftsearchxref") ){ |
| 465 | + int n = db_int(0, "SELECT count(*) FROM ftsearchxref"); |
| 466 | + fossil_print("search is enabled with %d documents indexed\n", n); |
| 467 | + }else{ |
| 468 | + fossil_print("search is disabled\n"); |
| 469 | + } |
| 356 | 470 | } |
| 357 | 471 | db_end_transaction(0); |
| 358 | 472 | } |
| 359 | 473 | |