Fossil SCM
merge trunk
Commit
7da1aa6f3103ed961c331db4de4ca9d99616ee5354123510fc8d51ce34011f03
Parent
6e88c64bb28eb48…
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
~
src/alerts.c
~
src/branch.c
~
src/browse.c
~
src/capabilities.c
~
src/captcha.c
~
src/cgi.c
~
src/db.c
~
src/default_css.txt
~
src/doc.c
~
src/file.c
~
src/forum.c
~
src/http.c
~
src/info.c
~
src/json.c
~
src/login.c
~
src/main.c
~
src/main.mk
~
src/makemake.tcl
~
src/rss.c
~
src/security_audit.c
~
src/setup.c
~
src/setupuser.c
~
src/shell.c
~
src/sounds/0.wav
~
src/sounds/1.wav
~
src/sounds/2.wav
~
src/sounds/3.wav
~
src/sounds/4.wav
~
src/sounds/5.wav
~
src/sounds/6.wav
~
src/sounds/7.wav
~
src/sounds/8.wav
~
src/sounds/9.wav
~
src/sounds/README.md
~
src/sounds/a.wav
~
src/sounds/b.wav
~
src/sounds/c.wav
~
src/sounds/d.wav
~
src/sounds/e.wav
~
src/sounds/f.wav
~
src/sqlcmd.c
~
src/sqlite3.c
~
src/sqlite3.h
~
src/stat.c
~
src/style.c
~
src/timeline.c
~
src/tkt.c
~
src/undo.c
~
src/unversioned.c
~
src/wiki.c
~
win/Makefile.mingw
~
win/Makefile.mingw.mistachkin
~
win/Makefile.msc
~
www/adding_code.wiki
~
www/alerts.md
~
www/build.wiki
~
www/caps/index.md
~
www/caps/ref.html
~
www/changes.wiki
~
www/embeddeddoc.wiki
~
www/globs.md
~
www/grep.md
~
www/hacker-howto.wiki
~
www/loadmgmt.md
~
www/mkindex.tcl
~
www/permutedindex.html
+1
-1
| --- skins/default/header.txt | ||
| +++ skins/default/header.txt | ||
| @@ -1,10 +1,10 @@ | ||
| 1 | 1 | <div class="header"> |
| 2 | 2 | <div class="title"><h1>$<project_name></h1>$<title></div> |
| 3 | 3 | <div class="status"><th1> |
| 4 | 4 | if {[info exists login]} { |
| 5 | - html "$login — <a href='$home/login'>Logout</a>\n" | |
| 5 | + html "<a href='$home/login'>$login</a>\n" | |
| 6 | 6 | } else { |
| 7 | 7 | html "<a href='$home/login'>Login</a>\n" |
| 8 | 8 | } |
| 9 | 9 | </th1></div> |
| 10 | 10 | </div> |
| 11 | 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 "$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 @@ | ||
| 46 | 46 | @ -- |
| 47 | 47 | @ -- The ssub field is a string where each character indicates a particular |
| 48 | 48 | @ -- type of event to subscribe to. Choices: |
| 49 | 49 | @ -- a - Announcements |
| 50 | 50 | @ -- c - Check-ins |
| 51 | +@ -- f - Forum posts | |
| 51 | 52 | @ -- t - Ticket changes |
| 52 | 53 | @ -- w - Wiki changes |
| 53 | 54 | @ -- Probably different codes will be added in the future. In the future |
| 54 | 55 | @ -- we might also add a separate table that allows subscribing to email |
| 55 | 56 | @ -- notifications for specific branches or tags or tickets. |
| @@ -114,11 +115,11 @@ | ||
| 114 | 115 | if( bOnlyIfEnabled |
| 115 | 116 | && fossil_strcmp(db_get("email-send-method",0),"off")==0 |
| 116 | 117 | ){ |
| 117 | 118 | return; /* Don't create table for disabled email */ |
| 118 | 119 | } |
| 119 | - db_multi_exec(zAlertInit/*works-like:""*/); | |
| 120 | + db_exec_sql(zAlertInit); | |
| 120 | 121 | alert_triggers_enable(); |
| 121 | 122 | }else if( !db_table_has_column("repository","pending_alert","sentMod") ){ |
| 122 | 123 | db_multi_exec( |
| 123 | 124 | "ALTER TABLE repository.pending_alert" |
| 124 | 125 | " ADD COLUMN sentMod BOOLEAN DEFAULT false;" |
| @@ -183,11 +184,11 @@ | ||
| 183 | 184 | ** is an administrator. |
| 184 | 185 | */ |
| 185 | 186 | void alert_submenu_common(void){ |
| 186 | 187 | if( g.perm.Admin ){ |
| 187 | 188 | if( fossil_strcmp(g.zPath,"subscribers") ){ |
| 188 | - style_submenu_element("List Subscribers","%R/subscribers"); | |
| 189 | + style_submenu_element("Subscribers","%R/subscribers"); | |
| 189 | 190 | } |
| 190 | 191 | if( fossil_strcmp(g.zPath,"subscribe") ){ |
| 191 | 192 | style_submenu_element("Add New Subscriber","%R/subscribe"); |
| 192 | 193 | } |
| 193 | 194 | } |
| @@ -238,10 +239,17 @@ | ||
| 238 | 239 | @ This is URL used as the basename for hyperlinks included in |
| 239 | 240 | @ email alert text. Omit the trailing "/". |
| 240 | 241 | @ Suggested value: "%h(g.zBaseURL)" |
| 241 | 242 | @ (Property: "email-url")</p> |
| 242 | 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> | |
| 243 | 251 | |
| 244 | 252 | entry_attribute("\"Return-Path\" email address", 20, "email-self", |
| 245 | 253 | "eself", "", 0); |
| 246 | 254 | @ <p><b>Required.</b> |
| 247 | 255 | @ This is the email to which email notification bounces should be sent. |
| @@ -297,17 +305,10 @@ | ||
| 297 | 305 | @ append a colon and TCP port number (ex: smtp.example.com:587). |
| 298 | 306 | @ The default TCP port number is 25. |
| 299 | 307 | @ (Property: "email-send-relayhost")</p> |
| 300 | 308 | @ <hr> |
| 301 | 309 | |
| 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 | 310 | @ <p><input type="submit" name="submit" value="Apply Changes" /></p> |
| 310 | 311 | @ </div></form> |
| 311 | 312 | db_end_transaction(0); |
| 312 | 313 | style_footer(); |
| 313 | 314 | } |
| @@ -574,22 +575,18 @@ | ||
| 574 | 575 | } |
| 575 | 576 | return 0; |
| 576 | 577 | } |
| 577 | 578 | |
| 578 | 579 | /* |
| 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){ | |
| 591 | 588 | int i; |
| 592 | 589 | int nAt = 0; |
| 593 | 590 | int nDot = 0; |
| 594 | 591 | char c; |
| 595 | 592 | if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */ |
| @@ -619,13 +616,28 @@ | ||
| 619 | 616 | } |
| 620 | 617 | } |
| 621 | 618 | if( c!=cTerm ) return 0; /* Missing terminator */ |
| 622 | 619 | if( nAt==0 ) return 0; /* No "@" found anywhere */ |
| 623 | 620 | if( nDot==0 ) return 0; /* No "." in the domain */ |
| 621 | + return i; | |
| 622 | +} | |
| 624 | 623 | |
| 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); | |
| 627 | 639 | } |
| 628 | 640 | |
| 629 | 641 | /* |
| 630 | 642 | ** Scan the input string for a valid email address enclosed in <...> |
| 631 | 643 | ** If the string contains one or more email addresses, extract the first |
| @@ -659,10 +671,38 @@ | ||
| 659 | 671 | char *zOut = alert_find_emailaddr(zIn); |
| 660 | 672 | if( zOut ){ |
| 661 | 673 | sqlite3_result_text(context, zOut, -1, fossil_free); |
| 662 | 674 | } |
| 663 | 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 | +} | |
| 664 | 704 | |
| 665 | 705 | /* |
| 666 | 706 | ** Return the hostname portion of an email address - the part following |
| 667 | 707 | ** the @ |
| 668 | 708 | */ |
| @@ -762,11 +802,11 @@ | ||
| 762 | 802 | ** Date: |
| 763 | 803 | ** Message-Id: |
| 764 | 804 | ** Content-Type: |
| 765 | 805 | ** Content-Transfer-Encoding: |
| 766 | 806 | ** MIME-Version: |
| 767 | -** X-Fossil-From: | |
| 807 | +** Sender: | |
| 768 | 808 | ** |
| 769 | 809 | ** The caller maintains ownership of the input Blobs. This routine will |
| 770 | 810 | ** read the Blobs and send them onward to the email system, but it will |
| 771 | 811 | ** not free them. |
| 772 | 812 | ** |
| @@ -774,14 +814,14 @@ | ||
| 774 | 814 | ** in the pHdr parameter. |
| 775 | 815 | ** |
| 776 | 816 | ** If the zFromName argument is not NULL, then it should be a human-readable |
| 777 | 817 | ** name or handle for the sender. In that case, "From:" becomes a made-up |
| 778 | 818 | ** 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 | |
| 781 | 821 | ** 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 | |
| 783 | 823 | ** omitted. |
| 784 | 824 | */ |
| 785 | 825 | void alert_send( |
| 786 | 826 | AlertSender *p, /* Emailer context */ |
| 787 | 827 | Blob *pHdr, /* Email header (incomplete) */ |
| @@ -794,26 +834,26 @@ | ||
| 794 | 834 | fossil_print("Sending email\n"); |
| 795 | 835 | } |
| 796 | 836 | if( fossil_strcmp(p->zDest, "off")==0 ){ |
| 797 | 837 | return; |
| 798 | 838 | } |
| 839 | + blob_init(&all, 0, 0); | |
| 799 | 840 | if( fossil_strcmp(p->zDest, "blob")==0 ){ |
| 800 | 841 | pOut = &p->out; |
| 801 | 842 | if( blob_size(pOut) ){ |
| 802 | 843 | blob_appendf(pOut, "%.72c\n", '='); |
| 803 | 844 | } |
| 804 | 845 | }else{ |
| 805 | - blob_init(&all, 0, 0); | |
| 806 | 846 | pOut = &all; |
| 807 | 847 | } |
| 808 | 848 | blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr)); |
| 809 | 849 | if( p->zFrom==0 || p->zFrom[0]==0 ){ |
| 810 | 850 | return; /* email-self is not set. Error will be reported separately */ |
| 811 | 851 | }else if( zFromName ){ |
| 812 | 852 | blob_appendf(pOut, "From: %s <%s@%s>\r\n", |
| 813 | 853 | 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); | |
| 815 | 855 | }else{ |
| 816 | 856 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 817 | 857 | } |
| 818 | 858 | blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
| 819 | 859 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| @@ -879,10 +919,26 @@ | ||
| 879 | 919 | fossil_print("%s", blob_str(&all)); |
| 880 | 920 | } |
| 881 | 921 | blob_reset(&all); |
| 882 | 922 | } |
| 883 | 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 | +*/ | |
| 884 | 940 | /* |
| 885 | 941 | ** SETTING: email-send-method width=5 default=off |
| 886 | 942 | ** Determine the method used to send email. Allowed values are |
| 887 | 943 | ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value |
| 888 | 944 | ** means no email is ever sent. The "relay" value means emails are sent |
| @@ -1259,11 +1315,11 @@ | ||
| 1259 | 1315 | ** page. To allow anonymous passers-by to sign up for email |
| 1260 | 1316 | ** notification, set Email-Alerts on user "nobody" or "anonymous". |
| 1261 | 1317 | */ |
| 1262 | 1318 | void subscribe_page(void){ |
| 1263 | 1319 | int needCaptcha; |
| 1264 | - unsigned int uSeed; | |
| 1320 | + unsigned int uSeed = 0; | |
| 1265 | 1321 | const char *zDecoded; |
| 1266 | 1322 | char *zCaptcha = 0; |
| 1267 | 1323 | char *zErr = 0; |
| 1268 | 1324 | int eErr = 0; |
| 1269 | 1325 | int di; |
| @@ -1392,10 +1448,11 @@ | ||
| 1392 | 1448 | zDecoded = captcha_decode(uSeed); |
| 1393 | 1449 | zCaptcha = captcha_render(zDecoded); |
| 1394 | 1450 | @ <tr> |
| 1395 | 1451 | @ <td class="form_label">Security Code:</td> |
| 1396 | 1452 | @ <td><input type="text" name="captcha" value="" size="30"> |
| 1453 | + captcha_speakit_button(uSeed, "Speak the code"); | |
| 1397 | 1454 | @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
| 1398 | 1455 | @ </tr> |
| 1399 | 1456 | if( eErr==2 ){ |
| 1400 | 1457 | @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
| 1401 | 1458 | } |
| @@ -1461,11 +1518,11 @@ | ||
| 1461 | 1518 | @ </table> |
| 1462 | 1519 | if( needCaptcha ){ |
| 1463 | 1520 | @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
| 1464 | 1521 | @ %h(zCaptcha) |
| 1465 | 1522 | @ </pre> |
| 1466 | - @ Enter the 8 characters above in the "Security Code" box | |
| 1523 | + @ Enter the 8 characters above in the "Security Code" box<br/> | |
| 1467 | 1524 | @ </td></tr></table></div> |
| 1468 | 1525 | } |
| 1469 | 1526 | @ </form> |
| 1470 | 1527 | fossil_free(zErr); |
| 1471 | 1528 | style_footer(); |
| @@ -1511,16 +1568,17 @@ | ||
| 1511 | 1568 | ** to know the subscriber code. |
| 1512 | 1569 | */ |
| 1513 | 1570 | void alert_page(void){ |
| 1514 | 1571 | const char *zName = P("name"); |
| 1515 | 1572 | 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; | |
| 1520 | 1578 | const char *smip; |
| 1521 | - const char *suname; | |
| 1579 | + const char *suname = 0; | |
| 1522 | 1580 | const char *mtime; |
| 1523 | 1581 | const char *sctime; |
| 1524 | 1582 | int eErr = 0; |
| 1525 | 1583 | char *zErr = 0; |
| 1526 | 1584 | |
| @@ -1528,68 +1586,71 @@ | ||
| 1528 | 1586 | login_check_credentials(); |
| 1529 | 1587 | if( !g.perm.EmailAlert ){ |
| 1530 | 1588 | login_needed(g.anon.EmailAlert); |
| 1531 | 1589 | return; |
| 1532 | 1590 | } |
| 1533 | - if( zName==0 && login_is_individual() ){ | |
| 1591 | + isLogin = login_is_individual(); | |
| 1592 | + if( zName==0 && isLogin ){ | |
| 1534 | 1593 | zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber" |
| 1535 | 1594 | " WHERE suname=%Q", g.zLogin); |
| 1536 | 1595 | } |
| 1537 | 1596 | if( zName==0 || !validate16(zName, -1) ){ |
| 1538 | 1597 | cgi_redirect("subscribe"); |
| 1539 | 1598 | return; |
| 1540 | 1599 | } |
| 1541 | 1600 | alert_submenu_common(); |
| 1542 | 1601 | 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); | |
| 1591 | 1652 | } |
| 1592 | 1653 | if( P("delete")!=0 && cgi_csrf_safe(1) ){ |
| 1593 | 1654 | if( !PB("dodelete") ){ |
| 1594 | 1655 | eErr = 9; |
| 1595 | 1656 | zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to" |
| @@ -1597,10 +1658,11 @@ | ||
| 1597 | 1658 | }else{ |
| 1598 | 1659 | alert_unsubscribe(zName); |
| 1599 | 1660 | return; |
| 1600 | 1661 | } |
| 1601 | 1662 | } |
| 1663 | + style_header("Update Subscription"); | |
| 1602 | 1664 | db_prepare(&q, |
| 1603 | 1665 | "SELECT" |
| 1604 | 1666 | " semail," /* 0 */ |
| 1605 | 1667 | " sverified," /* 1 */ |
| 1606 | 1668 | " sdonotcall," /* 2 */ |
| @@ -1614,23 +1676,27 @@ | ||
| 1614 | 1676 | if( db_step(&q)!=SQLITE_ROW ){ |
| 1615 | 1677 | db_finalize(&q); |
| 1616 | 1678 | cgi_redirect("subscribe"); |
| 1617 | 1679 | return; |
| 1618 | 1680 | } |
| 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 | + } | |
| 1625 | 1691 | sa = strchr(ssub,'a')!=0; |
| 1626 | 1692 | sc = strchr(ssub,'c')!=0; |
| 1627 | 1693 | sf = strchr(ssub,'f')!=0; |
| 1628 | 1694 | st = strchr(ssub,'t')!=0; |
| 1629 | 1695 | sw = strchr(ssub,'w')!=0; |
| 1696 | + sx = strchr(ssub,'x')!=0; | |
| 1630 | 1697 | smip = db_column_text(&q, 5); |
| 1631 | - suname = db_column_text(&q, 6); | |
| 1632 | 1698 | mtime = db_column_text(&q, 7); |
| 1633 | 1699 | sctime = db_column_text(&q, 8); |
| 1634 | 1700 | if( !g.perm.Admin && !sverified ){ |
| 1635 | 1701 | db_multi_exec( |
| 1636 | 1702 | "UPDATE subscriber SET sverified=1 WHERE subscriberCode=hextoblob(%Q)", |
| @@ -1646,13 +1712,25 @@ | ||
| 1646 | 1712 | form_begin(0, "%R/alerts"); |
| 1647 | 1713 | @ <input type="hidden" name="name" value="%h(zName)"> |
| 1648 | 1714 | @ <table class="subscribe"> |
| 1649 | 1715 | @ <tr> |
| 1650 | 1716 | @ <td class="form_label">Email 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'>← not a valid email address!</span> | |
| 1721 | + }else if( g.perm.Admin ){ | |
| 1722 | + @ <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 | + } | |
| 1652 | 1729 | @ </tr> |
| 1653 | 1730 | if( g.perm.Admin ){ |
| 1731 | + int uid; | |
| 1654 | 1732 | @ <tr> |
| 1655 | 1733 | @ <td class='form_label'>Created:</td> |
| 1656 | 1734 | @ <td>%h(sctime)</td> |
| 1657 | 1735 | @ </tr> |
| 1658 | 1736 | @ <tr> |
| @@ -1664,11 +1742,16 @@ | ||
| 1664 | 1742 | @ <td>%h(smip)</td> |
| 1665 | 1743 | @ </tr> |
| 1666 | 1744 | @ <tr> |
| 1667 | 1745 | @ <td class="form_label">User:</td> |
| 1668 | 1746 | @ <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 | + @ <a href='%R/setup_uedit?id=%d(uid)'>\ | |
| 1751 | + @ (login info for %h(suname))</a>\ | |
| 1752 | + } | |
| 1670 | 1753 | @ </tr> |
| 1671 | 1754 | } |
| 1672 | 1755 | @ <tr> |
| 1673 | 1756 | @ <td class="form_label">Topics:</td> |
| 1674 | 1757 | @ <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\ |
| @@ -1678,10 +1761,12 @@ | ||
| 1678 | 1761 | @ Check-ins</label><br> |
| 1679 | 1762 | } |
| 1680 | 1763 | if( g.perm.RdForum ){ |
| 1681 | 1764 | @ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\ |
| 1682 | 1765 | @ Forum Posts</label><br> |
| 1766 | + @ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\ | |
| 1767 | + @ Forum Edits</label><br> | |
| 1683 | 1768 | } |
| 1684 | 1769 | if( g.perm.RdTkt ){ |
| 1685 | 1770 | @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\ |
| 1686 | 1771 | @ Ticket changes</label><br> |
| 1687 | 1772 | } |
| @@ -1703,11 +1788,11 @@ | ||
| 1703 | 1788 | #endif |
| 1704 | 1789 | if( g.perm.Admin ){ |
| 1705 | 1790 | @ <tr> |
| 1706 | 1791 | @ <td class="form_label">Admin Options:</td><td> |
| 1707 | 1792 | @ <label><input type="checkbox" name="sdonotcall" \ |
| 1708 | - @ %s(sdonotcall?"checked":"")> Do not call</label><br> | |
| 1793 | + @ %s(sdonotcall?"checked":"")> Do not disturb</label><br> | |
| 1709 | 1794 | @ <label><input type="checkbox" name="sverified" \ |
| 1710 | 1795 | @ %s(sverified?"checked":"")>\ |
| 1711 | 1796 | @ Verified</label></td></tr> |
| 1712 | 1797 | } |
| 1713 | 1798 | if( eErr==9 ){ |
| @@ -1760,11 +1845,11 @@ | ||
| 1760 | 1845 | */ |
| 1761 | 1846 | void unsubscribe_page(void){ |
| 1762 | 1847 | const char *zName = P("name"); |
| 1763 | 1848 | char *zErr = 0; |
| 1764 | 1849 | int eErr = 0; |
| 1765 | - unsigned int uSeed; | |
| 1850 | + unsigned int uSeed = 0; | |
| 1766 | 1851 | const char *zDecoded; |
| 1767 | 1852 | char *zCaptcha = 0; |
| 1768 | 1853 | int dx; |
| 1769 | 1854 | int bSubmit; |
| 1770 | 1855 | const char *zEAddr; |
| @@ -1855,10 +1940,11 @@ | ||
| 1855 | 1940 | zDecoded = captcha_decode(uSeed); |
| 1856 | 1941 | zCaptcha = captcha_render(zDecoded); |
| 1857 | 1942 | @ <tr> |
| 1858 | 1943 | @ <td class="form_label">Security Code:</td> |
| 1859 | 1944 | @ <td><input type="text" name="captcha" value="" size="30"> |
| 1945 | + captcha_speakit_button(uSeed, "Speak the code"); | |
| 1860 | 1946 | @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
| 1861 | 1947 | if( eErr==2 ){ |
| 1862 | 1948 | @ <td><span class="loginError">← %h(zErr)</span></td> |
| 1863 | 1949 | } |
| 1864 | 1950 | @ </tr> |
| @@ -1874,11 +1960,11 @@ | ||
| 1874 | 1960 | @ </tr> |
| 1875 | 1961 | @ </table> |
| 1876 | 1962 | @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
| 1877 | 1963 | @ %h(zCaptcha) |
| 1878 | 1964 | @ </pre> |
| 1879 | - @ Enter the 8 characters above in the "Security Code" box | |
| 1965 | + @ Enter the 8 characters above in the "Security Code" box<br/> | |
| 1880 | 1966 | @ </td></tr></table></div> |
| 1881 | 1967 | @ </form> |
| 1882 | 1968 | fossil_free(zErr); |
| 1883 | 1969 | style_footer(); |
| 1884 | 1970 | } |
| @@ -1885,37 +1971,69 @@ | ||
| 1885 | 1971 | |
| 1886 | 1972 | /* |
| 1887 | 1973 | ** WEBPAGE: subscribers |
| 1888 | 1974 | ** |
| 1889 | 1975 | ** This page, accessible to administrators only, |
| 1890 | -** shows a list of email notification email addresses. | |
| 1976 | +** shows a list of subscriber email addresses. | |
| 1891 | 1977 | ** Clicking on an email takes one to the /alerts page |
| 1892 | 1978 | ** for that email where the delivery settings can be |
| 1893 | 1979 | ** modified. |
| 1894 | 1980 | */ |
| 1895 | 1981 | void subscriber_list_page(void){ |
| 1896 | 1982 | Blob sql; |
| 1897 | 1983 | Stmt q; |
| 1898 | 1984 | sqlite3_int64 iNow; |
| 1985 | + int nTotal; | |
| 1986 | + int nPending; | |
| 1987 | + int nDel = 0; | |
| 1899 | 1988 | if( alert_webpages_disabled() ) return; |
| 1900 | 1989 | login_check_credentials(); |
| 1901 | 1990 | if( !g.perm.Admin ){ |
| 1902 | 1991 | login_needed(0); |
| 1903 | 1992 | return; |
| 1904 | 1993 | } |
| 1905 | 1994 | alert_submenu_common(); |
| 1995 | + style_submenu_element("Users","setup_ulist"); | |
| 1906 | 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 | + } | |
| 1907 | 2024 | blob_init(&sql, 0, 0); |
| 1908 | 2025 | blob_append_sql(&sql, |
| 1909 | 2026 | "SELECT hex(subscriberCode)," /* 0 */ |
| 1910 | 2027 | " semail," /* 1 */ |
| 1911 | 2028 | " ssub," /* 2 */ |
| 1912 | 2029 | " suname," /* 3 */ |
| 1913 | 2030 | " sverified," /* 4 */ |
| 1914 | 2031 | " sdigest," /* 5 */ |
| 1915 | 2032 | " mtime," /* 6 */ |
| 1916 | - " date(sctime,'unixepoch')" /* 7 */ | |
| 2033 | + " date(sctime,'unixepoch')," /* 7 */ | |
| 2034 | + " (SELECT uid FROM user WHERE login=subscriber.suname)" /* 8 */ | |
| 1917 | 2035 | " FROM subscriber" |
| 1918 | 2036 | ); |
| 1919 | 2037 | if( P("only")!=0 ){ |
| 1920 | 2038 | blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only")); |
| 1921 | 2039 | style_submenu_element("Show All","%R/subscribers"); |
| @@ -1937,16 +2055,22 @@ | ||
| 1937 | 2055 | @ </tr> |
| 1938 | 2056 | @ </thead><tbody> |
| 1939 | 2057 | while( db_step(&q)==SQLITE_ROW ){ |
| 1940 | 2058 | sqlite3_int64 iMtime = db_column_int64(&q, 6); |
| 1941 | 2059 | double rAge = (iNow - iMtime)/86400.0; |
| 2060 | + int uid = db_column_int(&q, 8); | |
| 2061 | + const char *zUname = db_column_text(&q, 3); | |
| 1942 | 2062 | @ <tr> |
| 1943 | 2063 | @ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\ |
| 1944 | 2064 | @ %h(db_column_text(&q,1))</a></td> |
| 1945 | 2065 | @ <td>%h(db_column_text(&q,2))</td> |
| 1946 | 2066 | @ <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 | + } | |
| 1948 | 2072 | @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td> |
| 1949 | 2073 | @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td> |
| 1950 | 2074 | @ <td>%h(db_column_text(&q,7))</td> |
| 1951 | 2075 | @ </tr> |
| 1952 | 2076 | } |
| @@ -1958,13 +2082,21 @@ | ||
| 1958 | 2082 | |
| 1959 | 2083 | #if LOCAL_INTERFACE |
| 1960 | 2084 | /* |
| 1961 | 2085 | ** A single event that might appear in an alert is recorded as an |
| 1962 | 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 | |
| 1963 | 2095 | */ |
| 1964 | 2096 | struct EmailEvent { |
| 1965 | - int type; /* 'c', 'f', 'm', 't', 'w' */ | |
| 2097 | + int type; /* 'c', 'f', 't', 'w', 'x' */ | |
| 1966 | 2098 | int needMod; /* Pending moderator approval */ |
| 1967 | 2099 | Blob hdr; /* Header content, for forum entries */ |
| 1968 | 2100 | Blob txt; /* Text description to appear in an alert */ |
| 1969 | 2101 | char *zFromName; /* Human name of the sender */ |
| 1970 | 2102 | EmailEvent *pNext; /* Next in chronological order */ |
| @@ -2039,20 +2171,20 @@ | ||
| 2039 | 2171 | p->needMod = db_column_int(&q, 4); |
| 2040 | 2172 | p->zFromName = 0; |
| 2041 | 2173 | p->pNext = 0; |
| 2042 | 2174 | switch( p->type ){ |
| 2043 | 2175 | case 'c': zType = "Check-In"; break; |
| 2044 | - case 'f': zType = "Forum post"; break; | |
| 2176 | + /* case 'f': -- forum posts omitted from this loop. See below */ | |
| 2045 | 2177 | case 't': zType = "Ticket Change"; break; |
| 2046 | 2178 | case 'w': zType = "Wiki Edit"; break; |
| 2047 | 2179 | } |
| 2048 | 2180 | blob_init(&p->hdr, 0, 0); |
| 2049 | 2181 | blob_init(&p->txt, 0, 0); |
| 2050 | 2182 | blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n", |
| 2051 | 2183 | db_column_text(&q,1), |
| 2052 | 2184 | zType, |
| 2053 | - db_column_text(&q,2), | |
| 2185 | + db_column_text(&q, 2), | |
| 2054 | 2186 | zUrl, |
| 2055 | 2187 | db_column_text(&q,0) |
| 2056 | 2188 | ); |
| 2057 | 2189 | if( p->needMod ){ |
| 2058 | 2190 | blob_appendf(&p->txt, |
| @@ -2084,11 +2216,12 @@ | ||
| 2084 | 2216 | " (SELECT uuid FROM blob WHERE rid=forumpost.fpid)," /* 1 */ |
| 2085 | 2217 | " datetime(event.mtime)," /* 2 */ |
| 2086 | 2218 | " substr(comment,instr(comment,':')+2)," /* 3 */ |
| 2087 | 2219 | " (SELECT uuid FROM blob WHERE rid=forumpost.firt)," /* 4 */ |
| 2088 | 2220 | " 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 */ | |
| 2090 | 2223 | " FROM temp.wantalert, event, forumpost" |
| 2091 | 2224 | " LEFT JOIN user ON (login=coalesce(euser,user))" |
| 2092 | 2225 | " WHERE event.objid=substr(wantalert.eventId,2)+0" |
| 2093 | 2226 | " AND eventId GLOB 'f*'" |
| 2094 | 2227 | " AND forumpost.fpid=event.objid" |
| @@ -2104,11 +2237,11 @@ | ||
| 2104 | 2237 | const char *z; |
| 2105 | 2238 | if( pPost==0 ) continue; |
| 2106 | 2239 | p = fossil_malloc( sizeof(EmailEvent) ); |
| 2107 | 2240 | pLast->pNext = p; |
| 2108 | 2241 | pLast = p; |
| 2109 | - p->type = 'f'; | |
| 2242 | + p->type = db_column_int(&q,7) ? 'f' : 'x'; | |
| 2110 | 2243 | p->needMod = db_column_int(&q, 5); |
| 2111 | 2244 | z = db_column_text(&q,6); |
| 2112 | 2245 | p->zFromName = z && z[0] ? fossil_strdup(z) : 0; |
| 2113 | 2246 | p->pNext = 0; |
| 2114 | 2247 | blob_init(&p->hdr, 0, 0); |
| @@ -2436,13 +2569,13 @@ | ||
| 2436 | 2569 | ** if the recipient is a moderator for that type of event. Setup |
| 2437 | 2570 | ** and Admin users always get notified. */ |
| 2438 | 2571 | char xType = '*'; |
| 2439 | 2572 | if( strpbrk(zCap,"as")==0 ){ |
| 2440 | 2573 | 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; | |
| 2444 | 2577 | } |
| 2445 | 2578 | if( strchr(zCap,xType)==0 ) continue; |
| 2446 | 2579 | } |
| 2447 | 2580 | }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){ |
| 2448 | 2581 | /* Setup and admin users can get any notification that does not |
| @@ -2450,14 +2583,14 @@ | ||
| 2450 | 2583 | }else{ |
| 2451 | 2584 | /* Other users only see the alert if they have sufficient |
| 2452 | 2585 | ** privilege to view the event itself */ |
| 2453 | 2586 | char xType = '*'; |
| 2454 | 2587 | 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; | |
| 2459 | 2592 | } |
| 2460 | 2593 | if( strchr(zCap,xType)==0 ) continue; |
| 2461 | 2594 | } |
| 2462 | 2595 | if( blob_size(&p->hdr)>0 ){ |
| 2463 | 2596 | /* This alert should be sent as a separate email */ |
| @@ -2594,10 +2727,11 @@ | ||
| 2594 | 2727 | @ <table class="subscribe"> |
| 2595 | 2728 | if( zCaptcha ){ |
| 2596 | 2729 | @ <tr> |
| 2597 | 2730 | @ <td class="form_label">Security Code:</td> |
| 2598 | 2731 | @ <td><input type="text" name="captcha" value="" size="10"> |
| 2732 | + captcha_speakit_button(uSeed, "Speak the code"); | |
| 2599 | 2733 | @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
| 2600 | 2734 | @ </tr> |
| 2601 | 2735 | } |
| 2602 | 2736 | @ <tr> |
| 2603 | 2737 | @ <td class="form_label">Your Email Address:</td> |
| @@ -2620,11 +2754,11 @@ | ||
| 2620 | 2754 | @ </table> |
| 2621 | 2755 | if( zCaptcha ){ |
| 2622 | 2756 | @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
| 2623 | 2757 | @ %h(zCaptcha) |
| 2624 | 2758 | @ </pre> |
| 2625 | - @ Enter the 8 characters above in the "Security Code" box | |
| 2759 | + @ Enter the 8 characters above in the "Security Code" box<br/> | |
| 2626 | 2760 | @ </td></tr></table></div> |
| 2627 | 2761 | } |
| 2628 | 2762 | @ </form> |
| 2629 | 2763 | style_footer(); |
| 2630 | 2764 | } |
| @@ -2638,10 +2772,11 @@ | ||
| 2638 | 2772 | char *zErr; |
| 2639 | 2773 | const char *zTo = PT("to"); |
| 2640 | 2774 | char *zSubject = PT("subject"); |
| 2641 | 2775 | int bAll = PB("all"); |
| 2642 | 2776 | int bAA = PB("aa"); |
| 2777 | + int bMods = PB("mods"); | |
| 2643 | 2778 | const char *zSub = db_get("email-subname", "[Fossil Repo]"); |
| 2644 | 2779 | int bTest2 = fossil_strcmp(P("name"),"test2")==0; |
| 2645 | 2780 | Blob hdr, body; |
| 2646 | 2781 | blob_init(&body, 0, 0); |
| 2647 | 2782 | blob_init(&hdr, 0, 0); |
| @@ -2649,17 +2784,29 @@ | ||
| 2649 | 2784 | pSender = alert_sender_new(bTest2 ? "blob" : 0, 0); |
| 2650 | 2785 | if( zTo[0] ){ |
| 2651 | 2786 | blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject); |
| 2652 | 2787 | alert_send(pSender, &hdr, &body, 0); |
| 2653 | 2788 | } |
| 2654 | - if( bAll || bAA ){ | |
| 2789 | + if( bAll || bAA || bMods ){ | |
| 2655 | 2790 | Stmt q; |
| 2656 | 2791 | int nUsed = blob_size(&body); |
| 2657 | 2792 | 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 | + } | |
| 2661 | 2808 | while( db_step(&q)==SQLITE_ROW ){ |
| 2662 | 2809 | const char *zCode = db_column_text(&q, 1); |
| 2663 | 2810 | zTo = db_column_text(&q, 0); |
| 2664 | 2811 | blob_truncate(&hdr, 0); |
| 2665 | 2812 | blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject); |
| @@ -2716,11 +2863,12 @@ | ||
| 2716 | 2863 | @ <p>The following error was reported by the system: |
| 2717 | 2864 | @ <blockquote><pre> |
| 2718 | 2865 | @ %h(zErr) |
| 2719 | 2866 | @ </pre></blockquote> |
| 2720 | 2867 | }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> | |
| 2722 | 2870 | } |
| 2723 | 2871 | style_footer(); |
| 2724 | 2872 | return; |
| 2725 | 2873 | } else if( !alert_enabled() ){ |
| 2726 | 2874 | style_header("Cannot Send Announcement"); |
| @@ -2734,21 +2882,26 @@ | ||
| 2734 | 2882 | @ <form method="POST"> |
| 2735 | 2883 | @ <table class="subscribe"> |
| 2736 | 2884 | if( g.perm.Admin ){ |
| 2737 | 2885 | int aa = PB("aa"); |
| 2738 | 2886 | int all = PB("all"); |
| 2887 | + int aMod = PB("mods"); | |
| 2739 | 2888 | const char *aack = aa ? "checked" : ""; |
| 2740 | 2889 | const char *allck = all ? "checked" : ""; |
| 2890 | + const char *modck = aMod ? "checked" : ""; | |
| 2741 | 2891 | @ <tr> |
| 2742 | 2892 | @ <td class="form_label">To:</td> |
| 2743 | 2893 | @ <td><input type="text" name="to" value="%h(PT("to"))" size="30"><br> |
| 2744 | 2894 | @ <label><input type="checkbox" name="aa" %s(aack)> \ |
| 2745 | 2895 | @ All "announcement" subscribers</label> \ |
| 2746 | 2896 | @ <a href="%R/subscribers?only=a" target="_blank">(list)</a><br> |
| 2747 | 2897 | @ <label><input type="checkbox" name="all" %s(allck)> \ |
| 2748 | 2898 | @ 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> | |
| 2750 | 2903 | @ </tr> |
| 2751 | 2904 | } |
| 2752 | 2905 | @ <tr> |
| 2753 | 2906 | @ <td class="form_label">Subject:</td> |
| 2754 | 2907 | @ <td><input type="text" name="subject" value="%h(PT("subject"))"\ |
| @@ -2759,11 +2912,15 @@ | ||
| 2759 | 2912 | @ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\ |
| 2760 | 2913 | @ %h(PT("msg"))</textarea> |
| 2761 | 2914 | @ </tr> |
| 2762 | 2915 | @ <tr> |
| 2763 | 2916 | @ <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 | + } | |
| 2765 | 2922 | @ </tr> |
| 2766 | 2923 | @ </table> |
| 2767 | 2924 | @ </form> |
| 2768 | 2925 | style_footer(); |
| 2769 | 2926 | } |
| 2770 | 2927 |
| --- 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'>↑ %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 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">← %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 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 Email 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'>↑ %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 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'>← not a valid email address!</span> |
| 1721 | }else if( g.perm.Admin ){ |
| 1722 | @ <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 | @ <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">← %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 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 Email 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 @@ | ||
| 245 | 245 | @ GROUP BY 1; |
| 246 | 246 | ; |
| 247 | 247 | |
| 248 | 248 | /* Call this routine to create the TEMP table */ |
| 249 | 249 | static void brlist_create_temp_table(void){ |
| 250 | - db_multi_exec(createBrlistQuery/*works-like:""*/); | |
| 250 | + db_exec_sql(createBrlistQuery); | |
| 251 | 251 | } |
| 252 | 252 | |
| 253 | 253 | |
| 254 | 254 | #if INTERFACE |
| 255 | 255 | /* |
| 256 | 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_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 @@ | ||
| 918 | 918 | ** mtime on that check-in. If zGlob and *zGlob then only files matching |
| 919 | 919 | ** the given glob are computed. |
| 920 | 920 | */ |
| 921 | 921 | int compute_fileage(int vid, const char* zGlob){ |
| 922 | 922 | Stmt q; |
| 923 | - db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/); | |
| 923 | + db_exec_sql(zComputeFileAgeSetup); | |
| 924 | 924 | db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/); |
| 925 | 925 | db_bind_int(&q, ":ckin", vid); |
| 926 | 926 | db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*"); |
| 927 | 927 | db_exec(&q); |
| 928 | 928 | db_finalize(&q); |
| 929 | 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_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 |
+5
-3
| --- src/capabilities.c | ||
| +++ src/capabilities.c | ||
| @@ -242,12 +242,14 @@ | ||
| 242 | 242 | "Admin", "Create and delete users" }, |
| 243 | 243 | { 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0, |
| 244 | 244 | "Attach", "Add attchments to wiki or tickets" }, |
| 245 | 245 | { 'c', CAPCLASS_TKT, 0, |
| 246 | 246 | "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 | + */ | |
| 249 | 251 | { 'e', CAPCLASS_DATA, 0, |
| 250 | 252 | "View-PII", "View sensitive info such as email addresses" }, |
| 251 | 253 | { 'f', CAPCLASS_WIKI, 0, |
| 252 | 254 | "New-Wiki", "Create new wiki pages" }, |
| 253 | 255 | { 'g', CAPCLASS_DATA, 0, |
| @@ -430,11 +432,11 @@ | ||
| 430 | 432 | eType = 0; |
| 431 | 433 | } |
| 432 | 434 | @ <td class="%s(azClass[eType])">%s(azType[eType])</td> |
| 433 | 435 | |
| 434 | 436 | /* Ticket */ |
| 435 | - if( sqlite3_strglob("*[ascdnqtw]*",zCap)==0 ){ | |
| 437 | + if( sqlite3_strglob("*[ascnqtw]*",zCap)==0 ){ | |
| 436 | 438 | eType = 2; |
| 437 | 439 | }else if( sqlite3_strglob("*r*",zCap)==0 ){ |
| 438 | 440 | eType = 1; |
| 439 | 441 | }else{ |
| 440 | 442 | eType = 0; |
| 441 | 443 |
| --- 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 |
+94
| --- src/captcha.c | ||
| +++ src/captcha.c | ||
| @@ -548,12 +548,31 @@ | ||
| 548 | 548 | @ <input type="hidden" name="captchaseed" value="%u(uSeed)" /> |
| 549 | 549 | @ <input type="text" name="captcha" size=8 /> |
| 550 | 550 | if( showButton ){ |
| 551 | 551 | @ <input type="submit" value="Submit"> |
| 552 | 552 | } |
| 553 | + @ <br/>\ | |
| 554 | + captcha_speakit_button(uSeed, 0); | |
| 553 | 555 | @ </td></tr></table></div> |
| 554 | 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 | +} | |
| 555 | 574 | |
| 556 | 575 | /* |
| 557 | 576 | ** WEBPAGE: test-captcha |
| 558 | 577 | ** Test the captcha-generator by rendering the value of the name= query |
| 559 | 578 | ** parameter using ascii-art. If name= is omitted, show a random 16-digit |
| @@ -608,5 +627,80 @@ | ||
| 608 | 627 | captcha_generate(1); |
| 609 | 628 | @ </form> |
| 610 | 629 | style_footer(); |
| 611 | 630 | return 1; |
| 612 | 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 | +} | |
| 613 | 707 |
| --- 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 @@ | ||
| 178 | 178 | */ |
| 179 | 179 | static const char *zContentType = "text/html"; /* Content type of the reply */ |
| 180 | 180 | static const char *zReplyStatus = "OK"; /* Reply status description */ |
| 181 | 181 | static int iReplyStatus = 200; /* Reply status code */ |
| 182 | 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 */ | |
| 183 | 185 | |
| 184 | 186 | /* |
| 185 | 187 | ** Set the reply content type |
| 186 | 188 | */ |
| 187 | 189 | void cgi_set_content_type(const char *zType){ |
| @@ -272,15 +274,23 @@ | ||
| 272 | 274 | iReplyStatus = 200; |
| 273 | 275 | zReplyStatus = "OK"; |
| 274 | 276 | } |
| 275 | 277 | |
| 276 | 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 | + } | |
| 277 | 286 | fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus); |
| 278 | 287 | fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0))); |
| 279 | 288 | fprintf(g.httpOut, "Connection: close\r\n"); |
| 280 | 289 | fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n"); |
| 281 | 290 | }else{ |
| 291 | + assert( rangeEnd==0 ); | |
| 282 | 292 | fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); |
| 283 | 293 | } |
| 284 | 294 | if( g.isConst ){ |
| 285 | 295 | /* isConst means that the reply is guaranteed to be invariant, even |
| 286 | 296 | ** after configuration changes and/or Fossil binary recompiles. */ |
| @@ -325,12 +335,12 @@ | ||
| 325 | 335 | if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){ |
| 326 | 336 | cgi_combine_header_and_body(); |
| 327 | 337 | blob_compress(&cgiContent[0], &cgiContent[0]); |
| 328 | 338 | } |
| 329 | 339 | |
| 330 | - if( iReplyStatus != 304 ) { | |
| 331 | - if( is_gzippable() ){ | |
| 340 | + if( iReplyStatus!=304 ) { | |
| 341 | + if( is_gzippable() && iReplyStatus!=206 ){ | |
| 332 | 342 | int i; |
| 333 | 343 | gzip_begin(0); |
| 334 | 344 | for( i=0; i<2; i++ ){ |
| 335 | 345 | int size = blob_size(&cgiContent[i]); |
| 336 | 346 | if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size); |
| @@ -339,10 +349,15 @@ | ||
| 339 | 349 | gzip_finish(&cgiContent[0]); |
| 340 | 350 | fprintf(g.httpOut, "Content-Encoding: gzip\r\n"); |
| 341 | 351 | fprintf(g.httpOut, "Vary: Accept-Encoding\r\n"); |
| 342 | 352 | } |
| 343 | 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 | + } | |
| 344 | 359 | fprintf(g.httpOut, "Content-Length: %d\r\n", total_size); |
| 345 | 360 | }else{ |
| 346 | 361 | total_size = 0; |
| 347 | 362 | } |
| 348 | 363 | fprintf(g.httpOut, "\r\n"); |
| @@ -350,12 +365,20 @@ | ||
| 350 | 365 | && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0 |
| 351 | 366 | ){ |
| 352 | 367 | int i, size; |
| 353 | 368 | for(i=0; i<2; i++){ |
| 354 | 369 | 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; | |
| 357 | 380 | } |
| 358 | 381 | } |
| 359 | 382 | } |
| 360 | 383 | fflush(g.httpOut); |
| 361 | 384 | CGIDEBUG(("-------- END cgi ---------\n")); |
| @@ -688,36 +711,36 @@ | ||
| 688 | 711 | return z; |
| 689 | 712 | } |
| 690 | 713 | |
| 691 | 714 | /* |
| 692 | 715 | ** 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 | |
| 695 | 718 | ** in *pz. |
| 696 | 719 | ** |
| 697 | 720 | ** This routine adds a "\000" to the end of the content (overwriting |
| 698 | 721 | ** 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. | |
| 700 | 723 | ** The length of the content is stored in *pnContent. |
| 701 | 724 | */ |
| 702 | 725 | static char *get_bounded_content( |
| 703 | 726 | char **pz, /* Content taken from here */ |
| 704 | 727 | 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 */ | |
| 706 | 729 | int *pnContent /* Write the size of the content here */ |
| 707 | 730 | ){ |
| 708 | 731 | char *z = *pz; |
| 709 | 732 | int len = *pLen; |
| 710 | 733 | int i; |
| 711 | - int nBoundry = strlen(zBoundry); | |
| 734 | + int nBoundary = strlen(zBoundary); | |
| 712 | 735 | *pnContent = len; |
| 713 | 736 | 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 ){ | |
| 715 | 738 | if( i>0 && z[i-1]=='\r' ) i--; |
| 716 | 739 | z[i] = 0; |
| 717 | 740 | *pnContent = i; |
| 718 | - i += nBoundry; | |
| 741 | + i += nBoundary; | |
| 719 | 742 | break; |
| 720 | 743 | } |
| 721 | 744 | } |
| 722 | 745 | *pz = &z[i]; |
| 723 | 746 | get_line_from_string(pz, pLen); |
| @@ -782,22 +805,22 @@ | ||
| 782 | 805 | ** table. |
| 783 | 806 | */ |
| 784 | 807 | static void process_multipart_form_data(char *z, int len){ |
| 785 | 808 | char *zLine; |
| 786 | 809 | int nArg, i; |
| 787 | - char *zBoundry; | |
| 810 | + char *zBoundary; | |
| 788 | 811 | char *zValue; |
| 789 | 812 | char *zName = 0; |
| 790 | 813 | int showBytes = 0; |
| 791 | 814 | char *azArg[50]; |
| 792 | 815 | |
| 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; | |
| 795 | 818 | while( (zLine = get_line_from_string(&z, &len))!=0 ){ |
| 796 | 819 | if( zLine[0]==0 ){ |
| 797 | 820 | int nContent = 0; |
| 798 | - zValue = get_bounded_content(&z, &len, zBoundry, &nContent); | |
| 821 | + zValue = get_bounded_content(&z, &len, zBoundary, &nContent); | |
| 799 | 822 | if( zName && zValue ){ |
| 800 | 823 | if( fossil_islower(zName[0]) ){ |
| 801 | 824 | cgi_set_parameter_nocopy(zName, zValue, 1); |
| 802 | 825 | if( showBytes ){ |
| 803 | 826 | cgi_set_parameter_nocopy(mprintf("%s:bytes", zName), |
| @@ -1658,10 +1681,17 @@ | ||
| 1658 | 1681 | }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){ |
| 1659 | 1682 | const char *zIpAddr = cgi_accept_forwarded_for(zVal); |
| 1660 | 1683 | if( zIpAddr!=0 ){ |
| 1661 | 1684 | g.zIpAddr = mprintf("%s", zIpAddr); |
| 1662 | 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; | |
| 1663 | 1693 | } |
| 1664 | 1694 | } |
| 1665 | 1695 | } |
| 1666 | 1696 | cgi_init(); |
| 1667 | 1697 | cgi_trace(0); |
| 1668 | 1698 |
| --- 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 |
M
src/db.c
+28
-16
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -653,23 +653,17 @@ | ||
| 653 | 653 | blob_reset(&sql); |
| 654 | 654 | return rc; |
| 655 | 655 | } |
| 656 | 656 | |
| 657 | 657 | /* |
| 658 | -** Execute multiple SQL statements. | |
| 658 | +** Execute multiple SQL statements. The input text is executed | |
| 659 | +** directly without any formatting. | |
| 659 | 660 | */ |
| 660 | -int db_multi_exec(const char *zSql, ...){ | |
| 661 | - Blob sql; | |
| 661 | +int db_exec_sql(const char *z){ | |
| 662 | 662 | int rc = SQLITE_OK; |
| 663 | - va_list ap; | |
| 664 | - const char *z, *zEnd; | |
| 665 | 663 | 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; | |
| 671 | 665 | while( rc==SQLITE_OK && z[0] ){ |
| 672 | 666 | pStmt = 0; |
| 673 | 667 | rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); |
| 674 | 668 | if( rc ){ |
| 675 | 669 | db_err("%s: {%s}", sqlite3_errmsg(g.db), z); |
| @@ -679,10 +673,26 @@ | ||
| 679 | 673 | rc = sqlite3_finalize(pStmt); |
| 680 | 674 | if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); |
| 681 | 675 | } |
| 682 | 676 | z = zEnd; |
| 683 | 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)); | |
| 684 | 694 | blob_reset(&sql); |
| 685 | 695 | return rc; |
| 686 | 696 | } |
| 687 | 697 | |
| 688 | 698 | /* |
| @@ -1031,10 +1041,12 @@ | ||
| 1031 | 1041 | 0, capability_union_step, capability_union_finalize); |
| 1032 | 1042 | sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0, |
| 1033 | 1043 | capability_fullcap, 0, 0); |
| 1034 | 1044 | sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0, |
| 1035 | 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); | |
| 1036 | 1048 | } |
| 1037 | 1049 | |
| 1038 | 1050 | #if USE_SEE |
| 1039 | 1051 | /* |
| 1040 | 1052 | ** This is a pointer to the saved database encryption key string. |
| @@ -1286,17 +1298,17 @@ | ||
| 1286 | 1298 | blob_init(&key, 0, 0); |
| 1287 | 1299 | db_maybe_obtain_encryption_key(zDbName, &key); |
| 1288 | 1300 | if( fossil_getenv("FOSSIL_USE_SEE_TEXTKEY")==0 ){ |
| 1289 | 1301 | char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY %Q", |
| 1290 | 1302 | zDbName, zLabel, blob_str(&key)); |
| 1291 | - db_multi_exec(zCmd /*works-like:""*/); | |
| 1303 | + db_exec_sql(zCmd); | |
| 1292 | 1304 | fossil_secure_zero(zCmd, strlen(zCmd)); |
| 1293 | 1305 | sqlite3_free(zCmd); |
| 1294 | 1306 | }else{ |
| 1295 | 1307 | char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY ''", |
| 1296 | 1308 | zDbName, zLabel); |
| 1297 | - db_multi_exec(zCmd /*works-like:""*/); | |
| 1309 | + db_exec_sql(zCmd); | |
| 1298 | 1310 | sqlite3_free(zCmd); |
| 1299 | 1311 | #if USE_SEE |
| 1300 | 1312 | if( blob_size(&key)>0 ){ |
| 1301 | 1313 | sqlite3_key_v2(g.db, zLabel, blob_str(&key), -1); |
| 1302 | 1314 | } |
| @@ -1762,13 +1774,13 @@ | ||
| 1762 | 1774 | "UPDATE vfile" |
| 1763 | 1775 | " SET mhash=(SELECT uuid FROM blob WHERE blob.rid=vfile.mrid)" |
| 1764 | 1776 | " WHERE mrid!=rid;" |
| 1765 | 1777 | ); |
| 1766 | 1778 | 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( | |
| 1770 | 1782 | "INSERT OR IGNORE INTO vmerge(id,merge,mhash)" |
| 1771 | 1783 | " SELECT id, merge, blob.uuid FROM old_vmerge, blob" |
| 1772 | 1784 | " WHERE old_vmerge.merge=blob.rid;" |
| 1773 | 1785 | "DROP TABLE old_vmerge;" |
| 1774 | 1786 | ); |
| @@ -2074,11 +2086,11 @@ | ||
| 2074 | 2086 | "INSERT OR IGNORE INTO user(login,pw,cap,info)" |
| 2075 | 2087 | " VALUES('anonymous',hex(randomblob(8)),'hmnc','Anon');" |
| 2076 | 2088 | "INSERT OR IGNORE INTO user(login,pw,cap,info)" |
| 2077 | 2089 | " VALUES('nobody','','gjorz','Nobody');" |
| 2078 | 2090 | "INSERT OR IGNORE INTO user(login,pw,cap,info)" |
| 2079 | - " VALUES('developer','','dei','Dev');" | |
| 2091 | + " VALUES('developer','','ei','Dev');" | |
| 2080 | 2092 | "INSERT OR IGNORE INTO user(login,pw,cap,info)" |
| 2081 | 2093 | " VALUES('reader','','kptw','Reader');" |
| 2082 | 2094 | ); |
| 2083 | 2095 | } |
| 2084 | 2096 | } |
| 2085 | 2097 |
| --- 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 |
+11
| --- src/default_css.txt | ||
| +++ src/default_css.txt | ||
| @@ -741,10 +741,21 @@ | ||
| 741 | 741 | div.forumEdit { |
| 742 | 742 | border: 1px solid black; |
| 743 | 743 | padding-left: 1ex; |
| 744 | 744 | padding-right: 1ex; |
| 745 | 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 | +} | |
| 746 | 757 | div.forumHier, div.forumTime { |
| 747 | 758 | border: 1px solid black; |
| 748 | 759 | padding-left: 1ex; |
| 749 | 760 | padding-right: 1ex; |
| 750 | 761 | margin-top: 1ex; |
| 751 | 762 |
| --- 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 @@ | ||
| 1123 | 1123 | } |
| 1124 | 1124 | cgi_set_content_type(zMime); |
| 1125 | 1125 | cgi_set_content(&bgimg); |
| 1126 | 1126 | } |
| 1127 | 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 | 1128 | |
| 1181 | 1129 | /* |
| 1182 | 1130 | ** WEBPAGE: favicon.ico |
| 1183 | 1131 | ** |
| 1184 | 1132 | ** Return the default favicon.ico image. The returned image is for the |
| 1185 | 1133 |
| --- 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 @@ | ||
| 499 | 499 | while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){ |
| 500 | 500 | fwrite(zBuf, 1, got, out); |
| 501 | 501 | } |
| 502 | 502 | fclose(in); |
| 503 | 503 | fclose(out); |
| 504 | + if( file_isexe(zFrom, ExtFILE) ) file_setexe(zTo, 1); | |
| 504 | 505 | } |
| 505 | 506 | |
| 506 | 507 | /* |
| 507 | 508 | ** COMMAND: test-file-copy |
| 508 | 509 | ** |
| @@ -1785,11 +1786,11 @@ | ||
| 1785 | 1786 | rc = 1; |
| 1786 | 1787 | } |
| 1787 | 1788 | return rc; |
| 1788 | 1789 | #else |
| 1789 | 1790 | extern char **environ; |
| 1790 | - environ = 0; | |
| 1791 | + environ[0] = 0; | |
| 1791 | 1792 | return 0; |
| 1792 | 1793 | #endif |
| 1793 | 1794 | } |
| 1794 | 1795 | |
| 1795 | 1796 | /* |
| 1796 | 1797 |
| --- 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 @@ | ||
| 57 | 57 | ForumEntry *pDisplay; /* Entries in display order */ |
| 58 | 58 | ForumEntry *pTail; /* Last on the display list */ |
| 59 | 59 | int mxIndent; /* Maximum indentation level */ |
| 60 | 60 | }; |
| 61 | 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 | +} | |
| 62 | 79 | |
| 63 | 80 | /* |
| 64 | 81 | ** Delete a complete ForumThread and all its entries. |
| 65 | 82 | */ |
| 66 | 83 | static void forumthread_delete(ForumThread *pThread){ |
| @@ -199,17 +216,46 @@ | ||
| 199 | 216 | } |
| 200 | 217 | |
| 201 | 218 | /* Return the result */ |
| 202 | 219 | return pThread; |
| 203 | 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 | +} | |
| 204 | 247 | |
| 205 | 248 | /* |
| 206 | 249 | ** COMMAND: test-forumthread |
| 207 | 250 | ** |
| 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. | |
| 209 | 255 | ** |
| 210 | -** Display a summary of all messages on a thread. | |
| 256 | +** This command is intended for testing an analysis only. | |
| 211 | 257 | */ |
| 212 | 258 | void forumthread_cmd(void){ |
| 213 | 259 | int fpid; |
| 214 | 260 | int froot; |
| 215 | 261 | const char *zName; |
| @@ -216,10 +262,14 @@ | ||
| 216 | 262 | ForumThread *pThread; |
| 217 | 263 | ForumEntry *p; |
| 218 | 264 | |
| 219 | 265 | db_find_and_open_repository(0,0); |
| 220 | 266 | verify_all_options(); |
| 267 | + if( g.argc==2 ){ | |
| 268 | + forum_thread_list(); | |
| 269 | + return; | |
| 270 | + } | |
| 221 | 271 | if( g.argc!=3 ) usage("THREADID"); |
| 222 | 272 | zName = g.argv[2]; |
| 223 | 273 | fpid = symbolic_name_to_rid(zName, "f"); |
| 224 | 274 | if( fpid<=0 ){ |
| 225 | 275 | fpid = db_int(0, "SELECT rid FROM blob WHERE rid=%d", atoi(zName)); |
| @@ -302,23 +352,59 @@ | ||
| 302 | 352 | @ Trust user "%h(pPost->zUser)" |
| 303 | 353 | @ so that future posts by "%h(pPost->zUser)" do not require moderation. |
| 304 | 354 | @ </label> |
| 305 | 355 | @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)"> |
| 306 | 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 | + | |
| 307 | 390 | |
| 308 | 391 | /* |
| 309 | 392 | ** Display all posts in a forum thread in chronological order |
| 310 | 393 | */ |
| 311 | -static void forum_display_chronological(int froot, int target){ | |
| 394 | +static void forum_display_chronological(int froot, int target, int bRawMode){ | |
| 312 | 395 | ForumThread *pThread = forumthread_create(froot, 0); |
| 313 | 396 | ForumEntry *p; |
| 314 | 397 | int notAnon = login_is_individual(); |
| 398 | + char cMode = bRawMode ? 'r' : 'c'; | |
| 315 | 399 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 316 | 400 | char *zDate; |
| 317 | 401 | Manifest *pPost; |
| 318 | 402 | int isPrivate; /* True for posts awaiting moderation */ |
| 319 | 403 | int sameUser; /* True if author is also the reader */ |
| 404 | + const char *zUuid; | |
| 405 | + char *zDisplayName; /* The display name */ | |
| 320 | 406 | |
| 321 | 407 | pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 322 | 408 | if( pPost==0 ) continue; |
| 323 | 409 | if( p->fpid==target ){ |
| 324 | 410 | @ <div id="forum%d(p->fpid)" class="forumTime forumSel"> |
| @@ -329,14 +415,16 @@ | ||
| 329 | 415 | } |
| 330 | 416 | if( pPost->zThreadTitle ){ |
| 331 | 417 | @ <h1>%h(pPost->zThreadTitle)</h1> |
| 332 | 418 | } |
| 333 | 419 | 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); | |
| 335 | 423 | fossil_free(zDate); |
| 336 | 424 | 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))\ | |
| 338 | 426 | @ %d(p->pEdit->sid)</a> |
| 339 | 427 | } |
| 340 | 428 | if( g.perm.Debug ){ |
| 341 | 429 | @ <span class="debug">\ |
| 342 | 430 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span> |
| @@ -343,27 +431,33 @@ | ||
| 343 | 431 | } |
| 344 | 432 | if( p->firt ){ |
| 345 | 433 | ForumEntry *pIrt = p->pPrev; |
| 346 | 434 | while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev; |
| 347 | 435 | 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))\ | |
| 349 | 437 | @ %d(pIrt->sid)</a> |
| 350 | 438 | } |
| 351 | 439 | } |
| 440 | + zUuid = p->zUuid; | |
| 352 | 441 | 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))\ | |
| 354 | 443 | @ %d(p->pLeaf->sid)</a> |
| 444 | + zUuid = p->pLeaf->zUuid; | |
| 355 | 445 | } |
| 356 | 446 | 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> | |
| 358 | 451 | } |
| 359 | 452 | isPrivate = content_is_private(p->fpid); |
| 360 | 453 | sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 454 | + @ </h3> | |
| 361 | 455 | if( isPrivate && !g.perm.ModForum && !sameUser ){ |
| 362 | 456 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 363 | 457 | }else{ |
| 364 | - forum_render(0, pPost->zMimetype, pPost->zWiki, 0); | |
| 458 | + forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki, 0); | |
| 365 | 459 | } |
| 366 | 460 | if( g.perm.WrForum && p->pLeaf==0 ){ |
| 367 | 461 | int sameUser = login_is_individual() |
| 368 | 462 | && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 369 | 463 | @ <p><form action="%R/forumedit" method="POST"> |
| @@ -421,10 +515,11 @@ | ||
| 421 | 515 | iIndentScale--; |
| 422 | 516 | } |
| 423 | 517 | for(p=pThread->pDisplay; p; p=p->pDisplay){ |
| 424 | 518 | int isPrivate; /* True for posts awaiting moderation */ |
| 425 | 519 | int sameUser; /* True if reader is also the poster */ |
| 520 | + char *zDisplayName; /* User name to be displayed */ | |
| 426 | 521 | pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 427 | 522 | if( p->pLeaf ){ |
| 428 | 523 | fpid = p->pLeaf->fpid; |
| 429 | 524 | zUuid = p->pLeaf->zUuid; |
| 430 | 525 | pPost = manifest_get(fpid, CFTYPE_FORUM, 0); |
| @@ -444,11 +539,14 @@ | ||
| 444 | 539 | if( pPost==0 ) continue; |
| 445 | 540 | if( pPost->zThreadTitle ){ |
| 446 | 541 | @ <h1>%h(pPost->zThreadTitle)</h1> |
| 447 | 542 | } |
| 448 | 543 | 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); | |
| 450 | 548 | fossil_free(zDate); |
| 451 | 549 | if( g.perm.Debug ){ |
| 452 | 550 | @ <span class="debug">\ |
| 453 | 551 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact)</a></span> |
| 454 | 552 | } |
| @@ -467,18 +565,22 @@ | ||
| 467 | 565 | manifest_destroy(pOPost); |
| 468 | 566 | } |
| 469 | 567 | if( fpid!=target ){ |
| 470 | 568 | @ %z(href("%R/forumpost/%S",zUuid))[link]</a> |
| 471 | 569 | } |
| 570 | + if( fossil_strcmp(pPost->zMimetype,"text/plain")!=0 ){ | |
| 571 | + @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a> | |
| 572 | + } | |
| 472 | 573 | if( p->firt ){ |
| 473 | 574 | ForumEntry *pIrt = p->pPrev; |
| 474 | 575 | while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev; |
| 475 | 576 | if( pIrt ){ |
| 476 | 577 | @ in reply to %z(href("%R/forumpost/%S?t=h",pIrt->zUuid))\ |
| 477 | 578 | @ %d(pIrt->sid)</a> |
| 478 | 579 | } |
| 479 | 580 | } |
| 581 | + @ </h3> | |
| 480 | 582 | isPrivate = content_is_private(fpid); |
| 481 | 583 | sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0; |
| 482 | 584 | if( isPrivate && !g.perm.ModForum && !sameUser ){ |
| 483 | 585 | @ <p><span class="modpending">Awaiting Moderator Approval</span></p> |
| 484 | 586 | }else{ |
| @@ -524,12 +626,17 @@ | ||
| 524 | 626 | ** selected posting into view after the page loads. |
| 525 | 627 | ** |
| 526 | 628 | ** Query parameters: |
| 527 | 629 | ** |
| 528 | 630 | ** 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. | |
| 531 | 638 | */ |
| 532 | 639 | void forumpost_page(void){ |
| 533 | 640 | forumthread_page(); |
| 534 | 641 | } |
| 535 | 642 | |
| @@ -536,37 +643,21 @@ | ||
| 536 | 643 | /* |
| 537 | 644 | ** Add an appropriate style_header() to include title of the |
| 538 | 645 | ** given forum post. |
| 539 | 646 | */ |
| 540 | 647 | static int forumthread_page_header(int froot, int fpid){ |
| 541 | - Blob title; | |
| 542 | - int mxForumPostTitleLen = 50; | |
| 543 | - char *zThreadTitle = ""; | |
| 648 | + char *zThreadTitle = 0; | |
| 544 | 649 | |
| 545 | 650 | zThreadTitle = db_text("", |
| 546 | 651 | "SELECT" |
| 547 | 652 | " substr(event.comment,instr(event.comment,':')+2)" |
| 548 | 653 | " FROM forumpost, event" |
| 549 | 654 | " WHERE event.objid=forumpost.fpid" |
| 550 | 655 | " AND forumpost.fpid=%d;", |
| 551 | 656 | fpid |
| 552 | 657 | ); |
| 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"); | |
| 568 | 659 | fossil_free(zThreadTitle); |
| 569 | 660 | return 0; |
| 570 | 661 | } |
| 571 | 662 | |
| 572 | 663 | /* |
| @@ -577,18 +668,22 @@ | ||
| 577 | 668 | ** the postings in the thread are selected. |
| 578 | 669 | ** |
| 579 | 670 | ** Query parameters: |
| 580 | 671 | ** |
| 581 | 672 | ** 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. | |
| 584 | 678 | */ |
| 585 | 679 | void forumthread_page(void){ |
| 586 | 680 | int fpid; |
| 587 | 681 | int froot; |
| 588 | 682 | const char *zName = P("name"); |
| 589 | 683 | const char *zMode = PD("t","a"); |
| 684 | + int bRaw = PB("raw"); | |
| 590 | 685 | login_check_credentials(); |
| 591 | 686 | if( !g.perm.RdForum ){ |
| 592 | 687 | login_needed(g.anon.RdForum); |
| 593 | 688 | return; |
| 594 | 689 | } |
| @@ -610,15 +705,37 @@ | ||
| 610 | 705 | }else{ |
| 611 | 706 | zMode = "h"; |
| 612 | 707 | } |
| 613 | 708 | } |
| 614 | 709 | 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); | |
| 616 | 732 | 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); | |
| 618 | 734 | }else{ |
| 619 | 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); | |
| 620 | 737 | forum_display_hierarchical(froot, fpid); |
| 621 | 738 | } |
| 622 | 739 | style_load_js("forum.js"); |
| 623 | 740 | style_footer(); |
| 624 | 741 | } |
| @@ -972,41 +1089,44 @@ | ||
| 972 | 1089 | if( zMimetype==0 ) zMimetype = fossil_strdup(pPost->zMimetype); |
| 973 | 1090 | if( zTitle==0 && pPost->zThreadTitle!=0 ){ |
| 974 | 1091 | zTitle = fossil_strdup(pPost->zThreadTitle); |
| 975 | 1092 | } |
| 976 | 1093 | style_header("Edit %s", zTitle ? "Post" : "Reply"); |
| 977 | - @ <h1>Original Post:</h1> | |
| 1094 | + @ <h2>Original Post:</h2> | |
| 978 | 1095 | forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki, |
| 979 | 1096 | "forumEdit"); |
| 980 | 1097 | if( P("preview") ){ |
| 981 | - @ <h1>Preview of Edited Post:</h1> | |
| 1098 | + @ <h2>Preview of Edited Post:</h2> | |
| 982 | 1099 | forum_render(zTitle, zMimetype, zContent,"forumEdit"); |
| 983 | 1100 | } |
| 984 | - @ <h1>Revised Message:</h1> | |
| 1101 | + @ <h2>Revised Message:</h2> | |
| 985 | 1102 | @ <form action="%R/forume2" method="POST"> |
| 986 | 1103 | @ <input type="hidden" name="fpid" value="%h(P("fpid"))"> |
| 987 | 1104 | @ <input type="hidden" name="edit" value="1"> |
| 988 | 1105 | forum_from_line(); |
| 989 | 1106 | forum_entry_widget(zTitle, zMimetype, zContent); |
| 990 | 1107 | }else{ |
| 991 | 1108 | /* Reply */ |
| 1109 | + char *zDisplayName; | |
| 992 | 1110 | zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE); |
| 993 | 1111 | zContent = PDT("content",""); |
| 994 | 1112 | style_header("Reply"); |
| 995 | - @ <h1>Replying To:</h1> | |
| 996 | 1113 | if( pRootPost->zThreadTitle ){ |
| 997 | - @ <h3>%h(pRootPost->zThreadTitle)</h3> | |
| 1114 | + @ <h1>Thread: %h(pRootPost->zThreadTitle)</h1> | |
| 998 | 1115 | } |
| 1116 | + @ <h2>Replying To:</h2> | |
| 999 | 1117 | 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); | |
| 1001 | 1121 | fossil_free(zDate); |
| 1002 | 1122 | forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit"); |
| 1003 | 1123 | if( P("preview") ){ |
| 1004 | - @ <h1>Preview:</h1> | |
| 1124 | + @ <h2>Preview:</h2> | |
| 1005 | 1125 | forum_render(0, zMimetype,zContent, "forumEdit"); |
| 1006 | 1126 | } |
| 1007 | - @ <h1>Enter Reply:</h1> | |
| 1127 | + @ <h2>Enter Reply:</h2> | |
| 1008 | 1128 | @ <form action="%R/forume2" method="POST"> |
| 1009 | 1129 | @ <input type="hidden" name="fpid" value="%h(P("fpid"))"> |
| 1010 | 1130 | @ <input type="hidden" name="reply" value="1"> |
| 1011 | 1131 | forum_from_line(); |
| 1012 | 1132 | forum_entry_widget(0, zMimetype, zContent); |
| @@ -1032,10 +1152,11 @@ | ||
| 1032 | 1152 | @ </form> |
| 1033 | 1153 | style_footer(); |
| 1034 | 1154 | } |
| 1035 | 1155 | |
| 1036 | 1156 | /* |
| 1157 | +** WEBPAGE: forummain | |
| 1037 | 1158 | ** WEBPAGE: forum |
| 1038 | 1159 | ** |
| 1039 | 1160 | ** The main page for the forum feature. Show a list of recent forum |
| 1040 | 1161 | ** threads. Also show a search box at the top if search is enabled, |
| 1041 | 1162 | ** and a button for creating a new thread, if enabled. |
| 1042 | 1163 |
| --- 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 @@ | ||
| 484 | 484 | ** --compress Use ZLIB compression on the payload |
| 485 | 485 | ** --mimetype TYPE Mimetype of the payload |
| 486 | 486 | ** --out FILE Store the reply in FILE |
| 487 | 487 | ** -v Verbose output |
| 488 | 488 | */ |
| 489 | -void test_wget_command(void){ | |
| 489 | +void test_httpmsg_command(void){ | |
| 490 | 490 | const char *zMimetype; |
| 491 | 491 | const char *zInFile; |
| 492 | 492 | const char *zOutFile; |
| 493 | 493 | Blob in, out; |
| 494 | 494 | unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; |
| 495 | 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_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 @@ | ||
| 2477 | 2477 | @ </blockquote> |
| 2478 | 2478 | } |
| 2479 | 2479 | |
| 2480 | 2480 | @ <div class="section">Changes</div> |
| 2481 | 2481 | @ <p> |
| 2482 | - ticket_output_change_artifact(pTktChng, 0); | |
| 2482 | + ticket_output_change_artifact(pTktChng, 0, 1); | |
| 2483 | 2483 | manifest_destroy(pTktChng); |
| 2484 | 2484 | style_footer(); |
| 2485 | 2485 | } |
| 2486 | 2486 | |
| 2487 | 2487 | |
| 2488 | 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); |
| 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 @@ | ||
| 1898 | 1898 | obj = cson_value_get_object(sub); |
| 1899 | 1899 | |
| 1900 | 1900 | #define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X)) |
| 1901 | 1901 | ADD(Setup,"setup"); |
| 1902 | 1902 | ADD(Admin,"admin"); |
| 1903 | - ADD(Delete,"delete"); | |
| 1904 | 1903 | ADD(Password,"password"); |
| 1905 | 1904 | ADD(Query,"query"); /* don't think this one is actually used */ |
| 1906 | 1905 | ADD(Write,"checkin"); |
| 1907 | 1906 | ADD(Read,"checkout"); |
| 1908 | 1907 | ADD(Hyperlink,"history"); |
| 1909 | 1908 |
| --- 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 @@ | ||
| 111 | 111 | }else{ |
| 112 | 112 | fossil_redirect_home(); |
| 113 | 113 | } |
| 114 | 114 | } |
| 115 | 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 | 116 | /* |
| 139 | 117 | ** Return an abbreviated project code. The abbreviation is the first |
| 140 | 118 | ** 16 characters of the project code. |
| 141 | 119 | ** |
| 142 | 120 | ** Memory is obtained from malloc. |
| @@ -295,30 +273,27 @@ | ||
| 295 | 273 | const char *zExpire = db_get("cookie-expire","8766"); |
| 296 | 274 | int expires = atoi(zExpire)*3600; |
| 297 | 275 | char *zHash; |
| 298 | 276 | char *zCookie; |
| 299 | 277 | const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */ |
| 300 | - char *zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */ | |
| 301 | 278 | |
| 302 | 279 | assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data."); |
| 303 | 280 | zHash = db_text(0, |
| 304 | 281 | "SELECT cookie FROM user" |
| 305 | 282 | " WHERE uid=%d" |
| 306 | - " AND ipaddr=%Q" | |
| 307 | 283 | " AND cexpire>julianday('now')" |
| 308 | 284 | " AND length(cookie)>30", |
| 309 | - uid, zRemoteAddr); | |
| 285 | + uid); | |
| 310 | 286 | if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))"); |
| 311 | 287 | zCookie = login_gen_user_cookie_value(zUsername, zHash); |
| 312 | 288 | cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); |
| 313 | 289 | record_login_attempt(zUsername, zIpAddr, 1); |
| 314 | 290 | db_multi_exec( |
| 315 | - "UPDATE user SET cookie=%Q, ipaddr=%Q, " | |
| 291 | + "UPDATE user SET cookie=%Q," | |
| 316 | 292 | " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", |
| 317 | - zHash, zRemoteAddr, expires, uid | |
| 293 | + zHash, expires, uid | |
| 318 | 294 | ); |
| 319 | - free(zRemoteAddr); | |
| 320 | 295 | free(zHash); |
| 321 | 296 | if( zDest ){ |
| 322 | 297 | *zDest = zCookie; |
| 323 | 298 | }else{ |
| 324 | 299 | free(zCookie); |
| @@ -327,34 +302,25 @@ | ||
| 327 | 302 | |
| 328 | 303 | /* Sets a cookie for an anonymous user login, which looks like this: |
| 329 | 304 | ** |
| 330 | 305 | ** HASH/TIME/anonymous |
| 331 | 306 | ** |
| 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. | |
| 337 | 308 | ** |
| 338 | 309 | ** If zCookieDest is not NULL then the generated cookie is assigned to |
| 339 | 310 | ** *zCookieDest and the caller must eventually free() it. |
| 340 | 311 | */ |
| 341 | 312 | void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest ){ |
| 342 | 313 | const char *zNow; /* Current time (julian day number) */ |
| 343 | 314 | char *zCookie; /* The login cookie */ |
| 344 | 315 | const char *zCookieName; /* Name of the login cookie */ |
| 345 | 316 | 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 | 317 | zCookieName = login_cookie_name(); |
| 352 | 318 | zNow = db_text("0", "SELECT julianday('now')"); |
| 353 | - assert( zCookieName && zRemoteAddr && zIpAddr && zNow ); | |
| 319 | + assert( zCookieName && zNow ); | |
| 354 | 320 | 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","")); | |
| 356 | 322 | sha1sum_blob(&b, &b); |
| 357 | 323 | zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); |
| 358 | 324 | blob_reset(&b); |
| 359 | 325 | cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600); |
| 360 | 326 | if( zCookieDest ){ |
| @@ -702,10 +668,18 @@ | ||
| 702 | 668 | if( g.zLogin ){ |
| 703 | 669 | @ <p>Currently logged in as <b>%h(g.zLogin)</b>. |
| 704 | 670 | @ <input type="submit" name="out" value="Logout"></p> |
| 705 | 671 | @ </form> |
| 706 | 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 | + } | |
| 707 | 681 | @ <table class="login_out"> |
| 708 | 682 | @ <tr> |
| 709 | 683 | @ <td class="form_label">User ID:</td> |
| 710 | 684 | if( anonFlag ){ |
| 711 | 685 | @ <td><input type="text" id="u" name="u" value="anonymous" size="30"></td> |
| @@ -713,11 +687,15 @@ | ||
| 713 | 687 | @ <td><input type="text" id="u" name="u" value="" size="30" /></td> |
| 714 | 688 | } |
| 715 | 689 | @ </tr> |
| 716 | 690 | @ <tr> |
| 717 | 691 | @ <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> | |
| 719 | 697 | @ </tr> |
| 720 | 698 | if( P("HTTPS")==0 ){ |
| 721 | 699 | @ <tr><td class="form_label">Warning:</td> |
| 722 | 700 | @ <td><span class='securityWarning'> |
| 723 | 701 | @ Your password will be sent in the clear over an |
| @@ -728,15 +706,10 @@ | ||
| 728 | 706 | @ Consider logging in at |
| 729 | 707 | @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead. |
| 730 | 708 | } |
| 731 | 709 | @ </span></td></tr> |
| 732 | 710 | } |
| 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 | 711 | @ <tr> |
| 739 | 712 | @ <td></td> |
| 740 | 713 | @ <td><input type="submit" name="in" value="Login"></td> |
| 741 | 714 | @ </tr> |
| 742 | 715 | if( !noAnon && login_self_register_available(0) ){ |
| @@ -745,11 +718,10 @@ | ||
| 745 | 718 | @ <td><input type="submit" name="self" value="Create A New Account"> |
| 746 | 719 | @ </tr> |
| 747 | 720 | } |
| 748 | 721 | @ </table> |
| 749 | 722 | if( zAnonPw && !noAnon ){ |
| 750 | - unsigned int uSeed = captcha_seed(); | |
| 751 | 723 | const char *zDecoded = captcha_decode(uSeed); |
| 752 | 724 | int bAutoCaptcha = db_get_boolean("auto-captcha", 0); |
| 753 | 725 | char *zCaptcha = captcha_render(zDecoded); |
| 754 | 726 | |
| 755 | 727 | @ <p><input type="hidden" name="cs" value="%u(uSeed)" /> |
| @@ -803,12 +775,11 @@ | ||
| 803 | 775 | ** Return true if a transfer was made and false if not. |
| 804 | 776 | */ |
| 805 | 777 | static int login_transfer_credentials( |
| 806 | 778 | const char *zLogin, /* Login we are looking for */ |
| 807 | 779 | 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 */ | |
| 810 | 781 | ){ |
| 811 | 782 | sqlite3 *pOther = 0; /* The other repository */ |
| 812 | 783 | sqlite3_stmt *pStmt; /* Query against the other repository */ |
| 813 | 784 | char *zSQL; /* SQL of the query against other repo */ |
| 814 | 785 | char *zOtherRepo; /* Filename of the other repository */ |
| @@ -832,24 +803,23 @@ | ||
| 832 | 803 | constant_time_cmp_function, 0, 0); |
| 833 | 804 | sqlite3_busy_timeout(pOther, 5000); |
| 834 | 805 | zSQL = mprintf( |
| 835 | 806 | "SELECT cexpire FROM user" |
| 836 | 807 | " WHERE login=%Q" |
| 837 | - " AND ipaddr=%Q" | |
| 838 | 808 | " AND length(cap)>0" |
| 839 | 809 | " AND length(pw)>0" |
| 840 | 810 | " AND cexpire>julianday('now')" |
| 841 | 811 | " AND constant_time_cmp(cookie,%Q)=0", |
| 842 | - zLogin, zRemoteAddr, zHash | |
| 812 | + zLogin, zHash | |
| 843 | 813 | ); |
| 844 | 814 | pStmt = 0; |
| 845 | 815 | rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0); |
| 846 | 816 | if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 847 | 817 | db_multi_exec( |
| 848 | - "UPDATE user SET cookie=%Q, ipaddr=%Q, cexpire=%.17g" | |
| 818 | + "UPDATE user SET cookie=%Q, cexpire=%.17g" | |
| 849 | 819 | " WHERE login=%Q", |
| 850 | - zHash, zRemoteAddr, | |
| 820 | + zHash, | |
| 851 | 821 | sqlite3_column_double(pStmt, 0), zLogin |
| 852 | 822 | ); |
| 853 | 823 | nXfer++; |
| 854 | 824 | } |
| 855 | 825 | sqlite3_finalize(pStmt); |
| @@ -869,33 +839,30 @@ | ||
| 869 | 839 | if( fossil_strcmp(zLogin, "reader")==0 ) return 1; |
| 870 | 840 | return 0; |
| 871 | 841 | } |
| 872 | 842 | |
| 873 | 843 | /* |
| 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. | |
| 876 | 846 | ** |
| 877 | 847 | ** 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. | |
| 880 | 849 | */ |
| 881 | 850 | static int login_find_user( |
| 882 | 851 | 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 */ | |
| 885 | 853 | ){ |
| 886 | 854 | int uid; |
| 887 | 855 | if( login_is_special(zLogin) ) return 0; |
| 888 | 856 | uid = db_int(0, |
| 889 | 857 | "SELECT uid FROM user" |
| 890 | 858 | " WHERE login=%Q" |
| 891 | - " AND ipaddr=%Q" | |
| 892 | 859 | " AND cexpire>julianday('now')" |
| 893 | 860 | " AND length(cap)>0" |
| 894 | 861 | " AND length(pw)>0" |
| 895 | 862 | " AND constant_time_cmp(cookie,%Q)=0", |
| 896 | - zLogin, zRemoteAddr, zCookie | |
| 863 | + zLogin, zCookie | |
| 897 | 864 | ); |
| 898 | 865 | return uid; |
| 899 | 866 | } |
| 900 | 867 | |
| 901 | 868 | /* |
| @@ -964,11 +931,10 @@ | ||
| 964 | 931 | */ |
| 965 | 932 | void login_check_credentials(void){ |
| 966 | 933 | int uid = 0; /* User id */ |
| 967 | 934 | const char *zCookie; /* Text of the login cookie */ |
| 968 | 935 | const char *zIpAddr; /* Raw IP address of the requestor */ |
| 969 | - char *zRemoteAddr; /* Abbreviated IP address of the requestor */ | |
| 970 | 936 | const char *zCap = 0; /* Capability string */ |
| 971 | 937 | const char *zPublicPages = 0; /* GLOB patterns of public pages */ |
| 972 | 938 | const char *zLogin = 0; /* Login user for credentials */ |
| 973 | 939 | |
| 974 | 940 | /* Only run this check once. */ |
| @@ -982,11 +948,11 @@ | ||
| 982 | 948 | ** then there is no need to check user credentials. |
| 983 | 949 | ** |
| 984 | 950 | ** This feature allows the "fossil ui" command to give the user |
| 985 | 951 | ** full access rights without having to log in. |
| 986 | 952 | */ |
| 987 | - zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil")); | |
| 953 | + zIpAddr = PD("REMOTE_ADDR","nil"); | |
| 988 | 954 | if( ( cgi_is_loopback(zIpAddr) |
| 989 | 955 | || (g.fSshClient & CGI_SSH_CLIENT)!=0 ) |
| 990 | 956 | && g.useLocalauth |
| 991 | 957 | && db_get_int("localauth",0)==0 |
| 992 | 958 | && P("HTTPS")==0 |
| @@ -1031,12 +997,11 @@ | ||
| 1031 | 997 | ** SECRET is the "captcha-secret" value in the repository. |
| 1032 | 998 | */ |
| 1033 | 999 | double rTime = atof(zArg); |
| 1034 | 1000 | Blob b; |
| 1035 | 1001 | 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","")); | |
| 1038 | 1003 | sha1sum_blob(&b, &b); |
| 1039 | 1004 | if( fossil_strcmp(zHash, blob_str(&b))==0 ){ |
| 1040 | 1005 | uid = db_int(0, |
| 1041 | 1006 | "SELECT uid FROM user WHERE login='anonymous'" |
| 1042 | 1007 | " AND length(cap)>0" |
| @@ -1049,13 +1014,13 @@ | ||
| 1049 | 1014 | }else{ |
| 1050 | 1015 | /* Cookies of the form "HASH/CODE/USER". Search first in the |
| 1051 | 1016 | ** local user table, then the user table for project CODE if we |
| 1052 | 1017 | ** are part of a login-group. |
| 1053 | 1018 | */ |
| 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); | |
| 1057 | 1022 | if( uid ) record_login_attempt(zUser, zIpAddr, 1); |
| 1058 | 1023 | } |
| 1059 | 1024 | } |
| 1060 | 1025 | sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash); |
| 1061 | 1026 | } |
| @@ -1227,20 +1192,19 @@ | ||
| 1227 | 1192 | case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip = |
| 1228 | 1193 | p->RdWiki = p->WrWiki = p->NewWiki = |
| 1229 | 1194 | p->ApndWiki = p->Hyperlink = p->Clone = |
| 1230 | 1195 | p->NewTkt = p->Password = p->RdAddr = |
| 1231 | 1196 | p->TktFmt = p->Attach = p->ApndTkt = |
| 1232 | - p->ModWiki = p->ModTkt = p->Delete = | |
| 1197 | + p->ModWiki = p->ModTkt = | |
| 1233 | 1198 | p->RdForum = p->WrForum = p->ModForum = |
| 1234 | 1199 | p->WrTForum = p->AdminForum = |
| 1235 | 1200 | p->EmailAlert = p->Announce = p->Debug = 1; |
| 1236 | 1201 | /* Fall thru into Read/Write */ |
| 1237 | 1202 | case 'i': p->Read = p->Write = 1; break; |
| 1238 | 1203 | case 'o': p->Read = 1; break; |
| 1239 | 1204 | case 'z': p->Zip = 1; break; |
| 1240 | 1205 | |
| 1241 | - case 'd': p->Delete = 1; break; | |
| 1242 | 1206 | case 'h': p->Hyperlink = 1; break; |
| 1243 | 1207 | case 'g': p->Clone = 1; break; |
| 1244 | 1208 | case 'p': p->Password = 1; break; |
| 1245 | 1209 | |
| 1246 | 1210 | case 'j': p->RdWiki = 1; break; |
| @@ -1320,11 +1284,11 @@ | ||
| 1320 | 1284 | for(i=0; i<nCap && rc && zCap[i]; i++){ |
| 1321 | 1285 | switch( zCap[i] ){ |
| 1322 | 1286 | case 'a': rc = p->Admin; break; |
| 1323 | 1287 | case 'b': rc = p->Attach; break; |
| 1324 | 1288 | case 'c': rc = p->ApndTkt; break; |
| 1325 | - case 'd': rc = p->Delete; break; | |
| 1289 | + /* d unused: see comment in capabilities.c */ | |
| 1326 | 1290 | case 'e': rc = p->RdAddr; break; |
| 1327 | 1291 | case 'f': rc = p->NewWiki; break; |
| 1328 | 1292 | case 'g': rc = p->Clone; break; |
| 1329 | 1293 | case 'h': rc = p->Hyperlink; break; |
| 1330 | 1294 | case 'i': rc = p->Write; break; |
| @@ -1480,10 +1444,25 @@ | ||
| 1480 | 1444 | g.okCsrf = 1; |
| 1481 | 1445 | return; |
| 1482 | 1446 | } |
| 1483 | 1447 | fossil_fatal("Cross-site request forgery attempt"); |
| 1484 | 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 | +} | |
| 1485 | 1464 | |
| 1486 | 1465 | /* |
| 1487 | 1466 | ** WEBPAGE: register |
| 1488 | 1467 | ** |
| 1489 | 1468 | ** Page to allow users to self-register. The "self-register" setting |
| @@ -1527,32 +1506,32 @@ | ||
| 1527 | 1506 | /* This is not a valid form submission. Fall through into |
| 1528 | 1507 | ** the form display */ |
| 1529 | 1508 | }else if( !captcha_is_correct(1) ){ |
| 1530 | 1509 | iErrLine = 6; |
| 1531 | 1510 | zErr = "Incorrect CAPTCHA"; |
| 1532 | - }else if( strlen(zUserID)<3 ){ | |
| 1511 | + }else if( strlen(zUserID)<6 ){ | |
| 1533 | 1512 | 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."; | |
| 1535 | 1514 | }else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){ |
| 1536 | 1515 | iErrLine = 1; |
| 1537 | 1516 | zErr = "User ID may not contain spaces or special characters."; |
| 1538 | 1517 | }else if( zDName[0]==0 ){ |
| 1539 | 1518 | iErrLine = 2; |
| 1540 | 1519 | zErr = "Required"; |
| 1541 | 1520 | }else if( zEAddr[0]==0 ){ |
| 1542 | 1521 | iErrLine = 3; |
| 1543 | 1522 | zErr = "Required"; |
| 1544 | - }else if( email_copy_addr(zEAddr,0)==0 ){ | |
| 1523 | + }else if( email_address_is_valid(zEAddr,0)==0 ){ | |
| 1545 | 1524 | iErrLine = 3; |
| 1546 | 1525 | zErr = "Not a valid email address"; |
| 1547 | 1526 | }else if( strlen(zPasswd)<6 ){ |
| 1548 | 1527 | iErrLine = 4; |
| 1549 | 1528 | zErr = "Password must be at least 6 characters long"; |
| 1550 | 1529 | }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){ |
| 1551 | 1530 | iErrLine = 5; |
| 1552 | 1531 | 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) ){ | |
| 1554 | 1533 | iErrLine = 1; |
| 1555 | 1534 | zErr = "This User ID is already taken. Choose something different."; |
| 1556 | 1535 | }else if( |
| 1557 | 1536 | /* If the email is found anywhere in USER.INFO... */ |
| 1558 | 1537 | db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr) |
| @@ -1710,11 +1689,13 @@ | ||
| 1710 | 1689 | if( iErrLine==5 ){ |
| 1711 | 1690 | @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
| 1712 | 1691 | } |
| 1713 | 1692 | @ <tr> |
| 1714 | 1693 | @ <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> | |
| 1716 | 1697 | @ </tr> |
| 1717 | 1698 | if( iErrLine==6 ){ |
| 1718 | 1699 | @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
| 1719 | 1700 | } |
| 1720 | 1701 | @ <tr><td></td> |
| 1721 | 1702 |
| --- 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'>↑ %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'>↑ %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'>↑ %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'>↑ %h(zErr)</span></td></tr> |
| 1700 | } |
| 1701 | @ <tr><td></td> |
| 1702 |
+1
-2
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -78,11 +78,10 @@ | ||
| 78 | 78 | ** Holds flags for fossil user permissions. |
| 79 | 79 | */ |
| 80 | 80 | struct FossilUserPerms { |
| 81 | 81 | char Setup; /* s: use Setup screens on web interface */ |
| 82 | 82 | char Admin; /* a: administrative permission */ |
| 83 | - char Delete; /* d: delete wiki or tickets */ | |
| 84 | 83 | char Password; /* p: change password */ |
| 85 | 84 | char Query; /* q: create new reports */ |
| 86 | 85 | char Write; /* i: xfer inbound. check-in */ |
| 87 | 86 | char Read; /* o: xfer outbound. check-out */ |
| 88 | 87 | char Hyperlink; /* h: enable the display of hyperlinks */ |
| @@ -1687,11 +1686,11 @@ | ||
| 1687 | 1686 | @ <meta name="viewport" \ |
| 1688 | 1687 | @ content="width=device-width, initial-scale=1.0"> |
| 1689 | 1688 | @ </head><body> |
| 1690 | 1689 | @ <h1>Not Found</h1> |
| 1691 | 1690 | @ </body> |
| 1692 | - cgi_set_status(404, "not found"); | |
| 1691 | + cgi_set_status(404, "Not Found"); | |
| 1693 | 1692 | cgi_reply(); |
| 1694 | 1693 | } |
| 1695 | 1694 | return; |
| 1696 | 1695 | } |
| 1697 | 1696 | break; |
| 1698 | 1697 |
| --- 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 @@ | ||
| 224 | 224 | $(SRCDIR)/menu.js \ |
| 225 | 225 | $(SRCDIR)/sbsdiff.js \ |
| 226 | 226 | $(SRCDIR)/scroll.js \ |
| 227 | 227 | $(SRCDIR)/skin.js \ |
| 228 | 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 \ | |
| 229 | 245 | $(SRCDIR)/tree.js \ |
| 230 | 246 | $(SRCDIR)/useredit.js \ |
| 231 | 247 | $(SRCDIR)/wiki.wiki |
| 232 | 248 | |
| 233 | 249 | TRANS_SRC = \ |
| 234 | 250 |
| --- 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 |
+3
-2
| --- src/makemake.tcl | ||
| +++ src/makemake.tcl | ||
| @@ -174,10 +174,11 @@ | ||
| 174 | 174 | diff.tcl |
| 175 | 175 | markdown.md |
| 176 | 176 | wiki.wiki |
| 177 | 177 | *.js |
| 178 | 178 | ../skins/*/*.txt |
| 179 | + sounds/*.wav | |
| 179 | 180 | } |
| 180 | 181 | |
| 181 | 182 | # Options used to compile the included SQLite library. |
| 182 | 183 | # |
| 183 | 184 | set SQLITE_OPTIONS { |
| @@ -712,11 +713,11 @@ | ||
| 712 | 713 | #### The directories where the OpenSSL include and library files are located. |
| 713 | 714 | # The recommended usage here is to use the Sysinternals junction tool |
| 714 | 715 | # to create a hard link between an "openssl-1.x" sub-directory of the |
| 715 | 716 | # Fossil source code directory and the target OpenSSL source directory. |
| 716 | 717 | # |
| 717 | -OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d | |
| 718 | +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e | |
| 718 | 719 | OPENSSLINCDIR = $(OPENSSLDIR)/include |
| 719 | 720 | OPENSSLLIBDIR = $(OPENSSLDIR) |
| 720 | 721 | |
| 721 | 722 | #### Either the directory where the Tcl library is installed or the Tcl |
| 722 | 723 | # source code directory resides (depending on the value of the macro |
| @@ -1569,11 +1570,11 @@ | ||
| 1569 | 1570 | !ifndef USE_SEE |
| 1570 | 1571 | USE_SEE = 0 |
| 1571 | 1572 | !endif |
| 1572 | 1573 | |
| 1573 | 1574 | !if $(FOSSIL_ENABLE_SSL)!=0 |
| 1574 | -SSLDIR = $(B)\compat\openssl-1.1.1d | |
| 1575 | +SSLDIR = $(B)\compat\openssl-1.1.1e | |
| 1575 | 1576 | SSLINCDIR = $(SSLDIR)\include |
| 1576 | 1577 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 |
| 1577 | 1578 | SSLLIBDIR = $(SSLDIR) |
| 1578 | 1579 | !else |
| 1579 | 1580 | SSLLIBDIR = $(SSLDIR) |
| 1580 | 1581 |
| --- 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 @@ | ||
| 26 | 26 | ** WEBPAGE: timeline.rss |
| 27 | 27 | ** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME |
| 28 | 28 | ** |
| 29 | 29 | ** Produce an RSS feed of the timeline. |
| 30 | 30 | ** |
| 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). | |
| 33 | 34 | ** |
| 34 | 35 | ** LIMIT is the number of items to show. |
| 35 | 36 | ** |
| 36 | 37 | ** tkt=UUID filters for only those events for the specified ticket. tag=TAG |
| 37 | 38 | ** filters for a tag, and wiki=NAME for a wiki page. Only one may be used. |
| 38 | 39 |
| --- 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 |
+13
-8
| --- src/security_audit.c | ||
| +++ src/security_audit.c | ||
| @@ -94,10 +94,12 @@ | ||
| 94 | 94 | ** accessed using the Admin/Security-Audit menu option |
| 95 | 95 | ** from any of the default skins. |
| 96 | 96 | */ |
| 97 | 97 | void secaudit0_page(void){ |
| 98 | 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" */ | |
| 99 | 101 | const char *zPubPages; /* GLOB pattern for public pages */ |
| 100 | 102 | const char *zSelfCap; /* Capabilities of self-registered users */ |
| 101 | 103 | int hasSelfReg = 0; /* True if able to self-register */ |
| 102 | 104 | char *z; |
| 103 | 105 | int n; |
| @@ -116,10 +118,12 @@ | ||
| 116 | 118 | ** means that any anonymous user on the internet can access all content. |
| 117 | 119 | ** "Private" repos require (non-anonymous) login to access all content, |
| 118 | 120 | ** though some content may be accessible anonymously. |
| 119 | 121 | */ |
| 120 | 122 | zAnonCap = db_text("", "SELECT fullcap(NULL)"); |
| 123 | + zDevCap = db_text("", "SELECT fullcap('v')"); | |
| 124 | + zReadCap = db_text("", "SELECT fullcap('u')"); | |
| 121 | 125 | zPubPages = db_get("public-pages",0); |
| 122 | 126 | hasSelfReg = db_get_boolean("self-register",0); |
| 123 | 127 | pCap = capability_add(0, db_get("default-perms",0)); |
| 124 | 128 | capability_expand(pCap); |
| 125 | 129 | zSelfCap = capability_string(pCap); |
| @@ -278,19 +282,20 @@ | ||
| 278 | 282 | @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5") |
| 279 | 283 | @ from users "anonymous" and "nobody" |
| 280 | 284 | @ on the <a href="setup_ulist">User Configuration</a> page. |
| 281 | 285 | } |
| 282 | 286 | |
| 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") ){ | |
| 287 | 291 | @ <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. | |
| 292 | 297 | } |
| 293 | 298 | |
| 294 | 299 | /* If anonymous users are allowed to create new Wiki, then |
| 295 | 300 | ** wiki moderation should be activated to pervent spam. |
| 296 | 301 | */ |
| 297 | 302 |
| --- 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 |
-9
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -402,19 +402,10 @@ | ||
| 402 | 402 | @ variable or the "Authentication:" HTTP header to find the username and |
| 403 | 403 | @ password. This is another way of supporting Basic Authenitication. |
| 404 | 404 | @ (Property: "http_authentication_ok") |
| 405 | 405 | @ </p> |
| 406 | 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 | 407 | @ <hr /> |
| 417 | 408 | entry_attribute("Login expiration time", 6, "cookie-expire", "cex", |
| 418 | 409 | "8766", 0); |
| 419 | 410 | @ <p>The number of hours for which a login is valid. This must be a |
| 420 | 411 | @ positive number. The default is 8766 hours which is approximately equal |
| 421 | 412 |
| --- 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 @@ | ||
| 35 | 35 | */ |
| 36 | 36 | void setup_ulist(void){ |
| 37 | 37 | Stmt s; |
| 38 | 38 | double rNow; |
| 39 | 39 | const char *zWith = P("with"); |
| 40 | + int bUnusedOnly = P("unused")!=0; | |
| 40 | 41 | |
| 41 | 42 | login_check_credentials(); |
| 42 | 43 | if( !g.perm.Admin ){ |
| 43 | 44 | login_needed(0); |
| 44 | 45 | return; |
| @@ -45,12 +46,15 @@ | ||
| 45 | 46 | } |
| 46 | 47 | |
| 47 | 48 | style_submenu_element("Add", "setup_uedit"); |
| 48 | 49 | style_submenu_element("Log", "access_log"); |
| 49 | 50 | style_submenu_element("Help", "setup_ulist_notes"); |
| 51 | + if( alert_tables_exist() ){ | |
| 52 | + style_submenu_element("Subscribers", "subscribers"); | |
| 53 | + } | |
| 50 | 54 | style_header("User List"); |
| 51 | - if( zWith==0 || zWith[0]==0 ){ | |
| 55 | + if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){ | |
| 52 | 56 | @ <table border=1 cellpadding=2 cellspacing=0 class='userTable'> |
| 53 | 57 | @ <thead><tr> |
| 54 | 58 | @ <th>Category |
| 55 | 59 | @ <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>) |
| 56 | 60 | @ <th>Info <th>Last Change</tr></thead> |
| @@ -91,16 +95,23 @@ | ||
| 91 | 95 | db_finalize(&s); |
| 92 | 96 | @ </tbody></table> |
| 93 | 97 | @ <div class='section'>Users</div> |
| 94 | 98 | }else{ |
| 95 | 99 | 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 | + } | |
| 100 | 108 | } |
| 101 | 109 | } |
| 110 | + if( !bUnusedOnly ){ | |
| 111 | + style_submenu_element("Unused", "setup_ulist?unused"); | |
| 112 | + } | |
| 102 | 113 | @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \ |
| 103 | 114 | @ data-column-types='ktxTTK' data-init-sort='2'> |
| 104 | 115 | @ <thead><tr> |
| 105 | 116 | @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead> |
| 106 | 117 | @ <tbody> |
| @@ -117,11 +128,19 @@ | ||
| 117 | 128 | " SELECT login AS uname, rcvfrom.mtime AS mtime" |
| 118 | 129 | " FROM rcvfrom JOIN user USING(uid))" |
| 119 | 130 | " GROUP BY 1;" |
| 120 | 131 | ); |
| 121 | 132 | } |
| 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] ){ | |
| 123 | 142 | zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith); |
| 124 | 143 | }else{ |
| 125 | 144 | zWith = ""; |
| 126 | 145 | } |
| 127 | 146 | db_prepare(&s, |
| @@ -517,22 +536,33 @@ | ||
| 517 | 536 | @ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))"> |
| 518 | 537 | @ <table width="100%%"> |
| 519 | 538 | @ <tr> |
| 520 | 539 | @ <td class="usetupEditLabel">User ID:</td> |
| 521 | 540 | 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> | |
| 523 | 543 | }else{ |
| 524 | 544 | @ <td>(new user)<input type="hidden" name="id" value="0" /></td> |
| 525 | 545 | } |
| 526 | 546 | @ </tr> |
| 527 | 547 | @ <tr> |
| 528 | 548 | @ <td class="usetupEditLabel">Login:</td> |
| 529 | 549 | if( login_is_special(zLogin) ){ |
| 530 | 550 | @ <td><b>%h(zLogin)</b></td> |
| 531 | 551 | }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 | + @ <a href="%R/alerts/%s(zSCode)">\ | |
| 559 | + @ (subscription info for %h(zLogin))</a>\ | |
| 560 | + } | |
| 561 | + fossil_free(zSCode); | |
| 562 | + } | |
| 563 | + @ </td></tr> | |
| 534 | 564 | @ <tr> |
| 535 | 565 | @ <td class="usetupEditLabel">Contact Info:</td> |
| 536 | 566 | @ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td> |
| 537 | 567 | } |
| 538 | 568 | @ </tr> |
| @@ -550,12 +580,14 @@ | ||
| 550 | 580 | @ Admin%s(B('a'))</label> |
| 551 | 581 | @ <li><label><input type="checkbox" name="au"%s(oa['u']) /> |
| 552 | 582 | @ Reader%s(B('u'))</label> |
| 553 | 583 | @ <li><label><input type="checkbox" name="av"%s(oa['v']) /> |
| 554 | 584 | @ Developer%s(B('v'))</label> |
| 585 | +#if 0 /* Not Used */ | |
| 555 | 586 | @ <li><label><input type="checkbox" name="ad"%s(oa['d']) /> |
| 556 | 587 | @ Delete%s(B('d'))</label> |
| 588 | +#endif | |
| 557 | 589 | @ <li><label><input type="checkbox" name="ae"%s(oa['e']) /> |
| 558 | 590 | @ View-PII%s(B('e'))</label> |
| 559 | 591 | @ <li><label><input type="checkbox" name="ap"%s(oa['p']) /> |
| 560 | 592 | @ Password%s(B('p'))</label> |
| 561 | 593 | @ <li><label><input type="checkbox" name="ai"%s(oa['i']) /> |
| @@ -625,14 +657,16 @@ | ||
| 625 | 657 | if( !login_is_special(zLogin) ){ |
| 626 | 658 | @ <tr> |
| 627 | 659 | @ <td align="right">Password:</td> |
| 628 | 660 | if( zPw[0] ){ |
| 629 | 661 | /* 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> | |
| 631 | 664 | }else{ |
| 632 | 665 | /* 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> | |
| 634 | 668 | } |
| 635 | 669 | @ </tr> |
| 636 | 670 | } |
| 637 | 671 | zGroup = login_group_name(); |
| 638 | 672 | if( zGroup ){ |
| 639 | 673 |
| --- 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 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 | @ <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 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 @@ | ||
| 413 | 413 | /* |
| 414 | 414 | ** True if an interrupt (Control-C) has been received. |
| 415 | 415 | */ |
| 416 | 416 | static volatile int seenInterrupt = 0; |
| 417 | 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 | + | |
| 418 | 427 | /* |
| 419 | 428 | ** This is the name of our program. It is set in main(), used |
| 420 | 429 | ** in a number of other places, mostly for error messages. |
| 421 | 430 | */ |
| 422 | 431 | static char *Argv0; |
| @@ -463,10 +472,53 @@ | ||
| 463 | 472 | /* Indicate out-of-memory and exit. */ |
| 464 | 473 | static void shell_out_of_memory(void){ |
| 465 | 474 | raw_printf(stderr,"Error: out of memory\n"); |
| 466 | 475 | exit(1); |
| 467 | 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 | |
| 468 | 520 | |
| 469 | 521 | /* |
| 470 | 522 | ** Write I/O traces to the following stream. |
| 471 | 523 | */ |
| 472 | 524 | #ifdef SQLITE_ENABLE_IOTRACE |
| @@ -12086,10 +12138,22 @@ | ||
| 12086 | 12138 | " Run \".filectrl\" with no arguments for details", |
| 12087 | 12139 | ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", |
| 12088 | 12140 | ".headers on|off Turn display of headers on or off", |
| 12089 | 12141 | ".help ?-all? ?PATTERN? Show help text for PATTERN", |
| 12090 | 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.", | |
| 12091 | 12155 | #ifndef SQLITE_OMIT_TEST_CONTROL |
| 12092 | 12156 | ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", |
| 12093 | 12157 | #endif |
| 12094 | 12158 | ".indexes ?TABLE? Show names of indexes", |
| 12095 | 12159 | " If TABLE is specified, only show indexes for", |
| @@ -12121,10 +12185,13 @@ | ||
| 12121 | 12185 | ".once (-e|-x|FILE) Output for the next SQL command only to FILE", |
| 12122 | 12186 | " If FILE begins with '|' then open as a pipe", |
| 12123 | 12187 | " Other options:", |
| 12124 | 12188 | " -e Invoke system text editor", |
| 12125 | 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 | |
| 12126 | 12193 | ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE", |
| 12127 | 12194 | " Options:", |
| 12128 | 12195 | " --append Use appendvfs to append database to the end of FILE", |
| 12129 | 12196 | #ifdef SQLITE_ENABLE_DESERIALIZE |
| 12130 | 12197 | " --deserialize Load into memory useing sqlite3_deserialize()", |
| @@ -13071,10 +13138,12 @@ | ||
| 13071 | 13138 | FILE *in; /* Read the CSV text from this input stream */ |
| 13072 | 13139 | char *z; /* Accumulated text for a field */ |
| 13073 | 13140 | int n; /* Number of bytes in z */ |
| 13074 | 13141 | int nAlloc; /* Space allocated for z[] */ |
| 13075 | 13142 | int nLine; /* Current line number */ |
| 13143 | + int nRow; /* Number of rows imported */ | |
| 13144 | + int nErr; /* Number of errors encountered */ | |
| 13076 | 13145 | int bNotFirst; /* True if one or more bytes already read */ |
| 13077 | 13146 | int cTerm; /* Character that terminated the most recent field */ |
| 13078 | 13147 | int cColSep; /* The column separator character. (Usually ",") */ |
| 13079 | 13148 | int cRowSep; /* The row separator character. (Usually "\n") */ |
| 13080 | 13149 | }; |
| @@ -16131,12 +16200,12 @@ | ||
| 16131 | 16200 | showHelp(p->out, 0); |
| 16132 | 16201 | } |
| 16133 | 16202 | }else |
| 16134 | 16203 | |
| 16135 | 16204 | 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 */ | |
| 16138 | 16207 | sqlite3_stmt *pStmt = NULL; /* A statement */ |
| 16139 | 16208 | int nCol; /* Number of columns in the table */ |
| 16140 | 16209 | int nByte; /* Number of bytes in an SQL string */ |
| 16141 | 16210 | int i, j; /* Loop counters */ |
| 16142 | 16211 | int needCommit; /* True to COMMIT or ROLLBACK at end */ |
| @@ -16143,75 +16212,141 @@ | ||
| 16143 | 16212 | int nSep; /* Number of bytes in p->colSeparator[] */ |
| 16144 | 16213 | char *zSql; /* An SQL statement */ |
| 16145 | 16214 | ImportCtx sCtx; /* Reader context */ |
| 16146 | 16215 | char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ |
| 16147 | 16216 | 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 | + | |
| 16156 | 16221 | 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]; | |
| 16186 | 16311 | } |
| 16187 | 16312 | sCtx.zFile = zFile; |
| 16188 | 16313 | sCtx.nLine = 1; |
| 16189 | 16314 | if( sCtx.zFile[0]=='|' ){ |
| 16190 | 16315 | #ifdef SQLITE_OMIT_POPEN |
| 16191 | 16316 | raw_printf(stderr, "Error: pipes are not supported in this OS\n"); |
| 16192 | - return 1; | |
| 16317 | + rc = 1; | |
| 16318 | + goto meta_command_exit; | |
| 16193 | 16319 | #else |
| 16194 | 16320 | sCtx.in = popen(sCtx.zFile+1, "r"); |
| 16195 | 16321 | sCtx.zFile = "<pipe>"; |
| 16196 | 16322 | xCloser = pclose; |
| 16197 | 16323 | #endif |
| 16198 | 16324 | }else{ |
| 16199 | 16325 | sCtx.in = fopen(sCtx.zFile, "rb"); |
| 16200 | 16326 | xCloser = fclose; |
| 16201 | 16327 | } |
| 16202 | - if( p->mode==MODE_Ascii ){ | |
| 16203 | - xRead = ascii_read_one_field; | |
| 16204 | - }else{ | |
| 16205 | - xRead = csv_read_one_field; | |
| 16206 | - } | |
| 16207 | 16328 | if( sCtx.in==0 ){ |
| 16208 | 16329 | utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); |
| 16209 | - return 1; | |
| 16330 | + rc = 1; | |
| 16331 | + goto meta_command_exit; | |
| 16210 | 16332 | } |
| 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 | + } | |
| 16213 | 16348 | zSql = sqlite3_mprintf("SELECT * FROM %s", zTable); |
| 16214 | 16349 | if( zSql==0 ){ |
| 16215 | 16350 | xCloser(sCtx.in); |
| 16216 | 16351 | shell_out_of_memory(); |
| 16217 | 16352 | } |
| @@ -16229,30 +16364,36 @@ | ||
| 16229 | 16364 | if( cSep=='(' ){ |
| 16230 | 16365 | sqlite3_free(zCreate); |
| 16231 | 16366 | sqlite3_free(sCtx.z); |
| 16232 | 16367 | xCloser(sCtx.in); |
| 16233 | 16368 | utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); |
| 16234 | - return 1; | |
| 16369 | + rc = 1; | |
| 16370 | + goto meta_command_exit; | |
| 16235 | 16371 | } |
| 16236 | 16372 | zCreate = sqlite3_mprintf("%z\n)", zCreate); |
| 16373 | + if( eVerbose>=1 ){ | |
| 16374 | + utf8_printf(p->out, "%s\n", zCreate); | |
| 16375 | + } | |
| 16237 | 16376 | rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); |
| 16238 | 16377 | sqlite3_free(zCreate); |
| 16239 | 16378 | if( rc ){ |
| 16240 | 16379 | utf8_printf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable, |
| 16241 | 16380 | sqlite3_errmsg(p->db)); |
| 16242 | 16381 | sqlite3_free(sCtx.z); |
| 16243 | 16382 | xCloser(sCtx.in); |
| 16244 | - return 1; | |
| 16383 | + rc = 1; | |
| 16384 | + goto meta_command_exit; | |
| 16245 | 16385 | } |
| 16246 | 16386 | rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); |
| 16247 | 16387 | } |
| 16248 | 16388 | sqlite3_free(zSql); |
| 16249 | 16389 | if( rc ){ |
| 16250 | 16390 | if (pStmt) sqlite3_finalize(pStmt); |
| 16251 | 16391 | utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); |
| 16252 | 16392 | xCloser(sCtx.in); |
| 16253 | - return 1; | |
| 16393 | + rc = 1; | |
| 16394 | + goto meta_command_exit; | |
| 16254 | 16395 | } |
| 16255 | 16396 | nCol = sqlite3_column_count(pStmt); |
| 16256 | 16397 | sqlite3_finalize(pStmt); |
| 16257 | 16398 | pStmt = 0; |
| 16258 | 16399 | if( nCol==0 ) return 0; /* no columns, no error */ |
| @@ -16267,17 +16408,21 @@ | ||
| 16267 | 16408 | zSql[j++] = ','; |
| 16268 | 16409 | zSql[j++] = '?'; |
| 16269 | 16410 | } |
| 16270 | 16411 | zSql[j++] = ')'; |
| 16271 | 16412 | zSql[j] = 0; |
| 16413 | + if( eVerbose>=2 ){ | |
| 16414 | + utf8_printf(p->out, "Insert using: %s\n", zSql); | |
| 16415 | + } | |
| 16272 | 16416 | rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); |
| 16273 | 16417 | sqlite3_free(zSql); |
| 16274 | 16418 | if( rc ){ |
| 16275 | 16419 | utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); |
| 16276 | 16420 | if (pStmt) sqlite3_finalize(pStmt); |
| 16277 | 16421 | xCloser(sCtx.in); |
| 16278 | - return 1; | |
| 16422 | + rc = 1; | |
| 16423 | + goto meta_command_exit; | |
| 16279 | 16424 | } |
| 16280 | 16425 | needCommit = sqlite3_get_autocommit(p->db); |
| 16281 | 16426 | if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); |
| 16282 | 16427 | do{ |
| 16283 | 16428 | int startLine = sCtx.nLine; |
| @@ -16316,18 +16461,26 @@ | ||
| 16316 | 16461 | sqlite3_step(pStmt); |
| 16317 | 16462 | rc = sqlite3_reset(pStmt); |
| 16318 | 16463 | if( rc!=SQLITE_OK ){ |
| 16319 | 16464 | utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, |
| 16320 | 16465 | startLine, sqlite3_errmsg(p->db)); |
| 16466 | + sCtx.nErr++; | |
| 16467 | + }else{ | |
| 16468 | + sCtx.nRow++; | |
| 16321 | 16469 | } |
| 16322 | 16470 | } |
| 16323 | 16471 | }while( sCtx.cTerm!=EOF ); |
| 16324 | 16472 | |
| 16325 | 16473 | xCloser(sCtx.in); |
| 16326 | 16474 | sqlite3_free(sCtx.z); |
| 16327 | 16475 | sqlite3_finalize(pStmt); |
| 16328 | 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 | + } | |
| 16329 | 16482 | }else |
| 16330 | 16483 | |
| 16331 | 16484 | #ifndef SQLITE_UNTESTABLE |
| 16332 | 16485 | if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){ |
| 16333 | 16486 | char *zSql; |
| @@ -16602,10 +16755,38 @@ | ||
| 16602 | 16755 | raw_printf(stderr, "Usage: .nullvalue STRING\n"); |
| 16603 | 16756 | rc = 1; |
| 16604 | 16757 | } |
| 16605 | 16758 | }else |
| 16606 | 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 | + | |
| 16607 | 16788 | if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ |
| 16608 | 16789 | char *zNewFilename; /* Name of the database file to open */ |
| 16609 | 16790 | int iName = 1; /* Index in azArg[] of the filename */ |
| 16610 | 16791 | int newFlag = 0; /* True to delete file before opening */ |
| 16611 | 16792 | /* Close the existing database */ |
| @@ -18684,10 +18865,14 @@ | ||
| 18684 | 18865 | |
| 18685 | 18866 | setBinaryMode(stdin, 0); |
| 18686 | 18867 | setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ |
| 18687 | 18868 | stdin_is_interactive = isatty(0); |
| 18688 | 18869 | stdout_is_console = isatty(1); |
| 18870 | + | |
| 18871 | +#ifdef SQLITE_DEBUG | |
| 18872 | + registerOomSimulator(); | |
| 18873 | +#endif | |
| 18689 | 18874 | |
| 18690 | 18875 | #if !defined(_WIN32_WCE) |
| 18691 | 18876 | if( getenv("SQLITE_DEBUG_BREAK") ){ |
| 18692 | 18877 | if( isatty(0) && isatty(2) ){ |
| 18693 | 18878 | fprintf(stderr, |
| 18694 | 18879 | |
| 18695 | 18880 | ADDED src/sounds/0.wav |
| 18696 | 18881 | ADDED src/sounds/1.wav |
| 18697 | 18882 | ADDED src/sounds/2.wav |
| 18698 | 18883 | ADDED src/sounds/3.wav |
| 18699 | 18884 | ADDED src/sounds/4.wav |
| 18700 | 18885 | ADDED src/sounds/5.wav |
| 18701 | 18886 | ADDED src/sounds/6.wav |
| 18702 | 18887 | ADDED src/sounds/7.wav |
| 18703 | 18888 | ADDED src/sounds/8.wav |
| 18704 | 18889 | ADDED src/sounds/9.wav |
| 18705 | 18890 | ADDED src/sounds/README.md |
| 18706 | 18891 | ADDED src/sounds/a.wav |
| 18707 | 18892 | ADDED src/sounds/b.wav |
| 18708 | 18893 | ADDED src/sounds/c.wav |
| 18709 | 18894 | ADDED src/sounds/d.wav |
| 18710 | 18895 | ADDED src/sounds/e.wav |
| 18711 | 18896 | 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
+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 |
| --- 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 @@ | ||
| 124 | 124 | }else{ |
| 125 | 125 | sqlite3_free(pOut); |
| 126 | 126 | sqlite3_result_error(context, "input is not zlib compressed", -1); |
| 127 | 127 | } |
| 128 | 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 | +} | |
| 129 | 142 | |
| 130 | 143 | /* |
| 131 | 144 | ** Add the content(), compress(), and decompress() SQL functions to |
| 132 | 145 | ** database connection db. |
| 133 | 146 | */ |
| @@ -136,10 +149,12 @@ | ||
| 136 | 149 | sqlcmd_content, 0, 0); |
| 137 | 150 | sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0, |
| 138 | 151 | sqlcmd_compress, 0, 0); |
| 139 | 152 | sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0, |
| 140 | 153 | sqlcmd_decompress, 0, 0); |
| 154 | + sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0, | |
| 155 | + sqlcmd_gather_artifact_stats, 0, 0); | |
| 141 | 156 | return SQLITE_OK; |
| 142 | 157 | } |
| 143 | 158 | |
| 144 | 159 | /* |
| 145 | 160 | ** This is the "automatic extension" initializer that runs right after |
| 146 | 161 |
| --- 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 @@ | ||
| 1162 | 1162 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 1163 | 1163 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 1164 | 1164 | */ |
| 1165 | 1165 | #define SQLITE_VERSION "3.32.0" |
| 1166 | 1166 | #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" | |
| 1168 | 1168 | |
| 1169 | 1169 | /* |
| 1170 | 1170 | ** CAPI3REF: Run-Time Library Version Numbers |
| 1171 | 1171 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 1172 | 1172 | ** |
| @@ -16539,11 +16539,10 @@ | ||
| 16539 | 16539 | ** have been filled out. If the schema changes, these column names might |
| 16540 | 16540 | ** changes and so the view will need to be reset. |
| 16541 | 16541 | */ |
| 16542 | 16542 | #define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ |
| 16543 | 16543 | #define DB_UnresetViews 0x0002 /* Some views have defined column names */ |
| 16544 | -#define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */ | |
| 16545 | 16544 | #define DB_ResetWanted 0x0008 /* Reset the schema when nSchemaLock==0 */ |
| 16546 | 16545 | |
| 16547 | 16546 | /* |
| 16548 | 16547 | ** The number of different kinds of things that can be limited |
| 16549 | 16548 | ** using the sqlite3_limit() interface. |
| @@ -16697,11 +16696,11 @@ | ||
| 16697 | 16696 | ** Each database connection is an instance of the following structure. |
| 16698 | 16697 | */ |
| 16699 | 16698 | struct sqlite3 { |
| 16700 | 16699 | sqlite3_vfs *pVfs; /* OS Interface */ |
| 16701 | 16700 | 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 */ | |
| 16703 | 16702 | sqlite3_mutex *mutex; /* Connection mutex */ |
| 16704 | 16703 | Db *aDb; /* All backends */ |
| 16705 | 16704 | int nDb; /* Number of backends currently in use */ |
| 16706 | 16705 | u32 mDbFlags; /* flags recording internal state */ |
| 16707 | 16706 | u64 flags; /* flags settable by pragmas. See below */ |
| @@ -16906,10 +16905,11 @@ | ||
| 16906 | 16905 | #define DBFLAG_PreferBuiltin 0x0002 /* Preference to built-in funcs */ |
| 16907 | 16906 | #define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */ |
| 16908 | 16907 | #define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */ |
| 16909 | 16908 | #define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */ |
| 16910 | 16909 | #define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */ |
| 16910 | +#define DBFLAG_EncodingFixed 0x0040 /* No longer possible to change enc. */ | |
| 16911 | 16911 | |
| 16912 | 16912 | /* |
| 16913 | 16913 | ** Bits of the sqlite3.dbOptFlags field that are used by the |
| 16914 | 16914 | ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to |
| 16915 | 16915 | ** selectively disable various optimizations. |
| @@ -17869,10 +17869,13 @@ | ||
| 17869 | 17869 | char affExpr; /* affinity, or RAISE type */ |
| 17870 | 17870 | u8 op2; /* TK_REGISTER/TK_TRUTH: original value of Expr.op |
| 17871 | 17871 | ** TK_COLUMN: the value of p5 for OP_Column |
| 17872 | 17872 | ** TK_AGG_FUNCTION: nesting depth |
| 17873 | 17873 | ** TK_FUNCTION: NC_SelfRef flag if needs OP_PureFunc */ |
| 17874 | +#ifdef SQLITE_DEBUG | |
| 17875 | + u8 vvaFlags; /* Verification flags. */ | |
| 17876 | +#endif | |
| 17874 | 17877 | u32 flags; /* Various flags. EP_* See below */ |
| 17875 | 17878 | union { |
| 17876 | 17879 | char *zToken; /* Token value. Zero terminated and dequoted */ |
| 17877 | 17880 | int iValue; /* Non-negative integer value if EP_IntValue */ |
| 17878 | 17881 | } u; |
| @@ -17943,11 +17946,11 @@ | ||
| 17943 | 17946 | #define EP_Skip 0x001000 /* Operator does not contribute to affinity */ |
| 17944 | 17947 | #define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ |
| 17945 | 17948 | #define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ |
| 17946 | 17949 | #define EP_Win 0x008000 /* Contains window functions */ |
| 17947 | 17950 | #define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */ |
| 17948 | -#define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */ | |
| 17951 | + /* 0x020000 // available for reuse */ | |
| 17949 | 17952 | #define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */ |
| 17950 | 17953 | #define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ |
| 17951 | 17954 | #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ |
| 17952 | 17955 | #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ |
| 17953 | 17956 | #define EP_Alias 0x400000 /* Is an alias for a result set column */ |
| @@ -17957,10 +17960,11 @@ | ||
| 17957 | 17960 | #define EP_Quoted 0x4000000 /* TK_ID was originally quoted */ |
| 17958 | 17961 | #define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */ |
| 17959 | 17962 | #define EP_IsTrue 0x10000000 /* Always has boolean value of TRUE */ |
| 17960 | 17963 | #define EP_IsFalse 0x20000000 /* Always has boolean value of FALSE */ |
| 17961 | 17964 | #define EP_FromDDL 0x40000000 /* Originates from sqlite_master */ |
| 17965 | + /* 0x80000000 // Available */ | |
| 17962 | 17966 | |
| 17963 | 17967 | /* |
| 17964 | 17968 | ** The EP_Propagate mask is a set of properties that automatically propagate |
| 17965 | 17969 | ** upwards into parent nodes. |
| 17966 | 17970 | */ |
| @@ -17975,18 +17979,28 @@ | ||
| 17975 | 17979 | #define ExprSetProperty(E,P) (E)->flags|=(P) |
| 17976 | 17980 | #define ExprClearProperty(E,P) (E)->flags&=~(P) |
| 17977 | 17981 | #define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue) |
| 17978 | 17982 | #define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse) |
| 17979 | 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 | + | |
| 17980 | 17990 | /* The ExprSetVVAProperty() macro is used for Verification, Validation, |
| 17981 | 17991 | ** and Accreditation only. It works like ExprSetProperty() during VVA |
| 17982 | 17992 | ** processes but is a no-op for delivery. |
| 17983 | 17993 | */ |
| 17984 | 17994 | #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 | |
| 17986 | 17998 | #else |
| 17987 | 17999 | # define ExprSetVVAProperty(E,P) |
| 18000 | +# define ExprHasVVAProperty(E,P) 0 | |
| 18001 | +# define ExprClearVVAProperties(E) | |
| 17988 | 18002 | #endif |
| 17989 | 18003 | |
| 17990 | 18004 | /* |
| 17991 | 18005 | ** Macros to determine the number of bytes required by a normal Expr |
| 17992 | 18006 | ** struct, an Expr struct with the EP_Reduced flag set in Expr.flags |
| @@ -18956,10 +18970,11 @@ | ||
| 18956 | 18970 | Select *pSelect; /* HAVING to WHERE clause ctx */ |
| 18957 | 18971 | struct WindowRewrite *pRewrite; /* Window rewrite context */ |
| 18958 | 18972 | struct WhereConst *pConst; /* WHERE clause constants */ |
| 18959 | 18973 | struct RenameCtx *pRename; /* RENAME COLUMN context */ |
| 18960 | 18974 | struct Table *pTab; /* Table of generated column */ |
| 18975 | + struct SrcList_item *pSrcItem; /* A single FROM clause item */ | |
| 18961 | 18976 | } u; |
| 18962 | 18977 | }; |
| 18963 | 18978 | |
| 18964 | 18979 | /* Forward declarations */ |
| 18965 | 18980 | SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*); |
| @@ -19500,11 +19515,11 @@ | ||
| 19500 | 19515 | #ifndef SQLITE_OMIT_GENERATED_COLUMNS |
| 19501 | 19516 | SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Column*, int); |
| 19502 | 19517 | #endif |
| 19503 | 19518 | SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); |
| 19504 | 19519 | SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); |
| 19505 | -SQLITE_PRIVATE int sqlite3ExprCodeAtInit(Parse*, Expr*, int); | |
| 19520 | +SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int); | |
| 19506 | 19521 | SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); |
| 19507 | 19522 | SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); |
| 19508 | 19523 | SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); |
| 19509 | 19524 | #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ |
| 19510 | 19525 | #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ |
| @@ -19655,10 +19670,11 @@ | ||
| 19655 | 19670 | # define sqlite3AuthRead(a,b,c,d) |
| 19656 | 19671 | # define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK |
| 19657 | 19672 | # define sqlite3AuthContextPush(a,b,c) |
| 19658 | 19673 | # define sqlite3AuthContextPop(a) ((void)(a)) |
| 19659 | 19674 | #endif |
| 19675 | +SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName); | |
| 19660 | 19676 | SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*); |
| 19661 | 19677 | SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*); |
| 19662 | 19678 | SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*); |
| 19663 | 19679 | SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*); |
| 19664 | 19680 | SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*); |
| @@ -19714,14 +19730,14 @@ | ||
| 19714 | 19730 | #define putVarint sqlite3PutVarint |
| 19715 | 19731 | |
| 19716 | 19732 | |
| 19717 | 19733 | SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*); |
| 19718 | 19734 | 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); | |
| 19721 | 19737 | SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int); |
| 19722 | -SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr); | |
| 19738 | +SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr); | |
| 19723 | 19739 | SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8); |
| 19724 | 19740 | SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*); |
| 19725 | 19741 | SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); |
| 19726 | 19742 | SQLITE_PRIVATE void sqlite3Error(sqlite3*,int); |
| 19727 | 19743 | SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int); |
| @@ -19740,13 +19756,14 @@ | ||
| 19740 | 19756 | SQLITE_PRIVATE const char *sqlite3ErrStr(int); |
| 19741 | 19757 | SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse); |
| 19742 | 19758 | SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int); |
| 19743 | 19759 | SQLITE_PRIVATE int sqlite3IsBinary(const CollSeq*); |
| 19744 | 19760 | 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*); | |
| 19748 | 19765 | SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int); |
| 19749 | 19766 | SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*); |
| 19750 | 19767 | SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*); |
| 19751 | 19768 | SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*); |
| 19752 | 19769 | SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *); |
| @@ -19809,10 +19826,11 @@ | ||
| 19809 | 19826 | const struct ExprList_item*, |
| 19810 | 19827 | const char*, |
| 19811 | 19828 | const char*, |
| 19812 | 19829 | const char* |
| 19813 | 19830 | ); |
| 19831 | +SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr*); | |
| 19814 | 19832 | SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*); |
| 19815 | 19833 | SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*); |
| 19816 | 19834 | SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*); |
| 19817 | 19835 | SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*); |
| 19818 | 19836 | SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*); |
| @@ -19973,12 +19991,12 @@ | ||
| 19973 | 19991 | #ifdef SQLITE_ENABLE_NORMALIZE |
| 19974 | 19992 | SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*); |
| 19975 | 19993 | #endif |
| 19976 | 19994 | SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*); |
| 19977 | 19995 | 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*); | |
| 19980 | 19998 | SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*); |
| 19981 | 19999 | SQLITE_PRIVATE const char *sqlite3JournalModename(int); |
| 19982 | 20000 | #ifndef SQLITE_OMIT_WAL |
| 19983 | 20001 | SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*); |
| 19984 | 20002 | SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); |
| @@ -20947,13 +20965,13 @@ | ||
| 20947 | 20965 | #endif |
| 20948 | 20966 | u16 nResColumn; /* Number of columns in one row of the result set */ |
| 20949 | 20967 | u8 errorAction; /* Recovery action to do in case of an error */ |
| 20950 | 20968 | u8 minWriteFileFormat; /* Minimum file format for writable database files */ |
| 20951 | 20969 | u8 prepFlags; /* SQLITE_PREPARE_* flags */ |
| 20970 | + u8 doingRerun; /* True if rerunning after an auto-reprepare */ | |
| 20952 | 20971 | bft expired:2; /* 1: recompile VM immediately 2: when convenient */ |
| 20953 | 20972 | bft explain:2; /* True if EXPLAIN present on SQL command */ |
| 20954 | - bft doingRerun:1; /* True if rerunning after an auto-reprepare */ | |
| 20955 | 20973 | bft changeCntOn:1; /* True to update the change-counter */ |
| 20956 | 20974 | bft runOnlyOnce:1; /* Automatically expire on reset */ |
| 20957 | 20975 | bft usesStmtJournal:1; /* True if uses a statement journal */ |
| 20958 | 20976 | bft readOnly:1; /* True for statements that do not write */ |
| 20959 | 20977 | bft bIsReader:1; /* True for statements that read */ |
| @@ -29390,12 +29408,12 @@ | ||
| 29390 | 29408 | sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName); |
| 29391 | 29409 | }else if( pItem->zName ){ |
| 29392 | 29410 | sqlite3_str_appendf(&x, " %s", pItem->zName); |
| 29393 | 29411 | } |
| 29394 | 29412 | 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); | |
| 29397 | 29415 | } |
| 29398 | 29416 | if( pItem->zAlias ){ |
| 29399 | 29417 | sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias); |
| 29400 | 29418 | } |
| 29401 | 29419 | if( pItem->fg.jointype & JT_LEFT ){ |
| @@ -29650,27 +29668,30 @@ | ||
| 29650 | 29668 | ** Generate a human-readable explanation of an expression tree. |
| 29651 | 29669 | */ |
| 29652 | 29670 | SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ |
| 29653 | 29671 | const char *zBinOp = 0; /* Binary operator */ |
| 29654 | 29672 | const char *zUniOp = 0; /* Unary operator */ |
| 29655 | - char zFlgs[60]; | |
| 29673 | + char zFlgs[200]; | |
| 29656 | 29674 | pView = sqlite3TreeViewPush(pView, moreToFollow); |
| 29657 | 29675 | if( pExpr==0 ){ |
| 29658 | 29676 | sqlite3TreeViewLine(pView, "nil"); |
| 29659 | 29677 | sqlite3TreeViewPop(pView); |
| 29660 | 29678 | return; |
| 29661 | 29679 | } |
| 29662 | - if( pExpr->flags || pExpr->affExpr ){ | |
| 29680 | + if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags ){ | |
| 29663 | 29681 | StrAccum x; |
| 29664 | 29682 | sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0); |
| 29665 | 29683 | sqlite3_str_appendf(&x, " fg.af=%x.%c", |
| 29666 | 29684 | pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n'); |
| 29667 | 29685 | if( ExprHasProperty(pExpr, EP_FromJoin) ){ |
| 29668 | 29686 | sqlite3_str_appendf(&x, " iRJT=%d", pExpr->iRightJoinTable); |
| 29669 | 29687 | } |
| 29670 | 29688 | if( ExprHasProperty(pExpr, EP_FromDDL) ){ |
| 29671 | 29689 | sqlite3_str_appendf(&x, " DDL"); |
| 29690 | + } | |
| 29691 | + if( ExprHasVVAProperty(pExpr, EP_Immutable) ){ | |
| 29692 | + sqlite3_str_appendf(&x, " IMMUTABLE"); | |
| 29672 | 29693 | } |
| 29673 | 29694 | sqlite3StrAccumFinish(&x); |
| 29674 | 29695 | }else{ |
| 29675 | 29696 | zFlgs[0] = 0; |
| 29676 | 29697 | } |
| @@ -29774,10 +29795,11 @@ | ||
| 29774 | 29795 | case TK_SLASH: zBinOp = "DIV"; break; |
| 29775 | 29796 | case TK_LSHIFT: zBinOp = "LSHIFT"; break; |
| 29776 | 29797 | case TK_RSHIFT: zBinOp = "RSHIFT"; break; |
| 29777 | 29798 | case TK_CONCAT: zBinOp = "CONCAT"; break; |
| 29778 | 29799 | case TK_DOT: zBinOp = "DOT"; break; |
| 29800 | + case TK_LIMIT: zBinOp = "LIMIT"; break; | |
| 29779 | 29801 | |
| 29780 | 29802 | case TK_UMINUS: zUniOp = "UMINUS"; break; |
| 29781 | 29803 | case TK_UPLUS: zUniOp = "UPLUS"; break; |
| 29782 | 29804 | case TK_BITNOT: zUniOp = "BITNOT"; break; |
| 29783 | 29805 | case TK_NOT: zUniOp = "NOT"; break; |
| @@ -50895,11 +50917,11 @@ | ||
| 50895 | 50917 | } |
| 50896 | 50918 | |
| 50897 | 50919 | /* |
| 50898 | 50920 | ** Allocate a new RowSetEntry object that is associated with the |
| 50899 | 50921 | ** given RowSet. Return a pointer to the new and completely uninitialized |
| 50900 | -** objected. | |
| 50922 | +** object. | |
| 50901 | 50923 | ** |
| 50902 | 50924 | ** In an OOM situation, the RowSet.db->mallocFailed flag is set and this |
| 50903 | 50925 | ** routine returns NULL. |
| 50904 | 50926 | */ |
| 50905 | 50927 | static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){ |
| @@ -51171,11 +51193,11 @@ | ||
| 51171 | 51193 | if( iBatch!=pRowSet->iBatch ){ /*OPTIMIZATION-IF-FALSE*/ |
| 51172 | 51194 | p = pRowSet->pEntry; |
| 51173 | 51195 | if( p ){ |
| 51174 | 51196 | struct RowSetEntry **ppPrevTree = &pRowSet->pForest; |
| 51175 | 51197 | 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 */ | |
| 51177 | 51199 | p = rowSetEntrySort(p); |
| 51178 | 51200 | } |
| 51179 | 51201 | for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){ |
| 51180 | 51202 | ppPrevTree = &pTree->pRight; |
| 51181 | 51203 | if( pTree->pLeft==0 ){ |
| @@ -64536,11 +64558,11 @@ | ||
| 64536 | 64558 | ** free-list for reuse. It returns false if it is safe to retrieve the |
| 64537 | 64559 | ** page from the pager layer with the 'no-content' flag set. True otherwise. |
| 64538 | 64560 | */ |
| 64539 | 64561 | static int btreeGetHasContent(BtShared *pBt, Pgno pgno){ |
| 64540 | 64562 | Bitvec *p = pBt->pHasContent; |
| 64541 | - return (p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTest(p, pgno))); | |
| 64563 | + return p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTestNotNull(p, pgno)); | |
| 64542 | 64564 | } |
| 64543 | 64565 | |
| 64544 | 64566 | /* |
| 64545 | 64567 | ** Clear (destroy) the BtShared.pHasContent bitvec. This should be |
| 64546 | 64568 | ** invoked at the conclusion of each write-transaction. |
| @@ -76180,23 +76202,18 @@ | ||
| 76180 | 76202 | u16 mFlags; |
| 76181 | 76203 | if( pVdbe->db->flags & SQLITE_VdbeTrace ){ |
| 76182 | 76204 | sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n", |
| 76183 | 76205 | (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem)); |
| 76184 | 76206 | } |
| 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 | |
| 76186 | 76208 | ** no significant changes have been made to pX since the OP_SCopy. |
| 76187 | 76209 | ** A significant change would indicated a missed call to this |
| 76188 | 76210 | ** function for pX. Minor changes, such as adding or removing a |
| 76189 | 76211 | ** dual type, are allowed, as long as the underlying value is the |
| 76190 | 76212 | ** same. */ |
| 76191 | 76213 | mFlags = pMem->flags & pX->flags & pX->mScopyFlags; |
| 76192 | 76214 | 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 | 76215 | |
| 76199 | 76216 | /* pMem is the register that is changing. But also mark pX as |
| 76200 | 76217 | ** undefined so that we can quickly detect the shallow-copy error */ |
| 76201 | 76218 | pX->flags = MEM_Undefined; |
| 76202 | 76219 | pX->pScopyFrom = 0; |
| @@ -77547,11 +77564,11 @@ | ||
| 77547 | 77564 | (void)z2; |
| 77548 | 77565 | } |
| 77549 | 77566 | #endif |
| 77550 | 77567 | |
| 77551 | 77568 | /* |
| 77552 | -** Add a new OP_ opcode. | |
| 77569 | +** Add a new OP_Explain opcode. | |
| 77553 | 77570 | ** |
| 77554 | 77571 | ** If the bPush flag is true, then make this opcode the parent for |
| 77555 | 77572 | ** subsequent Explains until sqlite3VdbeExplainPop() is called. |
| 77556 | 77573 | */ |
| 77557 | 77574 | SQLITE_PRIVATE void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ |
| @@ -78781,12 +78798,15 @@ | ||
| 78781 | 78798 | displayP4Expr(&x, pOp->p4.pExpr); |
| 78782 | 78799 | break; |
| 78783 | 78800 | } |
| 78784 | 78801 | #endif |
| 78785 | 78802 | case P4_COLLSEQ: { |
| 78803 | + static const char *const encnames[] = {"?", "8", "16LE", "16BE"}; | |
| 78786 | 78804 | 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]); | |
| 78788 | 78808 | break; |
| 78789 | 78809 | } |
| 78790 | 78810 | case P4_FUNCDEF: { |
| 78791 | 78811 | FuncDef *pDef = pOp->p4.pFunc; |
| 78792 | 78812 | sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg); |
| @@ -79495,10 +79515,11 @@ | ||
| 79495 | 79515 | "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", |
| 79496 | 79516 | "id", "parent", "notused", "detail" |
| 79497 | 79517 | }; |
| 79498 | 79518 | int iFirst, mx, i; |
| 79499 | 79519 | if( nMem<10 ) nMem = 10; |
| 79520 | + p->explain = pParse->explain; | |
| 79500 | 79521 | if( pParse->explain==2 ){ |
| 79501 | 79522 | sqlite3VdbeSetNumCols(p, 4); |
| 79502 | 79523 | iFirst = 8; |
| 79503 | 79524 | mx = 12; |
| 79504 | 79525 | }else{ |
| @@ -79545,11 +79566,10 @@ | ||
| 79545 | 79566 | } |
| 79546 | 79567 | } |
| 79547 | 79568 | |
| 79548 | 79569 | p->pVList = pParse->pVList; |
| 79549 | 79570 | pParse->pVList = 0; |
| 79550 | - p->explain = pParse->explain; | |
| 79551 | 79571 | if( db->mallocFailed ){ |
| 79552 | 79572 | p->nVar = 0; |
| 79553 | 79573 | p->nCursor = 0; |
| 79554 | 79574 | p->nMem = 0; |
| 79555 | 79575 | }else{ |
| @@ -86230,11 +86250,10 @@ | ||
| 86230 | 86250 | u16 flags2; /* Initial flags for P2 */ |
| 86231 | 86251 | |
| 86232 | 86252 | pIn1 = &aMem[pOp->p1]; |
| 86233 | 86253 | pIn2 = &aMem[pOp->p2]; |
| 86234 | 86254 | pOut = &aMem[pOp->p3]; |
| 86235 | - testcase( pIn1==pIn2 ); | |
| 86236 | 86255 | testcase( pOut==pIn2 ); |
| 86237 | 86256 | assert( pIn1!=pOut ); |
| 86238 | 86257 | flags1 = pIn1->flags; |
| 86239 | 86258 | testcase( flags1 & MEM_Null ); |
| 86240 | 86259 | testcase( pIn2->flags & MEM_Null ); |
| @@ -88351,11 +88370,11 @@ | ||
| 88351 | 88370 | ** |
| 88352 | 88371 | ** Allowed P5 bits: |
| 88353 | 88372 | ** <ul> |
| 88354 | 88373 | ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for |
| 88355 | 88374 | ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT |
| 88356 | -** of OP_SeekLE/OP_IdxGT) | |
| 88375 | +** of OP_SeekLE/OP_IdxLT) | |
| 88357 | 88376 | ** </ul> |
| 88358 | 88377 | ** |
| 88359 | 88378 | ** The P4 value may be either an integer (P4_INT32) or a pointer to |
| 88360 | 88379 | ** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo |
| 88361 | 88380 | ** object, then table being opened must be an [index b-tree] where the |
| @@ -88381,11 +88400,11 @@ | ||
| 88381 | 88400 | ** |
| 88382 | 88401 | ** Allowed P5 bits: |
| 88383 | 88402 | ** <ul> |
| 88384 | 88403 | ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for |
| 88385 | 88404 | ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT |
| 88386 | -** of OP_SeekLE/OP_IdxGT) | |
| 88405 | +** of OP_SeekLE/OP_IdxLT) | |
| 88387 | 88406 | ** </ul> |
| 88388 | 88407 | ** |
| 88389 | 88408 | ** See also: OP_OpenRead, OP_OpenWrite |
| 88390 | 88409 | */ |
| 88391 | 88410 | /* Opcode: OpenWrite P1 P2 P3 P4 P5 |
| @@ -88405,11 +88424,11 @@ | ||
| 88405 | 88424 | ** |
| 88406 | 88425 | ** Allowed P5 bits: |
| 88407 | 88426 | ** <ul> |
| 88408 | 88427 | ** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for |
| 88409 | 88428 | ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT |
| 88410 | -** of OP_SeekLE/OP_IdxGT) | |
| 88429 | +** of OP_SeekLE/OP_IdxLT) | |
| 88411 | 88430 | ** <li> <b>0x08 OPFLAG_FORDELETE</b>: This cursor is used only to seek |
| 88412 | 88431 | ** and subsequently delete entries in an index btree. This is a |
| 88413 | 88432 | ** hint to the storage engine that the storage engine is allowed to |
| 88414 | 88433 | ** ignore. The hint is not used by the official SQLite b*tree storage |
| 88415 | 88434 | ** engine, but is used by COMDB2. |
| @@ -88517,13 +88536,11 @@ | ||
| 88517 | 88536 | |
| 88518 | 88537 | open_cursor_set_hints: |
| 88519 | 88538 | assert( OPFLAG_BULKCSR==BTREE_BULKLOAD ); |
| 88520 | 88539 | assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ ); |
| 88521 | 88540 | testcase( pOp->p5 & OPFLAG_BULKCSR ); |
| 88522 | -#ifdef SQLITE_ENABLE_CURSOR_HINTS | |
| 88523 | 88541 | testcase( pOp->p2 & OPFLAG_SEEKEQ ); |
| 88524 | -#endif | |
| 88525 | 88542 | sqlite3BtreeCursorHintFlags(pCur->uc.pCursor, |
| 88526 | 88543 | (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ))); |
| 88527 | 88544 | if( rc ) goto abort_due_to_error; |
| 88528 | 88545 | break; |
| 88529 | 88546 | } |
| @@ -88775,15 +88792,17 @@ | ||
| 88775 | 88792 | ** Reposition cursor P1 so that it points to the smallest entry that |
| 88776 | 88793 | ** is greater than or equal to the key value. If there are no records |
| 88777 | 88794 | ** greater than or equal to the key and P2 is not zero, then jump to P2. |
| 88778 | 88795 | ** |
| 88779 | 88796 | ** 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. | |
| 88785 | 88804 | ** |
| 88786 | 88805 | ** This opcode leaves the cursor configured to move in forward order, |
| 88787 | 88806 | ** from the beginning toward the end. In other words, the cursor is |
| 88788 | 88807 | ** configured to use Next, not Prev. |
| 88789 | 88808 | ** |
| @@ -88795,11 +88814,11 @@ | ||
| 88795 | 88814 | ** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), |
| 88796 | 88815 | ** use the value in register P3 as a key. If cursor P1 refers |
| 88797 | 88816 | ** to an SQL index, then P3 is the first in an array of P4 registers |
| 88798 | 88817 | ** that are used as an unpacked index key. |
| 88799 | 88818 | ** |
| 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 | |
| 88801 | 88820 | ** is greater than the key value. If there are no records greater than |
| 88802 | 88821 | ** the key and P2 is not zero, then jump to P2. |
| 88803 | 88822 | ** |
| 88804 | 88823 | ** This opcode leaves the cursor configured to move in forward order, |
| 88805 | 88824 | ** from the beginning toward the end. In other words, the cursor is |
| @@ -88840,15 +88859,17 @@ | ||
| 88840 | 88859 | ** This opcode leaves the cursor configured to move in reverse order, |
| 88841 | 88860 | ** from the end toward the beginning. In other words, the cursor is |
| 88842 | 88861 | ** configured to use Prev, not Next. |
| 88843 | 88862 | ** |
| 88844 | 88863 | ** 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. | |
| 88848 | 88867 | ** 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. | |
| 88850 | 88871 | ** |
| 88851 | 88872 | ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt |
| 88852 | 88873 | */ |
| 88853 | 88874 | case OP_SeekLT: /* jump, in3, group */ |
| 88854 | 88875 | case OP_SeekLE: /* jump, in3, group */ |
| @@ -88881,11 +88902,11 @@ | ||
| 88881 | 88902 | |
| 88882 | 88903 | pC->deferredMoveto = 0; |
| 88883 | 88904 | pC->cacheStatus = CACHE_STALE; |
| 88884 | 88905 | if( pC->isTable ){ |
| 88885 | 88906 | 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 */ | |
| 88887 | 88908 | assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 |
| 88888 | 88909 | || CORRUPT_DB ); |
| 88889 | 88910 | |
| 88890 | 88911 | /* The input value in P3 might be of any type: integer, real, string, |
| 88891 | 88912 | ** blob, or NULL. But it needs to be an integer before we can do |
| @@ -88940,18 +88961,21 @@ | ||
| 88940 | 88961 | pC->movetoTarget = iKey; /* Used by OP_Delete */ |
| 88941 | 88962 | if( rc!=SQLITE_OK ){ |
| 88942 | 88963 | goto abort_due_to_error; |
| 88943 | 88964 | } |
| 88944 | 88965 | }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. | |
| 88948 | 88970 | */ |
| 88949 | 88971 | if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){ |
| 88950 | 88972 | eqOnly = 1; |
| 88951 | 88973 | assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); |
| 88952 | 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 ); | |
| 88953 | 88977 | assert( pOp[1].p1==pOp[0].p1 ); |
| 88954 | 88978 | assert( pOp[1].p2==pOp[0].p2 ); |
| 88955 | 88979 | assert( pOp[1].p3==pOp[0].p3 ); |
| 88956 | 88980 | assert( pOp[1].p4.i==pOp[0].p4.i ); |
| 88957 | 88981 | } |
| @@ -91162,11 +91186,11 @@ | ||
| 91162 | 91186 | ** try to reuse register values from the first use. */ |
| 91163 | 91187 | { |
| 91164 | 91188 | int i; |
| 91165 | 91189 | for(i=0; i<p->nMem; i++){ |
| 91166 | 91190 | 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 */ | |
| 91168 | 91192 | } |
| 91169 | 91193 | } |
| 91170 | 91194 | #endif |
| 91171 | 91195 | pOp = &aOp[-1]; |
| 91172 | 91196 | goto check_for_interrupt; |
| @@ -96555,19 +96579,20 @@ | ||
| 96555 | 96579 | SrcList *pSrc; |
| 96556 | 96580 | int i; |
| 96557 | 96581 | struct SrcList_item *pItem; |
| 96558 | 96582 | |
| 96559 | 96583 | 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 | + } | |
| 96569 | 96594 | } |
| 96570 | 96595 | } |
| 96571 | 96596 | return WRC_Continue; |
| 96572 | 96597 | } |
| 96573 | 96598 | |
| @@ -96784,10 +96809,35 @@ | ||
| 96784 | 96809 | }else{ |
| 96785 | 96810 | /* Currently parsing a DML statement */ |
| 96786 | 96811 | return (db->flags & SQLITE_DqsDML)!=0; |
| 96787 | 96812 | } |
| 96788 | 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 | +} | |
| 96789 | 96839 | |
| 96790 | 96840 | /* |
| 96791 | 96841 | ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up |
| 96792 | 96842 | ** that name in the set of source tables in pSrcList and make the pExpr |
| 96793 | 96843 | ** expression node refer back to that source column. The following changes |
| @@ -96861,10 +96911,16 @@ | ||
| 96861 | 96911 | assert( db->aDb[i].zDbSName ); |
| 96862 | 96912 | if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){ |
| 96863 | 96913 | pSchema = db->aDb[i].pSchema; |
| 96864 | 96914 | break; |
| 96865 | 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; | |
| 96866 | 96922 | } |
| 96867 | 96923 | } |
| 96868 | 96924 | } |
| 96869 | 96925 | |
| 96870 | 96926 | /* Start at the inner-most context and move outward until a match is found */ |
| @@ -97181,26 +97237,11 @@ | ||
| 97181 | 97237 | ** |
| 97182 | 97238 | ** If a generated column is referenced, set bits for every column |
| 97183 | 97239 | ** of the table. |
| 97184 | 97240 | */ |
| 97185 | 97241 | 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); | |
| 97202 | 97243 | } |
| 97203 | 97244 | |
| 97204 | 97245 | /* Clean up and return |
| 97205 | 97246 | */ |
| 97206 | 97247 | sqlite3ExprDelete(db, pExpr->pLeft); |
| @@ -98557,11 +98598,11 @@ | ||
| 98557 | 98598 | ** CREATE TABLE t1(a); |
| 98558 | 98599 | ** SELECT * FROM t1 WHERE a; |
| 98559 | 98600 | ** SELECT a AS b FROM t1 WHERE b; |
| 98560 | 98601 | ** SELECT * FROM t1 WHERE (select a from t1); |
| 98561 | 98602 | */ |
| 98562 | -SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){ | |
| 98603 | +SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){ | |
| 98563 | 98604 | int op; |
| 98564 | 98605 | while( ExprHasProperty(pExpr, EP_Skip) ){ |
| 98565 | 98606 | assert( pExpr->op==TK_COLLATE ); |
| 98566 | 98607 | pExpr = pExpr->pLeft; |
| 98567 | 98608 | assert( pExpr!=0 ); |
| @@ -98667,14 +98708,14 @@ | ||
| 98667 | 98708 | ** The collating sequence might be determined by a COLLATE operator |
| 98668 | 98709 | ** or by the presence of a column with a defined collating sequence. |
| 98669 | 98710 | ** COLLATE operators take first precedence. Left operands take |
| 98670 | 98711 | ** precedence over right operands. |
| 98671 | 98712 | */ |
| 98672 | -SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){ | |
| 98713 | +SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ | |
| 98673 | 98714 | sqlite3 *db = pParse->db; |
| 98674 | 98715 | CollSeq *pColl = 0; |
| 98675 | - Expr *p = pExpr; | |
| 98716 | + const Expr *p = pExpr; | |
| 98676 | 98717 | while( p ){ |
| 98677 | 98718 | int op = p->op; |
| 98678 | 98719 | if( op==TK_REGISTER ) op = p->op2; |
| 98679 | 98720 | if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER) |
| 98680 | 98721 | && p->y.pTab!=0 |
| @@ -98739,21 +98780,21 @@ | ||
| 98739 | 98780 | ** See also: sqlite3ExprCollSeq() |
| 98740 | 98781 | ** |
| 98741 | 98782 | ** The sqlite3ExprCollSeq() routine works the same except that it |
| 98742 | 98783 | ** returns NULL if there is no defined collation. |
| 98743 | 98784 | */ |
| 98744 | -SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr){ | |
| 98785 | +SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr){ | |
| 98745 | 98786 | CollSeq *p = sqlite3ExprCollSeq(pParse, pExpr); |
| 98746 | 98787 | if( p==0 ) p = pParse->db->pDfltColl; |
| 98747 | 98788 | assert( p!=0 ); |
| 98748 | 98789 | return p; |
| 98749 | 98790 | } |
| 98750 | 98791 | |
| 98751 | 98792 | /* |
| 98752 | 98793 | ** Return TRUE if the two expressions have equivalent collating sequences. |
| 98753 | 98794 | */ |
| 98754 | -SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, Expr *pE1, Expr *pE2){ | |
| 98795 | +SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, const Expr *pE1, const Expr *pE2){ | |
| 98755 | 98796 | CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1); |
| 98756 | 98797 | CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2); |
| 98757 | 98798 | return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0; |
| 98758 | 98799 | } |
| 98759 | 98800 | |
| @@ -98760,11 +98801,11 @@ | ||
| 98760 | 98801 | /* |
| 98761 | 98802 | ** pExpr is an operand of a comparison operator. aff2 is the |
| 98762 | 98803 | ** type affinity of the other operand. This routine returns the |
| 98763 | 98804 | ** type affinity that should be used for the comparison operator. |
| 98764 | 98805 | */ |
| 98765 | -SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){ | |
| 98806 | +SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2){ | |
| 98766 | 98807 | char aff1 = sqlite3ExprAffinity(pExpr); |
| 98767 | 98808 | if( aff1>SQLITE_AFF_NONE && aff2>SQLITE_AFF_NONE ){ |
| 98768 | 98809 | /* Both sides of the comparison are columns. If one has numeric |
| 98769 | 98810 | ** affinity, use that. Otherwise use no affinity. |
| 98770 | 98811 | */ |
| @@ -98782,11 +98823,11 @@ | ||
| 98782 | 98823 | |
| 98783 | 98824 | /* |
| 98784 | 98825 | ** pExpr is a comparison operator. Return the type affinity that should |
| 98785 | 98826 | ** be applied to both operands prior to doing the comparison. |
| 98786 | 98827 | */ |
| 98787 | -static char comparisonAffinity(Expr *pExpr){ | |
| 98828 | +static char comparisonAffinity(const Expr *pExpr){ | |
| 98788 | 98829 | char aff; |
| 98789 | 98830 | assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT || |
| 98790 | 98831 | pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE || |
| 98791 | 98832 | pExpr->op==TK_NE || pExpr->op==TK_IS || pExpr->op==TK_ISNOT ); |
| 98792 | 98833 | assert( pExpr->pLeft ); |
| @@ -98805,11 +98846,11 @@ | ||
| 98805 | 98846 | ** pExpr is a comparison expression, eg. '=', '<', IN(...) etc. |
| 98806 | 98847 | ** idx_affinity is the affinity of an indexed column. Return true |
| 98807 | 98848 | ** if the index with affinity idx_affinity may be used to implement |
| 98808 | 98849 | ** the comparison in pExpr. |
| 98809 | 98850 | */ |
| 98810 | -SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){ | |
| 98851 | +SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity){ | |
| 98811 | 98852 | char aff = comparisonAffinity(pExpr); |
| 98812 | 98853 | if( aff<SQLITE_AFF_TEXT ){ |
| 98813 | 98854 | return 1; |
| 98814 | 98855 | } |
| 98815 | 98856 | if( aff==SQLITE_AFF_TEXT ){ |
| @@ -98820,11 +98861,15 @@ | ||
| 98820 | 98861 | |
| 98821 | 98862 | /* |
| 98822 | 98863 | ** Return the P5 value that should be used for a binary comparison |
| 98823 | 98864 | ** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2. |
| 98824 | 98865 | */ |
| 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 | +){ | |
| 98826 | 98871 | u8 aff = (char)sqlite3ExprAffinity(pExpr2); |
| 98827 | 98872 | aff = (u8)sqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull; |
| 98828 | 98873 | return aff; |
| 98829 | 98874 | } |
| 98830 | 98875 | |
| @@ -98840,12 +98885,12 @@ | ||
| 98840 | 98885 | ** Argument pRight (but not pLeft) may be a null pointer. In this case, |
| 98841 | 98886 | ** it is not considered. |
| 98842 | 98887 | */ |
| 98843 | 98888 | SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq( |
| 98844 | 98889 | Parse *pParse, |
| 98845 | - Expr *pLeft, | |
| 98846 | - Expr *pRight | |
| 98890 | + const Expr *pLeft, | |
| 98891 | + const Expr *pRight | |
| 98847 | 98892 | ){ |
| 98848 | 98893 | CollSeq *pColl; |
| 98849 | 98894 | assert( pLeft ); |
| 98850 | 98895 | if( pLeft->flags & EP_Collate ){ |
| 98851 | 98896 | pColl = sqlite3ExprCollSeq(pParse, pLeft); |
| @@ -98866,11 +98911,11 @@ | ||
| 98866 | 98911 | ** This is normally just a wrapper around sqlite3BinaryCompareCollSeq(). |
| 98867 | 98912 | ** However, if the OP_Commuted flag is set, then the order of the operands |
| 98868 | 98913 | ** is reversed in the sqlite3BinaryCompareCollSeq() call so that the |
| 98869 | 98914 | ** correct collating sequence is found. |
| 98870 | 98915 | */ |
| 98871 | -SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse *pParse, Expr *p){ | |
| 98916 | +SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse *pParse, const Expr *p){ | |
| 98872 | 98917 | if( ExprHasProperty(p, EP_Commuted) ){ |
| 98873 | 98918 | return sqlite3BinaryCompareCollSeq(pParse, p->pRight, p->pLeft); |
| 98874 | 98919 | }else{ |
| 98875 | 98920 | return sqlite3BinaryCompareCollSeq(pParse, p->pLeft, p->pRight); |
| 98876 | 98921 | } |
| @@ -99109,10 +99154,11 @@ | ||
| 99109 | 99154 | int regRight = 0; |
| 99110 | 99155 | u8 opx = op; |
| 99111 | 99156 | int addrDone = sqlite3VdbeMakeLabel(pParse); |
| 99112 | 99157 | int isCommuted = ExprHasProperty(pExpr,EP_Commuted); |
| 99113 | 99158 | |
| 99159 | + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); | |
| 99114 | 99160 | if( pParse->nErr ) return; |
| 99115 | 99161 | if( nLeft!=sqlite3ExprVectorSize(pRight) ){ |
| 99116 | 99162 | sqlite3ErrorMsg(pParse, "row value misused"); |
| 99117 | 99163 | return; |
| 99118 | 99164 | } |
| @@ -99721,11 +99767,11 @@ | ||
| 99721 | 99767 | nSize = EXPR_FULLSIZE; |
| 99722 | 99768 | }else{ |
| 99723 | 99769 | assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); |
| 99724 | 99770 | assert( !ExprHasProperty(p, EP_FromJoin) ); |
| 99725 | 99771 | assert( !ExprHasProperty(p, EP_MemToken) ); |
| 99726 | - assert( !ExprHasProperty(p, EP_NoReduce) ); | |
| 99772 | + assert( !ExprHasVVAProperty(p, EP_NoReduce) ); | |
| 99727 | 99773 | if( p->pLeft || p->x.pList ){ |
| 99728 | 99774 | nSize = EXPR_REDUCEDSIZE | EP_Reduced; |
| 99729 | 99775 | }else{ |
| 99730 | 99776 | assert( p->pRight==0 ); |
| 99731 | 99777 | nSize = EXPR_TOKENONLYSIZE | EP_TokenOnly; |
| @@ -99826,10 +99872,14 @@ | ||
| 99826 | 99872 | |
| 99827 | 99873 | /* Set the EP_Reduced, EP_TokenOnly, and EP_Static flags appropriately. */ |
| 99828 | 99874 | pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken); |
| 99829 | 99875 | pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly); |
| 99830 | 99876 | pNew->flags |= staticFlag; |
| 99877 | + ExprClearVVAProperties(pNew); | |
| 99878 | + if( dupFlags ){ | |
| 99879 | + ExprSetVVAProperty(pNew, EP_Immutable); | |
| 99880 | + } | |
| 99831 | 99881 | |
| 99832 | 99882 | /* Copy the p->u.zToken string, if any. */ |
| 99833 | 99883 | if( nToken ){ |
| 99834 | 99884 | char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize]; |
| 99835 | 99885 | memcpy(zToken, p->u.zToken, nToken); |
| @@ -100602,11 +100652,11 @@ | ||
| 100602 | 100652 | ** (3) the expression does not contain any EP_FixedCol TK_COLUMN |
| 100603 | 100653 | ** operands created by the constant propagation optimization. |
| 100604 | 100654 | ** |
| 100605 | 100655 | ** When this routine returns true, it indicates that the expression |
| 100606 | 100656 | ** 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(). | |
| 100608 | 100658 | */ |
| 100609 | 100659 | SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){ |
| 100610 | 100660 | return exprIsConst(p, 2, 0); |
| 100611 | 100661 | } |
| 100612 | 100662 | |
| @@ -101365,10 +101415,11 @@ | ||
| 101365 | 101415 | return; |
| 101366 | 101416 | } |
| 101367 | 101417 | |
| 101368 | 101418 | /* Begin coding the subroutine */ |
| 101369 | 101419 | ExprSetProperty(pExpr, EP_Subrtn); |
| 101420 | + assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); | |
| 101370 | 101421 | pExpr->y.sub.regReturn = ++pParse->nMem; |
| 101371 | 101422 | pExpr->y.sub.iAddr = |
| 101372 | 101423 | sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; |
| 101373 | 101424 | VdbeComment((v, "return address")); |
| 101374 | 101425 | |
| @@ -101685,10 +101736,11 @@ | ||
| 101685 | 101736 | int addrTruthOp; /* Address of opcode that determines the IN is true */ |
| 101686 | 101737 | int destNotNull; /* Jump here if a comparison is not true in step 6 */ |
| 101687 | 101738 | int addrTop; /* Top of the step-6 loop */ |
| 101688 | 101739 | int iTab = 0; /* Index to use */ |
| 101689 | 101740 | |
| 101741 | + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); | |
| 101690 | 101742 | pLeft = pExpr->pLeft; |
| 101691 | 101743 | if( sqlite3ExprCheckIN(pParse, pExpr) ) return; |
| 101692 | 101744 | zAff = exprINAffinity(pParse, pExpr); |
| 101693 | 101745 | nVector = sqlite3ExprVectorSize(pExpr->pLeft); |
| 101694 | 101746 | aiMap = (int*)sqlite3DbMallocZero( |
| @@ -102011,11 +102063,11 @@ | ||
| 102011 | 102063 | if( pParse->iSelfTab>0 ){ |
| 102012 | 102064 | iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut); |
| 102013 | 102065 | }else{ |
| 102014 | 102066 | iAddr = 0; |
| 102015 | 102067 | } |
| 102016 | - sqlite3ExprCode(pParse, pCol->pDflt, regOut); | |
| 102068 | + sqlite3ExprCodeCopy(pParse, pCol->pDflt, regOut); | |
| 102017 | 102069 | if( pCol->affinity>=SQLITE_AFF_TEXT ){ |
| 102018 | 102070 | sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1); |
| 102019 | 102071 | } |
| 102020 | 102072 | if( iAddr ) sqlite3VdbeJumpHere(v, iAddr); |
| 102021 | 102073 | } |
| @@ -102295,10 +102347,11 @@ | ||
| 102295 | 102347 | |
| 102296 | 102348 | expr_code_doover: |
| 102297 | 102349 | if( pExpr==0 ){ |
| 102298 | 102350 | op = TK_NULL; |
| 102299 | 102351 | }else{ |
| 102352 | + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); | |
| 102300 | 102353 | op = pExpr->op; |
| 102301 | 102354 | } |
| 102302 | 102355 | switch( op ){ |
| 102303 | 102356 | case TK_AGG_COLUMN: { |
| 102304 | 102357 | AggInfo *pAggInfo = pExpr->pAggInfo; |
| @@ -102305,12 +102358,21 @@ | ||
| 102305 | 102358 | struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg]; |
| 102306 | 102359 | if( !pAggInfo->directMode ){ |
| 102307 | 102360 | assert( pCol->iMem>0 ); |
| 102308 | 102361 | return pCol->iMem; |
| 102309 | 102362 | }else if( pAggInfo->useSortingIdx ){ |
| 102363 | + Table *pTab = pCol->pTab; | |
| 102310 | 102364 | sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, |
| 102311 | 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 | + } | |
| 102312 | 102374 | return target; |
| 102313 | 102375 | } |
| 102314 | 102376 | /* Otherwise, fall thru into the TK_COLUMN case */ |
| 102315 | 102377 | } |
| 102316 | 102378 | case TK_COLUMN: { |
| @@ -102549,10 +102611,11 @@ | ||
| 102549 | 102611 | #endif |
| 102550 | 102612 | }else{ |
| 102551 | 102613 | tempX.op = TK_INTEGER; |
| 102552 | 102614 | tempX.flags = EP_IntValue|EP_TokenOnly; |
| 102553 | 102615 | tempX.u.iValue = 0; |
| 102616 | + ExprClearVVAProperties(&tempX); | |
| 102554 | 102617 | r1 = sqlite3ExprCodeTemp(pParse, &tempX, ®Free1); |
| 102555 | 102618 | r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free2); |
| 102556 | 102619 | sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target); |
| 102557 | 102620 | testcase( regFree2==0 ); |
| 102558 | 102621 | } |
| @@ -102620,20 +102683,17 @@ | ||
| 102620 | 102683 | return pExpr->y.pWin->regResult; |
| 102621 | 102684 | } |
| 102622 | 102685 | #endif |
| 102623 | 102686 | |
| 102624 | 102687 | 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); | |
| 102628 | 102691 | } |
| 102629 | 102692 | 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; | |
| 102635 | 102695 | nFarg = pFarg ? pFarg->nExpr : 0; |
| 102636 | 102696 | assert( !ExprHasProperty(pExpr, EP_IntValue) ); |
| 102637 | 102697 | zId = pExpr->u.zToken; |
| 102638 | 102698 | pDef = sqlite3FindFunction(db, zId, nFarg, enc, 0); |
| 102639 | 102699 | #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION |
| @@ -103003,19 +103063,27 @@ | ||
| 103003 | 103063 | sqlite3ReleaseTempReg(pParse, regFree2); |
| 103004 | 103064 | return inReg; |
| 103005 | 103065 | } |
| 103006 | 103066 | |
| 103007 | 103067 | /* |
| 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. | |
| 103009 | 103075 | ** |
| 103010 | 103076 | ** If regDest>=0 then the result is always stored in that register and the |
| 103011 | 103077 | ** result is not reusable. If regDest<0 then this routine is free to |
| 103012 | 103078 | ** 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. | |
| 103015 | 103083 | */ |
| 103016 | -SQLITE_PRIVATE int sqlite3ExprCodeAtInit( | |
| 103084 | +SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce( | |
| 103017 | 103085 | Parse *pParse, /* Parsing context */ |
| 103018 | 103086 | Expr *pExpr, /* The expression to code when the VDBE initializes */ |
| 103019 | 103087 | int regDest /* Store the value in this register */ |
| 103020 | 103088 | ){ |
| 103021 | 103089 | ExprList *p; |
| @@ -103029,18 +103097,33 @@ | ||
| 103029 | 103097 | return pItem->u.iConstExprReg; |
| 103030 | 103098 | } |
| 103031 | 103099 | } |
| 103032 | 103100 | } |
| 103033 | 103101 | 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 | + } | |
| 103042 | 103125 | return regDest; |
| 103043 | 103126 | } |
| 103044 | 103127 | |
| 103045 | 103128 | /* |
| 103046 | 103129 | ** Generate code to evaluate an expression and store the results |
| @@ -103061,11 +103144,11 @@ | ||
| 103061 | 103144 | if( ConstFactorOk(pParse) |
| 103062 | 103145 | && pExpr->op!=TK_REGISTER |
| 103063 | 103146 | && sqlite3ExprIsConstantNotJoin(pExpr) |
| 103064 | 103147 | ){ |
| 103065 | 103148 | *pReg = 0; |
| 103066 | - r2 = sqlite3ExprCodeAtInit(pParse, pExpr, -1); | |
| 103149 | + r2 = sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); | |
| 103067 | 103150 | }else{ |
| 103068 | 103151 | int r1 = sqlite3GetTempReg(pParse); |
| 103069 | 103152 | r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); |
| 103070 | 103153 | if( r2==r1 ){ |
| 103071 | 103154 | *pReg = r1; |
| @@ -103083,10 +103166,11 @@ | ||
| 103083 | 103166 | ** in register target. |
| 103084 | 103167 | */ |
| 103085 | 103168 | SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ |
| 103086 | 103169 | int inReg; |
| 103087 | 103170 | |
| 103171 | + assert( pExpr==0 || !ExprHasVVAProperty(pExpr,EP_Immutable) ); | |
| 103088 | 103172 | assert( target>0 && target<=pParse->nMem ); |
| 103089 | 103173 | inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); |
| 103090 | 103174 | assert( pParse->pVdbe!=0 || pParse->db->mallocFailed ); |
| 103091 | 103175 | if( inReg!=target && pParse->pVdbe ){ |
| 103092 | 103176 | u8 op; |
| @@ -103117,13 +103201,13 @@ | ||
| 103117 | 103201 | ** in register target. If the expression is constant, then this routine |
| 103118 | 103202 | ** might choose to code the expression at initialization time. |
| 103119 | 103203 | */ |
| 103120 | 103204 | SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ |
| 103121 | 103205 | if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){ |
| 103122 | - sqlite3ExprCodeAtInit(pParse, pExpr, target); | |
| 103206 | + sqlite3ExprCodeRunJustOnce(pParse, pExpr, target); | |
| 103123 | 103207 | }else{ |
| 103124 | - sqlite3ExprCode(pParse, pExpr, target); | |
| 103208 | + sqlite3ExprCodeCopy(pParse, pExpr, target); | |
| 103125 | 103209 | } |
| 103126 | 103210 | } |
| 103127 | 103211 | |
| 103128 | 103212 | /* |
| 103129 | 103213 | ** Generate code that pushes the value of every element of the given |
| @@ -103177,11 +103261,11 @@ | ||
| 103177 | 103261 | sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); |
| 103178 | 103262 | } |
| 103179 | 103263 | }else if( (flags & SQLITE_ECEL_FACTOR)!=0 |
| 103180 | 103264 | && sqlite3ExprIsConstantNotJoin(pExpr) |
| 103181 | 103265 | ){ |
| 103182 | - sqlite3ExprCodeAtInit(pParse, pExpr, target+i); | |
| 103266 | + sqlite3ExprCodeRunJustOnce(pParse, pExpr, target+i); | |
| 103183 | 103267 | }else{ |
| 103184 | 103268 | int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); |
| 103185 | 103269 | if( inReg!=target+i ){ |
| 103186 | 103270 | VdbeOp *pOp; |
| 103187 | 103271 | if( copyOp==OP_Copy |
| @@ -103300,10 +103384,11 @@ | ||
| 103300 | 103384 | int r1, r2; |
| 103301 | 103385 | |
| 103302 | 103386 | assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 ); |
| 103303 | 103387 | if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ |
| 103304 | 103388 | if( NEVER(pExpr==0) ) return; /* No way this can happen */ |
| 103389 | + assert( !ExprHasVVAProperty(pExpr, EP_Immutable) ); | |
| 103305 | 103390 | op = pExpr->op; |
| 103306 | 103391 | switch( op ){ |
| 103307 | 103392 | case TK_AND: |
| 103308 | 103393 | case TK_OR: { |
| 103309 | 103394 | Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr); |
| @@ -103441,10 +103526,11 @@ | ||
| 103441 | 103526 | int r1, r2; |
| 103442 | 103527 | |
| 103443 | 103528 | assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 ); |
| 103444 | 103529 | if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ |
| 103445 | 103530 | if( pExpr==0 ) return; |
| 103531 | + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); | |
| 103446 | 103532 | |
| 103447 | 103533 | /* The value of pExpr->op and op are related as follows: |
| 103448 | 103534 | ** |
| 103449 | 103535 | ** pExpr->op op |
| 103450 | 103536 | ** --------- ---------- |
| @@ -103724,36 +103810,22 @@ | ||
| 103724 | 103810 | return 2; |
| 103725 | 103811 | } |
| 103726 | 103812 | } |
| 103727 | 103813 | if( (pA->flags & (EP_Distinct|EP_Commuted)) |
| 103728 | 103814 | != (pB->flags & (EP_Distinct|EP_Commuted)) ) return 2; |
| 103729 | - if( (combinedFlags & EP_TokenOnly)==0 ){ | |
| 103815 | + if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){ | |
| 103730 | 103816 | if( combinedFlags & EP_xIsSelect ) return 2; |
| 103731 | 103817 | if( (combinedFlags & EP_FixedCol)==0 |
| 103732 | 103818 | && sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2; |
| 103733 | 103819 | if( sqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2; |
| 103734 | 103820 | if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2; |
| 103735 | 103821 | if( pA->op!=TK_STRING |
| 103736 | 103822 | && pA->op!=TK_TRUEFALSE |
| 103737 | - && (combinedFlags & EP_Reduced)==0 | |
| 103823 | + && ALWAYS((combinedFlags & EP_Reduced)==0) | |
| 103738 | 103824 | ){ |
| 103739 | 103825 | 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; | |
| 103755 | 103827 | if( pA->op!=TK_IN && pA->iTable!=pB->iTable && pA->iTable!=iTab ){ |
| 103756 | 103828 | return 2; |
| 103757 | 103829 | } |
| 103758 | 103830 | } |
| 103759 | 103831 | } |
| @@ -106442,13 +106514,13 @@ | ||
| 106442 | 106514 | /* |
| 106443 | 106515 | ** Three SQL functions - stat_init(), stat_push(), and stat_get() - |
| 106444 | 106516 | ** share an instance of the following structure to hold their state |
| 106445 | 106517 | ** information. |
| 106446 | 106518 | */ |
| 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 { | |
| 106450 | 106522 | tRowcnt *anEq; /* sqlite_stat4.nEq */ |
| 106451 | 106523 | tRowcnt *anDLt; /* sqlite_stat4.nDLt */ |
| 106452 | 106524 | #ifdef SQLITE_ENABLE_STAT4 |
| 106453 | 106525 | tRowcnt *anLt; /* sqlite_stat4.nLt */ |
| 106454 | 106526 | union { |
| @@ -106459,31 +106531,33 @@ | ||
| 106459 | 106531 | u8 isPSample; /* True if a periodic sample */ |
| 106460 | 106532 | int iCol; /* If !isPSample, the reason for inclusion */ |
| 106461 | 106533 | u32 iHash; /* Tiebreaker hash */ |
| 106462 | 106534 | #endif |
| 106463 | 106535 | }; |
| 106464 | -struct Stat4Accum { | |
| 106536 | +struct StatAccum { | |
| 106537 | + sqlite3 *db; /* Database connection, for malloc() */ | |
| 106465 | 106538 | tRowcnt nRow; /* Number of rows in the entire table */ |
| 106466 | - tRowcnt nPSample; /* How often to do a periodic sample */ | |
| 106467 | 106539 | int nCol; /* Number of columns in index + pk/rowid */ |
| 106468 | 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 */ | |
| 106469 | 106544 | int mxSample; /* Maximum number of samples to accumulate */ |
| 106470 | - Stat4Sample current; /* Current row as a Stat4Sample */ | |
| 106471 | 106545 | u32 iPrn; /* Pseudo-random number used for sampling */ |
| 106472 | - Stat4Sample *aBest; /* Array of nCol best samples */ | |
| 106546 | + StatSample *aBest; /* Array of nCol best samples */ | |
| 106473 | 106547 | int iMin; /* Index in a[] of entry with minimum score */ |
| 106474 | 106548 | int nSample; /* Current number of samples */ |
| 106475 | 106549 | int nMaxEqZero; /* Max leading 0 in anEq[] for any a[] entry */ |
| 106476 | 106550 | 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 | |
| 106479 | 106553 | }; |
| 106480 | 106554 | |
| 106481 | -/* Reclaim memory used by a Stat4Sample | |
| 106555 | +/* Reclaim memory used by a StatSample | |
| 106482 | 106556 | */ |
| 106483 | 106557 | #ifdef SQLITE_ENABLE_STAT4 |
| 106484 | -static void sampleClear(sqlite3 *db, Stat4Sample *p){ | |
| 106558 | +static void sampleClear(sqlite3 *db, StatSample *p){ | |
| 106485 | 106559 | assert( db!=0 ); |
| 106486 | 106560 | if( p->nRowid ){ |
| 106487 | 106561 | sqlite3DbFree(db, p->u.aRowid); |
| 106488 | 106562 | p->nRowid = 0; |
| 106489 | 106563 | } |
| @@ -106491,11 +106565,11 @@ | ||
| 106491 | 106565 | #endif |
| 106492 | 106566 | |
| 106493 | 106567 | /* Initialize the BLOB value of a ROWID |
| 106494 | 106568 | */ |
| 106495 | 106569 | #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){ | |
| 106497 | 106571 | assert( db!=0 ); |
| 106498 | 106572 | if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); |
| 106499 | 106573 | p->u.aRowid = sqlite3DbMallocRawNN(db, n); |
| 106500 | 106574 | if( p->u.aRowid ){ |
| 106501 | 106575 | p->nRowid = n; |
| @@ -106507,11 +106581,11 @@ | ||
| 106507 | 106581 | #endif |
| 106508 | 106582 | |
| 106509 | 106583 | /* Initialize the INTEGER value of a ROWID. |
| 106510 | 106584 | */ |
| 106511 | 106585 | #ifdef SQLITE_ENABLE_STAT4 |
| 106512 | -static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){ | |
| 106586 | +static void sampleSetRowidInt64(sqlite3 *db, StatSample *p, i64 iRowid){ | |
| 106513 | 106587 | assert( db!=0 ); |
| 106514 | 106588 | if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); |
| 106515 | 106589 | p->nRowid = 0; |
| 106516 | 106590 | p->u.iRowid = iRowid; |
| 106517 | 106591 | } |
| @@ -106520,11 +106594,11 @@ | ||
| 106520 | 106594 | |
| 106521 | 106595 | /* |
| 106522 | 106596 | ** Copy the contents of object (*pFrom) into (*pTo). |
| 106523 | 106597 | */ |
| 106524 | 106598 | #ifdef SQLITE_ENABLE_STAT4 |
| 106525 | -static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ | |
| 106599 | +static void sampleCopy(StatAccum *p, StatSample *pTo, StatSample *pFrom){ | |
| 106526 | 106600 | pTo->isPSample = pFrom->isPSample; |
| 106527 | 106601 | pTo->iCol = pFrom->iCol; |
| 106528 | 106602 | pTo->iHash = pFrom->iHash; |
| 106529 | 106603 | memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol); |
| 106530 | 106604 | memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol); |
| @@ -106536,14 +106610,14 @@ | ||
| 106536 | 106610 | } |
| 106537 | 106611 | } |
| 106538 | 106612 | #endif |
| 106539 | 106613 | |
| 106540 | 106614 | /* |
| 106541 | -** Reclaim all memory of a Stat4Accum structure. | |
| 106615 | +** Reclaim all memory of a StatAccum structure. | |
| 106542 | 106616 | */ |
| 106543 | -static void stat4Destructor(void *pOld){ | |
| 106544 | - Stat4Accum *p = (Stat4Accum*)pOld; | |
| 106617 | +static void statAccumDestructor(void *pOld){ | |
| 106618 | + StatAccum *p = (StatAccum*)pOld; | |
| 106545 | 106619 | #ifdef SQLITE_ENABLE_STAT4 |
| 106546 | 106620 | int i; |
| 106547 | 106621 | for(i=0; i<p->nCol; i++) sampleClear(p->db, p->aBest+i); |
| 106548 | 106622 | for(i=0; i<p->mxSample; i++) sampleClear(p->db, p->a+i); |
| 106549 | 106623 | sampleClear(p->db, &p->current); |
| @@ -106567,21 +106641,21 @@ | ||
| 106567 | 106641 | ** For indexes on ordinary rowid tables, N==K+1. But for indexes on |
| 106568 | 106642 | ** WITHOUT ROWID tables, N=K+P where P is the number of columns in the |
| 106569 | 106643 | ** PRIMARY KEY of the table. The covering index that implements the |
| 106570 | 106644 | ** original WITHOUT ROWID table as N==K as a special case. |
| 106571 | 106645 | ** |
| 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 | |
| 106575 | 106649 | ** object. |
| 106576 | 106650 | */ |
| 106577 | 106651 | static void statInit( |
| 106578 | 106652 | sqlite3_context *context, |
| 106579 | 106653 | int argc, |
| 106580 | 106654 | sqlite3_value **argv |
| 106581 | 106655 | ){ |
| 106582 | - Stat4Accum *p; | |
| 106656 | + StatAccum *p; | |
| 106583 | 106657 | int nCol; /* Number of columns in index being sampled */ |
| 106584 | 106658 | int nKeyCol; /* Number of key columns */ |
| 106585 | 106659 | int nColUp; /* nCol rounded up for alignment */ |
| 106586 | 106660 | int n; /* Bytes of space to allocate */ |
| 106587 | 106661 | sqlite3 *db; /* Database connection */ |
| @@ -106596,17 +106670,17 @@ | ||
| 106596 | 106670 | nColUp = sizeof(tRowcnt)<8 ? (nCol+1)&~1 : nCol; |
| 106597 | 106671 | nKeyCol = sqlite3_value_int(argv[1]); |
| 106598 | 106672 | assert( nKeyCol<=nCol ); |
| 106599 | 106673 | assert( nKeyCol>0 ); |
| 106600 | 106674 | |
| 106601 | - /* Allocate the space required for the Stat4Accum object */ | |
| 106675 | + /* Allocate the space required for the StatAccum object */ | |
| 106602 | 106676 | 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 */ | |
| 106605 | 106679 | #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[] */ | |
| 106608 | 106682 | + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample) |
| 106609 | 106683 | #endif |
| 106610 | 106684 | ; |
| 106611 | 106685 | db = sqlite3_context_db_handle(context); |
| 106612 | 106686 | p = sqlite3DbMallocZero(db, n); |
| @@ -106631,12 +106705,12 @@ | ||
| 106631 | 106705 | p->mxSample = mxSample; |
| 106632 | 106706 | p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1); |
| 106633 | 106707 | p->current.anLt = &p->current.anEq[nColUp]; |
| 106634 | 106708 | p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]); |
| 106635 | 106709 | |
| 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]; | |
| 106638 | 106712 | p->aBest = &p->a[mxSample]; |
| 106639 | 106713 | pSpace = (u8*)(&p->a[mxSample+nCol]); |
| 106640 | 106714 | for(i=0; i<(mxSample+nCol); i++){ |
| 106641 | 106715 | p->a[i].anEq = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp); |
| 106642 | 106716 | p->a[i].anLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp); |
| @@ -106652,11 +106726,11 @@ | ||
| 106652 | 106726 | |
| 106653 | 106727 | /* Return a pointer to the allocated object to the caller. Note that |
| 106654 | 106728 | ** only the pointer (the 2nd parameter) matters. The size of the object |
| 106655 | 106729 | ** (given by the 3rd parameter) is never used and can be any positive |
| 106656 | 106730 | ** value. */ |
| 106657 | - sqlite3_result_blob(context, p, sizeof(*p), stat4Destructor); | |
| 106731 | + sqlite3_result_blob(context, p, sizeof(*p), statAccumDestructor); | |
| 106658 | 106732 | } |
| 106659 | 106733 | static const FuncDef statInitFuncdef = { |
| 106660 | 106734 | 2+IsStat4, /* nArg */ |
| 106661 | 106735 | SQLITE_UTF8, /* funcFlags */ |
| 106662 | 106736 | 0, /* pUserData */ |
| @@ -106679,13 +106753,13 @@ | ||
| 106679 | 106753 | ** |
| 106680 | 106754 | ** This function assumes that for each argument sample, the contents of |
| 106681 | 106755 | ** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid. |
| 106682 | 106756 | */ |
| 106683 | 106757 | static int sampleIsBetterPost( |
| 106684 | - Stat4Accum *pAccum, | |
| 106685 | - Stat4Sample *pNew, | |
| 106686 | - Stat4Sample *pOld | |
| 106758 | + StatAccum *pAccum, | |
| 106759 | + StatSample *pNew, | |
| 106760 | + StatSample *pOld | |
| 106687 | 106761 | ){ |
| 106688 | 106762 | int nCol = pAccum->nCol; |
| 106689 | 106763 | int i; |
| 106690 | 106764 | assert( pNew->iCol==pOld->iCol ); |
| 106691 | 106765 | for(i=pNew->iCol+1; i<nCol; i++){ |
| @@ -106703,13 +106777,13 @@ | ||
| 106703 | 106777 | ** |
| 106704 | 106778 | ** This function assumes that for each argument sample, the contents of |
| 106705 | 106779 | ** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid. |
| 106706 | 106780 | */ |
| 106707 | 106781 | static int sampleIsBetter( |
| 106708 | - Stat4Accum *pAccum, | |
| 106709 | - Stat4Sample *pNew, | |
| 106710 | - Stat4Sample *pOld | |
| 106782 | + StatAccum *pAccum, | |
| 106783 | + StatSample *pNew, | |
| 106784 | + StatSample *pOld | |
| 106711 | 106785 | ){ |
| 106712 | 106786 | tRowcnt nEqNew = pNew->anEq[pNew->iCol]; |
| 106713 | 106787 | tRowcnt nEqOld = pOld->anEq[pOld->iCol]; |
| 106714 | 106788 | |
| 106715 | 106789 | assert( pOld->isPSample==0 && pNew->isPSample==0 ); |
| @@ -106725,34 +106799,34 @@ | ||
| 106725 | 106799 | |
| 106726 | 106800 | /* |
| 106727 | 106801 | ** Copy the contents of sample *pNew into the p->a[] array. If necessary, |
| 106728 | 106802 | ** remove the least desirable sample from p->a[] to make room. |
| 106729 | 106803 | */ |
| 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; | |
| 106732 | 106806 | int i; |
| 106733 | 106807 | |
| 106734 | 106808 | assert( IsStat4 || nEqZero==0 ); |
| 106735 | 106809 | |
| 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 | |
| 106738 | 106812 | ** 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). */ | |
| 106740 | 106814 | if( nEqZero>p->nMaxEqZero ){ |
| 106741 | 106815 | p->nMaxEqZero = nEqZero; |
| 106742 | 106816 | } |
| 106743 | 106817 | if( pNew->isPSample==0 ){ |
| 106744 | - Stat4Sample *pUpgrade = 0; | |
| 106818 | + StatSample *pUpgrade = 0; | |
| 106745 | 106819 | assert( pNew->anEq[pNew->iCol]>0 ); |
| 106746 | 106820 | |
| 106747 | 106821 | /* This sample is being added because the prefix that ends in column |
| 106748 | 106822 | ** iCol occurs many times in the table. However, if we have already |
| 106749 | 106823 | ** added a sample that shares this prefix, there is no need to add |
| 106750 | 106824 | ** this one. Instead, upgrade the priority of the highest priority |
| 106751 | 106825 | ** existing sample that shares this prefix. */ |
| 106752 | 106826 | for(i=p->nSample-1; i>=0; i--){ |
| 106753 | - Stat4Sample *pOld = &p->a[i]; | |
| 106827 | + StatSample *pOld = &p->a[i]; | |
| 106754 | 106828 | if( pOld->anEq[pNew->iCol]==0 ){ |
| 106755 | 106829 | if( pOld->isPSample ) return; |
| 106756 | 106830 | assert( pOld->iCol>pNew->iCol ); |
| 106757 | 106831 | assert( sampleIsBetter(p, pNew, pOld) ); |
| 106758 | 106832 | if( pUpgrade==0 || sampleIsBetter(p, pOld, pUpgrade) ){ |
| @@ -106767,11 +106841,11 @@ | ||
| 106767 | 106841 | } |
| 106768 | 106842 | } |
| 106769 | 106843 | |
| 106770 | 106844 | /* If necessary, remove sample iMin to make room for the new sample. */ |
| 106771 | 106845 | if( p->nSample>=p->mxSample ){ |
| 106772 | - Stat4Sample *pMin = &p->a[p->iMin]; | |
| 106846 | + StatSample *pMin = &p->a[p->iMin]; | |
| 106773 | 106847 | tRowcnt *anEq = pMin->anEq; |
| 106774 | 106848 | tRowcnt *anLt = pMin->anLt; |
| 106775 | 106849 | tRowcnt *anDLt = pMin->anDLt; |
| 106776 | 106850 | sampleClear(p->db, pMin); |
| 106777 | 106851 | memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1)); |
| @@ -106810,24 +106884,24 @@ | ||
| 106810 | 106884 | p->iMin = iMin; |
| 106811 | 106885 | } |
| 106812 | 106886 | } |
| 106813 | 106887 | #endif /* SQLITE_ENABLE_STAT4 */ |
| 106814 | 106888 | |
| 106889 | +#ifdef SQLITE_ENABLE_STAT4 | |
| 106815 | 106890 | /* |
| 106816 | 106891 | ** Field iChng of the index being scanned has changed. So at this point |
| 106817 | 106892 | ** p->current contains a sample that reflects the previous row of the |
| 106818 | 106893 | ** index. The value of anEq[iChng] and subsequent anEq[] elements are |
| 106819 | 106894 | ** correct at this point. |
| 106820 | 106895 | */ |
| 106821 | -static void samplePushPrevious(Stat4Accum *p, int iChng){ | |
| 106822 | -#ifdef SQLITE_ENABLE_STAT4 | |
| 106896 | +static void samplePushPrevious(StatAccum *p, int iChng){ | |
| 106823 | 106897 | int i; |
| 106824 | 106898 | |
| 106825 | 106899 | /* Check if any samples from the aBest[] array should be pushed |
| 106826 | 106900 | ** into IndexSample.a[] at this point. */ |
| 106827 | 106901 | for(i=(p->nCol-2); i>=iChng; i--){ |
| 106828 | - Stat4Sample *pBest = &p->aBest[i]; | |
| 106902 | + StatSample *pBest = &p->aBest[i]; | |
| 106829 | 106903 | pBest->anEq[i] = p->current.anEq[i]; |
| 106830 | 106904 | if( p->nSample<p->mxSample || sampleIsBetter(p, pBest, &p->a[p->iMin]) ){ |
| 106831 | 106905 | sampleInsert(p, pBest, i); |
| 106832 | 106906 | } |
| 106833 | 106907 | } |
| @@ -106847,29 +106921,24 @@ | ||
| 106847 | 106921 | if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j]; |
| 106848 | 106922 | } |
| 106849 | 106923 | } |
| 106850 | 106924 | p->nMaxEqZero = iChng; |
| 106851 | 106925 | } |
| 106852 | -#endif | |
| 106853 | - | |
| 106854 | -#ifndef SQLITE_ENABLE_STAT4 | |
| 106855 | - UNUSED_PARAMETER( p ); | |
| 106856 | - UNUSED_PARAMETER( iChng ); | |
| 106857 | -#endif | |
| 106858 | 106926 | } |
| 106927 | +#endif /* SQLITE_ENABLE_STAT4 */ | |
| 106859 | 106928 | |
| 106860 | 106929 | /* |
| 106861 | 106930 | ** Implementation of the stat_push SQL function: stat_push(P,C,R) |
| 106862 | 106931 | ** Arguments: |
| 106863 | 106932 | ** |
| 106864 | -** P Pointer to the Stat4Accum object created by stat_init() | |
| 106933 | +** P Pointer to the StatAccum object created by stat_init() | |
| 106865 | 106934 | ** C Index of left-most column to differ from previous row |
| 106866 | 106935 | ** R Rowid for the current row. Might be a key record for |
| 106867 | 106936 | ** WITHOUT ROWID tables. |
| 106868 | 106937 | ** |
| 106869 | 106938 | ** 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 | |
| 106871 | 106940 | ** index being analyzed. The stat_get() SQL function will later be used to |
| 106872 | 106941 | ** extract relevant information for constructing the sqlite_statN tables. |
| 106873 | 106942 | ** |
| 106874 | 106943 | ** The R parameter is only used for STAT4 |
| 106875 | 106944 | */ |
| @@ -106879,11 +106948,11 @@ | ||
| 106879 | 106948 | sqlite3_value **argv |
| 106880 | 106949 | ){ |
| 106881 | 106950 | int i; |
| 106882 | 106951 | |
| 106883 | 106952 | /* The three function arguments */ |
| 106884 | - Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]); | |
| 106953 | + StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]); | |
| 106885 | 106954 | int iChng = sqlite3_value_int(argv[1]); |
| 106886 | 106955 | |
| 106887 | 106956 | UNUSED_PARAMETER( argc ); |
| 106888 | 106957 | UNUSED_PARAMETER( context ); |
| 106889 | 106958 | assert( p->nCol>0 ); |
| @@ -106892,11 +106961,13 @@ | ||
| 106892 | 106961 | if( p->nRow==0 ){ |
| 106893 | 106962 | /* This is the first call to this function. Do initialization. */ |
| 106894 | 106963 | for(i=0; i<p->nCol; i++) p->current.anEq[i] = 1; |
| 106895 | 106964 | }else{ |
| 106896 | 106965 | /* Second and subsequent calls get processed here */ |
| 106966 | +#ifdef SQLITE_ENABLE_STAT4 | |
| 106897 | 106967 | samplePushPrevious(p, iChng); |
| 106968 | +#endif | |
| 106898 | 106969 | |
| 106899 | 106970 | /* Update anDLt[], anLt[] and anEq[] to reflect the values that apply |
| 106900 | 106971 | ** to the current row of the index. */ |
| 106901 | 106972 | for(i=0; i<iChng; i++){ |
| 106902 | 106973 | p->current.anEq[i]++; |
| @@ -106961,19 +107032,19 @@ | ||
| 106961 | 107032 | #define STAT_GET_NDLT 4 /* "ndlt" column of stat[34] entry */ |
| 106962 | 107033 | |
| 106963 | 107034 | /* |
| 106964 | 107035 | ** Implementation of the stat_get(P,J) SQL function. This routine is |
| 106965 | 107036 | ** 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. | |
| 106968 | 107039 | ** The content to returned is determined by the parameter J |
| 106969 | 107040 | ** which is one of the STAT_GET_xxxx values defined above. |
| 106970 | 107041 | ** |
| 106971 | 107042 | ** The stat_get(P,J) function is not available to generic SQL. It is |
| 106972 | 107043 | ** inserted as part of a manually constructed bytecode program. (See |
| 106973 | 107044 | ** 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 | |
| 106975 | 107046 | ** NULL. |
| 106976 | 107047 | ** |
| 106977 | 107048 | ** If STAT4 is not enabled, then J is always |
| 106978 | 107049 | ** STAT_GET_STAT1 and is hence omitted and this routine becomes |
| 106979 | 107050 | ** a one-parameter function, stat_get(P), that always returns the |
| @@ -106982,11 +107053,11 @@ | ||
| 106982 | 107053 | static void statGet( |
| 106983 | 107054 | sqlite3_context *context, |
| 106984 | 107055 | int argc, |
| 106985 | 107056 | sqlite3_value **argv |
| 106986 | 107057 | ){ |
| 106987 | - Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]); | |
| 107058 | + StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]); | |
| 106988 | 107059 | #ifdef SQLITE_ENABLE_STAT4 |
| 106989 | 107060 | /* STAT4 has a parameter on this routine. */ |
| 106990 | 107061 | int eCall = sqlite3_value_int(argv[1]); |
| 106991 | 107062 | assert( argc==2 ); |
| 106992 | 107063 | assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ |
| @@ -107003,11 +107074,11 @@ | ||
| 107003 | 107074 | ** |
| 107004 | 107075 | ** The value is a string composed of a list of integers describing |
| 107005 | 107076 | ** the index. The first integer in the list is the total number of |
| 107006 | 107077 | ** entries in the index. There is one additional integer in the list |
| 107007 | 107078 | ** 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 | |
| 107009 | 107080 | ** a key with the corresponding number of fields. In other words, |
| 107010 | 107081 | ** if the index is on columns (a,b) and the sqlite_stat1 value is |
| 107011 | 107082 | ** "100 10 2", then SQLite estimates that: |
| 107012 | 107083 | ** |
| 107013 | 107084 | ** * the index contains 100 rows, |
| @@ -107046,11 +107117,11 @@ | ||
| 107046 | 107117 | if( p->iGet<0 ){ |
| 107047 | 107118 | samplePushPrevious(p, 0); |
| 107048 | 107119 | p->iGet = 0; |
| 107049 | 107120 | } |
| 107050 | 107121 | if( p->iGet<p->nSample ){ |
| 107051 | - Stat4Sample *pS = p->a + p->iGet; | |
| 107122 | + StatSample *pS = p->a + p->iGet; | |
| 107052 | 107123 | if( pS->nRowid==0 ){ |
| 107053 | 107124 | sqlite3_result_int64(context, pS->u.iRowid); |
| 107054 | 107125 | }else{ |
| 107055 | 107126 | sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid, |
| 107056 | 107127 | SQLITE_TRANSIENT); |
| @@ -107137,11 +107208,11 @@ | ||
| 107137 | 107208 | int i; /* Loop counter */ |
| 107138 | 107209 | int jZeroRows = -1; /* Jump from here if number of rows is zero */ |
| 107139 | 107210 | int iDb; /* Index of database containing pTab */ |
| 107140 | 107211 | u8 needTableCnt = 1; /* True to count the table */ |
| 107141 | 107212 | 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 */ | |
| 107143 | 107214 | int regChng = iMem++; /* Index of changed index field */ |
| 107144 | 107215 | #ifdef SQLITE_ENABLE_STAT4 |
| 107145 | 107216 | int regRowid = iMem++; /* Rowid argument passed to stat_push() */ |
| 107146 | 107217 | #endif |
| 107147 | 107218 | int regTemp = iMem++; /* Temporary use register */ |
| @@ -108105,10 +108176,21 @@ | ||
| 108105 | 108176 | pExpr->op = TK_STRING; |
| 108106 | 108177 | } |
| 108107 | 108178 | } |
| 108108 | 108179 | return rc; |
| 108109 | 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 | +} | |
| 108110 | 108192 | |
| 108111 | 108193 | /* |
| 108112 | 108194 | ** An SQL user-function registered to do the work of an ATTACH statement. The |
| 108113 | 108195 | ** three arguments to the function come directly from an attach statement: |
| 108114 | 108196 | ** |
| @@ -108178,13 +108260,12 @@ | ||
| 108178 | 108260 | db->aLimit[SQLITE_LIMIT_ATTACHED] |
| 108179 | 108261 | ); |
| 108180 | 108262 | goto attach_error; |
| 108181 | 108263 | } |
| 108182 | 108264 | 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) ){ | |
| 108186 | 108267 | zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName); |
| 108187 | 108268 | goto attach_error; |
| 108188 | 108269 | } |
| 108189 | 108270 | } |
| 108190 | 108271 | |
| @@ -108333,11 +108414,11 @@ | ||
| 108333 | 108414 | |
| 108334 | 108415 | if( zName==0 ) zName = ""; |
| 108335 | 108416 | for(i=0; i<db->nDb; i++){ |
| 108336 | 108417 | pDb = &db->aDb[i]; |
| 108337 | 108418 | if( pDb->pBt==0 ) continue; |
| 108338 | - if( sqlite3StrICmp(pDb->zDbSName, zName)==0 ) break; | |
| 108419 | + if( sqlite3DbIsNamed(db, i, zName) ) break; | |
| 108339 | 108420 | } |
| 108340 | 108421 | |
| 108341 | 108422 | if( i>=db->nDb ){ |
| 108342 | 108423 | sqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName); |
| 108343 | 108424 | goto detach_error; |
| @@ -108524,24 +108605,25 @@ | ||
| 108524 | 108605 | SQLITE_PRIVATE int sqlite3FixSrcList( |
| 108525 | 108606 | DbFixer *pFix, /* Context of the fixation */ |
| 108526 | 108607 | SrcList *pList /* The Source list to check and modify */ |
| 108527 | 108608 | ){ |
| 108528 | 108609 | int i; |
| 108529 | - const char *zDb; | |
| 108530 | 108610 | struct SrcList_item *pItem; |
| 108611 | + sqlite3 *db = pFix->pParse->db; | |
| 108612 | + int iDb = sqlite3FindDbName(db, pFix->zDb); | |
| 108531 | 108613 | |
| 108532 | 108614 | if( NEVER(pList==0) ) return 0; |
| 108533 | - zDb = pFix->zDb; | |
| 108615 | + | |
| 108534 | 108616 | for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){ |
| 108535 | 108617 | if( pFix->bTemp==0 ){ |
| 108536 | - if( pItem->zDatabase && sqlite3StrICmp(pItem->zDatabase, zDb) ){ | |
| 108618 | + if( pItem->zDatabase && iDb!=sqlite3FindDbName(db, pItem->zDatabase) ){ | |
| 108537 | 108619 | sqlite3ErrorMsg(pFix->pParse, |
| 108538 | 108620 | "%s %T cannot reference objects in database %s", |
| 108539 | 108621 | pFix->zType, pFix->pName, pItem->zDatabase); |
| 108540 | 108622 | return 1; |
| 108541 | 108623 | } |
| 108542 | - sqlite3DbFree(pFix->pParse->db, pItem->zDatabase); | |
| 108624 | + sqlite3DbFree(db, pItem->zDatabase); | |
| 108543 | 108625 | pItem->zDatabase = 0; |
| 108544 | 108626 | pItem->pSchema = pFix->pSchema; |
| 108545 | 108627 | pItem->fg.fromDDL = 1; |
| 108546 | 108628 | } |
| 108547 | 108629 | #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) |
| @@ -109262,11 +109344,11 @@ | ||
| 109262 | 109344 | } |
| 109263 | 109345 | #endif |
| 109264 | 109346 | while(1){ |
| 109265 | 109347 | for(i=OMIT_TEMPDB; i<db->nDb; i++){ |
| 109266 | 109348 | 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) ){ | |
| 109268 | 109350 | assert( sqlite3SchemaMutexHeld(db, j, 0) ); |
| 109269 | 109351 | p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); |
| 109270 | 109352 | if( p ) return p; |
| 109271 | 109353 | } |
| 109272 | 109354 | } |
| @@ -109384,11 +109466,11 @@ | ||
| 109384 | 109466 | assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); |
| 109385 | 109467 | for(i=OMIT_TEMPDB; i<db->nDb; i++){ |
| 109386 | 109468 | int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ |
| 109387 | 109469 | Schema *pSchema = db->aDb[j].pSchema; |
| 109388 | 109470 | assert( pSchema ); |
| 109389 | - if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue; | |
| 109471 | + if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue; | |
| 109390 | 109472 | assert( sqlite3SchemaMutexHeld(db, j, 0) ); |
| 109391 | 109473 | p = sqlite3HashFind(&pSchema->idxHash, zName); |
| 109392 | 109474 | if( p ) break; |
| 109393 | 109475 | } |
| 109394 | 109476 | return p; |
| @@ -111103,10 +111185,36 @@ | ||
| 111103 | 111185 | if( pMod->pModule->iVersion<3 ) return 0; |
| 111104 | 111186 | if( pMod->pModule->xShadowName==0 ) return 0; |
| 111105 | 111187 | return pMod->pModule->xShadowName(zTail+1); |
| 111106 | 111188 | } |
| 111107 | 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 | + | |
| 111108 | 111216 | |
| 111109 | 111217 | /* |
| 111110 | 111218 | ** This routine is called to report the final ")" that terminates |
| 111111 | 111219 | ** a CREATE TABLE statement. |
| 111112 | 111220 | ** |
| @@ -111196,10 +111304,12 @@ | ||
| 111196 | 111304 | if( pParse->nErr ){ |
| 111197 | 111305 | /* If errors are seen, delete the CHECK constraints now, else they might |
| 111198 | 111306 | ** actually be used if PRAGMA writable_schema=ON is set. */ |
| 111199 | 111307 | sqlite3ExprListDelete(db, p->pCheck); |
| 111200 | 111308 | p->pCheck = 0; |
| 111309 | + }else{ | |
| 111310 | + markExprListImmutable(p->pCheck); | |
| 111201 | 111311 | } |
| 111202 | 111312 | } |
| 111203 | 111313 | #endif /* !defined(SQLITE_OMIT_CHECK) */ |
| 111204 | 111314 | #ifndef SQLITE_OMIT_GENERATED_COLUMNS |
| 111205 | 111315 | if( p->tabFlags & TF_HasGenerated ){ |
| @@ -114137,20 +114247,33 @@ | ||
| 114137 | 114247 | u8 enc, /* Desired text encoding */ |
| 114138 | 114248 | const char *zName, /* Name of the collating sequence. Might be NULL */ |
| 114139 | 114249 | int create /* True to create CollSeq if doesn't already exist */ |
| 114140 | 114250 | ){ |
| 114141 | 114251 | CollSeq *pColl; |
| 114252 | + assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); | |
| 114253 | + assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE ); | |
| 114142 | 114254 | if( zName ){ |
| 114143 | 114255 | pColl = findCollSeqEntry(db, zName, create); |
| 114256 | + if( pColl ) pColl += enc-1; | |
| 114144 | 114257 | }else{ |
| 114145 | 114258 | pColl = db->pDfltColl; |
| 114146 | 114259 | } |
| 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 | 114260 | return pColl; |
| 114151 | 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 | +} | |
| 114152 | 114275 | |
| 114153 | 114276 | /* |
| 114154 | 114277 | ** This function is responsible for invoking the collation factory callback |
| 114155 | 114278 | ** or substituting a collation sequence of a different encoding when the |
| 114156 | 114279 | ** requested collation sequence is not available in the desired encoding. |
| @@ -116321,10 +116444,11 @@ | ||
| 116321 | 116444 | const unsigned char *zA, *zB; |
| 116322 | 116445 | u32 escape; |
| 116323 | 116446 | int nPat; |
| 116324 | 116447 | sqlite3 *db = sqlite3_context_db_handle(context); |
| 116325 | 116448 | struct compareInfo *pInfo = sqlite3_user_data(context); |
| 116449 | + struct compareInfo backupInfo; | |
| 116326 | 116450 | |
| 116327 | 116451 | #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS |
| 116328 | 116452 | if( sqlite3_value_type(argv[0])==SQLITE_BLOB |
| 116329 | 116453 | || sqlite3_value_type(argv[1])==SQLITE_BLOB |
| 116330 | 116454 | ){ |
| @@ -116356,10 +116480,16 @@ | ||
| 116356 | 116480 | sqlite3_result_error(context, |
| 116357 | 116481 | "ESCAPE expression must be a single character", -1); |
| 116358 | 116482 | return; |
| 116359 | 116483 | } |
| 116360 | 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 | + } | |
| 116361 | 116491 | }else{ |
| 116362 | 116492 | escape = pInfo->matchSet; |
| 116363 | 116493 | } |
| 116364 | 116494 | zB = sqlite3_value_text(argv[0]); |
| 116365 | 116495 | zA = sqlite3_value_text(argv[1]); |
| @@ -117338,29 +117468,33 @@ | ||
| 117338 | 117468 | if( pDef==0 ) return 0; |
| 117339 | 117469 | #endif |
| 117340 | 117470 | if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){ |
| 117341 | 117471 | return 0; |
| 117342 | 117472 | } |
| 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 | 117473 | |
| 117354 | 117474 | /* The memcpy() statement assumes that the wildcard characters are |
| 117355 | 117475 | ** the first three statements in the compareInfo structure. The |
| 117356 | 117476 | ** asserts() that follow verify that assumption |
| 117357 | 117477 | */ |
| 117358 | 117478 | memcpy(aWc, pDef->pUserData, 3); |
| 117359 | 117479 | assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll ); |
| 117360 | 117480 | assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne ); |
| 117361 | 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 | + | |
| 117362 | 117496 | *pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0; |
| 117363 | 117497 | return 1; |
| 117364 | 117498 | } |
| 117365 | 117499 | |
| 117366 | 117500 | /* |
| @@ -120564,11 +120698,11 @@ | ||
| 120564 | 120698 | case OE_Replace: { |
| 120565 | 120699 | int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg); |
| 120566 | 120700 | VdbeCoverage(v); |
| 120567 | 120701 | assert( (pCol->colFlags & COLFLAG_GENERATED)==0 ); |
| 120568 | 120702 | nSeenReplace++; |
| 120569 | - sqlite3ExprCode(pParse, pCol->pDflt, iReg); | |
| 120703 | + sqlite3ExprCodeCopy(pParse, pCol->pDflt, iReg); | |
| 120570 | 120704 | sqlite3VdbeJumpHere(v, addr1); |
| 120571 | 120705 | break; |
| 120572 | 120706 | } |
| 120573 | 120707 | case OE_Abort: |
| 120574 | 120708 | sqlite3MayAbort(pParse); |
| @@ -120619,10 +120753,11 @@ | ||
| 120619 | 120753 | ExprList *pCheck = pTab->pCheck; |
| 120620 | 120754 | pParse->iSelfTab = -(regNewData+1); |
| 120621 | 120755 | onError = overrideError!=OE_Default ? overrideError : OE_Abort; |
| 120622 | 120756 | for(i=0; i<pCheck->nExpr; i++){ |
| 120623 | 120757 | int allOk; |
| 120758 | + Expr *pCopy; | |
| 120624 | 120759 | Expr *pExpr = pCheck->a[i].pExpr; |
| 120625 | 120760 | if( aiChng |
| 120626 | 120761 | && !sqlite3ExprReferencesUpdatedColumn(pExpr, aiChng, pkChng) |
| 120627 | 120762 | ){ |
| 120628 | 120763 | /* The check constraints do not reference any of the columns being |
| @@ -120633,11 +120768,15 @@ | ||
| 120633 | 120768 | sqlite3TableAffinity(v, pTab, regNewData+1); |
| 120634 | 120769 | bAffinityDone = 1; |
| 120635 | 120770 | } |
| 120636 | 120771 | allOk = sqlite3VdbeMakeLabel(pParse); |
| 120637 | 120772 | 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); | |
| 120639 | 120778 | if( onError==OE_Ignore ){ |
| 120640 | 120779 | sqlite3VdbeGoto(v, ignoreDest); |
| 120641 | 120780 | }else{ |
| 120642 | 120781 | char *zName = pCheck->a[i].zEName; |
| 120643 | 120782 | if( zName==0 ) zName = pTab->zName; |
| @@ -125952,25 +126091,16 @@ | ||
| 125952 | 126091 | /* Only change the value of sqlite.enc if the database handle is not |
| 125953 | 126092 | ** initialized. If the main database exists, the new sqlite.enc value |
| 125954 | 126093 | ** will be overwritten when the schema is next loaded. If it does not |
| 125955 | 126094 | ** already exists, it will be created to use the new encoding value. |
| 125956 | 126095 | */ |
| 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 ){ | |
| 125968 | 126097 | for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ |
| 125969 | 126098 | 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); | |
| 125972 | 126102 | break; |
| 125973 | 126103 | } |
| 125974 | 126104 | } |
| 125975 | 126105 | if( !pEnc->zName ){ |
| 125976 | 126106 | sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight); |
| @@ -126775,11 +126905,11 @@ | ||
| 126775 | 126905 | int iDb = pData->iDb; |
| 126776 | 126906 | |
| 126777 | 126907 | assert( argc==5 ); |
| 126778 | 126908 | UNUSED_PARAMETER2(NotUsed, argc); |
| 126779 | 126909 | assert( sqlite3_mutex_held(db->mutex) ); |
| 126780 | - DbClearProperty(db, iDb, DB_Empty); | |
| 126910 | + db->mDbFlags |= DBFLAG_EncodingFixed; | |
| 126781 | 126911 | pData->nInitRow++; |
| 126782 | 126912 | if( db->mallocFailed ){ |
| 126783 | 126913 | corruptSchema(pData, argv[1], 0); |
| 126784 | 126914 | return 1; |
| 126785 | 126915 | } |
| @@ -126863,10 +126993,11 @@ | ||
| 126863 | 126993 | char const *azArg[6]; |
| 126864 | 126994 | int meta[5]; |
| 126865 | 126995 | InitData initData; |
| 126866 | 126996 | const char *zMasterName; |
| 126867 | 126997 | int openedTransaction = 0; |
| 126998 | + int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed); | |
| 126868 | 126999 | |
| 126869 | 127000 | assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 ); |
| 126870 | 127001 | assert( iDb>=0 && iDb<db->nDb ); |
| 126871 | 127002 | assert( db->aDb[iDb].pSchema ); |
| 126872 | 127003 | assert( sqlite3_mutex_held(db->mutex) ); |
| @@ -126891,10 +127022,11 @@ | ||
| 126891 | 127022 | initData.rc = SQLITE_OK; |
| 126892 | 127023 | initData.pzErrMsg = pzErrMsg; |
| 126893 | 127024 | initData.mInitFlags = mFlags; |
| 126894 | 127025 | initData.nInitRow = 0; |
| 126895 | 127026 | sqlite3InitCallback(&initData, 5, (char **)azArg, 0); |
| 127027 | + db->mDbFlags &= mask; | |
| 126896 | 127028 | if( initData.rc ){ |
| 126897 | 127029 | rc = initData.rc; |
| 126898 | 127030 | goto error_out; |
| 126899 | 127031 | } |
| 126900 | 127032 | |
| @@ -126950,31 +127082,29 @@ | ||
| 126950 | 127082 | ** main database, set sqlite3.enc to the encoding of the main database. |
| 126951 | 127083 | ** For an attached db, it is an error if the encoding is not the same |
| 126952 | 127084 | ** as sqlite3.enc. |
| 126953 | 127085 | */ |
| 126954 | 127086 | 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 ){ | |
| 126957 | 127088 | u8 encoding; |
| 127089 | +#ifndef SQLITE_OMIT_UTF16 | |
| 126958 | 127090 | /* If opening the main database, set ENC(db). */ |
| 126959 | 127091 | encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3; |
| 126960 | 127092 | if( encoding==0 ) encoding = SQLITE_UTF8; |
| 126961 | - ENC(db) = encoding; | |
| 126962 | 127093 | #else |
| 126963 | - ENC(db) = SQLITE_UTF8; | |
| 127094 | + encoding = SQLITE_UTF8; | |
| 126964 | 127095 | #endif |
| 127096 | + sqlite3SetTextEncoding(db, encoding); | |
| 126965 | 127097 | }else{ |
| 126966 | 127098 | /* 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) ){ | |
| 126968 | 127100 | sqlite3SetString(pzErrMsg, db, "attached databases must use the same" |
| 126969 | 127101 | " text encoding as main database"); |
| 126970 | 127102 | rc = SQLITE_ERROR; |
| 126971 | 127103 | goto initone_error_out; |
| 126972 | 127104 | } |
| 126973 | 127105 | } |
| 126974 | - }else{ | |
| 126975 | - DbSetProperty(db, iDb, DB_Empty); | |
| 126976 | 127106 | } |
| 126977 | 127107 | pDb->pSchema->enc = ENC(db); |
| 126978 | 127108 | |
| 126979 | 127109 | if( pDb->pSchema->cache_size==0 ){ |
| 126980 | 127110 | #ifndef SQLITE_OMIT_DEPRECATED |
| @@ -127082,12 +127212,11 @@ | ||
| 127082 | 127212 | ** used to store temporary tables, and any additional database files |
| 127083 | 127213 | ** created using ATTACH statements. Return a success code. If an |
| 127084 | 127214 | ** error occurs, write an error message into *pzErrMsg. |
| 127085 | 127215 | ** |
| 127086 | 127216 | ** 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. | |
| 127089 | 127218 | */ |
| 127090 | 127219 | SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){ |
| 127091 | 127220 | int i, rc; |
| 127092 | 127221 | int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange); |
| 127093 | 127222 | |
| @@ -127719,11 +127848,10 @@ | ||
| 127719 | 127848 | sqlite3ExprDelete(db, p->pLimit); |
| 127720 | 127849 | #ifndef SQLITE_OMIT_WINDOWFUNC |
| 127721 | 127850 | if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){ |
| 127722 | 127851 | sqlite3WindowListDelete(db, p->pWinDefn); |
| 127723 | 127852 | } |
| 127724 | - assert( p->pWin==0 ); | |
| 127725 | 127853 | #endif |
| 127726 | 127854 | if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); |
| 127727 | 127855 | if( bFree ) sqlite3DbFreeNN(db, p); |
| 127728 | 127856 | p = pPrior; |
| 127729 | 127857 | bFree = 1; |
| @@ -131197,10 +131325,42 @@ | ||
| 131197 | 131325 | } |
| 131198 | 131326 | }while( doPrior && (p = p->pPrior)!=0 ); |
| 131199 | 131327 | } |
| 131200 | 131328 | #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ |
| 131201 | 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 | + | |
| 131202 | 131362 | #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) |
| 131203 | 131363 | /* |
| 131204 | 131364 | ** This routine attempts to flatten subqueries as a performance optimization. |
| 131205 | 131365 | ** This routine returns 1 if it makes changes and 0 if no flattening occurs. |
| 131206 | 131366 | ** |
| @@ -131735,10 +131895,16 @@ | ||
| 131735 | 131895 | */ |
| 131736 | 131896 | if( pSub->pLimit ){ |
| 131737 | 131897 | pParent->pLimit = pSub->pLimit; |
| 131738 | 131898 | pSub->pLimit = 0; |
| 131739 | 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 | + } | |
| 131740 | 131906 | } |
| 131741 | 131907 | |
| 131742 | 131908 | /* Finially, delete what is left of the subquery and return |
| 131743 | 131909 | ** success. |
| 131744 | 131910 | */ |
| @@ -135184,11 +135350,11 @@ | ||
| 135184 | 135350 | zDb = pName->a[0].zDatabase; |
| 135185 | 135351 | zName = pName->a[0].zName; |
| 135186 | 135352 | assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); |
| 135187 | 135353 | for(i=OMIT_TEMPDB; i<db->nDb; i++){ |
| 135188 | 135354 | 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; | |
| 135190 | 135356 | assert( sqlite3SchemaMutexHeld(db, j, 0) ); |
| 135191 | 135357 | pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName); |
| 135192 | 135358 | if( pTrigger ) break; |
| 135193 | 135359 | } |
| 135194 | 135360 | if( !pTrigger ){ |
| @@ -141206,10 +141372,13 @@ | ||
| 141206 | 141372 | assert( pRangeEnd==0 && pRangeStart==0 ); |
| 141207 | 141373 | testcase( pLoop->nSkip>0 ); |
| 141208 | 141374 | nExtraReg = 1; |
| 141209 | 141375 | bSeekPastNull = 1; |
| 141210 | 141376 | pLevel->regBignull = regBignull = ++pParse->nMem; |
| 141377 | + if( pLevel->iLeftJoin ){ | |
| 141378 | + sqlite3VdbeAddOp2(v, OP_Integer, 0, regBignull); | |
| 141379 | + } | |
| 141211 | 141380 | pLevel->addrBignull = sqlite3VdbeMakeLabel(pParse); |
| 141212 | 141381 | } |
| 141213 | 141382 | |
| 141214 | 141383 | /* If we are doing a reverse order scan on an ascending index, or |
| 141215 | 141384 | ** a forward order scan on a descending index, interchange the |
| @@ -148759,11 +148928,11 @@ | ||
| 148759 | 148928 | && (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0 |
| 148760 | 148929 | && (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0 |
| 148761 | 148930 | && (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 |
| 148762 | 148931 | && pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED |
| 148763 | 148932 | ){ |
| 148764 | - sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */ | |
| 148933 | + sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); | |
| 148765 | 148934 | } |
| 148766 | 148935 | VdbeComment((v, "%s", pIx->zName)); |
| 148767 | 148936 | #ifdef SQLITE_ENABLE_COLUMN_USED_MASK |
| 148768 | 148937 | { |
| 148769 | 148938 | u64 colUsed = 0; |
| @@ -148917,16 +149086,10 @@ | ||
| 148917 | 149086 | for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ |
| 148918 | 149087 | sqlite3VdbeJumpHere(v, pIn->addrInTop+1); |
| 148919 | 149088 | if( pIn->eEndLoopOp!=OP_Noop ){ |
| 148920 | 149089 | if( pIn->nPrefix ){ |
| 148921 | 149090 | 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 | 149091 | if( pLevel->iLeftJoin ){ |
| 148929 | 149092 | /* For LEFT JOIN queries, cursor pIn->iCur may not have been |
| 148930 | 149093 | ** opened yet. This occurs for WHERE clauses such as |
| 148931 | 149094 | ** "a = ? AND b IN (...)", where the index is on (a, b). If |
| 148932 | 149095 | ** the RHS of the (a=?) is NULL, then the "b IN (...)" may |
| @@ -148933,13 +149096,20 @@ | ||
| 148933 | 149096 | ** never have been coded, but the body of the loop run to |
| 148934 | 149097 | ** return the null-row. So, if the cursor is not open yet, |
| 148935 | 149098 | ** jump over the OP_Next or OP_Prev instruction about to |
| 148936 | 149099 | ** be coded. */ |
| 148937 | 149100 | sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur, |
| 148938 | - sqlite3VdbeCurrentAddr(v) + 2 | |
| 149101 | + sqlite3VdbeCurrentAddr(v) + 2 + | |
| 149102 | + ((pLoop->wsFlags & WHERE_VIRTUALTABLE)==0) | |
| 148939 | 149103 | ); |
| 148940 | 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); | |
| 148941 | 149111 | } |
| 148942 | 149112 | } |
| 148943 | 149113 | sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); |
| 148944 | 149114 | VdbeCoverage(v); |
| 148945 | 149115 | VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Prev); |
| @@ -150053,10 +150223,11 @@ | ||
| 150053 | 150223 | |
| 150054 | 150224 | ExprList *pSublist = 0; /* Expression list for sub-query */ |
| 150055 | 150225 | Window *pMWin = p->pWin; /* Master window object */ |
| 150056 | 150226 | Window *pWin; /* Window object iterator */ |
| 150057 | 150227 | Table *pTab; |
| 150228 | + u32 selFlags = p->selFlags; | |
| 150058 | 150229 | |
| 150059 | 150230 | pTab = sqlite3DbMallocZero(db, sizeof(Table)); |
| 150060 | 150231 | if( pTab==0 ){ |
| 150061 | 150232 | return sqlite3ErrorToParser(db, SQLITE_NOMEM); |
| 150062 | 150233 | } |
| @@ -150142,10 +150313,11 @@ | ||
| 150142 | 150313 | Table *pTab2; |
| 150143 | 150314 | p->pSrc->a[0].pSelect = pSub; |
| 150144 | 150315 | sqlite3SrcListAssignCursors(pParse, p->pSrc); |
| 150145 | 150316 | pSub->selFlags |= SF_Expanded; |
| 150146 | 150317 | pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE); |
| 150318 | + pSub->selFlags |= (selFlags & SF_Aggregate); | |
| 150147 | 150319 | if( pTab2==0 ){ |
| 150148 | 150320 | /* Might actually be some other kind of error, but in that case |
| 150149 | 150321 | ** pParse->nErr will be set, so if SQLITE_NOMEM is set, we will get |
| 150150 | 150322 | ** the correct error message regardless. */ |
| 150151 | 150323 | rc = SQLITE_NOMEM; |
| @@ -151030,10 +151202,11 @@ | ||
| 151030 | 151202 | int regArg; |
| 151031 | 151203 | int nArg = 0; |
| 151032 | 151204 | Window *pWin; |
| 151033 | 151205 | for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ |
| 151034 | 151206 | FuncDef *pFunc = pWin->pFunc; |
| 151207 | + assert( pWin->regAccum ); | |
| 151035 | 151208 | sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); |
| 151036 | 151209 | nArg = MAX(nArg, windowArgCount(pWin)); |
| 151037 | 151210 | if( pMWin->regStartRowid==0 ){ |
| 151038 | 151211 | if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){ |
| 151039 | 151212 | sqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp); |
| @@ -151408,10 +151581,14 @@ | ||
| 151408 | 151581 | pNew->eFrmType = p->eFrmType; |
| 151409 | 151582 | pNew->eEnd = p->eEnd; |
| 151410 | 151583 | pNew->eStart = p->eStart; |
| 151411 | 151584 | pNew->eExclude = p->eExclude; |
| 151412 | 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; | |
| 151413 | 151590 | pNew->pStart = sqlite3ExprDup(db, p->pStart, 0); |
| 151414 | 151591 | pNew->pEnd = sqlite3ExprDup(db, p->pEnd, 0); |
| 151415 | 151592 | pNew->pOwner = pOwner; |
| 151416 | 151593 | pNew->bImplicitFrame = p->bImplicitFrame; |
| 151417 | 151594 | } |
| @@ -152245,10 +152422,11 @@ | ||
| 152245 | 152422 | if( p ){ |
| 152246 | 152423 | /* memset(p, 0, sizeof(Expr)); */ |
| 152247 | 152424 | p->op = (u8)op; |
| 152248 | 152425 | p->affExpr = 0; |
| 152249 | 152426 | p->flags = EP_Leaf; |
| 152427 | + ExprClearVVAProperties(p); | |
| 152250 | 152428 | p->iAgg = -1; |
| 152251 | 152429 | p->pLeft = p->pRight = 0; |
| 152252 | 152430 | p->x.pList = 0; |
| 152253 | 152431 | p->pAggInfo = 0; |
| 152254 | 152432 | p->y.pTab = 0; |
| @@ -162084,15 +162262,10 @@ | ||
| 162084 | 162262 | createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); |
| 162085 | 162263 | createCollation(db, "RTRIM", SQLITE_UTF8, 0, rtrimCollFunc, 0); |
| 162086 | 162264 | if( db->mallocFailed ){ |
| 162087 | 162265 | goto opendb_out; |
| 162088 | 162266 | } |
| 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 | 162267 | |
| 162095 | 162268 | /* Parse the filename/URI argument |
| 162096 | 162269 | ** |
| 162097 | 162270 | ** Only allow sensible combinations of bits in the flags argument. |
| 162098 | 162271 | ** Throw an error if any non-sense combination is used. If we |
| @@ -162133,11 +162306,13 @@ | ||
| 162133 | 162306 | sqlite3Error(db, rc); |
| 162134 | 162307 | goto opendb_out; |
| 162135 | 162308 | } |
| 162136 | 162309 | sqlite3BtreeEnter(db->aDb[0].pBt); |
| 162137 | 162310 | 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 | + } | |
| 162139 | 162314 | sqlite3BtreeLeave(db->aDb[0].pBt); |
| 162140 | 162315 | db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); |
| 162141 | 162316 | |
| 162142 | 162317 | /* The default safety_level for the main database is FULL; for the temp |
| 162143 | 162318 | ** database it is OFF. This matches the pager layer defaults. |
| @@ -166664,10 +166839,11 @@ | ||
| 166664 | 166839 | const char *zEnd = &zCsr[nNode];/* End of interior node buffer */ |
| 166665 | 166840 | char *zBuffer = 0; /* Buffer to load terms into */ |
| 166666 | 166841 | i64 nAlloc = 0; /* Size of allocated buffer */ |
| 166667 | 166842 | int isFirstTerm = 1; /* True when processing first term on page */ |
| 166668 | 166843 | sqlite3_int64 iChild; /* Block id of child node to descend to */ |
| 166844 | + int nBuffer = 0; /* Total term size */ | |
| 166669 | 166845 | |
| 166670 | 166846 | /* Skip over the 'height' varint that occurs at the start of every |
| 166671 | 166847 | ** interior node. Then load the blockid of the left-child of the b-tree |
| 166672 | 166848 | ** node into variable iChild. |
| 166673 | 166849 | ** |
| @@ -166688,16 +166864,19 @@ | ||
| 166688 | 166864 | |
| 166689 | 166865 | while( zCsr<zEnd && (piFirst || piLast) ){ |
| 166690 | 166866 | int cmp; /* memcmp() result */ |
| 166691 | 166867 | int nSuffix; /* Size of term suffix */ |
| 166692 | 166868 | int nPrefix = 0; /* Size of term prefix */ |
| 166693 | - int nBuffer; /* Total term size */ | |
| 166694 | 166869 | |
| 166695 | 166870 | /* Load the next term on the node into zBuffer. Use realloc() to expand |
| 166696 | 166871 | ** the size of zBuffer if required. */ |
| 166697 | 166872 | if( !isFirstTerm ){ |
| 166698 | 166873 | zCsr += fts3GetVarint32(zCsr, &nPrefix); |
| 166874 | + if( nPrefix>nBuffer ){ | |
| 166875 | + rc = FTS_CORRUPT_VTAB; | |
| 166876 | + goto finish_scan; | |
| 166877 | + } | |
| 166699 | 166878 | } |
| 166700 | 166879 | isFirstTerm = 0; |
| 166701 | 166880 | zCsr += fts3GetVarint32(zCsr, &nSuffix); |
| 166702 | 166881 | |
| 166703 | 166882 | assert( nPrefix>=0 && nSuffix>=0 ); |
| @@ -179916,10 +180095,16 @@ | ||
| 179916 | 180095 | |
| 179917 | 180096 | /* If nSeg is less that zero, then there is no level with at least |
| 179918 | 180097 | ** nMin segments and no hint in the %_stat table. No work to do. |
| 179919 | 180098 | ** Exit early in this case. */ |
| 179920 | 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 | + } | |
| 179921 | 180106 | |
| 179922 | 180107 | /* Open a cursor to iterate through the contents of the oldest nSeg |
| 179923 | 180108 | ** indexes of absolute level iAbsLevel. If this cursor is opened using |
| 179924 | 180109 | ** the 'hint' parameters, it is possible that there are less than nSeg |
| 179925 | 180110 | ** segments available in level iAbsLevel. In this case, no work is |
| @@ -189652,12 +189837,14 @@ | ||
| 189652 | 189837 | pRtree->nAux++; |
| 189653 | 189838 | sqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1); |
| 189654 | 189839 | }else if( pRtree->nAux>0 ){ |
| 189655 | 189840 | break; |
| 189656 | 189841 | }else{ |
| 189842 | + static const char *azFormat[] = {",%.*s REAL", ",%.*s INT"}; | |
| 189657 | 189843 | pRtree->nDim2++; |
| 189658 | - sqlite3_str_appendf(pSql, ",%.*s NUM", rtreeTokenLength(zArg), zArg); | |
| 189844 | + sqlite3_str_appendf(pSql, azFormat[eCoordType], | |
| 189845 | + rtreeTokenLength(zArg), zArg); | |
| 189659 | 189846 | } |
| 189660 | 189847 | } |
| 189661 | 189848 | sqlite3_str_appendf(pSql, ");"); |
| 189662 | 189849 | zSql = sqlite3_str_finish(pSql); |
| 189663 | 189850 | if( !zSql ){ |
| @@ -192389,11 +192576,11 @@ | ||
| 192389 | 192576 | ** 1. uPattern is an unescaped match-all character "%", |
| 192390 | 192577 | ** 2. uPattern is an unescaped match-one character "_", |
| 192391 | 192578 | ** 3. uPattern is an unescaped escape character, or |
| 192392 | 192579 | ** 4. uPattern is to be handled as an ordinary character |
| 192393 | 192580 | */ |
| 192394 | - if( !prevEscape && uPattern==MATCH_ALL ){ | |
| 192581 | + if( uPattern==MATCH_ALL && !prevEscape && uPattern!=(uint32_t)uEsc ){ | |
| 192395 | 192582 | /* Case 1. */ |
| 192396 | 192583 | uint8_t c; |
| 192397 | 192584 | |
| 192398 | 192585 | /* Skip any MATCH_ALL or MATCH_ONE characters that follow a |
| 192399 | 192586 | ** MATCH_ALL. For each MATCH_ONE, skip one character in the |
| @@ -192415,16 +192602,16 @@ | ||
| 192415 | 192602 | } |
| 192416 | 192603 | SQLITE_ICU_SKIP_UTF8(zString); |
| 192417 | 192604 | } |
| 192418 | 192605 | return 0; |
| 192419 | 192606 | |
| 192420 | - }else if( !prevEscape && uPattern==MATCH_ONE ){ | |
| 192607 | + }else if( uPattern==MATCH_ONE && !prevEscape && uPattern!=(uint32_t)uEsc ){ | |
| 192421 | 192608 | /* Case 2. */ |
| 192422 | 192609 | if( *zString==0 ) return 0; |
| 192423 | 192610 | SQLITE_ICU_SKIP_UTF8(zString); |
| 192424 | 192611 | |
| 192425 | - }else if( !prevEscape && uPattern==(uint32_t)uEsc){ | |
| 192612 | + }else if( uPattern==(uint32_t)uEsc && !prevEscape ){ | |
| 192426 | 192613 | /* Case 3. */ |
| 192427 | 192614 | prevEscape = 1; |
| 192428 | 192615 | |
| 192429 | 192616 | }else{ |
| 192430 | 192617 | /* Case 4. */ |
| @@ -199222,10 +199409,11 @@ | ||
| 199222 | 199409 | } |
| 199223 | 199410 | } |
| 199224 | 199411 | i = 0; |
| 199225 | 199412 | if( iSchema>=0 ){ |
| 199226 | 199413 | pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i; |
| 199414 | + pIdxInfo->aConstraintUsage[iSchema].omit = 1; | |
| 199227 | 199415 | pIdxInfo->idxNum |= 0x01; |
| 199228 | 199416 | } |
| 199229 | 199417 | if( iName>=0 ){ |
| 199230 | 199418 | pIdxInfo->aConstraintUsage[iName].argvIndex = ++i; |
| 199231 | 199419 | pIdxInfo->idxNum |= 0x02; |
| @@ -199436,11 +199624,13 @@ | ||
| 199436 | 199624 | assert( nPayload>=(u32)nLocal ); |
| 199437 | 199625 | assert( nLocal<=(nUsable-35) ); |
| 199438 | 199626 | if( nPayload>(u32)nLocal ){ |
| 199439 | 199627 | int j; |
| 199440 | 199628 | 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 | + } | |
| 199442 | 199632 | pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4); |
| 199443 | 199633 | pCell->nOvfl = nOvfl; |
| 199444 | 199634 | pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl); |
| 199445 | 199635 | if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT; |
| 199446 | 199636 | pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]); |
| @@ -203769,11 +203959,11 @@ | ||
| 203769 | 203959 | const char *zSep = ""; |
| 203770 | 203960 | int rc = SQLITE_OK; |
| 203771 | 203961 | SessionBuffer buf = {0, 0, 0}; |
| 203772 | 203962 | int nPk = 0; |
| 203773 | 203963 | |
| 203774 | - sessionAppendStr(&buf, "DELETE FROM ", &rc); | |
| 203964 | + sessionAppendStr(&buf, "DELETE FROM main.", &rc); | |
| 203775 | 203965 | sessionAppendIdent(&buf, zTab, &rc); |
| 203776 | 203966 | sessionAppendStr(&buf, " WHERE ", &rc); |
| 203777 | 203967 | |
| 203778 | 203968 | for(i=0; i<p->nCol; i++){ |
| 203779 | 203969 | if( p->abPK[i] ){ |
| @@ -203852,11 +204042,11 @@ | ||
| 203852 | 204042 | int i; |
| 203853 | 204043 | const char *zSep = ""; |
| 203854 | 204044 | SessionBuffer buf = {0, 0, 0}; |
| 203855 | 204045 | |
| 203856 | 204046 | /* Append "UPDATE tbl SET " */ |
| 203857 | - sessionAppendStr(&buf, "UPDATE ", &rc); | |
| 204047 | + sessionAppendStr(&buf, "UPDATE main.", &rc); | |
| 203858 | 204048 | sessionAppendIdent(&buf, zTab, &rc); |
| 203859 | 204049 | sessionAppendStr(&buf, " SET ", &rc); |
| 203860 | 204050 | |
| 203861 | 204051 | /* Append the assignments */ |
| 203862 | 204052 | for(i=0; i<p->nCol; i++){ |
| @@ -223538,11 +223728,11 @@ | ||
| 223538 | 223728 | int nArg, /* Number of args */ |
| 223539 | 223729 | sqlite3_value **apUnused /* Function arguments */ |
| 223540 | 223730 | ){ |
| 223541 | 223731 | assert( nArg==0 ); |
| 223542 | 223732 | 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); | |
| 223544 | 223734 | } |
| 223545 | 223735 | |
| 223546 | 223736 | /* |
| 223547 | 223737 | ** Return true if zName is the extension on one of the shadow tables used |
| 223548 | 223738 | ** by this module. |
| @@ -228320,12 +228510,12 @@ | ||
| 228320 | 228510 | } |
| 228321 | 228511 | #endif /* SQLITE_CORE */ |
| 228322 | 228512 | #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ |
| 228323 | 228513 | |
| 228324 | 228514 | /************** End of stmt.c ************************************************/ |
| 228325 | -#if __LINE__!=228325 | |
| 228515 | +#if __LINE__!=228515 | |
| 228326 | 228516 | #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" | |
| 228328 | 228518 | #endif |
| 228329 | 228519 | /* Return the source-id for this library */ |
| 228330 | 228520 | SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } |
| 228331 | 228521 | /************************** End of sqlite3.c ******************************/ |
| 228332 | 228522 |
| --- 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, ®Free1); |
| 102555 | r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free2); |
| 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, ®Free1); |
| 102618 | r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free2); |
| 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 @@ | ||
| 123 | 123 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 124 | 124 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 125 | 125 | */ |
| 126 | 126 | #define SQLITE_VERSION "3.32.0" |
| 127 | 127 | #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" | |
| 129 | 129 | |
| 130 | 130 | /* |
| 131 | 131 | ** CAPI3REF: Run-Time Library Version Numbers |
| 132 | 132 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 133 | 133 | ** |
| 134 | 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-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 @@ | ||
| 659 | 659 | /* |
| 660 | 660 | ** Gather statistics on artifact types, counts, and sizes. |
| 661 | 661 | ** |
| 662 | 662 | ** Only populate the artstat.atype field if the bWithTypes parameter is true. |
| 663 | 663 | */ |
| 664 | -static void gather_artifact_stats(int bWithTypes){ | |
| 664 | +void gather_artifact_stats(int bWithTypes){ | |
| 665 | 665 | static const char zSql[] = |
| 666 | 666 | @ CREATE TEMP TABLE artstat( |
| 667 | 667 | @ id INTEGER PRIMARY KEY, -- Corresponds to BLOB.RID |
| 668 | 668 | @ atype TEXT, -- 'data', 'manifest', 'tag', 'wiki', etc. |
| 669 | 669 | @ isDelta BOOLEAN, -- true if stored as a delta |
| @@ -896,12 +896,12 @@ | ||
| 896 | 896 | sqlite3_int64 szExp = db_column_int64(&q, 4); |
| 897 | 897 | @ <tr><td>%h(zType)</td> |
| 898 | 898 | @ <td data-sortkey='%08x(nTotal)' align='right'>%,d(nTotal)</td> |
| 899 | 899 | @ <td data-sortkey='%08x(nFull)' align='right'>%,d(nFull)</td> |
| 900 | 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> | |
| 901 | + @ <td data-sortkey='%016llx(szCmpr)' align='right'>%,lld(szCmpr)</td> | |
| 902 | + @ <td data-sortkey='%016llx(szExp)' align='right'>%,lld(szExp)</td> | |
| 903 | 903 | } |
| 904 | 904 | @ </tbody></table> |
| 905 | 905 | db_finalize(&q); |
| 906 | 906 | |
| 907 | 907 | if( db_exists("SELECT 1 FROM artstat WHERE atype='unused'") ){ |
| 908 | 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 | 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 @@ | ||
| 79 | 79 | /* |
| 80 | 80 | ** Ad-unit styles. |
| 81 | 81 | */ |
| 82 | 82 | static unsigned adUnitFlags = 0; |
| 83 | 83 | |
| 84 | +/* | |
| 85 | +** Submenu disable flag | |
| 86 | +*/ | |
| 87 | +static int submenuEnable = 1; | |
| 88 | + | |
| 84 | 89 | /* |
| 85 | 90 | ** Flags for various javascript files needed prior to </body> |
| 86 | 91 | */ |
| 87 | 92 | static int needHrefJs = 0; /* href.js */ |
| 88 | 93 | static int needSortJs = 0; /* sorttable.js */ |
| @@ -316,10 +321,17 @@ | ||
| 316 | 321 | aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL; |
| 317 | 322 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI; |
| 318 | 323 | nSubmenuCtrl++; |
| 319 | 324 | } |
| 320 | 325 | } |
| 326 | + | |
| 327 | +/* | |
| 328 | +** Disable or enable the submenu | |
| 329 | +*/ | |
| 330 | +void style_submenu_enable(int onOff){ | |
| 331 | + submenuEnable = onOff; | |
| 332 | +} | |
| 321 | 333 | |
| 322 | 334 | |
| 323 | 335 | /* |
| 324 | 336 | ** Compare two submenu items for sorting purposes |
| 325 | 337 | */ |
| @@ -774,11 +786,11 @@ | ||
| 774 | 786 | /* Go back and put the submenu at the top of the page. We delay the |
| 775 | 787 | ** creation of the submenu until the end so that we can add elements |
| 776 | 788 | ** to the submenu while generating page text. |
| 777 | 789 | */ |
| 778 | 790 | cgi_destination(CGI_HEADER); |
| 779 | - if( nSubmenu+nSubmenuCtrl>0 ){ | |
| 791 | + if( submenuEnable && nSubmenu+nSubmenuCtrl>0 ){ | |
| 780 | 792 | int i; |
| 781 | 793 | if( nSubmenuCtrl ){ |
| 782 | 794 | @ <form id='f01' method='GET' action='%R/%s(g.zPath)'> |
| 783 | 795 | @ <input type='hidden' name='udc' value='1'> |
| 784 | 796 | cgi_tag_query_parameter("udc"); |
| 785 | 797 |
| --- 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 @@ | ||
| 115 | 115 | #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */ |
| 116 | 116 | #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */ |
| 117 | 117 | #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */ |
| 118 | 118 | #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */ |
| 119 | 119 | #define TIMELINE_NOTKT 0x2000000 /* Omit extra ticket classes */ |
| 120 | +#define TIMELINE_FORUMTXT 0x4000000 /* Render all forum messages */ | |
| 120 | 121 | #endif |
| 121 | 122 | |
| 122 | 123 | /* |
| 123 | 124 | ** Hash a string and use the hash to determine a background color. |
| 124 | 125 | */ |
| @@ -233,11 +234,11 @@ | ||
| 233 | 234 | ** 2. Date/Time |
| 234 | 235 | ** 3. Comment string |
| 235 | 236 | ** 4. User |
| 236 | 237 | ** 5. True if is a leaf |
| 237 | 238 | ** 6. background color |
| 238 | -** 7. type ("ci", "w", "t", "e", "g", "div") | |
| 239 | +** 7. type ("ci", "w", "t", "e", "g", "f", "div") | |
| 239 | 240 | ** 8. list of symbolic tags. |
| 240 | 241 | ** 9. tagid for ticket or wiki or event |
| 241 | 242 | ** 10. Short comment to user for repeated tickets and wiki |
| 242 | 243 | */ |
| 243 | 244 | void www_print_timeline( |
| @@ -428,17 +429,17 @@ | ||
| 428 | 429 | zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", |
| 429 | 430 | tagid); |
| 430 | 431 | zDateLink = href("%R/technote/%s",zId); |
| 431 | 432 | free(zId); |
| 432 | 433 | }else{ |
| 433 | - zDateLink = href("%R/timeline?c=%t",zDate); | |
| 434 | + zDateLink = href("%R/timeline?c=%t&y=a",zDate); | |
| 434 | 435 | } |
| 435 | 436 | }else if( zUuid ){ |
| 436 | 437 | if( bTimestampLinksToInfo ){ |
| 437 | 438 | zDateLink = chref("timelineHistLink", "%R/info/%!S", zUuid); |
| 438 | 439 | }else{ |
| 439 | - zDateLink = chref("timelineHistLink", "%R/timeline?c=%!S", zUuid); | |
| 440 | + zDateLink = chref("timelineHistLink", "%R/timeline?c=%!S&y=a", zUuid); | |
| 440 | 441 | } |
| 441 | 442 | }else{ |
| 442 | 443 | zDateLink = mprintf("<a>"); |
| 443 | 444 | } |
| 444 | 445 | @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td> |
| @@ -785,10 +786,26 @@ | ||
| 785 | 786 | db_reset(&fchngQuery); |
| 786 | 787 | if( inUl ){ |
| 787 | 788 | @ </ul> |
| 788 | 789 | } |
| 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 | + } | |
| 790 | 807 | } |
| 791 | 808 | if( suppressCnt ){ |
| 792 | 809 | @ <span class="timelineDisabled">... %d(suppressCnt) similar |
| 793 | 810 | @ event%s(suppressCnt>1?"s":"") omitted.</span> |
| 794 | 811 | suppressCnt = 0; |
| @@ -1565,11 +1582,13 @@ | ||
| 1565 | 1582 | ** ss=VIEWSTYLE c: "Compact" v: "Verbose" m: "Modern" j: "Columnar" |
| 1566 | 1583 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1567 | 1584 | ** ng No Graph. |
| 1568 | 1585 | ** ncp Omit cherrypick merges |
| 1569 | 1586 | ** nd Do not highlight the focus check-in |
| 1587 | +** nsm Omit the submenu | |
| 1570 | 1588 | ** v Show details of files changed |
| 1589 | +** vfx Show complete text of forum messages | |
| 1571 | 1590 | ** f=CHECKIN Show family (immediate parents and children) of CHECKIN |
| 1572 | 1591 | ** from=CHECKIN Path from... |
| 1573 | 1592 | ** to=CHECKIN ... to this |
| 1574 | 1593 | ** shortest ... show only the shortest path |
| 1575 | 1594 | ** rel ... also show related checkins |
| @@ -1790,10 +1809,13 @@ | ||
| 1790 | 1809 | tmFlags &= ~TIMELINE_CHPICK; |
| 1791 | 1810 | } |
| 1792 | 1811 | if( PB("ng") || zSearch!=0 ){ |
| 1793 | 1812 | tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK); |
| 1794 | 1813 | } |
| 1814 | + if( PB("nsm") ){ | |
| 1815 | + style_submenu_enable(0); | |
| 1816 | + } | |
| 1795 | 1817 | if( PB("brbg") ){ |
| 1796 | 1818 | tmFlags |= TIMELINE_BRCOLOR; |
| 1797 | 1819 | } |
| 1798 | 1820 | if( PB("unhide") ){ |
| 1799 | 1821 | tmFlags |= TIMELINE_UNHIDE; |
| @@ -1874,10 +1896,13 @@ | ||
| 1874 | 1896 | blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1); |
| 1875 | 1897 | blob_append(&sql, timeline_query_for_www(), -1); |
| 1876 | 1898 | if( PB("fc") || PB("v") || PB("detail") ){ |
| 1877 | 1899 | tmFlags |= TIMELINE_FCHANGES; |
| 1878 | 1900 | } |
| 1901 | + if( PB("vfx") ){ | |
| 1902 | + tmFlags |= TIMELINE_FORUMTXT; | |
| 1903 | + } | |
| 1879 | 1904 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 1880 | 1905 | blob_append_sql(&sql, |
| 1881 | 1906 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 1882 | 1907 | " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", |
| 1883 | 1908 | TAG_HIDDEN |
| 1884 | 1909 |
| --- 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 @@ | ||
| 932 | 932 | |
| 933 | 933 | /* |
| 934 | 934 | ** WEBPAGE: tkthistory |
| 935 | 935 | ** URL: /tkthistory?name=TICKETUUID |
| 936 | 936 | ** |
| 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. | |
| 938 | 945 | */ |
| 939 | 946 | void tkthistory_page(void){ |
| 940 | 947 | Stmt q; |
| 941 | 948 | char *zTitle; |
| 942 | 949 | const char *zUuid; |
| @@ -952,23 +959,28 @@ | ||
| 952 | 959 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| 953 | 960 | style_submenu_element("Status", "%s/info/%s", g.zTop, zUuid); |
| 954 | 961 | style_submenu_element("Check-ins", "%s/tkttimeline?name=%s&y=ci", |
| 955 | 962 | g.zTop, zUuid); |
| 956 | 963 | 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); | |
| 961 | 968 | } |
| 962 | 969 | style_header("%z", zTitle); |
| 963 | 970 | |
| 964 | 971 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); |
| 965 | 972 | if( tagid==0 ){ |
| 966 | 973 | @ No such ticket: %h(zUuid) |
| 967 | 974 | style_footer(); |
| 968 | 975 | return; |
| 969 | 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 | + } | |
| 970 | 982 | db_prepare(&q, |
| 971 | 983 | "SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL" |
| 972 | 984 | " FROM event, blob" |
| 973 | 985 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 974 | 986 | " AND blob.rid=event.objid" |
| @@ -978,20 +990,19 @@ | ||
| 978 | 990 | " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" |
| 979 | 991 | " AND blob.rid=attachid" |
| 980 | 992 | " ORDER BY 1", |
| 981 | 993 | tagid, tagid |
| 982 | 994 | ); |
| 983 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 995 | + for(nChng=0; db_step(&q)==SQLITE_ROW; nChng++){ | |
| 984 | 996 | Manifest *pTicket; |
| 985 | 997 | const char *zDate = db_column_text(&q, 0); |
| 986 | 998 | int rid = db_column_int(&q, 1); |
| 987 | 999 | const char *zChngUuid = db_column_text(&q, 2); |
| 988 | 1000 | const char *zFile = db_column_text(&q, 4); |
| 989 | 1001 | if( nChng==0 ){ |
| 990 | 1002 | @ <ol> |
| 991 | 1003 | } |
| 992 | - nChng++; | |
| 993 | 1004 | if( zFile!=0 ){ |
| 994 | 1005 | const char *zSrc = db_column_text(&q, 3); |
| 995 | 1006 | const char *zUser = db_column_text(&q, 5); |
| 996 | 1007 | if( zSrc==0 || zSrc[0]==0 ){ |
| 997 | 1008 | @ |
| @@ -1013,11 +1024,20 @@ | ||
| 1013 | 1024 | @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>] |
| 1014 | 1025 | @ (rid %d(rid)) by |
| 1015 | 1026 | hyperlink_to_user(pTicket->zUser,zDate," on"); |
| 1016 | 1027 | hyperlink_to_date(zDate, ":"); |
| 1017 | 1028 | @ </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 | + } | |
| 1019 | 1039 | } |
| 1020 | 1040 | manifest_destroy(pTicket); |
| 1021 | 1041 | } |
| 1022 | 1042 | } |
| 1023 | 1043 | db_finalize(&q); |
| @@ -1041,37 +1061,45 @@ | ||
| 1041 | 1061 | |
| 1042 | 1062 | /* |
| 1043 | 1063 | ** The pTkt object is a ticket change artifact. Output a detailed |
| 1044 | 1064 | ** description of this object. |
| 1045 | 1065 | */ |
| 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 | +){ | |
| 1047 | 1071 | 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 | 1072 | if( zListType==0 ) zListType = "1"; |
| 1073 | + getAllTicketFields(); | |
| 1057 | 1074 | @ <ol type="%s(zListType)"> |
| 1058 | 1075 | for(i=0; i<pTkt->nField; i++){ |
| 1059 | 1076 | Blob val; |
| 1060 | - const char *z; | |
| 1077 | + const char *z, *zX; | |
| 1078 | + int id; | |
| 1061 | 1079 | z = pTkt->aField[i].zName; |
| 1062 | 1080 | 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> | |
| 1071 | 1099 | }else{ |
| 1072 | - @ <li>Change %h(z) to "%h(blob_str(&val))"</li> | |
| 1100 | + @ "%h(blob_str(&val))"</li> | |
| 1073 | 1101 | } |
| 1074 | 1102 | blob_reset(&val); |
| 1075 | 1103 | } |
| 1076 | 1104 | @ </ol> |
| 1077 | 1105 | } |
| 1078 | 1106 |
| --- 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 @@ | ||
| 178 | 178 | @ DROP TABLE IF EXISTS undo_vfile; |
| 179 | 179 | @ DROP TABLE IF EXISTS undo_vmerge; |
| 180 | 180 | @ DROP TABLE IF EXISTS undo_stash; |
| 181 | 181 | @ DROP TABLE IF EXISTS undo_stashfile; |
| 182 | 182 | ; |
| 183 | - db_multi_exec(zSql /*works-like:""*/); | |
| 183 | + db_exec_sql(zSql); | |
| 184 | 184 | db_lset_int("undo_available", 0); |
| 185 | 185 | db_lset_int("undo_checkout", 0); |
| 186 | 186 | } |
| 187 | 187 | |
| 188 | 188 | /* |
| @@ -235,11 +235,11 @@ | ||
| 235 | 235 | @ CREATE TABLE localdb.undo_vfile AS SELECT * FROM vfile; |
| 236 | 236 | @ CREATE TABLE localdb.undo_vmerge AS SELECT * FROM vmerge; |
| 237 | 237 | ; |
| 238 | 238 | if( undoDisable ) return; |
| 239 | 239 | undo_reset(); |
| 240 | - db_multi_exec(zSql/*works-like:""*/); | |
| 240 | + db_exec_sql(zSql); | |
| 241 | 241 | cid = db_lget_int("checkout", 0); |
| 242 | 242 | db_lset_int("undo_checkout", cid); |
| 243 | 243 | db_lset_int("undo_available", 1); |
| 244 | 244 | db_lset("undo_cmdline", undoCmd); |
| 245 | 245 | undoActive = 1; |
| 246 | 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_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 |
+1
| --- src/unversioned.c | ||
| +++ src/unversioned.c | ||
| @@ -267,10 +267,11 @@ | ||
| 267 | 267 | ** |
| 268 | 268 | ** Options: |
| 269 | 269 | ** |
| 270 | 270 | ** --mtime TIMESTAMP Use TIMESTAMP instead of "now" for the "add", |
| 271 | 271 | ** "edit", "remove", and "touch" subcommands. |
| 272 | +** -R|--repository FILE Use FILE as the repository | |
| 272 | 273 | */ |
| 273 | 274 | void unversioned_cmd(void){ |
| 274 | 275 | const char *zCmd; |
| 275 | 276 | int nCmd; |
| 276 | 277 | const char *zMtime = find_option("mtime", 0, 1); |
| 277 | 278 |
| --- 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 @@ | ||
| 452 | 452 | return g.perm.Write; |
| 453 | 453 | } |
| 454 | 454 | |
| 455 | 455 | /* |
| 456 | 456 | ** 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 | +** | |
| 458 | 465 | */ |
| 459 | 466 | void wiki_page(void){ |
| 460 | 467 | char *zTag; |
| 461 | 468 | int rid = 0; |
| 462 | 469 | int isSandbox; |
| @@ -464,10 +471,11 @@ | ||
| 464 | 471 | Blob wiki; |
| 465 | 472 | Manifest *pWiki = 0; |
| 466 | 473 | const char *zPageName; |
| 467 | 474 | const char *zMimetype = 0; |
| 468 | 475 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 476 | + int noSubmenu = P("nsm")!=0; | |
| 469 | 477 | |
| 470 | 478 | login_check_credentials(); |
| 471 | 479 | if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 472 | 480 | zPageName = P("name"); |
| 473 | 481 | if( zPageName==0 ){ |
| @@ -501,11 +509,11 @@ | ||
| 501 | 509 | zBody = pWiki->zWiki; |
| 502 | 510 | zMimetype = pWiki->zMimetype; |
| 503 | 511 | } |
| 504 | 512 | } |
| 505 | 513 | zMimetype = wiki_filter_mimetypes(zMimetype); |
| 506 | - if( !g.isHome ){ | |
| 514 | + if( !g.isHome && !noSubmenu ){ | |
| 507 | 515 | if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki)) |
| 508 | 516 | && wiki_special_permission(zPageName) |
| 509 | 517 | ){ |
| 510 | 518 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| 511 | 519 | style_submenu_element("Edit", "%R/wikiedit?name=%T&wysiwyg=1", |
| @@ -520,11 +528,13 @@ | ||
| 520 | 528 | style_submenu_element("History", "%R/whistory?name=%T", zPageName); |
| 521 | 529 | } |
| 522 | 530 | } |
| 523 | 531 | style_set_current_page("%T?name=%T", g.zPath, zPageName); |
| 524 | 532 | wiki_page_header(WIKITYPE_UNKNOWN, zPageName, ""); |
| 525 | - wiki_standard_submenu(submenuFlags); | |
| 533 | + if( !noSubmenu ){ | |
| 534 | + wiki_standard_submenu(submenuFlags); | |
| 535 | + } | |
| 526 | 536 | if( zBody[0]==0 ){ |
| 527 | 537 | @ <i>This page has been deleted</i> |
| 528 | 538 | }else{ |
| 529 | 539 | blob_init(&wiki, zBody, -1); |
| 530 | 540 | wiki_render_by_mimetype(&wiki, zMimetype); |
| @@ -1224,11 +1234,11 @@ | ||
| 1224 | 1234 | if( wrid==0 ){ |
| 1225 | 1235 | if( !showAll ) continue; |
| 1226 | 1236 | @ <tr><td data-sortkey="%h(zSort)">\ |
| 1227 | 1237 | @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td> |
| 1228 | 1238 | }else{ |
| 1229 | - @ <tr><td data=sortkey="%h(zSort)">\ | |
| 1239 | + @ <tr><td data-sortkey="%h(zSort)">\ | |
| 1230 | 1240 | @ %z(href("%R/wiki?name=%T",zWName))%h(zWDisplayName)</a></td> |
| 1231 | 1241 | } |
| 1232 | 1242 | zAge = human_readable_age(rNow - rWmtime); |
| 1233 | 1243 | @ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td> |
| 1234 | 1244 | fossil_free(zAge); |
| 1235 | 1245 |
| --- 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 |
+17
-1
| --- win/Makefile.mingw | ||
| +++ win/Makefile.mingw | ||
| @@ -174,11 +174,11 @@ | ||
| 174 | 174 | #### The directories where the OpenSSL include and library files are located. |
| 175 | 175 | # The recommended usage here is to use the Sysinternals junction tool |
| 176 | 176 | # to create a hard link between an "openssl-1.x" sub-directory of the |
| 177 | 177 | # Fossil source code directory and the target OpenSSL source directory. |
| 178 | 178 | # |
| 179 | -OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d | |
| 179 | +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e | |
| 180 | 180 | OPENSSLINCDIR = $(OPENSSLDIR)/include |
| 181 | 181 | OPENSSLLIBDIR = $(OPENSSLDIR) |
| 182 | 182 | |
| 183 | 183 | #### Either the directory where the Tcl library is installed or the Tcl |
| 184 | 184 | # source code directory resides (depending on the value of the macro |
| @@ -646,10 +646,26 @@ | ||
| 646 | 646 | $(SRCDIR)/menu.js \ |
| 647 | 647 | $(SRCDIR)/sbsdiff.js \ |
| 648 | 648 | $(SRCDIR)/scroll.js \ |
| 649 | 649 | $(SRCDIR)/skin.js \ |
| 650 | 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 \ | |
| 651 | 667 | $(SRCDIR)/tree.js \ |
| 652 | 668 | $(SRCDIR)/useredit.js \ |
| 653 | 669 | $(SRCDIR)/wiki.wiki |
| 654 | 670 | |
| 655 | 671 | TRANS_SRC = \ |
| 656 | 672 |
| --- 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 |
+17
-1
| --- win/Makefile.mingw.mistachkin | ||
| +++ win/Makefile.mingw.mistachkin | ||
| @@ -174,11 +174,11 @@ | ||
| 174 | 174 | #### The directories where the OpenSSL include and library files are located. |
| 175 | 175 | # The recommended usage here is to use the Sysinternals junction tool |
| 176 | 176 | # to create a hard link between an "openssl-1.x" sub-directory of the |
| 177 | 177 | # Fossil source code directory and the target OpenSSL source directory. |
| 178 | 178 | # |
| 179 | -OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1d | |
| 179 | +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1e | |
| 180 | 180 | OPENSSLINCDIR = $(OPENSSLDIR)/include |
| 181 | 181 | OPENSSLLIBDIR = $(OPENSSLDIR) |
| 182 | 182 | |
| 183 | 183 | #### Either the directory where the Tcl library is installed or the Tcl |
| 184 | 184 | # source code directory resides (depending on the value of the macro |
| @@ -646,10 +646,26 @@ | ||
| 646 | 646 | $(SRCDIR)/menu.js \ |
| 647 | 647 | $(SRCDIR)/sbsdiff.js \ |
| 648 | 648 | $(SRCDIR)/scroll.js \ |
| 649 | 649 | $(SRCDIR)/skin.js \ |
| 650 | 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 \ | |
| 651 | 667 | $(SRCDIR)/tree.js \ |
| 652 | 668 | $(SRCDIR)/useredit.js \ |
| 653 | 669 | $(SRCDIR)/wiki.wiki |
| 654 | 670 | |
| 655 | 671 | TRANS_SRC = \ |
| 656 | 672 |
| --- 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 @@ | ||
| 98 | 98 | !ifndef USE_SEE |
| 99 | 99 | USE_SEE = 0 |
| 100 | 100 | !endif |
| 101 | 101 | |
| 102 | 102 | !if $(FOSSIL_ENABLE_SSL)!=0 |
| 103 | -SSLDIR = $(B)\compat\openssl-1.1.1d | |
| 103 | +SSLDIR = $(B)\compat\openssl-1.1.1e | |
| 104 | 104 | SSLINCDIR = $(SSLDIR)\include |
| 105 | 105 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 |
| 106 | 106 | SSLLIBDIR = $(SSLDIR) |
| 107 | 107 | !else |
| 108 | 108 | SSLLIBDIR = $(SSLDIR) |
| @@ -553,10 +553,26 @@ | ||
| 553 | 553 | $(SRCDIR)\menu.js \ |
| 554 | 554 | $(SRCDIR)\sbsdiff.js \ |
| 555 | 555 | $(SRCDIR)\scroll.js \ |
| 556 | 556 | $(SRCDIR)\skin.js \ |
| 557 | 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 \ | |
| 558 | 574 | $(SRCDIR)\tree.js \ |
| 559 | 575 | $(SRCDIR)\useredit.js \ |
| 560 | 576 | $(SRCDIR)\wiki.wiki |
| 561 | 577 | |
| 562 | 578 | OBJ = $(OX)\add$O \ |
| 563 | 579 |
| --- 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 |
+2
| --- www/adding_code.wiki | ||
| +++ www/adding_code.wiki | ||
| @@ -102,10 +102,11 @@ | ||
| 102 | 102 | It is recommended that you try this. |
| 103 | 103 | |
| 104 | 104 | Be sure to [/help/add|fossil add] your new source file to the self-hosting |
| 105 | 105 | Fossil repository and then [/help/commit|commit] your changes! |
| 106 | 106 | |
| 107 | +<a name="newcmd"></a> | |
| 107 | 108 | <h2>4.0 Creating A New Command</h2> |
| 108 | 109 | |
| 109 | 110 | By "commands" we mean the keywords that follow "fossil" when invoking |
| 110 | 111 | Fossil from the command-line. So, for example, in |
| 111 | 112 | |
| @@ -167,10 +168,11 @@ | ||
| 167 | 168 | the working check-out. Study implementations of existing commands |
| 168 | 169 | to get an idea of how things are done. You can easily find the implementations |
| 169 | 170 | of existing commands by searching for "COMMAND: <i>name</i>" in the |
| 170 | 171 | files of the "src/" directory. |
| 171 | 172 | |
| 173 | +<a name="newpage"></a> | |
| 172 | 174 | <h2>5.0 Creating A New Web Page</h2> |
| 173 | 175 | |
| 174 | 176 | As with commands, new webpages can be added simply by inserting a function |
| 175 | 177 | that generates the webpage together with a special header comment. A |
| 176 | 178 | template follows: |
| 177 | 179 |
| --- 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 @@ | ||
| 718 | 718 | Almost all of the email alert code is found in the |
| 719 | 719 | [`src/alerts.c`](/file/src/alerts.c) source file. |
| 720 | 720 | |
| 721 | 721 | When email alerts are enabled, a trigger is created in the schema |
| 722 | 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 | |
| 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 | 725 | want users to get alerts for every historical check-in, the trigger is |
| 726 | 726 | disabled during `rebuild`. |
| 727 | 727 | |
| 728 | 728 | Email alerts are sent out by the `alert_send_alerts()` function, which |
| 729 | 729 | is normally called automatically due to the `email-autoexec` setting, |
| 730 | 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 `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 @@ | ||
| 161 | 161 | the optional <a href="https://www.openssl.org/">OpenSSL</a> support, |
| 162 | 162 | first <a href="https://www.openssl.org/source/">download the official |
| 163 | 163 | source code for OpenSSL</a> and extract it to an appropriately named |
| 164 | 164 | "<b>openssl-X.Y.ZA</b>" subdirectory within the local |
| 165 | 165 | [/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 | |
| 167 | 167 | <a href="http://www.perl.org/">Perl</a> binaries are installed locally, |
| 168 | 168 | and finally run one of the following commands: |
| 169 | 169 | <blockquote><pre> |
| 170 | 170 | nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin |
| 171 | 171 | </pre></blockquote> |
| @@ -310,5 +310,58 @@ | ||
| 310 | 310 | </code></pre> |
| 311 | 311 | |
| 312 | 312 | Note the IDs of the images named <tt>fossil_static</tt> and <tt>alpine</tt>, then: |
| 313 | 313 | |
| 314 | 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 | +> cd /data/local/tmp | |
| 350 | +# Fossil requires a HOME directory to work with: | |
| 351 | +> export HOME=$PWD | |
| 352 | +> export PATH=$PWD:$PATH | |
| 353 | +> fossil version | |
| 354 | +This is fossil version 2.11 [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]. | |
| 315 | 368 |
| --- 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 | > cd /data/local/tmp |
| 350 | # Fossil requires a HOME directory to work with: |
| 351 | > export HOME=$PWD |
| 352 | > export PATH=$PWD:$PATH |
| 353 | > fossil version |
| 354 | This is fossil version 2.11 [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 |
+4
-4
| --- www/caps/index.md | ||
| +++ www/caps/index.md | ||
| @@ -65,12 +65,12 @@ | ||
| 65 | 65 | Fossil shows how these capabilities apply hierarchically in the user |
| 66 | 66 | editing screen (Admin → Users → name) with the `[N]` `[A]` `[D]` `[R]` |
| 67 | 67 | tags next to each capability check box. If a user gets a capability from |
| 68 | 68 | one of the user categories already assigned to it, there is no value in |
| 69 | 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 | |
| 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 | 72 | **e**. |
| 73 | 73 | |
| 74 | 74 | We suggest that you lean heavily on these fixed user categories when |
| 75 | 75 | setting up new users. Ideally, your users will group neatly into one of |
| 76 | 76 | the predefined categories, but if not, you might be able to shoehorn |
| @@ -151,12 +151,12 @@ | ||
| 151 | 151 | are all about modifying repository content: edit existing wiki pages, |
| 152 | 152 | change one’s own password, create new ticket report formats, and modify |
| 153 | 153 | existing tickets. This category would be better named “participant”. |
| 154 | 154 | |
| 155 | 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. | |
| 156 | +sets plus **[e][e][i][i]**: view | |
| 157 | +sensitive user material and check in changes. | |
| 158 | 158 | |
| 159 | 159 | [bot]: ../antibot.wiki |
| 160 | 160 | |
| 161 | 161 | |
| 162 | 162 | ## <a name="pvt"></a>Consequences of Taking a Repository Private |
| 163 | 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 **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 |
+11
-3
| --- www/caps/ref.html | ||
| +++ www/caps/ref.html | ||
| @@ -73,15 +73,23 @@ | ||
| 73 | 73 | </td> |
| 74 | 74 | </tr> |
| 75 | 75 | |
| 76 | 76 | <tr id="d"> |
| 77 | 77 | <th>d</th> |
| 78 | - <th>Delete</th> | |
| 78 | + <th>n/a</th> | |
| 79 | 79 | <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. | |
| 81 | 89 | </td> |
| 82 | - </tr> | |
| 90 | + </tr> | |
| 83 | 91 | |
| 84 | 92 | <tr id="e"> |
| 85 | 93 | <th>e</th> |
| 86 | 94 | <th>RdAddr</th> |
| 87 | 95 | <td> |
| 88 | 96 |
| --- 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 |
+3
-1
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -30,16 +30,18 @@ | ||
| 30 | 30 | the CSP in the process. |
| 31 | 31 | * The Content-Security-Policy is now set using the |
| 32 | 32 | [/help?cmd=default-csp|default-csp setting]. |
| 33 | 33 | * Merge conflicts caused via the [/help?cmd=merge|merge] and |
| 34 | 34 | [/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 | |
| 36 | 36 | is used. |
| 37 | 37 | * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible |
| 38 | 38 | to all users if the new "artifact_stats_enable" setting is turned |
| 39 | 39 | on. There is a new checkbox under the /Admin/Access menu to turn |
| 40 | 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. | |
| 41 | 43 | * Bug fix: the "fossil git export" command is now working on Windows |
| 42 | 44 | * Bug fix: display Technote items on the timeline correctly |
| 43 | 45 | * Bug fix: fix the capability summary matrix of the Security Audit |
| 44 | 46 | page so that it does not add "anonymous" capabilities to the |
| 45 | 47 | "nobody" user. |
| 46 | 48 |
| --- 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 |
+1
-1
| --- www/embeddeddoc.wiki | ||
| +++ www/embeddeddoc.wiki | ||
| @@ -53,11 +53,11 @@ | ||
| 53 | 53 | pull the documentation file from the local source tree on disk, not |
| 54 | 54 | from the any check-in. The "<b>ckout</b>" keyword |
| 55 | 55 | only works when you start your server using the |
| 56 | 56 | "[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]" |
| 57 | 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. | |
| 58 | +the documentation you are currently editing but have not yet you checked in. | |
| 59 | 59 | |
| 60 | 60 | Finally, the <i><filename></i> element of the URL is the |
| 61 | 61 | pathname of the documentation file relative to the root of the source |
| 62 | 62 | tree. |
| 63 | 63 | |
| 64 | 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 but have not yet you checked in. |
| 59 | |
| 60 | Finally, the <i><filename></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><filename></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 @@ | ||
| 4 | 4 | A [glob pattern][glob] is a text expression that matches one or more |
| 5 | 5 | file names using wild cards familiar to most users of a command line. |
| 6 | 6 | For example, `*` is a glob that matches any name at all and |
| 7 | 7 | `Readme.txt` is a glob that matches exactly one file. |
| 8 | 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]. | |
| 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. | |
| 12 | 14 | |
| 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 | |
| 14 | 17 | [regexp]: https://en.wikipedia.org/wiki/Regular_expression |
| 15 | 18 | |
| 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. | |
| 31 | 28 | |
| 32 | 29 | |
| 33 | 30 | ## Syntax |
| 34 | 31 | |
| 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 | |
| 36 | 34 | by white space or commas. If a glob must contain white spaces or |
| 37 | 35 | 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 | |
| 39 | 37 | matches. |
| 40 | 38 | |
| 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: | |
| 52 | 49 | |
| 53 | 50 | :Pattern |:Effect |
| 54 | 51 | --------------------------------------------------------------------- |
| 55 | 52 | `*` | Matches any sequence of zero or more characters |
| 56 | 53 | `?` | Matches exactly one character |
| 57 | 54 | `[...]` | 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 | |
| 59 | 56 | |
| 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. | |
| 61 | 62 | |
| 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 | |
| 64 | 69 | 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 | + | |
| 74 | 88 | * Beware that a range must be specified from low value to high |
| 75 | 89 | value: `[z-a]` does not match any character at all, preventing the |
| 76 | 90 | 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 | 91 | |
| 82 | 92 | Some examples of character lists: |
| 83 | 93 | |
| 84 | 94 | :Pattern |:Effect |
| 85 | 95 | --------------------------------------------------------------------- |
| @@ -92,45 +102,56 @@ | ||
| 92 | 102 | `[]^]` | Matches either `]` or `^` |
| 93 | 103 | `[^-]` | Matches exactly one character other than `-` |
| 94 | 104 | |
| 95 | 105 | White space means the specific ASCII characters TAB, LF, VT, FF, CR, |
| 96 | 106 | 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. | |
| 99 | 109 | |
| 100 | 110 | Because both LF and CR are white space and leading and trailing spaces |
| 101 | 111 | 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. | |
| 104 | 114 | |
| 105 | -Similarly 'single quotes' and "double quotes" are the ASCII straight | |
| 115 | +Note that 'single quotes' and "double quotes" are the ASCII straight | |
| 106 | 116 | quote characters, not any of the other quotation marks provided in |
| 107 | 117 | Unicode and specifically not the "curly" quotes preferred by |
| 108 | 118 | typesetters and word processors. |
| 109 | 119 | |
| 110 | 120 | |
| 111 | 121 | ## File Names to Match |
| 112 | 122 | |
| 113 | 123 | 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. | |
| 122 | 136 | |
| 123 | 137 | 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. | |
| 126 | 142 | |
| 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 | |
| 130 | 147 | 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. | |
| 132 | 153 | |
| 133 | 154 | Some example cases: |
| 134 | 155 | |
| 135 | 156 | :Pattern |:Effect |
| 136 | 157 | -------------------------------------------------------------------------------- |
| @@ -188,10 +209,11 @@ | ||
| 188 | 209 | * [`commit`][] |
| 189 | 210 | * [`extras`][] |
| 190 | 211 | * [`merge`][] |
| 191 | 212 | * [`settings`][] |
| 192 | 213 | * [`status`][] |
| 214 | + * [`touch`][] | |
| 193 | 215 | * [`unset`][] |
| 194 | 216 | |
| 195 | 217 | The commands [`tarball`][] and [`zip`][] produce compressed archives of a |
| 196 | 218 | specific checkin. They may be further restricted by options that |
| 197 | 219 | specify glob patterns that name files to include or exclude rather |
| @@ -209,10 +231,11 @@ | ||
| 209 | 231 | [`commit`]: /help?cmd=commit |
| 210 | 232 | [`extras`]: /help?cmd=extras |
| 211 | 233 | [`merge`]: /help?cmd=merge |
| 212 | 234 | [`settings`]: /help?cmd=settings |
| 213 | 235 | [`status`]: /help?cmd=status |
| 236 | +[`touch`]: /help?cmd=touch | |
| 214 | 237 | [`unset`]: /help?cmd=unset |
| 215 | 238 | |
| 216 | 239 | [`tarball`]: /help?cmd=tarball |
| 217 | 240 | [`zip`]: /help?cmd=zip |
| 218 | 241 | |
| @@ -259,11 +282,11 @@ | ||
| 259 | 282 | That advice does not help you when you are giving one-off glob patterns |
| 260 | 283 | in `fossil` commands. The remainder of this section gives remedies and |
| 261 | 284 | workarounds for these problems. |
| 262 | 285 | |
| 263 | 286 | |
| 264 | -## POSIX Systems | |
| 287 | +### <a name="posix"></a>POSIX Systems | |
| 265 | 288 | |
| 266 | 289 | If you are using Fossil on a system with a POSIX-compatible shell |
| 267 | 290 | — Linux, macOS, the BSDs, Unix, Cygwin, WSL etc. — the shell |
| 268 | 291 | may expand the glob patterns before passing the result to the `fossil` |
| 269 | 292 | executable. |
| @@ -346,11 +369,28 @@ | ||
| 346 | 369 | accidentally check something like a password, an API key, or the |
| 347 | 370 | private half of a public cryptographic key into Fossil repository that |
| 348 | 371 | can be read by people who should not have such secrets. |
| 349 | 372 | |
| 350 | 373 | |
| 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.) | |
| 352 | 392 | |
| 353 | 393 | Neither standard Windows command shell — `cmd.exe` or PowerShell |
| 354 | 394 | — expands glob patterns the way POSIX shells do. Windows command |
| 355 | 395 | shells rely on the command itself to do the glob pattern expansion. The |
| 356 | 396 | way this works depends on several factors: |
| @@ -362,82 +402,111 @@ | ||
| 362 | 402 | * whether the command is built against a runtime system that does this |
| 363 | 403 | at all |
| 364 | 404 | * whether the Fossil command is being run from a file named `*.BAT` vs |
| 365 | 405 | being named `*.CMD` |
| 366 | 406 | |
| 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. | |
| 373 | 414 | |
| 374 | 415 | The most common problem is figuring out how to get a glob pattern passed |
| 375 | 416 | on the command line into `fossil.exe` without it being expanded by the C |
| 376 | 417 | 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 | |
| 378 | 419 | not strongly governed by POSIX, so it has not historically hewed closely |
| 379 | 420 | to its strictures. |
| 380 | 421 | |
| 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 | 422 | 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ï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ïve approach will not work: | |
| 393 | 425 | |
| 394 | 426 | C:\...> fossil setting crlf-glob * |
| 395 | 427 | |
| 396 | 428 | The C runtime library will expand that to the list of all files in the |
| 397 | 429 | current directory, which will probably cause a Fossil error because |
| 398 | 430 | 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.) | |
| 400 | 434 | |
| 401 | 435 | Let's try again: |
| 402 | 436 | |
| 403 | 437 | C:\...> fossil setting crlf-glob '*' |
| 404 | 438 | |
| 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. | |
| 408 | 450 | |
| 409 | 451 | An approach that *will* work reliably is: |
| 410 | 452 | |
| 411 | 453 | C:\...> echo * | fossil setting crlf-glob --args - |
| 412 | 454 | |
| 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 | |
| 416 | 458 | 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!) | |
| 418 | 468 | |
| 419 | -Another (usually) correct approach is: | |
| 469 | +Another (usually) correct approach which will work on both Windows and | |
| 470 | +POSIX systems: | |
| 420 | 471 | |
| 421 | 472 | C:\...> fossil setting crlf-glob *, |
| 422 | 473 | |
| 423 | -This works because the trailing comma prevents the command shell from | |
| 474 | +This works because the trailing comma prevents the glob pattern from | |
| 424 | 475 | matching any files, unless you happen to have files named with a |
| 425 | 476 | trailing comma in the current directory. If the pattern matches no |
| 426 | 477 | files, it is passed into Fossil's `main()` function as-is by the C |
| 427 | 478 | 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 | |
| 430 | 499 | |
| 431 | 500 | |
| 432 | 501 | ## Converting `.gitignore` to `ignore-glob` |
| 433 | 502 | |
| 434 | 503 | 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 | |
| 436 | 505 | individual "ignore" files in each folder, which specify things ignored |
| 437 | 506 | 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. | |
| 439 | 508 | |
| 440 | 509 | In many simple cases, you can just store a top level "ignore" file in |
| 441 | 510 | `.fossil-settings/ignore-glob`. But as usual, there will be lots of |
| 442 | 511 | edge cases. |
| 443 | 512 | |
| @@ -447,33 +516,33 @@ | ||
| 447 | 516 | version controlled files. Some of the files used have no set name, but |
| 448 | 517 | are called out in configuration files. |
| 449 | 518 | |
| 450 | 519 | [gitignore]: https://git-scm.com/docs/gitignore |
| 451 | 520 | |
| 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 | |
| 454 | 523 | command's `--ignore` option replaces the `ignore-glob` setting rather |
| 455 | 524 | than extending it. |
| 456 | 525 | |
| 457 | 526 | With that in mind, translating a `.gitignore` file into |
| 458 | 527 | `.fossil-settings/ignore-glob` may be possible in many cases. Here are |
| 459 | 528 | some of features of `.gitignore` and comments on how they relate to |
| 460 | -fossil: | |
| 529 | +Fossil: | |
| 461 | 530 | |
| 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. | |
| 464 | 533 | * "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 | |
| 473 | 542 | differently. Git includes a notation for zero or more directories |
| 474 | - that is not needed in fossil. | |
| 543 | + that is not needed in Fossil. | |
| 475 | 544 | |
| 476 | 545 | ### Example |
| 477 | 546 | |
| 478 | 547 | In a project with source and documentation: |
| 479 | 548 | |
| @@ -502,30 +571,26 @@ | ||
| 502 | 571 | |
| 503 | 572 | |
| 504 | 573 | |
| 505 | 574 | ## Implementation and References |
| 506 | 575 | |
| 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: | |
| 515 | 577 | |
| 516 | 578 | :File |:Description |
| 517 | 579 | -------------------------------------------------------------------------------- |
| 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 | |
| 520 | 582 | |
| 521 | 583 | [`src/glob.c`]: https://www.fossil-scm.org/index.html/file/src/glob.c |
| 522 | 584 | [`src/file.c`]: https://www.fossil-scm.org/index.html/file/src/file.c |
| 523 | 585 | |
| 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 | |
| 532 | 597 |
| --- 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 | — Linux, macOS, the BSDs, Unix, Cygwin, WSL etc. — 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 — `cmd.exe` or PowerShell |
| 354 | — 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ï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 | — Linux, macOS, the BSDs, Unix, Cygwin, WSL etc. — 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 — `cmd.exe` or PowerShell |
| 394 | — 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ï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 @@ | ||
| 39 | 39 | * There is no way to suppress all output, returning only a status code |
| 40 | 40 | to indicate whether the pattern matched, as with `grep -q`. |
| 41 | 41 | |
| 42 | 42 | * There is no way to suppress error output, as with `grep -s`. |
| 43 | 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 | |
| 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 | |
| 50 | 49 | convention.) |
| 51 | 50 | |
| 52 | 51 | * You cannot invert the match, as with `grep -v`. |
| 53 | 52 | |
| 54 | 53 | Patches to remove those limitations will be thoughtfully considered. |
| 55 | 54 |
| --- 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 |
+3
-3
| --- 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> | |
| 2 | 2 | |
| 3 | 3 | 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. | |
| 5 | 5 | |
| 6 | 6 | * [./build.wiki | How To Compile And Install Fossil] |
| 7 | 7 | * [./customskin.md | Theming Fossil] |
| 8 | + * [./adding_code.wiki#newcmd | Adding New Commands To Fossil] | |
| 8 | 9 | * [./makefile.wiki | The Fossil Build Process] |
| 9 | 10 | * [./tech_overview.wiki | A Technical Overview of Fossil] |
| 10 | - * [./adding_code.wiki | Adding Features To Fossil] | |
| 11 | 11 | * [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project] |
| 12 | 12 | * [./fileformat.wiki|Fossil Artifact File Format] |
| 13 | 13 | * [./sync.wiki|The Sync Protocol] |
| 14 | 14 | * [./style.wiki | Coding Style Guidelines] |
| 15 | 15 | * [./checkin.wiki | Pre-checkin Checklist] |
| 16 | 16 |
| --- 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 @@ | ||
| 31 | 31 | received while the host load average is too high. |
| 32 | 32 | |
| 33 | 33 | Both of these load-control mechanisms are turned off by default, but |
| 34 | 34 | they are recommended for high-traffic sites. |
| 35 | 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 | |
| 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 | 38 | specify the specific repository for which to enable caching. If running |
| 39 | 39 | this command as root, be sure to “`chown`” the cache database to give |
| 40 | 40 | the Fossil server write permission for the user ID of the web server; |
| 41 | 41 | this is a separate file in the same directory and with the same name as |
| 42 | 42 | the repository but with the “`.fossil`” suffix changed to “`.cache`”. |
| 43 | 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 |
| 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 @@ | ||
| 50 | 50 | fossil_prompt.wiki {Fossilized Bash Prompt} |
| 51 | 51 | fossil-v-git.wiki {Fossil Versus Git} |
| 52 | 52 | globs.md {File Name Glob Patterns} |
| 53 | 53 | grep.md {Fossil grep vs POSIX grep} |
| 54 | 54 | hacker-howto.wiki {Hacker How-To} |
| 55 | + hacker-howto.wiki {Fossil Developers Guide} | |
| 55 | 56 | hashpolicy.wiki {Hash Policy: Choosing Between SHA1 and SHA3-256} |
| 56 | 57 | /help {Lists of Commands and Webpages} |
| 57 | 58 | hints.wiki {Fossil Tips And Usage Hints} |
| 58 | 59 | index.wiki {Home Page} |
| 59 | 60 | inout.wiki {Import And Export To And From Git} |
| @@ -139,11 +140,11 @@ | ||
| 139 | 140 | <li> <a href='build.wiki'>Compiling and installing Fossil</a> |
| 140 | 141 | <li> <a href='../COPYRIGHT-BSD2.txt'>License</a> |
| 141 | 142 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 142 | 143 | book</a> |
| 143 | 144 | <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> | |
| 145 | 146 | </ul> |
| 146 | 147 | <a name="pindex"></a> |
| 147 | 148 | <h2>Permuted Index:</h2> |
| 148 | 149 | <ul>} |
| 149 | 150 | foreach entry $permindex { |
| 150 | 151 |
| --- 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 |
+4
-1
| --- www/permutedindex.html | ||
| +++ www/permutedindex.html | ||
| @@ -13,11 +13,11 @@ | ||
| 13 | 13 | <li> <a href='build.wiki'>Compiling and installing Fossil</a> |
| 14 | 14 | <li> <a href='../COPYRIGHT-BSD2.txt'>License</a> |
| 15 | 15 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 16 | 16 | book</a> |
| 17 | 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> | |
| 18 | +<li> <a href='hacker-howto.wiki'>Fossil Developer's Guide</a> | |
| 19 | 19 | </ul> |
| 20 | 20 | <a name="pindex"></a> |
| 21 | 21 | <h2>Permuted Index:</h2> |
| 22 | 22 | <ul> |
| 23 | 23 | <li><a href="fiveminutes.wiki">5 Minutes as a Single User — Up and Running in</a></li> |
| @@ -89,10 +89,11 @@ | ||
| 89 | 89 | <li><a href="private.wiki">Deleting Private Branches — Creating, Syncing, and</a></li> |
| 90 | 90 | <li><a href="delta_encoder_algorithm.wiki">Delta Encoding Algorithm — Fossil</a></li> |
| 91 | 91 | <li><a href="delta_format.wiki">Delta Format — Fossil</a></li> |
| 92 | 92 | <li><a href="tech_overview.wiki">Design And Implementation Of Fossil — A Technical Overview Of The</a></li> |
| 93 | 93 | <li><a href="theory1.wiki">Design Of The Fossil DVCS — Thoughts On The</a></li> |
| 94 | +<li><a href="hacker-howto.wiki">Developers Guide — Fossil</a></li> | |
| 94 | 95 | <li><a href="caps/admin-v-setup.md"><b>Differences Between Setup and Admin Users</b></a></li> |
| 95 | 96 | <li><a href="embeddeddoc.wiki">Documentation — Embedded Project</a></li> |
| 96 | 97 | <li><a href="contribute.wiki">Documentation To The Fossil Project — Contributing Code or</a></li> |
| 97 | 98 | <li><a href="aboutdownload.wiki">Download Page Works — How The</a></li> |
| 98 | 99 | <li><a href="theory1.wiki">DVCS — Thoughts On The Design Of The Fossil</a></li> |
| @@ -122,10 +123,11 @@ | ||
| 122 | 123 | <li><a href="blockchain.md"><b>Fossil As Blockchain</b></a></li> |
| 123 | 124 | <li><a href="changes.wiki"><b>Fossil Changelog</b></a></li> |
| 124 | 125 | <li><a href="concepts.wiki"><b>Fossil Core Concepts</b></a></li> |
| 125 | 126 | <li><a href="delta_encoder_algorithm.wiki"><b>Fossil Delta Encoding Algorithm</b></a></li> |
| 126 | 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> | |
| 127 | 129 | <li><a href="fileformat.wiki"><b>Fossil File Format</b></a></li> |
| 128 | 130 | <li><a href="forum.wiki"><b>Fossil Forums</b></a></li> |
| 129 | 131 | <li><a href="grep.md"><b>Fossil grep vs POSIX grep</b></a></li> |
| 130 | 132 | <li><a href="quickstart.wiki"><b>Fossil Quick Start Guide</b></a></li> |
| 131 | 133 | <li><a href="selfcheck.wiki"><b>Fossil Repository Integrity Self Checks</b></a></li> |
| @@ -145,10 +147,11 @@ | ||
| 145 | 147 | <li><a href="globs.md">Glob Patterns — File Name</a></li> |
| 146 | 148 | <li><a href="env-opts.md">Global Options — Environment Variables and</a></li> |
| 147 | 149 | <li><a href="customgraph.md">Graph — Theming: Customizing the Timeline</a></li> |
| 148 | 150 | <li><a href="grep.md">grep — Fossil grep vs POSIX</a></li> |
| 149 | 151 | <li><a href="grep.md">grep vs POSIX grep — Fossil</a></li> |
| 152 | +<li><a href="hacker-howto.wiki">Guide — Fossil Developers</a></li> | |
| 150 | 153 | <li><a href="quickstart.wiki">Guide — Fossil Quick Start</a></li> |
| 151 | 154 | <li><a href="style.wiki">Guidelines — Source Code Style</a></li> |
| 152 | 155 | <li><a href="hacker-howto.wiki"><b>Hacker How-To</b></a></li> |
| 153 | 156 | <li><a href="adding_code.wiki"><b>Hacking Fossil</b></a></li> |
| 154 | 157 | <li><a href="rebaseharm.md">Harmful — Rebase Considered</a></li> |
| 155 | 158 |
| --- 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 — Up and Running in</a></li> |
| @@ -89,10 +89,11 @@ | |
| 89 | <li><a href="private.wiki">Deleting Private Branches — Creating, Syncing, and</a></li> |
| 90 | <li><a href="delta_encoder_algorithm.wiki">Delta Encoding Algorithm — Fossil</a></li> |
| 91 | <li><a href="delta_format.wiki">Delta Format — Fossil</a></li> |
| 92 | <li><a href="tech_overview.wiki">Design And Implementation Of Fossil — A Technical Overview Of The</a></li> |
| 93 | <li><a href="theory1.wiki">Design Of The Fossil DVCS — 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 — Embedded Project</a></li> |
| 96 | <li><a href="contribute.wiki">Documentation To The Fossil Project — Contributing Code or</a></li> |
| 97 | <li><a href="aboutdownload.wiki">Download Page Works — How The</a></li> |
| 98 | <li><a href="theory1.wiki">DVCS — 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 — File Name</a></li> |
| 146 | <li><a href="env-opts.md">Global Options — Environment Variables and</a></li> |
| 147 | <li><a href="customgraph.md">Graph — Theming: Customizing the Timeline</a></li> |
| 148 | <li><a href="grep.md">grep — Fossil grep vs POSIX</a></li> |
| 149 | <li><a href="grep.md">grep vs POSIX grep — Fossil</a></li> |
| 150 | <li><a href="quickstart.wiki">Guide — Fossil Quick Start</a></li> |
| 151 | <li><a href="style.wiki">Guidelines — 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 — 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 — Up and Running in</a></li> |
| @@ -89,10 +89,11 @@ | |
| 89 | <li><a href="private.wiki">Deleting Private Branches — Creating, Syncing, and</a></li> |
| 90 | <li><a href="delta_encoder_algorithm.wiki">Delta Encoding Algorithm — Fossil</a></li> |
| 91 | <li><a href="delta_format.wiki">Delta Format — Fossil</a></li> |
| 92 | <li><a href="tech_overview.wiki">Design And Implementation Of Fossil — A Technical Overview Of The</a></li> |
| 93 | <li><a href="theory1.wiki">Design Of The Fossil DVCS — Thoughts On The</a></li> |
| 94 | <li><a href="hacker-howto.wiki">Developers Guide — 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 — Embedded Project</a></li> |
| 97 | <li><a href="contribute.wiki">Documentation To The Fossil Project — Contributing Code or</a></li> |
| 98 | <li><a href="aboutdownload.wiki">Download Page Works — How The</a></li> |
| 99 | <li><a href="theory1.wiki">DVCS — 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 — File Name</a></li> |
| 148 | <li><a href="env-opts.md">Global Options — Environment Variables and</a></li> |
| 149 | <li><a href="customgraph.md">Graph — Theming: Customizing the Timeline</a></li> |
| 150 | <li><a href="grep.md">grep — Fossil grep vs POSIX</a></li> |
| 151 | <li><a href="grep.md">grep vs POSIX grep — Fossil</a></li> |
| 152 | <li><a href="hacker-howto.wiki">Guide — Fossil Developers</a></li> |
| 153 | <li><a href="quickstart.wiki">Guide — Fossil Quick Start</a></li> |
| 154 | <li><a href="style.wiki">Guidelines — 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 — Rebase Considered</a></li> |
| 158 |