Fossil SCM

Merge updates from trunk.

mistachkin 2011-10-26 15:17 UTC tcl-integration merge
Commit 7017c87b40c921916bf947c43b0196dca1489121
+1 -1
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1
-1.19
1
+1.20
22
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 1.19
2
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 1.20
2
+2 -3
--- src/cgi.c
+++ src/cgi.c
@@ -1056,18 +1056,17 @@
10561056
cgi_setenv("HTTP_HOST", zVal);
10571057
}else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
10581058
cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
10591059
}else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
10601060
cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
1061
- }
10621061
#if 0
1063
- else if( fossil_strcmp(zFieldName,"referer:")==0 ){
1062
+ }else if( fossil_strcmp(zFieldName,"referer:")==0 ){
10641063
cgi_setenv("HTTP_REFERER", zVal);
1064
+#endif
10651065
}else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
10661066
cgi_setenv("HTTP_USER_AGENT", zVal);
10671067
}
1068
-#endif
10691068
}
10701069
10711070
cgi_init();
10721071
}
10731072
10741073
--- src/cgi.c
+++ src/cgi.c
@@ -1056,18 +1056,17 @@
1056 cgi_setenv("HTTP_HOST", zVal);
1057 }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
1058 cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
1059 }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
1060 cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
1061 }
1062 #if 0
1063 else if( fossil_strcmp(zFieldName,"referer:")==0 ){
1064 cgi_setenv("HTTP_REFERER", zVal);
 
1065 }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
1066 cgi_setenv("HTTP_USER_AGENT", zVal);
1067 }
1068 #endif
1069 }
1070
1071 cgi_init();
1072 }
1073
1074
--- src/cgi.c
+++ src/cgi.c
@@ -1056,18 +1056,17 @@
1056 cgi_setenv("HTTP_HOST", zVal);
1057 }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
1058 cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
1059 }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
1060 cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
 
1061 #if 0
1062 }else if( fossil_strcmp(zFieldName,"referer:")==0 ){
1063 cgi_setenv("HTTP_REFERER", zVal);
1064 #endif
1065 }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
1066 cgi_setenv("HTTP_USER_AGENT", zVal);
1067 }
 
1068 }
1069
1070 cgi_init();
1071 }
1072
1073
--- src/descendants.c
+++ src/descendants.c
@@ -202,11 +202,12 @@
202202
void compute_direct_ancestors(int rid, int N){
203203
Stmt ins;
204204
Stmt q;
205205
int gen = 0;
206206
db_multi_exec(
207
- "CREATE TEMP TABLE ancestor(rid INTEGER, generation INTEGER PRIMARY KEY);"
207
+ "CREATE TEMP TABLE IF NOT EXISTS ancestor(rid INTEGER, generation INTEGER PRIMARY KEY);"
208
+ "DELETE FROM ancestor;"
208209
"INSERT INTO ancestor VALUES(%d, 0);", rid
209210
);
210211
db_prepare(&ins, "INSERT INTO ancestor VALUES(:rid, :gen)");
211212
db_prepare(&q,
212213
"SELECT pid FROM plink"
213214
--- src/descendants.c
+++ src/descendants.c
@@ -202,11 +202,12 @@
202 void compute_direct_ancestors(int rid, int N){
203 Stmt ins;
204 Stmt q;
205 int gen = 0;
206 db_multi_exec(
207 "CREATE TEMP TABLE ancestor(rid INTEGER, generation INTEGER PRIMARY KEY);"
 
208 "INSERT INTO ancestor VALUES(%d, 0);", rid
209 );
210 db_prepare(&ins, "INSERT INTO ancestor VALUES(:rid, :gen)");
211 db_prepare(&q,
212 "SELECT pid FROM plink"
213
--- src/descendants.c
+++ src/descendants.c
@@ -202,11 +202,12 @@
202 void compute_direct_ancestors(int rid, int N){
203 Stmt ins;
204 Stmt q;
205 int gen = 0;
206 db_multi_exec(
207 "CREATE TEMP TABLE IF NOT EXISTS ancestor(rid INTEGER, generation INTEGER PRIMARY KEY);"
208 "DELETE FROM ancestor;"
209 "INSERT INTO ancestor VALUES(%d, 0);", rid
210 );
211 db_prepare(&ins, "INSERT INTO ancestor VALUES(:rid, :gen)");
212 db_prepare(&q,
213 "SELECT pid FROM plink"
214
+277 -11
--- src/diff.c
+++ src/diff.c
@@ -21,10 +21,22 @@
2121
#include "config.h"
2222
#include "diff.h"
2323
#include <assert.h>
2424
2525
26
+#if INTERFACE
27
+/*
28
+** Allowed flag parameters to the text_diff() and html_sbsdiff() funtions:
29
+*/
30
+#define DIFF_CONTEXT_MASK 0x0000fff /* Lines of context. Default if 0 */
31
+#define DIFF_WIDTH_MASK 0x00ff000 /* side-by-side column width */
32
+#define DIFF_IGNORE_EOLWS 0x0100000 /* Ignore end-of-line whitespace */
33
+#define DIFF_SIDEBYSIDE 0x0200000 /* Generate a side-by-side diff */
34
+#define DIFF_NEWFILE 0x0400000 /* Missing files are as empty files */
35
+
36
+#endif /* INTERFACE */
37
+
2638
/*
2739
** Maximum length of a line in a text file. (8192)
2840
*/
2941
#define LENGTH_MASK_SZ 13
3042
#define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1)
@@ -285,10 +297,196 @@
285297
for(j=0; j<m; j++){
286298
appendDiffLine(pOut, " ", &B[b+j]);
287299
}
288300
}
289301
}
302
+
303
+/*
304
+** Write a 6-digit line number into the buffer z[]. z[] is guaranteed to
305
+** have space for at least 7 characters.
306
+*/
307
+static void sbsWriteLineno(char *z, int ln){
308
+ sqlite3_snprintf(7, z, "%6d", ln+1);
309
+ z[6] = ' ';
310
+}
311
+
312
+/*
313
+** Write up to width characters of pLine into z[]. Translate tabs into
314
+** spaces. If trunc is true, then append \n\000 after the last character
315
+** written.
316
+*/
317
+static int sbsWriteText(char *z, DLine *pLine, int width, int trunc){
318
+ int n = pLine->h & LENGTH_MASK;
319
+ int i, j;
320
+ const char *zIn = pLine->z;
321
+ for(i=j=0; i<n && j<width; i++){
322
+ char c = zIn[i];
323
+ if( c=='\t' ){
324
+ z[j++] = ' ';
325
+ while( (j&7)!=0 && j<width ) z[j++] = ' ';
326
+ }else if( c=='\r' || c=='\f' ){
327
+ z[j++] = ' ';
328
+ }else{
329
+ z[j++] = c;
330
+ }
331
+ }
332
+ if( trunc ){
333
+ z[j++] = '\n';
334
+ z[j] = 0;
335
+ }
336
+ return j;
337
+}
338
+
339
+
340
+/*
341
+** Given a diff context in which the aEdit[] array has been filled
342
+** in, compute a side-by-side diff into pOut.
343
+*/
344
+static void sbsDiff(DContext *p, Blob *pOut, int nContext, int width){
345
+ DLine *A; /* Left side of the diff */
346
+ DLine *B; /* Right side of the diff */
347
+ int a = 0; /* Index of next line in A[] */
348
+ int b = 0; /* Index of next line in B[] */
349
+ int *R; /* Array of COPY/DELETE/INSERT triples */
350
+ int r; /* Index into R[] */
351
+ int nr; /* Number of COPY/DELETE/INSERT triples to process */
352
+ int mxr; /* Maximum value for r */
353
+ int na, nb; /* Number of lines shown from A and B */
354
+ int i, j; /* Loop counters */
355
+ int m, ma, mb;/* Number of lines to output */
356
+ int skip; /* Number of lines to skip */
357
+ int mxLine; /* Length of a line of text */
358
+ char *zLine; /* A line of text being formatted */
359
+ int len; /* Length of an output line */
360
+
361
+ mxLine = width*2 + 2*7 + 3 + 1;
362
+ zLine = fossil_malloc( mxLine + 1 );
363
+ if( zLine==0 ) return;
364
+ zLine[mxLine] = 0;
365
+ A = p->aFrom;
366
+ B = p->aTo;
367
+ R = p->aEdit;
368
+ mxr = p->nEdit;
369
+ while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
370
+ for(r=0; r<mxr; r += 3*nr){
371
+ /* Figure out how many triples to show in a single block */
372
+ for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
373
+ /* printf("r=%d nr=%d\n", r, nr); */
374
+
375
+ /* For the current block comprising nr triples, figure out
376
+ ** how many lines of A and B are to be displayed
377
+ */
378
+ if( R[r]>nContext ){
379
+ na = nb = nContext;
380
+ skip = R[r] - nContext;
381
+ }else{
382
+ na = nb = R[r];
383
+ skip = 0;
384
+ }
385
+ for(i=0; i<nr; i++){
386
+ na += R[r+i*3+1];
387
+ nb += R[r+i*3+2];
388
+ }
389
+ if( R[r+nr*3]>nContext ){
390
+ na += nContext;
391
+ nb += nContext;
392
+ }else{
393
+ na += R[r+nr*3];
394
+ nb += R[r+nr*3];
395
+ }
396
+ for(i=1; i<nr; i++){
397
+ na += R[r+i*3];
398
+ nb += R[r+i*3];
399
+ }
400
+ /*
401
+ * If the patch changes an empty file or results in an empty file,
402
+ * the block header must use 0,0 as position indicator and not 1,0.
403
+ * Otherwise, patch would be confused and may reject the diff.
404
+ */
405
+ if( r>0 ) blob_appendf(pOut,"%.*c\n", width*2+16, '.');
406
+
407
+ /* Show the initial common area */
408
+ a += skip;
409
+ b += skip;
410
+ m = R[r] - skip;
411
+ for(j=0; j<m; j++){
412
+ memset(zLine, ' ', mxLine);
413
+ sbsWriteLineno(zLine, a+j);
414
+ sbsWriteText(&zLine[7], &A[a+j], width, 0);
415
+ sbsWriteLineno(&zLine[width+10], b+j);
416
+ len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
417
+ blob_append(pOut, zLine, len+width+17);
418
+ }
419
+ a += m;
420
+ b += m;
421
+
422
+ /* Show the differences */
423
+ for(i=0; i<nr; i++){
424
+ ma = R[r+i*3+1];
425
+ mb = R[r+i*3+2];
426
+ m = ma<mb ? ma : mb;
427
+ for(j=0; j<m; j++){
428
+ memset(zLine, ' ', mxLine);
429
+ sbsWriteLineno(zLine, a+j);
430
+ sbsWriteText(&zLine[7], &A[a+j], width, 0);
431
+ zLine[width+8] = '|';
432
+ sbsWriteLineno(&zLine[width+10], b+j);
433
+ len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
434
+ blob_append(pOut, zLine, len+width+17);
435
+ }
436
+ a += m;
437
+ b += m;
438
+ ma -= m;
439
+ mb -= m;
440
+ for(j=0; j<ma; j++){
441
+ memset(zLine, ' ', width+7);
442
+ sbsWriteLineno(zLine, a+j);
443
+ sbsWriteText(&zLine[7], &A[a+j], width, 0);
444
+ zLine[width+8] = '<';
445
+ zLine[width+9] = '\n';
446
+ zLine[width+10] = 0;
447
+ blob_append(pOut, zLine, width+10);
448
+ }
449
+ a += ma;
450
+ for(j=0; j<mb; j++){
451
+ memset(zLine, ' ', mxLine);
452
+ zLine[width+8] = '>';
453
+ sbsWriteLineno(&zLine[width+10], b+j);
454
+ len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
455
+ blob_append(pOut, zLine, len+width+17);
456
+ }
457
+ b += mb;
458
+ if( i<nr-1 ){
459
+ m = R[r+i*3+3];
460
+ for(j=0; j<m; j++){
461
+ memset(zLine, ' ', mxLine);
462
+ sbsWriteLineno(zLine, a+j);
463
+ sbsWriteText(&zLine[7], &A[a+j], width, 0);
464
+ sbsWriteLineno(&zLine[width+10], b+j);
465
+ len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
466
+ blob_append(pOut, zLine, len+width+17);
467
+ }
468
+ b += m;
469
+ a += m;
470
+ }
471
+ }
472
+
473
+ /* Show the final common area */
474
+ assert( nr==i );
475
+ m = R[r+nr*3];
476
+ if( m>nContext ) m = nContext;
477
+ for(j=0; j<m; j++){
478
+ memset(zLine, ' ', mxLine);
479
+ sbsWriteLineno(zLine, a+j);
480
+ sbsWriteText(&zLine[7], &A[a+j], width, 0);
481
+ sbsWriteLineno(&zLine[width+10], b+j);
482
+ len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
483
+ blob_append(pOut, zLine, len+width+17);
484
+ }
485
+ }
486
+ free(zLine);
487
+}
290488
291489
/*
292490
** Compute the optimal longest common subsequence (LCS) using an
293491
** exhaustive search. This version of the LCS is only used for
294492
** shorter input strings since runtime is O(N*N) where N is the
@@ -520,10 +718,30 @@
520718
p->aEdit[p->nEdit++] = 0;
521719
p->aEdit[p->nEdit++] = 0;
522720
p->aEdit[p->nEdit++] = 0;
523721
}
524722
}
723
+
724
+/*
725
+** Extract the number of lines of context from diffFlags. Supply an
726
+** appropriate default if no context width is specified.
727
+*/
728
+int diff_context_lines(int diffFlags){
729
+ int n = diffFlags & DIFF_CONTEXT_MASK;
730
+ if( n==0 ) n = 5;
731
+ return n;
732
+}
733
+
734
+/*
735
+** Extract the width of columns for side-by-side diff. Supply an
736
+** appropriate default if no width is given.
737
+*/
738
+int diff_width(int diffFlags){
739
+ int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
740
+ if( w==0 ) w = 80;
741
+ return w;
742
+}
525743
526744
/*
527745
** Generate a report of the differences between files pA and pB.
528746
** If pOut is not NULL then a unified diff is appended there. It
529747
** is assumed that pOut has already been initialized. If pOut is
@@ -538,16 +756,20 @@
538756
** text "cannot compute difference between binary files".
539757
*/
540758
int *text_diff(
541759
Blob *pA_Blob, /* FROM file */
542760
Blob *pB_Blob, /* TO file */
543
- Blob *pOut, /* Write unified diff here if not NULL */
544
- int nContext, /* Amount of context to unified diff */
545
- int ignoreEolWs /* Ignore whitespace at the end of lines */
761
+ Blob *pOut, /* Write diff here if not NULL */
762
+ int diffFlags /* DIFF_* flags defined above */
546763
){
764
+ int ignoreEolWs; /* Ignore whitespace at the end of lines */
765
+ int nContext; /* Amount of context to display */
547766
DContext c;
548
-
767
+
768
+ nContext = diff_context_lines(diffFlags);
769
+ ignoreEolWs = (diffFlags & DIFF_IGNORE_EOLWS)!=0;
770
+
549771
/* Prepare the input files */
550772
memset(&c, 0, sizeof(c));
551773
c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
552774
&c.nFrom, ignoreEolWs);
553775
c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
@@ -563,12 +785,17 @@
563785
564786
/* Compute the difference */
565787
diff_all(&c);
566788
567789
if( pOut ){
568
- /* Compute a context diff if requested */
569
- contextDiff(&c, pOut, nContext);
790
+ /* Compute a context or side-by-side diff into pOut */
791
+ if( diffFlags & DIFF_SIDEBYSIDE ){
792
+ int width = diff_width(diffFlags);
793
+ sbsDiff(&c, pOut, nContext, width);
794
+ }else{
795
+ contextDiff(&c, pOut, nContext);
796
+ }
570797
free(c.aFrom);
571798
free(c.aTo);
572799
free(c.aEdit);
573800
return 0;
574801
}else{
@@ -588,11 +815,11 @@
588815
static char *copylimline(char *out, DLine *dl, int lim){
589816
int len;
590817
len = dl->h & LENGTH_MASK;
591818
if( lim && len > lim ){
592819
memcpy(out, dl->z, lim-3);
593
- strcpy(&out[lim-3], "...");
820
+ memcpy(&out[lim-3], "...", 4);
594821
}else{
595822
memcpy(out, dl->z, len);
596823
out[len] = '\0';
597824
}
598825
return out;
@@ -660,11 +887,11 @@
660887
while( i<c.nEdit ){
661888
int j;
662889
/* Copied lines */
663890
for( j=0; j<c.aEdit[i]; j++){
664891
/* Hide lines which are copied and are further away from block boundaries
665
- ** than nConext lines. For each block with hidden lines, show a row
892
+ ** than nContext lines. For each block with hidden lines, show a row
666893
** notifying the user about the hidden rows.
667894
*/
668895
if( j<nContext || j>c.aEdit[i]-nContext-1 ){
669896
@ <tr>
670897
}else if( j==nContext && j<c.aEdit[i]-nContext-1 ){
@@ -776,29 +1003,58 @@
7761003
if( g.argc<4 ) usage("FILE1 FILE2 ...");
7771004
blob_read_from_file(&a, g.argv[2]);
7781005
for(i=3; i<g.argc; i++){
7791006
if( i>3 ) fossil_print("-------------------------------\n");
7801007
blob_read_from_file(&b, g.argv[i]);
781
- R = text_diff(&a, &b, 0, 0, 0);
1008
+ R = text_diff(&a, &b, 0, 0);
7821009
for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
7831010
fossil_print(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]);
7841011
}
7851012
/* free(R); */
7861013
blob_reset(&b);
7871014
}
7881015
}
1016
+
1017
+/*
1018
+** Process diff-related command-line options and return an appropriate
1019
+** "diffFlags" integer.
1020
+**
1021
+** --side-by-side|-y Side-by-side diff. DIFF_SIDEBYSIDE
1022
+** --context|-c N N lines of context. DIFF_CONTEXT_MASK
1023
+** --width|-W N N character lines. DIFF_WIDTH_MASK
1024
+*/
1025
+int diff_options(void){
1026
+ int diffFlags = 0;
1027
+ const char *z;
1028
+ int f;
1029
+ if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
1030
+ if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>0 ){
1031
+ if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK;
1032
+ diffFlags |= f;
1033
+ }
1034
+ if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
1035
+ f *= DIFF_CONTEXT_MASK+1;
1036
+ if( f > DIFF_WIDTH_MASK ) f = DIFF_CONTEXT_MASK;
1037
+ diffFlags |= f;
1038
+ }
1039
+ return diffFlags;
1040
+}
7891041
7901042
/*
7911043
** COMMAND: test-udiff
1044
+**
1045
+** Print the difference between two files. The usual diff options apply.
7921046
*/
7931047
void test_udiff_cmd(void){
7941048
Blob a, b, out;
1049
+ int diffFlag = diff_options();
1050
+
7951051
if( g.argc!=4 ) usage("FILE1 FILE2");
7961052
blob_read_from_file(&a, g.argv[2]);
7971053
blob_read_from_file(&b, g.argv[3]);
7981054
blob_zero(&out);
799
- text_diff(&a, &b, &out, 3, 0);
1055
+ text_diff(&a, &b, &out, diffFlag);
8001056
blob_write_to_file(&out, "-");
8011057
}
8021058
8031059
/**************************************************************************
8041060
** The basic difference engine is above. What follows is the annotation
@@ -1068,10 +1324,11 @@
10681324
*/
10691325
void annotate_cmd(void){
10701326
int fnid; /* Filename ID */
10711327
int fid; /* File instance ID */
10721328
int mid; /* Manifest where file was checked in */
1329
+ int cid; /* Checkout ID */
10731330
Blob treename; /* FILENAME translated to canonical form */
10741331
char *zFilename; /* Cannonical filename */
10751332
Annotator ann; /* The annotation of the file */
10761333
int i; /* Loop counter */
10771334
const char *zLimit; /* The value to the --limit option */
@@ -1097,11 +1354,20 @@
10971354
}
10981355
fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename);
10991356
if( fid==0 ){
11001357
fossil_fatal("not part of current checkout: %s", zFilename);
11011358
}
1102
- mid = db_int(0, "SELECT mid FROM mlink WHERE fid=%d AND fnid=%d", fid, fnid);
1359
+ cid = db_lget_int("checkout", 0);
1360
+ if (cid == 0){
1361
+ fossil_fatal("Not in a checkout");
1362
+ }
1363
+ if( iLimit<=0 ) iLimit = 1000000000;
1364
+ compute_direct_ancestors(cid, iLimit);
1365
+ mid = db_int(0, "SELECT mlink.mid FROM mlink, ancestor "
1366
+ " WHERE mlink.fid=%d AND mlink.fnid=%d AND mlink.mid=ancestor.rid"
1367
+ " ORDER BY ancestor.generation ASC LIMIT 1",
1368
+ fid, fnid);
11031369
if( mid==0 ){
11041370
fossil_panic("unable to find manifest");
11051371
}
11061372
if( fileVers ) annFlags |= ANN_FILE_VERS;
11071373
annotate_file(&ann, fnid, mid, 0, iLimit, annFlags);
11081374
--- src/diff.c
+++ src/diff.c
@@ -21,10 +21,22 @@
21 #include "config.h"
22 #include "diff.h"
23 #include <assert.h>
24
25
 
 
 
 
 
 
 
 
 
 
 
 
26 /*
27 ** Maximum length of a line in a text file. (8192)
28 */
29 #define LENGTH_MASK_SZ 13
30 #define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1)
@@ -285,10 +297,196 @@
285 for(j=0; j<m; j++){
286 appendDiffLine(pOut, " ", &B[b+j]);
287 }
288 }
289 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
291 /*
292 ** Compute the optimal longest common subsequence (LCS) using an
293 ** exhaustive search. This version of the LCS is only used for
294 ** shorter input strings since runtime is O(N*N) where N is the
@@ -520,10 +718,30 @@
520 p->aEdit[p->nEdit++] = 0;
521 p->aEdit[p->nEdit++] = 0;
522 p->aEdit[p->nEdit++] = 0;
523 }
524 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
526 /*
527 ** Generate a report of the differences between files pA and pB.
528 ** If pOut is not NULL then a unified diff is appended there. It
529 ** is assumed that pOut has already been initialized. If pOut is
@@ -538,16 +756,20 @@
538 ** text "cannot compute difference between binary files".
539 */
540 int *text_diff(
541 Blob *pA_Blob, /* FROM file */
542 Blob *pB_Blob, /* TO file */
543 Blob *pOut, /* Write unified diff here if not NULL */
544 int nContext, /* Amount of context to unified diff */
545 int ignoreEolWs /* Ignore whitespace at the end of lines */
546 ){
 
 
547 DContext c;
548
 
 
 
549 /* Prepare the input files */
550 memset(&c, 0, sizeof(c));
551 c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
552 &c.nFrom, ignoreEolWs);
553 c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
@@ -563,12 +785,17 @@
563
564 /* Compute the difference */
565 diff_all(&c);
566
567 if( pOut ){
568 /* Compute a context diff if requested */
569 contextDiff(&c, pOut, nContext);
 
 
 
 
 
570 free(c.aFrom);
571 free(c.aTo);
572 free(c.aEdit);
573 return 0;
574 }else{
@@ -588,11 +815,11 @@
588 static char *copylimline(char *out, DLine *dl, int lim){
589 int len;
590 len = dl->h & LENGTH_MASK;
591 if( lim && len > lim ){
592 memcpy(out, dl->z, lim-3);
593 strcpy(&out[lim-3], "...");
594 }else{
595 memcpy(out, dl->z, len);
596 out[len] = '\0';
597 }
598 return out;
@@ -660,11 +887,11 @@
660 while( i<c.nEdit ){
661 int j;
662 /* Copied lines */
663 for( j=0; j<c.aEdit[i]; j++){
664 /* Hide lines which are copied and are further away from block boundaries
665 ** than nConext lines. For each block with hidden lines, show a row
666 ** notifying the user about the hidden rows.
667 */
668 if( j<nContext || j>c.aEdit[i]-nContext-1 ){
669 @ <tr>
670 }else if( j==nContext && j<c.aEdit[i]-nContext-1 ){
@@ -776,29 +1003,58 @@
776 if( g.argc<4 ) usage("FILE1 FILE2 ...");
777 blob_read_from_file(&a, g.argv[2]);
778 for(i=3; i<g.argc; i++){
779 if( i>3 ) fossil_print("-------------------------------\n");
780 blob_read_from_file(&b, g.argv[i]);
781 R = text_diff(&a, &b, 0, 0, 0);
782 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
783 fossil_print(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]);
784 }
785 /* free(R); */
786 blob_reset(&b);
787 }
788 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
790 /*
791 ** COMMAND: test-udiff
 
 
792 */
793 void test_udiff_cmd(void){
794 Blob a, b, out;
 
 
795 if( g.argc!=4 ) usage("FILE1 FILE2");
796 blob_read_from_file(&a, g.argv[2]);
797 blob_read_from_file(&b, g.argv[3]);
798 blob_zero(&out);
799 text_diff(&a, &b, &out, 3, 0);
800 blob_write_to_file(&out, "-");
801 }
802
803 /**************************************************************************
804 ** The basic difference engine is above. What follows is the annotation
@@ -1068,10 +1324,11 @@
1068 */
1069 void annotate_cmd(void){
1070 int fnid; /* Filename ID */
1071 int fid; /* File instance ID */
1072 int mid; /* Manifest where file was checked in */
 
1073 Blob treename; /* FILENAME translated to canonical form */
1074 char *zFilename; /* Cannonical filename */
1075 Annotator ann; /* The annotation of the file */
1076 int i; /* Loop counter */
1077 const char *zLimit; /* The value to the --limit option */
@@ -1097,11 +1354,20 @@
1097 }
1098 fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename);
1099 if( fid==0 ){
1100 fossil_fatal("not part of current checkout: %s", zFilename);
1101 }
1102 mid = db_int(0, "SELECT mid FROM mlink WHERE fid=%d AND fnid=%d", fid, fnid);
 
 
 
 
 
 
 
 
 
1103 if( mid==0 ){
1104 fossil_panic("unable to find manifest");
1105 }
1106 if( fileVers ) annFlags |= ANN_FILE_VERS;
1107 annotate_file(&ann, fnid, mid, 0, iLimit, annFlags);
1108
--- src/diff.c
+++ src/diff.c
@@ -21,10 +21,22 @@
21 #include "config.h"
22 #include "diff.h"
23 #include <assert.h>
24
25
26 #if INTERFACE
27 /*
28 ** Allowed flag parameters to the text_diff() and html_sbsdiff() funtions:
29 */
30 #define DIFF_CONTEXT_MASK 0x0000fff /* Lines of context. Default if 0 */
31 #define DIFF_WIDTH_MASK 0x00ff000 /* side-by-side column width */
32 #define DIFF_IGNORE_EOLWS 0x0100000 /* Ignore end-of-line whitespace */
33 #define DIFF_SIDEBYSIDE 0x0200000 /* Generate a side-by-side diff */
34 #define DIFF_NEWFILE 0x0400000 /* Missing files are as empty files */
35
36 #endif /* INTERFACE */
37
38 /*
39 ** Maximum length of a line in a text file. (8192)
40 */
41 #define LENGTH_MASK_SZ 13
42 #define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1)
@@ -285,10 +297,196 @@
297 for(j=0; j<m; j++){
298 appendDiffLine(pOut, " ", &B[b+j]);
299 }
300 }
301 }
302
303 /*
304 ** Write a 6-digit line number into the buffer z[]. z[] is guaranteed to
305 ** have space for at least 7 characters.
306 */
307 static void sbsWriteLineno(char *z, int ln){
308 sqlite3_snprintf(7, z, "%6d", ln+1);
309 z[6] = ' ';
310 }
311
312 /*
313 ** Write up to width characters of pLine into z[]. Translate tabs into
314 ** spaces. If trunc is true, then append \n\000 after the last character
315 ** written.
316 */
317 static int sbsWriteText(char *z, DLine *pLine, int width, int trunc){
318 int n = pLine->h & LENGTH_MASK;
319 int i, j;
320 const char *zIn = pLine->z;
321 for(i=j=0; i<n && j<width; i++){
322 char c = zIn[i];
323 if( c=='\t' ){
324 z[j++] = ' ';
325 while( (j&7)!=0 && j<width ) z[j++] = ' ';
326 }else if( c=='\r' || c=='\f' ){
327 z[j++] = ' ';
328 }else{
329 z[j++] = c;
330 }
331 }
332 if( trunc ){
333 z[j++] = '\n';
334 z[j] = 0;
335 }
336 return j;
337 }
338
339
340 /*
341 ** Given a diff context in which the aEdit[] array has been filled
342 ** in, compute a side-by-side diff into pOut.
343 */
344 static void sbsDiff(DContext *p, Blob *pOut, int nContext, int width){
345 DLine *A; /* Left side of the diff */
346 DLine *B; /* Right side of the diff */
347 int a = 0; /* Index of next line in A[] */
348 int b = 0; /* Index of next line in B[] */
349 int *R; /* Array of COPY/DELETE/INSERT triples */
350 int r; /* Index into R[] */
351 int nr; /* Number of COPY/DELETE/INSERT triples to process */
352 int mxr; /* Maximum value for r */
353 int na, nb; /* Number of lines shown from A and B */
354 int i, j; /* Loop counters */
355 int m, ma, mb;/* Number of lines to output */
356 int skip; /* Number of lines to skip */
357 int mxLine; /* Length of a line of text */
358 char *zLine; /* A line of text being formatted */
359 int len; /* Length of an output line */
360
361 mxLine = width*2 + 2*7 + 3 + 1;
362 zLine = fossil_malloc( mxLine + 1 );
363 if( zLine==0 ) return;
364 zLine[mxLine] = 0;
365 A = p->aFrom;
366 B = p->aTo;
367 R = p->aEdit;
368 mxr = p->nEdit;
369 while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
370 for(r=0; r<mxr; r += 3*nr){
371 /* Figure out how many triples to show in a single block */
372 for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
373 /* printf("r=%d nr=%d\n", r, nr); */
374
375 /* For the current block comprising nr triples, figure out
376 ** how many lines of A and B are to be displayed
377 */
378 if( R[r]>nContext ){
379 na = nb = nContext;
380 skip = R[r] - nContext;
381 }else{
382 na = nb = R[r];
383 skip = 0;
384 }
385 for(i=0; i<nr; i++){
386 na += R[r+i*3+1];
387 nb += R[r+i*3+2];
388 }
389 if( R[r+nr*3]>nContext ){
390 na += nContext;
391 nb += nContext;
392 }else{
393 na += R[r+nr*3];
394 nb += R[r+nr*3];
395 }
396 for(i=1; i<nr; i++){
397 na += R[r+i*3];
398 nb += R[r+i*3];
399 }
400 /*
401 * If the patch changes an empty file or results in an empty file,
402 * the block header must use 0,0 as position indicator and not 1,0.
403 * Otherwise, patch would be confused and may reject the diff.
404 */
405 if( r>0 ) blob_appendf(pOut,"%.*c\n", width*2+16, '.');
406
407 /* Show the initial common area */
408 a += skip;
409 b += skip;
410 m = R[r] - skip;
411 for(j=0; j<m; j++){
412 memset(zLine, ' ', mxLine);
413 sbsWriteLineno(zLine, a+j);
414 sbsWriteText(&zLine[7], &A[a+j], width, 0);
415 sbsWriteLineno(&zLine[width+10], b+j);
416 len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
417 blob_append(pOut, zLine, len+width+17);
418 }
419 a += m;
420 b += m;
421
422 /* Show the differences */
423 for(i=0; i<nr; i++){
424 ma = R[r+i*3+1];
425 mb = R[r+i*3+2];
426 m = ma<mb ? ma : mb;
427 for(j=0; j<m; j++){
428 memset(zLine, ' ', mxLine);
429 sbsWriteLineno(zLine, a+j);
430 sbsWriteText(&zLine[7], &A[a+j], width, 0);
431 zLine[width+8] = '|';
432 sbsWriteLineno(&zLine[width+10], b+j);
433 len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
434 blob_append(pOut, zLine, len+width+17);
435 }
436 a += m;
437 b += m;
438 ma -= m;
439 mb -= m;
440 for(j=0; j<ma; j++){
441 memset(zLine, ' ', width+7);
442 sbsWriteLineno(zLine, a+j);
443 sbsWriteText(&zLine[7], &A[a+j], width, 0);
444 zLine[width+8] = '<';
445 zLine[width+9] = '\n';
446 zLine[width+10] = 0;
447 blob_append(pOut, zLine, width+10);
448 }
449 a += ma;
450 for(j=0; j<mb; j++){
451 memset(zLine, ' ', mxLine);
452 zLine[width+8] = '>';
453 sbsWriteLineno(&zLine[width+10], b+j);
454 len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
455 blob_append(pOut, zLine, len+width+17);
456 }
457 b += mb;
458 if( i<nr-1 ){
459 m = R[r+i*3+3];
460 for(j=0; j<m; j++){
461 memset(zLine, ' ', mxLine);
462 sbsWriteLineno(zLine, a+j);
463 sbsWriteText(&zLine[7], &A[a+j], width, 0);
464 sbsWriteLineno(&zLine[width+10], b+j);
465 len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
466 blob_append(pOut, zLine, len+width+17);
467 }
468 b += m;
469 a += m;
470 }
471 }
472
473 /* Show the final common area */
474 assert( nr==i );
475 m = R[r+nr*3];
476 if( m>nContext ) m = nContext;
477 for(j=0; j<m; j++){
478 memset(zLine, ' ', mxLine);
479 sbsWriteLineno(zLine, a+j);
480 sbsWriteText(&zLine[7], &A[a+j], width, 0);
481 sbsWriteLineno(&zLine[width+10], b+j);
482 len = sbsWriteText(&zLine[width+17], &B[b+j], width, 1);
483 blob_append(pOut, zLine, len+width+17);
484 }
485 }
486 free(zLine);
487 }
488
489 /*
490 ** Compute the optimal longest common subsequence (LCS) using an
491 ** exhaustive search. This version of the LCS is only used for
492 ** shorter input strings since runtime is O(N*N) where N is the
@@ -520,10 +718,30 @@
718 p->aEdit[p->nEdit++] = 0;
719 p->aEdit[p->nEdit++] = 0;
720 p->aEdit[p->nEdit++] = 0;
721 }
722 }
723
724 /*
725 ** Extract the number of lines of context from diffFlags. Supply an
726 ** appropriate default if no context width is specified.
727 */
728 int diff_context_lines(int diffFlags){
729 int n = diffFlags & DIFF_CONTEXT_MASK;
730 if( n==0 ) n = 5;
731 return n;
732 }
733
734 /*
735 ** Extract the width of columns for side-by-side diff. Supply an
736 ** appropriate default if no width is given.
737 */
738 int diff_width(int diffFlags){
739 int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
740 if( w==0 ) w = 80;
741 return w;
742 }
743
744 /*
745 ** Generate a report of the differences between files pA and pB.
746 ** If pOut is not NULL then a unified diff is appended there. It
747 ** is assumed that pOut has already been initialized. If pOut is
@@ -538,16 +756,20 @@
756 ** text "cannot compute difference between binary files".
757 */
758 int *text_diff(
759 Blob *pA_Blob, /* FROM file */
760 Blob *pB_Blob, /* TO file */
761 Blob *pOut, /* Write diff here if not NULL */
762 int diffFlags /* DIFF_* flags defined above */
 
763 ){
764 int ignoreEolWs; /* Ignore whitespace at the end of lines */
765 int nContext; /* Amount of context to display */
766 DContext c;
767
768 nContext = diff_context_lines(diffFlags);
769 ignoreEolWs = (diffFlags & DIFF_IGNORE_EOLWS)!=0;
770
771 /* Prepare the input files */
772 memset(&c, 0, sizeof(c));
773 c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
774 &c.nFrom, ignoreEolWs);
775 c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
@@ -563,12 +785,17 @@
785
786 /* Compute the difference */
787 diff_all(&c);
788
789 if( pOut ){
790 /* Compute a context or side-by-side diff into pOut */
791 if( diffFlags & DIFF_SIDEBYSIDE ){
792 int width = diff_width(diffFlags);
793 sbsDiff(&c, pOut, nContext, width);
794 }else{
795 contextDiff(&c, pOut, nContext);
796 }
797 free(c.aFrom);
798 free(c.aTo);
799 free(c.aEdit);
800 return 0;
801 }else{
@@ -588,11 +815,11 @@
815 static char *copylimline(char *out, DLine *dl, int lim){
816 int len;
817 len = dl->h & LENGTH_MASK;
818 if( lim && len > lim ){
819 memcpy(out, dl->z, lim-3);
820 memcpy(&out[lim-3], "...", 4);
821 }else{
822 memcpy(out, dl->z, len);
823 out[len] = '\0';
824 }
825 return out;
@@ -660,11 +887,11 @@
887 while( i<c.nEdit ){
888 int j;
889 /* Copied lines */
890 for( j=0; j<c.aEdit[i]; j++){
891 /* Hide lines which are copied and are further away from block boundaries
892 ** than nContext lines. For each block with hidden lines, show a row
893 ** notifying the user about the hidden rows.
894 */
895 if( j<nContext || j>c.aEdit[i]-nContext-1 ){
896 @ <tr>
897 }else if( j==nContext && j<c.aEdit[i]-nContext-1 ){
@@ -776,29 +1003,58 @@
1003 if( g.argc<4 ) usage("FILE1 FILE2 ...");
1004 blob_read_from_file(&a, g.argv[2]);
1005 for(i=3; i<g.argc; i++){
1006 if( i>3 ) fossil_print("-------------------------------\n");
1007 blob_read_from_file(&b, g.argv[i]);
1008 R = text_diff(&a, &b, 0, 0);
1009 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
1010 fossil_print(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]);
1011 }
1012 /* free(R); */
1013 blob_reset(&b);
1014 }
1015 }
1016
1017 /*
1018 ** Process diff-related command-line options and return an appropriate
1019 ** "diffFlags" integer.
1020 **
1021 ** --side-by-side|-y Side-by-side diff. DIFF_SIDEBYSIDE
1022 ** --context|-c N N lines of context. DIFF_CONTEXT_MASK
1023 ** --width|-W N N character lines. DIFF_WIDTH_MASK
1024 */
1025 int diff_options(void){
1026 int diffFlags = 0;
1027 const char *z;
1028 int f;
1029 if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
1030 if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>0 ){
1031 if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK;
1032 diffFlags |= f;
1033 }
1034 if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
1035 f *= DIFF_CONTEXT_MASK+1;
1036 if( f > DIFF_WIDTH_MASK ) f = DIFF_CONTEXT_MASK;
1037 diffFlags |= f;
1038 }
1039 return diffFlags;
1040 }
1041
1042 /*
1043 ** COMMAND: test-udiff
1044 **
1045 ** Print the difference between two files. The usual diff options apply.
1046 */
1047 void test_udiff_cmd(void){
1048 Blob a, b, out;
1049 int diffFlag = diff_options();
1050
1051 if( g.argc!=4 ) usage("FILE1 FILE2");
1052 blob_read_from_file(&a, g.argv[2]);
1053 blob_read_from_file(&b, g.argv[3]);
1054 blob_zero(&out);
1055 text_diff(&a, &b, &out, diffFlag);
1056 blob_write_to_file(&out, "-");
1057 }
1058
1059 /**************************************************************************
1060 ** The basic difference engine is above. What follows is the annotation
@@ -1068,10 +1324,11 @@
1324 */
1325 void annotate_cmd(void){
1326 int fnid; /* Filename ID */
1327 int fid; /* File instance ID */
1328 int mid; /* Manifest where file was checked in */
1329 int cid; /* Checkout ID */
1330 Blob treename; /* FILENAME translated to canonical form */
1331 char *zFilename; /* Cannonical filename */
1332 Annotator ann; /* The annotation of the file */
1333 int i; /* Loop counter */
1334 const char *zLimit; /* The value to the --limit option */
@@ -1097,11 +1354,20 @@
1354 }
1355 fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename);
1356 if( fid==0 ){
1357 fossil_fatal("not part of current checkout: %s", zFilename);
1358 }
1359 cid = db_lget_int("checkout", 0);
1360 if (cid == 0){
1361 fossil_fatal("Not in a checkout");
1362 }
1363 if( iLimit<=0 ) iLimit = 1000000000;
1364 compute_direct_ancestors(cid, iLimit);
1365 mid = db_int(0, "SELECT mlink.mid FROM mlink, ancestor "
1366 " WHERE mlink.fid=%d AND mlink.fnid=%d AND mlink.mid=ancestor.rid"
1367 " ORDER BY ancestor.generation ASC LIMIT 1",
1368 fid, fnid);
1369 if( mid==0 ){
1370 fossil_panic("unable to find manifest");
1371 }
1372 if( fileVers ) annFlags |= ANN_FILE_VERS;
1373 annotate_file(&ann, fnid, mid, 0, iLimit, annFlags);
1374
+55 -38
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -19,16 +19,10 @@
1919
*/
2020
#include "config.h"
2121
#include "diffcmd.h"
2222
#include <assert.h>
2323
24
-/*
25
-** Diff option flags
26
-*/
27
-#define DIFF_NEWFILE 0x01 /* Treat non-existing fails as empty files */
28
-#define DIFF_NOEOLWS 0x02 /* Ignore whitespace at the end of lines */
29
-
3024
/*
3125
** Output the results of a diff. Output goes to stdout for command-line
3226
** or to the CGI/HTTP result buffer for web pages.
3327
*/
3428
static void diff_printf(const char *zFormat, ...){
@@ -41,15 +35,38 @@
4135
}
4236
va_end(ap);
4337
}
4438
4539
/*
46
-** Print the "Index:" message that patch wants to see at the top of a diff.
40
+** Print the "Index:" message that patches wants to see at the top of a diff.
41
+*/
42
+void diff_print_index(const char *zFile, int diffFlags){
43
+ if( (diffFlags & DIFF_SIDEBYSIDE)==0 ){
44
+ char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
45
+ diff_printf("%s", z);
46
+ fossil_free(z);
47
+ }
48
+}
49
+
50
+/*
51
+** Print the +++/--- filename lines for a diff operation.
4752
*/
48
-void diff_print_index(const char *zFile){
49
- diff_printf("Index: %s\n======================================="
50
- "============================\n", zFile);
53
+void diff_print_filenames(const char *zLeft, const char *zRight, int diffFlags){
54
+ char *z = 0;
55
+ if( diffFlags & DIFF_SIDEBYSIDE ){
56
+ int w = diff_width(diffFlags);
57
+ int n1 = strlen(zLeft);
58
+ int x;
59
+ if( n1>w*2 ) n1 = w*2;
60
+ x = w*2+17 - (n1+2);
61
+ z = mprintf("%.*c %.*s %.*c\n",
62
+ x/2, '=', n1, zLeft, (x+1)/2, '=');
63
+ }else{
64
+ z = mprintf("--- %s\n+++ %s\n", zLeft, zRight);
65
+ }
66
+ diff_printf("%s", z);
67
+ fossil_free(z);
5168
}
5269
5370
/*
5471
** Show the difference between two files, one in memory and one on disk.
5572
**
@@ -62,11 +79,11 @@
6279
void diff_file(
6380
Blob *pFile1, /* In memory content to compare from */
6481
const char *zFile2, /* On disk content to compare to */
6582
const char *zName, /* Display name of the file */
6683
const char *zDiffCmd, /* Command for comparison */
67
- int ignoreEolWs /* Ignore whitespace at end of line */
84
+ int diffFlags /* Flags to control the diff */
6885
){
6986
if( zDiffCmd==0 ){
7087
Blob out; /* Diff output text */
7188
Blob file2; /* Content of zFile2 */
7289
const char *zName2; /* Name of zFile2 for display */
@@ -84,13 +101,13 @@
84101
zName2 = zName;
85102
}
86103
87104
/* Compute and output the differences */
88105
blob_zero(&out);
89
- text_diff(pFile1, &file2, &out, 5, ignoreEolWs);
106
+ text_diff(pFile1, &file2, &out, diffFlags);
90107
if( blob_size(&out) ){
91
- diff_printf("--- %s\n+++ %s\n", zName, zName2);
108
+ diff_print_filenames(zName, zName2, diffFlags);
92109
diff_printf("%s\n", blob_str(&out));
93110
}
94111
95112
/* Release memory resources */
96113
blob_reset(&file2);
@@ -138,18 +155,18 @@
138155
void diff_file_mem(
139156
Blob *pFile1, /* In memory content to compare from */
140157
Blob *pFile2, /* In memory content to compare to */
141158
const char *zName, /* Display name of the file */
142159
const char *zDiffCmd, /* Command for comparison */
143
- int ignoreEolWs /* Ignore whitespace at end of lines */
160
+ int diffFlags /* Diff flags */
144161
){
145162
if( zDiffCmd==0 ){
146163
Blob out; /* Diff output text */
147164
148165
blob_zero(&out);
149
- text_diff(pFile1, pFile2, &out, 5, ignoreEolWs);
150
- diff_printf("--- %s\n+++ %s\n", zName, zName);
166
+ text_diff(pFile1, pFile2, &out, diffFlags);
167
+ diff_print_filenames(zName, zName, diffFlags);
151168
diff_printf("%s\n", blob_str(&out));
152169
153170
/* Release memory resources */
154171
blob_reset(&out);
155172
}else{
@@ -185,11 +202,11 @@
185202
** against the same file on disk.
186203
*/
187204
static void diff_one_against_disk(
188205
const char *zFrom, /* Name of file */
189206
const char *zDiffCmd, /* Use this "diff" command */
190
- int ignoreEolWs, /* Ignore whitespace changes at end of lines */
207
+ int diffFlags, /* Diff control flags */
191208
const char *zFileTreeName
192209
){
193210
Blob fname;
194211
Blob content;
195212
int isLink;
@@ -196,11 +213,11 @@
196213
file_tree_name(zFileTreeName, &fname, 1);
197214
historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
198215
if( !isLink != !file_wd_islink(zFrom) ){
199216
diff_printf("cannot compute difference between symlink and regular file\n");
200217
}else{
201
- diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, ignoreEolWs);
218
+ diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags);
202219
}
203220
blob_reset(&content);
204221
blob_reset(&fname);
205222
}
206223
@@ -215,14 +232,12 @@
215232
int diffFlags /* Flags controlling diff output */
216233
){
217234
int vid;
218235
Blob sql;
219236
Stmt q;
220
- int ignoreEolWs; /* Ignore end-of-line whitespace */
221237
int asNewFile; /* Treat non-existant files as empty files */
222238
223
- ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0;
224239
asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
225240
vid = db_lget_int("checkout", 0);
226241
vfile_check_signature(vid, 1, 0);
227242
blob_zero(&sql);
228243
db_begin_transaction();
@@ -289,22 +304,22 @@
289304
if( !asNewFile ){ showDiff = 0; }
290305
}
291306
if( showDiff ){
292307
Blob content;
293308
if( !isLink != !file_wd_islink(zFullName) ){
294
- diff_print_index(zPathname);
295
- diff_printf("--- %s\n+++ %s\n", zPathname, zPathname);
309
+ diff_print_index(zPathname, diffFlags);
310
+ diff_print_filenames(zPathname, zPathname, diffFlags);
296311
diff_printf("cannot compute difference between symlink and regular file\n");
297312
continue;
298313
}
299314
if( srcid>0 ){
300315
content_get(srcid, &content);
301316
}else{
302317
blob_zero(&content);
303318
}
304
- diff_print_index(zPathname);
305
- diff_file(&content, zFullName, zPathname, zDiffCmd, ignoreEolWs);
319
+ diff_print_index(zPathname, diffFlags);
320
+ diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags);
306321
blob_reset(&content);
307322
}
308323
free(zToFree);
309324
}
310325
db_finalize(&q);
@@ -317,11 +332,11 @@
317332
*/
318333
static void diff_one_two_versions(
319334
const char *zFrom,
320335
const char *zTo,
321336
const char *zDiffCmd,
322
- int ignoreEolWs,
337
+ int diffFlags,
323338
const char *zFileTreeName
324339
){
325340
char *zName;
326341
Blob fname;
327342
Blob v1, v2;
@@ -329,14 +344,14 @@
329344
file_tree_name(zFileTreeName, &fname, 1);
330345
zName = blob_str(&fname);
331346
historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0);
332347
historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
333348
if( isLink1 != isLink2 ){
334
- diff_printf("--- %s\n+++ %s\n", zName, zName);
349
+ diff_print_filenames(zName, zName, diffFlags);
335350
diff_printf("cannot compute difference between symlink and regular file\n");
336351
}else{
337
- diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs);
352
+ diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags);
338353
}
339354
blob_reset(&v1);
340355
blob_reset(&v2);
341356
blob_reset(&fname);
342357
}
@@ -347,16 +362,16 @@
347362
*/
348363
static void diff_manifest_entry(
349364
struct ManifestFile *pFrom,
350365
struct ManifestFile *pTo,
351366
const char *zDiffCmd,
352
- int ignoreEolWs
367
+ int diffFlags
353368
){
354369
Blob f1, f2;
355370
int rid;
356371
const char *zName = pFrom ? pFrom->zName : pTo->zName;
357
- diff_print_index(zName);
372
+ diff_print_index(zName, diffFlags);
358373
if( pFrom ){
359374
rid = uuid_to_rid(pFrom->zUuid, 0);
360375
content_get(rid, &f1);
361376
}else{
362377
blob_zero(&f1);
@@ -365,11 +380,11 @@
365380
rid = uuid_to_rid(pTo->zUuid, 0);
366381
content_get(rid, &f2);
367382
}else{
368383
blob_zero(&f2);
369384
}
370
- diff_file_mem(&f1, &f2, zName, zDiffCmd, ignoreEolWs);
385
+ diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags);
371386
blob_reset(&f1);
372387
blob_reset(&f2);
373388
}
374389
375390
/*
@@ -381,11 +396,10 @@
381396
const char *zDiffCmd,
382397
int diffFlags
383398
){
384399
Manifest *pFrom, *pTo;
385400
ManifestFile *pFromFile, *pToFile;
386
- int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
387401
int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
388402
389403
pFrom = manifest_get_by_name(zFrom, 0);
390404
manifest_file_rewind(pFrom);
391405
pFromFile = manifest_file_next(pFrom,0);
@@ -403,26 +417,26 @@
403417
cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
404418
}
405419
if( cmp<0 ){
406420
diff_printf("DELETED %s\n", pFromFile->zName);
407421
if( asNewFlag ){
408
- diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs);
422
+ diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags);
409423
}
410424
pFromFile = manifest_file_next(pFrom,0);
411425
}else if( cmp>0 ){
412426
diff_printf("ADDED %s\n", pToFile->zName);
413427
if( asNewFlag ){
414
- diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs);
428
+ diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags);
415429
}
416430
pToFile = manifest_file_next(pTo,0);
417431
}else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
418432
/* No changes */
419433
pFromFile = manifest_file_next(pFrom,0);
420434
pToFile = manifest_file_next(pTo,0);
421435
}else{
422436
/* diff_printf("CHANGED %s\n", pFromFile->zName); */
423
- diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs);
437
+ diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags);
424438
pFromFile = manifest_file_next(pFrom,0);
425439
pToFile = manifest_file_next(pTo,0);
426440
}
427441
}
428442
manifest_destroy(pFrom);
@@ -456,14 +470,17 @@
456470
**
457471
** The "-N" or "--new-file" option causes the complete text of added or
458472
** deleted files to be displayed.
459473
**
460474
** Options:
475
+** --context|-c N Use N lines of context
461476
** --from|-r VERSION select VERSION as source for the diff
462477
** --new-file|-N output complete text of added or deleted files
463478
** -i use internal diff logic
464479
** --to VERSION select VERSION as target for the diff
480
+** --side-by-side|-y side-by-side diff
481
+** --width|-W N Width of lines in side-by-side diff
465482
*/
466483
void diff_cmd(void){
467484
int isGDiff; /* True for gdiff. False for normal diff */
468485
int isInternDiff; /* True for internal diff */
469486
int hasNFlag; /* True if -N or --new-file flag is used */
@@ -475,23 +492,23 @@
475492
476493
isGDiff = g.argv[1][0]=='g';
477494
isInternDiff = find_option("internal","i",0)!=0;
478495
zFrom = find_option("from", "r", 1);
479496
zTo = find_option("to", 0, 1);
497
+ diffFlags = diff_options();
480498
hasNFlag = find_option("new-file","N",0)!=0;
481
-
482
-
483499
if( hasNFlag ) diffFlags |= DIFF_NEWFILE;
500
+
484501
if( zTo==0 ){
485502
db_must_be_within_tree();
486503
verify_all_options();
487504
if( !isInternDiff ){
488505
zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
489506
}
490507
if( g.argc>=3 ){
491508
for(f=2; f<g.argc; ++f){
492
- diff_one_against_disk(zFrom, zDiffCmd, 0, g.argv[f]);
509
+ diff_one_against_disk(zFrom, zDiffCmd, diffFlags, g.argv[f]);
493510
}
494511
}else{
495512
diff_all_against_disk(zFrom, zDiffCmd, diffFlags);
496513
}
497514
}else if( zFrom==0 ){
@@ -502,11 +519,11 @@
502519
if( !isInternDiff ){
503520
zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
504521
}
505522
if( g.argc>=3 ){
506523
for(f=2; f<g.argc; ++f){
507
- diff_one_two_versions(zFrom, zTo, zDiffCmd, 0, g.argv[f]);
524
+ diff_one_two_versions(zFrom, zTo, zDiffCmd, diffFlags, g.argv[f]);
508525
}
509526
}else{
510527
diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags);
511528
}
512529
}
513530
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -19,16 +19,10 @@
19 */
20 #include "config.h"
21 #include "diffcmd.h"
22 #include <assert.h>
23
24 /*
25 ** Diff option flags
26 */
27 #define DIFF_NEWFILE 0x01 /* Treat non-existing fails as empty files */
28 #define DIFF_NOEOLWS 0x02 /* Ignore whitespace at the end of lines */
29
30 /*
31 ** Output the results of a diff. Output goes to stdout for command-line
32 ** or to the CGI/HTTP result buffer for web pages.
33 */
34 static void diff_printf(const char *zFormat, ...){
@@ -41,15 +35,38 @@
41 }
42 va_end(ap);
43 }
44
45 /*
46 ** Print the "Index:" message that patch wants to see at the top of a diff.
 
 
 
 
 
 
 
 
 
 
 
47 */
48 void diff_print_index(const char *zFile){
49 diff_printf("Index: %s\n======================================="
50 "============================\n", zFile);
 
 
 
 
 
 
 
 
 
 
 
 
51 }
52
53 /*
54 ** Show the difference between two files, one in memory and one on disk.
55 **
@@ -62,11 +79,11 @@
62 void diff_file(
63 Blob *pFile1, /* In memory content to compare from */
64 const char *zFile2, /* On disk content to compare to */
65 const char *zName, /* Display name of the file */
66 const char *zDiffCmd, /* Command for comparison */
67 int ignoreEolWs /* Ignore whitespace at end of line */
68 ){
69 if( zDiffCmd==0 ){
70 Blob out; /* Diff output text */
71 Blob file2; /* Content of zFile2 */
72 const char *zName2; /* Name of zFile2 for display */
@@ -84,13 +101,13 @@
84 zName2 = zName;
85 }
86
87 /* Compute and output the differences */
88 blob_zero(&out);
89 text_diff(pFile1, &file2, &out, 5, ignoreEolWs);
90 if( blob_size(&out) ){
91 diff_printf("--- %s\n+++ %s\n", zName, zName2);
92 diff_printf("%s\n", blob_str(&out));
93 }
94
95 /* Release memory resources */
96 blob_reset(&file2);
@@ -138,18 +155,18 @@
138 void diff_file_mem(
139 Blob *pFile1, /* In memory content to compare from */
140 Blob *pFile2, /* In memory content to compare to */
141 const char *zName, /* Display name of the file */
142 const char *zDiffCmd, /* Command for comparison */
143 int ignoreEolWs /* Ignore whitespace at end of lines */
144 ){
145 if( zDiffCmd==0 ){
146 Blob out; /* Diff output text */
147
148 blob_zero(&out);
149 text_diff(pFile1, pFile2, &out, 5, ignoreEolWs);
150 diff_printf("--- %s\n+++ %s\n", zName, zName);
151 diff_printf("%s\n", blob_str(&out));
152
153 /* Release memory resources */
154 blob_reset(&out);
155 }else{
@@ -185,11 +202,11 @@
185 ** against the same file on disk.
186 */
187 static void diff_one_against_disk(
188 const char *zFrom, /* Name of file */
189 const char *zDiffCmd, /* Use this "diff" command */
190 int ignoreEolWs, /* Ignore whitespace changes at end of lines */
191 const char *zFileTreeName
192 ){
193 Blob fname;
194 Blob content;
195 int isLink;
@@ -196,11 +213,11 @@
196 file_tree_name(zFileTreeName, &fname, 1);
197 historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
198 if( !isLink != !file_wd_islink(zFrom) ){
199 diff_printf("cannot compute difference between symlink and regular file\n");
200 }else{
201 diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, ignoreEolWs);
202 }
203 blob_reset(&content);
204 blob_reset(&fname);
205 }
206
@@ -215,14 +232,12 @@
215 int diffFlags /* Flags controlling diff output */
216 ){
217 int vid;
218 Blob sql;
219 Stmt q;
220 int ignoreEolWs; /* Ignore end-of-line whitespace */
221 int asNewFile; /* Treat non-existant files as empty files */
222
223 ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0;
224 asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
225 vid = db_lget_int("checkout", 0);
226 vfile_check_signature(vid, 1, 0);
227 blob_zero(&sql);
228 db_begin_transaction();
@@ -289,22 +304,22 @@
289 if( !asNewFile ){ showDiff = 0; }
290 }
291 if( showDiff ){
292 Blob content;
293 if( !isLink != !file_wd_islink(zFullName) ){
294 diff_print_index(zPathname);
295 diff_printf("--- %s\n+++ %s\n", zPathname, zPathname);
296 diff_printf("cannot compute difference between symlink and regular file\n");
297 continue;
298 }
299 if( srcid>0 ){
300 content_get(srcid, &content);
301 }else{
302 blob_zero(&content);
303 }
304 diff_print_index(zPathname);
305 diff_file(&content, zFullName, zPathname, zDiffCmd, ignoreEolWs);
306 blob_reset(&content);
307 }
308 free(zToFree);
309 }
310 db_finalize(&q);
@@ -317,11 +332,11 @@
317 */
318 static void diff_one_two_versions(
319 const char *zFrom,
320 const char *zTo,
321 const char *zDiffCmd,
322 int ignoreEolWs,
323 const char *zFileTreeName
324 ){
325 char *zName;
326 Blob fname;
327 Blob v1, v2;
@@ -329,14 +344,14 @@
329 file_tree_name(zFileTreeName, &fname, 1);
330 zName = blob_str(&fname);
331 historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0);
332 historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
333 if( isLink1 != isLink2 ){
334 diff_printf("--- %s\n+++ %s\n", zName, zName);
335 diff_printf("cannot compute difference between symlink and regular file\n");
336 }else{
337 diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs);
338 }
339 blob_reset(&v1);
340 blob_reset(&v2);
341 blob_reset(&fname);
342 }
@@ -347,16 +362,16 @@
347 */
348 static void diff_manifest_entry(
349 struct ManifestFile *pFrom,
350 struct ManifestFile *pTo,
351 const char *zDiffCmd,
352 int ignoreEolWs
353 ){
354 Blob f1, f2;
355 int rid;
356 const char *zName = pFrom ? pFrom->zName : pTo->zName;
357 diff_print_index(zName);
358 if( pFrom ){
359 rid = uuid_to_rid(pFrom->zUuid, 0);
360 content_get(rid, &f1);
361 }else{
362 blob_zero(&f1);
@@ -365,11 +380,11 @@
365 rid = uuid_to_rid(pTo->zUuid, 0);
366 content_get(rid, &f2);
367 }else{
368 blob_zero(&f2);
369 }
370 diff_file_mem(&f1, &f2, zName, zDiffCmd, ignoreEolWs);
371 blob_reset(&f1);
372 blob_reset(&f2);
373 }
374
375 /*
@@ -381,11 +396,10 @@
381 const char *zDiffCmd,
382 int diffFlags
383 ){
384 Manifest *pFrom, *pTo;
385 ManifestFile *pFromFile, *pToFile;
386 int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
387 int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
388
389 pFrom = manifest_get_by_name(zFrom, 0);
390 manifest_file_rewind(pFrom);
391 pFromFile = manifest_file_next(pFrom,0);
@@ -403,26 +417,26 @@
403 cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
404 }
405 if( cmp<0 ){
406 diff_printf("DELETED %s\n", pFromFile->zName);
407 if( asNewFlag ){
408 diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs);
409 }
410 pFromFile = manifest_file_next(pFrom,0);
411 }else if( cmp>0 ){
412 diff_printf("ADDED %s\n", pToFile->zName);
413 if( asNewFlag ){
414 diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs);
415 }
416 pToFile = manifest_file_next(pTo,0);
417 }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
418 /* No changes */
419 pFromFile = manifest_file_next(pFrom,0);
420 pToFile = manifest_file_next(pTo,0);
421 }else{
422 /* diff_printf("CHANGED %s\n", pFromFile->zName); */
423 diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs);
424 pFromFile = manifest_file_next(pFrom,0);
425 pToFile = manifest_file_next(pTo,0);
426 }
427 }
428 manifest_destroy(pFrom);
@@ -456,14 +470,17 @@
456 **
457 ** The "-N" or "--new-file" option causes the complete text of added or
458 ** deleted files to be displayed.
459 **
460 ** Options:
 
461 ** --from|-r VERSION select VERSION as source for the diff
462 ** --new-file|-N output complete text of added or deleted files
463 ** -i use internal diff logic
464 ** --to VERSION select VERSION as target for the diff
 
 
465 */
466 void diff_cmd(void){
467 int isGDiff; /* True for gdiff. False for normal diff */
468 int isInternDiff; /* True for internal diff */
469 int hasNFlag; /* True if -N or --new-file flag is used */
@@ -475,23 +492,23 @@
475
476 isGDiff = g.argv[1][0]=='g';
477 isInternDiff = find_option("internal","i",0)!=0;
478 zFrom = find_option("from", "r", 1);
479 zTo = find_option("to", 0, 1);
 
480 hasNFlag = find_option("new-file","N",0)!=0;
481
482
483 if( hasNFlag ) diffFlags |= DIFF_NEWFILE;
 
484 if( zTo==0 ){
485 db_must_be_within_tree();
486 verify_all_options();
487 if( !isInternDiff ){
488 zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
489 }
490 if( g.argc>=3 ){
491 for(f=2; f<g.argc; ++f){
492 diff_one_against_disk(zFrom, zDiffCmd, 0, g.argv[f]);
493 }
494 }else{
495 diff_all_against_disk(zFrom, zDiffCmd, diffFlags);
496 }
497 }else if( zFrom==0 ){
@@ -502,11 +519,11 @@
502 if( !isInternDiff ){
503 zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
504 }
505 if( g.argc>=3 ){
506 for(f=2; f<g.argc; ++f){
507 diff_one_two_versions(zFrom, zTo, zDiffCmd, 0, g.argv[f]);
508 }
509 }else{
510 diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags);
511 }
512 }
513
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -19,16 +19,10 @@
19 */
20 #include "config.h"
21 #include "diffcmd.h"
22 #include <assert.h>
23
 
 
 
 
 
 
24 /*
25 ** Output the results of a diff. Output goes to stdout for command-line
26 ** or to the CGI/HTTP result buffer for web pages.
27 */
28 static void diff_printf(const char *zFormat, ...){
@@ -41,15 +35,38 @@
35 }
36 va_end(ap);
37 }
38
39 /*
40 ** Print the "Index:" message that patches wants to see at the top of a diff.
41 */
42 void diff_print_index(const char *zFile, int diffFlags){
43 if( (diffFlags & DIFF_SIDEBYSIDE)==0 ){
44 char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
45 diff_printf("%s", z);
46 fossil_free(z);
47 }
48 }
49
50 /*
51 ** Print the +++/--- filename lines for a diff operation.
52 */
53 void diff_print_filenames(const char *zLeft, const char *zRight, int diffFlags){
54 char *z = 0;
55 if( diffFlags & DIFF_SIDEBYSIDE ){
56 int w = diff_width(diffFlags);
57 int n1 = strlen(zLeft);
58 int x;
59 if( n1>w*2 ) n1 = w*2;
60 x = w*2+17 - (n1+2);
61 z = mprintf("%.*c %.*s %.*c\n",
62 x/2, '=', n1, zLeft, (x+1)/2, '=');
63 }else{
64 z = mprintf("--- %s\n+++ %s\n", zLeft, zRight);
65 }
66 diff_printf("%s", z);
67 fossil_free(z);
68 }
69
70 /*
71 ** Show the difference between two files, one in memory and one on disk.
72 **
@@ -62,11 +79,11 @@
79 void diff_file(
80 Blob *pFile1, /* In memory content to compare from */
81 const char *zFile2, /* On disk content to compare to */
82 const char *zName, /* Display name of the file */
83 const char *zDiffCmd, /* Command for comparison */
84 int diffFlags /* Flags to control the diff */
85 ){
86 if( zDiffCmd==0 ){
87 Blob out; /* Diff output text */
88 Blob file2; /* Content of zFile2 */
89 const char *zName2; /* Name of zFile2 for display */
@@ -84,13 +101,13 @@
101 zName2 = zName;
102 }
103
104 /* Compute and output the differences */
105 blob_zero(&out);
106 text_diff(pFile1, &file2, &out, diffFlags);
107 if( blob_size(&out) ){
108 diff_print_filenames(zName, zName2, diffFlags);
109 diff_printf("%s\n", blob_str(&out));
110 }
111
112 /* Release memory resources */
113 blob_reset(&file2);
@@ -138,18 +155,18 @@
155 void diff_file_mem(
156 Blob *pFile1, /* In memory content to compare from */
157 Blob *pFile2, /* In memory content to compare to */
158 const char *zName, /* Display name of the file */
159 const char *zDiffCmd, /* Command for comparison */
160 int diffFlags /* Diff flags */
161 ){
162 if( zDiffCmd==0 ){
163 Blob out; /* Diff output text */
164
165 blob_zero(&out);
166 text_diff(pFile1, pFile2, &out, diffFlags);
167 diff_print_filenames(zName, zName, diffFlags);
168 diff_printf("%s\n", blob_str(&out));
169
170 /* Release memory resources */
171 blob_reset(&out);
172 }else{
@@ -185,11 +202,11 @@
202 ** against the same file on disk.
203 */
204 static void diff_one_against_disk(
205 const char *zFrom, /* Name of file */
206 const char *zDiffCmd, /* Use this "diff" command */
207 int diffFlags, /* Diff control flags */
208 const char *zFileTreeName
209 ){
210 Blob fname;
211 Blob content;
212 int isLink;
@@ -196,11 +213,11 @@
213 file_tree_name(zFileTreeName, &fname, 1);
214 historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
215 if( !isLink != !file_wd_islink(zFrom) ){
216 diff_printf("cannot compute difference between symlink and regular file\n");
217 }else{
218 diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags);
219 }
220 blob_reset(&content);
221 blob_reset(&fname);
222 }
223
@@ -215,14 +232,12 @@
232 int diffFlags /* Flags controlling diff output */
233 ){
234 int vid;
235 Blob sql;
236 Stmt q;
 
237 int asNewFile; /* Treat non-existant files as empty files */
238
 
239 asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
240 vid = db_lget_int("checkout", 0);
241 vfile_check_signature(vid, 1, 0);
242 blob_zero(&sql);
243 db_begin_transaction();
@@ -289,22 +304,22 @@
304 if( !asNewFile ){ showDiff = 0; }
305 }
306 if( showDiff ){
307 Blob content;
308 if( !isLink != !file_wd_islink(zFullName) ){
309 diff_print_index(zPathname, diffFlags);
310 diff_print_filenames(zPathname, zPathname, diffFlags);
311 diff_printf("cannot compute difference between symlink and regular file\n");
312 continue;
313 }
314 if( srcid>0 ){
315 content_get(srcid, &content);
316 }else{
317 blob_zero(&content);
318 }
319 diff_print_index(zPathname, diffFlags);
320 diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags);
321 blob_reset(&content);
322 }
323 free(zToFree);
324 }
325 db_finalize(&q);
@@ -317,11 +332,11 @@
332 */
333 static void diff_one_two_versions(
334 const char *zFrom,
335 const char *zTo,
336 const char *zDiffCmd,
337 int diffFlags,
338 const char *zFileTreeName
339 ){
340 char *zName;
341 Blob fname;
342 Blob v1, v2;
@@ -329,14 +344,14 @@
344 file_tree_name(zFileTreeName, &fname, 1);
345 zName = blob_str(&fname);
346 historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0);
347 historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
348 if( isLink1 != isLink2 ){
349 diff_print_filenames(zName, zName, diffFlags);
350 diff_printf("cannot compute difference between symlink and regular file\n");
351 }else{
352 diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags);
353 }
354 blob_reset(&v1);
355 blob_reset(&v2);
356 blob_reset(&fname);
357 }
@@ -347,16 +362,16 @@
362 */
363 static void diff_manifest_entry(
364 struct ManifestFile *pFrom,
365 struct ManifestFile *pTo,
366 const char *zDiffCmd,
367 int diffFlags
368 ){
369 Blob f1, f2;
370 int rid;
371 const char *zName = pFrom ? pFrom->zName : pTo->zName;
372 diff_print_index(zName, diffFlags);
373 if( pFrom ){
374 rid = uuid_to_rid(pFrom->zUuid, 0);
375 content_get(rid, &f1);
376 }else{
377 blob_zero(&f1);
@@ -365,11 +380,11 @@
380 rid = uuid_to_rid(pTo->zUuid, 0);
381 content_get(rid, &f2);
382 }else{
383 blob_zero(&f2);
384 }
385 diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags);
386 blob_reset(&f1);
387 blob_reset(&f2);
388 }
389
390 /*
@@ -381,11 +396,10 @@
396 const char *zDiffCmd,
397 int diffFlags
398 ){
399 Manifest *pFrom, *pTo;
400 ManifestFile *pFromFile, *pToFile;
 
401 int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
402
403 pFrom = manifest_get_by_name(zFrom, 0);
404 manifest_file_rewind(pFrom);
405 pFromFile = manifest_file_next(pFrom,0);
@@ -403,26 +417,26 @@
417 cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
418 }
419 if( cmp<0 ){
420 diff_printf("DELETED %s\n", pFromFile->zName);
421 if( asNewFlag ){
422 diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags);
423 }
424 pFromFile = manifest_file_next(pFrom,0);
425 }else if( cmp>0 ){
426 diff_printf("ADDED %s\n", pToFile->zName);
427 if( asNewFlag ){
428 diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags);
429 }
430 pToFile = manifest_file_next(pTo,0);
431 }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
432 /* No changes */
433 pFromFile = manifest_file_next(pFrom,0);
434 pToFile = manifest_file_next(pTo,0);
435 }else{
436 /* diff_printf("CHANGED %s\n", pFromFile->zName); */
437 diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags);
438 pFromFile = manifest_file_next(pFrom,0);
439 pToFile = manifest_file_next(pTo,0);
440 }
441 }
442 manifest_destroy(pFrom);
@@ -456,14 +470,17 @@
470 **
471 ** The "-N" or "--new-file" option causes the complete text of added or
472 ** deleted files to be displayed.
473 **
474 ** Options:
475 ** --context|-c N Use N lines of context
476 ** --from|-r VERSION select VERSION as source for the diff
477 ** --new-file|-N output complete text of added or deleted files
478 ** -i use internal diff logic
479 ** --to VERSION select VERSION as target for the diff
480 ** --side-by-side|-y side-by-side diff
481 ** --width|-W N Width of lines in side-by-side diff
482 */
483 void diff_cmd(void){
484 int isGDiff; /* True for gdiff. False for normal diff */
485 int isInternDiff; /* True for internal diff */
486 int hasNFlag; /* True if -N or --new-file flag is used */
@@ -475,23 +492,23 @@
492
493 isGDiff = g.argv[1][0]=='g';
494 isInternDiff = find_option("internal","i",0)!=0;
495 zFrom = find_option("from", "r", 1);
496 zTo = find_option("to", 0, 1);
497 diffFlags = diff_options();
498 hasNFlag = find_option("new-file","N",0)!=0;
 
 
499 if( hasNFlag ) diffFlags |= DIFF_NEWFILE;
500
501 if( zTo==0 ){
502 db_must_be_within_tree();
503 verify_all_options();
504 if( !isInternDiff ){
505 zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
506 }
507 if( g.argc>=3 ){
508 for(f=2; f<g.argc; ++f){
509 diff_one_against_disk(zFrom, zDiffCmd, diffFlags, g.argv[f]);
510 }
511 }else{
512 diff_all_against_disk(zFrom, zDiffCmd, diffFlags);
513 }
514 }else if( zFrom==0 ){
@@ -502,11 +519,11 @@
519 if( !isInternDiff ){
520 zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
521 }
522 if( g.argc>=3 ){
523 for(f=2; f<g.argc; ++f){
524 diff_one_two_versions(zFrom, zTo, zDiffCmd, diffFlags, g.argv[f]);
525 }
526 }else{
527 diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags);
528 }
529 }
530
+1 -1
--- src/finfo.c
+++ src/finfo.c
@@ -287,11 +287,11 @@
287287
char zShortCkin[20];
288288
if( zBr==0 ) zBr = "trunk";
289289
if( uBg ){
290290
zBgClr = hash_color(zUser);
291291
}else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
292
- zBgClr = strcmp(zBr,"trunk")==0 ? "white" : hash_color(zBr);
292
+ zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
293293
}
294294
gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, 0);
295295
if( memcmp(zDate, zPrevDate, 10) ){
296296
sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
297297
@ <tr><td>
298298
--- src/finfo.c
+++ src/finfo.c
@@ -287,11 +287,11 @@
287 char zShortCkin[20];
288 if( zBr==0 ) zBr = "trunk";
289 if( uBg ){
290 zBgClr = hash_color(zUser);
291 }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
292 zBgClr = strcmp(zBr,"trunk")==0 ? "white" : hash_color(zBr);
293 }
294 gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, 0);
295 if( memcmp(zDate, zPrevDate, 10) ){
296 sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
297 @ <tr><td>
298
--- src/finfo.c
+++ src/finfo.c
@@ -287,11 +287,11 @@
287 char zShortCkin[20];
288 if( zBr==0 ) zBr = "trunk";
289 if( uBg ){
290 zBgClr = hash_color(zUser);
291 }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
292 zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
293 }
294 gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, 0);
295 if( memcmp(zDate, zPrevDate, 10) ){
296 sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
297 @ <tr><td>
298
+42 -40
--- src/info.c
+++ src/info.c
@@ -270,11 +270,11 @@
270270
content_get(toid, &to);
271271
}else{
272272
blob_zero(&to);
273273
}
274274
blob_zero(&out);
275
- text_diff(&from, &to, &out, 5, 1);
275
+ text_diff(&from, &to, &out, DIFF_IGNORE_EOLWS | 5);
276276
@ %h(blob_str(&out))
277277
blob_reset(&from);
278278
blob_reset(&to);
279279
blob_reset(&out);
280280
}
@@ -334,13 +334,17 @@
334334
@ for %h(zName)</p>
335335
}else{
336336
@ <p>Changes to %h(zName)</p>
337337
}
338338
if( showDiff ){
339
- @ <blockquote><pre>
340
- append_diff(zOld, zNew);
341
- @ </pre></blockquote>
339
+ if( sideBySide ){
340
+ generate_sbsdiff(zOld, zNew);
341
+ }else{
342
+ @ <blockquote><pre>
343
+ append_diff(zOld, zNew);
344
+ @ </pre></blockquote>
345
+ }
342346
}
343347
}else{
344348
if( zOld && zNew ){
345349
if( fossil_strcmp(zOld, zNew)!=0 ){
346350
@ <p>Modified <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
@@ -361,11 +365,11 @@
361365
@ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
362366
@ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
363367
}
364368
if( showDiff ){
365369
if( sideBySide ){
366
- generate_sbsdiff(zOld, zNew);
370
+ generate_sbsdiff(zOld, zNew);
367371
}else{
368372
@ <blockquote><pre>
369373
append_diff(zOld, zNew);
370374
@ </pre></blockquote>
371375
}
@@ -543,51 +547,49 @@
543547
}
544548
db_finalize(&q);
545549
showTags(rid, "");
546550
if( zParent ){
547551
@ <div class="section">Changes</div>
552
+ @ <div class="sectionmenu">
548553
showDiff = g.zPath[0]!='c';
549554
if( db_get_boolean("show-version-diffs", 0)==0 ){
550555
showDiff = !showDiff;
551556
if( showDiff ){
552
- @ <a href="%s(g.zTop)/vinfo/%T(zName)">[hide&nbsp;diffs]</a>
553
- @ &nbsp;&nbsp;
554
- if( sideBySide ){
555
- @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0">
556
- @ [unified&nbsp;diffs]</a>
557
- }else{
558
- @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1">
559
- @ [side-by-side&nbsp;diffs]</a>
560
- }
561
- }else{
562
- @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0">
563
- @ [show&nbsp;unified&nbsp;diffs]</a>
564
- @ &nbsp;&nbsp;
565
- @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1">
566
- @ [show&nbsp;side-by-side&nbsp;diffs]</a>
557
+ @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)">
558
+ @ hide&nbsp;diffs</a>
559
+ if( sideBySide ){
560
+ @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
561
+ @ unified&nbsp;diffs</a>
562
+ }else{
563
+ @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
564
+ @ side-by-side&nbsp;diffs</a>
565
+ }
566
+ }else{
567
+ @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
568
+ @ show&nbsp;unified&nbsp;diffs</a>
569
+ @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
570
+ @ show&nbsp;side-by-side&nbsp;diffs</a>
567571
}
568572
}else{
569573
if( showDiff ){
570
- @ <a href="%s(g.zTop)/ci/%T(zName)">[hide&nbsp;diffs]</a>
571
- @ &nbsp;&nbsp;
572
- if( sideBySide ){
573
- @ <a href="%s(g.zTop)/info/%T(zName)?sbs=0">
574
- @ [unified&nbsp;diffs]</a>
575
- }else{
576
- @ <a href="%s(g.zTop)/info/%T(zName)?sbs=1">
577
- @ [side-by-side&nbsp;diffs]</a>
578
- }
579
- }else{
580
- @ <a href="%s(g.zTop)/vinfo/%T(zName)?sbs=0">
581
- @ [show&nbsp;unified&nbsp;diffs]</a>
582
- @ &nbsp;&nbsp;
583
- @ <a href="%s(g.zTop)/vinfo/%T(zName)?sbs=1">
584
- @ [show&nbsp;side-by-side&nbsp;diffs]</a>
585
- }
586
- }
587
- @ &nbsp;&nbsp;
588
- @ <a href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)">[patch]</a><br/>
574
+ @ <a class="button" href="%s(g.zTop)/ci/%T(zName)">hide&nbsp;diffs</a>
575
+ if( sideBySide ){
576
+ @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=0">
577
+ @ unified&nbsp;diffs</a>
578
+ }else{
579
+ @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=1">
580
+ @ side-by-side&nbsp;diffs</a>
581
+ }
582
+ }else{
583
+ @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=0">
584
+ @ show&nbsp;unified&nbsp;diffs</a>
585
+ @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=1">
586
+ @ show&nbsp;side-by-side&nbsp;diffs</a>
587
+ }
588
+ }
589
+ @ <a class="button" href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)">
590
+ @ patch</a></div>
589591
db_prepare(&q,
590592
"SELECT name,"
591593
" mperm,"
592594
" (SELECT uuid FROM blob WHERE rid=mlink.pid),"
593595
" (SELECT uuid FROM blob WHERE rid=mlink.fid),"
@@ -1089,11 +1091,11 @@
10891091
pOut = &diff;
10901092
}
10911093
if( !sideBySide || isPatch ){
10921094
content_get(v1, &c1);
10931095
content_get(v2, &c2);
1094
- text_diff(&c1, &c2, pOut, 4, 1);
1096
+ text_diff(&c1, &c2, pOut, 4 | 0);
10951097
blob_reset(&c1);
10961098
blob_reset(&c2);
10971099
}
10981100
if( !isPatch ){
10991101
style_header("Diff");
11001102
--- src/info.c
+++ src/info.c
@@ -270,11 +270,11 @@
270 content_get(toid, &to);
271 }else{
272 blob_zero(&to);
273 }
274 blob_zero(&out);
275 text_diff(&from, &to, &out, 5, 1);
276 @ %h(blob_str(&out))
277 blob_reset(&from);
278 blob_reset(&to);
279 blob_reset(&out);
280 }
@@ -334,13 +334,17 @@
334 @ for %h(zName)</p>
335 }else{
336 @ <p>Changes to %h(zName)</p>
337 }
338 if( showDiff ){
339 @ <blockquote><pre>
340 append_diff(zOld, zNew);
341 @ </pre></blockquote>
 
 
 
 
342 }
343 }else{
344 if( zOld && zNew ){
345 if( fossil_strcmp(zOld, zNew)!=0 ){
346 @ <p>Modified <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
@@ -361,11 +365,11 @@
361 @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
362 @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
363 }
364 if( showDiff ){
365 if( sideBySide ){
366 generate_sbsdiff(zOld, zNew);
367 }else{
368 @ <blockquote><pre>
369 append_diff(zOld, zNew);
370 @ </pre></blockquote>
371 }
@@ -543,51 +547,49 @@
543 }
544 db_finalize(&q);
545 showTags(rid, "");
546 if( zParent ){
547 @ <div class="section">Changes</div>
 
548 showDiff = g.zPath[0]!='c';
549 if( db_get_boolean("show-version-diffs", 0)==0 ){
550 showDiff = !showDiff;
551 if( showDiff ){
552 @ <a href="%s(g.zTop)/vinfo/%T(zName)">[hide&nbsp;diffs]</a>
553 @ &nbsp;&nbsp;
554 if( sideBySide ){
555 @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0">
556 @ [unified&nbsp;diffs]</a>
557 }else{
558 @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1">
559 @ [side-by-side&nbsp;diffs]</a>
560 }
561 }else{
562 @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0">
563 @ [show&nbsp;unified&nbsp;diffs]</a>
564 @ &nbsp;&nbsp;
565 @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1">
566 @ [show&nbsp;side-by-side&nbsp;diffs]</a>
567 }
568 }else{
569 if( showDiff ){
570 @ <a href="%s(g.zTop)/ci/%T(zName)">[hide&nbsp;diffs]</a>
571 @ &nbsp;&nbsp;
572 if( sideBySide ){
573 @ <a href="%s(g.zTop)/info/%T(zName)?sbs=0">
574 @ [unified&nbsp;diffs]</a>
575 }else{
576 @ <a href="%s(g.zTop)/info/%T(zName)?sbs=1">
577 @ [side-by-side&nbsp;diffs]</a>
578 }
579 }else{
580 @ <a href="%s(g.zTop)/vinfo/%T(zName)?sbs=0">
581 @ [show&nbsp;unified&nbsp;diffs]</a>
582 @ &nbsp;&nbsp;
583 @ <a href="%s(g.zTop)/vinfo/%T(zName)?sbs=1">
584 @ [show&nbsp;side-by-side&nbsp;diffs]</a>
585 }
586 }
587 @ &nbsp;&nbsp;
588 @ <a href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)">[patch]</a><br/>
589 db_prepare(&q,
590 "SELECT name,"
591 " mperm,"
592 " (SELECT uuid FROM blob WHERE rid=mlink.pid),"
593 " (SELECT uuid FROM blob WHERE rid=mlink.fid),"
@@ -1089,11 +1091,11 @@
1089 pOut = &diff;
1090 }
1091 if( !sideBySide || isPatch ){
1092 content_get(v1, &c1);
1093 content_get(v2, &c2);
1094 text_diff(&c1, &c2, pOut, 4, 1);
1095 blob_reset(&c1);
1096 blob_reset(&c2);
1097 }
1098 if( !isPatch ){
1099 style_header("Diff");
1100
--- src/info.c
+++ src/info.c
@@ -270,11 +270,11 @@
270 content_get(toid, &to);
271 }else{
272 blob_zero(&to);
273 }
274 blob_zero(&out);
275 text_diff(&from, &to, &out, DIFF_IGNORE_EOLWS | 5);
276 @ %h(blob_str(&out))
277 blob_reset(&from);
278 blob_reset(&to);
279 blob_reset(&out);
280 }
@@ -334,13 +334,17 @@
334 @ for %h(zName)</p>
335 }else{
336 @ <p>Changes to %h(zName)</p>
337 }
338 if( showDiff ){
339 if( sideBySide ){
340 generate_sbsdiff(zOld, zNew);
341 }else{
342 @ <blockquote><pre>
343 append_diff(zOld, zNew);
344 @ </pre></blockquote>
345 }
346 }
347 }else{
348 if( zOld && zNew ){
349 if( fossil_strcmp(zOld, zNew)!=0 ){
350 @ <p>Modified <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
@@ -361,11 +365,11 @@
365 @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
366 @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
367 }
368 if( showDiff ){
369 if( sideBySide ){
370 generate_sbsdiff(zOld, zNew);
371 }else{
372 @ <blockquote><pre>
373 append_diff(zOld, zNew);
374 @ </pre></blockquote>
375 }
@@ -543,51 +547,49 @@
547 }
548 db_finalize(&q);
549 showTags(rid, "");
550 if( zParent ){
551 @ <div class="section">Changes</div>
552 @ <div class="sectionmenu">
553 showDiff = g.zPath[0]!='c';
554 if( db_get_boolean("show-version-diffs", 0)==0 ){
555 showDiff = !showDiff;
556 if( showDiff ){
557 @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)">
558 @ hide&nbsp;diffs</a>
559 if( sideBySide ){
560 @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
561 @ unified&nbsp;diffs</a>
562 }else{
563 @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
564 @ side-by-side&nbsp;diffs</a>
565 }
566 }else{
567 @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
568 @ show&nbsp;unified&nbsp;diffs</a>
569 @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
570 @ show&nbsp;side-by-side&nbsp;diffs</a>
 
571 }
572 }else{
573 if( showDiff ){
574 @ <a class="button" href="%s(g.zTop)/ci/%T(zName)">hide&nbsp;diffs</a>
575 if( sideBySide ){
576 @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=0">
577 @ unified&nbsp;diffs</a>
578 }else{
579 @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=1">
580 @ side-by-side&nbsp;diffs</a>
581 }
582 }else{
583 @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=0">
584 @ show&nbsp;unified&nbsp;diffs</a>
585 @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=1">
586 @ show&nbsp;side-by-side&nbsp;diffs</a>
587 }
588 }
589 @ <a class="button" href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)">
590 @ patch</a></div>
 
 
591 db_prepare(&q,
592 "SELECT name,"
593 " mperm,"
594 " (SELECT uuid FROM blob WHERE rid=mlink.pid),"
595 " (SELECT uuid FROM blob WHERE rid=mlink.fid),"
@@ -1089,11 +1091,11 @@
1091 pOut = &diff;
1092 }
1093 if( !sideBySide || isPatch ){
1094 content_get(v1, &c1);
1095 content_get(v2, &c2);
1096 text_diff(&c1, &c2, pOut, 4 | 0);
1097 blob_reset(&c1);
1098 blob_reset(&c2);
1099 }
1100 if( !isPatch ){
1101 style_header("Diff");
1102
+62 -30
--- src/login.c
+++ src/login.c
@@ -117,15 +117,20 @@
117117
** But some clients are behind firewalls that shift the IP address
118118
** with each HTTP request. To allow such (broken) clients to log in,
119119
** extract just a prefix of the IP address.
120120
*/
121121
static char *ipPrefix(const char *zIP){
122
- int i, j;
122
+ int i, j;
123
+ static int ip_prefix_terms = -1;
124
+ if( ip_prefix_terms<0 ){
125
+ ip_prefix_terms = db_get_int("ip-prefix-terms",2);
126
+ }
127
+ if( ip_prefix_terms==0 ) return mprintf("0");
123128
for(i=j=0; zIP[i]; i++){
124129
if( zIP[i]=='.' ){
125130
j++;
126
- if( j==2 ) break;
131
+ if( j==ip_prefix_terms ) break;
127132
}
128133
}
129134
return mprintf("%.*s", i, zIP);
130135
}
131136
@@ -192,10 +197,56 @@
192197
"INSERT INTO accesslog(uname,ipaddr,success,mtime)"
193198
"VALUES(%Q,%Q,%d,julianday('now'));",
194199
zUsername, zIpAddr, bSuccess
195200
);
196201
}
202
+
203
+/*
204
+** Look at the HTTP_USER_AGENT parameter and try to determine if the user agent
205
+** is a manually operated browser or a bot. When in doubt, assume a bot. Return
206
+** true if we believe the agent is a real person.
207
+*/
208
+static int isHuman(void){
209
+ const char *zAgent = P("HTTP_USER_AGENT");
210
+ int i;
211
+ if( zAgent==0 ) return 0;
212
+ for(i=0; zAgent[i]; i++){
213
+ if( zAgent[i]=='b' && memcmp(&zAgent[i],"bot",3)==0 ) return 0;
214
+ if( zAgent[i]=='s' && memcmp(&zAgent[i],"spider",6)==0 ) return 0;
215
+ }
216
+ if( memcmp(zAgent, "Mozilla/", 8)==0 ) return 1;
217
+ if( memcmp(zAgent, "Opera/", 6)==0 ) return 1;
218
+ if( memcmp(zAgent, "Safari/", 7)==0 ) return 1;
219
+ return 0;
220
+}
221
+
222
+/*
223
+** SQL function for constant time comparison of two values.
224
+** Sets result to 0 if two values are equal.
225
+*/
226
+static void constant_time_cmp_function(
227
+ sqlite3_context *context,
228
+ int argc,
229
+ sqlite3_value **argv
230
+){
231
+ const unsigned char *buf1, *buf2;
232
+ int len, i;
233
+ unsigned char rc = 0;
234
+
235
+ assert( argc==2 );
236
+ len = sqlite3_value_bytes(argv[0]);
237
+ if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){
238
+ rc = 1;
239
+ }else{
240
+ buf1 = sqlite3_value_text(argv[0]);
241
+ buf2 = sqlite3_value_text(argv[1]);
242
+ for( i=0; i<len; i++ ){
243
+ rc = rc | (buf1[i] ^ buf2[i]);
244
+ }
245
+ }
246
+ sqlite3_result_int(context, rc);
247
+}
197248
198249
/*
199250
** WEBPAGE: login
200251
** WEBPAGE: logout
201252
** WEBPAGE: my
@@ -217,20 +268,24 @@
217268
char *zSha1Pw;
218269
const char *zIpAddr; /* IP address of requestor */
219270
char *zRemoteAddr; /* Abbreviated IP address of requestor */
220271
221272
login_check_credentials();
273
+ sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
274
+ constant_time_cmp_function, 0, 0);
222275
zUsername = P("u");
223276
zPasswd = P("p");
224277
anonFlag = P("anon")!=0;
225278
if( P("out")!=0 ){
226279
/* To logout, change the cookie value to an empty string */
227280
const char *zCookieName = login_cookie_name();
228281
cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400);
229282
redirect_to_g();
230283
}
231
- if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
284
+ if( g.perm.Password && zPasswd
285
+ && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
286
+ ){
232287
/* The user requests a password change */
233288
zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
234289
if( db_int(1, "SELECT 0 FROM user"
235290
" WHERE uid=%d"
236291
" AND (constant_time_cmp(pw,%Q)=0"
@@ -454,37 +509,10 @@
454509
@ </form>
455510
}
456511
style_footer();
457512
}
458513
459
-/*
460
-** SQL function for constant time comparison of two values.
461
-** Sets result to 0 if two values are equal.
462
-*/
463
-static void constant_time_cmp_function(
464
- sqlite3_context *context,
465
- int argc,
466
- sqlite3_value **argv
467
-){
468
- const unsigned char *buf1, *buf2;
469
- int len, i;
470
- unsigned char rc = 0;
471
-
472
- assert( argc==2 );
473
- len = sqlite3_value_bytes(argv[0]);
474
- if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){
475
- rc = 1;
476
- }else{
477
- buf1 = sqlite3_value_text(argv[0]);
478
- buf2 = sqlite3_value_text(argv[1]);
479
- for( i=0; i<len; i++ ){
480
- rc = rc | (buf1[i] ^ buf2[i]);
481
- }
482
- }
483
- sqlite3_result_int(context, rc);
484
-}
485
-
486514
/*
487515
** Attempt to find login credentials for user zLogin on a peer repository
488516
** with project code zCode. Transfer those credentials to the local
489517
** repository.
490518
**
@@ -718,10 +746,14 @@
718746
}
719747
720748
/* Set the capabilities */
721749
login_set_capabilities(zCap, 0);
722750
login_set_anon_nobody_capabilities();
751
+ if( zCap[0] && !g.perm.History && db_get_boolean("auto-enable-hyperlinks",1)
752
+ && isHuman() ){
753
+ g.perm.History = 1;
754
+ }
723755
}
724756
725757
/*
726758
** Memory of settings
727759
*/
728760
--- src/login.c
+++ src/login.c
@@ -117,15 +117,20 @@
117 ** But some clients are behind firewalls that shift the IP address
118 ** with each HTTP request. To allow such (broken) clients to log in,
119 ** extract just a prefix of the IP address.
120 */
121 static char *ipPrefix(const char *zIP){
122 int i, j;
 
 
 
 
 
123 for(i=j=0; zIP[i]; i++){
124 if( zIP[i]=='.' ){
125 j++;
126 if( j==2 ) break;
127 }
128 }
129 return mprintf("%.*s", i, zIP);
130 }
131
@@ -192,10 +197,56 @@
192 "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
193 "VALUES(%Q,%Q,%d,julianday('now'));",
194 zUsername, zIpAddr, bSuccess
195 );
196 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
198 /*
199 ** WEBPAGE: login
200 ** WEBPAGE: logout
201 ** WEBPAGE: my
@@ -217,20 +268,24 @@
217 char *zSha1Pw;
218 const char *zIpAddr; /* IP address of requestor */
219 char *zRemoteAddr; /* Abbreviated IP address of requestor */
220
221 login_check_credentials();
 
 
222 zUsername = P("u");
223 zPasswd = P("p");
224 anonFlag = P("anon")!=0;
225 if( P("out")!=0 ){
226 /* To logout, change the cookie value to an empty string */
227 const char *zCookieName = login_cookie_name();
228 cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400);
229 redirect_to_g();
230 }
231 if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
 
 
232 /* The user requests a password change */
233 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
234 if( db_int(1, "SELECT 0 FROM user"
235 " WHERE uid=%d"
236 " AND (constant_time_cmp(pw,%Q)=0"
@@ -454,37 +509,10 @@
454 @ </form>
455 }
456 style_footer();
457 }
458
459 /*
460 ** SQL function for constant time comparison of two values.
461 ** Sets result to 0 if two values are equal.
462 */
463 static void constant_time_cmp_function(
464 sqlite3_context *context,
465 int argc,
466 sqlite3_value **argv
467 ){
468 const unsigned char *buf1, *buf2;
469 int len, i;
470 unsigned char rc = 0;
471
472 assert( argc==2 );
473 len = sqlite3_value_bytes(argv[0]);
474 if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){
475 rc = 1;
476 }else{
477 buf1 = sqlite3_value_text(argv[0]);
478 buf2 = sqlite3_value_text(argv[1]);
479 for( i=0; i<len; i++ ){
480 rc = rc | (buf1[i] ^ buf2[i]);
481 }
482 }
483 sqlite3_result_int(context, rc);
484 }
485
486 /*
487 ** Attempt to find login credentials for user zLogin on a peer repository
488 ** with project code zCode. Transfer those credentials to the local
489 ** repository.
490 **
@@ -718,10 +746,14 @@
718 }
719
720 /* Set the capabilities */
721 login_set_capabilities(zCap, 0);
722 login_set_anon_nobody_capabilities();
 
 
 
 
723 }
724
725 /*
726 ** Memory of settings
727 */
728
--- src/login.c
+++ src/login.c
@@ -117,15 +117,20 @@
117 ** But some clients are behind firewalls that shift the IP address
118 ** with each HTTP request. To allow such (broken) clients to log in,
119 ** extract just a prefix of the IP address.
120 */
121 static char *ipPrefix(const char *zIP){
122 int i, j;
123 static int ip_prefix_terms = -1;
124 if( ip_prefix_terms<0 ){
125 ip_prefix_terms = db_get_int("ip-prefix-terms",2);
126 }
127 if( ip_prefix_terms==0 ) return mprintf("0");
128 for(i=j=0; zIP[i]; i++){
129 if( zIP[i]=='.' ){
130 j++;
131 if( j==ip_prefix_terms ) break;
132 }
133 }
134 return mprintf("%.*s", i, zIP);
135 }
136
@@ -192,10 +197,56 @@
197 "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
198 "VALUES(%Q,%Q,%d,julianday('now'));",
199 zUsername, zIpAddr, bSuccess
200 );
201 }
202
203 /*
204 ** Look at the HTTP_USER_AGENT parameter and try to determine if the user agent
205 ** is a manually operated browser or a bot. When in doubt, assume a bot. Return
206 ** true if we believe the agent is a real person.
207 */
208 static int isHuman(void){
209 const char *zAgent = P("HTTP_USER_AGENT");
210 int i;
211 if( zAgent==0 ) return 0;
212 for(i=0; zAgent[i]; i++){
213 if( zAgent[i]=='b' && memcmp(&zAgent[i],"bot",3)==0 ) return 0;
214 if( zAgent[i]=='s' && memcmp(&zAgent[i],"spider",6)==0 ) return 0;
215 }
216 if( memcmp(zAgent, "Mozilla/", 8)==0 ) return 1;
217 if( memcmp(zAgent, "Opera/", 6)==0 ) return 1;
218 if( memcmp(zAgent, "Safari/", 7)==0 ) return 1;
219 return 0;
220 }
221
222 /*
223 ** SQL function for constant time comparison of two values.
224 ** Sets result to 0 if two values are equal.
225 */
226 static void constant_time_cmp_function(
227 sqlite3_context *context,
228 int argc,
229 sqlite3_value **argv
230 ){
231 const unsigned char *buf1, *buf2;
232 int len, i;
233 unsigned char rc = 0;
234
235 assert( argc==2 );
236 len = sqlite3_value_bytes(argv[0]);
237 if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){
238 rc = 1;
239 }else{
240 buf1 = sqlite3_value_text(argv[0]);
241 buf2 = sqlite3_value_text(argv[1]);
242 for( i=0; i<len; i++ ){
243 rc = rc | (buf1[i] ^ buf2[i]);
244 }
245 }
246 sqlite3_result_int(context, rc);
247 }
248
249 /*
250 ** WEBPAGE: login
251 ** WEBPAGE: logout
252 ** WEBPAGE: my
@@ -217,20 +268,24 @@
268 char *zSha1Pw;
269 const char *zIpAddr; /* IP address of requestor */
270 char *zRemoteAddr; /* Abbreviated IP address of requestor */
271
272 login_check_credentials();
273 sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
274 constant_time_cmp_function, 0, 0);
275 zUsername = P("u");
276 zPasswd = P("p");
277 anonFlag = P("anon")!=0;
278 if( P("out")!=0 ){
279 /* To logout, change the cookie value to an empty string */
280 const char *zCookieName = login_cookie_name();
281 cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400);
282 redirect_to_g();
283 }
284 if( g.perm.Password && zPasswd
285 && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
286 ){
287 /* The user requests a password change */
288 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
289 if( db_int(1, "SELECT 0 FROM user"
290 " WHERE uid=%d"
291 " AND (constant_time_cmp(pw,%Q)=0"
@@ -454,37 +509,10 @@
509 @ </form>
510 }
511 style_footer();
512 }
513
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514 /*
515 ** Attempt to find login credentials for user zLogin on a peer repository
516 ** with project code zCode. Transfer those credentials to the local
517 ** repository.
518 **
@@ -718,10 +746,14 @@
746 }
747
748 /* Set the capabilities */
749 login_set_capabilities(zCap, 0);
750 login_set_anon_nobody_capabilities();
751 if( zCap[0] && !g.perm.History && db_get_boolean("auto-enable-hyperlinks",1)
752 && isHuman() ){
753 g.perm.History = 1;
754 }
755 }
756
757 /*
758 ** Memory of settings
759 */
760
+2 -2
--- src/main.mk
+++ src/main.mk
@@ -287,12 +287,12 @@
287287
$(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c
288288
289289
# WARNING. DANGER. Running the testsuite modifies the repository the
290290
# build is done from, i.e. the checkout belongs to. Do not sync/push
291291
# the repository after running the tests.
292
-test: $(APPNAME)
293
- $(TCLSH) test/tester.tcl $(APPNAME)
292
+test: $(OBJDIR) $(APPNAME)
293
+ $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
294294
295295
$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
296296
$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
297297
298298
# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set
299299
--- src/main.mk
+++ src/main.mk
@@ -287,12 +287,12 @@
287 $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c
288
289 # WARNING. DANGER. Running the testsuite modifies the repository the
290 # build is done from, i.e. the checkout belongs to. Do not sync/push
291 # the repository after running the tests.
292 test: $(APPNAME)
293 $(TCLSH) test/tester.tcl $(APPNAME)
294
295 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
296 $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
297
298 # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set
299
--- src/main.mk
+++ src/main.mk
@@ -287,12 +287,12 @@
287 $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c
288
289 # WARNING. DANGER. Running the testsuite modifies the repository the
290 # build is done from, i.e. the checkout belongs to. Do not sync/push
291 # the repository after running the tests.
292 test: $(OBJDIR) $(APPNAME)
293 $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
294
295 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
296 $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
297
298 # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set
299
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -182,12 +182,12 @@
182182
$(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c
183183
184184
# WARNING. DANGER. Running the testsuite modifies the repository the
185185
# build is done from, i.e. the checkout belongs to. Do not sync/push
186186
# the repository after running the tests.
187
-test: $(APPNAME)
188
- $(TCLSH) test/tester.tcl $(APPNAME)
187
+test: $(OBJDIR) $(APPNAME)
188
+ $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
189189
190190
$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
191191
$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \
192192
$(SRCDIR)/../manifest \
193193
$(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
194194
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -182,12 +182,12 @@
182 $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c
183
184 # WARNING. DANGER. Running the testsuite modifies the repository the
185 # build is done from, i.e. the checkout belongs to. Do not sync/push
186 # the repository after running the tests.
187 test: $(APPNAME)
188 $(TCLSH) test/tester.tcl $(APPNAME)
189
190 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
191 $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \
192 $(SRCDIR)/../manifest \
193 $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
194
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -182,12 +182,12 @@
182 $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c
183
184 # WARNING. DANGER. Running the testsuite modifies the repository the
185 # build is done from, i.e. the checkout belongs to. Do not sync/push
186 # the repository after running the tests.
187 test: $(OBJDIR) $(APPNAME)
188 $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
189
190 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
191 $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \
192 $(SRCDIR)/../manifest \
193 $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
194
+2 -2
--- src/merge3.c
+++ src/merge3.c
@@ -171,12 +171,12 @@
171171
** is the number of lines of text to copy directly from the pivot,
172172
** the second integer is the number of lines of text to omit from the
173173
** pivot, and the third integer is the number of lines of text that are
174174
** inserted. The edit array ends with a triple of 0,0,0.
175175
*/
176
- aC1 = text_diff(pPivot, pV1, 0, 0, 0);
177
- aC2 = text_diff(pPivot, pV2, 0, 0, 0);
176
+ aC1 = text_diff(pPivot, pV1, 0, 0);
177
+ aC2 = text_diff(pPivot, pV2, 0, 0);
178178
if( aC1==0 || aC2==0 ){
179179
free(aC1);
180180
free(aC2);
181181
return -1;
182182
}
183183
--- src/merge3.c
+++ src/merge3.c
@@ -171,12 +171,12 @@
171 ** is the number of lines of text to copy directly from the pivot,
172 ** the second integer is the number of lines of text to omit from the
173 ** pivot, and the third integer is the number of lines of text that are
174 ** inserted. The edit array ends with a triple of 0,0,0.
175 */
176 aC1 = text_diff(pPivot, pV1, 0, 0, 0);
177 aC2 = text_diff(pPivot, pV2, 0, 0, 0);
178 if( aC1==0 || aC2==0 ){
179 free(aC1);
180 free(aC2);
181 return -1;
182 }
183
--- src/merge3.c
+++ src/merge3.c
@@ -171,12 +171,12 @@
171 ** is the number of lines of text to copy directly from the pivot,
172 ** the second integer is the number of lines of text to omit from the
173 ** pivot, and the third integer is the number of lines of text that are
174 ** inserted. The edit array ends with a triple of 0,0,0.
175 */
176 aC1 = text_diff(pPivot, pV1, 0, 0);
177 aC2 = text_diff(pPivot, pV2, 0, 0);
178 if( aC1==0 || aC2==0 ){
179 free(aC1);
180 free(aC2);
181 return -1;
182 }
183
+1 -1
--- src/printf.c
+++ src/printf.c
@@ -545,11 +545,11 @@
545545
buf[0] = '%';
546546
bufpt = buf;
547547
length = 1;
548548
break;
549549
case etCHARX:
550
- c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
550
+ c = buf[0] = va_arg(ap,int);
551551
if( precision>=0 ){
552552
for(idx=1; idx<precision; idx++) buf[idx] = c;
553553
length = precision;
554554
}else{
555555
length =1;
556556
--- src/printf.c
+++ src/printf.c
@@ -545,11 +545,11 @@
545 buf[0] = '%';
546 bufpt = buf;
547 length = 1;
548 break;
549 case etCHARX:
550 c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
551 if( precision>=0 ){
552 for(idx=1; idx<precision; idx++) buf[idx] = c;
553 length = precision;
554 }else{
555 length =1;
556
--- src/printf.c
+++ src/printf.c
@@ -545,11 +545,11 @@
545 buf[0] = '%';
546 bufpt = buf;
547 length = 1;
548 break;
549 case etCHARX:
550 c = buf[0] = va_arg(ap,int);
551 if( precision>=0 ){
552 for(idx=1; idx<precision; idx++) buf[idx] = c;
553 length = precision;
554 }else{
555 length =1;
556
+1 -1
--- src/report.c
+++ src/report.c
@@ -1115,11 +1115,11 @@
11151115
if( rn ){
11161116
db_prepare(&q,
11171117
"SELECT sqlcode FROM reportfmt WHERE rn=%d", rn);
11181118
}else{
11191119
db_prepare(&q,
1120
- "SELECT sqlcode FROM reportfmt WHERE title='%s'", zRep);
1120
+ "SELECT sqlcode FROM reportfmt WHERE title=%Q", zRep);
11211121
}
11221122
if( db_step(&q)!=SQLITE_ROW ){
11231123
db_finalize(&q);
11241124
rpt_list_reports();
11251125
fossil_fatal("unknown report format(%s)!",zRep);
11261126
--- src/report.c
+++ src/report.c
@@ -1115,11 +1115,11 @@
1115 if( rn ){
1116 db_prepare(&q,
1117 "SELECT sqlcode FROM reportfmt WHERE rn=%d", rn);
1118 }else{
1119 db_prepare(&q,
1120 "SELECT sqlcode FROM reportfmt WHERE title='%s'", zRep);
1121 }
1122 if( db_step(&q)!=SQLITE_ROW ){
1123 db_finalize(&q);
1124 rpt_list_reports();
1125 fossil_fatal("unknown report format(%s)!",zRep);
1126
--- src/report.c
+++ src/report.c
@@ -1115,11 +1115,11 @@
1115 if( rn ){
1116 db_prepare(&q,
1117 "SELECT sqlcode FROM reportfmt WHERE rn=%d", rn);
1118 }else{
1119 db_prepare(&q,
1120 "SELECT sqlcode FROM reportfmt WHERE title=%Q", zRep);
1121 }
1122 if( db_step(&q)!=SQLITE_ROW ){
1123 db_finalize(&q);
1124 rpt_list_reports();
1125 fossil_fatal("unknown report format(%s)!",zRep);
1126
+18
--- src/setup.c
+++ src/setup.c
@@ -864,10 +864,17 @@
864864
"remote_user_ok", "remote_user_ok", 0);
865865
@ <p>When enabled, if the REMOTE_USER environment variable is set to the
866866
@ login name of a valid user and no other login credentials are available,
867867
@ then the REMOTE_USER is accepted as an authenticated user.
868868
@ </p>
869
+ @
870
+ @ <hr />
871
+ entry_attribute("IP address turns used in login cookie", 3, "ip-prefix-terms", "ipt",
872
+ "2");
873
+ @ <p>The number of octets of of the IP address used in the login cookie. Set to
874
+ @ zero to omit the IP address from the login cookie. A value of 2 is recommended.
875
+ @ </p>
869876
@
870877
@ <hr />
871878
entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
872879
@ <p>The number of hours for which a login is valid. This must be a
873880
@ positive number. The default is 8760 hours which is approximately equal
@@ -879,10 +886,21 @@
879886
@ <p>Fossil tries to limit out-bound sync, clone, and pull packets
880887
@ to this many bytes, uncompressed. If the client requires more data
881888
@ than this, then the client will issue multiple HTTP requests.
882889
@ Values below 1 million are not recommended. 5 million is a
883890
@ reasonable number.</p>
891
+
892
+ @ <hr />
893
+ onoff_attribute("Enable hyperlinks for \"nobody\" based on User-Agent",
894
+ "auto-enable-hyperlinks", "autohyperlink", 1);
895
+ @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users
896
+ @ including user "nobody", as long as the User-Agent string in the HTTP header
897
+ @ indicates that the request is coming from an actual human being and not a
898
+ @ a robot or script. Note: Bots can specify whatever User-Agent string they
899
+ @ that want. So a bot that wants to impersonate a human can easily do so.
900
+ @ Hence, this technique does not necessarily exclude malicious bots.
901
+ @ </p>
884902
885903
@ <hr />
886904
onoff_attribute("Allow users to register themselves",
887905
"self-register", "selfregister", 0);
888906
@ <p>Allow users to register themselves through the HTTP UI.
889907
--- src/setup.c
+++ src/setup.c
@@ -864,10 +864,17 @@
864 "remote_user_ok", "remote_user_ok", 0);
865 @ <p>When enabled, if the REMOTE_USER environment variable is set to the
866 @ login name of a valid user and no other login credentials are available,
867 @ then the REMOTE_USER is accepted as an authenticated user.
868 @ </p>
 
 
 
 
 
 
 
869 @
870 @ <hr />
871 entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
872 @ <p>The number of hours for which a login is valid. This must be a
873 @ positive number. The default is 8760 hours which is approximately equal
@@ -879,10 +886,21 @@
879 @ <p>Fossil tries to limit out-bound sync, clone, and pull packets
880 @ to this many bytes, uncompressed. If the client requires more data
881 @ than this, then the client will issue multiple HTTP requests.
882 @ Values below 1 million are not recommended. 5 million is a
883 @ reasonable number.</p>
 
 
 
 
 
 
 
 
 
 
 
884
885 @ <hr />
886 onoff_attribute("Allow users to register themselves",
887 "self-register", "selfregister", 0);
888 @ <p>Allow users to register themselves through the HTTP UI.
889
--- src/setup.c
+++ src/setup.c
@@ -864,10 +864,17 @@
864 "remote_user_ok", "remote_user_ok", 0);
865 @ <p>When enabled, if the REMOTE_USER environment variable is set to the
866 @ login name of a valid user and no other login credentials are available,
867 @ then the REMOTE_USER is accepted as an authenticated user.
868 @ </p>
869 @
870 @ <hr />
871 entry_attribute("IP address turns used in login cookie", 3, "ip-prefix-terms", "ipt",
872 "2");
873 @ <p>The number of octets of of the IP address used in the login cookie. Set to
874 @ zero to omit the IP address from the login cookie. A value of 2 is recommended.
875 @ </p>
876 @
877 @ <hr />
878 entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
879 @ <p>The number of hours for which a login is valid. This must be a
880 @ positive number. The default is 8760 hours which is approximately equal
@@ -879,10 +886,21 @@
886 @ <p>Fossil tries to limit out-bound sync, clone, and pull packets
887 @ to this many bytes, uncompressed. If the client requires more data
888 @ than this, then the client will issue multiple HTTP requests.
889 @ Values below 1 million are not recommended. 5 million is a
890 @ reasonable number.</p>
891
892 @ <hr />
893 onoff_attribute("Enable hyperlinks for \"nobody\" based on User-Agent",
894 "auto-enable-hyperlinks", "autohyperlink", 1);
895 @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users
896 @ including user "nobody", as long as the User-Agent string in the HTTP header
897 @ indicates that the request is coming from an actual human being and not a
898 @ a robot or script. Note: Bots can specify whatever User-Agent string they
899 @ that want. So a bot that wants to impersonate a human can easily do so.
900 @ Hence, this technique does not necessarily exclude malicious bots.
901 @ </p>
902
903 @ <hr />
904 onoff_attribute("Allow users to register themselves",
905 "self-register", "selfregister", 0);
906 @ <p>Allow users to register themselves through the HTTP UI.
907
+73 -67
--- src/skins.c
+++ src/skins.c
@@ -86,29 +86,30 @@
8686
@ text-align: center;
8787
@ letter-spacing: 1px;
8888
@ background-color: #404040;
8989
@ color: white;
9090
@ }
91
-@
91
+@
9292
@ /* The submenu bar that *sometimes* appears below the main menu */
93
-@ div.submenu {
93
+@ div.submenu, div.sectionmenu {
9494
@ padding: 3px 10px 3px 0px;
9595
@ font-size: 0.9em;
9696
@ text-align: center;
9797
@ background-color: #606060;
9898
@ color: white;
9999
@ }
100
-@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
100
+@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
101
+@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
101102
@ padding: 3px 10px 3px 10px;
102103
@ color: white;
103104
@ text-decoration: none;
104105
@ }
105
-@ div.mainmenu a:hover, div.submenu a:hover {
106
+@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
106107
@ color: #404040;
107108
@ background-color: white;
108109
@ }
109
-@
110
+@
110111
@ /* All page content from the bottom of the menu or submenu down to
111112
@ ** the footer */
112113
@ div.content {
113114
@ padding: 0ex 0ex 0ex 0ex;
114115
@ }
@@ -208,49 +209,47 @@
208209
@ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
209210
@ media="screen">
210211
@ </head>
211212
@ <body>
212213
@ <div class="header">
213
-@ <div class="logo">
214
-@ <img src="$home/logo" alt="logo">
215
-@ </div>
216214
@ <div class="title"><small>$<project_name></small><br />$<title></div>
217215
@ <div class="status"><nobr><th1>
218216
@ if {[info exists login]} {
219217
@ puts "Logged in as $login"
220218
@ } else {
221219
@ puts "Not logged in"
222220
@ }
223221
@ </th1></nobr></div>
224222
@ </div>
225
-@ <div class="mainmenu"><th1>
226
-@ html "<a href=''$home$index_page''>Home</a> "
223
+@ <div class="mainmenu">
224
+@ <th1>
225
+@ html "<a href=''$home$index_page''>Home</a>\n"
227226
@ if {[anycap jor]} {
228
-@ html "<a href=''$home/timeline''>Timeline</a> "
227
+@ html "<a href=''$home/timeline''>Timeline</a>\n"
229228
@ }
230229
@ if {[hascap oh]} {
231
-@ html "<a href=''$home/dir?ci=tip''>Files</a> "
230
+@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
232231
@ }
233232
@ if {[hascap o]} {
234
-@ html "<a href=''$home/brlist''>Branches</a> "
235
-@ html "<a href=''$home/taglist''>Tags</a> "
233
+@ html "<a href=''$home/brlist''>Branches</a>\n"
234
+@ html "<a href=''$home/taglist''>Tags</a>\n"
236235
@ }
237236
@ if {[hascap r]} {
238
-@ html "<a href=''$home/reportlist''>Tickets</a> "
237
+@ html "<a href=''$home/reportlist''>Tickets</a>\n"
239238
@ }
240239
@ if {[hascap j]} {
241
-@ html "<a href=''$home/wiki''>Wiki</a> "
240
+@ html "<a href=''$home/wiki''>Wiki</a>\n"
242241
@ }
243242
@ if {[hascap s]} {
244
-@ html "<a href=''$home/setup''>Admin</a> "
243
+@ html "<a href=''$home/setup''>Admin</a>\n"
245244
@ } elseif {[hascap a]} {
246
-@ html "<a href=''$home/setup_ulist''>Users</a> "
245
+@ html "<a href=''$home/setup_ulist''>Users</a>\n"
247246
@ }
248247
@ if {[info exists login]} {
249
-@ html "<a href=''$home/login''>Logout</a> "
248
+@ html "<a href=''$home/login''>Logout</a>\n"
250249
@ } else {
251
-@ html "<a href=''$home/login''>Login</a> "
250
+@ html "<a href=''$home/login''>Login</a>\n"
252251
@ }
253252
@ </th1></div>
254253
@ ');
255254
@ REPLACE INTO config(name,mtime,value)
256255
@ VALUES('footer',now(),'<div class="footer">
@@ -324,23 +323,24 @@
324323
@ background-color: #a09048;
325324
@ color: black;
326325
@ }
327326
@
328327
@ /* The submenu bar that *sometimes* appears below the main menu */
329
-@ div.submenu {
328
+@ div.submenu, div.sectionmenu {
330329
@ padding: 3px 10px 3px 0px;
331330
@ font-size: 0.9em;
332331
@ text-align: center;
333332
@ background-color: #c0af58;
334333
@ color: white;
335334
@ }
336
-@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
335
+@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
336
+@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
337337
@ padding: 3px 10px 3px 10px;
338338
@ color: white;
339339
@ text-decoration: none;
340340
@ }
341
-@ div.mainmenu a:hover, div.submenu a:hover {
341
+@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
342342
@ color: #a09048;
343343
@ background-color: white;
344344
@ }
345345
@
346346
@ /* All page content from the bottom of the menu or submenu down to
@@ -466,37 +466,38 @@
466466
@ } else {
467467
@ puts "Not logged in"
468468
@ }
469469
@ </th1></nobr></div>
470470
@ </div>
471
-@ <div class="mainmenu"><th1>
472
-@ html "<a href=''$home$index_page''>Home</a> "
471
+@ <div class="mainmenu">
472
+@ <th1>
473
+@ html "<a href=''$home$index_page''>Home</a>\n"
473474
@ if {[anycap jor]} {
474
-@ html "<a href=''$home/timeline''>Timeline</a> "
475
+@ html "<a href=''$home/timeline''>Timeline</a>\n"
475476
@ }
476477
@ if {[hascap oh]} {
477
-@ html "<a href=''$home/dir?ci=tip''>Files</a> "
478
+@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
478479
@ }
479480
@ if {[hascap o]} {
480
-@ html "<a href=''$home/brlist''>Branches</a> "
481
-@ html "<a href=''$home/taglist''>Tags</a> "
481
+@ html "<a href=''$home/brlist''>Branches</a>\n"
482
+@ html "<a href=''$home/taglist''>Tags</a>\n"
482483
@ }
483484
@ if {[hascap r]} {
484
-@ html "<a href=''$home/reportlist''>Tickets</a> "
485
+@ html "<a href=''$home/reportlist''>Tickets</a>\n"
485486
@ }
486487
@ if {[hascap j]} {
487
-@ html "<a href=''$home/wiki''>Wiki</a> "
488
+@ html "<a href=''$home/wiki''>Wiki</a>\n"
488489
@ }
489490
@ if {[hascap s]} {
490
-@ html "<a href=''$home/setup''>Admin</a> "
491
+@ html "<a href=''$home/setup''>Admin</a>\n"
491492
@ } elseif {[hascap a]} {
492
-@ html "<a href=''$home/setup_ulist''>Users</a> "
493
+@ html "<a href=''$home/setup_ulist''>Users</a>\n"
493494
@ }
494495
@ if {[info exists login]} {
495
-@ html "<a href=''$home/login''>Logout</a> "
496
+@ html "<a href=''$home/login''>Logout</a>\n"
496497
@ } else {
497
-@ html "<a href=''$home/login''>Login</a> "
498
+@ html "<a href=''$home/login''>Login</a>\n"
498499
@ }
499500
@ </th1></div>
500501
@ ');
501502
@ REPLACE INTO config(name,mtime,value)
502503
@ VALUES('footer',now(),'<div class="footer">
@@ -605,25 +606,26 @@
605606
@ #container {
606607
@ padding-left: 9em;
607608
@ }
608609
@
609610
@ /* The submenu bar that *sometimes* appears below the main menu */
610
-@ div.submenu {
611
+@ div.submenu, div.sectionmenu {
611612
@ padding: 3px 10px 3px 10px;
612613
@ font-size: 0.9em;
613614
@ text-align: center;
614615
@ border:1px solid #999;
615616
@ border-width:1px 0px;
616617
@ background-color: #eee;
617618
@ color: #333;
618619
@ }
619
-@ div.submenu a, div.submenu a:visited {
620
+@ div.submenu a, div.submenu a:visited, div.sectionmenu>a.button:link,
621
+@ div.sectionmenu>a.button:visited {
620622
@ padding: 3px 10px 3px 10px;
621623
@ color: #333;
622624
@ text-decoration: none;
623625
@ }
624
-@ div.submenu a:hover {
626
+@ div.submenu a:hover, div.sectionmenu>a.button:hover {
625627
@ color: #eee;
626628
@ background-color: #333;
627629
@ }
628630
@
629631
@ /* All page content from the bottom of the menu or submenu down to
@@ -748,37 +750,38 @@
748750
@ } else {
749751
@ puts "Not logged in"
750752
@ }
751753
@ </th1></nobr></div>
752754
@ </div>
753
-@ <div class="mainmenu"><th1>
754
-@ html "<li><a href=''$home$index_page''>Home</a></li>"
755
+@ <div class="mainmenu">
756
+@ <th1>
757
+@ html "<a href=''$home$index_page''>Home</a>\n"
755758
@ if {[anycap jor]} {
756
-@ html "<li><a href=''$home/timeline''>Timeline</a></li>"
759
+@ html "<a href=''$home/timeline''>Timeline</a>\n"
757760
@ }
758761
@ if {[hascap oh]} {
759
-@ html "<li><a href=''$home/dir?ci=tip''>Files</a></li>"
762
+@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
760763
@ }
761764
@ if {[hascap o]} {
762
-@ html "<li><a href=''$home/brlist''>Branches</a></li>"
763
-@ html "<li><a href=''$home/taglist''>Tags</a></li>"
765
+@ html "<a href=''$home/brlist''>Branches</a>\n"
766
+@ html "<a href=''$home/taglist''>Tags</a>\n"
764767
@ }
765768
@ if {[hascap r]} {
766
-@ html "<li><a href=''$home/reportlist''>Tickets</a></li>"
769
+@ html "<a href=''$home/reportlist''>Tickets</a>\n"
767770
@ }
768771
@ if {[hascap j]} {
769
-@ html "<li><a href=''$home/wiki''>Wiki</a></li>"
772
+@ html "<a href=''$home/wiki''>Wiki</a>\n"
770773
@ }
771774
@ if {[hascap s]} {
772
-@ html "<li><a href=''$home/setup''>Admin</a></li>"
775
+@ html "<a href=''$home/setup''>Admin</a>\n"
773776
@ } elseif {[hascap a]} {
774
-@ html "<li><a href=''$home/setup_ulist''>Users</a></li>"
777
+@ html "<a href=''$home/setup_ulist''>Users</a>\n"
775778
@ }
776779
@ if {[info exists login]} {
777
-@ html "<li><a href=''$home/login''>Logout</a></li>"
780
+@ html "<a href=''$home/login''>Logout</a>\n"
778781
@ } else {
779
-@ html "<li><a href=''$home/login''>Login</a></li>"
782
+@ html "<a href=''$home/login''>Login</a>\n"
780783
@ }
781784
@ </th1></ul></div>
782785
@ <div id="container">
783786
@ ');
784787
@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
@@ -859,12 +862,13 @@
859862
@ -webkit-border-top-left-radius: 5px;
860863
@ -border-top-right-radius: 5px;
861864
@ -border-top-left-radius: 5px;
862865
@ border-top-left-radius: 5px;
863866
@ border-top-right-radius: 5px;
864
-@ vertical-align: center;
865
-@ min-height: 2em;
867
+@ vertical-align: middle;
868
+@ padding-top: 8px;
869
+@ padding-bottom: 8px;
866870
@ background-color: #446979;
867871
@ background: -webkit-gradient(linear,left bottom,left top, color-stop(0.02, rgb(51,81,94)), color-stop(0.76, rgb(85,129,149)));
868872
@ background: -moz-linear-gradient(center bottom,rgb(51,81,94) 2%, rgb(85,129,149) 76%);
869873
@ -webkit-box-shadow: 0px 3px 4px #333333;
870874
@ -moz-box-shadow: 0px 3px 4px #333333;
@@ -887,11 +891,12 @@
887891
@ div.mainmenu a, div.mainmenu a:visited {
888892
@ padding: 3px 10px 3px 10px;
889893
@ color: white;
890894
@ text-decoration: none;
891895
@ }
892
-@ div.submenu a, div.submenu a:visited {
896
+@ div.submenu a, div.submenu a:visited, a.button,
897
+@ div.sectionmenu>a.button:link, div.sectinmenu>a.button:visited {
893898
@ padding: 2px 8px;
894899
@ color: #000;
895900
@ font-family: Arial;
896901
@ text-decoration: none;
897902
@ margin:auto;
@@ -909,11 +914,11 @@
909914
@ div.mainmenu a:hover {
910915
@ color: #000;
911916
@ background-color: white;
912917
@ }
913918
@
914
-@ div.submenu a:hover {
919
+@ div.submenu a:hover, div.sectionmenu>a.button:hover {
915920
@ background: -webkit-gradient(linear,left bottom, left top, color-stop(0, rgb(214,214,214)), color-stop(0.75, rgb(184,184,184)));
916921
@ background: -moz-linear-gradient(center bottom, rgb(214,214,214) 0%, rgb(184,184,184) 75%);
917922
@ background-color: #c0c0c0 ;
918923
@ }
919924
@
@@ -1110,39 +1115,40 @@
11101115
@ } else {
11111116
@ puts "Not logged in"
11121117
@ }
11131118
@ </th1></nobr></div>
11141119
@ </div>
1115
-@ <div class="mainmenu"><ul><th1>
1116
-@ html "<a href=''$home$index_page''>Home</a>"
1120
+@ <div class="mainmenu">
1121
+@ <th1>
1122
+@ html "<a href=''$home$index_page''>Home</a>\n"
11171123
@ if {[anycap jor]} {
1118
-@ html "<a href=''$home/timeline''>Timeline</a>"
1124
+@ html "<a href=''$home/timeline''>Timeline</a>\n"
11191125
@ }
11201126
@ if {[hascap oh]} {
1121
-@ html "<a href=''$home/dir?ci=tip''>Files</a>"
1127
+@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
11221128
@ }
11231129
@ if {[hascap o]} {
1124
-@ html "<a href=''$home/brlist''>Branches</a>"
1125
-@ html "<a href=''$home/taglist''>Tags</a>"
1130
+@ html "<a href=''$home/brlist''>Branches</a>\n"
1131
+@ html "<a href=''$home/taglist''>Tags</a>\n"
11261132
@ }
11271133
@ if {[hascap r]} {
1128
-@ html "<a href=''$home/reportlist''>Tickets</a>"
1134
+@ html "<a href=''$home/reportlist''>Tickets</a>\n"
11291135
@ }
11301136
@ if {[hascap j]} {
1131
-@ html "<a href=''$home/wiki''>Wiki</a>"
1137
+@ html "<a href=''$home/wiki''>Wiki</a>\n"
11321138
@ }
11331139
@ if {[hascap s]} {
1134
-@ html "<a href=''$home/setup''>Admin</a>"
1140
+@ html "<a href=''$home/setup''>Admin</a>\n"
11351141
@ } elseif {[hascap a]} {
1136
-@ html "<a href=''$home/setup_ulist''>Users</a>"
1142
+@ html "<a href=''$home/setup_ulist''>Users</a>\n"
11371143
@ }
11381144
@ if {[info exists login]} {
1139
-@ html "<a href=''$home/login''>Logout</a>"
1145
+@ html "<a href=''$home/login''>Logout</a>\n"
11401146
@ } else {
1141
-@ html "<a href=''$home/login''>Login</a>"
1147
+@ html "<a href=''$home/login''>Login</a>\n"
11421148
@ }
1143
-@ </th1></ul></div>
1149
+@ </th1></div>
11441150
@ <div id="container">
11451151
@ ');
11461152
@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
11471153
@ <div class="footer">
11481154
@ Fossil version $manifest_version $manifest_date
11491155
--- src/skins.c
+++ src/skins.c
@@ -86,29 +86,30 @@
86 @ text-align: center;
87 @ letter-spacing: 1px;
88 @ background-color: #404040;
89 @ color: white;
90 @ }
91 @
92 @ /* The submenu bar that *sometimes* appears below the main menu */
93 @ div.submenu {
94 @ padding: 3px 10px 3px 0px;
95 @ font-size: 0.9em;
96 @ text-align: center;
97 @ background-color: #606060;
98 @ color: white;
99 @ }
100 @ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
 
101 @ padding: 3px 10px 3px 10px;
102 @ color: white;
103 @ text-decoration: none;
104 @ }
105 @ div.mainmenu a:hover, div.submenu a:hover {
106 @ color: #404040;
107 @ background-color: white;
108 @ }
109 @
110 @ /* All page content from the bottom of the menu or submenu down to
111 @ ** the footer */
112 @ div.content {
113 @ padding: 0ex 0ex 0ex 0ex;
114 @ }
@@ -208,49 +209,47 @@
208 @ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
209 @ media="screen">
210 @ </head>
211 @ <body>
212 @ <div class="header">
213 @ <div class="logo">
214 @ <img src="$home/logo" alt="logo">
215 @ </div>
216 @ <div class="title"><small>$<project_name></small><br />$<title></div>
217 @ <div class="status"><nobr><th1>
218 @ if {[info exists login]} {
219 @ puts "Logged in as $login"
220 @ } else {
221 @ puts "Not logged in"
222 @ }
223 @ </th1></nobr></div>
224 @ </div>
225 @ <div class="mainmenu"><th1>
226 @ html "<a href=''$home$index_page''>Home</a> "
 
227 @ if {[anycap jor]} {
228 @ html "<a href=''$home/timeline''>Timeline</a> "
229 @ }
230 @ if {[hascap oh]} {
231 @ html "<a href=''$home/dir?ci=tip''>Files</a> "
232 @ }
233 @ if {[hascap o]} {
234 @ html "<a href=''$home/brlist''>Branches</a> "
235 @ html "<a href=''$home/taglist''>Tags</a> "
236 @ }
237 @ if {[hascap r]} {
238 @ html "<a href=''$home/reportlist''>Tickets</a> "
239 @ }
240 @ if {[hascap j]} {
241 @ html "<a href=''$home/wiki''>Wiki</a> "
242 @ }
243 @ if {[hascap s]} {
244 @ html "<a href=''$home/setup''>Admin</a> "
245 @ } elseif {[hascap a]} {
246 @ html "<a href=''$home/setup_ulist''>Users</a> "
247 @ }
248 @ if {[info exists login]} {
249 @ html "<a href=''$home/login''>Logout</a> "
250 @ } else {
251 @ html "<a href=''$home/login''>Login</a> "
252 @ }
253 @ </th1></div>
254 @ ');
255 @ REPLACE INTO config(name,mtime,value)
256 @ VALUES('footer',now(),'<div class="footer">
@@ -324,23 +323,24 @@
324 @ background-color: #a09048;
325 @ color: black;
326 @ }
327 @
328 @ /* The submenu bar that *sometimes* appears below the main menu */
329 @ div.submenu {
330 @ padding: 3px 10px 3px 0px;
331 @ font-size: 0.9em;
332 @ text-align: center;
333 @ background-color: #c0af58;
334 @ color: white;
335 @ }
336 @ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
 
337 @ padding: 3px 10px 3px 10px;
338 @ color: white;
339 @ text-decoration: none;
340 @ }
341 @ div.mainmenu a:hover, div.submenu a:hover {
342 @ color: #a09048;
343 @ background-color: white;
344 @ }
345 @
346 @ /* All page content from the bottom of the menu or submenu down to
@@ -466,37 +466,38 @@
466 @ } else {
467 @ puts "Not logged in"
468 @ }
469 @ </th1></nobr></div>
470 @ </div>
471 @ <div class="mainmenu"><th1>
472 @ html "<a href=''$home$index_page''>Home</a> "
 
473 @ if {[anycap jor]} {
474 @ html "<a href=''$home/timeline''>Timeline</a> "
475 @ }
476 @ if {[hascap oh]} {
477 @ html "<a href=''$home/dir?ci=tip''>Files</a> "
478 @ }
479 @ if {[hascap o]} {
480 @ html "<a href=''$home/brlist''>Branches</a> "
481 @ html "<a href=''$home/taglist''>Tags</a> "
482 @ }
483 @ if {[hascap r]} {
484 @ html "<a href=''$home/reportlist''>Tickets</a> "
485 @ }
486 @ if {[hascap j]} {
487 @ html "<a href=''$home/wiki''>Wiki</a> "
488 @ }
489 @ if {[hascap s]} {
490 @ html "<a href=''$home/setup''>Admin</a> "
491 @ } elseif {[hascap a]} {
492 @ html "<a href=''$home/setup_ulist''>Users</a> "
493 @ }
494 @ if {[info exists login]} {
495 @ html "<a href=''$home/login''>Logout</a> "
496 @ } else {
497 @ html "<a href=''$home/login''>Login</a> "
498 @ }
499 @ </th1></div>
500 @ ');
501 @ REPLACE INTO config(name,mtime,value)
502 @ VALUES('footer',now(),'<div class="footer">
@@ -605,25 +606,26 @@
605 @ #container {
606 @ padding-left: 9em;
607 @ }
608 @
609 @ /* The submenu bar that *sometimes* appears below the main menu */
610 @ div.submenu {
611 @ padding: 3px 10px 3px 10px;
612 @ font-size: 0.9em;
613 @ text-align: center;
614 @ border:1px solid #999;
615 @ border-width:1px 0px;
616 @ background-color: #eee;
617 @ color: #333;
618 @ }
619 @ div.submenu a, div.submenu a:visited {
 
620 @ padding: 3px 10px 3px 10px;
621 @ color: #333;
622 @ text-decoration: none;
623 @ }
624 @ div.submenu a:hover {
625 @ color: #eee;
626 @ background-color: #333;
627 @ }
628 @
629 @ /* All page content from the bottom of the menu or submenu down to
@@ -748,37 +750,38 @@
748 @ } else {
749 @ puts "Not logged in"
750 @ }
751 @ </th1></nobr></div>
752 @ </div>
753 @ <div class="mainmenu"><th1>
754 @ html "<li><a href=''$home$index_page''>Home</a></li>"
 
755 @ if {[anycap jor]} {
756 @ html "<li><a href=''$home/timeline''>Timeline</a></li>"
757 @ }
758 @ if {[hascap oh]} {
759 @ html "<li><a href=''$home/dir?ci=tip''>Files</a></li>"
760 @ }
761 @ if {[hascap o]} {
762 @ html "<li><a href=''$home/brlist''>Branches</a></li>"
763 @ html "<li><a href=''$home/taglist''>Tags</a></li>"
764 @ }
765 @ if {[hascap r]} {
766 @ html "<li><a href=''$home/reportlist''>Tickets</a></li>"
767 @ }
768 @ if {[hascap j]} {
769 @ html "<li><a href=''$home/wiki''>Wiki</a></li>"
770 @ }
771 @ if {[hascap s]} {
772 @ html "<li><a href=''$home/setup''>Admin</a></li>"
773 @ } elseif {[hascap a]} {
774 @ html "<li><a href=''$home/setup_ulist''>Users</a></li>"
775 @ }
776 @ if {[info exists login]} {
777 @ html "<li><a href=''$home/login''>Logout</a></li>"
778 @ } else {
779 @ html "<li><a href=''$home/login''>Login</a></li>"
780 @ }
781 @ </th1></ul></div>
782 @ <div id="container">
783 @ ');
784 @ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
@@ -859,12 +862,13 @@
859 @ -webkit-border-top-left-radius: 5px;
860 @ -border-top-right-radius: 5px;
861 @ -border-top-left-radius: 5px;
862 @ border-top-left-radius: 5px;
863 @ border-top-right-radius: 5px;
864 @ vertical-align: center;
865 @ min-height: 2em;
 
866 @ background-color: #446979;
867 @ background: -webkit-gradient(linear,left bottom,left top, color-stop(0.02, rgb(51,81,94)), color-stop(0.76, rgb(85,129,149)));
868 @ background: -moz-linear-gradient(center bottom,rgb(51,81,94) 2%, rgb(85,129,149) 76%);
869 @ -webkit-box-shadow: 0px 3px 4px #333333;
870 @ -moz-box-shadow: 0px 3px 4px #333333;
@@ -887,11 +891,12 @@
887 @ div.mainmenu a, div.mainmenu a:visited {
888 @ padding: 3px 10px 3px 10px;
889 @ color: white;
890 @ text-decoration: none;
891 @ }
892 @ div.submenu a, div.submenu a:visited {
 
893 @ padding: 2px 8px;
894 @ color: #000;
895 @ font-family: Arial;
896 @ text-decoration: none;
897 @ margin:auto;
@@ -909,11 +914,11 @@
909 @ div.mainmenu a:hover {
910 @ color: #000;
911 @ background-color: white;
912 @ }
913 @
914 @ div.submenu a:hover {
915 @ background: -webkit-gradient(linear,left bottom, left top, color-stop(0, rgb(214,214,214)), color-stop(0.75, rgb(184,184,184)));
916 @ background: -moz-linear-gradient(center bottom, rgb(214,214,214) 0%, rgb(184,184,184) 75%);
917 @ background-color: #c0c0c0 ;
918 @ }
919 @
@@ -1110,39 +1115,40 @@
1110 @ } else {
1111 @ puts "Not logged in"
1112 @ }
1113 @ </th1></nobr></div>
1114 @ </div>
1115 @ <div class="mainmenu"><ul><th1>
1116 @ html "<a href=''$home$index_page''>Home</a>"
 
1117 @ if {[anycap jor]} {
1118 @ html "<a href=''$home/timeline''>Timeline</a>"
1119 @ }
1120 @ if {[hascap oh]} {
1121 @ html "<a href=''$home/dir?ci=tip''>Files</a>"
1122 @ }
1123 @ if {[hascap o]} {
1124 @ html "<a href=''$home/brlist''>Branches</a>"
1125 @ html "<a href=''$home/taglist''>Tags</a>"
1126 @ }
1127 @ if {[hascap r]} {
1128 @ html "<a href=''$home/reportlist''>Tickets</a>"
1129 @ }
1130 @ if {[hascap j]} {
1131 @ html "<a href=''$home/wiki''>Wiki</a>"
1132 @ }
1133 @ if {[hascap s]} {
1134 @ html "<a href=''$home/setup''>Admin</a>"
1135 @ } elseif {[hascap a]} {
1136 @ html "<a href=''$home/setup_ulist''>Users</a>"
1137 @ }
1138 @ if {[info exists login]} {
1139 @ html "<a href=''$home/login''>Logout</a>"
1140 @ } else {
1141 @ html "<a href=''$home/login''>Login</a>"
1142 @ }
1143 @ </th1></ul></div>
1144 @ <div id="container">
1145 @ ');
1146 @ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
1147 @ <div class="footer">
1148 @ Fossil version $manifest_version $manifest_date
1149
--- src/skins.c
+++ src/skins.c
@@ -86,29 +86,30 @@
86 @ text-align: center;
87 @ letter-spacing: 1px;
88 @ background-color: #404040;
89 @ color: white;
90 @ }
91 @
92 @ /* The submenu bar that *sometimes* appears below the main menu */
93 @ div.submenu, div.sectionmenu {
94 @ padding: 3px 10px 3px 0px;
95 @ font-size: 0.9em;
96 @ text-align: center;
97 @ background-color: #606060;
98 @ color: white;
99 @ }
100 @ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
101 @ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
102 @ padding: 3px 10px 3px 10px;
103 @ color: white;
104 @ text-decoration: none;
105 @ }
106 @ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
107 @ color: #404040;
108 @ background-color: white;
109 @ }
110 @
111 @ /* All page content from the bottom of the menu or submenu down to
112 @ ** the footer */
113 @ div.content {
114 @ padding: 0ex 0ex 0ex 0ex;
115 @ }
@@ -208,49 +209,47 @@
209 @ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
210 @ media="screen">
211 @ </head>
212 @ <body>
213 @ <div class="header">
 
 
 
214 @ <div class="title"><small>$<project_name></small><br />$<title></div>
215 @ <div class="status"><nobr><th1>
216 @ if {[info exists login]} {
217 @ puts "Logged in as $login"
218 @ } else {
219 @ puts "Not logged in"
220 @ }
221 @ </th1></nobr></div>
222 @ </div>
223 @ <div class="mainmenu">
224 @ <th1>
225 @ html "<a href=''$home$index_page''>Home</a>\n"
226 @ if {[anycap jor]} {
227 @ html "<a href=''$home/timeline''>Timeline</a>\n"
228 @ }
229 @ if {[hascap oh]} {
230 @ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
231 @ }
232 @ if {[hascap o]} {
233 @ html "<a href=''$home/brlist''>Branches</a>\n"
234 @ html "<a href=''$home/taglist''>Tags</a>\n"
235 @ }
236 @ if {[hascap r]} {
237 @ html "<a href=''$home/reportlist''>Tickets</a>\n"
238 @ }
239 @ if {[hascap j]} {
240 @ html "<a href=''$home/wiki''>Wiki</a>\n"
241 @ }
242 @ if {[hascap s]} {
243 @ html "<a href=''$home/setup''>Admin</a>\n"
244 @ } elseif {[hascap a]} {
245 @ html "<a href=''$home/setup_ulist''>Users</a>\n"
246 @ }
247 @ if {[info exists login]} {
248 @ html "<a href=''$home/login''>Logout</a>\n"
249 @ } else {
250 @ html "<a href=''$home/login''>Login</a>\n"
251 @ }
252 @ </th1></div>
253 @ ');
254 @ REPLACE INTO config(name,mtime,value)
255 @ VALUES('footer',now(),'<div class="footer">
@@ -324,23 +323,24 @@
323 @ background-color: #a09048;
324 @ color: black;
325 @ }
326 @
327 @ /* The submenu bar that *sometimes* appears below the main menu */
328 @ div.submenu, div.sectionmenu {
329 @ padding: 3px 10px 3px 0px;
330 @ font-size: 0.9em;
331 @ text-align: center;
332 @ background-color: #c0af58;
333 @ color: white;
334 @ }
335 @ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
336 @ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
337 @ padding: 3px 10px 3px 10px;
338 @ color: white;
339 @ text-decoration: none;
340 @ }
341 @ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
342 @ color: #a09048;
343 @ background-color: white;
344 @ }
345 @
346 @ /* All page content from the bottom of the menu or submenu down to
@@ -466,37 +466,38 @@
466 @ } else {
467 @ puts "Not logged in"
468 @ }
469 @ </th1></nobr></div>
470 @ </div>
471 @ <div class="mainmenu">
472 @ <th1>
473 @ html "<a href=''$home$index_page''>Home</a>\n"
474 @ if {[anycap jor]} {
475 @ html "<a href=''$home/timeline''>Timeline</a>\n"
476 @ }
477 @ if {[hascap oh]} {
478 @ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
479 @ }
480 @ if {[hascap o]} {
481 @ html "<a href=''$home/brlist''>Branches</a>\n"
482 @ html "<a href=''$home/taglist''>Tags</a>\n"
483 @ }
484 @ if {[hascap r]} {
485 @ html "<a href=''$home/reportlist''>Tickets</a>\n"
486 @ }
487 @ if {[hascap j]} {
488 @ html "<a href=''$home/wiki''>Wiki</a>\n"
489 @ }
490 @ if {[hascap s]} {
491 @ html "<a href=''$home/setup''>Admin</a>\n"
492 @ } elseif {[hascap a]} {
493 @ html "<a href=''$home/setup_ulist''>Users</a>\n"
494 @ }
495 @ if {[info exists login]} {
496 @ html "<a href=''$home/login''>Logout</a>\n"
497 @ } else {
498 @ html "<a href=''$home/login''>Login</a>\n"
499 @ }
500 @ </th1></div>
501 @ ');
502 @ REPLACE INTO config(name,mtime,value)
503 @ VALUES('footer',now(),'<div class="footer">
@@ -605,25 +606,26 @@
606 @ #container {
607 @ padding-left: 9em;
608 @ }
609 @
610 @ /* The submenu bar that *sometimes* appears below the main menu */
611 @ div.submenu, div.sectionmenu {
612 @ padding: 3px 10px 3px 10px;
613 @ font-size: 0.9em;
614 @ text-align: center;
615 @ border:1px solid #999;
616 @ border-width:1px 0px;
617 @ background-color: #eee;
618 @ color: #333;
619 @ }
620 @ div.submenu a, div.submenu a:visited, div.sectionmenu>a.button:link,
621 @ div.sectionmenu>a.button:visited {
622 @ padding: 3px 10px 3px 10px;
623 @ color: #333;
624 @ text-decoration: none;
625 @ }
626 @ div.submenu a:hover, div.sectionmenu>a.button:hover {
627 @ color: #eee;
628 @ background-color: #333;
629 @ }
630 @
631 @ /* All page content from the bottom of the menu or submenu down to
@@ -748,37 +750,38 @@
750 @ } else {
751 @ puts "Not logged in"
752 @ }
753 @ </th1></nobr></div>
754 @ </div>
755 @ <div class="mainmenu">
756 @ <th1>
757 @ html "<a href=''$home$index_page''>Home</a>\n"
758 @ if {[anycap jor]} {
759 @ html "<a href=''$home/timeline''>Timeline</a>\n"
760 @ }
761 @ if {[hascap oh]} {
762 @ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
763 @ }
764 @ if {[hascap o]} {
765 @ html "<a href=''$home/brlist''>Branches</a>\n"
766 @ html "<a href=''$home/taglist''>Tags</a>\n"
767 @ }
768 @ if {[hascap r]} {
769 @ html "<a href=''$home/reportlist''>Tickets</a>\n"
770 @ }
771 @ if {[hascap j]} {
772 @ html "<a href=''$home/wiki''>Wiki</a>\n"
773 @ }
774 @ if {[hascap s]} {
775 @ html "<a href=''$home/setup''>Admin</a>\n"
776 @ } elseif {[hascap a]} {
777 @ html "<a href=''$home/setup_ulist''>Users</a>\n"
778 @ }
779 @ if {[info exists login]} {
780 @ html "<a href=''$home/login''>Logout</a>\n"
781 @ } else {
782 @ html "<a href=''$home/login''>Login</a>\n"
783 @ }
784 @ </th1></ul></div>
785 @ <div id="container">
786 @ ');
787 @ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
@@ -859,12 +862,13 @@
862 @ -webkit-border-top-left-radius: 5px;
863 @ -border-top-right-radius: 5px;
864 @ -border-top-left-radius: 5px;
865 @ border-top-left-radius: 5px;
866 @ border-top-right-radius: 5px;
867 @ vertical-align: middle;
868 @ padding-top: 8px;
869 @ padding-bottom: 8px;
870 @ background-color: #446979;
871 @ background: -webkit-gradient(linear,left bottom,left top, color-stop(0.02, rgb(51,81,94)), color-stop(0.76, rgb(85,129,149)));
872 @ background: -moz-linear-gradient(center bottom,rgb(51,81,94) 2%, rgb(85,129,149) 76%);
873 @ -webkit-box-shadow: 0px 3px 4px #333333;
874 @ -moz-box-shadow: 0px 3px 4px #333333;
@@ -887,11 +891,12 @@
891 @ div.mainmenu a, div.mainmenu a:visited {
892 @ padding: 3px 10px 3px 10px;
893 @ color: white;
894 @ text-decoration: none;
895 @ }
896 @ div.submenu a, div.submenu a:visited, a.button,
897 @ div.sectionmenu>a.button:link, div.sectinmenu>a.button:visited {
898 @ padding: 2px 8px;
899 @ color: #000;
900 @ font-family: Arial;
901 @ text-decoration: none;
902 @ margin:auto;
@@ -909,11 +914,11 @@
914 @ div.mainmenu a:hover {
915 @ color: #000;
916 @ background-color: white;
917 @ }
918 @
919 @ div.submenu a:hover, div.sectionmenu>a.button:hover {
920 @ background: -webkit-gradient(linear,left bottom, left top, color-stop(0, rgb(214,214,214)), color-stop(0.75, rgb(184,184,184)));
921 @ background: -moz-linear-gradient(center bottom, rgb(214,214,214) 0%, rgb(184,184,184) 75%);
922 @ background-color: #c0c0c0 ;
923 @ }
924 @
@@ -1110,39 +1115,40 @@
1115 @ } else {
1116 @ puts "Not logged in"
1117 @ }
1118 @ </th1></nobr></div>
1119 @ </div>
1120 @ <div class="mainmenu">
1121 @ <th1>
1122 @ html "<a href=''$home$index_page''>Home</a>\n"
1123 @ if {[anycap jor]} {
1124 @ html "<a href=''$home/timeline''>Timeline</a>\n"
1125 @ }
1126 @ if {[hascap oh]} {
1127 @ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
1128 @ }
1129 @ if {[hascap o]} {
1130 @ html "<a href=''$home/brlist''>Branches</a>\n"
1131 @ html "<a href=''$home/taglist''>Tags</a>\n"
1132 @ }
1133 @ if {[hascap r]} {
1134 @ html "<a href=''$home/reportlist''>Tickets</a>\n"
1135 @ }
1136 @ if {[hascap j]} {
1137 @ html "<a href=''$home/wiki''>Wiki</a>\n"
1138 @ }
1139 @ if {[hascap s]} {
1140 @ html "<a href=''$home/setup''>Admin</a>\n"
1141 @ } elseif {[hascap a]} {
1142 @ html "<a href=''$home/setup_ulist''>Users</a>\n"
1143 @ }
1144 @ if {[info exists login]} {
1145 @ html "<a href=''$home/login''>Logout</a>\n"
1146 @ } else {
1147 @ html "<a href=''$home/login''>Login</a>\n"
1148 @ }
1149 @ </th1></div>
1150 @ <div id="container">
1151 @ ');
1152 @ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
1153 @ <div class="footer">
1154 @ Fossil version $manifest_version $manifest_date
1155
+573 -170
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -656,11 +656,11 @@
656656
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
657657
** [sqlite_version()] and [sqlite_source_id()].
658658
*/
659659
#define SQLITE_VERSION "3.7.9"
660660
#define SQLITE_VERSION_NUMBER 3007009
661
-#define SQLITE_SOURCE_ID "2011-10-15 00:16:30 39408702a989f907261c298bf0947f3e68bd10fe"
661
+#define SQLITE_SOURCE_ID "2011-10-20 00:55:54 4344483f7d7f64dffadde0053e6c745948db9486"
662662
663663
/*
664664
** CAPI3REF: Run-Time Library Version Numbers
665665
** KEYWORDS: sqlite3_version, sqlite3_sourceid
666666
**
@@ -1951,12 +1951,12 @@
19511951
** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
19521952
** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
19531953
** allocator is engaged to handle all of SQLites memory allocation needs.
19541954
** The first pointer (the memory pointer) must be aligned to an 8-byte
19551955
** boundary or subsequent behavior of SQLite will be undefined.
1956
-** The minimum allocation size is capped at 2^12. Reasonable values
1957
-** for the minimum allocation size are 2^5 through 2^8.</dd>
1956
+** The minimum allocation size is capped at 2**12. Reasonable values
1957
+** for the minimum allocation size are 2**5 through 2**8.</dd>
19581958
**
19591959
** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
19601960
** <dd> ^(This option takes a single argument which is a pointer to an
19611961
** instance of the [sqlite3_mutex_methods] structure. The argument specifies
19621962
** alternative low-level mutex routines to be used in place
@@ -20969,11 +20969,11 @@
2096920969
}else if( *z=='+' ){
2097020970
z+=incr;
2097120971
}
2097220972
/* copy digits to exponent */
2097320973
while( z<zEnd && sqlite3Isdigit(*z) ){
20974
- e = e*10 + (*z - '0');
20974
+ e = e<10000 ? (e*10 + (*z - '0')) : 10000;
2097520975
z+=incr;
2097620976
eValid = 1;
2097720977
}
2097820978
}
2097920979
@@ -21020,10 +21020,16 @@
2102021020
result /= 1.0e+308;
2102121021
}else{
2102221022
result = s * scale;
2102321023
result *= 1.0e+308;
2102421024
}
21025
+ }else if( e>=342 ){
21026
+ if( esign<0 ){
21027
+ result = 0.0*s;
21028
+ }else{
21029
+ result = 1e308*1e308*s; /* Infinity */
21030
+ }
2102521031
}else{
2102621032
/* 1.0e+22 is the largest power of 10 than can be
2102721033
** represented exactly. */
2102821034
while( e%22 ) { scale *= 1.0e+1; e -= 1; }
2102921035
while( e>0 ) { scale *= 1.0e+22; e -= 22; }
@@ -68962,11 +68968,11 @@
6896268968
6896368969
/* Do not allow a transition to journal_mode=WAL for a database
6896468970
** in temporary storage or if the VFS does not support shared memory
6896568971
*/
6896668972
if( u.ch.eNew==PAGER_JOURNALMODE_WAL
68967
- && (u.ch.zFilename[0]==0 /* Temp file */
68973
+ && (sqlite3Strlen30(u.ch.zFilename)==0 /* Temp file */
6896868974
|| !sqlite3PagerWalSupported(u.ch.pPager)) /* No shared-memory support */
6896968975
){
6897068976
u.ch.eNew = u.ch.eOld;
6897168977
}
6897268978
@@ -69397,14 +69403,19 @@
6939769403
u.co.pName = &aMem[pOp->p1];
6939869404
assert( u.co.pVtab->pModule->xRename );
6939969405
assert( memIsValid(u.co.pName) );
6940069406
REGISTER_TRACE(pOp->p1, u.co.pName);
6940169407
assert( u.co.pName->flags & MEM_Str );
69402
- rc = u.co.pVtab->pModule->xRename(u.co.pVtab, u.co.pName->z);
69403
- importVtabErrMsg(p, u.co.pVtab);
69404
- p->expired = 0;
69405
-
69408
+ testcase( u.co.pName->enc==SQLITE_UTF8 );
69409
+ testcase( u.co.pName->enc==SQLITE_UTF16BE );
69410
+ testcase( u.co.pName->enc==SQLITE_UTF16LE );
69411
+ rc = sqlite3VdbeChangeEncoding(u.co.pName, SQLITE_UTF8);
69412
+ if( rc==SQLITE_OK ){
69413
+ rc = u.co.pVtab->pModule->xRename(u.co.pVtab, u.co.pName->z);
69414
+ importVtabErrMsg(p, u.co.pVtab);
69415
+ p->expired = 0;
69416
+ }
6940669417
break;
6940769418
}
6940869419
#endif
6940969420
6941069421
#ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -71758,10 +71769,28 @@
7175871769
ExprSetProperty(pExpr, EP_Static);
7175971770
sqlite3ExprDelete(db, pExpr);
7176071771
memcpy(pExpr, pDup, sizeof(*pExpr));
7176171772
sqlite3DbFree(db, pDup);
7176271773
}
71774
+
71775
+
71776
+/*
71777
+** Return TRUE if the name zCol occurs anywhere in the USING clause.
71778
+**
71779
+** Return FALSE if the USING clause is NULL or if it does not contain
71780
+** zCol.
71781
+*/
71782
+static int nameInUsingClause(IdList *pUsing, const char *zCol){
71783
+ if( pUsing ){
71784
+ int k;
71785
+ for(k=0; k<pUsing->nId; k++){
71786
+ if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1;
71787
+ }
71788
+ }
71789
+ return 0;
71790
+}
71791
+
7176371792
7176471793
/*
7176571794
** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
7176671795
** that name in the set of source tables in pSrcList and make the pExpr
7176771796
** expression node refer back to that source column. The following changes
@@ -71850,38 +71879,25 @@
7185071879
pSchema = pTab->pSchema;
7185171880
pMatch = pItem;
7185271881
}
7185371882
for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){
7185471883
if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
71855
- IdList *pUsing;
71884
+ /* If there has been exactly one prior match and this match
71885
+ ** is for the right-hand table of a NATURAL JOIN or is in a
71886
+ ** USING clause, then skip this match.
71887
+ */
71888
+ if( cnt==1 ){
71889
+ if( pItem->jointype & JT_NATURAL ) continue;
71890
+ if( nameInUsingClause(pItem->pUsing, zCol) ) continue;
71891
+ }
7185671892
cnt++;
7185771893
pExpr->iTable = pItem->iCursor;
7185871894
pExpr->pTab = pTab;
7185971895
pMatch = pItem;
7186071896
pSchema = pTab->pSchema;
7186171897
/* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
7186271898
pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j;
71863
- if( i<pSrcList->nSrc-1 ){
71864
- if( pItem[1].jointype & JT_NATURAL ){
71865
- /* If this match occurred in the left table of a natural join,
71866
- ** then skip the right table to avoid a duplicate match */
71867
- pItem++;
71868
- i++;
71869
- }else if( (pUsing = pItem[1].pUsing)!=0 ){
71870
- /* If this match occurs on a column that is in the USING clause
71871
- ** of a join, skip the search of the right table of the join
71872
- ** to avoid a duplicate match there. */
71873
- int k;
71874
- for(k=0; k<pUsing->nId; k++){
71875
- if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ){
71876
- pItem++;
71877
- i++;
71878
- break;
71879
- }
71880
- }
71881
- }
71882
- }
7188371899
break;
7188471900
}
7188571901
}
7188671902
}
7188771903
}
@@ -102501,10 +102517,11 @@
102501102517
tempWC.pParse = pWC->pParse;
102502102518
tempWC.pMaskSet = pWC->pMaskSet;
102503102519
tempWC.pOuter = pWC;
102504102520
tempWC.op = TK_AND;
102505102521
tempWC.a = pOrTerm;
102522
+ tempWC.wctrlFlags = 0;
102506102523
tempWC.nTerm = 1;
102507102524
bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost);
102508102525
}else{
102509102526
continue;
102510102527
}
@@ -114528,10 +114545,11 @@
114528114545
const char *zDb; /* logical database name */
114529114546
const char *zName; /* virtual table name */
114530114547
int nColumn; /* number of named columns in virtual table */
114531114548
char **azColumn; /* column names. malloced */
114532114549
sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */
114550
+ char *zContentTbl; /* content=xxx option, or NULL */
114533114551
114534114552
/* Precompiled statements used by the implementation. Each of these
114535114553
** statements is run and reset within a single virtual table API call.
114536114554
*/
114537114555
sqlite3_stmt *aStmt[27];
@@ -114568,11 +114586,11 @@
114568114586
} *aIndex;
114569114587
int nMaxPendingData; /* Max pending data before flush to disk */
114570114588
int nPendingData; /* Current bytes of pending data */
114571114589
sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */
114572114590
114573
-#if defined(SQLITE_DEBUG)
114591
+#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
114574114592
/* State variables used for validating that the transaction control
114575114593
** methods of the virtual table are called at appropriate times. These
114576114594
** values do not contribution to the FTS computation; they are used for
114577114595
** verifying the SQLite core.
114578114596
*/
@@ -114653,10 +114671,11 @@
114653114671
*/
114654114672
struct Fts3PhraseToken {
114655114673
char *z; /* Text of the token */
114656114674
int n; /* Number of bytes in buffer z */
114657114675
int isPrefix; /* True if token ends with a "*" character */
114676
+ int bFirst; /* True if token must appear at position 0 */
114658114677
114659114678
/* Variables above this point are populated when the expression is
114660114679
** parsed (by code in fts3_expr.c). Below this point the variables are
114661114680
** used when evaluating the expression. */
114662114681
Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
@@ -114771,10 +114790,11 @@
114771114790
#define FTS3_SEGMENT_REQUIRE_POS 0x00000001
114772114791
#define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002
114773114792
#define FTS3_SEGMENT_COLUMN_FILTER 0x00000004
114774114793
#define FTS3_SEGMENT_PREFIX 0x00000008
114775114794
#define FTS3_SEGMENT_SCAN 0x00000010
114795
+#define FTS3_SEGMENT_FIRST 0x00000020
114776114796
114777114797
/* Type passed as 4th argument to SegmentReaderIterate() */
114778114798
struct Fts3SegFilter {
114779114799
const char *zTerm;
114780114800
int nTerm;
@@ -114810,12 +114830,12 @@
114810114830
SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
114811114831
SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *);
114812114832
SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64);
114813114833
SQLITE_PRIVATE void sqlite3Fts3Dequote(char *);
114814114834
SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
114815
-
114816114835
SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *);
114836
+SQLITE_PRIVATE int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *);
114817114837
114818114838
/* fts3_tokenizer.c */
114819114839
SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *, int *);
114820114840
SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *);
114821114841
SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *,
@@ -114830,11 +114850,11 @@
114830114850
);
114831114851
SQLITE_PRIVATE void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *);
114832114852
114833114853
/* fts3_expr.c */
114834114854
SQLITE_PRIVATE int sqlite3Fts3ExprParse(sqlite3_tokenizer *,
114835
- char **, int, int, const char *, int, Fts3Expr **
114855
+ char **, int, int, int, const char *, int, Fts3Expr **
114836114856
);
114837114857
SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *);
114838114858
#ifdef SQLITE_TEST
114839114859
SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db);
114840114860
SQLITE_PRIVATE int sqlite3Fts3InitTerm(sqlite3 *db);
@@ -115031,10 +115051,11 @@
115031115051
sqlite3_finalize(p->aStmt[i]);
115032115052
}
115033115053
sqlite3_free(p->zSegmentsTbl);
115034115054
sqlite3_free(p->zReadExprlist);
115035115055
sqlite3_free(p->zWriteExprlist);
115056
+ sqlite3_free(p->zContentTbl);
115036115057
115037115058
/* Invoke the tokenizer destructor to free the tokenizer. */
115038115059
p->pTokenizer->pModule->xDestroy(p->pTokenizer);
115039115060
115040115061
sqlite3_free(p);
@@ -115070,20 +115091,23 @@
115070115091
115071115092
/*
115072115093
** The xDestroy() virtual table method.
115073115094
*/
115074115095
static int fts3DestroyMethod(sqlite3_vtab *pVtab){
115075
- int rc = SQLITE_OK; /* Return code */
115076115096
Fts3Table *p = (Fts3Table *)pVtab;
115077
- sqlite3 *db = p->db;
115097
+ int rc = SQLITE_OK; /* Return code */
115098
+ const char *zDb = p->zDb; /* Name of database (e.g. "main", "temp") */
115099
+ sqlite3 *db = p->db; /* Database handle */
115078115100
115079115101
/* Drop the shadow tables */
115080
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", p->zDb, p->zName);
115081
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", p->zDb,p->zName);
115082
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", p->zDb, p->zName);
115083
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", p->zDb, p->zName);
115084
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", p->zDb, p->zName);
115102
+ if( p->zContentTbl==0 ){
115103
+ fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", zDb, p->zName);
115104
+ }
115105
+ fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", zDb,p->zName);
115106
+ fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", zDb, p->zName);
115107
+ fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", zDb, p->zName);
115108
+ fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", zDb, p->zName);
115085115109
115086115110
/* If everything has worked, invoke fts3DisconnectMethod() to free the
115087115111
** memory associated with the Fts3Table structure and return SQLITE_OK.
115088115112
** Otherwise, return an SQLite error code.
115089115113
*/
@@ -115141,27 +115165,31 @@
115141115165
** %_stat tables required by FTS4.
115142115166
*/
115143115167
static int fts3CreateTables(Fts3Table *p){
115144115168
int rc = SQLITE_OK; /* Return code */
115145115169
int i; /* Iterator variable */
115146
- char *zContentCols; /* Columns of %_content table */
115147115170
sqlite3 *db = p->db; /* The database connection */
115148115171
115149
- /* Create a list of user columns for the content table */
115150
- zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY");
115151
- for(i=0; zContentCols && i<p->nColumn; i++){
115152
- char *z = p->azColumn[i];
115153
- zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
115154
- }
115155
- if( zContentCols==0 ) rc = SQLITE_NOMEM;
115156
-
115157
- /* Create the content table */
115158
- fts3DbExec(&rc, db,
115159
- "CREATE TABLE %Q.'%q_content'(%s)",
115160
- p->zDb, p->zName, zContentCols
115161
- );
115162
- sqlite3_free(zContentCols);
115172
+ if( p->zContentTbl==0 ){
115173
+ char *zContentCols; /* Columns of %_content table */
115174
+
115175
+ /* Create a list of user columns for the content table */
115176
+ zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY");
115177
+ for(i=0; zContentCols && i<p->nColumn; i++){
115178
+ char *z = p->azColumn[i];
115179
+ zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
115180
+ }
115181
+ if( zContentCols==0 ) rc = SQLITE_NOMEM;
115182
+
115183
+ /* Create the content table */
115184
+ fts3DbExec(&rc, db,
115185
+ "CREATE TABLE %Q.'%q_content'(%s)",
115186
+ p->zDb, p->zName, zContentCols
115187
+ );
115188
+ sqlite3_free(zContentCols);
115189
+ }
115190
+
115163115191
/* Create other tables */
115164115192
fts3DbExec(&rc, db,
115165115193
"CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);",
115166115194
p->zDb, p->zName
115167115195
);
@@ -115308,12 +115336,12 @@
115308115336
}
115309115337
return zRet;
115310115338
}
115311115339
115312115340
/*
115313
-** Return a list of comma separated SQL expressions that could be used
115314
-** in a SELECT statement such as the following:
115341
+** Return a list of comma separated SQL expressions and a FROM clause that
115342
+** could be used in a SELECT statement such as the following:
115315115343
**
115316115344
** SELECT <list of expressions> FROM %_content AS x ...
115317115345
**
115318115346
** to return the docid, followed by each column of text data in order
115319115347
** from left to write. If parameter zFunc is not NULL, then instead of
@@ -115320,11 +115348,11 @@
115320115348
** being returned directly each column of text data is passed to an SQL
115321115349
** function named zFunc first. For example, if zFunc is "unzip" and the
115322115350
** table has the three user-defined columns "a", "b", and "c", the following
115323115351
** string is returned:
115324115352
**
115325
-** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c')"
115353
+** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c') FROM %_content AS x"
115326115354
**
115327115355
** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
115328115356
** is the responsibility of the caller to eventually free it.
115329115357
**
115330115358
** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and
@@ -115336,20 +115364,32 @@
115336115364
char *zRet = 0;
115337115365
char *zFree = 0;
115338115366
char *zFunction;
115339115367
int i;
115340115368
115341
- if( !zFunc ){
115342
- zFunction = "";
115369
+ if( p->zContentTbl==0 ){
115370
+ if( !zFunc ){
115371
+ zFunction = "";
115372
+ }else{
115373
+ zFree = zFunction = fts3QuoteId(zFunc);
115374
+ }
115375
+ fts3Appendf(pRc, &zRet, "docid");
115376
+ for(i=0; i<p->nColumn; i++){
115377
+ fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]);
115378
+ }
115379
+ sqlite3_free(zFree);
115343115380
}else{
115344
- zFree = zFunction = fts3QuoteId(zFunc);
115381
+ fts3Appendf(pRc, &zRet, "rowid");
115382
+ for(i=0; i<p->nColumn; i++){
115383
+ fts3Appendf(pRc, &zRet, ", x.'%q'", p->azColumn[i]);
115384
+ }
115345115385
}
115346
- fts3Appendf(pRc, &zRet, "docid");
115347
- for(i=0; i<p->nColumn; i++){
115348
- fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]);
115349
- }
115350
- sqlite3_free(zFree);
115386
+ fts3Appendf(pRc, &zRet, "FROM '%q'.'%q%s' AS x",
115387
+ p->zDb,
115388
+ (p->zContentTbl ? p->zContentTbl : p->zName),
115389
+ (p->zContentTbl ? "" : "_content")
115390
+ );
115351115391
return zRet;
115352115392
}
115353115393
115354115394
/*
115355115395
** Return a list of N comma separated question marks, where N is the number
@@ -115468,10 +115508,95 @@
115468115508
}
115469115509
}
115470115510
115471115511
return SQLITE_OK;
115472115512
}
115513
+
115514
+/*
115515
+** This function is called when initializing an FTS4 table that uses the
115516
+** content=xxx option. It determines the number of and names of the columns
115517
+** of the new FTS4 table.
115518
+**
115519
+** The third argument passed to this function is the value passed to the
115520
+** config=xxx option (i.e. "xxx"). This function queries the database for
115521
+** a table of that name. If found, the output variables are populated
115522
+** as follows:
115523
+**
115524
+** *pnCol: Set to the number of columns table xxx has,
115525
+**
115526
+** *pnStr: Set to the total amount of space required to store a copy
115527
+** of each columns name, including the nul-terminator.
115528
+**
115529
+** *pazCol: Set to point to an array of *pnCol strings. Each string is
115530
+** the name of the corresponding column in table xxx. The array
115531
+** and its contents are allocated using a single allocation. It
115532
+** is the responsibility of the caller to free this allocation
115533
+** by eventually passing the *pazCol value to sqlite3_free().
115534
+**
115535
+** If the table cannot be found, an error code is returned and the output
115536
+** variables are undefined. Or, if an OOM is encountered, SQLITE_NOMEM is
115537
+** returned (and the output variables are undefined).
115538
+*/
115539
+static int fts3ContentColumns(
115540
+ sqlite3 *db, /* Database handle */
115541
+ const char *zDb, /* Name of db (i.e. "main", "temp" etc.) */
115542
+ const char *zTbl, /* Name of content table */
115543
+ const char ***pazCol, /* OUT: Malloc'd array of column names */
115544
+ int *pnCol, /* OUT: Size of array *pazCol */
115545
+ int *pnStr /* OUT: Bytes of string content */
115546
+){
115547
+ int rc = SQLITE_OK; /* Return code */
115548
+ char *zSql; /* "SELECT *" statement on zTbl */
115549
+ sqlite3_stmt *pStmt = 0; /* Compiled version of zSql */
115550
+
115551
+ zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl);
115552
+ if( !zSql ){
115553
+ rc = SQLITE_NOMEM;
115554
+ }else{
115555
+ rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
115556
+ }
115557
+ sqlite3_free(zSql);
115558
+
115559
+ if( rc==SQLITE_OK ){
115560
+ const char **azCol; /* Output array */
115561
+ int nStr = 0; /* Size of all column names (incl. 0x00) */
115562
+ int nCol; /* Number of table columns */
115563
+ int i; /* Used to iterate through columns */
115564
+
115565
+ /* Loop through the returned columns. Set nStr to the number of bytes of
115566
+ ** space required to store a copy of each column name, including the
115567
+ ** nul-terminator byte. */
115568
+ nCol = sqlite3_column_count(pStmt);
115569
+ for(i=0; i<nCol; i++){
115570
+ const char *zCol = sqlite3_column_name(pStmt, i);
115571
+ nStr += strlen(zCol) + 1;
115572
+ }
115573
+
115574
+ /* Allocate and populate the array to return. */
115575
+ azCol = (const char **)sqlite3_malloc(sizeof(char *) * nCol + nStr);
115576
+ if( azCol==0 ){
115577
+ rc = SQLITE_NOMEM;
115578
+ }else{
115579
+ char *p = (char *)&azCol[nCol];
115580
+ for(i=0; i<nCol; i++){
115581
+ const char *zCol = sqlite3_column_name(pStmt, i);
115582
+ int n = strlen(zCol)+1;
115583
+ memcpy(p, zCol, n);
115584
+ azCol[i] = p;
115585
+ p += n;
115586
+ }
115587
+ }
115588
+ sqlite3_finalize(pStmt);
115589
+
115590
+ /* Set the output variables. */
115591
+ *pnCol = nCol;
115592
+ *pnStr = nStr;
115593
+ *pazCol = azCol;
115594
+ }
115595
+
115596
+ return rc;
115597
+}
115473115598
115474115599
/*
115475115600
** This function is the implementation of both the xConnect and xCreate
115476115601
** methods of the FTS3 virtual table.
115477115602
**
@@ -115513,10 +115638,11 @@
115513115638
int bNoDocsize = 0; /* True to omit %_docsize table */
115514115639
int bDescIdx = 0; /* True to store descending indexes */
115515115640
char *zPrefix = 0; /* Prefix parameter value (or NULL) */
115516115641
char *zCompress = 0; /* compress=? parameter (or NULL) */
115517115642
char *zUncompress = 0; /* uncompress=? parameter (or NULL) */
115643
+ char *zContent = 0; /* content=? parameter (or NULL) */
115518115644
115519115645
assert( strlen(argv[0])==4 );
115520115646
assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
115521115647
|| (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4)
115522115648
);
@@ -115556,17 +115682,17 @@
115556115682
/* Check if it is an FTS4 special argument. */
115557115683
else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){
115558115684
struct Fts4Option {
115559115685
const char *zOpt;
115560115686
int nOpt;
115561
- char **pzVar;
115562115687
} aFts4Opt[] = {
115563
- { "matchinfo", 9, 0 }, /* 0 -> MATCHINFO */
115564
- { "prefix", 6, 0 }, /* 1 -> PREFIX */
115565
- { "compress", 8, 0 }, /* 2 -> COMPRESS */
115566
- { "uncompress", 10, 0 }, /* 3 -> UNCOMPRESS */
115567
- { "order", 5, 0 } /* 4 -> ORDER */
115688
+ { "matchinfo", 9 }, /* 0 -> MATCHINFO */
115689
+ { "prefix", 6 }, /* 1 -> PREFIX */
115690
+ { "compress", 8 }, /* 2 -> COMPRESS */
115691
+ { "uncompress", 10 }, /* 3 -> UNCOMPRESS */
115692
+ { "order", 5 }, /* 4 -> ORDER */
115693
+ { "content", 7 } /* 5 -> CONTENT */
115568115694
};
115569115695
115570115696
int iOpt;
115571115697
if( !zVal ){
115572115698
rc = SQLITE_NOMEM;
@@ -115608,17 +115734,24 @@
115608115734
zVal = 0;
115609115735
break;
115610115736
115611115737
case 4: /* ORDER */
115612115738
if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3))
115613
- && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 3))
115739
+ && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4))
115614115740
){
115615115741
*pzErr = sqlite3_mprintf("unrecognized order: %s", zVal);
115616115742
rc = SQLITE_ERROR;
115617115743
}
115618115744
bDescIdx = (zVal[0]=='d' || zVal[0]=='D');
115619115745
break;
115746
+
115747
+ default: /* CONTENT */
115748
+ assert( iOpt==5 );
115749
+ sqlite3_free(zUncompress);
115750
+ zContent = zVal;
115751
+ zVal = 0;
115752
+ break;
115620115753
}
115621115754
}
115622115755
sqlite3_free(zVal);
115623115756
}
115624115757
}
@@ -115627,10 +115760,30 @@
115627115760
else {
115628115761
nString += (int)(strlen(z) + 1);
115629115762
aCol[nCol++] = z;
115630115763
}
115631115764
}
115765
+
115766
+ /* If a content=xxx option was specified, the following:
115767
+ **
115768
+ ** 1. Ignore any compress= and uncompress= options.
115769
+ **
115770
+ ** 2. If no column names were specified as part of the CREATE VIRTUAL
115771
+ ** TABLE statement, use all columns from the content table.
115772
+ */
115773
+ if( rc==SQLITE_OK && zContent ){
115774
+ sqlite3_free(zCompress);
115775
+ sqlite3_free(zUncompress);
115776
+ zCompress = 0;
115777
+ zUncompress = 0;
115778
+ if( nCol==0 ){
115779
+ sqlite3_free((void*)aCol);
115780
+ aCol = 0;
115781
+ rc = fts3ContentColumns(db, argv[1], zContent, &aCol, &nCol, &nString);
115782
+ }
115783
+ assert( rc!=SQLITE_OK || nCol>0 );
115784
+ }
115632115785
if( rc!=SQLITE_OK ) goto fts3_init_out;
115633115786
115634115787
if( nCol==0 ){
115635115788
assert( nString==0 );
115636115789
aCol[0] = "content";
@@ -115671,10 +115824,12 @@
115671115824
p->pTokenizer = pTokenizer;
115672115825
p->nMaxPendingData = FTS3_MAX_PENDING_DATA;
115673115826
p->bHasDocsize = (isFts4 && bNoDocsize==0);
115674115827
p->bHasStat = isFts4;
115675115828
p->bDescIdx = bDescIdx;
115829
+ p->zContentTbl = zContent;
115830
+ zContent = 0;
115676115831
TESTONLY( p->inTransaction = -1 );
115677115832
TESTONLY( p->mxSavepoint = -1 );
115678115833
115679115834
p->aIndex = (struct Fts3Index *)&p->azColumn[nCol];
115680115835
memcpy(p->aIndex, aIndex, sizeof(struct Fts3Index) * nIndex);
@@ -115732,10 +115887,11 @@
115732115887
fts3_init_out:
115733115888
sqlite3_free(zPrefix);
115734115889
sqlite3_free(aIndex);
115735115890
sqlite3_free(zCompress);
115736115891
sqlite3_free(zUncompress);
115892
+ sqlite3_free(zContent);
115737115893
sqlite3_free((void *)aCol);
115738115894
if( rc!=SQLITE_OK ){
115739115895
if( p ){
115740115896
fts3DisconnectMethod((sqlite3_vtab *)p);
115741115897
}else if( pTokenizer ){
@@ -115882,40 +116038,69 @@
115882116038
sqlite3_free(pCsr->aMatchinfo);
115883116039
assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
115884116040
sqlite3_free(pCsr);
115885116041
return SQLITE_OK;
115886116042
}
116043
+
116044
+/*
116045
+** If pCsr->pStmt has not been prepared (i.e. if pCsr->pStmt==0), then
116046
+** compose and prepare an SQL statement of the form:
116047
+**
116048
+** "SELECT <columns> FROM %_content WHERE rowid = ?"
116049
+**
116050
+** (or the equivalent for a content=xxx table) and set pCsr->pStmt to
116051
+** it. If an error occurs, return an SQLite error code.
116052
+**
116053
+** Otherwise, set *ppStmt to point to pCsr->pStmt and return SQLITE_OK.
116054
+*/
116055
+static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){
116056
+ int rc = SQLITE_OK;
116057
+ if( pCsr->pStmt==0 ){
116058
+ Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
116059
+ char *zSql;
116060
+ zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist);
116061
+ if( !zSql ) return SQLITE_NOMEM;
116062
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
116063
+ sqlite3_free(zSql);
116064
+ }
116065
+ *ppStmt = pCsr->pStmt;
116066
+ return rc;
116067
+}
115887116068
115888116069
/*
115889116070
** Position the pCsr->pStmt statement so that it is on the row
115890116071
** of the %_content table that contains the last match. Return
115891116072
** SQLITE_OK on success.
115892116073
*/
115893116074
static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){
116075
+ int rc = SQLITE_OK;
115894116076
if( pCsr->isRequireSeek ){
115895
- sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId);
115896
- pCsr->isRequireSeek = 0;
115897
- if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){
115898
- return SQLITE_OK;
115899
- }else{
115900
- int rc = sqlite3_reset(pCsr->pStmt);
115901
- if( rc==SQLITE_OK ){
115902
- /* If no row was found and no error has occured, then the %_content
115903
- ** table is missing a row that is present in the full-text index.
115904
- ** The data structures are corrupt.
115905
- */
115906
- rc = FTS_CORRUPT_VTAB;
115907
- }
115908
- pCsr->isEof = 1;
115909
- if( pContext ){
115910
- sqlite3_result_error_code(pContext, rc);
115911
- }
115912
- return rc;
115913
- }
115914
- }else{
115915
- return SQLITE_OK;
115916
- }
116077
+ sqlite3_stmt *pStmt = 0;
116078
+
116079
+ rc = fts3CursorSeekStmt(pCsr, &pStmt);
116080
+ if( rc==SQLITE_OK ){
116081
+ sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId);
116082
+ pCsr->isRequireSeek = 0;
116083
+ if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){
116084
+ return SQLITE_OK;
116085
+ }else{
116086
+ rc = sqlite3_reset(pCsr->pStmt);
116087
+ if( rc==SQLITE_OK && ((Fts3Table *)pCsr->base.pVtab)->zContentTbl==0 ){
116088
+ /* If no row was found and no error has occured, then the %_content
116089
+ ** table is missing a row that is present in the full-text index.
116090
+ ** The data structures are corrupt. */
116091
+ rc = FTS_CORRUPT_VTAB;
116092
+ pCsr->isEof = 1;
116093
+ }
116094
+ }
116095
+ }
116096
+ }
116097
+
116098
+ if( rc!=SQLITE_OK && pContext ){
116099
+ sqlite3_result_error_code(pContext, rc);
116100
+ }
116101
+ return rc;
115917116102
}
115918116103
115919116104
/*
115920116105
** This function is used to process a single interior node when searching
115921116106
** a b-tree for a term or term prefix. The node data is passed to this
@@ -116351,20 +116536,20 @@
116351116536
int isSaveLeft, /* Save the left position */
116352116537
int isExact, /* If *pp1 is exactly nTokens before *pp2 */
116353116538
char **pp1, /* IN/OUT: Left input list */
116354116539
char **pp2 /* IN/OUT: Right input list */
116355116540
){
116356
- char *p = (pp ? *pp : 0);
116541
+ char *p = *pp;
116357116542
char *p1 = *pp1;
116358116543
char *p2 = *pp2;
116359116544
int iCol1 = 0;
116360116545
int iCol2 = 0;
116361116546
116362116547
/* Never set both isSaveLeft and isExact for the same invocation. */
116363116548
assert( isSaveLeft==0 || isExact==0 );
116364116549
116365
- assert( *p1!=0 && *p2!=0 );
116550
+ assert( p!=0 && *p1!=0 && *p2!=0 );
116366116551
if( *p1==POS_COLUMN ){
116367116552
p1++;
116368116553
p1 += sqlite3Fts3GetVarint32(p1, &iCol1);
116369116554
}
116370116555
if( *p2==POS_COLUMN ){
@@ -116377,11 +116562,11 @@
116377116562
char *pSave = p;
116378116563
sqlite3_int64 iPrev = 0;
116379116564
sqlite3_int64 iPos1 = 0;
116380116565
sqlite3_int64 iPos2 = 0;
116381116566
116382
- if( pp && iCol1 ){
116567
+ if( iCol1 ){
116383116568
*p++ = POS_COLUMN;
116384116569
p += sqlite3Fts3PutVarint(p, iCol1);
116385116570
}
116386116571
116387116572
assert( *p1!=POS_END && *p1!=POS_COLUMN );
@@ -116392,20 +116577,14 @@
116392116577
while( 1 ){
116393116578
if( iPos2==iPos1+nToken
116394116579
|| (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken)
116395116580
){
116396116581
sqlite3_int64 iSave;
116397
- if( !pp ){
116398
- fts3PoslistCopy(0, &p2);
116399
- fts3PoslistCopy(0, &p1);
116400
- *pp1 = p1;
116401
- *pp2 = p2;
116402
- return 1;
116403
- }
116404116582
iSave = isSaveLeft ? iPos1 : iPos2;
116405116583
fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2;
116406116584
pSave = 0;
116585
+ assert( p );
116407116586
}
116408116587
if( (!isSaveLeft && iPos2<=(iPos1+nToken)) || iPos2<=iPos1 ){
116409116588
if( (*p2&0xFE)==0 ) break;
116410116589
fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2;
116411116590
}else{
@@ -116450,11 +116629,11 @@
116450116629
116451116630
fts3PoslistCopy(0, &p2);
116452116631
fts3PoslistCopy(0, &p1);
116453116632
*pp1 = p1;
116454116633
*pp2 = p2;
116455
- if( !pp || *pp==p ){
116634
+ if( *pp==p ){
116456116635
return 0;
116457116636
}
116458116637
*p++ = 0x00;
116459116638
*pp = p;
116460116639
return 1;
@@ -116752,10 +116931,60 @@
116752116931
}
116753116932
}
116754116933
116755116934
*pnRight = p - aOut;
116756116935
}
116936
+
116937
+/*
116938
+** Argument pList points to a position list nList bytes in size. This
116939
+** function checks to see if the position list contains any entries for
116940
+** a token in position 0 (of any column). If so, it writes argument iDelta
116941
+** to the output buffer pOut, followed by a position list consisting only
116942
+** of the entries from pList at position 0, and terminated by an 0x00 byte.
116943
+** The value returned is the number of bytes written to pOut (if any).
116944
+*/
116945
+SQLITE_PRIVATE int sqlite3Fts3FirstFilter(
116946
+ sqlite3_int64 iDelta, /* Varint that may be written to pOut */
116947
+ char *pList, /* Position list (no 0x00 term) */
116948
+ int nList, /* Size of pList in bytes */
116949
+ char *pOut /* Write output here */
116950
+){
116951
+ int nOut = 0;
116952
+ int bWritten = 0; /* True once iDelta has been written */
116953
+ char *p = pList;
116954
+ char *pEnd = &pList[nList];
116955
+
116956
+ if( *p!=0x01 ){
116957
+ if( *p==0x02 ){
116958
+ nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta);
116959
+ pOut[nOut++] = 0x02;
116960
+ bWritten = 1;
116961
+ }
116962
+ fts3ColumnlistCopy(0, &p);
116963
+ }
116964
+
116965
+ while( p<pEnd && *p==0x01 ){
116966
+ sqlite3_int64 iCol;
116967
+ p++;
116968
+ p += sqlite3Fts3GetVarint(p, &iCol);
116969
+ if( *p==0x02 ){
116970
+ if( bWritten==0 ){
116971
+ nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta);
116972
+ bWritten = 1;
116973
+ }
116974
+ pOut[nOut++] = 0x01;
116975
+ nOut += sqlite3Fts3PutVarint(&pOut[nOut], iCol);
116976
+ pOut[nOut++] = 0x02;
116977
+ }
116978
+ fts3ColumnlistCopy(0, &p);
116979
+ }
116980
+ if( bWritten ){
116981
+ pOut[nOut++] = 0x00;
116982
+ }
116983
+
116984
+ return nOut;
116985
+}
116757116986
116758116987
116759116988
/*
116760116989
** Merge all doclists in the TermSelect.aaOutput[] array into a single
116761116990
** doclist stored in TermSelect.aaOutput[0]. If successful, delete all
@@ -117109,10 +117338,11 @@
117109117338
pSegcsr = pTok->pSegcsr;
117110117339
memset(&tsc, 0, sizeof(TermSelect));
117111117340
117112117341
filter.flags = FTS3_SEGMENT_IGNORE_EMPTY | FTS3_SEGMENT_REQUIRE_POS
117113117342
| (pTok->isPrefix ? FTS3_SEGMENT_PREFIX : 0)
117343
+ | (pTok->bFirst ? FTS3_SEGMENT_FIRST : 0)
117114117344
| (iColumn<p->nColumn ? FTS3_SEGMENT_COLUMN_FILTER : 0);
117115117345
filter.iCol = iColumn;
117116117346
filter.zTerm = pTok->z;
117117117347
filter.nTerm = pTok->n;
117118117348
@@ -117249,12 +117479,12 @@
117249117479
117250117480
if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
117251117481
return SQLITE_NOMEM;
117252117482
}
117253117483
117254
- rc = sqlite3Fts3ExprParse(p->pTokenizer, p->azColumn, p->nColumn,
117255
- iCol, zQuery, -1, &pCsr->pExpr
117484
+ rc = sqlite3Fts3ExprParse(p->pTokenizer, p->azColumn, p->bHasStat,
117485
+ p->nColumn, iCol, zQuery, -1, &pCsr->pExpr
117256117486
);
117257117487
if( rc!=SQLITE_OK ){
117258117488
if( rc==SQLITE_ERROR ){
117259117489
static const char *zErr = "malformed MATCH expression: [%s]";
117260117490
p->base.zErrMsg = sqlite3_mprintf(zErr, zQuery);
@@ -117277,26 +117507,27 @@
117277117507
** statement loops through all rows of the %_content table. For a
117278117508
** full-text query or docid lookup, the statement retrieves a single
117279117509
** row by docid.
117280117510
*/
117281117511
if( idxNum==FTS3_FULLSCAN_SEARCH ){
117282
- const char *zSort = (pCsr->bDesc ? "DESC" : "ASC");
117283
- const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s";
117284
- zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName, zSort);
117285
- }else{
117286
- const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?";
117287
- zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName);
117288
- }
117289
- if( !zSql ) return SQLITE_NOMEM;
117290
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
117291
- sqlite3_free(zSql);
117292
- if( rc!=SQLITE_OK ) return rc;
117293
-
117294
- if( idxNum==FTS3_DOCID_SEARCH ){
117295
- rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
117296
- if( rc!=SQLITE_OK ) return rc;
117297
- }
117512
+ zSql = sqlite3_mprintf(
117513
+ "SELECT %s ORDER BY rowid %s",
117514
+ p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC")
117515
+ );
117516
+ if( zSql ){
117517
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
117518
+ sqlite3_free(zSql);
117519
+ }else{
117520
+ rc = SQLITE_NOMEM;
117521
+ }
117522
+ }else if( idxNum==FTS3_DOCID_SEARCH ){
117523
+ rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt);
117524
+ if( rc==SQLITE_OK ){
117525
+ rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
117526
+ }
117527
+ }
117528
+ if( rc!=SQLITE_OK ) return rc;
117298117529
117299117530
return fts3NextMethod(pCursor);
117300117531
}
117301117532
117302117533
/*
@@ -117345,11 +117576,11 @@
117345117576
** Return a blob which is a pointer to the cursor.
117346117577
*/
117347117578
sqlite3_result_blob(pContext, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT);
117348117579
}else{
117349117580
rc = fts3CursorSeek(0, pCsr);
117350
- if( rc==SQLITE_OK ){
117581
+ if( rc==SQLITE_OK && sqlite3_data_count(pCsr->pStmt)>(iCol+1) ){
117351117582
sqlite3_result_value(pContext, sqlite3_column_value(pCsr->pStmt, iCol+1));
117352117583
}
117353117584
}
117354117585
117355117586
assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
@@ -117638,19 +117869,26 @@
117638117869
){
117639117870
Fts3Table *p = (Fts3Table *)pVtab;
117640117871
sqlite3 *db = p->db; /* Database connection */
117641117872
int rc; /* Return Code */
117642117873
117874
+ /* As it happens, the pending terms table is always empty here. This is
117875
+ ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction
117876
+ ** always opens a savepoint transaction. And the xSavepoint() method
117877
+ ** flushes the pending terms table. But leave the (no-op) call to
117878
+ ** PendingTermsFlush() in in case that changes.
117879
+ */
117880
+ assert( p->nPendingData==0 );
117643117881
rc = sqlite3Fts3PendingTermsFlush(p);
117644
- if( rc!=SQLITE_OK ){
117645
- return rc;
117882
+
117883
+ if( p->zContentTbl==0 ){
117884
+ fts3DbExec(&rc, db,
117885
+ "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';",
117886
+ p->zDb, p->zName, zName
117887
+ );
117646117888
}
117647117889
117648
- fts3DbExec(&rc, db,
117649
- "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';",
117650
- p->zDb, p->zName, zName
117651
- );
117652117890
if( p->bHasDocsize ){
117653117891
fts3DbExec(&rc, db,
117654117892
"ALTER TABLE %Q.'%q_docsize' RENAME TO '%q_docsize';",
117655117893
p->zDb, p->zName, zName
117656117894
);
@@ -118005,25 +118243,24 @@
118005118243
**
118006118244
** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code.
118007118245
*/
118008118246
static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
118009118247
int iToken; /* Used to iterate through phrase tokens */
118010
- int rc = SQLITE_OK; /* Return code */
118011118248
char *aPoslist = 0; /* Position list for deferred tokens */
118012118249
int nPoslist = 0; /* Number of bytes in aPoslist */
118013118250
int iPrev = -1; /* Token number of previous deferred token */
118014118251
118015118252
assert( pPhrase->doclist.bFreeList==0 );
118016118253
118017
- for(iToken=0; rc==SQLITE_OK && iToken<pPhrase->nToken; iToken++){
118254
+ for(iToken=0; iToken<pPhrase->nToken; iToken++){
118018118255
Fts3PhraseToken *pToken = &pPhrase->aToken[iToken];
118019118256
Fts3DeferredToken *pDeferred = pToken->pDeferred;
118020118257
118021118258
if( pDeferred ){
118022118259
char *pList;
118023118260
int nList;
118024
- rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList);
118261
+ int rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList);
118025118262
if( rc!=SQLITE_OK ) return rc;
118026118263
118027118264
if( pList==0 ){
118028118265
sqlite3_free(aPoslist);
118029118266
pPhrase->doclist.pList = 0;
@@ -118120,10 +118357,11 @@
118120118357
if( pCsr->bDesc==pTab->bDescIdx
118121118358
&& bOptOk==1
118122118359
&& p->nToken==1
118123118360
&& pFirst->pSegcsr
118124118361
&& pFirst->pSegcsr->bLookup
118362
+ && pFirst->bFirst==0
118125118363
){
118126118364
/* Use the incremental approach. */
118127118365
int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn);
118128118366
rc = sqlite3Fts3MsrIncrStart(
118129118367
pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n);
@@ -118349,11 +118587,11 @@
118349118587
Fts3Expr *pExpr, /* Expression to consider */
118350118588
Fts3TokenAndCost **ppTC, /* Write new entries to *(*ppTC)++ */
118351118589
Fts3Expr ***ppOr, /* Write new OR root to *(*ppOr)++ */
118352118590
int *pRc /* IN/OUT: Error code */
118353118591
){
118354
- if( *pRc==SQLITE_OK && pExpr ){
118592
+ if( *pRc==SQLITE_OK ){
118355118593
if( pExpr->eType==FTSQUERY_PHRASE ){
118356118594
Fts3Phrase *pPhrase = pExpr->pPhrase;
118357118595
int i;
118358118596
for(i=0; *pRc==SQLITE_OK && i<pPhrase->nToken; i++){
118359118597
Fts3TokenAndCost *pTC = (*ppTC)++;
@@ -118363,10 +118601,15 @@
118363118601
pTC->pToken = &pPhrase->aToken[i];
118364118602
pTC->iCol = pPhrase->iColumn;
118365118603
*pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl);
118366118604
}
118367118605
}else if( pExpr->eType!=FTSQUERY_NOT ){
118606
+ assert( pExpr->eType==FTSQUERY_OR
118607
+ || pExpr->eType==FTSQUERY_AND
118608
+ || pExpr->eType==FTSQUERY_NEAR
118609
+ );
118610
+ assert( pExpr->pLeft && pExpr->pRight );
118368118611
if( pExpr->eType==FTSQUERY_OR ){
118369118612
pRoot = pExpr->pLeft;
118370118613
**ppOr = pRoot;
118371118614
(*ppOr)++;
118372118615
}
@@ -118466,10 +118709,19 @@
118466118709
int nOvfl = 0; /* Total overflow pages used by doclists */
118467118710
int nToken = 0; /* Total number of tokens in cluster */
118468118711
118469118712
int nMinEst = 0; /* The minimum count for any phrase so far. */
118470118713
int nLoad4 = 1; /* (Phrases that will be loaded)^4. */
118714
+
118715
+ /* Tokens are never deferred for FTS tables created using the content=xxx
118716
+ ** option. The reason being that it is not guaranteed that the content
118717
+ ** table actually contains the same data as the index. To prevent this from
118718
+ ** causing any problems, the deferred token optimization is completely
118719
+ ** disabled for content=xxx tables. */
118720
+ if( pTab->zContentTbl ){
118721
+ return SQLITE_OK;
118722
+ }
118471118723
118472118724
/* Count the tokens in this AND/NEAR cluster. If none of the doclists
118473118725
** associated with the tokens spill onto overflow pages, or if there is
118474118726
** only 1 token, exit early. No tokens to defer in this case. */
118475118727
for(ii=0; ii<nTC; ii++){
@@ -118529,11 +118781,15 @@
118529118781
Fts3PhraseToken *pToken = pTC->pToken;
118530118782
rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol);
118531118783
fts3SegReaderCursorFree(pToken->pSegcsr);
118532118784
pToken->pSegcsr = 0;
118533118785
}else{
118534
- nLoad4 = nLoad4*4;
118786
+ /* Set nLoad4 to the value of (4^nOther) for the next iteration of the
118787
+ ** for-loop. Except, limit the value to 2^24 to prevent it from
118788
+ ** overflowing the 32-bit integer it is stored in. */
118789
+ if( ii<12 ) nLoad4 = nLoad4*4;
118790
+
118535118791
if( ii==0 || pTC->pPhrase->nToken>1 ){
118536118792
/* Either this is the cheapest token in the entire query, or it is
118537118793
** part of a multi-token phrase. Either way, the entire doclist will
118538118794
** (eventually) be loaded into memory. It may as well be now. */
118539118795
Fts3PhraseToken *pToken = pTC->pToken;
@@ -119990,10 +120246,11 @@
119990120246
*/
119991120247
typedef struct ParseContext ParseContext;
119992120248
struct ParseContext {
119993120249
sqlite3_tokenizer *pTokenizer; /* Tokenizer module */
119994120250
const char **azCol; /* Array of column names for fts3 table */
120251
+ int bFts4; /* True to allow FTS4-only syntax */
119995120252
int nCol; /* Number of entries in azCol[] */
119996120253
int iDefaultCol; /* Default column to query */
119997120254
int isNot; /* True if getNextNode() sees a unary - */
119998120255
sqlite3_context *pCtx; /* Write error message here */
119999120256
int nNest; /* Number of nested brackets */
@@ -120077,13 +120334,25 @@
120077120334
120078120335
if( iEnd<n && z[iEnd]=='*' ){
120079120336
pRet->pPhrase->aToken[0].isPrefix = 1;
120080120337
iEnd++;
120081120338
}
120082
- if( !sqlite3_fts3_enable_parentheses && iStart>0 && z[iStart-1]=='-' ){
120083
- pParse->isNot = 1;
120339
+
120340
+ while( 1 ){
120341
+ if( !sqlite3_fts3_enable_parentheses
120342
+ && iStart>0 && z[iStart-1]=='-'
120343
+ ){
120344
+ pParse->isNot = 1;
120345
+ iStart--;
120346
+ }else if( pParse->bFts4 && iStart>0 && z[iStart-1]=='^' ){
120347
+ pRet->pPhrase->aToken[0].bFirst = 1;
120348
+ iStart--;
120349
+ }else{
120350
+ break;
120351
+ }
120084120352
}
120353
+
120085120354
}
120086120355
nConsumed = iEnd;
120087120356
}
120088120357
120089120358
pModule->xClose(pCursor);
@@ -120178,10 +120447,11 @@
120178120447
memcpy(&zTemp[nTemp], zByte, nByte);
120179120448
nTemp += nByte;
120180120449
120181120450
pToken->n = nByte;
120182120451
pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
120452
+ pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^');
120183120453
nToken = ii+1;
120184120454
}
120185120455
}
120186120456
120187120457
pModule->xClose(pCursor);
@@ -120629,10 +120899,11 @@
120629120899
** match any table column.
120630120900
*/
120631120901
SQLITE_PRIVATE int sqlite3Fts3ExprParse(
120632120902
sqlite3_tokenizer *pTokenizer, /* Tokenizer module */
120633120903
char **azCol, /* Array of column names for fts3 table */
120904
+ int bFts4, /* True to allow FTS4-only syntax */
120634120905
int nCol, /* Number of entries in azCol[] */
120635120906
int iDefaultCol, /* Default column to query */
120636120907
const char *z, int n, /* Text of MATCH query */
120637120908
Fts3Expr **ppExpr /* OUT: Parsed query structure */
120638120909
){
@@ -120642,10 +120913,11 @@
120642120913
sParse.pTokenizer = pTokenizer;
120643120914
sParse.azCol = (const char **)azCol;
120644120915
sParse.nCol = nCol;
120645120916
sParse.iDefaultCol = iDefaultCol;
120646120917
sParse.nNest = 0;
120918
+ sParse.bFts4 = bFts4;
120647120919
if( z==0 ){
120648120920
*ppExpr = 0;
120649120921
return SQLITE_OK;
120650120922
}
120651120923
if( n<0 ){
@@ -120831,11 +121103,11 @@
120831121103
for(ii=0; ii<nCol; ii++){
120832121104
azCol[ii] = (char *)sqlite3_value_text(argv[ii+2]);
120833121105
}
120834121106
120835121107
rc = sqlite3Fts3ExprParse(
120836
- pTokenizer, azCol, nCol, nCol, zExpr, nExpr, &pExpr
121108
+ pTokenizer, azCol, 0, nCol, nCol, zExpr, nExpr, &pExpr
120837121109
);
120838121110
if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){
120839121111
sqlite3_result_error(context, "Error parsing expression", -1);
120840121112
}else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){
120841121113
sqlite3_result_error_nomem(context);
@@ -122878,11 +123150,11 @@
122878123150
/* 2 */ "DELETE FROM %Q.'%q_content'",
122879123151
/* 3 */ "DELETE FROM %Q.'%q_segments'",
122880123152
/* 4 */ "DELETE FROM %Q.'%q_segdir'",
122881123153
/* 5 */ "DELETE FROM %Q.'%q_docsize'",
122882123154
/* 6 */ "DELETE FROM %Q.'%q_stat'",
122883
-/* 7 */ "SELECT %s FROM %Q.'%q_content' AS x WHERE rowid=?",
123155
+/* 7 */ "SELECT %s WHERE rowid=?",
122884123156
/* 8 */ "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1",
122885123157
/* 9 */ "INSERT INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)",
122886123158
/* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)",
122887123159
/* 11 */ "INSERT INTO %Q.'%q_segdir' VALUES(?,?,?,?,?,?)",
122888123160
@@ -122920,11 +123192,11 @@
122920123192
if( !pStmt ){
122921123193
char *zSql;
122922123194
if( eStmt==SQL_CONTENT_INSERT ){
122923123195
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
122924123196
}else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
122925
- zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist, p->zDb, p->zName);
123197
+ zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist);
122926123198
}else{
122927123199
zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
122928123200
}
122929123201
if( !zSql ){
122930123202
rc = SQLITE_NOMEM;
@@ -123031,21 +123303,28 @@
123031123303
** We try to avoid this because if FTS3 returns any error when committing
123032123304
** a transaction, the whole transaction will be rolled back. And this is
123033123305
** not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. It can
123034123306
** still happen if the user reads data directly from the %_segments or
123035123307
** %_segdir tables instead of going through FTS3 though.
123308
+**
123309
+** This reasoning does not apply to a content=xxx table.
123036123310
*/
123037123311
SQLITE_PRIVATE int sqlite3Fts3ReadLock(Fts3Table *p){
123038123312
int rc; /* Return code */
123039123313
sqlite3_stmt *pStmt; /* Statement used to obtain lock */
123040123314
123041
- rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pStmt, 0);
123042
- if( rc==SQLITE_OK ){
123043
- sqlite3_bind_null(pStmt, 1);
123044
- sqlite3_step(pStmt);
123045
- rc = sqlite3_reset(pStmt);
123315
+ if( p->zContentTbl==0 ){
123316
+ rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pStmt, 0);
123317
+ if( rc==SQLITE_OK ){
123318
+ sqlite3_bind_null(pStmt, 1);
123319
+ sqlite3_step(pStmt);
123320
+ rc = sqlite3_reset(pStmt);
123321
+ }
123322
+ }else{
123323
+ rc = SQLITE_OK;
123046123324
}
123325
+
123047123326
return rc;
123048123327
}
123049123328
123050123329
/*
123051123330
** Set *ppStmt to a statement handle that may be used to iterate through
@@ -123401,10 +123680,22 @@
123401123680
sqlite3_value **apVal, /* Array of values to insert */
123402123681
sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */
123403123682
){
123404123683
int rc; /* Return code */
123405123684
sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */
123685
+
123686
+ if( p->zContentTbl ){
123687
+ sqlite3_value *pRowid = apVal[p->nColumn+3];
123688
+ if( sqlite3_value_type(pRowid)==SQLITE_NULL ){
123689
+ pRowid = apVal[1];
123690
+ }
123691
+ if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){
123692
+ return SQLITE_CONSTRAINT;
123693
+ }
123694
+ *piDocid = sqlite3_value_int64(pRowid);
123695
+ return SQLITE_OK;
123696
+ }
123406123697
123407123698
/* Locate the statement handle used to insert data into the %_content
123408123699
** table. The SQL for this statement is:
123409123700
**
123410123701
** INSERT INTO %_content VALUES(?, ?, ?, ...)
@@ -123452,18 +123743,20 @@
123452123743
123453123744
/*
123454123745
** Remove all data from the FTS3 table. Clear the hash table containing
123455123746
** pending terms.
123456123747
*/
123457
-static int fts3DeleteAll(Fts3Table *p){
123748
+static int fts3DeleteAll(Fts3Table *p, int bContent){
123458123749
int rc = SQLITE_OK; /* Return code */
123459123750
123460123751
/* Discard the contents of the pending-terms hash table. */
123461123752
sqlite3Fts3PendingTermsClear(p);
123462123753
123463
- /* Delete everything from the %_content, %_segments and %_segdir tables. */
123464
- fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0);
123754
+ /* Delete everything from the shadow tables. Except, leave %_content as
123755
+ ** is if bContent is false. */
123756
+ assert( p->zContentTbl==0 || bContent==0 );
123757
+ if( bContent ) fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0);
123465123758
fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGMENTS, 0);
123466123759
fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0);
123467123760
if( p->bHasDocsize ){
123468123761
fts3SqlExec(&rc, p, SQL_DELETE_ALL_DOCSIZE, 0);
123469123762
}
@@ -124747,16 +125040,22 @@
124747125040
** error occurs, an SQLite error code is returned.
124748125041
*/
124749125042
static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
124750125043
sqlite3_stmt *pStmt;
124751125044
int rc;
124752
- rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
124753
- if( rc==SQLITE_OK ){
124754
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
124755
- *pisEmpty = sqlite3_column_int(pStmt, 0);
125045
+ if( p->zContentTbl ){
125046
+ /* If using the content=xxx option, assume the table is never empty */
125047
+ *pisEmpty = 0;
125048
+ rc = SQLITE_OK;
125049
+ }else{
125050
+ rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
125051
+ if( rc==SQLITE_OK ){
125052
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
125053
+ *pisEmpty = sqlite3_column_int(pStmt, 0);
125054
+ }
125055
+ rc = sqlite3_reset(pStmt);
124756125056
}
124757
- rc = sqlite3_reset(pStmt);
124758125057
}
124759125058
return rc;
124760125059
}
124761125060
124762125061
/*
@@ -125104,10 +125403,11 @@
125104125403
int isIgnoreEmpty = (pCsr->pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY);
125105125404
int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS);
125106125405
int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER);
125107125406
int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX);
125108125407
int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN);
125408
+ int isFirst = (pCsr->pFilter->flags & FTS3_SEGMENT_FIRST);
125109125409
125110125410
Fts3SegReader **apSegment = pCsr->apSegment;
125111125411
int nSegment = pCsr->nSegment;
125112125412
Fts3SegFilter *pFilter = pCsr->pFilter;
125113125413
int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
@@ -125163,10 +125463,11 @@
125163125463
}
125164125464
125165125465
assert( isIgnoreEmpty || (isRequirePos && !isColFilter) );
125166125466
if( nMerge==1
125167125467
&& !isIgnoreEmpty
125468
+ && !isFirst
125168125469
&& (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0)
125169125470
){
125170125471
pCsr->nDoclist = apSegment[0]->nDoclist;
125171125472
if( fts3SegReaderIsPending(apSegment[0]) ){
125172125473
rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist);
@@ -125228,16 +125529,28 @@
125228125529
if( !aNew ){
125229125530
return SQLITE_NOMEM;
125230125531
}
125231125532
pCsr->aBuffer = aNew;
125232125533
}
125233
- nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
125234
- iPrev = iDocid;
125235
- if( isRequirePos ){
125236
- memcpy(&pCsr->aBuffer[nDoclist], pList, nList);
125237
- nDoclist += nList;
125238
- pCsr->aBuffer[nDoclist++] = '\0';
125534
+
125535
+ if( isFirst ){
125536
+ char *a = &pCsr->aBuffer[nDoclist];
125537
+ int nWrite;
125538
+
125539
+ nWrite = sqlite3Fts3FirstFilter(iDelta, pList, nList, a);
125540
+ if( nWrite ){
125541
+ iPrev = iDocid;
125542
+ nDoclist += nWrite;
125543
+ }
125544
+ }else{
125545
+ nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
125546
+ iPrev = iDocid;
125547
+ if( isRequirePos ){
125548
+ memcpy(&pCsr->aBuffer[nDoclist], pList, nList);
125549
+ nDoclist += nList;
125550
+ pCsr->aBuffer[nDoclist++] = '\0';
125551
+ }
125239125552
}
125240125553
}
125241125554
125242125555
fts3SegReaderSort(apSegment, nMerge, j, xCmp);
125243125556
}
@@ -125409,13 +125722,13 @@
125409125722
** Insert the sizes (in tokens) for each column of the document
125410125723
** with docid equal to p->iPrevDocid. The sizes are encoded as
125411125724
** a blob of varints.
125412125725
*/
125413125726
static void fts3InsertDocsize(
125414
- int *pRC, /* Result code */
125415
- Fts3Table *p, /* Table into which to insert */
125416
- u32 *aSz /* Sizes of each column */
125727
+ int *pRC, /* Result code */
125728
+ Fts3Table *p, /* Table into which to insert */
125729
+ u32 *aSz /* Sizes of each column, in tokens */
125417125730
){
125418125731
char *pBlob; /* The BLOB encoding of the document size */
125419125732
int nBlob; /* Number of bytes in the BLOB */
125420125733
sqlite3_stmt *pStmt; /* Statement used to insert the encoding */
125421125734
int rc; /* Result code from subfunctions */
@@ -125532,10 +125845,90 @@
125532125845
sqlite3Fts3SegmentsClose(p);
125533125846
sqlite3Fts3PendingTermsClear(p);
125534125847
125535125848
return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc;
125536125849
}
125850
+
125851
+/*
125852
+** This function is called when the user executes the following statement:
125853
+**
125854
+** INSERT INTO <tbl>(<tbl>) VALUES('rebuild');
125855
+**
125856
+** The entire FTS index is discarded and rebuilt. If the table is one
125857
+** created using the content=xxx option, then the new index is based on
125858
+** the current contents of the xxx table. Otherwise, it is rebuilt based
125859
+** on the contents of the %_content table.
125860
+*/
125861
+static int fts3DoRebuild(Fts3Table *p){
125862
+ int rc; /* Return Code */
125863
+
125864
+ rc = fts3DeleteAll(p, 0);
125865
+ if( rc==SQLITE_OK ){
125866
+ u32 *aSz = 0;
125867
+ u32 *aSzIns = 0;
125868
+ u32 *aSzDel = 0;
125869
+ sqlite3_stmt *pStmt = 0;
125870
+ int nEntry = 0;
125871
+
125872
+ /* Compose and prepare an SQL statement to loop through the content table */
125873
+ char *zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist);
125874
+ if( !zSql ){
125875
+ rc = SQLITE_NOMEM;
125876
+ }else{
125877
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
125878
+ sqlite3_free(zSql);
125879
+ }
125880
+
125881
+ if( rc==SQLITE_OK ){
125882
+ int nByte = sizeof(u32) * (p->nColumn+1)*3;
125883
+ aSz = (u32 *)sqlite3_malloc(nByte);
125884
+ if( aSz==0 ){
125885
+ rc = SQLITE_NOMEM;
125886
+ }else{
125887
+ memset(aSz, 0, nByte);
125888
+ aSzIns = &aSz[p->nColumn+1];
125889
+ aSzDel = &aSzIns[p->nColumn+1];
125890
+ }
125891
+ }
125892
+
125893
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
125894
+ int iCol;
125895
+ rc = fts3PendingTermsDocid(p, sqlite3_column_int64(pStmt, 0));
125896
+ aSz[p->nColumn] = 0;
125897
+ for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){
125898
+ const char *z = (const char *) sqlite3_column_text(pStmt, iCol+1);
125899
+ rc = fts3PendingTermsAdd(p, z, iCol, &aSz[iCol]);
125900
+ aSz[p->nColumn] += sqlite3_column_bytes(pStmt, iCol+1);
125901
+ }
125902
+ if( p->bHasDocsize ){
125903
+ fts3InsertDocsize(&rc, p, aSz);
125904
+ }
125905
+ if( rc!=SQLITE_OK ){
125906
+ sqlite3_finalize(pStmt);
125907
+ pStmt = 0;
125908
+ }else{
125909
+ nEntry++;
125910
+ for(iCol=0; iCol<=p->nColumn; iCol++){
125911
+ aSzIns[iCol] += aSz[iCol];
125912
+ }
125913
+ }
125914
+ }
125915
+ if( p->bHasStat ){
125916
+ fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nEntry);
125917
+ }
125918
+ sqlite3_free(aSz);
125919
+
125920
+ if( pStmt ){
125921
+ int rc2 = sqlite3_finalize(pStmt);
125922
+ if( rc==SQLITE_OK ){
125923
+ rc = rc2;
125924
+ }
125925
+ }
125926
+ }
125927
+
125928
+ return rc;
125929
+}
125537125930
125538125931
/*
125539125932
** Handle a 'special' INSERT of the form:
125540125933
**
125541125934
** "INSERT INTO tbl(tbl) VALUES(<expr>)"
@@ -125550,10 +125943,12 @@
125550125943
125551125944
if( !zVal ){
125552125945
return SQLITE_NOMEM;
125553125946
}else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){
125554125947
rc = fts3DoOptimize(p, 0);
125948
+ }else if( nVal==7 && 0==sqlite3_strnicmp(zVal, "rebuild", 7) ){
125949
+ rc = fts3DoRebuild(p);
125555125950
#ifdef SQLITE_TEST
125556125951
}else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
125557125952
p->nNodeSize = atoi(&zVal[9]);
125558125953
rc = SQLITE_OK;
125559125954
}else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){
@@ -125630,10 +126025,11 @@
125630126025
pTC->pTokenizer = pT;
125631126026
rc = pModule->xNext(pTC, &zToken, &nToken, &iDum1, &iDum2, &iPos);
125632126027
for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){
125633126028
Fts3PhraseToken *pPT = pDef->pToken;
125634126029
if( (pDef->iCol>=p->nColumn || pDef->iCol==i)
126030
+ && (pPT->bFirst==0 || iPos==0)
125635126031
&& (pPT->n==nToken || (pPT->isPrefix && pPT->n<nToken))
125636126032
&& (0==memcmp(zToken, pPT->z, pPT->n))
125637126033
){
125638126034
fts3PendingListAppend(&pDef->pList, iDocid, i, iPos, &rc);
125639126035
}
@@ -125721,18 +126117,22 @@
125721126117
if( rc==SQLITE_OK ){
125722126118
if( isEmpty ){
125723126119
/* Deleting this row means the whole table is empty. In this case
125724126120
** delete the contents of all three tables and throw away any
125725126121
** data in the pendingTerms hash table. */
125726
- rc = fts3DeleteAll(p);
126122
+ rc = fts3DeleteAll(p, 1);
125727126123
*pnDoc = *pnDoc - 1;
125728126124
}else{
125729126125
sqlite3_int64 iRemove = sqlite3_value_int64(pRowid);
125730126126
rc = fts3PendingTermsDocid(p, iRemove);
125731126127
fts3DeleteTerms(&rc, p, pRowid, aSzDel);
125732
- fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
125733
- if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1;
126128
+ if( p->zContentTbl==0 ){
126129
+ fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
126130
+ if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1;
126131
+ }else{
126132
+ *pnDoc = *pnDoc - 1;
126133
+ }
125734126134
if( p->bHasDocsize ){
125735126135
fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid);
125736126136
}
125737126137
}
125738126138
}
@@ -125788,11 +126188,11 @@
125788126188
** should be deleted from the database before inserting the new row. Or,
125789126189
** if the on-conflict mode is other than REPLACE, then this method must
125790126190
** detect the conflict and return SQLITE_CONSTRAINT before beginning to
125791126191
** modify the database file.
125792126192
*/
125793
- if( nArg>1 ){
126193
+ if( nArg>1 && p->zContentTbl==0 ){
125794126194
/* Find the value object that holds the new rowid value. */
125795126195
sqlite3_value *pNewRowid = apVal[3+p->nColumn];
125796126196
if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
125797126197
pNewRowid = apVal[1];
125798126198
}
@@ -125839,11 +126239,13 @@
125839126239
125840126240
/* If this is an INSERT or UPDATE operation, insert the new record. */
125841126241
if( nArg>1 && rc==SQLITE_OK ){
125842126242
if( bInsertDone==0 ){
125843126243
rc = fts3InsertData(p, apVal, pRowid);
125844
- if( rc==SQLITE_CONSTRAINT ) rc = FTS_CORRUPT_VTAB;
126244
+ if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){
126245
+ rc = FTS_CORRUPT_VTAB;
126246
+ }
125845126247
}
125846126248
if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){
125847126249
rc = fts3PendingTermsDocid(p, *pRowid);
125848126250
}
125849126251
if( rc==SQLITE_OK ){
@@ -126259,10 +126661,11 @@
126259126661
pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol);
126260126662
if( pCsr ){
126261126663
int iFirst = 0;
126262126664
pPhrase->pList = pCsr;
126263126665
fts3GetDeltaPosition(&pCsr, &iFirst);
126666
+ assert( iFirst>=0 );
126264126667
pPhrase->pHead = pCsr;
126265126668
pPhrase->pTail = pCsr;
126266126669
pPhrase->iHead = iFirst;
126267126670
pPhrase->iTail = iFirst;
126268126671
}else{
@@ -127300,11 +127703,11 @@
127300127703
}
127301127704
}
127302127705
127303127706
if( !pTerm ){
127304127707
/* All offsets for this column have been gathered. */
127305
- break;
127708
+ rc = SQLITE_DONE;
127306127709
}else{
127307127710
assert( iCurrent<=iMinPos );
127308127711
if( 0==(0xFE&*pTerm->pList) ){
127309127712
pTerm->pList = 0;
127310127713
}else{
@@ -127317,11 +127720,11 @@
127317127720
char aBuffer[64];
127318127721
sqlite3_snprintf(sizeof(aBuffer), aBuffer,
127319127722
"%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart
127320127723
);
127321127724
rc = fts3StringAppend(&res, aBuffer, -1);
127322
- }else if( rc==SQLITE_DONE ){
127725
+ }else if( rc==SQLITE_DONE && pTab->zContentTbl==0 ){
127323127726
rc = FTS_CORRUPT_VTAB;
127324127727
}
127325127728
}
127326127729
}
127327127730
if( rc==SQLITE_DONE ){
127328127731
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -656,11 +656,11 @@
656 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
657 ** [sqlite_version()] and [sqlite_source_id()].
658 */
659 #define SQLITE_VERSION "3.7.9"
660 #define SQLITE_VERSION_NUMBER 3007009
661 #define SQLITE_SOURCE_ID "2011-10-15 00:16:30 39408702a989f907261c298bf0947f3e68bd10fe"
662
663 /*
664 ** CAPI3REF: Run-Time Library Version Numbers
665 ** KEYWORDS: sqlite3_version, sqlite3_sourceid
666 **
@@ -1951,12 +1951,12 @@
1951 ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
1952 ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
1953 ** allocator is engaged to handle all of SQLites memory allocation needs.
1954 ** The first pointer (the memory pointer) must be aligned to an 8-byte
1955 ** boundary or subsequent behavior of SQLite will be undefined.
1956 ** The minimum allocation size is capped at 2^12. Reasonable values
1957 ** for the minimum allocation size are 2^5 through 2^8.</dd>
1958 **
1959 ** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
1960 ** <dd> ^(This option takes a single argument which is a pointer to an
1961 ** instance of the [sqlite3_mutex_methods] structure. The argument specifies
1962 ** alternative low-level mutex routines to be used in place
@@ -20969,11 +20969,11 @@
20969 }else if( *z=='+' ){
20970 z+=incr;
20971 }
20972 /* copy digits to exponent */
20973 while( z<zEnd && sqlite3Isdigit(*z) ){
20974 e = e*10 + (*z - '0');
20975 z+=incr;
20976 eValid = 1;
20977 }
20978 }
20979
@@ -21020,10 +21020,16 @@
21020 result /= 1.0e+308;
21021 }else{
21022 result = s * scale;
21023 result *= 1.0e+308;
21024 }
 
 
 
 
 
 
21025 }else{
21026 /* 1.0e+22 is the largest power of 10 than can be
21027 ** represented exactly. */
21028 while( e%22 ) { scale *= 1.0e+1; e -= 1; }
21029 while( e>0 ) { scale *= 1.0e+22; e -= 22; }
@@ -68962,11 +68968,11 @@
68962
68963 /* Do not allow a transition to journal_mode=WAL for a database
68964 ** in temporary storage or if the VFS does not support shared memory
68965 */
68966 if( u.ch.eNew==PAGER_JOURNALMODE_WAL
68967 && (u.ch.zFilename[0]==0 /* Temp file */
68968 || !sqlite3PagerWalSupported(u.ch.pPager)) /* No shared-memory support */
68969 ){
68970 u.ch.eNew = u.ch.eOld;
68971 }
68972
@@ -69397,14 +69403,19 @@
69397 u.co.pName = &aMem[pOp->p1];
69398 assert( u.co.pVtab->pModule->xRename );
69399 assert( memIsValid(u.co.pName) );
69400 REGISTER_TRACE(pOp->p1, u.co.pName);
69401 assert( u.co.pName->flags & MEM_Str );
69402 rc = u.co.pVtab->pModule->xRename(u.co.pVtab, u.co.pName->z);
69403 importVtabErrMsg(p, u.co.pVtab);
69404 p->expired = 0;
69405
 
 
 
 
 
69406 break;
69407 }
69408 #endif
69409
69410 #ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -71758,10 +71769,28 @@
71758 ExprSetProperty(pExpr, EP_Static);
71759 sqlite3ExprDelete(db, pExpr);
71760 memcpy(pExpr, pDup, sizeof(*pExpr));
71761 sqlite3DbFree(db, pDup);
71762 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71763
71764 /*
71765 ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
71766 ** that name in the set of source tables in pSrcList and make the pExpr
71767 ** expression node refer back to that source column. The following changes
@@ -71850,38 +71879,25 @@
71850 pSchema = pTab->pSchema;
71851 pMatch = pItem;
71852 }
71853 for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){
71854 if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
71855 IdList *pUsing;
 
 
 
 
 
 
 
71856 cnt++;
71857 pExpr->iTable = pItem->iCursor;
71858 pExpr->pTab = pTab;
71859 pMatch = pItem;
71860 pSchema = pTab->pSchema;
71861 /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
71862 pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j;
71863 if( i<pSrcList->nSrc-1 ){
71864 if( pItem[1].jointype & JT_NATURAL ){
71865 /* If this match occurred in the left table of a natural join,
71866 ** then skip the right table to avoid a duplicate match */
71867 pItem++;
71868 i++;
71869 }else if( (pUsing = pItem[1].pUsing)!=0 ){
71870 /* If this match occurs on a column that is in the USING clause
71871 ** of a join, skip the search of the right table of the join
71872 ** to avoid a duplicate match there. */
71873 int k;
71874 for(k=0; k<pUsing->nId; k++){
71875 if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ){
71876 pItem++;
71877 i++;
71878 break;
71879 }
71880 }
71881 }
71882 }
71883 break;
71884 }
71885 }
71886 }
71887 }
@@ -102501,10 +102517,11 @@
102501 tempWC.pParse = pWC->pParse;
102502 tempWC.pMaskSet = pWC->pMaskSet;
102503 tempWC.pOuter = pWC;
102504 tempWC.op = TK_AND;
102505 tempWC.a = pOrTerm;
 
102506 tempWC.nTerm = 1;
102507 bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost);
102508 }else{
102509 continue;
102510 }
@@ -114528,10 +114545,11 @@
114528 const char *zDb; /* logical database name */
114529 const char *zName; /* virtual table name */
114530 int nColumn; /* number of named columns in virtual table */
114531 char **azColumn; /* column names. malloced */
114532 sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */
 
114533
114534 /* Precompiled statements used by the implementation. Each of these
114535 ** statements is run and reset within a single virtual table API call.
114536 */
114537 sqlite3_stmt *aStmt[27];
@@ -114568,11 +114586,11 @@
114568 } *aIndex;
114569 int nMaxPendingData; /* Max pending data before flush to disk */
114570 int nPendingData; /* Current bytes of pending data */
114571 sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */
114572
114573 #if defined(SQLITE_DEBUG)
114574 /* State variables used for validating that the transaction control
114575 ** methods of the virtual table are called at appropriate times. These
114576 ** values do not contribution to the FTS computation; they are used for
114577 ** verifying the SQLite core.
114578 */
@@ -114653,10 +114671,11 @@
114653 */
114654 struct Fts3PhraseToken {
114655 char *z; /* Text of the token */
114656 int n; /* Number of bytes in buffer z */
114657 int isPrefix; /* True if token ends with a "*" character */
 
114658
114659 /* Variables above this point are populated when the expression is
114660 ** parsed (by code in fts3_expr.c). Below this point the variables are
114661 ** used when evaluating the expression. */
114662 Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
@@ -114771,10 +114790,11 @@
114771 #define FTS3_SEGMENT_REQUIRE_POS 0x00000001
114772 #define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002
114773 #define FTS3_SEGMENT_COLUMN_FILTER 0x00000004
114774 #define FTS3_SEGMENT_PREFIX 0x00000008
114775 #define FTS3_SEGMENT_SCAN 0x00000010
 
114776
114777 /* Type passed as 4th argument to SegmentReaderIterate() */
114778 struct Fts3SegFilter {
114779 const char *zTerm;
114780 int nTerm;
@@ -114810,12 +114830,12 @@
114810 SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
114811 SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *);
114812 SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64);
114813 SQLITE_PRIVATE void sqlite3Fts3Dequote(char *);
114814 SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
114815
114816 SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *);
 
114817
114818 /* fts3_tokenizer.c */
114819 SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *, int *);
114820 SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *);
114821 SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *,
@@ -114830,11 +114850,11 @@
114830 );
114831 SQLITE_PRIVATE void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *);
114832
114833 /* fts3_expr.c */
114834 SQLITE_PRIVATE int sqlite3Fts3ExprParse(sqlite3_tokenizer *,
114835 char **, int, int, const char *, int, Fts3Expr **
114836 );
114837 SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *);
114838 #ifdef SQLITE_TEST
114839 SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db);
114840 SQLITE_PRIVATE int sqlite3Fts3InitTerm(sqlite3 *db);
@@ -115031,10 +115051,11 @@
115031 sqlite3_finalize(p->aStmt[i]);
115032 }
115033 sqlite3_free(p->zSegmentsTbl);
115034 sqlite3_free(p->zReadExprlist);
115035 sqlite3_free(p->zWriteExprlist);
 
115036
115037 /* Invoke the tokenizer destructor to free the tokenizer. */
115038 p->pTokenizer->pModule->xDestroy(p->pTokenizer);
115039
115040 sqlite3_free(p);
@@ -115070,20 +115091,23 @@
115070
115071 /*
115072 ** The xDestroy() virtual table method.
115073 */
115074 static int fts3DestroyMethod(sqlite3_vtab *pVtab){
115075 int rc = SQLITE_OK; /* Return code */
115076 Fts3Table *p = (Fts3Table *)pVtab;
115077 sqlite3 *db = p->db;
 
 
115078
115079 /* Drop the shadow tables */
115080 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", p->zDb, p->zName);
115081 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", p->zDb,p->zName);
115082 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", p->zDb, p->zName);
115083 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", p->zDb, p->zName);
115084 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", p->zDb, p->zName);
 
 
115085
115086 /* If everything has worked, invoke fts3DisconnectMethod() to free the
115087 ** memory associated with the Fts3Table structure and return SQLITE_OK.
115088 ** Otherwise, return an SQLite error code.
115089 */
@@ -115141,27 +115165,31 @@
115141 ** %_stat tables required by FTS4.
115142 */
115143 static int fts3CreateTables(Fts3Table *p){
115144 int rc = SQLITE_OK; /* Return code */
115145 int i; /* Iterator variable */
115146 char *zContentCols; /* Columns of %_content table */
115147 sqlite3 *db = p->db; /* The database connection */
115148
115149 /* Create a list of user columns for the content table */
115150 zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY");
115151 for(i=0; zContentCols && i<p->nColumn; i++){
115152 char *z = p->azColumn[i];
115153 zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
115154 }
115155 if( zContentCols==0 ) rc = SQLITE_NOMEM;
115156
115157 /* Create the content table */
115158 fts3DbExec(&rc, db,
115159 "CREATE TABLE %Q.'%q_content'(%s)",
115160 p->zDb, p->zName, zContentCols
115161 );
115162 sqlite3_free(zContentCols);
 
 
 
 
 
115163 /* Create other tables */
115164 fts3DbExec(&rc, db,
115165 "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);",
115166 p->zDb, p->zName
115167 );
@@ -115308,12 +115336,12 @@
115308 }
115309 return zRet;
115310 }
115311
115312 /*
115313 ** Return a list of comma separated SQL expressions that could be used
115314 ** in a SELECT statement such as the following:
115315 **
115316 ** SELECT <list of expressions> FROM %_content AS x ...
115317 **
115318 ** to return the docid, followed by each column of text data in order
115319 ** from left to write. If parameter zFunc is not NULL, then instead of
@@ -115320,11 +115348,11 @@
115320 ** being returned directly each column of text data is passed to an SQL
115321 ** function named zFunc first. For example, if zFunc is "unzip" and the
115322 ** table has the three user-defined columns "a", "b", and "c", the following
115323 ** string is returned:
115324 **
115325 ** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c')"
115326 **
115327 ** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
115328 ** is the responsibility of the caller to eventually free it.
115329 **
115330 ** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and
@@ -115336,20 +115364,32 @@
115336 char *zRet = 0;
115337 char *zFree = 0;
115338 char *zFunction;
115339 int i;
115340
115341 if( !zFunc ){
115342 zFunction = "";
 
 
 
 
 
 
 
 
 
115343 }else{
115344 zFree = zFunction = fts3QuoteId(zFunc);
 
 
 
115345 }
115346 fts3Appendf(pRc, &zRet, "docid");
115347 for(i=0; i<p->nColumn; i++){
115348 fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]);
115349 }
115350 sqlite3_free(zFree);
115351 return zRet;
115352 }
115353
115354 /*
115355 ** Return a list of N comma separated question marks, where N is the number
@@ -115468,10 +115508,95 @@
115468 }
115469 }
115470
115471 return SQLITE_OK;
115472 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115473
115474 /*
115475 ** This function is the implementation of both the xConnect and xCreate
115476 ** methods of the FTS3 virtual table.
115477 **
@@ -115513,10 +115638,11 @@
115513 int bNoDocsize = 0; /* True to omit %_docsize table */
115514 int bDescIdx = 0; /* True to store descending indexes */
115515 char *zPrefix = 0; /* Prefix parameter value (or NULL) */
115516 char *zCompress = 0; /* compress=? parameter (or NULL) */
115517 char *zUncompress = 0; /* uncompress=? parameter (or NULL) */
 
115518
115519 assert( strlen(argv[0])==4 );
115520 assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
115521 || (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4)
115522 );
@@ -115556,17 +115682,17 @@
115556 /* Check if it is an FTS4 special argument. */
115557 else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){
115558 struct Fts4Option {
115559 const char *zOpt;
115560 int nOpt;
115561 char **pzVar;
115562 } aFts4Opt[] = {
115563 { "matchinfo", 9, 0 }, /* 0 -> MATCHINFO */
115564 { "prefix", 6, 0 }, /* 1 -> PREFIX */
115565 { "compress", 8, 0 }, /* 2 -> COMPRESS */
115566 { "uncompress", 10, 0 }, /* 3 -> UNCOMPRESS */
115567 { "order", 5, 0 } /* 4 -> ORDER */
 
115568 };
115569
115570 int iOpt;
115571 if( !zVal ){
115572 rc = SQLITE_NOMEM;
@@ -115608,17 +115734,24 @@
115608 zVal = 0;
115609 break;
115610
115611 case 4: /* ORDER */
115612 if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3))
115613 && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 3))
115614 ){
115615 *pzErr = sqlite3_mprintf("unrecognized order: %s", zVal);
115616 rc = SQLITE_ERROR;
115617 }
115618 bDescIdx = (zVal[0]=='d' || zVal[0]=='D');
115619 break;
 
 
 
 
 
 
 
115620 }
115621 }
115622 sqlite3_free(zVal);
115623 }
115624 }
@@ -115627,10 +115760,30 @@
115627 else {
115628 nString += (int)(strlen(z) + 1);
115629 aCol[nCol++] = z;
115630 }
115631 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115632 if( rc!=SQLITE_OK ) goto fts3_init_out;
115633
115634 if( nCol==0 ){
115635 assert( nString==0 );
115636 aCol[0] = "content";
@@ -115671,10 +115824,12 @@
115671 p->pTokenizer = pTokenizer;
115672 p->nMaxPendingData = FTS3_MAX_PENDING_DATA;
115673 p->bHasDocsize = (isFts4 && bNoDocsize==0);
115674 p->bHasStat = isFts4;
115675 p->bDescIdx = bDescIdx;
 
 
115676 TESTONLY( p->inTransaction = -1 );
115677 TESTONLY( p->mxSavepoint = -1 );
115678
115679 p->aIndex = (struct Fts3Index *)&p->azColumn[nCol];
115680 memcpy(p->aIndex, aIndex, sizeof(struct Fts3Index) * nIndex);
@@ -115732,10 +115887,11 @@
115732 fts3_init_out:
115733 sqlite3_free(zPrefix);
115734 sqlite3_free(aIndex);
115735 sqlite3_free(zCompress);
115736 sqlite3_free(zUncompress);
 
115737 sqlite3_free((void *)aCol);
115738 if( rc!=SQLITE_OK ){
115739 if( p ){
115740 fts3DisconnectMethod((sqlite3_vtab *)p);
115741 }else if( pTokenizer ){
@@ -115882,40 +116038,69 @@
115882 sqlite3_free(pCsr->aMatchinfo);
115883 assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
115884 sqlite3_free(pCsr);
115885 return SQLITE_OK;
115886 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115887
115888 /*
115889 ** Position the pCsr->pStmt statement so that it is on the row
115890 ** of the %_content table that contains the last match. Return
115891 ** SQLITE_OK on success.
115892 */
115893 static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){
 
115894 if( pCsr->isRequireSeek ){
115895 sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId);
115896 pCsr->isRequireSeek = 0;
115897 if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){
115898 return SQLITE_OK;
115899 }else{
115900 int rc = sqlite3_reset(pCsr->pStmt);
115901 if( rc==SQLITE_OK ){
115902 /* If no row was found and no error has occured, then the %_content
115903 ** table is missing a row that is present in the full-text index.
115904 ** The data structures are corrupt.
115905 */
115906 rc = FTS_CORRUPT_VTAB;
115907 }
115908 pCsr->isEof = 1;
115909 if( pContext ){
115910 sqlite3_result_error_code(pContext, rc);
115911 }
115912 return rc;
115913 }
115914 }else{
115915 return SQLITE_OK;
115916 }
 
 
 
115917 }
115918
115919 /*
115920 ** This function is used to process a single interior node when searching
115921 ** a b-tree for a term or term prefix. The node data is passed to this
@@ -116351,20 +116536,20 @@
116351 int isSaveLeft, /* Save the left position */
116352 int isExact, /* If *pp1 is exactly nTokens before *pp2 */
116353 char **pp1, /* IN/OUT: Left input list */
116354 char **pp2 /* IN/OUT: Right input list */
116355 ){
116356 char *p = (pp ? *pp : 0);
116357 char *p1 = *pp1;
116358 char *p2 = *pp2;
116359 int iCol1 = 0;
116360 int iCol2 = 0;
116361
116362 /* Never set both isSaveLeft and isExact for the same invocation. */
116363 assert( isSaveLeft==0 || isExact==0 );
116364
116365 assert( *p1!=0 && *p2!=0 );
116366 if( *p1==POS_COLUMN ){
116367 p1++;
116368 p1 += sqlite3Fts3GetVarint32(p1, &iCol1);
116369 }
116370 if( *p2==POS_COLUMN ){
@@ -116377,11 +116562,11 @@
116377 char *pSave = p;
116378 sqlite3_int64 iPrev = 0;
116379 sqlite3_int64 iPos1 = 0;
116380 sqlite3_int64 iPos2 = 0;
116381
116382 if( pp && iCol1 ){
116383 *p++ = POS_COLUMN;
116384 p += sqlite3Fts3PutVarint(p, iCol1);
116385 }
116386
116387 assert( *p1!=POS_END && *p1!=POS_COLUMN );
@@ -116392,20 +116577,14 @@
116392 while( 1 ){
116393 if( iPos2==iPos1+nToken
116394 || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken)
116395 ){
116396 sqlite3_int64 iSave;
116397 if( !pp ){
116398 fts3PoslistCopy(0, &p2);
116399 fts3PoslistCopy(0, &p1);
116400 *pp1 = p1;
116401 *pp2 = p2;
116402 return 1;
116403 }
116404 iSave = isSaveLeft ? iPos1 : iPos2;
116405 fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2;
116406 pSave = 0;
 
116407 }
116408 if( (!isSaveLeft && iPos2<=(iPos1+nToken)) || iPos2<=iPos1 ){
116409 if( (*p2&0xFE)==0 ) break;
116410 fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2;
116411 }else{
@@ -116450,11 +116629,11 @@
116450
116451 fts3PoslistCopy(0, &p2);
116452 fts3PoslistCopy(0, &p1);
116453 *pp1 = p1;
116454 *pp2 = p2;
116455 if( !pp || *pp==p ){
116456 return 0;
116457 }
116458 *p++ = 0x00;
116459 *pp = p;
116460 return 1;
@@ -116752,10 +116931,60 @@
116752 }
116753 }
116754
116755 *pnRight = p - aOut;
116756 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116757
116758
116759 /*
116760 ** Merge all doclists in the TermSelect.aaOutput[] array into a single
116761 ** doclist stored in TermSelect.aaOutput[0]. If successful, delete all
@@ -117109,10 +117338,11 @@
117109 pSegcsr = pTok->pSegcsr;
117110 memset(&tsc, 0, sizeof(TermSelect));
117111
117112 filter.flags = FTS3_SEGMENT_IGNORE_EMPTY | FTS3_SEGMENT_REQUIRE_POS
117113 | (pTok->isPrefix ? FTS3_SEGMENT_PREFIX : 0)
 
117114 | (iColumn<p->nColumn ? FTS3_SEGMENT_COLUMN_FILTER : 0);
117115 filter.iCol = iColumn;
117116 filter.zTerm = pTok->z;
117117 filter.nTerm = pTok->n;
117118
@@ -117249,12 +117479,12 @@
117249
117250 if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
117251 return SQLITE_NOMEM;
117252 }
117253
117254 rc = sqlite3Fts3ExprParse(p->pTokenizer, p->azColumn, p->nColumn,
117255 iCol, zQuery, -1, &pCsr->pExpr
117256 );
117257 if( rc!=SQLITE_OK ){
117258 if( rc==SQLITE_ERROR ){
117259 static const char *zErr = "malformed MATCH expression: [%s]";
117260 p->base.zErrMsg = sqlite3_mprintf(zErr, zQuery);
@@ -117277,26 +117507,27 @@
117277 ** statement loops through all rows of the %_content table. For a
117278 ** full-text query or docid lookup, the statement retrieves a single
117279 ** row by docid.
117280 */
117281 if( idxNum==FTS3_FULLSCAN_SEARCH ){
117282 const char *zSort = (pCsr->bDesc ? "DESC" : "ASC");
117283 const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s";
117284 zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName, zSort);
117285 }else{
117286 const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?";
117287 zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName);
117288 }
117289 if( !zSql ) return SQLITE_NOMEM;
117290 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
117291 sqlite3_free(zSql);
117292 if( rc!=SQLITE_OK ) return rc;
117293
117294 if( idxNum==FTS3_DOCID_SEARCH ){
117295 rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
117296 if( rc!=SQLITE_OK ) return rc;
117297 }
 
117298
117299 return fts3NextMethod(pCursor);
117300 }
117301
117302 /*
@@ -117345,11 +117576,11 @@
117345 ** Return a blob which is a pointer to the cursor.
117346 */
117347 sqlite3_result_blob(pContext, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT);
117348 }else{
117349 rc = fts3CursorSeek(0, pCsr);
117350 if( rc==SQLITE_OK ){
117351 sqlite3_result_value(pContext, sqlite3_column_value(pCsr->pStmt, iCol+1));
117352 }
117353 }
117354
117355 assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
@@ -117638,19 +117869,26 @@
117638 ){
117639 Fts3Table *p = (Fts3Table *)pVtab;
117640 sqlite3 *db = p->db; /* Database connection */
117641 int rc; /* Return Code */
117642
 
 
 
 
 
 
 
117643 rc = sqlite3Fts3PendingTermsFlush(p);
117644 if( rc!=SQLITE_OK ){
117645 return rc;
 
 
 
 
117646 }
117647
117648 fts3DbExec(&rc, db,
117649 "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';",
117650 p->zDb, p->zName, zName
117651 );
117652 if( p->bHasDocsize ){
117653 fts3DbExec(&rc, db,
117654 "ALTER TABLE %Q.'%q_docsize' RENAME TO '%q_docsize';",
117655 p->zDb, p->zName, zName
117656 );
@@ -118005,25 +118243,24 @@
118005 **
118006 ** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code.
118007 */
118008 static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
118009 int iToken; /* Used to iterate through phrase tokens */
118010 int rc = SQLITE_OK; /* Return code */
118011 char *aPoslist = 0; /* Position list for deferred tokens */
118012 int nPoslist = 0; /* Number of bytes in aPoslist */
118013 int iPrev = -1; /* Token number of previous deferred token */
118014
118015 assert( pPhrase->doclist.bFreeList==0 );
118016
118017 for(iToken=0; rc==SQLITE_OK && iToken<pPhrase->nToken; iToken++){
118018 Fts3PhraseToken *pToken = &pPhrase->aToken[iToken];
118019 Fts3DeferredToken *pDeferred = pToken->pDeferred;
118020
118021 if( pDeferred ){
118022 char *pList;
118023 int nList;
118024 rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList);
118025 if( rc!=SQLITE_OK ) return rc;
118026
118027 if( pList==0 ){
118028 sqlite3_free(aPoslist);
118029 pPhrase->doclist.pList = 0;
@@ -118120,10 +118357,11 @@
118120 if( pCsr->bDesc==pTab->bDescIdx
118121 && bOptOk==1
118122 && p->nToken==1
118123 && pFirst->pSegcsr
118124 && pFirst->pSegcsr->bLookup
 
118125 ){
118126 /* Use the incremental approach. */
118127 int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn);
118128 rc = sqlite3Fts3MsrIncrStart(
118129 pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n);
@@ -118349,11 +118587,11 @@
118349 Fts3Expr *pExpr, /* Expression to consider */
118350 Fts3TokenAndCost **ppTC, /* Write new entries to *(*ppTC)++ */
118351 Fts3Expr ***ppOr, /* Write new OR root to *(*ppOr)++ */
118352 int *pRc /* IN/OUT: Error code */
118353 ){
118354 if( *pRc==SQLITE_OK && pExpr ){
118355 if( pExpr->eType==FTSQUERY_PHRASE ){
118356 Fts3Phrase *pPhrase = pExpr->pPhrase;
118357 int i;
118358 for(i=0; *pRc==SQLITE_OK && i<pPhrase->nToken; i++){
118359 Fts3TokenAndCost *pTC = (*ppTC)++;
@@ -118363,10 +118601,15 @@
118363 pTC->pToken = &pPhrase->aToken[i];
118364 pTC->iCol = pPhrase->iColumn;
118365 *pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl);
118366 }
118367 }else if( pExpr->eType!=FTSQUERY_NOT ){
 
 
 
 
 
118368 if( pExpr->eType==FTSQUERY_OR ){
118369 pRoot = pExpr->pLeft;
118370 **ppOr = pRoot;
118371 (*ppOr)++;
118372 }
@@ -118466,10 +118709,19 @@
118466 int nOvfl = 0; /* Total overflow pages used by doclists */
118467 int nToken = 0; /* Total number of tokens in cluster */
118468
118469 int nMinEst = 0; /* The minimum count for any phrase so far. */
118470 int nLoad4 = 1; /* (Phrases that will be loaded)^4. */
 
 
 
 
 
 
 
 
 
118471
118472 /* Count the tokens in this AND/NEAR cluster. If none of the doclists
118473 ** associated with the tokens spill onto overflow pages, or if there is
118474 ** only 1 token, exit early. No tokens to defer in this case. */
118475 for(ii=0; ii<nTC; ii++){
@@ -118529,11 +118781,15 @@
118529 Fts3PhraseToken *pToken = pTC->pToken;
118530 rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol);
118531 fts3SegReaderCursorFree(pToken->pSegcsr);
118532 pToken->pSegcsr = 0;
118533 }else{
118534 nLoad4 = nLoad4*4;
 
 
 
 
118535 if( ii==0 || pTC->pPhrase->nToken>1 ){
118536 /* Either this is the cheapest token in the entire query, or it is
118537 ** part of a multi-token phrase. Either way, the entire doclist will
118538 ** (eventually) be loaded into memory. It may as well be now. */
118539 Fts3PhraseToken *pToken = pTC->pToken;
@@ -119990,10 +120246,11 @@
119990 */
119991 typedef struct ParseContext ParseContext;
119992 struct ParseContext {
119993 sqlite3_tokenizer *pTokenizer; /* Tokenizer module */
119994 const char **azCol; /* Array of column names for fts3 table */
 
119995 int nCol; /* Number of entries in azCol[] */
119996 int iDefaultCol; /* Default column to query */
119997 int isNot; /* True if getNextNode() sees a unary - */
119998 sqlite3_context *pCtx; /* Write error message here */
119999 int nNest; /* Number of nested brackets */
@@ -120077,13 +120334,25 @@
120077
120078 if( iEnd<n && z[iEnd]=='*' ){
120079 pRet->pPhrase->aToken[0].isPrefix = 1;
120080 iEnd++;
120081 }
120082 if( !sqlite3_fts3_enable_parentheses && iStart>0 && z[iStart-1]=='-' ){
120083 pParse->isNot = 1;
 
 
 
 
 
 
 
 
 
 
 
120084 }
 
120085 }
120086 nConsumed = iEnd;
120087 }
120088
120089 pModule->xClose(pCursor);
@@ -120178,10 +120447,11 @@
120178 memcpy(&zTemp[nTemp], zByte, nByte);
120179 nTemp += nByte;
120180
120181 pToken->n = nByte;
120182 pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
 
120183 nToken = ii+1;
120184 }
120185 }
120186
120187 pModule->xClose(pCursor);
@@ -120629,10 +120899,11 @@
120629 ** match any table column.
120630 */
120631 SQLITE_PRIVATE int sqlite3Fts3ExprParse(
120632 sqlite3_tokenizer *pTokenizer, /* Tokenizer module */
120633 char **azCol, /* Array of column names for fts3 table */
 
120634 int nCol, /* Number of entries in azCol[] */
120635 int iDefaultCol, /* Default column to query */
120636 const char *z, int n, /* Text of MATCH query */
120637 Fts3Expr **ppExpr /* OUT: Parsed query structure */
120638 ){
@@ -120642,10 +120913,11 @@
120642 sParse.pTokenizer = pTokenizer;
120643 sParse.azCol = (const char **)azCol;
120644 sParse.nCol = nCol;
120645 sParse.iDefaultCol = iDefaultCol;
120646 sParse.nNest = 0;
 
120647 if( z==0 ){
120648 *ppExpr = 0;
120649 return SQLITE_OK;
120650 }
120651 if( n<0 ){
@@ -120831,11 +121103,11 @@
120831 for(ii=0; ii<nCol; ii++){
120832 azCol[ii] = (char *)sqlite3_value_text(argv[ii+2]);
120833 }
120834
120835 rc = sqlite3Fts3ExprParse(
120836 pTokenizer, azCol, nCol, nCol, zExpr, nExpr, &pExpr
120837 );
120838 if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){
120839 sqlite3_result_error(context, "Error parsing expression", -1);
120840 }else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){
120841 sqlite3_result_error_nomem(context);
@@ -122878,11 +123150,11 @@
122878 /* 2 */ "DELETE FROM %Q.'%q_content'",
122879 /* 3 */ "DELETE FROM %Q.'%q_segments'",
122880 /* 4 */ "DELETE FROM %Q.'%q_segdir'",
122881 /* 5 */ "DELETE FROM %Q.'%q_docsize'",
122882 /* 6 */ "DELETE FROM %Q.'%q_stat'",
122883 /* 7 */ "SELECT %s FROM %Q.'%q_content' AS x WHERE rowid=?",
122884 /* 8 */ "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1",
122885 /* 9 */ "INSERT INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)",
122886 /* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)",
122887 /* 11 */ "INSERT INTO %Q.'%q_segdir' VALUES(?,?,?,?,?,?)",
122888
@@ -122920,11 +123192,11 @@
122920 if( !pStmt ){
122921 char *zSql;
122922 if( eStmt==SQL_CONTENT_INSERT ){
122923 zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
122924 }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
122925 zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist, p->zDb, p->zName);
122926 }else{
122927 zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
122928 }
122929 if( !zSql ){
122930 rc = SQLITE_NOMEM;
@@ -123031,21 +123303,28 @@
123031 ** We try to avoid this because if FTS3 returns any error when committing
123032 ** a transaction, the whole transaction will be rolled back. And this is
123033 ** not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. It can
123034 ** still happen if the user reads data directly from the %_segments or
123035 ** %_segdir tables instead of going through FTS3 though.
 
 
123036 */
123037 SQLITE_PRIVATE int sqlite3Fts3ReadLock(Fts3Table *p){
123038 int rc; /* Return code */
123039 sqlite3_stmt *pStmt; /* Statement used to obtain lock */
123040
123041 rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pStmt, 0);
123042 if( rc==SQLITE_OK ){
123043 sqlite3_bind_null(pStmt, 1);
123044 sqlite3_step(pStmt);
123045 rc = sqlite3_reset(pStmt);
 
 
 
 
123046 }
 
123047 return rc;
123048 }
123049
123050 /*
123051 ** Set *ppStmt to a statement handle that may be used to iterate through
@@ -123401,10 +123680,22 @@
123401 sqlite3_value **apVal, /* Array of values to insert */
123402 sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */
123403 ){
123404 int rc; /* Return code */
123405 sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */
 
 
 
 
 
 
 
 
 
 
 
 
123406
123407 /* Locate the statement handle used to insert data into the %_content
123408 ** table. The SQL for this statement is:
123409 **
123410 ** INSERT INTO %_content VALUES(?, ?, ?, ...)
@@ -123452,18 +123743,20 @@
123452
123453 /*
123454 ** Remove all data from the FTS3 table. Clear the hash table containing
123455 ** pending terms.
123456 */
123457 static int fts3DeleteAll(Fts3Table *p){
123458 int rc = SQLITE_OK; /* Return code */
123459
123460 /* Discard the contents of the pending-terms hash table. */
123461 sqlite3Fts3PendingTermsClear(p);
123462
123463 /* Delete everything from the %_content, %_segments and %_segdir tables. */
123464 fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0);
 
 
123465 fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGMENTS, 0);
123466 fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0);
123467 if( p->bHasDocsize ){
123468 fts3SqlExec(&rc, p, SQL_DELETE_ALL_DOCSIZE, 0);
123469 }
@@ -124747,16 +125040,22 @@
124747 ** error occurs, an SQLite error code is returned.
124748 */
124749 static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
124750 sqlite3_stmt *pStmt;
124751 int rc;
124752 rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
124753 if( rc==SQLITE_OK ){
124754 if( SQLITE_ROW==sqlite3_step(pStmt) ){
124755 *pisEmpty = sqlite3_column_int(pStmt, 0);
 
 
 
 
 
 
 
124756 }
124757 rc = sqlite3_reset(pStmt);
124758 }
124759 return rc;
124760 }
124761
124762 /*
@@ -125104,10 +125403,11 @@
125104 int isIgnoreEmpty = (pCsr->pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY);
125105 int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS);
125106 int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER);
125107 int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX);
125108 int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN);
 
125109
125110 Fts3SegReader **apSegment = pCsr->apSegment;
125111 int nSegment = pCsr->nSegment;
125112 Fts3SegFilter *pFilter = pCsr->pFilter;
125113 int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
@@ -125163,10 +125463,11 @@
125163 }
125164
125165 assert( isIgnoreEmpty || (isRequirePos && !isColFilter) );
125166 if( nMerge==1
125167 && !isIgnoreEmpty
 
125168 && (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0)
125169 ){
125170 pCsr->nDoclist = apSegment[0]->nDoclist;
125171 if( fts3SegReaderIsPending(apSegment[0]) ){
125172 rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist);
@@ -125228,16 +125529,28 @@
125228 if( !aNew ){
125229 return SQLITE_NOMEM;
125230 }
125231 pCsr->aBuffer = aNew;
125232 }
125233 nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
125234 iPrev = iDocid;
125235 if( isRequirePos ){
125236 memcpy(&pCsr->aBuffer[nDoclist], pList, nList);
125237 nDoclist += nList;
125238 pCsr->aBuffer[nDoclist++] = '\0';
 
 
 
 
 
 
 
 
 
 
 
 
125239 }
125240 }
125241
125242 fts3SegReaderSort(apSegment, nMerge, j, xCmp);
125243 }
@@ -125409,13 +125722,13 @@
125409 ** Insert the sizes (in tokens) for each column of the document
125410 ** with docid equal to p->iPrevDocid. The sizes are encoded as
125411 ** a blob of varints.
125412 */
125413 static void fts3InsertDocsize(
125414 int *pRC, /* Result code */
125415 Fts3Table *p, /* Table into which to insert */
125416 u32 *aSz /* Sizes of each column */
125417 ){
125418 char *pBlob; /* The BLOB encoding of the document size */
125419 int nBlob; /* Number of bytes in the BLOB */
125420 sqlite3_stmt *pStmt; /* Statement used to insert the encoding */
125421 int rc; /* Result code from subfunctions */
@@ -125532,10 +125845,90 @@
125532 sqlite3Fts3SegmentsClose(p);
125533 sqlite3Fts3PendingTermsClear(p);
125534
125535 return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc;
125536 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125537
125538 /*
125539 ** Handle a 'special' INSERT of the form:
125540 **
125541 ** "INSERT INTO tbl(tbl) VALUES(<expr>)"
@@ -125550,10 +125943,12 @@
125550
125551 if( !zVal ){
125552 return SQLITE_NOMEM;
125553 }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){
125554 rc = fts3DoOptimize(p, 0);
 
 
125555 #ifdef SQLITE_TEST
125556 }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
125557 p->nNodeSize = atoi(&zVal[9]);
125558 rc = SQLITE_OK;
125559 }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){
@@ -125630,10 +126025,11 @@
125630 pTC->pTokenizer = pT;
125631 rc = pModule->xNext(pTC, &zToken, &nToken, &iDum1, &iDum2, &iPos);
125632 for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){
125633 Fts3PhraseToken *pPT = pDef->pToken;
125634 if( (pDef->iCol>=p->nColumn || pDef->iCol==i)
 
125635 && (pPT->n==nToken || (pPT->isPrefix && pPT->n<nToken))
125636 && (0==memcmp(zToken, pPT->z, pPT->n))
125637 ){
125638 fts3PendingListAppend(&pDef->pList, iDocid, i, iPos, &rc);
125639 }
@@ -125721,18 +126117,22 @@
125721 if( rc==SQLITE_OK ){
125722 if( isEmpty ){
125723 /* Deleting this row means the whole table is empty. In this case
125724 ** delete the contents of all three tables and throw away any
125725 ** data in the pendingTerms hash table. */
125726 rc = fts3DeleteAll(p);
125727 *pnDoc = *pnDoc - 1;
125728 }else{
125729 sqlite3_int64 iRemove = sqlite3_value_int64(pRowid);
125730 rc = fts3PendingTermsDocid(p, iRemove);
125731 fts3DeleteTerms(&rc, p, pRowid, aSzDel);
125732 fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
125733 if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1;
 
 
 
 
125734 if( p->bHasDocsize ){
125735 fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid);
125736 }
125737 }
125738 }
@@ -125788,11 +126188,11 @@
125788 ** should be deleted from the database before inserting the new row. Or,
125789 ** if the on-conflict mode is other than REPLACE, then this method must
125790 ** detect the conflict and return SQLITE_CONSTRAINT before beginning to
125791 ** modify the database file.
125792 */
125793 if( nArg>1 ){
125794 /* Find the value object that holds the new rowid value. */
125795 sqlite3_value *pNewRowid = apVal[3+p->nColumn];
125796 if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
125797 pNewRowid = apVal[1];
125798 }
@@ -125839,11 +126239,13 @@
125839
125840 /* If this is an INSERT or UPDATE operation, insert the new record. */
125841 if( nArg>1 && rc==SQLITE_OK ){
125842 if( bInsertDone==0 ){
125843 rc = fts3InsertData(p, apVal, pRowid);
125844 if( rc==SQLITE_CONSTRAINT ) rc = FTS_CORRUPT_VTAB;
 
 
125845 }
125846 if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){
125847 rc = fts3PendingTermsDocid(p, *pRowid);
125848 }
125849 if( rc==SQLITE_OK ){
@@ -126259,10 +126661,11 @@
126259 pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol);
126260 if( pCsr ){
126261 int iFirst = 0;
126262 pPhrase->pList = pCsr;
126263 fts3GetDeltaPosition(&pCsr, &iFirst);
 
126264 pPhrase->pHead = pCsr;
126265 pPhrase->pTail = pCsr;
126266 pPhrase->iHead = iFirst;
126267 pPhrase->iTail = iFirst;
126268 }else{
@@ -127300,11 +127703,11 @@
127300 }
127301 }
127302
127303 if( !pTerm ){
127304 /* All offsets for this column have been gathered. */
127305 break;
127306 }else{
127307 assert( iCurrent<=iMinPos );
127308 if( 0==(0xFE&*pTerm->pList) ){
127309 pTerm->pList = 0;
127310 }else{
@@ -127317,11 +127720,11 @@
127317 char aBuffer[64];
127318 sqlite3_snprintf(sizeof(aBuffer), aBuffer,
127319 "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart
127320 );
127321 rc = fts3StringAppend(&res, aBuffer, -1);
127322 }else if( rc==SQLITE_DONE ){
127323 rc = FTS_CORRUPT_VTAB;
127324 }
127325 }
127326 }
127327 if( rc==SQLITE_DONE ){
127328
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -656,11 +656,11 @@
656 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
657 ** [sqlite_version()] and [sqlite_source_id()].
658 */
659 #define SQLITE_VERSION "3.7.9"
660 #define SQLITE_VERSION_NUMBER 3007009
661 #define SQLITE_SOURCE_ID "2011-10-20 00:55:54 4344483f7d7f64dffadde0053e6c745948db9486"
662
663 /*
664 ** CAPI3REF: Run-Time Library Version Numbers
665 ** KEYWORDS: sqlite3_version, sqlite3_sourceid
666 **
@@ -1951,12 +1951,12 @@
1951 ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
1952 ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
1953 ** allocator is engaged to handle all of SQLites memory allocation needs.
1954 ** The first pointer (the memory pointer) must be aligned to an 8-byte
1955 ** boundary or subsequent behavior of SQLite will be undefined.
1956 ** The minimum allocation size is capped at 2**12. Reasonable values
1957 ** for the minimum allocation size are 2**5 through 2**8.</dd>
1958 **
1959 ** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
1960 ** <dd> ^(This option takes a single argument which is a pointer to an
1961 ** instance of the [sqlite3_mutex_methods] structure. The argument specifies
1962 ** alternative low-level mutex routines to be used in place
@@ -20969,11 +20969,11 @@
20969 }else if( *z=='+' ){
20970 z+=incr;
20971 }
20972 /* copy digits to exponent */
20973 while( z<zEnd && sqlite3Isdigit(*z) ){
20974 e = e<10000 ? (e*10 + (*z - '0')) : 10000;
20975 z+=incr;
20976 eValid = 1;
20977 }
20978 }
20979
@@ -21020,10 +21020,16 @@
21020 result /= 1.0e+308;
21021 }else{
21022 result = s * scale;
21023 result *= 1.0e+308;
21024 }
21025 }else if( e>=342 ){
21026 if( esign<0 ){
21027 result = 0.0*s;
21028 }else{
21029 result = 1e308*1e308*s; /* Infinity */
21030 }
21031 }else{
21032 /* 1.0e+22 is the largest power of 10 than can be
21033 ** represented exactly. */
21034 while( e%22 ) { scale *= 1.0e+1; e -= 1; }
21035 while( e>0 ) { scale *= 1.0e+22; e -= 22; }
@@ -68962,11 +68968,11 @@
68968
68969 /* Do not allow a transition to journal_mode=WAL for a database
68970 ** in temporary storage or if the VFS does not support shared memory
68971 */
68972 if( u.ch.eNew==PAGER_JOURNALMODE_WAL
68973 && (sqlite3Strlen30(u.ch.zFilename)==0 /* Temp file */
68974 || !sqlite3PagerWalSupported(u.ch.pPager)) /* No shared-memory support */
68975 ){
68976 u.ch.eNew = u.ch.eOld;
68977 }
68978
@@ -69397,14 +69403,19 @@
69403 u.co.pName = &aMem[pOp->p1];
69404 assert( u.co.pVtab->pModule->xRename );
69405 assert( memIsValid(u.co.pName) );
69406 REGISTER_TRACE(pOp->p1, u.co.pName);
69407 assert( u.co.pName->flags & MEM_Str );
69408 testcase( u.co.pName->enc==SQLITE_UTF8 );
69409 testcase( u.co.pName->enc==SQLITE_UTF16BE );
69410 testcase( u.co.pName->enc==SQLITE_UTF16LE );
69411 rc = sqlite3VdbeChangeEncoding(u.co.pName, SQLITE_UTF8);
69412 if( rc==SQLITE_OK ){
69413 rc = u.co.pVtab->pModule->xRename(u.co.pVtab, u.co.pName->z);
69414 importVtabErrMsg(p, u.co.pVtab);
69415 p->expired = 0;
69416 }
69417 break;
69418 }
69419 #endif
69420
69421 #ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -71758,10 +71769,28 @@
71769 ExprSetProperty(pExpr, EP_Static);
71770 sqlite3ExprDelete(db, pExpr);
71771 memcpy(pExpr, pDup, sizeof(*pExpr));
71772 sqlite3DbFree(db, pDup);
71773 }
71774
71775
71776 /*
71777 ** Return TRUE if the name zCol occurs anywhere in the USING clause.
71778 **
71779 ** Return FALSE if the USING clause is NULL or if it does not contain
71780 ** zCol.
71781 */
71782 static int nameInUsingClause(IdList *pUsing, const char *zCol){
71783 if( pUsing ){
71784 int k;
71785 for(k=0; k<pUsing->nId; k++){
71786 if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1;
71787 }
71788 }
71789 return 0;
71790 }
71791
71792
71793 /*
71794 ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
71795 ** that name in the set of source tables in pSrcList and make the pExpr
71796 ** expression node refer back to that source column. The following changes
@@ -71850,38 +71879,25 @@
71879 pSchema = pTab->pSchema;
71880 pMatch = pItem;
71881 }
71882 for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){
71883 if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
71884 /* If there has been exactly one prior match and this match
71885 ** is for the right-hand table of a NATURAL JOIN or is in a
71886 ** USING clause, then skip this match.
71887 */
71888 if( cnt==1 ){
71889 if( pItem->jointype & JT_NATURAL ) continue;
71890 if( nameInUsingClause(pItem->pUsing, zCol) ) continue;
71891 }
71892 cnt++;
71893 pExpr->iTable = pItem->iCursor;
71894 pExpr->pTab = pTab;
71895 pMatch = pItem;
71896 pSchema = pTab->pSchema;
71897 /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
71898 pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71899 break;
71900 }
71901 }
71902 }
71903 }
@@ -102501,10 +102517,11 @@
102517 tempWC.pParse = pWC->pParse;
102518 tempWC.pMaskSet = pWC->pMaskSet;
102519 tempWC.pOuter = pWC;
102520 tempWC.op = TK_AND;
102521 tempWC.a = pOrTerm;
102522 tempWC.wctrlFlags = 0;
102523 tempWC.nTerm = 1;
102524 bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost);
102525 }else{
102526 continue;
102527 }
@@ -114528,10 +114545,11 @@
114545 const char *zDb; /* logical database name */
114546 const char *zName; /* virtual table name */
114547 int nColumn; /* number of named columns in virtual table */
114548 char **azColumn; /* column names. malloced */
114549 sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */
114550 char *zContentTbl; /* content=xxx option, or NULL */
114551
114552 /* Precompiled statements used by the implementation. Each of these
114553 ** statements is run and reset within a single virtual table API call.
114554 */
114555 sqlite3_stmt *aStmt[27];
@@ -114568,11 +114586,11 @@
114586 } *aIndex;
114587 int nMaxPendingData; /* Max pending data before flush to disk */
114588 int nPendingData; /* Current bytes of pending data */
114589 sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */
114590
114591 #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
114592 /* State variables used for validating that the transaction control
114593 ** methods of the virtual table are called at appropriate times. These
114594 ** values do not contribution to the FTS computation; they are used for
114595 ** verifying the SQLite core.
114596 */
@@ -114653,10 +114671,11 @@
114671 */
114672 struct Fts3PhraseToken {
114673 char *z; /* Text of the token */
114674 int n; /* Number of bytes in buffer z */
114675 int isPrefix; /* True if token ends with a "*" character */
114676 int bFirst; /* True if token must appear at position 0 */
114677
114678 /* Variables above this point are populated when the expression is
114679 ** parsed (by code in fts3_expr.c). Below this point the variables are
114680 ** used when evaluating the expression. */
114681 Fts3DeferredToken *pDeferred; /* Deferred token object for this token */
@@ -114771,10 +114790,11 @@
114790 #define FTS3_SEGMENT_REQUIRE_POS 0x00000001
114791 #define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002
114792 #define FTS3_SEGMENT_COLUMN_FILTER 0x00000004
114793 #define FTS3_SEGMENT_PREFIX 0x00000008
114794 #define FTS3_SEGMENT_SCAN 0x00000010
114795 #define FTS3_SEGMENT_FIRST 0x00000020
114796
114797 /* Type passed as 4th argument to SegmentReaderIterate() */
114798 struct Fts3SegFilter {
114799 const char *zTerm;
114800 int nTerm;
@@ -114810,12 +114830,12 @@
114830 SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
114831 SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *);
114832 SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64);
114833 SQLITE_PRIVATE void sqlite3Fts3Dequote(char *);
114834 SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
 
114835 SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *);
114836 SQLITE_PRIVATE int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *);
114837
114838 /* fts3_tokenizer.c */
114839 SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *, int *);
114840 SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *);
114841 SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *,
@@ -114830,11 +114850,11 @@
114850 );
114851 SQLITE_PRIVATE void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *);
114852
114853 /* fts3_expr.c */
114854 SQLITE_PRIVATE int sqlite3Fts3ExprParse(sqlite3_tokenizer *,
114855 char **, int, int, int, const char *, int, Fts3Expr **
114856 );
114857 SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *);
114858 #ifdef SQLITE_TEST
114859 SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db);
114860 SQLITE_PRIVATE int sqlite3Fts3InitTerm(sqlite3 *db);
@@ -115031,10 +115051,11 @@
115051 sqlite3_finalize(p->aStmt[i]);
115052 }
115053 sqlite3_free(p->zSegmentsTbl);
115054 sqlite3_free(p->zReadExprlist);
115055 sqlite3_free(p->zWriteExprlist);
115056 sqlite3_free(p->zContentTbl);
115057
115058 /* Invoke the tokenizer destructor to free the tokenizer. */
115059 p->pTokenizer->pModule->xDestroy(p->pTokenizer);
115060
115061 sqlite3_free(p);
@@ -115070,20 +115091,23 @@
115091
115092 /*
115093 ** The xDestroy() virtual table method.
115094 */
115095 static int fts3DestroyMethod(sqlite3_vtab *pVtab){
 
115096 Fts3Table *p = (Fts3Table *)pVtab;
115097 int rc = SQLITE_OK; /* Return code */
115098 const char *zDb = p->zDb; /* Name of database (e.g. "main", "temp") */
115099 sqlite3 *db = p->db; /* Database handle */
115100
115101 /* Drop the shadow tables */
115102 if( p->zContentTbl==0 ){
115103 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", zDb, p->zName);
115104 }
115105 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", zDb,p->zName);
115106 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", zDb, p->zName);
115107 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", zDb, p->zName);
115108 fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", zDb, p->zName);
115109
115110 /* If everything has worked, invoke fts3DisconnectMethod() to free the
115111 ** memory associated with the Fts3Table structure and return SQLITE_OK.
115112 ** Otherwise, return an SQLite error code.
115113 */
@@ -115141,27 +115165,31 @@
115165 ** %_stat tables required by FTS4.
115166 */
115167 static int fts3CreateTables(Fts3Table *p){
115168 int rc = SQLITE_OK; /* Return code */
115169 int i; /* Iterator variable */
 
115170 sqlite3 *db = p->db; /* The database connection */
115171
115172 if( p->zContentTbl==0 ){
115173 char *zContentCols; /* Columns of %_content table */
115174
115175 /* Create a list of user columns for the content table */
115176 zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY");
115177 for(i=0; zContentCols && i<p->nColumn; i++){
115178 char *z = p->azColumn[i];
115179 zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
115180 }
115181 if( zContentCols==0 ) rc = SQLITE_NOMEM;
115182
115183 /* Create the content table */
115184 fts3DbExec(&rc, db,
115185 "CREATE TABLE %Q.'%q_content'(%s)",
115186 p->zDb, p->zName, zContentCols
115187 );
115188 sqlite3_free(zContentCols);
115189 }
115190
115191 /* Create other tables */
115192 fts3DbExec(&rc, db,
115193 "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);",
115194 p->zDb, p->zName
115195 );
@@ -115308,12 +115336,12 @@
115336 }
115337 return zRet;
115338 }
115339
115340 /*
115341 ** Return a list of comma separated SQL expressions and a FROM clause that
115342 ** could be used in a SELECT statement such as the following:
115343 **
115344 ** SELECT <list of expressions> FROM %_content AS x ...
115345 **
115346 ** to return the docid, followed by each column of text data in order
115347 ** from left to write. If parameter zFunc is not NULL, then instead of
@@ -115320,11 +115348,11 @@
115348 ** being returned directly each column of text data is passed to an SQL
115349 ** function named zFunc first. For example, if zFunc is "unzip" and the
115350 ** table has the three user-defined columns "a", "b", and "c", the following
115351 ** string is returned:
115352 **
115353 ** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c') FROM %_content AS x"
115354 **
115355 ** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
115356 ** is the responsibility of the caller to eventually free it.
115357 **
115358 ** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and
@@ -115336,20 +115364,32 @@
115364 char *zRet = 0;
115365 char *zFree = 0;
115366 char *zFunction;
115367 int i;
115368
115369 if( p->zContentTbl==0 ){
115370 if( !zFunc ){
115371 zFunction = "";
115372 }else{
115373 zFree = zFunction = fts3QuoteId(zFunc);
115374 }
115375 fts3Appendf(pRc, &zRet, "docid");
115376 for(i=0; i<p->nColumn; i++){
115377 fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]);
115378 }
115379 sqlite3_free(zFree);
115380 }else{
115381 fts3Appendf(pRc, &zRet, "rowid");
115382 for(i=0; i<p->nColumn; i++){
115383 fts3Appendf(pRc, &zRet, ", x.'%q'", p->azColumn[i]);
115384 }
115385 }
115386 fts3Appendf(pRc, &zRet, "FROM '%q'.'%q%s' AS x",
115387 p->zDb,
115388 (p->zContentTbl ? p->zContentTbl : p->zName),
115389 (p->zContentTbl ? "" : "_content")
115390 );
115391 return zRet;
115392 }
115393
115394 /*
115395 ** Return a list of N comma separated question marks, where N is the number
@@ -115468,10 +115508,95 @@
115508 }
115509 }
115510
115511 return SQLITE_OK;
115512 }
115513
115514 /*
115515 ** This function is called when initializing an FTS4 table that uses the
115516 ** content=xxx option. It determines the number of and names of the columns
115517 ** of the new FTS4 table.
115518 **
115519 ** The third argument passed to this function is the value passed to the
115520 ** config=xxx option (i.e. "xxx"). This function queries the database for
115521 ** a table of that name. If found, the output variables are populated
115522 ** as follows:
115523 **
115524 ** *pnCol: Set to the number of columns table xxx has,
115525 **
115526 ** *pnStr: Set to the total amount of space required to store a copy
115527 ** of each columns name, including the nul-terminator.
115528 **
115529 ** *pazCol: Set to point to an array of *pnCol strings. Each string is
115530 ** the name of the corresponding column in table xxx. The array
115531 ** and its contents are allocated using a single allocation. It
115532 ** is the responsibility of the caller to free this allocation
115533 ** by eventually passing the *pazCol value to sqlite3_free().
115534 **
115535 ** If the table cannot be found, an error code is returned and the output
115536 ** variables are undefined. Or, if an OOM is encountered, SQLITE_NOMEM is
115537 ** returned (and the output variables are undefined).
115538 */
115539 static int fts3ContentColumns(
115540 sqlite3 *db, /* Database handle */
115541 const char *zDb, /* Name of db (i.e. "main", "temp" etc.) */
115542 const char *zTbl, /* Name of content table */
115543 const char ***pazCol, /* OUT: Malloc'd array of column names */
115544 int *pnCol, /* OUT: Size of array *pazCol */
115545 int *pnStr /* OUT: Bytes of string content */
115546 ){
115547 int rc = SQLITE_OK; /* Return code */
115548 char *zSql; /* "SELECT *" statement on zTbl */
115549 sqlite3_stmt *pStmt = 0; /* Compiled version of zSql */
115550
115551 zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl);
115552 if( !zSql ){
115553 rc = SQLITE_NOMEM;
115554 }else{
115555 rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
115556 }
115557 sqlite3_free(zSql);
115558
115559 if( rc==SQLITE_OK ){
115560 const char **azCol; /* Output array */
115561 int nStr = 0; /* Size of all column names (incl. 0x00) */
115562 int nCol; /* Number of table columns */
115563 int i; /* Used to iterate through columns */
115564
115565 /* Loop through the returned columns. Set nStr to the number of bytes of
115566 ** space required to store a copy of each column name, including the
115567 ** nul-terminator byte. */
115568 nCol = sqlite3_column_count(pStmt);
115569 for(i=0; i<nCol; i++){
115570 const char *zCol = sqlite3_column_name(pStmt, i);
115571 nStr += strlen(zCol) + 1;
115572 }
115573
115574 /* Allocate and populate the array to return. */
115575 azCol = (const char **)sqlite3_malloc(sizeof(char *) * nCol + nStr);
115576 if( azCol==0 ){
115577 rc = SQLITE_NOMEM;
115578 }else{
115579 char *p = (char *)&azCol[nCol];
115580 for(i=0; i<nCol; i++){
115581 const char *zCol = sqlite3_column_name(pStmt, i);
115582 int n = strlen(zCol)+1;
115583 memcpy(p, zCol, n);
115584 azCol[i] = p;
115585 p += n;
115586 }
115587 }
115588 sqlite3_finalize(pStmt);
115589
115590 /* Set the output variables. */
115591 *pnCol = nCol;
115592 *pnStr = nStr;
115593 *pazCol = azCol;
115594 }
115595
115596 return rc;
115597 }
115598
115599 /*
115600 ** This function is the implementation of both the xConnect and xCreate
115601 ** methods of the FTS3 virtual table.
115602 **
@@ -115513,10 +115638,11 @@
115638 int bNoDocsize = 0; /* True to omit %_docsize table */
115639 int bDescIdx = 0; /* True to store descending indexes */
115640 char *zPrefix = 0; /* Prefix parameter value (or NULL) */
115641 char *zCompress = 0; /* compress=? parameter (or NULL) */
115642 char *zUncompress = 0; /* uncompress=? parameter (or NULL) */
115643 char *zContent = 0; /* content=? parameter (or NULL) */
115644
115645 assert( strlen(argv[0])==4 );
115646 assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
115647 || (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4)
115648 );
@@ -115556,17 +115682,17 @@
115682 /* Check if it is an FTS4 special argument. */
115683 else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){
115684 struct Fts4Option {
115685 const char *zOpt;
115686 int nOpt;
 
115687 } aFts4Opt[] = {
115688 { "matchinfo", 9 }, /* 0 -> MATCHINFO */
115689 { "prefix", 6 }, /* 1 -> PREFIX */
115690 { "compress", 8 }, /* 2 -> COMPRESS */
115691 { "uncompress", 10 }, /* 3 -> UNCOMPRESS */
115692 { "order", 5 }, /* 4 -> ORDER */
115693 { "content", 7 } /* 5 -> CONTENT */
115694 };
115695
115696 int iOpt;
115697 if( !zVal ){
115698 rc = SQLITE_NOMEM;
@@ -115608,17 +115734,24 @@
115734 zVal = 0;
115735 break;
115736
115737 case 4: /* ORDER */
115738 if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3))
115739 && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4))
115740 ){
115741 *pzErr = sqlite3_mprintf("unrecognized order: %s", zVal);
115742 rc = SQLITE_ERROR;
115743 }
115744 bDescIdx = (zVal[0]=='d' || zVal[0]=='D');
115745 break;
115746
115747 default: /* CONTENT */
115748 assert( iOpt==5 );
115749 sqlite3_free(zUncompress);
115750 zContent = zVal;
115751 zVal = 0;
115752 break;
115753 }
115754 }
115755 sqlite3_free(zVal);
115756 }
115757 }
@@ -115627,10 +115760,30 @@
115760 else {
115761 nString += (int)(strlen(z) + 1);
115762 aCol[nCol++] = z;
115763 }
115764 }
115765
115766 /* If a content=xxx option was specified, the following:
115767 **
115768 ** 1. Ignore any compress= and uncompress= options.
115769 **
115770 ** 2. If no column names were specified as part of the CREATE VIRTUAL
115771 ** TABLE statement, use all columns from the content table.
115772 */
115773 if( rc==SQLITE_OK && zContent ){
115774 sqlite3_free(zCompress);
115775 sqlite3_free(zUncompress);
115776 zCompress = 0;
115777 zUncompress = 0;
115778 if( nCol==0 ){
115779 sqlite3_free((void*)aCol);
115780 aCol = 0;
115781 rc = fts3ContentColumns(db, argv[1], zContent, &aCol, &nCol, &nString);
115782 }
115783 assert( rc!=SQLITE_OK || nCol>0 );
115784 }
115785 if( rc!=SQLITE_OK ) goto fts3_init_out;
115786
115787 if( nCol==0 ){
115788 assert( nString==0 );
115789 aCol[0] = "content";
@@ -115671,10 +115824,12 @@
115824 p->pTokenizer = pTokenizer;
115825 p->nMaxPendingData = FTS3_MAX_PENDING_DATA;
115826 p->bHasDocsize = (isFts4 && bNoDocsize==0);
115827 p->bHasStat = isFts4;
115828 p->bDescIdx = bDescIdx;
115829 p->zContentTbl = zContent;
115830 zContent = 0;
115831 TESTONLY( p->inTransaction = -1 );
115832 TESTONLY( p->mxSavepoint = -1 );
115833
115834 p->aIndex = (struct Fts3Index *)&p->azColumn[nCol];
115835 memcpy(p->aIndex, aIndex, sizeof(struct Fts3Index) * nIndex);
@@ -115732,10 +115887,11 @@
115887 fts3_init_out:
115888 sqlite3_free(zPrefix);
115889 sqlite3_free(aIndex);
115890 sqlite3_free(zCompress);
115891 sqlite3_free(zUncompress);
115892 sqlite3_free(zContent);
115893 sqlite3_free((void *)aCol);
115894 if( rc!=SQLITE_OK ){
115895 if( p ){
115896 fts3DisconnectMethod((sqlite3_vtab *)p);
115897 }else if( pTokenizer ){
@@ -115882,40 +116038,69 @@
116038 sqlite3_free(pCsr->aMatchinfo);
116039 assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
116040 sqlite3_free(pCsr);
116041 return SQLITE_OK;
116042 }
116043
116044 /*
116045 ** If pCsr->pStmt has not been prepared (i.e. if pCsr->pStmt==0), then
116046 ** compose and prepare an SQL statement of the form:
116047 **
116048 ** "SELECT <columns> FROM %_content WHERE rowid = ?"
116049 **
116050 ** (or the equivalent for a content=xxx table) and set pCsr->pStmt to
116051 ** it. If an error occurs, return an SQLite error code.
116052 **
116053 ** Otherwise, set *ppStmt to point to pCsr->pStmt and return SQLITE_OK.
116054 */
116055 static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){
116056 int rc = SQLITE_OK;
116057 if( pCsr->pStmt==0 ){
116058 Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
116059 char *zSql;
116060 zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist);
116061 if( !zSql ) return SQLITE_NOMEM;
116062 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
116063 sqlite3_free(zSql);
116064 }
116065 *ppStmt = pCsr->pStmt;
116066 return rc;
116067 }
116068
116069 /*
116070 ** Position the pCsr->pStmt statement so that it is on the row
116071 ** of the %_content table that contains the last match. Return
116072 ** SQLITE_OK on success.
116073 */
116074 static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){
116075 int rc = SQLITE_OK;
116076 if( pCsr->isRequireSeek ){
116077 sqlite3_stmt *pStmt = 0;
116078
116079 rc = fts3CursorSeekStmt(pCsr, &pStmt);
116080 if( rc==SQLITE_OK ){
116081 sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId);
116082 pCsr->isRequireSeek = 0;
116083 if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){
116084 return SQLITE_OK;
116085 }else{
116086 rc = sqlite3_reset(pCsr->pStmt);
116087 if( rc==SQLITE_OK && ((Fts3Table *)pCsr->base.pVtab)->zContentTbl==0 ){
116088 /* If no row was found and no error has occured, then the %_content
116089 ** table is missing a row that is present in the full-text index.
116090 ** The data structures are corrupt. */
116091 rc = FTS_CORRUPT_VTAB;
116092 pCsr->isEof = 1;
116093 }
116094 }
116095 }
116096 }
116097
116098 if( rc!=SQLITE_OK && pContext ){
116099 sqlite3_result_error_code(pContext, rc);
116100 }
116101 return rc;
116102 }
116103
116104 /*
116105 ** This function is used to process a single interior node when searching
116106 ** a b-tree for a term or term prefix. The node data is passed to this
@@ -116351,20 +116536,20 @@
116536 int isSaveLeft, /* Save the left position */
116537 int isExact, /* If *pp1 is exactly nTokens before *pp2 */
116538 char **pp1, /* IN/OUT: Left input list */
116539 char **pp2 /* IN/OUT: Right input list */
116540 ){
116541 char *p = *pp;
116542 char *p1 = *pp1;
116543 char *p2 = *pp2;
116544 int iCol1 = 0;
116545 int iCol2 = 0;
116546
116547 /* Never set both isSaveLeft and isExact for the same invocation. */
116548 assert( isSaveLeft==0 || isExact==0 );
116549
116550 assert( p!=0 && *p1!=0 && *p2!=0 );
116551 if( *p1==POS_COLUMN ){
116552 p1++;
116553 p1 += sqlite3Fts3GetVarint32(p1, &iCol1);
116554 }
116555 if( *p2==POS_COLUMN ){
@@ -116377,11 +116562,11 @@
116562 char *pSave = p;
116563 sqlite3_int64 iPrev = 0;
116564 sqlite3_int64 iPos1 = 0;
116565 sqlite3_int64 iPos2 = 0;
116566
116567 if( iCol1 ){
116568 *p++ = POS_COLUMN;
116569 p += sqlite3Fts3PutVarint(p, iCol1);
116570 }
116571
116572 assert( *p1!=POS_END && *p1!=POS_COLUMN );
@@ -116392,20 +116577,14 @@
116577 while( 1 ){
116578 if( iPos2==iPos1+nToken
116579 || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken)
116580 ){
116581 sqlite3_int64 iSave;
 
 
 
 
 
 
 
116582 iSave = isSaveLeft ? iPos1 : iPos2;
116583 fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2;
116584 pSave = 0;
116585 assert( p );
116586 }
116587 if( (!isSaveLeft && iPos2<=(iPos1+nToken)) || iPos2<=iPos1 ){
116588 if( (*p2&0xFE)==0 ) break;
116589 fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2;
116590 }else{
@@ -116450,11 +116629,11 @@
116629
116630 fts3PoslistCopy(0, &p2);
116631 fts3PoslistCopy(0, &p1);
116632 *pp1 = p1;
116633 *pp2 = p2;
116634 if( *pp==p ){
116635 return 0;
116636 }
116637 *p++ = 0x00;
116638 *pp = p;
116639 return 1;
@@ -116752,10 +116931,60 @@
116931 }
116932 }
116933
116934 *pnRight = p - aOut;
116935 }
116936
116937 /*
116938 ** Argument pList points to a position list nList bytes in size. This
116939 ** function checks to see if the position list contains any entries for
116940 ** a token in position 0 (of any column). If so, it writes argument iDelta
116941 ** to the output buffer pOut, followed by a position list consisting only
116942 ** of the entries from pList at position 0, and terminated by an 0x00 byte.
116943 ** The value returned is the number of bytes written to pOut (if any).
116944 */
116945 SQLITE_PRIVATE int sqlite3Fts3FirstFilter(
116946 sqlite3_int64 iDelta, /* Varint that may be written to pOut */
116947 char *pList, /* Position list (no 0x00 term) */
116948 int nList, /* Size of pList in bytes */
116949 char *pOut /* Write output here */
116950 ){
116951 int nOut = 0;
116952 int bWritten = 0; /* True once iDelta has been written */
116953 char *p = pList;
116954 char *pEnd = &pList[nList];
116955
116956 if( *p!=0x01 ){
116957 if( *p==0x02 ){
116958 nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta);
116959 pOut[nOut++] = 0x02;
116960 bWritten = 1;
116961 }
116962 fts3ColumnlistCopy(0, &p);
116963 }
116964
116965 while( p<pEnd && *p==0x01 ){
116966 sqlite3_int64 iCol;
116967 p++;
116968 p += sqlite3Fts3GetVarint(p, &iCol);
116969 if( *p==0x02 ){
116970 if( bWritten==0 ){
116971 nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta);
116972 bWritten = 1;
116973 }
116974 pOut[nOut++] = 0x01;
116975 nOut += sqlite3Fts3PutVarint(&pOut[nOut], iCol);
116976 pOut[nOut++] = 0x02;
116977 }
116978 fts3ColumnlistCopy(0, &p);
116979 }
116980 if( bWritten ){
116981 pOut[nOut++] = 0x00;
116982 }
116983
116984 return nOut;
116985 }
116986
116987
116988 /*
116989 ** Merge all doclists in the TermSelect.aaOutput[] array into a single
116990 ** doclist stored in TermSelect.aaOutput[0]. If successful, delete all
@@ -117109,10 +117338,11 @@
117338 pSegcsr = pTok->pSegcsr;
117339 memset(&tsc, 0, sizeof(TermSelect));
117340
117341 filter.flags = FTS3_SEGMENT_IGNORE_EMPTY | FTS3_SEGMENT_REQUIRE_POS
117342 | (pTok->isPrefix ? FTS3_SEGMENT_PREFIX : 0)
117343 | (pTok->bFirst ? FTS3_SEGMENT_FIRST : 0)
117344 | (iColumn<p->nColumn ? FTS3_SEGMENT_COLUMN_FILTER : 0);
117345 filter.iCol = iColumn;
117346 filter.zTerm = pTok->z;
117347 filter.nTerm = pTok->n;
117348
@@ -117249,12 +117479,12 @@
117479
117480 if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
117481 return SQLITE_NOMEM;
117482 }
117483
117484 rc = sqlite3Fts3ExprParse(p->pTokenizer, p->azColumn, p->bHasStat,
117485 p->nColumn, iCol, zQuery, -1, &pCsr->pExpr
117486 );
117487 if( rc!=SQLITE_OK ){
117488 if( rc==SQLITE_ERROR ){
117489 static const char *zErr = "malformed MATCH expression: [%s]";
117490 p->base.zErrMsg = sqlite3_mprintf(zErr, zQuery);
@@ -117277,26 +117507,27 @@
117507 ** statement loops through all rows of the %_content table. For a
117508 ** full-text query or docid lookup, the statement retrieves a single
117509 ** row by docid.
117510 */
117511 if( idxNum==FTS3_FULLSCAN_SEARCH ){
117512 zSql = sqlite3_mprintf(
117513 "SELECT %s ORDER BY rowid %s",
117514 p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC")
117515 );
117516 if( zSql ){
117517 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
117518 sqlite3_free(zSql);
117519 }else{
117520 rc = SQLITE_NOMEM;
117521 }
117522 }else if( idxNum==FTS3_DOCID_SEARCH ){
117523 rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt);
117524 if( rc==SQLITE_OK ){
117525 rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
117526 }
117527 }
117528 if( rc!=SQLITE_OK ) return rc;
117529
117530 return fts3NextMethod(pCursor);
117531 }
117532
117533 /*
@@ -117345,11 +117576,11 @@
117576 ** Return a blob which is a pointer to the cursor.
117577 */
117578 sqlite3_result_blob(pContext, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT);
117579 }else{
117580 rc = fts3CursorSeek(0, pCsr);
117581 if( rc==SQLITE_OK && sqlite3_data_count(pCsr->pStmt)>(iCol+1) ){
117582 sqlite3_result_value(pContext, sqlite3_column_value(pCsr->pStmt, iCol+1));
117583 }
117584 }
117585
117586 assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
@@ -117638,19 +117869,26 @@
117869 ){
117870 Fts3Table *p = (Fts3Table *)pVtab;
117871 sqlite3 *db = p->db; /* Database connection */
117872 int rc; /* Return Code */
117873
117874 /* As it happens, the pending terms table is always empty here. This is
117875 ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction
117876 ** always opens a savepoint transaction. And the xSavepoint() method
117877 ** flushes the pending terms table. But leave the (no-op) call to
117878 ** PendingTermsFlush() in in case that changes.
117879 */
117880 assert( p->nPendingData==0 );
117881 rc = sqlite3Fts3PendingTermsFlush(p);
117882
117883 if( p->zContentTbl==0 ){
117884 fts3DbExec(&rc, db,
117885 "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';",
117886 p->zDb, p->zName, zName
117887 );
117888 }
117889
 
 
 
 
117890 if( p->bHasDocsize ){
117891 fts3DbExec(&rc, db,
117892 "ALTER TABLE %Q.'%q_docsize' RENAME TO '%q_docsize';",
117893 p->zDb, p->zName, zName
117894 );
@@ -118005,25 +118243,24 @@
118243 **
118244 ** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code.
118245 */
118246 static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
118247 int iToken; /* Used to iterate through phrase tokens */
 
118248 char *aPoslist = 0; /* Position list for deferred tokens */
118249 int nPoslist = 0; /* Number of bytes in aPoslist */
118250 int iPrev = -1; /* Token number of previous deferred token */
118251
118252 assert( pPhrase->doclist.bFreeList==0 );
118253
118254 for(iToken=0; iToken<pPhrase->nToken; iToken++){
118255 Fts3PhraseToken *pToken = &pPhrase->aToken[iToken];
118256 Fts3DeferredToken *pDeferred = pToken->pDeferred;
118257
118258 if( pDeferred ){
118259 char *pList;
118260 int nList;
118261 int rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList);
118262 if( rc!=SQLITE_OK ) return rc;
118263
118264 if( pList==0 ){
118265 sqlite3_free(aPoslist);
118266 pPhrase->doclist.pList = 0;
@@ -118120,10 +118357,11 @@
118357 if( pCsr->bDesc==pTab->bDescIdx
118358 && bOptOk==1
118359 && p->nToken==1
118360 && pFirst->pSegcsr
118361 && pFirst->pSegcsr->bLookup
118362 && pFirst->bFirst==0
118363 ){
118364 /* Use the incremental approach. */
118365 int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn);
118366 rc = sqlite3Fts3MsrIncrStart(
118367 pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n);
@@ -118349,11 +118587,11 @@
118587 Fts3Expr *pExpr, /* Expression to consider */
118588 Fts3TokenAndCost **ppTC, /* Write new entries to *(*ppTC)++ */
118589 Fts3Expr ***ppOr, /* Write new OR root to *(*ppOr)++ */
118590 int *pRc /* IN/OUT: Error code */
118591 ){
118592 if( *pRc==SQLITE_OK ){
118593 if( pExpr->eType==FTSQUERY_PHRASE ){
118594 Fts3Phrase *pPhrase = pExpr->pPhrase;
118595 int i;
118596 for(i=0; *pRc==SQLITE_OK && i<pPhrase->nToken; i++){
118597 Fts3TokenAndCost *pTC = (*ppTC)++;
@@ -118363,10 +118601,15 @@
118601 pTC->pToken = &pPhrase->aToken[i];
118602 pTC->iCol = pPhrase->iColumn;
118603 *pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl);
118604 }
118605 }else if( pExpr->eType!=FTSQUERY_NOT ){
118606 assert( pExpr->eType==FTSQUERY_OR
118607 || pExpr->eType==FTSQUERY_AND
118608 || pExpr->eType==FTSQUERY_NEAR
118609 );
118610 assert( pExpr->pLeft && pExpr->pRight );
118611 if( pExpr->eType==FTSQUERY_OR ){
118612 pRoot = pExpr->pLeft;
118613 **ppOr = pRoot;
118614 (*ppOr)++;
118615 }
@@ -118466,10 +118709,19 @@
118709 int nOvfl = 0; /* Total overflow pages used by doclists */
118710 int nToken = 0; /* Total number of tokens in cluster */
118711
118712 int nMinEst = 0; /* The minimum count for any phrase so far. */
118713 int nLoad4 = 1; /* (Phrases that will be loaded)^4. */
118714
118715 /* Tokens are never deferred for FTS tables created using the content=xxx
118716 ** option. The reason being that it is not guaranteed that the content
118717 ** table actually contains the same data as the index. To prevent this from
118718 ** causing any problems, the deferred token optimization is completely
118719 ** disabled for content=xxx tables. */
118720 if( pTab->zContentTbl ){
118721 return SQLITE_OK;
118722 }
118723
118724 /* Count the tokens in this AND/NEAR cluster. If none of the doclists
118725 ** associated with the tokens spill onto overflow pages, or if there is
118726 ** only 1 token, exit early. No tokens to defer in this case. */
118727 for(ii=0; ii<nTC; ii++){
@@ -118529,11 +118781,15 @@
118781 Fts3PhraseToken *pToken = pTC->pToken;
118782 rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol);
118783 fts3SegReaderCursorFree(pToken->pSegcsr);
118784 pToken->pSegcsr = 0;
118785 }else{
118786 /* Set nLoad4 to the value of (4^nOther) for the next iteration of the
118787 ** for-loop. Except, limit the value to 2^24 to prevent it from
118788 ** overflowing the 32-bit integer it is stored in. */
118789 if( ii<12 ) nLoad4 = nLoad4*4;
118790
118791 if( ii==0 || pTC->pPhrase->nToken>1 ){
118792 /* Either this is the cheapest token in the entire query, or it is
118793 ** part of a multi-token phrase. Either way, the entire doclist will
118794 ** (eventually) be loaded into memory. It may as well be now. */
118795 Fts3PhraseToken *pToken = pTC->pToken;
@@ -119990,10 +120246,11 @@
120246 */
120247 typedef struct ParseContext ParseContext;
120248 struct ParseContext {
120249 sqlite3_tokenizer *pTokenizer; /* Tokenizer module */
120250 const char **azCol; /* Array of column names for fts3 table */
120251 int bFts4; /* True to allow FTS4-only syntax */
120252 int nCol; /* Number of entries in azCol[] */
120253 int iDefaultCol; /* Default column to query */
120254 int isNot; /* True if getNextNode() sees a unary - */
120255 sqlite3_context *pCtx; /* Write error message here */
120256 int nNest; /* Number of nested brackets */
@@ -120077,13 +120334,25 @@
120334
120335 if( iEnd<n && z[iEnd]=='*' ){
120336 pRet->pPhrase->aToken[0].isPrefix = 1;
120337 iEnd++;
120338 }
120339
120340 while( 1 ){
120341 if( !sqlite3_fts3_enable_parentheses
120342 && iStart>0 && z[iStart-1]=='-'
120343 ){
120344 pParse->isNot = 1;
120345 iStart--;
120346 }else if( pParse->bFts4 && iStart>0 && z[iStart-1]=='^' ){
120347 pRet->pPhrase->aToken[0].bFirst = 1;
120348 iStart--;
120349 }else{
120350 break;
120351 }
120352 }
120353
120354 }
120355 nConsumed = iEnd;
120356 }
120357
120358 pModule->xClose(pCursor);
@@ -120178,10 +120447,11 @@
120447 memcpy(&zTemp[nTemp], zByte, nByte);
120448 nTemp += nByte;
120449
120450 pToken->n = nByte;
120451 pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
120452 pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^');
120453 nToken = ii+1;
120454 }
120455 }
120456
120457 pModule->xClose(pCursor);
@@ -120629,10 +120899,11 @@
120899 ** match any table column.
120900 */
120901 SQLITE_PRIVATE int sqlite3Fts3ExprParse(
120902 sqlite3_tokenizer *pTokenizer, /* Tokenizer module */
120903 char **azCol, /* Array of column names for fts3 table */
120904 int bFts4, /* True to allow FTS4-only syntax */
120905 int nCol, /* Number of entries in azCol[] */
120906 int iDefaultCol, /* Default column to query */
120907 const char *z, int n, /* Text of MATCH query */
120908 Fts3Expr **ppExpr /* OUT: Parsed query structure */
120909 ){
@@ -120642,10 +120913,11 @@
120913 sParse.pTokenizer = pTokenizer;
120914 sParse.azCol = (const char **)azCol;
120915 sParse.nCol = nCol;
120916 sParse.iDefaultCol = iDefaultCol;
120917 sParse.nNest = 0;
120918 sParse.bFts4 = bFts4;
120919 if( z==0 ){
120920 *ppExpr = 0;
120921 return SQLITE_OK;
120922 }
120923 if( n<0 ){
@@ -120831,11 +121103,11 @@
121103 for(ii=0; ii<nCol; ii++){
121104 azCol[ii] = (char *)sqlite3_value_text(argv[ii+2]);
121105 }
121106
121107 rc = sqlite3Fts3ExprParse(
121108 pTokenizer, azCol, 0, nCol, nCol, zExpr, nExpr, &pExpr
121109 );
121110 if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){
121111 sqlite3_result_error(context, "Error parsing expression", -1);
121112 }else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){
121113 sqlite3_result_error_nomem(context);
@@ -122878,11 +123150,11 @@
123150 /* 2 */ "DELETE FROM %Q.'%q_content'",
123151 /* 3 */ "DELETE FROM %Q.'%q_segments'",
123152 /* 4 */ "DELETE FROM %Q.'%q_segdir'",
123153 /* 5 */ "DELETE FROM %Q.'%q_docsize'",
123154 /* 6 */ "DELETE FROM %Q.'%q_stat'",
123155 /* 7 */ "SELECT %s WHERE rowid=?",
123156 /* 8 */ "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1",
123157 /* 9 */ "INSERT INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)",
123158 /* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)",
123159 /* 11 */ "INSERT INTO %Q.'%q_segdir' VALUES(?,?,?,?,?,?)",
123160
@@ -122920,11 +123192,11 @@
123192 if( !pStmt ){
123193 char *zSql;
123194 if( eStmt==SQL_CONTENT_INSERT ){
123195 zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
123196 }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
123197 zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist);
123198 }else{
123199 zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
123200 }
123201 if( !zSql ){
123202 rc = SQLITE_NOMEM;
@@ -123031,21 +123303,28 @@
123303 ** We try to avoid this because if FTS3 returns any error when committing
123304 ** a transaction, the whole transaction will be rolled back. And this is
123305 ** not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. It can
123306 ** still happen if the user reads data directly from the %_segments or
123307 ** %_segdir tables instead of going through FTS3 though.
123308 **
123309 ** This reasoning does not apply to a content=xxx table.
123310 */
123311 SQLITE_PRIVATE int sqlite3Fts3ReadLock(Fts3Table *p){
123312 int rc; /* Return code */
123313 sqlite3_stmt *pStmt; /* Statement used to obtain lock */
123314
123315 if( p->zContentTbl==0 ){
123316 rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pStmt, 0);
123317 if( rc==SQLITE_OK ){
123318 sqlite3_bind_null(pStmt, 1);
123319 sqlite3_step(pStmt);
123320 rc = sqlite3_reset(pStmt);
123321 }
123322 }else{
123323 rc = SQLITE_OK;
123324 }
123325
123326 return rc;
123327 }
123328
123329 /*
123330 ** Set *ppStmt to a statement handle that may be used to iterate through
@@ -123401,10 +123680,22 @@
123680 sqlite3_value **apVal, /* Array of values to insert */
123681 sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */
123682 ){
123683 int rc; /* Return code */
123684 sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */
123685
123686 if( p->zContentTbl ){
123687 sqlite3_value *pRowid = apVal[p->nColumn+3];
123688 if( sqlite3_value_type(pRowid)==SQLITE_NULL ){
123689 pRowid = apVal[1];
123690 }
123691 if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){
123692 return SQLITE_CONSTRAINT;
123693 }
123694 *piDocid = sqlite3_value_int64(pRowid);
123695 return SQLITE_OK;
123696 }
123697
123698 /* Locate the statement handle used to insert data into the %_content
123699 ** table. The SQL for this statement is:
123700 **
123701 ** INSERT INTO %_content VALUES(?, ?, ?, ...)
@@ -123452,18 +123743,20 @@
123743
123744 /*
123745 ** Remove all data from the FTS3 table. Clear the hash table containing
123746 ** pending terms.
123747 */
123748 static int fts3DeleteAll(Fts3Table *p, int bContent){
123749 int rc = SQLITE_OK; /* Return code */
123750
123751 /* Discard the contents of the pending-terms hash table. */
123752 sqlite3Fts3PendingTermsClear(p);
123753
123754 /* Delete everything from the shadow tables. Except, leave %_content as
123755 ** is if bContent is false. */
123756 assert( p->zContentTbl==0 || bContent==0 );
123757 if( bContent ) fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0);
123758 fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGMENTS, 0);
123759 fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0);
123760 if( p->bHasDocsize ){
123761 fts3SqlExec(&rc, p, SQL_DELETE_ALL_DOCSIZE, 0);
123762 }
@@ -124747,16 +125040,22 @@
125040 ** error occurs, an SQLite error code is returned.
125041 */
125042 static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
125043 sqlite3_stmt *pStmt;
125044 int rc;
125045 if( p->zContentTbl ){
125046 /* If using the content=xxx option, assume the table is never empty */
125047 *pisEmpty = 0;
125048 rc = SQLITE_OK;
125049 }else{
125050 rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
125051 if( rc==SQLITE_OK ){
125052 if( SQLITE_ROW==sqlite3_step(pStmt) ){
125053 *pisEmpty = sqlite3_column_int(pStmt, 0);
125054 }
125055 rc = sqlite3_reset(pStmt);
125056 }
 
125057 }
125058 return rc;
125059 }
125060
125061 /*
@@ -125104,10 +125403,11 @@
125403 int isIgnoreEmpty = (pCsr->pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY);
125404 int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS);
125405 int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER);
125406 int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX);
125407 int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN);
125408 int isFirst = (pCsr->pFilter->flags & FTS3_SEGMENT_FIRST);
125409
125410 Fts3SegReader **apSegment = pCsr->apSegment;
125411 int nSegment = pCsr->nSegment;
125412 Fts3SegFilter *pFilter = pCsr->pFilter;
125413 int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = (
@@ -125163,10 +125463,11 @@
125463 }
125464
125465 assert( isIgnoreEmpty || (isRequirePos && !isColFilter) );
125466 if( nMerge==1
125467 && !isIgnoreEmpty
125468 && !isFirst
125469 && (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0)
125470 ){
125471 pCsr->nDoclist = apSegment[0]->nDoclist;
125472 if( fts3SegReaderIsPending(apSegment[0]) ){
125473 rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist);
@@ -125228,16 +125529,28 @@
125529 if( !aNew ){
125530 return SQLITE_NOMEM;
125531 }
125532 pCsr->aBuffer = aNew;
125533 }
125534
125535 if( isFirst ){
125536 char *a = &pCsr->aBuffer[nDoclist];
125537 int nWrite;
125538
125539 nWrite = sqlite3Fts3FirstFilter(iDelta, pList, nList, a);
125540 if( nWrite ){
125541 iPrev = iDocid;
125542 nDoclist += nWrite;
125543 }
125544 }else{
125545 nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
125546 iPrev = iDocid;
125547 if( isRequirePos ){
125548 memcpy(&pCsr->aBuffer[nDoclist], pList, nList);
125549 nDoclist += nList;
125550 pCsr->aBuffer[nDoclist++] = '\0';
125551 }
125552 }
125553 }
125554
125555 fts3SegReaderSort(apSegment, nMerge, j, xCmp);
125556 }
@@ -125409,13 +125722,13 @@
125722 ** Insert the sizes (in tokens) for each column of the document
125723 ** with docid equal to p->iPrevDocid. The sizes are encoded as
125724 ** a blob of varints.
125725 */
125726 static void fts3InsertDocsize(
125727 int *pRC, /* Result code */
125728 Fts3Table *p, /* Table into which to insert */
125729 u32 *aSz /* Sizes of each column, in tokens */
125730 ){
125731 char *pBlob; /* The BLOB encoding of the document size */
125732 int nBlob; /* Number of bytes in the BLOB */
125733 sqlite3_stmt *pStmt; /* Statement used to insert the encoding */
125734 int rc; /* Result code from subfunctions */
@@ -125532,10 +125845,90 @@
125845 sqlite3Fts3SegmentsClose(p);
125846 sqlite3Fts3PendingTermsClear(p);
125847
125848 return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc;
125849 }
125850
125851 /*
125852 ** This function is called when the user executes the following statement:
125853 **
125854 ** INSERT INTO <tbl>(<tbl>) VALUES('rebuild');
125855 **
125856 ** The entire FTS index is discarded and rebuilt. If the table is one
125857 ** created using the content=xxx option, then the new index is based on
125858 ** the current contents of the xxx table. Otherwise, it is rebuilt based
125859 ** on the contents of the %_content table.
125860 */
125861 static int fts3DoRebuild(Fts3Table *p){
125862 int rc; /* Return Code */
125863
125864 rc = fts3DeleteAll(p, 0);
125865 if( rc==SQLITE_OK ){
125866 u32 *aSz = 0;
125867 u32 *aSzIns = 0;
125868 u32 *aSzDel = 0;
125869 sqlite3_stmt *pStmt = 0;
125870 int nEntry = 0;
125871
125872 /* Compose and prepare an SQL statement to loop through the content table */
125873 char *zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist);
125874 if( !zSql ){
125875 rc = SQLITE_NOMEM;
125876 }else{
125877 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
125878 sqlite3_free(zSql);
125879 }
125880
125881 if( rc==SQLITE_OK ){
125882 int nByte = sizeof(u32) * (p->nColumn+1)*3;
125883 aSz = (u32 *)sqlite3_malloc(nByte);
125884 if( aSz==0 ){
125885 rc = SQLITE_NOMEM;
125886 }else{
125887 memset(aSz, 0, nByte);
125888 aSzIns = &aSz[p->nColumn+1];
125889 aSzDel = &aSzIns[p->nColumn+1];
125890 }
125891 }
125892
125893 while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
125894 int iCol;
125895 rc = fts3PendingTermsDocid(p, sqlite3_column_int64(pStmt, 0));
125896 aSz[p->nColumn] = 0;
125897 for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){
125898 const char *z = (const char *) sqlite3_column_text(pStmt, iCol+1);
125899 rc = fts3PendingTermsAdd(p, z, iCol, &aSz[iCol]);
125900 aSz[p->nColumn] += sqlite3_column_bytes(pStmt, iCol+1);
125901 }
125902 if( p->bHasDocsize ){
125903 fts3InsertDocsize(&rc, p, aSz);
125904 }
125905 if( rc!=SQLITE_OK ){
125906 sqlite3_finalize(pStmt);
125907 pStmt = 0;
125908 }else{
125909 nEntry++;
125910 for(iCol=0; iCol<=p->nColumn; iCol++){
125911 aSzIns[iCol] += aSz[iCol];
125912 }
125913 }
125914 }
125915 if( p->bHasStat ){
125916 fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nEntry);
125917 }
125918 sqlite3_free(aSz);
125919
125920 if( pStmt ){
125921 int rc2 = sqlite3_finalize(pStmt);
125922 if( rc==SQLITE_OK ){
125923 rc = rc2;
125924 }
125925 }
125926 }
125927
125928 return rc;
125929 }
125930
125931 /*
125932 ** Handle a 'special' INSERT of the form:
125933 **
125934 ** "INSERT INTO tbl(tbl) VALUES(<expr>)"
@@ -125550,10 +125943,12 @@
125943
125944 if( !zVal ){
125945 return SQLITE_NOMEM;
125946 }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){
125947 rc = fts3DoOptimize(p, 0);
125948 }else if( nVal==7 && 0==sqlite3_strnicmp(zVal, "rebuild", 7) ){
125949 rc = fts3DoRebuild(p);
125950 #ifdef SQLITE_TEST
125951 }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
125952 p->nNodeSize = atoi(&zVal[9]);
125953 rc = SQLITE_OK;
125954 }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){
@@ -125630,10 +126025,11 @@
126025 pTC->pTokenizer = pT;
126026 rc = pModule->xNext(pTC, &zToken, &nToken, &iDum1, &iDum2, &iPos);
126027 for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){
126028 Fts3PhraseToken *pPT = pDef->pToken;
126029 if( (pDef->iCol>=p->nColumn || pDef->iCol==i)
126030 && (pPT->bFirst==0 || iPos==0)
126031 && (pPT->n==nToken || (pPT->isPrefix && pPT->n<nToken))
126032 && (0==memcmp(zToken, pPT->z, pPT->n))
126033 ){
126034 fts3PendingListAppend(&pDef->pList, iDocid, i, iPos, &rc);
126035 }
@@ -125721,18 +126117,22 @@
126117 if( rc==SQLITE_OK ){
126118 if( isEmpty ){
126119 /* Deleting this row means the whole table is empty. In this case
126120 ** delete the contents of all three tables and throw away any
126121 ** data in the pendingTerms hash table. */
126122 rc = fts3DeleteAll(p, 1);
126123 *pnDoc = *pnDoc - 1;
126124 }else{
126125 sqlite3_int64 iRemove = sqlite3_value_int64(pRowid);
126126 rc = fts3PendingTermsDocid(p, iRemove);
126127 fts3DeleteTerms(&rc, p, pRowid, aSzDel);
126128 if( p->zContentTbl==0 ){
126129 fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
126130 if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1;
126131 }else{
126132 *pnDoc = *pnDoc - 1;
126133 }
126134 if( p->bHasDocsize ){
126135 fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid);
126136 }
126137 }
126138 }
@@ -125788,11 +126188,11 @@
126188 ** should be deleted from the database before inserting the new row. Or,
126189 ** if the on-conflict mode is other than REPLACE, then this method must
126190 ** detect the conflict and return SQLITE_CONSTRAINT before beginning to
126191 ** modify the database file.
126192 */
126193 if( nArg>1 && p->zContentTbl==0 ){
126194 /* Find the value object that holds the new rowid value. */
126195 sqlite3_value *pNewRowid = apVal[3+p->nColumn];
126196 if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
126197 pNewRowid = apVal[1];
126198 }
@@ -125839,11 +126239,13 @@
126239
126240 /* If this is an INSERT or UPDATE operation, insert the new record. */
126241 if( nArg>1 && rc==SQLITE_OK ){
126242 if( bInsertDone==0 ){
126243 rc = fts3InsertData(p, apVal, pRowid);
126244 if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){
126245 rc = FTS_CORRUPT_VTAB;
126246 }
126247 }
126248 if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){
126249 rc = fts3PendingTermsDocid(p, *pRowid);
126250 }
126251 if( rc==SQLITE_OK ){
@@ -126259,10 +126661,11 @@
126661 pCsr = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol);
126662 if( pCsr ){
126663 int iFirst = 0;
126664 pPhrase->pList = pCsr;
126665 fts3GetDeltaPosition(&pCsr, &iFirst);
126666 assert( iFirst>=0 );
126667 pPhrase->pHead = pCsr;
126668 pPhrase->pTail = pCsr;
126669 pPhrase->iHead = iFirst;
126670 pPhrase->iTail = iFirst;
126671 }else{
@@ -127300,11 +127703,11 @@
127703 }
127704 }
127705
127706 if( !pTerm ){
127707 /* All offsets for this column have been gathered. */
127708 rc = SQLITE_DONE;
127709 }else{
127710 assert( iCurrent<=iMinPos );
127711 if( 0==(0xFE&*pTerm->pList) ){
127712 pTerm->pList = 0;
127713 }else{
@@ -127317,11 +127720,11 @@
127720 char aBuffer[64];
127721 sqlite3_snprintf(sizeof(aBuffer), aBuffer,
127722 "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart
127723 );
127724 rc = fts3StringAppend(&res, aBuffer, -1);
127725 }else if( rc==SQLITE_DONE && pTab->zContentTbl==0 ){
127726 rc = FTS_CORRUPT_VTAB;
127727 }
127728 }
127729 }
127730 if( rc==SQLITE_DONE ){
127731
+3 -3
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -107,11 +107,11 @@
107107
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
108108
** [sqlite_version()] and [sqlite_source_id()].
109109
*/
110110
#define SQLITE_VERSION "3.7.9"
111111
#define SQLITE_VERSION_NUMBER 3007009
112
-#define SQLITE_SOURCE_ID "2011-10-15 00:16:30 39408702a989f907261c298bf0947f3e68bd10fe"
112
+#define SQLITE_SOURCE_ID "2011-10-20 00:55:54 4344483f7d7f64dffadde0053e6c745948db9486"
113113
114114
/*
115115
** CAPI3REF: Run-Time Library Version Numbers
116116
** KEYWORDS: sqlite3_version, sqlite3_sourceid
117117
**
@@ -1402,12 +1402,12 @@
14021402
** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
14031403
** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
14041404
** allocator is engaged to handle all of SQLites memory allocation needs.
14051405
** The first pointer (the memory pointer) must be aligned to an 8-byte
14061406
** boundary or subsequent behavior of SQLite will be undefined.
1407
-** The minimum allocation size is capped at 2^12. Reasonable values
1408
-** for the minimum allocation size are 2^5 through 2^8.</dd>
1407
+** The minimum allocation size is capped at 2**12. Reasonable values
1408
+** for the minimum allocation size are 2**5 through 2**8.</dd>
14091409
**
14101410
** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
14111411
** <dd> ^(This option takes a single argument which is a pointer to an
14121412
** instance of the [sqlite3_mutex_methods] structure. The argument specifies
14131413
** alternative low-level mutex routines to be used in place
14141414
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -107,11 +107,11 @@
107 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
108 ** [sqlite_version()] and [sqlite_source_id()].
109 */
110 #define SQLITE_VERSION "3.7.9"
111 #define SQLITE_VERSION_NUMBER 3007009
112 #define SQLITE_SOURCE_ID "2011-10-15 00:16:30 39408702a989f907261c298bf0947f3e68bd10fe"
113
114 /*
115 ** CAPI3REF: Run-Time Library Version Numbers
116 ** KEYWORDS: sqlite3_version, sqlite3_sourceid
117 **
@@ -1402,12 +1402,12 @@
1402 ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
1403 ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
1404 ** allocator is engaged to handle all of SQLites memory allocation needs.
1405 ** The first pointer (the memory pointer) must be aligned to an 8-byte
1406 ** boundary or subsequent behavior of SQLite will be undefined.
1407 ** The minimum allocation size is capped at 2^12. Reasonable values
1408 ** for the minimum allocation size are 2^5 through 2^8.</dd>
1409 **
1410 ** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
1411 ** <dd> ^(This option takes a single argument which is a pointer to an
1412 ** instance of the [sqlite3_mutex_methods] structure. The argument specifies
1413 ** alternative low-level mutex routines to be used in place
1414
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -107,11 +107,11 @@
107 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
108 ** [sqlite_version()] and [sqlite_source_id()].
109 */
110 #define SQLITE_VERSION "3.7.9"
111 #define SQLITE_VERSION_NUMBER 3007009
112 #define SQLITE_SOURCE_ID "2011-10-20 00:55:54 4344483f7d7f64dffadde0053e6c745948db9486"
113
114 /*
115 ** CAPI3REF: Run-Time Library Version Numbers
116 ** KEYWORDS: sqlite3_version, sqlite3_sourceid
117 **
@@ -1402,12 +1402,12 @@
1402 ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or
1403 ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory
1404 ** allocator is engaged to handle all of SQLites memory allocation needs.
1405 ** The first pointer (the memory pointer) must be aligned to an 8-byte
1406 ** boundary or subsequent behavior of SQLite will be undefined.
1407 ** The minimum allocation size is capped at 2**12. Reasonable values
1408 ** for the minimum allocation size are 2**5 through 2**8.</dd>
1409 **
1410 ** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
1411 ** <dd> ^(This option takes a single argument which is a pointer to an
1412 ** instance of the [sqlite3_mutex_methods] structure. The argument specifies
1413 ** alternative low-level mutex routines to be used in place
1414
+12 -10
--- src/stash.c
+++ src/stash.c
@@ -266,11 +266,11 @@
266266
}
267267
268268
/*
269269
** Show the diffs associate with a single stash.
270270
*/
271
-static void stash_diff(int stashid, const char *zDiffCmd){
271
+static void stash_diff(int stashid, const char *zDiffCmd, int diffFlags){
272272
Stmt q;
273273
Blob empty;
274274
blob_zero(&empty);
275275
db_prepare(&q,
276276
"SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
@@ -286,21 +286,21 @@
286286
char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
287287
Blob delta;
288288
if( rid==0 ){
289289
db_ephemeral_blob(&q, 6, &delta);
290290
fossil_print("ADDED %s\n", zNew);
291
- diff_print_index(zNew);
292
- diff_file_mem(&empty, &delta, zNew, zDiffCmd, 0);
291
+ diff_print_index(zNew, diffFlags);
292
+ diff_file_mem(&empty, &delta, zNew, zDiffCmd, diffFlags);
293293
}else if( isRemoved ){
294294
fossil_print("DELETE %s\n", zOrig);
295295
if( file_wd_islink(zOPath) ){
296296
blob_read_link(&delta, zOPath);
297297
}else{
298298
blob_read_from_file(&delta, zOPath);
299299
}
300
- diff_print_index(zNew);
301
- diff_file_mem(&delta, &empty, zOrig, zDiffCmd, 0);
300
+ diff_print_index(zNew, diffFlags);
301
+ diff_file_mem(&delta, &empty, zOrig, zDiffCmd, diffFlags);
302302
}else{
303303
Blob a, b, disk;
304304
int isOrigLink = file_wd_islink(zOPath);
305305
db_ephemeral_blob(&q, 6, &delta);
306306
if( isOrigLink ){
@@ -308,17 +308,17 @@
308308
}else{
309309
blob_read_from_file(&disk, zOPath);
310310
}
311311
fossil_print("CHANGED %s\n", zNew);
312312
if( !isOrigLink != !isLink ){
313
- diff_print_index(zNew);
314
- printf("--- %s\n+++ %s\n", zOrig, zNew);
313
+ diff_print_index(zNew, diffFlags);
314
+ diff_print_filenames(zOrig, zNew, diffFlags);
315315
printf("cannot compute difference between symlink and regular file\n");
316316
}else{
317317
content_get(rid, &a);
318318
blob_delta_apply(&a, &delta, &b);
319
- diff_file_mem(&disk, &b, zNew, zDiffCmd, 0);
319
+ diff_file_mem(&disk, &b, zNew, zDiffCmd, diffFlags);
320320
blob_reset(&a);
321321
blob_reset(&b);
322322
}
323323
blob_reset(&disk);
324324
}
@@ -526,20 +526,22 @@
526526
stashid);
527527
undo_finish();
528528
}else
529529
if( memcmp(zCmd, "diff", nCmd)==0 ){
530530
const char *zDiffCmd = db_get("diff-command", 0);
531
+ int diffFlags = diff_options();
531532
if( g.argc>4 ) usage("diff STASHID");
532533
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
533
- stash_diff(stashid, zDiffCmd);
534
+ stash_diff(stashid, zDiffCmd, diffFlags);
534535
}else
535536
if( memcmp(zCmd, "gdiff", nCmd)==0 ){
536537
const char *zDiffCmd = db_get("gdiff-command", 0);
538
+ int diffFlags = diff_options();
537539
if( g.argc>4 ) usage("diff STASHID");
538540
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
539
- stash_diff(stashid, zDiffCmd);
541
+ stash_diff(stashid, zDiffCmd, diffFlags);
540542
}else
541543
{
542544
usage("SUBCOMMAND ARGS...");
543545
}
544546
db_end_transaction(0);
545547
}
546548
--- src/stash.c
+++ src/stash.c
@@ -266,11 +266,11 @@
266 }
267
268 /*
269 ** Show the diffs associate with a single stash.
270 */
271 static void stash_diff(int stashid, const char *zDiffCmd){
272 Stmt q;
273 Blob empty;
274 blob_zero(&empty);
275 db_prepare(&q,
276 "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
@@ -286,21 +286,21 @@
286 char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
287 Blob delta;
288 if( rid==0 ){
289 db_ephemeral_blob(&q, 6, &delta);
290 fossil_print("ADDED %s\n", zNew);
291 diff_print_index(zNew);
292 diff_file_mem(&empty, &delta, zNew, zDiffCmd, 0);
293 }else if( isRemoved ){
294 fossil_print("DELETE %s\n", zOrig);
295 if( file_wd_islink(zOPath) ){
296 blob_read_link(&delta, zOPath);
297 }else{
298 blob_read_from_file(&delta, zOPath);
299 }
300 diff_print_index(zNew);
301 diff_file_mem(&delta, &empty, zOrig, zDiffCmd, 0);
302 }else{
303 Blob a, b, disk;
304 int isOrigLink = file_wd_islink(zOPath);
305 db_ephemeral_blob(&q, 6, &delta);
306 if( isOrigLink ){
@@ -308,17 +308,17 @@
308 }else{
309 blob_read_from_file(&disk, zOPath);
310 }
311 fossil_print("CHANGED %s\n", zNew);
312 if( !isOrigLink != !isLink ){
313 diff_print_index(zNew);
314 printf("--- %s\n+++ %s\n", zOrig, zNew);
315 printf("cannot compute difference between symlink and regular file\n");
316 }else{
317 content_get(rid, &a);
318 blob_delta_apply(&a, &delta, &b);
319 diff_file_mem(&disk, &b, zNew, zDiffCmd, 0);
320 blob_reset(&a);
321 blob_reset(&b);
322 }
323 blob_reset(&disk);
324 }
@@ -526,20 +526,22 @@
526 stashid);
527 undo_finish();
528 }else
529 if( memcmp(zCmd, "diff", nCmd)==0 ){
530 const char *zDiffCmd = db_get("diff-command", 0);
 
531 if( g.argc>4 ) usage("diff STASHID");
532 stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
533 stash_diff(stashid, zDiffCmd);
534 }else
535 if( memcmp(zCmd, "gdiff", nCmd)==0 ){
536 const char *zDiffCmd = db_get("gdiff-command", 0);
 
537 if( g.argc>4 ) usage("diff STASHID");
538 stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
539 stash_diff(stashid, zDiffCmd);
540 }else
541 {
542 usage("SUBCOMMAND ARGS...");
543 }
544 db_end_transaction(0);
545 }
546
--- src/stash.c
+++ src/stash.c
@@ -266,11 +266,11 @@
266 }
267
268 /*
269 ** Show the diffs associate with a single stash.
270 */
271 static void stash_diff(int stashid, const char *zDiffCmd, int diffFlags){
272 Stmt q;
273 Blob empty;
274 blob_zero(&empty);
275 db_prepare(&q,
276 "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
@@ -286,21 +286,21 @@
286 char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
287 Blob delta;
288 if( rid==0 ){
289 db_ephemeral_blob(&q, 6, &delta);
290 fossil_print("ADDED %s\n", zNew);
291 diff_print_index(zNew, diffFlags);
292 diff_file_mem(&empty, &delta, zNew, zDiffCmd, diffFlags);
293 }else if( isRemoved ){
294 fossil_print("DELETE %s\n", zOrig);
295 if( file_wd_islink(zOPath) ){
296 blob_read_link(&delta, zOPath);
297 }else{
298 blob_read_from_file(&delta, zOPath);
299 }
300 diff_print_index(zNew, diffFlags);
301 diff_file_mem(&delta, &empty, zOrig, zDiffCmd, diffFlags);
302 }else{
303 Blob a, b, disk;
304 int isOrigLink = file_wd_islink(zOPath);
305 db_ephemeral_blob(&q, 6, &delta);
306 if( isOrigLink ){
@@ -308,17 +308,17 @@
308 }else{
309 blob_read_from_file(&disk, zOPath);
310 }
311 fossil_print("CHANGED %s\n", zNew);
312 if( !isOrigLink != !isLink ){
313 diff_print_index(zNew, diffFlags);
314 diff_print_filenames(zOrig, zNew, diffFlags);
315 printf("cannot compute difference between symlink and regular file\n");
316 }else{
317 content_get(rid, &a);
318 blob_delta_apply(&a, &delta, &b);
319 diff_file_mem(&disk, &b, zNew, zDiffCmd, diffFlags);
320 blob_reset(&a);
321 blob_reset(&b);
322 }
323 blob_reset(&disk);
324 }
@@ -526,20 +526,22 @@
526 stashid);
527 undo_finish();
528 }else
529 if( memcmp(zCmd, "diff", nCmd)==0 ){
530 const char *zDiffCmd = db_get("diff-command", 0);
531 int diffFlags = diff_options();
532 if( g.argc>4 ) usage("diff STASHID");
533 stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
534 stash_diff(stashid, zDiffCmd, diffFlags);
535 }else
536 if( memcmp(zCmd, "gdiff", nCmd)==0 ){
537 const char *zDiffCmd = db_get("gdiff-command", 0);
538 int diffFlags = diff_options();
539 if( g.argc>4 ) usage("diff STASHID");
540 stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
541 stash_diff(stashid, zDiffCmd, diffFlags);
542 }else
543 {
544 usage("SUBCOMMAND ARGS...");
545 }
546 db_end_transaction(0);
547 }
548
+27 -25
--- src/style.c
+++ src/style.c
@@ -210,37 +210,38 @@
210210
@ } else {
211211
@ puts "Not logged in"
212212
@ }
213213
@ </th1></div>
214214
@ </div>
215
-@ <div class="mainmenu"><th1>
216
-@ html "<a href='$home$index_page'>Home</a> "
215
+@ <div class="mainmenu">
216
+@ <th1>
217
+@ html "<a href='$home$index_page'>Home</a>\n"
217218
@ if {[anycap jor]} {
218
-@ html "<a href='$home/timeline'>Timeline</a> "
219
+@ html "<a href='$home/timeline'>Timeline</a>\n"
219220
@ }
220221
@ if {[hascap oh]} {
221
-@ html "<a href='$home/dir?ci=tip'>Files</a> "
222
+@ html "<a href='$home/dir?ci=tip'>Files</a>\n"
222223
@ }
223224
@ if {[hascap o]} {
224
-@ html "<a href='$home/brlist'>Branches</a> "
225
-@ html "<a href='$home/taglist'>Tags</a> "
225
+@ html "<a href='$home/brlist'>Branches</a>\n"
226
+@ html "<a href='$home/taglist'>Tags</a>\n"
226227
@ }
227228
@ if {[hascap r]} {
228
-@ html "<a href='$home/reportlist'>Tickets</a> "
229
+@ html "<a href='$home/reportlist'>Tickets</a>\n"
229230
@ }
230231
@ if {[hascap j]} {
231
-@ html "<a href='$home/wiki'>Wiki</a> "
232
+@ html "<a href='$home/wiki'>Wiki</a>\n"
232233
@ }
233234
@ if {[hascap s]} {
234
-@ html "<a href='$home/setup'>Admin</a> "
235
+@ html "<a href='$home/setup'>Admin</a>\n"
235236
@ } elseif {[hascap a]} {
236
-@ html "<a href='$home/setup_ulist'>Users</a> "
237
+@ html "<a href='$home/setup_ulist'>Users</a>\n"
237238
@ }
238239
@ if {[info exists login]} {
239
-@ html "<a href='$home/login'>Logout</a> "
240
+@ html "<a href='$home/login'>Logout</a>\n"
240241
@ } else {
241
-@ html "<a href='$home/login'>Login</a> "
242
+@ html "<a href='$home/login'>Login</a>\n"
242243
@ }
243244
@ </th1></div>
244245
;
245246
246247
/*
@@ -320,23 +321,24 @@
320321
@ background-color: #558195;
321322
@ color: white;
322323
@ }
323324
@
324325
@ /* The submenu bar that *sometimes* appears below the main menu */
325
-@ div.submenu {
326
+@ div.submenu, div.sectionmenu {
326327
@ padding: 3px 10px 3px 0px;
327328
@ font-size: 0.9em;
328329
@ text-align: center;
329330
@ background-color: #456878;
330331
@ color: white;
331332
@ }
332
-@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
333
+@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
334
+@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
333335
@ padding: 3px 10px 3px 10px;
334336
@ color: white;
335337
@ text-decoration: none;
336338
@ }
337
-@ div.mainmenu a:hover, div.submenu a:hover {
339
+@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
338340
@ color: #558195;
339341
@ background-color: white;
340342
@ }
341343
@
342344
@ /* All page content from the bottom of the menu or submenu down to
@@ -529,11 +531,11 @@
529531
@ vertical-align: top;
530532
@ text-align: right;
531533
},
532534
{ "td.timelineGraph",
533535
"the format for the grap placeholder cells in timelines",
534
- @ width: 20;
536
+ @ width: 20px;
535537
@ text-align: left;
536538
@ vertical-align: top;
537539
},
538540
{ "a.tagLink",
539541
"the format for the tag links",
@@ -623,11 +625,11 @@
623625
@ vertical-align: top
624626
},
625627
{ "table.usetupUserList",
626628
"format for the user list table on the user setup page",
627629
@ outline-style: double;
628
- @ outline-width: 1;
630
+ @ outline-width: 1px;
629631
@ padding: 10px;
630632
},
631633
{ "th.usetupListUser",
632634
"format for table header user in user list on user setup page",
633635
@ text-align: right;
@@ -747,17 +749,17 @@
747749
@ border-color: #000000;
748750
@ border-style: solid;
749751
},
750752
{ "input.checkinUserColor",
751753
"format for user color input on checkin edit page",
752
- @ # no special definitions, class defined, to enable color pickers, f.e.:
753
- @ # add the color picker found at http:jscolor.com as java script include
754
- @ # to the header and configure the java script file with
755
- @ # 1. use as bindClass :checkinUserColor
756
- @ # 2. change the default hash adding behaviour to ON
757
- @ # or change the class defition of element identified by id="clrcust"
758
- @ # to a standard jscolor definition with java script in the footer.
754
+ @ /* no special definitions, class defined, to enable color pickers, f.e.:
755
+ @ ** add the color picker found at http:jscolor.com as java script include
756
+ @ ** to the header and configure the java script file with
757
+ @ ** 1. use as bindClass :checkinUserColor
758
+ @ ** 2. change the default hash adding behaviour to ON
759
+ @ ** or change the class defition of element identified by id="clrcust"
760
+ @ ** to a standard jscolor definition with java script in the footer. */
759761
},
760762
{ "div.endContent",
761763
"format for end of content area, to be used to clear page flow(sidebox on branch,..",
762764
@ clear: both;
763765
},
@@ -777,11 +779,11 @@
777779
},
778780
{ "span.thTrace",
779781
"format for th script trace messages",
780782
@ color: red;
781783
},
782
- { "p:reportError",
784
+ { "p.reportError",
783785
"format for report configuration errors",
784786
@ color: red;
785787
@ font-weight: bold;
786788
},
787789
{ "blockquote.reportError",
788790
--- src/style.c
+++ src/style.c
@@ -210,37 +210,38 @@
210 @ } else {
211 @ puts "Not logged in"
212 @ }
213 @ </th1></div>
214 @ </div>
215 @ <div class="mainmenu"><th1>
216 @ html "<a href='$home$index_page'>Home</a> "
 
217 @ if {[anycap jor]} {
218 @ html "<a href='$home/timeline'>Timeline</a> "
219 @ }
220 @ if {[hascap oh]} {
221 @ html "<a href='$home/dir?ci=tip'>Files</a> "
222 @ }
223 @ if {[hascap o]} {
224 @ html "<a href='$home/brlist'>Branches</a> "
225 @ html "<a href='$home/taglist'>Tags</a> "
226 @ }
227 @ if {[hascap r]} {
228 @ html "<a href='$home/reportlist'>Tickets</a> "
229 @ }
230 @ if {[hascap j]} {
231 @ html "<a href='$home/wiki'>Wiki</a> "
232 @ }
233 @ if {[hascap s]} {
234 @ html "<a href='$home/setup'>Admin</a> "
235 @ } elseif {[hascap a]} {
236 @ html "<a href='$home/setup_ulist'>Users</a> "
237 @ }
238 @ if {[info exists login]} {
239 @ html "<a href='$home/login'>Logout</a> "
240 @ } else {
241 @ html "<a href='$home/login'>Login</a> "
242 @ }
243 @ </th1></div>
244 ;
245
246 /*
@@ -320,23 +321,24 @@
320 @ background-color: #558195;
321 @ color: white;
322 @ }
323 @
324 @ /* The submenu bar that *sometimes* appears below the main menu */
325 @ div.submenu {
326 @ padding: 3px 10px 3px 0px;
327 @ font-size: 0.9em;
328 @ text-align: center;
329 @ background-color: #456878;
330 @ color: white;
331 @ }
332 @ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
 
333 @ padding: 3px 10px 3px 10px;
334 @ color: white;
335 @ text-decoration: none;
336 @ }
337 @ div.mainmenu a:hover, div.submenu a:hover {
338 @ color: #558195;
339 @ background-color: white;
340 @ }
341 @
342 @ /* All page content from the bottom of the menu or submenu down to
@@ -529,11 +531,11 @@
529 @ vertical-align: top;
530 @ text-align: right;
531 },
532 { "td.timelineGraph",
533 "the format for the grap placeholder cells in timelines",
534 @ width: 20;
535 @ text-align: left;
536 @ vertical-align: top;
537 },
538 { "a.tagLink",
539 "the format for the tag links",
@@ -623,11 +625,11 @@
623 @ vertical-align: top
624 },
625 { "table.usetupUserList",
626 "format for the user list table on the user setup page",
627 @ outline-style: double;
628 @ outline-width: 1;
629 @ padding: 10px;
630 },
631 { "th.usetupListUser",
632 "format for table header user in user list on user setup page",
633 @ text-align: right;
@@ -747,17 +749,17 @@
747 @ border-color: #000000;
748 @ border-style: solid;
749 },
750 { "input.checkinUserColor",
751 "format for user color input on checkin edit page",
752 @ # no special definitions, class defined, to enable color pickers, f.e.:
753 @ # add the color picker found at http:jscolor.com as java script include
754 @ # to the header and configure the java script file with
755 @ # 1. use as bindClass :checkinUserColor
756 @ # 2. change the default hash adding behaviour to ON
757 @ # or change the class defition of element identified by id="clrcust"
758 @ # to a standard jscolor definition with java script in the footer.
759 },
760 { "div.endContent",
761 "format for end of content area, to be used to clear page flow(sidebox on branch,..",
762 @ clear: both;
763 },
@@ -777,11 +779,11 @@
777 },
778 { "span.thTrace",
779 "format for th script trace messages",
780 @ color: red;
781 },
782 { "p:reportError",
783 "format for report configuration errors",
784 @ color: red;
785 @ font-weight: bold;
786 },
787 { "blockquote.reportError",
788
--- src/style.c
+++ src/style.c
@@ -210,37 +210,38 @@
210 @ } else {
211 @ puts "Not logged in"
212 @ }
213 @ </th1></div>
214 @ </div>
215 @ <div class="mainmenu">
216 @ <th1>
217 @ html "<a href='$home$index_page'>Home</a>\n"
218 @ if {[anycap jor]} {
219 @ html "<a href='$home/timeline'>Timeline</a>\n"
220 @ }
221 @ if {[hascap oh]} {
222 @ html "<a href='$home/dir?ci=tip'>Files</a>\n"
223 @ }
224 @ if {[hascap o]} {
225 @ html "<a href='$home/brlist'>Branches</a>\n"
226 @ html "<a href='$home/taglist'>Tags</a>\n"
227 @ }
228 @ if {[hascap r]} {
229 @ html "<a href='$home/reportlist'>Tickets</a>\n"
230 @ }
231 @ if {[hascap j]} {
232 @ html "<a href='$home/wiki'>Wiki</a>\n"
233 @ }
234 @ if {[hascap s]} {
235 @ html "<a href='$home/setup'>Admin</a>\n"
236 @ } elseif {[hascap a]} {
237 @ html "<a href='$home/setup_ulist'>Users</a>\n"
238 @ }
239 @ if {[info exists login]} {
240 @ html "<a href='$home/login'>Logout</a>\n"
241 @ } else {
242 @ html "<a href='$home/login'>Login</a>\n"
243 @ }
244 @ </th1></div>
245 ;
246
247 /*
@@ -320,23 +321,24 @@
321 @ background-color: #558195;
322 @ color: white;
323 @ }
324 @
325 @ /* The submenu bar that *sometimes* appears below the main menu */
326 @ div.submenu, div.sectionmenu {
327 @ padding: 3px 10px 3px 0px;
328 @ font-size: 0.9em;
329 @ text-align: center;
330 @ background-color: #456878;
331 @ color: white;
332 @ }
333 @ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
334 @ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
335 @ padding: 3px 10px 3px 10px;
336 @ color: white;
337 @ text-decoration: none;
338 @ }
339 @ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
340 @ color: #558195;
341 @ background-color: white;
342 @ }
343 @
344 @ /* All page content from the bottom of the menu or submenu down to
@@ -529,11 +531,11 @@
531 @ vertical-align: top;
532 @ text-align: right;
533 },
534 { "td.timelineGraph",
535 "the format for the grap placeholder cells in timelines",
536 @ width: 20px;
537 @ text-align: left;
538 @ vertical-align: top;
539 },
540 { "a.tagLink",
541 "the format for the tag links",
@@ -623,11 +625,11 @@
625 @ vertical-align: top
626 },
627 { "table.usetupUserList",
628 "format for the user list table on the user setup page",
629 @ outline-style: double;
630 @ outline-width: 1px;
631 @ padding: 10px;
632 },
633 { "th.usetupListUser",
634 "format for table header user in user list on user setup page",
635 @ text-align: right;
@@ -747,17 +749,17 @@
749 @ border-color: #000000;
750 @ border-style: solid;
751 },
752 { "input.checkinUserColor",
753 "format for user color input on checkin edit page",
754 @ /* no special definitions, class defined, to enable color pickers, f.e.:
755 @ ** add the color picker found at http:jscolor.com as java script include
756 @ ** to the header and configure the java script file with
757 @ ** 1. use as bindClass :checkinUserColor
758 @ ** 2. change the default hash adding behaviour to ON
759 @ ** or change the class defition of element identified by id="clrcust"
760 @ ** to a standard jscolor definition with java script in the footer. */
761 },
762 { "div.endContent",
763 "format for end of content area, to be used to clear page flow(sidebox on branch,..",
764 @ clear: both;
765 },
@@ -777,11 +779,11 @@
779 },
780 { "span.thTrace",
781 "format for th script trace messages",
782 @ color: red;
783 },
784 { "p.reportError",
785 "format for report configuration errors",
786 @ color: red;
787 @ font-weight: bold;
788 },
789 { "blockquote.reportError",
790
+1 -1
--- src/wiki.c
+++ src/wiki.c
@@ -621,11 +621,11 @@
621621
blob_zero(&w2);
622622
if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
623623
blob_init(&w2, pW2->zWiki, -1);
624624
}
625625
blob_zero(&d);
626
- text_diff(&w2, &w1, &d, 5, 1);
626
+ text_diff(&w2, &w1, &d, 5 | DIFF_IGNORE_EOLWS);
627627
@ <pre>
628628
@ %h(blob_str(&d))
629629
@ </pre>
630630
manifest_destroy(pW1);
631631
manifest_destroy(pW2);
632632
--- src/wiki.c
+++ src/wiki.c
@@ -621,11 +621,11 @@
621 blob_zero(&w2);
622 if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
623 blob_init(&w2, pW2->zWiki, -1);
624 }
625 blob_zero(&d);
626 text_diff(&w2, &w1, &d, 5, 1);
627 @ <pre>
628 @ %h(blob_str(&d))
629 @ </pre>
630 manifest_destroy(pW1);
631 manifest_destroy(pW2);
632
--- src/wiki.c
+++ src/wiki.c
@@ -621,11 +621,11 @@
621 blob_zero(&w2);
622 if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
623 blob_init(&w2, pW2->zWiki, -1);
624 }
625 blob_zero(&d);
626 text_diff(&w2, &w1, &d, 5 | DIFF_IGNORE_EOLWS);
627 @ <pre>
628 @ %h(blob_str(&d))
629 @ </pre>
630 manifest_destroy(pW1);
631 manifest_destroy(pW2);
632
--- test/merge5.test
+++ test/merge5.test
@@ -38,14 +38,18 @@
3838
}
3939
}
4040
4141
catch {exec $::fossilexe info} res
4242
puts res=$res
43
-if {![regexp {not within an open checkout} $res]} {
43
+if {![regexp {use --repository} $res]} {
4444
puts stderr "Cannot run this test within an open checkout"
4545
return
4646
}
47
+#
48
+# Fossil will write data on $HOME, running 'fossil open' here.
49
+# We need not to clutter the $HOME of the test caller.
50
+set env(HOME) [pwd]
4751
4852
# Construct a test repository
4953
#
5054
exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql
5155
fossil rebuild m5.fossil
5256
--- test/merge5.test
+++ test/merge5.test
@@ -38,14 +38,18 @@
38 }
39 }
40
41 catch {exec $::fossilexe info} res
42 puts res=$res
43 if {![regexp {not within an open checkout} $res]} {
44 puts stderr "Cannot run this test within an open checkout"
45 return
46 }
 
 
 
 
47
48 # Construct a test repository
49 #
50 exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql
51 fossil rebuild m5.fossil
52
--- test/merge5.test
+++ test/merge5.test
@@ -38,14 +38,18 @@
38 }
39 }
40
41 catch {exec $::fossilexe info} res
42 puts res=$res
43 if {![regexp {use --repository} $res]} {
44 puts stderr "Cannot run this test within an open checkout"
45 return
46 }
47 #
48 # Fossil will write data on $HOME, running 'fossil open' here.
49 # We need not to clutter the $HOME of the test caller.
50 set env(HOME) [pwd]
51
52 # Construct a test repository
53 #
54 exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql
55 fossil rebuild m5.fossil
56
--- test/merge_renames.test
+++ test/merge_renames.test
@@ -7,10 +7,16 @@
77
puts res=$res
88
if {![regexp {use --repository} $res]} {
99
puts stderr "Cannot run this test within an open checkout"
1010
return
1111
}
12
+
13
+
14
+# Fossil will write data on $HOME, running 'fossil new' here.
15
+# We need not to clutter the $HOME of the test caller.
16
+set env(HOME) [pwd]
17
+
1218
1319
######################################
1420
# Test 1 #
1521
# Reported: Ticket [554f44ee74e3d] #
1622
######################################
1723
--- test/merge_renames.test
+++ test/merge_renames.test
@@ -7,10 +7,16 @@
7 puts res=$res
8 if {![regexp {use --repository} $res]} {
9 puts stderr "Cannot run this test within an open checkout"
10 return
11 }
 
 
 
 
 
 
12
13 ######################################
14 # Test 1 #
15 # Reported: Ticket [554f44ee74e3d] #
16 ######################################
17
--- test/merge_renames.test
+++ test/merge_renames.test
@@ -7,10 +7,16 @@
7 puts res=$res
8 if {![regexp {use --repository} $res]} {
9 puts stderr "Cannot run this test within an open checkout"
10 return
11 }
12
13
14 # Fossil will write data on $HOME, running 'fossil new' here.
15 # We need not to clutter the $HOME of the test caller.
16 set env(HOME) [pwd]
17
18
19 ######################################
20 # Test 1 #
21 # Reported: Ticket [554f44ee74e3d] #
22 ######################################
23
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -417,12 +417,12 @@
417417
$(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c
418418
419419
# WARNING. DANGER. Running the testsuite modifies the repository the
420420
# build is done from, i.e. the checkout belongs to. Do not sync/push
421421
# the repository after running the tests.
422
-test: $(APPNAME)
423
- $(TCLSH) test/tester.tcl $(APPNAME)
422
+test: $(OBJDIR) $(APPNAME)
423
+ $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
424424
425425
$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
426426
$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
427427
428428
EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o
429429
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -417,12 +417,12 @@
417 $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c
418
419 # WARNING. DANGER. Running the testsuite modifies the repository the
420 # build is done from, i.e. the checkout belongs to. Do not sync/push
421 # the repository after running the tests.
422 test: $(APPNAME)
423 $(TCLSH) test/tester.tcl $(APPNAME)
424
425 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
426 $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
427
428 EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o
429
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -417,12 +417,12 @@
417 $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c
418
419 # WARNING. DANGER. Running the testsuite modifies the repository the
420 # build is done from, i.e. the checkout belongs to. Do not sync/push
421 # the repository after running the tests.
422 test: $(OBJDIR) $(APPNAME)
423 $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
424
425 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
426 $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
427
428 EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o
429
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -417,12 +417,12 @@
417417
$(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c
418418
419419
# WARNING. DANGER. Running the testsuite modifies the repository the
420420
# build is done from, i.e. the checkout belongs to. Do not sync/push
421421
# the repository after running the tests.
422
-test: $(APPNAME)
423
- $(TCLSH) test/tester.tcl $(APPNAME)
422
+test: $(OBJDIR) $(APPNAME)
423
+ $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
424424
425425
$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
426426
$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
427427
428428
EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o
429429
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -417,12 +417,12 @@
417 $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c
418
419 # WARNING. DANGER. Running the testsuite modifies the repository the
420 # build is done from, i.e. the checkout belongs to. Do not sync/push
421 # the repository after running the tests.
422 test: $(APPNAME)
423 $(TCLSH) test/tester.tcl $(APPNAME)
424
425 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
426 $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
427
428 EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o
429
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -417,12 +417,12 @@
417 $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c
418
419 # WARNING. DANGER. Running the testsuite modifies the repository the
420 # build is done from, i.e. the checkout belongs to. Do not sync/push
421 # the repository after running the tests.
422 test: $(OBJDIR) $(APPNAME)
423 $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
424
425 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
426 $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
427
428 EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o
429
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,44 @@
11
<title>Change Log</title>
22
3
+<h2>Changes For Version 1.20 (2011-10-21)</h2>
4
+ * Added side-by-side diffs in HTML interface. [0bde74ea1e]
5
+ * Added support for symlinks. (Controlled by "allow-symlinks" setting,
6
+ off by default). [e4f1c1fe95]
7
+ * Fixed CLI annotate to show the proper file version in case there
8
+ are multiple equal versions in history. [e161670939]
9
+ * Timeline now shows tag changes (requires rebuild).[87540ed6e6]
10
+ * Fixed annotate to show "more relevant" versions of lines in
11
+ some cases. [e161670939]
12
+ * New command: ticket history. [98a855c508]
13
+ * Disabled SSLv2 in HTTPS client.[ea1d369d23]
14
+ * Fixed constant prompting regarding previously-saved SSL
15
+ certificates. [636804745b]
16
+ * Other SSL improvements.
17
+ * Added -R REPOFILE support to several more CLI commands. [e080560378]
18
+ * Generated tarballs now have constant timestamps, so they are
19
+ always identical for any given checkin. [e080560378]
20
+ * A number of minor HTML-related tweaks and fixes.
21
+ * Added --args FILENAME global CLI argument to import arbitrary
22
+ CLI arguments from a file (e.g. long file lists). [e080560378]
23
+ * Fixed significant memory leak in annotation of files with long
24
+ histories.[9929bab702]
25
+ * Added warnings when a merge operation overwrites local copies
26
+ (UNDO is available, but previously this condition normally went
27
+ silently unnoticed). [39f979b08c]
28
+ * Improved performance when adding many files. [a369dc7721]
29
+ * Improve merges which contain many file renames. [0b93b0f958]
30
+ * Added protection against timing attacks. [d4a341b49d]
31
+ * Firefox now remembers filled fields when returning to forms. [3fac77d7b0]
32
+ * Added the --stats option to the rebuild command. [f25e5e53c4]
33
+ * RSS feed now passes validation. [ce354d0a9f]
34
+ * Show overridden user when entering commit comment. [ce354d0a9f]
35
+ * Made rebuilding from web interface silent. [ce354d0a9f]
36
+ * Now works on MSVC with repos >2GB. [6092935ff2]
37
+ * A number of code cleanups to resolve warnings from various compilers.
38
+ * Update the built-in SQLite to version 3.7.9 beta.
39
+
340
<h2>Changes For Version 1.19 (2011-09-02)</h2>
441
542
* Added a ./configure script based on autosetup.
643
* Added the "[/help/winsrv | fossil winsrv]" command
744
for creating a Fossil service on windows systems.
845
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,44 @@
1 <title>Change Log</title>
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3 <h2>Changes For Version 1.19 (2011-09-02)</h2>
4
5 * Added a ./configure script based on autosetup.
6 * Added the "[/help/winsrv | fossil winsrv]" command
7 for creating a Fossil service on windows systems.
8
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,44 @@
1 <title>Change Log</title>
2
3 <h2>Changes For Version 1.20 (2011-10-21)</h2>
4 * Added side-by-side diffs in HTML interface. [0bde74ea1e]
5 * Added support for symlinks. (Controlled by "allow-symlinks" setting,
6 off by default). [e4f1c1fe95]
7 * Fixed CLI annotate to show the proper file version in case there
8 are multiple equal versions in history. [e161670939]
9 * Timeline now shows tag changes (requires rebuild).[87540ed6e6]
10 * Fixed annotate to show "more relevant" versions of lines in
11 some cases. [e161670939]
12 * New command: ticket history. [98a855c508]
13 * Disabled SSLv2 in HTTPS client.[ea1d369d23]
14 * Fixed constant prompting regarding previously-saved SSL
15 certificates. [636804745b]
16 * Other SSL improvements.
17 * Added -R REPOFILE support to several more CLI commands. [e080560378]
18 * Generated tarballs now have constant timestamps, so they are
19 always identical for any given checkin. [e080560378]
20 * A number of minor HTML-related tweaks and fixes.
21 * Added --args FILENAME global CLI argument to import arbitrary
22 CLI arguments from a file (e.g. long file lists). [e080560378]
23 * Fixed significant memory leak in annotation of files with long
24 histories.[9929bab702]
25 * Added warnings when a merge operation overwrites local copies
26 (UNDO is available, but previously this condition normally went
27 silently unnoticed). [39f979b08c]
28 * Improved performance when adding many files. [a369dc7721]
29 * Improve merges which contain many file renames. [0b93b0f958]
30 * Added protection against timing attacks. [d4a341b49d]
31 * Firefox now remembers filled fields when returning to forms. [3fac77d7b0]
32 * Added the --stats option to the rebuild command. [f25e5e53c4]
33 * RSS feed now passes validation. [ce354d0a9f]
34 * Show overridden user when entering commit comment. [ce354d0a9f]
35 * Made rebuilding from web interface silent. [ce354d0a9f]
36 * Now works on MSVC with repos >2GB. [6092935ff2]
37 * A number of code cleanups to resolve warnings from various compilers.
38 * Update the built-in SQLite to version 3.7.9 beta.
39
40 <h2>Changes For Version 1.19 (2011-09-02)</h2>
41
42 * Added a ./configure script based on autosetup.
43 * Added the "[/help/winsrv | fossil winsrv]" command
44 for creating a Fossil service on windows systems.
45

Keyboard Shortcuts

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