Fossil SCM

merge trunk

bch 2020-04-01 17:04 NULLSeparated merge
Commit 7da1aa6f3103ed961c331db4de4ca9d99616ee5354123510fc8d51ce34011f03
67 files changed +1 -1 +277 -120 +1 -1 +1 -1 +5 -3 +94 +45 -15 +28 -16 +11 -52 +2 -1 +163 -42 +1 -1 +1 -1 -1 +57 -76 +1 -2 +16 +3 -2 +3 -2 +13 -8 -9 +45 -11 +237 -52 +15 +15 +460 -270 +1 -1 +3 -3 +13 -1 +28 -3 +55 -27 +2 -2 +1 +14 -4 +17 -1 +17 -1 +17 -1 +2 +2 -2 +54 -1 +4 -4 +11 -3 +3 -1 +1 -1 +199 -134 +5 -6 +3 -3 +2 -2 +2 -1 +4 -1
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -1,10 +1,10 @@
11
<div class="header">
22
<div class="title"><h1>$<project_name></h1>$<title></div>
33
<div class="status"><th1>
44
if {[info exists login]} {
5
- html "$login — <a href='$home/login'>Logout</a>\n"
5
+ html "<a href='$home/login'>$login</a>\n"
66
} else {
77
html "<a href='$home/login'>Login</a>\n"
88
}
99
</th1></div>
1010
</div>
1111
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -1,10 +1,10 @@
1 <div class="header">
2 <div class="title"><h1>$<project_name></h1>$<title></div>
3 <div class="status"><th1>
4 if {[info exists login]} {
5 html "$login — <a href='$home/login'>Logout</a>\n"
6 } else {
7 html "<a href='$home/login'>Login</a>\n"
8 }
9 </th1></div>
10 </div>
11
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -1,10 +1,10 @@
1 <div class="header">
2 <div class="title"><h1>$<project_name></h1>$<title></div>
3 <div class="status"><th1>
4 if {[info exists login]} {
5 html "<a href='$home/login'>$login</a>\n"
6 } else {
7 html "<a href='$home/login'>Login</a>\n"
8 }
9 </th1></div>
10 </div>
11
+277 -120
--- src/alerts.c
+++ src/alerts.c
@@ -46,10 +46,11 @@
4646
@ --
4747
@ -- The ssub field is a string where each character indicates a particular
4848
@ -- type of event to subscribe to. Choices:
4949
@ -- a - Announcements
5050
@ -- c - Check-ins
51
+@ -- f - Forum posts
5152
@ -- t - Ticket changes
5253
@ -- w - Wiki changes
5354
@ -- Probably different codes will be added in the future. In the future
5455
@ -- we might also add a separate table that allows subscribing to email
5556
@ -- notifications for specific branches or tags or tickets.
@@ -114,11 +115,11 @@
114115
if( bOnlyIfEnabled
115116
&& fossil_strcmp(db_get("email-send-method",0),"off")==0
116117
){
117118
return; /* Don't create table for disabled email */
118119
}
119
- db_multi_exec(zAlertInit/*works-like:""*/);
120
+ db_exec_sql(zAlertInit);
120121
alert_triggers_enable();
121122
}else if( !db_table_has_column("repository","pending_alert","sentMod") ){
122123
db_multi_exec(
123124
"ALTER TABLE repository.pending_alert"
124125
" ADD COLUMN sentMod BOOLEAN DEFAULT false;"
@@ -183,11 +184,11 @@
183184
** is an administrator.
184185
*/
185186
void alert_submenu_common(void){
186187
if( g.perm.Admin ){
187188
if( fossil_strcmp(g.zPath,"subscribers") ){
188
- style_submenu_element("List Subscribers","%R/subscribers");
189
+ style_submenu_element("Subscribers","%R/subscribers");
189190
}
190191
if( fossil_strcmp(g.zPath,"subscribe") ){
191192
style_submenu_element("Add New Subscriber","%R/subscribe");
192193
}
193194
}
@@ -238,10 +239,17 @@
238239
@ This is URL used as the basename for hyperlinks included in
239240
@ email alert text. Omit the trailing "/".
240241
@ Suggested value: "%h(g.zBaseURL)"
241242
@ (Property: "email-url")</p>
242243
@ <hr>
244
+
245
+ entry_attribute("Administrator email address", 40, "email-admin",
246
+ "eadmin", "", 0);
247
+ @ <p>This is the email for the human administrator for the system.
248
+ @ Abuse and trouble reports and password reset requests are send here.
249
+ @ (Property: "email-admin")</p>
250
+ @ <hr>
243251
244252
entry_attribute("\"Return-Path\" email address", 20, "email-self",
245253
"eself", "", 0);
246254
@ <p><b>Required.</b>
247255
@ This is the email to which email notification bounces should be sent.
@@ -297,17 +305,10 @@
297305
@ append a colon and TCP port number (ex: smtp.example.com:587).
298306
@ The default TCP port number is 25.
299307
@ (Property: "email-send-relayhost")</p>
300308
@ <hr>
301309
302
- entry_attribute("Administrator email address", 40, "email-admin",
303
- "eadmin", "", 0);
304
- @ <p>This is the email for the human administrator for the system.
305
- @ Abuse and trouble reports are send here.
306
- @ (Property: "email-admin")</p>
307
- @ <hr>
308
-
309310
@ <p><input type="submit" name="submit" value="Apply Changes" /></p>
310311
@ </div></form>
311312
db_end_transaction(0);
312313
style_footer();
313314
}
@@ -574,22 +575,18 @@
574575
}
575576
return 0;
576577
}
577578
578579
/*
579
-** Make a copy of the input string up to but not including the
580
-** first cTerm character.
581
-**
582
-** Verify that the string really that is to be copied really is a
583
-** valid email address. If it is not, then return NULL.
584
-**
585
-** This routine is more restrictive than necessary. It does not
586
-** allow comments, IP address, quoted strings, or certain uncommon
587
-** characters. The only non-alphanumerics allowed in the local
588
-** part are "_", "+", "-" and "+".
589
-*/
590
-char *email_copy_addr(const char *z, char cTerm ){
580
+** Determine whether or not the input string is a valid email address.
581
+** Only look at character up to but not including the first \000 or
582
+** the first cTerm character, whichever comes first.
583
+**
584
+** Return the length of the email addresss string in bytes if the email
585
+** address is valid. If the email address is misformed, return 0.
586
+*/
587
+int email_address_is_valid(const char *z, char cTerm){
591588
int i;
592589
int nAt = 0;
593590
int nDot = 0;
594591
char c;
595592
if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
@@ -619,13 +616,28 @@
619616
}
620617
}
621618
if( c!=cTerm ) return 0; /* Missing terminator */
622619
if( nAt==0 ) return 0; /* No "@" found anywhere */
623620
if( nDot==0 ) return 0; /* No "." in the domain */
621
+ return i;
622
+}
624623
625
- /* If we reach this point, the email address is valid */
626
- return mprintf("%.*s", i, z);
624
+/*
625
+** Make a copy of the input string up to but not including the
626
+** first cTerm character.
627
+**
628
+** Verify that the string really that is to be copied really is a
629
+** valid email address. If it is not, then return NULL.
630
+**
631
+** This routine is more restrictive than necessary. It does not
632
+** allow comments, IP address, quoted strings, or certain uncommon
633
+** characters. The only non-alphanumerics allowed in the local
634
+** part are "_", "+", "-" and "+".
635
+*/
636
+char *email_copy_addr(const char *z, char cTerm ){
637
+ int i = email_address_is_valid(z, cTerm);
638
+ return i==0 ? 0 : mprintf("%.*s", i, z);
627639
}
628640
629641
/*
630642
** Scan the input string for a valid email address enclosed in <...>
631643
** If the string contains one or more email addresses, extract the first
@@ -659,10 +671,38 @@
659671
char *zOut = alert_find_emailaddr(zIn);
660672
if( zOut ){
661673
sqlite3_result_text(context, zOut, -1, fossil_free);
662674
}
663675
}
676
+
677
+/*
678
+** SQL function: display_name(X)
679
+**
680
+** If X is a string, search for a user name at the beginning of that
681
+** string. The user name must be followed by an email address. If
682
+** found, return the user name. If not found, return NULL.
683
+**
684
+** This routine is used to extract the display name from the USER.INFO
685
+** field.
686
+*/
687
+void alert_display_name_func(
688
+ sqlite3_context *context,
689
+ int argc,
690
+ sqlite3_value **argv
691
+){
692
+ const char *zIn = (const char*)sqlite3_value_text(argv[0]);
693
+ int i;
694
+ if( zIn==0 ) return;
695
+ while( fossil_isspace(zIn[0]) ) zIn++;
696
+ for(i=0; zIn[i] && zIn[i]!='<' && zIn[i]!='\n'; i++){}
697
+ if( zIn[i]=='<' ){
698
+ while( i>0 && fossil_isspace(zIn[i-1]) ){ i--; }
699
+ if( i>0 ){
700
+ sqlite3_result_text(context, zIn, i, SQLITE_TRANSIENT);
701
+ }
702
+ }
703
+}
664704
665705
/*
666706
** Return the hostname portion of an email address - the part following
667707
** the @
668708
*/
@@ -762,11 +802,11 @@
762802
** Date:
763803
** Message-Id:
764804
** Content-Type:
765805
** Content-Transfer-Encoding:
766806
** MIME-Version:
767
-** X-Fossil-From:
807
+** Sender:
768808
**
769809
** The caller maintains ownership of the input Blobs. This routine will
770810
** read the Blobs and send them onward to the email system, but it will
771811
** not free them.
772812
**
@@ -774,14 +814,14 @@
774814
** in the pHdr parameter.
775815
**
776816
** If the zFromName argument is not NULL, then it should be a human-readable
777817
** name or handle for the sender. In that case, "From:" becomes a made-up
778818
** email address based on a hash of zFromName and the domain of email-self,
779
-** and an additional "X-Fossil-From:" field is inserted with the email-self
780
-** address. Downstream software might use the X-Fossil-From header to set
819
+** and an additional "Sender:" field is inserted with the email-self
820
+** address. Downstream software might use the Sender header to set
781821
** the envelope-from address of the email. If zFromName is a NULL pointer,
782
-** then the "From:" is set to the email-self value and X-Fossil-From is
822
+** then the "From:" is set to the email-self value and Sender is
783823
** omitted.
784824
*/
785825
void alert_send(
786826
AlertSender *p, /* Emailer context */
787827
Blob *pHdr, /* Email header (incomplete) */
@@ -794,26 +834,26 @@
794834
fossil_print("Sending email\n");
795835
}
796836
if( fossil_strcmp(p->zDest, "off")==0 ){
797837
return;
798838
}
839
+ blob_init(&all, 0, 0);
799840
if( fossil_strcmp(p->zDest, "blob")==0 ){
800841
pOut = &p->out;
801842
if( blob_size(pOut) ){
802843
blob_appendf(pOut, "%.72c\n", '=');
803844
}
804845
}else{
805
- blob_init(&all, 0, 0);
806846
pOut = &all;
807847
}
808848
blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
809849
if( p->zFrom==0 || p->zFrom[0]==0 ){
810850
return; /* email-self is not set. Error will be reported separately */
811851
}else if( zFromName ){
812852
blob_appendf(pOut, "From: %s <%s@%s>\r\n",
813853
zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
814
- blob_appendf(pOut, "X-Fossil-From: <%s>\r\n", p->zFrom);
854
+ blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
815855
}else{
816856
blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
817857
}
818858
blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
819859
if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
@@ -879,10 +919,26 @@
879919
fossil_print("%s", blob_str(&all));
880920
}
881921
blob_reset(&all);
882922
}
883923
924
+/*
925
+** SETTING: email-url width=40
926
+** This URL is used as the basename for hyperlinks included in email alert
927
+** text. Omit the trailing "/".
928
+*/
929
+/*
930
+** SETTING: email-admin width=40
931
+** This is the email address for the human administrator for the system.
932
+** Abuse and trouble reports and password reset requests are send here.
933
+*/
934
+/*
935
+** SETTING: email-subname width=16
936
+** This is a short name used to identifies the repository in the Subject:
937
+** line of email alerts. Traditionally this name is included in square
938
+** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939
+*/
884940
/*
885941
** SETTING: email-send-method width=5 default=off
886942
** Determine the method used to send email. Allowed values are
887943
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
888944
** means no email is ever sent. The "relay" value means emails are sent
@@ -1259,11 +1315,11 @@
12591315
** page. To allow anonymous passers-by to sign up for email
12601316
** notification, set Email-Alerts on user "nobody" or "anonymous".
12611317
*/
12621318
void subscribe_page(void){
12631319
int needCaptcha;
1264
- unsigned int uSeed;
1320
+ unsigned int uSeed = 0;
12651321
const char *zDecoded;
12661322
char *zCaptcha = 0;
12671323
char *zErr = 0;
12681324
int eErr = 0;
12691325
int di;
@@ -1392,10 +1448,11 @@
13921448
zDecoded = captcha_decode(uSeed);
13931449
zCaptcha = captcha_render(zDecoded);
13941450
@ <tr>
13951451
@ <td class="form_label">Security Code:</td>
13961452
@ <td><input type="text" name="captcha" value="" size="30">
1453
+ captcha_speakit_button(uSeed, "Speak the code");
13971454
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
13981455
@ </tr>
13991456
if( eErr==2 ){
14001457
@ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
14011458
}
@@ -1461,11 +1518,11 @@
14611518
@ </table>
14621519
if( needCaptcha ){
14631520
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
14641521
@ %h(zCaptcha)
14651522
@ </pre>
1466
- @ Enter the 8 characters above in the "Security Code" box
1523
+ @ Enter the 8 characters above in the "Security Code" box<br/>
14671524
@ </td></tr></table></div>
14681525
}
14691526
@ </form>
14701527
fossil_free(zErr);
14711528
style_footer();
@@ -1511,16 +1568,17 @@
15111568
** to know the subscriber code.
15121569
*/
15131570
void alert_page(void){
15141571
const char *zName = P("name");
15151572
Stmt q;
1516
- int sa, sc, sf, st, sw;
1517
- int sdigest, sdonotcall, sverified;
1518
- const char *ssub;
1519
- const char *semail;
1573
+ int sa, sc, sf, st, sw, sx;
1574
+ int sdigest = 0, sdonotcall = 0, sverified = 0;
1575
+ int isLogin; /* Logged in as an individual */
1576
+ const char *ssub = 0;
1577
+ const char *semail = 0;
15201578
const char *smip;
1521
- const char *suname;
1579
+ const char *suname = 0;
15221580
const char *mtime;
15231581
const char *sctime;
15241582
int eErr = 0;
15251583
char *zErr = 0;
15261584
@@ -1528,68 +1586,71 @@
15281586
login_check_credentials();
15291587
if( !g.perm.EmailAlert ){
15301588
login_needed(g.anon.EmailAlert);
15311589
return;
15321590
}
1533
- if( zName==0 && login_is_individual() ){
1591
+ isLogin = login_is_individual();
1592
+ if( zName==0 && isLogin ){
15341593
zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
15351594
" WHERE suname=%Q", g.zLogin);
15361595
}
15371596
if( zName==0 || !validate16(zName, -1) ){
15381597
cgi_redirect("subscribe");
15391598
return;
15401599
}
15411600
alert_submenu_common();
15421601
if( P("submit")!=0 && cgi_csrf_safe(1) ){
1543
- int sdonotcall = PB("sdonotcall");
1544
- int sdigest = PB("sdigest");
1545
- char ssub[10];
1546
- int nsub = 0;
1547
- if( PB("sa") ) ssub[nsub++] = 'a';
1548
- if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
1549
- if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
1550
- if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
1551
- if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1552
- ssub[nsub] = 0;
1553
- if( g.perm.Admin ){
1554
- const char *suname = PT("suname");
1555
- int sverified = PB("sverified");
1556
- if( suname && suname[0]==0 ) suname = 0;
1557
- db_multi_exec(
1558
- "UPDATE subscriber SET"
1559
- " sdonotcall=%d,"
1560
- " sdigest=%d,"
1561
- " ssub=%Q,"
1562
- " mtime=strftime('%%s','now'),"
1563
- " smip=%Q,"
1564
- " suname=%Q,"
1565
- " sverified=%d"
1566
- " WHERE subscriberCode=hextoblob(%Q)",
1567
- sdonotcall,
1568
- sdigest,
1569
- ssub,
1570
- g.zIpAddr,
1571
- suname,
1572
- sverified,
1573
- zName
1574
- );
1575
- }else{
1576
- db_multi_exec(
1577
- "UPDATE subscriber SET"
1578
- " sdonotcall=%d,"
1579
- " sdigest=%d,"
1580
- " ssub=%Q,"
1581
- " mtime=strftime('%%s','now'),"
1582
- " smip=%Q"
1583
- " WHERE subscriberCode=hextoblob(%Q)",
1584
- sdonotcall,
1585
- sdigest,
1586
- ssub,
1587
- g.zIpAddr,
1588
- zName
1589
- );
1590
- }
1602
+ char newSsub[10];
1603
+ int nsub = 0;
1604
+ Blob update;
1605
+
1606
+ sdonotcall = PB("sdonotcall");
1607
+ sdigest = PB("sdigest");
1608
+ semail = P("semail");
1609
+ if( PB("sa") ) newSsub[nsub++] = 'a';
1610
+ if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c';
1611
+ if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f';
1612
+ if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't';
1613
+ if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w';
1614
+ if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x';
1615
+ newSsub[nsub] = 0;
1616
+ ssub = newSsub;
1617
+ blob_init(&update, "UPDATE subscriber SET", -1);
1618
+ blob_append_sql(&update,
1619
+ " sdonotcall=%d,"
1620
+ " sdigest=%d,"
1621
+ " ssub=%Q,"
1622
+ " mtime=strftime('%%s','now'),"
1623
+ " smip=%Q",
1624
+ sdonotcall,
1625
+ sdigest,
1626
+ ssub,
1627
+ g.zIpAddr
1628
+ );
1629
+ if( g.perm.Admin ){
1630
+ suname = PT("suname");
1631
+ sverified = PB("sverified");
1632
+ if( suname && suname[0]==0 ) suname = 0;
1633
+ blob_append_sql(&update,
1634
+ ", suname=%Q,"
1635
+ " sverified=%d",
1636
+ suname,
1637
+ sverified
1638
+ );
1639
+ }
1640
+ if( isLogin ){
1641
+ if( semail==0 || email_address_is_valid(semail,0)==0 ){
1642
+ eErr = 8;
1643
+ }
1644
+ blob_append_sql(&update, ", semail=%Q", semail);
1645
+ }
1646
+ blob_append_sql(&update," WHERE subscriberCode=hextoblob(%Q)", zName);
1647
+ if( eErr==0 ){
1648
+ db_exec_sql(blob_str(&update));
1649
+ ssub = 0;
1650
+ }
1651
+ blob_reset(&update);
15911652
}
15921653
if( P("delete")!=0 && cgi_csrf_safe(1) ){
15931654
if( !PB("dodelete") ){
15941655
eErr = 9;
15951656
zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
@@ -1597,10 +1658,11 @@
15971658
}else{
15981659
alert_unsubscribe(zName);
15991660
return;
16001661
}
16011662
}
1663
+ style_header("Update Subscription");
16021664
db_prepare(&q,
16031665
"SELECT"
16041666
" semail," /* 0 */
16051667
" sverified," /* 1 */
16061668
" sdonotcall," /* 2 */
@@ -1614,23 +1676,27 @@
16141676
if( db_step(&q)!=SQLITE_ROW ){
16151677
db_finalize(&q);
16161678
cgi_redirect("subscribe");
16171679
return;
16181680
}
1619
- style_header("Update Subscription");
1620
- semail = db_column_text(&q, 0);
1621
- sverified = db_column_int(&q, 1);
1622
- sdonotcall = db_column_int(&q, 2);
1623
- sdigest = db_column_int(&q, 3);
1624
- ssub = db_column_text(&q, 4);
1681
+ if( ssub==0 ){
1682
+ semail = db_column_text(&q, 0);
1683
+ sdonotcall = db_column_int(&q, 2);
1684
+ sdigest = db_column_int(&q, 3);
1685
+ ssub = db_column_text(&q, 4);
1686
+ }
1687
+ if( suname==0 ){
1688
+ suname = db_column_text(&q, 6);
1689
+ sverified = db_column_int(&q, 1);
1690
+ }
16251691
sa = strchr(ssub,'a')!=0;
16261692
sc = strchr(ssub,'c')!=0;
16271693
sf = strchr(ssub,'f')!=0;
16281694
st = strchr(ssub,'t')!=0;
16291695
sw = strchr(ssub,'w')!=0;
1696
+ sx = strchr(ssub,'x')!=0;
16301697
smip = db_column_text(&q, 5);
1631
- suname = db_column_text(&q, 6);
16321698
mtime = db_column_text(&q, 7);
16331699
sctime = db_column_text(&q, 8);
16341700
if( !g.perm.Admin && !sverified ){
16351701
db_multi_exec(
16361702
"UPDATE subscriber SET sverified=1 WHERE subscriberCode=hextoblob(%Q)",
@@ -1646,13 +1712,25 @@
16461712
form_begin(0, "%R/alerts");
16471713
@ <input type="hidden" name="name" value="%h(zName)">
16481714
@ <table class="subscribe">
16491715
@ <tr>
16501716
@ <td class="form_label">Email&nbsp;Address:</td>
1651
- @ <td>%h(semail)</td>
1717
+ if( isLogin ){
1718
+ @ <td><input type="text" name="semail" value="%h(semail)" size="30">\
1719
+ if( eErr==8 ){
1720
+ @ <span class='loginError'>&larr; not a valid email address!</span>
1721
+ }else if( g.perm.Admin ){
1722
+ @ &nbsp;&nbsp;<a href="%R/announce?to=%t(semail)">\
1723
+ @ (Send a message to %h(semail))</a>\
1724
+ }
1725
+ @ </td>
1726
+ }else{
1727
+ @ <td>%h(semail)</td>
1728
+ }
16521729
@ </tr>
16531730
if( g.perm.Admin ){
1731
+ int uid;
16541732
@ <tr>
16551733
@ <td class='form_label'>Created:</td>
16561734
@ <td>%h(sctime)</td>
16571735
@ </tr>
16581736
@ <tr>
@@ -1664,11 +1742,16 @@
16641742
@ <td>%h(smip)</td>
16651743
@ </tr>
16661744
@ <tr>
16671745
@ <td class="form_label">User:</td>
16681746
@ <td><input type="text" name="suname" value="%h(suname?suname:"")" \
1669
- @ size="30"></td>
1747
+ @ size="30">\
1748
+ uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname);
1749
+ if( uid ){
1750
+ @ &nbsp;&nbsp;<a href='%R/setup_uedit?id=%d(uid)'>\
1751
+ @ (login info for %h(suname))</a>\
1752
+ }
16701753
@ </tr>
16711754
}
16721755
@ <tr>
16731756
@ <td class="form_label">Topics:</td>
16741757
@ <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\
@@ -1678,10 +1761,12 @@
16781761
@ Check-ins</label><br>
16791762
}
16801763
if( g.perm.RdForum ){
16811764
@ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\
16821765
@ Forum Posts</label><br>
1766
+ @ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\
1767
+ @ Forum Edits</label><br>
16831768
}
16841769
if( g.perm.RdTkt ){
16851770
@ <label><input type="checkbox" name="st" %s(st?"checked":"")>\
16861771
@ Ticket changes</label><br>
16871772
}
@@ -1703,11 +1788,11 @@
17031788
#endif
17041789
if( g.perm.Admin ){
17051790
@ <tr>
17061791
@ <td class="form_label">Admin Options:</td><td>
17071792
@ <label><input type="checkbox" name="sdonotcall" \
1708
- @ %s(sdonotcall?"checked":"")> Do not call</label><br>
1793
+ @ %s(sdonotcall?"checked":"")> Do not disturb</label><br>
17091794
@ <label><input type="checkbox" name="sverified" \
17101795
@ %s(sverified?"checked":"")>\
17111796
@ Verified</label></td></tr>
17121797
}
17131798
if( eErr==9 ){
@@ -1760,11 +1845,11 @@
17601845
*/
17611846
void unsubscribe_page(void){
17621847
const char *zName = P("name");
17631848
char *zErr = 0;
17641849
int eErr = 0;
1765
- unsigned int uSeed;
1850
+ unsigned int uSeed = 0;
17661851
const char *zDecoded;
17671852
char *zCaptcha = 0;
17681853
int dx;
17691854
int bSubmit;
17701855
const char *zEAddr;
@@ -1855,10 +1940,11 @@
18551940
zDecoded = captcha_decode(uSeed);
18561941
zCaptcha = captcha_render(zDecoded);
18571942
@ <tr>
18581943
@ <td class="form_label">Security Code:</td>
18591944
@ <td><input type="text" name="captcha" value="" size="30">
1945
+ captcha_speakit_button(uSeed, "Speak the code");
18601946
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
18611947
if( eErr==2 ){
18621948
@ <td><span class="loginError">&larr; %h(zErr)</span></td>
18631949
}
18641950
@ </tr>
@@ -1874,11 +1960,11 @@
18741960
@ </tr>
18751961
@ </table>
18761962
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
18771963
@ %h(zCaptcha)
18781964
@ </pre>
1879
- @ Enter the 8 characters above in the "Security Code" box
1965
+ @ Enter the 8 characters above in the "Security Code" box<br/>
18801966
@ </td></tr></table></div>
18811967
@ </form>
18821968
fossil_free(zErr);
18831969
style_footer();
18841970
}
@@ -1885,37 +1971,69 @@
18851971
18861972
/*
18871973
** WEBPAGE: subscribers
18881974
**
18891975
** This page, accessible to administrators only,
1890
-** shows a list of email notification email addresses.
1976
+** shows a list of subscriber email addresses.
18911977
** Clicking on an email takes one to the /alerts page
18921978
** for that email where the delivery settings can be
18931979
** modified.
18941980
*/
18951981
void subscriber_list_page(void){
18961982
Blob sql;
18971983
Stmt q;
18981984
sqlite3_int64 iNow;
1985
+ int nTotal;
1986
+ int nPending;
1987
+ int nDel = 0;
18991988
if( alert_webpages_disabled() ) return;
19001989
login_check_credentials();
19011990
if( !g.perm.Admin ){
19021991
login_needed(0);
19031992
return;
19041993
}
19051994
alert_submenu_common();
1995
+ style_submenu_element("Users","setup_ulist");
19061996
style_header("Subscriber List");
1997
+ nTotal = db_int(0, "SELECT count(*) FROM subscriber");
1998
+ nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified");
1999
+ if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){
2000
+ int nNewPending;
2001
+ db_multi_exec(
2002
+ "DELETE FROM subscriber"
2003
+ " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')"
2004
+ );
2005
+ nNewPending = db_int(0, "SELECT count(*) FROM subscriber"
2006
+ " WHERE NOT sverified");
2007
+ nDel = nPending - nNewPending;
2008
+ nPending = nNewPending;
2009
+ nTotal -= nDel;
2010
+ }
2011
+ if( nPending>0 ){
2012
+ @ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1>
2013
+ if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber"
2014
+ " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')")
2015
+ ){
2016
+ style_submenu_element("Purge Pending","subscribers?purge");
2017
+ }
2018
+ }else{
2019
+ @ <h1>%,d(nTotal) Subscribers</h1>
2020
+ }
2021
+ if( nDel>0 ){
2022
+ @ <p>*** %d(nDel) pending subscriptions deleted ***</p>
2023
+ }
19072024
blob_init(&sql, 0, 0);
19082025
blob_append_sql(&sql,
19092026
"SELECT hex(subscriberCode)," /* 0 */
19102027
" semail," /* 1 */
19112028
" ssub," /* 2 */
19122029
" suname," /* 3 */
19132030
" sverified," /* 4 */
19142031
" sdigest," /* 5 */
19152032
" mtime," /* 6 */
1916
- " date(sctime,'unixepoch')" /* 7 */
2033
+ " date(sctime,'unixepoch')," /* 7 */
2034
+ " (SELECT uid FROM user WHERE login=subscriber.suname)" /* 8 */
19172035
" FROM subscriber"
19182036
);
19192037
if( P("only")!=0 ){
19202038
blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
19212039
style_submenu_element("Show All","%R/subscribers");
@@ -1937,16 +2055,22 @@
19372055
@ </tr>
19382056
@ </thead><tbody>
19392057
while( db_step(&q)==SQLITE_ROW ){
19402058
sqlite3_int64 iMtime = db_column_int64(&q, 6);
19412059
double rAge = (iNow - iMtime)/86400.0;
2060
+ int uid = db_column_int(&q, 8);
2061
+ const char *zUname = db_column_text(&q, 3);
19422062
@ <tr>
19432063
@ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\
19442064
@ %h(db_column_text(&q,1))</a></td>
19452065
@ <td>%h(db_column_text(&q,2))</td>
19462066
@ <td>%s(db_column_int(&q,5)?"digest":"")</td>
1947
- @ <td>%h(db_column_text(&q,3))</td>
2067
+ if( uid ){
2068
+ @ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a>
2069
+ }else{
2070
+ @ <td>%h(zUname)</td>
2071
+ }
19482072
@ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
19492073
@ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
19502074
@ <td>%h(db_column_text(&q,7))</td>
19512075
@ </tr>
19522076
}
@@ -1958,13 +2082,21 @@
19582082
19592083
#if LOCAL_INTERFACE
19602084
/*
19612085
** A single event that might appear in an alert is recorded as an
19622086
** instance of the following object.
2087
+**
2088
+** type values:
2089
+**
2090
+** c A new check-in
2091
+** f An original forum post
2092
+** x An edit to a prior forum post
2093
+** t A new ticket or a change to an existing ticket
2094
+** w A change to a wiki page
19632095
*/
19642096
struct EmailEvent {
1965
- int type; /* 'c', 'f', 'm', 't', 'w' */
2097
+ int type; /* 'c', 'f', 't', 'w', 'x' */
19662098
int needMod; /* Pending moderator approval */
19672099
Blob hdr; /* Header content, for forum entries */
19682100
Blob txt; /* Text description to appear in an alert */
19692101
char *zFromName; /* Human name of the sender */
19702102
EmailEvent *pNext; /* Next in chronological order */
@@ -2039,20 +2171,20 @@
20392171
p->needMod = db_column_int(&q, 4);
20402172
p->zFromName = 0;
20412173
p->pNext = 0;
20422174
switch( p->type ){
20432175
case 'c': zType = "Check-In"; break;
2044
- case 'f': zType = "Forum post"; break;
2176
+ /* case 'f': -- forum posts omitted from this loop. See below */
20452177
case 't': zType = "Ticket Change"; break;
20462178
case 'w': zType = "Wiki Edit"; break;
20472179
}
20482180
blob_init(&p->hdr, 0, 0);
20492181
blob_init(&p->txt, 0, 0);
20502182
blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n",
20512183
db_column_text(&q,1),
20522184
zType,
2053
- db_column_text(&q,2),
2185
+ db_column_text(&q, 2),
20542186
zUrl,
20552187
db_column_text(&q,0)
20562188
);
20572189
if( p->needMod ){
20582190
blob_appendf(&p->txt,
@@ -2084,11 +2216,12 @@
20842216
" (SELECT uuid FROM blob WHERE rid=forumpost.fpid)," /* 1 */
20852217
" datetime(event.mtime)," /* 2 */
20862218
" substr(comment,instr(comment,':')+2)," /* 3 */
20872219
" (SELECT uuid FROM blob WHERE rid=forumpost.firt)," /* 4 */
20882220
" wantalert.needMod," /* 5 */
2089
- " coalesce(trim(substr(info,1,instr(info,'<')-1)),euser,user)" /* 6 */
2221
+ " coalesce(display_name(info),euser,user)," /* 6 */
2222
+ " forumpost.fprev IS NULL" /* 7 */
20902223
" FROM temp.wantalert, event, forumpost"
20912224
" LEFT JOIN user ON (login=coalesce(euser,user))"
20922225
" WHERE event.objid=substr(wantalert.eventId,2)+0"
20932226
" AND eventId GLOB 'f*'"
20942227
" AND forumpost.fpid=event.objid"
@@ -2104,11 +2237,11 @@
21042237
const char *z;
21052238
if( pPost==0 ) continue;
21062239
p = fossil_malloc( sizeof(EmailEvent) );
21072240
pLast->pNext = p;
21082241
pLast = p;
2109
- p->type = 'f';
2242
+ p->type = db_column_int(&q,7) ? 'f' : 'x';
21102243
p->needMod = db_column_int(&q, 5);
21112244
z = db_column_text(&q,6);
21122245
p->zFromName = z && z[0] ? fossil_strdup(z) : 0;
21132246
p->pNext = 0;
21142247
blob_init(&p->hdr, 0, 0);
@@ -2436,13 +2569,13 @@
24362569
** if the recipient is a moderator for that type of event. Setup
24372570
** and Admin users always get notified. */
24382571
char xType = '*';
24392572
if( strpbrk(zCap,"as")==0 ){
24402573
switch( p->type ){
2441
- case 'f': xType = '5'; break;
2442
- case 't': xType = 'q'; break;
2443
- case 'w': xType = 'l'; break;
2574
+ case 'x': case 'f': xType = '5'; break;
2575
+ case 't': xType = 'q'; break;
2576
+ case 'w': xType = 'l'; break;
24442577
}
24452578
if( strchr(zCap,xType)==0 ) continue;
24462579
}
24472580
}else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
24482581
/* Setup and admin users can get any notification that does not
@@ -2450,14 +2583,14 @@
24502583
}else{
24512584
/* Other users only see the alert if they have sufficient
24522585
** privilege to view the event itself */
24532586
char xType = '*';
24542587
switch( p->type ){
2455
- case 'c': xType = 'o'; break;
2456
- case 'f': xType = '2'; break;
2457
- case 't': xType = 'r'; break;
2458
- case 'w': xType = 'j'; break;
2588
+ case 'c': xType = 'o'; break;
2589
+ case 'x': case 'f': xType = '2'; break;
2590
+ case 't': xType = 'r'; break;
2591
+ case 'w': xType = 'j'; break;
24592592
}
24602593
if( strchr(zCap,xType)==0 ) continue;
24612594
}
24622595
if( blob_size(&p->hdr)>0 ){
24632596
/* This alert should be sent as a separate email */
@@ -2594,10 +2727,11 @@
25942727
@ <table class="subscribe">
25952728
if( zCaptcha ){
25962729
@ <tr>
25972730
@ <td class="form_label">Security&nbsp;Code:</td>
25982731
@ <td><input type="text" name="captcha" value="" size="10">
2732
+ captcha_speakit_button(uSeed, "Speak the code");
25992733
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
26002734
@ </tr>
26012735
}
26022736
@ <tr>
26032737
@ <td class="form_label">Your&nbsp;Email&nbsp;Address:</td>
@@ -2620,11 +2754,11 @@
26202754
@ </table>
26212755
if( zCaptcha ){
26222756
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
26232757
@ %h(zCaptcha)
26242758
@ </pre>
2625
- @ Enter the 8 characters above in the "Security Code" box
2759
+ @ Enter the 8 characters above in the "Security Code" box<br/>
26262760
@ </td></tr></table></div>
26272761
}
26282762
@ </form>
26292763
style_footer();
26302764
}
@@ -2638,10 +2772,11 @@
26382772
char *zErr;
26392773
const char *zTo = PT("to");
26402774
char *zSubject = PT("subject");
26412775
int bAll = PB("all");
26422776
int bAA = PB("aa");
2777
+ int bMods = PB("mods");
26432778
const char *zSub = db_get("email-subname", "[Fossil Repo]");
26442779
int bTest2 = fossil_strcmp(P("name"),"test2")==0;
26452780
Blob hdr, body;
26462781
blob_init(&body, 0, 0);
26472782
blob_init(&hdr, 0, 0);
@@ -2649,17 +2784,29 @@
26492784
pSender = alert_sender_new(bTest2 ? "blob" : 0, 0);
26502785
if( zTo[0] ){
26512786
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
26522787
alert_send(pSender, &hdr, &body, 0);
26532788
}
2654
- if( bAll || bAA ){
2789
+ if( bAll || bAA || bMods ){
26552790
Stmt q;
26562791
int nUsed = blob_size(&body);
26572792
const char *zURL = db_get("email-url",0);
2658
- db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
2659
- " WHERE sverified AND NOT sdonotcall %s",
2660
- bAll ? "" : " AND ssub LIKE '%a%'");
2793
+ if( bAll ){
2794
+ db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
2795
+ " WHERE sverified AND NOT sdonotcall");
2796
+ }else if( bAA ){
2797
+ db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
2798
+ " WHERE sverified AND NOT sdonotcall"
2799
+ " AND ssub LIKE '%%a%%'");
2800
+ }else if( bMods ){
2801
+ db_prepare(&q,
2802
+ "SELECT semail, hex(subscriberCode)"
2803
+ " FROM subscriber, user "
2804
+ " WHERE sverified AND NOT sdonotcall"
2805
+ " AND suname=login"
2806
+ " AND fullcap(cap) GLOB '*5*'");
2807
+ }
26612808
while( db_step(&q)==SQLITE_ROW ){
26622809
const char *zCode = db_column_text(&q, 1);
26632810
zTo = db_column_text(&q, 0);
26642811
blob_truncate(&hdr, 0);
26652812
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
@@ -2716,11 +2863,12 @@
27162863
@ <p>The following error was reported by the system:
27172864
@ <blockquote><pre>
27182865
@ %h(zErr)
27192866
@ </pre></blockquote>
27202867
}else{
2721
- @ <p>The announcement has been sent.</p>
2868
+ @ <p>The announcement has been sent.
2869
+ @ <a href="%h(PD("REQUEST_URI","/"))">Send another</a></p>
27222870
}
27232871
style_footer();
27242872
return;
27252873
} else if( !alert_enabled() ){
27262874
style_header("Cannot Send Announcement");
@@ -2734,21 +2882,26 @@
27342882
@ <form method="POST">
27352883
@ <table class="subscribe">
27362884
if( g.perm.Admin ){
27372885
int aa = PB("aa");
27382886
int all = PB("all");
2887
+ int aMod = PB("mods");
27392888
const char *aack = aa ? "checked" : "";
27402889
const char *allck = all ? "checked" : "";
2890
+ const char *modck = aMod ? "checked" : "";
27412891
@ <tr>
27422892
@ <td class="form_label">To:</td>
27432893
@ <td><input type="text" name="to" value="%h(PT("to"))" size="30"><br>
27442894
@ <label><input type="checkbox" name="aa" %s(aack)> \
27452895
@ All "announcement" subscribers</label> \
27462896
@ <a href="%R/subscribers?only=a" target="_blank">(list)</a><br>
27472897
@ <label><input type="checkbox" name="all" %s(allck)> \
27482898
@ All subscribers</label> \
2749
- @ <a href="%R/subscribers" target="_blank">(list)</a><br></td>
2899
+ @ <a href="%R/subscribers" target="_blank">(list)</a><br>
2900
+ @ <label><input type="checkbox" name="mods" %s(modck)> \
2901
+ @ All moderators</label> \
2902
+ @ <a href="%R/setup_ulist?with=5" target="_blank">(list)</a><br></td>
27502903
@ </tr>
27512904
}
27522905
@ <tr>
27532906
@ <td class="form_label">Subject:</td>
27542907
@ <td><input type="text" name="subject" value="%h(PT("subject"))"\
@@ -2759,11 +2912,15 @@
27592912
@ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\
27602913
@ %h(PT("msg"))</textarea>
27612914
@ </tr>
27622915
@ <tr>
27632916
@ <td></td>
2764
- @ <td><input type="submit" name="submit" value="Send Message">
2917
+ if( fossil_strcmp(P("name"),"test2")==0 ){
2918
+ @ <td><input type="submit" name="submit" value="Dry Run">
2919
+ }else{
2920
+ @ <td><input type="submit" name="submit" value="Send Message">
2921
+ }
27652922
@ </tr>
27662923
@ </table>
27672924
@ </form>
27682925
style_footer();
27692926
}
27702927
--- src/alerts.c
+++ src/alerts.c
@@ -46,10 +46,11 @@
46 @ --
47 @ -- The ssub field is a string where each character indicates a particular
48 @ -- type of event to subscribe to. Choices:
49 @ -- a - Announcements
50 @ -- c - Check-ins
 
51 @ -- t - Ticket changes
52 @ -- w - Wiki changes
53 @ -- Probably different codes will be added in the future. In the future
54 @ -- we might also add a separate table that allows subscribing to email
55 @ -- notifications for specific branches or tags or tickets.
@@ -114,11 +115,11 @@
114 if( bOnlyIfEnabled
115 && fossil_strcmp(db_get("email-send-method",0),"off")==0
116 ){
117 return; /* Don't create table for disabled email */
118 }
119 db_multi_exec(zAlertInit/*works-like:""*/);
120 alert_triggers_enable();
121 }else if( !db_table_has_column("repository","pending_alert","sentMod") ){
122 db_multi_exec(
123 "ALTER TABLE repository.pending_alert"
124 " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
@@ -183,11 +184,11 @@
183 ** is an administrator.
184 */
185 void alert_submenu_common(void){
186 if( g.perm.Admin ){
187 if( fossil_strcmp(g.zPath,"subscribers") ){
188 style_submenu_element("List Subscribers","%R/subscribers");
189 }
190 if( fossil_strcmp(g.zPath,"subscribe") ){
191 style_submenu_element("Add New Subscriber","%R/subscribe");
192 }
193 }
@@ -238,10 +239,17 @@
238 @ This is URL used as the basename for hyperlinks included in
239 @ email alert text. Omit the trailing "/".
240 @ Suggested value: "%h(g.zBaseURL)"
241 @ (Property: "email-url")</p>
242 @ <hr>
 
 
 
 
 
 
 
243
244 entry_attribute("\"Return-Path\" email address", 20, "email-self",
245 "eself", "", 0);
246 @ <p><b>Required.</b>
247 @ This is the email to which email notification bounces should be sent.
@@ -297,17 +305,10 @@
297 @ append a colon and TCP port number (ex: smtp.example.com:587).
298 @ The default TCP port number is 25.
299 @ (Property: "email-send-relayhost")</p>
300 @ <hr>
301
302 entry_attribute("Administrator email address", 40, "email-admin",
303 "eadmin", "", 0);
304 @ <p>This is the email for the human administrator for the system.
305 @ Abuse and trouble reports are send here.
306 @ (Property: "email-admin")</p>
307 @ <hr>
308
309 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
310 @ </div></form>
311 db_end_transaction(0);
312 style_footer();
313 }
@@ -574,22 +575,18 @@
574 }
575 return 0;
576 }
577
578 /*
579 ** Make a copy of the input string up to but not including the
580 ** first cTerm character.
581 **
582 ** Verify that the string really that is to be copied really is a
583 ** valid email address. If it is not, then return NULL.
584 **
585 ** This routine is more restrictive than necessary. It does not
586 ** allow comments, IP address, quoted strings, or certain uncommon
587 ** characters. The only non-alphanumerics allowed in the local
588 ** part are "_", "+", "-" and "+".
589 */
590 char *email_copy_addr(const char *z, char cTerm ){
591 int i;
592 int nAt = 0;
593 int nDot = 0;
594 char c;
595 if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
@@ -619,13 +616,28 @@
619 }
620 }
621 if( c!=cTerm ) return 0; /* Missing terminator */
622 if( nAt==0 ) return 0; /* No "@" found anywhere */
623 if( nDot==0 ) return 0; /* No "." in the domain */
 
 
624
625 /* If we reach this point, the email address is valid */
626 return mprintf("%.*s", i, z);
 
 
 
 
 
 
 
 
 
 
 
 
 
627 }
628
629 /*
630 ** Scan the input string for a valid email address enclosed in <...>
631 ** If the string contains one or more email addresses, extract the first
@@ -659,10 +671,38 @@
659 char *zOut = alert_find_emailaddr(zIn);
660 if( zOut ){
661 sqlite3_result_text(context, zOut, -1, fossil_free);
662 }
663 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
665 /*
666 ** Return the hostname portion of an email address - the part following
667 ** the @
668 */
@@ -762,11 +802,11 @@
762 ** Date:
763 ** Message-Id:
764 ** Content-Type:
765 ** Content-Transfer-Encoding:
766 ** MIME-Version:
767 ** X-Fossil-From:
768 **
769 ** The caller maintains ownership of the input Blobs. This routine will
770 ** read the Blobs and send them onward to the email system, but it will
771 ** not free them.
772 **
@@ -774,14 +814,14 @@
774 ** in the pHdr parameter.
775 **
776 ** If the zFromName argument is not NULL, then it should be a human-readable
777 ** name or handle for the sender. In that case, "From:" becomes a made-up
778 ** email address based on a hash of zFromName and the domain of email-self,
779 ** and an additional "X-Fossil-From:" field is inserted with the email-self
780 ** address. Downstream software might use the X-Fossil-From header to set
781 ** the envelope-from address of the email. If zFromName is a NULL pointer,
782 ** then the "From:" is set to the email-self value and X-Fossil-From is
783 ** omitted.
784 */
785 void alert_send(
786 AlertSender *p, /* Emailer context */
787 Blob *pHdr, /* Email header (incomplete) */
@@ -794,26 +834,26 @@
794 fossil_print("Sending email\n");
795 }
796 if( fossil_strcmp(p->zDest, "off")==0 ){
797 return;
798 }
 
799 if( fossil_strcmp(p->zDest, "blob")==0 ){
800 pOut = &p->out;
801 if( blob_size(pOut) ){
802 blob_appendf(pOut, "%.72c\n", '=');
803 }
804 }else{
805 blob_init(&all, 0, 0);
806 pOut = &all;
807 }
808 blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
809 if( p->zFrom==0 || p->zFrom[0]==0 ){
810 return; /* email-self is not set. Error will be reported separately */
811 }else if( zFromName ){
812 blob_appendf(pOut, "From: %s <%s@%s>\r\n",
813 zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
814 blob_appendf(pOut, "X-Fossil-From: <%s>\r\n", p->zFrom);
815 }else{
816 blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
817 }
818 blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
819 if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
@@ -879,10 +919,26 @@
879 fossil_print("%s", blob_str(&all));
880 }
881 blob_reset(&all);
882 }
883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884 /*
885 ** SETTING: email-send-method width=5 default=off
886 ** Determine the method used to send email. Allowed values are
887 ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
888 ** means no email is ever sent. The "relay" value means emails are sent
@@ -1259,11 +1315,11 @@
1259 ** page. To allow anonymous passers-by to sign up for email
1260 ** notification, set Email-Alerts on user "nobody" or "anonymous".
1261 */
1262 void subscribe_page(void){
1263 int needCaptcha;
1264 unsigned int uSeed;
1265 const char *zDecoded;
1266 char *zCaptcha = 0;
1267 char *zErr = 0;
1268 int eErr = 0;
1269 int di;
@@ -1392,10 +1448,11 @@
1392 zDecoded = captcha_decode(uSeed);
1393 zCaptcha = captcha_render(zDecoded);
1394 @ <tr>
1395 @ <td class="form_label">Security Code:</td>
1396 @ <td><input type="text" name="captcha" value="" size="30">
 
1397 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
1398 @ </tr>
1399 if( eErr==2 ){
1400 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1401 }
@@ -1461,11 +1518,11 @@
1461 @ </table>
1462 if( needCaptcha ){
1463 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
1464 @ %h(zCaptcha)
1465 @ </pre>
1466 @ Enter the 8 characters above in the "Security Code" box
1467 @ </td></tr></table></div>
1468 }
1469 @ </form>
1470 fossil_free(zErr);
1471 style_footer();
@@ -1511,16 +1568,17 @@
1511 ** to know the subscriber code.
1512 */
1513 void alert_page(void){
1514 const char *zName = P("name");
1515 Stmt q;
1516 int sa, sc, sf, st, sw;
1517 int sdigest, sdonotcall, sverified;
1518 const char *ssub;
1519 const char *semail;
 
1520 const char *smip;
1521 const char *suname;
1522 const char *mtime;
1523 const char *sctime;
1524 int eErr = 0;
1525 char *zErr = 0;
1526
@@ -1528,68 +1586,71 @@
1528 login_check_credentials();
1529 if( !g.perm.EmailAlert ){
1530 login_needed(g.anon.EmailAlert);
1531 return;
1532 }
1533 if( zName==0 && login_is_individual() ){
 
1534 zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
1535 " WHERE suname=%Q", g.zLogin);
1536 }
1537 if( zName==0 || !validate16(zName, -1) ){
1538 cgi_redirect("subscribe");
1539 return;
1540 }
1541 alert_submenu_common();
1542 if( P("submit")!=0 && cgi_csrf_safe(1) ){
1543 int sdonotcall = PB("sdonotcall");
1544 int sdigest = PB("sdigest");
1545 char ssub[10];
1546 int nsub = 0;
1547 if( PB("sa") ) ssub[nsub++] = 'a';
1548 if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
1549 if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
1550 if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
1551 if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1552 ssub[nsub] = 0;
1553 if( g.perm.Admin ){
1554 const char *suname = PT("suname");
1555 int sverified = PB("sverified");
1556 if( suname && suname[0]==0 ) suname = 0;
1557 db_multi_exec(
1558 "UPDATE subscriber SET"
1559 " sdonotcall=%d,"
1560 " sdigest=%d,"
1561 " ssub=%Q,"
1562 " mtime=strftime('%%s','now'),"
1563 " smip=%Q,"
1564 " suname=%Q,"
1565 " sverified=%d"
1566 " WHERE subscriberCode=hextoblob(%Q)",
1567 sdonotcall,
1568 sdigest,
1569 ssub,
1570 g.zIpAddr,
1571 suname,
1572 sverified,
1573 zName
1574 );
1575 }else{
1576 db_multi_exec(
1577 "UPDATE subscriber SET"
1578 " sdonotcall=%d,"
1579 " sdigest=%d,"
1580 " ssub=%Q,"
1581 " mtime=strftime('%%s','now'),"
1582 " smip=%Q"
1583 " WHERE subscriberCode=hextoblob(%Q)",
1584 sdonotcall,
1585 sdigest,
1586 ssub,
1587 g.zIpAddr,
1588 zName
1589 );
1590 }
 
 
1591 }
1592 if( P("delete")!=0 && cgi_csrf_safe(1) ){
1593 if( !PB("dodelete") ){
1594 eErr = 9;
1595 zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
@@ -1597,10 +1658,11 @@
1597 }else{
1598 alert_unsubscribe(zName);
1599 return;
1600 }
1601 }
 
1602 db_prepare(&q,
1603 "SELECT"
1604 " semail," /* 0 */
1605 " sverified," /* 1 */
1606 " sdonotcall," /* 2 */
@@ -1614,23 +1676,27 @@
1614 if( db_step(&q)!=SQLITE_ROW ){
1615 db_finalize(&q);
1616 cgi_redirect("subscribe");
1617 return;
1618 }
1619 style_header("Update Subscription");
1620 semail = db_column_text(&q, 0);
1621 sverified = db_column_int(&q, 1);
1622 sdonotcall = db_column_int(&q, 2);
1623 sdigest = db_column_int(&q, 3);
1624 ssub = db_column_text(&q, 4);
 
 
 
 
1625 sa = strchr(ssub,'a')!=0;
1626 sc = strchr(ssub,'c')!=0;
1627 sf = strchr(ssub,'f')!=0;
1628 st = strchr(ssub,'t')!=0;
1629 sw = strchr(ssub,'w')!=0;
 
1630 smip = db_column_text(&q, 5);
1631 suname = db_column_text(&q, 6);
1632 mtime = db_column_text(&q, 7);
1633 sctime = db_column_text(&q, 8);
1634 if( !g.perm.Admin && !sverified ){
1635 db_multi_exec(
1636 "UPDATE subscriber SET sverified=1 WHERE subscriberCode=hextoblob(%Q)",
@@ -1646,13 +1712,25 @@
1646 form_begin(0, "%R/alerts");
1647 @ <input type="hidden" name="name" value="%h(zName)">
1648 @ <table class="subscribe">
1649 @ <tr>
1650 @ <td class="form_label">Email&nbsp;Address:</td>
1651 @ <td>%h(semail)</td>
 
 
 
 
 
 
 
 
 
 
 
1652 @ </tr>
1653 if( g.perm.Admin ){
 
1654 @ <tr>
1655 @ <td class='form_label'>Created:</td>
1656 @ <td>%h(sctime)</td>
1657 @ </tr>
1658 @ <tr>
@@ -1664,11 +1742,16 @@
1664 @ <td>%h(smip)</td>
1665 @ </tr>
1666 @ <tr>
1667 @ <td class="form_label">User:</td>
1668 @ <td><input type="text" name="suname" value="%h(suname?suname:"")" \
1669 @ size="30"></td>
 
 
 
 
 
1670 @ </tr>
1671 }
1672 @ <tr>
1673 @ <td class="form_label">Topics:</td>
1674 @ <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\
@@ -1678,10 +1761,12 @@
1678 @ Check-ins</label><br>
1679 }
1680 if( g.perm.RdForum ){
1681 @ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\
1682 @ Forum Posts</label><br>
 
 
1683 }
1684 if( g.perm.RdTkt ){
1685 @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\
1686 @ Ticket changes</label><br>
1687 }
@@ -1703,11 +1788,11 @@
1703 #endif
1704 if( g.perm.Admin ){
1705 @ <tr>
1706 @ <td class="form_label">Admin Options:</td><td>
1707 @ <label><input type="checkbox" name="sdonotcall" \
1708 @ %s(sdonotcall?"checked":"")> Do not call</label><br>
1709 @ <label><input type="checkbox" name="sverified" \
1710 @ %s(sverified?"checked":"")>\
1711 @ Verified</label></td></tr>
1712 }
1713 if( eErr==9 ){
@@ -1760,11 +1845,11 @@
1760 */
1761 void unsubscribe_page(void){
1762 const char *zName = P("name");
1763 char *zErr = 0;
1764 int eErr = 0;
1765 unsigned int uSeed;
1766 const char *zDecoded;
1767 char *zCaptcha = 0;
1768 int dx;
1769 int bSubmit;
1770 const char *zEAddr;
@@ -1855,10 +1940,11 @@
1855 zDecoded = captcha_decode(uSeed);
1856 zCaptcha = captcha_render(zDecoded);
1857 @ <tr>
1858 @ <td class="form_label">Security Code:</td>
1859 @ <td><input type="text" name="captcha" value="" size="30">
 
1860 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
1861 if( eErr==2 ){
1862 @ <td><span class="loginError">&larr; %h(zErr)</span></td>
1863 }
1864 @ </tr>
@@ -1874,11 +1960,11 @@
1874 @ </tr>
1875 @ </table>
1876 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
1877 @ %h(zCaptcha)
1878 @ </pre>
1879 @ Enter the 8 characters above in the "Security Code" box
1880 @ </td></tr></table></div>
1881 @ </form>
1882 fossil_free(zErr);
1883 style_footer();
1884 }
@@ -1885,37 +1971,69 @@
1885
1886 /*
1887 ** WEBPAGE: subscribers
1888 **
1889 ** This page, accessible to administrators only,
1890 ** shows a list of email notification email addresses.
1891 ** Clicking on an email takes one to the /alerts page
1892 ** for that email where the delivery settings can be
1893 ** modified.
1894 */
1895 void subscriber_list_page(void){
1896 Blob sql;
1897 Stmt q;
1898 sqlite3_int64 iNow;
 
 
 
1899 if( alert_webpages_disabled() ) return;
1900 login_check_credentials();
1901 if( !g.perm.Admin ){
1902 login_needed(0);
1903 return;
1904 }
1905 alert_submenu_common();
 
1906 style_header("Subscriber List");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1907 blob_init(&sql, 0, 0);
1908 blob_append_sql(&sql,
1909 "SELECT hex(subscriberCode)," /* 0 */
1910 " semail," /* 1 */
1911 " ssub," /* 2 */
1912 " suname," /* 3 */
1913 " sverified," /* 4 */
1914 " sdigest," /* 5 */
1915 " mtime," /* 6 */
1916 " date(sctime,'unixepoch')" /* 7 */
 
1917 " FROM subscriber"
1918 );
1919 if( P("only")!=0 ){
1920 blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
1921 style_submenu_element("Show All","%R/subscribers");
@@ -1937,16 +2055,22 @@
1937 @ </tr>
1938 @ </thead><tbody>
1939 while( db_step(&q)==SQLITE_ROW ){
1940 sqlite3_int64 iMtime = db_column_int64(&q, 6);
1941 double rAge = (iNow - iMtime)/86400.0;
 
 
1942 @ <tr>
1943 @ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\
1944 @ %h(db_column_text(&q,1))</a></td>
1945 @ <td>%h(db_column_text(&q,2))</td>
1946 @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
1947 @ <td>%h(db_column_text(&q,3))</td>
 
 
 
 
1948 @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
1949 @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
1950 @ <td>%h(db_column_text(&q,7))</td>
1951 @ </tr>
1952 }
@@ -1958,13 +2082,21 @@
1958
1959 #if LOCAL_INTERFACE
1960 /*
1961 ** A single event that might appear in an alert is recorded as an
1962 ** instance of the following object.
 
 
 
 
 
 
 
 
1963 */
1964 struct EmailEvent {
1965 int type; /* 'c', 'f', 'm', 't', 'w' */
1966 int needMod; /* Pending moderator approval */
1967 Blob hdr; /* Header content, for forum entries */
1968 Blob txt; /* Text description to appear in an alert */
1969 char *zFromName; /* Human name of the sender */
1970 EmailEvent *pNext; /* Next in chronological order */
@@ -2039,20 +2171,20 @@
2039 p->needMod = db_column_int(&q, 4);
2040 p->zFromName = 0;
2041 p->pNext = 0;
2042 switch( p->type ){
2043 case 'c': zType = "Check-In"; break;
2044 case 'f': zType = "Forum post"; break;
2045 case 't': zType = "Ticket Change"; break;
2046 case 'w': zType = "Wiki Edit"; break;
2047 }
2048 blob_init(&p->hdr, 0, 0);
2049 blob_init(&p->txt, 0, 0);
2050 blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n",
2051 db_column_text(&q,1),
2052 zType,
2053 db_column_text(&q,2),
2054 zUrl,
2055 db_column_text(&q,0)
2056 );
2057 if( p->needMod ){
2058 blob_appendf(&p->txt,
@@ -2084,11 +2216,12 @@
2084 " (SELECT uuid FROM blob WHERE rid=forumpost.fpid)," /* 1 */
2085 " datetime(event.mtime)," /* 2 */
2086 " substr(comment,instr(comment,':')+2)," /* 3 */
2087 " (SELECT uuid FROM blob WHERE rid=forumpost.firt)," /* 4 */
2088 " wantalert.needMod," /* 5 */
2089 " coalesce(trim(substr(info,1,instr(info,'<')-1)),euser,user)" /* 6 */
 
2090 " FROM temp.wantalert, event, forumpost"
2091 " LEFT JOIN user ON (login=coalesce(euser,user))"
2092 " WHERE event.objid=substr(wantalert.eventId,2)+0"
2093 " AND eventId GLOB 'f*'"
2094 " AND forumpost.fpid=event.objid"
@@ -2104,11 +2237,11 @@
2104 const char *z;
2105 if( pPost==0 ) continue;
2106 p = fossil_malloc( sizeof(EmailEvent) );
2107 pLast->pNext = p;
2108 pLast = p;
2109 p->type = 'f';
2110 p->needMod = db_column_int(&q, 5);
2111 z = db_column_text(&q,6);
2112 p->zFromName = z && z[0] ? fossil_strdup(z) : 0;
2113 p->pNext = 0;
2114 blob_init(&p->hdr, 0, 0);
@@ -2436,13 +2569,13 @@
2436 ** if the recipient is a moderator for that type of event. Setup
2437 ** and Admin users always get notified. */
2438 char xType = '*';
2439 if( strpbrk(zCap,"as")==0 ){
2440 switch( p->type ){
2441 case 'f': xType = '5'; break;
2442 case 't': xType = 'q'; break;
2443 case 'w': xType = 'l'; break;
2444 }
2445 if( strchr(zCap,xType)==0 ) continue;
2446 }
2447 }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
2448 /* Setup and admin users can get any notification that does not
@@ -2450,14 +2583,14 @@
2450 }else{
2451 /* Other users only see the alert if they have sufficient
2452 ** privilege to view the event itself */
2453 char xType = '*';
2454 switch( p->type ){
2455 case 'c': xType = 'o'; break;
2456 case 'f': xType = '2'; break;
2457 case 't': xType = 'r'; break;
2458 case 'w': xType = 'j'; break;
2459 }
2460 if( strchr(zCap,xType)==0 ) continue;
2461 }
2462 if( blob_size(&p->hdr)>0 ){
2463 /* This alert should be sent as a separate email */
@@ -2594,10 +2727,11 @@
2594 @ <table class="subscribe">
2595 if( zCaptcha ){
2596 @ <tr>
2597 @ <td class="form_label">Security&nbsp;Code:</td>
2598 @ <td><input type="text" name="captcha" value="" size="10">
 
2599 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
2600 @ </tr>
2601 }
2602 @ <tr>
2603 @ <td class="form_label">Your&nbsp;Email&nbsp;Address:</td>
@@ -2620,11 +2754,11 @@
2620 @ </table>
2621 if( zCaptcha ){
2622 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
2623 @ %h(zCaptcha)
2624 @ </pre>
2625 @ Enter the 8 characters above in the "Security Code" box
2626 @ </td></tr></table></div>
2627 }
2628 @ </form>
2629 style_footer();
2630 }
@@ -2638,10 +2772,11 @@
2638 char *zErr;
2639 const char *zTo = PT("to");
2640 char *zSubject = PT("subject");
2641 int bAll = PB("all");
2642 int bAA = PB("aa");
 
2643 const char *zSub = db_get("email-subname", "[Fossil Repo]");
2644 int bTest2 = fossil_strcmp(P("name"),"test2")==0;
2645 Blob hdr, body;
2646 blob_init(&body, 0, 0);
2647 blob_init(&hdr, 0, 0);
@@ -2649,17 +2784,29 @@
2649 pSender = alert_sender_new(bTest2 ? "blob" : 0, 0);
2650 if( zTo[0] ){
2651 blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
2652 alert_send(pSender, &hdr, &body, 0);
2653 }
2654 if( bAll || bAA ){
2655 Stmt q;
2656 int nUsed = blob_size(&body);
2657 const char *zURL = db_get("email-url",0);
2658 db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
2659 " WHERE sverified AND NOT sdonotcall %s",
2660 bAll ? "" : " AND ssub LIKE '%a%'");
 
 
 
 
 
 
 
 
 
 
 
 
2661 while( db_step(&q)==SQLITE_ROW ){
2662 const char *zCode = db_column_text(&q, 1);
2663 zTo = db_column_text(&q, 0);
2664 blob_truncate(&hdr, 0);
2665 blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
@@ -2716,11 +2863,12 @@
2716 @ <p>The following error was reported by the system:
2717 @ <blockquote><pre>
2718 @ %h(zErr)
2719 @ </pre></blockquote>
2720 }else{
2721 @ <p>The announcement has been sent.</p>
 
2722 }
2723 style_footer();
2724 return;
2725 } else if( !alert_enabled() ){
2726 style_header("Cannot Send Announcement");
@@ -2734,21 +2882,26 @@
2734 @ <form method="POST">
2735 @ <table class="subscribe">
2736 if( g.perm.Admin ){
2737 int aa = PB("aa");
2738 int all = PB("all");
 
2739 const char *aack = aa ? "checked" : "";
2740 const char *allck = all ? "checked" : "";
 
2741 @ <tr>
2742 @ <td class="form_label">To:</td>
2743 @ <td><input type="text" name="to" value="%h(PT("to"))" size="30"><br>
2744 @ <label><input type="checkbox" name="aa" %s(aack)> \
2745 @ All "announcement" subscribers</label> \
2746 @ <a href="%R/subscribers?only=a" target="_blank">(list)</a><br>
2747 @ <label><input type="checkbox" name="all" %s(allck)> \
2748 @ All subscribers</label> \
2749 @ <a href="%R/subscribers" target="_blank">(list)</a><br></td>
 
 
 
2750 @ </tr>
2751 }
2752 @ <tr>
2753 @ <td class="form_label">Subject:</td>
2754 @ <td><input type="text" name="subject" value="%h(PT("subject"))"\
@@ -2759,11 +2912,15 @@
2759 @ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\
2760 @ %h(PT("msg"))</textarea>
2761 @ </tr>
2762 @ <tr>
2763 @ <td></td>
2764 @ <td><input type="submit" name="submit" value="Send Message">
 
 
 
 
2765 @ </tr>
2766 @ </table>
2767 @ </form>
2768 style_footer();
2769 }
2770
--- src/alerts.c
+++ src/alerts.c
@@ -46,10 +46,11 @@
46 @ --
47 @ -- The ssub field is a string where each character indicates a particular
48 @ -- type of event to subscribe to. Choices:
49 @ -- a - Announcements
50 @ -- c - Check-ins
51 @ -- f - Forum posts
52 @ -- t - Ticket changes
53 @ -- w - Wiki changes
54 @ -- Probably different codes will be added in the future. In the future
55 @ -- we might also add a separate table that allows subscribing to email
56 @ -- notifications for specific branches or tags or tickets.
@@ -114,11 +115,11 @@
115 if( bOnlyIfEnabled
116 && fossil_strcmp(db_get("email-send-method",0),"off")==0
117 ){
118 return; /* Don't create table for disabled email */
119 }
120 db_exec_sql(zAlertInit);
121 alert_triggers_enable();
122 }else if( !db_table_has_column("repository","pending_alert","sentMod") ){
123 db_multi_exec(
124 "ALTER TABLE repository.pending_alert"
125 " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
@@ -183,11 +184,11 @@
184 ** is an administrator.
185 */
186 void alert_submenu_common(void){
187 if( g.perm.Admin ){
188 if( fossil_strcmp(g.zPath,"subscribers") ){
189 style_submenu_element("Subscribers","%R/subscribers");
190 }
191 if( fossil_strcmp(g.zPath,"subscribe") ){
192 style_submenu_element("Add New Subscriber","%R/subscribe");
193 }
194 }
@@ -238,10 +239,17 @@
239 @ This is URL used as the basename for hyperlinks included in
240 @ email alert text. Omit the trailing "/".
241 @ Suggested value: "%h(g.zBaseURL)"
242 @ (Property: "email-url")</p>
243 @ <hr>
244
245 entry_attribute("Administrator email address", 40, "email-admin",
246 "eadmin", "", 0);
247 @ <p>This is the email for the human administrator for the system.
248 @ Abuse and trouble reports and password reset requests are send here.
249 @ (Property: "email-admin")</p>
250 @ <hr>
251
252 entry_attribute("\"Return-Path\" email address", 20, "email-self",
253 "eself", "", 0);
254 @ <p><b>Required.</b>
255 @ This is the email to which email notification bounces should be sent.
@@ -297,17 +305,10 @@
305 @ append a colon and TCP port number (ex: smtp.example.com:587).
306 @ The default TCP port number is 25.
307 @ (Property: "email-send-relayhost")</p>
308 @ <hr>
309
 
 
 
 
 
 
 
310 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
311 @ </div></form>
312 db_end_transaction(0);
313 style_footer();
314 }
@@ -574,22 +575,18 @@
575 }
576 return 0;
577 }
578
579 /*
580 ** Determine whether or not the input string is a valid email address.
581 ** Only look at character up to but not including the first \000 or
582 ** the first cTerm character, whichever comes first.
583 **
584 ** Return the length of the email addresss string in bytes if the email
585 ** address is valid. If the email address is misformed, return 0.
586 */
587 int email_address_is_valid(const char *z, char cTerm){
 
 
 
 
588 int i;
589 int nAt = 0;
590 int nDot = 0;
591 char c;
592 if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
@@ -619,13 +616,28 @@
616 }
617 }
618 if( c!=cTerm ) return 0; /* Missing terminator */
619 if( nAt==0 ) return 0; /* No "@" found anywhere */
620 if( nDot==0 ) return 0; /* No "." in the domain */
621 return i;
622 }
623
624 /*
625 ** Make a copy of the input string up to but not including the
626 ** first cTerm character.
627 **
628 ** Verify that the string really that is to be copied really is a
629 ** valid email address. If it is not, then return NULL.
630 **
631 ** This routine is more restrictive than necessary. It does not
632 ** allow comments, IP address, quoted strings, or certain uncommon
633 ** characters. The only non-alphanumerics allowed in the local
634 ** part are "_", "+", "-" and "+".
635 */
636 char *email_copy_addr(const char *z, char cTerm ){
637 int i = email_address_is_valid(z, cTerm);
638 return i==0 ? 0 : mprintf("%.*s", i, z);
639 }
640
641 /*
642 ** Scan the input string for a valid email address enclosed in <...>
643 ** If the string contains one or more email addresses, extract the first
@@ -659,10 +671,38 @@
671 char *zOut = alert_find_emailaddr(zIn);
672 if( zOut ){
673 sqlite3_result_text(context, zOut, -1, fossil_free);
674 }
675 }
676
677 /*
678 ** SQL function: display_name(X)
679 **
680 ** If X is a string, search for a user name at the beginning of that
681 ** string. The user name must be followed by an email address. If
682 ** found, return the user name. If not found, return NULL.
683 **
684 ** This routine is used to extract the display name from the USER.INFO
685 ** field.
686 */
687 void alert_display_name_func(
688 sqlite3_context *context,
689 int argc,
690 sqlite3_value **argv
691 ){
692 const char *zIn = (const char*)sqlite3_value_text(argv[0]);
693 int i;
694 if( zIn==0 ) return;
695 while( fossil_isspace(zIn[0]) ) zIn++;
696 for(i=0; zIn[i] && zIn[i]!='<' && zIn[i]!='\n'; i++){}
697 if( zIn[i]=='<' ){
698 while( i>0 && fossil_isspace(zIn[i-1]) ){ i--; }
699 if( i>0 ){
700 sqlite3_result_text(context, zIn, i, SQLITE_TRANSIENT);
701 }
702 }
703 }
704
705 /*
706 ** Return the hostname portion of an email address - the part following
707 ** the @
708 */
@@ -762,11 +802,11 @@
802 ** Date:
803 ** Message-Id:
804 ** Content-Type:
805 ** Content-Transfer-Encoding:
806 ** MIME-Version:
807 ** Sender:
808 **
809 ** The caller maintains ownership of the input Blobs. This routine will
810 ** read the Blobs and send them onward to the email system, but it will
811 ** not free them.
812 **
@@ -774,14 +814,14 @@
814 ** in the pHdr parameter.
815 **
816 ** If the zFromName argument is not NULL, then it should be a human-readable
817 ** name or handle for the sender. In that case, "From:" becomes a made-up
818 ** email address based on a hash of zFromName and the domain of email-self,
819 ** and an additional "Sender:" field is inserted with the email-self
820 ** address. Downstream software might use the Sender header to set
821 ** the envelope-from address of the email. If zFromName is a NULL pointer,
822 ** then the "From:" is set to the email-self value and Sender is
823 ** omitted.
824 */
825 void alert_send(
826 AlertSender *p, /* Emailer context */
827 Blob *pHdr, /* Email header (incomplete) */
@@ -794,26 +834,26 @@
834 fossil_print("Sending email\n");
835 }
836 if( fossil_strcmp(p->zDest, "off")==0 ){
837 return;
838 }
839 blob_init(&all, 0, 0);
840 if( fossil_strcmp(p->zDest, "blob")==0 ){
841 pOut = &p->out;
842 if( blob_size(pOut) ){
843 blob_appendf(pOut, "%.72c\n", '=');
844 }
845 }else{
 
846 pOut = &all;
847 }
848 blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
849 if( p->zFrom==0 || p->zFrom[0]==0 ){
850 return; /* email-self is not set. Error will be reported separately */
851 }else if( zFromName ){
852 blob_appendf(pOut, "From: %s <%s@%s>\r\n",
853 zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
854 blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
855 }else{
856 blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
857 }
858 blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
859 if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
@@ -879,10 +919,26 @@
919 fossil_print("%s", blob_str(&all));
920 }
921 blob_reset(&all);
922 }
923
924 /*
925 ** SETTING: email-url width=40
926 ** This URL is used as the basename for hyperlinks included in email alert
927 ** text. Omit the trailing "/".
928 */
929 /*
930 ** SETTING: email-admin width=40
931 ** This is the email address for the human administrator for the system.
932 ** Abuse and trouble reports and password reset requests are send here.
933 */
934 /*
935 ** SETTING: email-subname width=16
936 ** This is a short name used to identifies the repository in the Subject:
937 ** line of email alerts. Traditionally this name is included in square
938 ** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939 */
940 /*
941 ** SETTING: email-send-method width=5 default=off
942 ** Determine the method used to send email. Allowed values are
943 ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
944 ** means no email is ever sent. The "relay" value means emails are sent
@@ -1259,11 +1315,11 @@
1315 ** page. To allow anonymous passers-by to sign up for email
1316 ** notification, set Email-Alerts on user "nobody" or "anonymous".
1317 */
1318 void subscribe_page(void){
1319 int needCaptcha;
1320 unsigned int uSeed = 0;
1321 const char *zDecoded;
1322 char *zCaptcha = 0;
1323 char *zErr = 0;
1324 int eErr = 0;
1325 int di;
@@ -1392,10 +1448,11 @@
1448 zDecoded = captcha_decode(uSeed);
1449 zCaptcha = captcha_render(zDecoded);
1450 @ <tr>
1451 @ <td class="form_label">Security Code:</td>
1452 @ <td><input type="text" name="captcha" value="" size="30">
1453 captcha_speakit_button(uSeed, "Speak the code");
1454 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
1455 @ </tr>
1456 if( eErr==2 ){
1457 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1458 }
@@ -1461,11 +1518,11 @@
1518 @ </table>
1519 if( needCaptcha ){
1520 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
1521 @ %h(zCaptcha)
1522 @ </pre>
1523 @ Enter the 8 characters above in the "Security Code" box<br/>
1524 @ </td></tr></table></div>
1525 }
1526 @ </form>
1527 fossil_free(zErr);
1528 style_footer();
@@ -1511,16 +1568,17 @@
1568 ** to know the subscriber code.
1569 */
1570 void alert_page(void){
1571 const char *zName = P("name");
1572 Stmt q;
1573 int sa, sc, sf, st, sw, sx;
1574 int sdigest = 0, sdonotcall = 0, sverified = 0;
1575 int isLogin; /* Logged in as an individual */
1576 const char *ssub = 0;
1577 const char *semail = 0;
1578 const char *smip;
1579 const char *suname = 0;
1580 const char *mtime;
1581 const char *sctime;
1582 int eErr = 0;
1583 char *zErr = 0;
1584
@@ -1528,68 +1586,71 @@
1586 login_check_credentials();
1587 if( !g.perm.EmailAlert ){
1588 login_needed(g.anon.EmailAlert);
1589 return;
1590 }
1591 isLogin = login_is_individual();
1592 if( zName==0 && isLogin ){
1593 zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
1594 " WHERE suname=%Q", g.zLogin);
1595 }
1596 if( zName==0 || !validate16(zName, -1) ){
1597 cgi_redirect("subscribe");
1598 return;
1599 }
1600 alert_submenu_common();
1601 if( P("submit")!=0 && cgi_csrf_safe(1) ){
1602 char newSsub[10];
1603 int nsub = 0;
1604 Blob update;
1605
1606 sdonotcall = PB("sdonotcall");
1607 sdigest = PB("sdigest");
1608 semail = P("semail");
1609 if( PB("sa") ) newSsub[nsub++] = 'a';
1610 if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c';
1611 if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f';
1612 if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't';
1613 if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w';
1614 if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x';
1615 newSsub[nsub] = 0;
1616 ssub = newSsub;
1617 blob_init(&update, "UPDATE subscriber SET", -1);
1618 blob_append_sql(&update,
1619 " sdonotcall=%d,"
1620 " sdigest=%d,"
1621 " ssub=%Q,"
1622 " mtime=strftime('%%s','now'),"
1623 " smip=%Q",
1624 sdonotcall,
1625 sdigest,
1626 ssub,
1627 g.zIpAddr
1628 );
1629 if( g.perm.Admin ){
1630 suname = PT("suname");
1631 sverified = PB("sverified");
1632 if( suname && suname[0]==0 ) suname = 0;
1633 blob_append_sql(&update,
1634 ", suname=%Q,"
1635 " sverified=%d",
1636 suname,
1637 sverified
1638 );
1639 }
1640 if( isLogin ){
1641 if( semail==0 || email_address_is_valid(semail,0)==0 ){
1642 eErr = 8;
1643 }
1644 blob_append_sql(&update, ", semail=%Q", semail);
1645 }
1646 blob_append_sql(&update," WHERE subscriberCode=hextoblob(%Q)", zName);
1647 if( eErr==0 ){
1648 db_exec_sql(blob_str(&update));
1649 ssub = 0;
1650 }
1651 blob_reset(&update);
1652 }
1653 if( P("delete")!=0 && cgi_csrf_safe(1) ){
1654 if( !PB("dodelete") ){
1655 eErr = 9;
1656 zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
@@ -1597,10 +1658,11 @@
1658 }else{
1659 alert_unsubscribe(zName);
1660 return;
1661 }
1662 }
1663 style_header("Update Subscription");
1664 db_prepare(&q,
1665 "SELECT"
1666 " semail," /* 0 */
1667 " sverified," /* 1 */
1668 " sdonotcall," /* 2 */
@@ -1614,23 +1676,27 @@
1676 if( db_step(&q)!=SQLITE_ROW ){
1677 db_finalize(&q);
1678 cgi_redirect("subscribe");
1679 return;
1680 }
1681 if( ssub==0 ){
1682 semail = db_column_text(&q, 0);
1683 sdonotcall = db_column_int(&q, 2);
1684 sdigest = db_column_int(&q, 3);
1685 ssub = db_column_text(&q, 4);
1686 }
1687 if( suname==0 ){
1688 suname = db_column_text(&q, 6);
1689 sverified = db_column_int(&q, 1);
1690 }
1691 sa = strchr(ssub,'a')!=0;
1692 sc = strchr(ssub,'c')!=0;
1693 sf = strchr(ssub,'f')!=0;
1694 st = strchr(ssub,'t')!=0;
1695 sw = strchr(ssub,'w')!=0;
1696 sx = strchr(ssub,'x')!=0;
1697 smip = db_column_text(&q, 5);
 
1698 mtime = db_column_text(&q, 7);
1699 sctime = db_column_text(&q, 8);
1700 if( !g.perm.Admin && !sverified ){
1701 db_multi_exec(
1702 "UPDATE subscriber SET sverified=1 WHERE subscriberCode=hextoblob(%Q)",
@@ -1646,13 +1712,25 @@
1712 form_begin(0, "%R/alerts");
1713 @ <input type="hidden" name="name" value="%h(zName)">
1714 @ <table class="subscribe">
1715 @ <tr>
1716 @ <td class="form_label">Email&nbsp;Address:</td>
1717 if( isLogin ){
1718 @ <td><input type="text" name="semail" value="%h(semail)" size="30">\
1719 if( eErr==8 ){
1720 @ <span class='loginError'>&larr; not a valid email address!</span>
1721 }else if( g.perm.Admin ){
1722 @ &nbsp;&nbsp;<a href="%R/announce?to=%t(semail)">\
1723 @ (Send a message to %h(semail))</a>\
1724 }
1725 @ </td>
1726 }else{
1727 @ <td>%h(semail)</td>
1728 }
1729 @ </tr>
1730 if( g.perm.Admin ){
1731 int uid;
1732 @ <tr>
1733 @ <td class='form_label'>Created:</td>
1734 @ <td>%h(sctime)</td>
1735 @ </tr>
1736 @ <tr>
@@ -1664,11 +1742,16 @@
1742 @ <td>%h(smip)</td>
1743 @ </tr>
1744 @ <tr>
1745 @ <td class="form_label">User:</td>
1746 @ <td><input type="text" name="suname" value="%h(suname?suname:"")" \
1747 @ size="30">\
1748 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname);
1749 if( uid ){
1750 @ &nbsp;&nbsp;<a href='%R/setup_uedit?id=%d(uid)'>\
1751 @ (login info for %h(suname))</a>\
1752 }
1753 @ </tr>
1754 }
1755 @ <tr>
1756 @ <td class="form_label">Topics:</td>
1757 @ <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\
@@ -1678,10 +1761,12 @@
1761 @ Check-ins</label><br>
1762 }
1763 if( g.perm.RdForum ){
1764 @ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\
1765 @ Forum Posts</label><br>
1766 @ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\
1767 @ Forum Edits</label><br>
1768 }
1769 if( g.perm.RdTkt ){
1770 @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\
1771 @ Ticket changes</label><br>
1772 }
@@ -1703,11 +1788,11 @@
1788 #endif
1789 if( g.perm.Admin ){
1790 @ <tr>
1791 @ <td class="form_label">Admin Options:</td><td>
1792 @ <label><input type="checkbox" name="sdonotcall" \
1793 @ %s(sdonotcall?"checked":"")> Do not disturb</label><br>
1794 @ <label><input type="checkbox" name="sverified" \
1795 @ %s(sverified?"checked":"")>\
1796 @ Verified</label></td></tr>
1797 }
1798 if( eErr==9 ){
@@ -1760,11 +1845,11 @@
1845 */
1846 void unsubscribe_page(void){
1847 const char *zName = P("name");
1848 char *zErr = 0;
1849 int eErr = 0;
1850 unsigned int uSeed = 0;
1851 const char *zDecoded;
1852 char *zCaptcha = 0;
1853 int dx;
1854 int bSubmit;
1855 const char *zEAddr;
@@ -1855,10 +1940,11 @@
1940 zDecoded = captcha_decode(uSeed);
1941 zCaptcha = captcha_render(zDecoded);
1942 @ <tr>
1943 @ <td class="form_label">Security Code:</td>
1944 @ <td><input type="text" name="captcha" value="" size="30">
1945 captcha_speakit_button(uSeed, "Speak the code");
1946 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
1947 if( eErr==2 ){
1948 @ <td><span class="loginError">&larr; %h(zErr)</span></td>
1949 }
1950 @ </tr>
@@ -1874,11 +1960,11 @@
1960 @ </tr>
1961 @ </table>
1962 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
1963 @ %h(zCaptcha)
1964 @ </pre>
1965 @ Enter the 8 characters above in the "Security Code" box<br/>
1966 @ </td></tr></table></div>
1967 @ </form>
1968 fossil_free(zErr);
1969 style_footer();
1970 }
@@ -1885,37 +1971,69 @@
1971
1972 /*
1973 ** WEBPAGE: subscribers
1974 **
1975 ** This page, accessible to administrators only,
1976 ** shows a list of subscriber email addresses.
1977 ** Clicking on an email takes one to the /alerts page
1978 ** for that email where the delivery settings can be
1979 ** modified.
1980 */
1981 void subscriber_list_page(void){
1982 Blob sql;
1983 Stmt q;
1984 sqlite3_int64 iNow;
1985 int nTotal;
1986 int nPending;
1987 int nDel = 0;
1988 if( alert_webpages_disabled() ) return;
1989 login_check_credentials();
1990 if( !g.perm.Admin ){
1991 login_needed(0);
1992 return;
1993 }
1994 alert_submenu_common();
1995 style_submenu_element("Users","setup_ulist");
1996 style_header("Subscriber List");
1997 nTotal = db_int(0, "SELECT count(*) FROM subscriber");
1998 nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified");
1999 if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){
2000 int nNewPending;
2001 db_multi_exec(
2002 "DELETE FROM subscriber"
2003 " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')"
2004 );
2005 nNewPending = db_int(0, "SELECT count(*) FROM subscriber"
2006 " WHERE NOT sverified");
2007 nDel = nPending - nNewPending;
2008 nPending = nNewPending;
2009 nTotal -= nDel;
2010 }
2011 if( nPending>0 ){
2012 @ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1>
2013 if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber"
2014 " WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')")
2015 ){
2016 style_submenu_element("Purge Pending","subscribers?purge");
2017 }
2018 }else{
2019 @ <h1>%,d(nTotal) Subscribers</h1>
2020 }
2021 if( nDel>0 ){
2022 @ <p>*** %d(nDel) pending subscriptions deleted ***</p>
2023 }
2024 blob_init(&sql, 0, 0);
2025 blob_append_sql(&sql,
2026 "SELECT hex(subscriberCode)," /* 0 */
2027 " semail," /* 1 */
2028 " ssub," /* 2 */
2029 " suname," /* 3 */
2030 " sverified," /* 4 */
2031 " sdigest," /* 5 */
2032 " mtime," /* 6 */
2033 " date(sctime,'unixepoch')," /* 7 */
2034 " (SELECT uid FROM user WHERE login=subscriber.suname)" /* 8 */
2035 " FROM subscriber"
2036 );
2037 if( P("only")!=0 ){
2038 blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
2039 style_submenu_element("Show All","%R/subscribers");
@@ -1937,16 +2055,22 @@
2055 @ </tr>
2056 @ </thead><tbody>
2057 while( db_step(&q)==SQLITE_ROW ){
2058 sqlite3_int64 iMtime = db_column_int64(&q, 6);
2059 double rAge = (iNow - iMtime)/86400.0;
2060 int uid = db_column_int(&q, 8);
2061 const char *zUname = db_column_text(&q, 3);
2062 @ <tr>
2063 @ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\
2064 @ %h(db_column_text(&q,1))</a></td>
2065 @ <td>%h(db_column_text(&q,2))</td>
2066 @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
2067 if( uid ){
2068 @ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a>
2069 }else{
2070 @ <td>%h(zUname)</td>
2071 }
2072 @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
2073 @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
2074 @ <td>%h(db_column_text(&q,7))</td>
2075 @ </tr>
2076 }
@@ -1958,13 +2082,21 @@
2082
2083 #if LOCAL_INTERFACE
2084 /*
2085 ** A single event that might appear in an alert is recorded as an
2086 ** instance of the following object.
2087 **
2088 ** type values:
2089 **
2090 ** c A new check-in
2091 ** f An original forum post
2092 ** x An edit to a prior forum post
2093 ** t A new ticket or a change to an existing ticket
2094 ** w A change to a wiki page
2095 */
2096 struct EmailEvent {
2097 int type; /* 'c', 'f', 't', 'w', 'x' */
2098 int needMod; /* Pending moderator approval */
2099 Blob hdr; /* Header content, for forum entries */
2100 Blob txt; /* Text description to appear in an alert */
2101 char *zFromName; /* Human name of the sender */
2102 EmailEvent *pNext; /* Next in chronological order */
@@ -2039,20 +2171,20 @@
2171 p->needMod = db_column_int(&q, 4);
2172 p->zFromName = 0;
2173 p->pNext = 0;
2174 switch( p->type ){
2175 case 'c': zType = "Check-In"; break;
2176 /* case 'f': -- forum posts omitted from this loop. See below */
2177 case 't': zType = "Ticket Change"; break;
2178 case 'w': zType = "Wiki Edit"; break;
2179 }
2180 blob_init(&p->hdr, 0, 0);
2181 blob_init(&p->txt, 0, 0);
2182 blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n",
2183 db_column_text(&q,1),
2184 zType,
2185 db_column_text(&q, 2),
2186 zUrl,
2187 db_column_text(&q,0)
2188 );
2189 if( p->needMod ){
2190 blob_appendf(&p->txt,
@@ -2084,11 +2216,12 @@
2216 " (SELECT uuid FROM blob WHERE rid=forumpost.fpid)," /* 1 */
2217 " datetime(event.mtime)," /* 2 */
2218 " substr(comment,instr(comment,':')+2)," /* 3 */
2219 " (SELECT uuid FROM blob WHERE rid=forumpost.firt)," /* 4 */
2220 " wantalert.needMod," /* 5 */
2221 " coalesce(display_name(info),euser,user)," /* 6 */
2222 " forumpost.fprev IS NULL" /* 7 */
2223 " FROM temp.wantalert, event, forumpost"
2224 " LEFT JOIN user ON (login=coalesce(euser,user))"
2225 " WHERE event.objid=substr(wantalert.eventId,2)+0"
2226 " AND eventId GLOB 'f*'"
2227 " AND forumpost.fpid=event.objid"
@@ -2104,11 +2237,11 @@
2237 const char *z;
2238 if( pPost==0 ) continue;
2239 p = fossil_malloc( sizeof(EmailEvent) );
2240 pLast->pNext = p;
2241 pLast = p;
2242 p->type = db_column_int(&q,7) ? 'f' : 'x';
2243 p->needMod = db_column_int(&q, 5);
2244 z = db_column_text(&q,6);
2245 p->zFromName = z && z[0] ? fossil_strdup(z) : 0;
2246 p->pNext = 0;
2247 blob_init(&p->hdr, 0, 0);
@@ -2436,13 +2569,13 @@
2569 ** if the recipient is a moderator for that type of event. Setup
2570 ** and Admin users always get notified. */
2571 char xType = '*';
2572 if( strpbrk(zCap,"as")==0 ){
2573 switch( p->type ){
2574 case 'x': case 'f': xType = '5'; break;
2575 case 't': xType = 'q'; break;
2576 case 'w': xType = 'l'; break;
2577 }
2578 if( strchr(zCap,xType)==0 ) continue;
2579 }
2580 }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
2581 /* Setup and admin users can get any notification that does not
@@ -2450,14 +2583,14 @@
2583 }else{
2584 /* Other users only see the alert if they have sufficient
2585 ** privilege to view the event itself */
2586 char xType = '*';
2587 switch( p->type ){
2588 case 'c': xType = 'o'; break;
2589 case 'x': case 'f': xType = '2'; break;
2590 case 't': xType = 'r'; break;
2591 case 'w': xType = 'j'; break;
2592 }
2593 if( strchr(zCap,xType)==0 ) continue;
2594 }
2595 if( blob_size(&p->hdr)>0 ){
2596 /* This alert should be sent as a separate email */
@@ -2594,10 +2727,11 @@
2727 @ <table class="subscribe">
2728 if( zCaptcha ){
2729 @ <tr>
2730 @ <td class="form_label">Security&nbsp;Code:</td>
2731 @ <td><input type="text" name="captcha" value="" size="10">
2732 captcha_speakit_button(uSeed, "Speak the code");
2733 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
2734 @ </tr>
2735 }
2736 @ <tr>
2737 @ <td class="form_label">Your&nbsp;Email&nbsp;Address:</td>
@@ -2620,11 +2754,11 @@
2754 @ </table>
2755 if( zCaptcha ){
2756 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
2757 @ %h(zCaptcha)
2758 @ </pre>
2759 @ Enter the 8 characters above in the "Security Code" box<br/>
2760 @ </td></tr></table></div>
2761 }
2762 @ </form>
2763 style_footer();
2764 }
@@ -2638,10 +2772,11 @@
2772 char *zErr;
2773 const char *zTo = PT("to");
2774 char *zSubject = PT("subject");
2775 int bAll = PB("all");
2776 int bAA = PB("aa");
2777 int bMods = PB("mods");
2778 const char *zSub = db_get("email-subname", "[Fossil Repo]");
2779 int bTest2 = fossil_strcmp(P("name"),"test2")==0;
2780 Blob hdr, body;
2781 blob_init(&body, 0, 0);
2782 blob_init(&hdr, 0, 0);
@@ -2649,17 +2784,29 @@
2784 pSender = alert_sender_new(bTest2 ? "blob" : 0, 0);
2785 if( zTo[0] ){
2786 blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
2787 alert_send(pSender, &hdr, &body, 0);
2788 }
2789 if( bAll || bAA || bMods ){
2790 Stmt q;
2791 int nUsed = blob_size(&body);
2792 const char *zURL = db_get("email-url",0);
2793 if( bAll ){
2794 db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
2795 " WHERE sverified AND NOT sdonotcall");
2796 }else if( bAA ){
2797 db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
2798 " WHERE sverified AND NOT sdonotcall"
2799 " AND ssub LIKE '%%a%%'");
2800 }else if( bMods ){
2801 db_prepare(&q,
2802 "SELECT semail, hex(subscriberCode)"
2803 " FROM subscriber, user "
2804 " WHERE sverified AND NOT sdonotcall"
2805 " AND suname=login"
2806 " AND fullcap(cap) GLOB '*5*'");
2807 }
2808 while( db_step(&q)==SQLITE_ROW ){
2809 const char *zCode = db_column_text(&q, 1);
2810 zTo = db_column_text(&q, 0);
2811 blob_truncate(&hdr, 0);
2812 blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
@@ -2716,11 +2863,12 @@
2863 @ <p>The following error was reported by the system:
2864 @ <blockquote><pre>
2865 @ %h(zErr)
2866 @ </pre></blockquote>
2867 }else{
2868 @ <p>The announcement has been sent.
2869 @ <a href="%h(PD("REQUEST_URI","/"))">Send another</a></p>
2870 }
2871 style_footer();
2872 return;
2873 } else if( !alert_enabled() ){
2874 style_header("Cannot Send Announcement");
@@ -2734,21 +2882,26 @@
2882 @ <form method="POST">
2883 @ <table class="subscribe">
2884 if( g.perm.Admin ){
2885 int aa = PB("aa");
2886 int all = PB("all");
2887 int aMod = PB("mods");
2888 const char *aack = aa ? "checked" : "";
2889 const char *allck = all ? "checked" : "";
2890 const char *modck = aMod ? "checked" : "";
2891 @ <tr>
2892 @ <td class="form_label">To:</td>
2893 @ <td><input type="text" name="to" value="%h(PT("to"))" size="30"><br>
2894 @ <label><input type="checkbox" name="aa" %s(aack)> \
2895 @ All "announcement" subscribers</label> \
2896 @ <a href="%R/subscribers?only=a" target="_blank">(list)</a><br>
2897 @ <label><input type="checkbox" name="all" %s(allck)> \
2898 @ All subscribers</label> \
2899 @ <a href="%R/subscribers" target="_blank">(list)</a><br>
2900 @ <label><input type="checkbox" name="mods" %s(modck)> \
2901 @ All moderators</label> \
2902 @ <a href="%R/setup_ulist?with=5" target="_blank">(list)</a><br></td>
2903 @ </tr>
2904 }
2905 @ <tr>
2906 @ <td class="form_label">Subject:</td>
2907 @ <td><input type="text" name="subject" value="%h(PT("subject"))"\
@@ -2759,11 +2912,15 @@
2912 @ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\
2913 @ %h(PT("msg"))</textarea>
2914 @ </tr>
2915 @ <tr>
2916 @ <td></td>
2917 if( fossil_strcmp(P("name"),"test2")==0 ){
2918 @ <td><input type="submit" name="submit" value="Dry Run">
2919 }else{
2920 @ <td><input type="submit" name="submit" value="Send Message">
2921 }
2922 @ </tr>
2923 @ </table>
2924 @ </form>
2925 style_footer();
2926 }
2927
+1 -1
--- src/branch.c
+++ src/branch.c
@@ -245,11 +245,11 @@
245245
@ GROUP BY 1;
246246
;
247247
248248
/* Call this routine to create the TEMP table */
249249
static void brlist_create_temp_table(void){
250
- db_multi_exec(createBrlistQuery/*works-like:""*/);
250
+ db_exec_sql(createBrlistQuery);
251251
}
252252
253253
254254
#if INTERFACE
255255
/*
256256
--- src/branch.c
+++ src/branch.c
@@ -245,11 +245,11 @@
245 @ GROUP BY 1;
246 ;
247
248 /* Call this routine to create the TEMP table */
249 static void brlist_create_temp_table(void){
250 db_multi_exec(createBrlistQuery/*works-like:""*/);
251 }
252
253
254 #if INTERFACE
255 /*
256
--- src/branch.c
+++ src/branch.c
@@ -245,11 +245,11 @@
245 @ GROUP BY 1;
246 ;
247
248 /* Call this routine to create the TEMP table */
249 static void brlist_create_temp_table(void){
250 db_exec_sql(createBrlistQuery);
251 }
252
253
254 #if INTERFACE
255 /*
256
+1 -1
--- src/browse.c
+++ src/browse.c
@@ -918,11 +918,11 @@
918918
** mtime on that check-in. If zGlob and *zGlob then only files matching
919919
** the given glob are computed.
920920
*/
921921
int compute_fileage(int vid, const char* zGlob){
922922
Stmt q;
923
- db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/);
923
+ db_exec_sql(zComputeFileAgeSetup);
924924
db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
925925
db_bind_int(&q, ":ckin", vid);
926926
db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
927927
db_exec(&q);
928928
db_finalize(&q);
929929
--- src/browse.c
+++ src/browse.c
@@ -918,11 +918,11 @@
918 ** mtime on that check-in. If zGlob and *zGlob then only files matching
919 ** the given glob are computed.
920 */
921 int compute_fileage(int vid, const char* zGlob){
922 Stmt q;
923 db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/);
924 db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
925 db_bind_int(&q, ":ckin", vid);
926 db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
927 db_exec(&q);
928 db_finalize(&q);
929
--- src/browse.c
+++ src/browse.c
@@ -918,11 +918,11 @@
918 ** mtime on that check-in. If zGlob and *zGlob then only files matching
919 ** the given glob are computed.
920 */
921 int compute_fileage(int vid, const char* zGlob){
922 Stmt q;
923 db_exec_sql(zComputeFileAgeSetup);
924 db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
925 db_bind_int(&q, ":ckin", vid);
926 db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
927 db_exec(&q);
928 db_finalize(&q);
929
--- src/capabilities.c
+++ src/capabilities.c
@@ -242,12 +242,14 @@
242242
"Admin", "Create and delete users" },
243243
{ 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
244244
"Attach", "Add attchments to wiki or tickets" },
245245
{ 'c', CAPCLASS_TKT, 0,
246246
"Append-Tkt", "Append to existing tickets" },
247
- { 'd', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
248
- "Delete", "Delete wiki or tickets" },
247
+ /*
248
+ ** d unused since fork from CVSTrac;
249
+ ** see https://fossil-scm.org/forum/forumpost/43c78f4bef
250
+ */
249251
{ 'e', CAPCLASS_DATA, 0,
250252
"View-PII", "View sensitive info such as email addresses" },
251253
{ 'f', CAPCLASS_WIKI, 0,
252254
"New-Wiki", "Create new wiki pages" },
253255
{ 'g', CAPCLASS_DATA, 0,
@@ -430,11 +432,11 @@
430432
eType = 0;
431433
}
432434
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
433435
434436
/* Ticket */
435
- if( sqlite3_strglob("*[ascdnqtw]*",zCap)==0 ){
437
+ if( sqlite3_strglob("*[ascnqtw]*",zCap)==0 ){
436438
eType = 2;
437439
}else if( sqlite3_strglob("*r*",zCap)==0 ){
438440
eType = 1;
439441
}else{
440442
eType = 0;
441443
--- src/capabilities.c
+++ src/capabilities.c
@@ -242,12 +242,14 @@
242 "Admin", "Create and delete users" },
243 { 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
244 "Attach", "Add attchments to wiki or tickets" },
245 { 'c', CAPCLASS_TKT, 0,
246 "Append-Tkt", "Append to existing tickets" },
247 { 'd', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
248 "Delete", "Delete wiki or tickets" },
 
 
249 { 'e', CAPCLASS_DATA, 0,
250 "View-PII", "View sensitive info such as email addresses" },
251 { 'f', CAPCLASS_WIKI, 0,
252 "New-Wiki", "Create new wiki pages" },
253 { 'g', CAPCLASS_DATA, 0,
@@ -430,11 +432,11 @@
430 eType = 0;
431 }
432 @ <td class="%s(azClass[eType])">%s(azType[eType])</td>
433
434 /* Ticket */
435 if( sqlite3_strglob("*[ascdnqtw]*",zCap)==0 ){
436 eType = 2;
437 }else if( sqlite3_strglob("*r*",zCap)==0 ){
438 eType = 1;
439 }else{
440 eType = 0;
441
--- src/capabilities.c
+++ src/capabilities.c
@@ -242,12 +242,14 @@
242 "Admin", "Create and delete users" },
243 { 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
244 "Attach", "Add attchments to wiki or tickets" },
245 { 'c', CAPCLASS_TKT, 0,
246 "Append-Tkt", "Append to existing tickets" },
247 /*
248 ** d unused since fork from CVSTrac;
249 ** see https://fossil-scm.org/forum/forumpost/43c78f4bef
250 */
251 { 'e', CAPCLASS_DATA, 0,
252 "View-PII", "View sensitive info such as email addresses" },
253 { 'f', CAPCLASS_WIKI, 0,
254 "New-Wiki", "Create new wiki pages" },
255 { 'g', CAPCLASS_DATA, 0,
@@ -430,11 +432,11 @@
432 eType = 0;
433 }
434 @ <td class="%s(azClass[eType])">%s(azType[eType])</td>
435
436 /* Ticket */
437 if( sqlite3_strglob("*[ascnqtw]*",zCap)==0 ){
438 eType = 2;
439 }else if( sqlite3_strglob("*r*",zCap)==0 ){
440 eType = 1;
441 }else{
442 eType = 0;
443
--- src/captcha.c
+++ src/captcha.c
@@ -548,12 +548,31 @@
548548
@ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
549549
@ <input type="text" name="captcha" size=8 />
550550
if( showButton ){
551551
@ <input type="submit" value="Submit">
552552
}
553
+ @ <br/>\
554
+ captcha_speakit_button(uSeed, 0);
553555
@ </td></tr></table></div>
554556
}
557
+
558
+/*
559
+** Add a "Speak the captcha" button.
560
+*/
561
+void captcha_speakit_button(unsigned int uSeed, const char *zMsg){
562
+ if( zMsg==0 ) zMsg = "Speak the text";
563
+ @ <input type="button" value="%h(zMsg)" id="speakthetext">
564
+ @ <script nonce="%h(style_nonce())">
565
+ @ document.getElementById("speakthetext").onclick = function(){
566
+ @ var audio = window.fossilAudioCaptcha \
567
+ @ || new Audio("%R/captcha-audio/%u(uSeed)");
568
+ @ window.fossilAudioCaptcha = audio;
569
+ @ audio.currentTime = 0;
570
+ @ audio.play();
571
+ @ }
572
+ @ </script>
573
+}
555574
556575
/*
557576
** WEBPAGE: test-captcha
558577
** Test the captcha-generator by rendering the value of the name= query
559578
** parameter using ascii-art. If name= is omitted, show a random 16-digit
@@ -608,5 +627,80 @@
608627
captcha_generate(1);
609628
@ </form>
610629
style_footer();
611630
return 1;
612631
}
632
+
633
+/*
634
+** Generate a WAV file that reads aloud the hex digits given by
635
+** zHex.
636
+*/
637
+static void captcha_wav(const char *zHex, Blob *pOut){
638
+ int i;
639
+ const int szWavHdr = 44;
640
+ blob_init(pOut, 0, 0);
641
+ blob_resize(pOut, szWavHdr); /* Space for the WAV header */
642
+ pOut->nUsed = szWavHdr;
643
+ memset(pOut->aData, 0, szWavHdr);
644
+ if( zHex==0 || zHex[0]==0 ) zHex = "0";
645
+ for(i=0; zHex[i]; i++){
646
+ int v = hex_digit_value(zHex[i]);
647
+ int sz;
648
+ int nData;
649
+ const unsigned char *pData;
650
+ char zSoundName[50];
651
+ sqlite3_snprintf(sizeof(zSoundName),zSoundName,"sounds/%c.wav",
652
+ "0123456789abcdef"[v]);
653
+ /* Extra silence in between letters */
654
+ if( i>0 ){
655
+ int nQuiet = 3000;
656
+ blob_resize(pOut, pOut->nUsed+nQuiet);
657
+ memset(pOut->aData+pOut->nUsed-nQuiet, 0x80, nQuiet);
658
+ }
659
+ pData = builtin_file(zSoundName, &sz);
660
+ nData = sz - szWavHdr;
661
+ blob_resize(pOut, pOut->nUsed+nData);
662
+ memcpy(pOut->aData+pOut->nUsed-nData, pData+szWavHdr, nData);
663
+ if( zHex[i+1]==0 ){
664
+ int len = pOut->nUsed + 36;
665
+ memcpy(pOut->aData, pData, szWavHdr);
666
+ pOut->aData[4] = (char)(len&0xff);
667
+ pOut->aData[5] = (char)((len>>8)&0xff);
668
+ pOut->aData[6] = (char)((len>>16)&0xff);
669
+ pOut->aData[7] = (char)((len>>24)&0xff);
670
+ len = pOut->nUsed;
671
+ pOut->aData[40] = (char)(len&0xff);
672
+ pOut->aData[41] = (char)((len>>8)&0xff);
673
+ pOut->aData[42] = (char)((len>>16)&0xff);
674
+ pOut->aData[43] = (char)((len>>24)&0xff);
675
+ }
676
+ }
677
+}
678
+
679
+/*
680
+** WEBPAGE: /captcha-audio
681
+**
682
+** Return a WAV file that pronounces the digits of the captcha that
683
+** is determined by the seed given in the name= query parameter.
684
+*/
685
+void captcha_wav_page(void){
686
+ const char *zSeed = P("name");
687
+ const char *zDecode = captcha_decode((unsigned int)atoi(zSeed));
688
+ Blob audio;
689
+ captcha_wav(zDecode, &audio);
690
+ cgi_set_content_type("audio/wav");
691
+ cgi_set_content(&audio);
692
+}
693
+
694
+/*
695
+** WEBPAGE: /test-captcha-audio
696
+**
697
+** Return a WAV file that pronounces the hex digits of the name=
698
+** query parameter.
699
+*/
700
+void captcha_test_wav_page(void){
701
+ const char *zSeed = P("name");
702
+ Blob audio;
703
+ captcha_wav(zSeed, &audio);
704
+ cgi_set_content_type("audio/wav");
705
+ cgi_set_content(&audio);
706
+}
613707
--- src/captcha.c
+++ src/captcha.c
@@ -548,12 +548,31 @@
548 @ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
549 @ <input type="text" name="captcha" size=8 />
550 if( showButton ){
551 @ <input type="submit" value="Submit">
552 }
 
 
553 @ </td></tr></table></div>
554 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
556 /*
557 ** WEBPAGE: test-captcha
558 ** Test the captcha-generator by rendering the value of the name= query
559 ** parameter using ascii-art. If name= is omitted, show a random 16-digit
@@ -608,5 +627,80 @@
608 captcha_generate(1);
609 @ </form>
610 style_footer();
611 return 1;
612 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
--- src/captcha.c
+++ src/captcha.c
@@ -548,12 +548,31 @@
548 @ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
549 @ <input type="text" name="captcha" size=8 />
550 if( showButton ){
551 @ <input type="submit" value="Submit">
552 }
553 @ <br/>\
554 captcha_speakit_button(uSeed, 0);
555 @ </td></tr></table></div>
556 }
557
558 /*
559 ** Add a "Speak the captcha" button.
560 */
561 void captcha_speakit_button(unsigned int uSeed, const char *zMsg){
562 if( zMsg==0 ) zMsg = "Speak the text";
563 @ <input type="button" value="%h(zMsg)" id="speakthetext">
564 @ <script nonce="%h(style_nonce())">
565 @ document.getElementById("speakthetext").onclick = function(){
566 @ var audio = window.fossilAudioCaptcha \
567 @ || new Audio("%R/captcha-audio/%u(uSeed)");
568 @ window.fossilAudioCaptcha = audio;
569 @ audio.currentTime = 0;
570 @ audio.play();
571 @ }
572 @ </script>
573 }
574
575 /*
576 ** WEBPAGE: test-captcha
577 ** Test the captcha-generator by rendering the value of the name= query
578 ** parameter using ascii-art. If name= is omitted, show a random 16-digit
@@ -608,5 +627,80 @@
627 captcha_generate(1);
628 @ </form>
629 style_footer();
630 return 1;
631 }
632
633 /*
634 ** Generate a WAV file that reads aloud the hex digits given by
635 ** zHex.
636 */
637 static void captcha_wav(const char *zHex, Blob *pOut){
638 int i;
639 const int szWavHdr = 44;
640 blob_init(pOut, 0, 0);
641 blob_resize(pOut, szWavHdr); /* Space for the WAV header */
642 pOut->nUsed = szWavHdr;
643 memset(pOut->aData, 0, szWavHdr);
644 if( zHex==0 || zHex[0]==0 ) zHex = "0";
645 for(i=0; zHex[i]; i++){
646 int v = hex_digit_value(zHex[i]);
647 int sz;
648 int nData;
649 const unsigned char *pData;
650 char zSoundName[50];
651 sqlite3_snprintf(sizeof(zSoundName),zSoundName,"sounds/%c.wav",
652 "0123456789abcdef"[v]);
653 /* Extra silence in between letters */
654 if( i>0 ){
655 int nQuiet = 3000;
656 blob_resize(pOut, pOut->nUsed+nQuiet);
657 memset(pOut->aData+pOut->nUsed-nQuiet, 0x80, nQuiet);
658 }
659 pData = builtin_file(zSoundName, &sz);
660 nData = sz - szWavHdr;
661 blob_resize(pOut, pOut->nUsed+nData);
662 memcpy(pOut->aData+pOut->nUsed-nData, pData+szWavHdr, nData);
663 if( zHex[i+1]==0 ){
664 int len = pOut->nUsed + 36;
665 memcpy(pOut->aData, pData, szWavHdr);
666 pOut->aData[4] = (char)(len&0xff);
667 pOut->aData[5] = (char)((len>>8)&0xff);
668 pOut->aData[6] = (char)((len>>16)&0xff);
669 pOut->aData[7] = (char)((len>>24)&0xff);
670 len = pOut->nUsed;
671 pOut->aData[40] = (char)(len&0xff);
672 pOut->aData[41] = (char)((len>>8)&0xff);
673 pOut->aData[42] = (char)((len>>16)&0xff);
674 pOut->aData[43] = (char)((len>>24)&0xff);
675 }
676 }
677 }
678
679 /*
680 ** WEBPAGE: /captcha-audio
681 **
682 ** Return a WAV file that pronounces the digits of the captcha that
683 ** is determined by the seed given in the name= query parameter.
684 */
685 void captcha_wav_page(void){
686 const char *zSeed = P("name");
687 const char *zDecode = captcha_decode((unsigned int)atoi(zSeed));
688 Blob audio;
689 captcha_wav(zDecode, &audio);
690 cgi_set_content_type("audio/wav");
691 cgi_set_content(&audio);
692 }
693
694 /*
695 ** WEBPAGE: /test-captcha-audio
696 **
697 ** Return a WAV file that pronounces the hex digits of the name=
698 ** query parameter.
699 */
700 void captcha_test_wav_page(void){
701 const char *zSeed = P("name");
702 Blob audio;
703 captcha_wav(zSeed, &audio);
704 cgi_set_content_type("audio/wav");
705 cgi_set_content(&audio);
706 }
707
+45 -15
--- src/cgi.c
+++ src/cgi.c
@@ -178,10 +178,12 @@
178178
*/
179179
static const char *zContentType = "text/html"; /* Content type of the reply */
180180
static const char *zReplyStatus = "OK"; /* Reply status description */
181181
static int iReplyStatus = 200; /* Reply status code */
182182
static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */
183
+static int rangeStart = 0; /* Start of Range: */
184
+static int rangeEnd = 0; /* End of Range: plus 1 */
183185
184186
/*
185187
** Set the reply content type
186188
*/
187189
void cgi_set_content_type(const char *zType){
@@ -272,15 +274,23 @@
272274
iReplyStatus = 200;
273275
zReplyStatus = "OK";
274276
}
275277
276278
if( g.fullHttpReply ){
279
+ if( rangeEnd>0
280
+ && iReplyStatus==200
281
+ && fossil_strcmp(P("REQUEST_METHOD"),"GET")==0
282
+ ){
283
+ iReplyStatus = 206;
284
+ zReplyStatus = "Partial Content";
285
+ }
277286
fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
278287
fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
279288
fprintf(g.httpOut, "Connection: close\r\n");
280289
fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
281290
}else{
291
+ assert( rangeEnd==0 );
282292
fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
283293
}
284294
if( g.isConst ){
285295
/* isConst means that the reply is guaranteed to be invariant, even
286296
** after configuration changes and/or Fossil binary recompiles. */
@@ -325,12 +335,12 @@
325335
if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
326336
cgi_combine_header_and_body();
327337
blob_compress(&cgiContent[0], &cgiContent[0]);
328338
}
329339
330
- if( iReplyStatus != 304 ) {
331
- if( is_gzippable() ){
340
+ if( iReplyStatus!=304 ) {
341
+ if( is_gzippable() && iReplyStatus!=206 ){
332342
int i;
333343
gzip_begin(0);
334344
for( i=0; i<2; i++ ){
335345
int size = blob_size(&cgiContent[i]);
336346
if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size);
@@ -339,10 +349,15 @@
339349
gzip_finish(&cgiContent[0]);
340350
fprintf(g.httpOut, "Content-Encoding: gzip\r\n");
341351
fprintf(g.httpOut, "Vary: Accept-Encoding\r\n");
342352
}
343353
total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]);
354
+ if( iReplyStatus==206 ){
355
+ fprintf(g.httpOut, "Content-Range: bytes %d-%d/%d\r\n",
356
+ rangeStart, rangeEnd-1, total_size);
357
+ total_size = rangeEnd - rangeStart;
358
+ }
344359
fprintf(g.httpOut, "Content-Length: %d\r\n", total_size);
345360
}else{
346361
total_size = 0;
347362
}
348363
fprintf(g.httpOut, "\r\n");
@@ -350,12 +365,20 @@
350365
&& fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
351366
){
352367
int i, size;
353368
for(i=0; i<2; i++){
354369
size = blob_size(&cgiContent[i]);
355
- if( size>0 ){
356
- fwrite(blob_buffer(&cgiContent[i]), 1, size, g.httpOut);
370
+ if( size<=rangeStart ){
371
+ rangeStart -= size;
372
+ }else{
373
+ int n = size - rangeStart;
374
+ if( n>total_size ){
375
+ n = total_size;
376
+ }
377
+ fwrite(blob_buffer(&cgiContent[i])+rangeStart, 1, n, g.httpOut);
378
+ rangeStart = 0;
379
+ total_size -= n;
357380
}
358381
}
359382
}
360383
fflush(g.httpOut);
361384
CGIDEBUG(("-------- END cgi ---------\n"));
@@ -688,36 +711,36 @@
688711
return z;
689712
}
690713
691714
/*
692715
** The input *pz points to content that is terminated by a "\r\n"
693
-** followed by the boundry marker zBoundry. An extra "--" may or
694
-** may not be appended to the boundry marker. There are *pLen characters
716
+** followed by the boundary marker zBoundary. An extra "--" may or
717
+** may not be appended to the boundary marker. There are *pLen characters
695718
** in *pz.
696719
**
697720
** This routine adds a "\000" to the end of the content (overwriting
698721
** the "\r\n") and returns a pointer to the content. The *pz input
699
-** is adjusted to point to the first line following the boundry.
722
+** is adjusted to point to the first line following the boundary.
700723
** The length of the content is stored in *pnContent.
701724
*/
702725
static char *get_bounded_content(
703726
char **pz, /* Content taken from here */
704727
int *pLen, /* Number of bytes of data in (*pz)[] */
705
- char *zBoundry, /* Boundry text marking the end of content */
728
+ char *zBoundary, /* Boundary text marking the end of content */
706729
int *pnContent /* Write the size of the content here */
707730
){
708731
char *z = *pz;
709732
int len = *pLen;
710733
int i;
711
- int nBoundry = strlen(zBoundry);
734
+ int nBoundary = strlen(zBoundary);
712735
*pnContent = len;
713736
for(i=0; i<len; i++){
714
- if( z[i]=='\n' && strncmp(zBoundry, &z[i+1], nBoundry)==0 ){
737
+ if( z[i]=='\n' && strncmp(zBoundary, &z[i+1], nBoundary)==0 ){
715738
if( i>0 && z[i-1]=='\r' ) i--;
716739
z[i] = 0;
717740
*pnContent = i;
718
- i += nBoundry;
741
+ i += nBoundary;
719742
break;
720743
}
721744
}
722745
*pz = &z[i];
723746
get_line_from_string(pz, pLen);
@@ -782,22 +805,22 @@
782805
** table.
783806
*/
784807
static void process_multipart_form_data(char *z, int len){
785808
char *zLine;
786809
int nArg, i;
787
- char *zBoundry;
810
+ char *zBoundary;
788811
char *zValue;
789812
char *zName = 0;
790813
int showBytes = 0;
791814
char *azArg[50];
792815
793
- zBoundry = get_line_from_string(&z, &len);
794
- if( zBoundry==0 ) return;
816
+ zBoundary = get_line_from_string(&z, &len);
817
+ if( zBoundary==0 ) return;
795818
while( (zLine = get_line_from_string(&z, &len))!=0 ){
796819
if( zLine[0]==0 ){
797820
int nContent = 0;
798
- zValue = get_bounded_content(&z, &len, zBoundry, &nContent);
821
+ zValue = get_bounded_content(&z, &len, zBoundary, &nContent);
799822
if( zName && zValue ){
800823
if( fossil_islower(zName[0]) ){
801824
cgi_set_parameter_nocopy(zName, zValue, 1);
802825
if( showBytes ){
803826
cgi_set_parameter_nocopy(mprintf("%s:bytes", zName),
@@ -1658,10 +1681,17 @@
16581681
}else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
16591682
const char *zIpAddr = cgi_accept_forwarded_for(zVal);
16601683
if( zIpAddr!=0 ){
16611684
g.zIpAddr = mprintf("%s", zIpAddr);
16621685
cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
1686
+ }
1687
+ }else if( fossil_strcmp(zFieldName,"range:")==0 ){
1688
+ int x1 = 0;
1689
+ int x2 = 0;
1690
+ if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){
1691
+ rangeStart = x1;
1692
+ rangeEnd = x2+1;
16631693
}
16641694
}
16651695
}
16661696
cgi_init();
16671697
cgi_trace(0);
16681698
--- src/cgi.c
+++ src/cgi.c
@@ -178,10 +178,12 @@
178 */
179 static const char *zContentType = "text/html"; /* Content type of the reply */
180 static const char *zReplyStatus = "OK"; /* Reply status description */
181 static int iReplyStatus = 200; /* Reply status code */
182 static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */
 
 
183
184 /*
185 ** Set the reply content type
186 */
187 void cgi_set_content_type(const char *zType){
@@ -272,15 +274,23 @@
272 iReplyStatus = 200;
273 zReplyStatus = "OK";
274 }
275
276 if( g.fullHttpReply ){
 
 
 
 
 
 
 
277 fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
278 fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
279 fprintf(g.httpOut, "Connection: close\r\n");
280 fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
281 }else{
 
282 fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
283 }
284 if( g.isConst ){
285 /* isConst means that the reply is guaranteed to be invariant, even
286 ** after configuration changes and/or Fossil binary recompiles. */
@@ -325,12 +335,12 @@
325 if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
326 cgi_combine_header_and_body();
327 blob_compress(&cgiContent[0], &cgiContent[0]);
328 }
329
330 if( iReplyStatus != 304 ) {
331 if( is_gzippable() ){
332 int i;
333 gzip_begin(0);
334 for( i=0; i<2; i++ ){
335 int size = blob_size(&cgiContent[i]);
336 if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size);
@@ -339,10 +349,15 @@
339 gzip_finish(&cgiContent[0]);
340 fprintf(g.httpOut, "Content-Encoding: gzip\r\n");
341 fprintf(g.httpOut, "Vary: Accept-Encoding\r\n");
342 }
343 total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]);
 
 
 
 
 
344 fprintf(g.httpOut, "Content-Length: %d\r\n", total_size);
345 }else{
346 total_size = 0;
347 }
348 fprintf(g.httpOut, "\r\n");
@@ -350,12 +365,20 @@
350 && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
351 ){
352 int i, size;
353 for(i=0; i<2; i++){
354 size = blob_size(&cgiContent[i]);
355 if( size>0 ){
356 fwrite(blob_buffer(&cgiContent[i]), 1, size, g.httpOut);
 
 
 
 
 
 
 
 
357 }
358 }
359 }
360 fflush(g.httpOut);
361 CGIDEBUG(("-------- END cgi ---------\n"));
@@ -688,36 +711,36 @@
688 return z;
689 }
690
691 /*
692 ** The input *pz points to content that is terminated by a "\r\n"
693 ** followed by the boundry marker zBoundry. An extra "--" may or
694 ** may not be appended to the boundry marker. There are *pLen characters
695 ** in *pz.
696 **
697 ** This routine adds a "\000" to the end of the content (overwriting
698 ** the "\r\n") and returns a pointer to the content. The *pz input
699 ** is adjusted to point to the first line following the boundry.
700 ** The length of the content is stored in *pnContent.
701 */
702 static char *get_bounded_content(
703 char **pz, /* Content taken from here */
704 int *pLen, /* Number of bytes of data in (*pz)[] */
705 char *zBoundry, /* Boundry text marking the end of content */
706 int *pnContent /* Write the size of the content here */
707 ){
708 char *z = *pz;
709 int len = *pLen;
710 int i;
711 int nBoundry = strlen(zBoundry);
712 *pnContent = len;
713 for(i=0; i<len; i++){
714 if( z[i]=='\n' && strncmp(zBoundry, &z[i+1], nBoundry)==0 ){
715 if( i>0 && z[i-1]=='\r' ) i--;
716 z[i] = 0;
717 *pnContent = i;
718 i += nBoundry;
719 break;
720 }
721 }
722 *pz = &z[i];
723 get_line_from_string(pz, pLen);
@@ -782,22 +805,22 @@
782 ** table.
783 */
784 static void process_multipart_form_data(char *z, int len){
785 char *zLine;
786 int nArg, i;
787 char *zBoundry;
788 char *zValue;
789 char *zName = 0;
790 int showBytes = 0;
791 char *azArg[50];
792
793 zBoundry = get_line_from_string(&z, &len);
794 if( zBoundry==0 ) return;
795 while( (zLine = get_line_from_string(&z, &len))!=0 ){
796 if( zLine[0]==0 ){
797 int nContent = 0;
798 zValue = get_bounded_content(&z, &len, zBoundry, &nContent);
799 if( zName && zValue ){
800 if( fossil_islower(zName[0]) ){
801 cgi_set_parameter_nocopy(zName, zValue, 1);
802 if( showBytes ){
803 cgi_set_parameter_nocopy(mprintf("%s:bytes", zName),
@@ -1658,10 +1681,17 @@
1658 }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
1659 const char *zIpAddr = cgi_accept_forwarded_for(zVal);
1660 if( zIpAddr!=0 ){
1661 g.zIpAddr = mprintf("%s", zIpAddr);
1662 cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
 
 
 
 
 
 
 
1663 }
1664 }
1665 }
1666 cgi_init();
1667 cgi_trace(0);
1668
--- src/cgi.c
+++ src/cgi.c
@@ -178,10 +178,12 @@
178 */
179 static const char *zContentType = "text/html"; /* Content type of the reply */
180 static const char *zReplyStatus = "OK"; /* Reply status description */
181 static int iReplyStatus = 200; /* Reply status code */
182 static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */
183 static int rangeStart = 0; /* Start of Range: */
184 static int rangeEnd = 0; /* End of Range: plus 1 */
185
186 /*
187 ** Set the reply content type
188 */
189 void cgi_set_content_type(const char *zType){
@@ -272,15 +274,23 @@
274 iReplyStatus = 200;
275 zReplyStatus = "OK";
276 }
277
278 if( g.fullHttpReply ){
279 if( rangeEnd>0
280 && iReplyStatus==200
281 && fossil_strcmp(P("REQUEST_METHOD"),"GET")==0
282 ){
283 iReplyStatus = 206;
284 zReplyStatus = "Partial Content";
285 }
286 fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
287 fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
288 fprintf(g.httpOut, "Connection: close\r\n");
289 fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
290 }else{
291 assert( rangeEnd==0 );
292 fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
293 }
294 if( g.isConst ){
295 /* isConst means that the reply is guaranteed to be invariant, even
296 ** after configuration changes and/or Fossil binary recompiles. */
@@ -325,12 +335,12 @@
335 if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
336 cgi_combine_header_and_body();
337 blob_compress(&cgiContent[0], &cgiContent[0]);
338 }
339
340 if( iReplyStatus!=304 ) {
341 if( is_gzippable() && iReplyStatus!=206 ){
342 int i;
343 gzip_begin(0);
344 for( i=0; i<2; i++ ){
345 int size = blob_size(&cgiContent[i]);
346 if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size);
@@ -339,10 +349,15 @@
349 gzip_finish(&cgiContent[0]);
350 fprintf(g.httpOut, "Content-Encoding: gzip\r\n");
351 fprintf(g.httpOut, "Vary: Accept-Encoding\r\n");
352 }
353 total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]);
354 if( iReplyStatus==206 ){
355 fprintf(g.httpOut, "Content-Range: bytes %d-%d/%d\r\n",
356 rangeStart, rangeEnd-1, total_size);
357 total_size = rangeEnd - rangeStart;
358 }
359 fprintf(g.httpOut, "Content-Length: %d\r\n", total_size);
360 }else{
361 total_size = 0;
362 }
363 fprintf(g.httpOut, "\r\n");
@@ -350,12 +365,20 @@
365 && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
366 ){
367 int i, size;
368 for(i=0; i<2; i++){
369 size = blob_size(&cgiContent[i]);
370 if( size<=rangeStart ){
371 rangeStart -= size;
372 }else{
373 int n = size - rangeStart;
374 if( n>total_size ){
375 n = total_size;
376 }
377 fwrite(blob_buffer(&cgiContent[i])+rangeStart, 1, n, g.httpOut);
378 rangeStart = 0;
379 total_size -= n;
380 }
381 }
382 }
383 fflush(g.httpOut);
384 CGIDEBUG(("-------- END cgi ---------\n"));
@@ -688,36 +711,36 @@
711 return z;
712 }
713
714 /*
715 ** The input *pz points to content that is terminated by a "\r\n"
716 ** followed by the boundary marker zBoundary. An extra "--" may or
717 ** may not be appended to the boundary marker. There are *pLen characters
718 ** in *pz.
719 **
720 ** This routine adds a "\000" to the end of the content (overwriting
721 ** the "\r\n") and returns a pointer to the content. The *pz input
722 ** is adjusted to point to the first line following the boundary.
723 ** The length of the content is stored in *pnContent.
724 */
725 static char *get_bounded_content(
726 char **pz, /* Content taken from here */
727 int *pLen, /* Number of bytes of data in (*pz)[] */
728 char *zBoundary, /* Boundary text marking the end of content */
729 int *pnContent /* Write the size of the content here */
730 ){
731 char *z = *pz;
732 int len = *pLen;
733 int i;
734 int nBoundary = strlen(zBoundary);
735 *pnContent = len;
736 for(i=0; i<len; i++){
737 if( z[i]=='\n' && strncmp(zBoundary, &z[i+1], nBoundary)==0 ){
738 if( i>0 && z[i-1]=='\r' ) i--;
739 z[i] = 0;
740 *pnContent = i;
741 i += nBoundary;
742 break;
743 }
744 }
745 *pz = &z[i];
746 get_line_from_string(pz, pLen);
@@ -782,22 +805,22 @@
805 ** table.
806 */
807 static void process_multipart_form_data(char *z, int len){
808 char *zLine;
809 int nArg, i;
810 char *zBoundary;
811 char *zValue;
812 char *zName = 0;
813 int showBytes = 0;
814 char *azArg[50];
815
816 zBoundary = get_line_from_string(&z, &len);
817 if( zBoundary==0 ) return;
818 while( (zLine = get_line_from_string(&z, &len))!=0 ){
819 if( zLine[0]==0 ){
820 int nContent = 0;
821 zValue = get_bounded_content(&z, &len, zBoundary, &nContent);
822 if( zName && zValue ){
823 if( fossil_islower(zName[0]) ){
824 cgi_set_parameter_nocopy(zName, zValue, 1);
825 if( showBytes ){
826 cgi_set_parameter_nocopy(mprintf("%s:bytes", zName),
@@ -1658,10 +1681,17 @@
1681 }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
1682 const char *zIpAddr = cgi_accept_forwarded_for(zVal);
1683 if( zIpAddr!=0 ){
1684 g.zIpAddr = mprintf("%s", zIpAddr);
1685 cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
1686 }
1687 }else if( fossil_strcmp(zFieldName,"range:")==0 ){
1688 int x1 = 0;
1689 int x2 = 0;
1690 if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){
1691 rangeStart = x1;
1692 rangeEnd = x2+1;
1693 }
1694 }
1695 }
1696 cgi_init();
1697 cgi_trace(0);
1698
+28 -16
--- src/db.c
+++ src/db.c
@@ -653,23 +653,17 @@
653653
blob_reset(&sql);
654654
return rc;
655655
}
656656
657657
/*
658
-** Execute multiple SQL statements.
658
+** Execute multiple SQL statements. The input text is executed
659
+** directly without any formatting.
659660
*/
660
-int db_multi_exec(const char *zSql, ...){
661
- Blob sql;
661
+int db_exec_sql(const char *z){
662662
int rc = SQLITE_OK;
663
- va_list ap;
664
- const char *z, *zEnd;
665663
sqlite3_stmt *pStmt;
666
- blob_init(&sql, 0, 0);
667
- va_start(ap, zSql);
668
- blob_vappendf(&sql, zSql, ap);
669
- va_end(ap);
670
- z = blob_str(&sql);
664
+ const char *zEnd;
671665
while( rc==SQLITE_OK && z[0] ){
672666
pStmt = 0;
673667
rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
674668
if( rc ){
675669
db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
@@ -679,10 +673,26 @@
679673
rc = sqlite3_finalize(pStmt);
680674
if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
681675
}
682676
z = zEnd;
683677
}
678
+ return rc;
679
+}
680
+
681
+/*
682
+** Execute multiple SQL statements using printf-style formatting.
683
+*/
684
+int db_multi_exec(const char *zSql, ...){
685
+ Blob sql;
686
+ int rc;
687
+ va_list ap;
688
+
689
+ blob_init(&sql, 0, 0);
690
+ va_start(ap, zSql);
691
+ blob_vappendf(&sql, zSql, ap);
692
+ va_end(ap);
693
+ rc = db_exec_sql(blob_str(&sql));
684694
blob_reset(&sql);
685695
return rc;
686696
}
687697
688698
/*
@@ -1031,10 +1041,12 @@
10311041
0, capability_union_step, capability_union_finalize);
10321042
sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
10331043
capability_fullcap, 0, 0);
10341044
sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
10351045
alert_find_emailaddr_func, 0, 0);
1046
+ sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
1047
+ alert_display_name_func, 0, 0);
10361048
}
10371049
10381050
#if USE_SEE
10391051
/*
10401052
** This is a pointer to the saved database encryption key string.
@@ -1286,17 +1298,17 @@
12861298
blob_init(&key, 0, 0);
12871299
db_maybe_obtain_encryption_key(zDbName, &key);
12881300
if( fossil_getenv("FOSSIL_USE_SEE_TEXTKEY")==0 ){
12891301
char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY %Q",
12901302
zDbName, zLabel, blob_str(&key));
1291
- db_multi_exec(zCmd /*works-like:""*/);
1303
+ db_exec_sql(zCmd);
12921304
fossil_secure_zero(zCmd, strlen(zCmd));
12931305
sqlite3_free(zCmd);
12941306
}else{
12951307
char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY ''",
12961308
zDbName, zLabel);
1297
- db_multi_exec(zCmd /*works-like:""*/);
1309
+ db_exec_sql(zCmd);
12981310
sqlite3_free(zCmd);
12991311
#if USE_SEE
13001312
if( blob_size(&key)>0 ){
13011313
sqlite3_key_v2(g.db, zLabel, blob_str(&key), -1);
13021314
}
@@ -1762,13 +1774,13 @@
17621774
"UPDATE vfile"
17631775
" SET mhash=(SELECT uuid FROM blob WHERE blob.rid=vfile.mrid)"
17641776
" WHERE mrid!=rid;"
17651777
);
17661778
if( !db_table_has_column("localdb", "vmerge", "mhash") ){
1767
- db_multi_exec("ALTER TABLE vmerge RENAME TO old_vmerge;");
1768
- db_multi_exec(zLocalSchemaVmerge /*works-like:""*/);
1769
- db_multi_exec(
1779
+ db_exec_sql("ALTER TABLE vmerge RENAME TO old_vmerge;");
1780
+ db_exec_sql(zLocalSchemaVmerge);
1781
+ db_exec_sql(
17701782
"INSERT OR IGNORE INTO vmerge(id,merge,mhash)"
17711783
" SELECT id, merge, blob.uuid FROM old_vmerge, blob"
17721784
" WHERE old_vmerge.merge=blob.rid;"
17731785
"DROP TABLE old_vmerge;"
17741786
);
@@ -2074,11 +2086,11 @@
20742086
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
20752087
" VALUES('anonymous',hex(randomblob(8)),'hmnc','Anon');"
20762088
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
20772089
" VALUES('nobody','','gjorz','Nobody');"
20782090
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
2079
- " VALUES('developer','','dei','Dev');"
2091
+ " VALUES('developer','','ei','Dev');"
20802092
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
20812093
" VALUES('reader','','kptw','Reader');"
20822094
);
20832095
}
20842096
}
20852097
--- src/db.c
+++ src/db.c
@@ -653,23 +653,17 @@
653 blob_reset(&sql);
654 return rc;
655 }
656
657 /*
658 ** Execute multiple SQL statements.
 
659 */
660 int db_multi_exec(const char *zSql, ...){
661 Blob sql;
662 int rc = SQLITE_OK;
663 va_list ap;
664 const char *z, *zEnd;
665 sqlite3_stmt *pStmt;
666 blob_init(&sql, 0, 0);
667 va_start(ap, zSql);
668 blob_vappendf(&sql, zSql, ap);
669 va_end(ap);
670 z = blob_str(&sql);
671 while( rc==SQLITE_OK && z[0] ){
672 pStmt = 0;
673 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
674 if( rc ){
675 db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
@@ -679,10 +673,26 @@
679 rc = sqlite3_finalize(pStmt);
680 if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
681 }
682 z = zEnd;
683 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684 blob_reset(&sql);
685 return rc;
686 }
687
688 /*
@@ -1031,10 +1041,12 @@
1031 0, capability_union_step, capability_union_finalize);
1032 sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
1033 capability_fullcap, 0, 0);
1034 sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
1035 alert_find_emailaddr_func, 0, 0);
 
 
1036 }
1037
1038 #if USE_SEE
1039 /*
1040 ** This is a pointer to the saved database encryption key string.
@@ -1286,17 +1298,17 @@
1286 blob_init(&key, 0, 0);
1287 db_maybe_obtain_encryption_key(zDbName, &key);
1288 if( fossil_getenv("FOSSIL_USE_SEE_TEXTKEY")==0 ){
1289 char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY %Q",
1290 zDbName, zLabel, blob_str(&key));
1291 db_multi_exec(zCmd /*works-like:""*/);
1292 fossil_secure_zero(zCmd, strlen(zCmd));
1293 sqlite3_free(zCmd);
1294 }else{
1295 char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY ''",
1296 zDbName, zLabel);
1297 db_multi_exec(zCmd /*works-like:""*/);
1298 sqlite3_free(zCmd);
1299 #if USE_SEE
1300 if( blob_size(&key)>0 ){
1301 sqlite3_key_v2(g.db, zLabel, blob_str(&key), -1);
1302 }
@@ -1762,13 +1774,13 @@
1762 "UPDATE vfile"
1763 " SET mhash=(SELECT uuid FROM blob WHERE blob.rid=vfile.mrid)"
1764 " WHERE mrid!=rid;"
1765 );
1766 if( !db_table_has_column("localdb", "vmerge", "mhash") ){
1767 db_multi_exec("ALTER TABLE vmerge RENAME TO old_vmerge;");
1768 db_multi_exec(zLocalSchemaVmerge /*works-like:""*/);
1769 db_multi_exec(
1770 "INSERT OR IGNORE INTO vmerge(id,merge,mhash)"
1771 " SELECT id, merge, blob.uuid FROM old_vmerge, blob"
1772 " WHERE old_vmerge.merge=blob.rid;"
1773 "DROP TABLE old_vmerge;"
1774 );
@@ -2074,11 +2086,11 @@
2074 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2075 " VALUES('anonymous',hex(randomblob(8)),'hmnc','Anon');"
2076 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2077 " VALUES('nobody','','gjorz','Nobody');"
2078 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2079 " VALUES('developer','','dei','Dev');"
2080 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2081 " VALUES('reader','','kptw','Reader');"
2082 );
2083 }
2084 }
2085
--- src/db.c
+++ src/db.c
@@ -653,23 +653,17 @@
653 blob_reset(&sql);
654 return rc;
655 }
656
657 /*
658 ** Execute multiple SQL statements. The input text is executed
659 ** directly without any formatting.
660 */
661 int db_exec_sql(const char *z){
 
662 int rc = SQLITE_OK;
 
 
663 sqlite3_stmt *pStmt;
664 const char *zEnd;
 
 
 
 
665 while( rc==SQLITE_OK && z[0] ){
666 pStmt = 0;
667 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
668 if( rc ){
669 db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
@@ -679,10 +673,26 @@
673 rc = sqlite3_finalize(pStmt);
674 if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
675 }
676 z = zEnd;
677 }
678 return rc;
679 }
680
681 /*
682 ** Execute multiple SQL statements using printf-style formatting.
683 */
684 int db_multi_exec(const char *zSql, ...){
685 Blob sql;
686 int rc;
687 va_list ap;
688
689 blob_init(&sql, 0, 0);
690 va_start(ap, zSql);
691 blob_vappendf(&sql, zSql, ap);
692 va_end(ap);
693 rc = db_exec_sql(blob_str(&sql));
694 blob_reset(&sql);
695 return rc;
696 }
697
698 /*
@@ -1031,10 +1041,12 @@
1041 0, capability_union_step, capability_union_finalize);
1042 sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
1043 capability_fullcap, 0, 0);
1044 sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
1045 alert_find_emailaddr_func, 0, 0);
1046 sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
1047 alert_display_name_func, 0, 0);
1048 }
1049
1050 #if USE_SEE
1051 /*
1052 ** This is a pointer to the saved database encryption key string.
@@ -1286,17 +1298,17 @@
1298 blob_init(&key, 0, 0);
1299 db_maybe_obtain_encryption_key(zDbName, &key);
1300 if( fossil_getenv("FOSSIL_USE_SEE_TEXTKEY")==0 ){
1301 char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY %Q",
1302 zDbName, zLabel, blob_str(&key));
1303 db_exec_sql(zCmd);
1304 fossil_secure_zero(zCmd, strlen(zCmd));
1305 sqlite3_free(zCmd);
1306 }else{
1307 char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY ''",
1308 zDbName, zLabel);
1309 db_exec_sql(zCmd);
1310 sqlite3_free(zCmd);
1311 #if USE_SEE
1312 if( blob_size(&key)>0 ){
1313 sqlite3_key_v2(g.db, zLabel, blob_str(&key), -1);
1314 }
@@ -1762,13 +1774,13 @@
1774 "UPDATE vfile"
1775 " SET mhash=(SELECT uuid FROM blob WHERE blob.rid=vfile.mrid)"
1776 " WHERE mrid!=rid;"
1777 );
1778 if( !db_table_has_column("localdb", "vmerge", "mhash") ){
1779 db_exec_sql("ALTER TABLE vmerge RENAME TO old_vmerge;");
1780 db_exec_sql(zLocalSchemaVmerge);
1781 db_exec_sql(
1782 "INSERT OR IGNORE INTO vmerge(id,merge,mhash)"
1783 " SELECT id, merge, blob.uuid FROM old_vmerge, blob"
1784 " WHERE old_vmerge.merge=blob.rid;"
1785 "DROP TABLE old_vmerge;"
1786 );
@@ -2074,11 +2086,11 @@
2086 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2087 " VALUES('anonymous',hex(randomblob(8)),'hmnc','Anon');"
2088 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2089 " VALUES('nobody','','gjorz','Nobody');"
2090 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2091 " VALUES('developer','','ei','Dev');"
2092 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2093 " VALUES('reader','','kptw','Reader');"
2094 );
2095 }
2096 }
2097
--- src/default_css.txt
+++ src/default_css.txt
@@ -741,10 +741,21 @@
741741
div.forumEdit {
742742
border: 1px solid black;
743743
padding-left: 1ex;
744744
padding-right: 1ex;
745745
}
746
+div.forumTimeline {
747
+ border: 1px solid black;
748
+ padding-left: 1ex;
749
+ padding-right: 1ex;
750
+}
751
+div.forumTimeline code {
752
+ white-space: pre-wrap;
753
+}
754
+div.markdown code {
755
+ white-space: pre-wrap;
756
+}
746757
div.forumHier, div.forumTime {
747758
border: 1px solid black;
748759
padding-left: 1ex;
749760
padding-right: 1ex;
750761
margin-top: 1ex;
751762
--- src/default_css.txt
+++ src/default_css.txt
@@ -741,10 +741,21 @@
741 div.forumEdit {
742 border: 1px solid black;
743 padding-left: 1ex;
744 padding-right: 1ex;
745 }
 
 
 
 
 
 
 
 
 
 
 
746 div.forumHier, div.forumTime {
747 border: 1px solid black;
748 padding-left: 1ex;
749 padding-right: 1ex;
750 margin-top: 1ex;
751
--- src/default_css.txt
+++ src/default_css.txt
@@ -741,10 +741,21 @@
741 div.forumEdit {
742 border: 1px solid black;
743 padding-left: 1ex;
744 padding-right: 1ex;
745 }
746 div.forumTimeline {
747 border: 1px solid black;
748 padding-left: 1ex;
749 padding-right: 1ex;
750 }
751 div.forumTimeline code {
752 white-space: pre-wrap;
753 }
754 div.markdown code {
755 white-space: pre-wrap;
756 }
757 div.forumHier, div.forumTime {
758 border: 1px solid black;
759 padding-left: 1ex;
760 padding-right: 1ex;
761 margin-top: 1ex;
762
-52
--- src/doc.c
+++ src/doc.c
@@ -1123,62 +1123,10 @@
11231123
}
11241124
cgi_set_content_type(zMime);
11251125
cgi_set_content(&bgimg);
11261126
}
11271127
1128
-/* The default favicon.ico
1129
-** A 62x71 pixel GIF image for the Fossil lizzard icon.
1130
-*/
1131
-static const unsigned char favicon[] = {
1132
- 71, 73, 70, 56, 55, 97, 62, 0, 71, 0,244, 0, 0, 85,129,149, 95,136,155,
1133
- 99,139,157,106,144,162,113,150,166,116,152,168,127,160,175,138,168,182,148,
1134
- 176,188,159,184,195,170,192,202,180,199,208,184,202,210,191,207,215,201,215,
1135
- 221,212,223,228,223,231,235,226,227,226,226,234,237,233,239,241,240,244,246,
1136
- 244,247,248,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1137
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0,
1138
- 62, 0, 71, 0, 0, 5,255, 96,100,141,100,105,158,168, 37, 41,132,192,164,
1139
- 112, 44,207,102, 99, 0, 56, 16, 84,116,239,199,141, 65,110,232,248, 25,141,
1140
- 193,161, 82,113,108,202, 32, 55,229,210, 73, 61, 41,164, 88,102,181, 10, 41,
1141
- 96,179, 91,106, 35,240, 5,135,143,137,242, 87,123,246, 33,190, 81,108,163,
1142
- 237,198, 14, 30,113,233,131, 78,115, 72, 11,115, 87,101, 19,124, 51, 66, 74,
1143
- 8, 19, 16, 67,100, 74,133, 50, 15,101,135, 56, 11, 74, 6,143, 49,126,106,
1144
- 56, 8,145, 67, 9,152, 48,139,155, 5, 22, 13, 74,115,161, 41,147,101, 13,
1145
- 130, 57,132,170, 40,167,155, 0, 94, 57, 3,178, 48,183,181, 57,160,186, 40,
1146
- 19,141,189, 0, 69,192, 40, 16,195,155,185,199, 41,201,189,191,205,193,188,
1147
- 131,210, 49,175, 88,209,214, 38, 19, 3, 11, 19,111,127, 60,219, 39, 55,204,
1148
- 19, 11, 6,100, 5, 10,227,228, 37,163, 0,239,117, 56,238,243, 49,195,177,
1149
- 247, 48,158, 56,251, 50,216,254,197, 56,128,107,158, 2,125,171,114, 92,218,
1150
- 246, 96, 66, 3, 4, 50,134,176,145, 6, 97, 64,144, 24, 19,136,108, 91,177,
1151
- 160, 0,194, 19,253, 0,216,107,214,224,192,129, 5, 16, 83,255,244, 43,213,
1152
- 195, 24,159, 27,169, 64,230, 88,208,227,129,182, 54, 4, 89,158, 24,181,163,
1153
- 199, 1,155, 52,233, 8,130,176, 83, 24,128,137, 50, 18, 32, 48, 48,114, 11,
1154
- 173,137, 19,110, 4, 64,105, 1,194, 30,140, 68, 15, 24, 24,224, 50, 76, 70,
1155
- 0, 11,171, 54, 26,160,181,194,149,148, 40,174,148,122, 64,180,208,161, 17,
1156
- 207,112,164, 1,128, 96,148, 78, 18, 21,194, 33,229, 51,247, 65,133, 97, 5,
1157
- 250, 69,229,100, 34,220,128,166,116,190, 62, 8,167,195,170, 47,163, 0,130,
1158
- 90,152, 11,160,173,170, 27,154, 26, 91,232,151,171, 18, 14,162,253, 98,170,
1159
- 18, 70,171, 64,219, 10, 67,136,134,187,116, 75,180, 46,179,174,135, 4,189,
1160
- 229,231, 78, 40, 10, 62,226,164,172, 64,240,167,170, 10, 18,124,188, 10,107,
1161
- 65,193, 94, 11, 93,171, 28,248, 17,239, 46,140, 78, 97, 34, 25,153, 36, 99,
1162
- 65,130, 7,203,183,168, 51, 34,136, 25,140, 10, 6, 16, 28,255,145,241,230,
1163
- 140, 10, 66,178,167,112, 48,192,128,129, 9, 31,141, 84,138, 63,163,162, 2,
1164
- 203,206,240, 56, 55, 98,192,188, 15,185, 50,160, 6, 0,125, 62, 33,214,195,
1165
- 33, 5, 24,184, 25,231, 14,201,245,144, 23,126,104,228, 0,145, 2, 13,140,
1166
- 244,212, 17, 21, 20,176,159, 17, 95,225,160,128, 16, 1, 32,224,142, 32,227,
1167
- 125, 87, 64, 0, 16, 54,129,205, 2,141, 76, 53,130,103, 37,166, 64,144,107,
1168
- 78,196, 5,192, 0, 54, 50,229, 9,141, 49, 84,194, 35, 12,196,153, 48,192,
1169
- 137, 57, 84, 24, 7, 87,159,249,240,215,143,105,241,118,149, 9,139, 4, 64,
1170
- 203,141, 35,140,129,131, 16,222,125,231,128, 2,238, 17,152, 66, 3, 5, 56,
1171
- 224,159,103, 16, 76, 25, 75, 5, 11,164,215, 96, 9, 14, 16, 36,225, 15, 11,
1172
- 40,144,192,156, 41, 10,178,199, 3, 66, 64, 80,193, 3,124, 90, 48,129,129,
1173
- 102,177, 18,192,154, 49, 84,240,208, 92, 22,149, 96, 39, 9, 31, 74, 17, 94,
1174
- 3, 8,177,199, 72, 59, 85, 76, 25,216, 8,139,194,197,138,163, 69, 96,115,
1175
- 0,147, 72, 72, 84, 28, 14, 79, 86,233,230, 23,113, 26,160,128, 3, 10, 58,
1176
- 129,103, 14,159,214,163,146,117,238,213,154,128,151,109, 84, 64,217, 13, 27,
1177
- 10,228, 39, 2,235,164,168, 74, 8, 0, 59,
1178
-};
1179
-
11801128
11811129
/*
11821130
** WEBPAGE: favicon.ico
11831131
**
11841132
** Return the default favicon.ico image. The returned image is for the
11851133
--- src/doc.c
+++ src/doc.c
@@ -1123,62 +1123,10 @@
1123 }
1124 cgi_set_content_type(zMime);
1125 cgi_set_content(&bgimg);
1126 }
1127
1128 /* The default favicon.ico
1129 ** A 62x71 pixel GIF image for the Fossil lizzard icon.
1130 */
1131 static const unsigned char favicon[] = {
1132 71, 73, 70, 56, 55, 97, 62, 0, 71, 0,244, 0, 0, 85,129,149, 95,136,155,
1133 99,139,157,106,144,162,113,150,166,116,152,168,127,160,175,138,168,182,148,
1134 176,188,159,184,195,170,192,202,180,199,208,184,202,210,191,207,215,201,215,
1135 221,212,223,228,223,231,235,226,227,226,226,234,237,233,239,241,240,244,246,
1136 244,247,248,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1137 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0,
1138 62, 0, 71, 0, 0, 5,255, 96,100,141,100,105,158,168, 37, 41,132,192,164,
1139 112, 44,207,102, 99, 0, 56, 16, 84,116,239,199,141, 65,110,232,248, 25,141,
1140 193,161, 82,113,108,202, 32, 55,229,210, 73, 61, 41,164, 88,102,181, 10, 41,
1141 96,179, 91,106, 35,240, 5,135,143,137,242, 87,123,246, 33,190, 81,108,163,
1142 237,198, 14, 30,113,233,131, 78,115, 72, 11,115, 87,101, 19,124, 51, 66, 74,
1143 8, 19, 16, 67,100, 74,133, 50, 15,101,135, 56, 11, 74, 6,143, 49,126,106,
1144 56, 8,145, 67, 9,152, 48,139,155, 5, 22, 13, 74,115,161, 41,147,101, 13,
1145 130, 57,132,170, 40,167,155, 0, 94, 57, 3,178, 48,183,181, 57,160,186, 40,
1146 19,141,189, 0, 69,192, 40, 16,195,155,185,199, 41,201,189,191,205,193,188,
1147 131,210, 49,175, 88,209,214, 38, 19, 3, 11, 19,111,127, 60,219, 39, 55,204,
1148 19, 11, 6,100, 5, 10,227,228, 37,163, 0,239,117, 56,238,243, 49,195,177,
1149 247, 48,158, 56,251, 50,216,254,197, 56,128,107,158, 2,125,171,114, 92,218,
1150 246, 96, 66, 3, 4, 50,134,176,145, 6, 97, 64,144, 24, 19,136,108, 91,177,
1151 160, 0,194, 19,253, 0,216,107,214,224,192,129, 5, 16, 83,255,244, 43,213,
1152 195, 24,159, 27,169, 64,230, 88,208,227,129,182, 54, 4, 89,158, 24,181,163,
1153 199, 1,155, 52,233, 8,130,176, 83, 24,128,137, 50, 18, 32, 48, 48,114, 11,
1154 173,137, 19,110, 4, 64,105, 1,194, 30,140, 68, 15, 24, 24,224, 50, 76, 70,
1155 0, 11,171, 54, 26,160,181,194,149,148, 40,174,148,122, 64,180,208,161, 17,
1156 207,112,164, 1,128, 96,148, 78, 18, 21,194, 33,229, 51,247, 65,133, 97, 5,
1157 250, 69,229,100, 34,220,128,166,116,190, 62, 8,167,195,170, 47,163, 0,130,
1158 90,152, 11,160,173,170, 27,154, 26, 91,232,151,171, 18, 14,162,253, 98,170,
1159 18, 70,171, 64,219, 10, 67,136,134,187,116, 75,180, 46,179,174,135, 4,189,
1160 229,231, 78, 40, 10, 62,226,164,172, 64,240,167,170, 10, 18,124,188, 10,107,
1161 65,193, 94, 11, 93,171, 28,248, 17,239, 46,140, 78, 97, 34, 25,153, 36, 99,
1162 65,130, 7,203,183,168, 51, 34,136, 25,140, 10, 6, 16, 28,255,145,241,230,
1163 140, 10, 66,178,167,112, 48,192,128,129, 9, 31,141, 84,138, 63,163,162, 2,
1164 203,206,240, 56, 55, 98,192,188, 15,185, 50,160, 6, 0,125, 62, 33,214,195,
1165 33, 5, 24,184, 25,231, 14,201,245,144, 23,126,104,228, 0,145, 2, 13,140,
1166 244,212, 17, 21, 20,176,159, 17, 95,225,160,128, 16, 1, 32,224,142, 32,227,
1167 125, 87, 64, 0, 16, 54,129,205, 2,141, 76, 53,130,103, 37,166, 64,144,107,
1168 78,196, 5,192, 0, 54, 50,229, 9,141, 49, 84,194, 35, 12,196,153, 48,192,
1169 137, 57, 84, 24, 7, 87,159,249,240,215,143,105,241,118,149, 9,139, 4, 64,
1170 203,141, 35,140,129,131, 16,222,125,231,128, 2,238, 17,152, 66, 3, 5, 56,
1171 224,159,103, 16, 76, 25, 75, 5, 11,164,215, 96, 9, 14, 16, 36,225, 15, 11,
1172 40,144,192,156, 41, 10,178,199, 3, 66, 64, 80,193, 3,124, 90, 48,129,129,
1173 102,177, 18,192,154, 49, 84,240,208, 92, 22,149, 96, 39, 9, 31, 74, 17, 94,
1174 3, 8,177,199, 72, 59, 85, 76, 25,216, 8,139,194,197,138,163, 69, 96,115,
1175 0,147, 72, 72, 84, 28, 14, 79, 86,233,230, 23,113, 26,160,128, 3, 10, 58,
1176 129,103, 14,159,214,163,146,117,238,213,154,128,151,109, 84, 64,217, 13, 27,
1177 10,228, 39, 2,235,164,168, 74, 8, 0, 59,
1178 };
1179
1180
1181 /*
1182 ** WEBPAGE: favicon.ico
1183 **
1184 ** Return the default favicon.ico image. The returned image is for the
1185
--- src/doc.c
+++ src/doc.c
@@ -1123,62 +1123,10 @@
1123 }
1124 cgi_set_content_type(zMime);
1125 cgi_set_content(&bgimg);
1126 }
1127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1128
1129 /*
1130 ** WEBPAGE: favicon.ico
1131 **
1132 ** Return the default favicon.ico image. The returned image is for the
1133
+2 -1
--- src/file.c
+++ src/file.c
@@ -499,10 +499,11 @@
499499
while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){
500500
fwrite(zBuf, 1, got, out);
501501
}
502502
fclose(in);
503503
fclose(out);
504
+ if( file_isexe(zFrom, ExtFILE) ) file_setexe(zTo, 1);
504505
}
505506
506507
/*
507508
** COMMAND: test-file-copy
508509
**
@@ -1785,11 +1786,11 @@
17851786
rc = 1;
17861787
}
17871788
return rc;
17881789
#else
17891790
extern char **environ;
1790
- environ = 0;
1791
+ environ[0] = 0;
17911792
return 0;
17921793
#endif
17931794
}
17941795
17951796
/*
17961797
--- src/file.c
+++ src/file.c
@@ -499,10 +499,11 @@
499 while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){
500 fwrite(zBuf, 1, got, out);
501 }
502 fclose(in);
503 fclose(out);
 
504 }
505
506 /*
507 ** COMMAND: test-file-copy
508 **
@@ -1785,11 +1786,11 @@
1785 rc = 1;
1786 }
1787 return rc;
1788 #else
1789 extern char **environ;
1790 environ = 0;
1791 return 0;
1792 #endif
1793 }
1794
1795 /*
1796
--- src/file.c
+++ src/file.c
@@ -499,10 +499,11 @@
499 while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){
500 fwrite(zBuf, 1, got, out);
501 }
502 fclose(in);
503 fclose(out);
504 if( file_isexe(zFrom, ExtFILE) ) file_setexe(zTo, 1);
505 }
506
507 /*
508 ** COMMAND: test-file-copy
509 **
@@ -1785,11 +1786,11 @@
1786 rc = 1;
1787 }
1788 return rc;
1789 #else
1790 extern char **environ;
1791 environ[0] = 0;
1792 return 0;
1793 #endif
1794 }
1795
1796 /*
1797
+163 -42
--- src/forum.c
+++ src/forum.c
@@ -57,10 +57,27 @@
5757
ForumEntry *pDisplay; /* Entries in display order */
5858
ForumEntry *pTail; /* Last on the display list */
5959
int mxIndent; /* Maximum indentation level */
6060
};
6161
#endif /* INTERFACE */
62
+
63
+/*
64
+** Return true if the forum entry with the given rid has been
65
+** subsequently edited.
66
+*/
67
+int forum_rid_has_been_edited(int rid){
68
+ static Stmt q;
69
+ int res;
70
+ db_static_prepare(&q,
71
+ "SELECT 1 FROM forumpost A, forumpost B"
72
+ " WHERE A.fpid=$rid AND B.froot=A.froot AND B.fprev=$rid"
73
+ );
74
+ db_bind_int(&q, "$rid", rid);
75
+ res = db_step(&q)==SQLITE_ROW;
76
+ db_reset(&q);
77
+ return res;
78
+}
6279
6380
/*
6481
** Delete a complete ForumThread and all its entries.
6582
*/
6683
static void forumthread_delete(ForumThread *pThread){
@@ -199,17 +216,46 @@
199216
}
200217
201218
/* Return the result */
202219
return pThread;
203220
}
221
+
222
+/*
223
+** List all forum threads to standard output.
224
+*/
225
+static void forum_thread_list(void){
226
+ Stmt q;
227
+ db_prepare(&q,
228
+ " SELECT"
229
+ " datetime(max(fmtime)),"
230
+ " sum(fprev IS NULL),"
231
+ " froot"
232
+ " FROM forumpost"
233
+ " GROUP BY froot"
234
+ " ORDER BY 1;"
235
+ );
236
+ fossil_print(" id cnt most recent post\n");
237
+ fossil_print("------ ---- -------------------\n");
238
+ while( db_step(&q)==SQLITE_ROW ){
239
+ fossil_print("%6d %4d %s\n",
240
+ db_column_int(&q, 2),
241
+ db_column_int(&q, 1),
242
+ db_column_text(&q, 0)
243
+ );
244
+ }
245
+ db_finalize(&q);
246
+}
204247
205248
/*
206249
** COMMAND: test-forumthread
207250
**
208
-** Usage: %fossil test-forumthread THREADID
251
+** Usage: %fossil test-forumthread [THREADID]
252
+**
253
+** Display a summary of all messages on a thread THREADID. If the
254
+** THREADID argument is omitted, then show a list of all threads.
209255
**
210
-** Display a summary of all messages on a thread.
256
+** This command is intended for testing an analysis only.
211257
*/
212258
void forumthread_cmd(void){
213259
int fpid;
214260
int froot;
215261
const char *zName;
@@ -216,10 +262,14 @@
216262
ForumThread *pThread;
217263
ForumEntry *p;
218264
219265
db_find_and_open_repository(0,0);
220266
verify_all_options();
267
+ if( g.argc==2 ){
268
+ forum_thread_list();
269
+ return;
270
+ }
221271
if( g.argc!=3 ) usage("THREADID");
222272
zName = g.argv[2];
223273
fpid = symbolic_name_to_rid(zName, "f");
224274
if( fpid<=0 ){
225275
fpid = db_int(0, "SELECT rid FROM blob WHERE rid=%d", atoi(zName));
@@ -302,23 +352,59 @@
302352
@ Trust user "%h(pPost->zUser)"
303353
@ so that future posts by "%h(pPost->zUser)" do not require moderation.
304354
@ </label>
305355
@ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
306356
}
357
+
358
+/*
359
+** Compute a display name from a login name.
360
+**
361
+** If the input login is found in the USER table, then check the USER.INFO
362
+** field to see if it has display-name followed by an email address.
363
+** If it does, that becomes the new display name. If not, let the display
364
+** name just be the login.
365
+**
366
+** Space to hold the returned name is obtained from fossil_strdup() or
367
+** mprintf() and should be freed by the caller.
368
+*/
369
+char *display_name_from_login(const char *zLogin){
370
+ static Stmt q;
371
+ char *zResult;
372
+ db_static_prepare(&q,
373
+ "SELECT display_name(info) FROM user WHERE login=$login"
374
+ );
375
+ db_bind_text(&q, "$login", zLogin);
376
+ if( db_step(&q)==SQLITE_ROW && db_column_type(&q,0)==SQLITE_TEXT ){
377
+ const char *zDisplay = db_column_text(&q,0);
378
+ if( fossil_strcmp(zDisplay,zLogin)==0 ){
379
+ zResult = fossil_strdup(zLogin);
380
+ }else{
381
+ zResult = mprintf("%s (%s)", zDisplay, zLogin);
382
+ }
383
+ }else{
384
+ zResult = fossil_strdup(zLogin);
385
+ }
386
+ db_reset(&q);
387
+ return zResult;
388
+}
389
+
307390
308391
/*
309392
** Display all posts in a forum thread in chronological order
310393
*/
311
-static void forum_display_chronological(int froot, int target){
394
+static void forum_display_chronological(int froot, int target, int bRawMode){
312395
ForumThread *pThread = forumthread_create(froot, 0);
313396
ForumEntry *p;
314397
int notAnon = login_is_individual();
398
+ char cMode = bRawMode ? 'r' : 'c';
315399
for(p=pThread->pFirst; p; p=p->pNext){
316400
char *zDate;
317401
Manifest *pPost;
318402
int isPrivate; /* True for posts awaiting moderation */
319403
int sameUser; /* True if author is also the reader */
404
+ const char *zUuid;
405
+ char *zDisplayName; /* The display name */
320406
321407
pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
322408
if( pPost==0 ) continue;
323409
if( p->fpid==target ){
324410
@ <div id="forum%d(p->fpid)" class="forumTime forumSel">
@@ -329,14 +415,16 @@
329415
}
330416
if( pPost->zThreadTitle ){
331417
@ <h1>%h(pPost->zThreadTitle)</h1>
332418
}
333419
zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
334
- @ <p>(%d(p->sid)) By %h(pPost->zUser) on %h(zDate)
420
+ zDisplayName = display_name_from_login(pPost->zUser);
421
+ @ <h3 class='forumPostHdr'>(%d(p->sid)) By %h(zDisplayName) on %h(zDate)
422
+ fossil_free(zDisplayName);
335423
fossil_free(zDate);
336424
if( p->pEdit ){
337
- @ edit of %z(href("%R/forumpost/%S?t=c",p->pEdit->zUuid))\
425
+ @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEdit->zUuid,cMode))\
338426
@ %d(p->pEdit->sid)</a>
339427
}
340428
if( g.perm.Debug ){
341429
@ <span class="debug">\
342430
@ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span>
@@ -343,27 +431,33 @@
343431
}
344432
if( p->firt ){
345433
ForumEntry *pIrt = p->pPrev;
346434
while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
347435
if( pIrt ){
348
- @ in reply to %z(href("%R/forumpost/%S?t=c",pIrt->zUuid))\
436
+ @ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
349437
@ %d(pIrt->sid)</a>
350438
}
351439
}
440
+ zUuid = p->zUuid;
352441
if( p->pLeaf ){
353
- @ updated by %z(href("%R/forumpost/%S?t=c",p->pLeaf->zUuid))\
442
+ @ updated by %z(href("%R/forumpost/%S?t=%c",p->pLeaf->zUuid,cMode))\
354443
@ %d(p->pLeaf->sid)</a>
444
+ zUuid = p->pLeaf->zUuid;
355445
}
356446
if( p->fpid!=target ){
357
- @ %z(href("%R/forumpost/%S?t=c",p->zUuid))[link]</a>
447
+ @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a>
448
+ }
449
+ if( !bRawMode && fossil_strcmp(pPost->zMimetype,"text/plain")!=0 ){
450
+ @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
358451
}
359452
isPrivate = content_is_private(p->fpid);
360453
sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
454
+ @ </h3>
361455
if( isPrivate && !g.perm.ModForum && !sameUser ){
362456
@ <p><span class="modpending">Awaiting Moderator Approval</span></p>
363457
}else{
364
- forum_render(0, pPost->zMimetype, pPost->zWiki, 0);
458
+ forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki, 0);
365459
}
366460
if( g.perm.WrForum && p->pLeaf==0 ){
367461
int sameUser = login_is_individual()
368462
&& fossil_strcmp(pPost->zUser, g.zLogin)==0;
369463
@ <p><form action="%R/forumedit" method="POST">
@@ -421,10 +515,11 @@
421515
iIndentScale--;
422516
}
423517
for(p=pThread->pDisplay; p; p=p->pDisplay){
424518
int isPrivate; /* True for posts awaiting moderation */
425519
int sameUser; /* True if reader is also the poster */
520
+ char *zDisplayName; /* User name to be displayed */
426521
pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
427522
if( p->pLeaf ){
428523
fpid = p->pLeaf->fpid;
429524
zUuid = p->pLeaf->zUuid;
430525
pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
@@ -444,11 +539,14 @@
444539
if( pPost==0 ) continue;
445540
if( pPost->zThreadTitle ){
446541
@ <h1>%h(pPost->zThreadTitle)</h1>
447542
}
448543
zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
449
- @ <p>(%d(p->pLeaf?p->pLeaf->sid:p->sid)) By %h(pOPost->zUser) on %h(zDate)
544
+ zDisplayName = display_name_from_login(pOPost->zUser);
545
+ @ <h3 class='forumPostHdr'>\
546
+ @ (%d(p->pLeaf?p->pLeaf->sid:p->sid)) By %h(zDisplayName) on %h(zDate)
547
+ fossil_free(zDisplayName);
450548
fossil_free(zDate);
451549
if( g.perm.Debug ){
452550
@ <span class="debug">\
453551
@ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span>
454552
}
@@ -467,18 +565,22 @@
467565
manifest_destroy(pOPost);
468566
}
469567
if( fpid!=target ){
470568
@ %z(href("%R/forumpost/%S",zUuid))[link]</a>
471569
}
570
+ if( fossil_strcmp(pPost->zMimetype,"text/plain")!=0 ){
571
+ @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
572
+ }
472573
if( p->firt ){
473574
ForumEntry *pIrt = p->pPrev;
474575
while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
475576
if( pIrt ){
476577
@ in reply to %z(href("%R/forumpost/%S?t=h",pIrt->zUuid))\
477578
@ %d(pIrt->sid)</a>
478579
}
479580
}
581
+ @ </h3>
480582
isPrivate = content_is_private(fpid);
481583
sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
482584
if( isPrivate && !g.perm.ModForum && !sameUser ){
483585
@ <p><span class="modpending">Awaiting Moderator Approval</span></p>
484586
}else{
@@ -524,12 +626,17 @@
524626
** selected posting into view after the page loads.
525627
**
526628
** Query parameters:
527629
**
528630
** name=X REQUIRED. The hash of the post to display
529
-** t=MODE Display mode. MODE is 'c' for chronological or
530
-** 'h' for hierarchical, or 'a' for automatic.
631
+** t=MODE Display mode.
632
+** 'c' for chronological
633
+** 'h' for hierarchical
634
+** 'a' for automatic
635
+** 'r' for raw
636
+** raw If present, show only the post specified and
637
+** show its original unformatted source text.
531638
*/
532639
void forumpost_page(void){
533640
forumthread_page();
534641
}
535642
@@ -536,37 +643,21 @@
536643
/*
537644
** Add an appropriate style_header() to include title of the
538645
** given forum post.
539646
*/
540647
static int forumthread_page_header(int froot, int fpid){
541
- Blob title;
542
- int mxForumPostTitleLen = 50;
543
- char *zThreadTitle = "";
648
+ char *zThreadTitle = 0;
544649
545650
zThreadTitle = db_text("",
546651
"SELECT"
547652
" substr(event.comment,instr(event.comment,':')+2)"
548653
" FROM forumpost, event"
549654
" WHERE event.objid=forumpost.fpid"
550655
" AND forumpost.fpid=%d;",
551656
fpid
552657
);
553
- blob_set(&title, zThreadTitle);
554
- /* truncate the title when longer than max allowed;
555
- * in case of UTF-8 make sure the truncated string remains valid,
556
- * otherwise (different encoding?) pass as-is
557
- */
558
- if( mxForumPostTitleLen>0 && blob_size(&title)>mxForumPostTitleLen ){
559
- int len;
560
- len = utf8_codepoint_index(blob_str(&title), mxForumPostTitleLen);
561
- if( len ){
562
- blob_truncate(&title, len);
563
- blob_append(&title, "...", 3);
564
- }
565
- }
566
- style_header("%s%s", blob_str(&title), blob_size(&title) ? " - Forum" : "Forum");
567
- blob_reset(&title);
658
+ style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum");
568659
fossil_free(zThreadTitle);
569660
return 0;
570661
}
571662
572663
/*
@@ -577,18 +668,22 @@
577668
** the postings in the thread are selected.
578669
**
579670
** Query parameters:
580671
**
581672
** name=X REQUIRED. The hash of any post of the thread.
582
-** t=MODE Display mode. MODE is 'c' for chronological or
583
-** 'h' for hierarchical, or 'a' for automatic.
673
+** t=MODE Display mode. MODE is...
674
+** 'c' for chronological, or
675
+** 'h' for hierarchical, or
676
+** 'a' for automatic, or
677
+** 'r' for raw.
584678
*/
585679
void forumthread_page(void){
586680
int fpid;
587681
int froot;
588682
const char *zName = P("name");
589683
const char *zMode = PD("t","a");
684
+ int bRaw = PB("raw");
590685
login_check_credentials();
591686
if( !g.perm.RdForum ){
592687
login_needed(g.anon.RdForum);
593688
return;
594689
}
@@ -610,15 +705,37 @@
610705
}else{
611706
zMode = "h";
612707
}
613708
}
614709
forumthread_page_header(froot, fpid);
615
- if( zMode[0]=='c' ){
710
+ if( bRaw && fpid ){
711
+ Manifest *pPost;
712
+ pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
713
+ if( pPost==0 ){
714
+ @ <p>No such forum post: %h(zName)
715
+ }else{
716
+ int isPrivate = content_is_private(fpid);
717
+ int notAnon = login_is_individual();
718
+ int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
719
+ if( isPrivate && !g.perm.ModForum && !sameUser ){
720
+ @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
721
+ }else{
722
+ forum_render(0, "text/plain", pPost->zWiki, 0);
723
+ }
724
+ manifest_destroy(pPost);
725
+ }
726
+ }else if( zMode[0]=='c' ){
727
+ style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
728
+ style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
729
+ forum_display_chronological(froot, fpid, 0);
730
+ }else if( zMode[0]=='r' ){
731
+ style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
616732
style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
617
- forum_display_chronological(froot, fpid);
733
+ forum_display_chronological(froot, fpid, 1);
618734
}else{
619735
style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
736
+ style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
620737
forum_display_hierarchical(froot, fpid);
621738
}
622739
style_load_js("forum.js");
623740
style_footer();
624741
}
@@ -972,41 +1089,44 @@
9721089
if( zMimetype==0 ) zMimetype = fossil_strdup(pPost->zMimetype);
9731090
if( zTitle==0 && pPost->zThreadTitle!=0 ){
9741091
zTitle = fossil_strdup(pPost->zThreadTitle);
9751092
}
9761093
style_header("Edit %s", zTitle ? "Post" : "Reply");
977
- @ <h1>Original Post:</h1>
1094
+ @ <h2>Original Post:</h2>
9781095
forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
9791096
"forumEdit");
9801097
if( P("preview") ){
981
- @ <h1>Preview of Edited Post:</h1>
1098
+ @ <h2>Preview of Edited Post:</h2>
9821099
forum_render(zTitle, zMimetype, zContent,"forumEdit");
9831100
}
984
- @ <h1>Revised Message:</h1>
1101
+ @ <h2>Revised Message:</h2>
9851102
@ <form action="%R/forume2" method="POST">
9861103
@ <input type="hidden" name="fpid" value="%h(P("fpid"))">
9871104
@ <input type="hidden" name="edit" value="1">
9881105
forum_from_line();
9891106
forum_entry_widget(zTitle, zMimetype, zContent);
9901107
}else{
9911108
/* Reply */
1109
+ char *zDisplayName;
9921110
zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
9931111
zContent = PDT("content","");
9941112
style_header("Reply");
995
- @ <h1>Replying To:</h1>
9961113
if( pRootPost->zThreadTitle ){
997
- @ <h3>%h(pRootPost->zThreadTitle)</h3>
1114
+ @ <h1>Thread: %h(pRootPost->zThreadTitle)</h1>
9981115
}
1116
+ @ <h2>Replying To:</h2>
9991117
zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
1000
- @ <p>%h(pPost->zThreadTitle ? "Post" : "Reply") by %h(pPost->zUser) on %h(zDate)
1118
+ zDisplayName = display_name_from_login(pPost->zUser);
1119
+ @ <h3 class='forumPostHdr'>By %h(zDisplayName) on %h(zDate)</h3>
1120
+ fossil_free(zDisplayName);
10011121
fossil_free(zDate);
10021122
forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit");
10031123
if( P("preview") ){
1004
- @ <h1>Preview:</h1>
1124
+ @ <h2>Preview:</h2>
10051125
forum_render(0, zMimetype,zContent, "forumEdit");
10061126
}
1007
- @ <h1>Enter Reply:</h1>
1127
+ @ <h2>Enter Reply:</h2>
10081128
@ <form action="%R/forume2" method="POST">
10091129
@ <input type="hidden" name="fpid" value="%h(P("fpid"))">
10101130
@ <input type="hidden" name="reply" value="1">
10111131
forum_from_line();
10121132
forum_entry_widget(0, zMimetype, zContent);
@@ -1032,10 +1152,11 @@
10321152
@ </form>
10331153
style_footer();
10341154
}
10351155
10361156
/*
1157
+** WEBPAGE: forummain
10371158
** WEBPAGE: forum
10381159
**
10391160
** The main page for the forum feature. Show a list of recent forum
10401161
** threads. Also show a search box at the top if search is enabled,
10411162
** and a button for creating a new thread, if enabled.
10421163
--- src/forum.c
+++ src/forum.c
@@ -57,10 +57,27 @@
57 ForumEntry *pDisplay; /* Entries in display order */
58 ForumEntry *pTail; /* Last on the display list */
59 int mxIndent; /* Maximum indentation level */
60 };
61 #endif /* INTERFACE */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
63 /*
64 ** Delete a complete ForumThread and all its entries.
65 */
66 static void forumthread_delete(ForumThread *pThread){
@@ -199,17 +216,46 @@
199 }
200
201 /* Return the result */
202 return pThread;
203 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
205 /*
206 ** COMMAND: test-forumthread
207 **
208 ** Usage: %fossil test-forumthread THREADID
 
 
 
209 **
210 ** Display a summary of all messages on a thread.
211 */
212 void forumthread_cmd(void){
213 int fpid;
214 int froot;
215 const char *zName;
@@ -216,10 +262,14 @@
216 ForumThread *pThread;
217 ForumEntry *p;
218
219 db_find_and_open_repository(0,0);
220 verify_all_options();
 
 
 
 
221 if( g.argc!=3 ) usage("THREADID");
222 zName = g.argv[2];
223 fpid = symbolic_name_to_rid(zName, "f");
224 if( fpid<=0 ){
225 fpid = db_int(0, "SELECT rid FROM blob WHERE rid=%d", atoi(zName));
@@ -302,23 +352,59 @@
302 @ Trust user "%h(pPost->zUser)"
303 @ so that future posts by "%h(pPost->zUser)" do not require moderation.
304 @ </label>
305 @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
306 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
308 /*
309 ** Display all posts in a forum thread in chronological order
310 */
311 static void forum_display_chronological(int froot, int target){
312 ForumThread *pThread = forumthread_create(froot, 0);
313 ForumEntry *p;
314 int notAnon = login_is_individual();
 
315 for(p=pThread->pFirst; p; p=p->pNext){
316 char *zDate;
317 Manifest *pPost;
318 int isPrivate; /* True for posts awaiting moderation */
319 int sameUser; /* True if author is also the reader */
 
 
320
321 pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
322 if( pPost==0 ) continue;
323 if( p->fpid==target ){
324 @ <div id="forum%d(p->fpid)" class="forumTime forumSel">
@@ -329,14 +415,16 @@
329 }
330 if( pPost->zThreadTitle ){
331 @ <h1>%h(pPost->zThreadTitle)</h1>
332 }
333 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
334 @ <p>(%d(p->sid)) By %h(pPost->zUser) on %h(zDate)
 
 
335 fossil_free(zDate);
336 if( p->pEdit ){
337 @ edit of %z(href("%R/forumpost/%S?t=c",p->pEdit->zUuid))\
338 @ %d(p->pEdit->sid)</a>
339 }
340 if( g.perm.Debug ){
341 @ <span class="debug">\
342 @ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span>
@@ -343,27 +431,33 @@
343 }
344 if( p->firt ){
345 ForumEntry *pIrt = p->pPrev;
346 while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
347 if( pIrt ){
348 @ in reply to %z(href("%R/forumpost/%S?t=c",pIrt->zUuid))\
349 @ %d(pIrt->sid)</a>
350 }
351 }
 
352 if( p->pLeaf ){
353 @ updated by %z(href("%R/forumpost/%S?t=c",p->pLeaf->zUuid))\
354 @ %d(p->pLeaf->sid)</a>
 
355 }
356 if( p->fpid!=target ){
357 @ %z(href("%R/forumpost/%S?t=c",p->zUuid))[link]</a>
 
 
 
358 }
359 isPrivate = content_is_private(p->fpid);
360 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
 
361 if( isPrivate && !g.perm.ModForum && !sameUser ){
362 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
363 }else{
364 forum_render(0, pPost->zMimetype, pPost->zWiki, 0);
365 }
366 if( g.perm.WrForum && p->pLeaf==0 ){
367 int sameUser = login_is_individual()
368 && fossil_strcmp(pPost->zUser, g.zLogin)==0;
369 @ <p><form action="%R/forumedit" method="POST">
@@ -421,10 +515,11 @@
421 iIndentScale--;
422 }
423 for(p=pThread->pDisplay; p; p=p->pDisplay){
424 int isPrivate; /* True for posts awaiting moderation */
425 int sameUser; /* True if reader is also the poster */
 
426 pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
427 if( p->pLeaf ){
428 fpid = p->pLeaf->fpid;
429 zUuid = p->pLeaf->zUuid;
430 pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
@@ -444,11 +539,14 @@
444 if( pPost==0 ) continue;
445 if( pPost->zThreadTitle ){
446 @ <h1>%h(pPost->zThreadTitle)</h1>
447 }
448 zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
449 @ <p>(%d(p->pLeaf?p->pLeaf->sid:p->sid)) By %h(pOPost->zUser) on %h(zDate)
 
 
 
450 fossil_free(zDate);
451 if( g.perm.Debug ){
452 @ <span class="debug">\
453 @ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span>
454 }
@@ -467,18 +565,22 @@
467 manifest_destroy(pOPost);
468 }
469 if( fpid!=target ){
470 @ %z(href("%R/forumpost/%S",zUuid))[link]</a>
471 }
 
 
 
472 if( p->firt ){
473 ForumEntry *pIrt = p->pPrev;
474 while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
475 if( pIrt ){
476 @ in reply to %z(href("%R/forumpost/%S?t=h",pIrt->zUuid))\
477 @ %d(pIrt->sid)</a>
478 }
479 }
 
480 isPrivate = content_is_private(fpid);
481 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
482 if( isPrivate && !g.perm.ModForum && !sameUser ){
483 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
484 }else{
@@ -524,12 +626,17 @@
524 ** selected posting into view after the page loads.
525 **
526 ** Query parameters:
527 **
528 ** name=X REQUIRED. The hash of the post to display
529 ** t=MODE Display mode. MODE is 'c' for chronological or
530 ** 'h' for hierarchical, or 'a' for automatic.
 
 
 
 
 
531 */
532 void forumpost_page(void){
533 forumthread_page();
534 }
535
@@ -536,37 +643,21 @@
536 /*
537 ** Add an appropriate style_header() to include title of the
538 ** given forum post.
539 */
540 static int forumthread_page_header(int froot, int fpid){
541 Blob title;
542 int mxForumPostTitleLen = 50;
543 char *zThreadTitle = "";
544
545 zThreadTitle = db_text("",
546 "SELECT"
547 " substr(event.comment,instr(event.comment,':')+2)"
548 " FROM forumpost, event"
549 " WHERE event.objid=forumpost.fpid"
550 " AND forumpost.fpid=%d;",
551 fpid
552 );
553 blob_set(&title, zThreadTitle);
554 /* truncate the title when longer than max allowed;
555 * in case of UTF-8 make sure the truncated string remains valid,
556 * otherwise (different encoding?) pass as-is
557 */
558 if( mxForumPostTitleLen>0 && blob_size(&title)>mxForumPostTitleLen ){
559 int len;
560 len = utf8_codepoint_index(blob_str(&title), mxForumPostTitleLen);
561 if( len ){
562 blob_truncate(&title, len);
563 blob_append(&title, "...", 3);
564 }
565 }
566 style_header("%s%s", blob_str(&title), blob_size(&title) ? " - Forum" : "Forum");
567 blob_reset(&title);
568 fossil_free(zThreadTitle);
569 return 0;
570 }
571
572 /*
@@ -577,18 +668,22 @@
577 ** the postings in the thread are selected.
578 **
579 ** Query parameters:
580 **
581 ** name=X REQUIRED. The hash of any post of the thread.
582 ** t=MODE Display mode. MODE is 'c' for chronological or
583 ** 'h' for hierarchical, or 'a' for automatic.
 
 
 
584 */
585 void forumthread_page(void){
586 int fpid;
587 int froot;
588 const char *zName = P("name");
589 const char *zMode = PD("t","a");
 
590 login_check_credentials();
591 if( !g.perm.RdForum ){
592 login_needed(g.anon.RdForum);
593 return;
594 }
@@ -610,15 +705,37 @@
610 }else{
611 zMode = "h";
612 }
613 }
614 forumthread_page_header(froot, fpid);
615 if( zMode[0]=='c' ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616 style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
617 forum_display_chronological(froot, fpid);
618 }else{
619 style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
 
620 forum_display_hierarchical(froot, fpid);
621 }
622 style_load_js("forum.js");
623 style_footer();
624 }
@@ -972,41 +1089,44 @@
972 if( zMimetype==0 ) zMimetype = fossil_strdup(pPost->zMimetype);
973 if( zTitle==0 && pPost->zThreadTitle!=0 ){
974 zTitle = fossil_strdup(pPost->zThreadTitle);
975 }
976 style_header("Edit %s", zTitle ? "Post" : "Reply");
977 @ <h1>Original Post:</h1>
978 forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
979 "forumEdit");
980 if( P("preview") ){
981 @ <h1>Preview of Edited Post:</h1>
982 forum_render(zTitle, zMimetype, zContent,"forumEdit");
983 }
984 @ <h1>Revised Message:</h1>
985 @ <form action="%R/forume2" method="POST">
986 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
987 @ <input type="hidden" name="edit" value="1">
988 forum_from_line();
989 forum_entry_widget(zTitle, zMimetype, zContent);
990 }else{
991 /* Reply */
 
992 zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
993 zContent = PDT("content","");
994 style_header("Reply");
995 @ <h1>Replying To:</h1>
996 if( pRootPost->zThreadTitle ){
997 @ <h3>%h(pRootPost->zThreadTitle)</h3>
998 }
 
999 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
1000 @ <p>%h(pPost->zThreadTitle ? "Post" : "Reply") by %h(pPost->zUser) on %h(zDate)
 
 
1001 fossil_free(zDate);
1002 forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit");
1003 if( P("preview") ){
1004 @ <h1>Preview:</h1>
1005 forum_render(0, zMimetype,zContent, "forumEdit");
1006 }
1007 @ <h1>Enter Reply:</h1>
1008 @ <form action="%R/forume2" method="POST">
1009 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
1010 @ <input type="hidden" name="reply" value="1">
1011 forum_from_line();
1012 forum_entry_widget(0, zMimetype, zContent);
@@ -1032,10 +1152,11 @@
1032 @ </form>
1033 style_footer();
1034 }
1035
1036 /*
 
1037 ** WEBPAGE: forum
1038 **
1039 ** The main page for the forum feature. Show a list of recent forum
1040 ** threads. Also show a search box at the top if search is enabled,
1041 ** and a button for creating a new thread, if enabled.
1042
--- src/forum.c
+++ src/forum.c
@@ -57,10 +57,27 @@
57 ForumEntry *pDisplay; /* Entries in display order */
58 ForumEntry *pTail; /* Last on the display list */
59 int mxIndent; /* Maximum indentation level */
60 };
61 #endif /* INTERFACE */
62
63 /*
64 ** Return true if the forum entry with the given rid has been
65 ** subsequently edited.
66 */
67 int forum_rid_has_been_edited(int rid){
68 static Stmt q;
69 int res;
70 db_static_prepare(&q,
71 "SELECT 1 FROM forumpost A, forumpost B"
72 " WHERE A.fpid=$rid AND B.froot=A.froot AND B.fprev=$rid"
73 );
74 db_bind_int(&q, "$rid", rid);
75 res = db_step(&q)==SQLITE_ROW;
76 db_reset(&q);
77 return res;
78 }
79
80 /*
81 ** Delete a complete ForumThread and all its entries.
82 */
83 static void forumthread_delete(ForumThread *pThread){
@@ -199,17 +216,46 @@
216 }
217
218 /* Return the result */
219 return pThread;
220 }
221
222 /*
223 ** List all forum threads to standard output.
224 */
225 static void forum_thread_list(void){
226 Stmt q;
227 db_prepare(&q,
228 " SELECT"
229 " datetime(max(fmtime)),"
230 " sum(fprev IS NULL),"
231 " froot"
232 " FROM forumpost"
233 " GROUP BY froot"
234 " ORDER BY 1;"
235 );
236 fossil_print(" id cnt most recent post\n");
237 fossil_print("------ ---- -------------------\n");
238 while( db_step(&q)==SQLITE_ROW ){
239 fossil_print("%6d %4d %s\n",
240 db_column_int(&q, 2),
241 db_column_int(&q, 1),
242 db_column_text(&q, 0)
243 );
244 }
245 db_finalize(&q);
246 }
247
248 /*
249 ** COMMAND: test-forumthread
250 **
251 ** Usage: %fossil test-forumthread [THREADID]
252 **
253 ** Display a summary of all messages on a thread THREADID. If the
254 ** THREADID argument is omitted, then show a list of all threads.
255 **
256 ** This command is intended for testing an analysis only.
257 */
258 void forumthread_cmd(void){
259 int fpid;
260 int froot;
261 const char *zName;
@@ -216,10 +262,14 @@
262 ForumThread *pThread;
263 ForumEntry *p;
264
265 db_find_and_open_repository(0,0);
266 verify_all_options();
267 if( g.argc==2 ){
268 forum_thread_list();
269 return;
270 }
271 if( g.argc!=3 ) usage("THREADID");
272 zName = g.argv[2];
273 fpid = symbolic_name_to_rid(zName, "f");
274 if( fpid<=0 ){
275 fpid = db_int(0, "SELECT rid FROM blob WHERE rid=%d", atoi(zName));
@@ -302,23 +352,59 @@
352 @ Trust user "%h(pPost->zUser)"
353 @ so that future posts by "%h(pPost->zUser)" do not require moderation.
354 @ </label>
355 @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
356 }
357
358 /*
359 ** Compute a display name from a login name.
360 **
361 ** If the input login is found in the USER table, then check the USER.INFO
362 ** field to see if it has display-name followed by an email address.
363 ** If it does, that becomes the new display name. If not, let the display
364 ** name just be the login.
365 **
366 ** Space to hold the returned name is obtained from fossil_strdup() or
367 ** mprintf() and should be freed by the caller.
368 */
369 char *display_name_from_login(const char *zLogin){
370 static Stmt q;
371 char *zResult;
372 db_static_prepare(&q,
373 "SELECT display_name(info) FROM user WHERE login=$login"
374 );
375 db_bind_text(&q, "$login", zLogin);
376 if( db_step(&q)==SQLITE_ROW && db_column_type(&q,0)==SQLITE_TEXT ){
377 const char *zDisplay = db_column_text(&q,0);
378 if( fossil_strcmp(zDisplay,zLogin)==0 ){
379 zResult = fossil_strdup(zLogin);
380 }else{
381 zResult = mprintf("%s (%s)", zDisplay, zLogin);
382 }
383 }else{
384 zResult = fossil_strdup(zLogin);
385 }
386 db_reset(&q);
387 return zResult;
388 }
389
390
391 /*
392 ** Display all posts in a forum thread in chronological order
393 */
394 static void forum_display_chronological(int froot, int target, int bRawMode){
395 ForumThread *pThread = forumthread_create(froot, 0);
396 ForumEntry *p;
397 int notAnon = login_is_individual();
398 char cMode = bRawMode ? 'r' : 'c';
399 for(p=pThread->pFirst; p; p=p->pNext){
400 char *zDate;
401 Manifest *pPost;
402 int isPrivate; /* True for posts awaiting moderation */
403 int sameUser; /* True if author is also the reader */
404 const char *zUuid;
405 char *zDisplayName; /* The display name */
406
407 pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
408 if( pPost==0 ) continue;
409 if( p->fpid==target ){
410 @ <div id="forum%d(p->fpid)" class="forumTime forumSel">
@@ -329,14 +415,16 @@
415 }
416 if( pPost->zThreadTitle ){
417 @ <h1>%h(pPost->zThreadTitle)</h1>
418 }
419 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
420 zDisplayName = display_name_from_login(pPost->zUser);
421 @ <h3 class='forumPostHdr'>(%d(p->sid)) By %h(zDisplayName) on %h(zDate)
422 fossil_free(zDisplayName);
423 fossil_free(zDate);
424 if( p->pEdit ){
425 @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEdit->zUuid,cMode))\
426 @ %d(p->pEdit->sid)</a>
427 }
428 if( g.perm.Debug ){
429 @ <span class="debug">\
430 @ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span>
@@ -343,27 +431,33 @@
431 }
432 if( p->firt ){
433 ForumEntry *pIrt = p->pPrev;
434 while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
435 if( pIrt ){
436 @ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
437 @ %d(pIrt->sid)</a>
438 }
439 }
440 zUuid = p->zUuid;
441 if( p->pLeaf ){
442 @ updated by %z(href("%R/forumpost/%S?t=%c",p->pLeaf->zUuid,cMode))\
443 @ %d(p->pLeaf->sid)</a>
444 zUuid = p->pLeaf->zUuid;
445 }
446 if( p->fpid!=target ){
447 @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a>
448 }
449 if( !bRawMode && fossil_strcmp(pPost->zMimetype,"text/plain")!=0 ){
450 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
451 }
452 isPrivate = content_is_private(p->fpid);
453 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
454 @ </h3>
455 if( isPrivate && !g.perm.ModForum && !sameUser ){
456 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
457 }else{
458 forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki, 0);
459 }
460 if( g.perm.WrForum && p->pLeaf==0 ){
461 int sameUser = login_is_individual()
462 && fossil_strcmp(pPost->zUser, g.zLogin)==0;
463 @ <p><form action="%R/forumedit" method="POST">
@@ -421,10 +515,11 @@
515 iIndentScale--;
516 }
517 for(p=pThread->pDisplay; p; p=p->pDisplay){
518 int isPrivate; /* True for posts awaiting moderation */
519 int sameUser; /* True if reader is also the poster */
520 char *zDisplayName; /* User name to be displayed */
521 pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
522 if( p->pLeaf ){
523 fpid = p->pLeaf->fpid;
524 zUuid = p->pLeaf->zUuid;
525 pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
@@ -444,11 +539,14 @@
539 if( pPost==0 ) continue;
540 if( pPost->zThreadTitle ){
541 @ <h1>%h(pPost->zThreadTitle)</h1>
542 }
543 zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
544 zDisplayName = display_name_from_login(pOPost->zUser);
545 @ <h3 class='forumPostHdr'>\
546 @ (%d(p->pLeaf?p->pLeaf->sid:p->sid)) By %h(zDisplayName) on %h(zDate)
547 fossil_free(zDisplayName);
548 fossil_free(zDate);
549 if( g.perm.Debug ){
550 @ <span class="debug">\
551 @ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span>
552 }
@@ -467,18 +565,22 @@
565 manifest_destroy(pOPost);
566 }
567 if( fpid!=target ){
568 @ %z(href("%R/forumpost/%S",zUuid))[link]</a>
569 }
570 if( fossil_strcmp(pPost->zMimetype,"text/plain")!=0 ){
571 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
572 }
573 if( p->firt ){
574 ForumEntry *pIrt = p->pPrev;
575 while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
576 if( pIrt ){
577 @ in reply to %z(href("%R/forumpost/%S?t=h",pIrt->zUuid))\
578 @ %d(pIrt->sid)</a>
579 }
580 }
581 @ </h3>
582 isPrivate = content_is_private(fpid);
583 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
584 if( isPrivate && !g.perm.ModForum && !sameUser ){
585 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
586 }else{
@@ -524,12 +626,17 @@
626 ** selected posting into view after the page loads.
627 **
628 ** Query parameters:
629 **
630 ** name=X REQUIRED. The hash of the post to display
631 ** t=MODE Display mode.
632 ** 'c' for chronological
633 ** 'h' for hierarchical
634 ** 'a' for automatic
635 ** 'r' for raw
636 ** raw If present, show only the post specified and
637 ** show its original unformatted source text.
638 */
639 void forumpost_page(void){
640 forumthread_page();
641 }
642
@@ -536,37 +643,21 @@
643 /*
644 ** Add an appropriate style_header() to include title of the
645 ** given forum post.
646 */
647 static int forumthread_page_header(int froot, int fpid){
648 char *zThreadTitle = 0;
 
 
649
650 zThreadTitle = db_text("",
651 "SELECT"
652 " substr(event.comment,instr(event.comment,':')+2)"
653 " FROM forumpost, event"
654 " WHERE event.objid=forumpost.fpid"
655 " AND forumpost.fpid=%d;",
656 fpid
657 );
658 style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659 fossil_free(zThreadTitle);
660 return 0;
661 }
662
663 /*
@@ -577,18 +668,22 @@
668 ** the postings in the thread are selected.
669 **
670 ** Query parameters:
671 **
672 ** name=X REQUIRED. The hash of any post of the thread.
673 ** t=MODE Display mode. MODE is...
674 ** 'c' for chronological, or
675 ** 'h' for hierarchical, or
676 ** 'a' for automatic, or
677 ** 'r' for raw.
678 */
679 void forumthread_page(void){
680 int fpid;
681 int froot;
682 const char *zName = P("name");
683 const char *zMode = PD("t","a");
684 int bRaw = PB("raw");
685 login_check_credentials();
686 if( !g.perm.RdForum ){
687 login_needed(g.anon.RdForum);
688 return;
689 }
@@ -610,15 +705,37 @@
705 }else{
706 zMode = "h";
707 }
708 }
709 forumthread_page_header(froot, fpid);
710 if( bRaw && fpid ){
711 Manifest *pPost;
712 pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
713 if( pPost==0 ){
714 @ <p>No such forum post: %h(zName)
715 }else{
716 int isPrivate = content_is_private(fpid);
717 int notAnon = login_is_individual();
718 int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
719 if( isPrivate && !g.perm.ModForum && !sameUser ){
720 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
721 }else{
722 forum_render(0, "text/plain", pPost->zWiki, 0);
723 }
724 manifest_destroy(pPost);
725 }
726 }else if( zMode[0]=='c' ){
727 style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
728 style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
729 forum_display_chronological(froot, fpid, 0);
730 }else if( zMode[0]=='r' ){
731 style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
732 style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
733 forum_display_chronological(froot, fpid, 1);
734 }else{
735 style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
736 style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
737 forum_display_hierarchical(froot, fpid);
738 }
739 style_load_js("forum.js");
740 style_footer();
741 }
@@ -972,41 +1089,44 @@
1089 if( zMimetype==0 ) zMimetype = fossil_strdup(pPost->zMimetype);
1090 if( zTitle==0 && pPost->zThreadTitle!=0 ){
1091 zTitle = fossil_strdup(pPost->zThreadTitle);
1092 }
1093 style_header("Edit %s", zTitle ? "Post" : "Reply");
1094 @ <h2>Original Post:</h2>
1095 forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
1096 "forumEdit");
1097 if( P("preview") ){
1098 @ <h2>Preview of Edited Post:</h2>
1099 forum_render(zTitle, zMimetype, zContent,"forumEdit");
1100 }
1101 @ <h2>Revised Message:</h2>
1102 @ <form action="%R/forume2" method="POST">
1103 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
1104 @ <input type="hidden" name="edit" value="1">
1105 forum_from_line();
1106 forum_entry_widget(zTitle, zMimetype, zContent);
1107 }else{
1108 /* Reply */
1109 char *zDisplayName;
1110 zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
1111 zContent = PDT("content","");
1112 style_header("Reply");
 
1113 if( pRootPost->zThreadTitle ){
1114 @ <h1>Thread: %h(pRootPost->zThreadTitle)</h1>
1115 }
1116 @ <h2>Replying To:</h2>
1117 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
1118 zDisplayName = display_name_from_login(pPost->zUser);
1119 @ <h3 class='forumPostHdr'>By %h(zDisplayName) on %h(zDate)</h3>
1120 fossil_free(zDisplayName);
1121 fossil_free(zDate);
1122 forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit");
1123 if( P("preview") ){
1124 @ <h2>Preview:</h2>
1125 forum_render(0, zMimetype,zContent, "forumEdit");
1126 }
1127 @ <h2>Enter Reply:</h2>
1128 @ <form action="%R/forume2" method="POST">
1129 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
1130 @ <input type="hidden" name="reply" value="1">
1131 forum_from_line();
1132 forum_entry_widget(0, zMimetype, zContent);
@@ -1032,10 +1152,11 @@
1152 @ </form>
1153 style_footer();
1154 }
1155
1156 /*
1157 ** WEBPAGE: forummain
1158 ** WEBPAGE: forum
1159 **
1160 ** The main page for the forum feature. Show a list of recent forum
1161 ** threads. Also show a search box at the top if search is enabled,
1162 ** and a button for creating a new thread, if enabled.
1163
+1 -1
--- src/http.c
+++ src/http.c
@@ -484,11 +484,11 @@
484484
** --compress Use ZLIB compression on the payload
485485
** --mimetype TYPE Mimetype of the payload
486486
** --out FILE Store the reply in FILE
487487
** -v Verbose output
488488
*/
489
-void test_wget_command(void){
489
+void test_httpmsg_command(void){
490490
const char *zMimetype;
491491
const char *zInFile;
492492
const char *zOutFile;
493493
Blob in, out;
494494
unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
495495
--- src/http.c
+++ src/http.c
@@ -484,11 +484,11 @@
484 ** --compress Use ZLIB compression on the payload
485 ** --mimetype TYPE Mimetype of the payload
486 ** --out FILE Store the reply in FILE
487 ** -v Verbose output
488 */
489 void test_wget_command(void){
490 const char *zMimetype;
491 const char *zInFile;
492 const char *zOutFile;
493 Blob in, out;
494 unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
495
--- src/http.c
+++ src/http.c
@@ -484,11 +484,11 @@
484 ** --compress Use ZLIB compression on the payload
485 ** --mimetype TYPE Mimetype of the payload
486 ** --out FILE Store the reply in FILE
487 ** -v Verbose output
488 */
489 void test_httpmsg_command(void){
490 const char *zMimetype;
491 const char *zInFile;
492 const char *zOutFile;
493 Blob in, out;
494 unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
495
+1 -1
--- src/info.c
+++ src/info.c
@@ -2477,11 +2477,11 @@
24772477
@ </blockquote>
24782478
}
24792479
24802480
@ <div class="section">Changes</div>
24812481
@ <p>
2482
- ticket_output_change_artifact(pTktChng, 0);
2482
+ ticket_output_change_artifact(pTktChng, 0, 1);
24832483
manifest_destroy(pTktChng);
24842484
style_footer();
24852485
}
24862486
24872487
24882488
--- src/info.c
+++ src/info.c
@@ -2477,11 +2477,11 @@
2477 @ </blockquote>
2478 }
2479
2480 @ <div class="section">Changes</div>
2481 @ <p>
2482 ticket_output_change_artifact(pTktChng, 0);
2483 manifest_destroy(pTktChng);
2484 style_footer();
2485 }
2486
2487
2488
--- src/info.c
+++ src/info.c
@@ -2477,11 +2477,11 @@
2477 @ </blockquote>
2478 }
2479
2480 @ <div class="section">Changes</div>
2481 @ <p>
2482 ticket_output_change_artifact(pTktChng, 0, 1);
2483 manifest_destroy(pTktChng);
2484 style_footer();
2485 }
2486
2487
2488
-1
--- src/json.c
+++ src/json.c
@@ -1898,11 +1898,10 @@
18981898
obj = cson_value_get_object(sub);
18991899
19001900
#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
19011901
ADD(Setup,"setup");
19021902
ADD(Admin,"admin");
1903
- ADD(Delete,"delete");
19041903
ADD(Password,"password");
19051904
ADD(Query,"query"); /* don't think this one is actually used */
19061905
ADD(Write,"checkin");
19071906
ADD(Read,"checkout");
19081907
ADD(Hyperlink,"history");
19091908
--- src/json.c
+++ src/json.c
@@ -1898,11 +1898,10 @@
1898 obj = cson_value_get_object(sub);
1899
1900 #define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
1901 ADD(Setup,"setup");
1902 ADD(Admin,"admin");
1903 ADD(Delete,"delete");
1904 ADD(Password,"password");
1905 ADD(Query,"query"); /* don't think this one is actually used */
1906 ADD(Write,"checkin");
1907 ADD(Read,"checkout");
1908 ADD(Hyperlink,"history");
1909
--- src/json.c
+++ src/json.c
@@ -1898,11 +1898,10 @@
1898 obj = cson_value_get_object(sub);
1899
1900 #define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
1901 ADD(Setup,"setup");
1902 ADD(Admin,"admin");
 
1903 ADD(Password,"password");
1904 ADD(Query,"query"); /* don't think this one is actually used */
1905 ADD(Write,"checkin");
1906 ADD(Read,"checkout");
1907 ADD(Hyperlink,"history");
1908
+57 -76
--- src/login.c
+++ src/login.c
@@ -111,32 +111,10 @@
111111
}else{
112112
fossil_redirect_home();
113113
}
114114
}
115115
116
-/*
117
-** The IP address of the client is stored as part of login cookies.
118
-** But some clients are behind firewalls that shift the IP address
119
-** with each HTTP request. To allow such (broken) clients to log in,
120
-** extract just a prefix of the IP address.
121
-*/
122
-static char *ipPrefix(const char *zIP){
123
- int i, j;
124
- static int ip_prefix_terms = -1;
125
- if( ip_prefix_terms<0 ){
126
- ip_prefix_terms = db_get_int("ip-prefix-terms",2);
127
- }
128
- if( ip_prefix_terms==0 ) return mprintf("0");
129
- for(i=j=0; zIP[i]; i++){
130
- if( zIP[i]=='.' ){
131
- j++;
132
- if( j==ip_prefix_terms ) break;
133
- }
134
- }
135
- return mprintf("%.*s", i, zIP);
136
-}
137
-
138116
/*
139117
** Return an abbreviated project code. The abbreviation is the first
140118
** 16 characters of the project code.
141119
**
142120
** Memory is obtained from malloc.
@@ -295,30 +273,27 @@
295273
const char *zExpire = db_get("cookie-expire","8766");
296274
int expires = atoi(zExpire)*3600;
297275
char *zHash;
298276
char *zCookie;
299277
const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
300
- char *zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */
301278
302279
assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
303280
zHash = db_text(0,
304281
"SELECT cookie FROM user"
305282
" WHERE uid=%d"
306
- " AND ipaddr=%Q"
307283
" AND cexpire>julianday('now')"
308284
" AND length(cookie)>30",
309
- uid, zRemoteAddr);
285
+ uid);
310286
if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
311287
zCookie = login_gen_user_cookie_value(zUsername, zHash);
312288
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
313289
record_login_attempt(zUsername, zIpAddr, 1);
314290
db_multi_exec(
315
- "UPDATE user SET cookie=%Q, ipaddr=%Q, "
291
+ "UPDATE user SET cookie=%Q,"
316292
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
317
- zHash, zRemoteAddr, expires, uid
293
+ zHash, expires, uid
318294
);
319
- free(zRemoteAddr);
320295
free(zHash);
321296
if( zDest ){
322297
*zDest = zCookie;
323298
}else{
324299
free(zCookie);
@@ -327,34 +302,25 @@
327302
328303
/* Sets a cookie for an anonymous user login, which looks like this:
329304
**
330305
** HASH/TIME/anonymous
331306
**
332
-** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
333
-** is the abbreviated IP address and SECRET is captcha-secret.
334
-**
335
-** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR
336
-** is used.
307
+** Where HASH is the sha1sum of TIME/SECRET, in which SECRET is captcha-secret.
337308
**
338309
** If zCookieDest is not NULL then the generated cookie is assigned to
339310
** *zCookieDest and the caller must eventually free() it.
340311
*/
341312
void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest ){
342313
const char *zNow; /* Current time (julian day number) */
343314
char *zCookie; /* The login cookie */
344315
const char *zCookieName; /* Name of the login cookie */
345316
Blob b; /* Blob used during cookie construction */
346
- char *zRemoteAddr; /* Abbreviated IP address */
347
- if(!zIpAddr){
348
- zIpAddr = PD("REMOTE_ADDR","nil");
349
- }
350
- zRemoteAddr = ipPrefix(zIpAddr);
351317
zCookieName = login_cookie_name();
352318
zNow = db_text("0", "SELECT julianday('now')");
353
- assert( zCookieName && zRemoteAddr && zIpAddr && zNow );
319
+ assert( zCookieName && zNow );
354320
blob_init(&b, zNow, -1);
355
- blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
321
+ blob_appendf(&b, "/%s", db_get("captcha-secret",""));
356322
sha1sum_blob(&b, &b);
357323
zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
358324
blob_reset(&b);
359325
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
360326
if( zCookieDest ){
@@ -702,10 +668,18 @@
702668
if( g.zLogin ){
703669
@ <p>Currently logged in as <b>%h(g.zLogin)</b>.
704670
@ <input type="submit" name="out" value="Logout"></p>
705671
@ </form>
706672
}else{
673
+ unsigned int uSeed = captcha_seed();
674
+ if( g.zLogin==0 && (anonFlag || zGoto==0) ){
675
+ zAnonPw = db_text(0, "SELECT pw FROM user"
676
+ " WHERE login='anonymous'"
677
+ " AND cap!=''");
678
+ }else{
679
+ zAnonPw = 0;
680
+ }
707681
@ <table class="login_out">
708682
@ <tr>
709683
@ <td class="form_label">User ID:</td>
710684
if( anonFlag ){
711685
@ <td><input type="text" id="u" name="u" value="anonymous" size="30"></td>
@@ -713,11 +687,15 @@
713687
@ <td><input type="text" id="u" name="u" value="" size="30" /></td>
714688
}
715689
@ </tr>
716690
@ <tr>
717691
@ <td class="form_label">Password:</td>
718
- @ <td><input type="password" id="p" name="p" value="" size="30" /></td>
692
+ @ <td><input type="password" id="p" name="p" value="" size="30" />\
693
+ if( zAnonPw && !noAnon ){
694
+ captcha_speakit_button(uSeed, "Speak password for \"anonymous\"");
695
+ }
696
+ @ </td>
719697
@ </tr>
720698
if( P("HTTPS")==0 ){
721699
@ <tr><td class="form_label">Warning:</td>
722700
@ <td><span class='securityWarning'>
723701
@ Your password will be sent in the clear over an
@@ -728,15 +706,10 @@
728706
@ Consider logging in at
729707
@ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
730708
}
731709
@ </span></td></tr>
732710
}
733
- if( g.zLogin==0 && (anonFlag || zGoto==0) ){
734
- zAnonPw = db_text(0, "SELECT pw FROM user"
735
- " WHERE login='anonymous'"
736
- " AND cap!=''");
737
- }
738711
@ <tr>
739712
@ <td></td>
740713
@ <td><input type="submit" name="in" value="Login"></td>
741714
@ </tr>
742715
if( !noAnon && login_self_register_available(0) ){
@@ -745,11 +718,10 @@
745718
@ <td><input type="submit" name="self" value="Create A New Account">
746719
@ </tr>
747720
}
748721
@ </table>
749722
if( zAnonPw && !noAnon ){
750
- unsigned int uSeed = captcha_seed();
751723
const char *zDecoded = captcha_decode(uSeed);
752724
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
753725
char *zCaptcha = captcha_render(zDecoded);
754726
755727
@ <p><input type="hidden" name="cs" value="%u(uSeed)" />
@@ -803,12 +775,11 @@
803775
** Return true if a transfer was made and false if not.
804776
*/
805777
static int login_transfer_credentials(
806778
const char *zLogin, /* Login we are looking for */
807779
const char *zCode, /* Project code of peer repository */
808
- const char *zHash, /* HASH from login cookie HASH/CODE/LOGIN */
809
- const char *zRemoteAddr /* Request comes from here */
780
+ const char *zHash /* HASH from login cookie HASH/CODE/LOGIN */
810781
){
811782
sqlite3 *pOther = 0; /* The other repository */
812783
sqlite3_stmt *pStmt; /* Query against the other repository */
813784
char *zSQL; /* SQL of the query against other repo */
814785
char *zOtherRepo; /* Filename of the other repository */
@@ -832,24 +803,23 @@
832803
constant_time_cmp_function, 0, 0);
833804
sqlite3_busy_timeout(pOther, 5000);
834805
zSQL = mprintf(
835806
"SELECT cexpire FROM user"
836807
" WHERE login=%Q"
837
- " AND ipaddr=%Q"
838808
" AND length(cap)>0"
839809
" AND length(pw)>0"
840810
" AND cexpire>julianday('now')"
841811
" AND constant_time_cmp(cookie,%Q)=0",
842
- zLogin, zRemoteAddr, zHash
812
+ zLogin, zHash
843813
);
844814
pStmt = 0;
845815
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
846816
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
847817
db_multi_exec(
848
- "UPDATE user SET cookie=%Q, ipaddr=%Q, cexpire=%.17g"
818
+ "UPDATE user SET cookie=%Q, cexpire=%.17g"
849819
" WHERE login=%Q",
850
- zHash, zRemoteAddr,
820
+ zHash,
851821
sqlite3_column_double(pStmt, 0), zLogin
852822
);
853823
nXfer++;
854824
}
855825
sqlite3_finalize(pStmt);
@@ -869,33 +839,30 @@
869839
if( fossil_strcmp(zLogin, "reader")==0 ) return 1;
870840
return 0;
871841
}
872842
873843
/*
874
-** Lookup the uid for a non-built-in user with zLogin and zCookie and
875
-** zRemoteAddr. Return 0 if not found.
844
+** Lookup the uid for a non-built-in user with zLogin and zCookie.
845
+** Return 0 if not found.
876846
**
877847
** Note that this only searches for logged-in entries with matching
878
-** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr)
879
-** entries.
848
+** zCookie (db: user.cookie) entries.
880849
*/
881850
static int login_find_user(
882851
const char *zLogin, /* User name */
883
- const char *zCookie, /* Login cookie value */
884
- const char *zRemoteAddr /* Abbreviated IP address for valid login */
852
+ const char *zCookie /* Login cookie value */
885853
){
886854
int uid;
887855
if( login_is_special(zLogin) ) return 0;
888856
uid = db_int(0,
889857
"SELECT uid FROM user"
890858
" WHERE login=%Q"
891
- " AND ipaddr=%Q"
892859
" AND cexpire>julianday('now')"
893860
" AND length(cap)>0"
894861
" AND length(pw)>0"
895862
" AND constant_time_cmp(cookie,%Q)=0",
896
- zLogin, zRemoteAddr, zCookie
863
+ zLogin, zCookie
897864
);
898865
return uid;
899866
}
900867
901868
/*
@@ -964,11 +931,10 @@
964931
*/
965932
void login_check_credentials(void){
966933
int uid = 0; /* User id */
967934
const char *zCookie; /* Text of the login cookie */
968935
const char *zIpAddr; /* Raw IP address of the requestor */
969
- char *zRemoteAddr; /* Abbreviated IP address of the requestor */
970936
const char *zCap = 0; /* Capability string */
971937
const char *zPublicPages = 0; /* GLOB patterns of public pages */
972938
const char *zLogin = 0; /* Login user for credentials */
973939
974940
/* Only run this check once. */
@@ -982,11 +948,11 @@
982948
** then there is no need to check user credentials.
983949
**
984950
** This feature allows the "fossil ui" command to give the user
985951
** full access rights without having to log in.
986952
*/
987
- zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
953
+ zIpAddr = PD("REMOTE_ADDR","nil");
988954
if( ( cgi_is_loopback(zIpAddr)
989955
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 )
990956
&& g.useLocalauth
991957
&& db_get_int("localauth",0)==0
992958
&& P("HTTPS")==0
@@ -1031,12 +997,11 @@
1031997
** SECRET is the "captcha-secret" value in the repository.
1032998
*/
1033999
double rTime = atof(zArg);
10341000
Blob b;
10351001
blob_zero(&b);
1036
- blob_appendf(&b, "%s/%s/%s",
1037
- zArg, zRemoteAddr, db_get("captcha-secret",""));
1002
+ blob_appendf(&b, "%s/%s", zArg, db_get("captcha-secret",""));
10381003
sha1sum_blob(&b, &b);
10391004
if( fossil_strcmp(zHash, blob_str(&b))==0 ){
10401005
uid = db_int(0,
10411006
"SELECT uid FROM user WHERE login='anonymous'"
10421007
" AND length(cap)>0"
@@ -1049,13 +1014,13 @@
10491014
}else{
10501015
/* Cookies of the form "HASH/CODE/USER". Search first in the
10511016
** local user table, then the user table for project CODE if we
10521017
** are part of a login-group.
10531018
*/
1054
- uid = login_find_user(zUser, zHash, zRemoteAddr);
1055
- if( uid==0 && login_transfer_credentials(zUser,zArg,zHash,zRemoteAddr) ){
1056
- uid = login_find_user(zUser, zHash, zRemoteAddr);
1019
+ uid = login_find_user(zUser, zHash);
1020
+ if( uid==0 && login_transfer_credentials(zUser,zArg,zHash) ){
1021
+ uid = login_find_user(zUser, zHash);
10571022
if( uid ) record_login_attempt(zUser, zIpAddr, 1);
10581023
}
10591024
}
10601025
sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
10611026
}
@@ -1227,20 +1192,19 @@
12271192
case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
12281193
p->RdWiki = p->WrWiki = p->NewWiki =
12291194
p->ApndWiki = p->Hyperlink = p->Clone =
12301195
p->NewTkt = p->Password = p->RdAddr =
12311196
p->TktFmt = p->Attach = p->ApndTkt =
1232
- p->ModWiki = p->ModTkt = p->Delete =
1197
+ p->ModWiki = p->ModTkt =
12331198
p->RdForum = p->WrForum = p->ModForum =
12341199
p->WrTForum = p->AdminForum =
12351200
p->EmailAlert = p->Announce = p->Debug = 1;
12361201
/* Fall thru into Read/Write */
12371202
case 'i': p->Read = p->Write = 1; break;
12381203
case 'o': p->Read = 1; break;
12391204
case 'z': p->Zip = 1; break;
12401205
1241
- case 'd': p->Delete = 1; break;
12421206
case 'h': p->Hyperlink = 1; break;
12431207
case 'g': p->Clone = 1; break;
12441208
case 'p': p->Password = 1; break;
12451209
12461210
case 'j': p->RdWiki = 1; break;
@@ -1320,11 +1284,11 @@
13201284
for(i=0; i<nCap && rc && zCap[i]; i++){
13211285
switch( zCap[i] ){
13221286
case 'a': rc = p->Admin; break;
13231287
case 'b': rc = p->Attach; break;
13241288
case 'c': rc = p->ApndTkt; break;
1325
- case 'd': rc = p->Delete; break;
1289
+ /* d unused: see comment in capabilities.c */
13261290
case 'e': rc = p->RdAddr; break;
13271291
case 'f': rc = p->NewWiki; break;
13281292
case 'g': rc = p->Clone; break;
13291293
case 'h': rc = p->Hyperlink; break;
13301294
case 'i': rc = p->Write; break;
@@ -1480,10 +1444,25 @@
14801444
g.okCsrf = 1;
14811445
return;
14821446
}
14831447
fossil_fatal("Cross-site request forgery attempt");
14841448
}
1449
+
1450
+/*
1451
+** Check to see if the candidate username zUserID is already used.
1452
+** Return 1 if it is already in use. Return 0 if the name is
1453
+** available for a self-registeration.
1454
+*/
1455
+static int login_self_choosen_userid_already_exists(const char *zUserID){
1456
+ int rc = db_exists(
1457
+ "SELECT 1 FROM user WHERE login=%Q "
1458
+ "UNION ALL "
1459
+ "SELECT 1 FROM event WHERE user=%Q OR euser=%Q",
1460
+ zUserID, zUserID, zUserID
1461
+ );
1462
+ return rc;
1463
+}
14851464
14861465
/*
14871466
** WEBPAGE: register
14881467
**
14891468
** Page to allow users to self-register. The "self-register" setting
@@ -1527,32 +1506,32 @@
15271506
/* This is not a valid form submission. Fall through into
15281507
** the form display */
15291508
}else if( !captcha_is_correct(1) ){
15301509
iErrLine = 6;
15311510
zErr = "Incorrect CAPTCHA";
1532
- }else if( strlen(zUserID)<3 ){
1511
+ }else if( strlen(zUserID)<6 ){
15331512
iErrLine = 1;
1534
- zErr = "User ID too short. Must be at least 3 characters.";
1513
+ zErr = "User ID too short. Must be at least 6 characters.";
15351514
}else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){
15361515
iErrLine = 1;
15371516
zErr = "User ID may not contain spaces or special characters.";
15381517
}else if( zDName[0]==0 ){
15391518
iErrLine = 2;
15401519
zErr = "Required";
15411520
}else if( zEAddr[0]==0 ){
15421521
iErrLine = 3;
15431522
zErr = "Required";
1544
- }else if( email_copy_addr(zEAddr,0)==0 ){
1523
+ }else if( email_address_is_valid(zEAddr,0)==0 ){
15451524
iErrLine = 3;
15461525
zErr = "Not a valid email address";
15471526
}else if( strlen(zPasswd)<6 ){
15481527
iErrLine = 4;
15491528
zErr = "Password must be at least 6 characters long";
15501529
}else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
15511530
iErrLine = 5;
15521531
zErr = "Passwords do not match";
1553
- }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zUserID) ){
1532
+ }else if( login_self_choosen_userid_already_exists(zUserID) ){
15541533
iErrLine = 1;
15551534
zErr = "This User ID is already taken. Choose something different.";
15561535
}else if(
15571536
/* If the email is found anywhere in USER.INFO... */
15581537
db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr)
@@ -1710,11 +1689,13 @@
17101689
if( iErrLine==5 ){
17111690
@ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
17121691
}
17131692
@ <tr>
17141693
@ <td class="form_label" align="right">Captcha:</td>
1715
- @ <td><input type="text" name="captcha" value="" size="30"></td>
1694
+ @ <td><input type="text" name="captcha" value="" size="30">
1695
+ captcha_speakit_button(uSeed, "Speak the captcha text");
1696
+ @ </td>
17161697
@ </tr>
17171698
if( iErrLine==6 ){
17181699
@ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
17191700
}
17201701
@ <tr><td></td>
17211702
--- src/login.c
+++ src/login.c
@@ -111,32 +111,10 @@
111 }else{
112 fossil_redirect_home();
113 }
114 }
115
116 /*
117 ** The IP address of the client is stored as part of login cookies.
118 ** But some clients are behind firewalls that shift the IP address
119 ** with each HTTP request. To allow such (broken) clients to log in,
120 ** extract just a prefix of the IP address.
121 */
122 static char *ipPrefix(const char *zIP){
123 int i, j;
124 static int ip_prefix_terms = -1;
125 if( ip_prefix_terms<0 ){
126 ip_prefix_terms = db_get_int("ip-prefix-terms",2);
127 }
128 if( ip_prefix_terms==0 ) return mprintf("0");
129 for(i=j=0; zIP[i]; i++){
130 if( zIP[i]=='.' ){
131 j++;
132 if( j==ip_prefix_terms ) break;
133 }
134 }
135 return mprintf("%.*s", i, zIP);
136 }
137
138 /*
139 ** Return an abbreviated project code. The abbreviation is the first
140 ** 16 characters of the project code.
141 **
142 ** Memory is obtained from malloc.
@@ -295,30 +273,27 @@
295 const char *zExpire = db_get("cookie-expire","8766");
296 int expires = atoi(zExpire)*3600;
297 char *zHash;
298 char *zCookie;
299 const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
300 char *zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */
301
302 assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
303 zHash = db_text(0,
304 "SELECT cookie FROM user"
305 " WHERE uid=%d"
306 " AND ipaddr=%Q"
307 " AND cexpire>julianday('now')"
308 " AND length(cookie)>30",
309 uid, zRemoteAddr);
310 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
311 zCookie = login_gen_user_cookie_value(zUsername, zHash);
312 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
313 record_login_attempt(zUsername, zIpAddr, 1);
314 db_multi_exec(
315 "UPDATE user SET cookie=%Q, ipaddr=%Q, "
316 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
317 zHash, zRemoteAddr, expires, uid
318 );
319 free(zRemoteAddr);
320 free(zHash);
321 if( zDest ){
322 *zDest = zCookie;
323 }else{
324 free(zCookie);
@@ -327,34 +302,25 @@
327
328 /* Sets a cookie for an anonymous user login, which looks like this:
329 **
330 ** HASH/TIME/anonymous
331 **
332 ** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
333 ** is the abbreviated IP address and SECRET is captcha-secret.
334 **
335 ** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR
336 ** is used.
337 **
338 ** If zCookieDest is not NULL then the generated cookie is assigned to
339 ** *zCookieDest and the caller must eventually free() it.
340 */
341 void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest ){
342 const char *zNow; /* Current time (julian day number) */
343 char *zCookie; /* The login cookie */
344 const char *zCookieName; /* Name of the login cookie */
345 Blob b; /* Blob used during cookie construction */
346 char *zRemoteAddr; /* Abbreviated IP address */
347 if(!zIpAddr){
348 zIpAddr = PD("REMOTE_ADDR","nil");
349 }
350 zRemoteAddr = ipPrefix(zIpAddr);
351 zCookieName = login_cookie_name();
352 zNow = db_text("0", "SELECT julianday('now')");
353 assert( zCookieName && zRemoteAddr && zIpAddr && zNow );
354 blob_init(&b, zNow, -1);
355 blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
356 sha1sum_blob(&b, &b);
357 zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
358 blob_reset(&b);
359 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
360 if( zCookieDest ){
@@ -702,10 +668,18 @@
702 if( g.zLogin ){
703 @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
704 @ <input type="submit" name="out" value="Logout"></p>
705 @ </form>
706 }else{
 
 
 
 
 
 
 
 
707 @ <table class="login_out">
708 @ <tr>
709 @ <td class="form_label">User ID:</td>
710 if( anonFlag ){
711 @ <td><input type="text" id="u" name="u" value="anonymous" size="30"></td>
@@ -713,11 +687,15 @@
713 @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
714 }
715 @ </tr>
716 @ <tr>
717 @ <td class="form_label">Password:</td>
718 @ <td><input type="password" id="p" name="p" value="" size="30" /></td>
 
 
 
 
719 @ </tr>
720 if( P("HTTPS")==0 ){
721 @ <tr><td class="form_label">Warning:</td>
722 @ <td><span class='securityWarning'>
723 @ Your password will be sent in the clear over an
@@ -728,15 +706,10 @@
728 @ Consider logging in at
729 @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
730 }
731 @ </span></td></tr>
732 }
733 if( g.zLogin==0 && (anonFlag || zGoto==0) ){
734 zAnonPw = db_text(0, "SELECT pw FROM user"
735 " WHERE login='anonymous'"
736 " AND cap!=''");
737 }
738 @ <tr>
739 @ <td></td>
740 @ <td><input type="submit" name="in" value="Login"></td>
741 @ </tr>
742 if( !noAnon && login_self_register_available(0) ){
@@ -745,11 +718,10 @@
745 @ <td><input type="submit" name="self" value="Create A New Account">
746 @ </tr>
747 }
748 @ </table>
749 if( zAnonPw && !noAnon ){
750 unsigned int uSeed = captcha_seed();
751 const char *zDecoded = captcha_decode(uSeed);
752 int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
753 char *zCaptcha = captcha_render(zDecoded);
754
755 @ <p><input type="hidden" name="cs" value="%u(uSeed)" />
@@ -803,12 +775,11 @@
803 ** Return true if a transfer was made and false if not.
804 */
805 static int login_transfer_credentials(
806 const char *zLogin, /* Login we are looking for */
807 const char *zCode, /* Project code of peer repository */
808 const char *zHash, /* HASH from login cookie HASH/CODE/LOGIN */
809 const char *zRemoteAddr /* Request comes from here */
810 ){
811 sqlite3 *pOther = 0; /* The other repository */
812 sqlite3_stmt *pStmt; /* Query against the other repository */
813 char *zSQL; /* SQL of the query against other repo */
814 char *zOtherRepo; /* Filename of the other repository */
@@ -832,24 +803,23 @@
832 constant_time_cmp_function, 0, 0);
833 sqlite3_busy_timeout(pOther, 5000);
834 zSQL = mprintf(
835 "SELECT cexpire FROM user"
836 " WHERE login=%Q"
837 " AND ipaddr=%Q"
838 " AND length(cap)>0"
839 " AND length(pw)>0"
840 " AND cexpire>julianday('now')"
841 " AND constant_time_cmp(cookie,%Q)=0",
842 zLogin, zRemoteAddr, zHash
843 );
844 pStmt = 0;
845 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
846 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
847 db_multi_exec(
848 "UPDATE user SET cookie=%Q, ipaddr=%Q, cexpire=%.17g"
849 " WHERE login=%Q",
850 zHash, zRemoteAddr,
851 sqlite3_column_double(pStmt, 0), zLogin
852 );
853 nXfer++;
854 }
855 sqlite3_finalize(pStmt);
@@ -869,33 +839,30 @@
869 if( fossil_strcmp(zLogin, "reader")==0 ) return 1;
870 return 0;
871 }
872
873 /*
874 ** Lookup the uid for a non-built-in user with zLogin and zCookie and
875 ** zRemoteAddr. Return 0 if not found.
876 **
877 ** Note that this only searches for logged-in entries with matching
878 ** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr)
879 ** entries.
880 */
881 static int login_find_user(
882 const char *zLogin, /* User name */
883 const char *zCookie, /* Login cookie value */
884 const char *zRemoteAddr /* Abbreviated IP address for valid login */
885 ){
886 int uid;
887 if( login_is_special(zLogin) ) return 0;
888 uid = db_int(0,
889 "SELECT uid FROM user"
890 " WHERE login=%Q"
891 " AND ipaddr=%Q"
892 " AND cexpire>julianday('now')"
893 " AND length(cap)>0"
894 " AND length(pw)>0"
895 " AND constant_time_cmp(cookie,%Q)=0",
896 zLogin, zRemoteAddr, zCookie
897 );
898 return uid;
899 }
900
901 /*
@@ -964,11 +931,10 @@
964 */
965 void login_check_credentials(void){
966 int uid = 0; /* User id */
967 const char *zCookie; /* Text of the login cookie */
968 const char *zIpAddr; /* Raw IP address of the requestor */
969 char *zRemoteAddr; /* Abbreviated IP address of the requestor */
970 const char *zCap = 0; /* Capability string */
971 const char *zPublicPages = 0; /* GLOB patterns of public pages */
972 const char *zLogin = 0; /* Login user for credentials */
973
974 /* Only run this check once. */
@@ -982,11 +948,11 @@
982 ** then there is no need to check user credentials.
983 **
984 ** This feature allows the "fossil ui" command to give the user
985 ** full access rights without having to log in.
986 */
987 zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
988 if( ( cgi_is_loopback(zIpAddr)
989 || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
990 && g.useLocalauth
991 && db_get_int("localauth",0)==0
992 && P("HTTPS")==0
@@ -1031,12 +997,11 @@
1031 ** SECRET is the "captcha-secret" value in the repository.
1032 */
1033 double rTime = atof(zArg);
1034 Blob b;
1035 blob_zero(&b);
1036 blob_appendf(&b, "%s/%s/%s",
1037 zArg, zRemoteAddr, db_get("captcha-secret",""));
1038 sha1sum_blob(&b, &b);
1039 if( fossil_strcmp(zHash, blob_str(&b))==0 ){
1040 uid = db_int(0,
1041 "SELECT uid FROM user WHERE login='anonymous'"
1042 " AND length(cap)>0"
@@ -1049,13 +1014,13 @@
1049 }else{
1050 /* Cookies of the form "HASH/CODE/USER". Search first in the
1051 ** local user table, then the user table for project CODE if we
1052 ** are part of a login-group.
1053 */
1054 uid = login_find_user(zUser, zHash, zRemoteAddr);
1055 if( uid==0 && login_transfer_credentials(zUser,zArg,zHash,zRemoteAddr) ){
1056 uid = login_find_user(zUser, zHash, zRemoteAddr);
1057 if( uid ) record_login_attempt(zUser, zIpAddr, 1);
1058 }
1059 }
1060 sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
1061 }
@@ -1227,20 +1192,19 @@
1227 case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
1228 p->RdWiki = p->WrWiki = p->NewWiki =
1229 p->ApndWiki = p->Hyperlink = p->Clone =
1230 p->NewTkt = p->Password = p->RdAddr =
1231 p->TktFmt = p->Attach = p->ApndTkt =
1232 p->ModWiki = p->ModTkt = p->Delete =
1233 p->RdForum = p->WrForum = p->ModForum =
1234 p->WrTForum = p->AdminForum =
1235 p->EmailAlert = p->Announce = p->Debug = 1;
1236 /* Fall thru into Read/Write */
1237 case 'i': p->Read = p->Write = 1; break;
1238 case 'o': p->Read = 1; break;
1239 case 'z': p->Zip = 1; break;
1240
1241 case 'd': p->Delete = 1; break;
1242 case 'h': p->Hyperlink = 1; break;
1243 case 'g': p->Clone = 1; break;
1244 case 'p': p->Password = 1; break;
1245
1246 case 'j': p->RdWiki = 1; break;
@@ -1320,11 +1284,11 @@
1320 for(i=0; i<nCap && rc && zCap[i]; i++){
1321 switch( zCap[i] ){
1322 case 'a': rc = p->Admin; break;
1323 case 'b': rc = p->Attach; break;
1324 case 'c': rc = p->ApndTkt; break;
1325 case 'd': rc = p->Delete; break;
1326 case 'e': rc = p->RdAddr; break;
1327 case 'f': rc = p->NewWiki; break;
1328 case 'g': rc = p->Clone; break;
1329 case 'h': rc = p->Hyperlink; break;
1330 case 'i': rc = p->Write; break;
@@ -1480,10 +1444,25 @@
1480 g.okCsrf = 1;
1481 return;
1482 }
1483 fossil_fatal("Cross-site request forgery attempt");
1484 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1485
1486 /*
1487 ** WEBPAGE: register
1488 **
1489 ** Page to allow users to self-register. The "self-register" setting
@@ -1527,32 +1506,32 @@
1527 /* This is not a valid form submission. Fall through into
1528 ** the form display */
1529 }else if( !captcha_is_correct(1) ){
1530 iErrLine = 6;
1531 zErr = "Incorrect CAPTCHA";
1532 }else if( strlen(zUserID)<3 ){
1533 iErrLine = 1;
1534 zErr = "User ID too short. Must be at least 3 characters.";
1535 }else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){
1536 iErrLine = 1;
1537 zErr = "User ID may not contain spaces or special characters.";
1538 }else if( zDName[0]==0 ){
1539 iErrLine = 2;
1540 zErr = "Required";
1541 }else if( zEAddr[0]==0 ){
1542 iErrLine = 3;
1543 zErr = "Required";
1544 }else if( email_copy_addr(zEAddr,0)==0 ){
1545 iErrLine = 3;
1546 zErr = "Not a valid email address";
1547 }else if( strlen(zPasswd)<6 ){
1548 iErrLine = 4;
1549 zErr = "Password must be at least 6 characters long";
1550 }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
1551 iErrLine = 5;
1552 zErr = "Passwords do not match";
1553 }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zUserID) ){
1554 iErrLine = 1;
1555 zErr = "This User ID is already taken. Choose something different.";
1556 }else if(
1557 /* If the email is found anywhere in USER.INFO... */
1558 db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr)
@@ -1710,11 +1689,13 @@
1710 if( iErrLine==5 ){
1711 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1712 }
1713 @ <tr>
1714 @ <td class="form_label" align="right">Captcha:</td>
1715 @ <td><input type="text" name="captcha" value="" size="30"></td>
 
 
1716 @ </tr>
1717 if( iErrLine==6 ){
1718 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1719 }
1720 @ <tr><td></td>
1721
--- src/login.c
+++ src/login.c
@@ -111,32 +111,10 @@
111 }else{
112 fossil_redirect_home();
113 }
114 }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116 /*
117 ** Return an abbreviated project code. The abbreviation is the first
118 ** 16 characters of the project code.
119 **
120 ** Memory is obtained from malloc.
@@ -295,30 +273,27 @@
273 const char *zExpire = db_get("cookie-expire","8766");
274 int expires = atoi(zExpire)*3600;
275 char *zHash;
276 char *zCookie;
277 const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
 
278
279 assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
280 zHash = db_text(0,
281 "SELECT cookie FROM user"
282 " WHERE uid=%d"
 
283 " AND cexpire>julianday('now')"
284 " AND length(cookie)>30",
285 uid);
286 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
287 zCookie = login_gen_user_cookie_value(zUsername, zHash);
288 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
289 record_login_attempt(zUsername, zIpAddr, 1);
290 db_multi_exec(
291 "UPDATE user SET cookie=%Q,"
292 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
293 zHash, expires, uid
294 );
 
295 free(zHash);
296 if( zDest ){
297 *zDest = zCookie;
298 }else{
299 free(zCookie);
@@ -327,34 +302,25 @@
302
303 /* Sets a cookie for an anonymous user login, which looks like this:
304 **
305 ** HASH/TIME/anonymous
306 **
307 ** Where HASH is the sha1sum of TIME/SECRET, in which SECRET is captcha-secret.
 
 
 
 
308 **
309 ** If zCookieDest is not NULL then the generated cookie is assigned to
310 ** *zCookieDest and the caller must eventually free() it.
311 */
312 void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest ){
313 const char *zNow; /* Current time (julian day number) */
314 char *zCookie; /* The login cookie */
315 const char *zCookieName; /* Name of the login cookie */
316 Blob b; /* Blob used during cookie construction */
 
 
 
 
 
317 zCookieName = login_cookie_name();
318 zNow = db_text("0", "SELECT julianday('now')");
319 assert( zCookieName && zNow );
320 blob_init(&b, zNow, -1);
321 blob_appendf(&b, "/%s", db_get("captcha-secret",""));
322 sha1sum_blob(&b, &b);
323 zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
324 blob_reset(&b);
325 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
326 if( zCookieDest ){
@@ -702,10 +668,18 @@
668 if( g.zLogin ){
669 @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
670 @ <input type="submit" name="out" value="Logout"></p>
671 @ </form>
672 }else{
673 unsigned int uSeed = captcha_seed();
674 if( g.zLogin==0 && (anonFlag || zGoto==0) ){
675 zAnonPw = db_text(0, "SELECT pw FROM user"
676 " WHERE login='anonymous'"
677 " AND cap!=''");
678 }else{
679 zAnonPw = 0;
680 }
681 @ <table class="login_out">
682 @ <tr>
683 @ <td class="form_label">User ID:</td>
684 if( anonFlag ){
685 @ <td><input type="text" id="u" name="u" value="anonymous" size="30"></td>
@@ -713,11 +687,15 @@
687 @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
688 }
689 @ </tr>
690 @ <tr>
691 @ <td class="form_label">Password:</td>
692 @ <td><input type="password" id="p" name="p" value="" size="30" />\
693 if( zAnonPw && !noAnon ){
694 captcha_speakit_button(uSeed, "Speak password for \"anonymous\"");
695 }
696 @ </td>
697 @ </tr>
698 if( P("HTTPS")==0 ){
699 @ <tr><td class="form_label">Warning:</td>
700 @ <td><span class='securityWarning'>
701 @ Your password will be sent in the clear over an
@@ -728,15 +706,10 @@
706 @ Consider logging in at
707 @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
708 }
709 @ </span></td></tr>
710 }
 
 
 
 
 
711 @ <tr>
712 @ <td></td>
713 @ <td><input type="submit" name="in" value="Login"></td>
714 @ </tr>
715 if( !noAnon && login_self_register_available(0) ){
@@ -745,11 +718,10 @@
718 @ <td><input type="submit" name="self" value="Create A New Account">
719 @ </tr>
720 }
721 @ </table>
722 if( zAnonPw && !noAnon ){
 
723 const char *zDecoded = captcha_decode(uSeed);
724 int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
725 char *zCaptcha = captcha_render(zDecoded);
726
727 @ <p><input type="hidden" name="cs" value="%u(uSeed)" />
@@ -803,12 +775,11 @@
775 ** Return true if a transfer was made and false if not.
776 */
777 static int login_transfer_credentials(
778 const char *zLogin, /* Login we are looking for */
779 const char *zCode, /* Project code of peer repository */
780 const char *zHash /* HASH from login cookie HASH/CODE/LOGIN */
 
781 ){
782 sqlite3 *pOther = 0; /* The other repository */
783 sqlite3_stmt *pStmt; /* Query against the other repository */
784 char *zSQL; /* SQL of the query against other repo */
785 char *zOtherRepo; /* Filename of the other repository */
@@ -832,24 +803,23 @@
803 constant_time_cmp_function, 0, 0);
804 sqlite3_busy_timeout(pOther, 5000);
805 zSQL = mprintf(
806 "SELECT cexpire FROM user"
807 " WHERE login=%Q"
 
808 " AND length(cap)>0"
809 " AND length(pw)>0"
810 " AND cexpire>julianday('now')"
811 " AND constant_time_cmp(cookie,%Q)=0",
812 zLogin, zHash
813 );
814 pStmt = 0;
815 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
816 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
817 db_multi_exec(
818 "UPDATE user SET cookie=%Q, cexpire=%.17g"
819 " WHERE login=%Q",
820 zHash,
821 sqlite3_column_double(pStmt, 0), zLogin
822 );
823 nXfer++;
824 }
825 sqlite3_finalize(pStmt);
@@ -869,33 +839,30 @@
839 if( fossil_strcmp(zLogin, "reader")==0 ) return 1;
840 return 0;
841 }
842
843 /*
844 ** Lookup the uid for a non-built-in user with zLogin and zCookie.
845 ** Return 0 if not found.
846 **
847 ** Note that this only searches for logged-in entries with matching
848 ** zCookie (db: user.cookie) entries.
 
849 */
850 static int login_find_user(
851 const char *zLogin, /* User name */
852 const char *zCookie /* Login cookie value */
 
853 ){
854 int uid;
855 if( login_is_special(zLogin) ) return 0;
856 uid = db_int(0,
857 "SELECT uid FROM user"
858 " WHERE login=%Q"
 
859 " AND cexpire>julianday('now')"
860 " AND length(cap)>0"
861 " AND length(pw)>0"
862 " AND constant_time_cmp(cookie,%Q)=0",
863 zLogin, zCookie
864 );
865 return uid;
866 }
867
868 /*
@@ -964,11 +931,10 @@
931 */
932 void login_check_credentials(void){
933 int uid = 0; /* User id */
934 const char *zCookie; /* Text of the login cookie */
935 const char *zIpAddr; /* Raw IP address of the requestor */
 
936 const char *zCap = 0; /* Capability string */
937 const char *zPublicPages = 0; /* GLOB patterns of public pages */
938 const char *zLogin = 0; /* Login user for credentials */
939
940 /* Only run this check once. */
@@ -982,11 +948,11 @@
948 ** then there is no need to check user credentials.
949 **
950 ** This feature allows the "fossil ui" command to give the user
951 ** full access rights without having to log in.
952 */
953 zIpAddr = PD("REMOTE_ADDR","nil");
954 if( ( cgi_is_loopback(zIpAddr)
955 || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
956 && g.useLocalauth
957 && db_get_int("localauth",0)==0
958 && P("HTTPS")==0
@@ -1031,12 +997,11 @@
997 ** SECRET is the "captcha-secret" value in the repository.
998 */
999 double rTime = atof(zArg);
1000 Blob b;
1001 blob_zero(&b);
1002 blob_appendf(&b, "%s/%s", zArg, db_get("captcha-secret",""));
 
1003 sha1sum_blob(&b, &b);
1004 if( fossil_strcmp(zHash, blob_str(&b))==0 ){
1005 uid = db_int(0,
1006 "SELECT uid FROM user WHERE login='anonymous'"
1007 " AND length(cap)>0"
@@ -1049,13 +1014,13 @@
1014 }else{
1015 /* Cookies of the form "HASH/CODE/USER". Search first in the
1016 ** local user table, then the user table for project CODE if we
1017 ** are part of a login-group.
1018 */
1019 uid = login_find_user(zUser, zHash);
1020 if( uid==0 && login_transfer_credentials(zUser,zArg,zHash) ){
1021 uid = login_find_user(zUser, zHash);
1022 if( uid ) record_login_attempt(zUser, zIpAddr, 1);
1023 }
1024 }
1025 sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
1026 }
@@ -1227,20 +1192,19 @@
1192 case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
1193 p->RdWiki = p->WrWiki = p->NewWiki =
1194 p->ApndWiki = p->Hyperlink = p->Clone =
1195 p->NewTkt = p->Password = p->RdAddr =
1196 p->TktFmt = p->Attach = p->ApndTkt =
1197 p->ModWiki = p->ModTkt =
1198 p->RdForum = p->WrForum = p->ModForum =
1199 p->WrTForum = p->AdminForum =
1200 p->EmailAlert = p->Announce = p->Debug = 1;
1201 /* Fall thru into Read/Write */
1202 case 'i': p->Read = p->Write = 1; break;
1203 case 'o': p->Read = 1; break;
1204 case 'z': p->Zip = 1; break;
1205
 
1206 case 'h': p->Hyperlink = 1; break;
1207 case 'g': p->Clone = 1; break;
1208 case 'p': p->Password = 1; break;
1209
1210 case 'j': p->RdWiki = 1; break;
@@ -1320,11 +1284,11 @@
1284 for(i=0; i<nCap && rc && zCap[i]; i++){
1285 switch( zCap[i] ){
1286 case 'a': rc = p->Admin; break;
1287 case 'b': rc = p->Attach; break;
1288 case 'c': rc = p->ApndTkt; break;
1289 /* d unused: see comment in capabilities.c */
1290 case 'e': rc = p->RdAddr; break;
1291 case 'f': rc = p->NewWiki; break;
1292 case 'g': rc = p->Clone; break;
1293 case 'h': rc = p->Hyperlink; break;
1294 case 'i': rc = p->Write; break;
@@ -1480,10 +1444,25 @@
1444 g.okCsrf = 1;
1445 return;
1446 }
1447 fossil_fatal("Cross-site request forgery attempt");
1448 }
1449
1450 /*
1451 ** Check to see if the candidate username zUserID is already used.
1452 ** Return 1 if it is already in use. Return 0 if the name is
1453 ** available for a self-registeration.
1454 */
1455 static int login_self_choosen_userid_already_exists(const char *zUserID){
1456 int rc = db_exists(
1457 "SELECT 1 FROM user WHERE login=%Q "
1458 "UNION ALL "
1459 "SELECT 1 FROM event WHERE user=%Q OR euser=%Q",
1460 zUserID, zUserID, zUserID
1461 );
1462 return rc;
1463 }
1464
1465 /*
1466 ** WEBPAGE: register
1467 **
1468 ** Page to allow users to self-register. The "self-register" setting
@@ -1527,32 +1506,32 @@
1506 /* This is not a valid form submission. Fall through into
1507 ** the form display */
1508 }else if( !captcha_is_correct(1) ){
1509 iErrLine = 6;
1510 zErr = "Incorrect CAPTCHA";
1511 }else if( strlen(zUserID)<6 ){
1512 iErrLine = 1;
1513 zErr = "User ID too short. Must be at least 6 characters.";
1514 }else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){
1515 iErrLine = 1;
1516 zErr = "User ID may not contain spaces or special characters.";
1517 }else if( zDName[0]==0 ){
1518 iErrLine = 2;
1519 zErr = "Required";
1520 }else if( zEAddr[0]==0 ){
1521 iErrLine = 3;
1522 zErr = "Required";
1523 }else if( email_address_is_valid(zEAddr,0)==0 ){
1524 iErrLine = 3;
1525 zErr = "Not a valid email address";
1526 }else if( strlen(zPasswd)<6 ){
1527 iErrLine = 4;
1528 zErr = "Password must be at least 6 characters long";
1529 }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
1530 iErrLine = 5;
1531 zErr = "Passwords do not match";
1532 }else if( login_self_choosen_userid_already_exists(zUserID) ){
1533 iErrLine = 1;
1534 zErr = "This User ID is already taken. Choose something different.";
1535 }else if(
1536 /* If the email is found anywhere in USER.INFO... */
1537 db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr)
@@ -1710,11 +1689,13 @@
1689 if( iErrLine==5 ){
1690 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1691 }
1692 @ <tr>
1693 @ <td class="form_label" align="right">Captcha:</td>
1694 @ <td><input type="text" name="captcha" value="" size="30">
1695 captcha_speakit_button(uSeed, "Speak the captcha text");
1696 @ </td>
1697 @ </tr>
1698 if( iErrLine==6 ){
1699 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1700 }
1701 @ <tr><td></td>
1702
+1 -2
--- src/main.c
+++ src/main.c
@@ -78,11 +78,10 @@
7878
** Holds flags for fossil user permissions.
7979
*/
8080
struct FossilUserPerms {
8181
char Setup; /* s: use Setup screens on web interface */
8282
char Admin; /* a: administrative permission */
83
- char Delete; /* d: delete wiki or tickets */
8483
char Password; /* p: change password */
8584
char Query; /* q: create new reports */
8685
char Write; /* i: xfer inbound. check-in */
8786
char Read; /* o: xfer outbound. check-out */
8887
char Hyperlink; /* h: enable the display of hyperlinks */
@@ -1687,11 +1686,11 @@
16871686
@ <meta name="viewport" \
16881687
@ content="width=device-width, initial-scale=1.0">
16891688
@ </head><body>
16901689
@ <h1>Not Found</h1>
16911690
@ </body>
1692
- cgi_set_status(404, "not found");
1691
+ cgi_set_status(404, "Not Found");
16931692
cgi_reply();
16941693
}
16951694
return;
16961695
}
16971696
break;
16981697
--- src/main.c
+++ src/main.c
@@ -78,11 +78,10 @@
78 ** Holds flags for fossil user permissions.
79 */
80 struct FossilUserPerms {
81 char Setup; /* s: use Setup screens on web interface */
82 char Admin; /* a: administrative permission */
83 char Delete; /* d: delete wiki or tickets */
84 char Password; /* p: change password */
85 char Query; /* q: create new reports */
86 char Write; /* i: xfer inbound. check-in */
87 char Read; /* o: xfer outbound. check-out */
88 char Hyperlink; /* h: enable the display of hyperlinks */
@@ -1687,11 +1686,11 @@
1687 @ <meta name="viewport" \
1688 @ content="width=device-width, initial-scale=1.0">
1689 @ </head><body>
1690 @ <h1>Not Found</h1>
1691 @ </body>
1692 cgi_set_status(404, "not found");
1693 cgi_reply();
1694 }
1695 return;
1696 }
1697 break;
1698
--- src/main.c
+++ src/main.c
@@ -78,11 +78,10 @@
78 ** Holds flags for fossil user permissions.
79 */
80 struct FossilUserPerms {
81 char Setup; /* s: use Setup screens on web interface */
82 char Admin; /* a: administrative permission */
 
83 char Password; /* p: change password */
84 char Query; /* q: create new reports */
85 char Write; /* i: xfer inbound. check-in */
86 char Read; /* o: xfer outbound. check-out */
87 char Hyperlink; /* h: enable the display of hyperlinks */
@@ -1687,11 +1686,11 @@
1686 @ <meta name="viewport" \
1687 @ content="width=device-width, initial-scale=1.0">
1688 @ </head><body>
1689 @ <h1>Not Found</h1>
1690 @ </body>
1691 cgi_set_status(404, "Not Found");
1692 cgi_reply();
1693 }
1694 return;
1695 }
1696 break;
1697
+16
--- src/main.mk
+++ src/main.mk
@@ -224,10 +224,26 @@
224224
$(SRCDIR)/menu.js \
225225
$(SRCDIR)/sbsdiff.js \
226226
$(SRCDIR)/scroll.js \
227227
$(SRCDIR)/skin.js \
228228
$(SRCDIR)/sorttable.js \
229
+ $(SRCDIR)/sounds/0.wav \
230
+ $(SRCDIR)/sounds/1.wav \
231
+ $(SRCDIR)/sounds/2.wav \
232
+ $(SRCDIR)/sounds/3.wav \
233
+ $(SRCDIR)/sounds/4.wav \
234
+ $(SRCDIR)/sounds/5.wav \
235
+ $(SRCDIR)/sounds/6.wav \
236
+ $(SRCDIR)/sounds/7.wav \
237
+ $(SRCDIR)/sounds/8.wav \
238
+ $(SRCDIR)/sounds/9.wav \
239
+ $(SRCDIR)/sounds/a.wav \
240
+ $(SRCDIR)/sounds/b.wav \
241
+ $(SRCDIR)/sounds/c.wav \
242
+ $(SRCDIR)/sounds/d.wav \
243
+ $(SRCDIR)/sounds/e.wav \
244
+ $(SRCDIR)/sounds/f.wav \
229245
$(SRCDIR)/tree.js \
230246
$(SRCDIR)/useredit.js \
231247
$(SRCDIR)/wiki.wiki
232248
233249
TRANS_SRC = \
234250
--- src/main.mk
+++ src/main.mk
@@ -224,10 +224,26 @@
224 $(SRCDIR)/menu.js \
225 $(SRCDIR)/sbsdiff.js \
226 $(SRCDIR)/scroll.js \
227 $(SRCDIR)/skin.js \
228 $(SRCDIR)/sorttable.js \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229 $(SRCDIR)/tree.js \
230 $(SRCDIR)/useredit.js \
231 $(SRCDIR)/wiki.wiki
232
233 TRANS_SRC = \
234
--- src/main.mk
+++ src/main.mk
@@ -224,10 +224,26 @@
224 $(SRCDIR)/menu.js \
225 $(SRCDIR)/sbsdiff.js \
226 $(SRCDIR)/scroll.js \
227 $(SRCDIR)/skin.js \
228 $(SRCDIR)/sorttable.js \
229 $(SRCDIR)/sounds/0.wav \
230 $(SRCDIR)/sounds/1.wav \
231 $(SRCDIR)/sounds/2.wav \
232 $(SRCDIR)/sounds/3.wav \
233 $(SRCDIR)/sounds/4.wav \
234 $(SRCDIR)/sounds/5.wav \
235 $(SRCDIR)/sounds/6.wav \
236 $(SRCDIR)/sounds/7.wav \
237 $(SRCDIR)/sounds/8.wav \
238 $(SRCDIR)/sounds/9.wav \
239 $(SRCDIR)/sounds/a.wav \
240 $(SRCDIR)/sounds/b.wav \
241 $(SRCDIR)/sounds/c.wav \
242 $(SRCDIR)/sounds/d.wav \
243 $(SRCDIR)/sounds/e.wav \
244 $(SRCDIR)/sounds/f.wav \
245 $(SRCDIR)/tree.js \
246 $(SRCDIR)/useredit.js \
247 $(SRCDIR)/wiki.wiki
248
249 TRANS_SRC = \
250
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -174,10 +174,11 @@
174174
diff.tcl
175175
markdown.md
176176
wiki.wiki
177177
*.js
178178
../skins/*/*.txt
179
+ sounds/*.wav
179180
}
180181
181182
# Options used to compile the included SQLite library.
182183
#
183184
set SQLITE_OPTIONS {
@@ -712,11 +713,11 @@
712713
#### The directories where the OpenSSL include and library files are located.
713714
# The recommended usage here is to use the Sysinternals junction tool
714715
# to create a hard link between an "openssl-1.x" sub-directory of the
715716
# Fossil source code directory and the target OpenSSL source directory.
716717
#
717
-OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d
718
+OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e
718719
OPENSSLINCDIR = $(OPENSSLDIR)/include
719720
OPENSSLLIBDIR = $(OPENSSLDIR)
720721
721722
#### Either the directory where the Tcl library is installed or the Tcl
722723
# source code directory resides (depending on the value of the macro
@@ -1569,11 +1570,11 @@
15691570
!ifndef USE_SEE
15701571
USE_SEE = 0
15711572
!endif
15721573
15731574
!if $(FOSSIL_ENABLE_SSL)!=0
1574
-SSLDIR = $(B)\compat\openssl-1.1.1d
1575
+SSLDIR = $(B)\compat\openssl-1.1.1e
15751576
SSLINCDIR = $(SSLDIR)\include
15761577
!if $(FOSSIL_DYNAMIC_BUILD)!=0
15771578
SSLLIBDIR = $(SSLDIR)
15781579
!else
15791580
SSLLIBDIR = $(SSLDIR)
15801581
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -174,10 +174,11 @@
174 diff.tcl
175 markdown.md
176 wiki.wiki
177 *.js
178 ../skins/*/*.txt
 
179 }
180
181 # Options used to compile the included SQLite library.
182 #
183 set SQLITE_OPTIONS {
@@ -712,11 +713,11 @@
712 #### The directories where the OpenSSL include and library files are located.
713 # The recommended usage here is to use the Sysinternals junction tool
714 # to create a hard link between an "openssl-1.x" sub-directory of the
715 # Fossil source code directory and the target OpenSSL source directory.
716 #
717 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d
718 OPENSSLINCDIR = $(OPENSSLDIR)/include
719 OPENSSLLIBDIR = $(OPENSSLDIR)
720
721 #### Either the directory where the Tcl library is installed or the Tcl
722 # source code directory resides (depending on the value of the macro
@@ -1569,11 +1570,11 @@
1569 !ifndef USE_SEE
1570 USE_SEE = 0
1571 !endif
1572
1573 !if $(FOSSIL_ENABLE_SSL)!=0
1574 SSLDIR = $(B)\compat\openssl-1.1.1d
1575 SSLINCDIR = $(SSLDIR)\include
1576 !if $(FOSSIL_DYNAMIC_BUILD)!=0
1577 SSLLIBDIR = $(SSLDIR)
1578 !else
1579 SSLLIBDIR = $(SSLDIR)
1580
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -174,10 +174,11 @@
174 diff.tcl
175 markdown.md
176 wiki.wiki
177 *.js
178 ../skins/*/*.txt
179 sounds/*.wav
180 }
181
182 # Options used to compile the included SQLite library.
183 #
184 set SQLITE_OPTIONS {
@@ -712,11 +713,11 @@
713 #### The directories where the OpenSSL include and library files are located.
714 # The recommended usage here is to use the Sysinternals junction tool
715 # to create a hard link between an "openssl-1.x" sub-directory of the
716 # Fossil source code directory and the target OpenSSL source directory.
717 #
718 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e
719 OPENSSLINCDIR = $(OPENSSLDIR)/include
720 OPENSSLLIBDIR = $(OPENSSLDIR)
721
722 #### Either the directory where the Tcl library is installed or the Tcl
723 # source code directory resides (depending on the value of the macro
@@ -1569,11 +1570,11 @@
1570 !ifndef USE_SEE
1571 USE_SEE = 0
1572 !endif
1573
1574 !if $(FOSSIL_ENABLE_SSL)!=0
1575 SSLDIR = $(B)\compat\openssl-1.1.1e
1576 SSLINCDIR = $(SSLDIR)\include
1577 !if $(FOSSIL_DYNAMIC_BUILD)!=0
1578 SSLLIBDIR = $(SSLDIR)
1579 !else
1580 SSLLIBDIR = $(SSLDIR)
1581
+3 -2
--- src/rss.c
+++ src/rss.c
@@ -26,12 +26,13 @@
2626
** WEBPAGE: timeline.rss
2727
** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME
2828
**
2929
** Produce an RSS feed of the timeline.
3030
**
31
-** TYPE may be: all, ci (show check-ins only), t (show tickets only),
32
-** w (show wiki only).
31
+** TYPE may be: all, ci (show check-ins only), t (show ticket changes only),
32
+** w (show wiki only), e (show tech notes only), f (show forum posts only),
33
+** g (show tag/branch changes only).
3334
**
3435
** LIMIT is the number of items to show.
3536
**
3637
** tkt=UUID filters for only those events for the specified ticket. tag=TAG
3738
** filters for a tag, and wiki=NAME for a wiki page. Only one may be used.
3839
--- src/rss.c
+++ src/rss.c
@@ -26,12 +26,13 @@
26 ** WEBPAGE: timeline.rss
27 ** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME
28 **
29 ** Produce an RSS feed of the timeline.
30 **
31 ** TYPE may be: all, ci (show check-ins only), t (show tickets only),
32 ** w (show wiki only).
 
33 **
34 ** LIMIT is the number of items to show.
35 **
36 ** tkt=UUID filters for only those events for the specified ticket. tag=TAG
37 ** filters for a tag, and wiki=NAME for a wiki page. Only one may be used.
38
--- src/rss.c
+++ src/rss.c
@@ -26,12 +26,13 @@
26 ** WEBPAGE: timeline.rss
27 ** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME
28 **
29 ** Produce an RSS feed of the timeline.
30 **
31 ** TYPE may be: all, ci (show check-ins only), t (show ticket changes only),
32 ** w (show wiki only), e (show tech notes only), f (show forum posts only),
33 ** g (show tag/branch changes only).
34 **
35 ** LIMIT is the number of items to show.
36 **
37 ** tkt=UUID filters for only those events for the specified ticket. tag=TAG
38 ** filters for a tag, and wiki=NAME for a wiki page. Only one may be used.
39
--- src/security_audit.c
+++ src/security_audit.c
@@ -94,10 +94,12 @@
9494
** accessed using the Admin/Security-Audit menu option
9595
** from any of the default skins.
9696
*/
9797
void secaudit0_page(void){
9898
const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */
99
+ const char *zDevCap; /* Capabilities of user group "developer" */
100
+ const char *zReadCap; /* Capabilities of user group "reader" */
99101
const char *zPubPages; /* GLOB pattern for public pages */
100102
const char *zSelfCap; /* Capabilities of self-registered users */
101103
int hasSelfReg = 0; /* True if able to self-register */
102104
char *z;
103105
int n;
@@ -116,10 +118,12 @@
116118
** means that any anonymous user on the internet can access all content.
117119
** "Private" repos require (non-anonymous) login to access all content,
118120
** though some content may be accessible anonymously.
119121
*/
120122
zAnonCap = db_text("", "SELECT fullcap(NULL)");
123
+ zDevCap = db_text("", "SELECT fullcap('v')");
124
+ zReadCap = db_text("", "SELECT fullcap('u')");
121125
zPubPages = db_get("public-pages",0);
122126
hasSelfReg = db_get_boolean("self-register",0);
123127
pCap = capability_add(0, db_get("default-perms",0));
124128
capability_expand(pCap);
125129
zSelfCap = capability_string(pCap);
@@ -278,19 +282,20 @@
278282
@ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
279283
@ from users "anonymous" and "nobody"
280284
@ on the <a href="setup_ulist">User Configuration</a> page.
281285
}
282286
283
- /* Anonymous users probably should not be allowed to delete
284
- ** wiki or tickets.
285
- */
286
- if( hasAnyCap(zAnonCap, "d") ){
287
+ /* Obsolete: */
288
+ if( hasAnyCap(zAnonCap, "d") ||
289
+ hasAnyCap(zDevCap, "d") ||
290
+ hasAnyCap(zReadCap, "d") ){
287291
@ <li><p><b>WARNING:</b>
288
- @ Anonymous users can delete wiki and tickets.
289
- @ <p>Fix this by removing the "Delete"
290
- @ privilege from users "anonymous" and "nobody" on the
291
- @ <a href="setup_ulist">User Configuration</a> page.
292
+ @ One or more users has the <a
293
+ @ href="https://fossil-scm.org/forum/forumpost/43c78f4bef">obsolete</a>
294
+ @ "d" capability. You should remove it using the
295
+ @ <a href="setup_ulist">User Configuration</a> page in case we
296
+ @ ever reuse the letter for another purpose.
292297
}
293298
294299
/* If anonymous users are allowed to create new Wiki, then
295300
** wiki moderation should be activated to pervent spam.
296301
*/
297302
--- src/security_audit.c
+++ src/security_audit.c
@@ -94,10 +94,12 @@
94 ** accessed using the Admin/Security-Audit menu option
95 ** from any of the default skins.
96 */
97 void secaudit0_page(void){
98 const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */
 
 
99 const char *zPubPages; /* GLOB pattern for public pages */
100 const char *zSelfCap; /* Capabilities of self-registered users */
101 int hasSelfReg = 0; /* True if able to self-register */
102 char *z;
103 int n;
@@ -116,10 +118,12 @@
116 ** means that any anonymous user on the internet can access all content.
117 ** "Private" repos require (non-anonymous) login to access all content,
118 ** though some content may be accessible anonymously.
119 */
120 zAnonCap = db_text("", "SELECT fullcap(NULL)");
 
 
121 zPubPages = db_get("public-pages",0);
122 hasSelfReg = db_get_boolean("self-register",0);
123 pCap = capability_add(0, db_get("default-perms",0));
124 capability_expand(pCap);
125 zSelfCap = capability_string(pCap);
@@ -278,19 +282,20 @@
278 @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
279 @ from users "anonymous" and "nobody"
280 @ on the <a href="setup_ulist">User Configuration</a> page.
281 }
282
283 /* Anonymous users probably should not be allowed to delete
284 ** wiki or tickets.
285 */
286 if( hasAnyCap(zAnonCap, "d") ){
287 @ <li><p><b>WARNING:</b>
288 @ Anonymous users can delete wiki and tickets.
289 @ <p>Fix this by removing the "Delete"
290 @ privilege from users "anonymous" and "nobody" on the
291 @ <a href="setup_ulist">User Configuration</a> page.
 
292 }
293
294 /* If anonymous users are allowed to create new Wiki, then
295 ** wiki moderation should be activated to pervent spam.
296 */
297
--- src/security_audit.c
+++ src/security_audit.c
@@ -94,10 +94,12 @@
94 ** accessed using the Admin/Security-Audit menu option
95 ** from any of the default skins.
96 */
97 void secaudit0_page(void){
98 const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */
99 const char *zDevCap; /* Capabilities of user group "developer" */
100 const char *zReadCap; /* Capabilities of user group "reader" */
101 const char *zPubPages; /* GLOB pattern for public pages */
102 const char *zSelfCap; /* Capabilities of self-registered users */
103 int hasSelfReg = 0; /* True if able to self-register */
104 char *z;
105 int n;
@@ -116,10 +118,12 @@
118 ** means that any anonymous user on the internet can access all content.
119 ** "Private" repos require (non-anonymous) login to access all content,
120 ** though some content may be accessible anonymously.
121 */
122 zAnonCap = db_text("", "SELECT fullcap(NULL)");
123 zDevCap = db_text("", "SELECT fullcap('v')");
124 zReadCap = db_text("", "SELECT fullcap('u')");
125 zPubPages = db_get("public-pages",0);
126 hasSelfReg = db_get_boolean("self-register",0);
127 pCap = capability_add(0, db_get("default-perms",0));
128 capability_expand(pCap);
129 zSelfCap = capability_string(pCap);
@@ -278,19 +282,20 @@
282 @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
283 @ from users "anonymous" and "nobody"
284 @ on the <a href="setup_ulist">User Configuration</a> page.
285 }
286
287 /* Obsolete: */
288 if( hasAnyCap(zAnonCap, "d") ||
289 hasAnyCap(zDevCap, "d") ||
290 hasAnyCap(zReadCap, "d") ){
291 @ <li><p><b>WARNING:</b>
292 @ One or more users has the <a
293 @ href="https://fossil-scm.org/forum/forumpost/43c78f4bef">obsolete</a>
294 @ "d" capability. You should remove it using the
295 @ <a href="setup_ulist">User Configuration</a> page in case we
296 @ ever reuse the letter for another purpose.
297 }
298
299 /* If anonymous users are allowed to create new Wiki, then
300 ** wiki moderation should be activated to pervent spam.
301 */
302
--- src/setup.c
+++ src/setup.c
@@ -402,19 +402,10 @@
402402
@ variable or the "Authentication:" HTTP header to find the username and
403403
@ password. This is another way of supporting Basic Authenitication.
404404
@ (Property: "http_authentication_ok")
405405
@ </p>
406406
@
407
- @ <hr />
408
- entry_attribute("IP address terms used in login cookie", 3,
409
- "ip-prefix-terms", "ipt", "2", 0);
410
- @ <p>The number of octets of of the IP address used in the login cookie.
411
- @ Set to zero to omit the IP address from the login cookie. A value of
412
- @ 2 is recommended.
413
- @ (Property: "ip-prefix-terms")
414
- @ </p>
415
- @
416407
@ <hr />
417408
entry_attribute("Login expiration time", 6, "cookie-expire", "cex",
418409
"8766", 0);
419410
@ <p>The number of hours for which a login is valid. This must be a
420411
@ positive number. The default is 8766 hours which is approximately equal
421412
--- src/setup.c
+++ src/setup.c
@@ -402,19 +402,10 @@
402 @ variable or the "Authentication:" HTTP header to find the username and
403 @ password. This is another way of supporting Basic Authenitication.
404 @ (Property: "http_authentication_ok")
405 @ </p>
406 @
407 @ <hr />
408 entry_attribute("IP address terms used in login cookie", 3,
409 "ip-prefix-terms", "ipt", "2", 0);
410 @ <p>The number of octets of of the IP address used in the login cookie.
411 @ Set to zero to omit the IP address from the login cookie. A value of
412 @ 2 is recommended.
413 @ (Property: "ip-prefix-terms")
414 @ </p>
415 @
416 @ <hr />
417 entry_attribute("Login expiration time", 6, "cookie-expire", "cex",
418 "8766", 0);
419 @ <p>The number of hours for which a login is valid. This must be a
420 @ positive number. The default is 8766 hours which is approximately equal
421
--- src/setup.c
+++ src/setup.c
@@ -402,19 +402,10 @@
402 @ variable or the "Authentication:" HTTP header to find the username and
403 @ password. This is another way of supporting Basic Authenitication.
404 @ (Property: "http_authentication_ok")
405 @ </p>
406 @
 
 
 
 
 
 
 
 
 
407 @ <hr />
408 entry_attribute("Login expiration time", 6, "cookie-expire", "cex",
409 "8766", 0);
410 @ <p>The number of hours for which a login is valid. This must be a
411 @ positive number. The default is 8766 hours which is approximately equal
412
+45 -11
--- src/setupuser.c
+++ src/setupuser.c
@@ -35,10 +35,11 @@
3535
*/
3636
void setup_ulist(void){
3737
Stmt s;
3838
double rNow;
3939
const char *zWith = P("with");
40
+ int bUnusedOnly = P("unused")!=0;
4041
4142
login_check_credentials();
4243
if( !g.perm.Admin ){
4344
login_needed(0);
4445
return;
@@ -45,12 +46,15 @@
4546
}
4647
4748
style_submenu_element("Add", "setup_uedit");
4849
style_submenu_element("Log", "access_log");
4950
style_submenu_element("Help", "setup_ulist_notes");
51
+ if( alert_tables_exist() ){
52
+ style_submenu_element("Subscribers", "subscribers");
53
+ }
5054
style_header("User List");
51
- if( zWith==0 || zWith[0]==0 ){
55
+ if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
5256
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
5357
@ <thead><tr>
5458
@ <th>Category
5559
@ <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>)
5660
@ <th>Info <th>Last Change</tr></thead>
@@ -91,16 +95,23 @@
9195
db_finalize(&s);
9296
@ </tbody></table>
9397
@ <div class='section'>Users</div>
9498
}else{
9599
style_submenu_element("All Users", "setup_ulist");
96
- if( zWith[1]==0 ){
97
- @ <div class='section'>Users with capability "%h(zWith)"</div>
98
- }else{
99
- @ <div class='section'>Users with any capability in "%h(zWith)"</div>
100
+ if( bUnusedOnly ){
101
+ @ <div class='section'>Unused logins</div>
102
+ }else if( zWith ){
103
+ if( zWith[1]==0 ){
104
+ @ <div class='section'>Users with capability "%h(zWith)"</div>
105
+ }else{
106
+ @ <div class='section'>Users with any capability in "%h(zWith)"</div>
107
+ }
100108
}
101109
}
110
+ if( !bUnusedOnly ){
111
+ style_submenu_element("Unused", "setup_ulist?unused");
112
+ }
102113
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
103114
@ data-column-types='ktxTTK' data-init-sort='2'>
104115
@ <thead><tr>
105116
@ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
106117
@ <tbody>
@@ -117,11 +128,19 @@
117128
" SELECT login AS uname, rcvfrom.mtime AS mtime"
118129
" FROM rcvfrom JOIN user USING(uid))"
119130
" GROUP BY 1;"
120131
);
121132
}
122
- if( zWith && zWith[0] ){
133
+ if( bUnusedOnly ){
134
+ zWith = mprintf(
135
+ " AND login NOT IN ("
136
+ "SELECT user FROM event WHERE user NOT NULL "
137
+ "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
138
+ " AND uid NOT IN (SELECT uid FROM rcvfrom)",
139
+ alert_tables_exist() ?
140
+ " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
141
+ }else if( zWith && zWith[0] ){
123142
zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
124143
}else{
125144
zWith = "";
126145
}
127146
db_prepare(&s,
@@ -517,22 +536,33 @@
517536
@ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))">
518537
@ <table width="100%%">
519538
@ <tr>
520539
@ <td class="usetupEditLabel">User ID:</td>
521540
if( uid ){
522
- @ <td>%d(uid) <input type="hidden" name="id" value="%d(uid)" /></td>
541
+ @ <td>%d(uid) <input type="hidden" name="id" value="%d(uid)" />\
542
+ @ </td>
523543
}else{
524544
@ <td>(new user)<input type="hidden" name="id" value="0" /></td>
525545
}
526546
@ </tr>
527547
@ <tr>
528548
@ <td class="usetupEditLabel">Login:</td>
529549
if( login_is_special(zLogin) ){
530550
@ <td><b>%h(zLogin)</b></td>
531551
}else{
532
- @ <td><input type="text" name="login" value="%h(zLogin)" /></td>
533
- @ </tr>
552
+ @ <td><input type="text" name="login" value="%h(zLogin)" />\
553
+ if( alert_tables_exist() ){
554
+ char *zSCode; /* Subscriber Code */
555
+ zSCode = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
556
+ " WHERE suname=%Q", zLogin);
557
+ if( zSCode && zSCode[0] ){
558
+ @ &nbsp;&nbsp;<a href="%R/alerts/%s(zSCode)">\
559
+ @ (subscription info for %h(zLogin))</a>\
560
+ }
561
+ fossil_free(zSCode);
562
+ }
563
+ @ </td></tr>
534564
@ <tr>
535565
@ <td class="usetupEditLabel">Contact&nbsp;Info:</td>
536566
@ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
537567
}
538568
@ </tr>
@@ -550,12 +580,14 @@
550580
@ Admin%s(B('a'))</label>
551581
@ <li><label><input type="checkbox" name="au"%s(oa['u']) />
552582
@ Reader%s(B('u'))</label>
553583
@ <li><label><input type="checkbox" name="av"%s(oa['v']) />
554584
@ Developer%s(B('v'))</label>
585
+#if 0 /* Not Used */
555586
@ <li><label><input type="checkbox" name="ad"%s(oa['d']) />
556587
@ Delete%s(B('d'))</label>
588
+#endif
557589
@ <li><label><input type="checkbox" name="ae"%s(oa['e']) />
558590
@ View-PII%s(B('e'))</label>
559591
@ <li><label><input type="checkbox" name="ap"%s(oa['p']) />
560592
@ Password%s(B('p'))</label>
561593
@ <li><label><input type="checkbox" name="ai"%s(oa['i']) />
@@ -625,14 +657,16 @@
625657
if( !login_is_special(zLogin) ){
626658
@ <tr>
627659
@ <td align="right">Password:</td>
628660
if( zPw[0] ){
629661
/* Obscure the password for all users */
630
- @ <td><input type="password" name="pw" value="**********" /></td>
662
+ @ <td><input type="password" autocomplete="off" name="pw"\
663
+ @ value="**********" /></td>
631664
}else{
632665
/* Show an empty password as an empty input field */
633
- @ <td><input type="password" name="pw" value="" /></td>
666
+ @ <td><input type="password" autocomplete="off" name="pw"\
667
+ @ value="" /></td>
634668
}
635669
@ </tr>
636670
}
637671
zGroup = login_group_name();
638672
if( zGroup ){
639673
--- src/setupuser.c
+++ src/setupuser.c
@@ -35,10 +35,11 @@
35 */
36 void setup_ulist(void){
37 Stmt s;
38 double rNow;
39 const char *zWith = P("with");
 
40
41 login_check_credentials();
42 if( !g.perm.Admin ){
43 login_needed(0);
44 return;
@@ -45,12 +46,15 @@
45 }
46
47 style_submenu_element("Add", "setup_uedit");
48 style_submenu_element("Log", "access_log");
49 style_submenu_element("Help", "setup_ulist_notes");
 
 
 
50 style_header("User List");
51 if( zWith==0 || zWith[0]==0 ){
52 @ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
53 @ <thead><tr>
54 @ <th>Category
55 @ <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>)
56 @ <th>Info <th>Last Change</tr></thead>
@@ -91,16 +95,23 @@
91 db_finalize(&s);
92 @ </tbody></table>
93 @ <div class='section'>Users</div>
94 }else{
95 style_submenu_element("All Users", "setup_ulist");
96 if( zWith[1]==0 ){
97 @ <div class='section'>Users with capability "%h(zWith)"</div>
98 }else{
99 @ <div class='section'>Users with any capability in "%h(zWith)"</div>
 
 
 
 
100 }
101 }
 
 
 
102 @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
103 @ data-column-types='ktxTTK' data-init-sort='2'>
104 @ <thead><tr>
105 @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
106 @ <tbody>
@@ -117,11 +128,19 @@
117 " SELECT login AS uname, rcvfrom.mtime AS mtime"
118 " FROM rcvfrom JOIN user USING(uid))"
119 " GROUP BY 1;"
120 );
121 }
122 if( zWith && zWith[0] ){
 
 
 
 
 
 
 
 
123 zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
124 }else{
125 zWith = "";
126 }
127 db_prepare(&s,
@@ -517,22 +536,33 @@
517 @ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))">
518 @ <table width="100%%">
519 @ <tr>
520 @ <td class="usetupEditLabel">User ID:</td>
521 if( uid ){
522 @ <td>%d(uid) <input type="hidden" name="id" value="%d(uid)" /></td>
 
523 }else{
524 @ <td>(new user)<input type="hidden" name="id" value="0" /></td>
525 }
526 @ </tr>
527 @ <tr>
528 @ <td class="usetupEditLabel">Login:</td>
529 if( login_is_special(zLogin) ){
530 @ <td><b>%h(zLogin)</b></td>
531 }else{
532 @ <td><input type="text" name="login" value="%h(zLogin)" /></td>
533 @ </tr>
 
 
 
 
 
 
 
 
 
 
534 @ <tr>
535 @ <td class="usetupEditLabel">Contact&nbsp;Info:</td>
536 @ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
537 }
538 @ </tr>
@@ -550,12 +580,14 @@
550 @ Admin%s(B('a'))</label>
551 @ <li><label><input type="checkbox" name="au"%s(oa['u']) />
552 @ Reader%s(B('u'))</label>
553 @ <li><label><input type="checkbox" name="av"%s(oa['v']) />
554 @ Developer%s(B('v'))</label>
 
555 @ <li><label><input type="checkbox" name="ad"%s(oa['d']) />
556 @ Delete%s(B('d'))</label>
 
557 @ <li><label><input type="checkbox" name="ae"%s(oa['e']) />
558 @ View-PII%s(B('e'))</label>
559 @ <li><label><input type="checkbox" name="ap"%s(oa['p']) />
560 @ Password%s(B('p'))</label>
561 @ <li><label><input type="checkbox" name="ai"%s(oa['i']) />
@@ -625,14 +657,16 @@
625 if( !login_is_special(zLogin) ){
626 @ <tr>
627 @ <td align="right">Password:</td>
628 if( zPw[0] ){
629 /* Obscure the password for all users */
630 @ <td><input type="password" name="pw" value="**********" /></td>
 
631 }else{
632 /* Show an empty password as an empty input field */
633 @ <td><input type="password" name="pw" value="" /></td>
 
634 }
635 @ </tr>
636 }
637 zGroup = login_group_name();
638 if( zGroup ){
639
--- src/setupuser.c
+++ src/setupuser.c
@@ -35,10 +35,11 @@
35 */
36 void setup_ulist(void){
37 Stmt s;
38 double rNow;
39 const char *zWith = P("with");
40 int bUnusedOnly = P("unused")!=0;
41
42 login_check_credentials();
43 if( !g.perm.Admin ){
44 login_needed(0);
45 return;
@@ -45,12 +46,15 @@
46 }
47
48 style_submenu_element("Add", "setup_uedit");
49 style_submenu_element("Log", "access_log");
50 style_submenu_element("Help", "setup_ulist_notes");
51 if( alert_tables_exist() ){
52 style_submenu_element("Subscribers", "subscribers");
53 }
54 style_header("User List");
55 if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
56 @ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
57 @ <thead><tr>
58 @ <th>Category
59 @ <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>)
60 @ <th>Info <th>Last Change</tr></thead>
@@ -91,16 +95,23 @@
95 db_finalize(&s);
96 @ </tbody></table>
97 @ <div class='section'>Users</div>
98 }else{
99 style_submenu_element("All Users", "setup_ulist");
100 if( bUnusedOnly ){
101 @ <div class='section'>Unused logins</div>
102 }else if( zWith ){
103 if( zWith[1]==0 ){
104 @ <div class='section'>Users with capability "%h(zWith)"</div>
105 }else{
106 @ <div class='section'>Users with any capability in "%h(zWith)"</div>
107 }
108 }
109 }
110 if( !bUnusedOnly ){
111 style_submenu_element("Unused", "setup_ulist?unused");
112 }
113 @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
114 @ data-column-types='ktxTTK' data-init-sort='2'>
115 @ <thead><tr>
116 @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
117 @ <tbody>
@@ -117,11 +128,19 @@
128 " SELECT login AS uname, rcvfrom.mtime AS mtime"
129 " FROM rcvfrom JOIN user USING(uid))"
130 " GROUP BY 1;"
131 );
132 }
133 if( bUnusedOnly ){
134 zWith = mprintf(
135 " AND login NOT IN ("
136 "SELECT user FROM event WHERE user NOT NULL "
137 "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
138 " AND uid NOT IN (SELECT uid FROM rcvfrom)",
139 alert_tables_exist() ?
140 " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
141 }else if( zWith && zWith[0] ){
142 zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
143 }else{
144 zWith = "";
145 }
146 db_prepare(&s,
@@ -517,22 +536,33 @@
536 @ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))">
537 @ <table width="100%%">
538 @ <tr>
539 @ <td class="usetupEditLabel">User ID:</td>
540 if( uid ){
541 @ <td>%d(uid) <input type="hidden" name="id" value="%d(uid)" />\
542 @ </td>
543 }else{
544 @ <td>(new user)<input type="hidden" name="id" value="0" /></td>
545 }
546 @ </tr>
547 @ <tr>
548 @ <td class="usetupEditLabel">Login:</td>
549 if( login_is_special(zLogin) ){
550 @ <td><b>%h(zLogin)</b></td>
551 }else{
552 @ <td><input type="text" name="login" value="%h(zLogin)" />\
553 if( alert_tables_exist() ){
554 char *zSCode; /* Subscriber Code */
555 zSCode = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
556 " WHERE suname=%Q", zLogin);
557 if( zSCode && zSCode[0] ){
558 @ &nbsp;&nbsp;<a href="%R/alerts/%s(zSCode)">\
559 @ (subscription info for %h(zLogin))</a>\
560 }
561 fossil_free(zSCode);
562 }
563 @ </td></tr>
564 @ <tr>
565 @ <td class="usetupEditLabel">Contact&nbsp;Info:</td>
566 @ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
567 }
568 @ </tr>
@@ -550,12 +580,14 @@
580 @ Admin%s(B('a'))</label>
581 @ <li><label><input type="checkbox" name="au"%s(oa['u']) />
582 @ Reader%s(B('u'))</label>
583 @ <li><label><input type="checkbox" name="av"%s(oa['v']) />
584 @ Developer%s(B('v'))</label>
585 #if 0 /* Not Used */
586 @ <li><label><input type="checkbox" name="ad"%s(oa['d']) />
587 @ Delete%s(B('d'))</label>
588 #endif
589 @ <li><label><input type="checkbox" name="ae"%s(oa['e']) />
590 @ View-PII%s(B('e'))</label>
591 @ <li><label><input type="checkbox" name="ap"%s(oa['p']) />
592 @ Password%s(B('p'))</label>
593 @ <li><label><input type="checkbox" name="ai"%s(oa['i']) />
@@ -625,14 +657,16 @@
657 if( !login_is_special(zLogin) ){
658 @ <tr>
659 @ <td align="right">Password:</td>
660 if( zPw[0] ){
661 /* Obscure the password for all users */
662 @ <td><input type="password" autocomplete="off" name="pw"\
663 @ value="**********" /></td>
664 }else{
665 /* Show an empty password as an empty input field */
666 @ <td><input type="password" autocomplete="off" name="pw"\
667 @ value="" /></td>
668 }
669 @ </tr>
670 }
671 zGroup = login_group_name();
672 if( zGroup ){
673
+237 -52
--- src/shell.c
+++ src/shell.c
@@ -413,10 +413,19 @@
413413
/*
414414
** True if an interrupt (Control-C) has been received.
415415
*/
416416
static volatile int seenInterrupt = 0;
417417
418
+#ifdef SQLITE_DEBUG
419
+/*
420
+** Out-of-memory simulator variables
421
+*/
422
+static unsigned int oomCounter = 0; /* Simulate OOM when equals 1 */
423
+static unsigned int oomRepeat = 0; /* Number of OOMs in a row */
424
+static void*(*defaultMalloc)(int) = 0; /* The low-level malloc routine */
425
+#endif /* SQLITE_DEBUG */
426
+
418427
/*
419428
** This is the name of our program. It is set in main(), used
420429
** in a number of other places, mostly for error messages.
421430
*/
422431
static char *Argv0;
@@ -463,10 +472,53 @@
463472
/* Indicate out-of-memory and exit. */
464473
static void shell_out_of_memory(void){
465474
raw_printf(stderr,"Error: out of memory\n");
466475
exit(1);
467476
}
477
+
478
+#ifdef SQLITE_DEBUG
479
+/* This routine is called when a simulated OOM occurs. It is broken
480
+** out as a separate routine to make it easy to set a breakpoint on
481
+** the OOM
482
+*/
483
+void shellOomFault(void){
484
+ if( oomRepeat>0 ){
485
+ oomRepeat--;
486
+ }else{
487
+ oomCounter--;
488
+ }
489
+}
490
+#endif /* SQLITE_DEBUG */
491
+
492
+#ifdef SQLITE_DEBUG
493
+/* This routine is a replacement malloc() that is used to simulate
494
+** Out-Of-Memory (OOM) errors for testing purposes.
495
+*/
496
+static void *oomMalloc(int nByte){
497
+ if( oomCounter ){
498
+ if( oomCounter==1 ){
499
+ shellOomFault();
500
+ return 0;
501
+ }else{
502
+ oomCounter--;
503
+ }
504
+ }
505
+ return defaultMalloc(nByte);
506
+}
507
+#endif /* SQLITE_DEBUG */
508
+
509
+#ifdef SQLITE_DEBUG
510
+/* Register the OOM simulator. This must occur before any memory
511
+** allocations */
512
+static void registerOomSimulator(void){
513
+ sqlite3_mem_methods mem;
514
+ sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem);
515
+ defaultMalloc = mem.xMalloc;
516
+ mem.xMalloc = oomMalloc;
517
+ sqlite3_config(SQLITE_CONFIG_MALLOC, &mem);
518
+}
519
+#endif
468520
469521
/*
470522
** Write I/O traces to the following stream.
471523
*/
472524
#ifdef SQLITE_ENABLE_IOTRACE
@@ -12086,10 +12138,22 @@
1208612138
" Run \".filectrl\" with no arguments for details",
1208712139
".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
1208812140
".headers on|off Turn display of headers on or off",
1208912141
".help ?-all? ?PATTERN? Show help text for PATTERN",
1209012142
".import FILE TABLE Import data from FILE into TABLE",
12143
+ " Options:",
12144
+ " --ascii Use \\037 and \\036 as column and row separators",
12145
+ " --csv Use , and \\n as column and row separators",
12146
+ " --skip N Skip the first N rows of input",
12147
+ " -v \"Verbose\" - increase auxiliary output",
12148
+ " Notes:",
12149
+ " * If TABLE does not exist, it is created. The first row of input",
12150
+ " determines the column names.",
12151
+ " * If neither --csv or --ascii are used, the input mode is derived",
12152
+ " from the \".mode\" output mode",
12153
+ " * If FILE begins with \"|\" then it is a command that generates the",
12154
+ " input text.",
1209112155
#ifndef SQLITE_OMIT_TEST_CONTROL
1209212156
".imposter INDEX TABLE Create imposter table TABLE on index INDEX",
1209312157
#endif
1209412158
".indexes ?TABLE? Show names of indexes",
1209512159
" If TABLE is specified, only show indexes for",
@@ -12121,10 +12185,13 @@
1212112185
".once (-e|-x|FILE) Output for the next SQL command only to FILE",
1212212186
" If FILE begins with '|' then open as a pipe",
1212312187
" Other options:",
1212412188
" -e Invoke system text editor",
1212512189
" -x Open in a spreadsheet",
12190
+#ifdef SQLITE_DEBUG
12191
+ ".oom [--repeat M] [N] Simulate an OOM error on the N-th allocation",
12192
+#endif
1212612193
".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
1212712194
" Options:",
1212812195
" --append Use appendvfs to append database to the end of FILE",
1212912196
#ifdef SQLITE_ENABLE_DESERIALIZE
1213012197
" --deserialize Load into memory useing sqlite3_deserialize()",
@@ -13071,10 +13138,12 @@
1307113138
FILE *in; /* Read the CSV text from this input stream */
1307213139
char *z; /* Accumulated text for a field */
1307313140
int n; /* Number of bytes in z */
1307413141
int nAlloc; /* Space allocated for z[] */
1307513142
int nLine; /* Current line number */
13143
+ int nRow; /* Number of rows imported */
13144
+ int nErr; /* Number of errors encountered */
1307613145
int bNotFirst; /* True if one or more bytes already read */
1307713146
int cTerm; /* Character that terminated the most recent field */
1307813147
int cColSep; /* The column separator character. (Usually ",") */
1307913148
int cRowSep; /* The row separator character. (Usually "\n") */
1308013149
};
@@ -16131,12 +16200,12 @@
1613116200
showHelp(p->out, 0);
1613216201
}
1613316202
}else
1613416203
1613516204
if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
16136
- char *zTable; /* Insert data into this table */
16137
- char *zFile; /* Name of file to extra content from */
16205
+ char *zTable = 0; /* Insert data into this table */
16206
+ char *zFile = 0; /* Name of file to extra content from */
1613816207
sqlite3_stmt *pStmt = NULL; /* A statement */
1613916208
int nCol; /* Number of columns in the table */
1614016209
int nByte; /* Number of bytes in an SQL string */
1614116210
int i, j; /* Loop counters */
1614216211
int needCommit; /* True to COMMIT or ROLLBACK at end */
@@ -16143,75 +16212,141 @@
1614316212
int nSep; /* Number of bytes in p->colSeparator[] */
1614416213
char *zSql; /* An SQL statement */
1614516214
ImportCtx sCtx; /* Reader context */
1614616215
char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
1614716216
int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close file */
16148
-
16149
- if( nArg!=3 ){
16150
- raw_printf(stderr, "Usage: .import FILE TABLE\n");
16151
- goto meta_command_exit;
16152
- }
16153
- zFile = azArg[1];
16154
- zTable = azArg[2];
16155
- seenInterrupt = 0;
16217
+ int eVerbose = 0; /* Larger for more console output */
16218
+ int nSkip = 0; /* Initial lines to skip */
16219
+ int useOutputMode = 1; /* Use output mode to determine separators */
16220
+
1615616221
memset(&sCtx, 0, sizeof(sCtx));
16157
- open_db(p, 0);
16158
- nSep = strlen30(p->colSeparator);
16159
- if( nSep==0 ){
16160
- raw_printf(stderr,
16161
- "Error: non-null column separator required for import\n");
16162
- return 1;
16163
- }
16164
- if( nSep>1 ){
16165
- raw_printf(stderr, "Error: multi-character column separators not allowed"
16166
- " for import\n");
16167
- return 1;
16168
- }
16169
- nSep = strlen30(p->rowSeparator);
16170
- if( nSep==0 ){
16171
- raw_printf(stderr, "Error: non-null row separator required for import\n");
16172
- return 1;
16173
- }
16174
- if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator, SEP_CrLf)==0 ){
16175
- /* When importing CSV (only), if the row separator is set to the
16176
- ** default output row separator, change it to the default input
16177
- ** row separator. This avoids having to maintain different input
16178
- ** and output row separators. */
16179
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
16180
- nSep = strlen30(p->rowSeparator);
16181
- }
16182
- if( nSep>1 ){
16183
- raw_printf(stderr, "Error: multi-character row separators not allowed"
16184
- " for import\n");
16185
- return 1;
16222
+ if( p->mode==MODE_Ascii ){
16223
+ xRead = ascii_read_one_field;
16224
+ }else{
16225
+ xRead = csv_read_one_field;
16226
+ }
16227
+ for(i=1; i<nArg; i++){
16228
+ char *z = azArg[i];
16229
+ if( z[0]=='-' && z[1]=='-' ) z++;
16230
+ if( z[0]!='-' ){
16231
+ if( zFile==0 ){
16232
+ zFile = z;
16233
+ }else if( zTable==0 ){
16234
+ zTable = z;
16235
+ }else{
16236
+ utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z);
16237
+ showHelp(p->out, "import");
16238
+ rc = 1;
16239
+ goto meta_command_exit;
16240
+ }
16241
+ }else if( strcmp(z,"-v")==0 ){
16242
+ eVerbose++;
16243
+ }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
16244
+ nSkip = integerValue(azArg[++i]);
16245
+ }else if( strcmp(z,"-ascii")==0 ){
16246
+ sCtx.cColSep = SEP_Unit[0];
16247
+ sCtx.cRowSep = SEP_Record[0];
16248
+ xRead = ascii_read_one_field;
16249
+ useOutputMode = 0;
16250
+ }else if( strcmp(z,"-csv")==0 ){
16251
+ sCtx.cColSep = ',';
16252
+ sCtx.cRowSep = '\n';
16253
+ xRead = csv_read_one_field;
16254
+ useOutputMode = 0;
16255
+ }else{
16256
+ utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z);
16257
+ showHelp(p->out, "import");
16258
+ rc = 1;
16259
+ goto meta_command_exit;
16260
+ }
16261
+ }
16262
+ if( zTable==0 ){
16263
+ utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
16264
+ zFile==0 ? "FILE" : "TABLE");
16265
+ showHelp(p->out, "import");
16266
+ rc = 1;
16267
+ goto meta_command_exit;
16268
+ }
16269
+ seenInterrupt = 0;
16270
+ open_db(p, 0);
16271
+ if( useOutputMode ){
16272
+ /* If neither the --csv or --ascii options are specified, then set
16273
+ ** the column and row separator characters from the output mode. */
16274
+ nSep = strlen30(p->colSeparator);
16275
+ if( nSep==0 ){
16276
+ raw_printf(stderr,
16277
+ "Error: non-null column separator required for import\n");
16278
+ rc = 1;
16279
+ goto meta_command_exit;
16280
+ }
16281
+ if( nSep>1 ){
16282
+ raw_printf(stderr,
16283
+ "Error: multi-character column separators not allowed"
16284
+ " for import\n");
16285
+ rc = 1;
16286
+ goto meta_command_exit;
16287
+ }
16288
+ nSep = strlen30(p->rowSeparator);
16289
+ if( nSep==0 ){
16290
+ raw_printf(stderr,
16291
+ "Error: non-null row separator required for import\n");
16292
+ rc = 1;
16293
+ goto meta_command_exit;
16294
+ }
16295
+ if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
16296
+ /* When importing CSV (only), if the row separator is set to the
16297
+ ** default output row separator, change it to the default input
16298
+ ** row separator. This avoids having to maintain different input
16299
+ ** and output row separators. */
16300
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
16301
+ nSep = strlen30(p->rowSeparator);
16302
+ }
16303
+ if( nSep>1 ){
16304
+ raw_printf(stderr, "Error: multi-character row separators not allowed"
16305
+ " for import\n");
16306
+ rc = 1;
16307
+ goto meta_command_exit;
16308
+ }
16309
+ sCtx.cColSep = p->colSeparator[0];
16310
+ sCtx.cRowSep = p->rowSeparator[0];
1618616311
}
1618716312
sCtx.zFile = zFile;
1618816313
sCtx.nLine = 1;
1618916314
if( sCtx.zFile[0]=='|' ){
1619016315
#ifdef SQLITE_OMIT_POPEN
1619116316
raw_printf(stderr, "Error: pipes are not supported in this OS\n");
16192
- return 1;
16317
+ rc = 1;
16318
+ goto meta_command_exit;
1619316319
#else
1619416320
sCtx.in = popen(sCtx.zFile+1, "r");
1619516321
sCtx.zFile = "<pipe>";
1619616322
xCloser = pclose;
1619716323
#endif
1619816324
}else{
1619916325
sCtx.in = fopen(sCtx.zFile, "rb");
1620016326
xCloser = fclose;
1620116327
}
16202
- if( p->mode==MODE_Ascii ){
16203
- xRead = ascii_read_one_field;
16204
- }else{
16205
- xRead = csv_read_one_field;
16206
- }
1620716328
if( sCtx.in==0 ){
1620816329
utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
16209
- return 1;
16330
+ rc = 1;
16331
+ goto meta_command_exit;
1621016332
}
16211
- sCtx.cColSep = p->colSeparator[0];
16212
- sCtx.cRowSep = p->rowSeparator[0];
16333
+ if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
16334
+ char zSep[2];
16335
+ zSep[1] = 0;
16336
+ zSep[0] = sCtx.cColSep;
16337
+ utf8_printf(p->out, "Column separator ");
16338
+ output_c_string(p->out, zSep);
16339
+ utf8_printf(p->out, ", row separator ");
16340
+ zSep[0] = sCtx.cRowSep;
16341
+ output_c_string(p->out, zSep);
16342
+ utf8_printf(p->out, "\n");
16343
+ }
16344
+ while( (nSkip--)>0 ){
16345
+ while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
16346
+ sCtx.nLine++;
16347
+ }
1621316348
zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
1621416349
if( zSql==0 ){
1621516350
xCloser(sCtx.in);
1621616351
shell_out_of_memory();
1621716352
}
@@ -16229,30 +16364,36 @@
1622916364
if( cSep=='(' ){
1623016365
sqlite3_free(zCreate);
1623116366
sqlite3_free(sCtx.z);
1623216367
xCloser(sCtx.in);
1623316368
utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
16234
- return 1;
16369
+ rc = 1;
16370
+ goto meta_command_exit;
1623516371
}
1623616372
zCreate = sqlite3_mprintf("%z\n)", zCreate);
16373
+ if( eVerbose>=1 ){
16374
+ utf8_printf(p->out, "%s\n", zCreate);
16375
+ }
1623716376
rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
1623816377
sqlite3_free(zCreate);
1623916378
if( rc ){
1624016379
utf8_printf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable,
1624116380
sqlite3_errmsg(p->db));
1624216381
sqlite3_free(sCtx.z);
1624316382
xCloser(sCtx.in);
16244
- return 1;
16383
+ rc = 1;
16384
+ goto meta_command_exit;
1624516385
}
1624616386
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
1624716387
}
1624816388
sqlite3_free(zSql);
1624916389
if( rc ){
1625016390
if (pStmt) sqlite3_finalize(pStmt);
1625116391
utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
1625216392
xCloser(sCtx.in);
16253
- return 1;
16393
+ rc = 1;
16394
+ goto meta_command_exit;
1625416395
}
1625516396
nCol = sqlite3_column_count(pStmt);
1625616397
sqlite3_finalize(pStmt);
1625716398
pStmt = 0;
1625816399
if( nCol==0 ) return 0; /* no columns, no error */
@@ -16267,17 +16408,21 @@
1626716408
zSql[j++] = ',';
1626816409
zSql[j++] = '?';
1626916410
}
1627016411
zSql[j++] = ')';
1627116412
zSql[j] = 0;
16413
+ if( eVerbose>=2 ){
16414
+ utf8_printf(p->out, "Insert using: %s\n", zSql);
16415
+ }
1627216416
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
1627316417
sqlite3_free(zSql);
1627416418
if( rc ){
1627516419
utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
1627616420
if (pStmt) sqlite3_finalize(pStmt);
1627716421
xCloser(sCtx.in);
16278
- return 1;
16422
+ rc = 1;
16423
+ goto meta_command_exit;
1627916424
}
1628016425
needCommit = sqlite3_get_autocommit(p->db);
1628116426
if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
1628216427
do{
1628316428
int startLine = sCtx.nLine;
@@ -16316,18 +16461,26 @@
1631616461
sqlite3_step(pStmt);
1631716462
rc = sqlite3_reset(pStmt);
1631816463
if( rc!=SQLITE_OK ){
1631916464
utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
1632016465
startLine, sqlite3_errmsg(p->db));
16466
+ sCtx.nErr++;
16467
+ }else{
16468
+ sCtx.nRow++;
1632116469
}
1632216470
}
1632316471
}while( sCtx.cTerm!=EOF );
1632416472
1632516473
xCloser(sCtx.in);
1632616474
sqlite3_free(sCtx.z);
1632716475
sqlite3_finalize(pStmt);
1632816476
if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
16477
+ if( eVerbose>0 ){
16478
+ utf8_printf(p->out,
16479
+ "Added %d rows with %d errors using %d lines of input\n",
16480
+ sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
16481
+ }
1632916482
}else
1633016483
1633116484
#ifndef SQLITE_UNTESTABLE
1633216485
if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){
1633316486
char *zSql;
@@ -16602,10 +16755,38 @@
1660216755
raw_printf(stderr, "Usage: .nullvalue STRING\n");
1660316756
rc = 1;
1660416757
}
1660516758
}else
1660616759
16760
+#ifdef SQLITE_DEBUG
16761
+ if( c=='o' && strcmp(azArg[0],"oom")==0 ){
16762
+ int i;
16763
+ for(i=1; i<nArg; i++){
16764
+ const char *z = azArg[i];
16765
+ if( z[0]=='-' && z[1]=='-' ) z++;
16766
+ if( strcmp(z,"-repeat")==0 ){
16767
+ if( i==nArg-1 ){
16768
+ raw_printf(p->out, "missing argument on \"%s\"\n", azArg[i]);
16769
+ rc = 1;
16770
+ }else{
16771
+ oomRepeat = (int)integerValue(azArg[++i]);
16772
+ }
16773
+ }else if( IsDigit(z[0]) ){
16774
+ oomCounter = (int)integerValue(azArg[i]);
16775
+ }else{
16776
+ raw_printf(p->out, "unknown argument: \"%s\"\n", azArg[i]);
16777
+ raw_printf(p->out, "Usage: .oom [--repeat N] [M]\n");
16778
+ rc = 1;
16779
+ }
16780
+ }
16781
+ if( rc==0 ){
16782
+ raw_printf(p->out, "oomCounter = %d\n", oomCounter);
16783
+ raw_printf(p->out, "oomRepeat = %d\n", oomRepeat);
16784
+ }
16785
+ }else
16786
+#endif /* SQLITE_DEBUG */
16787
+
1660716788
if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){
1660816789
char *zNewFilename; /* Name of the database file to open */
1660916790
int iName = 1; /* Index in azArg[] of the filename */
1661016791
int newFlag = 0; /* True to delete file before opening */
1661116792
/* Close the existing database */
@@ -18684,10 +18865,14 @@
1868418865
1868518866
setBinaryMode(stdin, 0);
1868618867
setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
1868718868
stdin_is_interactive = isatty(0);
1868818869
stdout_is_console = isatty(1);
18870
+
18871
+#ifdef SQLITE_DEBUG
18872
+ registerOomSimulator();
18873
+#endif
1868918874
1869018875
#if !defined(_WIN32_WCE)
1869118876
if( getenv("SQLITE_DEBUG_BREAK") ){
1869218877
if( isatty(0) && isatty(2) ){
1869318878
fprintf(stderr,
1869418879
1869518880
ADDED src/sounds/0.wav
1869618881
ADDED src/sounds/1.wav
1869718882
ADDED src/sounds/2.wav
1869818883
ADDED src/sounds/3.wav
1869918884
ADDED src/sounds/4.wav
1870018885
ADDED src/sounds/5.wav
1870118886
ADDED src/sounds/6.wav
1870218887
ADDED src/sounds/7.wav
1870318888
ADDED src/sounds/8.wav
1870418889
ADDED src/sounds/9.wav
1870518890
ADDED src/sounds/README.md
1870618891
ADDED src/sounds/a.wav
1870718892
ADDED src/sounds/b.wav
1870818893
ADDED src/sounds/c.wav
1870918894
ADDED src/sounds/d.wav
1871018895
ADDED src/sounds/e.wav
1871118896
ADDED src/sounds/f.wav
--- src/shell.c
+++ src/shell.c
@@ -413,10 +413,19 @@
413 /*
414 ** True if an interrupt (Control-C) has been received.
415 */
416 static volatile int seenInterrupt = 0;
417
 
 
 
 
 
 
 
 
 
418 /*
419 ** This is the name of our program. It is set in main(), used
420 ** in a number of other places, mostly for error messages.
421 */
422 static char *Argv0;
@@ -463,10 +472,53 @@
463 /* Indicate out-of-memory and exit. */
464 static void shell_out_of_memory(void){
465 raw_printf(stderr,"Error: out of memory\n");
466 exit(1);
467 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
469 /*
470 ** Write I/O traces to the following stream.
471 */
472 #ifdef SQLITE_ENABLE_IOTRACE
@@ -12086,10 +12138,22 @@
12086 " Run \".filectrl\" with no arguments for details",
12087 ".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
12088 ".headers on|off Turn display of headers on or off",
12089 ".help ?-all? ?PATTERN? Show help text for PATTERN",
12090 ".import FILE TABLE Import data from FILE into TABLE",
 
 
 
 
 
 
 
 
 
 
 
 
12091 #ifndef SQLITE_OMIT_TEST_CONTROL
12092 ".imposter INDEX TABLE Create imposter table TABLE on index INDEX",
12093 #endif
12094 ".indexes ?TABLE? Show names of indexes",
12095 " If TABLE is specified, only show indexes for",
@@ -12121,10 +12185,13 @@
12121 ".once (-e|-x|FILE) Output for the next SQL command only to FILE",
12122 " If FILE begins with '|' then open as a pipe",
12123 " Other options:",
12124 " -e Invoke system text editor",
12125 " -x Open in a spreadsheet",
 
 
 
12126 ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
12127 " Options:",
12128 " --append Use appendvfs to append database to the end of FILE",
12129 #ifdef SQLITE_ENABLE_DESERIALIZE
12130 " --deserialize Load into memory useing sqlite3_deserialize()",
@@ -13071,10 +13138,12 @@
13071 FILE *in; /* Read the CSV text from this input stream */
13072 char *z; /* Accumulated text for a field */
13073 int n; /* Number of bytes in z */
13074 int nAlloc; /* Space allocated for z[] */
13075 int nLine; /* Current line number */
 
 
13076 int bNotFirst; /* True if one or more bytes already read */
13077 int cTerm; /* Character that terminated the most recent field */
13078 int cColSep; /* The column separator character. (Usually ",") */
13079 int cRowSep; /* The row separator character. (Usually "\n") */
13080 };
@@ -16131,12 +16200,12 @@
16131 showHelp(p->out, 0);
16132 }
16133 }else
16134
16135 if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
16136 char *zTable; /* Insert data into this table */
16137 char *zFile; /* Name of file to extra content from */
16138 sqlite3_stmt *pStmt = NULL; /* A statement */
16139 int nCol; /* Number of columns in the table */
16140 int nByte; /* Number of bytes in an SQL string */
16141 int i, j; /* Loop counters */
16142 int needCommit; /* True to COMMIT or ROLLBACK at end */
@@ -16143,75 +16212,141 @@
16143 int nSep; /* Number of bytes in p->colSeparator[] */
16144 char *zSql; /* An SQL statement */
16145 ImportCtx sCtx; /* Reader context */
16146 char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
16147 int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close file */
16148
16149 if( nArg!=3 ){
16150 raw_printf(stderr, "Usage: .import FILE TABLE\n");
16151 goto meta_command_exit;
16152 }
16153 zFile = azArg[1];
16154 zTable = azArg[2];
16155 seenInterrupt = 0;
16156 memset(&sCtx, 0, sizeof(sCtx));
16157 open_db(p, 0);
16158 nSep = strlen30(p->colSeparator);
16159 if( nSep==0 ){
16160 raw_printf(stderr,
16161 "Error: non-null column separator required for import\n");
16162 return 1;
16163 }
16164 if( nSep>1 ){
16165 raw_printf(stderr, "Error: multi-character column separators not allowed"
16166 " for import\n");
16167 return 1;
16168 }
16169 nSep = strlen30(p->rowSeparator);
16170 if( nSep==0 ){
16171 raw_printf(stderr, "Error: non-null row separator required for import\n");
16172 return 1;
16173 }
16174 if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator, SEP_CrLf)==0 ){
16175 /* When importing CSV (only), if the row separator is set to the
16176 ** default output row separator, change it to the default input
16177 ** row separator. This avoids having to maintain different input
16178 ** and output row separators. */
16179 sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
16180 nSep = strlen30(p->rowSeparator);
16181 }
16182 if( nSep>1 ){
16183 raw_printf(stderr, "Error: multi-character row separators not allowed"
16184 " for import\n");
16185 return 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16186 }
16187 sCtx.zFile = zFile;
16188 sCtx.nLine = 1;
16189 if( sCtx.zFile[0]=='|' ){
16190 #ifdef SQLITE_OMIT_POPEN
16191 raw_printf(stderr, "Error: pipes are not supported in this OS\n");
16192 return 1;
 
16193 #else
16194 sCtx.in = popen(sCtx.zFile+1, "r");
16195 sCtx.zFile = "<pipe>";
16196 xCloser = pclose;
16197 #endif
16198 }else{
16199 sCtx.in = fopen(sCtx.zFile, "rb");
16200 xCloser = fclose;
16201 }
16202 if( p->mode==MODE_Ascii ){
16203 xRead = ascii_read_one_field;
16204 }else{
16205 xRead = csv_read_one_field;
16206 }
16207 if( sCtx.in==0 ){
16208 utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
16209 return 1;
 
16210 }
16211 sCtx.cColSep = p->colSeparator[0];
16212 sCtx.cRowSep = p->rowSeparator[0];
 
 
 
 
 
 
 
 
 
 
 
 
 
16213 zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
16214 if( zSql==0 ){
16215 xCloser(sCtx.in);
16216 shell_out_of_memory();
16217 }
@@ -16229,30 +16364,36 @@
16229 if( cSep=='(' ){
16230 sqlite3_free(zCreate);
16231 sqlite3_free(sCtx.z);
16232 xCloser(sCtx.in);
16233 utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
16234 return 1;
 
16235 }
16236 zCreate = sqlite3_mprintf("%z\n)", zCreate);
 
 
 
16237 rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
16238 sqlite3_free(zCreate);
16239 if( rc ){
16240 utf8_printf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable,
16241 sqlite3_errmsg(p->db));
16242 sqlite3_free(sCtx.z);
16243 xCloser(sCtx.in);
16244 return 1;
 
16245 }
16246 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
16247 }
16248 sqlite3_free(zSql);
16249 if( rc ){
16250 if (pStmt) sqlite3_finalize(pStmt);
16251 utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
16252 xCloser(sCtx.in);
16253 return 1;
 
16254 }
16255 nCol = sqlite3_column_count(pStmt);
16256 sqlite3_finalize(pStmt);
16257 pStmt = 0;
16258 if( nCol==0 ) return 0; /* no columns, no error */
@@ -16267,17 +16408,21 @@
16267 zSql[j++] = ',';
16268 zSql[j++] = '?';
16269 }
16270 zSql[j++] = ')';
16271 zSql[j] = 0;
 
 
 
16272 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
16273 sqlite3_free(zSql);
16274 if( rc ){
16275 utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
16276 if (pStmt) sqlite3_finalize(pStmt);
16277 xCloser(sCtx.in);
16278 return 1;
 
16279 }
16280 needCommit = sqlite3_get_autocommit(p->db);
16281 if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
16282 do{
16283 int startLine = sCtx.nLine;
@@ -16316,18 +16461,26 @@
16316 sqlite3_step(pStmt);
16317 rc = sqlite3_reset(pStmt);
16318 if( rc!=SQLITE_OK ){
16319 utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
16320 startLine, sqlite3_errmsg(p->db));
 
 
 
16321 }
16322 }
16323 }while( sCtx.cTerm!=EOF );
16324
16325 xCloser(sCtx.in);
16326 sqlite3_free(sCtx.z);
16327 sqlite3_finalize(pStmt);
16328 if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
 
 
 
 
 
16329 }else
16330
16331 #ifndef SQLITE_UNTESTABLE
16332 if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){
16333 char *zSql;
@@ -16602,10 +16755,38 @@
16602 raw_printf(stderr, "Usage: .nullvalue STRING\n");
16603 rc = 1;
16604 }
16605 }else
16606
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16607 if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){
16608 char *zNewFilename; /* Name of the database file to open */
16609 int iName = 1; /* Index in azArg[] of the filename */
16610 int newFlag = 0; /* True to delete file before opening */
16611 /* Close the existing database */
@@ -18684,10 +18865,14 @@
18684
18685 setBinaryMode(stdin, 0);
18686 setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
18687 stdin_is_interactive = isatty(0);
18688 stdout_is_console = isatty(1);
 
 
 
 
18689
18690 #if !defined(_WIN32_WCE)
18691 if( getenv("SQLITE_DEBUG_BREAK") ){
18692 if( isatty(0) && isatty(2) ){
18693 fprintf(stderr,
18694
18695 DDED src/sounds/0.wav
18696 DDED src/sounds/1.wav
18697 DDED src/sounds/2.wav
18698 DDED src/sounds/3.wav
18699 DDED src/sounds/4.wav
18700 DDED src/sounds/5.wav
18701 DDED src/sounds/6.wav
18702 DDED src/sounds/7.wav
18703 DDED src/sounds/8.wav
18704 DDED src/sounds/9.wav
18705 DDED src/sounds/README.md
18706 DDED src/sounds/a.wav
18707 DDED src/sounds/b.wav
18708 DDED src/sounds/c.wav
18709 DDED src/sounds/d.wav
18710 DDED src/sounds/e.wav
18711 DDED src/sounds/f.wav
--- src/shell.c
+++ src/shell.c
@@ -413,10 +413,19 @@
413 /*
414 ** True if an interrupt (Control-C) has been received.
415 */
416 static volatile int seenInterrupt = 0;
417
418 #ifdef SQLITE_DEBUG
419 /*
420 ** Out-of-memory simulator variables
421 */
422 static unsigned int oomCounter = 0; /* Simulate OOM when equals 1 */
423 static unsigned int oomRepeat = 0; /* Number of OOMs in a row */
424 static void*(*defaultMalloc)(int) = 0; /* The low-level malloc routine */
425 #endif /* SQLITE_DEBUG */
426
427 /*
428 ** This is the name of our program. It is set in main(), used
429 ** in a number of other places, mostly for error messages.
430 */
431 static char *Argv0;
@@ -463,10 +472,53 @@
472 /* Indicate out-of-memory and exit. */
473 static void shell_out_of_memory(void){
474 raw_printf(stderr,"Error: out of memory\n");
475 exit(1);
476 }
477
478 #ifdef SQLITE_DEBUG
479 /* This routine is called when a simulated OOM occurs. It is broken
480 ** out as a separate routine to make it easy to set a breakpoint on
481 ** the OOM
482 */
483 void shellOomFault(void){
484 if( oomRepeat>0 ){
485 oomRepeat--;
486 }else{
487 oomCounter--;
488 }
489 }
490 #endif /* SQLITE_DEBUG */
491
492 #ifdef SQLITE_DEBUG
493 /* This routine is a replacement malloc() that is used to simulate
494 ** Out-Of-Memory (OOM) errors for testing purposes.
495 */
496 static void *oomMalloc(int nByte){
497 if( oomCounter ){
498 if( oomCounter==1 ){
499 shellOomFault();
500 return 0;
501 }else{
502 oomCounter--;
503 }
504 }
505 return defaultMalloc(nByte);
506 }
507 #endif /* SQLITE_DEBUG */
508
509 #ifdef SQLITE_DEBUG
510 /* Register the OOM simulator. This must occur before any memory
511 ** allocations */
512 static void registerOomSimulator(void){
513 sqlite3_mem_methods mem;
514 sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem);
515 defaultMalloc = mem.xMalloc;
516 mem.xMalloc = oomMalloc;
517 sqlite3_config(SQLITE_CONFIG_MALLOC, &mem);
518 }
519 #endif
520
521 /*
522 ** Write I/O traces to the following stream.
523 */
524 #ifdef SQLITE_ENABLE_IOTRACE
@@ -12086,10 +12138,22 @@
12138 " Run \".filectrl\" with no arguments for details",
12139 ".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
12140 ".headers on|off Turn display of headers on or off",
12141 ".help ?-all? ?PATTERN? Show help text for PATTERN",
12142 ".import FILE TABLE Import data from FILE into TABLE",
12143 " Options:",
12144 " --ascii Use \\037 and \\036 as column and row separators",
12145 " --csv Use , and \\n as column and row separators",
12146 " --skip N Skip the first N rows of input",
12147 " -v \"Verbose\" - increase auxiliary output",
12148 " Notes:",
12149 " * If TABLE does not exist, it is created. The first row of input",
12150 " determines the column names.",
12151 " * If neither --csv or --ascii are used, the input mode is derived",
12152 " from the \".mode\" output mode",
12153 " * If FILE begins with \"|\" then it is a command that generates the",
12154 " input text.",
12155 #ifndef SQLITE_OMIT_TEST_CONTROL
12156 ".imposter INDEX TABLE Create imposter table TABLE on index INDEX",
12157 #endif
12158 ".indexes ?TABLE? Show names of indexes",
12159 " If TABLE is specified, only show indexes for",
@@ -12121,10 +12185,13 @@
12185 ".once (-e|-x|FILE) Output for the next SQL command only to FILE",
12186 " If FILE begins with '|' then open as a pipe",
12187 " Other options:",
12188 " -e Invoke system text editor",
12189 " -x Open in a spreadsheet",
12190 #ifdef SQLITE_DEBUG
12191 ".oom [--repeat M] [N] Simulate an OOM error on the N-th allocation",
12192 #endif
12193 ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
12194 " Options:",
12195 " --append Use appendvfs to append database to the end of FILE",
12196 #ifdef SQLITE_ENABLE_DESERIALIZE
12197 " --deserialize Load into memory useing sqlite3_deserialize()",
@@ -13071,10 +13138,12 @@
13138 FILE *in; /* Read the CSV text from this input stream */
13139 char *z; /* Accumulated text for a field */
13140 int n; /* Number of bytes in z */
13141 int nAlloc; /* Space allocated for z[] */
13142 int nLine; /* Current line number */
13143 int nRow; /* Number of rows imported */
13144 int nErr; /* Number of errors encountered */
13145 int bNotFirst; /* True if one or more bytes already read */
13146 int cTerm; /* Character that terminated the most recent field */
13147 int cColSep; /* The column separator character. (Usually ",") */
13148 int cRowSep; /* The row separator character. (Usually "\n") */
13149 };
@@ -16131,12 +16200,12 @@
16200 showHelp(p->out, 0);
16201 }
16202 }else
16203
16204 if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
16205 char *zTable = 0; /* Insert data into this table */
16206 char *zFile = 0; /* Name of file to extra content from */
16207 sqlite3_stmt *pStmt = NULL; /* A statement */
16208 int nCol; /* Number of columns in the table */
16209 int nByte; /* Number of bytes in an SQL string */
16210 int i, j; /* Loop counters */
16211 int needCommit; /* True to COMMIT or ROLLBACK at end */
@@ -16143,75 +16212,141 @@
16212 int nSep; /* Number of bytes in p->colSeparator[] */
16213 char *zSql; /* An SQL statement */
16214 ImportCtx sCtx; /* Reader context */
16215 char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
16216 int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close file */
16217 int eVerbose = 0; /* Larger for more console output */
16218 int nSkip = 0; /* Initial lines to skip */
16219 int useOutputMode = 1; /* Use output mode to determine separators */
16220
 
 
 
 
16221 memset(&sCtx, 0, sizeof(sCtx));
16222 if( p->mode==MODE_Ascii ){
16223 xRead = ascii_read_one_field;
16224 }else{
16225 xRead = csv_read_one_field;
16226 }
16227 for(i=1; i<nArg; i++){
16228 char *z = azArg[i];
16229 if( z[0]=='-' && z[1]=='-' ) z++;
16230 if( z[0]!='-' ){
16231 if( zFile==0 ){
16232 zFile = z;
16233 }else if( zTable==0 ){
16234 zTable = z;
16235 }else{
16236 utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z);
16237 showHelp(p->out, "import");
16238 rc = 1;
16239 goto meta_command_exit;
16240 }
16241 }else if( strcmp(z,"-v")==0 ){
16242 eVerbose++;
16243 }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
16244 nSkip = integerValue(azArg[++i]);
16245 }else if( strcmp(z,"-ascii")==0 ){
16246 sCtx.cColSep = SEP_Unit[0];
16247 sCtx.cRowSep = SEP_Record[0];
16248 xRead = ascii_read_one_field;
16249 useOutputMode = 0;
16250 }else if( strcmp(z,"-csv")==0 ){
16251 sCtx.cColSep = ',';
16252 sCtx.cRowSep = '\n';
16253 xRead = csv_read_one_field;
16254 useOutputMode = 0;
16255 }else{
16256 utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z);
16257 showHelp(p->out, "import");
16258 rc = 1;
16259 goto meta_command_exit;
16260 }
16261 }
16262 if( zTable==0 ){
16263 utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
16264 zFile==0 ? "FILE" : "TABLE");
16265 showHelp(p->out, "import");
16266 rc = 1;
16267 goto meta_command_exit;
16268 }
16269 seenInterrupt = 0;
16270 open_db(p, 0);
16271 if( useOutputMode ){
16272 /* If neither the --csv or --ascii options are specified, then set
16273 ** the column and row separator characters from the output mode. */
16274 nSep = strlen30(p->colSeparator);
16275 if( nSep==0 ){
16276 raw_printf(stderr,
16277 "Error: non-null column separator required for import\n");
16278 rc = 1;
16279 goto meta_command_exit;
16280 }
16281 if( nSep>1 ){
16282 raw_printf(stderr,
16283 "Error: multi-character column separators not allowed"
16284 " for import\n");
16285 rc = 1;
16286 goto meta_command_exit;
16287 }
16288 nSep = strlen30(p->rowSeparator);
16289 if( nSep==0 ){
16290 raw_printf(stderr,
16291 "Error: non-null row separator required for import\n");
16292 rc = 1;
16293 goto meta_command_exit;
16294 }
16295 if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
16296 /* When importing CSV (only), if the row separator is set to the
16297 ** default output row separator, change it to the default input
16298 ** row separator. This avoids having to maintain different input
16299 ** and output row separators. */
16300 sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
16301 nSep = strlen30(p->rowSeparator);
16302 }
16303 if( nSep>1 ){
16304 raw_printf(stderr, "Error: multi-character row separators not allowed"
16305 " for import\n");
16306 rc = 1;
16307 goto meta_command_exit;
16308 }
16309 sCtx.cColSep = p->colSeparator[0];
16310 sCtx.cRowSep = p->rowSeparator[0];
16311 }
16312 sCtx.zFile = zFile;
16313 sCtx.nLine = 1;
16314 if( sCtx.zFile[0]=='|' ){
16315 #ifdef SQLITE_OMIT_POPEN
16316 raw_printf(stderr, "Error: pipes are not supported in this OS\n");
16317 rc = 1;
16318 goto meta_command_exit;
16319 #else
16320 sCtx.in = popen(sCtx.zFile+1, "r");
16321 sCtx.zFile = "<pipe>";
16322 xCloser = pclose;
16323 #endif
16324 }else{
16325 sCtx.in = fopen(sCtx.zFile, "rb");
16326 xCloser = fclose;
16327 }
 
 
 
 
 
16328 if( sCtx.in==0 ){
16329 utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
16330 rc = 1;
16331 goto meta_command_exit;
16332 }
16333 if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
16334 char zSep[2];
16335 zSep[1] = 0;
16336 zSep[0] = sCtx.cColSep;
16337 utf8_printf(p->out, "Column separator ");
16338 output_c_string(p->out, zSep);
16339 utf8_printf(p->out, ", row separator ");
16340 zSep[0] = sCtx.cRowSep;
16341 output_c_string(p->out, zSep);
16342 utf8_printf(p->out, "\n");
16343 }
16344 while( (nSkip--)>0 ){
16345 while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
16346 sCtx.nLine++;
16347 }
16348 zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
16349 if( zSql==0 ){
16350 xCloser(sCtx.in);
16351 shell_out_of_memory();
16352 }
@@ -16229,30 +16364,36 @@
16364 if( cSep=='(' ){
16365 sqlite3_free(zCreate);
16366 sqlite3_free(sCtx.z);
16367 xCloser(sCtx.in);
16368 utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
16369 rc = 1;
16370 goto meta_command_exit;
16371 }
16372 zCreate = sqlite3_mprintf("%z\n)", zCreate);
16373 if( eVerbose>=1 ){
16374 utf8_printf(p->out, "%s\n", zCreate);
16375 }
16376 rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
16377 sqlite3_free(zCreate);
16378 if( rc ){
16379 utf8_printf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable,
16380 sqlite3_errmsg(p->db));
16381 sqlite3_free(sCtx.z);
16382 xCloser(sCtx.in);
16383 rc = 1;
16384 goto meta_command_exit;
16385 }
16386 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
16387 }
16388 sqlite3_free(zSql);
16389 if( rc ){
16390 if (pStmt) sqlite3_finalize(pStmt);
16391 utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
16392 xCloser(sCtx.in);
16393 rc = 1;
16394 goto meta_command_exit;
16395 }
16396 nCol = sqlite3_column_count(pStmt);
16397 sqlite3_finalize(pStmt);
16398 pStmt = 0;
16399 if( nCol==0 ) return 0; /* no columns, no error */
@@ -16267,17 +16408,21 @@
16408 zSql[j++] = ',';
16409 zSql[j++] = '?';
16410 }
16411 zSql[j++] = ')';
16412 zSql[j] = 0;
16413 if( eVerbose>=2 ){
16414 utf8_printf(p->out, "Insert using: %s\n", zSql);
16415 }
16416 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
16417 sqlite3_free(zSql);
16418 if( rc ){
16419 utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
16420 if (pStmt) sqlite3_finalize(pStmt);
16421 xCloser(sCtx.in);
16422 rc = 1;
16423 goto meta_command_exit;
16424 }
16425 needCommit = sqlite3_get_autocommit(p->db);
16426 if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
16427 do{
16428 int startLine = sCtx.nLine;
@@ -16316,18 +16461,26 @@
16461 sqlite3_step(pStmt);
16462 rc = sqlite3_reset(pStmt);
16463 if( rc!=SQLITE_OK ){
16464 utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
16465 startLine, sqlite3_errmsg(p->db));
16466 sCtx.nErr++;
16467 }else{
16468 sCtx.nRow++;
16469 }
16470 }
16471 }while( sCtx.cTerm!=EOF );
16472
16473 xCloser(sCtx.in);
16474 sqlite3_free(sCtx.z);
16475 sqlite3_finalize(pStmt);
16476 if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
16477 if( eVerbose>0 ){
16478 utf8_printf(p->out,
16479 "Added %d rows with %d errors using %d lines of input\n",
16480 sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
16481 }
16482 }else
16483
16484 #ifndef SQLITE_UNTESTABLE
16485 if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){
16486 char *zSql;
@@ -16602,10 +16755,38 @@
16755 raw_printf(stderr, "Usage: .nullvalue STRING\n");
16756 rc = 1;
16757 }
16758 }else
16759
16760 #ifdef SQLITE_DEBUG
16761 if( c=='o' && strcmp(azArg[0],"oom")==0 ){
16762 int i;
16763 for(i=1; i<nArg; i++){
16764 const char *z = azArg[i];
16765 if( z[0]=='-' && z[1]=='-' ) z++;
16766 if( strcmp(z,"-repeat")==0 ){
16767 if( i==nArg-1 ){
16768 raw_printf(p->out, "missing argument on \"%s\"\n", azArg[i]);
16769 rc = 1;
16770 }else{
16771 oomRepeat = (int)integerValue(azArg[++i]);
16772 }
16773 }else if( IsDigit(z[0]) ){
16774 oomCounter = (int)integerValue(azArg[i]);
16775 }else{
16776 raw_printf(p->out, "unknown argument: \"%s\"\n", azArg[i]);
16777 raw_printf(p->out, "Usage: .oom [--repeat N] [M]\n");
16778 rc = 1;
16779 }
16780 }
16781 if( rc==0 ){
16782 raw_printf(p->out, "oomCounter = %d\n", oomCounter);
16783 raw_printf(p->out, "oomRepeat = %d\n", oomRepeat);
16784 }
16785 }else
16786 #endif /* SQLITE_DEBUG */
16787
16788 if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){
16789 char *zNewFilename; /* Name of the database file to open */
16790 int iName = 1; /* Index in azArg[] of the filename */
16791 int newFlag = 0; /* True to delete file before opening */
16792 /* Close the existing database */
@@ -18684,10 +18865,14 @@
18865
18866 setBinaryMode(stdin, 0);
18867 setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
18868 stdin_is_interactive = isatty(0);
18869 stdout_is_console = isatty(1);
18870
18871 #ifdef SQLITE_DEBUG
18872 registerOomSimulator();
18873 #endif
18874
18875 #if !defined(_WIN32_WCE)
18876 if( getenv("SQLITE_DEBUG_BREAK") ){
18877 if( isatty(0) && isatty(2) ){
18878 fprintf(stderr,
18879
18880 DDED src/sounds/0.wav
18881 DDED src/sounds/1.wav
18882 DDED src/sounds/2.wav
18883 DDED src/sounds/3.wav
18884 DDED src/sounds/4.wav
18885 DDED src/sounds/5.wav
18886 DDED src/sounds/6.wav
18887 DDED src/sounds/7.wav
18888 DDED src/sounds/8.wav
18889 DDED src/sounds/9.wav
18890 DDED src/sounds/README.md
18891 DDED src/sounds/a.wav
18892 DDED src/sounds/b.wav
18893 DDED src/sounds/c.wav
18894 DDED src/sounds/d.wav
18895 DDED src/sounds/e.wav
18896 DDED src/sounds/f.wav

Binary file

Binary file

Binary file

Binary file

Binary file

Binary file

Binary file

Binary file

Binary file

Binary file

--- a/src/sounds/README.md
+++ b/src/sounds/README.md
@@ -0,0 +1,15 @@
1
+The *.wav files in this dire speaking each
2
+of the 16 hexadecimal d consists of just
3
+hexadecimal digits ( generated by the
4
+[../captcha.c module](.. files can be
5
+hen these WAV
6
+files can be con captcha, which
7
+ding of the
8
+captcha, ired users to complete the
9
+captcha.
10
+
11
+Each of the WAV files uses 8000 samples per second, 8 bits per sample
12
+and are 6000 samples in length.
13
+
14
+The recordings are made by Philip Bennefall and are of his own voice.
15
+Mr. Bennefall is ytemself blind and uses this system implemented w
--- a/src/sounds/README.md
+++ b/src/sounds/README.md
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/sounds/README.md
+++ b/src/sounds/README.md
@@ -0,0 +1,15 @@
1 The *.wav files in this dire speaking each
2 of the 16 hexadecimal d consists of just
3 hexadecimal digits ( generated by the
4 [../captcha.c module](.. files can be
5 hen these WAV
6 files can be con captcha, which
7 ding of the
8 captcha, ired users to complete the
9 captcha.
10
11 Each of the WAV files uses 8000 samples per second, 8 bits per sample
12 and are 6000 samples in length.
13
14 The recordings are made by Philip Bennefall and are of his own voice.
15 Mr. Bennefall is ytemself blind and uses this system implemented w

Binary file

Binary file

Binary file

Binary file

Binary file

Binary file

+15
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -124,10 +124,23 @@
124124
}else{
125125
sqlite3_free(pOut);
126126
sqlite3_result_error(context, "input is not zlib compressed", -1);
127127
}
128128
}
129
+
130
+/*
131
+** Implementation of the "gather_artifact_stats(X)" SQL function.
132
+** That function merely calls the gather_artifact_stats() function
133
+** in stat.c to populate the ARTSTAT temporary table.
134
+*/
135
+static void sqlcmd_gather_artifact_stats(
136
+ sqlite3_context *context,
137
+ int argc,
138
+ sqlite3_value **argv
139
+){
140
+ gather_artifact_stats(1);
141
+}
129142
130143
/*
131144
** Add the content(), compress(), and decompress() SQL functions to
132145
** database connection db.
133146
*/
@@ -136,10 +149,12 @@
136149
sqlcmd_content, 0, 0);
137150
sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
138151
sqlcmd_compress, 0, 0);
139152
sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0,
140153
sqlcmd_decompress, 0, 0);
154
+ sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155
+ sqlcmd_gather_artifact_stats, 0, 0);
141156
return SQLITE_OK;
142157
}
143158
144159
/*
145160
** This is the "automatic extension" initializer that runs right after
146161
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -124,10 +124,23 @@
124 }else{
125 sqlite3_free(pOut);
126 sqlite3_result_error(context, "input is not zlib compressed", -1);
127 }
128 }
 
 
 
 
 
 
 
 
 
 
 
 
 
129
130 /*
131 ** Add the content(), compress(), and decompress() SQL functions to
132 ** database connection db.
133 */
@@ -136,10 +149,12 @@
136 sqlcmd_content, 0, 0);
137 sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
138 sqlcmd_compress, 0, 0);
139 sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0,
140 sqlcmd_decompress, 0, 0);
 
 
141 return SQLITE_OK;
142 }
143
144 /*
145 ** This is the "automatic extension" initializer that runs right after
146
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -124,10 +124,23 @@
124 }else{
125 sqlite3_free(pOut);
126 sqlite3_result_error(context, "input is not zlib compressed", -1);
127 }
128 }
129
130 /*
131 ** Implementation of the "gather_artifact_stats(X)" SQL function.
132 ** That function merely calls the gather_artifact_stats() function
133 ** in stat.c to populate the ARTSTAT temporary table.
134 */
135 static void sqlcmd_gather_artifact_stats(
136 sqlite3_context *context,
137 int argc,
138 sqlite3_value **argv
139 ){
140 gather_artifact_stats(1);
141 }
142
143 /*
144 ** Add the content(), compress(), and decompress() SQL functions to
145 ** database connection db.
146 */
@@ -136,10 +149,12 @@
149 sqlcmd_content, 0, 0);
150 sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
151 sqlcmd_compress, 0, 0);
152 sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0,
153 sqlcmd_decompress, 0, 0);
154 sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155 sqlcmd_gather_artifact_stats, 0, 0);
156 return SQLITE_OK;
157 }
158
159 /*
160 ** This is the "automatic extension" initializer that runs right after
161
+460 -270
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -1162,11 +1162,11 @@
11621162
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
11631163
** [sqlite_version()] and [sqlite_source_id()].
11641164
*/
11651165
#define SQLITE_VERSION "3.32.0"
11661166
#define SQLITE_VERSION_NUMBER 3032000
1167
-#define SQLITE_SOURCE_ID "2020-02-27 16:21:39 951b39ca74c9bd933139e099d5555283278db475f410f202c162e5d1e6aef933"
1167
+#define SQLITE_SOURCE_ID "2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84f525"
11681168
11691169
/*
11701170
** CAPI3REF: Run-Time Library Version Numbers
11711171
** KEYWORDS: sqlite3_version sqlite3_sourceid
11721172
**
@@ -16539,11 +16539,10 @@
1653916539
** have been filled out. If the schema changes, these column names might
1654016540
** changes and so the view will need to be reset.
1654116541
*/
1654216542
#define DB_SchemaLoaded 0x0001 /* The schema has been loaded */
1654316543
#define DB_UnresetViews 0x0002 /* Some views have defined column names */
16544
-#define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */
1654516544
#define DB_ResetWanted 0x0008 /* Reset the schema when nSchemaLock==0 */
1654616545
1654716546
/*
1654816547
** The number of different kinds of things that can be limited
1654916548
** using the sqlite3_limit() interface.
@@ -16697,11 +16696,11 @@
1669716696
** Each database connection is an instance of the following structure.
1669816697
*/
1669916698
struct sqlite3 {
1670016699
sqlite3_vfs *pVfs; /* OS Interface */
1670116700
struct Vdbe *pVdbe; /* List of active virtual machines */
16702
- CollSeq *pDfltColl; /* The default collating sequence (BINARY) */
16701
+ CollSeq *pDfltColl; /* BINARY collseq for the database encoding */
1670316702
sqlite3_mutex *mutex; /* Connection mutex */
1670416703
Db *aDb; /* All backends */
1670516704
int nDb; /* Number of backends currently in use */
1670616705
u32 mDbFlags; /* flags recording internal state */
1670716706
u64 flags; /* flags settable by pragmas. See below */
@@ -16906,10 +16905,11 @@
1690616905
#define DBFLAG_PreferBuiltin 0x0002 /* Preference to built-in funcs */
1690716906
#define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */
1690816907
#define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */
1690916908
#define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */
1691016909
#define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */
16910
+#define DBFLAG_EncodingFixed 0x0040 /* No longer possible to change enc. */
1691116911
1691216912
/*
1691316913
** Bits of the sqlite3.dbOptFlags field that are used by the
1691416914
** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
1691516915
** selectively disable various optimizations.
@@ -17869,10 +17869,13 @@
1786917869
char affExpr; /* affinity, or RAISE type */
1787017870
u8 op2; /* TK_REGISTER/TK_TRUTH: original value of Expr.op
1787117871
** TK_COLUMN: the value of p5 for OP_Column
1787217872
** TK_AGG_FUNCTION: nesting depth
1787317873
** TK_FUNCTION: NC_SelfRef flag if needs OP_PureFunc */
17874
+#ifdef SQLITE_DEBUG
17875
+ u8 vvaFlags; /* Verification flags. */
17876
+#endif
1787417877
u32 flags; /* Various flags. EP_* See below */
1787517878
union {
1787617879
char *zToken; /* Token value. Zero terminated and dequoted */
1787717880
int iValue; /* Non-negative integer value if EP_IntValue */
1787817881
} u;
@@ -17943,11 +17946,11 @@
1794317946
#define EP_Skip 0x001000 /* Operator does not contribute to affinity */
1794417947
#define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
1794517948
#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
1794617949
#define EP_Win 0x008000 /* Contains window functions */
1794717950
#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */
17948
-#define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */
17951
+ /* 0x020000 // available for reuse */
1794917952
#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
1795017953
#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
1795117954
#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
1795217955
#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
1795317956
#define EP_Alias 0x400000 /* Is an alias for a result set column */
@@ -17957,10 +17960,11 @@
1795717960
#define EP_Quoted 0x4000000 /* TK_ID was originally quoted */
1795817961
#define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */
1795917962
#define EP_IsTrue 0x10000000 /* Always has boolean value of TRUE */
1796017963
#define EP_IsFalse 0x20000000 /* Always has boolean value of FALSE */
1796117964
#define EP_FromDDL 0x40000000 /* Originates from sqlite_master */
17965
+ /* 0x80000000 // Available */
1796217966
1796317967
/*
1796417968
** The EP_Propagate mask is a set of properties that automatically propagate
1796517969
** upwards into parent nodes.
1796617970
*/
@@ -17975,18 +17979,28 @@
1797517979
#define ExprSetProperty(E,P) (E)->flags|=(P)
1797617980
#define ExprClearProperty(E,P) (E)->flags&=~(P)
1797717981
#define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue)
1797817982
#define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse)
1797917983
17984
+
17985
+/* Flags for use with Expr.vvaFlags
17986
+*/
17987
+#define EP_NoReduce 0x01 /* Cannot EXPRDUP_REDUCE this Expr */
17988
+#define EP_Immutable 0x02 /* Do not change this Expr node */
17989
+
1798017990
/* The ExprSetVVAProperty() macro is used for Verification, Validation,
1798117991
** and Accreditation only. It works like ExprSetProperty() during VVA
1798217992
** processes but is a no-op for delivery.
1798317993
*/
1798417994
#ifdef SQLITE_DEBUG
17985
-# define ExprSetVVAProperty(E,P) (E)->flags|=(P)
17995
+# define ExprSetVVAProperty(E,P) (E)->vvaFlags|=(P)
17996
+# define ExprHasVVAProperty(E,P) (((E)->vvaFlags&(P))!=0)
17997
+# define ExprClearVVAProperties(E) (E)->vvaFlags = 0
1798617998
#else
1798717999
# define ExprSetVVAProperty(E,P)
18000
+# define ExprHasVVAProperty(E,P) 0
18001
+# define ExprClearVVAProperties(E)
1798818002
#endif
1798918003
1799018004
/*
1799118005
** Macros to determine the number of bytes required by a normal Expr
1799218006
** struct, an Expr struct with the EP_Reduced flag set in Expr.flags
@@ -18956,10 +18970,11 @@
1895618970
Select *pSelect; /* HAVING to WHERE clause ctx */
1895718971
struct WindowRewrite *pRewrite; /* Window rewrite context */
1895818972
struct WhereConst *pConst; /* WHERE clause constants */
1895918973
struct RenameCtx *pRename; /* RENAME COLUMN context */
1896018974
struct Table *pTab; /* Table of generated column */
18975
+ struct SrcList_item *pSrcItem; /* A single FROM clause item */
1896118976
} u;
1896218977
};
1896318978
1896418979
/* Forward declarations */
1896518980
SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*);
@@ -19500,11 +19515,11 @@
1950019515
#ifndef SQLITE_OMIT_GENERATED_COLUMNS
1950119516
SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Column*, int);
1950219517
#endif
1950319518
SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int);
1950419519
SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
19505
-SQLITE_PRIVATE int sqlite3ExprCodeAtInit(Parse*, Expr*, int);
19520
+SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int);
1950619521
SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
1950719522
SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
1950819523
SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
1950919524
#define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */
1951019525
#define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */
@@ -19655,10 +19670,11 @@
1965519670
# define sqlite3AuthRead(a,b,c,d)
1965619671
# define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK
1965719672
# define sqlite3AuthContextPush(a,b,c)
1965819673
# define sqlite3AuthContextPop(a) ((void)(a))
1965919674
#endif
19675
+SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName);
1966019676
SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*);
1966119677
SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*);
1966219678
SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
1966319679
SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*);
1966419680
SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*);
@@ -19714,14 +19730,14 @@
1971419730
#define putVarint sqlite3PutVarint
1971519731
1971619732
1971719733
SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
1971819734
SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
19719
-SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2);
19720
-SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
19735
+SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2);
19736
+SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity);
1972119737
SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int);
19722
-SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr);
19738
+SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr);
1972319739
SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
1972419740
SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
1972519741
SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
1972619742
SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
1972719743
SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int);
@@ -19740,13 +19756,14 @@
1974019756
SQLITE_PRIVATE const char *sqlite3ErrStr(int);
1974119757
SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse);
1974219758
SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
1974319759
SQLITE_PRIVATE int sqlite3IsBinary(const CollSeq*);
1974419760
SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
19745
-SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
19746
-SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr);
19747
-SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,Expr*,Expr*);
19761
+SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8);
19762
+SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr);
19763
+SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr);
19764
+SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*);
1974819765
SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
1974919766
SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
1975019767
SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
1975119768
SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*);
1975219769
SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
@@ -19809,10 +19826,11 @@
1980919826
const struct ExprList_item*,
1981019827
const char*,
1981119828
const char*,
1981219829
const char*
1981319830
);
19831
+SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr*);
1981419832
SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*);
1981519833
SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*);
1981619834
SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*);
1981719835
SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
1981819836
SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
@@ -19973,12 +19991,12 @@
1997319991
#ifdef SQLITE_ENABLE_NORMALIZE
1997419992
SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*);
1997519993
#endif
1997619994
SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*);
1997719995
SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*);
19978
-SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse*,Expr*);
19979
-SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *);
19996
+SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse*,const Expr*);
19997
+SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, const Expr*, const Expr*);
1998019998
SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*);
1998119999
SQLITE_PRIVATE const char *sqlite3JournalModename(int);
1998220000
#ifndef SQLITE_OMIT_WAL
1998320001
SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*);
1998420002
SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int);
@@ -20947,13 +20965,13 @@
2094720965
#endif
2094820966
u16 nResColumn; /* Number of columns in one row of the result set */
2094920967
u8 errorAction; /* Recovery action to do in case of an error */
2095020968
u8 minWriteFileFormat; /* Minimum file format for writable database files */
2095120969
u8 prepFlags; /* SQLITE_PREPARE_* flags */
20970
+ u8 doingRerun; /* True if rerunning after an auto-reprepare */
2095220971
bft expired:2; /* 1: recompile VM immediately 2: when convenient */
2095320972
bft explain:2; /* True if EXPLAIN present on SQL command */
20954
- bft doingRerun:1; /* True if rerunning after an auto-reprepare */
2095520973
bft changeCntOn:1; /* True to update the change-counter */
2095620974
bft runOnlyOnce:1; /* Automatically expire on reset */
2095720975
bft usesStmtJournal:1; /* True if uses a statement journal */
2095820976
bft readOnly:1; /* True for statements that do not write */
2095920977
bft bIsReader:1; /* True for statements that read */
@@ -29390,12 +29408,12 @@
2939029408
sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName);
2939129409
}else if( pItem->zName ){
2939229410
sqlite3_str_appendf(&x, " %s", pItem->zName);
2939329411
}
2939429412
if( pItem->pTab ){
29395
- sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p",
29396
- pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab);
29413
+ sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx",
29414
+ pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed);
2939729415
}
2939829416
if( pItem->zAlias ){
2939929417
sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias);
2940029418
}
2940129419
if( pItem->fg.jointype & JT_LEFT ){
@@ -29650,27 +29668,30 @@
2965029668
** Generate a human-readable explanation of an expression tree.
2965129669
*/
2965229670
SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
2965329671
const char *zBinOp = 0; /* Binary operator */
2965429672
const char *zUniOp = 0; /* Unary operator */
29655
- char zFlgs[60];
29673
+ char zFlgs[200];
2965629674
pView = sqlite3TreeViewPush(pView, moreToFollow);
2965729675
if( pExpr==0 ){
2965829676
sqlite3TreeViewLine(pView, "nil");
2965929677
sqlite3TreeViewPop(pView);
2966029678
return;
2966129679
}
29662
- if( pExpr->flags || pExpr->affExpr ){
29680
+ if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){
2966329681
StrAccum x;
2966429682
sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0);
2966529683
sqlite3_str_appendf(&x, " fg.af=%x.%c",
2966629684
pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n');
2966729685
if( ExprHasProperty(pExpr, EP_FromJoin) ){
2966829686
sqlite3_str_appendf(&x, " iRJT=%d", pExpr->iRightJoinTable);
2966929687
}
2967029688
if( ExprHasProperty(pExpr, EP_FromDDL) ){
2967129689
sqlite3_str_appendf(&x, " DDL");
29690
+ }
29691
+ if( ExprHasVVAProperty(pExpr, EP_Immutable) ){
29692
+ sqlite3_str_appendf(&x, " IMMUTABLE");
2967229693
}
2967329694
sqlite3StrAccumFinish(&x);
2967429695
}else{
2967529696
zFlgs[0] = 0;
2967629697
}
@@ -29774,10 +29795,11 @@
2977429795
case TK_SLASH: zBinOp = "DIV"; break;
2977529796
case TK_LSHIFT: zBinOp = "LSHIFT"; break;
2977629797
case TK_RSHIFT: zBinOp = "RSHIFT"; break;
2977729798
case TK_CONCAT: zBinOp = "CONCAT"; break;
2977829799
case TK_DOT: zBinOp = "DOT"; break;
29800
+ case TK_LIMIT: zBinOp = "LIMIT"; break;
2977929801
2978029802
case TK_UMINUS: zUniOp = "UMINUS"; break;
2978129803
case TK_UPLUS: zUniOp = "UPLUS"; break;
2978229804
case TK_BITNOT: zUniOp = "BITNOT"; break;
2978329805
case TK_NOT: zUniOp = "NOT"; break;
@@ -50895,11 +50917,11 @@
5089550917
}
5089650918
5089750919
/*
5089850920
** Allocate a new RowSetEntry object that is associated with the
5089950921
** given RowSet. Return a pointer to the new and completely uninitialized
50900
-** objected.
50922
+** object.
5090150923
**
5090250924
** In an OOM situation, the RowSet.db->mallocFailed flag is set and this
5090350925
** routine returns NULL.
5090450926
*/
5090550927
static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){
@@ -51171,11 +51193,11 @@
5117151193
if( iBatch!=pRowSet->iBatch ){ /*OPTIMIZATION-IF-FALSE*/
5117251194
p = pRowSet->pEntry;
5117351195
if( p ){
5117451196
struct RowSetEntry **ppPrevTree = &pRowSet->pForest;
5117551197
if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/
51176
- /* Only sort the current set of entiries if they need it */
51198
+ /* Only sort the current set of entries if they need it */
5117751199
p = rowSetEntrySort(p);
5117851200
}
5117951201
for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){
5118051202
ppPrevTree = &pTree->pRight;
5118151203
if( pTree->pLeft==0 ){
@@ -64536,11 +64558,11 @@
6453664558
** free-list for reuse. It returns false if it is safe to retrieve the
6453764559
** page from the pager layer with the 'no-content' flag set. True otherwise.
6453864560
*/
6453964561
static int btreeGetHasContent(BtShared *pBt, Pgno pgno){
6454064562
Bitvec *p = pBt->pHasContent;
64541
- return (p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTest(p, pgno)));
64563
+ return p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTestNotNull(p, pgno));
6454264564
}
6454364565
6454464566
/*
6454564567
** Clear (destroy) the BtShared.pHasContent bitvec. This should be
6454664568
** invoked at the conclusion of each write-transaction.
@@ -76180,23 +76202,18 @@
7618076202
u16 mFlags;
7618176203
if( pVdbe->db->flags & SQLITE_VdbeTrace ){
7618276204
sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n",
7618376205
(int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem));
7618476206
}
76185
- /* If pX is marked as a shallow copy of pMem, then verify that
76207
+ /* If pX is marked as a shallow copy of pMem, then try to verify that
7618676208
** no significant changes have been made to pX since the OP_SCopy.
7618776209
** A significant change would indicated a missed call to this
7618876210
** function for pX. Minor changes, such as adding or removing a
7618976211
** dual type, are allowed, as long as the underlying value is the
7619076212
** same. */
7619176213
mFlags = pMem->flags & pX->flags & pX->mScopyFlags;
7619276214
assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i );
76193
- /* assert( (mFlags&MEM_Real)==0 || pMem->u.r==pX->u.r ); */
76194
- /* ^^ */
76195
- /* Cannot reliably compare doubles for equality */
76196
- assert( (mFlags&MEM_Str)==0 || (pMem->n==pX->n && pMem->z==pX->z) );
76197
- assert( (mFlags&MEM_Blob)==0 || sqlite3BlobCompare(pMem,pX)==0 );
7619876215
7619976216
/* pMem is the register that is changing. But also mark pX as
7620076217
** undefined so that we can quickly detect the shallow-copy error */
7620176218
pX->flags = MEM_Undefined;
7620276219
pX->pScopyFrom = 0;
@@ -77547,11 +77564,11 @@
7754777564
(void)z2;
7754877565
}
7754977566
#endif
7755077567
7755177568
/*
77552
-** Add a new OP_ opcode.
77569
+** Add a new OP_Explain opcode.
7755377570
**
7755477571
** If the bPush flag is true, then make this opcode the parent for
7755577572
** subsequent Explains until sqlite3VdbeExplainPop() is called.
7755677573
*/
7755777574
SQLITE_PRIVATE void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){
@@ -78781,12 +78798,15 @@
7878178798
displayP4Expr(&x, pOp->p4.pExpr);
7878278799
break;
7878378800
}
7878478801
#endif
7878578802
case P4_COLLSEQ: {
78803
+ static const char *const encnames[] = {"?", "8", "16LE", "16BE"};
7878678804
CollSeq *pColl = pOp->p4.pColl;
78787
- sqlite3_str_appendf(&x, "(%.20s)", pColl->zName);
78805
+ assert( pColl->enc>=0 && pColl->enc<4 );
78806
+ sqlite3_str_appendf(&x, "%.18s-%s", pColl->zName,
78807
+ encnames[pColl->enc]);
7878878808
break;
7878978809
}
7879078810
case P4_FUNCDEF: {
7879178811
FuncDef *pDef = pOp->p4.pFunc;
7879278812
sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg);
@@ -79495,10 +79515,11 @@
7949579515
"addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
7949679516
"id", "parent", "notused", "detail"
7949779517
};
7949879518
int iFirst, mx, i;
7949979519
if( nMem<10 ) nMem = 10;
79520
+ p->explain = pParse->explain;
7950079521
if( pParse->explain==2 ){
7950179522
sqlite3VdbeSetNumCols(p, 4);
7950279523
iFirst = 8;
7950379524
mx = 12;
7950479525
}else{
@@ -79545,11 +79566,10 @@
7954579566
}
7954679567
}
7954779568
7954879569
p->pVList = pParse->pVList;
7954979570
pParse->pVList = 0;
79550
- p->explain = pParse->explain;
7955179571
if( db->mallocFailed ){
7955279572
p->nVar = 0;
7955379573
p->nCursor = 0;
7955479574
p->nMem = 0;
7955579575
}else{
@@ -86230,11 +86250,10 @@
8623086250
u16 flags2; /* Initial flags for P2 */
8623186251
8623286252
pIn1 = &aMem[pOp->p1];
8623386253
pIn2 = &aMem[pOp->p2];
8623486254
pOut = &aMem[pOp->p3];
86235
- testcase( pIn1==pIn2 );
8623686255
testcase( pOut==pIn2 );
8623786256
assert( pIn1!=pOut );
8623886257
flags1 = pIn1->flags;
8623986258
testcase( flags1 & MEM_Null );
8624086259
testcase( pIn2->flags & MEM_Null );
@@ -88351,11 +88370,11 @@
8835188370
**
8835288371
** Allowed P5 bits:
8835388372
** <ul>
8835488373
** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
8835588374
** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88356
-** of OP_SeekLE/OP_IdxGT)
88375
+** of OP_SeekLE/OP_IdxLT)
8835788376
** </ul>
8835888377
**
8835988378
** The P4 value may be either an integer (P4_INT32) or a pointer to
8836088379
** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo
8836188380
** object, then table being opened must be an [index b-tree] where the
@@ -88381,11 +88400,11 @@
8838188400
**
8838288401
** Allowed P5 bits:
8838388402
** <ul>
8838488403
** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
8838588404
** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88386
-** of OP_SeekLE/OP_IdxGT)
88405
+** of OP_SeekLE/OP_IdxLT)
8838788406
** </ul>
8838888407
**
8838988408
** See also: OP_OpenRead, OP_OpenWrite
8839088409
*/
8839188410
/* Opcode: OpenWrite P1 P2 P3 P4 P5
@@ -88405,11 +88424,11 @@
8840588424
**
8840688425
** Allowed P5 bits:
8840788426
** <ul>
8840888427
** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
8840988428
** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88410
-** of OP_SeekLE/OP_IdxGT)
88429
+** of OP_SeekLE/OP_IdxLT)
8841188430
** <li> <b>0x08 OPFLAG_FORDELETE</b>: This cursor is used only to seek
8841288431
** and subsequently delete entries in an index btree. This is a
8841388432
** hint to the storage engine that the storage engine is allowed to
8841488433
** ignore. The hint is not used by the official SQLite b*tree storage
8841588434
** engine, but is used by COMDB2.
@@ -88517,13 +88536,11 @@
8851788536
8851888537
open_cursor_set_hints:
8851988538
assert( OPFLAG_BULKCSR==BTREE_BULKLOAD );
8852088539
assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ );
8852188540
testcase( pOp->p5 & OPFLAG_BULKCSR );
88522
-#ifdef SQLITE_ENABLE_CURSOR_HINTS
8852388541
testcase( pOp->p2 & OPFLAG_SEEKEQ );
88524
-#endif
8852588542
sqlite3BtreeCursorHintFlags(pCur->uc.pCursor,
8852688543
(pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ)));
8852788544
if( rc ) goto abort_due_to_error;
8852888545
break;
8852988546
}
@@ -88775,15 +88792,17 @@
8877588792
** Reposition cursor P1 so that it points to the smallest entry that
8877688793
** is greater than or equal to the key value. If there are no records
8877788794
** greater than or equal to the key and P2 is not zero, then jump to P2.
8877888795
**
8877988796
** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
88780
-** opcode will always land on a record that equally equals the key, or
88781
-** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this
88782
-** opcode must be followed by an IdxLE opcode with the same arguments.
88783
-** The IdxLE opcode will be skipped if this opcode succeeds, but the
88784
-** IdxLE opcode will be used on subsequent loop iterations.
88797
+** opcode will either land on a record that exactly matches the key, or
88798
+** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ,
88799
+** this opcode must be followed by an IdxLE opcode with the same arguments.
88800
+** The IdxGT opcode will be skipped if this opcode succeeds, but the
88801
+** IdxGT opcode will be used on subsequent loop iterations. The
88802
+** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this
88803
+** is an equality search.
8878588804
**
8878688805
** This opcode leaves the cursor configured to move in forward order,
8878788806
** from the beginning toward the end. In other words, the cursor is
8878888807
** configured to use Next, not Prev.
8878988808
**
@@ -88795,11 +88814,11 @@
8879588814
** If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
8879688815
** use the value in register P3 as a key. If cursor P1 refers
8879788816
** to an SQL index, then P3 is the first in an array of P4 registers
8879888817
** that are used as an unpacked index key.
8879988818
**
88800
-** Reposition cursor P1 so that it points to the smallest entry that
88819
+** Reposition cursor P1 so that it points to the smallest entry that
8880188820
** is greater than the key value. If there are no records greater than
8880288821
** the key and P2 is not zero, then jump to P2.
8880388822
**
8880488823
** This opcode leaves the cursor configured to move in forward order,
8880588824
** from the beginning toward the end. In other words, the cursor is
@@ -88840,15 +88859,17 @@
8884088859
** This opcode leaves the cursor configured to move in reverse order,
8884188860
** from the end toward the beginning. In other words, the cursor is
8884288861
** configured to use Prev, not Next.
8884388862
**
8884488863
** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
88845
-** opcode will always land on a record that equally equals the key, or
88846
-** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this
88847
-** opcode must be followed by an IdxGE opcode with the same arguments.
88864
+** opcode will either land on a record that exactly matches the key, or
88865
+** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ,
88866
+** this opcode must be followed by an IdxLE opcode with the same arguments.
8884888867
** The IdxGE opcode will be skipped if this opcode succeeds, but the
88849
-** IdxGE opcode will be used on subsequent loop iterations.
88868
+** IdxGE opcode will be used on subsequent loop iterations. The
88869
+** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this
88870
+** is an equality search.
8885088871
**
8885188872
** See also: Found, NotFound, SeekGt, SeekGe, SeekLt
8885288873
*/
8885388874
case OP_SeekLT: /* jump, in3, group */
8885488875
case OP_SeekLE: /* jump, in3, group */
@@ -88881,11 +88902,11 @@
8888188902
8888288903
pC->deferredMoveto = 0;
8888388904
pC->cacheStatus = CACHE_STALE;
8888488905
if( pC->isTable ){
8888588906
u16 flags3, newType;
88886
- /* The BTREE_SEEK_EQ flag is only set on index cursors */
88907
+ /* The OPFLAG_SEEKEQ/BTREE_SEEK_EQ flag is only set on index cursors */
8888788908
assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0
8888888909
|| CORRUPT_DB );
8888988910
8889088911
/* The input value in P3 might be of any type: integer, real, string,
8889188912
** blob, or NULL. But it needs to be an integer before we can do
@@ -88940,18 +88961,21 @@
8894088961
pC->movetoTarget = iKey; /* Used by OP_Delete */
8894188962
if( rc!=SQLITE_OK ){
8894288963
goto abort_due_to_error;
8894388964
}
8894488965
}else{
88945
- /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and
88946
- ** OP_SeekLE opcodes are allowed, and these must be immediately followed
88947
- ** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key.
88966
+ /* For a cursor with the OPFLAG_SEEKEQ/BTREE_SEEK_EQ hint, only the
88967
+ ** OP_SeekGE and OP_SeekLE opcodes are allowed, and these must be
88968
+ ** immediately followed by an OP_IdxGT or OP_IdxLT opcode, respectively,
88969
+ ** with the same key.
8894888970
*/
8894988971
if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){
8895088972
eqOnly = 1;
8895188973
assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE );
8895288974
assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT );
88975
+ assert( pOp->opcode==OP_SeekGE || pOp[1].opcode==OP_IdxLT );
88976
+ assert( pOp->opcode==OP_SeekLE || pOp[1].opcode==OP_IdxGT );
8895388977
assert( pOp[1].p1==pOp[0].p1 );
8895488978
assert( pOp[1].p2==pOp[0].p2 );
8895588979
assert( pOp[1].p3==pOp[0].p3 );
8895688980
assert( pOp[1].p4.i==pOp[0].p4.i );
8895788981
}
@@ -91162,11 +91186,11 @@
9116291186
** try to reuse register values from the first use. */
9116391187
{
9116491188
int i;
9116591189
for(i=0; i<p->nMem; i++){
9116691190
aMem[i].pScopyFrom = 0; /* Prevent false-positive AboutToChange() errs */
91167
- aMem[i].flags |= MEM_Undefined; /* Cause a fault if this reg is reused */
91191
+ MemSetTypeFlag(&aMem[i], MEM_Undefined); /* Fault if this reg is reused */
9116891192
}
9116991193
}
9117091194
#endif
9117191195
pOp = &aOp[-1];
9117291196
goto check_for_interrupt;
@@ -96555,19 +96579,20 @@
9655596579
SrcList *pSrc;
9655696580
int i;
9655796581
struct SrcList_item *pItem;
9655896582
9655996583
pSrc = p->pSrc;
96560
- assert( pSrc!=0 );
96561
- for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
96562
- if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){
96563
- return WRC_Abort;
96564
- }
96565
- if( pItem->fg.isTabFunc
96566
- && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg)
96567
- ){
96568
- return WRC_Abort;
96584
+ if( pSrc ){
96585
+ for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
96586
+ if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){
96587
+ return WRC_Abort;
96588
+ }
96589
+ if( pItem->fg.isTabFunc
96590
+ && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg)
96591
+ ){
96592
+ return WRC_Abort;
96593
+ }
9656996594
}
9657096595
}
9657196596
return WRC_Continue;
9657296597
}
9657396598
@@ -96784,10 +96809,35 @@
9678496809
}else{
9678596810
/* Currently parsing a DML statement */
9678696811
return (db->flags & SQLITE_DqsDML)!=0;
9678796812
}
9678896813
}
96814
+
96815
+/*
96816
+** The argument is guaranteed to be a non-NULL Expr node of type TK_COLUMN.
96817
+** return the appropriate colUsed mask.
96818
+*/
96819
+SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr *pExpr){
96820
+ int n;
96821
+ Table *pExTab;
96822
+
96823
+ n = pExpr->iColumn;
96824
+ pExTab = pExpr->y.pTab;
96825
+ assert( pExTab!=0 );
96826
+ if( (pExTab->tabFlags & TF_HasGenerated)!=0
96827
+ && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0
96828
+ ){
96829
+ testcase( pExTab->nCol==BMS-1 );
96830
+ testcase( pExTab->nCol==BMS );
96831
+ return pExTab->nCol>=BMS ? ALLBITS : MASKBIT(pExTab->nCol)-1;
96832
+ }else{
96833
+ testcase( n==BMS-1 );
96834
+ testcase( n==BMS );
96835
+ if( n>=BMS ) n = BMS-1;
96836
+ return ((Bitmask)1)<<n;
96837
+ }
96838
+}
9678996839
9679096840
/*
9679196841
** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
9679296842
** that name in the set of source tables in pSrcList and make the pExpr
9679396843
** expression node refer back to that source column. The following changes
@@ -96861,10 +96911,16 @@
9686196911
assert( db->aDb[i].zDbSName );
9686296912
if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){
9686396913
pSchema = db->aDb[i].pSchema;
9686496914
break;
9686596915
}
96916
+ }
96917
+ if( i==db->nDb && sqlite3StrICmp("main", zDb)==0 ){
96918
+ /* This branch is taken when the main database has been renamed
96919
+ ** using SQLITE_DBCONFIG_MAINDBNAME. */
96920
+ pSchema = db->aDb[0].pSchema;
96921
+ zDb = db->aDb[0].zDbSName;
9686696922
}
9686796923
}
9686896924
}
9686996925
9687096926
/* Start at the inner-most context and move outward until a match is found */
@@ -97181,26 +97237,11 @@
9718197237
**
9718297238
** If a generated column is referenced, set bits for every column
9718397239
** of the table.
9718497240
*/
9718597241
if( pExpr->iColumn>=0 && pMatch!=0 ){
97186
- int n = pExpr->iColumn;
97187
- Table *pExTab = pExpr->y.pTab;
97188
- assert( pExTab!=0 );
97189
- assert( pMatch->iCursor==pExpr->iTable );
97190
- if( (pExTab->tabFlags & TF_HasGenerated)!=0
97191
- && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0
97192
- ){
97193
- testcase( pExTab->nCol==BMS-1 );
97194
- testcase( pExTab->nCol==BMS );
97195
- pMatch->colUsed = pExTab->nCol>=BMS ? ALLBITS : MASKBIT(pExTab->nCol)-1;
97196
- }else{
97197
- testcase( n==BMS-1 );
97198
- testcase( n==BMS );
97199
- if( n>=BMS ) n = BMS-1;
97200
- pMatch->colUsed |= ((Bitmask)1)<<n;
97201
- }
97242
+ pMatch->colUsed |= sqlite3ExprColUsed(pExpr);
9720297243
}
9720397244
9720497245
/* Clean up and return
9720597246
*/
9720697247
sqlite3ExprDelete(db, pExpr->pLeft);
@@ -98557,11 +98598,11 @@
9855798598
** CREATE TABLE t1(a);
9855898599
** SELECT * FROM t1 WHERE a;
9855998600
** SELECT a AS b FROM t1 WHERE b;
9856098601
** SELECT * FROM t1 WHERE (select a from t1);
9856198602
*/
98562
-SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){
98603
+SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){
9856398604
int op;
9856498605
while( ExprHasProperty(pExpr, EP_Skip) ){
9856598606
assert( pExpr->op==TK_COLLATE );
9856698607
pExpr = pExpr->pLeft;
9856798608
assert( pExpr!=0 );
@@ -98667,14 +98708,14 @@
9866798708
** The collating sequence might be determined by a COLLATE operator
9866898709
** or by the presence of a column with a defined collating sequence.
9866998710
** COLLATE operators take first precedence. Left operands take
9867098711
** precedence over right operands.
9867198712
*/
98672
-SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
98713
+SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){
9867398714
sqlite3 *db = pParse->db;
9867498715
CollSeq *pColl = 0;
98675
- Expr *p = pExpr;
98716
+ const Expr *p = pExpr;
9867698717
while( p ){
9867798718
int op = p->op;
9867898719
if( op==TK_REGISTER ) op = p->op2;
9867998720
if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER)
9868098721
&& p->y.pTab!=0
@@ -98739,21 +98780,21 @@
9873998780
** See also: sqlite3ExprCollSeq()
9874098781
**
9874198782
** The sqlite3ExprCollSeq() routine works the same except that it
9874298783
** returns NULL if there is no defined collation.
9874398784
*/
98744
-SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr){
98785
+SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr){
9874598786
CollSeq *p = sqlite3ExprCollSeq(pParse, pExpr);
9874698787
if( p==0 ) p = pParse->db->pDfltColl;
9874798788
assert( p!=0 );
9874898789
return p;
9874998790
}
9875098791
9875198792
/*
9875298793
** Return TRUE if the two expressions have equivalent collating sequences.
9875398794
*/
98754
-SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, Expr *pE1, Expr *pE2){
98795
+SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, const Expr *pE1, const Expr *pE2){
9875598796
CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1);
9875698797
CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2);
9875798798
return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0;
9875898799
}
9875998800
@@ -98760,11 +98801,11 @@
9876098801
/*
9876198802
** pExpr is an operand of a comparison operator. aff2 is the
9876298803
** type affinity of the other operand. This routine returns the
9876398804
** type affinity that should be used for the comparison operator.
9876498805
*/
98765
-SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){
98806
+SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2){
9876698807
char aff1 = sqlite3ExprAffinity(pExpr);
9876798808
if( aff1>SQLITE_AFF_NONE && aff2>SQLITE_AFF_NONE ){
9876898809
/* Both sides of the comparison are columns. If one has numeric
9876998810
** affinity, use that. Otherwise use no affinity.
9877098811
*/
@@ -98782,11 +98823,11 @@
9878298823
9878398824
/*
9878498825
** pExpr is a comparison operator. Return the type affinity that should
9878598826
** be applied to both operands prior to doing the comparison.
9878698827
*/
98787
-static char comparisonAffinity(Expr *pExpr){
98828
+static char comparisonAffinity(const Expr *pExpr){
9878898829
char aff;
9878998830
assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT ||
9879098831
pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE ||
9879198832
pExpr->op==TK_NE || pExpr->op==TK_IS || pExpr->op==TK_ISNOT );
9879298833
assert( pExpr->pLeft );
@@ -98805,11 +98846,11 @@
9880598846
** pExpr is a comparison expression, eg. '=', '<', IN(...) etc.
9880698847
** idx_affinity is the affinity of an indexed column. Return true
9880798848
** if the index with affinity idx_affinity may be used to implement
9880898849
** the comparison in pExpr.
9880998850
*/
98810
-SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){
98851
+SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity){
9881198852
char aff = comparisonAffinity(pExpr);
9881298853
if( aff<SQLITE_AFF_TEXT ){
9881398854
return 1;
9881498855
}
9881598856
if( aff==SQLITE_AFF_TEXT ){
@@ -98820,11 +98861,15 @@
9882098861
9882198862
/*
9882298863
** Return the P5 value that should be used for a binary comparison
9882398864
** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2.
9882498865
*/
98825
-static u8 binaryCompareP5(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){
98866
+static u8 binaryCompareP5(
98867
+ const Expr *pExpr1, /* Left operand */
98868
+ const Expr *pExpr2, /* Right operand */
98869
+ int jumpIfNull /* Extra flags added to P5 */
98870
+){
9882698871
u8 aff = (char)sqlite3ExprAffinity(pExpr2);
9882798872
aff = (u8)sqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull;
9882898873
return aff;
9882998874
}
9883098875
@@ -98840,12 +98885,12 @@
9884098885
** Argument pRight (but not pLeft) may be a null pointer. In this case,
9884198886
** it is not considered.
9884298887
*/
9884398888
SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(
9884498889
Parse *pParse,
98845
- Expr *pLeft,
98846
- Expr *pRight
98890
+ const Expr *pLeft,
98891
+ const Expr *pRight
9884798892
){
9884898893
CollSeq *pColl;
9884998894
assert( pLeft );
9885098895
if( pLeft->flags & EP_Collate ){
9885198896
pColl = sqlite3ExprCollSeq(pParse, pLeft);
@@ -98866,11 +98911,11 @@
9886698911
** This is normally just a wrapper around sqlite3BinaryCompareCollSeq().
9886798912
** However, if the OP_Commuted flag is set, then the order of the operands
9886898913
** is reversed in the sqlite3BinaryCompareCollSeq() call so that the
9886998914
** correct collating sequence is found.
9887098915
*/
98871
-SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse *pParse, Expr *p){
98916
+SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse *pParse, const Expr *p){
9887298917
if( ExprHasProperty(p, EP_Commuted) ){
9887398918
return sqlite3BinaryCompareCollSeq(pParse, p->pRight, p->pLeft);
9887498919
}else{
9887598920
return sqlite3BinaryCompareCollSeq(pParse, p->pLeft, p->pRight);
9887698921
}
@@ -99109,10 +99154,11 @@
9910999154
int regRight = 0;
9911099155
u8 opx = op;
9911199156
int addrDone = sqlite3VdbeMakeLabel(pParse);
9911299157
int isCommuted = ExprHasProperty(pExpr,EP_Commuted);
9911399158
99159
+ assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
9911499160
if( pParse->nErr ) return;
9911599161
if( nLeft!=sqlite3ExprVectorSize(pRight) ){
9911699162
sqlite3ErrorMsg(pParse, "row value misused");
9911799163
return;
9911899164
}
@@ -99721,11 +99767,11 @@
9972199767
nSize = EXPR_FULLSIZE;
9972299768
}else{
9972399769
assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
9972499770
assert( !ExprHasProperty(p, EP_FromJoin) );
9972599771
assert( !ExprHasProperty(p, EP_MemToken) );
99726
- assert( !ExprHasProperty(p, EP_NoReduce) );
99772
+ assert( !ExprHasVVAProperty(p, EP_NoReduce) );
9972799773
if( p->pLeft || p->x.pList ){
9972899774
nSize = EXPR_REDUCEDSIZE | EP_Reduced;
9972999775
}else{
9973099776
assert( p->pRight==0 );
9973199777
nSize = EXPR_TOKENONLYSIZE | EP_TokenOnly;
@@ -99826,10 +99872,14 @@
9982699872
9982799873
/* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */
9982899874
pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken);
9982999875
pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly);
9983099876
pNew->flags |= staticFlag;
99877
+ ExprClearVVAProperties(pNew);
99878
+ if( dupFlags ){
99879
+ ExprSetVVAProperty(pNew, EP_Immutable);
99880
+ }
9983199881
9983299882
/* Copy the p->u.zToken string, if any. */
9983399883
if( nToken ){
9983499884
char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize];
9983599885
memcpy(zToken, p->u.zToken, nToken);
@@ -100602,11 +100652,11 @@
100602100652
** (3) the expression does not contain any EP_FixedCol TK_COLUMN
100603100653
** operands created by the constant propagation optimization.
100604100654
**
100605100655
** When this routine returns true, it indicates that the expression
100606100656
** can be added to the pParse->pConstExpr list and evaluated once when
100607
-** the prepared statement starts up. See sqlite3ExprCodeAtInit().
100657
+** the prepared statement starts up. See sqlite3ExprCodeRunJustOnce().
100608100658
*/
100609100659
SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
100610100660
return exprIsConst(p, 2, 0);
100611100661
}
100612100662
@@ -101365,10 +101415,11 @@
101365101415
return;
101366101416
}
101367101417
101368101418
/* Begin coding the subroutine */
101369101419
ExprSetProperty(pExpr, EP_Subrtn);
101420
+ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
101370101421
pExpr->y.sub.regReturn = ++pParse->nMem;
101371101422
pExpr->y.sub.iAddr =
101372101423
sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
101373101424
VdbeComment((v, "return address"));
101374101425
@@ -101685,10 +101736,11 @@
101685101736
int addrTruthOp; /* Address of opcode that determines the IN is true */
101686101737
int destNotNull; /* Jump here if a comparison is not true in step 6 */
101687101738
int addrTop; /* Top of the step-6 loop */
101688101739
int iTab = 0; /* Index to use */
101689101740
101741
+ assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
101690101742
pLeft = pExpr->pLeft;
101691101743
if( sqlite3ExprCheckIN(pParse, pExpr) ) return;
101692101744
zAff = exprINAffinity(pParse, pExpr);
101693101745
nVector = sqlite3ExprVectorSize(pExpr->pLeft);
101694101746
aiMap = (int*)sqlite3DbMallocZero(
@@ -102011,11 +102063,11 @@
102011102063
if( pParse->iSelfTab>0 ){
102012102064
iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut);
102013102065
}else{
102014102066
iAddr = 0;
102015102067
}
102016
- sqlite3ExprCode(pParse, pCol->pDflt, regOut);
102068
+ sqlite3ExprCodeCopy(pParse, pCol->pDflt, regOut);
102017102069
if( pCol->affinity>=SQLITE_AFF_TEXT ){
102018102070
sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1);
102019102071
}
102020102072
if( iAddr ) sqlite3VdbeJumpHere(v, iAddr);
102021102073
}
@@ -102295,10 +102347,11 @@
102295102347
102296102348
expr_code_doover:
102297102349
if( pExpr==0 ){
102298102350
op = TK_NULL;
102299102351
}else{
102352
+ assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
102300102353
op = pExpr->op;
102301102354
}
102302102355
switch( op ){
102303102356
case TK_AGG_COLUMN: {
102304102357
AggInfo *pAggInfo = pExpr->pAggInfo;
@@ -102305,12 +102358,21 @@
102305102358
struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg];
102306102359
if( !pAggInfo->directMode ){
102307102360
assert( pCol->iMem>0 );
102308102361
return pCol->iMem;
102309102362
}else if( pAggInfo->useSortingIdx ){
102363
+ Table *pTab = pCol->pTab;
102310102364
sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
102311102365
pCol->iSorterColumn, target);
102366
+ if( pCol->iColumn<0 ){
102367
+ VdbeComment((v,"%s.rowid",pTab->zName));
102368
+ }else{
102369
+ VdbeComment((v,"%s.%s",pTab->zName,pTab->aCol[pCol->iColumn].zName));
102370
+ if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){
102371
+ sqlite3VdbeAddOp1(v, OP_RealAffinity, target);
102372
+ }
102373
+ }
102312102374
return target;
102313102375
}
102314102376
/* Otherwise, fall thru into the TK_COLUMN case */
102315102377
}
102316102378
case TK_COLUMN: {
@@ -102549,10 +102611,11 @@
102549102611
#endif
102550102612
}else{
102551102613
tempX.op = TK_INTEGER;
102552102614
tempX.flags = EP_IntValue|EP_TokenOnly;
102553102615
tempX.u.iValue = 0;
102616
+ ExprClearVVAProperties(&tempX);
102554102617
r1 = sqlite3ExprCodeTemp(pParse, &tempX, &regFree1);
102555102618
r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree2);
102556102619
sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target);
102557102620
testcase( regFree2==0 );
102558102621
}
@@ -102620,20 +102683,17 @@
102620102683
return pExpr->y.pWin->regResult;
102621102684
}
102622102685
#endif
102623102686
102624102687
if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){
102625
- /* SQL functions can be expensive. So try to move constant functions
102626
- ** out of the inner loop, even if that means an extra OP_Copy. */
102627
- return sqlite3ExprCodeAtInit(pParse, pExpr, -1);
102688
+ /* SQL functions can be expensive. So try to avoid running them
102689
+ ** multiple times if we know they always give the same result */
102690
+ return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1);
102628102691
}
102629102692
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
102630
- if( ExprHasProperty(pExpr, EP_TokenOnly) ){
102631
- pFarg = 0;
102632
- }else{
102633
- pFarg = pExpr->x.pList;
102634
- }
102693
+ assert( !ExprHasProperty(pExpr, EP_TokenOnly) );
102694
+ pFarg = pExpr->x.pList;
102635102695
nFarg = pFarg ? pFarg->nExpr : 0;
102636102696
assert( !ExprHasProperty(pExpr, EP_IntValue) );
102637102697
zId = pExpr->u.zToken;
102638102698
pDef = sqlite3FindFunction(db, zId, nFarg, enc, 0);
102639102699
#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
@@ -103003,19 +103063,27 @@
103003103063
sqlite3ReleaseTempReg(pParse, regFree2);
103004103064
return inReg;
103005103065
}
103006103066
103007103067
/*
103008
-** Factor out the code of the given expression to initialization time.
103068
+** Generate code that will evaluate expression pExpr just one time
103069
+** per prepared statement execution.
103070
+**
103071
+** If the expression uses functions (that might throw an exception) then
103072
+** guard them with an OP_Once opcode to ensure that the code is only executed
103073
+** once. If no functions are involved, then factor the code out and put it at
103074
+** the end of the prepared statement in the initialization section.
103009103075
**
103010103076
** If regDest>=0 then the result is always stored in that register and the
103011103077
** result is not reusable. If regDest<0 then this routine is free to
103012103078
** store the value whereever it wants. The register where the expression
103013
-** is stored is returned. When regDest<0, two identical expressions will
103014
-** code to the same register.
103079
+** is stored is returned. When regDest<0, two identical expressions might
103080
+** code to the same register, if they do not contain function calls and hence
103081
+** are factored out into the initialization section at the end of the
103082
+** prepared statement.
103015103083
*/
103016
-SQLITE_PRIVATE int sqlite3ExprCodeAtInit(
103084
+SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(
103017103085
Parse *pParse, /* Parsing context */
103018103086
Expr *pExpr, /* The expression to code when the VDBE initializes */
103019103087
int regDest /* Store the value in this register */
103020103088
){
103021103089
ExprList *p;
@@ -103029,18 +103097,33 @@
103029103097
return pItem->u.iConstExprReg;
103030103098
}
103031103099
}
103032103100
}
103033103101
pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
103034
- p = sqlite3ExprListAppend(pParse, p, pExpr);
103035
- if( p ){
103036
- struct ExprList_item *pItem = &p->a[p->nExpr-1];
103037
- pItem->reusable = regDest<0;
103038
- if( regDest<0 ) regDest = ++pParse->nMem;
103039
- pItem->u.iConstExprReg = regDest;
103040
- }
103041
- pParse->pConstExpr = p;
103102
+ if( pExpr!=0 && ExprHasProperty(pExpr, EP_HasFunc) ){
103103
+ Vdbe *v = pParse->pVdbe;
103104
+ int addr;
103105
+ assert( v );
103106
+ addr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
103107
+ pParse->okConstFactor = 0;
103108
+ if( !pParse->db->mallocFailed ){
103109
+ if( regDest<0 ) regDest = ++pParse->nMem;
103110
+ sqlite3ExprCode(pParse, pExpr, regDest);
103111
+ }
103112
+ pParse->okConstFactor = 1;
103113
+ sqlite3ExprDelete(pParse->db, pExpr);
103114
+ sqlite3VdbeJumpHere(v, addr);
103115
+ }else{
103116
+ p = sqlite3ExprListAppend(pParse, p, pExpr);
103117
+ if( p ){
103118
+ struct ExprList_item *pItem = &p->a[p->nExpr-1];
103119
+ pItem->reusable = regDest<0;
103120
+ if( regDest<0 ) regDest = ++pParse->nMem;
103121
+ pItem->u.iConstExprReg = regDest;
103122
+ }
103123
+ pParse->pConstExpr = p;
103124
+ }
103042103125
return regDest;
103043103126
}
103044103127
103045103128
/*
103046103129
** Generate code to evaluate an expression and store the results
@@ -103061,11 +103144,11 @@
103061103144
if( ConstFactorOk(pParse)
103062103145
&& pExpr->op!=TK_REGISTER
103063103146
&& sqlite3ExprIsConstantNotJoin(pExpr)
103064103147
){
103065103148
*pReg = 0;
103066
- r2 = sqlite3ExprCodeAtInit(pParse, pExpr, -1);
103149
+ r2 = sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1);
103067103150
}else{
103068103151
int r1 = sqlite3GetTempReg(pParse);
103069103152
r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1);
103070103153
if( r2==r1 ){
103071103154
*pReg = r1;
@@ -103083,10 +103166,11 @@
103083103166
** in register target.
103084103167
*/
103085103168
SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){
103086103169
int inReg;
103087103170
103171
+ assert( pExpr==0 || !ExprHasVVAProperty(pExpr,EP_Immutable) );
103088103172
assert( target>0 && target<=pParse->nMem );
103089103173
inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
103090103174
assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
103091103175
if( inReg!=target && pParse->pVdbe ){
103092103176
u8 op;
@@ -103117,13 +103201,13 @@
103117103201
** in register target. If the expression is constant, then this routine
103118103202
** might choose to code the expression at initialization time.
103119103203
*/
103120103204
SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){
103121103205
if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){
103122
- sqlite3ExprCodeAtInit(pParse, pExpr, target);
103206
+ sqlite3ExprCodeRunJustOnce(pParse, pExpr, target);
103123103207
}else{
103124
- sqlite3ExprCode(pParse, pExpr, target);
103208
+ sqlite3ExprCodeCopy(pParse, pExpr, target);
103125103209
}
103126103210
}
103127103211
103128103212
/*
103129103213
** Generate code that pushes the value of every element of the given
@@ -103177,11 +103261,11 @@
103177103261
sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i);
103178103262
}
103179103263
}else if( (flags & SQLITE_ECEL_FACTOR)!=0
103180103264
&& sqlite3ExprIsConstantNotJoin(pExpr)
103181103265
){
103182
- sqlite3ExprCodeAtInit(pParse, pExpr, target+i);
103266
+ sqlite3ExprCodeRunJustOnce(pParse, pExpr, target+i);
103183103267
}else{
103184103268
int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i);
103185103269
if( inReg!=target+i ){
103186103270
VdbeOp *pOp;
103187103271
if( copyOp==OP_Copy
@@ -103300,10 +103384,11 @@
103300103384
int r1, r2;
103301103385
103302103386
assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
103303103387
if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */
103304103388
if( NEVER(pExpr==0) ) return; /* No way this can happen */
103389
+ assert( !ExprHasVVAProperty(pExpr, EP_Immutable) );
103305103390
op = pExpr->op;
103306103391
switch( op ){
103307103392
case TK_AND:
103308103393
case TK_OR: {
103309103394
Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
@@ -103441,10 +103526,11 @@
103441103526
int r1, r2;
103442103527
103443103528
assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
103444103529
if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */
103445103530
if( pExpr==0 ) return;
103531
+ assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
103446103532
103447103533
/* The value of pExpr->op and op are related as follows:
103448103534
**
103449103535
** pExpr->op op
103450103536
** --------- ----------
@@ -103724,36 +103810,22 @@
103724103810
return 2;
103725103811
}
103726103812
}
103727103813
if( (pA->flags & (EP_Distinct|EP_Commuted))
103728103814
!= (pB->flags & (EP_Distinct|EP_Commuted)) ) return 2;
103729
- if( (combinedFlags & EP_TokenOnly)==0 ){
103815
+ if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){
103730103816
if( combinedFlags & EP_xIsSelect ) return 2;
103731103817
if( (combinedFlags & EP_FixedCol)==0
103732103818
&& sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2;
103733103819
if( sqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2;
103734103820
if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2;
103735103821
if( pA->op!=TK_STRING
103736103822
&& pA->op!=TK_TRUEFALSE
103737
- && (combinedFlags & EP_Reduced)==0
103823
+ && ALWAYS((combinedFlags & EP_Reduced)==0)
103738103824
){
103739103825
if( pA->iColumn!=pB->iColumn ) return 2;
103740
- if( pA->op2!=pB->op2 ){
103741
- if( pA->op==TK_TRUTH ) return 2;
103742
- if( pA->op==TK_FUNCTION && iTab<0 ){
103743
- /* Ex: CREATE TABLE t1(a CHECK( a<julianday('now') ));
103744
- ** INSERT INTO t1(a) VALUES(julianday('now')+10);
103745
- ** Without this test, sqlite3ExprCodeAtInit() will run on the
103746
- ** the julianday() of INSERT first, and remember that expression.
103747
- ** Then sqlite3ExprCodeInit() will see the julianday() in the CHECK
103748
- ** constraint as redundant, reusing the one from the INSERT, even
103749
- ** though the julianday() in INSERT lacks the critical NC_IsCheck
103750
- ** flag. See ticket [830277d9db6c3ba1] (2019-10-30)
103751
- */
103752
- return 2;
103753
- }
103754
- }
103826
+ if( pA->op2!=pB->op2 && pA->op==TK_TRUTH ) return 2;
103755103827
if( pA->op!=TK_IN && pA->iTable!=pB->iTable && pA->iTable!=iTab ){
103756103828
return 2;
103757103829
}
103758103830
}
103759103831
}
@@ -106442,13 +106514,13 @@
106442106514
/*
106443106515
** Three SQL functions - stat_init(), stat_push(), and stat_get() -
106444106516
** share an instance of the following structure to hold their state
106445106517
** information.
106446106518
*/
106447
-typedef struct Stat4Accum Stat4Accum;
106448
-typedef struct Stat4Sample Stat4Sample;
106449
-struct Stat4Sample {
106519
+typedef struct StatAccum StatAccum;
106520
+typedef struct StatSample StatSample;
106521
+struct StatSample {
106450106522
tRowcnt *anEq; /* sqlite_stat4.nEq */
106451106523
tRowcnt *anDLt; /* sqlite_stat4.nDLt */
106452106524
#ifdef SQLITE_ENABLE_STAT4
106453106525
tRowcnt *anLt; /* sqlite_stat4.nLt */
106454106526
union {
@@ -106459,31 +106531,33 @@
106459106531
u8 isPSample; /* True if a periodic sample */
106460106532
int iCol; /* If !isPSample, the reason for inclusion */
106461106533
u32 iHash; /* Tiebreaker hash */
106462106534
#endif
106463106535
};
106464
-struct Stat4Accum {
106536
+struct StatAccum {
106537
+ sqlite3 *db; /* Database connection, for malloc() */
106465106538
tRowcnt nRow; /* Number of rows in the entire table */
106466
- tRowcnt nPSample; /* How often to do a periodic sample */
106467106539
int nCol; /* Number of columns in index + pk/rowid */
106468106540
int nKeyCol; /* Number of index columns w/o the pk/rowid */
106541
+ StatSample current; /* Current row as a StatSample */
106542
+#ifdef SQLITE_ENABLE_STAT4
106543
+ tRowcnt nPSample; /* How often to do a periodic sample */
106469106544
int mxSample; /* Maximum number of samples to accumulate */
106470
- Stat4Sample current; /* Current row as a Stat4Sample */
106471106545
u32 iPrn; /* Pseudo-random number used for sampling */
106472
- Stat4Sample *aBest; /* Array of nCol best samples */
106546
+ StatSample *aBest; /* Array of nCol best samples */
106473106547
int iMin; /* Index in a[] of entry with minimum score */
106474106548
int nSample; /* Current number of samples */
106475106549
int nMaxEqZero; /* Max leading 0 in anEq[] for any a[] entry */
106476106550
int iGet; /* Index of current sample accessed by stat_get() */
106477
- Stat4Sample *a; /* Array of mxSample Stat4Sample objects */
106478
- sqlite3 *db; /* Database connection, for malloc() */
106551
+ StatSample *a; /* Array of mxSample StatSample objects */
106552
+#endif
106479106553
};
106480106554
106481
-/* Reclaim memory used by a Stat4Sample
106555
+/* Reclaim memory used by a StatSample
106482106556
*/
106483106557
#ifdef SQLITE_ENABLE_STAT4
106484
-static void sampleClear(sqlite3 *db, Stat4Sample *p){
106558
+static void sampleClear(sqlite3 *db, StatSample *p){
106485106559
assert( db!=0 );
106486106560
if( p->nRowid ){
106487106561
sqlite3DbFree(db, p->u.aRowid);
106488106562
p->nRowid = 0;
106489106563
}
@@ -106491,11 +106565,11 @@
106491106565
#endif
106492106566
106493106567
/* Initialize the BLOB value of a ROWID
106494106568
*/
106495106569
#ifdef SQLITE_ENABLE_STAT4
106496
-static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){
106570
+static void sampleSetRowid(sqlite3 *db, StatSample *p, int n, const u8 *pData){
106497106571
assert( db!=0 );
106498106572
if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
106499106573
p->u.aRowid = sqlite3DbMallocRawNN(db, n);
106500106574
if( p->u.aRowid ){
106501106575
p->nRowid = n;
@@ -106507,11 +106581,11 @@
106507106581
#endif
106508106582
106509106583
/* Initialize the INTEGER value of a ROWID.
106510106584
*/
106511106585
#ifdef SQLITE_ENABLE_STAT4
106512
-static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){
106586
+static void sampleSetRowidInt64(sqlite3 *db, StatSample *p, i64 iRowid){
106513106587
assert( db!=0 );
106514106588
if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
106515106589
p->nRowid = 0;
106516106590
p->u.iRowid = iRowid;
106517106591
}
@@ -106520,11 +106594,11 @@
106520106594
106521106595
/*
106522106596
** Copy the contents of object (*pFrom) into (*pTo).
106523106597
*/
106524106598
#ifdef SQLITE_ENABLE_STAT4
106525
-static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){
106599
+static void sampleCopy(StatAccum *p, StatSample *pTo, StatSample *pFrom){
106526106600
pTo->isPSample = pFrom->isPSample;
106527106601
pTo->iCol = pFrom->iCol;
106528106602
pTo->iHash = pFrom->iHash;
106529106603
memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol);
106530106604
memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol);
@@ -106536,14 +106610,14 @@
106536106610
}
106537106611
}
106538106612
#endif
106539106613
106540106614
/*
106541
-** Reclaim all memory of a Stat4Accum structure.
106615
+** Reclaim all memory of a StatAccum structure.
106542106616
*/
106543
-static void stat4Destructor(void *pOld){
106544
- Stat4Accum *p = (Stat4Accum*)pOld;
106617
+static void statAccumDestructor(void *pOld){
106618
+ StatAccum *p = (StatAccum*)pOld;
106545106619
#ifdef SQLITE_ENABLE_STAT4
106546106620
int i;
106547106621
for(i=0; i<p->nCol; i++) sampleClear(p->db, p->aBest+i);
106548106622
for(i=0; i<p->mxSample; i++) sampleClear(p->db, p->a+i);
106549106623
sampleClear(p->db, &p->current);
@@ -106567,21 +106641,21 @@
106567106641
** For indexes on ordinary rowid tables, N==K+1. But for indexes on
106568106642
** WITHOUT ROWID tables, N=K+P where P is the number of columns in the
106569106643
** PRIMARY KEY of the table. The covering index that implements the
106570106644
** original WITHOUT ROWID table as N==K as a special case.
106571106645
**
106572
-** This routine allocates the Stat4Accum object in heap memory. The return
106573
-** value is a pointer to the Stat4Accum object. The datatype of the
106574
-** return value is BLOB, but it is really just a pointer to the Stat4Accum
106646
+** This routine allocates the StatAccum object in heap memory. The return
106647
+** value is a pointer to the StatAccum object. The datatype of the
106648
+** return value is BLOB, but it is really just a pointer to the StatAccum
106575106649
** object.
106576106650
*/
106577106651
static void statInit(
106578106652
sqlite3_context *context,
106579106653
int argc,
106580106654
sqlite3_value **argv
106581106655
){
106582
- Stat4Accum *p;
106656
+ StatAccum *p;
106583106657
int nCol; /* Number of columns in index being sampled */
106584106658
int nKeyCol; /* Number of key columns */
106585106659
int nColUp; /* nCol rounded up for alignment */
106586106660
int n; /* Bytes of space to allocate */
106587106661
sqlite3 *db; /* Database connection */
@@ -106596,17 +106670,17 @@
106596106670
nColUp = sizeof(tRowcnt)<8 ? (nCol+1)&~1 : nCol;
106597106671
nKeyCol = sqlite3_value_int(argv[1]);
106598106672
assert( nKeyCol<=nCol );
106599106673
assert( nKeyCol>0 );
106600106674
106601
- /* Allocate the space required for the Stat4Accum object */
106675
+ /* Allocate the space required for the StatAccum object */
106602106676
n = sizeof(*p)
106603
- + sizeof(tRowcnt)*nColUp /* Stat4Accum.anEq */
106604
- + sizeof(tRowcnt)*nColUp /* Stat4Accum.anDLt */
106677
+ + sizeof(tRowcnt)*nColUp /* StatAccum.anEq */
106678
+ + sizeof(tRowcnt)*nColUp /* StatAccum.anDLt */
106605106679
#ifdef SQLITE_ENABLE_STAT4
106606
- + sizeof(tRowcnt)*nColUp /* Stat4Accum.anLt */
106607
- + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */
106680
+ + sizeof(tRowcnt)*nColUp /* StatAccum.anLt */
106681
+ + sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */
106608106682
+ sizeof(tRowcnt)*3*nColUp*(nCol+mxSample)
106609106683
#endif
106610106684
;
106611106685
db = sqlite3_context_db_handle(context);
106612106686
p = sqlite3DbMallocZero(db, n);
@@ -106631,12 +106705,12 @@
106631106705
p->mxSample = mxSample;
106632106706
p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1);
106633106707
p->current.anLt = &p->current.anEq[nColUp];
106634106708
p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]);
106635106709
106636
- /* Set up the Stat4Accum.a[] and aBest[] arrays */
106637
- p->a = (struct Stat4Sample*)&p->current.anLt[nColUp];
106710
+ /* Set up the StatAccum.a[] and aBest[] arrays */
106711
+ p->a = (struct StatSample*)&p->current.anLt[nColUp];
106638106712
p->aBest = &p->a[mxSample];
106639106713
pSpace = (u8*)(&p->a[mxSample+nCol]);
106640106714
for(i=0; i<(mxSample+nCol); i++){
106641106715
p->a[i].anEq = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp);
106642106716
p->a[i].anLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp);
@@ -106652,11 +106726,11 @@
106652106726
106653106727
/* Return a pointer to the allocated object to the caller. Note that
106654106728
** only the pointer (the 2nd parameter) matters. The size of the object
106655106729
** (given by the 3rd parameter) is never used and can be any positive
106656106730
** value. */
106657
- sqlite3_result_blob(context, p, sizeof(*p), stat4Destructor);
106731
+ sqlite3_result_blob(context, p, sizeof(*p), statAccumDestructor);
106658106732
}
106659106733
static const FuncDef statInitFuncdef = {
106660106734
2+IsStat4, /* nArg */
106661106735
SQLITE_UTF8, /* funcFlags */
106662106736
0, /* pUserData */
@@ -106679,13 +106753,13 @@
106679106753
**
106680106754
** This function assumes that for each argument sample, the contents of
106681106755
** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid.
106682106756
*/
106683106757
static int sampleIsBetterPost(
106684
- Stat4Accum *pAccum,
106685
- Stat4Sample *pNew,
106686
- Stat4Sample *pOld
106758
+ StatAccum *pAccum,
106759
+ StatSample *pNew,
106760
+ StatSample *pOld
106687106761
){
106688106762
int nCol = pAccum->nCol;
106689106763
int i;
106690106764
assert( pNew->iCol==pOld->iCol );
106691106765
for(i=pNew->iCol+1; i<nCol; i++){
@@ -106703,13 +106777,13 @@
106703106777
**
106704106778
** This function assumes that for each argument sample, the contents of
106705106779
** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid.
106706106780
*/
106707106781
static int sampleIsBetter(
106708
- Stat4Accum *pAccum,
106709
- Stat4Sample *pNew,
106710
- Stat4Sample *pOld
106782
+ StatAccum *pAccum,
106783
+ StatSample *pNew,
106784
+ StatSample *pOld
106711106785
){
106712106786
tRowcnt nEqNew = pNew->anEq[pNew->iCol];
106713106787
tRowcnt nEqOld = pOld->anEq[pOld->iCol];
106714106788
106715106789
assert( pOld->isPSample==0 && pNew->isPSample==0 );
@@ -106725,34 +106799,34 @@
106725106799
106726106800
/*
106727106801
** Copy the contents of sample *pNew into the p->a[] array. If necessary,
106728106802
** remove the least desirable sample from p->a[] to make room.
106729106803
*/
106730
-static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){
106731
- Stat4Sample *pSample = 0;
106804
+static void sampleInsert(StatAccum *p, StatSample *pNew, int nEqZero){
106805
+ StatSample *pSample = 0;
106732106806
int i;
106733106807
106734106808
assert( IsStat4 || nEqZero==0 );
106735106809
106736
- /* Stat4Accum.nMaxEqZero is set to the maximum number of leading 0
106737
- ** values in the anEq[] array of any sample in Stat4Accum.a[]. In
106810
+ /* StatAccum.nMaxEqZero is set to the maximum number of leading 0
106811
+ ** values in the anEq[] array of any sample in StatAccum.a[]. In
106738106812
** other words, if nMaxEqZero is n, then it is guaranteed that there
106739
- ** are no samples with Stat4Sample.anEq[m]==0 for (m>=n). */
106813
+ ** are no samples with StatSample.anEq[m]==0 for (m>=n). */
106740106814
if( nEqZero>p->nMaxEqZero ){
106741106815
p->nMaxEqZero = nEqZero;
106742106816
}
106743106817
if( pNew->isPSample==0 ){
106744
- Stat4Sample *pUpgrade = 0;
106818
+ StatSample *pUpgrade = 0;
106745106819
assert( pNew->anEq[pNew->iCol]>0 );
106746106820
106747106821
/* This sample is being added because the prefix that ends in column
106748106822
** iCol occurs many times in the table. However, if we have already
106749106823
** added a sample that shares this prefix, there is no need to add
106750106824
** this one. Instead, upgrade the priority of the highest priority
106751106825
** existing sample that shares this prefix. */
106752106826
for(i=p->nSample-1; i>=0; i--){
106753
- Stat4Sample *pOld = &p->a[i];
106827
+ StatSample *pOld = &p->a[i];
106754106828
if( pOld->anEq[pNew->iCol]==0 ){
106755106829
if( pOld->isPSample ) return;
106756106830
assert( pOld->iCol>pNew->iCol );
106757106831
assert( sampleIsBetter(p, pNew, pOld) );
106758106832
if( pUpgrade==0 || sampleIsBetter(p, pOld, pUpgrade) ){
@@ -106767,11 +106841,11 @@
106767106841
}
106768106842
}
106769106843
106770106844
/* If necessary, remove sample iMin to make room for the new sample. */
106771106845
if( p->nSample>=p->mxSample ){
106772
- Stat4Sample *pMin = &p->a[p->iMin];
106846
+ StatSample *pMin = &p->a[p->iMin];
106773106847
tRowcnt *anEq = pMin->anEq;
106774106848
tRowcnt *anLt = pMin->anLt;
106775106849
tRowcnt *anDLt = pMin->anDLt;
106776106850
sampleClear(p->db, pMin);
106777106851
memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1));
@@ -106810,24 +106884,24 @@
106810106884
p->iMin = iMin;
106811106885
}
106812106886
}
106813106887
#endif /* SQLITE_ENABLE_STAT4 */
106814106888
106889
+#ifdef SQLITE_ENABLE_STAT4
106815106890
/*
106816106891
** Field iChng of the index being scanned has changed. So at this point
106817106892
** p->current contains a sample that reflects the previous row of the
106818106893
** index. The value of anEq[iChng] and subsequent anEq[] elements are
106819106894
** correct at this point.
106820106895
*/
106821
-static void samplePushPrevious(Stat4Accum *p, int iChng){
106822
-#ifdef SQLITE_ENABLE_STAT4
106896
+static void samplePushPrevious(StatAccum *p, int iChng){
106823106897
int i;
106824106898
106825106899
/* Check if any samples from the aBest[] array should be pushed
106826106900
** into IndexSample.a[] at this point. */
106827106901
for(i=(p->nCol-2); i>=iChng; i--){
106828
- Stat4Sample *pBest = &p->aBest[i];
106902
+ StatSample *pBest = &p->aBest[i];
106829106903
pBest->anEq[i] = p->current.anEq[i];
106830106904
if( p->nSample<p->mxSample || sampleIsBetter(p, pBest, &p->a[p->iMin]) ){
106831106905
sampleInsert(p, pBest, i);
106832106906
}
106833106907
}
@@ -106847,29 +106921,24 @@
106847106921
if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j];
106848106922
}
106849106923
}
106850106924
p->nMaxEqZero = iChng;
106851106925
}
106852
-#endif
106853
-
106854
-#ifndef SQLITE_ENABLE_STAT4
106855
- UNUSED_PARAMETER( p );
106856
- UNUSED_PARAMETER( iChng );
106857
-#endif
106858106926
}
106927
+#endif /* SQLITE_ENABLE_STAT4 */
106859106928
106860106929
/*
106861106930
** Implementation of the stat_push SQL function: stat_push(P,C,R)
106862106931
** Arguments:
106863106932
**
106864
-** P Pointer to the Stat4Accum object created by stat_init()
106933
+** P Pointer to the StatAccum object created by stat_init()
106865106934
** C Index of left-most column to differ from previous row
106866106935
** R Rowid for the current row. Might be a key record for
106867106936
** WITHOUT ROWID tables.
106868106937
**
106869106938
** This SQL function always returns NULL. It's purpose it to accumulate
106870
-** statistical data and/or samples in the Stat4Accum object about the
106939
+** statistical data and/or samples in the StatAccum object about the
106871106940
** index being analyzed. The stat_get() SQL function will later be used to
106872106941
** extract relevant information for constructing the sqlite_statN tables.
106873106942
**
106874106943
** The R parameter is only used for STAT4
106875106944
*/
@@ -106879,11 +106948,11 @@
106879106948
sqlite3_value **argv
106880106949
){
106881106950
int i;
106882106951
106883106952
/* The three function arguments */
106884
- Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]);
106953
+ StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]);
106885106954
int iChng = sqlite3_value_int(argv[1]);
106886106955
106887106956
UNUSED_PARAMETER( argc );
106888106957
UNUSED_PARAMETER( context );
106889106958
assert( p->nCol>0 );
@@ -106892,11 +106961,13 @@
106892106961
if( p->nRow==0 ){
106893106962
/* This is the first call to this function. Do initialization. */
106894106963
for(i=0; i<p->nCol; i++) p->current.anEq[i] = 1;
106895106964
}else{
106896106965
/* Second and subsequent calls get processed here */
106966
+#ifdef SQLITE_ENABLE_STAT4
106897106967
samplePushPrevious(p, iChng);
106968
+#endif
106898106969
106899106970
/* Update anDLt[], anLt[] and anEq[] to reflect the values that apply
106900106971
** to the current row of the index. */
106901106972
for(i=0; i<iChng; i++){
106902106973
p->current.anEq[i]++;
@@ -106961,19 +107032,19 @@
106961107032
#define STAT_GET_NDLT 4 /* "ndlt" column of stat[34] entry */
106962107033
106963107034
/*
106964107035
** Implementation of the stat_get(P,J) SQL function. This routine is
106965107036
** used to query statistical information that has been gathered into
106966
-** the Stat4Accum object by prior calls to stat_push(). The P parameter
106967
-** has type BLOB but it is really just a pointer to the Stat4Accum object.
107037
+** the StatAccum object by prior calls to stat_push(). The P parameter
107038
+** has type BLOB but it is really just a pointer to the StatAccum object.
106968107039
** The content to returned is determined by the parameter J
106969107040
** which is one of the STAT_GET_xxxx values defined above.
106970107041
**
106971107042
** The stat_get(P,J) function is not available to generic SQL. It is
106972107043
** inserted as part of a manually constructed bytecode program. (See
106973107044
** the callStatGet() routine below.) It is guaranteed that the P
106974
-** parameter will always be a poiner to a Stat4Accum object, never a
107045
+** parameter will always be a pointer to a StatAccum object, never a
106975107046
** NULL.
106976107047
**
106977107048
** If STAT4 is not enabled, then J is always
106978107049
** STAT_GET_STAT1 and is hence omitted and this routine becomes
106979107050
** a one-parameter function, stat_get(P), that always returns the
@@ -106982,11 +107053,11 @@
106982107053
static void statGet(
106983107054
sqlite3_context *context,
106984107055
int argc,
106985107056
sqlite3_value **argv
106986107057
){
106987
- Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]);
107058
+ StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]);
106988107059
#ifdef SQLITE_ENABLE_STAT4
106989107060
/* STAT4 has a parameter on this routine. */
106990107061
int eCall = sqlite3_value_int(argv[1]);
106991107062
assert( argc==2 );
106992107063
assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ
@@ -107003,11 +107074,11 @@
107003107074
**
107004107075
** The value is a string composed of a list of integers describing
107005107076
** the index. The first integer in the list is the total number of
107006107077
** entries in the index. There is one additional integer in the list
107007107078
** for each indexed column. This additional integer is an estimate of
107008
- ** the number of rows matched by a stabbing query on the index using
107079
+ ** the number of rows matched by a equality query on the index using
107009107080
** a key with the corresponding number of fields. In other words,
107010107081
** if the index is on columns (a,b) and the sqlite_stat1 value is
107011107082
** "100 10 2", then SQLite estimates that:
107012107083
**
107013107084
** * the index contains 100 rows,
@@ -107046,11 +107117,11 @@
107046107117
if( p->iGet<0 ){
107047107118
samplePushPrevious(p, 0);
107048107119
p->iGet = 0;
107049107120
}
107050107121
if( p->iGet<p->nSample ){
107051
- Stat4Sample *pS = p->a + p->iGet;
107122
+ StatSample *pS = p->a + p->iGet;
107052107123
if( pS->nRowid==0 ){
107053107124
sqlite3_result_int64(context, pS->u.iRowid);
107054107125
}else{
107055107126
sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid,
107056107127
SQLITE_TRANSIENT);
@@ -107137,11 +107208,11 @@
107137107208
int i; /* Loop counter */
107138107209
int jZeroRows = -1; /* Jump from here if number of rows is zero */
107139107210
int iDb; /* Index of database containing pTab */
107140107211
u8 needTableCnt = 1; /* True to count the table */
107141107212
int regNewRowid = iMem++; /* Rowid for the inserted record */
107142
- int regStat4 = iMem++; /* Register to hold Stat4Accum object */
107213
+ int regStat4 = iMem++; /* Register to hold StatAccum object */
107143107214
int regChng = iMem++; /* Index of changed index field */
107144107215
#ifdef SQLITE_ENABLE_STAT4
107145107216
int regRowid = iMem++; /* Rowid argument passed to stat_push() */
107146107217
#endif
107147107218
int regTemp = iMem++; /* Temporary use register */
@@ -108105,10 +108176,21 @@
108105108176
pExpr->op = TK_STRING;
108106108177
}
108107108178
}
108108108179
return rc;
108109108180
}
108181
+
108182
+/*
108183
+** Return true if zName points to a name that may be used to refer to
108184
+** database iDb attached to handle db.
108185
+*/
108186
+SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName){
108187
+ return (
108188
+ sqlite3StrICmp(db->aDb[iDb].zDbSName, zName)==0
108189
+ || (iDb==0 && sqlite3StrICmp("main", zName)==0)
108190
+ );
108191
+}
108110108192
108111108193
/*
108112108194
** An SQL user-function registered to do the work of an ATTACH statement. The
108113108195
** three arguments to the function come directly from an attach statement:
108114108196
**
@@ -108178,13 +108260,12 @@
108178108260
db->aLimit[SQLITE_LIMIT_ATTACHED]
108179108261
);
108180108262
goto attach_error;
108181108263
}
108182108264
for(i=0; i<db->nDb; i++){
108183
- char *z = db->aDb[i].zDbSName;
108184
- assert( z && zName );
108185
- if( sqlite3StrICmp(z, zName)==0 ){
108265
+ assert( zName );
108266
+ if( sqlite3DbIsNamed(db, i, zName) ){
108186108267
zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName);
108187108268
goto attach_error;
108188108269
}
108189108270
}
108190108271
@@ -108333,11 +108414,11 @@
108333108414
108334108415
if( zName==0 ) zName = "";
108335108416
for(i=0; i<db->nDb; i++){
108336108417
pDb = &db->aDb[i];
108337108418
if( pDb->pBt==0 ) continue;
108338
- if( sqlite3StrICmp(pDb->zDbSName, zName)==0 ) break;
108419
+ if( sqlite3DbIsNamed(db, i, zName) ) break;
108339108420
}
108340108421
108341108422
if( i>=db->nDb ){
108342108423
sqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName);
108343108424
goto detach_error;
@@ -108524,24 +108605,25 @@
108524108605
SQLITE_PRIVATE int sqlite3FixSrcList(
108525108606
DbFixer *pFix, /* Context of the fixation */
108526108607
SrcList *pList /* The Source list to check and modify */
108527108608
){
108528108609
int i;
108529
- const char *zDb;
108530108610
struct SrcList_item *pItem;
108611
+ sqlite3 *db = pFix->pParse->db;
108612
+ int iDb = sqlite3FindDbName(db, pFix->zDb);
108531108613
108532108614
if( NEVER(pList==0) ) return 0;
108533
- zDb = pFix->zDb;
108615
+
108534108616
for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
108535108617
if( pFix->bTemp==0 ){
108536
- if( pItem->zDatabase && sqlite3StrICmp(pItem->zDatabase, zDb) ){
108618
+ if( pItem->zDatabase && iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){
108537108619
sqlite3ErrorMsg(pFix->pParse,
108538108620
"%s %T cannot reference objects in database %s",
108539108621
pFix->zType, pFix->pName, pItem->zDatabase);
108540108622
return 1;
108541108623
}
108542
- sqlite3DbFree(pFix->pParse->db, pItem->zDatabase);
108624
+ sqlite3DbFree(db, pItem->zDatabase);
108543108625
pItem->zDatabase = 0;
108544108626
pItem->pSchema = pFix->pSchema;
108545108627
pItem->fg.fromDDL = 1;
108546108628
}
108547108629
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
@@ -109262,11 +109344,11 @@
109262109344
}
109263109345
#endif
109264109346
while(1){
109265109347
for(i=OMIT_TEMPDB; i<db->nDb; i++){
109266109348
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
109267
- if( zDatabase==0 || sqlite3StrICmp(zDatabase, db->aDb[j].zDbSName)==0 ){
109349
+ if( zDatabase==0 || sqlite3DbIsNamed(db, j, zDatabase) ){
109268109350
assert( sqlite3SchemaMutexHeld(db, j, 0) );
109269109351
p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName);
109270109352
if( p ) return p;
109271109353
}
109272109354
}
@@ -109384,11 +109466,11 @@
109384109466
assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
109385109467
for(i=OMIT_TEMPDB; i<db->nDb; i++){
109386109468
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
109387109469
Schema *pSchema = db->aDb[j].pSchema;
109388109470
assert( pSchema );
109389
- if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue;
109471
+ if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue;
109390109472
assert( sqlite3SchemaMutexHeld(db, j, 0) );
109391109473
p = sqlite3HashFind(&pSchema->idxHash, zName);
109392109474
if( p ) break;
109393109475
}
109394109476
return p;
@@ -111103,10 +111185,36 @@
111103111185
if( pMod->pModule->iVersion<3 ) return 0;
111104111186
if( pMod->pModule->xShadowName==0 ) return 0;
111105111187
return pMod->pModule->xShadowName(zTail+1);
111106111188
}
111107111189
#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */
111190
+
111191
+#ifdef SQLITE_DEBUG
111192
+/*
111193
+** Mark all nodes of an expression as EP_Immutable, indicating that
111194
+** they should not be changed. Expressions attached to a table or
111195
+** index definition are tagged this way to help ensure that we do
111196
+** not pass them into code generator routines by mistake.
111197
+*/
111198
+static int markImmutableExprStep(Walker *pWalker, Expr *pExpr){
111199
+ ExprSetVVAProperty(pExpr, EP_Immutable);
111200
+ return WRC_Continue;
111201
+}
111202
+static void markExprListImmutable(ExprList *pList){
111203
+ if( pList ){
111204
+ Walker w;
111205
+ memset(&w, 0, sizeof(w));
111206
+ w.xExprCallback = markImmutableExprStep;
111207
+ w.xSelectCallback = sqlite3SelectWalkNoop;
111208
+ w.xSelectCallback2 = 0;
111209
+ sqlite3WalkExprList(&w, pList);
111210
+ }
111211
+}
111212
+#else
111213
+#define markExprListImmutable(X) /* no-op */
111214
+#endif /* SQLITE_DEBUG */
111215
+
111108111216
111109111217
/*
111110111218
** This routine is called to report the final ")" that terminates
111111111219
** a CREATE TABLE statement.
111112111220
**
@@ -111196,10 +111304,12 @@
111196111304
if( pParse->nErr ){
111197111305
/* If errors are seen, delete the CHECK constraints now, else they might
111198111306
** actually be used if PRAGMA writable_schema=ON is set. */
111199111307
sqlite3ExprListDelete(db, p->pCheck);
111200111308
p->pCheck = 0;
111309
+ }else{
111310
+ markExprListImmutable(p->pCheck);
111201111311
}
111202111312
}
111203111313
#endif /* !defined(SQLITE_OMIT_CHECK) */
111204111314
#ifndef SQLITE_OMIT_GENERATED_COLUMNS
111205111315
if( p->tabFlags & TF_HasGenerated ){
@@ -114137,20 +114247,33 @@
114137114247
u8 enc, /* Desired text encoding */
114138114248
const char *zName, /* Name of the collating sequence. Might be NULL */
114139114249
int create /* True to create CollSeq if doesn't already exist */
114140114250
){
114141114251
CollSeq *pColl;
114252
+ assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 );
114253
+ assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE );
114142114254
if( zName ){
114143114255
pColl = findCollSeqEntry(db, zName, create);
114256
+ if( pColl ) pColl += enc-1;
114144114257
}else{
114145114258
pColl = db->pDfltColl;
114146114259
}
114147
- assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 );
114148
- assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE );
114149
- if( pColl ) pColl += enc-1;
114150114260
return pColl;
114151114261
}
114262
+
114263
+/*
114264
+** Change the text encoding for a database connection. This means that
114265
+** the pDfltColl must change as well.
114266
+*/
114267
+SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8 enc){
114268
+ assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
114269
+ db->enc = enc;
114270
+ /* EVIDENCE-OF: R-08308-17224 The default collating function for all
114271
+ ** strings is BINARY.
114272
+ */
114273
+ db->pDfltColl = sqlite3FindCollSeq(db, enc, sqlite3StrBINARY, 0);
114274
+}
114152114275
114153114276
/*
114154114277
** This function is responsible for invoking the collation factory callback
114155114278
** or substituting a collation sequence of a different encoding when the
114156114279
** requested collation sequence is not available in the desired encoding.
@@ -116321,10 +116444,11 @@
116321116444
const unsigned char *zA, *zB;
116322116445
u32 escape;
116323116446
int nPat;
116324116447
sqlite3 *db = sqlite3_context_db_handle(context);
116325116448
struct compareInfo *pInfo = sqlite3_user_data(context);
116449
+ struct compareInfo backupInfo;
116326116450
116327116451
#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
116328116452
if( sqlite3_value_type(argv[0])==SQLITE_BLOB
116329116453
|| sqlite3_value_type(argv[1])==SQLITE_BLOB
116330116454
){
@@ -116356,10 +116480,16 @@
116356116480
sqlite3_result_error(context,
116357116481
"ESCAPE expression must be a single character", -1);
116358116482
return;
116359116483
}
116360116484
escape = sqlite3Utf8Read(&zEsc);
116485
+ if( escape==pInfo->matchAll || escape==pInfo->matchOne ){
116486
+ memcpy(&backupInfo, pInfo, sizeof(backupInfo));
116487
+ pInfo = &backupInfo;
116488
+ if( escape==pInfo->matchAll ) pInfo->matchAll = 0;
116489
+ if( escape==pInfo->matchOne ) pInfo->matchOne = 0;
116490
+ }
116361116491
}else{
116362116492
escape = pInfo->matchSet;
116363116493
}
116364116494
zB = sqlite3_value_text(argv[0]);
116365116495
zA = sqlite3_value_text(argv[1]);
@@ -117338,29 +117468,33 @@
117338117468
if( pDef==0 ) return 0;
117339117469
#endif
117340117470
if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){
117341117471
return 0;
117342117472
}
117343
- if( nExpr<3 ){
117344
- aWc[3] = 0;
117345
- }else{
117346
- Expr *pEscape = pExpr->x.pList->a[2].pExpr;
117347
- char *zEscape;
117348
- if( pEscape->op!=TK_STRING ) return 0;
117349
- zEscape = pEscape->u.zToken;
117350
- if( zEscape[0]==0 || zEscape[1]!=0 ) return 0;
117351
- aWc[3] = zEscape[0];
117352
- }
117353117473
117354117474
/* The memcpy() statement assumes that the wildcard characters are
117355117475
** the first three statements in the compareInfo structure. The
117356117476
** asserts() that follow verify that assumption
117357117477
*/
117358117478
memcpy(aWc, pDef->pUserData, 3);
117359117479
assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll );
117360117480
assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne );
117361117481
assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet );
117482
+
117483
+ if( nExpr<3 ){
117484
+ aWc[3] = 0;
117485
+ }else{
117486
+ Expr *pEscape = pExpr->x.pList->a[2].pExpr;
117487
+ char *zEscape;
117488
+ if( pEscape->op!=TK_STRING ) return 0;
117489
+ zEscape = pEscape->u.zToken;
117490
+ if( zEscape[0]==0 || zEscape[1]!=0 ) return 0;
117491
+ if( zEscape[0]==aWc[0] ) return 0;
117492
+ if( zEscape[0]==aWc[1] ) return 0;
117493
+ aWc[3] = zEscape[0];
117494
+ }
117495
+
117362117496
*pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0;
117363117497
return 1;
117364117498
}
117365117499
117366117500
/*
@@ -120564,11 +120698,11 @@
120564120698
case OE_Replace: {
120565120699
int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg);
120566120700
VdbeCoverage(v);
120567120701
assert( (pCol->colFlags & COLFLAG_GENERATED)==0 );
120568120702
nSeenReplace++;
120569
- sqlite3ExprCode(pParse, pCol->pDflt, iReg);
120703
+ sqlite3ExprCodeCopy(pParse, pCol->pDflt, iReg);
120570120704
sqlite3VdbeJumpHere(v, addr1);
120571120705
break;
120572120706
}
120573120707
case OE_Abort:
120574120708
sqlite3MayAbort(pParse);
@@ -120619,10 +120753,11 @@
120619120753
ExprList *pCheck = pTab->pCheck;
120620120754
pParse->iSelfTab = -(regNewData+1);
120621120755
onError = overrideError!=OE_Default ? overrideError : OE_Abort;
120622120756
for(i=0; i<pCheck->nExpr; i++){
120623120757
int allOk;
120758
+ Expr *pCopy;
120624120759
Expr *pExpr = pCheck->a[i].pExpr;
120625120760
if( aiChng
120626120761
&& !sqlite3ExprReferencesUpdatedColumn(pExpr, aiChng, pkChng)
120627120762
){
120628120763
/* The check constraints do not reference any of the columns being
@@ -120633,11 +120768,15 @@
120633120768
sqlite3TableAffinity(v, pTab, regNewData+1);
120634120769
bAffinityDone = 1;
120635120770
}
120636120771
allOk = sqlite3VdbeMakeLabel(pParse);
120637120772
sqlite3VdbeVerifyAbortable(v, onError);
120638
- sqlite3ExprIfTrue(pParse, pExpr, allOk, SQLITE_JUMPIFNULL);
120773
+ pCopy = sqlite3ExprDup(db, pExpr, 0);
120774
+ if( !db->mallocFailed ){
120775
+ sqlite3ExprIfTrue(pParse, pCopy, allOk, SQLITE_JUMPIFNULL);
120776
+ }
120777
+ sqlite3ExprDelete(db, pCopy);
120639120778
if( onError==OE_Ignore ){
120640120779
sqlite3VdbeGoto(v, ignoreDest);
120641120780
}else{
120642120781
char *zName = pCheck->a[i].zEName;
120643120782
if( zName==0 ) zName = pTab->zName;
@@ -125952,25 +126091,16 @@
125952126091
/* Only change the value of sqlite.enc if the database handle is not
125953126092
** initialized. If the main database exists, the new sqlite.enc value
125954126093
** will be overwritten when the schema is next loaded. If it does not
125955126094
** already exists, it will be created to use the new encoding value.
125956126095
*/
125957
- int canChangeEnc = 1; /* True if allowed to change the encoding */
125958
- int i; /* For looping over all attached databases */
125959
- for(i=0; i<db->nDb; i++){
125960
- if( db->aDb[i].pBt!=0
125961
- && DbHasProperty(db,i,DB_SchemaLoaded)
125962
- && !DbHasProperty(db,i,DB_Empty)
125963
- ){
125964
- canChangeEnc = 0;
125965
- }
125966
- }
125967
- if( canChangeEnc ){
126096
+ if( (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){
125968126097
for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
125969126098
if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){
125970
- SCHEMA_ENC(db) = ENC(db) =
125971
- pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
126099
+ u8 enc = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
126100
+ SCHEMA_ENC(db) = enc;
126101
+ sqlite3SetTextEncoding(db, enc);
125972126102
break;
125973126103
}
125974126104
}
125975126105
if( !pEnc->zName ){
125976126106
sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight);
@@ -126775,11 +126905,11 @@
126775126905
int iDb = pData->iDb;
126776126906
126777126907
assert( argc==5 );
126778126908
UNUSED_PARAMETER2(NotUsed, argc);
126779126909
assert( sqlite3_mutex_held(db->mutex) );
126780
- DbClearProperty(db, iDb, DB_Empty);
126910
+ db->mDbFlags |= DBFLAG_EncodingFixed;
126781126911
pData->nInitRow++;
126782126912
if( db->mallocFailed ){
126783126913
corruptSchema(pData, argv[1], 0);
126784126914
return 1;
126785126915
}
@@ -126863,10 +126993,11 @@
126863126993
char const *azArg[6];
126864126994
int meta[5];
126865126995
InitData initData;
126866126996
const char *zMasterName;
126867126997
int openedTransaction = 0;
126998
+ int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed);
126868126999
126869127000
assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 );
126870127001
assert( iDb>=0 && iDb<db->nDb );
126871127002
assert( db->aDb[iDb].pSchema );
126872127003
assert( sqlite3_mutex_held(db->mutex) );
@@ -126891,10 +127022,11 @@
126891127022
initData.rc = SQLITE_OK;
126892127023
initData.pzErrMsg = pzErrMsg;
126893127024
initData.mInitFlags = mFlags;
126894127025
initData.nInitRow = 0;
126895127026
sqlite3InitCallback(&initData, 5, (char **)azArg, 0);
127027
+ db->mDbFlags &= mask;
126896127028
if( initData.rc ){
126897127029
rc = initData.rc;
126898127030
goto error_out;
126899127031
}
126900127032
@@ -126950,31 +127082,29 @@
126950127082
** main database, set sqlite3.enc to the encoding of the main database.
126951127083
** For an attached db, it is an error if the encoding is not the same
126952127084
** as sqlite3.enc.
126953127085
*/
126954127086
if( meta[BTREE_TEXT_ENCODING-1] ){ /* text encoding */
126955
- if( iDb==0 ){
126956
-#ifndef SQLITE_OMIT_UTF16
127087
+ if( iDb==0 && (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){
126957127088
u8 encoding;
127089
+#ifndef SQLITE_OMIT_UTF16
126958127090
/* If opening the main database, set ENC(db). */
126959127091
encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
126960127092
if( encoding==0 ) encoding = SQLITE_UTF8;
126961
- ENC(db) = encoding;
126962127093
#else
126963
- ENC(db) = SQLITE_UTF8;
127094
+ encoding = SQLITE_UTF8;
126964127095
#endif
127096
+ sqlite3SetTextEncoding(db, encoding);
126965127097
}else{
126966127098
/* If opening an attached database, the encoding much match ENC(db) */
126967
- if( meta[BTREE_TEXT_ENCODING-1]!=ENC(db) ){
127099
+ if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){
126968127100
sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
126969127101
" text encoding as main database");
126970127102
rc = SQLITE_ERROR;
126971127103
goto initone_error_out;
126972127104
}
126973127105
}
126974
- }else{
126975
- DbSetProperty(db, iDb, DB_Empty);
126976127106
}
126977127107
pDb->pSchema->enc = ENC(db);
126978127108
126979127109
if( pDb->pSchema->cache_size==0 ){
126980127110
#ifndef SQLITE_OMIT_DEPRECATED
@@ -127082,12 +127212,11 @@
127082127212
** used to store temporary tables, and any additional database files
127083127213
** created using ATTACH statements. Return a success code. If an
127084127214
** error occurs, write an error message into *pzErrMsg.
127085127215
**
127086127216
** After a database is initialized, the DB_SchemaLoaded bit is set
127087
-** bit is set in the flags field of the Db structure. If the database
127088
-** file was of zero-length, then the DB_Empty flag is also set.
127217
+** bit is set in the flags field of the Db structure.
127089127218
*/
127090127219
SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){
127091127220
int i, rc;
127092127221
int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange);
127093127222
@@ -127719,11 +127848,10 @@
127719127848
sqlite3ExprDelete(db, p->pLimit);
127720127849
#ifndef SQLITE_OMIT_WINDOWFUNC
127721127850
if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){
127722127851
sqlite3WindowListDelete(db, p->pWinDefn);
127723127852
}
127724
- assert( p->pWin==0 );
127725127853
#endif
127726127854
if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith);
127727127855
if( bFree ) sqlite3DbFreeNN(db, p);
127728127856
p = pPrior;
127729127857
bFree = 1;
@@ -131197,10 +131325,42 @@
131197131325
}
131198131326
}while( doPrior && (p = p->pPrior)!=0 );
131199131327
}
131200131328
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
131201131329
131330
+#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
131331
+/*
131332
+** pSelect is a SELECT statement and pSrcItem is one item in the FROM
131333
+** clause of that SELECT.
131334
+**
131335
+** This routine scans the entire SELECT statement and recomputes the
131336
+** pSrcItem->colUsed mask.
131337
+*/
131338
+static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){
131339
+ struct SrcList_item *pItem;
131340
+ if( pExpr->op!=TK_COLUMN ) return WRC_Continue;
131341
+ pItem = pWalker->u.pSrcItem;
131342
+ if( pItem->iCursor!=pExpr->iTable ) return WRC_Continue;
131343
+ if( pExpr->iColumn<0 ) return WRC_Continue;
131344
+ pItem->colUsed |= sqlite3ExprColUsed(pExpr);
131345
+ return WRC_Continue;
131346
+}
131347
+static void recomputeColumnsUsed(
131348
+ Select *pSelect, /* The complete SELECT statement */
131349
+ struct SrcList_item *pSrcItem /* Which FROM clause item to recompute */
131350
+){
131351
+ Walker w;
131352
+ if( NEVER(pSrcItem->pTab==0) ) return;
131353
+ memset(&w, 0, sizeof(w));
131354
+ w.xExprCallback = recomputeColumnsUsedExpr;
131355
+ w.xSelectCallback = sqlite3SelectWalkNoop;
131356
+ w.u.pSrcItem = pSrcItem;
131357
+ pSrcItem->colUsed = 0;
131358
+ sqlite3WalkSelect(&w, pSelect);
131359
+}
131360
+#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
131361
+
131202131362
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
131203131363
/*
131204131364
** This routine attempts to flatten subqueries as a performance optimization.
131205131365
** This routine returns 1 if it makes changes and 0 if no flattening occurs.
131206131366
**
@@ -131735,10 +131895,16 @@
131735131895
*/
131736131896
if( pSub->pLimit ){
131737131897
pParent->pLimit = pSub->pLimit;
131738131898
pSub->pLimit = 0;
131739131899
}
131900
+
131901
+ /* Recompute the SrcList_item.colUsed masks for the flattened
131902
+ ** tables. */
131903
+ for(i=0; i<nSubSrc; i++){
131904
+ recomputeColumnsUsed(pParent, &pSrc->a[i+iFrom]);
131905
+ }
131740131906
}
131741131907
131742131908
/* Finially, delete what is left of the subquery and return
131743131909
** success.
131744131910
*/
@@ -135184,11 +135350,11 @@
135184135350
zDb = pName->a[0].zDatabase;
135185135351
zName = pName->a[0].zName;
135186135352
assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
135187135353
for(i=OMIT_TEMPDB; i<db->nDb; i++){
135188135354
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
135189
- if( zDb && sqlite3StrICmp(db->aDb[j].zDbSName, zDb) ) continue;
135355
+ if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue;
135190135356
assert( sqlite3SchemaMutexHeld(db, j, 0) );
135191135357
pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName);
135192135358
if( pTrigger ) break;
135193135359
}
135194135360
if( !pTrigger ){
@@ -141206,10 +141372,13 @@
141206141372
assert( pRangeEnd==0 && pRangeStart==0 );
141207141373
testcase( pLoop->nSkip>0 );
141208141374
nExtraReg = 1;
141209141375
bSeekPastNull = 1;
141210141376
pLevel->regBignull = regBignull = ++pParse->nMem;
141377
+ if( pLevel->iLeftJoin ){
141378
+ sqlite3VdbeAddOp2(v, OP_Integer, 0, regBignull);
141379
+ }
141211141380
pLevel->addrBignull = sqlite3VdbeMakeLabel(pParse);
141212141381
}
141213141382
141214141383
/* If we are doing a reverse order scan on an ascending index, or
141215141384
** a forward order scan on a descending index, interchange the
@@ -148759,11 +148928,11 @@
148759148928
&& (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0
148760148929
&& (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0
148761148930
&& (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0
148762148931
&& pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED
148763148932
){
148764
- sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */
148933
+ sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);
148765148934
}
148766148935
VdbeComment((v, "%s", pIx->zName));
148767148936
#ifdef SQLITE_ENABLE_COLUMN_USED_MASK
148768148937
{
148769148938
u64 colUsed = 0;
@@ -148917,16 +149086,10 @@
148917149086
for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){
148918149087
sqlite3VdbeJumpHere(v, pIn->addrInTop+1);
148919149088
if( pIn->eEndLoopOp!=OP_Noop ){
148920149089
if( pIn->nPrefix ){
148921149090
assert( pLoop->wsFlags & WHERE_IN_EARLYOUT );
148922
- if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ){
148923
- sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur,
148924
- sqlite3VdbeCurrentAddr(v)+2+(pLevel->iLeftJoin!=0),
148925
- pIn->iBase, pIn->nPrefix);
148926
- VdbeCoverage(v);
148927
- }
148928149091
if( pLevel->iLeftJoin ){
148929149092
/* For LEFT JOIN queries, cursor pIn->iCur may not have been
148930149093
** opened yet. This occurs for WHERE clauses such as
148931149094
** "a = ? AND b IN (...)", where the index is on (a, b). If
148932149095
** the RHS of the (a=?) is NULL, then the "b IN (...)" may
@@ -148933,13 +149096,20 @@
148933149096
** never have been coded, but the body of the loop run to
148934149097
** return the null-row. So, if the cursor is not open yet,
148935149098
** jump over the OP_Next or OP_Prev instruction about to
148936149099
** be coded. */
148937149100
sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur,
148938
- sqlite3VdbeCurrentAddr(v) + 2
149101
+ sqlite3VdbeCurrentAddr(v) + 2 +
149102
+ ((pLoop->wsFlags & WHERE_VIRTUALTABLE)==0)
148939149103
);
148940149104
VdbeCoverage(v);
149105
+ }
149106
+ if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ){
149107
+ sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur,
149108
+ sqlite3VdbeCurrentAddr(v)+2,
149109
+ pIn->iBase, pIn->nPrefix);
149110
+ VdbeCoverage(v);
148941149111
}
148942149112
}
148943149113
sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
148944149114
VdbeCoverage(v);
148945149115
VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Prev);
@@ -150053,10 +150223,11 @@
150053150223
150054150224
ExprList *pSublist = 0; /* Expression list for sub-query */
150055150225
Window *pMWin = p->pWin; /* Master window object */
150056150226
Window *pWin; /* Window object iterator */
150057150227
Table *pTab;
150228
+ u32 selFlags = p->selFlags;
150058150229
150059150230
pTab = sqlite3DbMallocZero(db, sizeof(Table));
150060150231
if( pTab==0 ){
150061150232
return sqlite3ErrorToParser(db, SQLITE_NOMEM);
150062150233
}
@@ -150142,10 +150313,11 @@
150142150313
Table *pTab2;
150143150314
p->pSrc->a[0].pSelect = pSub;
150144150315
sqlite3SrcListAssignCursors(pParse, p->pSrc);
150145150316
pSub->selFlags |= SF_Expanded;
150146150317
pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE);
150318
+ pSub->selFlags |= (selFlags & SF_Aggregate);
150147150319
if( pTab2==0 ){
150148150320
/* Might actually be some other kind of error, but in that case
150149150321
** pParse->nErr will be set, so if SQLITE_NOMEM is set, we will get
150150150322
** the correct error message regardless. */
150151150323
rc = SQLITE_NOMEM;
@@ -151030,10 +151202,11 @@
151030151202
int regArg;
151031151203
int nArg = 0;
151032151204
Window *pWin;
151033151205
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
151034151206
FuncDef *pFunc = pWin->pFunc;
151207
+ assert( pWin->regAccum );
151035151208
sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
151036151209
nArg = MAX(nArg, windowArgCount(pWin));
151037151210
if( pMWin->regStartRowid==0 ){
151038151211
if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){
151039151212
sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp);
@@ -151408,10 +151581,14 @@
151408151581
pNew->eFrmType = p->eFrmType;
151409151582
pNew->eEnd = p->eEnd;
151410151583
pNew->eStart = p->eStart;
151411151584
pNew->eExclude = p->eExclude;
151412151585
pNew->regResult = p->regResult;
151586
+ pNew->regAccum = p->regAccum;
151587
+ pNew->iArgCol = p->iArgCol;
151588
+ pNew->iEphCsr = p->iEphCsr;
151589
+ pNew->bExprArgs = p->bExprArgs;
151413151590
pNew->pStart = sqlite3ExprDup(db, p->pStart, 0);
151414151591
pNew->pEnd = sqlite3ExprDup(db, p->pEnd, 0);
151415151592
pNew->pOwner = pOwner;
151416151593
pNew->bImplicitFrame = p->bImplicitFrame;
151417151594
}
@@ -152245,10 +152422,11 @@
152245152422
if( p ){
152246152423
/* memset(p, 0, sizeof(Expr)); */
152247152424
p->op = (u8)op;
152248152425
p->affExpr = 0;
152249152426
p->flags = EP_Leaf;
152427
+ ExprClearVVAProperties(p);
152250152428
p->iAgg = -1;
152251152429
p->pLeft = p->pRight = 0;
152252152430
p->x.pList = 0;
152253152431
p->pAggInfo = 0;
152254152432
p->y.pTab = 0;
@@ -162084,15 +162262,10 @@
162084162262
createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
162085162263
createCollation(db, "RTRIM", SQLITE_UTF8, 0, rtrimCollFunc, 0);
162086162264
if( db->mallocFailed ){
162087162265
goto opendb_out;
162088162266
}
162089
- /* EVIDENCE-OF: R-08308-17224 The default collating function for all
162090
- ** strings is BINARY.
162091
- */
162092
- db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, sqlite3StrBINARY, 0);
162093
- assert( db->pDfltColl!=0 );
162094162267
162095162268
/* Parse the filename/URI argument
162096162269
**
162097162270
** Only allow sensible combinations of bits in the flags argument.
162098162271
** Throw an error if any non-sense combination is used. If we
@@ -162133,11 +162306,13 @@
162133162306
sqlite3Error(db, rc);
162134162307
goto opendb_out;
162135162308
}
162136162309
sqlite3BtreeEnter(db->aDb[0].pBt);
162137162310
db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
162138
- if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db);
162311
+ if( !db->mallocFailed ){
162312
+ sqlite3SetTextEncoding(db, SCHEMA_ENC(db));
162313
+ }
162139162314
sqlite3BtreeLeave(db->aDb[0].pBt);
162140162315
db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
162141162316
162142162317
/* The default safety_level for the main database is FULL; for the temp
162143162318
** database it is OFF. This matches the pager layer defaults.
@@ -166664,10 +166839,11 @@
166664166839
const char *zEnd = &zCsr[nNode];/* End of interior node buffer */
166665166840
char *zBuffer = 0; /* Buffer to load terms into */
166666166841
i64 nAlloc = 0; /* Size of allocated buffer */
166667166842
int isFirstTerm = 1; /* True when processing first term on page */
166668166843
sqlite3_int64 iChild; /* Block id of child node to descend to */
166844
+ int nBuffer = 0; /* Total term size */
166669166845
166670166846
/* Skip over the 'height' varint that occurs at the start of every
166671166847
** interior node. Then load the blockid of the left-child of the b-tree
166672166848
** node into variable iChild.
166673166849
**
@@ -166688,16 +166864,19 @@
166688166864
166689166865
while( zCsr<zEnd && (piFirst || piLast) ){
166690166866
int cmp; /* memcmp() result */
166691166867
int nSuffix; /* Size of term suffix */
166692166868
int nPrefix = 0; /* Size of term prefix */
166693
- int nBuffer; /* Total term size */
166694166869
166695166870
/* Load the next term on the node into zBuffer. Use realloc() to expand
166696166871
** the size of zBuffer if required. */
166697166872
if( !isFirstTerm ){
166698166873
zCsr += fts3GetVarint32(zCsr, &nPrefix);
166874
+ if( nPrefix>nBuffer ){
166875
+ rc = FTS_CORRUPT_VTAB;
166876
+ goto finish_scan;
166877
+ }
166699166878
}
166700166879
isFirstTerm = 0;
166701166880
zCsr += fts3GetVarint32(zCsr, &nSuffix);
166702166881
166703166882
assert( nPrefix>=0 && nSuffix>=0 );
@@ -179916,10 +180095,16 @@
179916180095
179917180096
/* If nSeg is less that zero, then there is no level with at least
179918180097
** nMin segments and no hint in the %_stat table. No work to do.
179919180098
** Exit early in this case. */
179920180099
if( nSeg<=0 ) break;
180100
+
180101
+ assert( nMod<=0x7FFFFFFF );
180102
+ if( iAbsLevel<0 || iAbsLevel>(nMod<<32) ){
180103
+ rc = FTS_CORRUPT_VTAB;
180104
+ break;
180105
+ }
179921180106
179922180107
/* Open a cursor to iterate through the contents of the oldest nSeg
179923180108
** indexes of absolute level iAbsLevel. If this cursor is opened using
179924180109
** the 'hint' parameters, it is possible that there are less than nSeg
179925180110
** segments available in level iAbsLevel. In this case, no work is
@@ -189652,12 +189837,14 @@
189652189837
pRtree->nAux++;
189653189838
sqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1);
189654189839
}else if( pRtree->nAux>0 ){
189655189840
break;
189656189841
}else{
189842
+ static const char *azFormat[] = {",%.*s REAL", ",%.*s INT"};
189657189843
pRtree->nDim2++;
189658
- sqlite3_str_appendf(pSql, ",%.*s NUM", rtreeTokenLength(zArg), zArg);
189844
+ sqlite3_str_appendf(pSql, azFormat[eCoordType],
189845
+ rtreeTokenLength(zArg), zArg);
189659189846
}
189660189847
}
189661189848
sqlite3_str_appendf(pSql, ");");
189662189849
zSql = sqlite3_str_finish(pSql);
189663189850
if( !zSql ){
@@ -192389,11 +192576,11 @@
192389192576
** 1. uPattern is an unescaped match-all character "%",
192390192577
** 2. uPattern is an unescaped match-one character "_",
192391192578
** 3. uPattern is an unescaped escape character, or
192392192579
** 4. uPattern is to be handled as an ordinary character
192393192580
*/
192394
- if( !prevEscape && uPattern==MATCH_ALL ){
192581
+ if( uPattern==MATCH_ALL && !prevEscape && uPattern!=(uint32_t)uEsc ){
192395192582
/* Case 1. */
192396192583
uint8_t c;
192397192584
192398192585
/* Skip any MATCH_ALL or MATCH_ONE characters that follow a
192399192586
** MATCH_ALL. For each MATCH_ONE, skip one character in the
@@ -192415,16 +192602,16 @@
192415192602
}
192416192603
SQLITE_ICU_SKIP_UTF8(zString);
192417192604
}
192418192605
return 0;
192419192606
192420
- }else if( !prevEscape && uPattern==MATCH_ONE ){
192607
+ }else if( uPattern==MATCH_ONE && !prevEscape && uPattern!=(uint32_t)uEsc ){
192421192608
/* Case 2. */
192422192609
if( *zString==0 ) return 0;
192423192610
SQLITE_ICU_SKIP_UTF8(zString);
192424192611
192425
- }else if( !prevEscape && uPattern==(uint32_t)uEsc){
192612
+ }else if( uPattern==(uint32_t)uEsc && !prevEscape ){
192426192613
/* Case 3. */
192427192614
prevEscape = 1;
192428192615
192429192616
}else{
192430192617
/* Case 4. */
@@ -199222,10 +199409,11 @@
199222199409
}
199223199410
}
199224199411
i = 0;
199225199412
if( iSchema>=0 ){
199226199413
pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i;
199414
+ pIdxInfo->aConstraintUsage[iSchema].omit = 1;
199227199415
pIdxInfo->idxNum |= 0x01;
199228199416
}
199229199417
if( iName>=0 ){
199230199418
pIdxInfo->aConstraintUsage[iName].argvIndex = ++i;
199231199419
pIdxInfo->idxNum |= 0x02;
@@ -199436,11 +199624,13 @@
199436199624
assert( nPayload>=(u32)nLocal );
199437199625
assert( nLocal<=(nUsable-35) );
199438199626
if( nPayload>(u32)nLocal ){
199439199627
int j;
199440199628
int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
199441
- if( iOff+nLocal>nUsable ) goto statPageIsCorrupt;
199629
+ if( iOff+nLocal>nUsable || nPayload>0x7fffffff ){
199630
+ goto statPageIsCorrupt;
199631
+ }
199442199632
pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
199443199633
pCell->nOvfl = nOvfl;
199444199634
pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
199445199635
if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT;
199446199636
pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]);
@@ -203769,11 +203959,11 @@
203769203959
const char *zSep = "";
203770203960
int rc = SQLITE_OK;
203771203961
SessionBuffer buf = {0, 0, 0};
203772203962
int nPk = 0;
203773203963
203774
- sessionAppendStr(&buf, "DELETE FROM ", &rc);
203964
+ sessionAppendStr(&buf, "DELETE FROM main.", &rc);
203775203965
sessionAppendIdent(&buf, zTab, &rc);
203776203966
sessionAppendStr(&buf, " WHERE ", &rc);
203777203967
203778203968
for(i=0; i<p->nCol; i++){
203779203969
if( p->abPK[i] ){
@@ -203852,11 +204042,11 @@
203852204042
int i;
203853204043
const char *zSep = "";
203854204044
SessionBuffer buf = {0, 0, 0};
203855204045
203856204046
/* Append "UPDATE tbl SET " */
203857
- sessionAppendStr(&buf, "UPDATE ", &rc);
204047
+ sessionAppendStr(&buf, "UPDATE main.", &rc);
203858204048
sessionAppendIdent(&buf, zTab, &rc);
203859204049
sessionAppendStr(&buf, " SET ", &rc);
203860204050
203861204051
/* Append the assignments */
203862204052
for(i=0; i<p->nCol; i++){
@@ -223538,11 +223728,11 @@
223538223728
int nArg, /* Number of args */
223539223729
sqlite3_value **apUnused /* Function arguments */
223540223730
){
223541223731
assert( nArg==0 );
223542223732
UNUSED_PARAM2(nArg, apUnused);
223543
- sqlite3_result_text(pCtx, "fts5: 2020-02-27 11:32:14 bfb09371d452d5d4dacab2ec476880bc729952f44ac0e5de90ea7ba203243c8c", -1, SQLITE_TRANSIENT);
223733
+ sqlite3_result_text(pCtx, "fts5: 2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84f525", -1, SQLITE_TRANSIENT);
223544223734
}
223545223735
223546223736
/*
223547223737
** Return true if zName is the extension on one of the shadow tables used
223548223738
** by this module.
@@ -228320,12 +228510,12 @@
228320228510
}
228321228511
#endif /* SQLITE_CORE */
228322228512
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
228323228513
228324228514
/************** End of stmt.c ************************************************/
228325
-#if __LINE__!=228325
228515
+#if __LINE__!=228515
228326228516
#undef SQLITE_SOURCE_ID
228327
-#define SQLITE_SOURCE_ID "2020-02-27 16:21:39 951b39ca74c9bd933139e099d5555283278db475f410f202c162e5d1e6aealt2"
228517
+#define SQLITE_SOURCE_ID "2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84alt2"
228328228518
#endif
228329228519
/* Return the source-id for this library */
228330228520
SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
228331228521
/************************** End of sqlite3.c ******************************/
228332228522
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -1162,11 +1162,11 @@
1162 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
1163 ** [sqlite_version()] and [sqlite_source_id()].
1164 */
1165 #define SQLITE_VERSION "3.32.0"
1166 #define SQLITE_VERSION_NUMBER 3032000
1167 #define SQLITE_SOURCE_ID "2020-02-27 16:21:39 951b39ca74c9bd933139e099d5555283278db475f410f202c162e5d1e6aef933"
1168
1169 /*
1170 ** CAPI3REF: Run-Time Library Version Numbers
1171 ** KEYWORDS: sqlite3_version sqlite3_sourceid
1172 **
@@ -16539,11 +16539,10 @@
16539 ** have been filled out. If the schema changes, these column names might
16540 ** changes and so the view will need to be reset.
16541 */
16542 #define DB_SchemaLoaded 0x0001 /* The schema has been loaded */
16543 #define DB_UnresetViews 0x0002 /* Some views have defined column names */
16544 #define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */
16545 #define DB_ResetWanted 0x0008 /* Reset the schema when nSchemaLock==0 */
16546
16547 /*
16548 ** The number of different kinds of things that can be limited
16549 ** using the sqlite3_limit() interface.
@@ -16697,11 +16696,11 @@
16697 ** Each database connection is an instance of the following structure.
16698 */
16699 struct sqlite3 {
16700 sqlite3_vfs *pVfs; /* OS Interface */
16701 struct Vdbe *pVdbe; /* List of active virtual machines */
16702 CollSeq *pDfltColl; /* The default collating sequence (BINARY) */
16703 sqlite3_mutex *mutex; /* Connection mutex */
16704 Db *aDb; /* All backends */
16705 int nDb; /* Number of backends currently in use */
16706 u32 mDbFlags; /* flags recording internal state */
16707 u64 flags; /* flags settable by pragmas. See below */
@@ -16906,10 +16905,11 @@
16906 #define DBFLAG_PreferBuiltin 0x0002 /* Preference to built-in funcs */
16907 #define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */
16908 #define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */
16909 #define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */
16910 #define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */
 
16911
16912 /*
16913 ** Bits of the sqlite3.dbOptFlags field that are used by the
16914 ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
16915 ** selectively disable various optimizations.
@@ -17869,10 +17869,13 @@
17869 char affExpr; /* affinity, or RAISE type */
17870 u8 op2; /* TK_REGISTER/TK_TRUTH: original value of Expr.op
17871 ** TK_COLUMN: the value of p5 for OP_Column
17872 ** TK_AGG_FUNCTION: nesting depth
17873 ** TK_FUNCTION: NC_SelfRef flag if needs OP_PureFunc */
 
 
 
17874 u32 flags; /* Various flags. EP_* See below */
17875 union {
17876 char *zToken; /* Token value. Zero terminated and dequoted */
17877 int iValue; /* Non-negative integer value if EP_IntValue */
17878 } u;
@@ -17943,11 +17946,11 @@
17943 #define EP_Skip 0x001000 /* Operator does not contribute to affinity */
17944 #define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
17945 #define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
17946 #define EP_Win 0x008000 /* Contains window functions */
17947 #define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */
17948 #define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */
17949 #define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
17950 #define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
17951 #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
17952 #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
17953 #define EP_Alias 0x400000 /* Is an alias for a result set column */
@@ -17957,10 +17960,11 @@
17957 #define EP_Quoted 0x4000000 /* TK_ID was originally quoted */
17958 #define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */
17959 #define EP_IsTrue 0x10000000 /* Always has boolean value of TRUE */
17960 #define EP_IsFalse 0x20000000 /* Always has boolean value of FALSE */
17961 #define EP_FromDDL 0x40000000 /* Originates from sqlite_master */
 
17962
17963 /*
17964 ** The EP_Propagate mask is a set of properties that automatically propagate
17965 ** upwards into parent nodes.
17966 */
@@ -17975,18 +17979,28 @@
17975 #define ExprSetProperty(E,P) (E)->flags|=(P)
17976 #define ExprClearProperty(E,P) (E)->flags&=~(P)
17977 #define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue)
17978 #define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse)
17979
 
 
 
 
 
 
17980 /* The ExprSetVVAProperty() macro is used for Verification, Validation,
17981 ** and Accreditation only. It works like ExprSetProperty() during VVA
17982 ** processes but is a no-op for delivery.
17983 */
17984 #ifdef SQLITE_DEBUG
17985 # define ExprSetVVAProperty(E,P) (E)->flags|=(P)
 
 
17986 #else
17987 # define ExprSetVVAProperty(E,P)
 
 
17988 #endif
17989
17990 /*
17991 ** Macros to determine the number of bytes required by a normal Expr
17992 ** struct, an Expr struct with the EP_Reduced flag set in Expr.flags
@@ -18956,10 +18970,11 @@
18956 Select *pSelect; /* HAVING to WHERE clause ctx */
18957 struct WindowRewrite *pRewrite; /* Window rewrite context */
18958 struct WhereConst *pConst; /* WHERE clause constants */
18959 struct RenameCtx *pRename; /* RENAME COLUMN context */
18960 struct Table *pTab; /* Table of generated column */
 
18961 } u;
18962 };
18963
18964 /* Forward declarations */
18965 SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*);
@@ -19500,11 +19515,11 @@
19500 #ifndef SQLITE_OMIT_GENERATED_COLUMNS
19501 SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Column*, int);
19502 #endif
19503 SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int);
19504 SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
19505 SQLITE_PRIVATE int sqlite3ExprCodeAtInit(Parse*, Expr*, int);
19506 SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
19507 SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
19508 SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
19509 #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */
19510 #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */
@@ -19655,10 +19670,11 @@
19655 # define sqlite3AuthRead(a,b,c,d)
19656 # define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK
19657 # define sqlite3AuthContextPush(a,b,c)
19658 # define sqlite3AuthContextPop(a) ((void)(a))
19659 #endif
 
19660 SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*);
19661 SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*);
19662 SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
19663 SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*);
19664 SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*);
@@ -19714,14 +19730,14 @@
19714 #define putVarint sqlite3PutVarint
19715
19716
19717 SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
19718 SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
19719 SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2);
19720 SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
19721 SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int);
19722 SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr);
19723 SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
19724 SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
19725 SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
19726 SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
19727 SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int);
@@ -19740,13 +19756,14 @@
19740 SQLITE_PRIVATE const char *sqlite3ErrStr(int);
19741 SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse);
19742 SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
19743 SQLITE_PRIVATE int sqlite3IsBinary(const CollSeq*);
19744 SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
19745 SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
19746 SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr);
19747 SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,Expr*,Expr*);
 
19748 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
19749 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
19750 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
19751 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*);
19752 SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
@@ -19809,10 +19826,11 @@
19809 const struct ExprList_item*,
19810 const char*,
19811 const char*,
19812 const char*
19813 );
 
19814 SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*);
19815 SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*);
19816 SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*);
19817 SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
19818 SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
@@ -19973,12 +19991,12 @@
19973 #ifdef SQLITE_ENABLE_NORMALIZE
19974 SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*);
19975 #endif
19976 SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*);
19977 SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*);
19978 SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse*,Expr*);
19979 SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *);
19980 SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*);
19981 SQLITE_PRIVATE const char *sqlite3JournalModename(int);
19982 #ifndef SQLITE_OMIT_WAL
19983 SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*);
19984 SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int);
@@ -20947,13 +20965,13 @@
20947 #endif
20948 u16 nResColumn; /* Number of columns in one row of the result set */
20949 u8 errorAction; /* Recovery action to do in case of an error */
20950 u8 minWriteFileFormat; /* Minimum file format for writable database files */
20951 u8 prepFlags; /* SQLITE_PREPARE_* flags */
 
20952 bft expired:2; /* 1: recompile VM immediately 2: when convenient */
20953 bft explain:2; /* True if EXPLAIN present on SQL command */
20954 bft doingRerun:1; /* True if rerunning after an auto-reprepare */
20955 bft changeCntOn:1; /* True to update the change-counter */
20956 bft runOnlyOnce:1; /* Automatically expire on reset */
20957 bft usesStmtJournal:1; /* True if uses a statement journal */
20958 bft readOnly:1; /* True for statements that do not write */
20959 bft bIsReader:1; /* True for statements that read */
@@ -29390,12 +29408,12 @@
29390 sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName);
29391 }else if( pItem->zName ){
29392 sqlite3_str_appendf(&x, " %s", pItem->zName);
29393 }
29394 if( pItem->pTab ){
29395 sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p",
29396 pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab);
29397 }
29398 if( pItem->zAlias ){
29399 sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias);
29400 }
29401 if( pItem->fg.jointype & JT_LEFT ){
@@ -29650,27 +29668,30 @@
29650 ** Generate a human-readable explanation of an expression tree.
29651 */
29652 SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
29653 const char *zBinOp = 0; /* Binary operator */
29654 const char *zUniOp = 0; /* Unary operator */
29655 char zFlgs[60];
29656 pView = sqlite3TreeViewPush(pView, moreToFollow);
29657 if( pExpr==0 ){
29658 sqlite3TreeViewLine(pView, "nil");
29659 sqlite3TreeViewPop(pView);
29660 return;
29661 }
29662 if( pExpr->flags || pExpr->affExpr ){
29663 StrAccum x;
29664 sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0);
29665 sqlite3_str_appendf(&x, " fg.af=%x.%c",
29666 pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n');
29667 if( ExprHasProperty(pExpr, EP_FromJoin) ){
29668 sqlite3_str_appendf(&x, " iRJT=%d", pExpr->iRightJoinTable);
29669 }
29670 if( ExprHasProperty(pExpr, EP_FromDDL) ){
29671 sqlite3_str_appendf(&x, " DDL");
 
 
 
29672 }
29673 sqlite3StrAccumFinish(&x);
29674 }else{
29675 zFlgs[0] = 0;
29676 }
@@ -29774,10 +29795,11 @@
29774 case TK_SLASH: zBinOp = "DIV"; break;
29775 case TK_LSHIFT: zBinOp = "LSHIFT"; break;
29776 case TK_RSHIFT: zBinOp = "RSHIFT"; break;
29777 case TK_CONCAT: zBinOp = "CONCAT"; break;
29778 case TK_DOT: zBinOp = "DOT"; break;
 
29779
29780 case TK_UMINUS: zUniOp = "UMINUS"; break;
29781 case TK_UPLUS: zUniOp = "UPLUS"; break;
29782 case TK_BITNOT: zUniOp = "BITNOT"; break;
29783 case TK_NOT: zUniOp = "NOT"; break;
@@ -50895,11 +50917,11 @@
50895 }
50896
50897 /*
50898 ** Allocate a new RowSetEntry object that is associated with the
50899 ** given RowSet. Return a pointer to the new and completely uninitialized
50900 ** objected.
50901 **
50902 ** In an OOM situation, the RowSet.db->mallocFailed flag is set and this
50903 ** routine returns NULL.
50904 */
50905 static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){
@@ -51171,11 +51193,11 @@
51171 if( iBatch!=pRowSet->iBatch ){ /*OPTIMIZATION-IF-FALSE*/
51172 p = pRowSet->pEntry;
51173 if( p ){
51174 struct RowSetEntry **ppPrevTree = &pRowSet->pForest;
51175 if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/
51176 /* Only sort the current set of entiries if they need it */
51177 p = rowSetEntrySort(p);
51178 }
51179 for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){
51180 ppPrevTree = &pTree->pRight;
51181 if( pTree->pLeft==0 ){
@@ -64536,11 +64558,11 @@
64536 ** free-list for reuse. It returns false if it is safe to retrieve the
64537 ** page from the pager layer with the 'no-content' flag set. True otherwise.
64538 */
64539 static int btreeGetHasContent(BtShared *pBt, Pgno pgno){
64540 Bitvec *p = pBt->pHasContent;
64541 return (p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTest(p, pgno)));
64542 }
64543
64544 /*
64545 ** Clear (destroy) the BtShared.pHasContent bitvec. This should be
64546 ** invoked at the conclusion of each write-transaction.
@@ -76180,23 +76202,18 @@
76180 u16 mFlags;
76181 if( pVdbe->db->flags & SQLITE_VdbeTrace ){
76182 sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n",
76183 (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem));
76184 }
76185 /* If pX is marked as a shallow copy of pMem, then verify that
76186 ** no significant changes have been made to pX since the OP_SCopy.
76187 ** A significant change would indicated a missed call to this
76188 ** function for pX. Minor changes, such as adding or removing a
76189 ** dual type, are allowed, as long as the underlying value is the
76190 ** same. */
76191 mFlags = pMem->flags & pX->flags & pX->mScopyFlags;
76192 assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i );
76193 /* assert( (mFlags&MEM_Real)==0 || pMem->u.r==pX->u.r ); */
76194 /* ^^ */
76195 /* Cannot reliably compare doubles for equality */
76196 assert( (mFlags&MEM_Str)==0 || (pMem->n==pX->n && pMem->z==pX->z) );
76197 assert( (mFlags&MEM_Blob)==0 || sqlite3BlobCompare(pMem,pX)==0 );
76198
76199 /* pMem is the register that is changing. But also mark pX as
76200 ** undefined so that we can quickly detect the shallow-copy error */
76201 pX->flags = MEM_Undefined;
76202 pX->pScopyFrom = 0;
@@ -77547,11 +77564,11 @@
77547 (void)z2;
77548 }
77549 #endif
77550
77551 /*
77552 ** Add a new OP_ opcode.
77553 **
77554 ** If the bPush flag is true, then make this opcode the parent for
77555 ** subsequent Explains until sqlite3VdbeExplainPop() is called.
77556 */
77557 SQLITE_PRIVATE void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){
@@ -78781,12 +78798,15 @@
78781 displayP4Expr(&x, pOp->p4.pExpr);
78782 break;
78783 }
78784 #endif
78785 case P4_COLLSEQ: {
 
78786 CollSeq *pColl = pOp->p4.pColl;
78787 sqlite3_str_appendf(&x, "(%.20s)", pColl->zName);
 
 
78788 break;
78789 }
78790 case P4_FUNCDEF: {
78791 FuncDef *pDef = pOp->p4.pFunc;
78792 sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg);
@@ -79495,10 +79515,11 @@
79495 "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
79496 "id", "parent", "notused", "detail"
79497 };
79498 int iFirst, mx, i;
79499 if( nMem<10 ) nMem = 10;
 
79500 if( pParse->explain==2 ){
79501 sqlite3VdbeSetNumCols(p, 4);
79502 iFirst = 8;
79503 mx = 12;
79504 }else{
@@ -79545,11 +79566,10 @@
79545 }
79546 }
79547
79548 p->pVList = pParse->pVList;
79549 pParse->pVList = 0;
79550 p->explain = pParse->explain;
79551 if( db->mallocFailed ){
79552 p->nVar = 0;
79553 p->nCursor = 0;
79554 p->nMem = 0;
79555 }else{
@@ -86230,11 +86250,10 @@
86230 u16 flags2; /* Initial flags for P2 */
86231
86232 pIn1 = &aMem[pOp->p1];
86233 pIn2 = &aMem[pOp->p2];
86234 pOut = &aMem[pOp->p3];
86235 testcase( pIn1==pIn2 );
86236 testcase( pOut==pIn2 );
86237 assert( pIn1!=pOut );
86238 flags1 = pIn1->flags;
86239 testcase( flags1 & MEM_Null );
86240 testcase( pIn2->flags & MEM_Null );
@@ -88351,11 +88370,11 @@
88351 **
88352 ** Allowed P5 bits:
88353 ** <ul>
88354 ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
88355 ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88356 ** of OP_SeekLE/OP_IdxGT)
88357 ** </ul>
88358 **
88359 ** The P4 value may be either an integer (P4_INT32) or a pointer to
88360 ** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo
88361 ** object, then table being opened must be an [index b-tree] where the
@@ -88381,11 +88400,11 @@
88381 **
88382 ** Allowed P5 bits:
88383 ** <ul>
88384 ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
88385 ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88386 ** of OP_SeekLE/OP_IdxGT)
88387 ** </ul>
88388 **
88389 ** See also: OP_OpenRead, OP_OpenWrite
88390 */
88391 /* Opcode: OpenWrite P1 P2 P3 P4 P5
@@ -88405,11 +88424,11 @@
88405 **
88406 ** Allowed P5 bits:
88407 ** <ul>
88408 ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
88409 ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88410 ** of OP_SeekLE/OP_IdxGT)
88411 ** <li> <b>0x08 OPFLAG_FORDELETE</b>: This cursor is used only to seek
88412 ** and subsequently delete entries in an index btree. This is a
88413 ** hint to the storage engine that the storage engine is allowed to
88414 ** ignore. The hint is not used by the official SQLite b*tree storage
88415 ** engine, but is used by COMDB2.
@@ -88517,13 +88536,11 @@
88517
88518 open_cursor_set_hints:
88519 assert( OPFLAG_BULKCSR==BTREE_BULKLOAD );
88520 assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ );
88521 testcase( pOp->p5 & OPFLAG_BULKCSR );
88522 #ifdef SQLITE_ENABLE_CURSOR_HINTS
88523 testcase( pOp->p2 & OPFLAG_SEEKEQ );
88524 #endif
88525 sqlite3BtreeCursorHintFlags(pCur->uc.pCursor,
88526 (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ)));
88527 if( rc ) goto abort_due_to_error;
88528 break;
88529 }
@@ -88775,15 +88792,17 @@
88775 ** Reposition cursor P1 so that it points to the smallest entry that
88776 ** is greater than or equal to the key value. If there are no records
88777 ** greater than or equal to the key and P2 is not zero, then jump to P2.
88778 **
88779 ** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
88780 ** opcode will always land on a record that equally equals the key, or
88781 ** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this
88782 ** opcode must be followed by an IdxLE opcode with the same arguments.
88783 ** The IdxLE opcode will be skipped if this opcode succeeds, but the
88784 ** IdxLE opcode will be used on subsequent loop iterations.
 
 
88785 **
88786 ** This opcode leaves the cursor configured to move in forward order,
88787 ** from the beginning toward the end. In other words, the cursor is
88788 ** configured to use Next, not Prev.
88789 **
@@ -88795,11 +88814,11 @@
88795 ** If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
88796 ** use the value in register P3 as a key. If cursor P1 refers
88797 ** to an SQL index, then P3 is the first in an array of P4 registers
88798 ** that are used as an unpacked index key.
88799 **
88800 ** Reposition cursor P1 so that it points to the smallest entry that
88801 ** is greater than the key value. If there are no records greater than
88802 ** the key and P2 is not zero, then jump to P2.
88803 **
88804 ** This opcode leaves the cursor configured to move in forward order,
88805 ** from the beginning toward the end. In other words, the cursor is
@@ -88840,15 +88859,17 @@
88840 ** This opcode leaves the cursor configured to move in reverse order,
88841 ** from the end toward the beginning. In other words, the cursor is
88842 ** configured to use Prev, not Next.
88843 **
88844 ** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
88845 ** opcode will always land on a record that equally equals the key, or
88846 ** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this
88847 ** opcode must be followed by an IdxGE opcode with the same arguments.
88848 ** The IdxGE opcode will be skipped if this opcode succeeds, but the
88849 ** IdxGE opcode will be used on subsequent loop iterations.
 
 
88850 **
88851 ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt
88852 */
88853 case OP_SeekLT: /* jump, in3, group */
88854 case OP_SeekLE: /* jump, in3, group */
@@ -88881,11 +88902,11 @@
88881
88882 pC->deferredMoveto = 0;
88883 pC->cacheStatus = CACHE_STALE;
88884 if( pC->isTable ){
88885 u16 flags3, newType;
88886 /* The BTREE_SEEK_EQ flag is only set on index cursors */
88887 assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0
88888 || CORRUPT_DB );
88889
88890 /* The input value in P3 might be of any type: integer, real, string,
88891 ** blob, or NULL. But it needs to be an integer before we can do
@@ -88940,18 +88961,21 @@
88940 pC->movetoTarget = iKey; /* Used by OP_Delete */
88941 if( rc!=SQLITE_OK ){
88942 goto abort_due_to_error;
88943 }
88944 }else{
88945 /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and
88946 ** OP_SeekLE opcodes are allowed, and these must be immediately followed
88947 ** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key.
 
88948 */
88949 if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){
88950 eqOnly = 1;
88951 assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE );
88952 assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT );
 
 
88953 assert( pOp[1].p1==pOp[0].p1 );
88954 assert( pOp[1].p2==pOp[0].p2 );
88955 assert( pOp[1].p3==pOp[0].p3 );
88956 assert( pOp[1].p4.i==pOp[0].p4.i );
88957 }
@@ -91162,11 +91186,11 @@
91162 ** try to reuse register values from the first use. */
91163 {
91164 int i;
91165 for(i=0; i<p->nMem; i++){
91166 aMem[i].pScopyFrom = 0; /* Prevent false-positive AboutToChange() errs */
91167 aMem[i].flags |= MEM_Undefined; /* Cause a fault if this reg is reused */
91168 }
91169 }
91170 #endif
91171 pOp = &aOp[-1];
91172 goto check_for_interrupt;
@@ -96555,19 +96579,20 @@
96555 SrcList *pSrc;
96556 int i;
96557 struct SrcList_item *pItem;
96558
96559 pSrc = p->pSrc;
96560 assert( pSrc!=0 );
96561 for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
96562 if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){
96563 return WRC_Abort;
96564 }
96565 if( pItem->fg.isTabFunc
96566 && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg)
96567 ){
96568 return WRC_Abort;
 
96569 }
96570 }
96571 return WRC_Continue;
96572 }
96573
@@ -96784,10 +96809,35 @@
96784 }else{
96785 /* Currently parsing a DML statement */
96786 return (db->flags & SQLITE_DqsDML)!=0;
96787 }
96788 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96789
96790 /*
96791 ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
96792 ** that name in the set of source tables in pSrcList and make the pExpr
96793 ** expression node refer back to that source column. The following changes
@@ -96861,10 +96911,16 @@
96861 assert( db->aDb[i].zDbSName );
96862 if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){
96863 pSchema = db->aDb[i].pSchema;
96864 break;
96865 }
 
 
 
 
 
 
96866 }
96867 }
96868 }
96869
96870 /* Start at the inner-most context and move outward until a match is found */
@@ -97181,26 +97237,11 @@
97181 **
97182 ** If a generated column is referenced, set bits for every column
97183 ** of the table.
97184 */
97185 if( pExpr->iColumn>=0 && pMatch!=0 ){
97186 int n = pExpr->iColumn;
97187 Table *pExTab = pExpr->y.pTab;
97188 assert( pExTab!=0 );
97189 assert( pMatch->iCursor==pExpr->iTable );
97190 if( (pExTab->tabFlags & TF_HasGenerated)!=0
97191 && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0
97192 ){
97193 testcase( pExTab->nCol==BMS-1 );
97194 testcase( pExTab->nCol==BMS );
97195 pMatch->colUsed = pExTab->nCol>=BMS ? ALLBITS : MASKBIT(pExTab->nCol)-1;
97196 }else{
97197 testcase( n==BMS-1 );
97198 testcase( n==BMS );
97199 if( n>=BMS ) n = BMS-1;
97200 pMatch->colUsed |= ((Bitmask)1)<<n;
97201 }
97202 }
97203
97204 /* Clean up and return
97205 */
97206 sqlite3ExprDelete(db, pExpr->pLeft);
@@ -98557,11 +98598,11 @@
98557 ** CREATE TABLE t1(a);
98558 ** SELECT * FROM t1 WHERE a;
98559 ** SELECT a AS b FROM t1 WHERE b;
98560 ** SELECT * FROM t1 WHERE (select a from t1);
98561 */
98562 SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){
98563 int op;
98564 while( ExprHasProperty(pExpr, EP_Skip) ){
98565 assert( pExpr->op==TK_COLLATE );
98566 pExpr = pExpr->pLeft;
98567 assert( pExpr!=0 );
@@ -98667,14 +98708,14 @@
98667 ** The collating sequence might be determined by a COLLATE operator
98668 ** or by the presence of a column with a defined collating sequence.
98669 ** COLLATE operators take first precedence. Left operands take
98670 ** precedence over right operands.
98671 */
98672 SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
98673 sqlite3 *db = pParse->db;
98674 CollSeq *pColl = 0;
98675 Expr *p = pExpr;
98676 while( p ){
98677 int op = p->op;
98678 if( op==TK_REGISTER ) op = p->op2;
98679 if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER)
98680 && p->y.pTab!=0
@@ -98739,21 +98780,21 @@
98739 ** See also: sqlite3ExprCollSeq()
98740 **
98741 ** The sqlite3ExprCollSeq() routine works the same except that it
98742 ** returns NULL if there is no defined collation.
98743 */
98744 SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr){
98745 CollSeq *p = sqlite3ExprCollSeq(pParse, pExpr);
98746 if( p==0 ) p = pParse->db->pDfltColl;
98747 assert( p!=0 );
98748 return p;
98749 }
98750
98751 /*
98752 ** Return TRUE if the two expressions have equivalent collating sequences.
98753 */
98754 SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, Expr *pE1, Expr *pE2){
98755 CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1);
98756 CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2);
98757 return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0;
98758 }
98759
@@ -98760,11 +98801,11 @@
98760 /*
98761 ** pExpr is an operand of a comparison operator. aff2 is the
98762 ** type affinity of the other operand. This routine returns the
98763 ** type affinity that should be used for the comparison operator.
98764 */
98765 SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){
98766 char aff1 = sqlite3ExprAffinity(pExpr);
98767 if( aff1>SQLITE_AFF_NONE && aff2>SQLITE_AFF_NONE ){
98768 /* Both sides of the comparison are columns. If one has numeric
98769 ** affinity, use that. Otherwise use no affinity.
98770 */
@@ -98782,11 +98823,11 @@
98782
98783 /*
98784 ** pExpr is a comparison operator. Return the type affinity that should
98785 ** be applied to both operands prior to doing the comparison.
98786 */
98787 static char comparisonAffinity(Expr *pExpr){
98788 char aff;
98789 assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT ||
98790 pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE ||
98791 pExpr->op==TK_NE || pExpr->op==TK_IS || pExpr->op==TK_ISNOT );
98792 assert( pExpr->pLeft );
@@ -98805,11 +98846,11 @@
98805 ** pExpr is a comparison expression, eg. '=', '<', IN(...) etc.
98806 ** idx_affinity is the affinity of an indexed column. Return true
98807 ** if the index with affinity idx_affinity may be used to implement
98808 ** the comparison in pExpr.
98809 */
98810 SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){
98811 char aff = comparisonAffinity(pExpr);
98812 if( aff<SQLITE_AFF_TEXT ){
98813 return 1;
98814 }
98815 if( aff==SQLITE_AFF_TEXT ){
@@ -98820,11 +98861,15 @@
98820
98821 /*
98822 ** Return the P5 value that should be used for a binary comparison
98823 ** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2.
98824 */
98825 static u8 binaryCompareP5(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){
 
 
 
 
98826 u8 aff = (char)sqlite3ExprAffinity(pExpr2);
98827 aff = (u8)sqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull;
98828 return aff;
98829 }
98830
@@ -98840,12 +98885,12 @@
98840 ** Argument pRight (but not pLeft) may be a null pointer. In this case,
98841 ** it is not considered.
98842 */
98843 SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(
98844 Parse *pParse,
98845 Expr *pLeft,
98846 Expr *pRight
98847 ){
98848 CollSeq *pColl;
98849 assert( pLeft );
98850 if( pLeft->flags & EP_Collate ){
98851 pColl = sqlite3ExprCollSeq(pParse, pLeft);
@@ -98866,11 +98911,11 @@
98866 ** This is normally just a wrapper around sqlite3BinaryCompareCollSeq().
98867 ** However, if the OP_Commuted flag is set, then the order of the operands
98868 ** is reversed in the sqlite3BinaryCompareCollSeq() call so that the
98869 ** correct collating sequence is found.
98870 */
98871 SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse *pParse, Expr *p){
98872 if( ExprHasProperty(p, EP_Commuted) ){
98873 return sqlite3BinaryCompareCollSeq(pParse, p->pRight, p->pLeft);
98874 }else{
98875 return sqlite3BinaryCompareCollSeq(pParse, p->pLeft, p->pRight);
98876 }
@@ -99109,10 +99154,11 @@
99109 int regRight = 0;
99110 u8 opx = op;
99111 int addrDone = sqlite3VdbeMakeLabel(pParse);
99112 int isCommuted = ExprHasProperty(pExpr,EP_Commuted);
99113
 
99114 if( pParse->nErr ) return;
99115 if( nLeft!=sqlite3ExprVectorSize(pRight) ){
99116 sqlite3ErrorMsg(pParse, "row value misused");
99117 return;
99118 }
@@ -99721,11 +99767,11 @@
99721 nSize = EXPR_FULLSIZE;
99722 }else{
99723 assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
99724 assert( !ExprHasProperty(p, EP_FromJoin) );
99725 assert( !ExprHasProperty(p, EP_MemToken) );
99726 assert( !ExprHasProperty(p, EP_NoReduce) );
99727 if( p->pLeft || p->x.pList ){
99728 nSize = EXPR_REDUCEDSIZE | EP_Reduced;
99729 }else{
99730 assert( p->pRight==0 );
99731 nSize = EXPR_TOKENONLYSIZE | EP_TokenOnly;
@@ -99826,10 +99872,14 @@
99826
99827 /* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */
99828 pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken);
99829 pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly);
99830 pNew->flags |= staticFlag;
 
 
 
 
99831
99832 /* Copy the p->u.zToken string, if any. */
99833 if( nToken ){
99834 char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize];
99835 memcpy(zToken, p->u.zToken, nToken);
@@ -100602,11 +100652,11 @@
100602 ** (3) the expression does not contain any EP_FixedCol TK_COLUMN
100603 ** operands created by the constant propagation optimization.
100604 **
100605 ** When this routine returns true, it indicates that the expression
100606 ** can be added to the pParse->pConstExpr list and evaluated once when
100607 ** the prepared statement starts up. See sqlite3ExprCodeAtInit().
100608 */
100609 SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
100610 return exprIsConst(p, 2, 0);
100611 }
100612
@@ -101365,10 +101415,11 @@
101365 return;
101366 }
101367
101368 /* Begin coding the subroutine */
101369 ExprSetProperty(pExpr, EP_Subrtn);
 
101370 pExpr->y.sub.regReturn = ++pParse->nMem;
101371 pExpr->y.sub.iAddr =
101372 sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
101373 VdbeComment((v, "return address"));
101374
@@ -101685,10 +101736,11 @@
101685 int addrTruthOp; /* Address of opcode that determines the IN is true */
101686 int destNotNull; /* Jump here if a comparison is not true in step 6 */
101687 int addrTop; /* Top of the step-6 loop */
101688 int iTab = 0; /* Index to use */
101689
 
101690 pLeft = pExpr->pLeft;
101691 if( sqlite3ExprCheckIN(pParse, pExpr) ) return;
101692 zAff = exprINAffinity(pParse, pExpr);
101693 nVector = sqlite3ExprVectorSize(pExpr->pLeft);
101694 aiMap = (int*)sqlite3DbMallocZero(
@@ -102011,11 +102063,11 @@
102011 if( pParse->iSelfTab>0 ){
102012 iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut);
102013 }else{
102014 iAddr = 0;
102015 }
102016 sqlite3ExprCode(pParse, pCol->pDflt, regOut);
102017 if( pCol->affinity>=SQLITE_AFF_TEXT ){
102018 sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1);
102019 }
102020 if( iAddr ) sqlite3VdbeJumpHere(v, iAddr);
102021 }
@@ -102295,10 +102347,11 @@
102295
102296 expr_code_doover:
102297 if( pExpr==0 ){
102298 op = TK_NULL;
102299 }else{
 
102300 op = pExpr->op;
102301 }
102302 switch( op ){
102303 case TK_AGG_COLUMN: {
102304 AggInfo *pAggInfo = pExpr->pAggInfo;
@@ -102305,12 +102358,21 @@
102305 struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg];
102306 if( !pAggInfo->directMode ){
102307 assert( pCol->iMem>0 );
102308 return pCol->iMem;
102309 }else if( pAggInfo->useSortingIdx ){
 
102310 sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
102311 pCol->iSorterColumn, target);
 
 
 
 
 
 
 
 
102312 return target;
102313 }
102314 /* Otherwise, fall thru into the TK_COLUMN case */
102315 }
102316 case TK_COLUMN: {
@@ -102549,10 +102611,11 @@
102549 #endif
102550 }else{
102551 tempX.op = TK_INTEGER;
102552 tempX.flags = EP_IntValue|EP_TokenOnly;
102553 tempX.u.iValue = 0;
 
102554 r1 = sqlite3ExprCodeTemp(pParse, &tempX, &regFree1);
102555 r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree2);
102556 sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target);
102557 testcase( regFree2==0 );
102558 }
@@ -102620,20 +102683,17 @@
102620 return pExpr->y.pWin->regResult;
102621 }
102622 #endif
102623
102624 if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){
102625 /* SQL functions can be expensive. So try to move constant functions
102626 ** out of the inner loop, even if that means an extra OP_Copy. */
102627 return sqlite3ExprCodeAtInit(pParse, pExpr, -1);
102628 }
102629 assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
102630 if( ExprHasProperty(pExpr, EP_TokenOnly) ){
102631 pFarg = 0;
102632 }else{
102633 pFarg = pExpr->x.pList;
102634 }
102635 nFarg = pFarg ? pFarg->nExpr : 0;
102636 assert( !ExprHasProperty(pExpr, EP_IntValue) );
102637 zId = pExpr->u.zToken;
102638 pDef = sqlite3FindFunction(db, zId, nFarg, enc, 0);
102639 #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
@@ -103003,19 +103063,27 @@
103003 sqlite3ReleaseTempReg(pParse, regFree2);
103004 return inReg;
103005 }
103006
103007 /*
103008 ** Factor out the code of the given expression to initialization time.
 
 
 
 
 
 
103009 **
103010 ** If regDest>=0 then the result is always stored in that register and the
103011 ** result is not reusable. If regDest<0 then this routine is free to
103012 ** store the value whereever it wants. The register where the expression
103013 ** is stored is returned. When regDest<0, two identical expressions will
103014 ** code to the same register.
 
 
103015 */
103016 SQLITE_PRIVATE int sqlite3ExprCodeAtInit(
103017 Parse *pParse, /* Parsing context */
103018 Expr *pExpr, /* The expression to code when the VDBE initializes */
103019 int regDest /* Store the value in this register */
103020 ){
103021 ExprList *p;
@@ -103029,18 +103097,33 @@
103029 return pItem->u.iConstExprReg;
103030 }
103031 }
103032 }
103033 pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
103034 p = sqlite3ExprListAppend(pParse, p, pExpr);
103035 if( p ){
103036 struct ExprList_item *pItem = &p->a[p->nExpr-1];
103037 pItem->reusable = regDest<0;
103038 if( regDest<0 ) regDest = ++pParse->nMem;
103039 pItem->u.iConstExprReg = regDest;
103040 }
103041 pParse->pConstExpr = p;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103042 return regDest;
103043 }
103044
103045 /*
103046 ** Generate code to evaluate an expression and store the results
@@ -103061,11 +103144,11 @@
103061 if( ConstFactorOk(pParse)
103062 && pExpr->op!=TK_REGISTER
103063 && sqlite3ExprIsConstantNotJoin(pExpr)
103064 ){
103065 *pReg = 0;
103066 r2 = sqlite3ExprCodeAtInit(pParse, pExpr, -1);
103067 }else{
103068 int r1 = sqlite3GetTempReg(pParse);
103069 r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1);
103070 if( r2==r1 ){
103071 *pReg = r1;
@@ -103083,10 +103166,11 @@
103083 ** in register target.
103084 */
103085 SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){
103086 int inReg;
103087
 
103088 assert( target>0 && target<=pParse->nMem );
103089 inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
103090 assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
103091 if( inReg!=target && pParse->pVdbe ){
103092 u8 op;
@@ -103117,13 +103201,13 @@
103117 ** in register target. If the expression is constant, then this routine
103118 ** might choose to code the expression at initialization time.
103119 */
103120 SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){
103121 if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){
103122 sqlite3ExprCodeAtInit(pParse, pExpr, target);
103123 }else{
103124 sqlite3ExprCode(pParse, pExpr, target);
103125 }
103126 }
103127
103128 /*
103129 ** Generate code that pushes the value of every element of the given
@@ -103177,11 +103261,11 @@
103177 sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i);
103178 }
103179 }else if( (flags & SQLITE_ECEL_FACTOR)!=0
103180 && sqlite3ExprIsConstantNotJoin(pExpr)
103181 ){
103182 sqlite3ExprCodeAtInit(pParse, pExpr, target+i);
103183 }else{
103184 int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i);
103185 if( inReg!=target+i ){
103186 VdbeOp *pOp;
103187 if( copyOp==OP_Copy
@@ -103300,10 +103384,11 @@
103300 int r1, r2;
103301
103302 assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
103303 if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */
103304 if( NEVER(pExpr==0) ) return; /* No way this can happen */
 
103305 op = pExpr->op;
103306 switch( op ){
103307 case TK_AND:
103308 case TK_OR: {
103309 Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
@@ -103441,10 +103526,11 @@
103441 int r1, r2;
103442
103443 assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
103444 if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */
103445 if( pExpr==0 ) return;
 
103446
103447 /* The value of pExpr->op and op are related as follows:
103448 **
103449 ** pExpr->op op
103450 ** --------- ----------
@@ -103724,36 +103810,22 @@
103724 return 2;
103725 }
103726 }
103727 if( (pA->flags & (EP_Distinct|EP_Commuted))
103728 != (pB->flags & (EP_Distinct|EP_Commuted)) ) return 2;
103729 if( (combinedFlags & EP_TokenOnly)==0 ){
103730 if( combinedFlags & EP_xIsSelect ) return 2;
103731 if( (combinedFlags & EP_FixedCol)==0
103732 && sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2;
103733 if( sqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2;
103734 if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2;
103735 if( pA->op!=TK_STRING
103736 && pA->op!=TK_TRUEFALSE
103737 && (combinedFlags & EP_Reduced)==0
103738 ){
103739 if( pA->iColumn!=pB->iColumn ) return 2;
103740 if( pA->op2!=pB->op2 ){
103741 if( pA->op==TK_TRUTH ) return 2;
103742 if( pA->op==TK_FUNCTION && iTab<0 ){
103743 /* Ex: CREATE TABLE t1(a CHECK( a<julianday('now') ));
103744 ** INSERT INTO t1(a) VALUES(julianday('now')+10);
103745 ** Without this test, sqlite3ExprCodeAtInit() will run on the
103746 ** the julianday() of INSERT first, and remember that expression.
103747 ** Then sqlite3ExprCodeInit() will see the julianday() in the CHECK
103748 ** constraint as redundant, reusing the one from the INSERT, even
103749 ** though the julianday() in INSERT lacks the critical NC_IsCheck
103750 ** flag. See ticket [830277d9db6c3ba1] (2019-10-30)
103751 */
103752 return 2;
103753 }
103754 }
103755 if( pA->op!=TK_IN && pA->iTable!=pB->iTable && pA->iTable!=iTab ){
103756 return 2;
103757 }
103758 }
103759 }
@@ -106442,13 +106514,13 @@
106442 /*
106443 ** Three SQL functions - stat_init(), stat_push(), and stat_get() -
106444 ** share an instance of the following structure to hold their state
106445 ** information.
106446 */
106447 typedef struct Stat4Accum Stat4Accum;
106448 typedef struct Stat4Sample Stat4Sample;
106449 struct Stat4Sample {
106450 tRowcnt *anEq; /* sqlite_stat4.nEq */
106451 tRowcnt *anDLt; /* sqlite_stat4.nDLt */
106452 #ifdef SQLITE_ENABLE_STAT4
106453 tRowcnt *anLt; /* sqlite_stat4.nLt */
106454 union {
@@ -106459,31 +106531,33 @@
106459 u8 isPSample; /* True if a periodic sample */
106460 int iCol; /* If !isPSample, the reason for inclusion */
106461 u32 iHash; /* Tiebreaker hash */
106462 #endif
106463 };
106464 struct Stat4Accum {
 
106465 tRowcnt nRow; /* Number of rows in the entire table */
106466 tRowcnt nPSample; /* How often to do a periodic sample */
106467 int nCol; /* Number of columns in index + pk/rowid */
106468 int nKeyCol; /* Number of index columns w/o the pk/rowid */
 
 
 
106469 int mxSample; /* Maximum number of samples to accumulate */
106470 Stat4Sample current; /* Current row as a Stat4Sample */
106471 u32 iPrn; /* Pseudo-random number used for sampling */
106472 Stat4Sample *aBest; /* Array of nCol best samples */
106473 int iMin; /* Index in a[] of entry with minimum score */
106474 int nSample; /* Current number of samples */
106475 int nMaxEqZero; /* Max leading 0 in anEq[] for any a[] entry */
106476 int iGet; /* Index of current sample accessed by stat_get() */
106477 Stat4Sample *a; /* Array of mxSample Stat4Sample objects */
106478 sqlite3 *db; /* Database connection, for malloc() */
106479 };
106480
106481 /* Reclaim memory used by a Stat4Sample
106482 */
106483 #ifdef SQLITE_ENABLE_STAT4
106484 static void sampleClear(sqlite3 *db, Stat4Sample *p){
106485 assert( db!=0 );
106486 if( p->nRowid ){
106487 sqlite3DbFree(db, p->u.aRowid);
106488 p->nRowid = 0;
106489 }
@@ -106491,11 +106565,11 @@
106491 #endif
106492
106493 /* Initialize the BLOB value of a ROWID
106494 */
106495 #ifdef SQLITE_ENABLE_STAT4
106496 static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){
106497 assert( db!=0 );
106498 if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
106499 p->u.aRowid = sqlite3DbMallocRawNN(db, n);
106500 if( p->u.aRowid ){
106501 p->nRowid = n;
@@ -106507,11 +106581,11 @@
106507 #endif
106508
106509 /* Initialize the INTEGER value of a ROWID.
106510 */
106511 #ifdef SQLITE_ENABLE_STAT4
106512 static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){
106513 assert( db!=0 );
106514 if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
106515 p->nRowid = 0;
106516 p->u.iRowid = iRowid;
106517 }
@@ -106520,11 +106594,11 @@
106520
106521 /*
106522 ** Copy the contents of object (*pFrom) into (*pTo).
106523 */
106524 #ifdef SQLITE_ENABLE_STAT4
106525 static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){
106526 pTo->isPSample = pFrom->isPSample;
106527 pTo->iCol = pFrom->iCol;
106528 pTo->iHash = pFrom->iHash;
106529 memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol);
106530 memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol);
@@ -106536,14 +106610,14 @@
106536 }
106537 }
106538 #endif
106539
106540 /*
106541 ** Reclaim all memory of a Stat4Accum structure.
106542 */
106543 static void stat4Destructor(void *pOld){
106544 Stat4Accum *p = (Stat4Accum*)pOld;
106545 #ifdef SQLITE_ENABLE_STAT4
106546 int i;
106547 for(i=0; i<p->nCol; i++) sampleClear(p->db, p->aBest+i);
106548 for(i=0; i<p->mxSample; i++) sampleClear(p->db, p->a+i);
106549 sampleClear(p->db, &p->current);
@@ -106567,21 +106641,21 @@
106567 ** For indexes on ordinary rowid tables, N==K+1. But for indexes on
106568 ** WITHOUT ROWID tables, N=K+P where P is the number of columns in the
106569 ** PRIMARY KEY of the table. The covering index that implements the
106570 ** original WITHOUT ROWID table as N==K as a special case.
106571 **
106572 ** This routine allocates the Stat4Accum object in heap memory. The return
106573 ** value is a pointer to the Stat4Accum object. The datatype of the
106574 ** return value is BLOB, but it is really just a pointer to the Stat4Accum
106575 ** object.
106576 */
106577 static void statInit(
106578 sqlite3_context *context,
106579 int argc,
106580 sqlite3_value **argv
106581 ){
106582 Stat4Accum *p;
106583 int nCol; /* Number of columns in index being sampled */
106584 int nKeyCol; /* Number of key columns */
106585 int nColUp; /* nCol rounded up for alignment */
106586 int n; /* Bytes of space to allocate */
106587 sqlite3 *db; /* Database connection */
@@ -106596,17 +106670,17 @@
106596 nColUp = sizeof(tRowcnt)<8 ? (nCol+1)&~1 : nCol;
106597 nKeyCol = sqlite3_value_int(argv[1]);
106598 assert( nKeyCol<=nCol );
106599 assert( nKeyCol>0 );
106600
106601 /* Allocate the space required for the Stat4Accum object */
106602 n = sizeof(*p)
106603 + sizeof(tRowcnt)*nColUp /* Stat4Accum.anEq */
106604 + sizeof(tRowcnt)*nColUp /* Stat4Accum.anDLt */
106605 #ifdef SQLITE_ENABLE_STAT4
106606 + sizeof(tRowcnt)*nColUp /* Stat4Accum.anLt */
106607 + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */
106608 + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample)
106609 #endif
106610 ;
106611 db = sqlite3_context_db_handle(context);
106612 p = sqlite3DbMallocZero(db, n);
@@ -106631,12 +106705,12 @@
106631 p->mxSample = mxSample;
106632 p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1);
106633 p->current.anLt = &p->current.anEq[nColUp];
106634 p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]);
106635
106636 /* Set up the Stat4Accum.a[] and aBest[] arrays */
106637 p->a = (struct Stat4Sample*)&p->current.anLt[nColUp];
106638 p->aBest = &p->a[mxSample];
106639 pSpace = (u8*)(&p->a[mxSample+nCol]);
106640 for(i=0; i<(mxSample+nCol); i++){
106641 p->a[i].anEq = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp);
106642 p->a[i].anLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp);
@@ -106652,11 +106726,11 @@
106652
106653 /* Return a pointer to the allocated object to the caller. Note that
106654 ** only the pointer (the 2nd parameter) matters. The size of the object
106655 ** (given by the 3rd parameter) is never used and can be any positive
106656 ** value. */
106657 sqlite3_result_blob(context, p, sizeof(*p), stat4Destructor);
106658 }
106659 static const FuncDef statInitFuncdef = {
106660 2+IsStat4, /* nArg */
106661 SQLITE_UTF8, /* funcFlags */
106662 0, /* pUserData */
@@ -106679,13 +106753,13 @@
106679 **
106680 ** This function assumes that for each argument sample, the contents of
106681 ** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid.
106682 */
106683 static int sampleIsBetterPost(
106684 Stat4Accum *pAccum,
106685 Stat4Sample *pNew,
106686 Stat4Sample *pOld
106687 ){
106688 int nCol = pAccum->nCol;
106689 int i;
106690 assert( pNew->iCol==pOld->iCol );
106691 for(i=pNew->iCol+1; i<nCol; i++){
@@ -106703,13 +106777,13 @@
106703 **
106704 ** This function assumes that for each argument sample, the contents of
106705 ** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid.
106706 */
106707 static int sampleIsBetter(
106708 Stat4Accum *pAccum,
106709 Stat4Sample *pNew,
106710 Stat4Sample *pOld
106711 ){
106712 tRowcnt nEqNew = pNew->anEq[pNew->iCol];
106713 tRowcnt nEqOld = pOld->anEq[pOld->iCol];
106714
106715 assert( pOld->isPSample==0 && pNew->isPSample==0 );
@@ -106725,34 +106799,34 @@
106725
106726 /*
106727 ** Copy the contents of sample *pNew into the p->a[] array. If necessary,
106728 ** remove the least desirable sample from p->a[] to make room.
106729 */
106730 static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){
106731 Stat4Sample *pSample = 0;
106732 int i;
106733
106734 assert( IsStat4 || nEqZero==0 );
106735
106736 /* Stat4Accum.nMaxEqZero is set to the maximum number of leading 0
106737 ** values in the anEq[] array of any sample in Stat4Accum.a[]. In
106738 ** other words, if nMaxEqZero is n, then it is guaranteed that there
106739 ** are no samples with Stat4Sample.anEq[m]==0 for (m>=n). */
106740 if( nEqZero>p->nMaxEqZero ){
106741 p->nMaxEqZero = nEqZero;
106742 }
106743 if( pNew->isPSample==0 ){
106744 Stat4Sample *pUpgrade = 0;
106745 assert( pNew->anEq[pNew->iCol]>0 );
106746
106747 /* This sample is being added because the prefix that ends in column
106748 ** iCol occurs many times in the table. However, if we have already
106749 ** added a sample that shares this prefix, there is no need to add
106750 ** this one. Instead, upgrade the priority of the highest priority
106751 ** existing sample that shares this prefix. */
106752 for(i=p->nSample-1; i>=0; i--){
106753 Stat4Sample *pOld = &p->a[i];
106754 if( pOld->anEq[pNew->iCol]==0 ){
106755 if( pOld->isPSample ) return;
106756 assert( pOld->iCol>pNew->iCol );
106757 assert( sampleIsBetter(p, pNew, pOld) );
106758 if( pUpgrade==0 || sampleIsBetter(p, pOld, pUpgrade) ){
@@ -106767,11 +106841,11 @@
106767 }
106768 }
106769
106770 /* If necessary, remove sample iMin to make room for the new sample. */
106771 if( p->nSample>=p->mxSample ){
106772 Stat4Sample *pMin = &p->a[p->iMin];
106773 tRowcnt *anEq = pMin->anEq;
106774 tRowcnt *anLt = pMin->anLt;
106775 tRowcnt *anDLt = pMin->anDLt;
106776 sampleClear(p->db, pMin);
106777 memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1));
@@ -106810,24 +106884,24 @@
106810 p->iMin = iMin;
106811 }
106812 }
106813 #endif /* SQLITE_ENABLE_STAT4 */
106814
 
106815 /*
106816 ** Field iChng of the index being scanned has changed. So at this point
106817 ** p->current contains a sample that reflects the previous row of the
106818 ** index. The value of anEq[iChng] and subsequent anEq[] elements are
106819 ** correct at this point.
106820 */
106821 static void samplePushPrevious(Stat4Accum *p, int iChng){
106822 #ifdef SQLITE_ENABLE_STAT4
106823 int i;
106824
106825 /* Check if any samples from the aBest[] array should be pushed
106826 ** into IndexSample.a[] at this point. */
106827 for(i=(p->nCol-2); i>=iChng; i--){
106828 Stat4Sample *pBest = &p->aBest[i];
106829 pBest->anEq[i] = p->current.anEq[i];
106830 if( p->nSample<p->mxSample || sampleIsBetter(p, pBest, &p->a[p->iMin]) ){
106831 sampleInsert(p, pBest, i);
106832 }
106833 }
@@ -106847,29 +106921,24 @@
106847 if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j];
106848 }
106849 }
106850 p->nMaxEqZero = iChng;
106851 }
106852 #endif
106853
106854 #ifndef SQLITE_ENABLE_STAT4
106855 UNUSED_PARAMETER( p );
106856 UNUSED_PARAMETER( iChng );
106857 #endif
106858 }
 
106859
106860 /*
106861 ** Implementation of the stat_push SQL function: stat_push(P,C,R)
106862 ** Arguments:
106863 **
106864 ** P Pointer to the Stat4Accum object created by stat_init()
106865 ** C Index of left-most column to differ from previous row
106866 ** R Rowid for the current row. Might be a key record for
106867 ** WITHOUT ROWID tables.
106868 **
106869 ** This SQL function always returns NULL. It's purpose it to accumulate
106870 ** statistical data and/or samples in the Stat4Accum object about the
106871 ** index being analyzed. The stat_get() SQL function will later be used to
106872 ** extract relevant information for constructing the sqlite_statN tables.
106873 **
106874 ** The R parameter is only used for STAT4
106875 */
@@ -106879,11 +106948,11 @@
106879 sqlite3_value **argv
106880 ){
106881 int i;
106882
106883 /* The three function arguments */
106884 Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]);
106885 int iChng = sqlite3_value_int(argv[1]);
106886
106887 UNUSED_PARAMETER( argc );
106888 UNUSED_PARAMETER( context );
106889 assert( p->nCol>0 );
@@ -106892,11 +106961,13 @@
106892 if( p->nRow==0 ){
106893 /* This is the first call to this function. Do initialization. */
106894 for(i=0; i<p->nCol; i++) p->current.anEq[i] = 1;
106895 }else{
106896 /* Second and subsequent calls get processed here */
 
106897 samplePushPrevious(p, iChng);
 
106898
106899 /* Update anDLt[], anLt[] and anEq[] to reflect the values that apply
106900 ** to the current row of the index. */
106901 for(i=0; i<iChng; i++){
106902 p->current.anEq[i]++;
@@ -106961,19 +107032,19 @@
106961 #define STAT_GET_NDLT 4 /* "ndlt" column of stat[34] entry */
106962
106963 /*
106964 ** Implementation of the stat_get(P,J) SQL function. This routine is
106965 ** used to query statistical information that has been gathered into
106966 ** the Stat4Accum object by prior calls to stat_push(). The P parameter
106967 ** has type BLOB but it is really just a pointer to the Stat4Accum object.
106968 ** The content to returned is determined by the parameter J
106969 ** which is one of the STAT_GET_xxxx values defined above.
106970 **
106971 ** The stat_get(P,J) function is not available to generic SQL. It is
106972 ** inserted as part of a manually constructed bytecode program. (See
106973 ** the callStatGet() routine below.) It is guaranteed that the P
106974 ** parameter will always be a poiner to a Stat4Accum object, never a
106975 ** NULL.
106976 **
106977 ** If STAT4 is not enabled, then J is always
106978 ** STAT_GET_STAT1 and is hence omitted and this routine becomes
106979 ** a one-parameter function, stat_get(P), that always returns the
@@ -106982,11 +107053,11 @@
106982 static void statGet(
106983 sqlite3_context *context,
106984 int argc,
106985 sqlite3_value **argv
106986 ){
106987 Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]);
106988 #ifdef SQLITE_ENABLE_STAT4
106989 /* STAT4 has a parameter on this routine. */
106990 int eCall = sqlite3_value_int(argv[1]);
106991 assert( argc==2 );
106992 assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ
@@ -107003,11 +107074,11 @@
107003 **
107004 ** The value is a string composed of a list of integers describing
107005 ** the index. The first integer in the list is the total number of
107006 ** entries in the index. There is one additional integer in the list
107007 ** for each indexed column. This additional integer is an estimate of
107008 ** the number of rows matched by a stabbing query on the index using
107009 ** a key with the corresponding number of fields. In other words,
107010 ** if the index is on columns (a,b) and the sqlite_stat1 value is
107011 ** "100 10 2", then SQLite estimates that:
107012 **
107013 ** * the index contains 100 rows,
@@ -107046,11 +107117,11 @@
107046 if( p->iGet<0 ){
107047 samplePushPrevious(p, 0);
107048 p->iGet = 0;
107049 }
107050 if( p->iGet<p->nSample ){
107051 Stat4Sample *pS = p->a + p->iGet;
107052 if( pS->nRowid==0 ){
107053 sqlite3_result_int64(context, pS->u.iRowid);
107054 }else{
107055 sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid,
107056 SQLITE_TRANSIENT);
@@ -107137,11 +107208,11 @@
107137 int i; /* Loop counter */
107138 int jZeroRows = -1; /* Jump from here if number of rows is zero */
107139 int iDb; /* Index of database containing pTab */
107140 u8 needTableCnt = 1; /* True to count the table */
107141 int regNewRowid = iMem++; /* Rowid for the inserted record */
107142 int regStat4 = iMem++; /* Register to hold Stat4Accum object */
107143 int regChng = iMem++; /* Index of changed index field */
107144 #ifdef SQLITE_ENABLE_STAT4
107145 int regRowid = iMem++; /* Rowid argument passed to stat_push() */
107146 #endif
107147 int regTemp = iMem++; /* Temporary use register */
@@ -108105,10 +108176,21 @@
108105 pExpr->op = TK_STRING;
108106 }
108107 }
108108 return rc;
108109 }
 
 
 
 
 
 
 
 
 
 
 
108110
108111 /*
108112 ** An SQL user-function registered to do the work of an ATTACH statement. The
108113 ** three arguments to the function come directly from an attach statement:
108114 **
@@ -108178,13 +108260,12 @@
108178 db->aLimit[SQLITE_LIMIT_ATTACHED]
108179 );
108180 goto attach_error;
108181 }
108182 for(i=0; i<db->nDb; i++){
108183 char *z = db->aDb[i].zDbSName;
108184 assert( z && zName );
108185 if( sqlite3StrICmp(z, zName)==0 ){
108186 zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName);
108187 goto attach_error;
108188 }
108189 }
108190
@@ -108333,11 +108414,11 @@
108333
108334 if( zName==0 ) zName = "";
108335 for(i=0; i<db->nDb; i++){
108336 pDb = &db->aDb[i];
108337 if( pDb->pBt==0 ) continue;
108338 if( sqlite3StrICmp(pDb->zDbSName, zName)==0 ) break;
108339 }
108340
108341 if( i>=db->nDb ){
108342 sqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName);
108343 goto detach_error;
@@ -108524,24 +108605,25 @@
108524 SQLITE_PRIVATE int sqlite3FixSrcList(
108525 DbFixer *pFix, /* Context of the fixation */
108526 SrcList *pList /* The Source list to check and modify */
108527 ){
108528 int i;
108529 const char *zDb;
108530 struct SrcList_item *pItem;
 
 
108531
108532 if( NEVER(pList==0) ) return 0;
108533 zDb = pFix->zDb;
108534 for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
108535 if( pFix->bTemp==0 ){
108536 if( pItem->zDatabase && sqlite3StrICmp(pItem->zDatabase, zDb) ){
108537 sqlite3ErrorMsg(pFix->pParse,
108538 "%s %T cannot reference objects in database %s",
108539 pFix->zType, pFix->pName, pItem->zDatabase);
108540 return 1;
108541 }
108542 sqlite3DbFree(pFix->pParse->db, pItem->zDatabase);
108543 pItem->zDatabase = 0;
108544 pItem->pSchema = pFix->pSchema;
108545 pItem->fg.fromDDL = 1;
108546 }
108547 #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
@@ -109262,11 +109344,11 @@
109262 }
109263 #endif
109264 while(1){
109265 for(i=OMIT_TEMPDB; i<db->nDb; i++){
109266 int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
109267 if( zDatabase==0 || sqlite3StrICmp(zDatabase, db->aDb[j].zDbSName)==0 ){
109268 assert( sqlite3SchemaMutexHeld(db, j, 0) );
109269 p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName);
109270 if( p ) return p;
109271 }
109272 }
@@ -109384,11 +109466,11 @@
109384 assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
109385 for(i=OMIT_TEMPDB; i<db->nDb; i++){
109386 int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
109387 Schema *pSchema = db->aDb[j].pSchema;
109388 assert( pSchema );
109389 if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue;
109390 assert( sqlite3SchemaMutexHeld(db, j, 0) );
109391 p = sqlite3HashFind(&pSchema->idxHash, zName);
109392 if( p ) break;
109393 }
109394 return p;
@@ -111103,10 +111185,36 @@
111103 if( pMod->pModule->iVersion<3 ) return 0;
111104 if( pMod->pModule->xShadowName==0 ) return 0;
111105 return pMod->pModule->xShadowName(zTail+1);
111106 }
111107 #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111108
111109 /*
111110 ** This routine is called to report the final ")" that terminates
111111 ** a CREATE TABLE statement.
111112 **
@@ -111196,10 +111304,12 @@
111196 if( pParse->nErr ){
111197 /* If errors are seen, delete the CHECK constraints now, else they might
111198 ** actually be used if PRAGMA writable_schema=ON is set. */
111199 sqlite3ExprListDelete(db, p->pCheck);
111200 p->pCheck = 0;
 
 
111201 }
111202 }
111203 #endif /* !defined(SQLITE_OMIT_CHECK) */
111204 #ifndef SQLITE_OMIT_GENERATED_COLUMNS
111205 if( p->tabFlags & TF_HasGenerated ){
@@ -114137,20 +114247,33 @@
114137 u8 enc, /* Desired text encoding */
114138 const char *zName, /* Name of the collating sequence. Might be NULL */
114139 int create /* True to create CollSeq if doesn't already exist */
114140 ){
114141 CollSeq *pColl;
 
 
114142 if( zName ){
114143 pColl = findCollSeqEntry(db, zName, create);
 
114144 }else{
114145 pColl = db->pDfltColl;
114146 }
114147 assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 );
114148 assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE );
114149 if( pColl ) pColl += enc-1;
114150 return pColl;
114151 }
 
 
 
 
 
 
 
 
 
 
 
 
 
114152
114153 /*
114154 ** This function is responsible for invoking the collation factory callback
114155 ** or substituting a collation sequence of a different encoding when the
114156 ** requested collation sequence is not available in the desired encoding.
@@ -116321,10 +116444,11 @@
116321 const unsigned char *zA, *zB;
116322 u32 escape;
116323 int nPat;
116324 sqlite3 *db = sqlite3_context_db_handle(context);
116325 struct compareInfo *pInfo = sqlite3_user_data(context);
 
116326
116327 #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
116328 if( sqlite3_value_type(argv[0])==SQLITE_BLOB
116329 || sqlite3_value_type(argv[1])==SQLITE_BLOB
116330 ){
@@ -116356,10 +116480,16 @@
116356 sqlite3_result_error(context,
116357 "ESCAPE expression must be a single character", -1);
116358 return;
116359 }
116360 escape = sqlite3Utf8Read(&zEsc);
 
 
 
 
 
 
116361 }else{
116362 escape = pInfo->matchSet;
116363 }
116364 zB = sqlite3_value_text(argv[0]);
116365 zA = sqlite3_value_text(argv[1]);
@@ -117338,29 +117468,33 @@
117338 if( pDef==0 ) return 0;
117339 #endif
117340 if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){
117341 return 0;
117342 }
117343 if( nExpr<3 ){
117344 aWc[3] = 0;
117345 }else{
117346 Expr *pEscape = pExpr->x.pList->a[2].pExpr;
117347 char *zEscape;
117348 if( pEscape->op!=TK_STRING ) return 0;
117349 zEscape = pEscape->u.zToken;
117350 if( zEscape[0]==0 || zEscape[1]!=0 ) return 0;
117351 aWc[3] = zEscape[0];
117352 }
117353
117354 /* The memcpy() statement assumes that the wildcard characters are
117355 ** the first three statements in the compareInfo structure. The
117356 ** asserts() that follow verify that assumption
117357 */
117358 memcpy(aWc, pDef->pUserData, 3);
117359 assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll );
117360 assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne );
117361 assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117362 *pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0;
117363 return 1;
117364 }
117365
117366 /*
@@ -120564,11 +120698,11 @@
120564 case OE_Replace: {
120565 int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg);
120566 VdbeCoverage(v);
120567 assert( (pCol->colFlags & COLFLAG_GENERATED)==0 );
120568 nSeenReplace++;
120569 sqlite3ExprCode(pParse, pCol->pDflt, iReg);
120570 sqlite3VdbeJumpHere(v, addr1);
120571 break;
120572 }
120573 case OE_Abort:
120574 sqlite3MayAbort(pParse);
@@ -120619,10 +120753,11 @@
120619 ExprList *pCheck = pTab->pCheck;
120620 pParse->iSelfTab = -(regNewData+1);
120621 onError = overrideError!=OE_Default ? overrideError : OE_Abort;
120622 for(i=0; i<pCheck->nExpr; i++){
120623 int allOk;
 
120624 Expr *pExpr = pCheck->a[i].pExpr;
120625 if( aiChng
120626 && !sqlite3ExprReferencesUpdatedColumn(pExpr, aiChng, pkChng)
120627 ){
120628 /* The check constraints do not reference any of the columns being
@@ -120633,11 +120768,15 @@
120633 sqlite3TableAffinity(v, pTab, regNewData+1);
120634 bAffinityDone = 1;
120635 }
120636 allOk = sqlite3VdbeMakeLabel(pParse);
120637 sqlite3VdbeVerifyAbortable(v, onError);
120638 sqlite3ExprIfTrue(pParse, pExpr, allOk, SQLITE_JUMPIFNULL);
 
 
 
 
120639 if( onError==OE_Ignore ){
120640 sqlite3VdbeGoto(v, ignoreDest);
120641 }else{
120642 char *zName = pCheck->a[i].zEName;
120643 if( zName==0 ) zName = pTab->zName;
@@ -125952,25 +126091,16 @@
125952 /* Only change the value of sqlite.enc if the database handle is not
125953 ** initialized. If the main database exists, the new sqlite.enc value
125954 ** will be overwritten when the schema is next loaded. If it does not
125955 ** already exists, it will be created to use the new encoding value.
125956 */
125957 int canChangeEnc = 1; /* True if allowed to change the encoding */
125958 int i; /* For looping over all attached databases */
125959 for(i=0; i<db->nDb; i++){
125960 if( db->aDb[i].pBt!=0
125961 && DbHasProperty(db,i,DB_SchemaLoaded)
125962 && !DbHasProperty(db,i,DB_Empty)
125963 ){
125964 canChangeEnc = 0;
125965 }
125966 }
125967 if( canChangeEnc ){
125968 for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
125969 if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){
125970 SCHEMA_ENC(db) = ENC(db) =
125971 pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
 
125972 break;
125973 }
125974 }
125975 if( !pEnc->zName ){
125976 sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight);
@@ -126775,11 +126905,11 @@
126775 int iDb = pData->iDb;
126776
126777 assert( argc==5 );
126778 UNUSED_PARAMETER2(NotUsed, argc);
126779 assert( sqlite3_mutex_held(db->mutex) );
126780 DbClearProperty(db, iDb, DB_Empty);
126781 pData->nInitRow++;
126782 if( db->mallocFailed ){
126783 corruptSchema(pData, argv[1], 0);
126784 return 1;
126785 }
@@ -126863,10 +126993,11 @@
126863 char const *azArg[6];
126864 int meta[5];
126865 InitData initData;
126866 const char *zMasterName;
126867 int openedTransaction = 0;
 
126868
126869 assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 );
126870 assert( iDb>=0 && iDb<db->nDb );
126871 assert( db->aDb[iDb].pSchema );
126872 assert( sqlite3_mutex_held(db->mutex) );
@@ -126891,10 +127022,11 @@
126891 initData.rc = SQLITE_OK;
126892 initData.pzErrMsg = pzErrMsg;
126893 initData.mInitFlags = mFlags;
126894 initData.nInitRow = 0;
126895 sqlite3InitCallback(&initData, 5, (char **)azArg, 0);
 
126896 if( initData.rc ){
126897 rc = initData.rc;
126898 goto error_out;
126899 }
126900
@@ -126950,31 +127082,29 @@
126950 ** main database, set sqlite3.enc to the encoding of the main database.
126951 ** For an attached db, it is an error if the encoding is not the same
126952 ** as sqlite3.enc.
126953 */
126954 if( meta[BTREE_TEXT_ENCODING-1] ){ /* text encoding */
126955 if( iDb==0 ){
126956 #ifndef SQLITE_OMIT_UTF16
126957 u8 encoding;
 
126958 /* If opening the main database, set ENC(db). */
126959 encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
126960 if( encoding==0 ) encoding = SQLITE_UTF8;
126961 ENC(db) = encoding;
126962 #else
126963 ENC(db) = SQLITE_UTF8;
126964 #endif
 
126965 }else{
126966 /* If opening an attached database, the encoding much match ENC(db) */
126967 if( meta[BTREE_TEXT_ENCODING-1]!=ENC(db) ){
126968 sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
126969 " text encoding as main database");
126970 rc = SQLITE_ERROR;
126971 goto initone_error_out;
126972 }
126973 }
126974 }else{
126975 DbSetProperty(db, iDb, DB_Empty);
126976 }
126977 pDb->pSchema->enc = ENC(db);
126978
126979 if( pDb->pSchema->cache_size==0 ){
126980 #ifndef SQLITE_OMIT_DEPRECATED
@@ -127082,12 +127212,11 @@
127082 ** used to store temporary tables, and any additional database files
127083 ** created using ATTACH statements. Return a success code. If an
127084 ** error occurs, write an error message into *pzErrMsg.
127085 **
127086 ** After a database is initialized, the DB_SchemaLoaded bit is set
127087 ** bit is set in the flags field of the Db structure. If the database
127088 ** file was of zero-length, then the DB_Empty flag is also set.
127089 */
127090 SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){
127091 int i, rc;
127092 int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange);
127093
@@ -127719,11 +127848,10 @@
127719 sqlite3ExprDelete(db, p->pLimit);
127720 #ifndef SQLITE_OMIT_WINDOWFUNC
127721 if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){
127722 sqlite3WindowListDelete(db, p->pWinDefn);
127723 }
127724 assert( p->pWin==0 );
127725 #endif
127726 if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith);
127727 if( bFree ) sqlite3DbFreeNN(db, p);
127728 p = pPrior;
127729 bFree = 1;
@@ -131197,10 +131325,42 @@
131197 }
131198 }while( doPrior && (p = p->pPrior)!=0 );
131199 }
131200 #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
131201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131202 #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
131203 /*
131204 ** This routine attempts to flatten subqueries as a performance optimization.
131205 ** This routine returns 1 if it makes changes and 0 if no flattening occurs.
131206 **
@@ -131735,10 +131895,16 @@
131735 */
131736 if( pSub->pLimit ){
131737 pParent->pLimit = pSub->pLimit;
131738 pSub->pLimit = 0;
131739 }
 
 
 
 
 
 
131740 }
131741
131742 /* Finially, delete what is left of the subquery and return
131743 ** success.
131744 */
@@ -135184,11 +135350,11 @@
135184 zDb = pName->a[0].zDatabase;
135185 zName = pName->a[0].zName;
135186 assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
135187 for(i=OMIT_TEMPDB; i<db->nDb; i++){
135188 int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
135189 if( zDb && sqlite3StrICmp(db->aDb[j].zDbSName, zDb) ) continue;
135190 assert( sqlite3SchemaMutexHeld(db, j, 0) );
135191 pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName);
135192 if( pTrigger ) break;
135193 }
135194 if( !pTrigger ){
@@ -141206,10 +141372,13 @@
141206 assert( pRangeEnd==0 && pRangeStart==0 );
141207 testcase( pLoop->nSkip>0 );
141208 nExtraReg = 1;
141209 bSeekPastNull = 1;
141210 pLevel->regBignull = regBignull = ++pParse->nMem;
 
 
 
141211 pLevel->addrBignull = sqlite3VdbeMakeLabel(pParse);
141212 }
141213
141214 /* If we are doing a reverse order scan on an ascending index, or
141215 ** a forward order scan on a descending index, interchange the
@@ -148759,11 +148928,11 @@
148759 && (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0
148760 && (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0
148761 && (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0
148762 && pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED
148763 ){
148764 sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */
148765 }
148766 VdbeComment((v, "%s", pIx->zName));
148767 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
148768 {
148769 u64 colUsed = 0;
@@ -148917,16 +149086,10 @@
148917 for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){
148918 sqlite3VdbeJumpHere(v, pIn->addrInTop+1);
148919 if( pIn->eEndLoopOp!=OP_Noop ){
148920 if( pIn->nPrefix ){
148921 assert( pLoop->wsFlags & WHERE_IN_EARLYOUT );
148922 if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ){
148923 sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur,
148924 sqlite3VdbeCurrentAddr(v)+2+(pLevel->iLeftJoin!=0),
148925 pIn->iBase, pIn->nPrefix);
148926 VdbeCoverage(v);
148927 }
148928 if( pLevel->iLeftJoin ){
148929 /* For LEFT JOIN queries, cursor pIn->iCur may not have been
148930 ** opened yet. This occurs for WHERE clauses such as
148931 ** "a = ? AND b IN (...)", where the index is on (a, b). If
148932 ** the RHS of the (a=?) is NULL, then the "b IN (...)" may
@@ -148933,13 +149096,20 @@
148933 ** never have been coded, but the body of the loop run to
148934 ** return the null-row. So, if the cursor is not open yet,
148935 ** jump over the OP_Next or OP_Prev instruction about to
148936 ** be coded. */
148937 sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur,
148938 sqlite3VdbeCurrentAddr(v) + 2
 
148939 );
148940 VdbeCoverage(v);
 
 
 
 
 
 
148941 }
148942 }
148943 sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
148944 VdbeCoverage(v);
148945 VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Prev);
@@ -150053,10 +150223,11 @@
150053
150054 ExprList *pSublist = 0; /* Expression list for sub-query */
150055 Window *pMWin = p->pWin; /* Master window object */
150056 Window *pWin; /* Window object iterator */
150057 Table *pTab;
 
150058
150059 pTab = sqlite3DbMallocZero(db, sizeof(Table));
150060 if( pTab==0 ){
150061 return sqlite3ErrorToParser(db, SQLITE_NOMEM);
150062 }
@@ -150142,10 +150313,11 @@
150142 Table *pTab2;
150143 p->pSrc->a[0].pSelect = pSub;
150144 sqlite3SrcListAssignCursors(pParse, p->pSrc);
150145 pSub->selFlags |= SF_Expanded;
150146 pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE);
 
150147 if( pTab2==0 ){
150148 /* Might actually be some other kind of error, but in that case
150149 ** pParse->nErr will be set, so if SQLITE_NOMEM is set, we will get
150150 ** the correct error message regardless. */
150151 rc = SQLITE_NOMEM;
@@ -151030,10 +151202,11 @@
151030 int regArg;
151031 int nArg = 0;
151032 Window *pWin;
151033 for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
151034 FuncDef *pFunc = pWin->pFunc;
 
151035 sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
151036 nArg = MAX(nArg, windowArgCount(pWin));
151037 if( pMWin->regStartRowid==0 ){
151038 if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){
151039 sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp);
@@ -151408,10 +151581,14 @@
151408 pNew->eFrmType = p->eFrmType;
151409 pNew->eEnd = p->eEnd;
151410 pNew->eStart = p->eStart;
151411 pNew->eExclude = p->eExclude;
151412 pNew->regResult = p->regResult;
 
 
 
 
151413 pNew->pStart = sqlite3ExprDup(db, p->pStart, 0);
151414 pNew->pEnd = sqlite3ExprDup(db, p->pEnd, 0);
151415 pNew->pOwner = pOwner;
151416 pNew->bImplicitFrame = p->bImplicitFrame;
151417 }
@@ -152245,10 +152422,11 @@
152245 if( p ){
152246 /* memset(p, 0, sizeof(Expr)); */
152247 p->op = (u8)op;
152248 p->affExpr = 0;
152249 p->flags = EP_Leaf;
 
152250 p->iAgg = -1;
152251 p->pLeft = p->pRight = 0;
152252 p->x.pList = 0;
152253 p->pAggInfo = 0;
152254 p->y.pTab = 0;
@@ -162084,15 +162262,10 @@
162084 createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
162085 createCollation(db, "RTRIM", SQLITE_UTF8, 0, rtrimCollFunc, 0);
162086 if( db->mallocFailed ){
162087 goto opendb_out;
162088 }
162089 /* EVIDENCE-OF: R-08308-17224 The default collating function for all
162090 ** strings is BINARY.
162091 */
162092 db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, sqlite3StrBINARY, 0);
162093 assert( db->pDfltColl!=0 );
162094
162095 /* Parse the filename/URI argument
162096 **
162097 ** Only allow sensible combinations of bits in the flags argument.
162098 ** Throw an error if any non-sense combination is used. If we
@@ -162133,11 +162306,13 @@
162133 sqlite3Error(db, rc);
162134 goto opendb_out;
162135 }
162136 sqlite3BtreeEnter(db->aDb[0].pBt);
162137 db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
162138 if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db);
 
 
162139 sqlite3BtreeLeave(db->aDb[0].pBt);
162140 db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
162141
162142 /* The default safety_level for the main database is FULL; for the temp
162143 ** database it is OFF. This matches the pager layer defaults.
@@ -166664,10 +166839,11 @@
166664 const char *zEnd = &zCsr[nNode];/* End of interior node buffer */
166665 char *zBuffer = 0; /* Buffer to load terms into */
166666 i64 nAlloc = 0; /* Size of allocated buffer */
166667 int isFirstTerm = 1; /* True when processing first term on page */
166668 sqlite3_int64 iChild; /* Block id of child node to descend to */
 
166669
166670 /* Skip over the 'height' varint that occurs at the start of every
166671 ** interior node. Then load the blockid of the left-child of the b-tree
166672 ** node into variable iChild.
166673 **
@@ -166688,16 +166864,19 @@
166688
166689 while( zCsr<zEnd && (piFirst || piLast) ){
166690 int cmp; /* memcmp() result */
166691 int nSuffix; /* Size of term suffix */
166692 int nPrefix = 0; /* Size of term prefix */
166693 int nBuffer; /* Total term size */
166694
166695 /* Load the next term on the node into zBuffer. Use realloc() to expand
166696 ** the size of zBuffer if required. */
166697 if( !isFirstTerm ){
166698 zCsr += fts3GetVarint32(zCsr, &nPrefix);
 
 
 
 
166699 }
166700 isFirstTerm = 0;
166701 zCsr += fts3GetVarint32(zCsr, &nSuffix);
166702
166703 assert( nPrefix>=0 && nSuffix>=0 );
@@ -179916,10 +180095,16 @@
179916
179917 /* If nSeg is less that zero, then there is no level with at least
179918 ** nMin segments and no hint in the %_stat table. No work to do.
179919 ** Exit early in this case. */
179920 if( nSeg<=0 ) break;
 
 
 
 
 
 
179921
179922 /* Open a cursor to iterate through the contents of the oldest nSeg
179923 ** indexes of absolute level iAbsLevel. If this cursor is opened using
179924 ** the 'hint' parameters, it is possible that there are less than nSeg
179925 ** segments available in level iAbsLevel. In this case, no work is
@@ -189652,12 +189837,14 @@
189652 pRtree->nAux++;
189653 sqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1);
189654 }else if( pRtree->nAux>0 ){
189655 break;
189656 }else{
 
189657 pRtree->nDim2++;
189658 sqlite3_str_appendf(pSql, ",%.*s NUM", rtreeTokenLength(zArg), zArg);
 
189659 }
189660 }
189661 sqlite3_str_appendf(pSql, ");");
189662 zSql = sqlite3_str_finish(pSql);
189663 if( !zSql ){
@@ -192389,11 +192576,11 @@
192389 ** 1. uPattern is an unescaped match-all character "%",
192390 ** 2. uPattern is an unescaped match-one character "_",
192391 ** 3. uPattern is an unescaped escape character, or
192392 ** 4. uPattern is to be handled as an ordinary character
192393 */
192394 if( !prevEscape && uPattern==MATCH_ALL ){
192395 /* Case 1. */
192396 uint8_t c;
192397
192398 /* Skip any MATCH_ALL or MATCH_ONE characters that follow a
192399 ** MATCH_ALL. For each MATCH_ONE, skip one character in the
@@ -192415,16 +192602,16 @@
192415 }
192416 SQLITE_ICU_SKIP_UTF8(zString);
192417 }
192418 return 0;
192419
192420 }else if( !prevEscape && uPattern==MATCH_ONE ){
192421 /* Case 2. */
192422 if( *zString==0 ) return 0;
192423 SQLITE_ICU_SKIP_UTF8(zString);
192424
192425 }else if( !prevEscape && uPattern==(uint32_t)uEsc){
192426 /* Case 3. */
192427 prevEscape = 1;
192428
192429 }else{
192430 /* Case 4. */
@@ -199222,10 +199409,11 @@
199222 }
199223 }
199224 i = 0;
199225 if( iSchema>=0 ){
199226 pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i;
 
199227 pIdxInfo->idxNum |= 0x01;
199228 }
199229 if( iName>=0 ){
199230 pIdxInfo->aConstraintUsage[iName].argvIndex = ++i;
199231 pIdxInfo->idxNum |= 0x02;
@@ -199436,11 +199624,13 @@
199436 assert( nPayload>=(u32)nLocal );
199437 assert( nLocal<=(nUsable-35) );
199438 if( nPayload>(u32)nLocal ){
199439 int j;
199440 int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
199441 if( iOff+nLocal>nUsable ) goto statPageIsCorrupt;
 
 
199442 pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
199443 pCell->nOvfl = nOvfl;
199444 pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
199445 if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT;
199446 pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]);
@@ -203769,11 +203959,11 @@
203769 const char *zSep = "";
203770 int rc = SQLITE_OK;
203771 SessionBuffer buf = {0, 0, 0};
203772 int nPk = 0;
203773
203774 sessionAppendStr(&buf, "DELETE FROM ", &rc);
203775 sessionAppendIdent(&buf, zTab, &rc);
203776 sessionAppendStr(&buf, " WHERE ", &rc);
203777
203778 for(i=0; i<p->nCol; i++){
203779 if( p->abPK[i] ){
@@ -203852,11 +204042,11 @@
203852 int i;
203853 const char *zSep = "";
203854 SessionBuffer buf = {0, 0, 0};
203855
203856 /* Append "UPDATE tbl SET " */
203857 sessionAppendStr(&buf, "UPDATE ", &rc);
203858 sessionAppendIdent(&buf, zTab, &rc);
203859 sessionAppendStr(&buf, " SET ", &rc);
203860
203861 /* Append the assignments */
203862 for(i=0; i<p->nCol; i++){
@@ -223538,11 +223728,11 @@
223538 int nArg, /* Number of args */
223539 sqlite3_value **apUnused /* Function arguments */
223540 ){
223541 assert( nArg==0 );
223542 UNUSED_PARAM2(nArg, apUnused);
223543 sqlite3_result_text(pCtx, "fts5: 2020-02-27 11:32:14 bfb09371d452d5d4dacab2ec476880bc729952f44ac0e5de90ea7ba203243c8c", -1, SQLITE_TRANSIENT);
223544 }
223545
223546 /*
223547 ** Return true if zName is the extension on one of the shadow tables used
223548 ** by this module.
@@ -228320,12 +228510,12 @@
228320 }
228321 #endif /* SQLITE_CORE */
228322 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
228323
228324 /************** End of stmt.c ************************************************/
228325 #if __LINE__!=228325
228326 #undef SQLITE_SOURCE_ID
228327 #define SQLITE_SOURCE_ID "2020-02-27 16:21:39 951b39ca74c9bd933139e099d5555283278db475f410f202c162e5d1e6aealt2"
228328 #endif
228329 /* Return the source-id for this library */
228330 SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
228331 /************************** End of sqlite3.c ******************************/
228332
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -1162,11 +1162,11 @@
1162 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
1163 ** [sqlite_version()] and [sqlite_source_id()].
1164 */
1165 #define SQLITE_VERSION "3.32.0"
1166 #define SQLITE_VERSION_NUMBER 3032000
1167 #define SQLITE_SOURCE_ID "2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84f525"
1168
1169 /*
1170 ** CAPI3REF: Run-Time Library Version Numbers
1171 ** KEYWORDS: sqlite3_version sqlite3_sourceid
1172 **
@@ -16539,11 +16539,10 @@
16539 ** have been filled out. If the schema changes, these column names might
16540 ** changes and so the view will need to be reset.
16541 */
16542 #define DB_SchemaLoaded 0x0001 /* The schema has been loaded */
16543 #define DB_UnresetViews 0x0002 /* Some views have defined column names */
 
16544 #define DB_ResetWanted 0x0008 /* Reset the schema when nSchemaLock==0 */
16545
16546 /*
16547 ** The number of different kinds of things that can be limited
16548 ** using the sqlite3_limit() interface.
@@ -16697,11 +16696,11 @@
16696 ** Each database connection is an instance of the following structure.
16697 */
16698 struct sqlite3 {
16699 sqlite3_vfs *pVfs; /* OS Interface */
16700 struct Vdbe *pVdbe; /* List of active virtual machines */
16701 CollSeq *pDfltColl; /* BINARY collseq for the database encoding */
16702 sqlite3_mutex *mutex; /* Connection mutex */
16703 Db *aDb; /* All backends */
16704 int nDb; /* Number of backends currently in use */
16705 u32 mDbFlags; /* flags recording internal state */
16706 u64 flags; /* flags settable by pragmas. See below */
@@ -16906,10 +16905,11 @@
16905 #define DBFLAG_PreferBuiltin 0x0002 /* Preference to built-in funcs */
16906 #define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */
16907 #define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */
16908 #define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */
16909 #define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */
16910 #define DBFLAG_EncodingFixed 0x0040 /* No longer possible to change enc. */
16911
16912 /*
16913 ** Bits of the sqlite3.dbOptFlags field that are used by the
16914 ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
16915 ** selectively disable various optimizations.
@@ -17869,10 +17869,13 @@
17869 char affExpr; /* affinity, or RAISE type */
17870 u8 op2; /* TK_REGISTER/TK_TRUTH: original value of Expr.op
17871 ** TK_COLUMN: the value of p5 for OP_Column
17872 ** TK_AGG_FUNCTION: nesting depth
17873 ** TK_FUNCTION: NC_SelfRef flag if needs OP_PureFunc */
17874 #ifdef SQLITE_DEBUG
17875 u8 vvaFlags; /* Verification flags. */
17876 #endif
17877 u32 flags; /* Various flags. EP_* See below */
17878 union {
17879 char *zToken; /* Token value. Zero terminated and dequoted */
17880 int iValue; /* Non-negative integer value if EP_IntValue */
17881 } u;
@@ -17943,11 +17946,11 @@
17946 #define EP_Skip 0x001000 /* Operator does not contribute to affinity */
17947 #define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
17948 #define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
17949 #define EP_Win 0x008000 /* Contains window functions */
17950 #define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */
17951 /* 0x020000 // available for reuse */
17952 #define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
17953 #define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
17954 #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
17955 #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
17956 #define EP_Alias 0x400000 /* Is an alias for a result set column */
@@ -17957,10 +17960,11 @@
17960 #define EP_Quoted 0x4000000 /* TK_ID was originally quoted */
17961 #define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */
17962 #define EP_IsTrue 0x10000000 /* Always has boolean value of TRUE */
17963 #define EP_IsFalse 0x20000000 /* Always has boolean value of FALSE */
17964 #define EP_FromDDL 0x40000000 /* Originates from sqlite_master */
17965 /* 0x80000000 // Available */
17966
17967 /*
17968 ** The EP_Propagate mask is a set of properties that automatically propagate
17969 ** upwards into parent nodes.
17970 */
@@ -17975,18 +17979,28 @@
17979 #define ExprSetProperty(E,P) (E)->flags|=(P)
17980 #define ExprClearProperty(E,P) (E)->flags&=~(P)
17981 #define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue)
17982 #define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse)
17983
17984
17985 /* Flags for use with Expr.vvaFlags
17986 */
17987 #define EP_NoReduce 0x01 /* Cannot EXPRDUP_REDUCE this Expr */
17988 #define EP_Immutable 0x02 /* Do not change this Expr node */
17989
17990 /* The ExprSetVVAProperty() macro is used for Verification, Validation,
17991 ** and Accreditation only. It works like ExprSetProperty() during VVA
17992 ** processes but is a no-op for delivery.
17993 */
17994 #ifdef SQLITE_DEBUG
17995 # define ExprSetVVAProperty(E,P) (E)->vvaFlags|=(P)
17996 # define ExprHasVVAProperty(E,P) (((E)->vvaFlags&(P))!=0)
17997 # define ExprClearVVAProperties(E) (E)->vvaFlags = 0
17998 #else
17999 # define ExprSetVVAProperty(E,P)
18000 # define ExprHasVVAProperty(E,P) 0
18001 # define ExprClearVVAProperties(E)
18002 #endif
18003
18004 /*
18005 ** Macros to determine the number of bytes required by a normal Expr
18006 ** struct, an Expr struct with the EP_Reduced flag set in Expr.flags
@@ -18956,10 +18970,11 @@
18970 Select *pSelect; /* HAVING to WHERE clause ctx */
18971 struct WindowRewrite *pRewrite; /* Window rewrite context */
18972 struct WhereConst *pConst; /* WHERE clause constants */
18973 struct RenameCtx *pRename; /* RENAME COLUMN context */
18974 struct Table *pTab; /* Table of generated column */
18975 struct SrcList_item *pSrcItem; /* A single FROM clause item */
18976 } u;
18977 };
18978
18979 /* Forward declarations */
18980 SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*);
@@ -19500,11 +19515,11 @@
19515 #ifndef SQLITE_OMIT_GENERATED_COLUMNS
19516 SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Column*, int);
19517 #endif
19518 SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int);
19519 SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
19520 SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int);
19521 SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
19522 SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
19523 SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
19524 #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */
19525 #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */
@@ -19655,10 +19670,11 @@
19670 # define sqlite3AuthRead(a,b,c,d)
19671 # define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK
19672 # define sqlite3AuthContextPush(a,b,c)
19673 # define sqlite3AuthContextPop(a) ((void)(a))
19674 #endif
19675 SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName);
19676 SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*);
19677 SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*);
19678 SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
19679 SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*);
19680 SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*);
@@ -19714,14 +19730,14 @@
19730 #define putVarint sqlite3PutVarint
19731
19732
19733 SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
19734 SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
19735 SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2);
19736 SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity);
19737 SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int);
19738 SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr);
19739 SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
19740 SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
19741 SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
19742 SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
19743 SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int);
@@ -19740,13 +19756,14 @@
19756 SQLITE_PRIVATE const char *sqlite3ErrStr(int);
19757 SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse);
19758 SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
19759 SQLITE_PRIVATE int sqlite3IsBinary(const CollSeq*);
19760 SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
19761 SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8);
19762 SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr);
19763 SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr);
19764 SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*);
19765 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
19766 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
19767 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
19768 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*);
19769 SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
@@ -19809,10 +19826,11 @@
19826 const struct ExprList_item*,
19827 const char*,
19828 const char*,
19829 const char*
19830 );
19831 SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr*);
19832 SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*);
19833 SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*);
19834 SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*);
19835 SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
19836 SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
@@ -19973,12 +19991,12 @@
19991 #ifdef SQLITE_ENABLE_NORMALIZE
19992 SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*);
19993 #endif
19994 SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*);
19995 SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*);
19996 SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse*,const Expr*);
19997 SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, const Expr*, const Expr*);
19998 SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*);
19999 SQLITE_PRIVATE const char *sqlite3JournalModename(int);
20000 #ifndef SQLITE_OMIT_WAL
20001 SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*);
20002 SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int);
@@ -20947,13 +20965,13 @@
20965 #endif
20966 u16 nResColumn; /* Number of columns in one row of the result set */
20967 u8 errorAction; /* Recovery action to do in case of an error */
20968 u8 minWriteFileFormat; /* Minimum file format for writable database files */
20969 u8 prepFlags; /* SQLITE_PREPARE_* flags */
20970 u8 doingRerun; /* True if rerunning after an auto-reprepare */
20971 bft expired:2; /* 1: recompile VM immediately 2: when convenient */
20972 bft explain:2; /* True if EXPLAIN present on SQL command */
 
20973 bft changeCntOn:1; /* True to update the change-counter */
20974 bft runOnlyOnce:1; /* Automatically expire on reset */
20975 bft usesStmtJournal:1; /* True if uses a statement journal */
20976 bft readOnly:1; /* True for statements that do not write */
20977 bft bIsReader:1; /* True for statements that read */
@@ -29390,12 +29408,12 @@
29408 sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName);
29409 }else if( pItem->zName ){
29410 sqlite3_str_appendf(&x, " %s", pItem->zName);
29411 }
29412 if( pItem->pTab ){
29413 sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx",
29414 pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab, pItem->colUsed);
29415 }
29416 if( pItem->zAlias ){
29417 sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias);
29418 }
29419 if( pItem->fg.jointype & JT_LEFT ){
@@ -29650,27 +29668,30 @@
29668 ** Generate a human-readable explanation of an expression tree.
29669 */
29670 SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
29671 const char *zBinOp = 0; /* Binary operator */
29672 const char *zUniOp = 0; /* Unary operator */
29673 char zFlgs[200];
29674 pView = sqlite3TreeViewPush(pView, moreToFollow);
29675 if( pExpr==0 ){
29676 sqlite3TreeViewLine(pView, "nil");
29677 sqlite3TreeViewPop(pView);
29678 return;
29679 }
29680 if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){
29681 StrAccum x;
29682 sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0);
29683 sqlite3_str_appendf(&x, " fg.af=%x.%c",
29684 pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n');
29685 if( ExprHasProperty(pExpr, EP_FromJoin) ){
29686 sqlite3_str_appendf(&x, " iRJT=%d", pExpr->iRightJoinTable);
29687 }
29688 if( ExprHasProperty(pExpr, EP_FromDDL) ){
29689 sqlite3_str_appendf(&x, " DDL");
29690 }
29691 if( ExprHasVVAProperty(pExpr, EP_Immutable) ){
29692 sqlite3_str_appendf(&x, " IMMUTABLE");
29693 }
29694 sqlite3StrAccumFinish(&x);
29695 }else{
29696 zFlgs[0] = 0;
29697 }
@@ -29774,10 +29795,11 @@
29795 case TK_SLASH: zBinOp = "DIV"; break;
29796 case TK_LSHIFT: zBinOp = "LSHIFT"; break;
29797 case TK_RSHIFT: zBinOp = "RSHIFT"; break;
29798 case TK_CONCAT: zBinOp = "CONCAT"; break;
29799 case TK_DOT: zBinOp = "DOT"; break;
29800 case TK_LIMIT: zBinOp = "LIMIT"; break;
29801
29802 case TK_UMINUS: zUniOp = "UMINUS"; break;
29803 case TK_UPLUS: zUniOp = "UPLUS"; break;
29804 case TK_BITNOT: zUniOp = "BITNOT"; break;
29805 case TK_NOT: zUniOp = "NOT"; break;
@@ -50895,11 +50917,11 @@
50917 }
50918
50919 /*
50920 ** Allocate a new RowSetEntry object that is associated with the
50921 ** given RowSet. Return a pointer to the new and completely uninitialized
50922 ** object.
50923 **
50924 ** In an OOM situation, the RowSet.db->mallocFailed flag is set and this
50925 ** routine returns NULL.
50926 */
50927 static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){
@@ -51171,11 +51193,11 @@
51193 if( iBatch!=pRowSet->iBatch ){ /*OPTIMIZATION-IF-FALSE*/
51194 p = pRowSet->pEntry;
51195 if( p ){
51196 struct RowSetEntry **ppPrevTree = &pRowSet->pForest;
51197 if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/
51198 /* Only sort the current set of entries if they need it */
51199 p = rowSetEntrySort(p);
51200 }
51201 for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){
51202 ppPrevTree = &pTree->pRight;
51203 if( pTree->pLeft==0 ){
@@ -64536,11 +64558,11 @@
64558 ** free-list for reuse. It returns false if it is safe to retrieve the
64559 ** page from the pager layer with the 'no-content' flag set. True otherwise.
64560 */
64561 static int btreeGetHasContent(BtShared *pBt, Pgno pgno){
64562 Bitvec *p = pBt->pHasContent;
64563 return p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTestNotNull(p, pgno));
64564 }
64565
64566 /*
64567 ** Clear (destroy) the BtShared.pHasContent bitvec. This should be
64568 ** invoked at the conclusion of each write-transaction.
@@ -76180,23 +76202,18 @@
76202 u16 mFlags;
76203 if( pVdbe->db->flags & SQLITE_VdbeTrace ){
76204 sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n",
76205 (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem));
76206 }
76207 /* If pX is marked as a shallow copy of pMem, then try to verify that
76208 ** no significant changes have been made to pX since the OP_SCopy.
76209 ** A significant change would indicated a missed call to this
76210 ** function for pX. Minor changes, such as adding or removing a
76211 ** dual type, are allowed, as long as the underlying value is the
76212 ** same. */
76213 mFlags = pMem->flags & pX->flags & pX->mScopyFlags;
76214 assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i );
 
 
 
 
 
76215
76216 /* pMem is the register that is changing. But also mark pX as
76217 ** undefined so that we can quickly detect the shallow-copy error */
76218 pX->flags = MEM_Undefined;
76219 pX->pScopyFrom = 0;
@@ -77547,11 +77564,11 @@
77564 (void)z2;
77565 }
77566 #endif
77567
77568 /*
77569 ** Add a new OP_Explain opcode.
77570 **
77571 ** If the bPush flag is true, then make this opcode the parent for
77572 ** subsequent Explains until sqlite3VdbeExplainPop() is called.
77573 */
77574 SQLITE_PRIVATE void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){
@@ -78781,12 +78798,15 @@
78798 displayP4Expr(&x, pOp->p4.pExpr);
78799 break;
78800 }
78801 #endif
78802 case P4_COLLSEQ: {
78803 static const char *const encnames[] = {"?", "8", "16LE", "16BE"};
78804 CollSeq *pColl = pOp->p4.pColl;
78805 assert( pColl->enc>=0 && pColl->enc<4 );
78806 sqlite3_str_appendf(&x, "%.18s-%s", pColl->zName,
78807 encnames[pColl->enc]);
78808 break;
78809 }
78810 case P4_FUNCDEF: {
78811 FuncDef *pDef = pOp->p4.pFunc;
78812 sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg);
@@ -79495,10 +79515,11 @@
79515 "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
79516 "id", "parent", "notused", "detail"
79517 };
79518 int iFirst, mx, i;
79519 if( nMem<10 ) nMem = 10;
79520 p->explain = pParse->explain;
79521 if( pParse->explain==2 ){
79522 sqlite3VdbeSetNumCols(p, 4);
79523 iFirst = 8;
79524 mx = 12;
79525 }else{
@@ -79545,11 +79566,10 @@
79566 }
79567 }
79568
79569 p->pVList = pParse->pVList;
79570 pParse->pVList = 0;
 
79571 if( db->mallocFailed ){
79572 p->nVar = 0;
79573 p->nCursor = 0;
79574 p->nMem = 0;
79575 }else{
@@ -86230,11 +86250,10 @@
86250 u16 flags2; /* Initial flags for P2 */
86251
86252 pIn1 = &aMem[pOp->p1];
86253 pIn2 = &aMem[pOp->p2];
86254 pOut = &aMem[pOp->p3];
 
86255 testcase( pOut==pIn2 );
86256 assert( pIn1!=pOut );
86257 flags1 = pIn1->flags;
86258 testcase( flags1 & MEM_Null );
86259 testcase( pIn2->flags & MEM_Null );
@@ -88351,11 +88370,11 @@
88370 **
88371 ** Allowed P5 bits:
88372 ** <ul>
88373 ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
88374 ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88375 ** of OP_SeekLE/OP_IdxLT)
88376 ** </ul>
88377 **
88378 ** The P4 value may be either an integer (P4_INT32) or a pointer to
88379 ** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo
88380 ** object, then table being opened must be an [index b-tree] where the
@@ -88381,11 +88400,11 @@
88400 **
88401 ** Allowed P5 bits:
88402 ** <ul>
88403 ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
88404 ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88405 ** of OP_SeekLE/OP_IdxLT)
88406 ** </ul>
88407 **
88408 ** See also: OP_OpenRead, OP_OpenWrite
88409 */
88410 /* Opcode: OpenWrite P1 P2 P3 P4 P5
@@ -88405,11 +88424,11 @@
88424 **
88425 ** Allowed P5 bits:
88426 ** <ul>
88427 ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
88428 ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
88429 ** of OP_SeekLE/OP_IdxLT)
88430 ** <li> <b>0x08 OPFLAG_FORDELETE</b>: This cursor is used only to seek
88431 ** and subsequently delete entries in an index btree. This is a
88432 ** hint to the storage engine that the storage engine is allowed to
88433 ** ignore. The hint is not used by the official SQLite b*tree storage
88434 ** engine, but is used by COMDB2.
@@ -88517,13 +88536,11 @@
88536
88537 open_cursor_set_hints:
88538 assert( OPFLAG_BULKCSR==BTREE_BULKLOAD );
88539 assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ );
88540 testcase( pOp->p5 & OPFLAG_BULKCSR );
 
88541 testcase( pOp->p2 & OPFLAG_SEEKEQ );
 
88542 sqlite3BtreeCursorHintFlags(pCur->uc.pCursor,
88543 (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ)));
88544 if( rc ) goto abort_due_to_error;
88545 break;
88546 }
@@ -88775,15 +88792,17 @@
88792 ** Reposition cursor P1 so that it points to the smallest entry that
88793 ** is greater than or equal to the key value. If there are no records
88794 ** greater than or equal to the key and P2 is not zero, then jump to P2.
88795 **
88796 ** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
88797 ** opcode will either land on a record that exactly matches the key, or
88798 ** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ,
88799 ** this opcode must be followed by an IdxLE opcode with the same arguments.
88800 ** The IdxGT opcode will be skipped if this opcode succeeds, but the
88801 ** IdxGT opcode will be used on subsequent loop iterations. The
88802 ** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this
88803 ** is an equality search.
88804 **
88805 ** This opcode leaves the cursor configured to move in forward order,
88806 ** from the beginning toward the end. In other words, the cursor is
88807 ** configured to use Next, not Prev.
88808 **
@@ -88795,11 +88814,11 @@
88814 ** If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
88815 ** use the value in register P3 as a key. If cursor P1 refers
88816 ** to an SQL index, then P3 is the first in an array of P4 registers
88817 ** that are used as an unpacked index key.
88818 **
88819 ** Reposition cursor P1 so that it points to the smallest entry that
88820 ** is greater than the key value. If there are no records greater than
88821 ** the key and P2 is not zero, then jump to P2.
88822 **
88823 ** This opcode leaves the cursor configured to move in forward order,
88824 ** from the beginning toward the end. In other words, the cursor is
@@ -88840,15 +88859,17 @@
88859 ** This opcode leaves the cursor configured to move in reverse order,
88860 ** from the end toward the beginning. In other words, the cursor is
88861 ** configured to use Prev, not Next.
88862 **
88863 ** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
88864 ** opcode will either land on a record that exactly matches the key, or
88865 ** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ,
88866 ** this opcode must be followed by an IdxLE opcode with the same arguments.
88867 ** The IdxGE opcode will be skipped if this opcode succeeds, but the
88868 ** IdxGE opcode will be used on subsequent loop iterations. The
88869 ** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this
88870 ** is an equality search.
88871 **
88872 ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt
88873 */
88874 case OP_SeekLT: /* jump, in3, group */
88875 case OP_SeekLE: /* jump, in3, group */
@@ -88881,11 +88902,11 @@
88902
88903 pC->deferredMoveto = 0;
88904 pC->cacheStatus = CACHE_STALE;
88905 if( pC->isTable ){
88906 u16 flags3, newType;
88907 /* The OPFLAG_SEEKEQ/BTREE_SEEK_EQ flag is only set on index cursors */
88908 assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0
88909 || CORRUPT_DB );
88910
88911 /* The input value in P3 might be of any type: integer, real, string,
88912 ** blob, or NULL. But it needs to be an integer before we can do
@@ -88940,18 +88961,21 @@
88961 pC->movetoTarget = iKey; /* Used by OP_Delete */
88962 if( rc!=SQLITE_OK ){
88963 goto abort_due_to_error;
88964 }
88965 }else{
88966 /* For a cursor with the OPFLAG_SEEKEQ/BTREE_SEEK_EQ hint, only the
88967 ** OP_SeekGE and OP_SeekLE opcodes are allowed, and these must be
88968 ** immediately followed by an OP_IdxGT or OP_IdxLT opcode, respectively,
88969 ** with the same key.
88970 */
88971 if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){
88972 eqOnly = 1;
88973 assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE );
88974 assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT );
88975 assert( pOp->opcode==OP_SeekGE || pOp[1].opcode==OP_IdxLT );
88976 assert( pOp->opcode==OP_SeekLE || pOp[1].opcode==OP_IdxGT );
88977 assert( pOp[1].p1==pOp[0].p1 );
88978 assert( pOp[1].p2==pOp[0].p2 );
88979 assert( pOp[1].p3==pOp[0].p3 );
88980 assert( pOp[1].p4.i==pOp[0].p4.i );
88981 }
@@ -91162,11 +91186,11 @@
91186 ** try to reuse register values from the first use. */
91187 {
91188 int i;
91189 for(i=0; i<p->nMem; i++){
91190 aMem[i].pScopyFrom = 0; /* Prevent false-positive AboutToChange() errs */
91191 MemSetTypeFlag(&aMem[i], MEM_Undefined); /* Fault if this reg is reused */
91192 }
91193 }
91194 #endif
91195 pOp = &aOp[-1];
91196 goto check_for_interrupt;
@@ -96555,19 +96579,20 @@
96579 SrcList *pSrc;
96580 int i;
96581 struct SrcList_item *pItem;
96582
96583 pSrc = p->pSrc;
96584 if( pSrc ){
96585 for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
96586 if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){
96587 return WRC_Abort;
96588 }
96589 if( pItem->fg.isTabFunc
96590 && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg)
96591 ){
96592 return WRC_Abort;
96593 }
96594 }
96595 }
96596 return WRC_Continue;
96597 }
96598
@@ -96784,10 +96809,35 @@
96809 }else{
96810 /* Currently parsing a DML statement */
96811 return (db->flags & SQLITE_DqsDML)!=0;
96812 }
96813 }
96814
96815 /*
96816 ** The argument is guaranteed to be a non-NULL Expr node of type TK_COLUMN.
96817 ** return the appropriate colUsed mask.
96818 */
96819 SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr *pExpr){
96820 int n;
96821 Table *pExTab;
96822
96823 n = pExpr->iColumn;
96824 pExTab = pExpr->y.pTab;
96825 assert( pExTab!=0 );
96826 if( (pExTab->tabFlags & TF_HasGenerated)!=0
96827 && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0
96828 ){
96829 testcase( pExTab->nCol==BMS-1 );
96830 testcase( pExTab->nCol==BMS );
96831 return pExTab->nCol>=BMS ? ALLBITS : MASKBIT(pExTab->nCol)-1;
96832 }else{
96833 testcase( n==BMS-1 );
96834 testcase( n==BMS );
96835 if( n>=BMS ) n = BMS-1;
96836 return ((Bitmask)1)<<n;
96837 }
96838 }
96839
96840 /*
96841 ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
96842 ** that name in the set of source tables in pSrcList and make the pExpr
96843 ** expression node refer back to that source column. The following changes
@@ -96861,10 +96911,16 @@
96911 assert( db->aDb[i].zDbSName );
96912 if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){
96913 pSchema = db->aDb[i].pSchema;
96914 break;
96915 }
96916 }
96917 if( i==db->nDb && sqlite3StrICmp("main", zDb)==0 ){
96918 /* This branch is taken when the main database has been renamed
96919 ** using SQLITE_DBCONFIG_MAINDBNAME. */
96920 pSchema = db->aDb[0].pSchema;
96921 zDb = db->aDb[0].zDbSName;
96922 }
96923 }
96924 }
96925
96926 /* Start at the inner-most context and move outward until a match is found */
@@ -97181,26 +97237,11 @@
97237 **
97238 ** If a generated column is referenced, set bits for every column
97239 ** of the table.
97240 */
97241 if( pExpr->iColumn>=0 && pMatch!=0 ){
97242 pMatch->colUsed |= sqlite3ExprColUsed(pExpr);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97243 }
97244
97245 /* Clean up and return
97246 */
97247 sqlite3ExprDelete(db, pExpr->pLeft);
@@ -98557,11 +98598,11 @@
98598 ** CREATE TABLE t1(a);
98599 ** SELECT * FROM t1 WHERE a;
98600 ** SELECT a AS b FROM t1 WHERE b;
98601 ** SELECT * FROM t1 WHERE (select a from t1);
98602 */
98603 SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){
98604 int op;
98605 while( ExprHasProperty(pExpr, EP_Skip) ){
98606 assert( pExpr->op==TK_COLLATE );
98607 pExpr = pExpr->pLeft;
98608 assert( pExpr!=0 );
@@ -98667,14 +98708,14 @@
98708 ** The collating sequence might be determined by a COLLATE operator
98709 ** or by the presence of a column with a defined collating sequence.
98710 ** COLLATE operators take first precedence. Left operands take
98711 ** precedence over right operands.
98712 */
98713 SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){
98714 sqlite3 *db = pParse->db;
98715 CollSeq *pColl = 0;
98716 const Expr *p = pExpr;
98717 while( p ){
98718 int op = p->op;
98719 if( op==TK_REGISTER ) op = p->op2;
98720 if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER)
98721 && p->y.pTab!=0
@@ -98739,21 +98780,21 @@
98780 ** See also: sqlite3ExprCollSeq()
98781 **
98782 ** The sqlite3ExprCollSeq() routine works the same except that it
98783 ** returns NULL if there is no defined collation.
98784 */
98785 SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr){
98786 CollSeq *p = sqlite3ExprCollSeq(pParse, pExpr);
98787 if( p==0 ) p = pParse->db->pDfltColl;
98788 assert( p!=0 );
98789 return p;
98790 }
98791
98792 /*
98793 ** Return TRUE if the two expressions have equivalent collating sequences.
98794 */
98795 SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, const Expr *pE1, const Expr *pE2){
98796 CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1);
98797 CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2);
98798 return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0;
98799 }
98800
@@ -98760,11 +98801,11 @@
98801 /*
98802 ** pExpr is an operand of a comparison operator. aff2 is the
98803 ** type affinity of the other operand. This routine returns the
98804 ** type affinity that should be used for the comparison operator.
98805 */
98806 SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2){
98807 char aff1 = sqlite3ExprAffinity(pExpr);
98808 if( aff1>SQLITE_AFF_NONE && aff2>SQLITE_AFF_NONE ){
98809 /* Both sides of the comparison are columns. If one has numeric
98810 ** affinity, use that. Otherwise use no affinity.
98811 */
@@ -98782,11 +98823,11 @@
98823
98824 /*
98825 ** pExpr is a comparison operator. Return the type affinity that should
98826 ** be applied to both operands prior to doing the comparison.
98827 */
98828 static char comparisonAffinity(const Expr *pExpr){
98829 char aff;
98830 assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT ||
98831 pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE ||
98832 pExpr->op==TK_NE || pExpr->op==TK_IS || pExpr->op==TK_ISNOT );
98833 assert( pExpr->pLeft );
@@ -98805,11 +98846,11 @@
98846 ** pExpr is a comparison expression, eg. '=', '<', IN(...) etc.
98847 ** idx_affinity is the affinity of an indexed column. Return true
98848 ** if the index with affinity idx_affinity may be used to implement
98849 ** the comparison in pExpr.
98850 */
98851 SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity){
98852 char aff = comparisonAffinity(pExpr);
98853 if( aff<SQLITE_AFF_TEXT ){
98854 return 1;
98855 }
98856 if( aff==SQLITE_AFF_TEXT ){
@@ -98820,11 +98861,15 @@
98861
98862 /*
98863 ** Return the P5 value that should be used for a binary comparison
98864 ** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2.
98865 */
98866 static u8 binaryCompareP5(
98867 const Expr *pExpr1, /* Left operand */
98868 const Expr *pExpr2, /* Right operand */
98869 int jumpIfNull /* Extra flags added to P5 */
98870 ){
98871 u8 aff = (char)sqlite3ExprAffinity(pExpr2);
98872 aff = (u8)sqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull;
98873 return aff;
98874 }
98875
@@ -98840,12 +98885,12 @@
98885 ** Argument pRight (but not pLeft) may be a null pointer. In this case,
98886 ** it is not considered.
98887 */
98888 SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(
98889 Parse *pParse,
98890 const Expr *pLeft,
98891 const Expr *pRight
98892 ){
98893 CollSeq *pColl;
98894 assert( pLeft );
98895 if( pLeft->flags & EP_Collate ){
98896 pColl = sqlite3ExprCollSeq(pParse, pLeft);
@@ -98866,11 +98911,11 @@
98911 ** This is normally just a wrapper around sqlite3BinaryCompareCollSeq().
98912 ** However, if the OP_Commuted flag is set, then the order of the operands
98913 ** is reversed in the sqlite3BinaryCompareCollSeq() call so that the
98914 ** correct collating sequence is found.
98915 */
98916 SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse *pParse, const Expr *p){
98917 if( ExprHasProperty(p, EP_Commuted) ){
98918 return sqlite3BinaryCompareCollSeq(pParse, p->pRight, p->pLeft);
98919 }else{
98920 return sqlite3BinaryCompareCollSeq(pParse, p->pLeft, p->pRight);
98921 }
@@ -99109,10 +99154,11 @@
99154 int regRight = 0;
99155 u8 opx = op;
99156 int addrDone = sqlite3VdbeMakeLabel(pParse);
99157 int isCommuted = ExprHasProperty(pExpr,EP_Commuted);
99158
99159 assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
99160 if( pParse->nErr ) return;
99161 if( nLeft!=sqlite3ExprVectorSize(pRight) ){
99162 sqlite3ErrorMsg(pParse, "row value misused");
99163 return;
99164 }
@@ -99721,11 +99767,11 @@
99767 nSize = EXPR_FULLSIZE;
99768 }else{
99769 assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
99770 assert( !ExprHasProperty(p, EP_FromJoin) );
99771 assert( !ExprHasProperty(p, EP_MemToken) );
99772 assert( !ExprHasVVAProperty(p, EP_NoReduce) );
99773 if( p->pLeft || p->x.pList ){
99774 nSize = EXPR_REDUCEDSIZE | EP_Reduced;
99775 }else{
99776 assert( p->pRight==0 );
99777 nSize = EXPR_TOKENONLYSIZE | EP_TokenOnly;
@@ -99826,10 +99872,14 @@
99872
99873 /* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */
99874 pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken);
99875 pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly);
99876 pNew->flags |= staticFlag;
99877 ExprClearVVAProperties(pNew);
99878 if( dupFlags ){
99879 ExprSetVVAProperty(pNew, EP_Immutable);
99880 }
99881
99882 /* Copy the p->u.zToken string, if any. */
99883 if( nToken ){
99884 char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize];
99885 memcpy(zToken, p->u.zToken, nToken);
@@ -100602,11 +100652,11 @@
100652 ** (3) the expression does not contain any EP_FixedCol TK_COLUMN
100653 ** operands created by the constant propagation optimization.
100654 **
100655 ** When this routine returns true, it indicates that the expression
100656 ** can be added to the pParse->pConstExpr list and evaluated once when
100657 ** the prepared statement starts up. See sqlite3ExprCodeRunJustOnce().
100658 */
100659 SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
100660 return exprIsConst(p, 2, 0);
100661 }
100662
@@ -101365,10 +101415,11 @@
101415 return;
101416 }
101417
101418 /* Begin coding the subroutine */
101419 ExprSetProperty(pExpr, EP_Subrtn);
101420 assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
101421 pExpr->y.sub.regReturn = ++pParse->nMem;
101422 pExpr->y.sub.iAddr =
101423 sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
101424 VdbeComment((v, "return address"));
101425
@@ -101685,10 +101736,11 @@
101736 int addrTruthOp; /* Address of opcode that determines the IN is true */
101737 int destNotNull; /* Jump here if a comparison is not true in step 6 */
101738 int addrTop; /* Top of the step-6 loop */
101739 int iTab = 0; /* Index to use */
101740
101741 assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
101742 pLeft = pExpr->pLeft;
101743 if( sqlite3ExprCheckIN(pParse, pExpr) ) return;
101744 zAff = exprINAffinity(pParse, pExpr);
101745 nVector = sqlite3ExprVectorSize(pExpr->pLeft);
101746 aiMap = (int*)sqlite3DbMallocZero(
@@ -102011,11 +102063,11 @@
102063 if( pParse->iSelfTab>0 ){
102064 iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut);
102065 }else{
102066 iAddr = 0;
102067 }
102068 sqlite3ExprCodeCopy(pParse, pCol->pDflt, regOut);
102069 if( pCol->affinity>=SQLITE_AFF_TEXT ){
102070 sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1);
102071 }
102072 if( iAddr ) sqlite3VdbeJumpHere(v, iAddr);
102073 }
@@ -102295,10 +102347,11 @@
102347
102348 expr_code_doover:
102349 if( pExpr==0 ){
102350 op = TK_NULL;
102351 }else{
102352 assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
102353 op = pExpr->op;
102354 }
102355 switch( op ){
102356 case TK_AGG_COLUMN: {
102357 AggInfo *pAggInfo = pExpr->pAggInfo;
@@ -102305,12 +102358,21 @@
102358 struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg];
102359 if( !pAggInfo->directMode ){
102360 assert( pCol->iMem>0 );
102361 return pCol->iMem;
102362 }else if( pAggInfo->useSortingIdx ){
102363 Table *pTab = pCol->pTab;
102364 sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
102365 pCol->iSorterColumn, target);
102366 if( pCol->iColumn<0 ){
102367 VdbeComment((v,"%s.rowid",pTab->zName));
102368 }else{
102369 VdbeComment((v,"%s.%s",pTab->zName,pTab->aCol[pCol->iColumn].zName));
102370 if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){
102371 sqlite3VdbeAddOp1(v, OP_RealAffinity, target);
102372 }
102373 }
102374 return target;
102375 }
102376 /* Otherwise, fall thru into the TK_COLUMN case */
102377 }
102378 case TK_COLUMN: {
@@ -102549,10 +102611,11 @@
102611 #endif
102612 }else{
102613 tempX.op = TK_INTEGER;
102614 tempX.flags = EP_IntValue|EP_TokenOnly;
102615 tempX.u.iValue = 0;
102616 ExprClearVVAProperties(&tempX);
102617 r1 = sqlite3ExprCodeTemp(pParse, &tempX, &regFree1);
102618 r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree2);
102619 sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target);
102620 testcase( regFree2==0 );
102621 }
@@ -102620,20 +102683,17 @@
102683 return pExpr->y.pWin->regResult;
102684 }
102685 #endif
102686
102687 if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){
102688 /* SQL functions can be expensive. So try to avoid running them
102689 ** multiple times if we know they always give the same result */
102690 return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1);
102691 }
102692 assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
102693 assert( !ExprHasProperty(pExpr, EP_TokenOnly) );
102694 pFarg = pExpr->x.pList;
 
 
 
102695 nFarg = pFarg ? pFarg->nExpr : 0;
102696 assert( !ExprHasProperty(pExpr, EP_IntValue) );
102697 zId = pExpr->u.zToken;
102698 pDef = sqlite3FindFunction(db, zId, nFarg, enc, 0);
102699 #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
@@ -103003,19 +103063,27 @@
103063 sqlite3ReleaseTempReg(pParse, regFree2);
103064 return inReg;
103065 }
103066
103067 /*
103068 ** Generate code that will evaluate expression pExpr just one time
103069 ** per prepared statement execution.
103070 **
103071 ** If the expression uses functions (that might throw an exception) then
103072 ** guard them with an OP_Once opcode to ensure that the code is only executed
103073 ** once. If no functions are involved, then factor the code out and put it at
103074 ** the end of the prepared statement in the initialization section.
103075 **
103076 ** If regDest>=0 then the result is always stored in that register and the
103077 ** result is not reusable. If regDest<0 then this routine is free to
103078 ** store the value whereever it wants. The register where the expression
103079 ** is stored is returned. When regDest<0, two identical expressions might
103080 ** code to the same register, if they do not contain function calls and hence
103081 ** are factored out into the initialization section at the end of the
103082 ** prepared statement.
103083 */
103084 SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(
103085 Parse *pParse, /* Parsing context */
103086 Expr *pExpr, /* The expression to code when the VDBE initializes */
103087 int regDest /* Store the value in this register */
103088 ){
103089 ExprList *p;
@@ -103029,18 +103097,33 @@
103097 return pItem->u.iConstExprReg;
103098 }
103099 }
103100 }
103101 pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
103102 if( pExpr!=0 && ExprHasProperty(pExpr, EP_HasFunc) ){
103103 Vdbe *v = pParse->pVdbe;
103104 int addr;
103105 assert( v );
103106 addr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
103107 pParse->okConstFactor = 0;
103108 if( !pParse->db->mallocFailed ){
103109 if( regDest<0 ) regDest = ++pParse->nMem;
103110 sqlite3ExprCode(pParse, pExpr, regDest);
103111 }
103112 pParse->okConstFactor = 1;
103113 sqlite3ExprDelete(pParse->db, pExpr);
103114 sqlite3VdbeJumpHere(v, addr);
103115 }else{
103116 p = sqlite3ExprListAppend(pParse, p, pExpr);
103117 if( p ){
103118 struct ExprList_item *pItem = &p->a[p->nExpr-1];
103119 pItem->reusable = regDest<0;
103120 if( regDest<0 ) regDest = ++pParse->nMem;
103121 pItem->u.iConstExprReg = regDest;
103122 }
103123 pParse->pConstExpr = p;
103124 }
103125 return regDest;
103126 }
103127
103128 /*
103129 ** Generate code to evaluate an expression and store the results
@@ -103061,11 +103144,11 @@
103144 if( ConstFactorOk(pParse)
103145 && pExpr->op!=TK_REGISTER
103146 && sqlite3ExprIsConstantNotJoin(pExpr)
103147 ){
103148 *pReg = 0;
103149 r2 = sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1);
103150 }else{
103151 int r1 = sqlite3GetTempReg(pParse);
103152 r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1);
103153 if( r2==r1 ){
103154 *pReg = r1;
@@ -103083,10 +103166,11 @@
103166 ** in register target.
103167 */
103168 SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){
103169 int inReg;
103170
103171 assert( pExpr==0 || !ExprHasVVAProperty(pExpr,EP_Immutable) );
103172 assert( target>0 && target<=pParse->nMem );
103173 inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
103174 assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
103175 if( inReg!=target && pParse->pVdbe ){
103176 u8 op;
@@ -103117,13 +103201,13 @@
103201 ** in register target. If the expression is constant, then this routine
103202 ** might choose to code the expression at initialization time.
103203 */
103204 SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){
103205 if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){
103206 sqlite3ExprCodeRunJustOnce(pParse, pExpr, target);
103207 }else{
103208 sqlite3ExprCodeCopy(pParse, pExpr, target);
103209 }
103210 }
103211
103212 /*
103213 ** Generate code that pushes the value of every element of the given
@@ -103177,11 +103261,11 @@
103261 sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i);
103262 }
103263 }else if( (flags & SQLITE_ECEL_FACTOR)!=0
103264 && sqlite3ExprIsConstantNotJoin(pExpr)
103265 ){
103266 sqlite3ExprCodeRunJustOnce(pParse, pExpr, target+i);
103267 }else{
103268 int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i);
103269 if( inReg!=target+i ){
103270 VdbeOp *pOp;
103271 if( copyOp==OP_Copy
@@ -103300,10 +103384,11 @@
103384 int r1, r2;
103385
103386 assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
103387 if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */
103388 if( NEVER(pExpr==0) ) return; /* No way this can happen */
103389 assert( !ExprHasVVAProperty(pExpr, EP_Immutable) );
103390 op = pExpr->op;
103391 switch( op ){
103392 case TK_AND:
103393 case TK_OR: {
103394 Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr);
@@ -103441,10 +103526,11 @@
103526 int r1, r2;
103527
103528 assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 );
103529 if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */
103530 if( pExpr==0 ) return;
103531 assert( !ExprHasVVAProperty(pExpr,EP_Immutable) );
103532
103533 /* The value of pExpr->op and op are related as follows:
103534 **
103535 ** pExpr->op op
103536 ** --------- ----------
@@ -103724,36 +103810,22 @@
103810 return 2;
103811 }
103812 }
103813 if( (pA->flags & (EP_Distinct|EP_Commuted))
103814 != (pB->flags & (EP_Distinct|EP_Commuted)) ) return 2;
103815 if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){
103816 if( combinedFlags & EP_xIsSelect ) return 2;
103817 if( (combinedFlags & EP_FixedCol)==0
103818 && sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2;
103819 if( sqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2;
103820 if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2;
103821 if( pA->op!=TK_STRING
103822 && pA->op!=TK_TRUEFALSE
103823 && ALWAYS((combinedFlags & EP_Reduced)==0)
103824 ){
103825 if( pA->iColumn!=pB->iColumn ) return 2;
103826 if( pA->op2!=pB->op2 && pA->op==TK_TRUTH ) return 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103827 if( pA->op!=TK_IN && pA->iTable!=pB->iTable && pA->iTable!=iTab ){
103828 return 2;
103829 }
103830 }
103831 }
@@ -106442,13 +106514,13 @@
106514 /*
106515 ** Three SQL functions - stat_init(), stat_push(), and stat_get() -
106516 ** share an instance of the following structure to hold their state
106517 ** information.
106518 */
106519 typedef struct StatAccum StatAccum;
106520 typedef struct StatSample StatSample;
106521 struct StatSample {
106522 tRowcnt *anEq; /* sqlite_stat4.nEq */
106523 tRowcnt *anDLt; /* sqlite_stat4.nDLt */
106524 #ifdef SQLITE_ENABLE_STAT4
106525 tRowcnt *anLt; /* sqlite_stat4.nLt */
106526 union {
@@ -106459,31 +106531,33 @@
106531 u8 isPSample; /* True if a periodic sample */
106532 int iCol; /* If !isPSample, the reason for inclusion */
106533 u32 iHash; /* Tiebreaker hash */
106534 #endif
106535 };
106536 struct StatAccum {
106537 sqlite3 *db; /* Database connection, for malloc() */
106538 tRowcnt nRow; /* Number of rows in the entire table */
 
106539 int nCol; /* Number of columns in index + pk/rowid */
106540 int nKeyCol; /* Number of index columns w/o the pk/rowid */
106541 StatSample current; /* Current row as a StatSample */
106542 #ifdef SQLITE_ENABLE_STAT4
106543 tRowcnt nPSample; /* How often to do a periodic sample */
106544 int mxSample; /* Maximum number of samples to accumulate */
 
106545 u32 iPrn; /* Pseudo-random number used for sampling */
106546 StatSample *aBest; /* Array of nCol best samples */
106547 int iMin; /* Index in a[] of entry with minimum score */
106548 int nSample; /* Current number of samples */
106549 int nMaxEqZero; /* Max leading 0 in anEq[] for any a[] entry */
106550 int iGet; /* Index of current sample accessed by stat_get() */
106551 StatSample *a; /* Array of mxSample StatSample objects */
106552 #endif
106553 };
106554
106555 /* Reclaim memory used by a StatSample
106556 */
106557 #ifdef SQLITE_ENABLE_STAT4
106558 static void sampleClear(sqlite3 *db, StatSample *p){
106559 assert( db!=0 );
106560 if( p->nRowid ){
106561 sqlite3DbFree(db, p->u.aRowid);
106562 p->nRowid = 0;
106563 }
@@ -106491,11 +106565,11 @@
106565 #endif
106566
106567 /* Initialize the BLOB value of a ROWID
106568 */
106569 #ifdef SQLITE_ENABLE_STAT4
106570 static void sampleSetRowid(sqlite3 *db, StatSample *p, int n, const u8 *pData){
106571 assert( db!=0 );
106572 if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
106573 p->u.aRowid = sqlite3DbMallocRawNN(db, n);
106574 if( p->u.aRowid ){
106575 p->nRowid = n;
@@ -106507,11 +106581,11 @@
106581 #endif
106582
106583 /* Initialize the INTEGER value of a ROWID.
106584 */
106585 #ifdef SQLITE_ENABLE_STAT4
106586 static void sampleSetRowidInt64(sqlite3 *db, StatSample *p, i64 iRowid){
106587 assert( db!=0 );
106588 if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
106589 p->nRowid = 0;
106590 p->u.iRowid = iRowid;
106591 }
@@ -106520,11 +106594,11 @@
106594
106595 /*
106596 ** Copy the contents of object (*pFrom) into (*pTo).
106597 */
106598 #ifdef SQLITE_ENABLE_STAT4
106599 static void sampleCopy(StatAccum *p, StatSample *pTo, StatSample *pFrom){
106600 pTo->isPSample = pFrom->isPSample;
106601 pTo->iCol = pFrom->iCol;
106602 pTo->iHash = pFrom->iHash;
106603 memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol);
106604 memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol);
@@ -106536,14 +106610,14 @@
106610 }
106611 }
106612 #endif
106613
106614 /*
106615 ** Reclaim all memory of a StatAccum structure.
106616 */
106617 static void statAccumDestructor(void *pOld){
106618 StatAccum *p = (StatAccum*)pOld;
106619 #ifdef SQLITE_ENABLE_STAT4
106620 int i;
106621 for(i=0; i<p->nCol; i++) sampleClear(p->db, p->aBest+i);
106622 for(i=0; i<p->mxSample; i++) sampleClear(p->db, p->a+i);
106623 sampleClear(p->db, &p->current);
@@ -106567,21 +106641,21 @@
106641 ** For indexes on ordinary rowid tables, N==K+1. But for indexes on
106642 ** WITHOUT ROWID tables, N=K+P where P is the number of columns in the
106643 ** PRIMARY KEY of the table. The covering index that implements the
106644 ** original WITHOUT ROWID table as N==K as a special case.
106645 **
106646 ** This routine allocates the StatAccum object in heap memory. The return
106647 ** value is a pointer to the StatAccum object. The datatype of the
106648 ** return value is BLOB, but it is really just a pointer to the StatAccum
106649 ** object.
106650 */
106651 static void statInit(
106652 sqlite3_context *context,
106653 int argc,
106654 sqlite3_value **argv
106655 ){
106656 StatAccum *p;
106657 int nCol; /* Number of columns in index being sampled */
106658 int nKeyCol; /* Number of key columns */
106659 int nColUp; /* nCol rounded up for alignment */
106660 int n; /* Bytes of space to allocate */
106661 sqlite3 *db; /* Database connection */
@@ -106596,17 +106670,17 @@
106670 nColUp = sizeof(tRowcnt)<8 ? (nCol+1)&~1 : nCol;
106671 nKeyCol = sqlite3_value_int(argv[1]);
106672 assert( nKeyCol<=nCol );
106673 assert( nKeyCol>0 );
106674
106675 /* Allocate the space required for the StatAccum object */
106676 n = sizeof(*p)
106677 + sizeof(tRowcnt)*nColUp /* StatAccum.anEq */
106678 + sizeof(tRowcnt)*nColUp /* StatAccum.anDLt */
106679 #ifdef SQLITE_ENABLE_STAT4
106680 + sizeof(tRowcnt)*nColUp /* StatAccum.anLt */
106681 + sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */
106682 + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample)
106683 #endif
106684 ;
106685 db = sqlite3_context_db_handle(context);
106686 p = sqlite3DbMallocZero(db, n);
@@ -106631,12 +106705,12 @@
106705 p->mxSample = mxSample;
106706 p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1);
106707 p->current.anLt = &p->current.anEq[nColUp];
106708 p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]);
106709
106710 /* Set up the StatAccum.a[] and aBest[] arrays */
106711 p->a = (struct StatSample*)&p->current.anLt[nColUp];
106712 p->aBest = &p->a[mxSample];
106713 pSpace = (u8*)(&p->a[mxSample+nCol]);
106714 for(i=0; i<(mxSample+nCol); i++){
106715 p->a[i].anEq = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp);
106716 p->a[i].anLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp);
@@ -106652,11 +106726,11 @@
106726
106727 /* Return a pointer to the allocated object to the caller. Note that
106728 ** only the pointer (the 2nd parameter) matters. The size of the object
106729 ** (given by the 3rd parameter) is never used and can be any positive
106730 ** value. */
106731 sqlite3_result_blob(context, p, sizeof(*p), statAccumDestructor);
106732 }
106733 static const FuncDef statInitFuncdef = {
106734 2+IsStat4, /* nArg */
106735 SQLITE_UTF8, /* funcFlags */
106736 0, /* pUserData */
@@ -106679,13 +106753,13 @@
106753 **
106754 ** This function assumes that for each argument sample, the contents of
106755 ** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid.
106756 */
106757 static int sampleIsBetterPost(
106758 StatAccum *pAccum,
106759 StatSample *pNew,
106760 StatSample *pOld
106761 ){
106762 int nCol = pAccum->nCol;
106763 int i;
106764 assert( pNew->iCol==pOld->iCol );
106765 for(i=pNew->iCol+1; i<nCol; i++){
@@ -106703,13 +106777,13 @@
106777 **
106778 ** This function assumes that for each argument sample, the contents of
106779 ** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid.
106780 */
106781 static int sampleIsBetter(
106782 StatAccum *pAccum,
106783 StatSample *pNew,
106784 StatSample *pOld
106785 ){
106786 tRowcnt nEqNew = pNew->anEq[pNew->iCol];
106787 tRowcnt nEqOld = pOld->anEq[pOld->iCol];
106788
106789 assert( pOld->isPSample==0 && pNew->isPSample==0 );
@@ -106725,34 +106799,34 @@
106799
106800 /*
106801 ** Copy the contents of sample *pNew into the p->a[] array. If necessary,
106802 ** remove the least desirable sample from p->a[] to make room.
106803 */
106804 static void sampleInsert(StatAccum *p, StatSample *pNew, int nEqZero){
106805 StatSample *pSample = 0;
106806 int i;
106807
106808 assert( IsStat4 || nEqZero==0 );
106809
106810 /* StatAccum.nMaxEqZero is set to the maximum number of leading 0
106811 ** values in the anEq[] array of any sample in StatAccum.a[]. In
106812 ** other words, if nMaxEqZero is n, then it is guaranteed that there
106813 ** are no samples with StatSample.anEq[m]==0 for (m>=n). */
106814 if( nEqZero>p->nMaxEqZero ){
106815 p->nMaxEqZero = nEqZero;
106816 }
106817 if( pNew->isPSample==0 ){
106818 StatSample *pUpgrade = 0;
106819 assert( pNew->anEq[pNew->iCol]>0 );
106820
106821 /* This sample is being added because the prefix that ends in column
106822 ** iCol occurs many times in the table. However, if we have already
106823 ** added a sample that shares this prefix, there is no need to add
106824 ** this one. Instead, upgrade the priority of the highest priority
106825 ** existing sample that shares this prefix. */
106826 for(i=p->nSample-1; i>=0; i--){
106827 StatSample *pOld = &p->a[i];
106828 if( pOld->anEq[pNew->iCol]==0 ){
106829 if( pOld->isPSample ) return;
106830 assert( pOld->iCol>pNew->iCol );
106831 assert( sampleIsBetter(p, pNew, pOld) );
106832 if( pUpgrade==0 || sampleIsBetter(p, pOld, pUpgrade) ){
@@ -106767,11 +106841,11 @@
106841 }
106842 }
106843
106844 /* If necessary, remove sample iMin to make room for the new sample. */
106845 if( p->nSample>=p->mxSample ){
106846 StatSample *pMin = &p->a[p->iMin];
106847 tRowcnt *anEq = pMin->anEq;
106848 tRowcnt *anLt = pMin->anLt;
106849 tRowcnt *anDLt = pMin->anDLt;
106850 sampleClear(p->db, pMin);
106851 memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1));
@@ -106810,24 +106884,24 @@
106884 p->iMin = iMin;
106885 }
106886 }
106887 #endif /* SQLITE_ENABLE_STAT4 */
106888
106889 #ifdef SQLITE_ENABLE_STAT4
106890 /*
106891 ** Field iChng of the index being scanned has changed. So at this point
106892 ** p->current contains a sample that reflects the previous row of the
106893 ** index. The value of anEq[iChng] and subsequent anEq[] elements are
106894 ** correct at this point.
106895 */
106896 static void samplePushPrevious(StatAccum *p, int iChng){
 
106897 int i;
106898
106899 /* Check if any samples from the aBest[] array should be pushed
106900 ** into IndexSample.a[] at this point. */
106901 for(i=(p->nCol-2); i>=iChng; i--){
106902 StatSample *pBest = &p->aBest[i];
106903 pBest->anEq[i] = p->current.anEq[i];
106904 if( p->nSample<p->mxSample || sampleIsBetter(p, pBest, &p->a[p->iMin]) ){
106905 sampleInsert(p, pBest, i);
106906 }
106907 }
@@ -106847,29 +106921,24 @@
106921 if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j];
106922 }
106923 }
106924 p->nMaxEqZero = iChng;
106925 }
 
 
 
 
 
 
106926 }
106927 #endif /* SQLITE_ENABLE_STAT4 */
106928
106929 /*
106930 ** Implementation of the stat_push SQL function: stat_push(P,C,R)
106931 ** Arguments:
106932 **
106933 ** P Pointer to the StatAccum object created by stat_init()
106934 ** C Index of left-most column to differ from previous row
106935 ** R Rowid for the current row. Might be a key record for
106936 ** WITHOUT ROWID tables.
106937 **
106938 ** This SQL function always returns NULL. It's purpose it to accumulate
106939 ** statistical data and/or samples in the StatAccum object about the
106940 ** index being analyzed. The stat_get() SQL function will later be used to
106941 ** extract relevant information for constructing the sqlite_statN tables.
106942 **
106943 ** The R parameter is only used for STAT4
106944 */
@@ -106879,11 +106948,11 @@
106948 sqlite3_value **argv
106949 ){
106950 int i;
106951
106952 /* The three function arguments */
106953 StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]);
106954 int iChng = sqlite3_value_int(argv[1]);
106955
106956 UNUSED_PARAMETER( argc );
106957 UNUSED_PARAMETER( context );
106958 assert( p->nCol>0 );
@@ -106892,11 +106961,13 @@
106961 if( p->nRow==0 ){
106962 /* This is the first call to this function. Do initialization. */
106963 for(i=0; i<p->nCol; i++) p->current.anEq[i] = 1;
106964 }else{
106965 /* Second and subsequent calls get processed here */
106966 #ifdef SQLITE_ENABLE_STAT4
106967 samplePushPrevious(p, iChng);
106968 #endif
106969
106970 /* Update anDLt[], anLt[] and anEq[] to reflect the values that apply
106971 ** to the current row of the index. */
106972 for(i=0; i<iChng; i++){
106973 p->current.anEq[i]++;
@@ -106961,19 +107032,19 @@
107032 #define STAT_GET_NDLT 4 /* "ndlt" column of stat[34] entry */
107033
107034 /*
107035 ** Implementation of the stat_get(P,J) SQL function. This routine is
107036 ** used to query statistical information that has been gathered into
107037 ** the StatAccum object by prior calls to stat_push(). The P parameter
107038 ** has type BLOB but it is really just a pointer to the StatAccum object.
107039 ** The content to returned is determined by the parameter J
107040 ** which is one of the STAT_GET_xxxx values defined above.
107041 **
107042 ** The stat_get(P,J) function is not available to generic SQL. It is
107043 ** inserted as part of a manually constructed bytecode program. (See
107044 ** the callStatGet() routine below.) It is guaranteed that the P
107045 ** parameter will always be a pointer to a StatAccum object, never a
107046 ** NULL.
107047 **
107048 ** If STAT4 is not enabled, then J is always
107049 ** STAT_GET_STAT1 and is hence omitted and this routine becomes
107050 ** a one-parameter function, stat_get(P), that always returns the
@@ -106982,11 +107053,11 @@
107053 static void statGet(
107054 sqlite3_context *context,
107055 int argc,
107056 sqlite3_value **argv
107057 ){
107058 StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]);
107059 #ifdef SQLITE_ENABLE_STAT4
107060 /* STAT4 has a parameter on this routine. */
107061 int eCall = sqlite3_value_int(argv[1]);
107062 assert( argc==2 );
107063 assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ
@@ -107003,11 +107074,11 @@
107074 **
107075 ** The value is a string composed of a list of integers describing
107076 ** the index. The first integer in the list is the total number of
107077 ** entries in the index. There is one additional integer in the list
107078 ** for each indexed column. This additional integer is an estimate of
107079 ** the number of rows matched by a equality query on the index using
107080 ** a key with the corresponding number of fields. In other words,
107081 ** if the index is on columns (a,b) and the sqlite_stat1 value is
107082 ** "100 10 2", then SQLite estimates that:
107083 **
107084 ** * the index contains 100 rows,
@@ -107046,11 +107117,11 @@
107117 if( p->iGet<0 ){
107118 samplePushPrevious(p, 0);
107119 p->iGet = 0;
107120 }
107121 if( p->iGet<p->nSample ){
107122 StatSample *pS = p->a + p->iGet;
107123 if( pS->nRowid==0 ){
107124 sqlite3_result_int64(context, pS->u.iRowid);
107125 }else{
107126 sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid,
107127 SQLITE_TRANSIENT);
@@ -107137,11 +107208,11 @@
107208 int i; /* Loop counter */
107209 int jZeroRows = -1; /* Jump from here if number of rows is zero */
107210 int iDb; /* Index of database containing pTab */
107211 u8 needTableCnt = 1; /* True to count the table */
107212 int regNewRowid = iMem++; /* Rowid for the inserted record */
107213 int regStat4 = iMem++; /* Register to hold StatAccum object */
107214 int regChng = iMem++; /* Index of changed index field */
107215 #ifdef SQLITE_ENABLE_STAT4
107216 int regRowid = iMem++; /* Rowid argument passed to stat_push() */
107217 #endif
107218 int regTemp = iMem++; /* Temporary use register */
@@ -108105,10 +108176,21 @@
108176 pExpr->op = TK_STRING;
108177 }
108178 }
108179 return rc;
108180 }
108181
108182 /*
108183 ** Return true if zName points to a name that may be used to refer to
108184 ** database iDb attached to handle db.
108185 */
108186 SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName){
108187 return (
108188 sqlite3StrICmp(db->aDb[iDb].zDbSName, zName)==0
108189 || (iDb==0 && sqlite3StrICmp("main", zName)==0)
108190 );
108191 }
108192
108193 /*
108194 ** An SQL user-function registered to do the work of an ATTACH statement. The
108195 ** three arguments to the function come directly from an attach statement:
108196 **
@@ -108178,13 +108260,12 @@
108260 db->aLimit[SQLITE_LIMIT_ATTACHED]
108261 );
108262 goto attach_error;
108263 }
108264 for(i=0; i<db->nDb; i++){
108265 assert( zName );
108266 if( sqlite3DbIsNamed(db, i, zName) ){
 
108267 zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName);
108268 goto attach_error;
108269 }
108270 }
108271
@@ -108333,11 +108414,11 @@
108414
108415 if( zName==0 ) zName = "";
108416 for(i=0; i<db->nDb; i++){
108417 pDb = &db->aDb[i];
108418 if( pDb->pBt==0 ) continue;
108419 if( sqlite3DbIsNamed(db, i, zName) ) break;
108420 }
108421
108422 if( i>=db->nDb ){
108423 sqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName);
108424 goto detach_error;
@@ -108524,24 +108605,25 @@
108605 SQLITE_PRIVATE int sqlite3FixSrcList(
108606 DbFixer *pFix, /* Context of the fixation */
108607 SrcList *pList /* The Source list to check and modify */
108608 ){
108609 int i;
 
108610 struct SrcList_item *pItem;
108611 sqlite3 *db = pFix->pParse->db;
108612 int iDb = sqlite3FindDbName(db, pFix->zDb);
108613
108614 if( NEVER(pList==0) ) return 0;
108615
108616 for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
108617 if( pFix->bTemp==0 ){
108618 if( pItem->zDatabase && iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){
108619 sqlite3ErrorMsg(pFix->pParse,
108620 "%s %T cannot reference objects in database %s",
108621 pFix->zType, pFix->pName, pItem->zDatabase);
108622 return 1;
108623 }
108624 sqlite3DbFree(db, pItem->zDatabase);
108625 pItem->zDatabase = 0;
108626 pItem->pSchema = pFix->pSchema;
108627 pItem->fg.fromDDL = 1;
108628 }
108629 #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
@@ -109262,11 +109344,11 @@
109344 }
109345 #endif
109346 while(1){
109347 for(i=OMIT_TEMPDB; i<db->nDb; i++){
109348 int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
109349 if( zDatabase==0 || sqlite3DbIsNamed(db, j, zDatabase) ){
109350 assert( sqlite3SchemaMutexHeld(db, j, 0) );
109351 p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName);
109352 if( p ) return p;
109353 }
109354 }
@@ -109384,11 +109466,11 @@
109466 assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
109467 for(i=OMIT_TEMPDB; i<db->nDb; i++){
109468 int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
109469 Schema *pSchema = db->aDb[j].pSchema;
109470 assert( pSchema );
109471 if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue;
109472 assert( sqlite3SchemaMutexHeld(db, j, 0) );
109473 p = sqlite3HashFind(&pSchema->idxHash, zName);
109474 if( p ) break;
109475 }
109476 return p;
@@ -111103,10 +111185,36 @@
111185 if( pMod->pModule->iVersion<3 ) return 0;
111186 if( pMod->pModule->xShadowName==0 ) return 0;
111187 return pMod->pModule->xShadowName(zTail+1);
111188 }
111189 #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */
111190
111191 #ifdef SQLITE_DEBUG
111192 /*
111193 ** Mark all nodes of an expression as EP_Immutable, indicating that
111194 ** they should not be changed. Expressions attached to a table or
111195 ** index definition are tagged this way to help ensure that we do
111196 ** not pass them into code generator routines by mistake.
111197 */
111198 static int markImmutableExprStep(Walker *pWalker, Expr *pExpr){
111199 ExprSetVVAProperty(pExpr, EP_Immutable);
111200 return WRC_Continue;
111201 }
111202 static void markExprListImmutable(ExprList *pList){
111203 if( pList ){
111204 Walker w;
111205 memset(&w, 0, sizeof(w));
111206 w.xExprCallback = markImmutableExprStep;
111207 w.xSelectCallback = sqlite3SelectWalkNoop;
111208 w.xSelectCallback2 = 0;
111209 sqlite3WalkExprList(&w, pList);
111210 }
111211 }
111212 #else
111213 #define markExprListImmutable(X) /* no-op */
111214 #endif /* SQLITE_DEBUG */
111215
111216
111217 /*
111218 ** This routine is called to report the final ")" that terminates
111219 ** a CREATE TABLE statement.
111220 **
@@ -111196,10 +111304,12 @@
111304 if( pParse->nErr ){
111305 /* If errors are seen, delete the CHECK constraints now, else they might
111306 ** actually be used if PRAGMA writable_schema=ON is set. */
111307 sqlite3ExprListDelete(db, p->pCheck);
111308 p->pCheck = 0;
111309 }else{
111310 markExprListImmutable(p->pCheck);
111311 }
111312 }
111313 #endif /* !defined(SQLITE_OMIT_CHECK) */
111314 #ifndef SQLITE_OMIT_GENERATED_COLUMNS
111315 if( p->tabFlags & TF_HasGenerated ){
@@ -114137,20 +114247,33 @@
114247 u8 enc, /* Desired text encoding */
114248 const char *zName, /* Name of the collating sequence. Might be NULL */
114249 int create /* True to create CollSeq if doesn't already exist */
114250 ){
114251 CollSeq *pColl;
114252 assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 );
114253 assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE );
114254 if( zName ){
114255 pColl = findCollSeqEntry(db, zName, create);
114256 if( pColl ) pColl += enc-1;
114257 }else{
114258 pColl = db->pDfltColl;
114259 }
 
 
 
114260 return pColl;
114261 }
114262
114263 /*
114264 ** Change the text encoding for a database connection. This means that
114265 ** the pDfltColl must change as well.
114266 */
114267 SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8 enc){
114268 assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
114269 db->enc = enc;
114270 /* EVIDENCE-OF: R-08308-17224 The default collating function for all
114271 ** strings is BINARY.
114272 */
114273 db->pDfltColl = sqlite3FindCollSeq(db, enc, sqlite3StrBINARY, 0);
114274 }
114275
114276 /*
114277 ** This function is responsible for invoking the collation factory callback
114278 ** or substituting a collation sequence of a different encoding when the
114279 ** requested collation sequence is not available in the desired encoding.
@@ -116321,10 +116444,11 @@
116444 const unsigned char *zA, *zB;
116445 u32 escape;
116446 int nPat;
116447 sqlite3 *db = sqlite3_context_db_handle(context);
116448 struct compareInfo *pInfo = sqlite3_user_data(context);
116449 struct compareInfo backupInfo;
116450
116451 #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
116452 if( sqlite3_value_type(argv[0])==SQLITE_BLOB
116453 || sqlite3_value_type(argv[1])==SQLITE_BLOB
116454 ){
@@ -116356,10 +116480,16 @@
116480 sqlite3_result_error(context,
116481 "ESCAPE expression must be a single character", -1);
116482 return;
116483 }
116484 escape = sqlite3Utf8Read(&zEsc);
116485 if( escape==pInfo->matchAll || escape==pInfo->matchOne ){
116486 memcpy(&backupInfo, pInfo, sizeof(backupInfo));
116487 pInfo = &backupInfo;
116488 if( escape==pInfo->matchAll ) pInfo->matchAll = 0;
116489 if( escape==pInfo->matchOne ) pInfo->matchOne = 0;
116490 }
116491 }else{
116492 escape = pInfo->matchSet;
116493 }
116494 zB = sqlite3_value_text(argv[0]);
116495 zA = sqlite3_value_text(argv[1]);
@@ -117338,29 +117468,33 @@
117468 if( pDef==0 ) return 0;
117469 #endif
117470 if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){
117471 return 0;
117472 }
 
 
 
 
 
 
 
 
 
 
117473
117474 /* The memcpy() statement assumes that the wildcard characters are
117475 ** the first three statements in the compareInfo structure. The
117476 ** asserts() that follow verify that assumption
117477 */
117478 memcpy(aWc, pDef->pUserData, 3);
117479 assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll );
117480 assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne );
117481 assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet );
117482
117483 if( nExpr<3 ){
117484 aWc[3] = 0;
117485 }else{
117486 Expr *pEscape = pExpr->x.pList->a[2].pExpr;
117487 char *zEscape;
117488 if( pEscape->op!=TK_STRING ) return 0;
117489 zEscape = pEscape->u.zToken;
117490 if( zEscape[0]==0 || zEscape[1]!=0 ) return 0;
117491 if( zEscape[0]==aWc[0] ) return 0;
117492 if( zEscape[0]==aWc[1] ) return 0;
117493 aWc[3] = zEscape[0];
117494 }
117495
117496 *pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0;
117497 return 1;
117498 }
117499
117500 /*
@@ -120564,11 +120698,11 @@
120698 case OE_Replace: {
120699 int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg);
120700 VdbeCoverage(v);
120701 assert( (pCol->colFlags & COLFLAG_GENERATED)==0 );
120702 nSeenReplace++;
120703 sqlite3ExprCodeCopy(pParse, pCol->pDflt, iReg);
120704 sqlite3VdbeJumpHere(v, addr1);
120705 break;
120706 }
120707 case OE_Abort:
120708 sqlite3MayAbort(pParse);
@@ -120619,10 +120753,11 @@
120753 ExprList *pCheck = pTab->pCheck;
120754 pParse->iSelfTab = -(regNewData+1);
120755 onError = overrideError!=OE_Default ? overrideError : OE_Abort;
120756 for(i=0; i<pCheck->nExpr; i++){
120757 int allOk;
120758 Expr *pCopy;
120759 Expr *pExpr = pCheck->a[i].pExpr;
120760 if( aiChng
120761 && !sqlite3ExprReferencesUpdatedColumn(pExpr, aiChng, pkChng)
120762 ){
120763 /* The check constraints do not reference any of the columns being
@@ -120633,11 +120768,15 @@
120768 sqlite3TableAffinity(v, pTab, regNewData+1);
120769 bAffinityDone = 1;
120770 }
120771 allOk = sqlite3VdbeMakeLabel(pParse);
120772 sqlite3VdbeVerifyAbortable(v, onError);
120773 pCopy = sqlite3ExprDup(db, pExpr, 0);
120774 if( !db->mallocFailed ){
120775 sqlite3ExprIfTrue(pParse, pCopy, allOk, SQLITE_JUMPIFNULL);
120776 }
120777 sqlite3ExprDelete(db, pCopy);
120778 if( onError==OE_Ignore ){
120779 sqlite3VdbeGoto(v, ignoreDest);
120780 }else{
120781 char *zName = pCheck->a[i].zEName;
120782 if( zName==0 ) zName = pTab->zName;
@@ -125952,25 +126091,16 @@
126091 /* Only change the value of sqlite.enc if the database handle is not
126092 ** initialized. If the main database exists, the new sqlite.enc value
126093 ** will be overwritten when the schema is next loaded. If it does not
126094 ** already exists, it will be created to use the new encoding value.
126095 */
126096 if( (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){
 
 
 
 
 
 
 
 
 
 
126097 for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
126098 if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){
126099 u8 enc = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
126100 SCHEMA_ENC(db) = enc;
126101 sqlite3SetTextEncoding(db, enc);
126102 break;
126103 }
126104 }
126105 if( !pEnc->zName ){
126106 sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight);
@@ -126775,11 +126905,11 @@
126905 int iDb = pData->iDb;
126906
126907 assert( argc==5 );
126908 UNUSED_PARAMETER2(NotUsed, argc);
126909 assert( sqlite3_mutex_held(db->mutex) );
126910 db->mDbFlags |= DBFLAG_EncodingFixed;
126911 pData->nInitRow++;
126912 if( db->mallocFailed ){
126913 corruptSchema(pData, argv[1], 0);
126914 return 1;
126915 }
@@ -126863,10 +126993,11 @@
126993 char const *azArg[6];
126994 int meta[5];
126995 InitData initData;
126996 const char *zMasterName;
126997 int openedTransaction = 0;
126998 int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed);
126999
127000 assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 );
127001 assert( iDb>=0 && iDb<db->nDb );
127002 assert( db->aDb[iDb].pSchema );
127003 assert( sqlite3_mutex_held(db->mutex) );
@@ -126891,10 +127022,11 @@
127022 initData.rc = SQLITE_OK;
127023 initData.pzErrMsg = pzErrMsg;
127024 initData.mInitFlags = mFlags;
127025 initData.nInitRow = 0;
127026 sqlite3InitCallback(&initData, 5, (char **)azArg, 0);
127027 db->mDbFlags &= mask;
127028 if( initData.rc ){
127029 rc = initData.rc;
127030 goto error_out;
127031 }
127032
@@ -126950,31 +127082,29 @@
127082 ** main database, set sqlite3.enc to the encoding of the main database.
127083 ** For an attached db, it is an error if the encoding is not the same
127084 ** as sqlite3.enc.
127085 */
127086 if( meta[BTREE_TEXT_ENCODING-1] ){ /* text encoding */
127087 if( iDb==0 && (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){
 
127088 u8 encoding;
127089 #ifndef SQLITE_OMIT_UTF16
127090 /* If opening the main database, set ENC(db). */
127091 encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
127092 if( encoding==0 ) encoding = SQLITE_UTF8;
 
127093 #else
127094 encoding = SQLITE_UTF8;
127095 #endif
127096 sqlite3SetTextEncoding(db, encoding);
127097 }else{
127098 /* If opening an attached database, the encoding much match ENC(db) */
127099 if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){
127100 sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
127101 " text encoding as main database");
127102 rc = SQLITE_ERROR;
127103 goto initone_error_out;
127104 }
127105 }
 
 
127106 }
127107 pDb->pSchema->enc = ENC(db);
127108
127109 if( pDb->pSchema->cache_size==0 ){
127110 #ifndef SQLITE_OMIT_DEPRECATED
@@ -127082,12 +127212,11 @@
127212 ** used to store temporary tables, and any additional database files
127213 ** created using ATTACH statements. Return a success code. If an
127214 ** error occurs, write an error message into *pzErrMsg.
127215 **
127216 ** After a database is initialized, the DB_SchemaLoaded bit is set
127217 ** bit is set in the flags field of the Db structure.
 
127218 */
127219 SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){
127220 int i, rc;
127221 int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange);
127222
@@ -127719,11 +127848,10 @@
127848 sqlite3ExprDelete(db, p->pLimit);
127849 #ifndef SQLITE_OMIT_WINDOWFUNC
127850 if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){
127851 sqlite3WindowListDelete(db, p->pWinDefn);
127852 }
 
127853 #endif
127854 if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith);
127855 if( bFree ) sqlite3DbFreeNN(db, p);
127856 p = pPrior;
127857 bFree = 1;
@@ -131197,10 +131325,42 @@
131325 }
131326 }while( doPrior && (p = p->pPrior)!=0 );
131327 }
131328 #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
131329
131330 #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
131331 /*
131332 ** pSelect is a SELECT statement and pSrcItem is one item in the FROM
131333 ** clause of that SELECT.
131334 **
131335 ** This routine scans the entire SELECT statement and recomputes the
131336 ** pSrcItem->colUsed mask.
131337 */
131338 static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){
131339 struct SrcList_item *pItem;
131340 if( pExpr->op!=TK_COLUMN ) return WRC_Continue;
131341 pItem = pWalker->u.pSrcItem;
131342 if( pItem->iCursor!=pExpr->iTable ) return WRC_Continue;
131343 if( pExpr->iColumn<0 ) return WRC_Continue;
131344 pItem->colUsed |= sqlite3ExprColUsed(pExpr);
131345 return WRC_Continue;
131346 }
131347 static void recomputeColumnsUsed(
131348 Select *pSelect, /* The complete SELECT statement */
131349 struct SrcList_item *pSrcItem /* Which FROM clause item to recompute */
131350 ){
131351 Walker w;
131352 if( NEVER(pSrcItem->pTab==0) ) return;
131353 memset(&w, 0, sizeof(w));
131354 w.xExprCallback = recomputeColumnsUsedExpr;
131355 w.xSelectCallback = sqlite3SelectWalkNoop;
131356 w.u.pSrcItem = pSrcItem;
131357 pSrcItem->colUsed = 0;
131358 sqlite3WalkSelect(&w, pSelect);
131359 }
131360 #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
131361
131362 #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
131363 /*
131364 ** This routine attempts to flatten subqueries as a performance optimization.
131365 ** This routine returns 1 if it makes changes and 0 if no flattening occurs.
131366 **
@@ -131735,10 +131895,16 @@
131895 */
131896 if( pSub->pLimit ){
131897 pParent->pLimit = pSub->pLimit;
131898 pSub->pLimit = 0;
131899 }
131900
131901 /* Recompute the SrcList_item.colUsed masks for the flattened
131902 ** tables. */
131903 for(i=0; i<nSubSrc; i++){
131904 recomputeColumnsUsed(pParent, &pSrc->a[i+iFrom]);
131905 }
131906 }
131907
131908 /* Finially, delete what is left of the subquery and return
131909 ** success.
131910 */
@@ -135184,11 +135350,11 @@
135350 zDb = pName->a[0].zDatabase;
135351 zName = pName->a[0].zName;
135352 assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
135353 for(i=OMIT_TEMPDB; i<db->nDb; i++){
135354 int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
135355 if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue;
135356 assert( sqlite3SchemaMutexHeld(db, j, 0) );
135357 pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName);
135358 if( pTrigger ) break;
135359 }
135360 if( !pTrigger ){
@@ -141206,10 +141372,13 @@
141372 assert( pRangeEnd==0 && pRangeStart==0 );
141373 testcase( pLoop->nSkip>0 );
141374 nExtraReg = 1;
141375 bSeekPastNull = 1;
141376 pLevel->regBignull = regBignull = ++pParse->nMem;
141377 if( pLevel->iLeftJoin ){
141378 sqlite3VdbeAddOp2(v, OP_Integer, 0, regBignull);
141379 }
141380 pLevel->addrBignull = sqlite3VdbeMakeLabel(pParse);
141381 }
141382
141383 /* If we are doing a reverse order scan on an ascending index, or
141384 ** a forward order scan on a descending index, interchange the
@@ -148759,11 +148928,11 @@
148928 && (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0
148929 && (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0
148930 && (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0
148931 && pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED
148932 ){
148933 sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);
148934 }
148935 VdbeComment((v, "%s", pIx->zName));
148936 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
148937 {
148938 u64 colUsed = 0;
@@ -148917,16 +149086,10 @@
149086 for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){
149087 sqlite3VdbeJumpHere(v, pIn->addrInTop+1);
149088 if( pIn->eEndLoopOp!=OP_Noop ){
149089 if( pIn->nPrefix ){
149090 assert( pLoop->wsFlags & WHERE_IN_EARLYOUT );
 
 
 
 
 
 
149091 if( pLevel->iLeftJoin ){
149092 /* For LEFT JOIN queries, cursor pIn->iCur may not have been
149093 ** opened yet. This occurs for WHERE clauses such as
149094 ** "a = ? AND b IN (...)", where the index is on (a, b). If
149095 ** the RHS of the (a=?) is NULL, then the "b IN (...)" may
@@ -148933,13 +149096,20 @@
149096 ** never have been coded, but the body of the loop run to
149097 ** return the null-row. So, if the cursor is not open yet,
149098 ** jump over the OP_Next or OP_Prev instruction about to
149099 ** be coded. */
149100 sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur,
149101 sqlite3VdbeCurrentAddr(v) + 2 +
149102 ((pLoop->wsFlags & WHERE_VIRTUALTABLE)==0)
149103 );
149104 VdbeCoverage(v);
149105 }
149106 if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ){
149107 sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur,
149108 sqlite3VdbeCurrentAddr(v)+2,
149109 pIn->iBase, pIn->nPrefix);
149110 VdbeCoverage(v);
149111 }
149112 }
149113 sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
149114 VdbeCoverage(v);
149115 VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Prev);
@@ -150053,10 +150223,11 @@
150223
150224 ExprList *pSublist = 0; /* Expression list for sub-query */
150225 Window *pMWin = p->pWin; /* Master window object */
150226 Window *pWin; /* Window object iterator */
150227 Table *pTab;
150228 u32 selFlags = p->selFlags;
150229
150230 pTab = sqlite3DbMallocZero(db, sizeof(Table));
150231 if( pTab==0 ){
150232 return sqlite3ErrorToParser(db, SQLITE_NOMEM);
150233 }
@@ -150142,10 +150313,11 @@
150313 Table *pTab2;
150314 p->pSrc->a[0].pSelect = pSub;
150315 sqlite3SrcListAssignCursors(pParse, p->pSrc);
150316 pSub->selFlags |= SF_Expanded;
150317 pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE);
150318 pSub->selFlags |= (selFlags & SF_Aggregate);
150319 if( pTab2==0 ){
150320 /* Might actually be some other kind of error, but in that case
150321 ** pParse->nErr will be set, so if SQLITE_NOMEM is set, we will get
150322 ** the correct error message regardless. */
150323 rc = SQLITE_NOMEM;
@@ -151030,10 +151202,11 @@
151202 int regArg;
151203 int nArg = 0;
151204 Window *pWin;
151205 for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
151206 FuncDef *pFunc = pWin->pFunc;
151207 assert( pWin->regAccum );
151208 sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
151209 nArg = MAX(nArg, windowArgCount(pWin));
151210 if( pMWin->regStartRowid==0 ){
151211 if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){
151212 sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp);
@@ -151408,10 +151581,14 @@
151581 pNew->eFrmType = p->eFrmType;
151582 pNew->eEnd = p->eEnd;
151583 pNew->eStart = p->eStart;
151584 pNew->eExclude = p->eExclude;
151585 pNew->regResult = p->regResult;
151586 pNew->regAccum = p->regAccum;
151587 pNew->iArgCol = p->iArgCol;
151588 pNew->iEphCsr = p->iEphCsr;
151589 pNew->bExprArgs = p->bExprArgs;
151590 pNew->pStart = sqlite3ExprDup(db, p->pStart, 0);
151591 pNew->pEnd = sqlite3ExprDup(db, p->pEnd, 0);
151592 pNew->pOwner = pOwner;
151593 pNew->bImplicitFrame = p->bImplicitFrame;
151594 }
@@ -152245,10 +152422,11 @@
152422 if( p ){
152423 /* memset(p, 0, sizeof(Expr)); */
152424 p->op = (u8)op;
152425 p->affExpr = 0;
152426 p->flags = EP_Leaf;
152427 ExprClearVVAProperties(p);
152428 p->iAgg = -1;
152429 p->pLeft = p->pRight = 0;
152430 p->x.pList = 0;
152431 p->pAggInfo = 0;
152432 p->y.pTab = 0;
@@ -162084,15 +162262,10 @@
162262 createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
162263 createCollation(db, "RTRIM", SQLITE_UTF8, 0, rtrimCollFunc, 0);
162264 if( db->mallocFailed ){
162265 goto opendb_out;
162266 }
 
 
 
 
 
162267
162268 /* Parse the filename/URI argument
162269 **
162270 ** Only allow sensible combinations of bits in the flags argument.
162271 ** Throw an error if any non-sense combination is used. If we
@@ -162133,11 +162306,13 @@
162306 sqlite3Error(db, rc);
162307 goto opendb_out;
162308 }
162309 sqlite3BtreeEnter(db->aDb[0].pBt);
162310 db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
162311 if( !db->mallocFailed ){
162312 sqlite3SetTextEncoding(db, SCHEMA_ENC(db));
162313 }
162314 sqlite3BtreeLeave(db->aDb[0].pBt);
162315 db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
162316
162317 /* The default safety_level for the main database is FULL; for the temp
162318 ** database it is OFF. This matches the pager layer defaults.
@@ -166664,10 +166839,11 @@
166839 const char *zEnd = &zCsr[nNode];/* End of interior node buffer */
166840 char *zBuffer = 0; /* Buffer to load terms into */
166841 i64 nAlloc = 0; /* Size of allocated buffer */
166842 int isFirstTerm = 1; /* True when processing first term on page */
166843 sqlite3_int64 iChild; /* Block id of child node to descend to */
166844 int nBuffer = 0; /* Total term size */
166845
166846 /* Skip over the 'height' varint that occurs at the start of every
166847 ** interior node. Then load the blockid of the left-child of the b-tree
166848 ** node into variable iChild.
166849 **
@@ -166688,16 +166864,19 @@
166864
166865 while( zCsr<zEnd && (piFirst || piLast) ){
166866 int cmp; /* memcmp() result */
166867 int nSuffix; /* Size of term suffix */
166868 int nPrefix = 0; /* Size of term prefix */
 
166869
166870 /* Load the next term on the node into zBuffer. Use realloc() to expand
166871 ** the size of zBuffer if required. */
166872 if( !isFirstTerm ){
166873 zCsr += fts3GetVarint32(zCsr, &nPrefix);
166874 if( nPrefix>nBuffer ){
166875 rc = FTS_CORRUPT_VTAB;
166876 goto finish_scan;
166877 }
166878 }
166879 isFirstTerm = 0;
166880 zCsr += fts3GetVarint32(zCsr, &nSuffix);
166881
166882 assert( nPrefix>=0 && nSuffix>=0 );
@@ -179916,10 +180095,16 @@
180095
180096 /* If nSeg is less that zero, then there is no level with at least
180097 ** nMin segments and no hint in the %_stat table. No work to do.
180098 ** Exit early in this case. */
180099 if( nSeg<=0 ) break;
180100
180101 assert( nMod<=0x7FFFFFFF );
180102 if( iAbsLevel<0 || iAbsLevel>(nMod<<32) ){
180103 rc = FTS_CORRUPT_VTAB;
180104 break;
180105 }
180106
180107 /* Open a cursor to iterate through the contents of the oldest nSeg
180108 ** indexes of absolute level iAbsLevel. If this cursor is opened using
180109 ** the 'hint' parameters, it is possible that there are less than nSeg
180110 ** segments available in level iAbsLevel. In this case, no work is
@@ -189652,12 +189837,14 @@
189837 pRtree->nAux++;
189838 sqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1);
189839 }else if( pRtree->nAux>0 ){
189840 break;
189841 }else{
189842 static const char *azFormat[] = {",%.*s REAL", ",%.*s INT"};
189843 pRtree->nDim2++;
189844 sqlite3_str_appendf(pSql, azFormat[eCoordType],
189845 rtreeTokenLength(zArg), zArg);
189846 }
189847 }
189848 sqlite3_str_appendf(pSql, ");");
189849 zSql = sqlite3_str_finish(pSql);
189850 if( !zSql ){
@@ -192389,11 +192576,11 @@
192576 ** 1. uPattern is an unescaped match-all character "%",
192577 ** 2. uPattern is an unescaped match-one character "_",
192578 ** 3. uPattern is an unescaped escape character, or
192579 ** 4. uPattern is to be handled as an ordinary character
192580 */
192581 if( uPattern==MATCH_ALL && !prevEscape && uPattern!=(uint32_t)uEsc ){
192582 /* Case 1. */
192583 uint8_t c;
192584
192585 /* Skip any MATCH_ALL or MATCH_ONE characters that follow a
192586 ** MATCH_ALL. For each MATCH_ONE, skip one character in the
@@ -192415,16 +192602,16 @@
192602 }
192603 SQLITE_ICU_SKIP_UTF8(zString);
192604 }
192605 return 0;
192606
192607 }else if( uPattern==MATCH_ONE && !prevEscape && uPattern!=(uint32_t)uEsc ){
192608 /* Case 2. */
192609 if( *zString==0 ) return 0;
192610 SQLITE_ICU_SKIP_UTF8(zString);
192611
192612 }else if( uPattern==(uint32_t)uEsc && !prevEscape ){
192613 /* Case 3. */
192614 prevEscape = 1;
192615
192616 }else{
192617 /* Case 4. */
@@ -199222,10 +199409,11 @@
199409 }
199410 }
199411 i = 0;
199412 if( iSchema>=0 ){
199413 pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i;
199414 pIdxInfo->aConstraintUsage[iSchema].omit = 1;
199415 pIdxInfo->idxNum |= 0x01;
199416 }
199417 if( iName>=0 ){
199418 pIdxInfo->aConstraintUsage[iName].argvIndex = ++i;
199419 pIdxInfo->idxNum |= 0x02;
@@ -199436,11 +199624,13 @@
199624 assert( nPayload>=(u32)nLocal );
199625 assert( nLocal<=(nUsable-35) );
199626 if( nPayload>(u32)nLocal ){
199627 int j;
199628 int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
199629 if( iOff+nLocal>nUsable || nPayload>0x7fffffff ){
199630 goto statPageIsCorrupt;
199631 }
199632 pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
199633 pCell->nOvfl = nOvfl;
199634 pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
199635 if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT;
199636 pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]);
@@ -203769,11 +203959,11 @@
203959 const char *zSep = "";
203960 int rc = SQLITE_OK;
203961 SessionBuffer buf = {0, 0, 0};
203962 int nPk = 0;
203963
203964 sessionAppendStr(&buf, "DELETE FROM main.", &rc);
203965 sessionAppendIdent(&buf, zTab, &rc);
203966 sessionAppendStr(&buf, " WHERE ", &rc);
203967
203968 for(i=0; i<p->nCol; i++){
203969 if( p->abPK[i] ){
@@ -203852,11 +204042,11 @@
204042 int i;
204043 const char *zSep = "";
204044 SessionBuffer buf = {0, 0, 0};
204045
204046 /* Append "UPDATE tbl SET " */
204047 sessionAppendStr(&buf, "UPDATE main.", &rc);
204048 sessionAppendIdent(&buf, zTab, &rc);
204049 sessionAppendStr(&buf, " SET ", &rc);
204050
204051 /* Append the assignments */
204052 for(i=0; i<p->nCol; i++){
@@ -223538,11 +223728,11 @@
223728 int nArg, /* Number of args */
223729 sqlite3_value **apUnused /* Function arguments */
223730 ){
223731 assert( nArg==0 );
223732 UNUSED_PARAM2(nArg, apUnused);
223733 sqlite3_result_text(pCtx, "fts5: 2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84f525", -1, SQLITE_TRANSIENT);
223734 }
223735
223736 /*
223737 ** Return true if zName is the extension on one of the shadow tables used
223738 ** by this module.
@@ -228320,12 +228510,12 @@
228510 }
228511 #endif /* SQLITE_CORE */
228512 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
228513
228514 /************** End of stmt.c ************************************************/
228515 #if __LINE__!=228515
228516 #undef SQLITE_SOURCE_ID
228517 #define SQLITE_SOURCE_ID "2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84alt2"
228518 #endif
228519 /* Return the source-id for this library */
228520 SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
228521 /************************** End of sqlite3.c ******************************/
228522
+1 -1
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -123,11 +123,11 @@
123123
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
124124
** [sqlite_version()] and [sqlite_source_id()].
125125
*/
126126
#define SQLITE_VERSION "3.32.0"
127127
#define SQLITE_VERSION_NUMBER 3032000
128
-#define SQLITE_SOURCE_ID "2020-02-27 16:21:39 951b39ca74c9bd933139e099d5555283278db475f410f202c162e5d1e6aef933"
128
+#define SQLITE_SOURCE_ID "2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84f525"
129129
130130
/*
131131
** CAPI3REF: Run-Time Library Version Numbers
132132
** KEYWORDS: sqlite3_version sqlite3_sourceid
133133
**
134134
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -123,11 +123,11 @@
123 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
124 ** [sqlite_version()] and [sqlite_source_id()].
125 */
126 #define SQLITE_VERSION "3.32.0"
127 #define SQLITE_VERSION_NUMBER 3032000
128 #define SQLITE_SOURCE_ID "2020-02-27 16:21:39 951b39ca74c9bd933139e099d5555283278db475f410f202c162e5d1e6aef933"
129
130 /*
131 ** CAPI3REF: Run-Time Library Version Numbers
132 ** KEYWORDS: sqlite3_version sqlite3_sourceid
133 **
134
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -123,11 +123,11 @@
123 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
124 ** [sqlite_version()] and [sqlite_source_id()].
125 */
126 #define SQLITE_VERSION "3.32.0"
127 #define SQLITE_VERSION_NUMBER 3032000
128 #define SQLITE_SOURCE_ID "2020-03-21 23:10:38 5d14a1c4f2fc17de98ad685ad1422cdfda89dfccb00afcaf32ee416b6f84f525"
129
130 /*
131 ** CAPI3REF: Run-Time Library Version Numbers
132 ** KEYWORDS: sqlite3_version sqlite3_sourceid
133 **
134
+3 -3
--- src/stat.c
+++ src/stat.c
@@ -659,11 +659,11 @@
659659
/*
660660
** Gather statistics on artifact types, counts, and sizes.
661661
**
662662
** Only populate the artstat.atype field if the bWithTypes parameter is true.
663663
*/
664
-static void gather_artifact_stats(int bWithTypes){
664
+void gather_artifact_stats(int bWithTypes){
665665
static const char zSql[] =
666666
@ CREATE TEMP TABLE artstat(
667667
@ id INTEGER PRIMARY KEY, -- Corresponds to BLOB.RID
668668
@ atype TEXT, -- 'data', 'manifest', 'tag', 'wiki', etc.
669669
@ isDelta BOOLEAN, -- true if stored as a delta
@@ -896,12 +896,12 @@
896896
sqlite3_int64 szExp = db_column_int64(&q, 4);
897897
@ <tr><td>%h(zType)</td>
898898
@ <td data-sortkey='%08x(nTotal)' align='right'>%,d(nTotal)</td>
899899
@ <td data-sortkey='%08x(nFull)' align='right'>%,d(nFull)</td>
900900
@ <td data-sortkey='%08x(nDelta)' align='right'>%,d(nDelta)</td>
901
- @ <td data-sortkey='%016x(szCmpr)' align='right'>%,lld(szCmpr)</td>
902
- @ <td data-sortkey='%016x(szExp)' align='right'>%,lld(szExp)</td>
901
+ @ <td data-sortkey='%016llx(szCmpr)' align='right'>%,lld(szCmpr)</td>
902
+ @ <td data-sortkey='%016llx(szExp)' align='right'>%,lld(szExp)</td>
903903
}
904904
@ </tbody></table>
905905
db_finalize(&q);
906906
907907
if( db_exists("SELECT 1 FROM artstat WHERE atype='unused'") ){
908908
--- src/stat.c
+++ src/stat.c
@@ -659,11 +659,11 @@
659 /*
660 ** Gather statistics on artifact types, counts, and sizes.
661 **
662 ** Only populate the artstat.atype field if the bWithTypes parameter is true.
663 */
664 static void gather_artifact_stats(int bWithTypes){
665 static const char zSql[] =
666 @ CREATE TEMP TABLE artstat(
667 @ id INTEGER PRIMARY KEY, -- Corresponds to BLOB.RID
668 @ atype TEXT, -- 'data', 'manifest', 'tag', 'wiki', etc.
669 @ isDelta BOOLEAN, -- true if stored as a delta
@@ -896,12 +896,12 @@
896 sqlite3_int64 szExp = db_column_int64(&q, 4);
897 @ <tr><td>%h(zType)</td>
898 @ <td data-sortkey='%08x(nTotal)' align='right'>%,d(nTotal)</td>
899 @ <td data-sortkey='%08x(nFull)' align='right'>%,d(nFull)</td>
900 @ <td data-sortkey='%08x(nDelta)' align='right'>%,d(nDelta)</td>
901 @ <td data-sortkey='%016x(szCmpr)' align='right'>%,lld(szCmpr)</td>
902 @ <td data-sortkey='%016x(szExp)' align='right'>%,lld(szExp)</td>
903 }
904 @ </tbody></table>
905 db_finalize(&q);
906
907 if( db_exists("SELECT 1 FROM artstat WHERE atype='unused'") ){
908
--- src/stat.c
+++ src/stat.c
@@ -659,11 +659,11 @@
659 /*
660 ** Gather statistics on artifact types, counts, and sizes.
661 **
662 ** Only populate the artstat.atype field if the bWithTypes parameter is true.
663 */
664 void gather_artifact_stats(int bWithTypes){
665 static const char zSql[] =
666 @ CREATE TEMP TABLE artstat(
667 @ id INTEGER PRIMARY KEY, -- Corresponds to BLOB.RID
668 @ atype TEXT, -- 'data', 'manifest', 'tag', 'wiki', etc.
669 @ isDelta BOOLEAN, -- true if stored as a delta
@@ -896,12 +896,12 @@
896 sqlite3_int64 szExp = db_column_int64(&q, 4);
897 @ <tr><td>%h(zType)</td>
898 @ <td data-sortkey='%08x(nTotal)' align='right'>%,d(nTotal)</td>
899 @ <td data-sortkey='%08x(nFull)' align='right'>%,d(nFull)</td>
900 @ <td data-sortkey='%08x(nDelta)' align='right'>%,d(nDelta)</td>
901 @ <td data-sortkey='%016llx(szCmpr)' align='right'>%,lld(szCmpr)</td>
902 @ <td data-sortkey='%016llx(szExp)' align='right'>%,lld(szExp)</td>
903 }
904 @ </tbody></table>
905 db_finalize(&q);
906
907 if( db_exists("SELECT 1 FROM artstat WHERE atype='unused'") ){
908
+13 -1
--- src/style.c
+++ src/style.c
@@ -79,10 +79,15 @@
7979
/*
8080
** Ad-unit styles.
8181
*/
8282
static unsigned adUnitFlags = 0;
8383
84
+/*
85
+** Submenu disable flag
86
+*/
87
+static int submenuEnable = 1;
88
+
8489
/*
8590
** Flags for various javascript files needed prior to </body>
8691
*/
8792
static int needHrefJs = 0; /* href.js */
8893
static int needSortJs = 0; /* sorttable.js */
@@ -316,10 +321,17 @@
316321
aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
317322
aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
318323
nSubmenuCtrl++;
319324
}
320325
}
326
+
327
+/*
328
+** Disable or enable the submenu
329
+*/
330
+void style_submenu_enable(int onOff){
331
+ submenuEnable = onOff;
332
+}
321333
322334
323335
/*
324336
** Compare two submenu items for sorting purposes
325337
*/
@@ -774,11 +786,11 @@
774786
/* Go back and put the submenu at the top of the page. We delay the
775787
** creation of the submenu until the end so that we can add elements
776788
** to the submenu while generating page text.
777789
*/
778790
cgi_destination(CGI_HEADER);
779
- if( nSubmenu+nSubmenuCtrl>0 ){
791
+ if( submenuEnable && nSubmenu+nSubmenuCtrl>0 ){
780792
int i;
781793
if( nSubmenuCtrl ){
782794
@ <form id='f01' method='GET' action='%R/%s(g.zPath)'>
783795
@ <input type='hidden' name='udc' value='1'>
784796
cgi_tag_query_parameter("udc");
785797
--- src/style.c
+++ src/style.c
@@ -79,10 +79,15 @@
79 /*
80 ** Ad-unit styles.
81 */
82 static unsigned adUnitFlags = 0;
83
 
 
 
 
 
84 /*
85 ** Flags for various javascript files needed prior to </body>
86 */
87 static int needHrefJs = 0; /* href.js */
88 static int needSortJs = 0; /* sorttable.js */
@@ -316,10 +321,17 @@
316 aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
317 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
318 nSubmenuCtrl++;
319 }
320 }
 
 
 
 
 
 
 
321
322
323 /*
324 ** Compare two submenu items for sorting purposes
325 */
@@ -774,11 +786,11 @@
774 /* Go back and put the submenu at the top of the page. We delay the
775 ** creation of the submenu until the end so that we can add elements
776 ** to the submenu while generating page text.
777 */
778 cgi_destination(CGI_HEADER);
779 if( nSubmenu+nSubmenuCtrl>0 ){
780 int i;
781 if( nSubmenuCtrl ){
782 @ <form id='f01' method='GET' action='%R/%s(g.zPath)'>
783 @ <input type='hidden' name='udc' value='1'>
784 cgi_tag_query_parameter("udc");
785
--- src/style.c
+++ src/style.c
@@ -79,10 +79,15 @@
79 /*
80 ** Ad-unit styles.
81 */
82 static unsigned adUnitFlags = 0;
83
84 /*
85 ** Submenu disable flag
86 */
87 static int submenuEnable = 1;
88
89 /*
90 ** Flags for various javascript files needed prior to </body>
91 */
92 static int needHrefJs = 0; /* href.js */
93 static int needSortJs = 0; /* sorttable.js */
@@ -316,10 +321,17 @@
321 aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
322 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
323 nSubmenuCtrl++;
324 }
325 }
326
327 /*
328 ** Disable or enable the submenu
329 */
330 void style_submenu_enable(int onOff){
331 submenuEnable = onOff;
332 }
333
334
335 /*
336 ** Compare two submenu items for sorting purposes
337 */
@@ -774,11 +786,11 @@
786 /* Go back and put the submenu at the top of the page. We delay the
787 ** creation of the submenu until the end so that we can add elements
788 ** to the submenu while generating page text.
789 */
790 cgi_destination(CGI_HEADER);
791 if( submenuEnable && nSubmenu+nSubmenuCtrl>0 ){
792 int i;
793 if( nSubmenuCtrl ){
794 @ <form id='f01' method='GET' action='%R/%s(g.zPath)'>
795 @ <input type='hidden' name='udc' value='1'>
796 cgi_tag_query_parameter("udc");
797
+28 -3
--- src/timeline.c
+++ src/timeline.c
@@ -115,10 +115,11 @@
115115
#define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
116116
#define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */
117117
#define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
118118
#define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */
119119
#define TIMELINE_NOTKT 0x2000000 /* Omit extra ticket classes */
120
+#define TIMELINE_FORUMTXT 0x4000000 /* Render all forum messages */
120121
#endif
121122
122123
/*
123124
** Hash a string and use the hash to determine a background color.
124125
*/
@@ -233,11 +234,11 @@
233234
** 2. Date/Time
234235
** 3. Comment string
235236
** 4. User
236237
** 5. True if is a leaf
237238
** 6. background color
238
-** 7. type ("ci", "w", "t", "e", "g", "div")
239
+** 7. type ("ci", "w", "t", "e", "g", "f", "div")
239240
** 8. list of symbolic tags.
240241
** 9. tagid for ticket or wiki or event
241242
** 10. Short comment to user for repeated tickets and wiki
242243
*/
243244
void www_print_timeline(
@@ -428,17 +429,17 @@
428429
zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
429430
tagid);
430431
zDateLink = href("%R/technote/%s",zId);
431432
free(zId);
432433
}else{
433
- zDateLink = href("%R/timeline?c=%t",zDate);
434
+ zDateLink = href("%R/timeline?c=%t&y=a",zDate);
434435
}
435436
}else if( zUuid ){
436437
if( bTimestampLinksToInfo ){
437438
zDateLink = chref("timelineHistLink", "%R/info/%!S", zUuid);
438439
}else{
439
- zDateLink = chref("timelineHistLink", "%R/timeline?c=%!S", zUuid);
440
+ zDateLink = chref("timelineHistLink", "%R/timeline?c=%!S&y=a", zUuid);
440441
}
441442
}else{
442443
zDateLink = mprintf("<a>");
443444
}
444445
@ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
@@ -785,10 +786,26 @@
785786
db_reset(&fchngQuery);
786787
if( inUl ){
787788
@ </ul>
788789
}
789790
}
791
+
792
+ /* Show the complete text of forum messages */
793
+ if( (tmFlags & (TIMELINE_FORUMTXT))!=0
794
+ && zType[0]=='f' && g.perm.Hyperlink
795
+ && (!content_is_private(rid) || g.perm.ModForum)
796
+ ){
797
+ Manifest *pPost = manifest_get(rid, CFTYPE_FORUM, 0);
798
+ if( pPost ){
799
+ const char *zClass = "forumTimeline";
800
+ if( forum_rid_has_been_edited(rid) ){
801
+ zClass = "forumTimeline forumObs";
802
+ }
803
+ forum_render(0, pPost->zMimetype, pPost->zWiki, zClass);
804
+ manifest_destroy(pPost);
805
+ }
806
+ }
790807
}
791808
if( suppressCnt ){
792809
@ <span class="timelineDisabled">... %d(suppressCnt) similar
793810
@ event%s(suppressCnt>1?"s":"") omitted.</span>
794811
suppressCnt = 0;
@@ -1565,11 +1582,13 @@
15651582
** ss=VIEWSTYLE c: "Compact" v: "Verbose" m: "Modern" j: "Columnar"
15661583
** advm Use the "Advanced" or "Busy" menu design.
15671584
** ng No Graph.
15681585
** ncp Omit cherrypick merges
15691586
** nd Do not highlight the focus check-in
1587
+** nsm Omit the submenu
15701588
** v Show details of files changed
1589
+** vfx Show complete text of forum messages
15711590
** f=CHECKIN Show family (immediate parents and children) of CHECKIN
15721591
** from=CHECKIN Path from...
15731592
** to=CHECKIN ... to this
15741593
** shortest ... show only the shortest path
15751594
** rel ... also show related checkins
@@ -1790,10 +1809,13 @@
17901809
tmFlags &= ~TIMELINE_CHPICK;
17911810
}
17921811
if( PB("ng") || zSearch!=0 ){
17931812
tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
17941813
}
1814
+ if( PB("nsm") ){
1815
+ style_submenu_enable(0);
1816
+ }
17951817
if( PB("brbg") ){
17961818
tmFlags |= TIMELINE_BRCOLOR;
17971819
}
17981820
if( PB("unhide") ){
17991821
tmFlags |= TIMELINE_UNHIDE;
@@ -1874,10 +1896,13 @@
18741896
blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
18751897
blob_append(&sql, timeline_query_for_www(), -1);
18761898
if( PB("fc") || PB("v") || PB("detail") ){
18771899
tmFlags |= TIMELINE_FCHANGES;
18781900
}
1901
+ if( PB("vfx") ){
1902
+ tmFlags |= TIMELINE_FORUMTXT;
1903
+ }
18791904
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
18801905
blob_append_sql(&sql,
18811906
" AND NOT EXISTS(SELECT 1 FROM tagxref"
18821907
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
18831908
TAG_HIDDEN
18841909
--- src/timeline.c
+++ src/timeline.c
@@ -115,10 +115,11 @@
115 #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
116 #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */
117 #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
118 #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */
119 #define TIMELINE_NOTKT 0x2000000 /* Omit extra ticket classes */
 
120 #endif
121
122 /*
123 ** Hash a string and use the hash to determine a background color.
124 */
@@ -233,11 +234,11 @@
233 ** 2. Date/Time
234 ** 3. Comment string
235 ** 4. User
236 ** 5. True if is a leaf
237 ** 6. background color
238 ** 7. type ("ci", "w", "t", "e", "g", "div")
239 ** 8. list of symbolic tags.
240 ** 9. tagid for ticket or wiki or event
241 ** 10. Short comment to user for repeated tickets and wiki
242 */
243 void www_print_timeline(
@@ -428,17 +429,17 @@
428 zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
429 tagid);
430 zDateLink = href("%R/technote/%s",zId);
431 free(zId);
432 }else{
433 zDateLink = href("%R/timeline?c=%t",zDate);
434 }
435 }else if( zUuid ){
436 if( bTimestampLinksToInfo ){
437 zDateLink = chref("timelineHistLink", "%R/info/%!S", zUuid);
438 }else{
439 zDateLink = chref("timelineHistLink", "%R/timeline?c=%!S", zUuid);
440 }
441 }else{
442 zDateLink = mprintf("<a>");
443 }
444 @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
@@ -785,10 +786,26 @@
785 db_reset(&fchngQuery);
786 if( inUl ){
787 @ </ul>
788 }
789 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
790 }
791 if( suppressCnt ){
792 @ <span class="timelineDisabled">... %d(suppressCnt) similar
793 @ event%s(suppressCnt>1?"s":"") omitted.</span>
794 suppressCnt = 0;
@@ -1565,11 +1582,13 @@
1565 ** ss=VIEWSTYLE c: "Compact" v: "Verbose" m: "Modern" j: "Columnar"
1566 ** advm Use the "Advanced" or "Busy" menu design.
1567 ** ng No Graph.
1568 ** ncp Omit cherrypick merges
1569 ** nd Do not highlight the focus check-in
 
1570 ** v Show details of files changed
 
1571 ** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1572 ** from=CHECKIN Path from...
1573 ** to=CHECKIN ... to this
1574 ** shortest ... show only the shortest path
1575 ** rel ... also show related checkins
@@ -1790,10 +1809,13 @@
1790 tmFlags &= ~TIMELINE_CHPICK;
1791 }
1792 if( PB("ng") || zSearch!=0 ){
1793 tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
1794 }
 
 
 
1795 if( PB("brbg") ){
1796 tmFlags |= TIMELINE_BRCOLOR;
1797 }
1798 if( PB("unhide") ){
1799 tmFlags |= TIMELINE_UNHIDE;
@@ -1874,10 +1896,13 @@
1874 blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
1875 blob_append(&sql, timeline_query_for_www(), -1);
1876 if( PB("fc") || PB("v") || PB("detail") ){
1877 tmFlags |= TIMELINE_FCHANGES;
1878 }
 
 
 
1879 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1880 blob_append_sql(&sql,
1881 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1882 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
1883 TAG_HIDDEN
1884
--- src/timeline.c
+++ src/timeline.c
@@ -115,10 +115,11 @@
115 #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
116 #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */
117 #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
118 #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */
119 #define TIMELINE_NOTKT 0x2000000 /* Omit extra ticket classes */
120 #define TIMELINE_FORUMTXT 0x4000000 /* Render all forum messages */
121 #endif
122
123 /*
124 ** Hash a string and use the hash to determine a background color.
125 */
@@ -233,11 +234,11 @@
234 ** 2. Date/Time
235 ** 3. Comment string
236 ** 4. User
237 ** 5. True if is a leaf
238 ** 6. background color
239 ** 7. type ("ci", "w", "t", "e", "g", "f", "div")
240 ** 8. list of symbolic tags.
241 ** 9. tagid for ticket or wiki or event
242 ** 10. Short comment to user for repeated tickets and wiki
243 */
244 void www_print_timeline(
@@ -428,17 +429,17 @@
429 zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
430 tagid);
431 zDateLink = href("%R/technote/%s",zId);
432 free(zId);
433 }else{
434 zDateLink = href("%R/timeline?c=%t&y=a",zDate);
435 }
436 }else if( zUuid ){
437 if( bTimestampLinksToInfo ){
438 zDateLink = chref("timelineHistLink", "%R/info/%!S", zUuid);
439 }else{
440 zDateLink = chref("timelineHistLink", "%R/timeline?c=%!S&y=a", zUuid);
441 }
442 }else{
443 zDateLink = mprintf("<a>");
444 }
445 @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
@@ -785,10 +786,26 @@
786 db_reset(&fchngQuery);
787 if( inUl ){
788 @ </ul>
789 }
790 }
791
792 /* Show the complete text of forum messages */
793 if( (tmFlags & (TIMELINE_FORUMTXT))!=0
794 && zType[0]=='f' && g.perm.Hyperlink
795 && (!content_is_private(rid) || g.perm.ModForum)
796 ){
797 Manifest *pPost = manifest_get(rid, CFTYPE_FORUM, 0);
798 if( pPost ){
799 const char *zClass = "forumTimeline";
800 if( forum_rid_has_been_edited(rid) ){
801 zClass = "forumTimeline forumObs";
802 }
803 forum_render(0, pPost->zMimetype, pPost->zWiki, zClass);
804 manifest_destroy(pPost);
805 }
806 }
807 }
808 if( suppressCnt ){
809 @ <span class="timelineDisabled">... %d(suppressCnt) similar
810 @ event%s(suppressCnt>1?"s":"") omitted.</span>
811 suppressCnt = 0;
@@ -1565,11 +1582,13 @@
1582 ** ss=VIEWSTYLE c: "Compact" v: "Verbose" m: "Modern" j: "Columnar"
1583 ** advm Use the "Advanced" or "Busy" menu design.
1584 ** ng No Graph.
1585 ** ncp Omit cherrypick merges
1586 ** nd Do not highlight the focus check-in
1587 ** nsm Omit the submenu
1588 ** v Show details of files changed
1589 ** vfx Show complete text of forum messages
1590 ** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1591 ** from=CHECKIN Path from...
1592 ** to=CHECKIN ... to this
1593 ** shortest ... show only the shortest path
1594 ** rel ... also show related checkins
@@ -1790,10 +1809,13 @@
1809 tmFlags &= ~TIMELINE_CHPICK;
1810 }
1811 if( PB("ng") || zSearch!=0 ){
1812 tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
1813 }
1814 if( PB("nsm") ){
1815 style_submenu_enable(0);
1816 }
1817 if( PB("brbg") ){
1818 tmFlags |= TIMELINE_BRCOLOR;
1819 }
1820 if( PB("unhide") ){
1821 tmFlags |= TIMELINE_UNHIDE;
@@ -1874,10 +1896,13 @@
1896 blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
1897 blob_append(&sql, timeline_query_for_www(), -1);
1898 if( PB("fc") || PB("v") || PB("detail") ){
1899 tmFlags |= TIMELINE_FCHANGES;
1900 }
1901 if( PB("vfx") ){
1902 tmFlags |= TIMELINE_FORUMTXT;
1903 }
1904 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1905 blob_append_sql(&sql,
1906 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1907 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
1908 TAG_HIDDEN
1909
+55 -27
--- src/tkt.c
+++ src/tkt.c
@@ -932,11 +932,18 @@
932932
933933
/*
934934
** WEBPAGE: tkthistory
935935
** URL: /tkthistory?name=TICKETUUID
936936
**
937
-** Show the complete change history for a single ticket
937
+** Show the complete change history for a single ticket. Or (to put it
938
+** another way) show a list of artifacts associated with a single ticket.
939
+**
940
+** By default, the artifacts are decoded and formatted. Text fields
941
+** are formatted as text/plain, since in the general case Fossil does
942
+** not have knowledge of the encoding. If the "raw" query parameter
943
+** is present, then the* undecoded and unformatted text of each artifact
944
+** is displayed.
938945
*/
939946
void tkthistory_page(void){
940947
Stmt q;
941948
char *zTitle;
942949
const char *zUuid;
@@ -952,23 +959,28 @@
952959
zTitle = mprintf("History Of Ticket %h", zUuid);
953960
style_submenu_element("Status", "%s/info/%s", g.zTop, zUuid);
954961
style_submenu_element("Check-ins", "%s/tkttimeline?name=%s&y=ci",
955962
g.zTop, zUuid);
956963
style_submenu_element("Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid);
957
- if( P("plaintext")!=0 ){
958
- style_submenu_element("Formatted", "%R/tkthistory/%s", zUuid);
959
- }else{
960
- style_submenu_element("Plaintext", "%R/tkthistory/%s?plaintext", zUuid);
964
+ if( P("raw")!=0 ){
965
+ style_submenu_element("Decoded", "%R/tkthistory/%s", zUuid);
966
+ }else if( g.perm.Admin ){
967
+ style_submenu_element("Raw", "%R/tkthistory/%s?raw", zUuid);
961968
}
962969
style_header("%z", zTitle);
963970
964971
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
965972
if( tagid==0 ){
966973
@ No such ticket: %h(zUuid)
967974
style_footer();
968975
return;
969976
}
977
+ if( P("raw")!=0 ){
978
+ @ <h2>Raw Artifacts Associated With Ticket %h(zUuid)</h2>
979
+ }else{
980
+ @ <h2>Artifacts Associated With Ticket %h(zUuid)</h2>
981
+ }
970982
db_prepare(&q,
971983
"SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL"
972984
" FROM event, blob"
973985
" WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
974986
" AND blob.rid=event.objid"
@@ -978,20 +990,19 @@
978990
" WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
979991
" AND blob.rid=attachid"
980992
" ORDER BY 1",
981993
tagid, tagid
982994
);
983
- while( db_step(&q)==SQLITE_ROW ){
995
+ for(nChng=0; db_step(&q)==SQLITE_ROW; nChng++){
984996
Manifest *pTicket;
985997
const char *zDate = db_column_text(&q, 0);
986998
int rid = db_column_int(&q, 1);
987999
const char *zChngUuid = db_column_text(&q, 2);
9881000
const char *zFile = db_column_text(&q, 4);
9891001
if( nChng==0 ){
9901002
@ <ol>
9911003
}
992
- nChng++;
9931004
if( zFile!=0 ){
9941005
const char *zSrc = db_column_text(&q, 3);
9951006
const char *zUser = db_column_text(&q, 5);
9961007
if( zSrc==0 || zSrc[0]==0 ){
9971008
@
@@ -1013,11 +1024,20 @@
10131024
@ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
10141025
@ (rid %d(rid)) by
10151026
hyperlink_to_user(pTicket->zUser,zDate," on");
10161027
hyperlink_to_date(zDate, ":");
10171028
@ </p>
1018
- ticket_output_change_artifact(pTicket, "a");
1029
+ if( P("raw")!=0 ){
1030
+ Blob c;
1031
+ content_get(rid, &c);
1032
+ @ <blockquote><pre>
1033
+ @ %h(blob_str(&c))
1034
+ @ </pre></blockquote>
1035
+ blob_reset(&c);
1036
+ }else{
1037
+ ticket_output_change_artifact(pTicket, "a", nChng);
1038
+ }
10191039
}
10201040
manifest_destroy(pTicket);
10211041
}
10221042
}
10231043
db_finalize(&q);
@@ -1041,37 +1061,45 @@
10411061
10421062
/*
10431063
** The pTkt object is a ticket change artifact. Output a detailed
10441064
** description of this object.
10451065
*/
1046
-void ticket_output_change_artifact(Manifest *pTkt, const char *zListType){
1066
+void ticket_output_change_artifact(
1067
+ Manifest *pTkt, /* Parsed artifact for the ticket change */
1068
+ const char *zListType, /* Which type of list */
1069
+ int n /* Which ticket change is this */
1070
+){
10471071
int i;
1048
- int wikiFlags = WIKI_NOBADLINKS;
1049
- const char *zBlock = "<blockquote>";
1050
- const char *zEnd = "</blockquote>";
1051
- if( P("plaintext")!=0 ){
1052
- wikiFlags |= WIKI_LINKSONLY;
1053
- zBlock = "<blockquote><pre class='verbatim'>";
1054
- zEnd = "</pre></blockquote>";
1055
- }
10561072
if( zListType==0 ) zListType = "1";
1073
+ getAllTicketFields();
10571074
@ <ol type="%s(zListType)">
10581075
for(i=0; i<pTkt->nField; i++){
10591076
Blob val;
1060
- const char *z;
1077
+ const char *z, *zX;
1078
+ int id;
10611079
z = pTkt->aField[i].zName;
10621080
blob_set(&val, pTkt->aField[i].zValue);
1063
- if( z[0]=='+' ){
1064
- @ <li>Appended to %h(&z[1]):%s(zBlock)
1065
- wiki_convert(&val, 0, wikiFlags);
1066
- @ %s(zEnd)</li>
1067
- }else if( blob_size(&val)>50 || contains_newline(&val) ){
1068
- @ <li>Change %h(z) to:%s(zBlock)
1069
- wiki_convert(&val, 0, wikiFlags);
1070
- @ %s(zEnd)</li>
1081
+ zX = z[0]=='+' ? z+1 : z;
1082
+ id = fieldId(zX);
1083
+ @ <li>\
1084
+ if( id<0 ){
1085
+ @ Untracked field %h(zX):
1086
+ }else if( aField[id].mUsed==USEDBY_TICKETCHNG ){
1087
+ @ %h(zX):
1088
+ }else if( n==0 ){
1089
+ @ %h(zX) initialized to:
1090
+ }else if( z[0]=='+' && (aField[id].mUsed&USEDBY_TICKET)!=0 ){
1091
+ @ Appended to %h(zX):
1092
+ }else{
1093
+ @ %h(zX) changed to:
1094
+ }
1095
+ if( blob_size(&val)>50 || contains_newline(&val) ){
1096
+ @ <blockquote><pre class='verbatim'>
1097
+ @ %h(blob_str(&val))
1098
+ @ </pre></blockquote></li>
10711099
}else{
1072
- @ <li>Change %h(z) to "%h(blob_str(&val))"</li>
1100
+ @ "%h(blob_str(&val))"</li>
10731101
}
10741102
blob_reset(&val);
10751103
}
10761104
@ </ol>
10771105
}
10781106
--- src/tkt.c
+++ src/tkt.c
@@ -932,11 +932,18 @@
932
933 /*
934 ** WEBPAGE: tkthistory
935 ** URL: /tkthistory?name=TICKETUUID
936 **
937 ** Show the complete change history for a single ticket
 
 
 
 
 
 
 
938 */
939 void tkthistory_page(void){
940 Stmt q;
941 char *zTitle;
942 const char *zUuid;
@@ -952,23 +959,28 @@
952 zTitle = mprintf("History Of Ticket %h", zUuid);
953 style_submenu_element("Status", "%s/info/%s", g.zTop, zUuid);
954 style_submenu_element("Check-ins", "%s/tkttimeline?name=%s&y=ci",
955 g.zTop, zUuid);
956 style_submenu_element("Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid);
957 if( P("plaintext")!=0 ){
958 style_submenu_element("Formatted", "%R/tkthistory/%s", zUuid);
959 }else{
960 style_submenu_element("Plaintext", "%R/tkthistory/%s?plaintext", zUuid);
961 }
962 style_header("%z", zTitle);
963
964 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
965 if( tagid==0 ){
966 @ No such ticket: %h(zUuid)
967 style_footer();
968 return;
969 }
 
 
 
 
 
970 db_prepare(&q,
971 "SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL"
972 " FROM event, blob"
973 " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
974 " AND blob.rid=event.objid"
@@ -978,20 +990,19 @@
978 " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
979 " AND blob.rid=attachid"
980 " ORDER BY 1",
981 tagid, tagid
982 );
983 while( db_step(&q)==SQLITE_ROW ){
984 Manifest *pTicket;
985 const char *zDate = db_column_text(&q, 0);
986 int rid = db_column_int(&q, 1);
987 const char *zChngUuid = db_column_text(&q, 2);
988 const char *zFile = db_column_text(&q, 4);
989 if( nChng==0 ){
990 @ <ol>
991 }
992 nChng++;
993 if( zFile!=0 ){
994 const char *zSrc = db_column_text(&q, 3);
995 const char *zUser = db_column_text(&q, 5);
996 if( zSrc==0 || zSrc[0]==0 ){
997 @
@@ -1013,11 +1024,20 @@
1013 @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
1014 @ (rid %d(rid)) by
1015 hyperlink_to_user(pTicket->zUser,zDate," on");
1016 hyperlink_to_date(zDate, ":");
1017 @ </p>
1018 ticket_output_change_artifact(pTicket, "a");
 
 
 
 
 
 
 
 
 
1019 }
1020 manifest_destroy(pTicket);
1021 }
1022 }
1023 db_finalize(&q);
@@ -1041,37 +1061,45 @@
1041
1042 /*
1043 ** The pTkt object is a ticket change artifact. Output a detailed
1044 ** description of this object.
1045 */
1046 void ticket_output_change_artifact(Manifest *pTkt, const char *zListType){
 
 
 
 
1047 int i;
1048 int wikiFlags = WIKI_NOBADLINKS;
1049 const char *zBlock = "<blockquote>";
1050 const char *zEnd = "</blockquote>";
1051 if( P("plaintext")!=0 ){
1052 wikiFlags |= WIKI_LINKSONLY;
1053 zBlock = "<blockquote><pre class='verbatim'>";
1054 zEnd = "</pre></blockquote>";
1055 }
1056 if( zListType==0 ) zListType = "1";
 
1057 @ <ol type="%s(zListType)">
1058 for(i=0; i<pTkt->nField; i++){
1059 Blob val;
1060 const char *z;
 
1061 z = pTkt->aField[i].zName;
1062 blob_set(&val, pTkt->aField[i].zValue);
1063 if( z[0]=='+' ){
1064 @ <li>Appended to %h(&z[1]):%s(zBlock)
1065 wiki_convert(&val, 0, wikiFlags);
1066 @ %s(zEnd)</li>
1067 }else if( blob_size(&val)>50 || contains_newline(&val) ){
1068 @ <li>Change %h(z) to:%s(zBlock)
1069 wiki_convert(&val, 0, wikiFlags);
1070 @ %s(zEnd)</li>
 
 
 
 
 
 
 
 
 
 
1071 }else{
1072 @ <li>Change %h(z) to "%h(blob_str(&val))"</li>
1073 }
1074 blob_reset(&val);
1075 }
1076 @ </ol>
1077 }
1078
--- src/tkt.c
+++ src/tkt.c
@@ -932,11 +932,18 @@
932
933 /*
934 ** WEBPAGE: tkthistory
935 ** URL: /tkthistory?name=TICKETUUID
936 **
937 ** Show the complete change history for a single ticket. Or (to put it
938 ** another way) show a list of artifacts associated with a single ticket.
939 **
940 ** By default, the artifacts are decoded and formatted. Text fields
941 ** are formatted as text/plain, since in the general case Fossil does
942 ** not have knowledge of the encoding. If the "raw" query parameter
943 ** is present, then the* undecoded and unformatted text of each artifact
944 ** is displayed.
945 */
946 void tkthistory_page(void){
947 Stmt q;
948 char *zTitle;
949 const char *zUuid;
@@ -952,23 +959,28 @@
959 zTitle = mprintf("History Of Ticket %h", zUuid);
960 style_submenu_element("Status", "%s/info/%s", g.zTop, zUuid);
961 style_submenu_element("Check-ins", "%s/tkttimeline?name=%s&y=ci",
962 g.zTop, zUuid);
963 style_submenu_element("Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid);
964 if( P("raw")!=0 ){
965 style_submenu_element("Decoded", "%R/tkthistory/%s", zUuid);
966 }else if( g.perm.Admin ){
967 style_submenu_element("Raw", "%R/tkthistory/%s?raw", zUuid);
968 }
969 style_header("%z", zTitle);
970
971 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
972 if( tagid==0 ){
973 @ No such ticket: %h(zUuid)
974 style_footer();
975 return;
976 }
977 if( P("raw")!=0 ){
978 @ <h2>Raw Artifacts Associated With Ticket %h(zUuid)</h2>
979 }else{
980 @ <h2>Artifacts Associated With Ticket %h(zUuid)</h2>
981 }
982 db_prepare(&q,
983 "SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL"
984 " FROM event, blob"
985 " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
986 " AND blob.rid=event.objid"
@@ -978,20 +990,19 @@
990 " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
991 " AND blob.rid=attachid"
992 " ORDER BY 1",
993 tagid, tagid
994 );
995 for(nChng=0; db_step(&q)==SQLITE_ROW; nChng++){
996 Manifest *pTicket;
997 const char *zDate = db_column_text(&q, 0);
998 int rid = db_column_int(&q, 1);
999 const char *zChngUuid = db_column_text(&q, 2);
1000 const char *zFile = db_column_text(&q, 4);
1001 if( nChng==0 ){
1002 @ <ol>
1003 }
 
1004 if( zFile!=0 ){
1005 const char *zSrc = db_column_text(&q, 3);
1006 const char *zUser = db_column_text(&q, 5);
1007 if( zSrc==0 || zSrc[0]==0 ){
1008 @
@@ -1013,11 +1024,20 @@
1024 @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
1025 @ (rid %d(rid)) by
1026 hyperlink_to_user(pTicket->zUser,zDate," on");
1027 hyperlink_to_date(zDate, ":");
1028 @ </p>
1029 if( P("raw")!=0 ){
1030 Blob c;
1031 content_get(rid, &c);
1032 @ <blockquote><pre>
1033 @ %h(blob_str(&c))
1034 @ </pre></blockquote>
1035 blob_reset(&c);
1036 }else{
1037 ticket_output_change_artifact(pTicket, "a", nChng);
1038 }
1039 }
1040 manifest_destroy(pTicket);
1041 }
1042 }
1043 db_finalize(&q);
@@ -1041,37 +1061,45 @@
1061
1062 /*
1063 ** The pTkt object is a ticket change artifact. Output a detailed
1064 ** description of this object.
1065 */
1066 void ticket_output_change_artifact(
1067 Manifest *pTkt, /* Parsed artifact for the ticket change */
1068 const char *zListType, /* Which type of list */
1069 int n /* Which ticket change is this */
1070 ){
1071 int i;
 
 
 
 
 
 
 
 
1072 if( zListType==0 ) zListType = "1";
1073 getAllTicketFields();
1074 @ <ol type="%s(zListType)">
1075 for(i=0; i<pTkt->nField; i++){
1076 Blob val;
1077 const char *z, *zX;
1078 int id;
1079 z = pTkt->aField[i].zName;
1080 blob_set(&val, pTkt->aField[i].zValue);
1081 zX = z[0]=='+' ? z+1 : z;
1082 id = fieldId(zX);
1083 @ <li>\
1084 if( id<0 ){
1085 @ Untracked field %h(zX):
1086 }else if( aField[id].mUsed==USEDBY_TICKETCHNG ){
1087 @ %h(zX):
1088 }else if( n==0 ){
1089 @ %h(zX) initialized to:
1090 }else if( z[0]=='+' && (aField[id].mUsed&USEDBY_TICKET)!=0 ){
1091 @ Appended to %h(zX):
1092 }else{
1093 @ %h(zX) changed to:
1094 }
1095 if( blob_size(&val)>50 || contains_newline(&val) ){
1096 @ <blockquote><pre class='verbatim'>
1097 @ %h(blob_str(&val))
1098 @ </pre></blockquote></li>
1099 }else{
1100 @ "%h(blob_str(&val))"</li>
1101 }
1102 blob_reset(&val);
1103 }
1104 @ </ol>
1105 }
1106
+2 -2
--- src/undo.c
+++ src/undo.c
@@ -178,11 +178,11 @@
178178
@ DROP TABLE IF EXISTS undo_vfile;
179179
@ DROP TABLE IF EXISTS undo_vmerge;
180180
@ DROP TABLE IF EXISTS undo_stash;
181181
@ DROP TABLE IF EXISTS undo_stashfile;
182182
;
183
- db_multi_exec(zSql /*works-like:""*/);
183
+ db_exec_sql(zSql);
184184
db_lset_int("undo_available", 0);
185185
db_lset_int("undo_checkout", 0);
186186
}
187187
188188
/*
@@ -235,11 +235,11 @@
235235
@ CREATE TABLE localdb.undo_vfile AS SELECT * FROM vfile;
236236
@ CREATE TABLE localdb.undo_vmerge AS SELECT * FROM vmerge;
237237
;
238238
if( undoDisable ) return;
239239
undo_reset();
240
- db_multi_exec(zSql/*works-like:""*/);
240
+ db_exec_sql(zSql);
241241
cid = db_lget_int("checkout", 0);
242242
db_lset_int("undo_checkout", cid);
243243
db_lset_int("undo_available", 1);
244244
db_lset("undo_cmdline", undoCmd);
245245
undoActive = 1;
246246
--- src/undo.c
+++ src/undo.c
@@ -178,11 +178,11 @@
178 @ DROP TABLE IF EXISTS undo_vfile;
179 @ DROP TABLE IF EXISTS undo_vmerge;
180 @ DROP TABLE IF EXISTS undo_stash;
181 @ DROP TABLE IF EXISTS undo_stashfile;
182 ;
183 db_multi_exec(zSql /*works-like:""*/);
184 db_lset_int("undo_available", 0);
185 db_lset_int("undo_checkout", 0);
186 }
187
188 /*
@@ -235,11 +235,11 @@
235 @ CREATE TABLE localdb.undo_vfile AS SELECT * FROM vfile;
236 @ CREATE TABLE localdb.undo_vmerge AS SELECT * FROM vmerge;
237 ;
238 if( undoDisable ) return;
239 undo_reset();
240 db_multi_exec(zSql/*works-like:""*/);
241 cid = db_lget_int("checkout", 0);
242 db_lset_int("undo_checkout", cid);
243 db_lset_int("undo_available", 1);
244 db_lset("undo_cmdline", undoCmd);
245 undoActive = 1;
246
--- src/undo.c
+++ src/undo.c
@@ -178,11 +178,11 @@
178 @ DROP TABLE IF EXISTS undo_vfile;
179 @ DROP TABLE IF EXISTS undo_vmerge;
180 @ DROP TABLE IF EXISTS undo_stash;
181 @ DROP TABLE IF EXISTS undo_stashfile;
182 ;
183 db_exec_sql(zSql);
184 db_lset_int("undo_available", 0);
185 db_lset_int("undo_checkout", 0);
186 }
187
188 /*
@@ -235,11 +235,11 @@
235 @ CREATE TABLE localdb.undo_vfile AS SELECT * FROM vfile;
236 @ CREATE TABLE localdb.undo_vmerge AS SELECT * FROM vmerge;
237 ;
238 if( undoDisable ) return;
239 undo_reset();
240 db_exec_sql(zSql);
241 cid = db_lget_int("checkout", 0);
242 db_lset_int("undo_checkout", cid);
243 db_lset_int("undo_available", 1);
244 db_lset("undo_cmdline", undoCmd);
245 undoActive = 1;
246
--- src/unversioned.c
+++ src/unversioned.c
@@ -267,10 +267,11 @@
267267
**
268268
** Options:
269269
**
270270
** --mtime TIMESTAMP Use TIMESTAMP instead of "now" for the "add",
271271
** "edit", "remove", and "touch" subcommands.
272
+** -R|--repository FILE Use FILE as the repository
272273
*/
273274
void unversioned_cmd(void){
274275
const char *zCmd;
275276
int nCmd;
276277
const char *zMtime = find_option("mtime", 0, 1);
277278
--- src/unversioned.c
+++ src/unversioned.c
@@ -267,10 +267,11 @@
267 **
268 ** Options:
269 **
270 ** --mtime TIMESTAMP Use TIMESTAMP instead of "now" for the "add",
271 ** "edit", "remove", and "touch" subcommands.
 
272 */
273 void unversioned_cmd(void){
274 const char *zCmd;
275 int nCmd;
276 const char *zMtime = find_option("mtime", 0, 1);
277
--- src/unversioned.c
+++ src/unversioned.c
@@ -267,10 +267,11 @@
267 **
268 ** Options:
269 **
270 ** --mtime TIMESTAMP Use TIMESTAMP instead of "now" for the "add",
271 ** "edit", "remove", and "touch" subcommands.
272 ** -R|--repository FILE Use FILE as the repository
273 */
274 void unversioned_cmd(void){
275 const char *zCmd;
276 int nCmd;
277 const char *zMtime = find_option("mtime", 0, 1);
278
+14 -4
--- src/wiki.c
+++ src/wiki.c
@@ -452,11 +452,18 @@
452452
return g.perm.Write;
453453
}
454454
455455
/*
456456
** WEBPAGE: wiki
457
-** URL: /wiki?name=PAGENAME
457
+**
458
+** Display a wiki page. Example: /wiki?name=PAGENAME
459
+**
460
+** Query parameters:
461
+**
462
+** name=NAME Name of the wiki page to display. Required.
463
+** nsm Omit the submenu if present. (Mnemonic: No SubMenu)
464
+**
458465
*/
459466
void wiki_page(void){
460467
char *zTag;
461468
int rid = 0;
462469
int isSandbox;
@@ -464,10 +471,11 @@
464471
Blob wiki;
465472
Manifest *pWiki = 0;
466473
const char *zPageName;
467474
const char *zMimetype = 0;
468475
char *zBody = mprintf("%s","<i>Empty Page</i>");
476
+ int noSubmenu = P("nsm")!=0;
469477
470478
login_check_credentials();
471479
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
472480
zPageName = P("name");
473481
if( zPageName==0 ){
@@ -501,11 +509,11 @@
501509
zBody = pWiki->zWiki;
502510
zMimetype = pWiki->zMimetype;
503511
}
504512
}
505513
zMimetype = wiki_filter_mimetypes(zMimetype);
506
- if( !g.isHome ){
514
+ if( !g.isHome && !noSubmenu ){
507515
if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki))
508516
&& wiki_special_permission(zPageName)
509517
){
510518
if( db_get_boolean("wysiwyg-wiki", 0) ){
511519
style_submenu_element("Edit", "%R/wikiedit?name=%T&wysiwyg=1",
@@ -520,11 +528,13 @@
520528
style_submenu_element("History", "%R/whistory?name=%T", zPageName);
521529
}
522530
}
523531
style_set_current_page("%T?name=%T", g.zPath, zPageName);
524532
wiki_page_header(WIKITYPE_UNKNOWN, zPageName, "");
525
- wiki_standard_submenu(submenuFlags);
533
+ if( !noSubmenu ){
534
+ wiki_standard_submenu(submenuFlags);
535
+ }
526536
if( zBody[0]==0 ){
527537
@ <i>This page has been deleted</i>
528538
}else{
529539
blob_init(&wiki, zBody, -1);
530540
wiki_render_by_mimetype(&wiki, zMimetype);
@@ -1224,11 +1234,11 @@
12241234
if( wrid==0 ){
12251235
if( !showAll ) continue;
12261236
@ <tr><td data-sortkey="%h(zSort)">\
12271237
@ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
12281238
}else{
1229
- @ <tr><td data=sortkey="%h(zSort)">\
1239
+ @ <tr><td data-sortkey="%h(zSort)">\
12301240
@ %z(href("%R/wiki?name=%T",zWName))%h(zWDisplayName)</a></td>
12311241
}
12321242
zAge = human_readable_age(rNow - rWmtime);
12331243
@ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
12341244
fossil_free(zAge);
12351245
--- src/wiki.c
+++ src/wiki.c
@@ -452,11 +452,18 @@
452 return g.perm.Write;
453 }
454
455 /*
456 ** WEBPAGE: wiki
457 ** URL: /wiki?name=PAGENAME
 
 
 
 
 
 
 
458 */
459 void wiki_page(void){
460 char *zTag;
461 int rid = 0;
462 int isSandbox;
@@ -464,10 +471,11 @@
464 Blob wiki;
465 Manifest *pWiki = 0;
466 const char *zPageName;
467 const char *zMimetype = 0;
468 char *zBody = mprintf("%s","<i>Empty Page</i>");
 
469
470 login_check_credentials();
471 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
472 zPageName = P("name");
473 if( zPageName==0 ){
@@ -501,11 +509,11 @@
501 zBody = pWiki->zWiki;
502 zMimetype = pWiki->zMimetype;
503 }
504 }
505 zMimetype = wiki_filter_mimetypes(zMimetype);
506 if( !g.isHome ){
507 if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki))
508 && wiki_special_permission(zPageName)
509 ){
510 if( db_get_boolean("wysiwyg-wiki", 0) ){
511 style_submenu_element("Edit", "%R/wikiedit?name=%T&wysiwyg=1",
@@ -520,11 +528,13 @@
520 style_submenu_element("History", "%R/whistory?name=%T", zPageName);
521 }
522 }
523 style_set_current_page("%T?name=%T", g.zPath, zPageName);
524 wiki_page_header(WIKITYPE_UNKNOWN, zPageName, "");
525 wiki_standard_submenu(submenuFlags);
 
 
526 if( zBody[0]==0 ){
527 @ <i>This page has been deleted</i>
528 }else{
529 blob_init(&wiki, zBody, -1);
530 wiki_render_by_mimetype(&wiki, zMimetype);
@@ -1224,11 +1234,11 @@
1224 if( wrid==0 ){
1225 if( !showAll ) continue;
1226 @ <tr><td data-sortkey="%h(zSort)">\
1227 @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
1228 }else{
1229 @ <tr><td data=sortkey="%h(zSort)">\
1230 @ %z(href("%R/wiki?name=%T",zWName))%h(zWDisplayName)</a></td>
1231 }
1232 zAge = human_readable_age(rNow - rWmtime);
1233 @ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
1234 fossil_free(zAge);
1235
--- src/wiki.c
+++ src/wiki.c
@@ -452,11 +452,18 @@
452 return g.perm.Write;
453 }
454
455 /*
456 ** WEBPAGE: wiki
457 **
458 ** Display a wiki page. Example: /wiki?name=PAGENAME
459 **
460 ** Query parameters:
461 **
462 ** name=NAME Name of the wiki page to display. Required.
463 ** nsm Omit the submenu if present. (Mnemonic: No SubMenu)
464 **
465 */
466 void wiki_page(void){
467 char *zTag;
468 int rid = 0;
469 int isSandbox;
@@ -464,10 +471,11 @@
471 Blob wiki;
472 Manifest *pWiki = 0;
473 const char *zPageName;
474 const char *zMimetype = 0;
475 char *zBody = mprintf("%s","<i>Empty Page</i>");
476 int noSubmenu = P("nsm")!=0;
477
478 login_check_credentials();
479 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
480 zPageName = P("name");
481 if( zPageName==0 ){
@@ -501,11 +509,11 @@
509 zBody = pWiki->zWiki;
510 zMimetype = pWiki->zMimetype;
511 }
512 }
513 zMimetype = wiki_filter_mimetypes(zMimetype);
514 if( !g.isHome && !noSubmenu ){
515 if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki))
516 && wiki_special_permission(zPageName)
517 ){
518 if( db_get_boolean("wysiwyg-wiki", 0) ){
519 style_submenu_element("Edit", "%R/wikiedit?name=%T&wysiwyg=1",
@@ -520,11 +528,13 @@
528 style_submenu_element("History", "%R/whistory?name=%T", zPageName);
529 }
530 }
531 style_set_current_page("%T?name=%T", g.zPath, zPageName);
532 wiki_page_header(WIKITYPE_UNKNOWN, zPageName, "");
533 if( !noSubmenu ){
534 wiki_standard_submenu(submenuFlags);
535 }
536 if( zBody[0]==0 ){
537 @ <i>This page has been deleted</i>
538 }else{
539 blob_init(&wiki, zBody, -1);
540 wiki_render_by_mimetype(&wiki, zMimetype);
@@ -1224,11 +1234,11 @@
1234 if( wrid==0 ){
1235 if( !showAll ) continue;
1236 @ <tr><td data-sortkey="%h(zSort)">\
1237 @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
1238 }else{
1239 @ <tr><td data-sortkey="%h(zSort)">\
1240 @ %z(href("%R/wiki?name=%T",zWName))%h(zWDisplayName)</a></td>
1241 }
1242 zAge = human_readable_age(rNow - rWmtime);
1243 @ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
1244 fossil_free(zAge);
1245
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -174,11 +174,11 @@
174174
#### The directories where the OpenSSL include and library files are located.
175175
# The recommended usage here is to use the Sysinternals junction tool
176176
# to create a hard link between an "openssl-1.x" sub-directory of the
177177
# Fossil source code directory and the target OpenSSL source directory.
178178
#
179
-OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d
179
+OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e
180180
OPENSSLINCDIR = $(OPENSSLDIR)/include
181181
OPENSSLLIBDIR = $(OPENSSLDIR)
182182
183183
#### Either the directory where the Tcl library is installed or the Tcl
184184
# source code directory resides (depending on the value of the macro
@@ -646,10 +646,26 @@
646646
$(SRCDIR)/menu.js \
647647
$(SRCDIR)/sbsdiff.js \
648648
$(SRCDIR)/scroll.js \
649649
$(SRCDIR)/skin.js \
650650
$(SRCDIR)/sorttable.js \
651
+ $(SRCDIR)/sounds/0.wav \
652
+ $(SRCDIR)/sounds/1.wav \
653
+ $(SRCDIR)/sounds/2.wav \
654
+ $(SRCDIR)/sounds/3.wav \
655
+ $(SRCDIR)/sounds/4.wav \
656
+ $(SRCDIR)/sounds/5.wav \
657
+ $(SRCDIR)/sounds/6.wav \
658
+ $(SRCDIR)/sounds/7.wav \
659
+ $(SRCDIR)/sounds/8.wav \
660
+ $(SRCDIR)/sounds/9.wav \
661
+ $(SRCDIR)/sounds/a.wav \
662
+ $(SRCDIR)/sounds/b.wav \
663
+ $(SRCDIR)/sounds/c.wav \
664
+ $(SRCDIR)/sounds/d.wav \
665
+ $(SRCDIR)/sounds/e.wav \
666
+ $(SRCDIR)/sounds/f.wav \
651667
$(SRCDIR)/tree.js \
652668
$(SRCDIR)/useredit.js \
653669
$(SRCDIR)/wiki.wiki
654670
655671
TRANS_SRC = \
656672
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
@@ -646,10 +646,26 @@
646 $(SRCDIR)/menu.js \
647 $(SRCDIR)/sbsdiff.js \
648 $(SRCDIR)/scroll.js \
649 $(SRCDIR)/skin.js \
650 $(SRCDIR)/sorttable.js \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651 $(SRCDIR)/tree.js \
652 $(SRCDIR)/useredit.js \
653 $(SRCDIR)/wiki.wiki
654
655 TRANS_SRC = \
656
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
@@ -646,10 +646,26 @@
646 $(SRCDIR)/menu.js \
647 $(SRCDIR)/sbsdiff.js \
648 $(SRCDIR)/scroll.js \
649 $(SRCDIR)/skin.js \
650 $(SRCDIR)/sorttable.js \
651 $(SRCDIR)/sounds/0.wav \
652 $(SRCDIR)/sounds/1.wav \
653 $(SRCDIR)/sounds/2.wav \
654 $(SRCDIR)/sounds/3.wav \
655 $(SRCDIR)/sounds/4.wav \
656 $(SRCDIR)/sounds/5.wav \
657 $(SRCDIR)/sounds/6.wav \
658 $(SRCDIR)/sounds/7.wav \
659 $(SRCDIR)/sounds/8.wav \
660 $(SRCDIR)/sounds/9.wav \
661 $(SRCDIR)/sounds/a.wav \
662 $(SRCDIR)/sounds/b.wav \
663 $(SRCDIR)/sounds/c.wav \
664 $(SRCDIR)/sounds/d.wav \
665 $(SRCDIR)/sounds/e.wav \
666 $(SRCDIR)/sounds/f.wav \
667 $(SRCDIR)/tree.js \
668 $(SRCDIR)/useredit.js \
669 $(SRCDIR)/wiki.wiki
670
671 TRANS_SRC = \
672
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -174,11 +174,11 @@
174174
#### The directories where the OpenSSL include and library files are located.
175175
# The recommended usage here is to use the Sysinternals junction tool
176176
# to create a hard link between an "openssl-1.x" sub-directory of the
177177
# Fossil source code directory and the target OpenSSL source directory.
178178
#
179
-OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d
179
+OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e
180180
OPENSSLINCDIR = $(OPENSSLDIR)/include
181181
OPENSSLLIBDIR = $(OPENSSLDIR)
182182
183183
#### Either the directory where the Tcl library is installed or the Tcl
184184
# source code directory resides (depending on the value of the macro
@@ -646,10 +646,26 @@
646646
$(SRCDIR)/menu.js \
647647
$(SRCDIR)/sbsdiff.js \
648648
$(SRCDIR)/scroll.js \
649649
$(SRCDIR)/skin.js \
650650
$(SRCDIR)/sorttable.js \
651
+ $(SRCDIR)/sounds/0.wav \
652
+ $(SRCDIR)/sounds/1.wav \
653
+ $(SRCDIR)/sounds/2.wav \
654
+ $(SRCDIR)/sounds/3.wav \
655
+ $(SRCDIR)/sounds/4.wav \
656
+ $(SRCDIR)/sounds/5.wav \
657
+ $(SRCDIR)/sounds/6.wav \
658
+ $(SRCDIR)/sounds/7.wav \
659
+ $(SRCDIR)/sounds/8.wav \
660
+ $(SRCDIR)/sounds/9.wav \
661
+ $(SRCDIR)/sounds/a.wav \
662
+ $(SRCDIR)/sounds/b.wav \
663
+ $(SRCDIR)/sounds/c.wav \
664
+ $(SRCDIR)/sounds/d.wav \
665
+ $(SRCDIR)/sounds/e.wav \
666
+ $(SRCDIR)/sounds/f.wav \
651667
$(SRCDIR)/tree.js \
652668
$(SRCDIR)/useredit.js \
653669
$(SRCDIR)/wiki.wiki
654670
655671
TRANS_SRC = \
656672
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
@@ -646,10 +646,26 @@
646 $(SRCDIR)/menu.js \
647 $(SRCDIR)/sbsdiff.js \
648 $(SRCDIR)/scroll.js \
649 $(SRCDIR)/skin.js \
650 $(SRCDIR)/sorttable.js \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651 $(SRCDIR)/tree.js \
652 $(SRCDIR)/useredit.js \
653 $(SRCDIR)/wiki.wiki
654
655 TRANS_SRC = \
656
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
@@ -646,10 +646,26 @@
646 $(SRCDIR)/menu.js \
647 $(SRCDIR)/sbsdiff.js \
648 $(SRCDIR)/scroll.js \
649 $(SRCDIR)/skin.js \
650 $(SRCDIR)/sorttable.js \
651 $(SRCDIR)/sounds/0.wav \
652 $(SRCDIR)/sounds/1.wav \
653 $(SRCDIR)/sounds/2.wav \
654 $(SRCDIR)/sounds/3.wav \
655 $(SRCDIR)/sounds/4.wav \
656 $(SRCDIR)/sounds/5.wav \
657 $(SRCDIR)/sounds/6.wav \
658 $(SRCDIR)/sounds/7.wav \
659 $(SRCDIR)/sounds/8.wav \
660 $(SRCDIR)/sounds/9.wav \
661 $(SRCDIR)/sounds/a.wav \
662 $(SRCDIR)/sounds/b.wav \
663 $(SRCDIR)/sounds/c.wav \
664 $(SRCDIR)/sounds/d.wav \
665 $(SRCDIR)/sounds/e.wav \
666 $(SRCDIR)/sounds/f.wav \
667 $(SRCDIR)/tree.js \
668 $(SRCDIR)/useredit.js \
669 $(SRCDIR)/wiki.wiki
670
671 TRANS_SRC = \
672
+17 -1
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -98,11 +98,11 @@
9898
!ifndef USE_SEE
9999
USE_SEE = 0
100100
!endif
101101
102102
!if $(FOSSIL_ENABLE_SSL)!=0
103
-SSLDIR = $(B)\compat\openssl-1.1.1d
103
+SSLDIR = $(B)\compat\openssl-1.1.1e
104104
SSLINCDIR = $(SSLDIR)\include
105105
!if $(FOSSIL_DYNAMIC_BUILD)!=0
106106
SSLLIBDIR = $(SSLDIR)
107107
!else
108108
SSLLIBDIR = $(SSLDIR)
@@ -553,10 +553,26 @@
553553
$(SRCDIR)\menu.js \
554554
$(SRCDIR)\sbsdiff.js \
555555
$(SRCDIR)\scroll.js \
556556
$(SRCDIR)\skin.js \
557557
$(SRCDIR)\sorttable.js \
558
+ $(SRCDIR)\sounds\0.wav \
559
+ $(SRCDIR)\sounds\1.wav \
560
+ $(SRCDIR)\sounds\2.wav \
561
+ $(SRCDIR)\sounds\3.wav \
562
+ $(SRCDIR)\sounds\4.wav \
563
+ $(SRCDIR)\sounds\5.wav \
564
+ $(SRCDIR)\sounds\6.wav \
565
+ $(SRCDIR)\sounds\7.wav \
566
+ $(SRCDIR)\sounds\8.wav \
567
+ $(SRCDIR)\sounds\9.wav \
568
+ $(SRCDIR)\sounds\a.wav \
569
+ $(SRCDIR)\sounds\b.wav \
570
+ $(SRCDIR)\sounds\c.wav \
571
+ $(SRCDIR)\sounds\d.wav \
572
+ $(SRCDIR)\sounds\e.wav \
573
+ $(SRCDIR)\sounds\f.wav \
558574
$(SRCDIR)\tree.js \
559575
$(SRCDIR)\useredit.js \
560576
$(SRCDIR)\wiki.wiki
561577
562578
OBJ = $(OX)\add$O \
563579
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -98,11 +98,11 @@
98 !ifndef USE_SEE
99 USE_SEE = 0
100 !endif
101
102 !if $(FOSSIL_ENABLE_SSL)!=0
103 SSLDIR = $(B)\compat\openssl-1.1.1d
104 SSLINCDIR = $(SSLDIR)\include
105 !if $(FOSSIL_DYNAMIC_BUILD)!=0
106 SSLLIBDIR = $(SSLDIR)
107 !else
108 SSLLIBDIR = $(SSLDIR)
@@ -553,10 +553,26 @@
553 $(SRCDIR)\menu.js \
554 $(SRCDIR)\sbsdiff.js \
555 $(SRCDIR)\scroll.js \
556 $(SRCDIR)\skin.js \
557 $(SRCDIR)\sorttable.js \
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558 $(SRCDIR)\tree.js \
559 $(SRCDIR)\useredit.js \
560 $(SRCDIR)\wiki.wiki
561
562 OBJ = $(OX)\add$O \
563
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -98,11 +98,11 @@
98 !ifndef USE_SEE
99 USE_SEE = 0
100 !endif
101
102 !if $(FOSSIL_ENABLE_SSL)!=0
103 SSLDIR = $(B)\compat\openssl-1.1.1e
104 SSLINCDIR = $(SSLDIR)\include
105 !if $(FOSSIL_DYNAMIC_BUILD)!=0
106 SSLLIBDIR = $(SSLDIR)
107 !else
108 SSLLIBDIR = $(SSLDIR)
@@ -553,10 +553,26 @@
553 $(SRCDIR)\menu.js \
554 $(SRCDIR)\sbsdiff.js \
555 $(SRCDIR)\scroll.js \
556 $(SRCDIR)\skin.js \
557 $(SRCDIR)\sorttable.js \
558 $(SRCDIR)\sounds\0.wav \
559 $(SRCDIR)\sounds\1.wav \
560 $(SRCDIR)\sounds\2.wav \
561 $(SRCDIR)\sounds\3.wav \
562 $(SRCDIR)\sounds\4.wav \
563 $(SRCDIR)\sounds\5.wav \
564 $(SRCDIR)\sounds\6.wav \
565 $(SRCDIR)\sounds\7.wav \
566 $(SRCDIR)\sounds\8.wav \
567 $(SRCDIR)\sounds\9.wav \
568 $(SRCDIR)\sounds\a.wav \
569 $(SRCDIR)\sounds\b.wav \
570 $(SRCDIR)\sounds\c.wav \
571 $(SRCDIR)\sounds\d.wav \
572 $(SRCDIR)\sounds\e.wav \
573 $(SRCDIR)\sounds\f.wav \
574 $(SRCDIR)\tree.js \
575 $(SRCDIR)\useredit.js \
576 $(SRCDIR)\wiki.wiki
577
578 OBJ = $(OX)\add$O \
579
--- www/adding_code.wiki
+++ www/adding_code.wiki
@@ -102,10 +102,11 @@
102102
It is recommended that you try this.
103103
104104
Be sure to [/help/add|fossil add] your new source file to the self-hosting
105105
Fossil repository and then [/help/commit|commit] your changes!
106106
107
+<a name="newcmd"></a>
107108
<h2>4.0 Creating A New Command</h2>
108109
109110
By "commands" we mean the keywords that follow "fossil" when invoking
110111
Fossil from the command-line. So, for example, in
111112
@@ -167,10 +168,11 @@
167168
the working check-out. Study implementations of existing commands
168169
to get an idea of how things are done. You can easily find the implementations
169170
of existing commands by searching for "COMMAND: <i>name</i>" in the
170171
files of the "src/" directory.
171172
173
+<a name="newpage"></a>
172174
<h2>5.0 Creating A New Web Page</h2>
173175
174176
As with commands, new webpages can be added simply by inserting a function
175177
that generates the webpage together with a special header comment. A
176178
template follows:
177179
--- www/adding_code.wiki
+++ www/adding_code.wiki
@@ -102,10 +102,11 @@
102 It is recommended that you try this.
103
104 Be sure to [/help/add|fossil add] your new source file to the self-hosting
105 Fossil repository and then [/help/commit|commit] your changes!
106
 
107 <h2>4.0 Creating A New Command</h2>
108
109 By "commands" we mean the keywords that follow "fossil" when invoking
110 Fossil from the command-line. So, for example, in
111
@@ -167,10 +168,11 @@
167 the working check-out. Study implementations of existing commands
168 to get an idea of how things are done. You can easily find the implementations
169 of existing commands by searching for "COMMAND: <i>name</i>" in the
170 files of the "src/" directory.
171
 
172 <h2>5.0 Creating A New Web Page</h2>
173
174 As with commands, new webpages can be added simply by inserting a function
175 that generates the webpage together with a special header comment. A
176 template follows:
177
--- www/adding_code.wiki
+++ www/adding_code.wiki
@@ -102,10 +102,11 @@
102 It is recommended that you try this.
103
104 Be sure to [/help/add|fossil add] your new source file to the self-hosting
105 Fossil repository and then [/help/commit|commit] your changes!
106
107 <a name="newcmd"></a>
108 <h2>4.0 Creating A New Command</h2>
109
110 By "commands" we mean the keywords that follow "fossil" when invoking
111 Fossil from the command-line. So, for example, in
112
@@ -167,10 +168,11 @@
168 the working check-out. Study implementations of existing commands
169 to get an idea of how things are done. You can easily find the implementations
170 of existing commands by searching for "COMMAND: <i>name</i>" in the
171 files of the "src/" directory.
172
173 <a name="newpage"></a>
174 <h2>5.0 Creating A New Web Page</h2>
175
176 As with commands, new webpages can be added simply by inserting a function
177 that generates the webpage together with a special header comment. A
178 template follows:
179
+2 -2
--- www/alerts.md
+++ www/alerts.md
@@ -718,12 +718,12 @@
718718
Almost all of the email alert code is found in the
719719
[`src/alerts.c`](/file/src/alerts.c) source file.
720720
721721
When email alerts are enabled, a trigger is created in the schema
722722
(`email_trigger1`) that adds a new entry to the `PENDING_ALERT` table
723
-every time a row is added to the `EVENT` table. During a `fossil
724
-rebuild`, the `EVENT` table is rebuilt from scratch; since we do not
723
+every time a row is added to the `EVENT` table. During a
724
+`fossil rebuild`, the `EVENT` table is rebuilt from scratch; since we do not
725725
want users to get alerts for every historical check-in, the trigger is
726726
disabled during `rebuild`.
727727
728728
Email alerts are sent out by the `alert_send_alerts()` function, which
729729
is normally called automatically due to the `email-autoexec` setting,
730730
--- www/alerts.md
+++ www/alerts.md
@@ -718,12 +718,12 @@
718 Almost all of the email alert code is found in the
719 [`src/alerts.c`](/file/src/alerts.c) source file.
720
721 When email alerts are enabled, a trigger is created in the schema
722 (`email_trigger1`) that adds a new entry to the `PENDING_ALERT` table
723 every time a row is added to the `EVENT` table. During a `fossil
724 rebuild`, the `EVENT` table is rebuilt from scratch; since we do not
725 want users to get alerts for every historical check-in, the trigger is
726 disabled during `rebuild`.
727
728 Email alerts are sent out by the `alert_send_alerts()` function, which
729 is normally called automatically due to the `email-autoexec` setting,
730
--- www/alerts.md
+++ www/alerts.md
@@ -718,12 +718,12 @@
718 Almost all of the email alert code is found in the
719 [`src/alerts.c`](/file/src/alerts.c) source file.
720
721 When email alerts are enabled, a trigger is created in the schema
722 (`email_trigger1`) that adds a new entry to the `PENDING_ALERT` table
723 every time a row is added to the `EVENT` table. During a
724 `fossil rebuild`, the `EVENT` table is rebuilt from scratch; since we do not
725 want users to get alerts for every historical check-in, the trigger is
726 disabled during `rebuild`.
727
728 Email alerts are sent out by the `alert_send_alerts()` function, which
729 is normally called automatically due to the `email-autoexec` setting,
730
+54 -1
--- www/build.wiki
+++ www/build.wiki
@@ -161,11 +161,11 @@
161161
the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
162162
first <a href="https://www.openssl.org/source/">download the official
163163
source code for OpenSSL</a> and extract it to an appropriately named
164164
"<b>openssl-X.Y.ZA</b>" subdirectory within the local
165165
[/tree?ci=trunk&name=compat | compat] directory (e.g.
166
-"<b>compat/openssl-1.1.1d</b>"), then make sure that some recent
166
+"<b>compat/openssl-1.1.1e</b>"), then make sure that some recent
167167
<a href="http://www.perl.org/">Perl</a> binaries are installed locally,
168168
and finally run one of the following commands:
169169
<blockquote><pre>
170170
nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
171171
</pre></blockquote>
@@ -310,5 +310,58 @@
310310
</code></pre>
311311
312312
Note the IDs of the images named <tt>fossil_static</tt> and <tt>alpine</tt>, then:
313313
314314
<pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre>
315
+
316
+
317
+<h2>6.0 Building on/for Android</h2>
318
+
319
+<h3>6.1 Cross-compiling from Linux</h3>
320
+
321
+The following instructions for building Fossil for Andoid,
322
+without requiring a rooted OS, are adapted from
323
+[https://fossil-scm.org/forum/forumpost/e0e9de4a7e | forumpost/e0e9de4a7e].
324
+
325
+On the development machine, from the fossil source tree:
326
+
327
+<pre><code>export CC=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang
328
+./configure --with-openssl=none
329
+make
330
+</code></pre>
331
+
332
+
333
+On the Android device, enable the <em>USB debugging</em> option from
334
+Developer menu in Device Options. Connect the device to the development
335
+system with USB. If it's configured and connected properly,
336
+the device should show up in the output of <code>adb devices</code>:
337
+
338
+<pre><code>sudo adb devices
339
+</code></pre>
340
+
341
+Copy the resulting fossil binary onto the device...
342
+
343
+<pre><code>sudo adb push fossil /data/local/tmp
344
+</code></pre>
345
+
346
+And run it from an <code>adb</code> shell:
347
+
348
+<pre><code>sudo adb shell
349
+&gt; cd /data/local/tmp
350
+# Fossil requires a HOME directory to work with:
351
+&gt; export HOME=$PWD
352
+&gt; export PATH=$PWD:$PATH
353
+&gt; fossil version
354
+This is fossil version 2.11 &#91;e5653a4ceb] 2020-03-26 18:54:02 UTC
355
+</code></pre>
356
+
357
+The output might, or might not, include warnings such as:
358
+
359
+<pre><code>WARNING: linker: ./fossil: unused DT entry: type 0x6ffffef5 arg 0x1464
360
+WARNING: linker: ./fossil: unused DT entry: type 0x6ffffffe arg 0x1ba8
361
+WARNING: linker: ./fossil: unused DT entry: type 0x6fffffff arg 0x2
362
+</code></pre>
363
+
364
+The source of such warnings is not 100% certain.
365
+Some information about these (reportedly harmless) warnings can
366
+be found
367
+[https://stackoverflow.com/a/41900551 | on this StackOverflow post].
315368
--- www/build.wiki
+++ www/build.wiki
@@ -161,11 +161,11 @@
161 the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
162 first <a href="https://www.openssl.org/source/">download the official
163 source code for OpenSSL</a> and extract it to an appropriately named
164 "<b>openssl-X.Y.ZA</b>" subdirectory within the local
165 [/tree?ci=trunk&name=compat | compat] directory (e.g.
166 "<b>compat/openssl-1.1.1d</b>"), then make sure that some recent
167 <a href="http://www.perl.org/">Perl</a> binaries are installed locally,
168 and finally run one of the following commands:
169 <blockquote><pre>
170 nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
171 </pre></blockquote>
@@ -310,5 +310,58 @@
310 </code></pre>
311
312 Note the IDs of the images named <tt>fossil_static</tt> and <tt>alpine</tt>, then:
313
314 <pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
--- www/build.wiki
+++ www/build.wiki
@@ -161,11 +161,11 @@
161 the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
162 first <a href="https://www.openssl.org/source/">download the official
163 source code for OpenSSL</a> and extract it to an appropriately named
164 "<b>openssl-X.Y.ZA</b>" subdirectory within the local
165 [/tree?ci=trunk&name=compat | compat] directory (e.g.
166 "<b>compat/openssl-1.1.1e</b>"), then make sure that some recent
167 <a href="http://www.perl.org/">Perl</a> binaries are installed locally,
168 and finally run one of the following commands:
169 <blockquote><pre>
170 nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
171 </pre></blockquote>
@@ -310,5 +310,58 @@
310 </code></pre>
311
312 Note the IDs of the images named <tt>fossil_static</tt> and <tt>alpine</tt>, then:
313
314 <pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre>
315
316
317 <h2>6.0 Building on/for Android</h2>
318
319 <h3>6.1 Cross-compiling from Linux</h3>
320
321 The following instructions for building Fossil for Andoid,
322 without requiring a rooted OS, are adapted from
323 [https://fossil-scm.org/forum/forumpost/e0e9de4a7e | forumpost/e0e9de4a7e].
324
325 On the development machine, from the fossil source tree:
326
327 <pre><code>export CC=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang
328 ./configure --with-openssl=none
329 make
330 </code></pre>
331
332
333 On the Android device, enable the <em>USB debugging</em> option from
334 Developer menu in Device Options. Connect the device to the development
335 system with USB. If it's configured and connected properly,
336 the device should show up in the output of <code>adb devices</code>:
337
338 <pre><code>sudo adb devices
339 </code></pre>
340
341 Copy the resulting fossil binary onto the device...
342
343 <pre><code>sudo adb push fossil /data/local/tmp
344 </code></pre>
345
346 And run it from an <code>adb</code> shell:
347
348 <pre><code>sudo adb shell
349 &gt; cd /data/local/tmp
350 # Fossil requires a HOME directory to work with:
351 &gt; export HOME=$PWD
352 &gt; export PATH=$PWD:$PATH
353 &gt; fossil version
354 This is fossil version 2.11 &#91;e5653a4ceb] 2020-03-26 18:54:02 UTC
355 </code></pre>
356
357 The output might, or might not, include warnings such as:
358
359 <pre><code>WARNING: linker: ./fossil: unused DT entry: type 0x6ffffef5 arg 0x1464
360 WARNING: linker: ./fossil: unused DT entry: type 0x6ffffffe arg 0x1ba8
361 WARNING: linker: ./fossil: unused DT entry: type 0x6fffffff arg 0x2
362 </code></pre>
363
364 The source of such warnings is not 100% certain.
365 Some information about these (reportedly harmless) warnings can
366 be found
367 [https://stackoverflow.com/a/41900551 | on this StackOverflow post].
368
--- www/caps/index.md
+++ www/caps/index.md
@@ -65,12 +65,12 @@
6565
Fossil shows how these capabilities apply hierarchically in the user
6666
editing screen (Admin → Users → name) with the `[N]` `[A]` `[D]` `[R]`
6767
tags next to each capability check box. If a user gets a capability from
6868
one of the user categories already assigned to it, there is no value in
6969
redundantly assigning that same cap to the user explicitly. For example,
70
-with the default **dei** cap set for the “developer” category, the cap
71
-set **ve** is redundant because **v** grants **dei**, which includes
70
+with the default **ei** cap set for the “developer” category, the cap
71
+set **ve** is redundant because **v** grants **ei**, which includes
7272
**e**.
7373
7474
We suggest that you lean heavily on these fixed user categories when
7575
setting up new users. Ideally, your users will group neatly into one of
7676
the predefined categories, but if not, you might be able to shoehorn
@@ -151,12 +151,12 @@
151151
are all about modifying repository content: edit existing wiki pages,
152152
change one’s own password, create new ticket report formats, and modify
153153
existing tickets. This category would be better named “participant”.
154154
155155
Those in the “developer” category get the “nobody” and “anonymous” cap
156
-sets plus **[d][d][e][e][i][i]**: delete wiki articles and tickets, view
157
-sensitive user material, and check in changes.
156
+sets plus **[e][e][i][i]**: view
157
+sensitive user material and check in changes.
158158
159159
[bot]: ../antibot.wiki
160160
161161
162162
## <a name="pvt"></a>Consequences of Taking a Repository Private
163163
--- www/caps/index.md
+++ www/caps/index.md
@@ -65,12 +65,12 @@
65 Fossil shows how these capabilities apply hierarchically in the user
66 editing screen (Admin → Users → name) with the `[N]` `[A]` `[D]` `[R]`
67 tags next to each capability check box. If a user gets a capability from
68 one of the user categories already assigned to it, there is no value in
69 redundantly assigning that same cap to the user explicitly. For example,
70 with the default **dei** cap set for the “developer” category, the cap
71 set **ve** is redundant because **v** grants **dei**, which includes
72 **e**.
73
74 We suggest that you lean heavily on these fixed user categories when
75 setting up new users. Ideally, your users will group neatly into one of
76 the predefined categories, but if not, you might be able to shoehorn
@@ -151,12 +151,12 @@
151 are all about modifying repository content: edit existing wiki pages,
152 change one’s own password, create new ticket report formats, and modify
153 existing tickets. This category would be better named “participant”.
154
155 Those in the “developer” category get the “nobody” and “anonymous” cap
156 sets plus **[d][d][e][e][i][i]**: delete wiki articles and tickets, view
157 sensitive user material, and check in changes.
158
159 [bot]: ../antibot.wiki
160
161
162 ## <a name="pvt"></a>Consequences of Taking a Repository Private
163
--- www/caps/index.md
+++ www/caps/index.md
@@ -65,12 +65,12 @@
65 Fossil shows how these capabilities apply hierarchically in the user
66 editing screen (Admin → Users → name) with the `[N]` `[A]` `[D]` `[R]`
67 tags next to each capability check box. If a user gets a capability from
68 one of the user categories already assigned to it, there is no value in
69 redundantly assigning that same cap to the user explicitly. For example,
70 with the default **ei** cap set for the “developer” category, the cap
71 set **ve** is redundant because **v** grants **ei**, which includes
72 **e**.
73
74 We suggest that you lean heavily on these fixed user categories when
75 setting up new users. Ideally, your users will group neatly into one of
76 the predefined categories, but if not, you might be able to shoehorn
@@ -151,12 +151,12 @@
151 are all about modifying repository content: edit existing wiki pages,
152 change one’s own password, create new ticket report formats, and modify
153 existing tickets. This category would be better named “participant”.
154
155 Those in the “developer” category get the “nobody” and “anonymous” cap
156 sets plus **[e][e][i][i]**: view
157 sensitive user material and check in changes.
158
159 [bot]: ../antibot.wiki
160
161
162 ## <a name="pvt"></a>Consequences of Taking a Repository Private
163
--- www/caps/ref.html
+++ www/caps/ref.html
@@ -73,15 +73,23 @@
7373
</td>
7474
</tr>
7575
7676
<tr id="d">
7777
<th>d</th>
78
- <th>Delete</th>
78
+ <th>n/a</th>
7979
<td>
80
- Delete wiki articles or tickets. Mnemonic: <b>d</b>elete.
80
+ Legacy capability letter from Fossil's forebear <a
81
+ href="http://cvstrac.org/">CVSTrac</a>, which has no useful
82
+ meaning in Fossil due to its durable blockchain nature. This
83
+ letter was assigned by default to Developer in repos created with
84
+ Fossil 2.10 or earlier, but it has no effect in current or past
85
+ versions of Fossil; we recommend that you remove it in case we
86
+ ever reuse this letter for another purpose. See <a
87
+ href="https://fossil-scm.org/forum/forumpost/43c78f4bef">this
88
+ post</a> for details.
8189
</td>
82
- </tr>
90
+ </tr>
8391
8492
<tr id="e">
8593
<th>e</th>
8694
<th>RdAddr</th>
8795
<td>
8896
--- www/caps/ref.html
+++ www/caps/ref.html
@@ -73,15 +73,23 @@
73 </td>
74 </tr>
75
76 <tr id="d">
77 <th>d</th>
78 <th>Delete</th>
79 <td>
80 Delete wiki articles or tickets. Mnemonic: <b>d</b>elete.
 
 
 
 
 
 
 
 
81 </td>
82 </tr>
83
84 <tr id="e">
85 <th>e</th>
86 <th>RdAddr</th>
87 <td>
88
--- www/caps/ref.html
+++ www/caps/ref.html
@@ -73,15 +73,23 @@
73 </td>
74 </tr>
75
76 <tr id="d">
77 <th>d</th>
78 <th>n/a</th>
79 <td>
80 Legacy capability letter from Fossil's forebear <a
81 href="http://cvstrac.org/">CVSTrac</a>, which has no useful
82 meaning in Fossil due to its durable blockchain nature. This
83 letter was assigned by default to Developer in repos created with
84 Fossil 2.10 or earlier, but it has no effect in current or past
85 versions of Fossil; we recommend that you remove it in case we
86 ever reuse this letter for another purpose. See <a
87 href="https://fossil-scm.org/forum/forumpost/43c78f4bef">this
88 post</a> for details.
89 </td>
90 </tr>
91
92 <tr id="e">
93 <th>e</th>
94 <th>RdAddr</th>
95 <td>
96
--- www/changes.wiki
+++ www/changes.wiki
@@ -30,16 +30,18 @@
3030
the CSP in the process.
3131
* The Content-Security-Policy is now set using the
3232
[/help?cmd=default-csp|default-csp setting].
3333
* Merge conflicts caused via the [/help?cmd=merge|merge] and
3434
[/help?cmd=update|update] commands no longer leave temporary
35
- files behind unless the new <tt>--keep-merge-file</tt> flag
35
+ files behind unless the new <tt>--keep-merge-files</tt> flag
3636
is used.
3737
* The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
3838
to all users if the new "artifact_stats_enable" setting is turned
3939
on. There is a new checkbox under the /Admin/Access menu to turn
4040
that capability on and off.
41
+ * Captchas all include a button to read the captcha using an audio
42
+ file, so that they can be completed by the visually impaired.
4143
* Bug fix: the "fossil git export" command is now working on Windows
4244
* Bug fix: display Technote items on the timeline correctly
4345
* Bug fix: fix the capability summary matrix of the Security Audit
4446
page so that it does not add "anonymous" capabilities to the
4547
"nobody" user.
4648
--- www/changes.wiki
+++ www/changes.wiki
@@ -30,16 +30,18 @@
30 the CSP in the process.
31 * The Content-Security-Policy is now set using the
32 [/help?cmd=default-csp|default-csp setting].
33 * Merge conflicts caused via the [/help?cmd=merge|merge] and
34 [/help?cmd=update|update] commands no longer leave temporary
35 files behind unless the new <tt>--keep-merge-file</tt> flag
36 is used.
37 * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
38 to all users if the new "artifact_stats_enable" setting is turned
39 on. There is a new checkbox under the /Admin/Access menu to turn
40 that capability on and off.
 
 
41 * Bug fix: the "fossil git export" command is now working on Windows
42 * Bug fix: display Technote items on the timeline correctly
43 * Bug fix: fix the capability summary matrix of the Security Audit
44 page so that it does not add "anonymous" capabilities to the
45 "nobody" user.
46
--- www/changes.wiki
+++ www/changes.wiki
@@ -30,16 +30,18 @@
30 the CSP in the process.
31 * The Content-Security-Policy is now set using the
32 [/help?cmd=default-csp|default-csp setting].
33 * Merge conflicts caused via the [/help?cmd=merge|merge] and
34 [/help?cmd=update|update] commands no longer leave temporary
35 files behind unless the new <tt>--keep-merge-files</tt> flag
36 is used.
37 * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
38 to all users if the new "artifact_stats_enable" setting is turned
39 on. There is a new checkbox under the /Admin/Access menu to turn
40 that capability on and off.
41 * Captchas all include a button to read the captcha using an audio
42 file, so that they can be completed by the visually impaired.
43 * Bug fix: the "fossil git export" command is now working on Windows
44 * Bug fix: display Technote items on the timeline correctly
45 * Bug fix: fix the capability summary matrix of the Security Audit
46 page so that it does not add "anonymous" capabilities to the
47 "nobody" user.
48
--- www/embeddeddoc.wiki
+++ www/embeddeddoc.wiki
@@ -53,11 +53,11 @@
5353
pull the documentation file from the local source tree on disk, not
5454
from the any check-in. The "<b>ckout</b>" keyword
5555
only works when you start your server using the
5656
"[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]"
5757
commands. The "/doc/ckout" URL is intended to show a preview of
58
-the documentation you are currently but have not yet you checked in.
58
+the documentation you are currently editing but have not yet you checked in.
5959
6060
Finally, the <i>&lt;filename&gt;</i> element of the URL is the
6161
pathname of the documentation file relative to the root of the source
6262
tree.
6363
6464
--- www/embeddeddoc.wiki
+++ www/embeddeddoc.wiki
@@ -53,11 +53,11 @@
53 pull the documentation file from the local source tree on disk, not
54 from the any check-in. The "<b>ckout</b>" keyword
55 only works when you start your server using the
56 "[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]"
57 commands. The "/doc/ckout" URL is intended to show a preview of
58 the documentation you are currently but have not yet you checked in.
59
60 Finally, the <i>&lt;filename&gt;</i> element of the URL is the
61 pathname of the documentation file relative to the root of the source
62 tree.
63
64
--- www/embeddeddoc.wiki
+++ www/embeddeddoc.wiki
@@ -53,11 +53,11 @@
53 pull the documentation file from the local source tree on disk, not
54 from the any check-in. The "<b>ckout</b>" keyword
55 only works when you start your server using the
56 "[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]"
57 commands. The "/doc/ckout" URL is intended to show a preview of
58 the documentation you are currently editing but have not yet you checked in.
59
60 Finally, the <i>&lt;filename&gt;</i> element of the URL is the
61 pathname of the documentation file relative to the root of the source
62 tree.
63
64
+199 -134
--- www/globs.md
+++ www/globs.md
@@ -4,82 +4,92 @@
44
A [glob pattern][glob] is a text expression that matches one or more
55
file names using wild cards familiar to most users of a command line.
66
For example, `*` is a glob that matches any name at all and
77
`Readme.txt` is a glob that matches exactly one file.
88
9
-Note that although both are notations for describing patterns in text,
10
-glob patterns are not the same thing as a [regular expression or
11
-regexp][regexp].
9
+A glob should not be confused with a [regular expression][regexp] (RE),
10
+even though they use some of the same special characters for similar
11
+purposes, because [they are not fully compatible][greinc] pattern
12
+matching languages. Fossil uses globs when matching file names with the
13
+settings described in this document, not REs.
1214
13
-[glob]: https://en.wikipedia.org/wiki/Glob_(programming) (Wikipedia)
15
+[glob]: https://en.wikipedia.org/wiki/Glob_(programming)
16
+[greinc]: https://unix.stackexchange.com/a/57958/138
1417
[regexp]: https://en.wikipedia.org/wiki/Regular_expression
1518
16
-
17
-A number of fossil setting values hold one or more file glob patterns
18
-that will identify files needing special treatment. Glob patterns are
19
-also accepted in options to certain commands as well as query
20
-parameters to certain pages.
21
-
22
-In many cases more than one glob may be specified in a setting,
23
-option, or query parameter by listing multiple globs separated by a
24
-comma or white space.
25
-
26
-Of course, many fossil commands also accept lists of files to act on,
27
-and those also may be specified with globs. Although those glob
28
-patterns are similar to what is described here, they are not defined
29
-by fossil, but rather by the conventions of the operating system in
30
-use.
19
+These settings hold one or more file glob patterns to cause Fossil to
20
+give matching named files special treatment. Glob patterns are also
21
+accepted in options to certain commands and as query parameters to
22
+certain Fossil UI web pages.
23
+
24
+Where Fossil also accepts globs in commands, this handling may interact
25
+with your OS’s command shell or its C runtime system, because they may
26
+have their own glob pattern handling. We will detail such interactions
27
+below.
3128
3229
3330
## Syntax
3431
35
-A list of glob patterns is simply one or more glob patterns separated
32
+Where Fossil accepts glob patterns, it will usually accept a *list* of
33
+such patterns, each individual pattern separated from the others
3634
by white space or commas. If a glob must contain white spaces or
3735
commas, it can be quoted with either single or double quotation marks.
38
-A list is said to match if any one (or more) globs in the list
36
+A list is said to match if any one glob in the list
3937
matches.
4038
41
-A glob pattern is a collection of characters compared to a target
42
-text, usually a file name. The whole glob is said to match if it
43
-successfully consumes and matches the entire target text. Glob
44
-patterns are made up of ordinary characters and special characters.
45
-
46
-Ordinary characters consume a single character of the target and must
47
-match it exactly.
48
-
49
-Special characters (and special character sequences) consume zero or
50
-more characters from the target and describe what matches. The special
51
-characters (and sequences) are:
39
+A glob pattern matches a given file name if it successfully consumes and
40
+matches the *entire* name. Partial matches are failed matches.
41
+
42
+Most characters in a glob pattern consume a single character of the file
43
+name and must match it exactly. For instance, “a” in a glob simply
44
+matches the letter “a” in the file name unless it is inside a special
45
+character sequence.
46
+
47
+Other characters have special meaning, and they may include otherwise
48
+normal characters to give them special meaning:
5249
5350
:Pattern |:Effect
5451
---------------------------------------------------------------------
5552
`*` | Matches any sequence of zero or more characters
5653
`?` | Matches exactly one character
5754
`[...]` | Matches one character from the enclosed list of characters
58
-`[^...]` | Matches one character not in the enclosed list
55
+`[^...]` | Matches one character *not* in the enclosed list
5956
60
-Special character sequences have some additional features:
57
+Note that unlike [POSIX globs][pg], these special characters and
58
+sequences are allowed to match `/` directory separators as well as the
59
+initial `.` in the name of a hidden file or directory. This is because
60
+Fossil file names are stored as complete path names. The distinction
61
+between file name and directory name is “below” Fossil in this sense.
6162
62
- * A range of characters may be specified with `-`, so `[a-d]` matches
63
- exactly the same characters as `[abcd]`. Ranges reflect Unicode
63
+[pg]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
64
+
65
+The bracket expresssions above require some additional explanation:
66
+
67
+ * A range of characters may be specified with `-`, so `[a-f]` matches
68
+ exactly the same characters as `[abcdef]`. Ranges reflect Unicode
6469
code points without any locale-specific collation sequence.
65
- * Include `-` in a list by placing it last, just before the `]`.
66
- * Include `]` in a list by making the first character after the `[` or
67
- `[^`. At any other place, `]` ends the list.
68
- * Include `^` in a list by placing anywhere except first after the
69
- `[`.
70
- * Beware that ranges in lists may include more than you expect:
71
- `[A-z]` Matches `A` and `Z`, but also matches `a` and some less
72
- obvious characters such as `[`, `\`, and `]` with code point
73
- values between `Z` and `a`.
70
+ Therefore, this particular sequence never matches the Unicode
71
+ pre-composed character `é`, for example. (U+00E9)
72
+
73
+ * This dependence on character/code point ordering may have other
74
+ effects to surprise you. For example, the glob `[A-z]` not only
75
+ matches upper and lowercase ASCII letters, it also matches several
76
+ punctuation characters placed between `Z` and `a` in both ASCII and
77
+ Unicode: `[`, `\`, `]`, `^`, `_`, and <tt>\`</tt>.
78
+
79
+ * You may include a literal `-` in a list by placing it last, just
80
+ before the `]`.
81
+
82
+ * You may include a literal `]` in a list by making the first
83
+ character after the `[` or `[^`. At any other place, `]` ends the list.
84
+
85
+ * You may include a literal `^` in a list by placing it anywhere
86
+ except after the opening `[`.
87
+
7488
* Beware that a range must be specified from low value to high
7589
value: `[z-a]` does not match any character at all, preventing the
7690
entire glob from matching.
77
- * Note that unlike typical Unix shell globs, wildcards (`*`, `?`,
78
- and character lists) are allowed to match `/` directory
79
- separators as well as the initial `.` in the name of a hidden
80
- file or directory.
8191
8292
Some examples of character lists:
8393
8494
:Pattern |:Effect
8595
---------------------------------------------------------------------
@@ -92,45 +102,56 @@
92102
`[]^]` | Matches either `]` or `^`
93103
`[^-]` | Matches exactly one character other than `-`
94104
95105
White space means the specific ASCII characters TAB, LF, VT, FF, CR,
96106
and SPACE. Note that this does not include any of the many additional
97
-spacing characters available in Unicode, and specifically does not
98
-include U+00A0 NO-BREAK SPACE.
107
+spacing characters available in Unicode such as
108
+U+00A0, NO-BREAK SPACE.
99109
100110
Because both LF and CR are white space and leading and trailing spaces
101111
are stripped from each glob in a list, a list of globs may be broken
102
-into lines between globs when the list is stored in a file (as for a
103
-versioned setting).
112
+into lines between globs when the list is stored in a file, as for a
113
+versioned setting.
104114
105
-Similarly 'single quotes' and "double quotes" are the ASCII straight
115
+Note that 'single quotes' and "double quotes" are the ASCII straight
106116
quote characters, not any of the other quotation marks provided in
107117
Unicode and specifically not the "curly" quotes preferred by
108118
typesetters and word processors.
109119
110120
111121
## File Names to Match
112122
113123
Before it is compared to a glob pattern, each file name is transformed
114
-to a canonical form. The glob must match the entire canonical file
115
-name to be considered a match.
116
-
117
-The canonical name of a file has all directory separators changed to
118
-`/`, redundant slashes are removed, all `.` path components are
119
-removed, and all `..` path components are resolved. (There are
120
-additional details we are ignoring here, but they cover rare edge
121
-cases and also follow the principle of least surprise.)
124
+to a canonical form:
125
+
126
+ * all directory separators are changed to `/`
127
+ * redundant slashes are removed
128
+ * all `.` path components are removed
129
+ * all `..` path components are resolved
130
+
131
+(There are additional details we are ignoring here, but they cover rare
132
+edge cases and follow the principle of least surprise.)
133
+
134
+The glob must match the *entire* canonical file name to be considered a
135
+match.
122136
123137
The goal is to have a name that is the simplest possible for each
124
-particular file, and that will be the same on Windows, Unix, and any
125
-other platform where fossil is run.
138
+particular file, and that will be the same regardless of the platform
139
+you run Fossil on. This is important when you have a repository cloned
140
+from multiple platforms and have globs in versioned settings: you want
141
+those settings to be interpreted the same way everywhere.
126142
127
-Beware, however, that all glob matching is case sensitive. This will
128
-not be a surprise on Unix where all file names are also case
129
-sensitive. However, most Windows file systems are case preserving and
143
+Beware, however, that all glob matching in Fossil is case sensitive
144
+regardless of host platform and file system. This will not be a surprise
145
+on POSIX platforms where file names are usually treated case
146
+sensitively. However, most Windows file systems are case preserving but
130147
case insensitive. That is, on Windows, the names `ReadMe` and `README`
131
-are names of the same file; on Unix they are different files.
148
+are usually names of the same file. The same is true in other cases,
149
+such as by default on macOS file systems and in the file system drivers
150
+for Windows file systems running on non-Windows systems. (e.g. exfat on
151
+Linux.) Therefore, write your Fossil glob patterns to match the name of
152
+the file as checked into the repository.
132153
133154
Some example cases:
134155
135156
:Pattern |:Effect
136157
--------------------------------------------------------------------------------
@@ -188,10 +209,11 @@
188209
* [`commit`][]
189210
* [`extras`][]
190211
* [`merge`][]
191212
* [`settings`][]
192213
* [`status`][]
214
+ * [`touch`][]
193215
* [`unset`][]
194216
195217
The commands [`tarball`][] and [`zip`][] produce compressed archives of a
196218
specific checkin. They may be further restricted by options that
197219
specify glob patterns that name files to include or exclude rather
@@ -209,10 +231,11 @@
209231
[`commit`]: /help?cmd=commit
210232
[`extras`]: /help?cmd=extras
211233
[`merge`]: /help?cmd=merge
212234
[`settings`]: /help?cmd=settings
213235
[`status`]: /help?cmd=status
236
+[`touch`]: /help?cmd=touch
214237
[`unset`]: /help?cmd=unset
215238
216239
[`tarball`]: /help?cmd=tarball
217240
[`zip`]: /help?cmd=zip
218241
@@ -259,11 +282,11 @@
259282
That advice does not help you when you are giving one-off glob patterns
260283
in `fossil` commands. The remainder of this section gives remedies and
261284
workarounds for these problems.
262285
263286
264
-## POSIX Systems
287
+### <a name="posix"></a>POSIX Systems
265288
266289
If you are using Fossil on a system with a POSIX-compatible shell
267290
&mdash; Linux, macOS, the BSDs, Unix, Cygwin, WSL etc. &mdash; the shell
268291
may expand the glob patterns before passing the result to the `fossil`
269292
executable.
@@ -346,11 +369,28 @@
346369
accidentally check something like a password, an API key, or the
347370
private half of a public cryptographic key into Fossil repository that
348371
can be read by people who should not have such secrets.
349372
350373
351
-## Windows
374
+### <a name="windows"></a>Windows
375
+
376
+Before we get into Windows-specific details here, beware that this
377
+section does not apply to the several Microsoft Windows extensions that
378
+provide POSIX semantics to Windows, for which you want to use the advice
379
+in [the POSIX section above](#posix) instead:
380
+
381
+ * the ancient and rarely-used [Microsoft POSIX subsystem][mps];
382
+ * its now-discontinued replacement feature, [Services for Unix][sfu]; or
383
+ * their modern replacement, the [Windows Subsystem for Linux][wsl]
384
+
385
+[mps]: https://en.wikipedia.org/wiki/Microsoft_POSIX_subsystem
386
+[sfu]: https://en.wikipedia.org/wiki/Windows_Services_for_UNIX
387
+[wsl]: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux
388
+
389
+(The latter is sometimes incorrectly called "Bash on Windows" or "Ubuntu
390
+on Windows," but the feature provides much more than just Bash or Ubuntu
391
+for Windows.)
352392
353393
Neither standard Windows command shell &mdash; `cmd.exe` or PowerShell
354394
&mdash; expands glob patterns the way POSIX shells do. Windows command
355395
shells rely on the command itself to do the glob pattern expansion. The
356396
way this works depends on several factors:
@@ -362,82 +402,111 @@
362402
* whether the command is built against a runtime system that does this
363403
at all
364404
* whether the Fossil command is being run from a file named `*.BAT` vs
365405
being named `*.CMD`
366406
367
-These factors also affect how a program like `fossil.exe` interprets
368
-quotation marks on its command line.
369
-
370
-The fifth item above does not apply to `fossil.exe` when built with
371
-typical tool chains, but we will see an example below where the exception
372
-applies in a way that affects how Fossil interprets the glob pattern.
407
+Usually (but not always!) the C runtime library that your `fossil.exe`
408
+executable is built against does this glob expansion on Windows so the
409
+program proper does not have to. This may then interact with the way the
410
+Windows command shell you’re using handles argument quoting. Because of
411
+these differences, it is common to find perfectly valid Fossil command
412
+examples that were written and tested on a POSIX system which then fail
413
+when tried on Windows.
373414
374415
The most common problem is figuring out how to get a glob pattern passed
375416
on the command line into `fossil.exe` without it being expanded by the C
376417
runtime library that your particular Fossil executable is linked to,
377
-which tries to act like the POSIX systems described above. Windows is
418
+which tries to act like [the POSIX systems described above](#posix). Windows is
378419
not strongly governed by POSIX, so it has not historically hewed closely
379420
to its strictures.
380421
381
-(This section does not cover the [Microsoft POSIX
382
-subsystem](https://en.wikipedia.org/wiki/Microsoft_POSIX_subsystem),
383
-Windows' obsolete [Services for Unix
384
-3.*x*](https://en.wikipedia.org/wiki/Windows_Services_for_UNIX) feature,
385
-or the [Windows Subsystem for
386
-Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux). (The
387
-latter is sometimes incorrectly called "Bash on Windows" or "Ubuntu on
388
-Windows.") See the POSIX Systems section above for those cases.)
389
-
390422
For example, consider how you would set `crlf-glob` to `*` in order to
391
-disable Fossil's "looks like a binary file" checks. The na&iuml;ve
392
-approach will not work:
423
+get normal Windows text files with CR+LF line endings past Fossil's
424
+"looks like a binary file" check. The na&iuml;ve approach will not work:
393425
394426
C:\...> fossil setting crlf-glob *
395427
396428
The C runtime library will expand that to the list of all files in the
397429
current directory, which will probably cause a Fossil error because
398430
Fossil expects either nothing or option flags after the setting's new
399
-value.
431
+value, not a list of file names. (To be fair, the same thing will happen
432
+on POSIX systems, only at the shell level, before `.../bin/fossil` even
433
+gets run by the shell.)
400434
401435
Let's try again:
402436
403437
C:\...> fossil setting crlf-glob '*'
404438
405
-That may or may not work. Either `'*'` or `*` needs to be passed through
406
-to Fossil untouched for this to do what you expect, which may or may not
407
-happen, depending on the factors listed above.
439
+Quoting the argument like that will work reliably on POSIX, but it may
440
+or may not work on Windows. If your Windows command shell interprets the
441
+quotes, it means `fossil.exe` will see only the bare `*` so the C
442
+runtime library it is linked to will likely expand the list of files in
443
+the current directory before the `setting` command gets a chance to
444
+parse the command line arguments, causing the same failure as above.
445
+This alternative only works if you’re using a Windows command shell that
446
+passes the quotes through to the executable *and* you have linked Fossil
447
+to a C runtime library that interprets the quotes properly itself,
448
+resulting in a bare `*` getting clear down to Fossil’s `setting` command
449
+parser.
408450
409451
An approach that *will* work reliably is:
410452
411453
C:\...> echo * | fossil setting crlf-glob --args -
412454
413
-This works because the built-in command `echo` does not expand its
414
-arguments, and the `--args -` option makes it read further command
415
-arguments from Fossil's standard input, which is connected to the output
455
+This works because the built-in Windows command `echo` does not expand its
456
+arguments, and the `--args -` option makes Fossil read further command
457
+arguments from its standard input, which is connected to the output
416458
of `echo` by the pipe. (`-` is a common Unix convention meaning
417
-"standard input.")
459
+"standard input," which Fossil obeys.) A [batch script][fng.cmd] to automate this trick was
460
+posted on the now-inactive Fossil Mailing List.
461
+
462
+[fng.cmd]: https://www.mail-archive.com/[email protected]/msg25099.html
463
+
464
+(Ironically, this method will *not* work on POSIX systems because it is
465
+not up to the command to expand globs. The shell will expand the `*` in
466
+the `echo` command, so the list of file names will be passed to the
467
+`fossil` standard input, just as with the first example above!)
418468
419
-Another (usually) correct approach is:
469
+Another (usually) correct approach which will work on both Windows and
470
+POSIX systems:
420471
421472
C:\...> fossil setting crlf-glob *,
422473
423
-This works because the trailing comma prevents the command shell from
474
+This works because the trailing comma prevents the glob pattern from
424475
matching any files, unless you happen to have files named with a
425476
trailing comma in the current directory. If the pattern matches no
426477
files, it is passed into Fossil's `main()` function as-is by the C
427478
runtime system. Since Fossil uses commas to separate multiple glob
428
-patterns, this means "all files at the root of the Fossil checkout
429
-directory and nothing else."
479
+patterns, this means "all files from the root of the Fossil checkout
480
+directory downward and nothing else," which is of course equivalent to
481
+"all managed files in this repository," our original goal.
482
+
483
+
484
+## Experimenting
485
+
486
+To preview the effects of command line glob pattern expansion for
487
+various glob patterns (unquoted, quoted, comma-terminated), for any
488
+combination of command shell, OS, C run time, and Fossil version,
489
+preceed the command you want to test with [`test-echo`][] like so:
490
+
491
+ $ fossil test-echo setting crlf-glob "*"
492
+ C:\> echo * | fossil test-echo setting crlf-glob --args -
493
+
494
+The [`test-glob`][] command is also handy to test if a string
495
+matches a glob pattern.
496
+
497
+[`test-echo`]: /help?cmd=test-echo
498
+[`test-glob`]: /help?cmd=test-glob
430499
431500
432501
## Converting `.gitignore` to `ignore-glob`
433502
434503
Many other version control systems handle the specific case of
435
-ignoring certain files differently from fossil: they have you create
504
+ignoring certain files differently from Fossil: they have you create
436505
individual "ignore" files in each folder, which specify things ignored
437506
in that folder and below. Usually some form of glob patterns are used
438
-in those files, but the details differ from fossil.
507
+in those files, but the details differ from Fossil.
439508
440509
In many simple cases, you can just store a top level "ignore" file in
441510
`.fossil-settings/ignore-glob`. But as usual, there will be lots of
442511
edge cases.
443512
@@ -447,33 +516,33 @@
447516
version controlled files. Some of the files used have no set name, but
448517
are called out in configuration files.
449518
450519
[gitignore]: https://git-scm.com/docs/gitignore
451520
452
-In contrast, fossil has a global setting and a local setting, but the local setting
453
-overrides the global rather than extending it. Similarly, a fossil
521
+In contrast, Fossil has a global setting and a local setting, but the local setting
522
+overrides the global rather than extending it. Similarly, a Fossil
454523
command's `--ignore` option replaces the `ignore-glob` setting rather
455524
than extending it.
456525
457526
With that in mind, translating a `.gitignore` file into
458527
`.fossil-settings/ignore-glob` may be possible in many cases. Here are
459528
some of features of `.gitignore` and comments on how they relate to
460
-fossil:
529
+Fossil:
461530
462
- * "A blank line matches no files..." is the same in fossil.
463
- * "A line starting with # serves as a comment...." not in fossil.
531
+ * "A blank line matches no files...": same in Fossil.
532
+ * "A line starting with # serves as a comment....": not in Fossil.
464533
* "Trailing spaces are ignored unless they are quoted..." is similar
465
- in fossil. All whitespace before and after a glob is trimmed in
466
- fossil unless quoted with single or double quotes. Git uses
467
- backslash quoting instead, which fossil does not.
468
- * "An optional prefix "!" which negates the pattern..." not in
469
- fossil.
470
- * Git's globs are relative to the location of the `.gitignore` file;
471
- fossil's globs are relative to the root of the workspace.
472
- * Git's globs and fossil's globs treat directory separators
534
+ in Fossil. All whitespace before and after a glob is trimmed in
535
+ Fossil unless quoted with single or double quotes. Git uses
536
+ backslash quoting instead, which Fossil does not.
537
+ * "An optional prefix "!" which negates the pattern...": not in
538
+ Fossil.
539
+ * Git's globs are relative to the location of the `.gitignore` file:
540
+ Fossil's globs are relative to the root of the workspace.
541
+ * Git's globs and Fossil's globs treat directory separators
473542
differently. Git includes a notation for zero or more directories
474
- that is not needed in fossil.
543
+ that is not needed in Fossil.
475544
476545
### Example
477546
478547
In a project with source and documentation:
479548
@@ -502,30 +571,26 @@
502571
503572
504573
505574
## Implementation and References
506575
507
-Most of the implementation of glob pattern handling in fossil is found
508
-`glob.c`, `file.c`, and each individual command and web page that uses
509
-a glob pattern. Find commands and pages in the fossil sources by
510
-looking for comments like `COMMAND: add` or `WEBPAGE: timeline` in
511
-front of the function that implements the command or page in files
512
-`src/*.c`. (Fossil's build system creates the tables used to dispatch
513
-commands at build time by searching the sources for those comments.) A
514
-few starting points:
576
+The implementation of the Fossil-specific glob pattern handling is here:
515577
516578
:File |:Description
517579
--------------------------------------------------------------------------------
518
-[`src/glob.c`][] | Implementation of glob pattern list loading, parsing, and matching.
519
-[`src/file.c`][] | Implementation of various kinds of canonical names of a file.
580
+[`src/glob.c`][] | pattern list loading, parsing, and generic matching code
581
+[`src/file.c`][] | application of glob patterns to file names
520582
521583
[`src/glob.c`]: https://www.fossil-scm.org/index.html/file/src/glob.c
522584
[`src/file.c`]: https://www.fossil-scm.org/index.html/file/src/file.c
523585
524
-The actual pattern matching is implemented in SQL, so the
525
-documentation for `GLOB` and the other string matching operators in
526
-[SQLite] (https://sqlite.org/lang_expr.html#like) is useful. Of
527
-course, the SQLite [source code]
528
-(https://www.sqlite.org/src/artifact?name=9d52522cc8ae7f5c&ln=570-768)
529
-and [test harnesses]
530
-(https://www.sqlite.org/src/artifact?name=66a2c9ac34f74f03&ln=586-673)
531
-also make entertaining reading.
586
+See the [Adding Features to Fossil][aff] document for broader details
587
+about finding and working with such code.
588
+
589
+The actual pattern matching leverages the `GLOB` operator in SQLite, so
590
+you may find [its documentation][gdoc], [source code][gsrc] and [test
591
+harness][gtst] helpful.
592
+
593
+[aff]: ./adding_code.wiki
594
+[gdoc]: https://sqlite.org/lang_expr.html#like
595
+[gsrc]: https://www.sqlite.org/src/artifact?name=9d52522cc8ae7f5c&ln=570-768
596
+[gtst]: https://www.sqlite.org/src/artifact?name=66a2c9ac34f74f03&ln=586-673
532597
--- www/globs.md
+++ www/globs.md
@@ -4,82 +4,92 @@
4 A [glob pattern][glob] is a text expression that matches one or more
5 file names using wild cards familiar to most users of a command line.
6 For example, `*` is a glob that matches any name at all and
7 `Readme.txt` is a glob that matches exactly one file.
8
9 Note that although both are notations for describing patterns in text,
10 glob patterns are not the same thing as a [regular expression or
11 regexp][regexp].
 
 
12
13 [glob]: https://en.wikipedia.org/wiki/Glob_(programming) (Wikipedia)
 
14 [regexp]: https://en.wikipedia.org/wiki/Regular_expression
15
16
17 A number of fossil setting values hold one or more file glob patterns
18 that will identify files needing special treatment. Glob patterns are
19 also accepted in options to certain commands as well as query
20 parameters to certain pages.
21
22 In many cases more than one glob may be specified in a setting,
23 option, or query parameter by listing multiple globs separated by a
24 comma or white space.
25
26 Of course, many fossil commands also accept lists of files to act on,
27 and those also may be specified with globs. Although those glob
28 patterns are similar to what is described here, they are not defined
29 by fossil, but rather by the conventions of the operating system in
30 use.
31
32
33 ## Syntax
34
35 A list of glob patterns is simply one or more glob patterns separated
 
36 by white space or commas. If a glob must contain white spaces or
37 commas, it can be quoted with either single or double quotation marks.
38 A list is said to match if any one (or more) globs in the list
39 matches.
40
41 A glob pattern is a collection of characters compared to a target
42 text, usually a file name. The whole glob is said to match if it
43 successfully consumes and matches the entire target text. Glob
44 patterns are made up of ordinary characters and special characters.
45
46 Ordinary characters consume a single character of the target and must
47 match it exactly.
48
49 Special characters (and special character sequences) consume zero or
50 more characters from the target and describe what matches. The special
51 characters (and sequences) are:
52
53 :Pattern |:Effect
54 ---------------------------------------------------------------------
55 `*` | Matches any sequence of zero or more characters
56 `?` | Matches exactly one character
57 `[...]` | Matches one character from the enclosed list of characters
58 `[^...]` | Matches one character not in the enclosed list
59
60 Special character sequences have some additional features:
 
 
 
 
61
62 * A range of characters may be specified with `-`, so `[a-d]` matches
63 exactly the same characters as `[abcd]`. Ranges reflect Unicode
 
 
 
 
64 code points without any locale-specific collation sequence.
65 * Include `-` in a list by placing it last, just before the `]`.
66 * Include `]` in a list by making the first character after the `[` or
67 `[^`. At any other place, `]` ends the list.
68 * Include `^` in a list by placing anywhere except first after the
69 `[`.
70 * Beware that ranges in lists may include more than you expect:
71 `[A-z]` Matches `A` and `Z`, but also matches `a` and some less
72 obvious characters such as `[`, `\`, and `]` with code point
73 values between `Z` and `a`.
 
 
 
 
 
 
 
 
 
74 * Beware that a range must be specified from low value to high
75 value: `[z-a]` does not match any character at all, preventing the
76 entire glob from matching.
77 * Note that unlike typical Unix shell globs, wildcards (`*`, `?`,
78 and character lists) are allowed to match `/` directory
79 separators as well as the initial `.` in the name of a hidden
80 file or directory.
81
82 Some examples of character lists:
83
84 :Pattern |:Effect
85 ---------------------------------------------------------------------
@@ -92,45 +102,56 @@
92 `[]^]` | Matches either `]` or `^`
93 `[^-]` | Matches exactly one character other than `-`
94
95 White space means the specific ASCII characters TAB, LF, VT, FF, CR,
96 and SPACE. Note that this does not include any of the many additional
97 spacing characters available in Unicode, and specifically does not
98 include U+00A0 NO-BREAK SPACE.
99
100 Because both LF and CR are white space and leading and trailing spaces
101 are stripped from each glob in a list, a list of globs may be broken
102 into lines between globs when the list is stored in a file (as for a
103 versioned setting).
104
105 Similarly 'single quotes' and "double quotes" are the ASCII straight
106 quote characters, not any of the other quotation marks provided in
107 Unicode and specifically not the "curly" quotes preferred by
108 typesetters and word processors.
109
110
111 ## File Names to Match
112
113 Before it is compared to a glob pattern, each file name is transformed
114 to a canonical form. The glob must match the entire canonical file
115 name to be considered a match.
116
117 The canonical name of a file has all directory separators changed to
118 `/`, redundant slashes are removed, all `.` path components are
119 removed, and all `..` path components are resolved. (There are
120 additional details we are ignoring here, but they cover rare edge
121 cases and also follow the principle of least surprise.)
 
 
 
 
122
123 The goal is to have a name that is the simplest possible for each
124 particular file, and that will be the same on Windows, Unix, and any
125 other platform where fossil is run.
 
 
126
127 Beware, however, that all glob matching is case sensitive. This will
128 not be a surprise on Unix where all file names are also case
129 sensitive. However, most Windows file systems are case preserving and
 
130 case insensitive. That is, on Windows, the names `ReadMe` and `README`
131 are names of the same file; on Unix they are different files.
 
 
 
 
132
133 Some example cases:
134
135 :Pattern |:Effect
136 --------------------------------------------------------------------------------
@@ -188,10 +209,11 @@
188 * [`commit`][]
189 * [`extras`][]
190 * [`merge`][]
191 * [`settings`][]
192 * [`status`][]
 
193 * [`unset`][]
194
195 The commands [`tarball`][] and [`zip`][] produce compressed archives of a
196 specific checkin. They may be further restricted by options that
197 specify glob patterns that name files to include or exclude rather
@@ -209,10 +231,11 @@
209 [`commit`]: /help?cmd=commit
210 [`extras`]: /help?cmd=extras
211 [`merge`]: /help?cmd=merge
212 [`settings`]: /help?cmd=settings
213 [`status`]: /help?cmd=status
 
214 [`unset`]: /help?cmd=unset
215
216 [`tarball`]: /help?cmd=tarball
217 [`zip`]: /help?cmd=zip
218
@@ -259,11 +282,11 @@
259 That advice does not help you when you are giving one-off glob patterns
260 in `fossil` commands. The remainder of this section gives remedies and
261 workarounds for these problems.
262
263
264 ## POSIX Systems
265
266 If you are using Fossil on a system with a POSIX-compatible shell
267 &mdash; Linux, macOS, the BSDs, Unix, Cygwin, WSL etc. &mdash; the shell
268 may expand the glob patterns before passing the result to the `fossil`
269 executable.
@@ -346,11 +369,28 @@
346 accidentally check something like a password, an API key, or the
347 private half of a public cryptographic key into Fossil repository that
348 can be read by people who should not have such secrets.
349
350
351 ## Windows
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
353 Neither standard Windows command shell &mdash; `cmd.exe` or PowerShell
354 &mdash; expands glob patterns the way POSIX shells do. Windows command
355 shells rely on the command itself to do the glob pattern expansion. The
356 way this works depends on several factors:
@@ -362,82 +402,111 @@
362 * whether the command is built against a runtime system that does this
363 at all
364 * whether the Fossil command is being run from a file named `*.BAT` vs
365 being named `*.CMD`
366
367 These factors also affect how a program like `fossil.exe` interprets
368 quotation marks on its command line.
369
370 The fifth item above does not apply to `fossil.exe` when built with
371 typical tool chains, but we will see an example below where the exception
372 applies in a way that affects how Fossil interprets the glob pattern.
 
373
374 The most common problem is figuring out how to get a glob pattern passed
375 on the command line into `fossil.exe` without it being expanded by the C
376 runtime library that your particular Fossil executable is linked to,
377 which tries to act like the POSIX systems described above. Windows is
378 not strongly governed by POSIX, so it has not historically hewed closely
379 to its strictures.
380
381 (This section does not cover the [Microsoft POSIX
382 subsystem](https://en.wikipedia.org/wiki/Microsoft_POSIX_subsystem),
383 Windows' obsolete [Services for Unix
384 3.*x*](https://en.wikipedia.org/wiki/Windows_Services_for_UNIX) feature,
385 or the [Windows Subsystem for
386 Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux). (The
387 latter is sometimes incorrectly called "Bash on Windows" or "Ubuntu on
388 Windows.") See the POSIX Systems section above for those cases.)
389
390 For example, consider how you would set `crlf-glob` to `*` in order to
391 disable Fossil's "looks like a binary file" checks. The na&iuml;ve
392 approach will not work:
393
394 C:\...> fossil setting crlf-glob *
395
396 The C runtime library will expand that to the list of all files in the
397 current directory, which will probably cause a Fossil error because
398 Fossil expects either nothing or option flags after the setting's new
399 value.
 
 
400
401 Let's try again:
402
403 C:\...> fossil setting crlf-glob '*'
404
405 That may or may not work. Either `'*'` or `*` needs to be passed through
406 to Fossil untouched for this to do what you expect, which may or may not
407 happen, depending on the factors listed above.
 
 
 
 
 
 
 
 
408
409 An approach that *will* work reliably is:
410
411 C:\...> echo * | fossil setting crlf-glob --args -
412
413 This works because the built-in command `echo` does not expand its
414 arguments, and the `--args -` option makes it read further command
415 arguments from Fossil's standard input, which is connected to the output
416 of `echo` by the pipe. (`-` is a common Unix convention meaning
417 "standard input.")
 
 
 
 
 
 
 
 
418
419 Another (usually) correct approach is:
 
420
421 C:\...> fossil setting crlf-glob *,
422
423 This works because the trailing comma prevents the command shell from
424 matching any files, unless you happen to have files named with a
425 trailing comma in the current directory. If the pattern matches no
426 files, it is passed into Fossil's `main()` function as-is by the C
427 runtime system. Since Fossil uses commas to separate multiple glob
428 patterns, this means "all files at the root of the Fossil checkout
429 directory and nothing else."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
431
432 ## Converting `.gitignore` to `ignore-glob`
433
434 Many other version control systems handle the specific case of
435 ignoring certain files differently from fossil: they have you create
436 individual "ignore" files in each folder, which specify things ignored
437 in that folder and below. Usually some form of glob patterns are used
438 in those files, but the details differ from fossil.
439
440 In many simple cases, you can just store a top level "ignore" file in
441 `.fossil-settings/ignore-glob`. But as usual, there will be lots of
442 edge cases.
443
@@ -447,33 +516,33 @@
447 version controlled files. Some of the files used have no set name, but
448 are called out in configuration files.
449
450 [gitignore]: https://git-scm.com/docs/gitignore
451
452 In contrast, fossil has a global setting and a local setting, but the local setting
453 overrides the global rather than extending it. Similarly, a fossil
454 command's `--ignore` option replaces the `ignore-glob` setting rather
455 than extending it.
456
457 With that in mind, translating a `.gitignore` file into
458 `.fossil-settings/ignore-glob` may be possible in many cases. Here are
459 some of features of `.gitignore` and comments on how they relate to
460 fossil:
461
462 * "A blank line matches no files..." is the same in fossil.
463 * "A line starting with # serves as a comment...." not in fossil.
464 * "Trailing spaces are ignored unless they are quoted..." is similar
465 in fossil. All whitespace before and after a glob is trimmed in
466 fossil unless quoted with single or double quotes. Git uses
467 backslash quoting instead, which fossil does not.
468 * "An optional prefix "!" which negates the pattern..." not in
469 fossil.
470 * Git's globs are relative to the location of the `.gitignore` file;
471 fossil's globs are relative to the root of the workspace.
472 * Git's globs and fossil's globs treat directory separators
473 differently. Git includes a notation for zero or more directories
474 that is not needed in fossil.
475
476 ### Example
477
478 In a project with source and documentation:
479
@@ -502,30 +571,26 @@
502
503
504
505 ## Implementation and References
506
507 Most of the implementation of glob pattern handling in fossil is found
508 `glob.c`, `file.c`, and each individual command and web page that uses
509 a glob pattern. Find commands and pages in the fossil sources by
510 looking for comments like `COMMAND: add` or `WEBPAGE: timeline` in
511 front of the function that implements the command or page in files
512 `src/*.c`. (Fossil's build system creates the tables used to dispatch
513 commands at build time by searching the sources for those comments.) A
514 few starting points:
515
516 :File |:Description
517 --------------------------------------------------------------------------------
518 [`src/glob.c`][] | Implementation of glob pattern list loading, parsing, and matching.
519 [`src/file.c`][] | Implementation of various kinds of canonical names of a file.
520
521 [`src/glob.c`]: https://www.fossil-scm.org/index.html/file/src/glob.c
522 [`src/file.c`]: https://www.fossil-scm.org/index.html/file/src/file.c
523
524 The actual pattern matching is implemented in SQL, so the
525 documentation for `GLOB` and the other string matching operators in
526 [SQLite] (https://sqlite.org/lang_expr.html#like) is useful. Of
527 course, the SQLite [source code]
528 (https://www.sqlite.org/src/artifact?name=9d52522cc8ae7f5c&ln=570-768)
529 and [test harnesses]
530 (https://www.sqlite.org/src/artifact?name=66a2c9ac34f74f03&ln=586-673)
531 also make entertaining reading.
 
 
 
532
--- www/globs.md
+++ www/globs.md
@@ -4,82 +4,92 @@
4 A [glob pattern][glob] is a text expression that matches one or more
5 file names using wild cards familiar to most users of a command line.
6 For example, `*` is a glob that matches any name at all and
7 `Readme.txt` is a glob that matches exactly one file.
8
9 A glob should not be confused with a [regular expression][regexp] (RE),
10 even though they use some of the same special characters for similar
11 purposes, because [they are not fully compatible][greinc] pattern
12 matching languages. Fossil uses globs when matching file names with the
13 settings described in this document, not REs.
14
15 [glob]: https://en.wikipedia.org/wiki/Glob_(programming)
16 [greinc]: https://unix.stackexchange.com/a/57958/138
17 [regexp]: https://en.wikipedia.org/wiki/Regular_expression
18
19 These settings hold one or more file glob patterns to cause Fossil to
20 give matching named files special treatment. Glob patterns are also
21 accepted in options to certain commands and as query parameters to
22 certain Fossil UI web pages.
23
24 Where Fossil also accepts globs in commands, this handling may interact
25 with your OS’s command shell or its C runtime system, because they may
26 have their own glob pattern handling. We will detail such interactions
27 below.
 
 
 
 
 
 
28
29
30 ## Syntax
31
32 Where Fossil accepts glob patterns, it will usually accept a *list* of
33 such patterns, each individual pattern separated from the others
34 by white space or commas. If a glob must contain white spaces or
35 commas, it can be quoted with either single or double quotation marks.
36 A list is said to match if any one glob in the list
37 matches.
38
39 A glob pattern matches a given file name if it successfully consumes and
40 matches the *entire* name. Partial matches are failed matches.
41
42 Most characters in a glob pattern consume a single character of the file
43 name and must match it exactly. For instance, “a” in a glob simply
44 matches the letter “a” in the file name unless it is inside a special
45 character sequence.
46
47 Other characters have special meaning, and they may include otherwise
48 normal characters to give them special meaning:
 
49
50 :Pattern |:Effect
51 ---------------------------------------------------------------------
52 `*` | Matches any sequence of zero or more characters
53 `?` | Matches exactly one character
54 `[...]` | Matches one character from the enclosed list of characters
55 `[^...]` | Matches one character *not* in the enclosed list
56
57 Note that unlike [POSIX globs][pg], these special characters and
58 sequences are allowed to match `/` directory separators as well as the
59 initial `.` in the name of a hidden file or directory. This is because
60 Fossil file names are stored as complete path names. The distinction
61 between file name and directory name is “below” Fossil in this sense.
62
63 [pg]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
64
65 The bracket expresssions above require some additional explanation:
66
67 * A range of characters may be specified with `-`, so `[a-f]` matches
68 exactly the same characters as `[abcdef]`. Ranges reflect Unicode
69 code points without any locale-specific collation sequence.
70 Therefore, this particular sequence never matches the Unicode
71 pre-composed character `é`, for example. (U+00E9)
72
73 * This dependence on character/code point ordering may have other
74 effects to surprise you. For example, the glob `[A-z]` not only
75 matches upper and lowercase ASCII letters, it also matches several
76 punctuation characters placed between `Z` and `a` in both ASCII and
77 Unicode: `[`, `\`, `]`, `^`, `_`, and <tt>\`</tt>.
78
79 * You may include a literal `-` in a list by placing it last, just
80 before the `]`.
81
82 * You may include a literal `]` in a list by making the first
83 character after the `[` or `[^`. At any other place, `]` ends the list.
84
85 * You may include a literal `^` in a list by placing it anywhere
86 except after the opening `[`.
87
88 * Beware that a range must be specified from low value to high
89 value: `[z-a]` does not match any character at all, preventing the
90 entire glob from matching.
 
 
 
 
91
92 Some examples of character lists:
93
94 :Pattern |:Effect
95 ---------------------------------------------------------------------
@@ -92,45 +102,56 @@
102 `[]^]` | Matches either `]` or `^`
103 `[^-]` | Matches exactly one character other than `-`
104
105 White space means the specific ASCII characters TAB, LF, VT, FF, CR,
106 and SPACE. Note that this does not include any of the many additional
107 spacing characters available in Unicode such as
108 U+00A0, NO-BREAK SPACE.
109
110 Because both LF and CR are white space and leading and trailing spaces
111 are stripped from each glob in a list, a list of globs may be broken
112 into lines between globs when the list is stored in a file, as for a
113 versioned setting.
114
115 Note that 'single quotes' and "double quotes" are the ASCII straight
116 quote characters, not any of the other quotation marks provided in
117 Unicode and specifically not the "curly" quotes preferred by
118 typesetters and word processors.
119
120
121 ## File Names to Match
122
123 Before it is compared to a glob pattern, each file name is transformed
124 to a canonical form:
125
126 * all directory separators are changed to `/`
127 * redundant slashes are removed
128 * all `.` path components are removed
129 * all `..` path components are resolved
130
131 (There are additional details we are ignoring here, but they cover rare
132 edge cases and follow the principle of least surprise.)
133
134 The glob must match the *entire* canonical file name to be considered a
135 match.
136
137 The goal is to have a name that is the simplest possible for each
138 particular file, and that will be the same regardless of the platform
139 you run Fossil on. This is important when you have a repository cloned
140 from multiple platforms and have globs in versioned settings: you want
141 those settings to be interpreted the same way everywhere.
142
143 Beware, however, that all glob matching in Fossil is case sensitive
144 regardless of host platform and file system. This will not be a surprise
145 on POSIX platforms where file names are usually treated case
146 sensitively. However, most Windows file systems are case preserving but
147 case insensitive. That is, on Windows, the names `ReadMe` and `README`
148 are usually names of the same file. The same is true in other cases,
149 such as by default on macOS file systems and in the file system drivers
150 for Windows file systems running on non-Windows systems. (e.g. exfat on
151 Linux.) Therefore, write your Fossil glob patterns to match the name of
152 the file as checked into the repository.
153
154 Some example cases:
155
156 :Pattern |:Effect
157 --------------------------------------------------------------------------------
@@ -188,10 +209,11 @@
209 * [`commit`][]
210 * [`extras`][]
211 * [`merge`][]
212 * [`settings`][]
213 * [`status`][]
214 * [`touch`][]
215 * [`unset`][]
216
217 The commands [`tarball`][] and [`zip`][] produce compressed archives of a
218 specific checkin. They may be further restricted by options that
219 specify glob patterns that name files to include or exclude rather
@@ -209,10 +231,11 @@
231 [`commit`]: /help?cmd=commit
232 [`extras`]: /help?cmd=extras
233 [`merge`]: /help?cmd=merge
234 [`settings`]: /help?cmd=settings
235 [`status`]: /help?cmd=status
236 [`touch`]: /help?cmd=touch
237 [`unset`]: /help?cmd=unset
238
239 [`tarball`]: /help?cmd=tarball
240 [`zip`]: /help?cmd=zip
241
@@ -259,11 +282,11 @@
282 That advice does not help you when you are giving one-off glob patterns
283 in `fossil` commands. The remainder of this section gives remedies and
284 workarounds for these problems.
285
286
287 ### <a name="posix"></a>POSIX Systems
288
289 If you are using Fossil on a system with a POSIX-compatible shell
290 &mdash; Linux, macOS, the BSDs, Unix, Cygwin, WSL etc. &mdash; the shell
291 may expand the glob patterns before passing the result to the `fossil`
292 executable.
@@ -346,11 +369,28 @@
369 accidentally check something like a password, an API key, or the
370 private half of a public cryptographic key into Fossil repository that
371 can be read by people who should not have such secrets.
372
373
374 ### <a name="windows"></a>Windows
375
376 Before we get into Windows-specific details here, beware that this
377 section does not apply to the several Microsoft Windows extensions that
378 provide POSIX semantics to Windows, for which you want to use the advice
379 in [the POSIX section above](#posix) instead:
380
381 * the ancient and rarely-used [Microsoft POSIX subsystem][mps];
382 * its now-discontinued replacement feature, [Services for Unix][sfu]; or
383 * their modern replacement, the [Windows Subsystem for Linux][wsl]
384
385 [mps]: https://en.wikipedia.org/wiki/Microsoft_POSIX_subsystem
386 [sfu]: https://en.wikipedia.org/wiki/Windows_Services_for_UNIX
387 [wsl]: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux
388
389 (The latter is sometimes incorrectly called "Bash on Windows" or "Ubuntu
390 on Windows," but the feature provides much more than just Bash or Ubuntu
391 for Windows.)
392
393 Neither standard Windows command shell &mdash; `cmd.exe` or PowerShell
394 &mdash; expands glob patterns the way POSIX shells do. Windows command
395 shells rely on the command itself to do the glob pattern expansion. The
396 way this works depends on several factors:
@@ -362,82 +402,111 @@
402 * whether the command is built against a runtime system that does this
403 at all
404 * whether the Fossil command is being run from a file named `*.BAT` vs
405 being named `*.CMD`
406
407 Usually (but not always!) the C runtime library that your `fossil.exe`
408 executable is built against does this glob expansion on Windows so the
409 program proper does not have to. This may then interact with the way the
410 Windows command shell you’re using handles argument quoting. Because of
411 these differences, it is common to find perfectly valid Fossil command
412 examples that were written and tested on a POSIX system which then fail
413 when tried on Windows.
414
415 The most common problem is figuring out how to get a glob pattern passed
416 on the command line into `fossil.exe` without it being expanded by the C
417 runtime library that your particular Fossil executable is linked to,
418 which tries to act like [the POSIX systems described above](#posix). Windows is
419 not strongly governed by POSIX, so it has not historically hewed closely
420 to its strictures.
421
 
 
 
 
 
 
 
 
 
422 For example, consider how you would set `crlf-glob` to `*` in order to
423 get normal Windows text files with CR+LF line endings past Fossil's
424 "looks like a binary file" check. The na&iuml;ve approach will not work:
425
426 C:\...> fossil setting crlf-glob *
427
428 The C runtime library will expand that to the list of all files in the
429 current directory, which will probably cause a Fossil error because
430 Fossil expects either nothing or option flags after the setting's new
431 value, not a list of file names. (To be fair, the same thing will happen
432 on POSIX systems, only at the shell level, before `.../bin/fossil` even
433 gets run by the shell.)
434
435 Let's try again:
436
437 C:\...> fossil setting crlf-glob '*'
438
439 Quoting the argument like that will work reliably on POSIX, but it may
440 or may not work on Windows. If your Windows command shell interprets the
441 quotes, it means `fossil.exe` will see only the bare `*` so the C
442 runtime library it is linked to will likely expand the list of files in
443 the current directory before the `setting` command gets a chance to
444 parse the command line arguments, causing the same failure as above.
445 This alternative only works if you’re using a Windows command shell that
446 passes the quotes through to the executable *and* you have linked Fossil
447 to a C runtime library that interprets the quotes properly itself,
448 resulting in a bare `*` getting clear down to Fossil’s `setting` command
449 parser.
450
451 An approach that *will* work reliably is:
452
453 C:\...> echo * | fossil setting crlf-glob --args -
454
455 This works because the built-in Windows command `echo` does not expand its
456 arguments, and the `--args -` option makes Fossil read further command
457 arguments from its standard input, which is connected to the output
458 of `echo` by the pipe. (`-` is a common Unix convention meaning
459 "standard input," which Fossil obeys.) A [batch script][fng.cmd] to automate this trick was
460 posted on the now-inactive Fossil Mailing List.
461
462 [fng.cmd]: https://www.mail-archive.com/[email protected]/msg25099.html
463
464 (Ironically, this method will *not* work on POSIX systems because it is
465 not up to the command to expand globs. The shell will expand the `*` in
466 the `echo` command, so the list of file names will be passed to the
467 `fossil` standard input, just as with the first example above!)
468
469 Another (usually) correct approach which will work on both Windows and
470 POSIX systems:
471
472 C:\...> fossil setting crlf-glob *,
473
474 This works because the trailing comma prevents the glob pattern from
475 matching any files, unless you happen to have files named with a
476 trailing comma in the current directory. If the pattern matches no
477 files, it is passed into Fossil's `main()` function as-is by the C
478 runtime system. Since Fossil uses commas to separate multiple glob
479 patterns, this means "all files from the root of the Fossil checkout
480 directory downward and nothing else," which is of course equivalent to
481 "all managed files in this repository," our original goal.
482
483
484 ## Experimenting
485
486 To preview the effects of command line glob pattern expansion for
487 various glob patterns (unquoted, quoted, comma-terminated), for any
488 combination of command shell, OS, C run time, and Fossil version,
489 preceed the command you want to test with [`test-echo`][] like so:
490
491 $ fossil test-echo setting crlf-glob "*"
492 C:\> echo * | fossil test-echo setting crlf-glob --args -
493
494 The [`test-glob`][] command is also handy to test if a string
495 matches a glob pattern.
496
497 [`test-echo`]: /help?cmd=test-echo
498 [`test-glob`]: /help?cmd=test-glob
499
500
501 ## Converting `.gitignore` to `ignore-glob`
502
503 Many other version control systems handle the specific case of
504 ignoring certain files differently from Fossil: they have you create
505 individual "ignore" files in each folder, which specify things ignored
506 in that folder and below. Usually some form of glob patterns are used
507 in those files, but the details differ from Fossil.
508
509 In many simple cases, you can just store a top level "ignore" file in
510 `.fossil-settings/ignore-glob`. But as usual, there will be lots of
511 edge cases.
512
@@ -447,33 +516,33 @@
516 version controlled files. Some of the files used have no set name, but
517 are called out in configuration files.
518
519 [gitignore]: https://git-scm.com/docs/gitignore
520
521 In contrast, Fossil has a global setting and a local setting, but the local setting
522 overrides the global rather than extending it. Similarly, a Fossil
523 command's `--ignore` option replaces the `ignore-glob` setting rather
524 than extending it.
525
526 With that in mind, translating a `.gitignore` file into
527 `.fossil-settings/ignore-glob` may be possible in many cases. Here are
528 some of features of `.gitignore` and comments on how they relate to
529 Fossil:
530
531 * "A blank line matches no files...": same in Fossil.
532 * "A line starting with # serves as a comment....": not in Fossil.
533 * "Trailing spaces are ignored unless they are quoted..." is similar
534 in Fossil. All whitespace before and after a glob is trimmed in
535 Fossil unless quoted with single or double quotes. Git uses
536 backslash quoting instead, which Fossil does not.
537 * "An optional prefix "!" which negates the pattern...": not in
538 Fossil.
539 * Git's globs are relative to the location of the `.gitignore` file:
540 Fossil's globs are relative to the root of the workspace.
541 * Git's globs and Fossil's globs treat directory separators
542 differently. Git includes a notation for zero or more directories
543 that is not needed in Fossil.
544
545 ### Example
546
547 In a project with source and documentation:
548
@@ -502,30 +571,26 @@
571
572
573
574 ## Implementation and References
575
576 The implementation of the Fossil-specific glob pattern handling is here:
 
 
 
 
 
 
 
577
578 :File |:Description
579 --------------------------------------------------------------------------------
580 [`src/glob.c`][] | pattern list loading, parsing, and generic matching code
581 [`src/file.c`][] | application of glob patterns to file names
582
583 [`src/glob.c`]: https://www.fossil-scm.org/index.html/file/src/glob.c
584 [`src/file.c`]: https://www.fossil-scm.org/index.html/file/src/file.c
585
586 See the [Adding Features to Fossil][aff] document for broader details
587 about finding and working with such code.
588
589 The actual pattern matching leverages the `GLOB` operator in SQLite, so
590 you may find [its documentation][gdoc], [source code][gsrc] and [test
591 harness][gtst] helpful.
592
593 [aff]: ./adding_code.wiki
594 [gdoc]: https://sqlite.org/lang_expr.html#like
595 [gsrc]: https://www.sqlite.org/src/artifact?name=9d52522cc8ae7f5c&ln=570-768
596 [gtst]: https://www.sqlite.org/src/artifact?name=66a2c9ac34f74f03&ln=586-673
597
+5 -6
--- www/grep.md
+++ www/grep.md
@@ -39,16 +39,15 @@
3939
* There is no way to suppress all output, returning only a status code
4040
to indicate whether the pattern matched, as with `grep -q`.
4141
4242
* There is no way to suppress error output, as with `grep -s`.
4343
44
-* Fossil `grep` accepts only a single input file name. You cannot give
45
- it a list of file names, and you cannot give it a directory name for
46
- Fossil to expand to the set of all files under that directory. This
47
- means Fossil `grep` has no equivalent of the common POSIX `grep -R`
48
- extension. (And if it did, it would probably have a different option
49
- letter, since `-R` in Fossil has a different meaning, by
44
+* Fossil `grep` does not accept a directory name for Fossil to
45
+ expand to the set of all files under that directory. This means
46
+ Fossil `grep` has no equivalent of the common POSIX `grep -R`
47
+ extension. (And if it did, it would probably have a different
48
+ option letter, since `-R` in Fossil has a different meaning, by
5049
convention.)
5150
5251
* You cannot invert the match, as with `grep -v`.
5352
5453
Patches to remove those limitations will be thoughtfully considered.
5554
--- www/grep.md
+++ www/grep.md
@@ -39,16 +39,15 @@
39 * There is no way to suppress all output, returning only a status code
40 to indicate whether the pattern matched, as with `grep -q`.
41
42 * There is no way to suppress error output, as with `grep -s`.
43
44 * Fossil `grep` accepts only a single input file name. You cannot give
45 it a list of file names, and you cannot give it a directory name for
46 Fossil to expand to the set of all files under that directory. This
47 means Fossil `grep` has no equivalent of the common POSIX `grep -R`
48 extension. (And if it did, it would probably have a different option
49 letter, since `-R` in Fossil has a different meaning, by
50 convention.)
51
52 * You cannot invert the match, as with `grep -v`.
53
54 Patches to remove those limitations will be thoughtfully considered.
55
--- www/grep.md
+++ www/grep.md
@@ -39,16 +39,15 @@
39 * There is no way to suppress all output, returning only a status code
40 to indicate whether the pattern matched, as with `grep -q`.
41
42 * There is no way to suppress error output, as with `grep -s`.
43
44 * Fossil `grep` does not accept a directory name for Fossil to
45 expand to the set of all files under that directory. This means
46 Fossil `grep` has no equivalent of the common POSIX `grep -R`
47 extension. (And if it did, it would probably have a different
48 option letter, since `-R` in Fossil has a different meaning, by
 
49 convention.)
50
51 * You cannot invert the match, as with `grep -v`.
52
53 Patches to remove those limitations will be thoughtfully considered.
54
--- www/hacker-howto.wiki
+++ www/hacker-howto.wiki
@@ -1,15 +1,15 @@
1
-<title>Fossil Hackers How-To</title>
1
+<title>Fossil Developer How-To</title>
22
33
The following links are of interest to programmers who want to modify
4
-or enhance Fossil. Ordinary users can safely ignore this information.
4
+or enhance Fossil itself. Ordinary users can safely ignore this information.
55
66
* [./build.wiki | How To Compile And Install Fossil]
77
* [./customskin.md | Theming Fossil]
8
+ * [./adding_code.wiki#newcmd | Adding New Commands To Fossil]
89
* [./makefile.wiki | The Fossil Build Process]
910
* [./tech_overview.wiki | A Technical Overview of Fossil]
10
- * [./adding_code.wiki | Adding Features To Fossil]
1111
* [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
1212
* [./fileformat.wiki|Fossil Artifact File Format]
1313
* [./sync.wiki|The Sync Protocol]
1414
* [./style.wiki | Coding Style Guidelines]
1515
* [./checkin.wiki | Pre-checkin Checklist]
1616
--- www/hacker-howto.wiki
+++ www/hacker-howto.wiki
@@ -1,15 +1,15 @@
1 <title>Fossil Hackers How-To</title>
2
3 The following links are of interest to programmers who want to modify
4 or enhance Fossil. Ordinary users can safely ignore this information.
5
6 * [./build.wiki | How To Compile And Install Fossil]
7 * [./customskin.md | Theming Fossil]
 
8 * [./makefile.wiki | The Fossil Build Process]
9 * [./tech_overview.wiki | A Technical Overview of Fossil]
10 * [./adding_code.wiki | Adding Features To Fossil]
11 * [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
12 * [./fileformat.wiki|Fossil Artifact File Format]
13 * [./sync.wiki|The Sync Protocol]
14 * [./style.wiki | Coding Style Guidelines]
15 * [./checkin.wiki | Pre-checkin Checklist]
16
--- www/hacker-howto.wiki
+++ www/hacker-howto.wiki
@@ -1,15 +1,15 @@
1 <title>Fossil Developer How-To</title>
2
3 The following links are of interest to programmers who want to modify
4 or enhance Fossil itself. Ordinary users can safely ignore this information.
5
6 * [./build.wiki | How To Compile And Install Fossil]
7 * [./customskin.md | Theming Fossil]
8 * [./adding_code.wiki#newcmd | Adding New Commands To Fossil]
9 * [./makefile.wiki | The Fossil Build Process]
10 * [./tech_overview.wiki | A Technical Overview of Fossil]
 
11 * [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
12 * [./fileformat.wiki|Fossil Artifact File Format]
13 * [./sync.wiki|The Sync Protocol]
14 * [./style.wiki | Coding Style Guidelines]
15 * [./checkin.wiki | Pre-checkin Checklist]
16
+2 -2
--- www/loadmgmt.md
+++ www/loadmgmt.md
@@ -31,12 +31,12 @@
3131
received while the host load average is too high.
3232
3333
Both of these load-control mechanisms are turned off by default, but
3434
they are recommended for high-traffic sites.
3535
36
-The webpage cache is activated using the [`fossil cache
37
-init`](/help/cache) command-line on the server. Add a `-R` option to
36
+The webpage cache is activated using the [`fossil cache init`](/help/cache)
37
+command-line on the server. Add a `-R` option to
3838
specify the specific repository for which to enable caching. If running
3939
this command as root, be sure to “`chown`” the cache database to give
4040
the Fossil server write permission for the user ID of the web server;
4141
this is a separate file in the same directory and with the same name as
4242
the repository but with the “`.fossil`” suffix changed to “`.cache`”.
4343
--- www/loadmgmt.md
+++ www/loadmgmt.md
@@ -31,12 +31,12 @@
31 received while the host load average is too high.
32
33 Both of these load-control mechanisms are turned off by default, but
34 they are recommended for high-traffic sites.
35
36 The webpage cache is activated using the [`fossil cache
37 init`](/help/cache) command-line on the server. Add a `-R` option to
38 specify the specific repository for which to enable caching. If running
39 this command as root, be sure to “`chown`” the cache database to give
40 the Fossil server write permission for the user ID of the web server;
41 this is a separate file in the same directory and with the same name as
42 the repository but with the “`.fossil`” suffix changed to “`.cache`”.
43
--- www/loadmgmt.md
+++ www/loadmgmt.md
@@ -31,12 +31,12 @@
31 received while the host load average is too high.
32
33 Both of these load-control mechanisms are turned off by default, but
34 they are recommended for high-traffic sites.
35
36 The webpage cache is activated using the [`fossil cache init`](/help/cache)
37 command-line on the server. Add a `-R` option to
38 specify the specific repository for which to enable caching. If running
39 this command as root, be sure to “`chown`” the cache database to give
40 the Fossil server write permission for the user ID of the web server;
41 this is a separate file in the same directory and with the same name as
42 the repository but with the “`.fossil`” suffix changed to “`.cache`”.
43
+2 -1
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -50,10 +50,11 @@
5050
fossil_prompt.wiki {Fossilized Bash Prompt}
5151
fossil-v-git.wiki {Fossil Versus Git}
5252
globs.md {File Name Glob Patterns}
5353
grep.md {Fossil grep vs POSIX grep}
5454
hacker-howto.wiki {Hacker How-To}
55
+ hacker-howto.wiki {Fossil Developers Guide}
5556
hashpolicy.wiki {Hash Policy: Choosing Between SHA1 and SHA3-256}
5657
/help {Lists of Commands and Webpages}
5758
hints.wiki {Fossil Tips And Usage Hints}
5859
index.wiki {Home Page}
5960
inout.wiki {Import And Export To And From Git}
@@ -139,11 +140,11 @@
139140
<li> <a href='build.wiki'>Compiling and installing Fossil</a>
140141
<li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
141142
<li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
142143
book</a>
143144
<li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a>
144
-<li> <a href='hacker-howto.wiki'>Hacker How-To</a>
145
+<li> <a href='hacker-howto.wiki'>Fossil Developer's Guide</a>
145146
</ul>
146147
<a name="pindex"></a>
147148
<h2>Permuted Index:</h2>
148149
<ul>}
149150
foreach entry $permindex {
150151
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -50,10 +50,11 @@
50 fossil_prompt.wiki {Fossilized Bash Prompt}
51 fossil-v-git.wiki {Fossil Versus Git}
52 globs.md {File Name Glob Patterns}
53 grep.md {Fossil grep vs POSIX grep}
54 hacker-howto.wiki {Hacker How-To}
 
55 hashpolicy.wiki {Hash Policy: Choosing Between SHA1 and SHA3-256}
56 /help {Lists of Commands and Webpages}
57 hints.wiki {Fossil Tips And Usage Hints}
58 index.wiki {Home Page}
59 inout.wiki {Import And Export To And From Git}
@@ -139,11 +140,11 @@
139 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
140 <li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
141 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
142 book</a>
143 <li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a>
144 <li> <a href='hacker-howto.wiki'>Hacker How-To</a>
145 </ul>
146 <a name="pindex"></a>
147 <h2>Permuted Index:</h2>
148 <ul>}
149 foreach entry $permindex {
150
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -50,10 +50,11 @@
50 fossil_prompt.wiki {Fossilized Bash Prompt}
51 fossil-v-git.wiki {Fossil Versus Git}
52 globs.md {File Name Glob Patterns}
53 grep.md {Fossil grep vs POSIX grep}
54 hacker-howto.wiki {Hacker How-To}
55 hacker-howto.wiki {Fossil Developers Guide}
56 hashpolicy.wiki {Hash Policy: Choosing Between SHA1 and SHA3-256}
57 /help {Lists of Commands and Webpages}
58 hints.wiki {Fossil Tips And Usage Hints}
59 index.wiki {Home Page}
60 inout.wiki {Import And Export To And From Git}
@@ -139,11 +140,11 @@
140 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
141 <li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
142 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
143 book</a>
144 <li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a>
145 <li> <a href='hacker-howto.wiki'>Fossil Developer's Guide</a>
146 </ul>
147 <a name="pindex"></a>
148 <h2>Permuted Index:</h2>
149 <ul>}
150 foreach entry $permindex {
151
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -13,11 +13,11 @@
1313
<li> <a href='build.wiki'>Compiling and installing Fossil</a>
1414
<li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
1515
<li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
1616
book</a>
1717
<li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a>
18
-<li> <a href='hacker-howto.wiki'>Hacker How-To</a>
18
+<li> <a href='hacker-howto.wiki'>Fossil Developer's Guide</a>
1919
</ul>
2020
<a name="pindex"></a>
2121
<h2>Permuted Index:</h2>
2222
<ul>
2323
<li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Up and Running in</a></li>
@@ -89,10 +89,11 @@
8989
<li><a href="private.wiki">Deleting Private Branches &mdash; Creating, Syncing, and</a></li>
9090
<li><a href="delta_encoder_algorithm.wiki">Delta Encoding Algorithm &mdash; Fossil</a></li>
9191
<li><a href="delta_format.wiki">Delta Format &mdash; Fossil</a></li>
9292
<li><a href="tech_overview.wiki">Design And Implementation Of Fossil &mdash; A Technical Overview Of The</a></li>
9393
<li><a href="theory1.wiki">Design Of The Fossil DVCS &mdash; Thoughts On The</a></li>
94
+<li><a href="hacker-howto.wiki">Developers Guide &mdash; Fossil</a></li>
9495
<li><a href="caps/admin-v-setup.md"><b>Differences Between Setup and Admin Users</b></a></li>
9596
<li><a href="embeddeddoc.wiki">Documentation &mdash; Embedded Project</a></li>
9697
<li><a href="contribute.wiki">Documentation To The Fossil Project &mdash; Contributing Code or</a></li>
9798
<li><a href="aboutdownload.wiki">Download Page Works &mdash; How The</a></li>
9899
<li><a href="theory1.wiki">DVCS &mdash; Thoughts On The Design Of The Fossil</a></li>
@@ -122,10 +123,11 @@
122123
<li><a href="blockchain.md"><b>Fossil As Blockchain</b></a></li>
123124
<li><a href="changes.wiki"><b>Fossil Changelog</b></a></li>
124125
<li><a href="concepts.wiki"><b>Fossil Core Concepts</b></a></li>
125126
<li><a href="delta_encoder_algorithm.wiki"><b>Fossil Delta Encoding Algorithm</b></a></li>
126127
<li><a href="delta_format.wiki"><b>Fossil Delta Format</b></a></li>
128
+<li><a href="hacker-howto.wiki"><b>Fossil Developers Guide</b></a></li>
127129
<li><a href="fileformat.wiki"><b>Fossil File Format</b></a></li>
128130
<li><a href="forum.wiki"><b>Fossil Forums</b></a></li>
129131
<li><a href="grep.md"><b>Fossil grep vs POSIX grep</b></a></li>
130132
<li><a href="quickstart.wiki"><b>Fossil Quick Start Guide</b></a></li>
131133
<li><a href="selfcheck.wiki"><b>Fossil Repository Integrity Self Checks</b></a></li>
@@ -145,10 +147,11 @@
145147
<li><a href="globs.md">Glob Patterns &mdash; File Name</a></li>
146148
<li><a href="env-opts.md">Global Options &mdash; Environment Variables and</a></li>
147149
<li><a href="customgraph.md">Graph &mdash; Theming: Customizing the Timeline</a></li>
148150
<li><a href="grep.md">grep &mdash; Fossil grep vs POSIX</a></li>
149151
<li><a href="grep.md">grep vs POSIX grep &mdash; Fossil</a></li>
152
+<li><a href="hacker-howto.wiki">Guide &mdash; Fossil Developers</a></li>
150153
<li><a href="quickstart.wiki">Guide &mdash; Fossil Quick Start</a></li>
151154
<li><a href="style.wiki">Guidelines &mdash; Source Code Style</a></li>
152155
<li><a href="hacker-howto.wiki"><b>Hacker How-To</b></a></li>
153156
<li><a href="adding_code.wiki"><b>Hacking Fossil</b></a></li>
154157
<li><a href="rebaseharm.md">Harmful &mdash; Rebase Considered</a></li>
155158
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -13,11 +13,11 @@
13 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
14 <li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
15 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
16 book</a>
17 <li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a>
18 <li> <a href='hacker-howto.wiki'>Hacker How-To</a>
19 </ul>
20 <a name="pindex"></a>
21 <h2>Permuted Index:</h2>
22 <ul>
23 <li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Up and Running in</a></li>
@@ -89,10 +89,11 @@
89 <li><a href="private.wiki">Deleting Private Branches &mdash; Creating, Syncing, and</a></li>
90 <li><a href="delta_encoder_algorithm.wiki">Delta Encoding Algorithm &mdash; Fossil</a></li>
91 <li><a href="delta_format.wiki">Delta Format &mdash; Fossil</a></li>
92 <li><a href="tech_overview.wiki">Design And Implementation Of Fossil &mdash; A Technical Overview Of The</a></li>
93 <li><a href="theory1.wiki">Design Of The Fossil DVCS &mdash; Thoughts On The</a></li>
 
94 <li><a href="caps/admin-v-setup.md"><b>Differences Between Setup and Admin Users</b></a></li>
95 <li><a href="embeddeddoc.wiki">Documentation &mdash; Embedded Project</a></li>
96 <li><a href="contribute.wiki">Documentation To The Fossil Project &mdash; Contributing Code or</a></li>
97 <li><a href="aboutdownload.wiki">Download Page Works &mdash; How The</a></li>
98 <li><a href="theory1.wiki">DVCS &mdash; Thoughts On The Design Of The Fossil</a></li>
@@ -122,10 +123,11 @@
122 <li><a href="blockchain.md"><b>Fossil As Blockchain</b></a></li>
123 <li><a href="changes.wiki"><b>Fossil Changelog</b></a></li>
124 <li><a href="concepts.wiki"><b>Fossil Core Concepts</b></a></li>
125 <li><a href="delta_encoder_algorithm.wiki"><b>Fossil Delta Encoding Algorithm</b></a></li>
126 <li><a href="delta_format.wiki"><b>Fossil Delta Format</b></a></li>
 
127 <li><a href="fileformat.wiki"><b>Fossil File Format</b></a></li>
128 <li><a href="forum.wiki"><b>Fossil Forums</b></a></li>
129 <li><a href="grep.md"><b>Fossil grep vs POSIX grep</b></a></li>
130 <li><a href="quickstart.wiki"><b>Fossil Quick Start Guide</b></a></li>
131 <li><a href="selfcheck.wiki"><b>Fossil Repository Integrity Self Checks</b></a></li>
@@ -145,10 +147,11 @@
145 <li><a href="globs.md">Glob Patterns &mdash; File Name</a></li>
146 <li><a href="env-opts.md">Global Options &mdash; Environment Variables and</a></li>
147 <li><a href="customgraph.md">Graph &mdash; Theming: Customizing the Timeline</a></li>
148 <li><a href="grep.md">grep &mdash; Fossil grep vs POSIX</a></li>
149 <li><a href="grep.md">grep vs POSIX grep &mdash; Fossil</a></li>
 
150 <li><a href="quickstart.wiki">Guide &mdash; Fossil Quick Start</a></li>
151 <li><a href="style.wiki">Guidelines &mdash; Source Code Style</a></li>
152 <li><a href="hacker-howto.wiki"><b>Hacker How-To</b></a></li>
153 <li><a href="adding_code.wiki"><b>Hacking Fossil</b></a></li>
154 <li><a href="rebaseharm.md">Harmful &mdash; Rebase Considered</a></li>
155
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -13,11 +13,11 @@
13 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
14 <li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
15 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
16 book</a>
17 <li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a>
18 <li> <a href='hacker-howto.wiki'>Fossil Developer's Guide</a>
19 </ul>
20 <a name="pindex"></a>
21 <h2>Permuted Index:</h2>
22 <ul>
23 <li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Up and Running in</a></li>
@@ -89,10 +89,11 @@
89 <li><a href="private.wiki">Deleting Private Branches &mdash; Creating, Syncing, and</a></li>
90 <li><a href="delta_encoder_algorithm.wiki">Delta Encoding Algorithm &mdash; Fossil</a></li>
91 <li><a href="delta_format.wiki">Delta Format &mdash; Fossil</a></li>
92 <li><a href="tech_overview.wiki">Design And Implementation Of Fossil &mdash; A Technical Overview Of The</a></li>
93 <li><a href="theory1.wiki">Design Of The Fossil DVCS &mdash; Thoughts On The</a></li>
94 <li><a href="hacker-howto.wiki">Developers Guide &mdash; Fossil</a></li>
95 <li><a href="caps/admin-v-setup.md"><b>Differences Between Setup and Admin Users</b></a></li>
96 <li><a href="embeddeddoc.wiki">Documentation &mdash; Embedded Project</a></li>
97 <li><a href="contribute.wiki">Documentation To The Fossil Project &mdash; Contributing Code or</a></li>
98 <li><a href="aboutdownload.wiki">Download Page Works &mdash; How The</a></li>
99 <li><a href="theory1.wiki">DVCS &mdash; Thoughts On The Design Of The Fossil</a></li>
@@ -122,10 +123,11 @@
123 <li><a href="blockchain.md"><b>Fossil As Blockchain</b></a></li>
124 <li><a href="changes.wiki"><b>Fossil Changelog</b></a></li>
125 <li><a href="concepts.wiki"><b>Fossil Core Concepts</b></a></li>
126 <li><a href="delta_encoder_algorithm.wiki"><b>Fossil Delta Encoding Algorithm</b></a></li>
127 <li><a href="delta_format.wiki"><b>Fossil Delta Format</b></a></li>
128 <li><a href="hacker-howto.wiki"><b>Fossil Developers Guide</b></a></li>
129 <li><a href="fileformat.wiki"><b>Fossil File Format</b></a></li>
130 <li><a href="forum.wiki"><b>Fossil Forums</b></a></li>
131 <li><a href="grep.md"><b>Fossil grep vs POSIX grep</b></a></li>
132 <li><a href="quickstart.wiki"><b>Fossil Quick Start Guide</b></a></li>
133 <li><a href="selfcheck.wiki"><b>Fossil Repository Integrity Self Checks</b></a></li>
@@ -145,10 +147,11 @@
147 <li><a href="globs.md">Glob Patterns &mdash; File Name</a></li>
148 <li><a href="env-opts.md">Global Options &mdash; Environment Variables and</a></li>
149 <li><a href="customgraph.md">Graph &mdash; Theming: Customizing the Timeline</a></li>
150 <li><a href="grep.md">grep &mdash; Fossil grep vs POSIX</a></li>
151 <li><a href="grep.md">grep vs POSIX grep &mdash; Fossil</a></li>
152 <li><a href="hacker-howto.wiki">Guide &mdash; Fossil Developers</a></li>
153 <li><a href="quickstart.wiki">Guide &mdash; Fossil Quick Start</a></li>
154 <li><a href="style.wiki">Guidelines &mdash; Source Code Style</a></li>
155 <li><a href="hacker-howto.wiki"><b>Hacker How-To</b></a></li>
156 <li><a href="adding_code.wiki"><b>Hacking Fossil</b></a></li>
157 <li><a href="rebaseharm.md">Harmful &mdash; Rebase Considered</a></li>
158

Keyboard Shortcuts

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