Fossil SCM

Document search now works with an index. Still no configuration screens for indexed search, however. full-scan search continues to work as before.

drh 2015-02-03 00:27 UTC indexed-fts
Commit 1bad221ecb5589703a500e99b63bb7902247d343
1 file changed +92 -73
+92 -73
--- src/search.c
+++ src/search.c
@@ -45,14 +45,15 @@
4545
char *zPattern; /* The search pattern */
4646
char *zMarkBegin; /* Start of a match */
4747
char *zMarkEnd; /* End of a match */
4848
char *zMarkGap; /* A gap between two matches */
4949
unsigned fSrchFlg; /* Flags */
50
+ int iScore; /* Score of the last match attempt */
51
+ Blob snip; /* Snippet for the most recent match */
5052
};
5153
5254
#define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */
53
-#define SRCHFLG_SCORE 0x02 /* Prepend the score to each snippet */
5455
#define SRCHFLG_STATIC 0x04 /* The static gSearch object */
5556
5657
#endif
5758
5859
/*
@@ -92,10 +93,11 @@
9293
if( p ){
9394
fossil_free(p->zPattern);
9495
fossil_free(p->zMarkBegin);
9596
fossil_free(p->zMarkEnd);
9697
fossil_free(p->zMarkGap);
98
+ if( p->iScore ) blob_reset(&p->snip);
9799
memset(p, 0, sizeof(*p));
98100
if( p!=&gSearch ) fossil_free(p);
99101
}
100102
}
101103
@@ -123,10 +125,11 @@
123125
p->zPattern = z = mprintf("%s", zPattern);
124126
p->zMarkBegin = mprintf("%s", zMarkBegin);
125127
p->zMarkEnd = mprintf("%s", zMarkEnd);
126128
p->zMarkGap = mprintf("%s", zMarkGap);
127129
p->fSrchFlg = fSrchFlg;
130
+ blob_init(&p->snip, 0, 0);
128131
while( *z && p->nTerm<SEARCH_MAX_TERM ){
129132
while( *z && !ISALNUM(*z) ){ z++; }
130133
if( *z==0 ) break;
131134
p->a[p->nTerm].z = z;
132135
for(i=1; ISALNUM(z[i]); i++){}
@@ -156,24 +159,26 @@
156159
}
157160
}
158161
159162
/*
160163
** Compare a search pattern against one or more input strings which
161
-** collectively comprise a document. Return a match score. Optionally
162
-** also return a "snippet".
164
+** collectively comprise a document. Return a match score. Any
165
+** postive value means there was a match. Zero means that one or
166
+** more terms are missing.
167
+**
168
+** The score and a snippet are record for future use.
163169
**
164170
** Scoring:
165171
** * All terms must match at least once or the score is zero
166172
** * One point for each matching term
167173
** * Extra points if consecutive words of the pattern are consecutive
168174
** in the document
169175
*/
170
-static int search_score(
176
+static int search_match(
171177
Search *p, /* Search pattern and flags */
172178
int nDoc, /* Number of strings in this document */
173
- const char **azDoc, /* Text of each string */
174
- Blob *pSnip /* If not NULL: Write a snippet here */
179
+ const char **azDoc /* Text of each string */
175180
){
176181
int score; /* Final score */
177182
int i; /* Offset into current document */
178183
int ii; /* Loop counter */
179184
int j; /* Loop over search terms */
@@ -226,18 +231,17 @@
226231
/* Finished search all documents.
227232
** Every term must be seen or else the score is zero
228233
*/
229234
score = 1;
230235
for(j=0; j<p->nTerm; j++) score *= anMatch[j];
231
- if( score==0 || pSnip==0 ) return score;
236
+ blob_reset(&p->snip);
237
+ p->iScore = score;
238
+ if( score==0 ) return score;
232239
233240
234241
/* Prepare a snippet that describes the matching text.
235242
*/
236
- blob_init(pSnip, 0, 0);
237
- if( p->fSrchFlg & SRCHFLG_SCORE ) blob_appendf(pSnip, "%08x", score);
238
-
239243
while(1){
240244
int iOfst;
241245
int iTail;
242246
int iBest;
243247
for(ii=0; ii<p->nTerm && anMatch[ii]==0; ii++){}
@@ -272,11 +276,11 @@
272276
if( iOfst<0 ) iOfst = 0;
273277
while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--;
274278
while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++;
275279
for(ii=0; ii<CTX && zDoc[iTail]; ii++, iTail++){}
276280
while( ISALNUM(zDoc[iTail]) ) iTail++;
277
- if( iOfst>0 || wantGap ) blob_append(pSnip, p->zMarkGap, -1);
281
+ if( iOfst>0 || wantGap ) blob_append(&p->snip, p->zMarkGap, -1);
278282
wantGap = zDoc[iTail]!=0;
279283
zDoc += iOfst;
280284
iTail -= iOfst;
281285
282286
/* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */
@@ -285,53 +289,51 @@
285289
for(j=0; j<p->nTerm; j++){
286290
int n = p->a[j].n;
287291
if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0
288292
&& (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*')
289293
){
290
- snippet_text_append(p, pSnip, zDoc, i);
294
+ snippet_text_append(p, &p->snip, zDoc, i);
291295
zDoc += i;
292296
iTail -= i;
293
- blob_append(pSnip, p->zMarkBegin, -1);
297
+ blob_append(&p->snip, p->zMarkBegin, -1);
294298
if( p->a[j].z[n]=='*' ){
295299
while( ISALNUM(zDoc[n]) ) n++;
296300
}
297
- snippet_text_append(p, pSnip, zDoc, n);
301
+ snippet_text_append(p, &p->snip, zDoc, n);
298302
zDoc += n;
299303
iTail -= n;
300
- blob_append(pSnip, p->zMarkEnd, -1);
304
+ blob_append(&p->snip, p->zMarkEnd, -1);
301305
i = -1;
302306
break;
303307
} /* end-if */
304308
} /* end for(j) */
305309
if( j<p->nTerm ){
306310
while( ISALNUM(zDoc[i]) && i<iTail ){ i++; }
307311
}
308312
} /* end for(i) */
309
- snippet_text_append(p, pSnip, zDoc, iTail);
313
+ snippet_text_append(p, &p->snip, zDoc, iTail);
310314
}
311
- if( wantGap ) blob_append(pSnip, p->zMarkGap, -1);
315
+ if( wantGap ) blob_append(&p->snip, p->zMarkGap, -1);
312316
return score;
313317
}
314318
315319
/*
316
-** COMMAND: test-snippet
320
+** COMMAND: test-match
317321
**
318
-** Usage: fossil test-snippet SEARCHSTRING FILE1 FILE2 ...
322
+** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ...
319323
*/
320
-void test_snippet_cmd(void){
324
+void test_match_cmd(void){
321325
Search *p;
322326
int i;
323327
Blob x;
324
- Blob snip;
325328
int score;
326329
char *zDoc;
327330
int flg = 0;
328331
char *zBegin = (char*)find_option("begin",0,1);
329332
char *zEnd = (char*)find_option("end",0,1);
330333
char *zGap = (char*)find_option("gap",0,1);
331334
if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML;
332
- if( find_option("score",0,0)!=0 ) flg |= SRCHFLG_SCORE;
333335
if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC;
334336
verify_all_options();
335337
if( g.argc<4 ) usage("SEARCHSTRING FILE1...");
336338
if( zBegin==0 ) zBegin = "[[";
337339
if( zEnd==0 ) zEnd = "]]";
@@ -338,18 +340,18 @@
338340
if( zGap==0 ) zGap = " ... ";
339341
p = search_init(g.argv[2], zBegin, zEnd, zGap, flg);
340342
for(i=3; i<g.argc; i++){
341343
blob_read_from_file(&x, g.argv[i]);
342344
zDoc = blob_str(&x);
343
- score = search_score(p, 1, (const char**)&zDoc, &snip);
344
- fossil_print("%s: %d\n", g.argv[i], score);
345
+ score = search_match(p, 1, (const char**)&zDoc);
346
+ fossil_print("%s: %d\n", g.argv[i], p->iScore);
345347
blob_reset(&x);
346348
if( score ){
347
- fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&snip), '=');
348
- blob_reset(&snip);
349
+ fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&p->snip), '=');
349350
}
350351
}
352
+ search_end(p);
351353
}
352354
353355
/*
354356
** An SQL function to initialize the global search pattern:
355357
**
@@ -385,35 +387,45 @@
385387
search_end(&gSearch);
386388
}
387389
}
388390
389391
/*
390
-** This is an SQLite function that scores its input using
391
-** the pattern from the previous call to search_init().
392
+** Try to match the input text against the search parameters set up
393
+** by the previous search_init() call. Remember the results globally.
394
+** Return non-zero on a match and zero on a miss.
395
+*/
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
410
+** call to the search_match() SQL function.
392411
*/
393412
static void search_score_sqlfunc(
394413
sqlite3_context *context,
395414
int argc,
396415
sqlite3_value **argv
397416
){
398
- int isSnippet = sqlite3_user_data(context)!=0;
399
- const char **azDoc;
400
- int score;
401
- int i;
402
- Blob snip;
403
-
404
- if( gSearch.nTerm==0 ) return;
405
- azDoc = fossil_malloc( sizeof(const char*)*(argc+1) );
406
- for(i=0; i<argc; i++) azDoc[i] = (const char*)sqlite3_value_text(argv[i]);
407
- score = search_score(&gSearch, argc, azDoc, isSnippet ? &snip : 0);
408
- fossil_free((void *)azDoc);
409
- if( isSnippet ){
410
- if( score ){
411
- sqlite3_result_text(context, blob_materialize(&snip), -1, fossil_free);
412
- }
413
- }else{
414
- sqlite3_result_int(context, score);
417
+ sqlite3_result_int(context, gSearch.iScore);
418
+}
419
+static void search_snippet_sqlfunc(
420
+ sqlite3_context *context,
421
+ int argc,
422
+ sqlite3_value **argv
423
+){
424
+ if( blob_size(&gSearch.snip)>0 ){
425
+ sqlite3_result_text(context, blob_str(&gSearch.snip), -1, fossil_free);
426
+ blob_init(&gSearch.snip, 0, 0);
415427
}
416428
}
417429
418430
/*
419431
** This is an SQLite function that computes the searchable text.
@@ -451,14 +463,16 @@
451463
** do not delete the Search object.
452464
*/
453465
void search_sql_setup(sqlite3 *db){
454466
static int once = 0;
455467
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,
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,
459471
search_score_sqlfunc, 0, 0);
472
+ sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0,
473
+ search_snippet_sqlfunc, 0, 0);
460474
sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0,
461475
search_init_sqlfunc, 0, 0);
462476
sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0,
463477
search_stext_sqlfunc, 0, 0);
464478
sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0,
@@ -587,28 +601,30 @@
587601
static void search_fullscan(
588602
const char *zPattern, /* The query pattern */
589603
unsigned int srchFlags /* What to search over */
590604
){
591605
search_init(zPattern, "<b>", "</b>", " ... ",
592
- SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE);
606
+ SRCHFLG_STATIC|SRCHFLG_HTML);
593607
if( (srchFlags & SRCH_DOC)!=0 ){
594608
char *zDocGlob = db_get("doc-glob","");
595609
char *zDocBr = db_get("doc-branch","trunk");
596610
if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
597611
db_multi_exec(
598612
"CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
599613
);
600614
db_multi_exec(
601
- "INSERT INTO x(label,url,date,snip)"
615
+ "INSERT INTO x(label,url,score,date,snip)"
602616
" SELECT printf('Document: %%s',foci.filename),"
603617
" printf('%R/doc/%T/%%s',foci.filename),"
618
+ " search_score(),"
604619
" (SELECT datetime(event.mtime) FROM event"
605620
" WHERE objid=symbolic_name_to_rid('trunk')),"
606
- " fsnippet(stext('d',blob.rid,foci.filename))"
621
+ " search_snippet()"
607622
" FROM foci CROSS JOIN blob"
608623
" WHERE checkinID=symbolic_name_to_rid('trunk')"
609624
" AND blob.uuid=foci.uuid"
625
+ " AND search_match(stext('d',blob.rid,foci.filename))"
610626
" AND %z",
611627
zDocBr, glob_expr("foci.filename", zDocGlob)
612628
);
613629
}
614630
}
@@ -619,16 +635,18 @@
619635
" FROM tag, tagxref"
620636
" WHERE tag.tagname GLOB 'wiki-*'"
621637
" AND tagxref.tagid=tag.tagid"
622638
" GROUP BY 1"
623639
")"
624
- "INSERT INTO x(label,url,date,snip)"
640
+ "INSERT INTO x(label,url,score,date,snip)"
625641
" SELECT printf('Wiki: %%s',name),"
626642
" printf('%R/wiki?name=%%s',urlencode(name)),"
643
+ " search_score(),"
627644
" datetime(mtime),"
628
- " fsnippet(stext('w',rid,name))"
629
- " FROM wiki;"
645
+ " search_snippet()"
646
+ " FROM wiki"
647
+ " WHERE search_match(stext('w',rid,name));"
630648
);
631649
}
632650
if( (srchFlags & SRCH_CKIN)!=0 ){
633651
db_multi_exec(
634652
"WITH ckin(uuid,rid,mtime) AS ("
@@ -635,32 +653,33 @@
635653
" SELECT blob.uuid, event.objid, event.mtime"
636654
" FROM event, blob"
637655
" WHERE event.type='ci'"
638656
" AND blob.rid=event.objid"
639657
")"
640
- "INSERT INTO x(label,url,date,snip)"
658
+ "INSERT INTO x(label,url,score,date,snip)"
641659
" SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
642660
" printf('%R/timeline?c=%%s&n=8&y=ci',uuid),"
661
+ " search_score(),"
643662
" datetime(mtime),"
644
- " fsnippet(stext('c',rid,NULL))"
645
- " FROM ckin;"
663
+ " search_snippet()"
664
+ " FROM ckin"
665
+ " WHERE search_match(stext('c',rid,NULL));"
646666
);
647667
}
648668
if( (srchFlags & SRCH_TKT)!=0 ){
649669
db_multi_exec(
650
- "INSERT INTO x(label,url,date,snip)"
670
+ "INSERT INTO x(label,url,score, date,snip)"
651671
" SELECT printf('Ticket [%%.17s] on %%s',"
652672
"tkt_uuid,datetime(tkt_mtime)),"
653673
" printf('%R/tktview/%%.20s',tkt_uuid),"
674
+ " search_score(),"
654675
" datetime(tkt_mtime),"
655
- " fsnippet(stext('t',tkt_id,NULL))"
656
- " FROM ticket;"
676
+ " search_snippet()"
677
+ " FROM ticket"
678
+ " WHERE search_match(stext('t',tkt_id,NULL));"
657679
);
658680
}
659
- db_multi_exec(
660
- "UPDATE x SET score=substr(snip,1,8), snip=substr(snip,9)"
661
- );
662681
}
663682
664683
/*
665684
** When this routine is called, there already exists a table
666685
**
@@ -679,14 +698,14 @@
679698
"INSERT INTO x(label,url,score,date,snip) "
680699
" SELECT ftsdocs.label,"
681700
" ftsdocs.url,"
682701
" 1," /*FIX ME*/
683702
" datetime(ftsdocs.mtime),"
684
- " fsnippet(ftsidx,'<b>','</b>',' ... ')"
703
+ " snippet(ftsidx)"
685704
" FROM ftsidx, ftsdocs"
686705
" WHERE ftsidx MATCH %Q"
687
- " AND ftsdocs.id=ftsidx.docid",
706
+ " AND ftsdocs.rowid=ftsidx.docid",
688707
zPattern
689708
);
690709
}
691710
692711
@@ -981,11 +1000,11 @@
9811000
/* The schema for the full-text index
9821001
*/
9831002
static const char zFtsSchema[] =
9841003
@ -- One entry for each possible search result
9851004
@ CREATE TABLE IF NOT EXISTS "%w".ftsdocs(
986
-@ id INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid
1005
+@ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid
9871006
@ type CHAR(1), -- Type of document
9881007
@ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document
9891008
@ name TEXT, -- Additional document description
9901009
@ idxed BOOLEAN, -- True if currently in the index
9911010
@ label TEXT, -- Label to print on search results
@@ -993,11 +1012,11 @@
9931012
@ mtime DATE, -- Date when document created
9941013
@ UNIQUE(type,rid)
9951014
@ );
9961015
@ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
9971016
@ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
998
-@ SELECT id, type, rid, name, idxed, label, url, mtime,fr 5
1017
+@ SELECT rowid, type, rid, name, idxed, label, url, mtime,
9991018
@ stext(type,rid,name) AS 'stext'
10001019
@ FROM ftsdocs;
10011020
@ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
10021021
@ USING fts4(content="ftscontent", stext);
10031022
;
@@ -1066,11 +1085,11 @@
10661085
*/
10671086
void search_doc_touch(char cType, int rid, const char *zName){
10681087
if( search_index_exists() ){
10691088
db_multi_exec(
10701089
"DELETE FROM ftsidx WHERE docid IN"
1071
- " (SELECT id FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)",
1090
+ " (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)",
10721091
cType, rid
10731092
);
10741093
db_multi_exec(
10751094
"REPLACE INTO ftsdocs(type,rid,name,idxed)"
10761095
" VALUES(%Q,%d,%Q,0)",
@@ -1107,11 +1126,11 @@
11071126
" AND %z",
11081127
ckid, glob_expr("foci.filename", db_get("doc-glob",""))
11091128
);
11101129
db_multi_exec(
11111130
"DELETE FROM ftsidx WHERE docid IN"
1112
- " (SELECT id FROM ftsdocs WHERE type='d'"
1131
+ " (SELECT rowid FROM ftsdocs WHERE type='d'"
11131132
" AND rid NOT IN (SELECT rid FROM current_docs))"
11141133
);
11151134
db_multi_exec(
11161135
"DELETE FROM ftsdocs WHERE type='d'"
11171136
" AND rid NOT IN (SELECT rid FROM current_docs)"
@@ -1125,11 +1144,11 @@
11251144
" FROM current_docs",
11261145
zBrUuid, rTime
11271146
);
11281147
db_multi_exec(
11291148
"INSERT INTO ftsidx(docid,stext)"
1130
- " SELECT id, stext FROM ftscontent WHERE type='d' AND NOT idxed"
1149
+ " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed"
11311150
);
11321151
db_multi_exec(
11331152
"UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed"
11341153
);
11351154
}
@@ -1184,11 +1203,11 @@
11841203
}
11851204
case 2: { assert( fossil_strncmp(zSubCmd, "drop", n)==0 );
11861205
search_drop_index();
11871206
break;
11881207
}
1189
- case 3: { assert( fossil_strncmp(zSubCmd, "exist", n)==0 );
1208
+ case 3: { assert( fossil_strncmp(zSubCmd, "exists", n)==0 );
11901209
fossil_print("search_index_exists() = %d\n", search_index_exists());
11911210
break;
11921211
}
11931212
case 4: { assert( fossil_strncmp(zSubCmd, "fill", n)==0 );
11941213
search_fill_index();
@@ -1201,11 +1220,11 @@
12011220
break;
12021221
}
12031222
case 5: { assert( fossil_strncmp(zSubCmd, "pending", n)==0 );
12041223
Stmt q;
12051224
if( !search_index_exists() ) break;
1206
- db_prepare(&q, "SELECT id, type, rid, quote(label), url, date(mtime)"
1225
+ db_prepare(&q, "SELECT rowid, type, rid, quote(label), url, date(mtime)"
12071226
" FROM ftsdocs"
12081227
" WHERE NOT idxed");
12091228
while( db_step(&q)==SQLITE_ROW ){
12101229
const char *zUrl = db_column_text(&q,4);
12111230
if( zUrl && zUrl[0] ){
@@ -1229,11 +1248,11 @@
12291248
break;
12301249
}
12311250
case 6: { assert( fossil_strncmp(zSubCmd, "all", n)==0 );
12321251
Stmt q;
12331252
if( !search_index_exists() ) break;
1234
- db_prepare(&q, "SELECT id, type, rid, quote(name), idxed FROM ftsdocs");
1253
+ db_prepare(&q, "SELECT rowid,type,rid,quote(name),idxed FROM ftsdocs");
12351254
while( db_step(&q)==SQLITE_ROW ){
12361255
fossil_print("%6d: %s %6d %s%s\n",
12371256
db_column_int(&q, 0),
12381257
db_column_text(&q, 1),
12391258
db_column_int(&q, 2),
12401259
--- src/search.c
+++ src/search.c
@@ -45,14 +45,15 @@
45 char *zPattern; /* The search pattern */
46 char *zMarkBegin; /* Start of a match */
47 char *zMarkEnd; /* End of a match */
48 char *zMarkGap; /* A gap between two matches */
49 unsigned fSrchFlg; /* Flags */
 
 
50 };
51
52 #define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */
53 #define SRCHFLG_SCORE 0x02 /* Prepend the score to each snippet */
54 #define SRCHFLG_STATIC 0x04 /* The static gSearch object */
55
56 #endif
57
58 /*
@@ -92,10 +93,11 @@
92 if( p ){
93 fossil_free(p->zPattern);
94 fossil_free(p->zMarkBegin);
95 fossil_free(p->zMarkEnd);
96 fossil_free(p->zMarkGap);
 
97 memset(p, 0, sizeof(*p));
98 if( p!=&gSearch ) fossil_free(p);
99 }
100 }
101
@@ -123,10 +125,11 @@
123 p->zPattern = z = mprintf("%s", zPattern);
124 p->zMarkBegin = mprintf("%s", zMarkBegin);
125 p->zMarkEnd = mprintf("%s", zMarkEnd);
126 p->zMarkGap = mprintf("%s", zMarkGap);
127 p->fSrchFlg = fSrchFlg;
 
128 while( *z && p->nTerm<SEARCH_MAX_TERM ){
129 while( *z && !ISALNUM(*z) ){ z++; }
130 if( *z==0 ) break;
131 p->a[p->nTerm].z = z;
132 for(i=1; ISALNUM(z[i]); i++){}
@@ -156,24 +159,26 @@
156 }
157 }
158
159 /*
160 ** Compare a search pattern against one or more input strings which
161 ** collectively comprise a document. Return a match score. Optionally
162 ** also return a "snippet".
 
 
 
163 **
164 ** Scoring:
165 ** * All terms must match at least once or the score is zero
166 ** * One point for each matching term
167 ** * Extra points if consecutive words of the pattern are consecutive
168 ** in the document
169 */
170 static int search_score(
171 Search *p, /* Search pattern and flags */
172 int nDoc, /* Number of strings in this document */
173 const char **azDoc, /* Text of each string */
174 Blob *pSnip /* If not NULL: Write a snippet here */
175 ){
176 int score; /* Final score */
177 int i; /* Offset into current document */
178 int ii; /* Loop counter */
179 int j; /* Loop over search terms */
@@ -226,18 +231,17 @@
226 /* Finished search all documents.
227 ** Every term must be seen or else the score is zero
228 */
229 score = 1;
230 for(j=0; j<p->nTerm; j++) score *= anMatch[j];
231 if( score==0 || pSnip==0 ) return score;
 
 
232
233
234 /* Prepare a snippet that describes the matching text.
235 */
236 blob_init(pSnip, 0, 0);
237 if( p->fSrchFlg & SRCHFLG_SCORE ) blob_appendf(pSnip, "%08x", score);
238
239 while(1){
240 int iOfst;
241 int iTail;
242 int iBest;
243 for(ii=0; ii<p->nTerm && anMatch[ii]==0; ii++){}
@@ -272,11 +276,11 @@
272 if( iOfst<0 ) iOfst = 0;
273 while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--;
274 while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++;
275 for(ii=0; ii<CTX && zDoc[iTail]; ii++, iTail++){}
276 while( ISALNUM(zDoc[iTail]) ) iTail++;
277 if( iOfst>0 || wantGap ) blob_append(pSnip, p->zMarkGap, -1);
278 wantGap = zDoc[iTail]!=0;
279 zDoc += iOfst;
280 iTail -= iOfst;
281
282 /* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */
@@ -285,53 +289,51 @@
285 for(j=0; j<p->nTerm; j++){
286 int n = p->a[j].n;
287 if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0
288 && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*')
289 ){
290 snippet_text_append(p, pSnip, zDoc, i);
291 zDoc += i;
292 iTail -= i;
293 blob_append(pSnip, p->zMarkBegin, -1);
294 if( p->a[j].z[n]=='*' ){
295 while( ISALNUM(zDoc[n]) ) n++;
296 }
297 snippet_text_append(p, pSnip, zDoc, n);
298 zDoc += n;
299 iTail -= n;
300 blob_append(pSnip, p->zMarkEnd, -1);
301 i = -1;
302 break;
303 } /* end-if */
304 } /* end for(j) */
305 if( j<p->nTerm ){
306 while( ISALNUM(zDoc[i]) && i<iTail ){ i++; }
307 }
308 } /* end for(i) */
309 snippet_text_append(p, pSnip, zDoc, iTail);
310 }
311 if( wantGap ) blob_append(pSnip, p->zMarkGap, -1);
312 return score;
313 }
314
315 /*
316 ** COMMAND: test-snippet
317 **
318 ** Usage: fossil test-snippet SEARCHSTRING FILE1 FILE2 ...
319 */
320 void test_snippet_cmd(void){
321 Search *p;
322 int i;
323 Blob x;
324 Blob snip;
325 int score;
326 char *zDoc;
327 int flg = 0;
328 char *zBegin = (char*)find_option("begin",0,1);
329 char *zEnd = (char*)find_option("end",0,1);
330 char *zGap = (char*)find_option("gap",0,1);
331 if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML;
332 if( find_option("score",0,0)!=0 ) flg |= SRCHFLG_SCORE;
333 if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC;
334 verify_all_options();
335 if( g.argc<4 ) usage("SEARCHSTRING FILE1...");
336 if( zBegin==0 ) zBegin = "[[";
337 if( zEnd==0 ) zEnd = "]]";
@@ -338,18 +340,18 @@
338 if( zGap==0 ) zGap = " ... ";
339 p = search_init(g.argv[2], zBegin, zEnd, zGap, flg);
340 for(i=3; i<g.argc; i++){
341 blob_read_from_file(&x, g.argv[i]);
342 zDoc = blob_str(&x);
343 score = search_score(p, 1, (const char**)&zDoc, &snip);
344 fossil_print("%s: %d\n", g.argv[i], score);
345 blob_reset(&x);
346 if( score ){
347 fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&snip), '=');
348 blob_reset(&snip);
349 }
350 }
 
351 }
352
353 /*
354 ** An SQL function to initialize the global search pattern:
355 **
@@ -385,35 +387,45 @@
385 search_end(&gSearch);
386 }
387 }
388
389 /*
390 ** This is an SQLite function that scores its input using
391 ** the pattern from the previous call to search_init().
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392 */
393 static void search_score_sqlfunc(
394 sqlite3_context *context,
395 int argc,
396 sqlite3_value **argv
397 ){
398 int isSnippet = sqlite3_user_data(context)!=0;
399 const char **azDoc;
400 int score;
401 int i;
402 Blob snip;
403
404 if( gSearch.nTerm==0 ) return;
405 azDoc = fossil_malloc( sizeof(const char*)*(argc+1) );
406 for(i=0; i<argc; i++) azDoc[i] = (const char*)sqlite3_value_text(argv[i]);
407 score = search_score(&gSearch, argc, azDoc, isSnippet ? &snip : 0);
408 fossil_free((void *)azDoc);
409 if( isSnippet ){
410 if( score ){
411 sqlite3_result_text(context, blob_materialize(&snip), -1, fossil_free);
412 }
413 }else{
414 sqlite3_result_int(context, score);
415 }
416 }
417
418 /*
419 ** This is an SQLite function that computes the searchable text.
@@ -451,14 +463,16 @@
451 ** do not delete the Search object.
452 */
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);
464 sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0,
@@ -587,28 +601,30 @@
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)
612 );
613 }
614 }
@@ -619,16 +635,18 @@
619 " FROM tag, tagxref"
620 " WHERE tag.tagname GLOB 'wiki-*'"
621 " AND tagxref.tagid=tag.tagid"
622 " GROUP BY 1"
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(
634 "WITH ckin(uuid,rid,mtime) AS ("
@@ -635,32 +653,33 @@
635 " SELECT blob.uuid, event.objid, event.mtime"
636 " FROM event, blob"
637 " WHERE event.type='ci'"
638 " AND blob.rid=event.objid"
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(
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 **
@@ -679,14 +698,14 @@
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
@@ -981,11 +1000,11 @@
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
@@ -993,11 +1012,11 @@
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 ;
@@ -1066,11 +1085,11 @@
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)",
@@ -1107,11 +1126,11 @@
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)"
@@ -1125,11 +1144,11 @@
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 }
@@ -1184,11 +1203,11 @@
1184 }
1185 case 2: { assert( fossil_strncmp(zSubCmd, "drop", n)==0 );
1186 search_drop_index();
1187 break;
1188 }
1189 case 3: { assert( fossil_strncmp(zSubCmd, "exist", n)==0 );
1190 fossil_print("search_index_exists() = %d\n", search_index_exists());
1191 break;
1192 }
1193 case 4: { assert( fossil_strncmp(zSubCmd, "fill", n)==0 );
1194 search_fill_index();
@@ -1201,11 +1220,11 @@
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] ){
@@ -1229,11 +1248,11 @@
1229 break;
1230 }
1231 case 6: { assert( fossil_strncmp(zSubCmd, "all", n)==0 );
1232 Stmt q;
1233 if( !search_index_exists() ) break;
1234 db_prepare(&q, "SELECT id, type, rid, quote(name), idxed FROM ftsdocs");
1235 while( db_step(&q)==SQLITE_ROW ){
1236 fossil_print("%6d: %s %6d %s%s\n",
1237 db_column_int(&q, 0),
1238 db_column_text(&q, 1),
1239 db_column_int(&q, 2),
1240
--- src/search.c
+++ src/search.c
@@ -45,14 +45,15 @@
45 char *zPattern; /* The search pattern */
46 char *zMarkBegin; /* Start of a match */
47 char *zMarkEnd; /* End of a match */
48 char *zMarkGap; /* A gap between two matches */
49 unsigned fSrchFlg; /* Flags */
50 int iScore; /* Score of the last match attempt */
51 Blob snip; /* Snippet for the most recent match */
52 };
53
54 #define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */
 
55 #define SRCHFLG_STATIC 0x04 /* The static gSearch object */
56
57 #endif
58
59 /*
@@ -92,10 +93,11 @@
93 if( p ){
94 fossil_free(p->zPattern);
95 fossil_free(p->zMarkBegin);
96 fossil_free(p->zMarkEnd);
97 fossil_free(p->zMarkGap);
98 if( p->iScore ) blob_reset(&p->snip);
99 memset(p, 0, sizeof(*p));
100 if( p!=&gSearch ) fossil_free(p);
101 }
102 }
103
@@ -123,10 +125,11 @@
125 p->zPattern = z = mprintf("%s", zPattern);
126 p->zMarkBegin = mprintf("%s", zMarkBegin);
127 p->zMarkEnd = mprintf("%s", zMarkEnd);
128 p->zMarkGap = mprintf("%s", zMarkGap);
129 p->fSrchFlg = fSrchFlg;
130 blob_init(&p->snip, 0, 0);
131 while( *z && p->nTerm<SEARCH_MAX_TERM ){
132 while( *z && !ISALNUM(*z) ){ z++; }
133 if( *z==0 ) break;
134 p->a[p->nTerm].z = z;
135 for(i=1; ISALNUM(z[i]); i++){}
@@ -156,24 +159,26 @@
159 }
160 }
161
162 /*
163 ** Compare a search pattern against one or more input strings which
164 ** collectively comprise a document. Return a match score. Any
165 ** postive value means there was a match. Zero means that one or
166 ** more terms are missing.
167 **
168 ** The score and a snippet are record for future use.
169 **
170 ** Scoring:
171 ** * All terms must match at least once or the score is zero
172 ** * One point for each matching term
173 ** * Extra points if consecutive words of the pattern are consecutive
174 ** in the document
175 */
176 static int search_match(
177 Search *p, /* Search pattern and flags */
178 int nDoc, /* Number of strings in this document */
179 const char **azDoc /* Text of each string */
 
180 ){
181 int score; /* Final score */
182 int i; /* Offset into current document */
183 int ii; /* Loop counter */
184 int j; /* Loop over search terms */
@@ -226,18 +231,17 @@
231 /* Finished search all documents.
232 ** Every term must be seen or else the score is zero
233 */
234 score = 1;
235 for(j=0; j<p->nTerm; j++) score *= anMatch[j];
236 blob_reset(&p->snip);
237 p->iScore = score;
238 if( score==0 ) return score;
239
240
241 /* Prepare a snippet that describes the matching text.
242 */
 
 
 
243 while(1){
244 int iOfst;
245 int iTail;
246 int iBest;
247 for(ii=0; ii<p->nTerm && anMatch[ii]==0; ii++){}
@@ -272,11 +276,11 @@
276 if( iOfst<0 ) iOfst = 0;
277 while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--;
278 while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++;
279 for(ii=0; ii<CTX && zDoc[iTail]; ii++, iTail++){}
280 while( ISALNUM(zDoc[iTail]) ) iTail++;
281 if( iOfst>0 || wantGap ) blob_append(&p->snip, p->zMarkGap, -1);
282 wantGap = zDoc[iTail]!=0;
283 zDoc += iOfst;
284 iTail -= iOfst;
285
286 /* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */
@@ -285,53 +289,51 @@
289 for(j=0; j<p->nTerm; j++){
290 int n = p->a[j].n;
291 if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0
292 && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*')
293 ){
294 snippet_text_append(p, &p->snip, zDoc, i);
295 zDoc += i;
296 iTail -= i;
297 blob_append(&p->snip, p->zMarkBegin, -1);
298 if( p->a[j].z[n]=='*' ){
299 while( ISALNUM(zDoc[n]) ) n++;
300 }
301 snippet_text_append(p, &p->snip, zDoc, n);
302 zDoc += n;
303 iTail -= n;
304 blob_append(&p->snip, p->zMarkEnd, -1);
305 i = -1;
306 break;
307 } /* end-if */
308 } /* end for(j) */
309 if( j<p->nTerm ){
310 while( ISALNUM(zDoc[i]) && i<iTail ){ i++; }
311 }
312 } /* end for(i) */
313 snippet_text_append(p, &p->snip, zDoc, iTail);
314 }
315 if( wantGap ) blob_append(&p->snip, p->zMarkGap, -1);
316 return score;
317 }
318
319 /*
320 ** COMMAND: test-match
321 **
322 ** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ...
323 */
324 void test_match_cmd(void){
325 Search *p;
326 int i;
327 Blob x;
 
328 int score;
329 char *zDoc;
330 int flg = 0;
331 char *zBegin = (char*)find_option("begin",0,1);
332 char *zEnd = (char*)find_option("end",0,1);
333 char *zGap = (char*)find_option("gap",0,1);
334 if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML;
 
335 if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC;
336 verify_all_options();
337 if( g.argc<4 ) usage("SEARCHSTRING FILE1...");
338 if( zBegin==0 ) zBegin = "[[";
339 if( zEnd==0 ) zEnd = "]]";
@@ -338,18 +340,18 @@
340 if( zGap==0 ) zGap = " ... ";
341 p = search_init(g.argv[2], zBegin, zEnd, zGap, flg);
342 for(i=3; i<g.argc; i++){
343 blob_read_from_file(&x, g.argv[i]);
344 zDoc = blob_str(&x);
345 score = search_match(p, 1, (const char**)&zDoc);
346 fossil_print("%s: %d\n", g.argv[i], p->iScore);
347 blob_reset(&x);
348 if( score ){
349 fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&p->snip), '=');
 
350 }
351 }
352 search_end(p);
353 }
354
355 /*
356 ** An SQL function to initialize the global search pattern:
357 **
@@ -385,35 +387,45 @@
387 search_end(&gSearch);
388 }
389 }
390
391 /*
392 ** Try to match the input text against the search parameters set up
393 ** by the previous search_init() call. Remember the results globally.
394 ** Return non-zero on a match and zero on a miss.
395 */
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
410 ** call to the search_match() SQL function.
411 */
412 static void search_score_sqlfunc(
413 sqlite3_context *context,
414 int argc,
415 sqlite3_value **argv
416 ){
417 sqlite3_result_int(context, gSearch.iScore);
418 }
419 static void search_snippet_sqlfunc(
420 sqlite3_context *context,
421 int argc,
422 sqlite3_value **argv
423 ){
424 if( blob_size(&gSearch.snip)>0 ){
425 sqlite3_result_text(context, blob_str(&gSearch.snip), -1, fossil_free);
426 blob_init(&gSearch.snip, 0, 0);
 
 
 
 
 
 
 
427 }
428 }
429
430 /*
431 ** This is an SQLite function that computes the searchable text.
@@ -451,14 +463,16 @@
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,
@@ -587,28 +601,30 @@
601 static void search_fullscan(
602 const char *zPattern, /* The query pattern */
603 unsigned int srchFlags /* What to search over */
604 ){
605 search_init(zPattern, "<b>", "</b>", " ... ",
606 SRCHFLG_STATIC|SRCHFLG_HTML);
607 if( (srchFlags & SRCH_DOC)!=0 ){
608 char *zDocGlob = db_get("doc-glob","");
609 char *zDocBr = db_get("doc-branch","trunk");
610 if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
611 db_multi_exec(
612 "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
613 );
614 db_multi_exec(
615 "INSERT INTO x(label,url,score,date,snip)"
616 " SELECT printf('Document: %%s',foci.filename),"
617 " printf('%R/doc/%T/%%s',foci.filename),"
618 " search_score(),"
619 " (SELECT datetime(event.mtime) FROM event"
620 " WHERE objid=symbolic_name_to_rid('trunk')),"
621 " search_snippet()"
622 " FROM foci CROSS JOIN blob"
623 " WHERE checkinID=symbolic_name_to_rid('trunk')"
624 " AND blob.uuid=foci.uuid"
625 " AND search_match(stext('d',blob.rid,foci.filename))"
626 " AND %z",
627 zDocBr, glob_expr("foci.filename", zDocGlob)
628 );
629 }
630 }
@@ -619,16 +635,18 @@
635 " FROM tag, tagxref"
636 " WHERE tag.tagname GLOB 'wiki-*'"
637 " AND tagxref.tagid=tag.tagid"
638 " GROUP BY 1"
639 ")"
640 "INSERT INTO x(label,url,score,date,snip)"
641 " SELECT printf('Wiki: %%s',name),"
642 " printf('%R/wiki?name=%%s',urlencode(name)),"
643 " search_score(),"
644 " datetime(mtime),"
645 " search_snippet()"
646 " FROM wiki"
647 " WHERE search_match(stext('w',rid,name));"
648 );
649 }
650 if( (srchFlags & SRCH_CKIN)!=0 ){
651 db_multi_exec(
652 "WITH ckin(uuid,rid,mtime) AS ("
@@ -635,32 +653,33 @@
653 " SELECT blob.uuid, event.objid, event.mtime"
654 " FROM event, blob"
655 " WHERE event.type='ci'"
656 " AND blob.rid=event.objid"
657 ")"
658 "INSERT INTO x(label,url,score,date,snip)"
659 " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
660 " printf('%R/timeline?c=%%s&n=8&y=ci',uuid),"
661 " search_score(),"
662 " datetime(mtime),"
663 " search_snippet()"
664 " FROM ckin"
665 " WHERE search_match(stext('c',rid,NULL));"
666 );
667 }
668 if( (srchFlags & SRCH_TKT)!=0 ){
669 db_multi_exec(
670 "INSERT INTO x(label,url,score, date,snip)"
671 " SELECT printf('Ticket [%%.17s] on %%s',"
672 "tkt_uuid,datetime(tkt_mtime)),"
673 " printf('%R/tktview/%%.20s',tkt_uuid),"
674 " search_score(),"
675 " datetime(tkt_mtime),"
676 " search_snippet()"
677 " FROM ticket"
678 " WHERE search_match(stext('t',tkt_id,NULL));"
679 );
680 }
 
 
 
681 }
682
683 /*
684 ** When this routine is called, there already exists a table
685 **
@@ -679,14 +698,14 @@
698 "INSERT INTO x(label,url,score,date,snip) "
699 " SELECT ftsdocs.label,"
700 " ftsdocs.url,"
701 " 1," /*FIX ME*/
702 " datetime(ftsdocs.mtime),"
703 " snippet(ftsidx)"
704 " FROM ftsidx, ftsdocs"
705 " WHERE ftsidx MATCH %Q"
706 " AND ftsdocs.rowid=ftsidx.docid",
707 zPattern
708 );
709 }
710
711
@@ -981,11 +1000,11 @@
1000 /* The schema for the full-text index
1001 */
1002 static const char zFtsSchema[] =
1003 @ -- One entry for each possible search result
1004 @ CREATE TABLE IF NOT EXISTS "%w".ftsdocs(
1005 @ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid
1006 @ type CHAR(1), -- Type of document
1007 @ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document
1008 @ name TEXT, -- Additional document description
1009 @ idxed BOOLEAN, -- True if currently in the index
1010 @ label TEXT, -- Label to print on search results
@@ -993,11 +1012,11 @@
1012 @ mtime DATE, -- Date when document created
1013 @ UNIQUE(type,rid)
1014 @ );
1015 @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
1016 @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
1017 @ SELECT rowid, type, rid, name, idxed, label, url, mtime,
1018 @ stext(type,rid,name) AS 'stext'
1019 @ FROM ftsdocs;
1020 @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
1021 @ USING fts4(content="ftscontent", stext);
1022 ;
@@ -1066,11 +1085,11 @@
1085 */
1086 void search_doc_touch(char cType, int rid, const char *zName){
1087 if( search_index_exists() ){
1088 db_multi_exec(
1089 "DELETE FROM ftsidx WHERE docid IN"
1090 " (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)",
1091 cType, rid
1092 );
1093 db_multi_exec(
1094 "REPLACE INTO ftsdocs(type,rid,name,idxed)"
1095 " VALUES(%Q,%d,%Q,0)",
@@ -1107,11 +1126,11 @@
1126 " AND %z",
1127 ckid, glob_expr("foci.filename", db_get("doc-glob",""))
1128 );
1129 db_multi_exec(
1130 "DELETE FROM ftsidx WHERE docid IN"
1131 " (SELECT rowid FROM ftsdocs WHERE type='d'"
1132 " AND rid NOT IN (SELECT rid FROM current_docs))"
1133 );
1134 db_multi_exec(
1135 "DELETE FROM ftsdocs WHERE type='d'"
1136 " AND rid NOT IN (SELECT rid FROM current_docs)"
@@ -1125,11 +1144,11 @@
1144 " FROM current_docs",
1145 zBrUuid, rTime
1146 );
1147 db_multi_exec(
1148 "INSERT INTO ftsidx(docid,stext)"
1149 " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed"
1150 );
1151 db_multi_exec(
1152 "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed"
1153 );
1154 }
@@ -1184,11 +1203,11 @@
1203 }
1204 case 2: { assert( fossil_strncmp(zSubCmd, "drop", n)==0 );
1205 search_drop_index();
1206 break;
1207 }
1208 case 3: { assert( fossil_strncmp(zSubCmd, "exists", n)==0 );
1209 fossil_print("search_index_exists() = %d\n", search_index_exists());
1210 break;
1211 }
1212 case 4: { assert( fossil_strncmp(zSubCmd, "fill", n)==0 );
1213 search_fill_index();
@@ -1201,11 +1220,11 @@
1220 break;
1221 }
1222 case 5: { assert( fossil_strncmp(zSubCmd, "pending", n)==0 );
1223 Stmt q;
1224 if( !search_index_exists() ) break;
1225 db_prepare(&q, "SELECT rowid, type, rid, quote(label), url, date(mtime)"
1226 " FROM ftsdocs"
1227 " WHERE NOT idxed");
1228 while( db_step(&q)==SQLITE_ROW ){
1229 const char *zUrl = db_column_text(&q,4);
1230 if( zUrl && zUrl[0] ){
@@ -1229,11 +1248,11 @@
1248 break;
1249 }
1250 case 6: { assert( fossil_strncmp(zSubCmd, "all", n)==0 );
1251 Stmt q;
1252 if( !search_index_exists() ) break;
1253 db_prepare(&q, "SELECT rowid,type,rid,quote(name),idxed FROM ftsdocs");
1254 while( db_step(&q)==SQLITE_ROW ){
1255 fossil_print("%6d: %s %6d %s%s\n",
1256 db_column_int(&q, 0),
1257 db_column_text(&q, 1),
1258 db_column_int(&q, 2),
1259

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button