Fossil SCM
merge trunk make Notepad the default comment editor on Windows
Commit
10cf72bd3bba470c4ef81dd9620ce3dd6a27d075
Parent
1aed9aabaa1cc54…
26 files changed
+1
+37
-23
+61
-28
+99
-81
+99
-81
+3
+16
-5
+45
-7
+196
-28
+1
-1
+27
-5
+22
-2
+2
-2
+1
+1
-1
+409
-302
+13
-1
+96
-29
+10
-3
+242
-17
+1
-1
+9
-3
+1
-1
+22
-2
+23
-3
+5
~
Makefile.in
~
auto.def
~
src/bisect.c
~
src/checkin.c
~
src/checkin.c
~
src/configure.c
~
src/db.c
~
src/diff.c
~
src/diffcmd.c
~
src/finfo.c
~
src/main.c
~
src/makemake.tcl
~
src/manifest.c
~
src/path.c
~
src/setup.c
~
src/sqlite3.c
~
src/sqlite3.h
~
src/stash.c
~
src/th_main.c
~
src/th_tcl.c
~
src/timeline.c
~
src/update.c
~
test/th1-tcl2.txt
~
win/Makefile.mingw
~
win/Makefile.mingw.mistachkin
~
win/fossil.rc
+1
| --- Makefile.in | ||
| +++ Makefile.in | ||
| @@ -41,10 +41,11 @@ | ||
| 41 | 41 | LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ |
| 42 | 42 | TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H |
| 43 | 43 | INSTALLDIR = $(DESTDIR)@prefix@/bin |
| 44 | 44 | USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ |
| 45 | 45 | FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@ |
| 46 | +FOSSIL_ENABLE_TCL_STUBS = @FOSSIL_ENABLE_TCL_STUBS@ | |
| 46 | 47 | |
| 47 | 48 | include $(SRCDIR)/main.mk |
| 48 | 49 | |
| 49 | 50 | distclean: clean |
| 50 | 51 | rm -f autoconfig.h config.log Makefile |
| 51 | 52 |
| --- Makefile.in | |
| +++ Makefile.in | |
| @@ -41,10 +41,11 @@ | |
| 41 | LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ |
| 42 | TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H |
| 43 | INSTALLDIR = $(DESTDIR)@prefix@/bin |
| 44 | USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ |
| 45 | FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@ |
| 46 | |
| 47 | include $(SRCDIR)/main.mk |
| 48 | |
| 49 | distclean: clean |
| 50 | rm -f autoconfig.h config.log Makefile |
| 51 |
| --- Makefile.in | |
| +++ Makefile.in | |
| @@ -41,10 +41,11 @@ | |
| 41 | LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ |
| 42 | TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H |
| 43 | INSTALLDIR = $(DESTDIR)@prefix@/bin |
| 44 | USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ |
| 45 | FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@ |
| 46 | FOSSIL_ENABLE_TCL_STUBS = @FOSSIL_ENABLE_TCL_STUBS@ |
| 47 | |
| 48 | include $(SRCDIR)/main.mk |
| 49 | |
| 50 | distclean: clean |
| 51 | rm -f autoconfig.h config.log Makefile |
| 52 |
M
auto.def
+37
-23
| --- auto.def | ||
| +++ auto.def | ||
| @@ -5,10 +5,11 @@ | ||
| 5 | 5 | options { |
| 6 | 6 | with-openssl:path|auto|none |
| 7 | 7 | => {Look for openssl in the given path, or auto or none} |
| 8 | 8 | with-zlib:path => {Look for zlib in the given path} |
| 9 | 9 | with-tcl:path => {Enable Tcl integration, with Tcl in the specified path} |
| 10 | + with-tcl-stubs=0 => {Enable Tcl integration via stubs mechanism} | |
| 10 | 11 | internal-sqlite=1 => {Don't use the internal sqlite, use the system one} |
| 11 | 12 | static=0 => {Link a static executable} |
| 12 | 13 | lineedit=1 => {Disable line editing} |
| 13 | 14 | fossil-debug=0 => {Build with fossil debugging enabled} |
| 14 | 15 | json=0 => {Build with fossil JSON API enabled} |
| @@ -32,11 +33,11 @@ | ||
| 32 | 33 | if {![opt-bool internal-sqlite]} { |
| 33 | 34 | proc find_internal_sqlite {} { |
| 34 | 35 | |
| 35 | 36 | # On some systems (slackware), libsqlite3 requires -ldl to link. So |
| 36 | 37 | # search for the system SQLite once with -ldl, and once without. If |
| 37 | - # the library can only be found with $extralibs set to -ldl, then | |
| 38 | + # the library can only be found with $extralibs set to -ldl, then | |
| 38 | 39 | # the code below will append -ldl to LIBS. |
| 39 | 40 | # |
| 40 | 41 | foreach extralibs {{} {-ldl}} { |
| 41 | 42 | |
| 42 | 43 | # Locate the system SQLite by searching for sqlite3_open(). Then check |
| @@ -92,35 +93,48 @@ | ||
| 92 | 93 | user-error "zlib not found please install it or specify the location with --with-zlib" |
| 93 | 94 | } |
| 94 | 95 | |
| 95 | 96 | set tclpath [opt-val with-tcl] |
| 96 | 97 | if {$tclpath ne ""} { |
| 97 | - # Note parse-tclconfig-sh is in autosetup/local.tcl | |
| 98 | + # Note parse-tclconfig-sh is in autosetup/local.tcl | |
| 98 | 99 | if {$tclpath eq "1"} { |
| 99 | 100 | # Use the system Tcl. Look in some likely places. |
| 100 | - array set tclconfig [parse-tclconfig-sh /usr /usr/local /usr/share /opt/local] | |
| 101 | - set msg "on your system" | |
| 102 | - } else { | |
| 101 | + array set tclconfig [parse-tclconfig-sh \ | |
| 102 | + /usr /usr/local /usr/share /opt/local] | |
| 103 | + set msg "on your system" | |
| 104 | + } else { | |
| 103 | 105 | array set tclconfig [parse-tclconfig-sh $tclpath] |
| 104 | - set msg "at $tclpath" | |
| 105 | - } | |
| 106 | - if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} { | |
| 107 | - user-error "Cannot find Tcl $msg" | |
| 108 | - } | |
| 109 | - set cflags $tclconfig(TCL_INCLUDE_SPEC) | |
| 110 | - set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)" | |
| 111 | - cc-with [list -cflags $cflags -libs $libs] { | |
| 112 | - if {![cc-check-functions Tcl_CreateInterp]} { | |
| 113 | - user-error "Cannot find a usable Tcl $msg" | |
| 114 | - } | |
| 115 | - } | |
| 116 | - set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL) | |
| 117 | - msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)" | |
| 118 | - define-append LIBS $libs | |
| 119 | - define-append EXTRA_CFLAGS $cflags | |
| 120 | - define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) | |
| 121 | - | |
| 106 | + set msg "at $tclpath" | |
| 107 | + } | |
| 108 | + if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} { | |
| 109 | + user-error "Cannot find Tcl $msg" | |
| 110 | + } | |
| 111 | + set tclstubs [opt-bool with-tcl-stubs] | |
| 112 | + if {$tclstubs && $tclconfig(TCL_SUPPORTS_STUBS)} { | |
| 113 | + set libs "$tclconfig(TCL_STUB_LIB_SPEC)" | |
| 114 | + define FOSSIL_ENABLE_TCL_STUBS | |
| 115 | + define USE_TCL_STUBS | |
| 116 | + } else { | |
| 117 | + set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)" | |
| 118 | + } | |
| 119 | + set cflags $tclconfig(TCL_INCLUDE_SPEC) | |
| 120 | + cc-with [list -cflags $cflags -libs $libs] { | |
| 121 | + if {$tclstubs} { | |
| 122 | + if {![cc-check-functions Tcl_InitStubs]} { | |
| 123 | + user-error "Cannot find a usable Tcl stubs library $msg" | |
| 124 | + } | |
| 125 | + } else { | |
| 126 | + if {![cc-check-functions Tcl_CreateInterp]} { | |
| 127 | + user-error "Cannot find a usable Tcl library $msg" | |
| 128 | + } | |
| 129 | + } | |
| 130 | + } | |
| 131 | + set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL) | |
| 132 | + msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)" | |
| 133 | + define-append LIBS $libs | |
| 134 | + define-append EXTRA_CFLAGS $cflags | |
| 135 | + define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) | |
| 122 | 136 | define FOSSIL_ENABLE_TCL |
| 123 | 137 | } |
| 124 | 138 | |
| 125 | 139 | # Helper for openssl checking |
| 126 | 140 | proc check-for-openssl {msg {cflags {}}} { |
| 127 | 141 |
| --- auto.def | |
| +++ auto.def | |
| @@ -5,10 +5,11 @@ | |
| 5 | options { |
| 6 | with-openssl:path|auto|none |
| 7 | => {Look for openssl in the given path, or auto or none} |
| 8 | with-zlib:path => {Look for zlib in the given path} |
| 9 | with-tcl:path => {Enable Tcl integration, with Tcl in the specified path} |
| 10 | internal-sqlite=1 => {Don't use the internal sqlite, use the system one} |
| 11 | static=0 => {Link a static executable} |
| 12 | lineedit=1 => {Disable line editing} |
| 13 | fossil-debug=0 => {Build with fossil debugging enabled} |
| 14 | json=0 => {Build with fossil JSON API enabled} |
| @@ -32,11 +33,11 @@ | |
| 32 | if {![opt-bool internal-sqlite]} { |
| 33 | proc find_internal_sqlite {} { |
| 34 | |
| 35 | # On some systems (slackware), libsqlite3 requires -ldl to link. So |
| 36 | # search for the system SQLite once with -ldl, and once without. If |
| 37 | # the library can only be found with $extralibs set to -ldl, then |
| 38 | # the code below will append -ldl to LIBS. |
| 39 | # |
| 40 | foreach extralibs {{} {-ldl}} { |
| 41 | |
| 42 | # Locate the system SQLite by searching for sqlite3_open(). Then check |
| @@ -92,35 +93,48 @@ | |
| 92 | user-error "zlib not found please install it or specify the location with --with-zlib" |
| 93 | } |
| 94 | |
| 95 | set tclpath [opt-val with-tcl] |
| 96 | if {$tclpath ne ""} { |
| 97 | # Note parse-tclconfig-sh is in autosetup/local.tcl |
| 98 | if {$tclpath eq "1"} { |
| 99 | # Use the system Tcl. Look in some likely places. |
| 100 | array set tclconfig [parse-tclconfig-sh /usr /usr/local /usr/share /opt/local] |
| 101 | set msg "on your system" |
| 102 | } else { |
| 103 | array set tclconfig [parse-tclconfig-sh $tclpath] |
| 104 | set msg "at $tclpath" |
| 105 | } |
| 106 | if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} { |
| 107 | user-error "Cannot find Tcl $msg" |
| 108 | } |
| 109 | set cflags $tclconfig(TCL_INCLUDE_SPEC) |
| 110 | set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)" |
| 111 | cc-with [list -cflags $cflags -libs $libs] { |
| 112 | if {![cc-check-functions Tcl_CreateInterp]} { |
| 113 | user-error "Cannot find a usable Tcl $msg" |
| 114 | } |
| 115 | } |
| 116 | set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL) |
| 117 | msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)" |
| 118 | define-append LIBS $libs |
| 119 | define-append EXTRA_CFLAGS $cflags |
| 120 | define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) |
| 121 | |
| 122 | define FOSSIL_ENABLE_TCL |
| 123 | } |
| 124 | |
| 125 | # Helper for openssl checking |
| 126 | proc check-for-openssl {msg {cflags {}}} { |
| 127 |
| --- auto.def | |
| +++ auto.def | |
| @@ -5,10 +5,11 @@ | |
| 5 | options { |
| 6 | with-openssl:path|auto|none |
| 7 | => {Look for openssl in the given path, or auto or none} |
| 8 | with-zlib:path => {Look for zlib in the given path} |
| 9 | with-tcl:path => {Enable Tcl integration, with Tcl in the specified path} |
| 10 | with-tcl-stubs=0 => {Enable Tcl integration via stubs mechanism} |
| 11 | internal-sqlite=1 => {Don't use the internal sqlite, use the system one} |
| 12 | static=0 => {Link a static executable} |
| 13 | lineedit=1 => {Disable line editing} |
| 14 | fossil-debug=0 => {Build with fossil debugging enabled} |
| 15 | json=0 => {Build with fossil JSON API enabled} |
| @@ -32,11 +33,11 @@ | |
| 33 | if {![opt-bool internal-sqlite]} { |
| 34 | proc find_internal_sqlite {} { |
| 35 | |
| 36 | # On some systems (slackware), libsqlite3 requires -ldl to link. So |
| 37 | # search for the system SQLite once with -ldl, and once without. If |
| 38 | # the library can only be found with $extralibs set to -ldl, then |
| 39 | # the code below will append -ldl to LIBS. |
| 40 | # |
| 41 | foreach extralibs {{} {-ldl}} { |
| 42 | |
| 43 | # Locate the system SQLite by searching for sqlite3_open(). Then check |
| @@ -92,35 +93,48 @@ | |
| 93 | user-error "zlib not found please install it or specify the location with --with-zlib" |
| 94 | } |
| 95 | |
| 96 | set tclpath [opt-val with-tcl] |
| 97 | if {$tclpath ne ""} { |
| 98 | # Note parse-tclconfig-sh is in autosetup/local.tcl |
| 99 | if {$tclpath eq "1"} { |
| 100 | # Use the system Tcl. Look in some likely places. |
| 101 | array set tclconfig [parse-tclconfig-sh \ |
| 102 | /usr /usr/local /usr/share /opt/local] |
| 103 | set msg "on your system" |
| 104 | } else { |
| 105 | array set tclconfig [parse-tclconfig-sh $tclpath] |
| 106 | set msg "at $tclpath" |
| 107 | } |
| 108 | if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} { |
| 109 | user-error "Cannot find Tcl $msg" |
| 110 | } |
| 111 | set tclstubs [opt-bool with-tcl-stubs] |
| 112 | if {$tclstubs && $tclconfig(TCL_SUPPORTS_STUBS)} { |
| 113 | set libs "$tclconfig(TCL_STUB_LIB_SPEC)" |
| 114 | define FOSSIL_ENABLE_TCL_STUBS |
| 115 | define USE_TCL_STUBS |
| 116 | } else { |
| 117 | set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)" |
| 118 | } |
| 119 | set cflags $tclconfig(TCL_INCLUDE_SPEC) |
| 120 | cc-with [list -cflags $cflags -libs $libs] { |
| 121 | if {$tclstubs} { |
| 122 | if {![cc-check-functions Tcl_InitStubs]} { |
| 123 | user-error "Cannot find a usable Tcl stubs library $msg" |
| 124 | } |
| 125 | } else { |
| 126 | if {![cc-check-functions Tcl_CreateInterp]} { |
| 127 | user-error "Cannot find a usable Tcl library $msg" |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL) |
| 132 | msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)" |
| 133 | define-append LIBS $libs |
| 134 | define-append EXTRA_CFLAGS $cflags |
| 135 | define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) |
| 136 | define FOSSIL_ENABLE_TCL |
| 137 | } |
| 138 | |
| 139 | # Helper for openssl checking |
| 140 | proc check-for-openssl {msg {cflags {}}} { |
| 141 |
+61
-28
| --- src/bisect.c | ||
| +++ src/bisect.c | ||
| @@ -85,10 +85,66 @@ | ||
| 85 | 85 | } |
| 86 | 86 | } |
| 87 | 87 | assert( r>=0 ); |
| 88 | 88 | return r; |
| 89 | 89 | } |
| 90 | + | |
| 91 | +/* | |
| 92 | +** List a bisect path. | |
| 93 | +*/ | |
| 94 | +static void bisect_list(int abbreviated){ | |
| 95 | + PathNode *p; | |
| 96 | + int vid = db_lget_int("checkout", 0); | |
| 97 | + int n; | |
| 98 | + Stmt s; | |
| 99 | + int nStep; | |
| 100 | + int nHidden = 0; | |
| 101 | + bisect_path(); | |
| 102 | + db_prepare(&s, "SELECT blob.uuid, datetime(event.mtime) " | |
| 103 | + " FROM blob, event" | |
| 104 | + " WHERE blob.rid=:rid AND event.objid=:rid" | |
| 105 | + " AND event.type='ci'"); | |
| 106 | + nStep = path_length(); | |
| 107 | + if( abbreviated ){ | |
| 108 | + for(p=path_last(); p; p=p->pFrom) p->isHidden = 1; | |
| 109 | + for(p=path_last(), n=0; p; p=p->pFrom, n++){ | |
| 110 | + if( p->rid==bisect.good | |
| 111 | + || p->rid==bisect.bad | |
| 112 | + || p->rid==vid | |
| 113 | + || (nStep>1 && n==nStep/2) | |
| 114 | + ){ | |
| 115 | + p->isHidden = 0; | |
| 116 | + if( p->pFrom ) p->pFrom->isHidden = 0; | |
| 117 | + } | |
| 118 | + } | |
| 119 | + for(p=path_last(); p; p=p->pFrom){ | |
| 120 | + if( p->pFrom && p->pFrom->isHidden==0 ) p->isHidden = 0; | |
| 121 | + } | |
| 122 | + } | |
| 123 | + for(p=path_last(), n=0; p; p=p->pFrom, n++){ | |
| 124 | + if( p->isHidden && (nHidden || (p->pFrom && p->pFrom->isHidden)) ){ | |
| 125 | + nHidden++; | |
| 126 | + continue; | |
| 127 | + }else if( nHidden ){ | |
| 128 | + fossil_print(" ... eliding %d check-ins\n", nHidden); | |
| 129 | + nHidden = 0; | |
| 130 | + } | |
| 131 | + db_bind_int(&s, ":rid", p->rid); | |
| 132 | + if( db_step(&s)==SQLITE_ROW ){ | |
| 133 | + const char *zUuid = db_column_text(&s, 0); | |
| 134 | + const char *zDate = db_column_text(&s, 1); | |
| 135 | + fossil_print("%s %S", zDate, zUuid); | |
| 136 | + if( p->rid==bisect.good ) fossil_print(" GOOD"); | |
| 137 | + if( p->rid==bisect.bad ) fossil_print(" BAD"); | |
| 138 | + if( p->rid==vid ) fossil_print(" CURRENT"); | |
| 139 | + if( nStep>1 && n==nStep/2 ) fossil_print(" NEXT"); | |
| 140 | + fossil_print("\n"); | |
| 141 | + } | |
| 142 | + db_reset(&s); | |
| 143 | + } | |
| 144 | + db_finalize(&s); | |
| 145 | +} | |
| 90 | 146 | |
| 91 | 147 | /* |
| 92 | 148 | ** COMMAND: bisect |
| 93 | 149 | ** |
| 94 | 150 | ** Usage: %fossil bisect SUBCOMMAND ... |
| @@ -118,11 +174,11 @@ | ||
| 118 | 174 | ** fossil bisect reset |
| 119 | 175 | ** |
| 120 | 176 | ** Reinitialize a bisect session. This cancels prior bisect history |
| 121 | 177 | ** and allows a bisect session to start over from the beginning. |
| 122 | 178 | ** |
| 123 | -** fossil bisect vlist | |
| 179 | +** fossil bisect vlist|ls ?--all? | |
| 124 | 180 | ** |
| 125 | 181 | ** List the versions in between "bad" and "good". |
| 126 | 182 | */ |
| 127 | 183 | void bisect_cmd(void){ |
| 128 | 184 | int n; |
| @@ -179,10 +235,11 @@ | ||
| 179 | 235 | g.argv[1] = "update"; |
| 180 | 236 | g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid); |
| 181 | 237 | g.argc = 3; |
| 182 | 238 | g.fNoSync = 1; |
| 183 | 239 | update_cmd(); |
| 240 | + bisect_list(1); | |
| 184 | 241 | }else if( memcmp(zCmd, "options", n)==0 ){ |
| 185 | 242 | if( g.argc==3 ){ |
| 186 | 243 | unsigned int i; |
| 187 | 244 | for(i=0; i<sizeof(aBisectOption)/sizeof(aBisectOption[0]); i++){ |
| 188 | 245 | char *z = mprintf("bisect-%s", aBisectOption[i].zName); |
| @@ -213,36 +270,12 @@ | ||
| 213 | 270 | } |
| 214 | 271 | }else if( memcmp(zCmd, "reset", n)==0 ){ |
| 215 | 272 | db_multi_exec( |
| 216 | 273 | "DELETE FROM vvar WHERE name IN ('bisect-good', 'bisect-bad');" |
| 217 | 274 | ); |
| 218 | - }else if( memcmp(zCmd, "vlist", n)==0 ){ | |
| 219 | - PathNode *p; | |
| 220 | - int vid = db_lget_int("checkout", 0); | |
| 221 | - int n; | |
| 222 | - Stmt s; | |
| 223 | - int nStep; | |
| 224 | - bisect_path(); | |
| 225 | - db_prepare(&s, "SELECT substr(blob.uuid,1,20) || ' ' || " | |
| 226 | - " datetime(event.mtime) FROM blob, event" | |
| 227 | - " WHERE blob.rid=:rid AND event.objid=:rid" | |
| 228 | - " AND event.type='ci'"); | |
| 229 | - nStep = path_length(); | |
| 230 | - for(p=path_last(), n=0; p; p=p->pFrom, n++){ | |
| 231 | - const char *z; | |
| 232 | - db_bind_int(&s, ":rid", p->rid); | |
| 233 | - if( db_step(&s)==SQLITE_ROW ){ | |
| 234 | - z = db_column_text(&s, 0); | |
| 235 | - fossil_print("%s", z); | |
| 236 | - if( p->rid==bisect.good ) fossil_print(" GOOD"); | |
| 237 | - if( p->rid==bisect.bad ) fossil_print(" BAD"); | |
| 238 | - if( p->rid==vid ) fossil_print(" CURRENT"); | |
| 239 | - if( nStep>1 && n==nStep/2 ) fossil_print(" NEXT"); | |
| 240 | - fossil_print("\n"); | |
| 241 | - } | |
| 242 | - db_reset(&s); | |
| 243 | - } | |
| 244 | - db_finalize(&s); | |
| 275 | + }else if( memcmp(zCmd, "vlist", n)==0 || memcmp(zCmd, "ls", n)==0 ){ | |
| 276 | + int fAll = find_option("all", 0, 0)!=0; | |
| 277 | + bisect_list(!fAll); | |
| 245 | 278 | }else{ |
| 246 | 279 | usage("bad|good|next|reset|vlist ..."); |
| 247 | 280 | } |
| 248 | 281 | } |
| 249 | 282 |
| --- src/bisect.c | |
| +++ src/bisect.c | |
| @@ -85,10 +85,66 @@ | |
| 85 | } |
| 86 | } |
| 87 | assert( r>=0 ); |
| 88 | return r; |
| 89 | } |
| 90 | |
| 91 | /* |
| 92 | ** COMMAND: bisect |
| 93 | ** |
| 94 | ** Usage: %fossil bisect SUBCOMMAND ... |
| @@ -118,11 +174,11 @@ | |
| 118 | ** fossil bisect reset |
| 119 | ** |
| 120 | ** Reinitialize a bisect session. This cancels prior bisect history |
| 121 | ** and allows a bisect session to start over from the beginning. |
| 122 | ** |
| 123 | ** fossil bisect vlist |
| 124 | ** |
| 125 | ** List the versions in between "bad" and "good". |
| 126 | */ |
| 127 | void bisect_cmd(void){ |
| 128 | int n; |
| @@ -179,10 +235,11 @@ | |
| 179 | g.argv[1] = "update"; |
| 180 | g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid); |
| 181 | g.argc = 3; |
| 182 | g.fNoSync = 1; |
| 183 | update_cmd(); |
| 184 | }else if( memcmp(zCmd, "options", n)==0 ){ |
| 185 | if( g.argc==3 ){ |
| 186 | unsigned int i; |
| 187 | for(i=0; i<sizeof(aBisectOption)/sizeof(aBisectOption[0]); i++){ |
| 188 | char *z = mprintf("bisect-%s", aBisectOption[i].zName); |
| @@ -213,36 +270,12 @@ | |
| 213 | } |
| 214 | }else if( memcmp(zCmd, "reset", n)==0 ){ |
| 215 | db_multi_exec( |
| 216 | "DELETE FROM vvar WHERE name IN ('bisect-good', 'bisect-bad');" |
| 217 | ); |
| 218 | }else if( memcmp(zCmd, "vlist", n)==0 ){ |
| 219 | PathNode *p; |
| 220 | int vid = db_lget_int("checkout", 0); |
| 221 | int n; |
| 222 | Stmt s; |
| 223 | int nStep; |
| 224 | bisect_path(); |
| 225 | db_prepare(&s, "SELECT substr(blob.uuid,1,20) || ' ' || " |
| 226 | " datetime(event.mtime) FROM blob, event" |
| 227 | " WHERE blob.rid=:rid AND event.objid=:rid" |
| 228 | " AND event.type='ci'"); |
| 229 | nStep = path_length(); |
| 230 | for(p=path_last(), n=0; p; p=p->pFrom, n++){ |
| 231 | const char *z; |
| 232 | db_bind_int(&s, ":rid", p->rid); |
| 233 | if( db_step(&s)==SQLITE_ROW ){ |
| 234 | z = db_column_text(&s, 0); |
| 235 | fossil_print("%s", z); |
| 236 | if( p->rid==bisect.good ) fossil_print(" GOOD"); |
| 237 | if( p->rid==bisect.bad ) fossil_print(" BAD"); |
| 238 | if( p->rid==vid ) fossil_print(" CURRENT"); |
| 239 | if( nStep>1 && n==nStep/2 ) fossil_print(" NEXT"); |
| 240 | fossil_print("\n"); |
| 241 | } |
| 242 | db_reset(&s); |
| 243 | } |
| 244 | db_finalize(&s); |
| 245 | }else{ |
| 246 | usage("bad|good|next|reset|vlist ..."); |
| 247 | } |
| 248 | } |
| 249 |
| --- src/bisect.c | |
| +++ src/bisect.c | |
| @@ -85,10 +85,66 @@ | |
| 85 | } |
| 86 | } |
| 87 | assert( r>=0 ); |
| 88 | return r; |
| 89 | } |
| 90 | |
| 91 | /* |
| 92 | ** List a bisect path. |
| 93 | */ |
| 94 | static void bisect_list(int abbreviated){ |
| 95 | PathNode *p; |
| 96 | int vid = db_lget_int("checkout", 0); |
| 97 | int n; |
| 98 | Stmt s; |
| 99 | int nStep; |
| 100 | int nHidden = 0; |
| 101 | bisect_path(); |
| 102 | db_prepare(&s, "SELECT blob.uuid, datetime(event.mtime) " |
| 103 | " FROM blob, event" |
| 104 | " WHERE blob.rid=:rid AND event.objid=:rid" |
| 105 | " AND event.type='ci'"); |
| 106 | nStep = path_length(); |
| 107 | if( abbreviated ){ |
| 108 | for(p=path_last(); p; p=p->pFrom) p->isHidden = 1; |
| 109 | for(p=path_last(), n=0; p; p=p->pFrom, n++){ |
| 110 | if( p->rid==bisect.good |
| 111 | || p->rid==bisect.bad |
| 112 | || p->rid==vid |
| 113 | || (nStep>1 && n==nStep/2) |
| 114 | ){ |
| 115 | p->isHidden = 0; |
| 116 | if( p->pFrom ) p->pFrom->isHidden = 0; |
| 117 | } |
| 118 | } |
| 119 | for(p=path_last(); p; p=p->pFrom){ |
| 120 | if( p->pFrom && p->pFrom->isHidden==0 ) p->isHidden = 0; |
| 121 | } |
| 122 | } |
| 123 | for(p=path_last(), n=0; p; p=p->pFrom, n++){ |
| 124 | if( p->isHidden && (nHidden || (p->pFrom && p->pFrom->isHidden)) ){ |
| 125 | nHidden++; |
| 126 | continue; |
| 127 | }else if( nHidden ){ |
| 128 | fossil_print(" ... eliding %d check-ins\n", nHidden); |
| 129 | nHidden = 0; |
| 130 | } |
| 131 | db_bind_int(&s, ":rid", p->rid); |
| 132 | if( db_step(&s)==SQLITE_ROW ){ |
| 133 | const char *zUuid = db_column_text(&s, 0); |
| 134 | const char *zDate = db_column_text(&s, 1); |
| 135 | fossil_print("%s %S", zDate, zUuid); |
| 136 | if( p->rid==bisect.good ) fossil_print(" GOOD"); |
| 137 | if( p->rid==bisect.bad ) fossil_print(" BAD"); |
| 138 | if( p->rid==vid ) fossil_print(" CURRENT"); |
| 139 | if( nStep>1 && n==nStep/2 ) fossil_print(" NEXT"); |
| 140 | fossil_print("\n"); |
| 141 | } |
| 142 | db_reset(&s); |
| 143 | } |
| 144 | db_finalize(&s); |
| 145 | } |
| 146 | |
| 147 | /* |
| 148 | ** COMMAND: bisect |
| 149 | ** |
| 150 | ** Usage: %fossil bisect SUBCOMMAND ... |
| @@ -118,11 +174,11 @@ | |
| 174 | ** fossil bisect reset |
| 175 | ** |
| 176 | ** Reinitialize a bisect session. This cancels prior bisect history |
| 177 | ** and allows a bisect session to start over from the beginning. |
| 178 | ** |
| 179 | ** fossil bisect vlist|ls ?--all? |
| 180 | ** |
| 181 | ** List the versions in between "bad" and "good". |
| 182 | */ |
| 183 | void bisect_cmd(void){ |
| 184 | int n; |
| @@ -179,10 +235,11 @@ | |
| 235 | g.argv[1] = "update"; |
| 236 | g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid); |
| 237 | g.argc = 3; |
| 238 | g.fNoSync = 1; |
| 239 | update_cmd(); |
| 240 | bisect_list(1); |
| 241 | }else if( memcmp(zCmd, "options", n)==0 ){ |
| 242 | if( g.argc==3 ){ |
| 243 | unsigned int i; |
| 244 | for(i=0; i<sizeof(aBisectOption)/sizeof(aBisectOption[0]); i++){ |
| 245 | char *z = mprintf("bisect-%s", aBisectOption[i].zName); |
| @@ -213,36 +270,12 @@ | |
| 270 | } |
| 271 | }else if( memcmp(zCmd, "reset", n)==0 ){ |
| 272 | db_multi_exec( |
| 273 | "DELETE FROM vvar WHERE name IN ('bisect-good', 'bisect-bad');" |
| 274 | ); |
| 275 | }else if( memcmp(zCmd, "vlist", n)==0 || memcmp(zCmd, "ls", n)==0 ){ |
| 276 | int fAll = find_option("all", 0, 0)!=0; |
| 277 | bisect_list(!fAll); |
| 278 | }else{ |
| 279 | usage("bad|good|next|reset|vlist ..."); |
| 280 | } |
| 281 | } |
| 282 |
+99
-81
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -417,10 +417,96 @@ | ||
| 417 | 417 | } |
| 418 | 418 | } |
| 419 | 419 | } |
| 420 | 420 | db_finalize(&q); |
| 421 | 421 | } |
| 422 | + | |
| 423 | +/* | |
| 424 | +** Prompt the user for a check-in or stash comment (given in pPrompt), | |
| 425 | +** gather the response, then return the response in pComment. | |
| 426 | +** | |
| 427 | +** Lines of the prompt that begin with # are discarded. Excess whitespace | |
| 428 | +** is removed from the reply. | |
| 429 | +** | |
| 430 | +** Appropriate encoding translations are made on windows. | |
| 431 | +*/ | |
| 432 | +void prompt_for_user_comment(Blob *pComment, Blob *pPrompt){ | |
| 433 | + const char *zEditor; | |
| 434 | + char *zCmd; | |
| 435 | + char *zFile; | |
| 436 | + Blob reply, line; | |
| 437 | + char *zComment; | |
| 438 | + int i; | |
| 439 | + | |
| 440 | + zEditor = db_get("editor", 0); | |
| 441 | + if( zEditor==0 ){ | |
| 442 | + zEditor = fossil_getenv("VISUAL"); | |
| 443 | + } | |
| 444 | + if( zEditor==0 ){ | |
| 445 | + zEditor = fossil_getenv("EDITOR"); | |
| 446 | + } | |
| 447 | +#ifdef _WIN32 | |
| 448 | + if( zEditor==0 ){ | |
| 449 | + zEditor = mprintf("%s\\notepad.exe", fossil_getenv("SystemRoot")); | |
| 450 | + } | |
| 451 | +#endif | |
| 452 | + if( zEditor==0 ){ | |
| 453 | + blob_append(pPrompt, | |
| 454 | + "#\n" | |
| 455 | + "# Since no default text editor is set using EDITOR or VISUAL\n" | |
| 456 | + "# environment variables or the \"fossil set editor\" command,\n" | |
| 457 | + "# and because no comment was specified using the \"-m\" or \"-M\"\n" | |
| 458 | + "# command-line options, you will need to enter the comment below.\n" | |
| 459 | + "# Type \".\" on a line by itself when you are done:\n", -1); | |
| 460 | + zFile = mprintf("-"); | |
| 461 | + }else{ | |
| 462 | + zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", | |
| 463 | + g.zLocalRoot); | |
| 464 | + } | |
| 465 | +#if defined(_WIN32) | |
| 466 | + blob_add_cr(pPrompt); | |
| 467 | +#endif | |
| 468 | + blob_write_to_file(pPrompt, zFile); | |
| 469 | + if( zEditor ){ | |
| 470 | + zCmd = mprintf("%s \"%s\"", zEditor, zFile); | |
| 471 | + fossil_print("%s\n", zCmd); | |
| 472 | + if( fossil_system(zCmd) ){ | |
| 473 | + fossil_fatal("editor aborted: \"%s\"", zCmd); | |
| 474 | + } | |
| 475 | + | |
| 476 | + blob_read_from_file(&reply, zFile); | |
| 477 | + }else{ | |
| 478 | + char zIn[300]; | |
| 479 | + blob_zero(&reply); | |
| 480 | + while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ | |
| 481 | + if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ | |
| 482 | + break; | |
| 483 | + } | |
| 484 | + blob_append(&reply, zIn, -1); | |
| 485 | + } | |
| 486 | + } | |
| 487 | + blob_remove_cr(&reply); | |
| 488 | + file_delete(zFile); | |
| 489 | + free(zFile); | |
| 490 | + blob_zero(pComment); | |
| 491 | + while( blob_line(&reply, &line) ){ | |
| 492 | + int i, n; | |
| 493 | + char *z; | |
| 494 | + n = blob_size(&line); | |
| 495 | + z = blob_buffer(&line); | |
| 496 | + for(i=0; i<n && fossil_isspace(z[i]); i++){} | |
| 497 | + if( i<n && z[i]=='#' ) continue; | |
| 498 | + if( i<n || blob_size(pComment)>0 ){ | |
| 499 | + blob_appendf(pComment, "%b", &line); | |
| 500 | + } | |
| 501 | + } | |
| 502 | + blob_reset(&reply); | |
| 503 | + zComment = blob_str(pComment); | |
| 504 | + i = strlen(zComment); | |
| 505 | + while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } | |
| 506 | + blob_resize(pComment, i); | |
| 507 | +} | |
| 422 | 508 | |
| 423 | 509 | /* |
| 424 | 510 | ** Prepare a commit comment. Let the user modify it using the |
| 425 | 511 | ** editor specified in the global_config table or either |
| 426 | 512 | ** the VISUAL or EDITOR environment variable. |
| @@ -442,111 +528,43 @@ | ||
| 442 | 528 | char *zInit, |
| 443 | 529 | const char *zBranch, |
| 444 | 530 | int parent_rid, |
| 445 | 531 | const char *zUserOvrd |
| 446 | 532 | ){ |
| 533 | + Blob prompt; | |
| 534 | +#ifdef _WIN32 | |
| 447 | 535 | static const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| 448 | - const char *zEditor; | |
| 449 | - char *zCmd; | |
| 450 | - char *zFile; | |
| 451 | - Blob text, line; | |
| 452 | - char *zComment; | |
| 453 | - int i; | |
| 454 | -#ifdef _WIN32 | |
| 455 | - blob_init(&text, (const char *) bom, 3); | |
| 536 | + blob_init(&prompt, (const char *) bom, 3); | |
| 456 | 537 | if( zInit && zInit[0]) { |
| 457 | - blob_append(&text, zInit, -1); | |
| 538 | + blob_append(&prompt, zInit, -1); | |
| 458 | 539 | } |
| 459 | 540 | #else |
| 460 | - blob_init(&text, zInit, -1); | |
| 541 | + blob_init(&prompt, zInit, -1); | |
| 461 | 542 | #endif |
| 462 | - blob_append(&text, | |
| 543 | + blob_append(&prompt, | |
| 463 | 544 | "\n" |
| 464 | 545 | "# Enter comments on this check-in. Lines beginning with # are ignored.\n" |
| 465 | 546 | "# The check-in comment follows wiki formatting rules.\n" |
| 466 | 547 | "#\n", -1 |
| 467 | 548 | ); |
| 468 | - blob_appendf(&text, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); | |
| 549 | + blob_appendf(&prompt, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); | |
| 469 | 550 | if( zBranch && zBranch[0] ){ |
| 470 | - blob_appendf(&text, "# tags: %s\n#\n", zBranch); | |
| 551 | + blob_appendf(&prompt, "# tags: %s\n#\n", zBranch); | |
| 471 | 552 | }else{ |
| 472 | 553 | char *zTags = info_tags_of_checkin(parent_rid, 1); |
| 473 | - if( zTags ) blob_appendf(&text, "# tags: %z\n#\n", zTags); | |
| 554 | + if( zTags ) blob_appendf(&prompt, "# tags: %z\n#\n", zTags); | |
| 474 | 555 | } |
| 556 | + status_report(&prompt, "# ", 1, 0); | |
| 475 | 557 | if( g.markPrivate ){ |
| 476 | - blob_append(&text, | |
| 558 | + blob_append(&prompt, | |
| 477 | 559 | "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" |
| 478 | 560 | "# repositories.\n" |
| 479 | 561 | "#\n", -1 |
| 480 | 562 | ); |
| 481 | 563 | } |
| 482 | - status_report(&text, "# ", 1, 0); | |
| 483 | - zEditor = db_get("editor", 0); | |
| 484 | - if( zEditor==0 ){ | |
| 485 | - zEditor = fossil_getenv("VISUAL"); | |
| 486 | - } | |
| 487 | - if( zEditor==0 ){ | |
| 488 | - zEditor = fossil_getenv("EDITOR"); | |
| 489 | - } | |
| 490 | - if( zEditor==0 ){ | |
| 491 | - blob_append(&text, | |
| 492 | - "#\n" | |
| 493 | - "# Since no default text editor is set using EDITOR or VISUAL\n" | |
| 494 | - "# environment variables or the \"fossil set editor\" command,\n" | |
| 495 | - "# and because no check-in comment was specified using the \"-m\"\n" | |
| 496 | - "# or \"-M\" command-line options, you will need to enter the\n" | |
| 497 | - "# check-in comment below. Type \".\" on a line by itself when\n" | |
| 498 | - "# you are done:\n", -1); | |
| 499 | - zFile = mprintf("-"); | |
| 500 | - }else{ | |
| 501 | - zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", | |
| 502 | - g.zLocalRoot); | |
| 503 | - } | |
| 504 | -#if defined(_WIN32) | |
| 505 | - blob_add_cr(&text); | |
| 506 | -#endif | |
| 507 | - blob_write_to_file(&text, zFile); | |
| 508 | - if( zEditor ){ | |
| 509 | - zCmd = mprintf("%s \"%s\"", zEditor, zFile); | |
| 510 | - fossil_print("%s\n", zCmd); | |
| 511 | - if( fossil_system(zCmd) ){ | |
| 512 | - fossil_panic("editor aborted"); | |
| 513 | - } | |
| 514 | - blob_reset(&text); | |
| 515 | - blob_read_from_file(&text, zFile); | |
| 516 | - }else{ | |
| 517 | - char zIn[300]; | |
| 518 | - blob_reset(&text); | |
| 519 | - while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ | |
| 520 | - if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ | |
| 521 | - break; | |
| 522 | - } | |
| 523 | - blob_append(&text, zIn, -1); | |
| 524 | - } | |
| 525 | - } | |
| 526 | - blob_remove_cr(&text); | |
| 527 | - if( zEditor ) { | |
| 528 | - file_delete(zFile); | |
| 529 | - } | |
| 530 | - fossil_free(zFile); | |
| 531 | - blob_zero(pComment); | |
| 532 | - while( blob_line(&text, &line) ){ | |
| 533 | - int i, n; | |
| 534 | - char *z; | |
| 535 | - n = blob_size(&line); | |
| 536 | - z = blob_buffer(&line); | |
| 537 | - for(i=0; i<n && fossil_isspace(z[i]); i++){} | |
| 538 | - if( i<n && z[i]=='#' ) continue; | |
| 539 | - if( i<n || blob_size(pComment)>0 ){ | |
| 540 | - blob_appendf(pComment, "%b", &line); | |
| 541 | - } | |
| 542 | - } | |
| 543 | - blob_reset(&text); | |
| 544 | - zComment = blob_str(pComment); | |
| 545 | - i = strlen(zComment); | |
| 546 | - while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } | |
| 547 | - blob_resize(pComment, i); | |
| 564 | + prompt_for_user_comment(pComment, &prompt); | |
| 565 | + blob_reset(&prompt); | |
| 548 | 566 | } |
| 549 | 567 | |
| 550 | 568 | /* |
| 551 | 569 | ** Populate the Global.aCommitFile[] based on the command line arguments |
| 552 | 570 | ** to a [commit] command. Global.aCommitFile is an array of integers |
| 553 | 571 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -417,10 +417,96 @@ | |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | db_finalize(&q); |
| 421 | } |
| 422 | |
| 423 | /* |
| 424 | ** Prepare a commit comment. Let the user modify it using the |
| 425 | ** editor specified in the global_config table or either |
| 426 | ** the VISUAL or EDITOR environment variable. |
| @@ -442,111 +528,43 @@ | |
| 442 | char *zInit, |
| 443 | const char *zBranch, |
| 444 | int parent_rid, |
| 445 | const char *zUserOvrd |
| 446 | ){ |
| 447 | static const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| 448 | const char *zEditor; |
| 449 | char *zCmd; |
| 450 | char *zFile; |
| 451 | Blob text, line; |
| 452 | char *zComment; |
| 453 | int i; |
| 454 | #ifdef _WIN32 |
| 455 | blob_init(&text, (const char *) bom, 3); |
| 456 | if( zInit && zInit[0]) { |
| 457 | blob_append(&text, zInit, -1); |
| 458 | } |
| 459 | #else |
| 460 | blob_init(&text, zInit, -1); |
| 461 | #endif |
| 462 | blob_append(&text, |
| 463 | "\n" |
| 464 | "# Enter comments on this check-in. Lines beginning with # are ignored.\n" |
| 465 | "# The check-in comment follows wiki formatting rules.\n" |
| 466 | "#\n", -1 |
| 467 | ); |
| 468 | blob_appendf(&text, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); |
| 469 | if( zBranch && zBranch[0] ){ |
| 470 | blob_appendf(&text, "# tags: %s\n#\n", zBranch); |
| 471 | }else{ |
| 472 | char *zTags = info_tags_of_checkin(parent_rid, 1); |
| 473 | if( zTags ) blob_appendf(&text, "# tags: %z\n#\n", zTags); |
| 474 | } |
| 475 | if( g.markPrivate ){ |
| 476 | blob_append(&text, |
| 477 | "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" |
| 478 | "# repositories.\n" |
| 479 | "#\n", -1 |
| 480 | ); |
| 481 | } |
| 482 | status_report(&text, "# ", 1, 0); |
| 483 | zEditor = db_get("editor", 0); |
| 484 | if( zEditor==0 ){ |
| 485 | zEditor = fossil_getenv("VISUAL"); |
| 486 | } |
| 487 | if( zEditor==0 ){ |
| 488 | zEditor = fossil_getenv("EDITOR"); |
| 489 | } |
| 490 | if( zEditor==0 ){ |
| 491 | blob_append(&text, |
| 492 | "#\n" |
| 493 | "# Since no default text editor is set using EDITOR or VISUAL\n" |
| 494 | "# environment variables or the \"fossil set editor\" command,\n" |
| 495 | "# and because no check-in comment was specified using the \"-m\"\n" |
| 496 | "# or \"-M\" command-line options, you will need to enter the\n" |
| 497 | "# check-in comment below. Type \".\" on a line by itself when\n" |
| 498 | "# you are done:\n", -1); |
| 499 | zFile = mprintf("-"); |
| 500 | }else{ |
| 501 | zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", |
| 502 | g.zLocalRoot); |
| 503 | } |
| 504 | #if defined(_WIN32) |
| 505 | blob_add_cr(&text); |
| 506 | #endif |
| 507 | blob_write_to_file(&text, zFile); |
| 508 | if( zEditor ){ |
| 509 | zCmd = mprintf("%s \"%s\"", zEditor, zFile); |
| 510 | fossil_print("%s\n", zCmd); |
| 511 | if( fossil_system(zCmd) ){ |
| 512 | fossil_panic("editor aborted"); |
| 513 | } |
| 514 | blob_reset(&text); |
| 515 | blob_read_from_file(&text, zFile); |
| 516 | }else{ |
| 517 | char zIn[300]; |
| 518 | blob_reset(&text); |
| 519 | while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ |
| 520 | if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ |
| 521 | break; |
| 522 | } |
| 523 | blob_append(&text, zIn, -1); |
| 524 | } |
| 525 | } |
| 526 | blob_remove_cr(&text); |
| 527 | if( zEditor ) { |
| 528 | file_delete(zFile); |
| 529 | } |
| 530 | fossil_free(zFile); |
| 531 | blob_zero(pComment); |
| 532 | while( blob_line(&text, &line) ){ |
| 533 | int i, n; |
| 534 | char *z; |
| 535 | n = blob_size(&line); |
| 536 | z = blob_buffer(&line); |
| 537 | for(i=0; i<n && fossil_isspace(z[i]); i++){} |
| 538 | if( i<n && z[i]=='#' ) continue; |
| 539 | if( i<n || blob_size(pComment)>0 ){ |
| 540 | blob_appendf(pComment, "%b", &line); |
| 541 | } |
| 542 | } |
| 543 | blob_reset(&text); |
| 544 | zComment = blob_str(pComment); |
| 545 | i = strlen(zComment); |
| 546 | while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } |
| 547 | blob_resize(pComment, i); |
| 548 | } |
| 549 | |
| 550 | /* |
| 551 | ** Populate the Global.aCommitFile[] based on the command line arguments |
| 552 | ** to a [commit] command. Global.aCommitFile is an array of integers |
| 553 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -417,10 +417,96 @@ | |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | db_finalize(&q); |
| 421 | } |
| 422 | |
| 423 | /* |
| 424 | ** Prompt the user for a check-in or stash comment (given in pPrompt), |
| 425 | ** gather the response, then return the response in pComment. |
| 426 | ** |
| 427 | ** Lines of the prompt that begin with # are discarded. Excess whitespace |
| 428 | ** is removed from the reply. |
| 429 | ** |
| 430 | ** Appropriate encoding translations are made on windows. |
| 431 | */ |
| 432 | void prompt_for_user_comment(Blob *pComment, Blob *pPrompt){ |
| 433 | const char *zEditor; |
| 434 | char *zCmd; |
| 435 | char *zFile; |
| 436 | Blob reply, line; |
| 437 | char *zComment; |
| 438 | int i; |
| 439 | |
| 440 | zEditor = db_get("editor", 0); |
| 441 | if( zEditor==0 ){ |
| 442 | zEditor = fossil_getenv("VISUAL"); |
| 443 | } |
| 444 | if( zEditor==0 ){ |
| 445 | zEditor = fossil_getenv("EDITOR"); |
| 446 | } |
| 447 | #ifdef _WIN32 |
| 448 | if( zEditor==0 ){ |
| 449 | zEditor = mprintf("%s\\notepad.exe", fossil_getenv("SystemRoot")); |
| 450 | } |
| 451 | #endif |
| 452 | if( zEditor==0 ){ |
| 453 | blob_append(pPrompt, |
| 454 | "#\n" |
| 455 | "# Since no default text editor is set using EDITOR or VISUAL\n" |
| 456 | "# environment variables or the \"fossil set editor\" command,\n" |
| 457 | "# and because no comment was specified using the \"-m\" or \"-M\"\n" |
| 458 | "# command-line options, you will need to enter the comment below.\n" |
| 459 | "# Type \".\" on a line by itself when you are done:\n", -1); |
| 460 | zFile = mprintf("-"); |
| 461 | }else{ |
| 462 | zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", |
| 463 | g.zLocalRoot); |
| 464 | } |
| 465 | #if defined(_WIN32) |
| 466 | blob_add_cr(pPrompt); |
| 467 | #endif |
| 468 | blob_write_to_file(pPrompt, zFile); |
| 469 | if( zEditor ){ |
| 470 | zCmd = mprintf("%s \"%s\"", zEditor, zFile); |
| 471 | fossil_print("%s\n", zCmd); |
| 472 | if( fossil_system(zCmd) ){ |
| 473 | fossil_fatal("editor aborted: \"%s\"", zCmd); |
| 474 | } |
| 475 | |
| 476 | blob_read_from_file(&reply, zFile); |
| 477 | }else{ |
| 478 | char zIn[300]; |
| 479 | blob_zero(&reply); |
| 480 | while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ |
| 481 | if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ |
| 482 | break; |
| 483 | } |
| 484 | blob_append(&reply, zIn, -1); |
| 485 | } |
| 486 | } |
| 487 | blob_remove_cr(&reply); |
| 488 | file_delete(zFile); |
| 489 | free(zFile); |
| 490 | blob_zero(pComment); |
| 491 | while( blob_line(&reply, &line) ){ |
| 492 | int i, n; |
| 493 | char *z; |
| 494 | n = blob_size(&line); |
| 495 | z = blob_buffer(&line); |
| 496 | for(i=0; i<n && fossil_isspace(z[i]); i++){} |
| 497 | if( i<n && z[i]=='#' ) continue; |
| 498 | if( i<n || blob_size(pComment)>0 ){ |
| 499 | blob_appendf(pComment, "%b", &line); |
| 500 | } |
| 501 | } |
| 502 | blob_reset(&reply); |
| 503 | zComment = blob_str(pComment); |
| 504 | i = strlen(zComment); |
| 505 | while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } |
| 506 | blob_resize(pComment, i); |
| 507 | } |
| 508 | |
| 509 | /* |
| 510 | ** Prepare a commit comment. Let the user modify it using the |
| 511 | ** editor specified in the global_config table or either |
| 512 | ** the VISUAL or EDITOR environment variable. |
| @@ -442,111 +528,43 @@ | |
| 528 | char *zInit, |
| 529 | const char *zBranch, |
| 530 | int parent_rid, |
| 531 | const char *zUserOvrd |
| 532 | ){ |
| 533 | Blob prompt; |
| 534 | #ifdef _WIN32 |
| 535 | static const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| 536 | blob_init(&prompt, (const char *) bom, 3); |
| 537 | if( zInit && zInit[0]) { |
| 538 | blob_append(&prompt, zInit, -1); |
| 539 | } |
| 540 | #else |
| 541 | blob_init(&prompt, zInit, -1); |
| 542 | #endif |
| 543 | blob_append(&prompt, |
| 544 | "\n" |
| 545 | "# Enter comments on this check-in. Lines beginning with # are ignored.\n" |
| 546 | "# The check-in comment follows wiki formatting rules.\n" |
| 547 | "#\n", -1 |
| 548 | ); |
| 549 | blob_appendf(&prompt, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); |
| 550 | if( zBranch && zBranch[0] ){ |
| 551 | blob_appendf(&prompt, "# tags: %s\n#\n", zBranch); |
| 552 | }else{ |
| 553 | char *zTags = info_tags_of_checkin(parent_rid, 1); |
| 554 | if( zTags ) blob_appendf(&prompt, "# tags: %z\n#\n", zTags); |
| 555 | } |
| 556 | status_report(&prompt, "# ", 1, 0); |
| 557 | if( g.markPrivate ){ |
| 558 | blob_append(&prompt, |
| 559 | "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" |
| 560 | "# repositories.\n" |
| 561 | "#\n", -1 |
| 562 | ); |
| 563 | } |
| 564 | prompt_for_user_comment(pComment, &prompt); |
| 565 | blob_reset(&prompt); |
| 566 | } |
| 567 | |
| 568 | /* |
| 569 | ** Populate the Global.aCommitFile[] based on the command line arguments |
| 570 | ** to a [commit] command. Global.aCommitFile is an array of integers |
| 571 |
+99
-81
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -417,10 +417,96 @@ | ||
| 417 | 417 | } |
| 418 | 418 | } |
| 419 | 419 | } |
| 420 | 420 | db_finalize(&q); |
| 421 | 421 | } |
| 422 | + | |
| 423 | +/* | |
| 424 | +** Prompt the user for a check-in or stash comment (given in pPrompt), | |
| 425 | +** gather the response, then return the response in pComment. | |
| 426 | +** | |
| 427 | +** Lines of the prompt that begin with # are discarded. Excess whitespace | |
| 428 | +** is removed from the reply. | |
| 429 | +** | |
| 430 | +** Appropriate encoding translations are made on windows. | |
| 431 | +*/ | |
| 432 | +void prompt_for_user_comment(Blob *pComment, Blob *pPrompt){ | |
| 433 | + const char *zEditor; | |
| 434 | + char *zCmd; | |
| 435 | + char *zFile; | |
| 436 | + Blob reply, line; | |
| 437 | + char *zComment; | |
| 438 | + int i; | |
| 439 | + | |
| 440 | + zEditor = db_get("editor", 0); | |
| 441 | + if( zEditor==0 ){ | |
| 442 | + zEditor = fossil_getenv("VISUAL"); | |
| 443 | + } | |
| 444 | + if( zEditor==0 ){ | |
| 445 | + zEditor = fossil_getenv("EDITOR"); | |
| 446 | + } | |
| 447 | +#ifdef _WIN32 | |
| 448 | + if( zEditor==0 ){ | |
| 449 | + zEditor = mprintf("%s\\notepad.exe", fossil_getenv("SystemRoot")); | |
| 450 | + } | |
| 451 | +#endif | |
| 452 | + if( zEditor==0 ){ | |
| 453 | + blob_append(pPrompt, | |
| 454 | + "#\n" | |
| 455 | + "# Since no default text editor is set using EDITOR or VISUAL\n" | |
| 456 | + "# environment variables or the \"fossil set editor\" command,\n" | |
| 457 | + "# and because no comment was specified using the \"-m\" or \"-M\"\n" | |
| 458 | + "# command-line options, you will need to enter the comment below.\n" | |
| 459 | + "# Type \".\" on a line by itself when you are done:\n", -1); | |
| 460 | + zFile = mprintf("-"); | |
| 461 | + }else{ | |
| 462 | + zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", | |
| 463 | + g.zLocalRoot); | |
| 464 | + } | |
| 465 | +#if defined(_WIN32) | |
| 466 | + blob_add_cr(pPrompt); | |
| 467 | +#endif | |
| 468 | + blob_write_to_file(pPrompt, zFile); | |
| 469 | + if( zEditor ){ | |
| 470 | + zCmd = mprintf("%s \"%s\"", zEditor, zFile); | |
| 471 | + fossil_print("%s\n", zCmd); | |
| 472 | + if( fossil_system(zCmd) ){ | |
| 473 | + fossil_fatal("editor aborted: \"%s\"", zCmd); | |
| 474 | + } | |
| 475 | + | |
| 476 | + blob_read_from_file(&reply, zFile); | |
| 477 | + }else{ | |
| 478 | + char zIn[300]; | |
| 479 | + blob_zero(&reply); | |
| 480 | + while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ | |
| 481 | + if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ | |
| 482 | + break; | |
| 483 | + } | |
| 484 | + blob_append(&reply, zIn, -1); | |
| 485 | + } | |
| 486 | + } | |
| 487 | + blob_remove_cr(&reply); | |
| 488 | + file_delete(zFile); | |
| 489 | + free(zFile); | |
| 490 | + blob_zero(pComment); | |
| 491 | + while( blob_line(&reply, &line) ){ | |
| 492 | + int i, n; | |
| 493 | + char *z; | |
| 494 | + n = blob_size(&line); | |
| 495 | + z = blob_buffer(&line); | |
| 496 | + for(i=0; i<n && fossil_isspace(z[i]); i++){} | |
| 497 | + if( i<n && z[i]=='#' ) continue; | |
| 498 | + if( i<n || blob_size(pComment)>0 ){ | |
| 499 | + blob_appendf(pComment, "%b", &line); | |
| 500 | + } | |
| 501 | + } | |
| 502 | + blob_reset(&reply); | |
| 503 | + zComment = blob_str(pComment); | |
| 504 | + i = strlen(zComment); | |
| 505 | + while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } | |
| 506 | + blob_resize(pComment, i); | |
| 507 | +} | |
| 422 | 508 | |
| 423 | 509 | /* |
| 424 | 510 | ** Prepare a commit comment. Let the user modify it using the |
| 425 | 511 | ** editor specified in the global_config table or either |
| 426 | 512 | ** the VISUAL or EDITOR environment variable. |
| @@ -442,111 +528,43 @@ | ||
| 442 | 528 | char *zInit, |
| 443 | 529 | const char *zBranch, |
| 444 | 530 | int parent_rid, |
| 445 | 531 | const char *zUserOvrd |
| 446 | 532 | ){ |
| 533 | + Blob prompt; | |
| 534 | +#ifdef _WIN32 | |
| 447 | 535 | static const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| 448 | - const char *zEditor; | |
| 449 | - char *zCmd; | |
| 450 | - char *zFile; | |
| 451 | - Blob text, line; | |
| 452 | - char *zComment; | |
| 453 | - int i; | |
| 454 | -#ifdef _WIN32 | |
| 455 | - blob_init(&text, (const char *) bom, 3); | |
| 536 | + blob_init(&prompt, (const char *) bom, 3); | |
| 456 | 537 | if( zInit && zInit[0]) { |
| 457 | - blob_append(&text, zInit, -1); | |
| 538 | + blob_append(&prompt, zInit, -1); | |
| 458 | 539 | } |
| 459 | 540 | #else |
| 460 | - blob_init(&text, zInit, -1); | |
| 541 | + blob_init(&prompt, zInit, -1); | |
| 461 | 542 | #endif |
| 462 | - blob_append(&text, | |
| 543 | + blob_append(&prompt, | |
| 463 | 544 | "\n" |
| 464 | 545 | "# Enter comments on this check-in. Lines beginning with # are ignored.\n" |
| 465 | 546 | "# The check-in comment follows wiki formatting rules.\n" |
| 466 | 547 | "#\n", -1 |
| 467 | 548 | ); |
| 468 | - blob_appendf(&text, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); | |
| 549 | + blob_appendf(&prompt, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); | |
| 469 | 550 | if( zBranch && zBranch[0] ){ |
| 470 | - blob_appendf(&text, "# tags: %s\n#\n", zBranch); | |
| 551 | + blob_appendf(&prompt, "# tags: %s\n#\n", zBranch); | |
| 471 | 552 | }else{ |
| 472 | 553 | char *zTags = info_tags_of_checkin(parent_rid, 1); |
| 473 | - if( zTags ) blob_appendf(&text, "# tags: %z\n#\n", zTags); | |
| 554 | + if( zTags ) blob_appendf(&prompt, "# tags: %z\n#\n", zTags); | |
| 474 | 555 | } |
| 556 | + status_report(&prompt, "# ", 1, 0); | |
| 475 | 557 | if( g.markPrivate ){ |
| 476 | - blob_append(&text, | |
| 558 | + blob_append(&prompt, | |
| 477 | 559 | "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" |
| 478 | 560 | "# repositories.\n" |
| 479 | 561 | "#\n", -1 |
| 480 | 562 | ); |
| 481 | 563 | } |
| 482 | - status_report(&text, "# ", 1, 0); | |
| 483 | - zEditor = db_get("editor", 0); | |
| 484 | - if( zEditor==0 ){ | |
| 485 | - zEditor = fossil_getenv("VISUAL"); | |
| 486 | - } | |
| 487 | - if( zEditor==0 ){ | |
| 488 | - zEditor = fossil_getenv("EDITOR"); | |
| 489 | - } | |
| 490 | - if( zEditor==0 ){ | |
| 491 | - blob_append(&text, | |
| 492 | - "#\n" | |
| 493 | - "# Since no default text editor is set using EDITOR or VISUAL\n" | |
| 494 | - "# environment variables or the \"fossil set editor\" command,\n" | |
| 495 | - "# and because no check-in comment was specified using the \"-m\"\n" | |
| 496 | - "# or \"-M\" command-line options, you will need to enter the\n" | |
| 497 | - "# check-in comment below. Type \".\" on a line by itself when\n" | |
| 498 | - "# you are done:\n", -1); | |
| 499 | - zFile = mprintf("-"); | |
| 500 | - }else{ | |
| 501 | - zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", | |
| 502 | - g.zLocalRoot); | |
| 503 | - } | |
| 504 | -#if defined(_WIN32) | |
| 505 | - blob_add_cr(&text); | |
| 506 | -#endif | |
| 507 | - blob_write_to_file(&text, zFile); | |
| 508 | - if( zEditor ){ | |
| 509 | - zCmd = mprintf("%s \"%s\"", zEditor, zFile); | |
| 510 | - fossil_print("%s\n", zCmd); | |
| 511 | - if( fossil_system(zCmd) ){ | |
| 512 | - fossil_panic("editor aborted"); | |
| 513 | - } | |
| 514 | - blob_reset(&text); | |
| 515 | - blob_read_from_file(&text, zFile); | |
| 516 | - }else{ | |
| 517 | - char zIn[300]; | |
| 518 | - blob_reset(&text); | |
| 519 | - while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ | |
| 520 | - if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ | |
| 521 | - break; | |
| 522 | - } | |
| 523 | - blob_append(&text, zIn, -1); | |
| 524 | - } | |
| 525 | - } | |
| 526 | - blob_remove_cr(&text); | |
| 527 | - if( zEditor ) { | |
| 528 | - file_delete(zFile); | |
| 529 | - } | |
| 530 | - fossil_free(zFile); | |
| 531 | - blob_zero(pComment); | |
| 532 | - while( blob_line(&text, &line) ){ | |
| 533 | - int i, n; | |
| 534 | - char *z; | |
| 535 | - n = blob_size(&line); | |
| 536 | - z = blob_buffer(&line); | |
| 537 | - for(i=0; i<n && fossil_isspace(z[i]); i++){} | |
| 538 | - if( i<n && z[i]=='#' ) continue; | |
| 539 | - if( i<n || blob_size(pComment)>0 ){ | |
| 540 | - blob_appendf(pComment, "%b", &line); | |
| 541 | - } | |
| 542 | - } | |
| 543 | - blob_reset(&text); | |
| 544 | - zComment = blob_str(pComment); | |
| 545 | - i = strlen(zComment); | |
| 546 | - while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } | |
| 547 | - blob_resize(pComment, i); | |
| 564 | + prompt_for_user_comment(pComment, &prompt); | |
| 565 | + blob_reset(&prompt); | |
| 548 | 566 | } |
| 549 | 567 | |
| 550 | 568 | /* |
| 551 | 569 | ** Populate the Global.aCommitFile[] based on the command line arguments |
| 552 | 570 | ** to a [commit] command. Global.aCommitFile is an array of integers |
| 553 | 571 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -417,10 +417,96 @@ | |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | db_finalize(&q); |
| 421 | } |
| 422 | |
| 423 | /* |
| 424 | ** Prepare a commit comment. Let the user modify it using the |
| 425 | ** editor specified in the global_config table or either |
| 426 | ** the VISUAL or EDITOR environment variable. |
| @@ -442,111 +528,43 @@ | |
| 442 | char *zInit, |
| 443 | const char *zBranch, |
| 444 | int parent_rid, |
| 445 | const char *zUserOvrd |
| 446 | ){ |
| 447 | static const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| 448 | const char *zEditor; |
| 449 | char *zCmd; |
| 450 | char *zFile; |
| 451 | Blob text, line; |
| 452 | char *zComment; |
| 453 | int i; |
| 454 | #ifdef _WIN32 |
| 455 | blob_init(&text, (const char *) bom, 3); |
| 456 | if( zInit && zInit[0]) { |
| 457 | blob_append(&text, zInit, -1); |
| 458 | } |
| 459 | #else |
| 460 | blob_init(&text, zInit, -1); |
| 461 | #endif |
| 462 | blob_append(&text, |
| 463 | "\n" |
| 464 | "# Enter comments on this check-in. Lines beginning with # are ignored.\n" |
| 465 | "# The check-in comment follows wiki formatting rules.\n" |
| 466 | "#\n", -1 |
| 467 | ); |
| 468 | blob_appendf(&text, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); |
| 469 | if( zBranch && zBranch[0] ){ |
| 470 | blob_appendf(&text, "# tags: %s\n#\n", zBranch); |
| 471 | }else{ |
| 472 | char *zTags = info_tags_of_checkin(parent_rid, 1); |
| 473 | if( zTags ) blob_appendf(&text, "# tags: %z\n#\n", zTags); |
| 474 | } |
| 475 | if( g.markPrivate ){ |
| 476 | blob_append(&text, |
| 477 | "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" |
| 478 | "# repositories.\n" |
| 479 | "#\n", -1 |
| 480 | ); |
| 481 | } |
| 482 | status_report(&text, "# ", 1, 0); |
| 483 | zEditor = db_get("editor", 0); |
| 484 | if( zEditor==0 ){ |
| 485 | zEditor = fossil_getenv("VISUAL"); |
| 486 | } |
| 487 | if( zEditor==0 ){ |
| 488 | zEditor = fossil_getenv("EDITOR"); |
| 489 | } |
| 490 | if( zEditor==0 ){ |
| 491 | blob_append(&text, |
| 492 | "#\n" |
| 493 | "# Since no default text editor is set using EDITOR or VISUAL\n" |
| 494 | "# environment variables or the \"fossil set editor\" command,\n" |
| 495 | "# and because no check-in comment was specified using the \"-m\"\n" |
| 496 | "# or \"-M\" command-line options, you will need to enter the\n" |
| 497 | "# check-in comment below. Type \".\" on a line by itself when\n" |
| 498 | "# you are done:\n", -1); |
| 499 | zFile = mprintf("-"); |
| 500 | }else{ |
| 501 | zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", |
| 502 | g.zLocalRoot); |
| 503 | } |
| 504 | #if defined(_WIN32) |
| 505 | blob_add_cr(&text); |
| 506 | #endif |
| 507 | blob_write_to_file(&text, zFile); |
| 508 | if( zEditor ){ |
| 509 | zCmd = mprintf("%s \"%s\"", zEditor, zFile); |
| 510 | fossil_print("%s\n", zCmd); |
| 511 | if( fossil_system(zCmd) ){ |
| 512 | fossil_panic("editor aborted"); |
| 513 | } |
| 514 | blob_reset(&text); |
| 515 | blob_read_from_file(&text, zFile); |
| 516 | }else{ |
| 517 | char zIn[300]; |
| 518 | blob_reset(&text); |
| 519 | while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ |
| 520 | if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ |
| 521 | break; |
| 522 | } |
| 523 | blob_append(&text, zIn, -1); |
| 524 | } |
| 525 | } |
| 526 | blob_remove_cr(&text); |
| 527 | if( zEditor ) { |
| 528 | file_delete(zFile); |
| 529 | } |
| 530 | fossil_free(zFile); |
| 531 | blob_zero(pComment); |
| 532 | while( blob_line(&text, &line) ){ |
| 533 | int i, n; |
| 534 | char *z; |
| 535 | n = blob_size(&line); |
| 536 | z = blob_buffer(&line); |
| 537 | for(i=0; i<n && fossil_isspace(z[i]); i++){} |
| 538 | if( i<n && z[i]=='#' ) continue; |
| 539 | if( i<n || blob_size(pComment)>0 ){ |
| 540 | blob_appendf(pComment, "%b", &line); |
| 541 | } |
| 542 | } |
| 543 | blob_reset(&text); |
| 544 | zComment = blob_str(pComment); |
| 545 | i = strlen(zComment); |
| 546 | while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } |
| 547 | blob_resize(pComment, i); |
| 548 | } |
| 549 | |
| 550 | /* |
| 551 | ** Populate the Global.aCommitFile[] based on the command line arguments |
| 552 | ** to a [commit] command. Global.aCommitFile is an array of integers |
| 553 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -417,10 +417,96 @@ | |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | db_finalize(&q); |
| 421 | } |
| 422 | |
| 423 | /* |
| 424 | ** Prompt the user for a check-in or stash comment (given in pPrompt), |
| 425 | ** gather the response, then return the response in pComment. |
| 426 | ** |
| 427 | ** Lines of the prompt that begin with # are discarded. Excess whitespace |
| 428 | ** is removed from the reply. |
| 429 | ** |
| 430 | ** Appropriate encoding translations are made on windows. |
| 431 | */ |
| 432 | void prompt_for_user_comment(Blob *pComment, Blob *pPrompt){ |
| 433 | const char *zEditor; |
| 434 | char *zCmd; |
| 435 | char *zFile; |
| 436 | Blob reply, line; |
| 437 | char *zComment; |
| 438 | int i; |
| 439 | |
| 440 | zEditor = db_get("editor", 0); |
| 441 | if( zEditor==0 ){ |
| 442 | zEditor = fossil_getenv("VISUAL"); |
| 443 | } |
| 444 | if( zEditor==0 ){ |
| 445 | zEditor = fossil_getenv("EDITOR"); |
| 446 | } |
| 447 | #ifdef _WIN32 |
| 448 | if( zEditor==0 ){ |
| 449 | zEditor = mprintf("%s\\notepad.exe", fossil_getenv("SystemRoot")); |
| 450 | } |
| 451 | #endif |
| 452 | if( zEditor==0 ){ |
| 453 | blob_append(pPrompt, |
| 454 | "#\n" |
| 455 | "# Since no default text editor is set using EDITOR or VISUAL\n" |
| 456 | "# environment variables or the \"fossil set editor\" command,\n" |
| 457 | "# and because no comment was specified using the \"-m\" or \"-M\"\n" |
| 458 | "# command-line options, you will need to enter the comment below.\n" |
| 459 | "# Type \".\" on a line by itself when you are done:\n", -1); |
| 460 | zFile = mprintf("-"); |
| 461 | }else{ |
| 462 | zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", |
| 463 | g.zLocalRoot); |
| 464 | } |
| 465 | #if defined(_WIN32) |
| 466 | blob_add_cr(pPrompt); |
| 467 | #endif |
| 468 | blob_write_to_file(pPrompt, zFile); |
| 469 | if( zEditor ){ |
| 470 | zCmd = mprintf("%s \"%s\"", zEditor, zFile); |
| 471 | fossil_print("%s\n", zCmd); |
| 472 | if( fossil_system(zCmd) ){ |
| 473 | fossil_fatal("editor aborted: \"%s\"", zCmd); |
| 474 | } |
| 475 | |
| 476 | blob_read_from_file(&reply, zFile); |
| 477 | }else{ |
| 478 | char zIn[300]; |
| 479 | blob_zero(&reply); |
| 480 | while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ |
| 481 | if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ |
| 482 | break; |
| 483 | } |
| 484 | blob_append(&reply, zIn, -1); |
| 485 | } |
| 486 | } |
| 487 | blob_remove_cr(&reply); |
| 488 | file_delete(zFile); |
| 489 | free(zFile); |
| 490 | blob_zero(pComment); |
| 491 | while( blob_line(&reply, &line) ){ |
| 492 | int i, n; |
| 493 | char *z; |
| 494 | n = blob_size(&line); |
| 495 | z = blob_buffer(&line); |
| 496 | for(i=0; i<n && fossil_isspace(z[i]); i++){} |
| 497 | if( i<n && z[i]=='#' ) continue; |
| 498 | if( i<n || blob_size(pComment)>0 ){ |
| 499 | blob_appendf(pComment, "%b", &line); |
| 500 | } |
| 501 | } |
| 502 | blob_reset(&reply); |
| 503 | zComment = blob_str(pComment); |
| 504 | i = strlen(zComment); |
| 505 | while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } |
| 506 | blob_resize(pComment, i); |
| 507 | } |
| 508 | |
| 509 | /* |
| 510 | ** Prepare a commit comment. Let the user modify it using the |
| 511 | ** editor specified in the global_config table or either |
| 512 | ** the VISUAL or EDITOR environment variable. |
| @@ -442,111 +528,43 @@ | |
| 528 | char *zInit, |
| 529 | const char *zBranch, |
| 530 | int parent_rid, |
| 531 | const char *zUserOvrd |
| 532 | ){ |
| 533 | Blob prompt; |
| 534 | #ifdef _WIN32 |
| 535 | static const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| 536 | blob_init(&prompt, (const char *) bom, 3); |
| 537 | if( zInit && zInit[0]) { |
| 538 | blob_append(&prompt, zInit, -1); |
| 539 | } |
| 540 | #else |
| 541 | blob_init(&prompt, zInit, -1); |
| 542 | #endif |
| 543 | blob_append(&prompt, |
| 544 | "\n" |
| 545 | "# Enter comments on this check-in. Lines beginning with # are ignored.\n" |
| 546 | "# The check-in comment follows wiki formatting rules.\n" |
| 547 | "#\n", -1 |
| 548 | ); |
| 549 | blob_appendf(&prompt, "# user: %s\n", zUserOvrd ? zUserOvrd : g.zLogin); |
| 550 | if( zBranch && zBranch[0] ){ |
| 551 | blob_appendf(&prompt, "# tags: %s\n#\n", zBranch); |
| 552 | }else{ |
| 553 | char *zTags = info_tags_of_checkin(parent_rid, 1); |
| 554 | if( zTags ) blob_appendf(&prompt, "# tags: %z\n#\n", zTags); |
| 555 | } |
| 556 | status_report(&prompt, "# ", 1, 0); |
| 557 | if( g.markPrivate ){ |
| 558 | blob_append(&prompt, |
| 559 | "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" |
| 560 | "# repositories.\n" |
| 561 | "#\n", -1 |
| 562 | ); |
| 563 | } |
| 564 | prompt_for_user_comment(pComment, &prompt); |
| 565 | blob_reset(&prompt); |
| 566 | } |
| 567 | |
| 568 | /* |
| 569 | ** Populate the Global.aCommitFile[] based on the command line arguments |
| 570 | ** to a [commit] command. Global.aCommitFile is an array of integers |
| 571 |
+3
| --- src/configure.c | ||
| +++ src/configure.c | ||
| @@ -89,17 +89,20 @@ | ||
| 89 | 89 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 90 | 90 | { "timeline-max-comment", CONFIGSET_SKIN }, |
| 91 | 91 | { "adunit", CONFIGSET_SKIN }, |
| 92 | 92 | { "adunit-omit-if-admin", CONFIGSET_SKIN }, |
| 93 | 93 | { "adunit-omit-if-user", CONFIGSET_SKIN }, |
| 94 | + | |
| 94 | 95 | #ifdef FOSSIL_ENABLE_TCL |
| 95 | 96 | { "tcl", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER }, |
| 97 | + { "tcl-setup", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER }, | |
| 96 | 98 | #endif |
| 97 | 99 | |
| 98 | 100 | { "project-name", CONFIGSET_PROJ }, |
| 99 | 101 | { "project-description", CONFIGSET_PROJ }, |
| 100 | 102 | { "manifest", CONFIGSET_PROJ }, |
| 103 | + { "binary-glob", CONFIGSET_PROJ }, | |
| 101 | 104 | { "ignore-glob", CONFIGSET_PROJ }, |
| 102 | 105 | { "crnl-glob", CONFIGSET_PROJ }, |
| 103 | 106 | { "empty-dirs", CONFIGSET_PROJ }, |
| 104 | 107 | { "allow-symlinks", CONFIGSET_PROJ }, |
| 105 | 108 | |
| 106 | 109 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -89,17 +89,20 @@ | |
| 89 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 90 | { "timeline-max-comment", CONFIGSET_SKIN }, |
| 91 | { "adunit", CONFIGSET_SKIN }, |
| 92 | { "adunit-omit-if-admin", CONFIGSET_SKIN }, |
| 93 | { "adunit-omit-if-user", CONFIGSET_SKIN }, |
| 94 | #ifdef FOSSIL_ENABLE_TCL |
| 95 | { "tcl", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER }, |
| 96 | #endif |
| 97 | |
| 98 | { "project-name", CONFIGSET_PROJ }, |
| 99 | { "project-description", CONFIGSET_PROJ }, |
| 100 | { "manifest", CONFIGSET_PROJ }, |
| 101 | { "ignore-glob", CONFIGSET_PROJ }, |
| 102 | { "crnl-glob", CONFIGSET_PROJ }, |
| 103 | { "empty-dirs", CONFIGSET_PROJ }, |
| 104 | { "allow-symlinks", CONFIGSET_PROJ }, |
| 105 | |
| 106 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -89,17 +89,20 @@ | |
| 89 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 90 | { "timeline-max-comment", CONFIGSET_SKIN }, |
| 91 | { "adunit", CONFIGSET_SKIN }, |
| 92 | { "adunit-omit-if-admin", CONFIGSET_SKIN }, |
| 93 | { "adunit-omit-if-user", CONFIGSET_SKIN }, |
| 94 | |
| 95 | #ifdef FOSSIL_ENABLE_TCL |
| 96 | { "tcl", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER }, |
| 97 | { "tcl-setup", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER }, |
| 98 | #endif |
| 99 | |
| 100 | { "project-name", CONFIGSET_PROJ }, |
| 101 | { "project-description", CONFIGSET_PROJ }, |
| 102 | { "manifest", CONFIGSET_PROJ }, |
| 103 | { "binary-glob", CONFIGSET_PROJ }, |
| 104 | { "ignore-glob", CONFIGSET_PROJ }, |
| 105 | { "crnl-glob", CONFIGSET_PROJ }, |
| 106 | { "empty-dirs", CONFIGSET_PROJ }, |
| 107 | { "allow-symlinks", CONFIGSET_PROJ }, |
| 108 | |
| 109 |
M
src/db.c
+16
-5
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -2014,10 +2014,11 @@ | ||
| 2014 | 2014 | { "binary-glob", 0, 32, 1, "" }, |
| 2015 | 2015 | { "clearsign", 0, 0, 0, "off" }, |
| 2016 | 2016 | { "case-sensitive",0, 0, 0, "on" }, |
| 2017 | 2017 | { "crnl-glob", 0, 16, 1, "" }, |
| 2018 | 2018 | { "default-perms", 0, 16, 0, "u" }, |
| 2019 | + { "diff-binary", 0, 0, 0, "on" }, | |
| 2019 | 2020 | { "diff-command", 0, 16, 0, "" }, |
| 2020 | 2021 | { "dont-push", 0, 0, 0, "off" }, |
| 2021 | 2022 | { "editor", 0, 16, 0, "" }, |
| 2022 | 2023 | { "gdiff-command", 0, 16, 0, "gdiff" }, |
| 2023 | 2024 | { "gmerge-command",0, 40, 0, "" }, |
| @@ -2038,10 +2039,11 @@ | ||
| 2038 | 2039 | { "ssl-ca-location",0, 40, 0, "" }, |
| 2039 | 2040 | { "ssl-identity", 0, 40, 0, "" }, |
| 2040 | 2041 | { "ssh-command", 0, 32, 0, "" }, |
| 2041 | 2042 | #ifdef FOSSIL_ENABLE_TCL |
| 2042 | 2043 | { "tcl", 0, 0, 0, "off" }, |
| 2044 | + { "tcl-setup", 0, 40, 0, "" }, | |
| 2043 | 2045 | #endif |
| 2044 | 2046 | { "web-browser", 0, 32, 0, "" }, |
| 2045 | 2047 | { "white-foreground", 0, 0, 0, "off" }, |
| 2046 | 2048 | { 0,0,0,0,0 } |
| 2047 | 2049 | }; |
| @@ -2106,10 +2108,14 @@ | ||
| 2106 | 2108 | ** Set to "*" to disable CR+NL checking. |
| 2107 | 2109 | ** |
| 2108 | 2110 | ** default-perms Permissions given automatically to new users. For more |
| 2109 | 2111 | ** information on permissions see Users page in Server |
| 2110 | 2112 | ** Administration of the HTTP UI. Default: u. |
| 2113 | +** | |
| 2114 | +** diff-binary If TRUE (the default), permit files that may be binary | |
| 2115 | +** or that match the "binary-glob" setting to be used with | |
| 2116 | +** external diff programs. If FALSE, skip these files. | |
| 2111 | 2117 | ** |
| 2112 | 2118 | ** diff-command External command to run when performing a diff. |
| 2113 | 2119 | ** If undefined, the internal text diff will be used. |
| 2114 | 2120 | ** |
| 2115 | 2121 | ** dont-push Prevent this repository from pushing from client to |
| @@ -2198,15 +2204,20 @@ | ||
| 2198 | 2204 | ** password authentication. |
| 2199 | 2205 | ** |
| 2200 | 2206 | ** ssh-command Command used to talk to a remote machine with |
| 2201 | 2207 | ** the "ssh://" protocol. |
| 2202 | 2208 | ** |
| 2203 | -** tcl If enabled, Tcl integration commands will be added to | |
| 2204 | -** the TH1 interpreter, allowing Tcl expressions and | |
| 2205 | -** scripts to be evaluated from TH1. Additionally, the | |
| 2206 | -** Tcl interpreter will be able to evaluate TH1 expressions | |
| 2207 | -** and scripts. Default: off. | |
| 2209 | +** tcl If enabled (and Fossil was compiled with Tcl support), | |
| 2210 | +** Tcl integration commands will be added to the TH1 | |
| 2211 | +** interpreter, allowing arbitrary Tcl expressions and | |
| 2212 | +** scripts to be evaluated from TH1. Additionally, the Tcl | |
| 2213 | +** interpreter will be able to evaluate arbitrary TH1 | |
| 2214 | +** expressions and scripts. Default: off. | |
| 2215 | +** | |
| 2216 | +** tcl-setup This is the setup script to be evaluated after creating | |
| 2217 | +** and initializing the Tcl interpreter. By default, this | |
| 2218 | +** is empty and no extra setup is performed. | |
| 2208 | 2219 | ** |
| 2209 | 2220 | ** web-browser A shell command used to launch your preferred |
| 2210 | 2221 | ** web browser when given a URL as an argument. |
| 2211 | 2222 | ** Defaults to "start" on windows, "open" on Mac, |
| 2212 | 2223 | ** and "firefox" on Unix. |
| 2213 | 2224 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -2014,10 +2014,11 @@ | |
| 2014 | { "binary-glob", 0, 32, 1, "" }, |
| 2015 | { "clearsign", 0, 0, 0, "off" }, |
| 2016 | { "case-sensitive",0, 0, 0, "on" }, |
| 2017 | { "crnl-glob", 0, 16, 1, "" }, |
| 2018 | { "default-perms", 0, 16, 0, "u" }, |
| 2019 | { "diff-command", 0, 16, 0, "" }, |
| 2020 | { "dont-push", 0, 0, 0, "off" }, |
| 2021 | { "editor", 0, 16, 0, "" }, |
| 2022 | { "gdiff-command", 0, 16, 0, "gdiff" }, |
| 2023 | { "gmerge-command",0, 40, 0, "" }, |
| @@ -2038,10 +2039,11 @@ | |
| 2038 | { "ssl-ca-location",0, 40, 0, "" }, |
| 2039 | { "ssl-identity", 0, 40, 0, "" }, |
| 2040 | { "ssh-command", 0, 32, 0, "" }, |
| 2041 | #ifdef FOSSIL_ENABLE_TCL |
| 2042 | { "tcl", 0, 0, 0, "off" }, |
| 2043 | #endif |
| 2044 | { "web-browser", 0, 32, 0, "" }, |
| 2045 | { "white-foreground", 0, 0, 0, "off" }, |
| 2046 | { 0,0,0,0,0 } |
| 2047 | }; |
| @@ -2106,10 +2108,14 @@ | |
| 2106 | ** Set to "*" to disable CR+NL checking. |
| 2107 | ** |
| 2108 | ** default-perms Permissions given automatically to new users. For more |
| 2109 | ** information on permissions see Users page in Server |
| 2110 | ** Administration of the HTTP UI. Default: u. |
| 2111 | ** |
| 2112 | ** diff-command External command to run when performing a diff. |
| 2113 | ** If undefined, the internal text diff will be used. |
| 2114 | ** |
| 2115 | ** dont-push Prevent this repository from pushing from client to |
| @@ -2198,15 +2204,20 @@ | |
| 2198 | ** password authentication. |
| 2199 | ** |
| 2200 | ** ssh-command Command used to talk to a remote machine with |
| 2201 | ** the "ssh://" protocol. |
| 2202 | ** |
| 2203 | ** tcl If enabled, Tcl integration commands will be added to |
| 2204 | ** the TH1 interpreter, allowing Tcl expressions and |
| 2205 | ** scripts to be evaluated from TH1. Additionally, the |
| 2206 | ** Tcl interpreter will be able to evaluate TH1 expressions |
| 2207 | ** and scripts. Default: off. |
| 2208 | ** |
| 2209 | ** web-browser A shell command used to launch your preferred |
| 2210 | ** web browser when given a URL as an argument. |
| 2211 | ** Defaults to "start" on windows, "open" on Mac, |
| 2212 | ** and "firefox" on Unix. |
| 2213 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -2014,10 +2014,11 @@ | |
| 2014 | { "binary-glob", 0, 32, 1, "" }, |
| 2015 | { "clearsign", 0, 0, 0, "off" }, |
| 2016 | { "case-sensitive",0, 0, 0, "on" }, |
| 2017 | { "crnl-glob", 0, 16, 1, "" }, |
| 2018 | { "default-perms", 0, 16, 0, "u" }, |
| 2019 | { "diff-binary", 0, 0, 0, "on" }, |
| 2020 | { "diff-command", 0, 16, 0, "" }, |
| 2021 | { "dont-push", 0, 0, 0, "off" }, |
| 2022 | { "editor", 0, 16, 0, "" }, |
| 2023 | { "gdiff-command", 0, 16, 0, "gdiff" }, |
| 2024 | { "gmerge-command",0, 40, 0, "" }, |
| @@ -2038,10 +2039,11 @@ | |
| 2039 | { "ssl-ca-location",0, 40, 0, "" }, |
| 2040 | { "ssl-identity", 0, 40, 0, "" }, |
| 2041 | { "ssh-command", 0, 32, 0, "" }, |
| 2042 | #ifdef FOSSIL_ENABLE_TCL |
| 2043 | { "tcl", 0, 0, 0, "off" }, |
| 2044 | { "tcl-setup", 0, 40, 0, "" }, |
| 2045 | #endif |
| 2046 | { "web-browser", 0, 32, 0, "" }, |
| 2047 | { "white-foreground", 0, 0, 0, "off" }, |
| 2048 | { 0,0,0,0,0 } |
| 2049 | }; |
| @@ -2106,10 +2108,14 @@ | |
| 2108 | ** Set to "*" to disable CR+NL checking. |
| 2109 | ** |
| 2110 | ** default-perms Permissions given automatically to new users. For more |
| 2111 | ** information on permissions see Users page in Server |
| 2112 | ** Administration of the HTTP UI. Default: u. |
| 2113 | ** |
| 2114 | ** diff-binary If TRUE (the default), permit files that may be binary |
| 2115 | ** or that match the "binary-glob" setting to be used with |
| 2116 | ** external diff programs. If FALSE, skip these files. |
| 2117 | ** |
| 2118 | ** diff-command External command to run when performing a diff. |
| 2119 | ** If undefined, the internal text diff will be used. |
| 2120 | ** |
| 2121 | ** dont-push Prevent this repository from pushing from client to |
| @@ -2198,15 +2204,20 @@ | |
| 2204 | ** password authentication. |
| 2205 | ** |
| 2206 | ** ssh-command Command used to talk to a remote machine with |
| 2207 | ** the "ssh://" protocol. |
| 2208 | ** |
| 2209 | ** tcl If enabled (and Fossil was compiled with Tcl support), |
| 2210 | ** Tcl integration commands will be added to the TH1 |
| 2211 | ** interpreter, allowing arbitrary Tcl expressions and |
| 2212 | ** scripts to be evaluated from TH1. Additionally, the Tcl |
| 2213 | ** interpreter will be able to evaluate arbitrary TH1 |
| 2214 | ** expressions and scripts. Default: off. |
| 2215 | ** |
| 2216 | ** tcl-setup This is the setup script to be evaluated after creating |
| 2217 | ** and initializing the Tcl interpreter. By default, this |
| 2218 | ** is empty and no extra setup is performed. |
| 2219 | ** |
| 2220 | ** web-browser A shell command used to launch your preferred |
| 2221 | ** web browser when given a URL as an argument. |
| 2222 | ** Defaults to "start" on windows, "open" on Mac, |
| 2223 | ** and "firefox" on Unix. |
| 2224 |
+45
-7
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -38,10 +38,20 @@ | ||
| 38 | 38 | #define DIFF_LINENO ((u64)0x20000000) /* Show line numbers */ |
| 39 | 39 | #define DIFF_WS_WARNING ((u64)0x40000000) /* Warn about whitespace */ |
| 40 | 40 | #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ |
| 41 | 41 | #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ |
| 42 | 42 | |
| 43 | +/* | |
| 44 | +** These error messages are shared in multiple locations. They are defined | |
| 45 | +** here for consistency. | |
| 46 | +*/ | |
| 47 | +#define DIFF_CANNOT_COMPUTE_BINARY \ | |
| 48 | + "cannot compute difference between binary files\n" | |
| 49 | + | |
| 50 | +#define DIFF_CANNOT_COMPUTE_SYMLINK \ | |
| 51 | + "cannot compute difference between symlink and regular file\n" | |
| 52 | + | |
| 43 | 53 | #endif /* INTERFACE */ |
| 44 | 54 | |
| 45 | 55 | /* |
| 46 | 56 | ** Maximum length of a line in a text file. (8192) |
| 47 | 57 | */ |
| @@ -157,10 +167,38 @@ | ||
| 157 | 167 | |
| 158 | 168 | /* Return results */ |
| 159 | 169 | *pnLine = nLine; |
| 160 | 170 | return a; |
| 161 | 171 | } |
| 172 | + | |
| 173 | +/* | |
| 174 | +** Returns non-zero if the specified content appears to be binary or | |
| 175 | +** contains a line that is too long. | |
| 176 | +*/ | |
| 177 | +int looks_like_binary(Blob *pContent){ | |
| 178 | + const char *z = blob_str(pContent); | |
| 179 | + int n = blob_size(pContent); | |
| 180 | + int i, j; | |
| 181 | + | |
| 182 | + /* Count the number of lines. Allocate space to hold | |
| 183 | + ** the returned array. | |
| 184 | + */ | |
| 185 | + for(i=j=0; i<n; i++, j++){ | |
| 186 | + int c = z[i]; | |
| 187 | + if( c==0 ) return 1; /* \000 byte in a file -> binary */ | |
| 188 | + if( c=='\n' && z[i+1]!=0 ){ | |
| 189 | + if( j>LENGTH_MASK ){ | |
| 190 | + return 1; /* Very long line -> binary */ | |
| 191 | + } | |
| 192 | + j = 0; | |
| 193 | + } | |
| 194 | + } | |
| 195 | + if( j>LENGTH_MASK ){ | |
| 196 | + return 1; /* Very long line -> binary */ | |
| 197 | + } | |
| 198 | + return 0; /* No problems seen -> not binary */ | |
| 199 | +} | |
| 162 | 200 | |
| 163 | 201 | /* |
| 164 | 202 | ** Return true if two DLine elements are identical. |
| 165 | 203 | */ |
| 166 | 204 | static int same_dline(DLine *pA, DLine *pB){ |
| @@ -1499,14 +1537,14 @@ | ||
| 1499 | 1537 | c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), |
| 1500 | 1538 | &c.nFrom, ignoreEolWs); |
| 1501 | 1539 | c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), |
| 1502 | 1540 | &c.nTo, ignoreEolWs); |
| 1503 | 1541 | if( c.aFrom==0 || c.aTo==0 ){ |
| 1504 | - free(c.aFrom); | |
| 1505 | - free(c.aTo); | |
| 1542 | + fossil_free(c.aFrom); | |
| 1543 | + fossil_free(c.aTo); | |
| 1506 | 1544 | if( pOut ){ |
| 1507 | - blob_appendf(pOut, "cannot compute difference between binary files\n"); | |
| 1545 | + blob_appendf(pOut, DIFF_CANNOT_COMPUTE_BINARY); | |
| 1508 | 1546 | } |
| 1509 | 1547 | return 0; |
| 1510 | 1548 | } |
| 1511 | 1549 | |
| 1512 | 1550 | /* Compute the difference */ |
| @@ -1521,13 +1559,13 @@ | ||
| 1521 | 1559 | sbsDiff(&c, pOut, nContext, width, escHtml); |
| 1522 | 1560 | }else{ |
| 1523 | 1561 | int showLn = (diffFlags & DIFF_LINENO)!=0; |
| 1524 | 1562 | contextDiff(&c, pOut, nContext, showLn, escHtml); |
| 1525 | 1563 | } |
| 1526 | - free(c.aFrom); | |
| 1527 | - free(c.aTo); | |
| 1528 | - free(c.aEdit); | |
| 1564 | + fossil_free(c.aFrom); | |
| 1565 | + fossil_free(c.aTo); | |
| 1566 | + fossil_free(c.aEdit); | |
| 1529 | 1567 | return 0; |
| 1530 | 1568 | }else{ |
| 1531 | 1569 | /* If a context diff is not requested, then return the |
| 1532 | 1570 | ** array of COPY/DELETE/INSERT triples. |
| 1533 | 1571 | */ |
| @@ -1702,11 +1740,11 @@ | ||
| 1702 | 1740 | } |
| 1703 | 1741 | lnTo += p->c.aEdit[i+2]; |
| 1704 | 1742 | } |
| 1705 | 1743 | |
| 1706 | 1744 | /* Clear out the diff results */ |
| 1707 | - free(p->c.aEdit); | |
| 1745 | + fossil_free(p->c.aEdit); | |
| 1708 | 1746 | p->c.aEdit = 0; |
| 1709 | 1747 | p->c.nEdit = 0; |
| 1710 | 1748 | p->c.nEditAlloc = 0; |
| 1711 | 1749 | |
| 1712 | 1750 | /* Clear out the from file */ |
| 1713 | 1751 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -38,10 +38,20 @@ | |
| 38 | #define DIFF_LINENO ((u64)0x20000000) /* Show line numbers */ |
| 39 | #define DIFF_WS_WARNING ((u64)0x40000000) /* Warn about whitespace */ |
| 40 | #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ |
| 41 | #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ |
| 42 | |
| 43 | #endif /* INTERFACE */ |
| 44 | |
| 45 | /* |
| 46 | ** Maximum length of a line in a text file. (8192) |
| 47 | */ |
| @@ -157,10 +167,38 @@ | |
| 157 | |
| 158 | /* Return results */ |
| 159 | *pnLine = nLine; |
| 160 | return a; |
| 161 | } |
| 162 | |
| 163 | /* |
| 164 | ** Return true if two DLine elements are identical. |
| 165 | */ |
| 166 | static int same_dline(DLine *pA, DLine *pB){ |
| @@ -1499,14 +1537,14 @@ | |
| 1499 | c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), |
| 1500 | &c.nFrom, ignoreEolWs); |
| 1501 | c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), |
| 1502 | &c.nTo, ignoreEolWs); |
| 1503 | if( c.aFrom==0 || c.aTo==0 ){ |
| 1504 | free(c.aFrom); |
| 1505 | free(c.aTo); |
| 1506 | if( pOut ){ |
| 1507 | blob_appendf(pOut, "cannot compute difference between binary files\n"); |
| 1508 | } |
| 1509 | return 0; |
| 1510 | } |
| 1511 | |
| 1512 | /* Compute the difference */ |
| @@ -1521,13 +1559,13 @@ | |
| 1521 | sbsDiff(&c, pOut, nContext, width, escHtml); |
| 1522 | }else{ |
| 1523 | int showLn = (diffFlags & DIFF_LINENO)!=0; |
| 1524 | contextDiff(&c, pOut, nContext, showLn, escHtml); |
| 1525 | } |
| 1526 | free(c.aFrom); |
| 1527 | free(c.aTo); |
| 1528 | free(c.aEdit); |
| 1529 | return 0; |
| 1530 | }else{ |
| 1531 | /* If a context diff is not requested, then return the |
| 1532 | ** array of COPY/DELETE/INSERT triples. |
| 1533 | */ |
| @@ -1702,11 +1740,11 @@ | |
| 1702 | } |
| 1703 | lnTo += p->c.aEdit[i+2]; |
| 1704 | } |
| 1705 | |
| 1706 | /* Clear out the diff results */ |
| 1707 | free(p->c.aEdit); |
| 1708 | p->c.aEdit = 0; |
| 1709 | p->c.nEdit = 0; |
| 1710 | p->c.nEditAlloc = 0; |
| 1711 | |
| 1712 | /* Clear out the from file */ |
| 1713 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -38,10 +38,20 @@ | |
| 38 | #define DIFF_LINENO ((u64)0x20000000) /* Show line numbers */ |
| 39 | #define DIFF_WS_WARNING ((u64)0x40000000) /* Warn about whitespace */ |
| 40 | #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ |
| 41 | #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ |
| 42 | |
| 43 | /* |
| 44 | ** These error messages are shared in multiple locations. They are defined |
| 45 | ** here for consistency. |
| 46 | */ |
| 47 | #define DIFF_CANNOT_COMPUTE_BINARY \ |
| 48 | "cannot compute difference between binary files\n" |
| 49 | |
| 50 | #define DIFF_CANNOT_COMPUTE_SYMLINK \ |
| 51 | "cannot compute difference between symlink and regular file\n" |
| 52 | |
| 53 | #endif /* INTERFACE */ |
| 54 | |
| 55 | /* |
| 56 | ** Maximum length of a line in a text file. (8192) |
| 57 | */ |
| @@ -157,10 +167,38 @@ | |
| 167 | |
| 168 | /* Return results */ |
| 169 | *pnLine = nLine; |
| 170 | return a; |
| 171 | } |
| 172 | |
| 173 | /* |
| 174 | ** Returns non-zero if the specified content appears to be binary or |
| 175 | ** contains a line that is too long. |
| 176 | */ |
| 177 | int looks_like_binary(Blob *pContent){ |
| 178 | const char *z = blob_str(pContent); |
| 179 | int n = blob_size(pContent); |
| 180 | int i, j; |
| 181 | |
| 182 | /* Count the number of lines. Allocate space to hold |
| 183 | ** the returned array. |
| 184 | */ |
| 185 | for(i=j=0; i<n; i++, j++){ |
| 186 | int c = z[i]; |
| 187 | if( c==0 ) return 1; /* \000 byte in a file -> binary */ |
| 188 | if( c=='\n' && z[i+1]!=0 ){ |
| 189 | if( j>LENGTH_MASK ){ |
| 190 | return 1; /* Very long line -> binary */ |
| 191 | } |
| 192 | j = 0; |
| 193 | } |
| 194 | } |
| 195 | if( j>LENGTH_MASK ){ |
| 196 | return 1; /* Very long line -> binary */ |
| 197 | } |
| 198 | return 0; /* No problems seen -> not binary */ |
| 199 | } |
| 200 | |
| 201 | /* |
| 202 | ** Return true if two DLine elements are identical. |
| 203 | */ |
| 204 | static int same_dline(DLine *pA, DLine *pB){ |
| @@ -1499,14 +1537,14 @@ | |
| 1537 | c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), |
| 1538 | &c.nFrom, ignoreEolWs); |
| 1539 | c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), |
| 1540 | &c.nTo, ignoreEolWs); |
| 1541 | if( c.aFrom==0 || c.aTo==0 ){ |
| 1542 | fossil_free(c.aFrom); |
| 1543 | fossil_free(c.aTo); |
| 1544 | if( pOut ){ |
| 1545 | blob_appendf(pOut, DIFF_CANNOT_COMPUTE_BINARY); |
| 1546 | } |
| 1547 | return 0; |
| 1548 | } |
| 1549 | |
| 1550 | /* Compute the difference */ |
| @@ -1521,13 +1559,13 @@ | |
| 1559 | sbsDiff(&c, pOut, nContext, width, escHtml); |
| 1560 | }else{ |
| 1561 | int showLn = (diffFlags & DIFF_LINENO)!=0; |
| 1562 | contextDiff(&c, pOut, nContext, showLn, escHtml); |
| 1563 | } |
| 1564 | fossil_free(c.aFrom); |
| 1565 | fossil_free(c.aTo); |
| 1566 | fossil_free(c.aEdit); |
| 1567 | return 0; |
| 1568 | }else{ |
| 1569 | /* If a context diff is not requested, then return the |
| 1570 | ** array of COPY/DELETE/INSERT triples. |
| 1571 | */ |
| @@ -1702,11 +1740,11 @@ | |
| 1740 | } |
| 1741 | lnTo += p->c.aEdit[i+2]; |
| 1742 | } |
| 1743 | |
| 1744 | /* Clear out the diff results */ |
| 1745 | fossil_free(p->c.aEdit); |
| 1746 | p->c.aEdit = 0; |
| 1747 | p->c.nEdit = 0; |
| 1748 | p->c.nEditAlloc = 0; |
| 1749 | |
| 1750 | /* Clear out the from file */ |
| 1751 |
+196
-28
| --- src/diffcmd.c | ||
| +++ src/diffcmd.c | ||
| @@ -69,16 +69,23 @@ | ||
| 69 | 69 | ** The difference is the set of edits needed to transform pFile1 into |
| 70 | 70 | ** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. |
| 71 | 71 | ** |
| 72 | 72 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 73 | 73 | ** command zDiffCmd to do the diffing. |
| 74 | +** | |
| 75 | +** When using an external diff program, zBinGlob contains the GLOB patterns | |
| 76 | +** for file names to treat as binary. If fIncludeBinary is zero, these files | |
| 77 | +** will be skipped in addition to files that may contain binary content. | |
| 74 | 78 | */ |
| 75 | 79 | void diff_file( |
| 76 | 80 | Blob *pFile1, /* In memory content to compare from */ |
| 81 | + int isBin1, /* Does the 'from' content appear to be binary */ | |
| 77 | 82 | const char *zFile2, /* On disk content to compare to */ |
| 78 | 83 | const char *zName, /* Display name of the file */ |
| 79 | 84 | const char *zDiffCmd, /* Command for comparison */ |
| 85 | + const char *zBinGlob, /* Treat file names matching this as binary */ | |
| 86 | + int fIncludeBinary, /* Include binary files for external diff */ | |
| 80 | 87 | u64 diffFlags /* Flags to control the diff */ |
| 81 | 88 | ){ |
| 82 | 89 | if( zDiffCmd==0 ){ |
| 83 | 90 | Blob out; /* Diff output text */ |
| 84 | 91 | Blob file2; /* Content of zFile2 */ |
| @@ -116,10 +123,41 @@ | ||
| 116 | 123 | blob_reset(&file2); |
| 117 | 124 | }else{ |
| 118 | 125 | int cnt = 0; |
| 119 | 126 | Blob nameFile1; /* Name of temporary file to old pFile1 content */ |
| 120 | 127 | Blob cmd; /* Text of command to run */ |
| 128 | + | |
| 129 | + if( !fIncludeBinary ){ | |
| 130 | + Blob file2; | |
| 131 | + if( isBin1 ){ | |
| 132 | + fossil_print(DIFF_CANNOT_COMPUTE_BINARY); | |
| 133 | + return; | |
| 134 | + } | |
| 135 | + if( zBinGlob ){ | |
| 136 | + Glob *pBinary = glob_create(zBinGlob); | |
| 137 | + if( glob_match(pBinary, zName) ){ | |
| 138 | + fossil_print(DIFF_CANNOT_COMPUTE_BINARY); | |
| 139 | + glob_free(pBinary); | |
| 140 | + return; | |
| 141 | + } | |
| 142 | + glob_free(pBinary); | |
| 143 | + } | |
| 144 | + blob_zero(&file2); | |
| 145 | + if( file_wd_size(zFile2)>=0 ){ | |
| 146 | + if( file_wd_islink(zFile2) ){ | |
| 147 | + blob_read_link(&file2, zFile2); | |
| 148 | + }else{ | |
| 149 | + blob_read_from_file(&file2, zFile2); | |
| 150 | + } | |
| 151 | + } | |
| 152 | + if( looks_like_binary(&file2) ){ | |
| 153 | + fossil_print(DIFF_CANNOT_COMPUTE_BINARY); | |
| 154 | + blob_reset(&file2); | |
| 155 | + return; | |
| 156 | + } | |
| 157 | + blob_reset(&file2); | |
| 158 | + } | |
| 121 | 159 | |
| 122 | 160 | /* Construct a temporary file to hold pFile1 based on the name of |
| 123 | 161 | ** zFile2 */ |
| 124 | 162 | blob_zero(&nameFile1); |
| 125 | 163 | do{ |
| @@ -151,16 +189,24 @@ | ||
| 151 | 189 | ** The difference is the set of edits needed to transform pFile1 into |
| 152 | 190 | ** pFile2. |
| 153 | 191 | ** |
| 154 | 192 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 155 | 193 | ** command zDiffCmd to do the diffing. |
| 194 | +** | |
| 195 | +** When using an external diff program, zBinGlob contains the GLOB patterns | |
| 196 | +** for file names to treat as binary. If fIncludeBinary is zero, these files | |
| 197 | +** will be skipped in addition to files that may contain binary content. | |
| 156 | 198 | */ |
| 157 | 199 | void diff_file_mem( |
| 158 | 200 | Blob *pFile1, /* In memory content to compare from */ |
| 159 | 201 | Blob *pFile2, /* In memory content to compare to */ |
| 202 | + int isBin1, /* Does the 'from' content appear to be binary */ | |
| 203 | + int isBin2, /* Does the 'to' content appear to be binary */ | |
| 160 | 204 | const char *zName, /* Display name of the file */ |
| 161 | 205 | const char *zDiffCmd, /* Command for comparison */ |
| 206 | + const char *zBinGlob, /* Treat file names matching this as binary */ | |
| 207 | + int fIncludeBinary, /* Include binary files for external diff */ | |
| 162 | 208 | u64 diffFlags /* Diff flags */ |
| 163 | 209 | ){ |
| 164 | 210 | if( diffFlags & DIFF_BRIEF ) return; |
| 165 | 211 | if( zDiffCmd==0 ){ |
| 166 | 212 | Blob out; /* Diff output text */ |
| @@ -174,10 +220,26 @@ | ||
| 174 | 220 | blob_reset(&out); |
| 175 | 221 | }else{ |
| 176 | 222 | Blob cmd; |
| 177 | 223 | char zTemp1[300]; |
| 178 | 224 | char zTemp2[300]; |
| 225 | + | |
| 226 | + if( !fIncludeBinary ){ | |
| 227 | + if( isBin1 || isBin2 ){ | |
| 228 | + fossil_print(DIFF_CANNOT_COMPUTE_BINARY); | |
| 229 | + return; | |
| 230 | + } | |
| 231 | + if( zBinGlob ){ | |
| 232 | + Glob *pBinary = glob_create(zBinGlob); | |
| 233 | + if( glob_match(pBinary, zName) ){ | |
| 234 | + fossil_print(DIFF_CANNOT_COMPUTE_BINARY); | |
| 235 | + glob_free(pBinary); | |
| 236 | + return; | |
| 237 | + } | |
| 238 | + glob_free(pBinary); | |
| 239 | + } | |
| 240 | + } | |
| 179 | 241 | |
| 180 | 242 | /* Construct a temporary file names */ |
| 181 | 243 | file_tempname(sizeof(zTemp1), zTemp1); |
| 182 | 244 | file_tempname(sizeof(zTemp2), zTemp2); |
| 183 | 245 | blob_write_to_file(pFile1, zTemp1); |
| @@ -201,40 +263,60 @@ | ||
| 201 | 263 | } |
| 202 | 264 | |
| 203 | 265 | /* |
| 204 | 266 | ** Do a diff against a single file named in zFileTreeName from version zFrom |
| 205 | 267 | ** against the same file on disk. |
| 268 | +** | |
| 269 | +** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the | |
| 270 | +** command zDiffCmd to do the diffing. | |
| 271 | +** | |
| 272 | +** When using an external diff program, zBinGlob contains the GLOB patterns | |
| 273 | +** for file names to treat as binary. If fIncludeBinary is zero, these files | |
| 274 | +** will be skipped in addition to files that may contain binary content. | |
| 206 | 275 | */ |
| 207 | 276 | static void diff_one_against_disk( |
| 208 | 277 | const char *zFrom, /* Name of file */ |
| 209 | 278 | const char *zDiffCmd, /* Use this "diff" command */ |
| 279 | + const char *zBinGlob, /* Treat file names matching this as binary */ | |
| 280 | + int fIncludeBinary, /* Include binary files for external diff */ | |
| 210 | 281 | u64 diffFlags, /* Diff control flags */ |
| 211 | 282 | const char *zFileTreeName |
| 212 | 283 | ){ |
| 213 | 284 | Blob fname; |
| 214 | 285 | Blob content; |
| 215 | 286 | int isLink; |
| 287 | + int isBin; | |
| 216 | 288 | file_tree_name(zFileTreeName, &fname, 1); |
| 217 | - historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0); | |
| 289 | + historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, | |
| 290 | + fIncludeBinary ? 0 : &isBin, 0); | |
| 218 | 291 | if( !isLink != !file_wd_islink(zFrom) ){ |
| 219 | - fossil_print("cannot compute difference between " | |
| 220 | - "symlink and regular file\n"); | |
| 292 | + fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); | |
| 221 | 293 | }else{ |
| 222 | - diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags); | |
| 294 | + diff_file(&content, isBin, zFileTreeName, zFileTreeName, | |
| 295 | + zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); | |
| 223 | 296 | } |
| 224 | 297 | blob_reset(&content); |
| 225 | 298 | blob_reset(&fname); |
| 226 | 299 | } |
| 227 | 300 | |
| 228 | 301 | /* |
| 229 | 302 | ** Run a diff between the version zFrom and files on disk. zFrom might |
| 230 | 303 | ** be NULL which means to simply show the difference between the edited |
| 231 | 304 | ** files on disk and the check-out on which they are based. |
| 305 | +** | |
| 306 | +** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the | |
| 307 | +** command zDiffCmd to do the diffing. | |
| 308 | +** | |
| 309 | +** When using an external diff program, zBinGlob contains the GLOB patterns | |
| 310 | +** for file names to treat as binary. If fIncludeBinary is zero, these files | |
| 311 | +** will be skipped in addition to files that may contain binary content. | |
| 232 | 312 | */ |
| 233 | 313 | static void diff_all_against_disk( |
| 234 | 314 | const char *zFrom, /* Version to difference from */ |
| 235 | 315 | const char *zDiffCmd, /* Use this diff command. NULL for built-in */ |
| 316 | + const char *zBinGlob, /* Treat file names matching this as binary */ | |
| 317 | + int fIncludeBinary, /* Treat file names matching this as binary */ | |
| 236 | 318 | u64 diffFlags /* Flags controlling diff output */ |
| 237 | 319 | ){ |
| 238 | 320 | int vid; |
| 239 | 321 | Blob sql; |
| 240 | 322 | Stmt q; |
| @@ -307,24 +389,26 @@ | ||
| 307 | 389 | srcid = 0; |
| 308 | 390 | if( !asNewFile ){ showDiff = 0; } |
| 309 | 391 | } |
| 310 | 392 | if( showDiff ){ |
| 311 | 393 | Blob content; |
| 394 | + int isBin; | |
| 312 | 395 | if( !isLink != !file_wd_islink(zFullName) ){ |
| 313 | 396 | diff_print_index(zPathname, diffFlags); |
| 314 | 397 | diff_print_filenames(zPathname, zPathname, diffFlags); |
| 315 | - fossil_print("cannot compute difference between " | |
| 316 | - "symlink and regular file\n"); | |
| 398 | + fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); | |
| 317 | 399 | continue; |
| 318 | 400 | } |
| 319 | 401 | if( srcid>0 ){ |
| 320 | 402 | content_get(srcid, &content); |
| 321 | 403 | }else{ |
| 322 | 404 | blob_zero(&content); |
| 323 | 405 | } |
| 406 | + isBin = fIncludeBinary ? 0 : looks_like_binary(&content); | |
| 324 | 407 | diff_print_index(zPathname, diffFlags); |
| 325 | - diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags); | |
| 408 | + diff_file(&content, isBin, zFullName, zPathname, zDiffCmd, | |
| 409 | + zBinGlob, fIncludeBinary, diffFlags); | |
| 326 | 410 | blob_reset(&content); |
| 327 | 411 | } |
| 328 | 412 | free(zToFree); |
| 329 | 413 | } |
| 330 | 414 | db_finalize(&q); |
| @@ -332,50 +416,72 @@ | ||
| 332 | 416 | } |
| 333 | 417 | |
| 334 | 418 | /* |
| 335 | 419 | ** Output the differences between two versions of a single file. |
| 336 | 420 | ** zFrom and zTo are the check-ins containing the two file versions. |
| 421 | +** | |
| 422 | +** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the | |
| 423 | +** command zDiffCmd to do the diffing. | |
| 424 | +** | |
| 425 | +** When using an external diff program, zBinGlob contains the GLOB patterns | |
| 426 | +** for file names to treat as binary. If fIncludeBinary is zero, these files | |
| 427 | +** will be skipped in addition to files that may contain binary content. | |
| 337 | 428 | */ |
| 338 | 429 | static void diff_one_two_versions( |
| 339 | 430 | const char *zFrom, |
| 340 | 431 | const char *zTo, |
| 341 | 432 | const char *zDiffCmd, |
| 433 | + const char *zBinGlob, | |
| 434 | + int fIncludeBinary, | |
| 342 | 435 | u64 diffFlags, |
| 343 | 436 | const char *zFileTreeName |
| 344 | 437 | ){ |
| 345 | 438 | char *zName; |
| 346 | 439 | Blob fname; |
| 347 | 440 | Blob v1, v2; |
| 348 | 441 | int isLink1, isLink2; |
| 442 | + int isBin1, isBin2; | |
| 349 | 443 | if( diffFlags & DIFF_BRIEF ) return; |
| 350 | 444 | file_tree_name(zFileTreeName, &fname, 1); |
| 351 | 445 | zName = blob_str(&fname); |
| 352 | - historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0); | |
| 353 | - historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0); | |
| 446 | + historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, | |
| 447 | + fIncludeBinary ? 0 : &isBin1, 0); | |
| 448 | + historical_version_of_file(zTo, zName, &v2, &isLink2, 0, | |
| 449 | + fIncludeBinary ? 0 : &isBin2, 0); | |
| 354 | 450 | if( isLink1 != isLink2 ){ |
| 355 | 451 | diff_print_filenames(zName, zName, diffFlags); |
| 356 | - fossil_print("cannot compute difference " | |
| 357 | - " between symlink and regular file\n"); | |
| 452 | + fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); | |
| 358 | 453 | }else{ |
| 359 | - diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags); | |
| 454 | + diff_file_mem(&v1, &v2, isBin1, isBin2, zName, zDiffCmd, | |
| 455 | + zBinGlob, fIncludeBinary, diffFlags); | |
| 360 | 456 | } |
| 361 | 457 | blob_reset(&v1); |
| 362 | 458 | blob_reset(&v2); |
| 363 | 459 | blob_reset(&fname); |
| 364 | 460 | } |
| 365 | 461 | |
| 366 | 462 | /* |
| 367 | 463 | ** Show the difference between two files identified by ManifestFile |
| 368 | 464 | ** entries. |
| 465 | +** | |
| 466 | +** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the | |
| 467 | +** command zDiffCmd to do the diffing. | |
| 468 | +** | |
| 469 | +** When using an external diff program, zBinGlob contains the GLOB patterns | |
| 470 | +** for file names to treat as binary. If fIncludeBinary is zero, these files | |
| 471 | +** will be skipped in addition to files that may contain binary content. | |
| 369 | 472 | */ |
| 370 | 473 | static void diff_manifest_entry( |
| 371 | 474 | struct ManifestFile *pFrom, |
| 372 | 475 | struct ManifestFile *pTo, |
| 373 | 476 | const char *zDiffCmd, |
| 477 | + const char *zBinGlob, | |
| 478 | + int fIncludeBinary, | |
| 374 | 479 | u64 diffFlags |
| 375 | 480 | ){ |
| 376 | 481 | Blob f1, f2; |
| 482 | + int isBin1, isBin2; | |
| 377 | 483 | int rid; |
| 378 | 484 | const char *zName = pFrom ? pFrom->zName : pTo->zName; |
| 379 | 485 | if( diffFlags & DIFF_BRIEF ) return; |
| 380 | 486 | diff_print_index(zName, diffFlags); |
| 381 | 487 | if( pFrom ){ |
| @@ -388,22 +494,34 @@ | ||
| 388 | 494 | rid = uuid_to_rid(pTo->zUuid, 0); |
| 389 | 495 | content_get(rid, &f2); |
| 390 | 496 | }else{ |
| 391 | 497 | blob_zero(&f2); |
| 392 | 498 | } |
| 393 | - diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags); | |
| 499 | + isBin1 = fIncludeBinary ? 0 : looks_like_binary(&f1); | |
| 500 | + isBin2 = fIncludeBinary ? 0 : looks_like_binary(&f2); | |
| 501 | + diff_file_mem(&f1, &f2, isBin1, isBin2, zName, zDiffCmd, | |
| 502 | + zBinGlob, fIncludeBinary, diffFlags); | |
| 394 | 503 | blob_reset(&f1); |
| 395 | 504 | blob_reset(&f2); |
| 396 | 505 | } |
| 397 | 506 | |
| 398 | 507 | /* |
| 399 | 508 | ** Output the differences between two check-ins. |
| 509 | +** | |
| 510 | +** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the | |
| 511 | +** command zDiffCmd to do the diffing. | |
| 512 | +** | |
| 513 | +** When using an external diff program, zBinGlob contains the GLOB patterns | |
| 514 | +** for file names to treat as binary. If fIncludeBinary is zero, these files | |
| 515 | +** will be skipped in addition to files that may contain binary content. | |
| 400 | 516 | */ |
| 401 | 517 | static void diff_all_two_versions( |
| 402 | 518 | const char *zFrom, |
| 403 | 519 | const char *zTo, |
| 404 | 520 | const char *zDiffCmd, |
| 521 | + const char *zBinGlob, | |
| 522 | + int fIncludeBinary, | |
| 405 | 523 | u64 diffFlags |
| 406 | 524 | ){ |
| 407 | 525 | Manifest *pFrom, *pTo; |
| 408 | 526 | ManifestFile *pFromFile, *pToFile; |
| 409 | 527 | int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; |
| @@ -425,17 +543,19 @@ | ||
| 425 | 543 | cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); |
| 426 | 544 | } |
| 427 | 545 | if( cmp<0 ){ |
| 428 | 546 | fossil_print("DELETED %s\n", pFromFile->zName); |
| 429 | 547 | if( asNewFlag ){ |
| 430 | - diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags); | |
| 548 | + diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob, | |
| 549 | + fIncludeBinary, diffFlags); | |
| 431 | 550 | } |
| 432 | 551 | pFromFile = manifest_file_next(pFrom,0); |
| 433 | 552 | }else if( cmp>0 ){ |
| 434 | 553 | fossil_print("ADDED %s\n", pToFile->zName); |
| 435 | 554 | if( asNewFlag ){ |
| 436 | - diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags); | |
| 555 | + diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob, | |
| 556 | + fIncludeBinary, diffFlags); | |
| 437 | 557 | } |
| 438 | 558 | pToFile = manifest_file_next(pTo,0); |
| 439 | 559 | }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ |
| 440 | 560 | /* No changes */ |
| 441 | 561 | pFromFile = manifest_file_next(pFrom,0); |
| @@ -442,11 +562,12 @@ | ||
| 442 | 562 | pToFile = manifest_file_next(pTo,0); |
| 443 | 563 | }else{ |
| 444 | 564 | if( diffFlags & DIFF_BRIEF ){ |
| 445 | 565 | fossil_print("CHANGED %s\n", pFromFile->zName); |
| 446 | 566 | }else{ |
| 447 | - diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags); | |
| 567 | + diff_manifest_entry(pFromFile, pToFile, zDiffCmd, zBinGlob, | |
| 568 | + fIncludeBinary, diffFlags); | |
| 448 | 569 | } |
| 449 | 570 | pFromFile = manifest_file_next(pFrom,0); |
| 450 | 571 | pToFile = manifest_file_next(pTo,0); |
| 451 | 572 | } |
| 452 | 573 | } |
| @@ -493,10 +614,11 @@ | ||
| 493 | 614 | @ .t tag config add -background {#c0ffc0} |
| 494 | 615 | @ .t tag config rm -background {#ffc0c0} |
| 495 | 616 | @ proc dehtml {x} { |
| 496 | 617 | @ return [string map {& & < < > > ' ' " \"} $x] |
| 497 | 618 | @ } |
| 619 | +@ # puts $cmd | |
| 498 | 620 | @ set in [open $cmd r] |
| 499 | 621 | @ while {![eof $in]} { |
| 500 | 622 | @ set line [gets $in] |
| 501 | 623 | @ if {[regexp {^<a name="chunk.*"></a>} $line]} continue |
| 502 | 624 | @ if {[regexp {^===} $line]} { |
| @@ -528,25 +650,52 @@ | ||
| 528 | 650 | ** Steps: |
| 529 | 651 | ** (1) Write the Tcl/Tk script used for rendering into a temp file. |
| 530 | 652 | ** (2) Invoke "wish" on the temp file using fossil_system(). |
| 531 | 653 | ** (3) Delete the temp file. |
| 532 | 654 | */ |
| 533 | -static void diff_tk(void){ | |
| 655 | +void diff_tk(const char *zSubCmd, int firstArg){ | |
| 534 | 656 | int i; |
| 535 | 657 | Blob script; |
| 536 | 658 | char *zTempFile; |
| 537 | 659 | char *zCmd; |
| 538 | 660 | blob_zero(&script); |
| 539 | - blob_appendf(&script, "set cmd {| \"%/\" diff --html -y", g.nameOfExe); | |
| 540 | - for(i=2; i<g.argc; i++){ | |
| 541 | - blob_appendf(&script, " \"%s\"", g.argv[i]); | |
| 661 | + blob_appendf(&script, "set cmd {| \"%/\" %s --html -y -i", | |
| 662 | + g.nameOfExe, zSubCmd); | |
| 663 | + for(i=firstArg; i<g.argc; i++){ | |
| 664 | + blob_append(&script, " ", 1); | |
| 665 | + shell_escape(&script, g.argv[i]); | |
| 542 | 666 | } |
| 543 | 667 | blob_appendf(&script, "}\n%s", zDiffScript); |
| 544 | 668 | zTempFile = write_blob_to_temp_file(&script); |
| 545 | 669 | zCmd = mprintf("tclsh \"%s\"", zTempFile); |
| 546 | 670 | fossil_system(zCmd); |
| 547 | 671 | file_delete(zTempFile); |
| 672 | + fossil_free(zCmd); | |
| 673 | +} | |
| 674 | + | |
| 675 | +/* | |
| 676 | +** Returns non-zero if files that may be binary should be used with external | |
| 677 | +** diff programs. | |
| 678 | +*/ | |
| 679 | +int diff_include_binary_files(void){ | |
| 680 | + if( is_truth(find_option("diff-binary", 0, 1)) ){ | |
| 681 | + return 1; | |
| 682 | + } | |
| 683 | + if( db_get_boolean("diff-binary", 1) ){ | |
| 684 | + return 1; | |
| 685 | + } | |
| 686 | + return 0; | |
| 687 | +} | |
| 688 | + | |
| 689 | +/* | |
| 690 | +** Returns the GLOB pattern for file names that should be treated as binary | |
| 691 | +** by the diff subsystem, if any. | |
| 692 | +*/ | |
| 693 | +const char *diff_get_binary_glob(void){ | |
| 694 | + const char *zBinGlob = find_option("binary", 0, 1); | |
| 695 | + if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); | |
| 696 | + return zBinGlob; | |
| 548 | 697 | } |
| 549 | 698 | |
| 550 | 699 | /* |
| 551 | 700 | ** COMMAND: diff |
| 552 | 701 | ** COMMAND: gdiff |
| @@ -573,10 +722,17 @@ | ||
| 573 | 722 | ** the "-i" option is a no-op. The "-i" option converts "gdiff" into "diff". |
| 574 | 723 | ** |
| 575 | 724 | ** The "-N" or "--new-file" option causes the complete text of added or |
| 576 | 725 | ** deleted files to be displayed. |
| 577 | 726 | ** |
| 727 | +** The "--diff-binary" option enables or disables the inclusion of binary files | |
| 728 | +** when using an external diff program. | |
| 729 | +** | |
| 730 | +** The "--binary" option causes files matching the glob PATTERN to be treated | |
| 731 | +** as binary when considering if they should be used with external diff program. | |
| 732 | +** This option overrides the "binary-glob" setting. | |
| 733 | +** | |
| 578 | 734 | ** Options: |
| 579 | 735 | ** --branch BRANCH Show diff of all changes on BRANCH |
| 580 | 736 | ** --brief Show filenames only |
| 581 | 737 | ** --context|-c N Use N lines of context |
| 582 | 738 | ** --from|-r VERSION select VERSION as source for the diff |
| @@ -585,24 +741,28 @@ | ||
| 585 | 741 | ** --tk Launch a Tcl/Tk GUI for display |
| 586 | 742 | ** --to VERSION select VERSION as target for the diff |
| 587 | 743 | ** --side-by-side|-y side-by-side diff |
| 588 | 744 | ** --unified unified diff |
| 589 | 745 | ** --width|-W N Width of lines in side-by-side diff |
| 746 | +** --diff-binary BOOL Include binary files when using external commands | |
| 747 | +** --binary PATTERN Treat files that match the glob PATTERN as binary | |
| 590 | 748 | */ |
| 591 | 749 | void diff_cmd(void){ |
| 592 | 750 | int isGDiff; /* True for gdiff. False for normal diff */ |
| 593 | 751 | int isInternDiff; /* True for internal diff */ |
| 594 | 752 | int hasNFlag; /* True if -N or --new-file flag is used */ |
| 595 | 753 | const char *zFrom; /* Source version number */ |
| 596 | 754 | const char *zTo; /* Target version number */ |
| 597 | 755 | const char *zBranch; /* Branch to diff */ |
| 598 | 756 | const char *zDiffCmd = 0; /* External diff command. NULL for internal diff */ |
| 757 | + const char *zBinGlob = 0; /* Treat file names matching this as binary */ | |
| 758 | + int fIncludeBinary = 0; /* Include binary files for external diff */ | |
| 599 | 759 | u64 diffFlags = 0; /* Flags to control the DIFF */ |
| 600 | 760 | int f; |
| 601 | 761 | |
| 602 | 762 | if( find_option("tk",0,0)!=0 ){ |
| 603 | - diff_tk(); | |
| 763 | + diff_tk("diff", 2); | |
| 604 | 764 | return; |
| 605 | 765 | } |
| 606 | 766 | isGDiff = g.argv[1][0]=='g'; |
| 607 | 767 | isInternDiff = find_option("internal","i",0)!=0; |
| 608 | 768 | zFrom = find_option("from", "r", 1); |
| @@ -619,35 +779,43 @@ | ||
| 619 | 779 | zTo = zBranch; |
| 620 | 780 | zFrom = mprintf("root:%s", zBranch); |
| 621 | 781 | } |
| 622 | 782 | if( zTo==0 ){ |
| 623 | 783 | db_must_be_within_tree(); |
| 624 | - verify_all_options(); | |
| 625 | 784 | if( !isInternDiff ){ |
| 626 | 785 | zDiffCmd = diff_command_external(isGDiff); |
| 627 | 786 | } |
| 787 | + zBinGlob = diff_get_binary_glob(); | |
| 788 | + fIncludeBinary = diff_include_binary_files(); | |
| 789 | + verify_all_options(); | |
| 628 | 790 | if( g.argc>=3 ){ |
| 629 | 791 | for(f=2; f<g.argc; ++f){ |
| 630 | - diff_one_against_disk(zFrom, zDiffCmd, diffFlags, g.argv[f]); | |
| 792 | + diff_one_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary, | |
| 793 | + diffFlags, g.argv[f]); | |
| 631 | 794 | } |
| 632 | 795 | }else{ |
| 633 | - diff_all_against_disk(zFrom, zDiffCmd, diffFlags); | |
| 796 | + diff_all_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary, | |
| 797 | + diffFlags); | |
| 634 | 798 | } |
| 635 | 799 | }else if( zFrom==0 ){ |
| 636 | 800 | fossil_fatal("must use --from if --to is present"); |
| 637 | 801 | }else{ |
| 638 | 802 | db_find_and_open_repository(0, 0); |
| 639 | - verify_all_options(); | |
| 640 | 803 | if( !isInternDiff ){ |
| 641 | 804 | zDiffCmd = diff_command_external(isGDiff); |
| 642 | 805 | } |
| 806 | + zBinGlob = diff_get_binary_glob(); | |
| 807 | + fIncludeBinary = diff_include_binary_files(); | |
| 808 | + verify_all_options(); | |
| 643 | 809 | if( g.argc>=3 ){ |
| 644 | 810 | for(f=2; f<g.argc; ++f){ |
| 645 | - diff_one_two_versions(zFrom, zTo, zDiffCmd, diffFlags, g.argv[f]); | |
| 811 | + diff_one_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary, | |
| 812 | + diffFlags, g.argv[f]); | |
| 646 | 813 | } |
| 647 | 814 | }else{ |
| 648 | - diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags); | |
| 815 | + diff_all_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary, | |
| 816 | + diffFlags); | |
| 649 | 817 | } |
| 650 | 818 | } |
| 651 | 819 | } |
| 652 | 820 | |
| 653 | 821 | /* |
| @@ -660,7 +828,7 @@ | ||
| 660 | 828 | login_check_credentials(); |
| 661 | 829 | if( !g.perm.Read ){ login_needed(); return; } |
| 662 | 830 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 663 | 831 | |
| 664 | 832 | cgi_set_content_type("text/plain"); |
| 665 | - diff_all_two_versions(zFrom, zTo, 0, DIFF_NEWFILE); | |
| 833 | + diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_NEWFILE); | |
| 666 | 834 | } |
| 667 | 835 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -69,16 +69,23 @@ | |
| 69 | ** The difference is the set of edits needed to transform pFile1 into |
| 70 | ** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. |
| 71 | ** |
| 72 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 73 | ** command zDiffCmd to do the diffing. |
| 74 | */ |
| 75 | void diff_file( |
| 76 | Blob *pFile1, /* In memory content to compare from */ |
| 77 | const char *zFile2, /* On disk content to compare to */ |
| 78 | const char *zName, /* Display name of the file */ |
| 79 | const char *zDiffCmd, /* Command for comparison */ |
| 80 | u64 diffFlags /* Flags to control the diff */ |
| 81 | ){ |
| 82 | if( zDiffCmd==0 ){ |
| 83 | Blob out; /* Diff output text */ |
| 84 | Blob file2; /* Content of zFile2 */ |
| @@ -116,10 +123,41 @@ | |
| 116 | blob_reset(&file2); |
| 117 | }else{ |
| 118 | int cnt = 0; |
| 119 | Blob nameFile1; /* Name of temporary file to old pFile1 content */ |
| 120 | Blob cmd; /* Text of command to run */ |
| 121 | |
| 122 | /* Construct a temporary file to hold pFile1 based on the name of |
| 123 | ** zFile2 */ |
| 124 | blob_zero(&nameFile1); |
| 125 | do{ |
| @@ -151,16 +189,24 @@ | |
| 151 | ** The difference is the set of edits needed to transform pFile1 into |
| 152 | ** pFile2. |
| 153 | ** |
| 154 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 155 | ** command zDiffCmd to do the diffing. |
| 156 | */ |
| 157 | void diff_file_mem( |
| 158 | Blob *pFile1, /* In memory content to compare from */ |
| 159 | Blob *pFile2, /* In memory content to compare to */ |
| 160 | const char *zName, /* Display name of the file */ |
| 161 | const char *zDiffCmd, /* Command for comparison */ |
| 162 | u64 diffFlags /* Diff flags */ |
| 163 | ){ |
| 164 | if( diffFlags & DIFF_BRIEF ) return; |
| 165 | if( zDiffCmd==0 ){ |
| 166 | Blob out; /* Diff output text */ |
| @@ -174,10 +220,26 @@ | |
| 174 | blob_reset(&out); |
| 175 | }else{ |
| 176 | Blob cmd; |
| 177 | char zTemp1[300]; |
| 178 | char zTemp2[300]; |
| 179 | |
| 180 | /* Construct a temporary file names */ |
| 181 | file_tempname(sizeof(zTemp1), zTemp1); |
| 182 | file_tempname(sizeof(zTemp2), zTemp2); |
| 183 | blob_write_to_file(pFile1, zTemp1); |
| @@ -201,40 +263,60 @@ | |
| 201 | } |
| 202 | |
| 203 | /* |
| 204 | ** Do a diff against a single file named in zFileTreeName from version zFrom |
| 205 | ** against the same file on disk. |
| 206 | */ |
| 207 | static void diff_one_against_disk( |
| 208 | const char *zFrom, /* Name of file */ |
| 209 | const char *zDiffCmd, /* Use this "diff" command */ |
| 210 | u64 diffFlags, /* Diff control flags */ |
| 211 | const char *zFileTreeName |
| 212 | ){ |
| 213 | Blob fname; |
| 214 | Blob content; |
| 215 | int isLink; |
| 216 | file_tree_name(zFileTreeName, &fname, 1); |
| 217 | historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0); |
| 218 | if( !isLink != !file_wd_islink(zFrom) ){ |
| 219 | fossil_print("cannot compute difference between " |
| 220 | "symlink and regular file\n"); |
| 221 | }else{ |
| 222 | diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags); |
| 223 | } |
| 224 | blob_reset(&content); |
| 225 | blob_reset(&fname); |
| 226 | } |
| 227 | |
| 228 | /* |
| 229 | ** Run a diff between the version zFrom and files on disk. zFrom might |
| 230 | ** be NULL which means to simply show the difference between the edited |
| 231 | ** files on disk and the check-out on which they are based. |
| 232 | */ |
| 233 | static void diff_all_against_disk( |
| 234 | const char *zFrom, /* Version to difference from */ |
| 235 | const char *zDiffCmd, /* Use this diff command. NULL for built-in */ |
| 236 | u64 diffFlags /* Flags controlling diff output */ |
| 237 | ){ |
| 238 | int vid; |
| 239 | Blob sql; |
| 240 | Stmt q; |
| @@ -307,24 +389,26 @@ | |
| 307 | srcid = 0; |
| 308 | if( !asNewFile ){ showDiff = 0; } |
| 309 | } |
| 310 | if( showDiff ){ |
| 311 | Blob content; |
| 312 | if( !isLink != !file_wd_islink(zFullName) ){ |
| 313 | diff_print_index(zPathname, diffFlags); |
| 314 | diff_print_filenames(zPathname, zPathname, diffFlags); |
| 315 | fossil_print("cannot compute difference between " |
| 316 | "symlink and regular file\n"); |
| 317 | continue; |
| 318 | } |
| 319 | if( srcid>0 ){ |
| 320 | content_get(srcid, &content); |
| 321 | }else{ |
| 322 | blob_zero(&content); |
| 323 | } |
| 324 | diff_print_index(zPathname, diffFlags); |
| 325 | diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags); |
| 326 | blob_reset(&content); |
| 327 | } |
| 328 | free(zToFree); |
| 329 | } |
| 330 | db_finalize(&q); |
| @@ -332,50 +416,72 @@ | |
| 332 | } |
| 333 | |
| 334 | /* |
| 335 | ** Output the differences between two versions of a single file. |
| 336 | ** zFrom and zTo are the check-ins containing the two file versions. |
| 337 | */ |
| 338 | static void diff_one_two_versions( |
| 339 | const char *zFrom, |
| 340 | const char *zTo, |
| 341 | const char *zDiffCmd, |
| 342 | u64 diffFlags, |
| 343 | const char *zFileTreeName |
| 344 | ){ |
| 345 | char *zName; |
| 346 | Blob fname; |
| 347 | Blob v1, v2; |
| 348 | int isLink1, isLink2; |
| 349 | if( diffFlags & DIFF_BRIEF ) return; |
| 350 | file_tree_name(zFileTreeName, &fname, 1); |
| 351 | zName = blob_str(&fname); |
| 352 | historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0); |
| 353 | historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0); |
| 354 | if( isLink1 != isLink2 ){ |
| 355 | diff_print_filenames(zName, zName, diffFlags); |
| 356 | fossil_print("cannot compute difference " |
| 357 | " between symlink and regular file\n"); |
| 358 | }else{ |
| 359 | diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags); |
| 360 | } |
| 361 | blob_reset(&v1); |
| 362 | blob_reset(&v2); |
| 363 | blob_reset(&fname); |
| 364 | } |
| 365 | |
| 366 | /* |
| 367 | ** Show the difference between two files identified by ManifestFile |
| 368 | ** entries. |
| 369 | */ |
| 370 | static void diff_manifest_entry( |
| 371 | struct ManifestFile *pFrom, |
| 372 | struct ManifestFile *pTo, |
| 373 | const char *zDiffCmd, |
| 374 | u64 diffFlags |
| 375 | ){ |
| 376 | Blob f1, f2; |
| 377 | int rid; |
| 378 | const char *zName = pFrom ? pFrom->zName : pTo->zName; |
| 379 | if( diffFlags & DIFF_BRIEF ) return; |
| 380 | diff_print_index(zName, diffFlags); |
| 381 | if( pFrom ){ |
| @@ -388,22 +494,34 @@ | |
| 388 | rid = uuid_to_rid(pTo->zUuid, 0); |
| 389 | content_get(rid, &f2); |
| 390 | }else{ |
| 391 | blob_zero(&f2); |
| 392 | } |
| 393 | diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags); |
| 394 | blob_reset(&f1); |
| 395 | blob_reset(&f2); |
| 396 | } |
| 397 | |
| 398 | /* |
| 399 | ** Output the differences between two check-ins. |
| 400 | */ |
| 401 | static void diff_all_two_versions( |
| 402 | const char *zFrom, |
| 403 | const char *zTo, |
| 404 | const char *zDiffCmd, |
| 405 | u64 diffFlags |
| 406 | ){ |
| 407 | Manifest *pFrom, *pTo; |
| 408 | ManifestFile *pFromFile, *pToFile; |
| 409 | int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; |
| @@ -425,17 +543,19 @@ | |
| 425 | cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); |
| 426 | } |
| 427 | if( cmp<0 ){ |
| 428 | fossil_print("DELETED %s\n", pFromFile->zName); |
| 429 | if( asNewFlag ){ |
| 430 | diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags); |
| 431 | } |
| 432 | pFromFile = manifest_file_next(pFrom,0); |
| 433 | }else if( cmp>0 ){ |
| 434 | fossil_print("ADDED %s\n", pToFile->zName); |
| 435 | if( asNewFlag ){ |
| 436 | diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags); |
| 437 | } |
| 438 | pToFile = manifest_file_next(pTo,0); |
| 439 | }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ |
| 440 | /* No changes */ |
| 441 | pFromFile = manifest_file_next(pFrom,0); |
| @@ -442,11 +562,12 @@ | |
| 442 | pToFile = manifest_file_next(pTo,0); |
| 443 | }else{ |
| 444 | if( diffFlags & DIFF_BRIEF ){ |
| 445 | fossil_print("CHANGED %s\n", pFromFile->zName); |
| 446 | }else{ |
| 447 | diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags); |
| 448 | } |
| 449 | pFromFile = manifest_file_next(pFrom,0); |
| 450 | pToFile = manifest_file_next(pTo,0); |
| 451 | } |
| 452 | } |
| @@ -493,10 +614,11 @@ | |
| 493 | @ .t tag config add -background {#c0ffc0} |
| 494 | @ .t tag config rm -background {#ffc0c0} |
| 495 | @ proc dehtml {x} { |
| 496 | @ return [string map {& & < < > > ' ' " \"} $x] |
| 497 | @ } |
| 498 | @ set in [open $cmd r] |
| 499 | @ while {![eof $in]} { |
| 500 | @ set line [gets $in] |
| 501 | @ if {[regexp {^<a name="chunk.*"></a>} $line]} continue |
| 502 | @ if {[regexp {^===} $line]} { |
| @@ -528,25 +650,52 @@ | |
| 528 | ** Steps: |
| 529 | ** (1) Write the Tcl/Tk script used for rendering into a temp file. |
| 530 | ** (2) Invoke "wish" on the temp file using fossil_system(). |
| 531 | ** (3) Delete the temp file. |
| 532 | */ |
| 533 | static void diff_tk(void){ |
| 534 | int i; |
| 535 | Blob script; |
| 536 | char *zTempFile; |
| 537 | char *zCmd; |
| 538 | blob_zero(&script); |
| 539 | blob_appendf(&script, "set cmd {| \"%/\" diff --html -y", g.nameOfExe); |
| 540 | for(i=2; i<g.argc; i++){ |
| 541 | blob_appendf(&script, " \"%s\"", g.argv[i]); |
| 542 | } |
| 543 | blob_appendf(&script, "}\n%s", zDiffScript); |
| 544 | zTempFile = write_blob_to_temp_file(&script); |
| 545 | zCmd = mprintf("tclsh \"%s\"", zTempFile); |
| 546 | fossil_system(zCmd); |
| 547 | file_delete(zTempFile); |
| 548 | } |
| 549 | |
| 550 | /* |
| 551 | ** COMMAND: diff |
| 552 | ** COMMAND: gdiff |
| @@ -573,10 +722,17 @@ | |
| 573 | ** the "-i" option is a no-op. The "-i" option converts "gdiff" into "diff". |
| 574 | ** |
| 575 | ** The "-N" or "--new-file" option causes the complete text of added or |
| 576 | ** deleted files to be displayed. |
| 577 | ** |
| 578 | ** Options: |
| 579 | ** --branch BRANCH Show diff of all changes on BRANCH |
| 580 | ** --brief Show filenames only |
| 581 | ** --context|-c N Use N lines of context |
| 582 | ** --from|-r VERSION select VERSION as source for the diff |
| @@ -585,24 +741,28 @@ | |
| 585 | ** --tk Launch a Tcl/Tk GUI for display |
| 586 | ** --to VERSION select VERSION as target for the diff |
| 587 | ** --side-by-side|-y side-by-side diff |
| 588 | ** --unified unified diff |
| 589 | ** --width|-W N Width of lines in side-by-side diff |
| 590 | */ |
| 591 | void diff_cmd(void){ |
| 592 | int isGDiff; /* True for gdiff. False for normal diff */ |
| 593 | int isInternDiff; /* True for internal diff */ |
| 594 | int hasNFlag; /* True if -N or --new-file flag is used */ |
| 595 | const char *zFrom; /* Source version number */ |
| 596 | const char *zTo; /* Target version number */ |
| 597 | const char *zBranch; /* Branch to diff */ |
| 598 | const char *zDiffCmd = 0; /* External diff command. NULL for internal diff */ |
| 599 | u64 diffFlags = 0; /* Flags to control the DIFF */ |
| 600 | int f; |
| 601 | |
| 602 | if( find_option("tk",0,0)!=0 ){ |
| 603 | diff_tk(); |
| 604 | return; |
| 605 | } |
| 606 | isGDiff = g.argv[1][0]=='g'; |
| 607 | isInternDiff = find_option("internal","i",0)!=0; |
| 608 | zFrom = find_option("from", "r", 1); |
| @@ -619,35 +779,43 @@ | |
| 619 | zTo = zBranch; |
| 620 | zFrom = mprintf("root:%s", zBranch); |
| 621 | } |
| 622 | if( zTo==0 ){ |
| 623 | db_must_be_within_tree(); |
| 624 | verify_all_options(); |
| 625 | if( !isInternDiff ){ |
| 626 | zDiffCmd = diff_command_external(isGDiff); |
| 627 | } |
| 628 | if( g.argc>=3 ){ |
| 629 | for(f=2; f<g.argc; ++f){ |
| 630 | diff_one_against_disk(zFrom, zDiffCmd, diffFlags, g.argv[f]); |
| 631 | } |
| 632 | }else{ |
| 633 | diff_all_against_disk(zFrom, zDiffCmd, diffFlags); |
| 634 | } |
| 635 | }else if( zFrom==0 ){ |
| 636 | fossil_fatal("must use --from if --to is present"); |
| 637 | }else{ |
| 638 | db_find_and_open_repository(0, 0); |
| 639 | verify_all_options(); |
| 640 | if( !isInternDiff ){ |
| 641 | zDiffCmd = diff_command_external(isGDiff); |
| 642 | } |
| 643 | if( g.argc>=3 ){ |
| 644 | for(f=2; f<g.argc; ++f){ |
| 645 | diff_one_two_versions(zFrom, zTo, zDiffCmd, diffFlags, g.argv[f]); |
| 646 | } |
| 647 | }else{ |
| 648 | diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags); |
| 649 | } |
| 650 | } |
| 651 | } |
| 652 | |
| 653 | /* |
| @@ -660,7 +828,7 @@ | |
| 660 | login_check_credentials(); |
| 661 | if( !g.perm.Read ){ login_needed(); return; } |
| 662 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 663 | |
| 664 | cgi_set_content_type("text/plain"); |
| 665 | diff_all_two_versions(zFrom, zTo, 0, DIFF_NEWFILE); |
| 666 | } |
| 667 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -69,16 +69,23 @@ | |
| 69 | ** The difference is the set of edits needed to transform pFile1 into |
| 70 | ** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. |
| 71 | ** |
| 72 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 73 | ** command zDiffCmd to do the diffing. |
| 74 | ** |
| 75 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 76 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 77 | ** will be skipped in addition to files that may contain binary content. |
| 78 | */ |
| 79 | void diff_file( |
| 80 | Blob *pFile1, /* In memory content to compare from */ |
| 81 | int isBin1, /* Does the 'from' content appear to be binary */ |
| 82 | const char *zFile2, /* On disk content to compare to */ |
| 83 | const char *zName, /* Display name of the file */ |
| 84 | const char *zDiffCmd, /* Command for comparison */ |
| 85 | const char *zBinGlob, /* Treat file names matching this as binary */ |
| 86 | int fIncludeBinary, /* Include binary files for external diff */ |
| 87 | u64 diffFlags /* Flags to control the diff */ |
| 88 | ){ |
| 89 | if( zDiffCmd==0 ){ |
| 90 | Blob out; /* Diff output text */ |
| 91 | Blob file2; /* Content of zFile2 */ |
| @@ -116,10 +123,41 @@ | |
| 123 | blob_reset(&file2); |
| 124 | }else{ |
| 125 | int cnt = 0; |
| 126 | Blob nameFile1; /* Name of temporary file to old pFile1 content */ |
| 127 | Blob cmd; /* Text of command to run */ |
| 128 | |
| 129 | if( !fIncludeBinary ){ |
| 130 | Blob file2; |
| 131 | if( isBin1 ){ |
| 132 | fossil_print(DIFF_CANNOT_COMPUTE_BINARY); |
| 133 | return; |
| 134 | } |
| 135 | if( zBinGlob ){ |
| 136 | Glob *pBinary = glob_create(zBinGlob); |
| 137 | if( glob_match(pBinary, zName) ){ |
| 138 | fossil_print(DIFF_CANNOT_COMPUTE_BINARY); |
| 139 | glob_free(pBinary); |
| 140 | return; |
| 141 | } |
| 142 | glob_free(pBinary); |
| 143 | } |
| 144 | blob_zero(&file2); |
| 145 | if( file_wd_size(zFile2)>=0 ){ |
| 146 | if( file_wd_islink(zFile2) ){ |
| 147 | blob_read_link(&file2, zFile2); |
| 148 | }else{ |
| 149 | blob_read_from_file(&file2, zFile2); |
| 150 | } |
| 151 | } |
| 152 | if( looks_like_binary(&file2) ){ |
| 153 | fossil_print(DIFF_CANNOT_COMPUTE_BINARY); |
| 154 | blob_reset(&file2); |
| 155 | return; |
| 156 | } |
| 157 | blob_reset(&file2); |
| 158 | } |
| 159 | |
| 160 | /* Construct a temporary file to hold pFile1 based on the name of |
| 161 | ** zFile2 */ |
| 162 | blob_zero(&nameFile1); |
| 163 | do{ |
| @@ -151,16 +189,24 @@ | |
| 189 | ** The difference is the set of edits needed to transform pFile1 into |
| 190 | ** pFile2. |
| 191 | ** |
| 192 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 193 | ** command zDiffCmd to do the diffing. |
| 194 | ** |
| 195 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 196 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 197 | ** will be skipped in addition to files that may contain binary content. |
| 198 | */ |
| 199 | void diff_file_mem( |
| 200 | Blob *pFile1, /* In memory content to compare from */ |
| 201 | Blob *pFile2, /* In memory content to compare to */ |
| 202 | int isBin1, /* Does the 'from' content appear to be binary */ |
| 203 | int isBin2, /* Does the 'to' content appear to be binary */ |
| 204 | const char *zName, /* Display name of the file */ |
| 205 | const char *zDiffCmd, /* Command for comparison */ |
| 206 | const char *zBinGlob, /* Treat file names matching this as binary */ |
| 207 | int fIncludeBinary, /* Include binary files for external diff */ |
| 208 | u64 diffFlags /* Diff flags */ |
| 209 | ){ |
| 210 | if( diffFlags & DIFF_BRIEF ) return; |
| 211 | if( zDiffCmd==0 ){ |
| 212 | Blob out; /* Diff output text */ |
| @@ -174,10 +220,26 @@ | |
| 220 | blob_reset(&out); |
| 221 | }else{ |
| 222 | Blob cmd; |
| 223 | char zTemp1[300]; |
| 224 | char zTemp2[300]; |
| 225 | |
| 226 | if( !fIncludeBinary ){ |
| 227 | if( isBin1 || isBin2 ){ |
| 228 | fossil_print(DIFF_CANNOT_COMPUTE_BINARY); |
| 229 | return; |
| 230 | } |
| 231 | if( zBinGlob ){ |
| 232 | Glob *pBinary = glob_create(zBinGlob); |
| 233 | if( glob_match(pBinary, zName) ){ |
| 234 | fossil_print(DIFF_CANNOT_COMPUTE_BINARY); |
| 235 | glob_free(pBinary); |
| 236 | return; |
| 237 | } |
| 238 | glob_free(pBinary); |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | /* Construct a temporary file names */ |
| 243 | file_tempname(sizeof(zTemp1), zTemp1); |
| 244 | file_tempname(sizeof(zTemp2), zTemp2); |
| 245 | blob_write_to_file(pFile1, zTemp1); |
| @@ -201,40 +263,60 @@ | |
| 263 | } |
| 264 | |
| 265 | /* |
| 266 | ** Do a diff against a single file named in zFileTreeName from version zFrom |
| 267 | ** against the same file on disk. |
| 268 | ** |
| 269 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 270 | ** command zDiffCmd to do the diffing. |
| 271 | ** |
| 272 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 273 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 274 | ** will be skipped in addition to files that may contain binary content. |
| 275 | */ |
| 276 | static void diff_one_against_disk( |
| 277 | const char *zFrom, /* Name of file */ |
| 278 | const char *zDiffCmd, /* Use this "diff" command */ |
| 279 | const char *zBinGlob, /* Treat file names matching this as binary */ |
| 280 | int fIncludeBinary, /* Include binary files for external diff */ |
| 281 | u64 diffFlags, /* Diff control flags */ |
| 282 | const char *zFileTreeName |
| 283 | ){ |
| 284 | Blob fname; |
| 285 | Blob content; |
| 286 | int isLink; |
| 287 | int isBin; |
| 288 | file_tree_name(zFileTreeName, &fname, 1); |
| 289 | historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, |
| 290 | fIncludeBinary ? 0 : &isBin, 0); |
| 291 | if( !isLink != !file_wd_islink(zFrom) ){ |
| 292 | fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); |
| 293 | }else{ |
| 294 | diff_file(&content, isBin, zFileTreeName, zFileTreeName, |
| 295 | zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); |
| 296 | } |
| 297 | blob_reset(&content); |
| 298 | blob_reset(&fname); |
| 299 | } |
| 300 | |
| 301 | /* |
| 302 | ** Run a diff between the version zFrom and files on disk. zFrom might |
| 303 | ** be NULL which means to simply show the difference between the edited |
| 304 | ** files on disk and the check-out on which they are based. |
| 305 | ** |
| 306 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 307 | ** command zDiffCmd to do the diffing. |
| 308 | ** |
| 309 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 310 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 311 | ** will be skipped in addition to files that may contain binary content. |
| 312 | */ |
| 313 | static void diff_all_against_disk( |
| 314 | const char *zFrom, /* Version to difference from */ |
| 315 | const char *zDiffCmd, /* Use this diff command. NULL for built-in */ |
| 316 | const char *zBinGlob, /* Treat file names matching this as binary */ |
| 317 | int fIncludeBinary, /* Treat file names matching this as binary */ |
| 318 | u64 diffFlags /* Flags controlling diff output */ |
| 319 | ){ |
| 320 | int vid; |
| 321 | Blob sql; |
| 322 | Stmt q; |
| @@ -307,24 +389,26 @@ | |
| 389 | srcid = 0; |
| 390 | if( !asNewFile ){ showDiff = 0; } |
| 391 | } |
| 392 | if( showDiff ){ |
| 393 | Blob content; |
| 394 | int isBin; |
| 395 | if( !isLink != !file_wd_islink(zFullName) ){ |
| 396 | diff_print_index(zPathname, diffFlags); |
| 397 | diff_print_filenames(zPathname, zPathname, diffFlags); |
| 398 | fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); |
| 399 | continue; |
| 400 | } |
| 401 | if( srcid>0 ){ |
| 402 | content_get(srcid, &content); |
| 403 | }else{ |
| 404 | blob_zero(&content); |
| 405 | } |
| 406 | isBin = fIncludeBinary ? 0 : looks_like_binary(&content); |
| 407 | diff_print_index(zPathname, diffFlags); |
| 408 | diff_file(&content, isBin, zFullName, zPathname, zDiffCmd, |
| 409 | zBinGlob, fIncludeBinary, diffFlags); |
| 410 | blob_reset(&content); |
| 411 | } |
| 412 | free(zToFree); |
| 413 | } |
| 414 | db_finalize(&q); |
| @@ -332,50 +416,72 @@ | |
| 416 | } |
| 417 | |
| 418 | /* |
| 419 | ** Output the differences between two versions of a single file. |
| 420 | ** zFrom and zTo are the check-ins containing the two file versions. |
| 421 | ** |
| 422 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 423 | ** command zDiffCmd to do the diffing. |
| 424 | ** |
| 425 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 426 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 427 | ** will be skipped in addition to files that may contain binary content. |
| 428 | */ |
| 429 | static void diff_one_two_versions( |
| 430 | const char *zFrom, |
| 431 | const char *zTo, |
| 432 | const char *zDiffCmd, |
| 433 | const char *zBinGlob, |
| 434 | int fIncludeBinary, |
| 435 | u64 diffFlags, |
| 436 | const char *zFileTreeName |
| 437 | ){ |
| 438 | char *zName; |
| 439 | Blob fname; |
| 440 | Blob v1, v2; |
| 441 | int isLink1, isLink2; |
| 442 | int isBin1, isBin2; |
| 443 | if( diffFlags & DIFF_BRIEF ) return; |
| 444 | file_tree_name(zFileTreeName, &fname, 1); |
| 445 | zName = blob_str(&fname); |
| 446 | historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, |
| 447 | fIncludeBinary ? 0 : &isBin1, 0); |
| 448 | historical_version_of_file(zTo, zName, &v2, &isLink2, 0, |
| 449 | fIncludeBinary ? 0 : &isBin2, 0); |
| 450 | if( isLink1 != isLink2 ){ |
| 451 | diff_print_filenames(zName, zName, diffFlags); |
| 452 | fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK); |
| 453 | }else{ |
| 454 | diff_file_mem(&v1, &v2, isBin1, isBin2, zName, zDiffCmd, |
| 455 | zBinGlob, fIncludeBinary, diffFlags); |
| 456 | } |
| 457 | blob_reset(&v1); |
| 458 | blob_reset(&v2); |
| 459 | blob_reset(&fname); |
| 460 | } |
| 461 | |
| 462 | /* |
| 463 | ** Show the difference between two files identified by ManifestFile |
| 464 | ** entries. |
| 465 | ** |
| 466 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 467 | ** command zDiffCmd to do the diffing. |
| 468 | ** |
| 469 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 470 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 471 | ** will be skipped in addition to files that may contain binary content. |
| 472 | */ |
| 473 | static void diff_manifest_entry( |
| 474 | struct ManifestFile *pFrom, |
| 475 | struct ManifestFile *pTo, |
| 476 | const char *zDiffCmd, |
| 477 | const char *zBinGlob, |
| 478 | int fIncludeBinary, |
| 479 | u64 diffFlags |
| 480 | ){ |
| 481 | Blob f1, f2; |
| 482 | int isBin1, isBin2; |
| 483 | int rid; |
| 484 | const char *zName = pFrom ? pFrom->zName : pTo->zName; |
| 485 | if( diffFlags & DIFF_BRIEF ) return; |
| 486 | diff_print_index(zName, diffFlags); |
| 487 | if( pFrom ){ |
| @@ -388,22 +494,34 @@ | |
| 494 | rid = uuid_to_rid(pTo->zUuid, 0); |
| 495 | content_get(rid, &f2); |
| 496 | }else{ |
| 497 | blob_zero(&f2); |
| 498 | } |
| 499 | isBin1 = fIncludeBinary ? 0 : looks_like_binary(&f1); |
| 500 | isBin2 = fIncludeBinary ? 0 : looks_like_binary(&f2); |
| 501 | diff_file_mem(&f1, &f2, isBin1, isBin2, zName, zDiffCmd, |
| 502 | zBinGlob, fIncludeBinary, diffFlags); |
| 503 | blob_reset(&f1); |
| 504 | blob_reset(&f2); |
| 505 | } |
| 506 | |
| 507 | /* |
| 508 | ** Output the differences between two check-ins. |
| 509 | ** |
| 510 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 511 | ** command zDiffCmd to do the diffing. |
| 512 | ** |
| 513 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 514 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 515 | ** will be skipped in addition to files that may contain binary content. |
| 516 | */ |
| 517 | static void diff_all_two_versions( |
| 518 | const char *zFrom, |
| 519 | const char *zTo, |
| 520 | const char *zDiffCmd, |
| 521 | const char *zBinGlob, |
| 522 | int fIncludeBinary, |
| 523 | u64 diffFlags |
| 524 | ){ |
| 525 | Manifest *pFrom, *pTo; |
| 526 | ManifestFile *pFromFile, *pToFile; |
| 527 | int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; |
| @@ -425,17 +543,19 @@ | |
| 543 | cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); |
| 544 | } |
| 545 | if( cmp<0 ){ |
| 546 | fossil_print("DELETED %s\n", pFromFile->zName); |
| 547 | if( asNewFlag ){ |
| 548 | diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob, |
| 549 | fIncludeBinary, diffFlags); |
| 550 | } |
| 551 | pFromFile = manifest_file_next(pFrom,0); |
| 552 | }else if( cmp>0 ){ |
| 553 | fossil_print("ADDED %s\n", pToFile->zName); |
| 554 | if( asNewFlag ){ |
| 555 | diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob, |
| 556 | fIncludeBinary, diffFlags); |
| 557 | } |
| 558 | pToFile = manifest_file_next(pTo,0); |
| 559 | }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ |
| 560 | /* No changes */ |
| 561 | pFromFile = manifest_file_next(pFrom,0); |
| @@ -442,11 +562,12 @@ | |
| 562 | pToFile = manifest_file_next(pTo,0); |
| 563 | }else{ |
| 564 | if( diffFlags & DIFF_BRIEF ){ |
| 565 | fossil_print("CHANGED %s\n", pFromFile->zName); |
| 566 | }else{ |
| 567 | diff_manifest_entry(pFromFile, pToFile, zDiffCmd, zBinGlob, |
| 568 | fIncludeBinary, diffFlags); |
| 569 | } |
| 570 | pFromFile = manifest_file_next(pFrom,0); |
| 571 | pToFile = manifest_file_next(pTo,0); |
| 572 | } |
| 573 | } |
| @@ -493,10 +614,11 @@ | |
| 614 | @ .t tag config add -background {#c0ffc0} |
| 615 | @ .t tag config rm -background {#ffc0c0} |
| 616 | @ proc dehtml {x} { |
| 617 | @ return [string map {& & < < > > ' ' " \"} $x] |
| 618 | @ } |
| 619 | @ # puts $cmd |
| 620 | @ set in [open $cmd r] |
| 621 | @ while {![eof $in]} { |
| 622 | @ set line [gets $in] |
| 623 | @ if {[regexp {^<a name="chunk.*"></a>} $line]} continue |
| 624 | @ if {[regexp {^===} $line]} { |
| @@ -528,25 +650,52 @@ | |
| 650 | ** Steps: |
| 651 | ** (1) Write the Tcl/Tk script used for rendering into a temp file. |
| 652 | ** (2) Invoke "wish" on the temp file using fossil_system(). |
| 653 | ** (3) Delete the temp file. |
| 654 | */ |
| 655 | void diff_tk(const char *zSubCmd, int firstArg){ |
| 656 | int i; |
| 657 | Blob script; |
| 658 | char *zTempFile; |
| 659 | char *zCmd; |
| 660 | blob_zero(&script); |
| 661 | blob_appendf(&script, "set cmd {| \"%/\" %s --html -y -i", |
| 662 | g.nameOfExe, zSubCmd); |
| 663 | for(i=firstArg; i<g.argc; i++){ |
| 664 | blob_append(&script, " ", 1); |
| 665 | shell_escape(&script, g.argv[i]); |
| 666 | } |
| 667 | blob_appendf(&script, "}\n%s", zDiffScript); |
| 668 | zTempFile = write_blob_to_temp_file(&script); |
| 669 | zCmd = mprintf("tclsh \"%s\"", zTempFile); |
| 670 | fossil_system(zCmd); |
| 671 | file_delete(zTempFile); |
| 672 | fossil_free(zCmd); |
| 673 | } |
| 674 | |
| 675 | /* |
| 676 | ** Returns non-zero if files that may be binary should be used with external |
| 677 | ** diff programs. |
| 678 | */ |
| 679 | int diff_include_binary_files(void){ |
| 680 | if( is_truth(find_option("diff-binary", 0, 1)) ){ |
| 681 | return 1; |
| 682 | } |
| 683 | if( db_get_boolean("diff-binary", 1) ){ |
| 684 | return 1; |
| 685 | } |
| 686 | return 0; |
| 687 | } |
| 688 | |
| 689 | /* |
| 690 | ** Returns the GLOB pattern for file names that should be treated as binary |
| 691 | ** by the diff subsystem, if any. |
| 692 | */ |
| 693 | const char *diff_get_binary_glob(void){ |
| 694 | const char *zBinGlob = find_option("binary", 0, 1); |
| 695 | if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
| 696 | return zBinGlob; |
| 697 | } |
| 698 | |
| 699 | /* |
| 700 | ** COMMAND: diff |
| 701 | ** COMMAND: gdiff |
| @@ -573,10 +722,17 @@ | |
| 722 | ** the "-i" option is a no-op. The "-i" option converts "gdiff" into "diff". |
| 723 | ** |
| 724 | ** The "-N" or "--new-file" option causes the complete text of added or |
| 725 | ** deleted files to be displayed. |
| 726 | ** |
| 727 | ** The "--diff-binary" option enables or disables the inclusion of binary files |
| 728 | ** when using an external diff program. |
| 729 | ** |
| 730 | ** The "--binary" option causes files matching the glob PATTERN to be treated |
| 731 | ** as binary when considering if they should be used with external diff program. |
| 732 | ** This option overrides the "binary-glob" setting. |
| 733 | ** |
| 734 | ** Options: |
| 735 | ** --branch BRANCH Show diff of all changes on BRANCH |
| 736 | ** --brief Show filenames only |
| 737 | ** --context|-c N Use N lines of context |
| 738 | ** --from|-r VERSION select VERSION as source for the diff |
| @@ -585,24 +741,28 @@ | |
| 741 | ** --tk Launch a Tcl/Tk GUI for display |
| 742 | ** --to VERSION select VERSION as target for the diff |
| 743 | ** --side-by-side|-y side-by-side diff |
| 744 | ** --unified unified diff |
| 745 | ** --width|-W N Width of lines in side-by-side diff |
| 746 | ** --diff-binary BOOL Include binary files when using external commands |
| 747 | ** --binary PATTERN Treat files that match the glob PATTERN as binary |
| 748 | */ |
| 749 | void diff_cmd(void){ |
| 750 | int isGDiff; /* True for gdiff. False for normal diff */ |
| 751 | int isInternDiff; /* True for internal diff */ |
| 752 | int hasNFlag; /* True if -N or --new-file flag is used */ |
| 753 | const char *zFrom; /* Source version number */ |
| 754 | const char *zTo; /* Target version number */ |
| 755 | const char *zBranch; /* Branch to diff */ |
| 756 | const char *zDiffCmd = 0; /* External diff command. NULL for internal diff */ |
| 757 | const char *zBinGlob = 0; /* Treat file names matching this as binary */ |
| 758 | int fIncludeBinary = 0; /* Include binary files for external diff */ |
| 759 | u64 diffFlags = 0; /* Flags to control the DIFF */ |
| 760 | int f; |
| 761 | |
| 762 | if( find_option("tk",0,0)!=0 ){ |
| 763 | diff_tk("diff", 2); |
| 764 | return; |
| 765 | } |
| 766 | isGDiff = g.argv[1][0]=='g'; |
| 767 | isInternDiff = find_option("internal","i",0)!=0; |
| 768 | zFrom = find_option("from", "r", 1); |
| @@ -619,35 +779,43 @@ | |
| 779 | zTo = zBranch; |
| 780 | zFrom = mprintf("root:%s", zBranch); |
| 781 | } |
| 782 | if( zTo==0 ){ |
| 783 | db_must_be_within_tree(); |
| 784 | if( !isInternDiff ){ |
| 785 | zDiffCmd = diff_command_external(isGDiff); |
| 786 | } |
| 787 | zBinGlob = diff_get_binary_glob(); |
| 788 | fIncludeBinary = diff_include_binary_files(); |
| 789 | verify_all_options(); |
| 790 | if( g.argc>=3 ){ |
| 791 | for(f=2; f<g.argc; ++f){ |
| 792 | diff_one_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary, |
| 793 | diffFlags, g.argv[f]); |
| 794 | } |
| 795 | }else{ |
| 796 | diff_all_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary, |
| 797 | diffFlags); |
| 798 | } |
| 799 | }else if( zFrom==0 ){ |
| 800 | fossil_fatal("must use --from if --to is present"); |
| 801 | }else{ |
| 802 | db_find_and_open_repository(0, 0); |
| 803 | if( !isInternDiff ){ |
| 804 | zDiffCmd = diff_command_external(isGDiff); |
| 805 | } |
| 806 | zBinGlob = diff_get_binary_glob(); |
| 807 | fIncludeBinary = diff_include_binary_files(); |
| 808 | verify_all_options(); |
| 809 | if( g.argc>=3 ){ |
| 810 | for(f=2; f<g.argc; ++f){ |
| 811 | diff_one_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary, |
| 812 | diffFlags, g.argv[f]); |
| 813 | } |
| 814 | }else{ |
| 815 | diff_all_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary, |
| 816 | diffFlags); |
| 817 | } |
| 818 | } |
| 819 | } |
| 820 | |
| 821 | /* |
| @@ -660,7 +828,7 @@ | |
| 828 | login_check_credentials(); |
| 829 | if( !g.perm.Read ){ login_needed(); return; } |
| 830 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 831 | |
| 832 | cgi_set_content_type("text/plain"); |
| 833 | diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_NEWFILE); |
| 834 | } |
| 835 |
+1
-1
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -115,11 +115,11 @@ | ||
| 115 | 115 | Blob fname; |
| 116 | 116 | const char *zRevision = find_option("revision", "r", 1); |
| 117 | 117 | |
| 118 | 118 | file_tree_name(g.argv[2], &fname, 1); |
| 119 | 119 | if( zRevision ){ |
| 120 | - historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0); | |
| 120 | + historical_version_of_file(zRevision, blob_str(&fname), &record, 0,0,0,0); | |
| 121 | 121 | }else{ |
| 122 | 122 | int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", |
| 123 | 123 | &fname, filename_collation()); |
| 124 | 124 | if( rid==0 ){ |
| 125 | 125 | fossil_fatal("no history for file: %b", &fname); |
| 126 | 126 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -115,11 +115,11 @@ | |
| 115 | Blob fname; |
| 116 | const char *zRevision = find_option("revision", "r", 1); |
| 117 | |
| 118 | file_tree_name(g.argv[2], &fname, 1); |
| 119 | if( zRevision ){ |
| 120 | historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0); |
| 121 | }else{ |
| 122 | int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", |
| 123 | &fname, filename_collation()); |
| 124 | if( rid==0 ){ |
| 125 | fossil_fatal("no history for file: %b", &fname); |
| 126 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -115,11 +115,11 @@ | |
| 115 | Blob fname; |
| 116 | const char *zRevision = find_option("revision", "r", 1); |
| 117 | |
| 118 | file_tree_name(g.argv[2], &fname, 1); |
| 119 | if( zRevision ){ |
| 120 | historical_version_of_file(zRevision, blob_str(&fname), &record, 0,0,0,0); |
| 121 | }else{ |
| 122 | int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", |
| 123 | &fname, filename_collation()); |
| 124 | if( rid==0 ){ |
| 125 | fossil_fatal("no history for file: %b", &fname); |
| 126 |
+27
-5
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -88,13 +88,21 @@ | ||
| 88 | 88 | ** All Tcl related context information is in this structure. This structure |
| 89 | 89 | ** definition has been copied from and should be kept in sync with the one in |
| 90 | 90 | ** "th_tcl.c". |
| 91 | 91 | */ |
| 92 | 92 | struct TclContext { |
| 93 | - int argc; | |
| 94 | - char **argv; | |
| 95 | - Tcl_Interp *interp; | |
| 93 | + int argc; /* Number of original (expanded) arguments. */ | |
| 94 | + char **argv; /* Full copy of the original (expanded) arguments. */ | |
| 95 | + void *library; /* The Tcl library module handle. */ | |
| 96 | + void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */ | |
| 97 | + void *xCreateInterp; /* See tcl_CreateInterpProc in th_tcl.c. */ | |
| 98 | + Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ | |
| 99 | + char *setup; /* The optional Tcl setup script. */ | |
| 100 | + void *xPreEval; /* Optional, called before Tcl_Eval*(). */ | |
| 101 | + void *pPreContext; /* Optional, provided to xPreEval(). */ | |
| 102 | + void *xPostEval; /* Optional, called after Tcl_Eval*(). */ | |
| 103 | + void *pPostContext; /* Optional, provided to xPostEval(). */ | |
| 96 | 104 | }; |
| 97 | 105 | #endif |
| 98 | 106 | |
| 99 | 107 | /* |
| 100 | 108 | ** All global variables are in this structure. |
| @@ -546,10 +554,24 @@ | ||
| 546 | 554 | while( i<g.argc ) newArgv[j++] = g.argv[i++]; |
| 547 | 555 | newArgv[j] = 0; |
| 548 | 556 | g.argc = j; |
| 549 | 557 | g.argv = newArgv; |
| 550 | 558 | } |
| 559 | + | |
| 560 | +/* | |
| 561 | +** Make a deep copy of the provided argument array and return it. | |
| 562 | +*/ | |
| 563 | +static char **copy_args(int argc, char **argv){ | |
| 564 | + char **zNewArgv; | |
| 565 | + int i; | |
| 566 | + zNewArgv = fossil_malloc( sizeof(char*)*(argc+1) ); | |
| 567 | + memset(zNewArgv, 0, sizeof(char*)*(argc+1)); | |
| 568 | + for(i=0; i<argc; i++){ | |
| 569 | + zNewArgv[i] = fossil_strdup(argv[i]); | |
| 570 | + } | |
| 571 | + return zNewArgv; | |
| 572 | +} | |
| 551 | 573 | |
| 552 | 574 | /* |
| 553 | 575 | ** This procedure runs first. |
| 554 | 576 | */ |
| 555 | 577 | int main(int argc, char **argv) |
| @@ -574,13 +596,13 @@ | ||
| 574 | 596 | g.json.outOpt.addNewline = 1; |
| 575 | 597 | g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */; |
| 576 | 598 | #endif /* FOSSIL_ENABLE_JSON */ |
| 577 | 599 | expand_args_option(argc, argv); |
| 578 | 600 | #ifdef FOSSIL_ENABLE_TCL |
| 601 | + memset(&g.tcl, 0, sizeof(TclContext)); | |
| 579 | 602 | g.tcl.argc = g.argc; |
| 580 | - g.tcl.argv = g.argv; | |
| 581 | - g.tcl.interp = 0; | |
| 603 | + g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */ | |
| 582 | 604 | #endif |
| 583 | 605 | if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){ |
| 584 | 606 | zCmdName = "cgi"; |
| 585 | 607 | g.isHTTP = 1; |
| 586 | 608 | }else if( g.argc<2 ){ |
| 587 | 609 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -88,13 +88,21 @@ | |
| 88 | ** All Tcl related context information is in this structure. This structure |
| 89 | ** definition has been copied from and should be kept in sync with the one in |
| 90 | ** "th_tcl.c". |
| 91 | */ |
| 92 | struct TclContext { |
| 93 | int argc; |
| 94 | char **argv; |
| 95 | Tcl_Interp *interp; |
| 96 | }; |
| 97 | #endif |
| 98 | |
| 99 | /* |
| 100 | ** All global variables are in this structure. |
| @@ -546,10 +554,24 @@ | |
| 546 | while( i<g.argc ) newArgv[j++] = g.argv[i++]; |
| 547 | newArgv[j] = 0; |
| 548 | g.argc = j; |
| 549 | g.argv = newArgv; |
| 550 | } |
| 551 | |
| 552 | /* |
| 553 | ** This procedure runs first. |
| 554 | */ |
| 555 | int main(int argc, char **argv) |
| @@ -574,13 +596,13 @@ | |
| 574 | g.json.outOpt.addNewline = 1; |
| 575 | g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */; |
| 576 | #endif /* FOSSIL_ENABLE_JSON */ |
| 577 | expand_args_option(argc, argv); |
| 578 | #ifdef FOSSIL_ENABLE_TCL |
| 579 | g.tcl.argc = g.argc; |
| 580 | g.tcl.argv = g.argv; |
| 581 | g.tcl.interp = 0; |
| 582 | #endif |
| 583 | if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){ |
| 584 | zCmdName = "cgi"; |
| 585 | g.isHTTP = 1; |
| 586 | }else if( g.argc<2 ){ |
| 587 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -88,13 +88,21 @@ | |
| 88 | ** All Tcl related context information is in this structure. This structure |
| 89 | ** definition has been copied from and should be kept in sync with the one in |
| 90 | ** "th_tcl.c". |
| 91 | */ |
| 92 | struct TclContext { |
| 93 | int argc; /* Number of original (expanded) arguments. */ |
| 94 | char **argv; /* Full copy of the original (expanded) arguments. */ |
| 95 | void *library; /* The Tcl library module handle. */ |
| 96 | void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */ |
| 97 | void *xCreateInterp; /* See tcl_CreateInterpProc in th_tcl.c. */ |
| 98 | Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ |
| 99 | char *setup; /* The optional Tcl setup script. */ |
| 100 | void *xPreEval; /* Optional, called before Tcl_Eval*(). */ |
| 101 | void *pPreContext; /* Optional, provided to xPreEval(). */ |
| 102 | void *xPostEval; /* Optional, called after Tcl_Eval*(). */ |
| 103 | void *pPostContext; /* Optional, provided to xPostEval(). */ |
| 104 | }; |
| 105 | #endif |
| 106 | |
| 107 | /* |
| 108 | ** All global variables are in this structure. |
| @@ -546,10 +554,24 @@ | |
| 554 | while( i<g.argc ) newArgv[j++] = g.argv[i++]; |
| 555 | newArgv[j] = 0; |
| 556 | g.argc = j; |
| 557 | g.argv = newArgv; |
| 558 | } |
| 559 | |
| 560 | /* |
| 561 | ** Make a deep copy of the provided argument array and return it. |
| 562 | */ |
| 563 | static char **copy_args(int argc, char **argv){ |
| 564 | char **zNewArgv; |
| 565 | int i; |
| 566 | zNewArgv = fossil_malloc( sizeof(char*)*(argc+1) ); |
| 567 | memset(zNewArgv, 0, sizeof(char*)*(argc+1)); |
| 568 | for(i=0; i<argc; i++){ |
| 569 | zNewArgv[i] = fossil_strdup(argv[i]); |
| 570 | } |
| 571 | return zNewArgv; |
| 572 | } |
| 573 | |
| 574 | /* |
| 575 | ** This procedure runs first. |
| 576 | */ |
| 577 | int main(int argc, char **argv) |
| @@ -574,13 +596,13 @@ | |
| 596 | g.json.outOpt.addNewline = 1; |
| 597 | g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */; |
| 598 | #endif /* FOSSIL_ENABLE_JSON */ |
| 599 | expand_args_option(argc, argv); |
| 600 | #ifdef FOSSIL_ENABLE_TCL |
| 601 | memset(&g.tcl, 0, sizeof(TclContext)); |
| 602 | g.tcl.argc = g.argc; |
| 603 | g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */ |
| 604 | #endif |
| 605 | if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){ |
| 606 | zCmdName = "cgi"; |
| 607 | g.isHTTP = 1; |
| 608 | }else if( g.argc<2 ){ |
| 609 |
+22
-2
| --- src/makemake.tcl | ||
| +++ src/makemake.tcl | ||
| @@ -375,10 +375,14 @@ | ||
| 375 | 375 | # FOSSIL_ENABLE_SSL = 1 |
| 376 | 376 | |
| 377 | 377 | #### Enable scripting support via Tcl/Tk |
| 378 | 378 | # |
| 379 | 379 | # FOSSIL_ENABLE_TCL = 1 |
| 380 | + | |
| 381 | +#### Load Tcl using the stubs mechanism | |
| 382 | +# | |
| 383 | +# FOSSIL_ENABLE_TCL_STUBS = 1 | |
| 380 | 384 | |
| 381 | 385 | #### Use the Tcl source directory instead of the install directory? |
| 382 | 386 | # This is useful when Tcl has been compiled statically with MinGW. |
| 383 | 387 | # |
| 384 | 388 | FOSSIL_TCL_SOURCE = 1 |
| @@ -424,11 +428,15 @@ | ||
| 424 | 428 | TCLINCDIR = $(TCLDIR)/include |
| 425 | 429 | TCLLIBDIR = $(TCLDIR)/lib |
| 426 | 430 | |
| 427 | 431 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 428 | 432 | # |
| 433 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 434 | +LIBTCL = -ltclstub86 | |
| 435 | +else | |
| 429 | 436 | LIBTCL = -ltcl86 |
| 437 | +endif | |
| 430 | 438 | |
| 431 | 439 | #### C Compile and options for use in building executables that |
| 432 | 440 | # will run on the target platform. This is usually the same |
| 433 | 441 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 434 | 442 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -462,14 +470,22 @@ | ||
| 462 | 470 | ifdef FOSSIL_ENABLE_SSL |
| 463 | 471 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 464 | 472 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 465 | 473 | endif |
| 466 | 474 | |
| 467 | -# With Tcl support (statically linked) | |
| 475 | +# With Tcl support | |
| 468 | 476 | ifdef FOSSIL_ENABLE_TCL |
| 469 | -TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD | |
| 477 | +TCC += -DFOSSIL_ENABLE_TCL=1 | |
| 470 | 478 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 479 | +# Either statically linked or via stubs | |
| 480 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 481 | +TCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS | |
| 482 | +RCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS | |
| 483 | +else | |
| 484 | +TCC += -DSTATIC_BUILD | |
| 485 | +RCC += -DSTATIC_BUILD | |
| 486 | +endif | |
| 471 | 487 | endif |
| 472 | 488 | |
| 473 | 489 | # With JSON support |
| 474 | 490 | ifdef FOSSIL_ENABLE_JSON |
| 475 | 491 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -499,11 +515,15 @@ | ||
| 499 | 515 | |
| 500 | 516 | #### These libraries MUST appear in the same order as they do for Tcl |
| 501 | 517 | # or linking with it will not work (exact reason unknown). |
| 502 | 518 | # |
| 503 | 519 | ifdef FOSSIL_ENABLE_TCL |
| 520 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 521 | +LIB += -lkernel32 -lws2_32 | |
| 522 | +else | |
| 504 | 523 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 524 | +endif | |
| 505 | 525 | else |
| 506 | 526 | LIB += -lkernel32 -lws2_32 |
| 507 | 527 | endif |
| 508 | 528 | |
| 509 | 529 | #### Tcl shell for use in running the fossil test suite. This is only |
| 510 | 530 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -375,10 +375,14 @@ | |
| 375 | # FOSSIL_ENABLE_SSL = 1 |
| 376 | |
| 377 | #### Enable scripting support via Tcl/Tk |
| 378 | # |
| 379 | # FOSSIL_ENABLE_TCL = 1 |
| 380 | |
| 381 | #### Use the Tcl source directory instead of the install directory? |
| 382 | # This is useful when Tcl has been compiled statically with MinGW. |
| 383 | # |
| 384 | FOSSIL_TCL_SOURCE = 1 |
| @@ -424,11 +428,15 @@ | |
| 424 | TCLINCDIR = $(TCLDIR)/include |
| 425 | TCLLIBDIR = $(TCLDIR)/lib |
| 426 | |
| 427 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 428 | # |
| 429 | LIBTCL = -ltcl86 |
| 430 | |
| 431 | #### C Compile and options for use in building executables that |
| 432 | # will run on the target platform. This is usually the same |
| 433 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 434 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -462,14 +470,22 @@ | |
| 462 | ifdef FOSSIL_ENABLE_SSL |
| 463 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 464 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 465 | endif |
| 466 | |
| 467 | # With Tcl support (statically linked) |
| 468 | ifdef FOSSIL_ENABLE_TCL |
| 469 | TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD |
| 470 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 471 | endif |
| 472 | |
| 473 | # With JSON support |
| 474 | ifdef FOSSIL_ENABLE_JSON |
| 475 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -499,11 +515,15 @@ | |
| 499 | |
| 500 | #### These libraries MUST appear in the same order as they do for Tcl |
| 501 | # or linking with it will not work (exact reason unknown). |
| 502 | # |
| 503 | ifdef FOSSIL_ENABLE_TCL |
| 504 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 505 | else |
| 506 | LIB += -lkernel32 -lws2_32 |
| 507 | endif |
| 508 | |
| 509 | #### Tcl shell for use in running the fossil test suite. This is only |
| 510 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -375,10 +375,14 @@ | |
| 375 | # FOSSIL_ENABLE_SSL = 1 |
| 376 | |
| 377 | #### Enable scripting support via Tcl/Tk |
| 378 | # |
| 379 | # FOSSIL_ENABLE_TCL = 1 |
| 380 | |
| 381 | #### Load Tcl using the stubs mechanism |
| 382 | # |
| 383 | # FOSSIL_ENABLE_TCL_STUBS = 1 |
| 384 | |
| 385 | #### Use the Tcl source directory instead of the install directory? |
| 386 | # This is useful when Tcl has been compiled statically with MinGW. |
| 387 | # |
| 388 | FOSSIL_TCL_SOURCE = 1 |
| @@ -424,11 +428,15 @@ | |
| 428 | TCLINCDIR = $(TCLDIR)/include |
| 429 | TCLLIBDIR = $(TCLDIR)/lib |
| 430 | |
| 431 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 432 | # |
| 433 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 434 | LIBTCL = -ltclstub86 |
| 435 | else |
| 436 | LIBTCL = -ltcl86 |
| 437 | endif |
| 438 | |
| 439 | #### C Compile and options for use in building executables that |
| 440 | # will run on the target platform. This is usually the same |
| 441 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 442 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -462,14 +470,22 @@ | |
| 470 | ifdef FOSSIL_ENABLE_SSL |
| 471 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 472 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 473 | endif |
| 474 | |
| 475 | # With Tcl support |
| 476 | ifdef FOSSIL_ENABLE_TCL |
| 477 | TCC += -DFOSSIL_ENABLE_TCL=1 |
| 478 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 479 | # Either statically linked or via stubs |
| 480 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 481 | TCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS |
| 482 | RCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS |
| 483 | else |
| 484 | TCC += -DSTATIC_BUILD |
| 485 | RCC += -DSTATIC_BUILD |
| 486 | endif |
| 487 | endif |
| 488 | |
| 489 | # With JSON support |
| 490 | ifdef FOSSIL_ENABLE_JSON |
| 491 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -499,11 +515,15 @@ | |
| 515 | |
| 516 | #### These libraries MUST appear in the same order as they do for Tcl |
| 517 | # or linking with it will not work (exact reason unknown). |
| 518 | # |
| 519 | ifdef FOSSIL_ENABLE_TCL |
| 520 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 521 | LIB += -lkernel32 -lws2_32 |
| 522 | else |
| 523 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 524 | endif |
| 525 | else |
| 526 | LIB += -lkernel32 -lws2_32 |
| 527 | endif |
| 528 | |
| 529 | #### Tcl shell for use in running the fossil test suite. This is only |
| 530 |
+2
-2
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -1879,12 +1879,12 @@ | ||
| 1879 | 1879 | for(i=0; i<p->nTag; i++){ |
| 1880 | 1880 | zUuid = p->aTag[i].zUuid; |
| 1881 | 1881 | if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){ |
| 1882 | 1882 | if( i>0 ) blob_append(&comment, " ", 1); |
| 1883 | 1883 | blob_appendf(&comment, |
| 1884 | - "Edit [[/info/%S | %S]]:", | |
| 1885 | - zUuid, zUuid); | |
| 1884 | + "Edit [%S]:", | |
| 1885 | + zUuid); | |
| 1886 | 1886 | branchMove = 0; |
| 1887 | 1887 | } |
| 1888 | 1888 | zName = p->aTag[i].zName; |
| 1889 | 1889 | zValue = p->aTag[i].zValue; |
| 1890 | 1890 | if( strcmp(zName, "*branch")==0 ){ |
| 1891 | 1891 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -1879,12 +1879,12 @@ | |
| 1879 | for(i=0; i<p->nTag; i++){ |
| 1880 | zUuid = p->aTag[i].zUuid; |
| 1881 | if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){ |
| 1882 | if( i>0 ) blob_append(&comment, " ", 1); |
| 1883 | blob_appendf(&comment, |
| 1884 | "Edit [[/info/%S | %S]]:", |
| 1885 | zUuid, zUuid); |
| 1886 | branchMove = 0; |
| 1887 | } |
| 1888 | zName = p->aTag[i].zName; |
| 1889 | zValue = p->aTag[i].zValue; |
| 1890 | if( strcmp(zName, "*branch")==0 ){ |
| 1891 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -1879,12 +1879,12 @@ | |
| 1879 | for(i=0; i<p->nTag; i++){ |
| 1880 | zUuid = p->aTag[i].zUuid; |
| 1881 | if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){ |
| 1882 | if( i>0 ) blob_append(&comment, " ", 1); |
| 1883 | blob_appendf(&comment, |
| 1884 | "Edit [%S]:", |
| 1885 | zUuid); |
| 1886 | branchMove = 0; |
| 1887 | } |
| 1888 | zName = p->aTag[i].zName; |
| 1889 | zValue = p->aTag[i].zValue; |
| 1890 | if( strcmp(zName, "*branch")==0 ){ |
| 1891 |
+1
| --- src/path.c | ||
| +++ src/path.c | ||
| @@ -26,10 +26,11 @@ | ||
| 26 | 26 | */ |
| 27 | 27 | struct PathNode { |
| 28 | 28 | int rid; /* ID for this node */ |
| 29 | 29 | u8 fromIsParent; /* True if pFrom is the parent of rid */ |
| 30 | 30 | u8 isPrim; /* True if primary side of common ancestor */ |
| 31 | + u8 isHidden; /* Abbreviate output in "fossil bisect ls" */ | |
| 31 | 32 | PathNode *pFrom; /* Node we came from */ |
| 32 | 33 | union { |
| 33 | 34 | PathNode *pPeer; /* List of nodes of the same generation */ |
| 34 | 35 | PathNode *pTo; /* Next on path from beginning to end */ |
| 35 | 36 | } u; |
| 36 | 37 |
| --- src/path.c | |
| +++ src/path.c | |
| @@ -26,10 +26,11 @@ | |
| 26 | */ |
| 27 | struct PathNode { |
| 28 | int rid; /* ID for this node */ |
| 29 | u8 fromIsParent; /* True if pFrom is the parent of rid */ |
| 30 | u8 isPrim; /* True if primary side of common ancestor */ |
| 31 | PathNode *pFrom; /* Node we came from */ |
| 32 | union { |
| 33 | PathNode *pPeer; /* List of nodes of the same generation */ |
| 34 | PathNode *pTo; /* Next on path from beginning to end */ |
| 35 | } u; |
| 36 |
| --- src/path.c | |
| +++ src/path.c | |
| @@ -26,10 +26,11 @@ | |
| 26 | */ |
| 27 | struct PathNode { |
| 28 | int rid; /* ID for this node */ |
| 29 | u8 fromIsParent; /* True if pFrom is the parent of rid */ |
| 30 | u8 isPrim; /* True if primary side of common ancestor */ |
| 31 | u8 isHidden; /* Abbreviate output in "fossil bisect ls" */ |
| 32 | PathNode *pFrom; /* Node we came from */ |
| 33 | union { |
| 34 | PathNode *pPeer; /* List of nodes of the same generation */ |
| 35 | PathNode *pTo; /* Next on path from beginning to end */ |
| 36 | } u; |
| 37 |
+1
-1
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -952,11 +952,11 @@ | ||
| 952 | 952 | @ </p> |
| 953 | 953 | @ |
| 954 | 954 | @ <hr /> |
| 955 | 955 | entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); |
| 956 | 956 | @ <p>The number of hours for which a login is valid. This must be a |
| 957 | - @ positive number. The default is 8760 hours which is approximately equal | |
| 957 | + @ positive number. The default is 8766 hours which is approximately equal | |
| 958 | 958 | @ to a year.</p> |
| 959 | 959 | |
| 960 | 960 | @ <hr /> |
| 961 | 961 | entry_attribute("Download packet limit", 10, "max-download", "mxdwn", |
| 962 | 962 | "5000000"); |
| 963 | 963 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -952,11 +952,11 @@ | |
| 952 | @ </p> |
| 953 | @ |
| 954 | @ <hr /> |
| 955 | entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); |
| 956 | @ <p>The number of hours for which a login is valid. This must be a |
| 957 | @ positive number. The default is 8760 hours which is approximately equal |
| 958 | @ to a year.</p> |
| 959 | |
| 960 | @ <hr /> |
| 961 | entry_attribute("Download packet limit", 10, "max-download", "mxdwn", |
| 962 | "5000000"); |
| 963 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -952,11 +952,11 @@ | |
| 952 | @ </p> |
| 953 | @ |
| 954 | @ <hr /> |
| 955 | entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); |
| 956 | @ <p>The number of hours for which a login is valid. This must be a |
| 957 | @ positive number. The default is 8766 hours which is approximately equal |
| 958 | @ to a year.</p> |
| 959 | |
| 960 | @ <hr /> |
| 961 | entry_attribute("Download packet limit", 10, "max-download", "mxdwn", |
| 962 | "5000000"); |
| 963 |
+409
-302
| --- src/sqlite3.c | ||
| +++ src/sqlite3.c | ||
| @@ -673,11 +673,11 @@ | ||
| 673 | 673 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 674 | 674 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 675 | 675 | */ |
| 676 | 676 | #define SQLITE_VERSION "3.7.15" |
| 677 | 677 | #define SQLITE_VERSION_NUMBER 3007015 |
| 678 | -#define SQLITE_SOURCE_ID "2012-09-28 00:44:28 1e874629d7cf568368b912b295bd3001147d0b52" | |
| 678 | +#define SQLITE_SOURCE_ID "2012-10-05 07:36:34 43155b1543bddbb84a8bc13a5b7344b228ddacb9" | |
| 679 | 679 | |
| 680 | 680 | /* |
| 681 | 681 | ** CAPI3REF: Run-Time Library Version Numbers |
| 682 | 682 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 683 | 683 | ** |
| @@ -1421,10 +1421,21 @@ | ||
| 1421 | 1421 | ** that the VFS encountered an error while handling the [PRAGMA] and the |
| 1422 | 1422 | ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] |
| 1423 | 1423 | ** file control occurs at the beginning of pragma statement analysis and so |
| 1424 | 1424 | ** it is able to override built-in [PRAGMA] statements. |
| 1425 | 1425 | ** </ul> |
| 1426 | +** | |
| 1427 | +** <li>[[SQLITE_FCNTL_BUSYHANDLER]] | |
| 1428 | +** ^This file-control may be invoked by SQLite on the database file handle | |
| 1429 | +** shortly after it is opened in order to provide a custom VFS with access | |
| 1430 | +** to the connections busy-handler callback. The argument is of type (void **) | |
| 1431 | +** - an array of two (void *) values. The first (void *) actually points | |
| 1432 | +** to a function of type (int (*)(void *)). In order to invoke the connections | |
| 1433 | +** busy-handler, this function should be invoked with the second (void *) in | |
| 1434 | +** the array as the only argument. If it returns non-zero, then the operation | |
| 1435 | +** should be retried. If it returns zero, the custom VFS should abandon the | |
| 1436 | +** current operation. | |
| 1426 | 1437 | */ |
| 1427 | 1438 | #define SQLITE_FCNTL_LOCKSTATE 1 |
| 1428 | 1439 | #define SQLITE_GET_LOCKPROXYFILE 2 |
| 1429 | 1440 | #define SQLITE_SET_LOCKPROXYFILE 3 |
| 1430 | 1441 | #define SQLITE_LAST_ERRNO 4 |
| @@ -1436,10 +1447,11 @@ | ||
| 1436 | 1447 | #define SQLITE_FCNTL_PERSIST_WAL 10 |
| 1437 | 1448 | #define SQLITE_FCNTL_OVERWRITE 11 |
| 1438 | 1449 | #define SQLITE_FCNTL_VFSNAME 12 |
| 1439 | 1450 | #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 |
| 1440 | 1451 | #define SQLITE_FCNTL_PRAGMA 14 |
| 1452 | +#define SQLITE_FCNTL_BUSYHANDLER 15 | |
| 1441 | 1453 | |
| 1442 | 1454 | /* |
| 1443 | 1455 | ** CAPI3REF: Mutex Handle |
| 1444 | 1456 | ** |
| 1445 | 1457 | ** The mutex module within SQLite defines [sqlite3_mutex] to be an |
| @@ -8381,10 +8393,13 @@ | ||
| 8381 | 8393 | SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); |
| 8382 | 8394 | SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); |
| 8383 | 8395 | SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree*); |
| 8384 | 8396 | SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int); |
| 8385 | 8397 | SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*); |
| 8398 | +#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_DEBUG) | |
| 8399 | +SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p); | |
| 8400 | +#endif | |
| 8386 | 8401 | SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); |
| 8387 | 8402 | SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); |
| 8388 | 8403 | SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int); |
| 8389 | 8404 | SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); |
| 8390 | 8405 | SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int); |
| @@ -9150,10 +9165,11 @@ | ||
| 9150 | 9165 | SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); |
| 9151 | 9166 | SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); |
| 9152 | 9167 | SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); |
| 9153 | 9168 | SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); |
| 9154 | 9169 | SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *); |
| 9170 | +SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *); | |
| 9155 | 9171 | |
| 9156 | 9172 | /* Functions used to truncate the database file. */ |
| 9157 | 9173 | SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); |
| 9158 | 9174 | |
| 9159 | 9175 | #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) |
| @@ -25825,10 +25841,12 @@ | ||
| 25825 | 25841 | int prior = 0; |
| 25826 | 25842 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) |
| 25827 | 25843 | i64 newOffset; |
| 25828 | 25844 | #endif |
| 25829 | 25845 | TIMER_START; |
| 25846 | + assert( cnt==(cnt&0x1ffff) ); | |
| 25847 | + cnt &= 0x1ffff; | |
| 25830 | 25848 | do{ |
| 25831 | 25849 | #if defined(USE_PREAD) |
| 25832 | 25850 | got = osPread(id->h, pBuf, cnt, offset); |
| 25833 | 25851 | SimulateIOError( got = -1 ); |
| 25834 | 25852 | #elif defined(USE_PREAD64) |
| @@ -25914,10 +25932,12 @@ | ||
| 25914 | 25932 | static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ |
| 25915 | 25933 | int got; |
| 25916 | 25934 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) |
| 25917 | 25935 | i64 newOffset; |
| 25918 | 25936 | #endif |
| 25937 | + assert( cnt==(cnt&0x1ffff) ); | |
| 25938 | + cnt &= 0x1ffff; | |
| 25919 | 25939 | TIMER_START; |
| 25920 | 25940 | #if defined(USE_PREAD) |
| 25921 | 25941 | do{ got = osPwrite(id->h, pBuf, cnt, offset); }while( got<0 && errno==EINTR ); |
| 25922 | 25942 | #elif defined(USE_PREAD64) |
| 25923 | 25943 | do{ got = osPwrite64(id->h, pBuf, cnt, offset);}while( got<0 && errno==EINTR); |
| @@ -39558,10 +39578,25 @@ | ||
| 39558 | 39578 | } |
| 39559 | 39579 | } |
| 39560 | 39580 | } |
| 39561 | 39581 | return rc; |
| 39562 | 39582 | } |
| 39583 | + | |
| 39584 | +/* | |
| 39585 | +** Return a sanitized version of the sector-size of OS file pFile. The | |
| 39586 | +** return value is guaranteed to lie between 32 and MAX_SECTOR_SIZE. | |
| 39587 | +*/ | |
| 39588 | +SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *pFile){ | |
| 39589 | + int iRet = sqlite3OsSectorSize(pFile); | |
| 39590 | + if( iRet<32 ){ | |
| 39591 | + iRet = 512; | |
| 39592 | + }else if( iRet>MAX_SECTOR_SIZE ){ | |
| 39593 | + assert( MAX_SECTOR_SIZE>=512 ); | |
| 39594 | + iRet = MAX_SECTOR_SIZE; | |
| 39595 | + } | |
| 39596 | + return iRet; | |
| 39597 | +} | |
| 39563 | 39598 | |
| 39564 | 39599 | /* |
| 39565 | 39600 | ** Set the value of the Pager.sectorSize variable for the given |
| 39566 | 39601 | ** pager based on the value returned by the xSectorSize method |
| 39567 | 39602 | ** of the open database file. The sector size will be used used |
| @@ -39594,18 +39629,11 @@ | ||
| 39594 | 39629 | /* Sector size doesn't matter for temporary files. Also, the file |
| 39595 | 39630 | ** may not have been opened yet, in which case the OsSectorSize() |
| 39596 | 39631 | ** call will segfault. */ |
| 39597 | 39632 | pPager->sectorSize = 512; |
| 39598 | 39633 | }else{ |
| 39599 | - pPager->sectorSize = sqlite3OsSectorSize(pPager->fd); | |
| 39600 | - if( pPager->sectorSize<32 ){ | |
| 39601 | - pPager->sectorSize = 512; | |
| 39602 | - } | |
| 39603 | - if( pPager->sectorSize>MAX_SECTOR_SIZE ){ | |
| 39604 | - assert( MAX_SECTOR_SIZE>=512 ); | |
| 39605 | - pPager->sectorSize = MAX_SECTOR_SIZE; | |
| 39606 | - } | |
| 39634 | + pPager->sectorSize = sqlite3SectorSize(pPager->fd); | |
| 39607 | 39635 | } |
| 39608 | 39636 | } |
| 39609 | 39637 | |
| 39610 | 39638 | /* |
| 39611 | 39639 | ** Playback the journal and thus restore the database file to |
| @@ -40518,13 +40546,20 @@ | ||
| 40518 | 40546 | */ |
| 40519 | 40547 | SQLITE_PRIVATE void sqlite3PagerSetBusyhandler( |
| 40520 | 40548 | Pager *pPager, /* Pager object */ |
| 40521 | 40549 | int (*xBusyHandler)(void *), /* Pointer to busy-handler function */ |
| 40522 | 40550 | void *pBusyHandlerArg /* Argument to pass to xBusyHandler */ |
| 40523 | -){ | |
| 40551 | +){ | |
| 40524 | 40552 | pPager->xBusyHandler = xBusyHandler; |
| 40525 | 40553 | pPager->pBusyHandlerArg = pBusyHandlerArg; |
| 40554 | + | |
| 40555 | + if( isOpen(pPager->fd) ){ | |
| 40556 | + void **ap = (void **)&pPager->xBusyHandler; | |
| 40557 | + assert( ((int(*)(void *))(ap[0]))==xBusyHandler ); | |
| 40558 | + assert( ap[1]==pBusyHandlerArg ); | |
| 40559 | + sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_BUSYHANDLER, (void *)ap); | |
| 40560 | + } | |
| 40526 | 40561 | } |
| 40527 | 40562 | |
| 40528 | 40563 | /* |
| 40529 | 40564 | ** Change the page size used by the Pager object. The new page size |
| 40530 | 40565 | ** is passed in *pPageSize. |
| @@ -46815,11 +46850,11 @@ | ||
| 46815 | 46850 | ** sector boundary is synced; the part of the last frame that extends |
| 46816 | 46851 | ** past the sector boundary is written after the sync. |
| 46817 | 46852 | */ |
| 46818 | 46853 | if( isCommit && (sync_flags & WAL_SYNC_TRANSACTIONS)!=0 ){ |
| 46819 | 46854 | if( pWal->padToSectorBoundary ){ |
| 46820 | - int sectorSize = sqlite3OsSectorSize(pWal->pWalFd); | |
| 46855 | + int sectorSize = sqlite3SectorSize(pWal->pWalFd); | |
| 46821 | 46856 | w.iSyncPoint = ((iOffset+sectorSize-1)/sectorSize)*sectorSize; |
| 46822 | 46857 | while( iOffset<w.iSyncPoint ){ |
| 46823 | 46858 | rc = walWriteOneFrame(&w, pLast, nTruncate, iOffset); |
| 46824 | 46859 | if( rc ) return rc; |
| 46825 | 46860 | iOffset += szFrame; |
| @@ -50227,10 +50262,28 @@ | ||
| 50227 | 50262 | ** Return the currently defined page size |
| 50228 | 50263 | */ |
| 50229 | 50264 | SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){ |
| 50230 | 50265 | return p->pBt->pageSize; |
| 50231 | 50266 | } |
| 50267 | + | |
| 50268 | +#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_DEBUG) | |
| 50269 | +/* | |
| 50270 | +** This function is similar to sqlite3BtreeGetReserve(), except that it | |
| 50271 | +** may only be called if it is guaranteed that the b-tree mutex is already | |
| 50272 | +** held. | |
| 50273 | +** | |
| 50274 | +** This is useful in one special case in the backup API code where it is | |
| 50275 | +** known that the shared b-tree mutex is held, but the mutex on the | |
| 50276 | +** database handle that owns *p is not. In this case if sqlite3BtreeEnter() | |
| 50277 | +** were to be called, it might collide with some other operation on the | |
| 50278 | +** database handle that owns *p, causing undefined behaviour. | |
| 50279 | +*/ | |
| 50280 | +SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){ | |
| 50281 | + assert( sqlite3_mutex_held(p->pBt->mutex) ); | |
| 50282 | + return p->pBt->pageSize - p->pBt->usableSize; | |
| 50283 | +} | |
| 50284 | +#endif /* SQLITE_HAS_CODEC || SQLITE_DEBUG */ | |
| 50232 | 50285 | |
| 50233 | 50286 | #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM) |
| 50234 | 50287 | /* |
| 50235 | 50288 | ** Return the number of bytes of space at the end of every page that |
| 50236 | 50289 | ** are intentually left unused. This is the "reserved" space that is |
| @@ -53284,11 +53337,11 @@ | ||
| 53284 | 53337 | btreeParseCellPtr(pPage, pCell, &info); |
| 53285 | 53338 | if( info.iOverflow==0 ){ |
| 53286 | 53339 | return SQLITE_OK; /* No overflow pages. Return without doing anything */ |
| 53287 | 53340 | } |
| 53288 | 53341 | if( pCell+info.iOverflow+3 > pPage->aData+pPage->maskPage ){ |
| 53289 | - return SQLITE_CORRUPT; /* Cell extends past end of page */ | |
| 53342 | + return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */ | |
| 53290 | 53343 | } |
| 53291 | 53344 | ovflPgno = get4byte(&pCell[info.iOverflow]); |
| 53292 | 53345 | assert( pBt->usableSize > 4 ); |
| 53293 | 53346 | ovflPageSize = pBt->usableSize - 4; |
| 53294 | 53347 | nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize; |
| @@ -53950,10 +54003,13 @@ | ||
| 53950 | 54003 | ** enough for all overflow cells. |
| 53951 | 54004 | ** |
| 53952 | 54005 | ** If aOvflSpace is set to a null pointer, this function returns |
| 53953 | 54006 | ** SQLITE_NOMEM. |
| 53954 | 54007 | */ |
| 54008 | +#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) | |
| 54009 | +#pragma optimize("", off) | |
| 54010 | +#endif | |
| 53955 | 54011 | static int balance_nonroot( |
| 53956 | 54012 | MemPage *pParent, /* Parent page of siblings being balanced */ |
| 53957 | 54013 | int iParentIdx, /* Index of "the page" in pParent */ |
| 53958 | 54014 | u8 *aOvflSpace, /* page-size bytes of space for parent ovfl */ |
| 53959 | 54015 | int isRoot, /* True if pParent is a root-page */ |
| @@ -54580,10 +54636,13 @@ | ||
| 54580 | 54636 | releasePage(apNew[i]); |
| 54581 | 54637 | } |
| 54582 | 54638 | |
| 54583 | 54639 | return rc; |
| 54584 | 54640 | } |
| 54641 | +#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) | |
| 54642 | +#pragma optimize("", on) | |
| 54643 | +#endif | |
| 54585 | 54644 | |
| 54586 | 54645 | |
| 54587 | 54646 | /* |
| 54588 | 54647 | ** This function is called when the root page of a b-tree structure is |
| 54589 | 54648 | ** overfull (has one or more overflow pages). |
| @@ -56558,17 +56617,20 @@ | ||
| 56558 | 56617 | const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc); |
| 56559 | 56618 | int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest); |
| 56560 | 56619 | const int nCopy = MIN(nSrcPgsz, nDestPgsz); |
| 56561 | 56620 | const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz; |
| 56562 | 56621 | #ifdef SQLITE_HAS_CODEC |
| 56563 | - int nSrcReserve = sqlite3BtreeGetReserve(p->pSrc); | |
| 56622 | + /* Use BtreeGetReserveNoMutex() for the source b-tree, as although it is | |
| 56623 | + ** guaranteed that the shared-mutex is held by this thread, handle | |
| 56624 | + ** p->pSrc may not actually be the owner. */ | |
| 56625 | + int nSrcReserve = sqlite3BtreeGetReserveNoMutex(p->pSrc); | |
| 56564 | 56626 | int nDestReserve = sqlite3BtreeGetReserve(p->pDest); |
| 56565 | 56627 | #endif |
| 56566 | - | |
| 56567 | 56628 | int rc = SQLITE_OK; |
| 56568 | 56629 | i64 iOff; |
| 56569 | 56630 | |
| 56631 | + assert( sqlite3BtreeGetReserveNoMutex(p->pSrc)>=0 ); | |
| 56570 | 56632 | assert( p->bDestLocked ); |
| 56571 | 56633 | assert( !isFatalError(p->rc) ); |
| 56572 | 56634 | assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ); |
| 56573 | 56635 | assert( zSrcData ); |
| 56574 | 56636 | |
| @@ -91600,10 +91662,11 @@ | ||
| 91600 | 91662 | */ |
| 91601 | 91663 | aFcntl[0] = 0; |
| 91602 | 91664 | aFcntl[1] = zLeft; |
| 91603 | 91665 | aFcntl[2] = zRight; |
| 91604 | 91666 | aFcntl[3] = 0; |
| 91667 | + db->busyHandler.nBusy = 0; | |
| 91605 | 91668 | rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); |
| 91606 | 91669 | if( rc==SQLITE_OK ){ |
| 91607 | 91670 | if( aFcntl[0] ){ |
| 91608 | 91671 | int mem = ++pParse->nMem; |
| 91609 | 91672 | sqlite3VdbeAddOp4(v, OP_String8, 0, mem, 0, aFcntl[0], 0); |
| @@ -102123,14 +102186,15 @@ | ||
| 102123 | 102186 | #define WHERE_NOT_FULLSCAN 0x100f3000 /* Does not do a full table scan */ |
| 102124 | 102187 | #define WHERE_IN_ABLE 0x000f1000 /* Able to support an IN operator */ |
| 102125 | 102188 | #define WHERE_TOP_LIMIT 0x00100000 /* x<EXPR or x<=EXPR constraint */ |
| 102126 | 102189 | #define WHERE_BTM_LIMIT 0x00200000 /* x>EXPR or x>=EXPR constraint */ |
| 102127 | 102190 | #define WHERE_BOTH_LIMIT 0x00300000 /* Both x>EXPR and x<EXPR */ |
| 102128 | -#define WHERE_IDX_ONLY 0x00800000 /* Use index only - omit table */ | |
| 102129 | -#define WHERE_ORDERBY 0x01000000 /* Output will appear in correct order */ | |
| 102130 | -#define WHERE_REVERSE 0x02000000 /* Scan in reverse order */ | |
| 102131 | -#define WHERE_UNIQUE 0x04000000 /* Selects no more than one row */ | |
| 102191 | +#define WHERE_IDX_ONLY 0x00400000 /* Use index only - omit table */ | |
| 102192 | +#define WHERE_ORDERED 0x00800000 /* Output will appear in correct order */ | |
| 102193 | +#define WHERE_REVERSE 0x01000000 /* Scan in reverse order */ | |
| 102194 | +#define WHERE_UNIQUE 0x02000000 /* Selects no more than one row */ | |
| 102195 | +#define WHERE_ALL_UNIQUE 0x04000000 /* This and all prior have one row */ | |
| 102132 | 102196 | #define WHERE_VIRTUALTABLE 0x08000000 /* Use virtual-table processing */ |
| 102133 | 102197 | #define WHERE_MULTI_OR 0x10000000 /* OR using multiple indices */ |
| 102134 | 102198 | #define WHERE_TEMP_INDEX 0x20000000 /* Uses an ephemeral index */ |
| 102135 | 102199 | #define WHERE_DISTINCT 0x40000000 /* Correct order for DISTINCT */ |
| 102136 | 102200 | #define WHERE_COVER_SCAN 0x80000000 /* Full scan of a covering index */ |
| @@ -102154,10 +102218,21 @@ | ||
| 102154 | 102218 | sqlite3_index_info **ppIdxInfo; /* Index information passed to xBestIndex */ |
| 102155 | 102219 | int i, n; /* Which loop is being coded; # of loops */ |
| 102156 | 102220 | WhereLevel *aLevel; /* Info about outer loops */ |
| 102157 | 102221 | WhereCost cost; /* Lowest cost query plan */ |
| 102158 | 102222 | }; |
| 102223 | + | |
| 102224 | +/* | |
| 102225 | +** Return TRUE if the probe cost is less than the baseline cost | |
| 102226 | +*/ | |
| 102227 | +static int compareCost(const WhereCost *pProbe, const WhereCost *pBaseline){ | |
| 102228 | + if( pProbe->rCost<pBaseline->rCost ) return 1; | |
| 102229 | + if( pProbe->rCost>pBaseline->rCost ) return 0; | |
| 102230 | + if( pProbe->plan.nOBSat>pBaseline->plan.nOBSat ) return 1; | |
| 102231 | + if( pProbe->plan.nRow<pBaseline->plan.nRow ) return 1; | |
| 102232 | + return 0; | |
| 102233 | +} | |
| 102159 | 102234 | |
| 102160 | 102235 | /* |
| 102161 | 102236 | ** Initialize a preallocated WhereClause structure. |
| 102162 | 102237 | */ |
| 102163 | 102238 | static void whereClauseInit( |
| @@ -103306,11 +103381,12 @@ | ||
| 103306 | 103381 | Table *pTab = pIdx->pTable; |
| 103307 | 103382 | int i; |
| 103308 | 103383 | if( pIdx->onError==OE_None ) return 0; |
| 103309 | 103384 | for(i=nSkip; i<pIdx->nColumn; i++){ |
| 103310 | 103385 | int j = pIdx->aiColumn[i]; |
| 103311 | - if( j>=0 && pTab->aCol[j].notNull==0 ) return 0; | |
| 103386 | + assert( j>=0 && j<pTab->nCol ); | |
| 103387 | + if( pTab->aCol[j].notNull==0 ) return 0; | |
| 103312 | 103388 | } |
| 103313 | 103389 | return 1; |
| 103314 | 103390 | } |
| 103315 | 103391 | |
| 103316 | 103392 | /* |
| @@ -103370,11 +103446,12 @@ | ||
| 103370 | 103446 | int nEqCol /* Number of index columns with == */ |
| 103371 | 103447 | ){ |
| 103372 | 103448 | Bitmask mask = 0; /* Mask of unaccounted for pDistinct exprs */ |
| 103373 | 103449 | int i; /* Iterator variable */ |
| 103374 | 103450 | |
| 103375 | - if( pIdx->zName==0 || pDistinct==0 || pDistinct->nExpr>=BMS ) return 0; | |
| 103451 | + assert( pDistinct!=0 ); | |
| 103452 | + if( pIdx->zName==0 || pDistinct->nExpr>=BMS ) return 0; | |
| 103376 | 103453 | testcase( pDistinct->nExpr==BMS-1 ); |
| 103377 | 103454 | |
| 103378 | 103455 | /* Loop through all the expressions in the distinct list. If any of them |
| 103379 | 103456 | ** are not simple column references, return early. Otherwise, test if the |
| 103380 | 103457 | ** WHERE clause contains a "col=X" clause. If it does, the expression |
| @@ -103472,170 +103549,10 @@ | ||
| 103472 | 103549 | } |
| 103473 | 103550 | |
| 103474 | 103551 | return 0; |
| 103475 | 103552 | } |
| 103476 | 103553 | |
| 103477 | -/* | |
| 103478 | -** This routine decides if pIdx can be used to satisfy the ORDER BY | |
| 103479 | -** clause, either in whole or in part. The return value is the | |
| 103480 | -** cumulative number of terms in the ORDER BY clause that are satisfied | |
| 103481 | -** by the index pIdx and other indices in outer loops. | |
| 103482 | -** | |
| 103483 | -** The table being queried has a cursor number of "base". pIdx is the | |
| 103484 | -** index that is postulated for use to access the table. | |
| 103485 | -** | |
| 103486 | -** nEqCol is the number of columns of pIdx that are used as equality | |
| 103487 | -** constraints and where the other side of the == is an ordered column | |
| 103488 | -** or constant. An "order column" in the previous sentence means a column | |
| 103489 | -** in table from an outer loop whose values will always appear in the | |
| 103490 | -** correct order due to othre index, or because the outer loop generates | |
| 103491 | -** a unique result. Any of the first nEqCol columns of pIdx may be missing | |
| 103492 | -** from the ORDER BY clause and the match can still be a success. | |
| 103493 | -** | |
| 103494 | -** The *pbRev value is set to 0 order 1 depending on whether or not | |
| 103495 | -** pIdx should be run in the forward order or in reverse order. | |
| 103496 | -*/ | |
| 103497 | -static int isSortingIndex( | |
| 103498 | - WhereBestIdx *p, /* Best index search context */ | |
| 103499 | - Index *pIdx, /* The index we are testing */ | |
| 103500 | - int base, /* Cursor number for the table to be sorted */ | |
| 103501 | - int nEqCol, /* Number of index columns with ordered == constraints */ | |
| 103502 | - int wsFlags, /* Index usages flags */ | |
| 103503 | - int bOuterRev, /* True if outer loops scan in reverse order */ | |
| 103504 | - int *pbRev /* Set to 1 for reverse-order scan of pIdx */ | |
| 103505 | -){ | |
| 103506 | - int i; /* Number of pIdx terms used */ | |
| 103507 | - int j; /* Number of ORDER BY terms satisfied */ | |
| 103508 | - int sortOrder = 0; /* XOR of index and ORDER BY sort direction */ | |
| 103509 | - int nTerm; /* Number of ORDER BY terms */ | |
| 103510 | - struct ExprList_item *pTerm; /* A term of the ORDER BY clause */ | |
| 103511 | - ExprList *pOrderBy; /* The ORDER BY clause */ | |
| 103512 | - Parse *pParse = p->pParse; /* Parser context */ | |
| 103513 | - sqlite3 *db = pParse->db; /* Database connection */ | |
| 103514 | - int nPriorSat; /* ORDER BY terms satisfied by outer loops */ | |
| 103515 | - int seenRowid = 0; /* True if an ORDER BY rowid term is seen */ | |
| 103516 | - int nEqOneRow; /* Idx columns that ref unique values */ | |
| 103517 | - | |
| 103518 | - if( p->i==0 ){ | |
| 103519 | - nPriorSat = 0; | |
| 103520 | - nEqOneRow = nEqCol; | |
| 103521 | - }else{ | |
| 103522 | - if( OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return 0; | |
| 103523 | - nPriorSat = p->aLevel[p->i-1].plan.nOBSat; | |
| 103524 | - sortOrder = bOuterRev; | |
| 103525 | - nEqOneRow = 0; | |
| 103526 | - } | |
| 103527 | - if( p->i>0 && nEqCol==0 /*&& !allOuterLoopsUnique(p)*/ ) return nPriorSat; | |
| 103528 | - pOrderBy = p->pOrderBy; | |
| 103529 | - if( !pOrderBy ) return nPriorSat; | |
| 103530 | - if( wsFlags & WHERE_COLUMN_IN ) return nPriorSat; | |
| 103531 | - if( pIdx->bUnordered ) return nPriorSat; | |
| 103532 | - nTerm = pOrderBy->nExpr; | |
| 103533 | - assert( nTerm>0 ); | |
| 103534 | - | |
| 103535 | - /* Argument pIdx must either point to a 'real' named index structure, | |
| 103536 | - ** or an index structure allocated on the stack by bestBtreeIndex() to | |
| 103537 | - ** represent the rowid index that is part of every table. */ | |
| 103538 | - assert( pIdx->zName || (pIdx->nColumn==1 && pIdx->aiColumn[0]==-1) ); | |
| 103539 | - | |
| 103540 | - /* Match terms of the ORDER BY clause against columns of | |
| 103541 | - ** the index. | |
| 103542 | - ** | |
| 103543 | - ** Note that indices have pIdx->nColumn regular columns plus | |
| 103544 | - ** one additional column containing the rowid. The rowid column | |
| 103545 | - ** of the index is also allowed to match against the ORDER BY | |
| 103546 | - ** clause. | |
| 103547 | - */ | |
| 103548 | - for(i=0,j=nPriorSat,pTerm=&pOrderBy->a[j]; j<nTerm && i<=pIdx->nColumn; i++){ | |
| 103549 | - Expr *pExpr; /* The expression of the ORDER BY pTerm */ | |
| 103550 | - CollSeq *pColl; /* The collating sequence of pExpr */ | |
| 103551 | - int termSortOrder; /* Sort order for this term */ | |
| 103552 | - int iColumn; /* The i-th column of the index. -1 for rowid */ | |
| 103553 | - int iSortOrder; /* 1 for DESC, 0 for ASC on the i-th index term */ | |
| 103554 | - const char *zColl; /* Name of the collating sequence for i-th index term */ | |
| 103555 | - | |
| 103556 | - pExpr = pTerm->pExpr; | |
| 103557 | - if( pExpr->op!=TK_COLUMN || pExpr->iTable!=base ){ | |
| 103558 | - /* Can not use an index sort on anything that is not a column in the | |
| 103559 | - ** left-most table of the FROM clause */ | |
| 103560 | - break; | |
| 103561 | - } | |
| 103562 | - pColl = sqlite3ExprCollSeq(pParse, pExpr); | |
| 103563 | - if( !pColl ){ | |
| 103564 | - pColl = db->pDfltColl; | |
| 103565 | - } | |
| 103566 | - if( pIdx->zName && i<pIdx->nColumn ){ | |
| 103567 | - iColumn = pIdx->aiColumn[i]; | |
| 103568 | - if( iColumn==pIdx->pTable->iPKey ){ | |
| 103569 | - iColumn = -1; | |
| 103570 | - } | |
| 103571 | - iSortOrder = pIdx->aSortOrder[i]; | |
| 103572 | - zColl = pIdx->azColl[i]; | |
| 103573 | - }else{ | |
| 103574 | - iColumn = -1; | |
| 103575 | - iSortOrder = 0; | |
| 103576 | - zColl = pColl->zName; | |
| 103577 | - } | |
| 103578 | - if( pExpr->iColumn!=iColumn || sqlite3StrICmp(pColl->zName, zColl) ){ | |
| 103579 | - /* Term j of the ORDER BY clause does not match column i of the index */ | |
| 103580 | - if( i<nEqCol ){ | |
| 103581 | - /* If an index column that is constrained by == fails to match an | |
| 103582 | - ** ORDER BY term, that is OK. Just ignore that column of the index | |
| 103583 | - */ | |
| 103584 | - continue; | |
| 103585 | - }else if( i==pIdx->nColumn ){ | |
| 103586 | - /* Index column i is the rowid. All other terms match. */ | |
| 103587 | - break; | |
| 103588 | - }else{ | |
| 103589 | - /* If an index column fails to match and is not constrained by == | |
| 103590 | - ** then the index cannot satisfy the ORDER BY constraint. | |
| 103591 | - */ | |
| 103592 | - return nPriorSat; | |
| 103593 | - } | |
| 103594 | - } | |
| 103595 | - assert( pIdx->aSortOrder!=0 || iColumn==-1 ); | |
| 103596 | - assert( pTerm->sortOrder==0 || pTerm->sortOrder==1 ); | |
| 103597 | - assert( iSortOrder==0 || iSortOrder==1 ); | |
| 103598 | - termSortOrder = iSortOrder ^ pTerm->sortOrder; | |
| 103599 | - if( i>nEqOneRow ){ | |
| 103600 | - if( termSortOrder!=sortOrder ){ | |
| 103601 | - /* Indices can only be used if all ORDER BY terms past the | |
| 103602 | - ** equality constraints are all either DESC or ASC. */ | |
| 103603 | - break; | |
| 103604 | - } | |
| 103605 | - }else{ | |
| 103606 | - sortOrder = termSortOrder; | |
| 103607 | - } | |
| 103608 | - j++; | |
| 103609 | - pTerm++; | |
| 103610 | - if( iColumn<0 ){ | |
| 103611 | - seenRowid = 1; | |
| 103612 | - break; | |
| 103613 | - } | |
| 103614 | - } | |
| 103615 | - *pbRev = sortOrder; | |
| 103616 | - | |
| 103617 | - /* If there was an "ORDER BY rowid" term that matched, or it is only | |
| 103618 | - ** possible for a single row from this table to match, then skip over | |
| 103619 | - ** any additional ORDER BY terms dealing with this table. | |
| 103620 | - */ | |
| 103621 | - if( seenRowid || | |
| 103622 | - ( (wsFlags & WHERE_COLUMN_NULL)==0 | |
| 103623 | - && i>=pIdx->nColumn | |
| 103624 | - && indexIsUniqueNotNull(pIdx, nEqCol) | |
| 103625 | - ) | |
| 103626 | - ){ | |
| 103627 | - /* Advance j over additional ORDER BY terms associated with base */ | |
| 103628 | - WhereMaskSet *pMS = p->pWC->pMaskSet; | |
| 103629 | - Bitmask m = ~getMask(pMS, base); | |
| 103630 | - while( j<nTerm && (exprTableUsage(pMS, pOrderBy->a[j].pExpr)&m)==0 ){ | |
| 103631 | - j++; | |
| 103632 | - } | |
| 103633 | - } | |
| 103634 | - return j; | |
| 103635 | -} | |
| 103636 | - | |
| 103637 | 103554 | /* |
| 103638 | 103555 | ** Prepare a crude estimate of the logarithm of the input value. |
| 103639 | 103556 | ** The results need not be exact. This is only used for estimating |
| 103640 | 103557 | ** the total cost of performing operations with O(logN) or O(NlogN) |
| 103641 | 103558 | ** complexity. Because N is just a guess, it is no great tragedy if |
| @@ -103785,10 +103702,11 @@ | ||
| 103785 | 103702 | WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow)); |
| 103786 | 103703 | if( rTotal<p->cost.rCost ){ |
| 103787 | 103704 | p->cost.rCost = rTotal; |
| 103788 | 103705 | p->cost.used = used; |
| 103789 | 103706 | p->cost.plan.nRow = nRow; |
| 103707 | + p->cost.plan.nOBSat = p->i ? p->aLevel[p->i-1].plan.nOBSat : 0; | |
| 103790 | 103708 | p->cost.plan.wsFlags = flags; |
| 103791 | 103709 | p->cost.plan.u.pTerm = pTerm; |
| 103792 | 103710 | } |
| 103793 | 103711 | } |
| 103794 | 103712 | } |
| @@ -104327,11 +104245,14 @@ | ||
| 104327 | 104245 | }else{ |
| 104328 | 104246 | p->cost.rCost = rCost; |
| 104329 | 104247 | } |
| 104330 | 104248 | p->cost.plan.u.pVtabIdx = pIdxInfo; |
| 104331 | 104249 | if( pIdxInfo->orderByConsumed ){ |
| 104332 | - p->cost.plan.wsFlags |= WHERE_ORDERBY; | |
| 104250 | + p->cost.plan.wsFlags |= WHERE_ORDERED; | |
| 104251 | + p->cost.plan.nOBSat = nOrderBy; | |
| 104252 | + }else{ | |
| 104253 | + p->cost.plan.nOBSat = p->i ? p->aLevel[p->i-1].plan.nOBSat : 0; | |
| 104333 | 104254 | } |
| 104334 | 104255 | p->cost.plan.nEq = 0; |
| 104335 | 104256 | pIdxInfo->nOrderBy = nOrderBy; |
| 104336 | 104257 | |
| 104337 | 104258 | /* Try to find a more efficient access pattern by using multiple indexes |
| @@ -104750,20 +104671,26 @@ | ||
| 104750 | 104671 | WhereLevel *pLevel = &p->aLevel[p->i-1]; |
| 104751 | 104672 | Index *pIdx; |
| 104752 | 104673 | u8 sortOrder; |
| 104753 | 104674 | for(i=p->i-1; i>=0; i--, pLevel--){ |
| 104754 | 104675 | if( pLevel->iTabCur!=iTab ) continue; |
| 104755 | - if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){ | |
| 104756 | - pIdx = pLevel->plan.u.pIdx; | |
| 104676 | + if( (pLevel->plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){ | |
| 104677 | + return 1; | |
| 104678 | + } | |
| 104679 | + if( (pLevel->plan.wsFlags & WHERE_ORDERED)==0 ){ | |
| 104680 | + return 0; | |
| 104681 | + } | |
| 104682 | + if( (pIdx = pLevel->plan.u.pIdx)!=0 ){ | |
| 104757 | 104683 | if( iCol<0 ){ |
| 104758 | 104684 | sortOrder = 0; |
| 104759 | 104685 | testcase( (pLevel->plan.wsFlags & WHERE_REVERSE)!=0 ); |
| 104760 | 104686 | }else{ |
| 104761 | - for(j=0; j<pIdx->nColumn; j++){ | |
| 104687 | + int n = pIdx->nColumn; | |
| 104688 | + for(j=0; j<n; j++){ | |
| 104762 | 104689 | if( iCol==pIdx->aiColumn[j] ) break; |
| 104763 | 104690 | } |
| 104764 | - if( j>=pIdx->nColumn ) return 0; | |
| 104691 | + if( j>=n ) return 0; | |
| 104765 | 104692 | sortOrder = pIdx->aSortOrder[j]; |
| 104766 | 104693 | testcase( (pLevel->plan.wsFlags & WHERE_REVERSE)!=0 ); |
| 104767 | 104694 | } |
| 104768 | 104695 | }else{ |
| 104769 | 104696 | if( iCol!=(-1) ) return 0; |
| @@ -104792,13 +104719,10 @@ | ||
| 104792 | 104719 | static int isOrderedTerm(WhereBestIdx *p, WhereTerm *pTerm, int *pbRev){ |
| 104793 | 104720 | Expr *pExpr = pTerm->pExpr; |
| 104794 | 104721 | assert( pExpr->op==TK_EQ ); |
| 104795 | 104722 | assert( pExpr->pLeft!=0 && pExpr->pLeft->op==TK_COLUMN ); |
| 104796 | 104723 | assert( pExpr->pRight!=0 ); |
| 104797 | - if( p->i==0 ){ | |
| 104798 | - return 1; /* All == are ordered in the outer loop */ | |
| 104799 | - } | |
| 104800 | 104724 | if( pTerm->prereqRight==0 ){ |
| 104801 | 104725 | return 1; /* RHS of the == is a constant */ |
| 104802 | 104726 | } |
| 104803 | 104727 | if( pExpr->pRight->op==TK_COLUMN |
| 104804 | 104728 | && isOrderedColumn(p, pExpr->pRight->iTable, pExpr->pRight->iColumn, pbRev) |
| @@ -104808,10 +104732,177 @@ | ||
| 104808 | 104732 | |
| 104809 | 104733 | /* If we cannot prove that the constraint is ordered, assume it is not */ |
| 104810 | 104734 | return 0; |
| 104811 | 104735 | } |
| 104812 | 104736 | |
| 104737 | +/* | |
| 104738 | +** This routine decides if pIdx can be used to satisfy the ORDER BY | |
| 104739 | +** clause, either in whole or in part. The return value is the | |
| 104740 | +** cumulative number of terms in the ORDER BY clause that are satisfied | |
| 104741 | +** by the index pIdx and other indices in outer loops. | |
| 104742 | +** | |
| 104743 | +** The table being queried has a cursor number of "base". pIdx is the | |
| 104744 | +** index that is postulated for use to access the table. | |
| 104745 | +** | |
| 104746 | +** nEqCol is the number of columns of pIdx that are used as equality | |
| 104747 | +** constraints and where the other side of the == is an ordered column | |
| 104748 | +** or constant. An "order column" in the previous sentence means a column | |
| 104749 | +** in table from an outer loop whose values will always appear in the | |
| 104750 | +** correct order due to othre index, or because the outer loop generates | |
| 104751 | +** a unique result. Any of the first nEqCol columns of pIdx may be missing | |
| 104752 | +** from the ORDER BY clause and the match can still be a success. | |
| 104753 | +** | |
| 104754 | +** The *pbRev value is set to 0 order 1 depending on whether or not | |
| 104755 | +** pIdx should be run in the forward order or in reverse order. | |
| 104756 | +*/ | |
| 104757 | +static int isSortingIndex( | |
| 104758 | + WhereBestIdx *p, /* Best index search context */ | |
| 104759 | + Index *pIdx, /* The index we are testing */ | |
| 104760 | + int base, /* Cursor number for the table to be sorted */ | |
| 104761 | + int nEqCol, /* Number of index columns with ordered == constraints */ | |
| 104762 | + int wsFlags, /* Index usages flags */ | |
| 104763 | + int bOuterRev, /* True if outer loops scan in reverse order */ | |
| 104764 | + int *pbRev /* Set to 1 for reverse-order scan of pIdx */ | |
| 104765 | +){ | |
| 104766 | + int i; /* Number of pIdx terms used */ | |
| 104767 | + int j; /* Number of ORDER BY terms satisfied */ | |
| 104768 | + int sortOrder = 0; /* XOR of index and ORDER BY sort direction */ | |
| 104769 | + int nTerm; /* Number of ORDER BY terms */ | |
| 104770 | + struct ExprList_item *pTerm; /* A term of the ORDER BY clause */ | |
| 104771 | + ExprList *pOrderBy; /* The ORDER BY clause */ | |
| 104772 | + Parse *pParse = p->pParse; /* Parser context */ | |
| 104773 | + sqlite3 *db = pParse->db; /* Database connection */ | |
| 104774 | + int nPriorSat; /* ORDER BY terms satisfied by outer loops */ | |
| 104775 | + int seenRowid = 0; /* True if an ORDER BY rowid term is seen */ | |
| 104776 | + int nEqOneRow; /* Idx columns that ref unique values */ | |
| 104777 | + | |
| 104778 | + if( p->i==0 ){ | |
| 104779 | + nPriorSat = 0; | |
| 104780 | + }else{ | |
| 104781 | + nPriorSat = p->aLevel[p->i-1].plan.nOBSat; | |
| 104782 | + if( OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return nPriorSat; | |
| 104783 | + } | |
| 104784 | + if( nEqCol==0 ){ | |
| 104785 | + if( p->i && (p->aLevel[p->i-1].plan.wsFlags & WHERE_ORDERED)==0 ){ | |
| 104786 | + return nPriorSat; | |
| 104787 | + } | |
| 104788 | + nEqOneRow = 0; | |
| 104789 | + }else if( p->i==0 || (p->aLevel[p->i-1].plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){ | |
| 104790 | + nEqOneRow = nEqCol; | |
| 104791 | + }else{ | |
| 104792 | + sortOrder = bOuterRev; | |
| 104793 | + nEqOneRow = -1; | |
| 104794 | + } | |
| 104795 | + pOrderBy = p->pOrderBy; | |
| 104796 | + assert( pOrderBy!=0 ); | |
| 104797 | + if( wsFlags & WHERE_COLUMN_IN ) return nPriorSat; | |
| 104798 | + if( pIdx->bUnordered ) return nPriorSat; | |
| 104799 | + nTerm = pOrderBy->nExpr; | |
| 104800 | + assert( nTerm>0 ); | |
| 104801 | + | |
| 104802 | + /* Argument pIdx must either point to a 'real' named index structure, | |
| 104803 | + ** or an index structure allocated on the stack by bestBtreeIndex() to | |
| 104804 | + ** represent the rowid index that is part of every table. */ | |
| 104805 | + assert( pIdx->zName || (pIdx->nColumn==1 && pIdx->aiColumn[0]==-1) ); | |
| 104806 | + | |
| 104807 | + /* Match terms of the ORDER BY clause against columns of | |
| 104808 | + ** the index. | |
| 104809 | + ** | |
| 104810 | + ** Note that indices have pIdx->nColumn regular columns plus | |
| 104811 | + ** one additional column containing the rowid. The rowid column | |
| 104812 | + ** of the index is also allowed to match against the ORDER BY | |
| 104813 | + ** clause. | |
| 104814 | + */ | |
| 104815 | + for(i=0,j=nPriorSat,pTerm=&pOrderBy->a[j]; j<nTerm; i++){ | |
| 104816 | + Expr *pExpr; /* The expression of the ORDER BY pTerm */ | |
| 104817 | + CollSeq *pColl; /* The collating sequence of pExpr */ | |
| 104818 | + int termSortOrder; /* Sort order for this term */ | |
| 104819 | + int iColumn; /* The i-th column of the index. -1 for rowid */ | |
| 104820 | + int iSortOrder; /* 1 for DESC, 0 for ASC on the i-th index term */ | |
| 104821 | + const char *zColl; /* Name of the collating sequence for i-th index term */ | |
| 104822 | + | |
| 104823 | + assert( i<=pIdx->nColumn ); | |
| 104824 | + pExpr = pTerm->pExpr; | |
| 104825 | + if( pExpr->op!=TK_COLUMN || pExpr->iTable!=base ){ | |
| 104826 | + /* Can not use an index sort on anything that is not a column in the | |
| 104827 | + ** left-most table of the FROM clause */ | |
| 104828 | + break; | |
| 104829 | + } | |
| 104830 | + pColl = sqlite3ExprCollSeq(pParse, pExpr); | |
| 104831 | + if( !pColl ){ | |
| 104832 | + pColl = db->pDfltColl; | |
| 104833 | + } | |
| 104834 | + if( pIdx->zName && i<pIdx->nColumn ){ | |
| 104835 | + iColumn = pIdx->aiColumn[i]; | |
| 104836 | + if( iColumn==pIdx->pTable->iPKey ){ | |
| 104837 | + iColumn = -1; | |
| 104838 | + } | |
| 104839 | + iSortOrder = pIdx->aSortOrder[i]; | |
| 104840 | + zColl = pIdx->azColl[i]; | |
| 104841 | + }else{ | |
| 104842 | + iColumn = -1; | |
| 104843 | + iSortOrder = 0; | |
| 104844 | + zColl = pColl->zName; | |
| 104845 | + } | |
| 104846 | + if( pExpr->iColumn!=iColumn || sqlite3StrICmp(pColl->zName, zColl) ){ | |
| 104847 | + /* Term j of the ORDER BY clause does not match column i of the index */ | |
| 104848 | + if( i<nEqCol ){ | |
| 104849 | + /* If an index column that is constrained by == fails to match an | |
| 104850 | + ** ORDER BY term, that is OK. Just ignore that column of the index | |
| 104851 | + */ | |
| 104852 | + continue; | |
| 104853 | + }else if( i==pIdx->nColumn ){ | |
| 104854 | + /* Index column i is the rowid. All other terms match. */ | |
| 104855 | + break; | |
| 104856 | + }else{ | |
| 104857 | + /* If an index column fails to match and is not constrained by == | |
| 104858 | + ** then the index cannot satisfy the ORDER BY constraint. | |
| 104859 | + */ | |
| 104860 | + return nPriorSat; | |
| 104861 | + } | |
| 104862 | + } | |
| 104863 | + assert( pIdx->aSortOrder!=0 || iColumn==-1 ); | |
| 104864 | + assert( pTerm->sortOrder==0 || pTerm->sortOrder==1 ); | |
| 104865 | + assert( iSortOrder==0 || iSortOrder==1 ); | |
| 104866 | + termSortOrder = iSortOrder ^ pTerm->sortOrder; | |
| 104867 | + if( i>nEqOneRow ){ | |
| 104868 | + if( termSortOrder!=sortOrder ){ | |
| 104869 | + /* Indices can only be used if all ORDER BY terms past the | |
| 104870 | + ** equality constraints have the correct DESC or ASC. */ | |
| 104871 | + break; | |
| 104872 | + } | |
| 104873 | + }else{ | |
| 104874 | + sortOrder = termSortOrder; | |
| 104875 | + } | |
| 104876 | + j++; | |
| 104877 | + pTerm++; | |
| 104878 | + if( iColumn<0 ){ | |
| 104879 | + seenRowid = 1; | |
| 104880 | + break; | |
| 104881 | + } | |
| 104882 | + } | |
| 104883 | + *pbRev = sortOrder; | |
| 104884 | + | |
| 104885 | + /* If there was an "ORDER BY rowid" term that matched, or it is only | |
| 104886 | + ** possible for a single row from this table to match, then skip over | |
| 104887 | + ** any additional ORDER BY terms dealing with this table. | |
| 104888 | + */ | |
| 104889 | + if( seenRowid || | |
| 104890 | + ( (wsFlags & WHERE_COLUMN_NULL)==0 | |
| 104891 | + && i>=pIdx->nColumn | |
| 104892 | + && indexIsUniqueNotNull(pIdx, nEqCol) | |
| 104893 | + ) | |
| 104894 | + ){ | |
| 104895 | + /* Advance j over additional ORDER BY terms associated with base */ | |
| 104896 | + WhereMaskSet *pMS = p->pWC->pMaskSet; | |
| 104897 | + Bitmask m = ~getMask(pMS, base); | |
| 104898 | + while( j<nTerm && (exprTableUsage(pMS, pOrderBy->a[j].pExpr)&m)==0 ){ | |
| 104899 | + j++; | |
| 104900 | + } | |
| 104901 | + } | |
| 104902 | + return j; | |
| 104903 | +} | |
| 104813 | 104904 | |
| 104814 | 104905 | /* |
| 104815 | 104906 | ** Find the best query plan for accessing a particular table. Write the |
| 104816 | 104907 | ** best query plan and its cost into the p->cost. |
| 104817 | 104908 | ** |
| @@ -104902,22 +104993,19 @@ | ||
| 104902 | 104993 | |
| 104903 | 104994 | /* Loop over all indices looking for the best one to use |
| 104904 | 104995 | */ |
| 104905 | 104996 | for(; pProbe; pIdx=pProbe=pProbe->pNext){ |
| 104906 | 104997 | const tRowcnt * const aiRowEst = pProbe->aiRowEst; |
| 104907 | - double cost; /* Cost of using pProbe */ | |
| 104908 | - double nRow; /* Estimated number of rows in result set */ | |
| 104998 | + WhereCost pc; /* Cost of using pProbe */ | |
| 104909 | 104999 | double log10N = (double)1; /* base-10 logarithm of nRow (inexact) */ |
| 104910 | 105000 | int bRev = 2; /* 0=forward scan. 1=reverse. 2=undecided */ |
| 104911 | - int wsFlags = 0; | |
| 104912 | - Bitmask used = 0; | |
| 104913 | 105001 | |
| 104914 | 105002 | /* The following variables are populated based on the properties of |
| 104915 | 105003 | ** index being evaluated. They are then used to determine the expected |
| 104916 | 105004 | ** cost and number of rows returned. |
| 104917 | 105005 | ** |
| 104918 | - ** nEq: | |
| 105006 | + ** pc.plan.nEq: | |
| 104919 | 105007 | ** Number of equality terms that can be implemented using the index. |
| 104920 | 105008 | ** In other words, the number of initial fields in the index that |
| 104921 | 105009 | ** are used in == or IN or NOT NULL constraints of the WHERE clause. |
| 104922 | 105010 | ** |
| 104923 | 105011 | ** nInMul: |
| @@ -104960,11 +105048,11 @@ | ||
| 104960 | 105048 | ** bSort: |
| 104961 | 105049 | ** Boolean. True if there is an ORDER BY clause that will require an |
| 104962 | 105050 | ** external sort (i.e. scanning the index being evaluated will not |
| 104963 | 105051 | ** correctly order records). |
| 104964 | 105052 | ** |
| 104965 | - ** bDistinct: | |
| 105053 | + ** bDist: | |
| 104966 | 105054 | ** Boolean. True if there is a DISTINCT clause that will require an |
| 104967 | 105055 | ** external btree. |
| 104968 | 105056 | ** |
| 104969 | 105057 | ** bLookup: |
| 104970 | 105058 | ** Boolean. True if a table lookup is required for each index entry |
| @@ -104979,132 +105067,148 @@ | ||
| 104979 | 105067 | ** both available in the index. |
| 104980 | 105068 | ** |
| 104981 | 105069 | ** SELECT a, b FROM tbl WHERE a = 1; |
| 104982 | 105070 | ** SELECT a, b, c FROM tbl WHERE a = 1; |
| 104983 | 105071 | */ |
| 104984 | - int nEq; /* Number of == or IN terms matching index */ | |
| 104985 | 105072 | int nOrdered; /* Number of ordered terms matching index */ |
| 104986 | 105073 | int bInEst = 0; /* True if "x IN (SELECT...)" seen */ |
| 104987 | 105074 | int nInMul = 1; /* Number of distinct equalities to lookup */ |
| 104988 | 105075 | double rangeDiv = (double)1; /* Estimated reduction in search space */ |
| 104989 | 105076 | int nBound = 0; /* Number of range constraints seen */ |
| 104990 | 105077 | int bSort; /* True if external sort required */ |
| 104991 | 105078 | int bDist; /* True if index cannot help with DISTINCT */ |
| 104992 | 105079 | int bLookup = 0; /* True if not a covering index */ |
| 104993 | - int nOBSat = 0; /* Number of ORDER BY terms satisfied */ | |
| 105080 | + int nPriorSat; /* ORDER BY terms satisfied by outer loops */ | |
| 104994 | 105081 | int nOrderBy; /* Number of ORDER BY terms */ |
| 104995 | 105082 | WhereTerm *pTerm; /* A single term of the WHERE clause */ |
| 104996 | 105083 | #ifdef SQLITE_ENABLE_STAT3 |
| 104997 | 105084 | WhereTerm *pFirstTerm = 0; /* First term matching the index */ |
| 104998 | 105085 | #endif |
| 104999 | 105086 | |
| 105087 | + memset(&pc, 0, sizeof(pc)); | |
| 105000 | 105088 | nOrderBy = p->pOrderBy ? p->pOrderBy->nExpr : 0; |
| 105001 | - bSort = nOrderBy>0 && (p->i==0 || p->aLevel[p->i-1].plan.nOBSat<nOrderBy); | |
| 105002 | - bDist = p->i==0 && p->pDistinct!=0; | |
| 105089 | + if( p->i ){ | |
| 105090 | + nPriorSat = pc.plan.nOBSat = p->aLevel[p->i-1].plan.nOBSat; | |
| 105091 | + bSort = nPriorSat<nOrderBy; | |
| 105092 | + bDist = 0; | |
| 105093 | + }else{ | |
| 105094 | + nPriorSat = pc.plan.nOBSat = 0; | |
| 105095 | + bSort = nOrderBy>0; | |
| 105096 | + bDist = p->pDistinct!=0; | |
| 105097 | + } | |
| 105003 | 105098 | |
| 105004 | - /* Determine the values of nEq and nInMul */ | |
| 105005 | - for(nEq=nOrdered=0; nEq<pProbe->nColumn; nEq++){ | |
| 105006 | - int j = pProbe->aiColumn[nEq]; | |
| 105099 | + /* Determine the values of pc.plan.nEq and nInMul */ | |
| 105100 | + for(pc.plan.nEq=nOrdered=0; pc.plan.nEq<pProbe->nColumn; pc.plan.nEq++){ | |
| 105101 | + int j = pProbe->aiColumn[pc.plan.nEq]; | |
| 105007 | 105102 | pTerm = findTerm(pWC, iCur, j, p->notReady, eqTermMask, pIdx); |
| 105008 | 105103 | if( pTerm==0 ) break; |
| 105009 | - wsFlags |= (WHERE_COLUMN_EQ|WHERE_ROWID_EQ); | |
| 105104 | + pc.plan.wsFlags |= (WHERE_COLUMN_EQ|WHERE_ROWID_EQ); | |
| 105010 | 105105 | testcase( pTerm->pWC!=pWC ); |
| 105011 | 105106 | if( pTerm->eOperator & WO_IN ){ |
| 105012 | 105107 | Expr *pExpr = pTerm->pExpr; |
| 105013 | - wsFlags |= WHERE_COLUMN_IN; | |
| 105108 | + pc.plan.wsFlags |= WHERE_COLUMN_IN; | |
| 105014 | 105109 | if( ExprHasProperty(pExpr, EP_xIsSelect) ){ |
| 105015 | 105110 | /* "x IN (SELECT ...)": Assume the SELECT returns 25 rows */ |
| 105016 | 105111 | nInMul *= 25; |
| 105017 | 105112 | bInEst = 1; |
| 105018 | 105113 | }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ |
| 105019 | 105114 | /* "x IN (value, value, ...)" */ |
| 105020 | 105115 | nInMul *= pExpr->x.pList->nExpr; |
| 105021 | 105116 | } |
| 105022 | 105117 | }else if( pTerm->eOperator & WO_ISNULL ){ |
| 105023 | - wsFlags |= WHERE_COLUMN_NULL; | |
| 105024 | - if( nEq==nOrdered ) nOrdered++; | |
| 105025 | - }else if( bSort && nEq==nOrdered && isOrderedTerm(p, pTerm, &bRev) ){ | |
| 105118 | + pc.plan.wsFlags |= WHERE_COLUMN_NULL; | |
| 105119 | + if( pc.plan.nEq==nOrdered ) nOrdered++; | |
| 105120 | + }else if( bSort && pc.plan.nEq==nOrdered | |
| 105121 | + && isOrderedTerm(p,pTerm,&bRev) ){ | |
| 105026 | 105122 | nOrdered++; |
| 105027 | 105123 | } |
| 105028 | 105124 | #ifdef SQLITE_ENABLE_STAT3 |
| 105029 | - if( nEq==0 && pProbe->aSample ) pFirstTerm = pTerm; | |
| 105125 | + if( pc.plan.nEq==0 && pProbe->aSample ) pFirstTerm = pTerm; | |
| 105030 | 105126 | #endif |
| 105031 | - used |= pTerm->prereqRight; | |
| 105127 | + pc.used |= pTerm->prereqRight; | |
| 105032 | 105128 | } |
| 105033 | 105129 | |
| 105034 | 105130 | /* If the index being considered is UNIQUE, and there is an equality |
| 105035 | 105131 | ** constraint for all columns in the index, then this search will find |
| 105036 | 105132 | ** at most a single row. In this case set the WHERE_UNIQUE flag to |
| 105037 | 105133 | ** indicate this to the caller. |
| 105038 | 105134 | ** |
| 105039 | 105135 | ** Otherwise, if the search may find more than one row, test to see if |
| 105040 | - ** there is a range constraint on indexed column (nEq+1) that can be | |
| 105136 | + ** there is a range constraint on indexed column (pc.plan.nEq+1) that can be | |
| 105041 | 105137 | ** optimized using the index. |
| 105042 | 105138 | */ |
| 105043 | - if( nEq==pProbe->nColumn && pProbe->onError!=OE_None ){ | |
| 105044 | - testcase( wsFlags & WHERE_COLUMN_IN ); | |
| 105045 | - testcase( wsFlags & WHERE_COLUMN_NULL ); | |
| 105046 | - if( (wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0 ){ | |
| 105047 | - wsFlags |= WHERE_UNIQUE; | |
| 105139 | + if( pc.plan.nEq==pProbe->nColumn && pProbe->onError!=OE_None ){ | |
| 105140 | + testcase( pc.plan.wsFlags & WHERE_COLUMN_IN ); | |
| 105141 | + testcase( pc.plan.wsFlags & WHERE_COLUMN_NULL ); | |
| 105142 | + if( (pc.plan.wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0 ){ | |
| 105143 | + pc.plan.wsFlags |= WHERE_UNIQUE; | |
| 105144 | + if( p->i==0 || (p->aLevel[p->i-1].plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){ | |
| 105145 | + pc.plan.wsFlags |= WHERE_ALL_UNIQUE; | |
| 105146 | + } | |
| 105048 | 105147 | } |
| 105049 | 105148 | }else if( pProbe->bUnordered==0 ){ |
| 105050 | - int j = (nEq==pProbe->nColumn ? -1 : pProbe->aiColumn[nEq]); | |
| 105149 | + int j; | |
| 105150 | + j = (pc.plan.nEq==pProbe->nColumn ? -1 : pProbe->aiColumn[pc.plan.nEq]); | |
| 105051 | 105151 | if( findTerm(pWC, iCur, j, p->notReady, WO_LT|WO_LE|WO_GT|WO_GE, pIdx) ){ |
| 105052 | 105152 | WhereTerm *pTop, *pBtm; |
| 105053 | 105153 | pTop = findTerm(pWC, iCur, j, p->notReady, WO_LT|WO_LE, pIdx); |
| 105054 | 105154 | pBtm = findTerm(pWC, iCur, j, p->notReady, WO_GT|WO_GE, pIdx); |
| 105055 | - whereRangeScanEst(pParse, pProbe, nEq, pBtm, pTop, &rangeDiv); | |
| 105155 | + whereRangeScanEst(pParse, pProbe, pc.plan.nEq, pBtm, pTop, &rangeDiv); | |
| 105056 | 105156 | if( pTop ){ |
| 105057 | 105157 | nBound = 1; |
| 105058 | - wsFlags |= WHERE_TOP_LIMIT; | |
| 105059 | - used |= pTop->prereqRight; | |
| 105158 | + pc.plan.wsFlags |= WHERE_TOP_LIMIT; | |
| 105159 | + pc.used |= pTop->prereqRight; | |
| 105060 | 105160 | testcase( pTop->pWC!=pWC ); |
| 105061 | 105161 | } |
| 105062 | 105162 | if( pBtm ){ |
| 105063 | 105163 | nBound++; |
| 105064 | - wsFlags |= WHERE_BTM_LIMIT; | |
| 105065 | - used |= pBtm->prereqRight; | |
| 105164 | + pc.plan.wsFlags |= WHERE_BTM_LIMIT; | |
| 105165 | + pc.used |= pBtm->prereqRight; | |
| 105066 | 105166 | testcase( pBtm->pWC!=pWC ); |
| 105067 | 105167 | } |
| 105068 | - wsFlags |= (WHERE_COLUMN_RANGE|WHERE_ROWID_RANGE); | |
| 105168 | + pc.plan.wsFlags |= (WHERE_COLUMN_RANGE|WHERE_ROWID_RANGE); | |
| 105069 | 105169 | } |
| 105070 | 105170 | } |
| 105071 | 105171 | |
| 105072 | 105172 | /* If there is an ORDER BY clause and the index being considered will |
| 105073 | 105173 | ** naturally scan rows in the required order, set the appropriate flags |
| 105074 | - ** in wsFlags. Otherwise, if there is an ORDER BY clause but the index | |
| 105075 | - ** will scan rows in a different order, set the bSort variable. */ | |
| 105174 | + ** in pc.plan.wsFlags. Otherwise, if there is an ORDER BY clause but | |
| 105175 | + ** the index will scan rows in a different order, set the bSort | |
| 105176 | + ** variable. */ | |
| 105076 | 105177 | assert( bRev>=0 && bRev<=2 ); |
| 105077 | 105178 | if( bSort ){ |
| 105078 | 105179 | testcase( bRev==0 ); |
| 105079 | 105180 | testcase( bRev==1 ); |
| 105080 | 105181 | testcase( bRev==2 ); |
| 105081 | - nOBSat = isSortingIndex(p, pProbe, iCur, nOrdered, | |
| 105082 | - wsFlags, bRev&1, &bRev); | |
| 105083 | - if( nOrderBy==nOBSat ){ | |
| 105182 | + pc.plan.nOBSat = isSortingIndex(p, pProbe, iCur, nOrdered, | |
| 105183 | + pc.plan.wsFlags, bRev&1, &bRev); | |
| 105184 | + if( nPriorSat<pc.plan.nOBSat || (pc.plan.wsFlags & WHERE_UNIQUE)!=0 ){ | |
| 105185 | + pc.plan.wsFlags |= WHERE_ORDERED; | |
| 105186 | + } | |
| 105187 | + if( nOrderBy==pc.plan.nOBSat ){ | |
| 105084 | 105188 | bSort = 0; |
| 105085 | - wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_ORDERBY; | |
| 105189 | + pc.plan.wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE; | |
| 105086 | 105190 | } |
| 105087 | - if( bRev & 1 ) wsFlags |= WHERE_REVERSE; | |
| 105191 | + if( bRev & 1 ) pc.plan.wsFlags |= WHERE_REVERSE; | |
| 105088 | 105192 | } |
| 105089 | 105193 | |
| 105090 | 105194 | /* If there is a DISTINCT qualifier and this index will scan rows in |
| 105091 | 105195 | ** order of the DISTINCT expressions, clear bDist and set the appropriate |
| 105092 | - ** flags in wsFlags. */ | |
| 105196 | + ** flags in pc.plan.wsFlags. */ | |
| 105093 | 105197 | if( bDist |
| 105094 | - && isDistinctIndex(pParse, pWC, pProbe, iCur, p->pDistinct, nEq) | |
| 105095 | - && (wsFlags & WHERE_COLUMN_IN)==0 | |
| 105198 | + && isDistinctIndex(pParse, pWC, pProbe, iCur, p->pDistinct, pc.plan.nEq) | |
| 105199 | + && (pc.plan.wsFlags & WHERE_COLUMN_IN)==0 | |
| 105096 | 105200 | ){ |
| 105097 | 105201 | bDist = 0; |
| 105098 | - wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_DISTINCT; | |
| 105202 | + pc.plan.wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_DISTINCT; | |
| 105099 | 105203 | } |
| 105100 | 105204 | |
| 105101 | 105205 | /* If currently calculating the cost of using an index (not the IPK |
| 105102 | 105206 | ** index), determine if all required column data may be obtained without |
| 105103 | 105207 | ** using the main table (i.e. if the index is a covering |
| 105104 | 105208 | ** index for this query). If it is, set the WHERE_IDX_ONLY flag in |
| 105105 | - ** wsFlags. Otherwise, set the bLookup variable to true. */ | |
| 105209 | + ** pc.plan.wsFlags. Otherwise, set the bLookup variable to true. */ | |
| 105106 | 105210 | if( pIdx ){ |
| 105107 | 105211 | Bitmask m = pSrc->colUsed; |
| 105108 | 105212 | int j; |
| 105109 | 105213 | for(j=0; j<pIdx->nColumn; j++){ |
| 105110 | 105214 | int x = pIdx->aiColumn[j]; |
| @@ -105111,51 +105215,54 @@ | ||
| 105111 | 105215 | if( x<BMS-1 ){ |
| 105112 | 105216 | m &= ~(((Bitmask)1)<<x); |
| 105113 | 105217 | } |
| 105114 | 105218 | } |
| 105115 | 105219 | if( m==0 ){ |
| 105116 | - wsFlags |= WHERE_IDX_ONLY; | |
| 105220 | + pc.plan.wsFlags |= WHERE_IDX_ONLY; | |
| 105117 | 105221 | }else{ |
| 105118 | 105222 | bLookup = 1; |
| 105119 | 105223 | } |
| 105120 | 105224 | } |
| 105121 | 105225 | |
| 105122 | 105226 | /* |
| 105123 | 105227 | ** Estimate the number of rows of output. For an "x IN (SELECT...)" |
| 105124 | 105228 | ** constraint, do not let the estimate exceed half the rows in the table. |
| 105125 | 105229 | */ |
| 105126 | - nRow = (double)(aiRowEst[nEq] * nInMul); | |
| 105127 | - if( bInEst && nRow*2>aiRowEst[0] ){ | |
| 105128 | - nRow = aiRowEst[0]/2; | |
| 105129 | - nInMul = (int)(nRow / aiRowEst[nEq]); | |
| 105230 | + pc.plan.nRow = (double)(aiRowEst[pc.plan.nEq] * nInMul); | |
| 105231 | + if( bInEst && pc.plan.nRow*2>aiRowEst[0] ){ | |
| 105232 | + pc.plan.nRow = aiRowEst[0]/2; | |
| 105233 | + nInMul = (int)(pc.plan.nRow / aiRowEst[pc.plan.nEq]); | |
| 105130 | 105234 | } |
| 105131 | 105235 | |
| 105132 | 105236 | #ifdef SQLITE_ENABLE_STAT3 |
| 105133 | 105237 | /* If the constraint is of the form x=VALUE or x IN (E1,E2,...) |
| 105134 | 105238 | ** and we do not think that values of x are unique and if histogram |
| 105135 | 105239 | ** data is available for column x, then it might be possible |
| 105136 | 105240 | ** to get a better estimate on the number of rows based on |
| 105137 | 105241 | ** VALUE and how common that value is according to the histogram. |
| 105138 | 105242 | */ |
| 105139 | - if( nRow>(double)1 && nEq==1 && pFirstTerm!=0 && aiRowEst[1]>1 ){ | |
| 105243 | + if( pc.plan.nRow>(double)1 && pc.plan.nEq==1 | |
| 105244 | + && pFirstTerm!=0 && aiRowEst[1]>1 ){ | |
| 105140 | 105245 | assert( (pFirstTerm->eOperator & (WO_EQ|WO_ISNULL|WO_IN))!=0 ); |
| 105141 | 105246 | if( pFirstTerm->eOperator & (WO_EQ|WO_ISNULL) ){ |
| 105142 | 105247 | testcase( pFirstTerm->eOperator==WO_EQ ); |
| 105143 | 105248 | testcase( pFirstTerm->eOperator==WO_ISNULL ); |
| 105144 | - whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, &nRow); | |
| 105249 | + whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, | |
| 105250 | + &pc.plan.nRow); | |
| 105145 | 105251 | }else if( bInEst==0 ){ |
| 105146 | 105252 | assert( pFirstTerm->eOperator==WO_IN ); |
| 105147 | - whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, &nRow); | |
| 105253 | + whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, | |
| 105254 | + &pc.plan.nRow); | |
| 105148 | 105255 | } |
| 105149 | 105256 | } |
| 105150 | 105257 | #endif /* SQLITE_ENABLE_STAT3 */ |
| 105151 | 105258 | |
| 105152 | 105259 | /* Adjust the number of output rows and downward to reflect rows |
| 105153 | 105260 | ** that are excluded by range constraints. |
| 105154 | 105261 | */ |
| 105155 | - nRow = nRow/rangeDiv; | |
| 105156 | - if( nRow<1 ) nRow = 1; | |
| 105262 | + pc.plan.nRow = pc.plan.nRow/rangeDiv; | |
| 105263 | + if( pc.plan.nRow<1 ) pc.plan.nRow = 1; | |
| 105157 | 105264 | |
| 105158 | 105265 | /* Experiments run on real SQLite databases show that the time needed |
| 105159 | 105266 | ** to do a binary search to locate a row in a table or index is roughly |
| 105160 | 105267 | ** log10(N) times the time to move from one row to the next row within |
| 105161 | 105268 | ** a table or index. The actual times can vary, with the size of |
| @@ -105166,57 +105273,58 @@ | ||
| 105166 | 105273 | ** The ANALYZE command and the sqlite_stat1 and sqlite_stat3 tables do |
| 105167 | 105274 | ** not give us data on the relative sizes of table and index records. |
| 105168 | 105275 | ** So this computation assumes table records are about twice as big |
| 105169 | 105276 | ** as index records |
| 105170 | 105277 | */ |
| 105171 | - if( (wsFlags&~WHERE_REVERSE)==WHERE_IDX_ONLY | |
| 105278 | + if( (pc.plan.wsFlags&~(WHERE_REVERSE|WHERE_ORDERED))==WHERE_IDX_ONLY | |
| 105172 | 105279 | && (pWC->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 |
| 105173 | 105280 | && sqlite3GlobalConfig.bUseCis |
| 105174 | 105281 | && OptimizationEnabled(pParse->db, SQLITE_CoverIdxScan) |
| 105175 | 105282 | ){ |
| 105176 | 105283 | /* This index is not useful for indexing, but it is a covering index. |
| 105177 | 105284 | ** A full-scan of the index might be a little faster than a full-scan |
| 105178 | 105285 | ** of the table, so give this case a cost slightly less than a table |
| 105179 | 105286 | ** scan. */ |
| 105180 | - cost = aiRowEst[0]*3 + pProbe->nColumn; | |
| 105181 | - wsFlags |= WHERE_COVER_SCAN|WHERE_COLUMN_RANGE; | |
| 105182 | - }else if( (wsFlags & WHERE_NOT_FULLSCAN)==0 ){ | |
| 105287 | + pc.rCost = aiRowEst[0]*3 + pProbe->nColumn; | |
| 105288 | + pc.plan.wsFlags |= WHERE_COVER_SCAN|WHERE_COLUMN_RANGE; | |
| 105289 | + }else if( (pc.plan.wsFlags & WHERE_NOT_FULLSCAN)==0 ){ | |
| 105183 | 105290 | /* The cost of a full table scan is a number of move operations equal |
| 105184 | 105291 | ** to the number of rows in the table. |
| 105185 | 105292 | ** |
| 105186 | 105293 | ** We add an additional 4x penalty to full table scans. This causes |
| 105187 | 105294 | ** the cost function to err on the side of choosing an index over |
| 105188 | 105295 | ** choosing a full scan. This 4x full-scan penalty is an arguable |
| 105189 | 105296 | ** decision and one which we expect to revisit in the future. But |
| 105190 | 105297 | ** it seems to be working well enough at the moment. |
| 105191 | 105298 | */ |
| 105192 | - cost = aiRowEst[0]*4; | |
| 105193 | - wsFlags &= ~WHERE_IDX_ONLY; | |
| 105299 | + pc.rCost = aiRowEst[0]*4; | |
| 105300 | + pc.plan.wsFlags &= ~WHERE_IDX_ONLY; | |
| 105301 | + if( pIdx ) pc.plan.wsFlags &= ~WHERE_ORDERED; | |
| 105194 | 105302 | }else{ |
| 105195 | 105303 | log10N = estLog(aiRowEst[0]); |
| 105196 | - cost = nRow; | |
| 105304 | + pc.rCost = pc.plan.nRow; | |
| 105197 | 105305 | if( pIdx ){ |
| 105198 | 105306 | if( bLookup ){ |
| 105199 | 105307 | /* For an index lookup followed by a table lookup: |
| 105200 | 105308 | ** nInMul index searches to find the start of each index range |
| 105201 | 105309 | ** + nRow steps through the index |
| 105202 | 105310 | ** + nRow table searches to lookup the table entry using the rowid |
| 105203 | 105311 | */ |
| 105204 | - cost += (nInMul + nRow)*log10N; | |
| 105312 | + pc.rCost += (nInMul + pc.plan.nRow)*log10N; | |
| 105205 | 105313 | }else{ |
| 105206 | 105314 | /* For a covering index: |
| 105207 | 105315 | ** nInMul index searches to find the initial entry |
| 105208 | 105316 | ** + nRow steps through the index |
| 105209 | 105317 | */ |
| 105210 | - cost += nInMul*log10N; | |
| 105318 | + pc.rCost += nInMul*log10N; | |
| 105211 | 105319 | } |
| 105212 | 105320 | }else{ |
| 105213 | 105321 | /* For a rowid primary key lookup: |
| 105214 | 105322 | ** nInMult table searches to find the initial entry for each range |
| 105215 | 105323 | ** + nRow steps through the table |
| 105216 | 105324 | */ |
| 105217 | - cost += nInMul*log10N; | |
| 105325 | + pc.rCost += nInMul*log10N; | |
| 105218 | 105326 | } |
| 105219 | 105327 | } |
| 105220 | 105328 | |
| 105221 | 105329 | /* Add in the estimated cost of sorting the result. Actual experimental |
| 105222 | 105330 | ** measurements of sorting performance in SQLite show that sorting time |
| @@ -105223,14 +105331,16 @@ | ||
| 105223 | 105331 | ** adds C*N*log10(N) to the cost, where N is the number of rows to be |
| 105224 | 105332 | ** sorted and C is a factor between 1.95 and 4.3. We will split the |
| 105225 | 105333 | ** difference and select C of 3.0. |
| 105226 | 105334 | */ |
| 105227 | 105335 | if( bSort ){ |
| 105228 | - cost += nRow*estLog(nRow*(nOrderBy - nOBSat)/nOrderBy)*3; | |
| 105336 | + double m = estLog(pc.plan.nRow*(nOrderBy - pc.plan.nOBSat)/nOrderBy); | |
| 105337 | + m *= (double)(pc.plan.nOBSat ? 2 : 3); | |
| 105338 | + pc.rCost += pc.plan.nRow*m; | |
| 105229 | 105339 | } |
| 105230 | 105340 | if( bDist ){ |
| 105231 | - cost += nRow*estLog(nRow)*3; | |
| 105341 | + pc.rCost += pc.plan.nRow*estLog(pc.plan.nRow)*3; | |
| 105232 | 105342 | } |
| 105233 | 105343 | |
| 105234 | 105344 | /**** Cost of using this index has now been computed ****/ |
| 105235 | 105345 | |
| 105236 | 105346 | /* If there are additional constraints on this table that cannot |
| @@ -105247,29 +105357,29 @@ | ||
| 105247 | 105357 | ** tables that are not in outer loops. If notReady is used here instead |
| 105248 | 105358 | ** of notValid, then a optimal index that depends on inner joins loops |
| 105249 | 105359 | ** might be selected even when there exists an optimal index that has |
| 105250 | 105360 | ** no such dependency. |
| 105251 | 105361 | */ |
| 105252 | - if( nRow>2 && cost<=p->cost.rCost ){ | |
| 105362 | + if( pc.plan.nRow>2 && pc.rCost<=p->cost.rCost ){ | |
| 105253 | 105363 | int k; /* Loop counter */ |
| 105254 | - int nSkipEq = nEq; /* Number of == constraints to skip */ | |
| 105364 | + int nSkipEq = pc.plan.nEq; /* Number of == constraints to skip */ | |
| 105255 | 105365 | int nSkipRange = nBound; /* Number of < constraints to skip */ |
| 105256 | 105366 | Bitmask thisTab; /* Bitmap for pSrc */ |
| 105257 | 105367 | |
| 105258 | 105368 | thisTab = getMask(pWC->pMaskSet, iCur); |
| 105259 | - for(pTerm=pWC->a, k=pWC->nTerm; nRow>2 && k; k--, pTerm++){ | |
| 105369 | + for(pTerm=pWC->a, k=pWC->nTerm; pc.plan.nRow>2 && k; k--, pTerm++){ | |
| 105260 | 105370 | if( pTerm->wtFlags & TERM_VIRTUAL ) continue; |
| 105261 | 105371 | if( (pTerm->prereqAll & p->notValid)!=thisTab ) continue; |
| 105262 | 105372 | if( pTerm->eOperator & (WO_EQ|WO_IN|WO_ISNULL) ){ |
| 105263 | 105373 | if( nSkipEq ){ |
| 105264 | - /* Ignore the first nEq equality matches since the index | |
| 105374 | + /* Ignore the first pc.plan.nEq equality matches since the index | |
| 105265 | 105375 | ** has already accounted for these */ |
| 105266 | 105376 | nSkipEq--; |
| 105267 | 105377 | }else{ |
| 105268 | 105378 | /* Assume each additional equality match reduces the result |
| 105269 | 105379 | ** set size by a factor of 10 */ |
| 105270 | - nRow /= 10; | |
| 105380 | + pc.plan.nRow /= 10; | |
| 105271 | 105381 | } |
| 105272 | 105382 | }else if( pTerm->eOperator & (WO_LT|WO_LE|WO_GT|WO_GE) ){ |
| 105273 | 105383 | if( nSkipRange ){ |
| 105274 | 105384 | /* Ignore the first nSkipRange range constraints since the index |
| 105275 | 105385 | ** has already accounted for these */ |
| @@ -105279,43 +105389,38 @@ | ||
| 105279 | 105389 | ** set size by a factor of 3. Indexed range constraints reduce |
| 105280 | 105390 | ** the search space by a larger factor: 4. We make indexed range |
| 105281 | 105391 | ** more selective intentionally because of the subjective |
| 105282 | 105392 | ** observation that indexed range constraints really are more |
| 105283 | 105393 | ** selective in practice, on average. */ |
| 105284 | - nRow /= 3; | |
| 105394 | + pc.plan.nRow /= 3; | |
| 105285 | 105395 | } |
| 105286 | 105396 | }else if( pTerm->eOperator!=WO_NOOP ){ |
| 105287 | 105397 | /* Any other expression lowers the output row count by half */ |
| 105288 | - nRow /= 2; | |
| 105398 | + pc.plan.nRow /= 2; | |
| 105289 | 105399 | } |
| 105290 | 105400 | } |
| 105291 | - if( nRow<2 ) nRow = 2; | |
| 105401 | + if( pc.plan.nRow<2 ) pc.plan.nRow = 2; | |
| 105292 | 105402 | } |
| 105293 | 105403 | |
| 105294 | 105404 | |
| 105295 | 105405 | WHERETRACE(( |
| 105296 | 105406 | "%s(%s):\n" |
| 105297 | 105407 | " nEq=%d nInMul=%d rangeDiv=%d bSort=%d bLookup=%d wsFlags=0x%08x\n" |
| 105298 | 105408 | " notReady=0x%llx log10N=%.1f nRow=%.1f cost=%.1f\n" |
| 105299 | 105409 | " used=0x%llx nOrdered=%d nOBSat=%d\n", |
| 105300 | 105410 | pSrc->pTab->zName, (pIdx ? pIdx->zName : "ipk"), |
| 105301 | - nEq, nInMul, (int)rangeDiv, bSort, bLookup, wsFlags, | |
| 105302 | - p->notReady, log10N, nRow, cost, used, nOrdered, nOBSat | |
| 105411 | + pc.plan.nEq, nInMul, (int)rangeDiv, bSort, bLookup, pc.plan.wsFlags, | |
| 105412 | + p->notReady, log10N, pc.plan.nRow, pc.rCost, pc.used, nOrdered, | |
| 105413 | + pc.plan.nOBSat | |
| 105303 | 105414 | )); |
| 105304 | 105415 | |
| 105305 | 105416 | /* If this index is the best we have seen so far, then record this |
| 105306 | - ** index and its cost in the pCost structure. | |
| 105417 | + ** index and its cost in the p->cost structure. | |
| 105307 | 105418 | */ |
| 105308 | - if( (!pIdx || wsFlags) | |
| 105309 | - && (cost<p->cost.rCost || (cost<=p->cost.rCost && nRow<p->cost.plan.nRow)) | |
| 105310 | - ){ | |
| 105311 | - p->cost.rCost = cost; | |
| 105312 | - p->cost.used = used; | |
| 105313 | - p->cost.plan.nRow = nRow; | |
| 105314 | - p->cost.plan.wsFlags = (wsFlags&wsFlagMask); | |
| 105315 | - p->cost.plan.nEq = nEq; | |
| 105316 | - p->cost.plan.nOBSat = nOBSat; | |
| 105419 | + if( (!pIdx || pc.plan.wsFlags) && compareCost(&pc, &p->cost) ){ | |
| 105420 | + p->cost = pc; | |
| 105421 | + p->cost.plan.wsFlags &= wsFlagMask; | |
| 105317 | 105422 | p->cost.plan.u.pIdx = pIdx; |
| 105318 | 105423 | } |
| 105319 | 105424 | |
| 105320 | 105425 | /* If there was an INDEXED BY clause, then only that one index is |
| 105321 | 105426 | ** considered. */ |
| @@ -105333,21 +105438,19 @@ | ||
| 105333 | 105438 | ** SQLite outputs rows in in the absence of an ORDER BY clause. */ |
| 105334 | 105439 | if( !p->pOrderBy && pParse->db->flags & SQLITE_ReverseOrder ){ |
| 105335 | 105440 | p->cost.plan.wsFlags |= WHERE_REVERSE; |
| 105336 | 105441 | } |
| 105337 | 105442 | |
| 105338 | - assert( p->pOrderBy || (p->cost.plan.wsFlags&WHERE_ORDERBY)==0 ); | |
| 105443 | + assert( p->pOrderBy || (p->cost.plan.wsFlags&WHERE_ORDERED)==0 ); | |
| 105339 | 105444 | assert( p->cost.plan.u.pIdx==0 || (p->cost.plan.wsFlags&WHERE_ROWID_EQ)==0 ); |
| 105340 | 105445 | assert( pSrc->pIndex==0 |
| 105341 | 105446 | || p->cost.plan.u.pIdx==0 |
| 105342 | 105447 | || p->cost.plan.u.pIdx==pSrc->pIndex |
| 105343 | 105448 | ); |
| 105344 | 105449 | |
| 105345 | - WHERETRACE(("best index is: %s\n", | |
| 105346 | - ((p->cost.plan.wsFlags & WHERE_NOT_FULLSCAN)==0 ? "none" : | |
| 105347 | - p->cost.plan.u.pIdx ? p->cost.plan.u.pIdx->zName : "ipk") | |
| 105348 | - )); | |
| 105450 | + WHERETRACE(("best index is: %s\n", | |
| 105451 | + p->cost.plan.u.pIdx ? p->cost.plan.u.pIdx->zName : "ipk")); | |
| 105349 | 105452 | |
| 105350 | 105453 | bestOrClauseIndex(p); |
| 105351 | 105454 | bestAutomaticIndex(p); |
| 105352 | 105455 | p->cost.plan.wsFlags |= eqTermMask; |
| 105353 | 105456 | } |
| @@ -106071,11 +106174,11 @@ | ||
| 106071 | 106174 | ** should not have a NULL value stored in 'x'. If column 'x' is |
| 106072 | 106175 | ** the first one after the nEq equality constraints in the index, |
| 106073 | 106176 | ** this requires some special handling. |
| 106074 | 106177 | */ |
| 106075 | 106178 | if( (wctrlFlags&WHERE_ORDERBY_MIN)!=0 |
| 106076 | - && (pLevel->plan.wsFlags&WHERE_ORDERBY) | |
| 106179 | + && (pLevel->plan.wsFlags&WHERE_ORDERED) | |
| 106077 | 106180 | && (pIdx->nColumn>nEq) |
| 106078 | 106181 | ){ |
| 106079 | 106182 | /* assert( pOrderBy->nExpr==1 ); */ |
| 106080 | 106183 | /* assert( pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq] ); */ |
| 106081 | 106184 | isMinQuery = 1; |
| @@ -106892,12 +106995,12 @@ | ||
| 106892 | 106995 | continue; |
| 106893 | 106996 | } |
| 106894 | 106997 | sWBI.notReady = (isOptimal ? m : sWBI.notValid); |
| 106895 | 106998 | if( sWBI.pSrc->pIndex==0 ) nUnconstrained++; |
| 106896 | 106999 | |
| 106897 | - WHERETRACE(("=== trying table %d with isOptimal=%d ===\n", | |
| 106898 | - j, isOptimal)); | |
| 107000 | + WHERETRACE(("=== trying table %d (%s) with isOptimal=%d ===\n", | |
| 107001 | + j, sWBI.pSrc->pTab->zName, isOptimal)); | |
| 106899 | 107002 | assert( sWBI.pSrc->pTab ); |
| 106900 | 107003 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 106901 | 107004 | if( IsVirtual(sWBI.pSrc->pTab) ){ |
| 106902 | 107005 | sWBI.ppIdxInfo = &pWInfo->a[j].pIdxInfo; |
| 106903 | 107006 | bestVirtualIndex(&sWBI); |
| @@ -106934,48 +107037,46 @@ | ||
| 106934 | 107037 | ** combination of INDEXED BY clauses are given. The error |
| 106935 | 107038 | ** will be detected and relayed back to the application later. |
| 106936 | 107039 | ** The NEVER() comes about because rule (2) above prevents |
| 106937 | 107040 | ** An indexable full-table-scan from reaching rule (3). |
| 106938 | 107041 | ** |
| 106939 | - ** (4) The plan cost must be lower than prior plans or else the | |
| 106940 | - ** cost must be the same and the number of rows must be lower. | |
| 107042 | + ** (4) The plan cost must be lower than prior plans, where "cost" | |
| 107043 | + ** is defined by the compareCost() function above. | |
| 106941 | 107044 | */ |
| 106942 | 107045 | if( (sWBI.cost.used&sWBI.notValid)==0 /* (1) */ |
| 106943 | 107046 | && (bestJ<0 || (notIndexed&m)!=0 /* (2) */ |
| 106944 | 107047 | || (bestPlan.plan.wsFlags & WHERE_NOT_FULLSCAN)==0 |
| 106945 | 107048 | || (sWBI.cost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0) |
| 106946 | 107049 | && (nUnconstrained==0 || sWBI.pSrc->pIndex==0 /* (3) */ |
| 106947 | 107050 | || NEVER((sWBI.cost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0)) |
| 106948 | - && (bestJ<0 || sWBI.cost.rCost<bestPlan.rCost /* (4) */ | |
| 106949 | - || (sWBI.cost.rCost<=bestPlan.rCost | |
| 106950 | - && sWBI.cost.plan.nRow<bestPlan.plan.nRow)) | |
| 107051 | + && (bestJ<0 || compareCost(&sWBI.cost, &bestPlan)) /* (4) */ | |
| 106951 | 107052 | ){ |
| 106952 | - WHERETRACE(("=== table %d is best so far" | |
| 106953 | - " with cost=%.1f, nRow=%.1f, nOBSat=%d\n", | |
| 106954 | - j, sWBI.cost.rCost, sWBI.cost.plan.nRow, | |
| 106955 | - sWBI.cost.plan.nOBSat)); | |
| 107053 | + WHERETRACE(("=== table %d (%s) is best so far\n" | |
| 107054 | + " cost=%.1f, nRow=%.1f, nOBSat=%d, wsFlags=%08x\n", | |
| 107055 | + j, sWBI.pSrc->pTab->zName, | |
| 107056 | + sWBI.cost.rCost, sWBI.cost.plan.nRow, | |
| 107057 | + sWBI.cost.plan.nOBSat, sWBI.cost.plan.wsFlags)); | |
| 106956 | 107058 | bestPlan = sWBI.cost; |
| 106957 | 107059 | bestJ = j; |
| 106958 | 107060 | } |
| 106959 | 107061 | if( doNotReorder ) break; |
| 106960 | 107062 | } |
| 106961 | 107063 | } |
| 106962 | 107064 | assert( bestJ>=0 ); |
| 106963 | 107065 | assert( sWBI.notValid & getMask(pMaskSet, pTabList->a[bestJ].iCursor) ); |
| 106964 | - WHERETRACE(("*** Optimizer selects table %d for loop %d with:\n" | |
| 106965 | - " cost=%.1f, nRow=%.1f, nOBSat=%d wsFlags=0x%08x\n", | |
| 106966 | - bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow, | |
| 107066 | + WHERETRACE(("*** Optimizer selects table %d (%s) for loop %d with:\n" | |
| 107067 | + " cost=%.1f, nRow=%.1f, nOBSat=%d, wsFlags=0x%08x\n", | |
| 107068 | + bestJ, pTabList->a[bestJ].pTab->zName, | |
| 107069 | + pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow, | |
| 106967 | 107070 | bestPlan.plan.nOBSat, bestPlan.plan.wsFlags)); |
| 106968 | - if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){ | |
| 106969 | - pWInfo->nOBSat = pOrderBy->nExpr; | |
| 106970 | - } | |
| 106971 | 107071 | if( (bestPlan.plan.wsFlags & WHERE_DISTINCT)!=0 ){ |
| 106972 | 107072 | assert( pWInfo->eDistinct==0 ); |
| 106973 | 107073 | pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; |
| 106974 | 107074 | } |
| 106975 | 107075 | andFlags &= bestPlan.plan.wsFlags; |
| 106976 | 107076 | pLevel->plan = bestPlan.plan; |
| 107077 | + pLevel->iTabCur = pTabList->a[bestJ].iCursor; | |
| 106977 | 107078 | testcase( bestPlan.plan.wsFlags & WHERE_INDEXED ); |
| 106978 | 107079 | testcase( bestPlan.plan.wsFlags & WHERE_TEMP_INDEX ); |
| 106979 | 107080 | if( bestPlan.plan.wsFlags & (WHERE_INDEXED|WHERE_TEMP_INDEX) ){ |
| 106980 | 107081 | if( (wctrlFlags & WHERE_ONETABLE_ONLY) |
| 106981 | 107082 | && (bestPlan.plan.wsFlags & WHERE_TEMP_INDEX)==0 |
| @@ -107013,15 +107114,22 @@ | ||
| 107013 | 107114 | } |
| 107014 | 107115 | WHERETRACE(("*** Optimizer Finished ***\n")); |
| 107015 | 107116 | if( pParse->nErr || db->mallocFailed ){ |
| 107016 | 107117 | goto whereBeginError; |
| 107017 | 107118 | } |
| 107119 | + if( nTabList ){ | |
| 107120 | + pLevel--; | |
| 107121 | + pWInfo->nOBSat = pLevel->plan.nOBSat; | |
| 107122 | + }else{ | |
| 107123 | + pWInfo->nOBSat = 0; | |
| 107124 | + } | |
| 107018 | 107125 | |
| 107019 | 107126 | /* If the total query only selects a single row, then the ORDER BY |
| 107020 | 107127 | ** clause is irrelevant. |
| 107021 | 107128 | */ |
| 107022 | 107129 | if( (andFlags & WHERE_UNIQUE)!=0 && pOrderBy ){ |
| 107130 | + assert( nTabList==0 || (pLevel->plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ); | |
| 107023 | 107131 | pWInfo->nOBSat = pOrderBy->nExpr; |
| 107024 | 107132 | } |
| 107025 | 107133 | |
| 107026 | 107134 | /* If the caller is an UPDATE or DELETE statement that is requesting |
| 107027 | 107135 | ** to use a one-pass algorithm, determine if this is appropriate. |
| @@ -107045,11 +107153,10 @@ | ||
| 107045 | 107153 | int iDb; /* Index of database containing table/index */ |
| 107046 | 107154 | struct SrcList_item *pTabItem; |
| 107047 | 107155 | |
| 107048 | 107156 | pTabItem = &pTabList->a[pLevel->iFrom]; |
| 107049 | 107157 | pTab = pTabItem->pTab; |
| 107050 | - pLevel->iTabCur = pTabItem->iCursor; | |
| 107051 | 107158 | pWInfo->nRowOut *= pLevel->plan.nRow; |
| 107052 | 107159 | iDb = sqlite3SchemaToIndex(db, pTab->pSchema); |
| 107053 | 107160 | if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ |
| 107054 | 107161 | /* Do nothing */ |
| 107055 | 107162 | }else |
| @@ -114978,11 +115085,11 @@ | ||
| 114978 | 115085 | ** with various optimizations disabled to verify that the same answer |
| 114979 | 115086 | ** is obtained in every case. |
| 114980 | 115087 | */ |
| 114981 | 115088 | case SQLITE_TESTCTRL_OPTIMIZATIONS: { |
| 114982 | 115089 | sqlite3 *db = va_arg(ap, sqlite3*); |
| 114983 | - db->dbOptFlags = (u16)(va_arg(ap, int) & 0xffff); | |
| 115090 | + db->dbOptFlags = (u8)(va_arg(ap, int) & 0xff); | |
| 114984 | 115091 | break; |
| 114985 | 115092 | } |
| 114986 | 115093 | |
| 114987 | 115094 | #ifdef SQLITE_N_KEYWORD |
| 114988 | 115095 | /* sqlite3_test_control(SQLITE_TESTCTRL_ISKEYWORD, const char *zWord) |
| @@ -135232,11 +135339,11 @@ | ||
| 135232 | 135339 | /* |
| 135233 | 135340 | ** Remove the entry with rowid=iDelete from the r-tree structure. |
| 135234 | 135341 | */ |
| 135235 | 135342 | static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ |
| 135236 | 135343 | int rc; /* Return code */ |
| 135237 | - RtreeNode *pLeaf; /* Leaf node containing record iDelete */ | |
| 135344 | + RtreeNode *pLeaf = 0; /* Leaf node containing record iDelete */ | |
| 135238 | 135345 | int iCell; /* Index of iDelete cell in pLeaf */ |
| 135239 | 135346 | RtreeNode *pRoot; /* Root node of rtree structure */ |
| 135240 | 135347 | |
| 135241 | 135348 | |
| 135242 | 135349 | /* Obtain a reference to the root node to initialise Rtree.iDepth */ |
| @@ -135435,11 +135542,11 @@ | ||
| 135435 | 135542 | ** (azData[2]..azData[argc-1]) contain a new record to insert into |
| 135436 | 135543 | ** the r-tree structure. |
| 135437 | 135544 | */ |
| 135438 | 135545 | if( rc==SQLITE_OK && nData>1 ){ |
| 135439 | 135546 | /* Insert the new record into the r-tree */ |
| 135440 | - RtreeNode *pLeaf; | |
| 135547 | + RtreeNode *pLeaf = 0; | |
| 135441 | 135548 | |
| 135442 | 135549 | /* Figure out the rowid of the new row. */ |
| 135443 | 135550 | if( bHaveRowid==0 ){ |
| 135444 | 135551 | rc = newRowid(pRtree, &cell.iRowid); |
| 135445 | 135552 | } |
| 135446 | 135553 |
| --- src/sqlite3.c | |
| +++ src/sqlite3.c | |
| @@ -673,11 +673,11 @@ | |
| 673 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 674 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 675 | */ |
| 676 | #define SQLITE_VERSION "3.7.15" |
| 677 | #define SQLITE_VERSION_NUMBER 3007015 |
| 678 | #define SQLITE_SOURCE_ID "2012-09-28 00:44:28 1e874629d7cf568368b912b295bd3001147d0b52" |
| 679 | |
| 680 | /* |
| 681 | ** CAPI3REF: Run-Time Library Version Numbers |
| 682 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 683 | ** |
| @@ -1421,10 +1421,21 @@ | |
| 1421 | ** that the VFS encountered an error while handling the [PRAGMA] and the |
| 1422 | ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] |
| 1423 | ** file control occurs at the beginning of pragma statement analysis and so |
| 1424 | ** it is able to override built-in [PRAGMA] statements. |
| 1425 | ** </ul> |
| 1426 | */ |
| 1427 | #define SQLITE_FCNTL_LOCKSTATE 1 |
| 1428 | #define SQLITE_GET_LOCKPROXYFILE 2 |
| 1429 | #define SQLITE_SET_LOCKPROXYFILE 3 |
| 1430 | #define SQLITE_LAST_ERRNO 4 |
| @@ -1436,10 +1447,11 @@ | |
| 1436 | #define SQLITE_FCNTL_PERSIST_WAL 10 |
| 1437 | #define SQLITE_FCNTL_OVERWRITE 11 |
| 1438 | #define SQLITE_FCNTL_VFSNAME 12 |
| 1439 | #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 |
| 1440 | #define SQLITE_FCNTL_PRAGMA 14 |
| 1441 | |
| 1442 | /* |
| 1443 | ** CAPI3REF: Mutex Handle |
| 1444 | ** |
| 1445 | ** The mutex module within SQLite defines [sqlite3_mutex] to be an |
| @@ -8381,10 +8393,13 @@ | |
| 8381 | SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); |
| 8382 | SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); |
| 8383 | SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree*); |
| 8384 | SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int); |
| 8385 | SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*); |
| 8386 | SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); |
| 8387 | SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); |
| 8388 | SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int); |
| 8389 | SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); |
| 8390 | SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int); |
| @@ -9150,10 +9165,11 @@ | |
| 9150 | SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); |
| 9151 | SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); |
| 9152 | SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); |
| 9153 | SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); |
| 9154 | SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *); |
| 9155 | |
| 9156 | /* Functions used to truncate the database file. */ |
| 9157 | SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); |
| 9158 | |
| 9159 | #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) |
| @@ -25825,10 +25841,12 @@ | |
| 25825 | int prior = 0; |
| 25826 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) |
| 25827 | i64 newOffset; |
| 25828 | #endif |
| 25829 | TIMER_START; |
| 25830 | do{ |
| 25831 | #if defined(USE_PREAD) |
| 25832 | got = osPread(id->h, pBuf, cnt, offset); |
| 25833 | SimulateIOError( got = -1 ); |
| 25834 | #elif defined(USE_PREAD64) |
| @@ -25914,10 +25932,12 @@ | |
| 25914 | static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ |
| 25915 | int got; |
| 25916 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) |
| 25917 | i64 newOffset; |
| 25918 | #endif |
| 25919 | TIMER_START; |
| 25920 | #if defined(USE_PREAD) |
| 25921 | do{ got = osPwrite(id->h, pBuf, cnt, offset); }while( got<0 && errno==EINTR ); |
| 25922 | #elif defined(USE_PREAD64) |
| 25923 | do{ got = osPwrite64(id->h, pBuf, cnt, offset);}while( got<0 && errno==EINTR); |
| @@ -39558,10 +39578,25 @@ | |
| 39558 | } |
| 39559 | } |
| 39560 | } |
| 39561 | return rc; |
| 39562 | } |
| 39563 | |
| 39564 | /* |
| 39565 | ** Set the value of the Pager.sectorSize variable for the given |
| 39566 | ** pager based on the value returned by the xSectorSize method |
| 39567 | ** of the open database file. The sector size will be used used |
| @@ -39594,18 +39629,11 @@ | |
| 39594 | /* Sector size doesn't matter for temporary files. Also, the file |
| 39595 | ** may not have been opened yet, in which case the OsSectorSize() |
| 39596 | ** call will segfault. */ |
| 39597 | pPager->sectorSize = 512; |
| 39598 | }else{ |
| 39599 | pPager->sectorSize = sqlite3OsSectorSize(pPager->fd); |
| 39600 | if( pPager->sectorSize<32 ){ |
| 39601 | pPager->sectorSize = 512; |
| 39602 | } |
| 39603 | if( pPager->sectorSize>MAX_SECTOR_SIZE ){ |
| 39604 | assert( MAX_SECTOR_SIZE>=512 ); |
| 39605 | pPager->sectorSize = MAX_SECTOR_SIZE; |
| 39606 | } |
| 39607 | } |
| 39608 | } |
| 39609 | |
| 39610 | /* |
| 39611 | ** Playback the journal and thus restore the database file to |
| @@ -40518,13 +40546,20 @@ | |
| 40518 | */ |
| 40519 | SQLITE_PRIVATE void sqlite3PagerSetBusyhandler( |
| 40520 | Pager *pPager, /* Pager object */ |
| 40521 | int (*xBusyHandler)(void *), /* Pointer to busy-handler function */ |
| 40522 | void *pBusyHandlerArg /* Argument to pass to xBusyHandler */ |
| 40523 | ){ |
| 40524 | pPager->xBusyHandler = xBusyHandler; |
| 40525 | pPager->pBusyHandlerArg = pBusyHandlerArg; |
| 40526 | } |
| 40527 | |
| 40528 | /* |
| 40529 | ** Change the page size used by the Pager object. The new page size |
| 40530 | ** is passed in *pPageSize. |
| @@ -46815,11 +46850,11 @@ | |
| 46815 | ** sector boundary is synced; the part of the last frame that extends |
| 46816 | ** past the sector boundary is written after the sync. |
| 46817 | */ |
| 46818 | if( isCommit && (sync_flags & WAL_SYNC_TRANSACTIONS)!=0 ){ |
| 46819 | if( pWal->padToSectorBoundary ){ |
| 46820 | int sectorSize = sqlite3OsSectorSize(pWal->pWalFd); |
| 46821 | w.iSyncPoint = ((iOffset+sectorSize-1)/sectorSize)*sectorSize; |
| 46822 | while( iOffset<w.iSyncPoint ){ |
| 46823 | rc = walWriteOneFrame(&w, pLast, nTruncate, iOffset); |
| 46824 | if( rc ) return rc; |
| 46825 | iOffset += szFrame; |
| @@ -50227,10 +50262,28 @@ | |
| 50227 | ** Return the currently defined page size |
| 50228 | */ |
| 50229 | SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){ |
| 50230 | return p->pBt->pageSize; |
| 50231 | } |
| 50232 | |
| 50233 | #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM) |
| 50234 | /* |
| 50235 | ** Return the number of bytes of space at the end of every page that |
| 50236 | ** are intentually left unused. This is the "reserved" space that is |
| @@ -53284,11 +53337,11 @@ | |
| 53284 | btreeParseCellPtr(pPage, pCell, &info); |
| 53285 | if( info.iOverflow==0 ){ |
| 53286 | return SQLITE_OK; /* No overflow pages. Return without doing anything */ |
| 53287 | } |
| 53288 | if( pCell+info.iOverflow+3 > pPage->aData+pPage->maskPage ){ |
| 53289 | return SQLITE_CORRUPT; /* Cell extends past end of page */ |
| 53290 | } |
| 53291 | ovflPgno = get4byte(&pCell[info.iOverflow]); |
| 53292 | assert( pBt->usableSize > 4 ); |
| 53293 | ovflPageSize = pBt->usableSize - 4; |
| 53294 | nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize; |
| @@ -53950,10 +54003,13 @@ | |
| 53950 | ** enough for all overflow cells. |
| 53951 | ** |
| 53952 | ** If aOvflSpace is set to a null pointer, this function returns |
| 53953 | ** SQLITE_NOMEM. |
| 53954 | */ |
| 53955 | static int balance_nonroot( |
| 53956 | MemPage *pParent, /* Parent page of siblings being balanced */ |
| 53957 | int iParentIdx, /* Index of "the page" in pParent */ |
| 53958 | u8 *aOvflSpace, /* page-size bytes of space for parent ovfl */ |
| 53959 | int isRoot, /* True if pParent is a root-page */ |
| @@ -54580,10 +54636,13 @@ | |
| 54580 | releasePage(apNew[i]); |
| 54581 | } |
| 54582 | |
| 54583 | return rc; |
| 54584 | } |
| 54585 | |
| 54586 | |
| 54587 | /* |
| 54588 | ** This function is called when the root page of a b-tree structure is |
| 54589 | ** overfull (has one or more overflow pages). |
| @@ -56558,17 +56617,20 @@ | |
| 56558 | const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc); |
| 56559 | int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest); |
| 56560 | const int nCopy = MIN(nSrcPgsz, nDestPgsz); |
| 56561 | const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz; |
| 56562 | #ifdef SQLITE_HAS_CODEC |
| 56563 | int nSrcReserve = sqlite3BtreeGetReserve(p->pSrc); |
| 56564 | int nDestReserve = sqlite3BtreeGetReserve(p->pDest); |
| 56565 | #endif |
| 56566 | |
| 56567 | int rc = SQLITE_OK; |
| 56568 | i64 iOff; |
| 56569 | |
| 56570 | assert( p->bDestLocked ); |
| 56571 | assert( !isFatalError(p->rc) ); |
| 56572 | assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ); |
| 56573 | assert( zSrcData ); |
| 56574 | |
| @@ -91600,10 +91662,11 @@ | |
| 91600 | */ |
| 91601 | aFcntl[0] = 0; |
| 91602 | aFcntl[1] = zLeft; |
| 91603 | aFcntl[2] = zRight; |
| 91604 | aFcntl[3] = 0; |
| 91605 | rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); |
| 91606 | if( rc==SQLITE_OK ){ |
| 91607 | if( aFcntl[0] ){ |
| 91608 | int mem = ++pParse->nMem; |
| 91609 | sqlite3VdbeAddOp4(v, OP_String8, 0, mem, 0, aFcntl[0], 0); |
| @@ -102123,14 +102186,15 @@ | |
| 102123 | #define WHERE_NOT_FULLSCAN 0x100f3000 /* Does not do a full table scan */ |
| 102124 | #define WHERE_IN_ABLE 0x000f1000 /* Able to support an IN operator */ |
| 102125 | #define WHERE_TOP_LIMIT 0x00100000 /* x<EXPR or x<=EXPR constraint */ |
| 102126 | #define WHERE_BTM_LIMIT 0x00200000 /* x>EXPR or x>=EXPR constraint */ |
| 102127 | #define WHERE_BOTH_LIMIT 0x00300000 /* Both x>EXPR and x<EXPR */ |
| 102128 | #define WHERE_IDX_ONLY 0x00800000 /* Use index only - omit table */ |
| 102129 | #define WHERE_ORDERBY 0x01000000 /* Output will appear in correct order */ |
| 102130 | #define WHERE_REVERSE 0x02000000 /* Scan in reverse order */ |
| 102131 | #define WHERE_UNIQUE 0x04000000 /* Selects no more than one row */ |
| 102132 | #define WHERE_VIRTUALTABLE 0x08000000 /* Use virtual-table processing */ |
| 102133 | #define WHERE_MULTI_OR 0x10000000 /* OR using multiple indices */ |
| 102134 | #define WHERE_TEMP_INDEX 0x20000000 /* Uses an ephemeral index */ |
| 102135 | #define WHERE_DISTINCT 0x40000000 /* Correct order for DISTINCT */ |
| 102136 | #define WHERE_COVER_SCAN 0x80000000 /* Full scan of a covering index */ |
| @@ -102154,10 +102218,21 @@ | |
| 102154 | sqlite3_index_info **ppIdxInfo; /* Index information passed to xBestIndex */ |
| 102155 | int i, n; /* Which loop is being coded; # of loops */ |
| 102156 | WhereLevel *aLevel; /* Info about outer loops */ |
| 102157 | WhereCost cost; /* Lowest cost query plan */ |
| 102158 | }; |
| 102159 | |
| 102160 | /* |
| 102161 | ** Initialize a preallocated WhereClause structure. |
| 102162 | */ |
| 102163 | static void whereClauseInit( |
| @@ -103306,11 +103381,12 @@ | |
| 103306 | Table *pTab = pIdx->pTable; |
| 103307 | int i; |
| 103308 | if( pIdx->onError==OE_None ) return 0; |
| 103309 | for(i=nSkip; i<pIdx->nColumn; i++){ |
| 103310 | int j = pIdx->aiColumn[i]; |
| 103311 | if( j>=0 && pTab->aCol[j].notNull==0 ) return 0; |
| 103312 | } |
| 103313 | return 1; |
| 103314 | } |
| 103315 | |
| 103316 | /* |
| @@ -103370,11 +103446,12 @@ | |
| 103370 | int nEqCol /* Number of index columns with == */ |
| 103371 | ){ |
| 103372 | Bitmask mask = 0; /* Mask of unaccounted for pDistinct exprs */ |
| 103373 | int i; /* Iterator variable */ |
| 103374 | |
| 103375 | if( pIdx->zName==0 || pDistinct==0 || pDistinct->nExpr>=BMS ) return 0; |
| 103376 | testcase( pDistinct->nExpr==BMS-1 ); |
| 103377 | |
| 103378 | /* Loop through all the expressions in the distinct list. If any of them |
| 103379 | ** are not simple column references, return early. Otherwise, test if the |
| 103380 | ** WHERE clause contains a "col=X" clause. If it does, the expression |
| @@ -103472,170 +103549,10 @@ | |
| 103472 | } |
| 103473 | |
| 103474 | return 0; |
| 103475 | } |
| 103476 | |
| 103477 | /* |
| 103478 | ** This routine decides if pIdx can be used to satisfy the ORDER BY |
| 103479 | ** clause, either in whole or in part. The return value is the |
| 103480 | ** cumulative number of terms in the ORDER BY clause that are satisfied |
| 103481 | ** by the index pIdx and other indices in outer loops. |
| 103482 | ** |
| 103483 | ** The table being queried has a cursor number of "base". pIdx is the |
| 103484 | ** index that is postulated for use to access the table. |
| 103485 | ** |
| 103486 | ** nEqCol is the number of columns of pIdx that are used as equality |
| 103487 | ** constraints and where the other side of the == is an ordered column |
| 103488 | ** or constant. An "order column" in the previous sentence means a column |
| 103489 | ** in table from an outer loop whose values will always appear in the |
| 103490 | ** correct order due to othre index, or because the outer loop generates |
| 103491 | ** a unique result. Any of the first nEqCol columns of pIdx may be missing |
| 103492 | ** from the ORDER BY clause and the match can still be a success. |
| 103493 | ** |
| 103494 | ** The *pbRev value is set to 0 order 1 depending on whether or not |
| 103495 | ** pIdx should be run in the forward order or in reverse order. |
| 103496 | */ |
| 103497 | static int isSortingIndex( |
| 103498 | WhereBestIdx *p, /* Best index search context */ |
| 103499 | Index *pIdx, /* The index we are testing */ |
| 103500 | int base, /* Cursor number for the table to be sorted */ |
| 103501 | int nEqCol, /* Number of index columns with ordered == constraints */ |
| 103502 | int wsFlags, /* Index usages flags */ |
| 103503 | int bOuterRev, /* True if outer loops scan in reverse order */ |
| 103504 | int *pbRev /* Set to 1 for reverse-order scan of pIdx */ |
| 103505 | ){ |
| 103506 | int i; /* Number of pIdx terms used */ |
| 103507 | int j; /* Number of ORDER BY terms satisfied */ |
| 103508 | int sortOrder = 0; /* XOR of index and ORDER BY sort direction */ |
| 103509 | int nTerm; /* Number of ORDER BY terms */ |
| 103510 | struct ExprList_item *pTerm; /* A term of the ORDER BY clause */ |
| 103511 | ExprList *pOrderBy; /* The ORDER BY clause */ |
| 103512 | Parse *pParse = p->pParse; /* Parser context */ |
| 103513 | sqlite3 *db = pParse->db; /* Database connection */ |
| 103514 | int nPriorSat; /* ORDER BY terms satisfied by outer loops */ |
| 103515 | int seenRowid = 0; /* True if an ORDER BY rowid term is seen */ |
| 103516 | int nEqOneRow; /* Idx columns that ref unique values */ |
| 103517 | |
| 103518 | if( p->i==0 ){ |
| 103519 | nPriorSat = 0; |
| 103520 | nEqOneRow = nEqCol; |
| 103521 | }else{ |
| 103522 | if( OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return 0; |
| 103523 | nPriorSat = p->aLevel[p->i-1].plan.nOBSat; |
| 103524 | sortOrder = bOuterRev; |
| 103525 | nEqOneRow = 0; |
| 103526 | } |
| 103527 | if( p->i>0 && nEqCol==0 /*&& !allOuterLoopsUnique(p)*/ ) return nPriorSat; |
| 103528 | pOrderBy = p->pOrderBy; |
| 103529 | if( !pOrderBy ) return nPriorSat; |
| 103530 | if( wsFlags & WHERE_COLUMN_IN ) return nPriorSat; |
| 103531 | if( pIdx->bUnordered ) return nPriorSat; |
| 103532 | nTerm = pOrderBy->nExpr; |
| 103533 | assert( nTerm>0 ); |
| 103534 | |
| 103535 | /* Argument pIdx must either point to a 'real' named index structure, |
| 103536 | ** or an index structure allocated on the stack by bestBtreeIndex() to |
| 103537 | ** represent the rowid index that is part of every table. */ |
| 103538 | assert( pIdx->zName || (pIdx->nColumn==1 && pIdx->aiColumn[0]==-1) ); |
| 103539 | |
| 103540 | /* Match terms of the ORDER BY clause against columns of |
| 103541 | ** the index. |
| 103542 | ** |
| 103543 | ** Note that indices have pIdx->nColumn regular columns plus |
| 103544 | ** one additional column containing the rowid. The rowid column |
| 103545 | ** of the index is also allowed to match against the ORDER BY |
| 103546 | ** clause. |
| 103547 | */ |
| 103548 | for(i=0,j=nPriorSat,pTerm=&pOrderBy->a[j]; j<nTerm && i<=pIdx->nColumn; i++){ |
| 103549 | Expr *pExpr; /* The expression of the ORDER BY pTerm */ |
| 103550 | CollSeq *pColl; /* The collating sequence of pExpr */ |
| 103551 | int termSortOrder; /* Sort order for this term */ |
| 103552 | int iColumn; /* The i-th column of the index. -1 for rowid */ |
| 103553 | int iSortOrder; /* 1 for DESC, 0 for ASC on the i-th index term */ |
| 103554 | const char *zColl; /* Name of the collating sequence for i-th index term */ |
| 103555 | |
| 103556 | pExpr = pTerm->pExpr; |
| 103557 | if( pExpr->op!=TK_COLUMN || pExpr->iTable!=base ){ |
| 103558 | /* Can not use an index sort on anything that is not a column in the |
| 103559 | ** left-most table of the FROM clause */ |
| 103560 | break; |
| 103561 | } |
| 103562 | pColl = sqlite3ExprCollSeq(pParse, pExpr); |
| 103563 | if( !pColl ){ |
| 103564 | pColl = db->pDfltColl; |
| 103565 | } |
| 103566 | if( pIdx->zName && i<pIdx->nColumn ){ |
| 103567 | iColumn = pIdx->aiColumn[i]; |
| 103568 | if( iColumn==pIdx->pTable->iPKey ){ |
| 103569 | iColumn = -1; |
| 103570 | } |
| 103571 | iSortOrder = pIdx->aSortOrder[i]; |
| 103572 | zColl = pIdx->azColl[i]; |
| 103573 | }else{ |
| 103574 | iColumn = -1; |
| 103575 | iSortOrder = 0; |
| 103576 | zColl = pColl->zName; |
| 103577 | } |
| 103578 | if( pExpr->iColumn!=iColumn || sqlite3StrICmp(pColl->zName, zColl) ){ |
| 103579 | /* Term j of the ORDER BY clause does not match column i of the index */ |
| 103580 | if( i<nEqCol ){ |
| 103581 | /* If an index column that is constrained by == fails to match an |
| 103582 | ** ORDER BY term, that is OK. Just ignore that column of the index |
| 103583 | */ |
| 103584 | continue; |
| 103585 | }else if( i==pIdx->nColumn ){ |
| 103586 | /* Index column i is the rowid. All other terms match. */ |
| 103587 | break; |
| 103588 | }else{ |
| 103589 | /* If an index column fails to match and is not constrained by == |
| 103590 | ** then the index cannot satisfy the ORDER BY constraint. |
| 103591 | */ |
| 103592 | return nPriorSat; |
| 103593 | } |
| 103594 | } |
| 103595 | assert( pIdx->aSortOrder!=0 || iColumn==-1 ); |
| 103596 | assert( pTerm->sortOrder==0 || pTerm->sortOrder==1 ); |
| 103597 | assert( iSortOrder==0 || iSortOrder==1 ); |
| 103598 | termSortOrder = iSortOrder ^ pTerm->sortOrder; |
| 103599 | if( i>nEqOneRow ){ |
| 103600 | if( termSortOrder!=sortOrder ){ |
| 103601 | /* Indices can only be used if all ORDER BY terms past the |
| 103602 | ** equality constraints are all either DESC or ASC. */ |
| 103603 | break; |
| 103604 | } |
| 103605 | }else{ |
| 103606 | sortOrder = termSortOrder; |
| 103607 | } |
| 103608 | j++; |
| 103609 | pTerm++; |
| 103610 | if( iColumn<0 ){ |
| 103611 | seenRowid = 1; |
| 103612 | break; |
| 103613 | } |
| 103614 | } |
| 103615 | *pbRev = sortOrder; |
| 103616 | |
| 103617 | /* If there was an "ORDER BY rowid" term that matched, or it is only |
| 103618 | ** possible for a single row from this table to match, then skip over |
| 103619 | ** any additional ORDER BY terms dealing with this table. |
| 103620 | */ |
| 103621 | if( seenRowid || |
| 103622 | ( (wsFlags & WHERE_COLUMN_NULL)==0 |
| 103623 | && i>=pIdx->nColumn |
| 103624 | && indexIsUniqueNotNull(pIdx, nEqCol) |
| 103625 | ) |
| 103626 | ){ |
| 103627 | /* Advance j over additional ORDER BY terms associated with base */ |
| 103628 | WhereMaskSet *pMS = p->pWC->pMaskSet; |
| 103629 | Bitmask m = ~getMask(pMS, base); |
| 103630 | while( j<nTerm && (exprTableUsage(pMS, pOrderBy->a[j].pExpr)&m)==0 ){ |
| 103631 | j++; |
| 103632 | } |
| 103633 | } |
| 103634 | return j; |
| 103635 | } |
| 103636 | |
| 103637 | /* |
| 103638 | ** Prepare a crude estimate of the logarithm of the input value. |
| 103639 | ** The results need not be exact. This is only used for estimating |
| 103640 | ** the total cost of performing operations with O(logN) or O(NlogN) |
| 103641 | ** complexity. Because N is just a guess, it is no great tragedy if |
| @@ -103785,10 +103702,11 @@ | |
| 103785 | WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow)); |
| 103786 | if( rTotal<p->cost.rCost ){ |
| 103787 | p->cost.rCost = rTotal; |
| 103788 | p->cost.used = used; |
| 103789 | p->cost.plan.nRow = nRow; |
| 103790 | p->cost.plan.wsFlags = flags; |
| 103791 | p->cost.plan.u.pTerm = pTerm; |
| 103792 | } |
| 103793 | } |
| 103794 | } |
| @@ -104327,11 +104245,14 @@ | |
| 104327 | }else{ |
| 104328 | p->cost.rCost = rCost; |
| 104329 | } |
| 104330 | p->cost.plan.u.pVtabIdx = pIdxInfo; |
| 104331 | if( pIdxInfo->orderByConsumed ){ |
| 104332 | p->cost.plan.wsFlags |= WHERE_ORDERBY; |
| 104333 | } |
| 104334 | p->cost.plan.nEq = 0; |
| 104335 | pIdxInfo->nOrderBy = nOrderBy; |
| 104336 | |
| 104337 | /* Try to find a more efficient access pattern by using multiple indexes |
| @@ -104750,20 +104671,26 @@ | |
| 104750 | WhereLevel *pLevel = &p->aLevel[p->i-1]; |
| 104751 | Index *pIdx; |
| 104752 | u8 sortOrder; |
| 104753 | for(i=p->i-1; i>=0; i--, pLevel--){ |
| 104754 | if( pLevel->iTabCur!=iTab ) continue; |
| 104755 | if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){ |
| 104756 | pIdx = pLevel->plan.u.pIdx; |
| 104757 | if( iCol<0 ){ |
| 104758 | sortOrder = 0; |
| 104759 | testcase( (pLevel->plan.wsFlags & WHERE_REVERSE)!=0 ); |
| 104760 | }else{ |
| 104761 | for(j=0; j<pIdx->nColumn; j++){ |
| 104762 | if( iCol==pIdx->aiColumn[j] ) break; |
| 104763 | } |
| 104764 | if( j>=pIdx->nColumn ) return 0; |
| 104765 | sortOrder = pIdx->aSortOrder[j]; |
| 104766 | testcase( (pLevel->plan.wsFlags & WHERE_REVERSE)!=0 ); |
| 104767 | } |
| 104768 | }else{ |
| 104769 | if( iCol!=(-1) ) return 0; |
| @@ -104792,13 +104719,10 @@ | |
| 104792 | static int isOrderedTerm(WhereBestIdx *p, WhereTerm *pTerm, int *pbRev){ |
| 104793 | Expr *pExpr = pTerm->pExpr; |
| 104794 | assert( pExpr->op==TK_EQ ); |
| 104795 | assert( pExpr->pLeft!=0 && pExpr->pLeft->op==TK_COLUMN ); |
| 104796 | assert( pExpr->pRight!=0 ); |
| 104797 | if( p->i==0 ){ |
| 104798 | return 1; /* All == are ordered in the outer loop */ |
| 104799 | } |
| 104800 | if( pTerm->prereqRight==0 ){ |
| 104801 | return 1; /* RHS of the == is a constant */ |
| 104802 | } |
| 104803 | if( pExpr->pRight->op==TK_COLUMN |
| 104804 | && isOrderedColumn(p, pExpr->pRight->iTable, pExpr->pRight->iColumn, pbRev) |
| @@ -104808,10 +104732,177 @@ | |
| 104808 | |
| 104809 | /* If we cannot prove that the constraint is ordered, assume it is not */ |
| 104810 | return 0; |
| 104811 | } |
| 104812 | |
| 104813 | |
| 104814 | /* |
| 104815 | ** Find the best query plan for accessing a particular table. Write the |
| 104816 | ** best query plan and its cost into the p->cost. |
| 104817 | ** |
| @@ -104902,22 +104993,19 @@ | |
| 104902 | |
| 104903 | /* Loop over all indices looking for the best one to use |
| 104904 | */ |
| 104905 | for(; pProbe; pIdx=pProbe=pProbe->pNext){ |
| 104906 | const tRowcnt * const aiRowEst = pProbe->aiRowEst; |
| 104907 | double cost; /* Cost of using pProbe */ |
| 104908 | double nRow; /* Estimated number of rows in result set */ |
| 104909 | double log10N = (double)1; /* base-10 logarithm of nRow (inexact) */ |
| 104910 | int bRev = 2; /* 0=forward scan. 1=reverse. 2=undecided */ |
| 104911 | int wsFlags = 0; |
| 104912 | Bitmask used = 0; |
| 104913 | |
| 104914 | /* The following variables are populated based on the properties of |
| 104915 | ** index being evaluated. They are then used to determine the expected |
| 104916 | ** cost and number of rows returned. |
| 104917 | ** |
| 104918 | ** nEq: |
| 104919 | ** Number of equality terms that can be implemented using the index. |
| 104920 | ** In other words, the number of initial fields in the index that |
| 104921 | ** are used in == or IN or NOT NULL constraints of the WHERE clause. |
| 104922 | ** |
| 104923 | ** nInMul: |
| @@ -104960,11 +105048,11 @@ | |
| 104960 | ** bSort: |
| 104961 | ** Boolean. True if there is an ORDER BY clause that will require an |
| 104962 | ** external sort (i.e. scanning the index being evaluated will not |
| 104963 | ** correctly order records). |
| 104964 | ** |
| 104965 | ** bDistinct: |
| 104966 | ** Boolean. True if there is a DISTINCT clause that will require an |
| 104967 | ** external btree. |
| 104968 | ** |
| 104969 | ** bLookup: |
| 104970 | ** Boolean. True if a table lookup is required for each index entry |
| @@ -104979,132 +105067,148 @@ | |
| 104979 | ** both available in the index. |
| 104980 | ** |
| 104981 | ** SELECT a, b FROM tbl WHERE a = 1; |
| 104982 | ** SELECT a, b, c FROM tbl WHERE a = 1; |
| 104983 | */ |
| 104984 | int nEq; /* Number of == or IN terms matching index */ |
| 104985 | int nOrdered; /* Number of ordered terms matching index */ |
| 104986 | int bInEst = 0; /* True if "x IN (SELECT...)" seen */ |
| 104987 | int nInMul = 1; /* Number of distinct equalities to lookup */ |
| 104988 | double rangeDiv = (double)1; /* Estimated reduction in search space */ |
| 104989 | int nBound = 0; /* Number of range constraints seen */ |
| 104990 | int bSort; /* True if external sort required */ |
| 104991 | int bDist; /* True if index cannot help with DISTINCT */ |
| 104992 | int bLookup = 0; /* True if not a covering index */ |
| 104993 | int nOBSat = 0; /* Number of ORDER BY terms satisfied */ |
| 104994 | int nOrderBy; /* Number of ORDER BY terms */ |
| 104995 | WhereTerm *pTerm; /* A single term of the WHERE clause */ |
| 104996 | #ifdef SQLITE_ENABLE_STAT3 |
| 104997 | WhereTerm *pFirstTerm = 0; /* First term matching the index */ |
| 104998 | #endif |
| 104999 | |
| 105000 | nOrderBy = p->pOrderBy ? p->pOrderBy->nExpr : 0; |
| 105001 | bSort = nOrderBy>0 && (p->i==0 || p->aLevel[p->i-1].plan.nOBSat<nOrderBy); |
| 105002 | bDist = p->i==0 && p->pDistinct!=0; |
| 105003 | |
| 105004 | /* Determine the values of nEq and nInMul */ |
| 105005 | for(nEq=nOrdered=0; nEq<pProbe->nColumn; nEq++){ |
| 105006 | int j = pProbe->aiColumn[nEq]; |
| 105007 | pTerm = findTerm(pWC, iCur, j, p->notReady, eqTermMask, pIdx); |
| 105008 | if( pTerm==0 ) break; |
| 105009 | wsFlags |= (WHERE_COLUMN_EQ|WHERE_ROWID_EQ); |
| 105010 | testcase( pTerm->pWC!=pWC ); |
| 105011 | if( pTerm->eOperator & WO_IN ){ |
| 105012 | Expr *pExpr = pTerm->pExpr; |
| 105013 | wsFlags |= WHERE_COLUMN_IN; |
| 105014 | if( ExprHasProperty(pExpr, EP_xIsSelect) ){ |
| 105015 | /* "x IN (SELECT ...)": Assume the SELECT returns 25 rows */ |
| 105016 | nInMul *= 25; |
| 105017 | bInEst = 1; |
| 105018 | }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ |
| 105019 | /* "x IN (value, value, ...)" */ |
| 105020 | nInMul *= pExpr->x.pList->nExpr; |
| 105021 | } |
| 105022 | }else if( pTerm->eOperator & WO_ISNULL ){ |
| 105023 | wsFlags |= WHERE_COLUMN_NULL; |
| 105024 | if( nEq==nOrdered ) nOrdered++; |
| 105025 | }else if( bSort && nEq==nOrdered && isOrderedTerm(p, pTerm, &bRev) ){ |
| 105026 | nOrdered++; |
| 105027 | } |
| 105028 | #ifdef SQLITE_ENABLE_STAT3 |
| 105029 | if( nEq==0 && pProbe->aSample ) pFirstTerm = pTerm; |
| 105030 | #endif |
| 105031 | used |= pTerm->prereqRight; |
| 105032 | } |
| 105033 | |
| 105034 | /* If the index being considered is UNIQUE, and there is an equality |
| 105035 | ** constraint for all columns in the index, then this search will find |
| 105036 | ** at most a single row. In this case set the WHERE_UNIQUE flag to |
| 105037 | ** indicate this to the caller. |
| 105038 | ** |
| 105039 | ** Otherwise, if the search may find more than one row, test to see if |
| 105040 | ** there is a range constraint on indexed column (nEq+1) that can be |
| 105041 | ** optimized using the index. |
| 105042 | */ |
| 105043 | if( nEq==pProbe->nColumn && pProbe->onError!=OE_None ){ |
| 105044 | testcase( wsFlags & WHERE_COLUMN_IN ); |
| 105045 | testcase( wsFlags & WHERE_COLUMN_NULL ); |
| 105046 | if( (wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0 ){ |
| 105047 | wsFlags |= WHERE_UNIQUE; |
| 105048 | } |
| 105049 | }else if( pProbe->bUnordered==0 ){ |
| 105050 | int j = (nEq==pProbe->nColumn ? -1 : pProbe->aiColumn[nEq]); |
| 105051 | if( findTerm(pWC, iCur, j, p->notReady, WO_LT|WO_LE|WO_GT|WO_GE, pIdx) ){ |
| 105052 | WhereTerm *pTop, *pBtm; |
| 105053 | pTop = findTerm(pWC, iCur, j, p->notReady, WO_LT|WO_LE, pIdx); |
| 105054 | pBtm = findTerm(pWC, iCur, j, p->notReady, WO_GT|WO_GE, pIdx); |
| 105055 | whereRangeScanEst(pParse, pProbe, nEq, pBtm, pTop, &rangeDiv); |
| 105056 | if( pTop ){ |
| 105057 | nBound = 1; |
| 105058 | wsFlags |= WHERE_TOP_LIMIT; |
| 105059 | used |= pTop->prereqRight; |
| 105060 | testcase( pTop->pWC!=pWC ); |
| 105061 | } |
| 105062 | if( pBtm ){ |
| 105063 | nBound++; |
| 105064 | wsFlags |= WHERE_BTM_LIMIT; |
| 105065 | used |= pBtm->prereqRight; |
| 105066 | testcase( pBtm->pWC!=pWC ); |
| 105067 | } |
| 105068 | wsFlags |= (WHERE_COLUMN_RANGE|WHERE_ROWID_RANGE); |
| 105069 | } |
| 105070 | } |
| 105071 | |
| 105072 | /* If there is an ORDER BY clause and the index being considered will |
| 105073 | ** naturally scan rows in the required order, set the appropriate flags |
| 105074 | ** in wsFlags. Otherwise, if there is an ORDER BY clause but the index |
| 105075 | ** will scan rows in a different order, set the bSort variable. */ |
| 105076 | assert( bRev>=0 && bRev<=2 ); |
| 105077 | if( bSort ){ |
| 105078 | testcase( bRev==0 ); |
| 105079 | testcase( bRev==1 ); |
| 105080 | testcase( bRev==2 ); |
| 105081 | nOBSat = isSortingIndex(p, pProbe, iCur, nOrdered, |
| 105082 | wsFlags, bRev&1, &bRev); |
| 105083 | if( nOrderBy==nOBSat ){ |
| 105084 | bSort = 0; |
| 105085 | wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_ORDERBY; |
| 105086 | } |
| 105087 | if( bRev & 1 ) wsFlags |= WHERE_REVERSE; |
| 105088 | } |
| 105089 | |
| 105090 | /* If there is a DISTINCT qualifier and this index will scan rows in |
| 105091 | ** order of the DISTINCT expressions, clear bDist and set the appropriate |
| 105092 | ** flags in wsFlags. */ |
| 105093 | if( bDist |
| 105094 | && isDistinctIndex(pParse, pWC, pProbe, iCur, p->pDistinct, nEq) |
| 105095 | && (wsFlags & WHERE_COLUMN_IN)==0 |
| 105096 | ){ |
| 105097 | bDist = 0; |
| 105098 | wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_DISTINCT; |
| 105099 | } |
| 105100 | |
| 105101 | /* If currently calculating the cost of using an index (not the IPK |
| 105102 | ** index), determine if all required column data may be obtained without |
| 105103 | ** using the main table (i.e. if the index is a covering |
| 105104 | ** index for this query). If it is, set the WHERE_IDX_ONLY flag in |
| 105105 | ** wsFlags. Otherwise, set the bLookup variable to true. */ |
| 105106 | if( pIdx ){ |
| 105107 | Bitmask m = pSrc->colUsed; |
| 105108 | int j; |
| 105109 | for(j=0; j<pIdx->nColumn; j++){ |
| 105110 | int x = pIdx->aiColumn[j]; |
| @@ -105111,51 +105215,54 @@ | |
| 105111 | if( x<BMS-1 ){ |
| 105112 | m &= ~(((Bitmask)1)<<x); |
| 105113 | } |
| 105114 | } |
| 105115 | if( m==0 ){ |
| 105116 | wsFlags |= WHERE_IDX_ONLY; |
| 105117 | }else{ |
| 105118 | bLookup = 1; |
| 105119 | } |
| 105120 | } |
| 105121 | |
| 105122 | /* |
| 105123 | ** Estimate the number of rows of output. For an "x IN (SELECT...)" |
| 105124 | ** constraint, do not let the estimate exceed half the rows in the table. |
| 105125 | */ |
| 105126 | nRow = (double)(aiRowEst[nEq] * nInMul); |
| 105127 | if( bInEst && nRow*2>aiRowEst[0] ){ |
| 105128 | nRow = aiRowEst[0]/2; |
| 105129 | nInMul = (int)(nRow / aiRowEst[nEq]); |
| 105130 | } |
| 105131 | |
| 105132 | #ifdef SQLITE_ENABLE_STAT3 |
| 105133 | /* If the constraint is of the form x=VALUE or x IN (E1,E2,...) |
| 105134 | ** and we do not think that values of x are unique and if histogram |
| 105135 | ** data is available for column x, then it might be possible |
| 105136 | ** to get a better estimate on the number of rows based on |
| 105137 | ** VALUE and how common that value is according to the histogram. |
| 105138 | */ |
| 105139 | if( nRow>(double)1 && nEq==1 && pFirstTerm!=0 && aiRowEst[1]>1 ){ |
| 105140 | assert( (pFirstTerm->eOperator & (WO_EQ|WO_ISNULL|WO_IN))!=0 ); |
| 105141 | if( pFirstTerm->eOperator & (WO_EQ|WO_ISNULL) ){ |
| 105142 | testcase( pFirstTerm->eOperator==WO_EQ ); |
| 105143 | testcase( pFirstTerm->eOperator==WO_ISNULL ); |
| 105144 | whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, &nRow); |
| 105145 | }else if( bInEst==0 ){ |
| 105146 | assert( pFirstTerm->eOperator==WO_IN ); |
| 105147 | whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, &nRow); |
| 105148 | } |
| 105149 | } |
| 105150 | #endif /* SQLITE_ENABLE_STAT3 */ |
| 105151 | |
| 105152 | /* Adjust the number of output rows and downward to reflect rows |
| 105153 | ** that are excluded by range constraints. |
| 105154 | */ |
| 105155 | nRow = nRow/rangeDiv; |
| 105156 | if( nRow<1 ) nRow = 1; |
| 105157 | |
| 105158 | /* Experiments run on real SQLite databases show that the time needed |
| 105159 | ** to do a binary search to locate a row in a table or index is roughly |
| 105160 | ** log10(N) times the time to move from one row to the next row within |
| 105161 | ** a table or index. The actual times can vary, with the size of |
| @@ -105166,57 +105273,58 @@ | |
| 105166 | ** The ANALYZE command and the sqlite_stat1 and sqlite_stat3 tables do |
| 105167 | ** not give us data on the relative sizes of table and index records. |
| 105168 | ** So this computation assumes table records are about twice as big |
| 105169 | ** as index records |
| 105170 | */ |
| 105171 | if( (wsFlags&~WHERE_REVERSE)==WHERE_IDX_ONLY |
| 105172 | && (pWC->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 |
| 105173 | && sqlite3GlobalConfig.bUseCis |
| 105174 | && OptimizationEnabled(pParse->db, SQLITE_CoverIdxScan) |
| 105175 | ){ |
| 105176 | /* This index is not useful for indexing, but it is a covering index. |
| 105177 | ** A full-scan of the index might be a little faster than a full-scan |
| 105178 | ** of the table, so give this case a cost slightly less than a table |
| 105179 | ** scan. */ |
| 105180 | cost = aiRowEst[0]*3 + pProbe->nColumn; |
| 105181 | wsFlags |= WHERE_COVER_SCAN|WHERE_COLUMN_RANGE; |
| 105182 | }else if( (wsFlags & WHERE_NOT_FULLSCAN)==0 ){ |
| 105183 | /* The cost of a full table scan is a number of move operations equal |
| 105184 | ** to the number of rows in the table. |
| 105185 | ** |
| 105186 | ** We add an additional 4x penalty to full table scans. This causes |
| 105187 | ** the cost function to err on the side of choosing an index over |
| 105188 | ** choosing a full scan. This 4x full-scan penalty is an arguable |
| 105189 | ** decision and one which we expect to revisit in the future. But |
| 105190 | ** it seems to be working well enough at the moment. |
| 105191 | */ |
| 105192 | cost = aiRowEst[0]*4; |
| 105193 | wsFlags &= ~WHERE_IDX_ONLY; |
| 105194 | }else{ |
| 105195 | log10N = estLog(aiRowEst[0]); |
| 105196 | cost = nRow; |
| 105197 | if( pIdx ){ |
| 105198 | if( bLookup ){ |
| 105199 | /* For an index lookup followed by a table lookup: |
| 105200 | ** nInMul index searches to find the start of each index range |
| 105201 | ** + nRow steps through the index |
| 105202 | ** + nRow table searches to lookup the table entry using the rowid |
| 105203 | */ |
| 105204 | cost += (nInMul + nRow)*log10N; |
| 105205 | }else{ |
| 105206 | /* For a covering index: |
| 105207 | ** nInMul index searches to find the initial entry |
| 105208 | ** + nRow steps through the index |
| 105209 | */ |
| 105210 | cost += nInMul*log10N; |
| 105211 | } |
| 105212 | }else{ |
| 105213 | /* For a rowid primary key lookup: |
| 105214 | ** nInMult table searches to find the initial entry for each range |
| 105215 | ** + nRow steps through the table |
| 105216 | */ |
| 105217 | cost += nInMul*log10N; |
| 105218 | } |
| 105219 | } |
| 105220 | |
| 105221 | /* Add in the estimated cost of sorting the result. Actual experimental |
| 105222 | ** measurements of sorting performance in SQLite show that sorting time |
| @@ -105223,14 +105331,16 @@ | |
| 105223 | ** adds C*N*log10(N) to the cost, where N is the number of rows to be |
| 105224 | ** sorted and C is a factor between 1.95 and 4.3. We will split the |
| 105225 | ** difference and select C of 3.0. |
| 105226 | */ |
| 105227 | if( bSort ){ |
| 105228 | cost += nRow*estLog(nRow*(nOrderBy - nOBSat)/nOrderBy)*3; |
| 105229 | } |
| 105230 | if( bDist ){ |
| 105231 | cost += nRow*estLog(nRow)*3; |
| 105232 | } |
| 105233 | |
| 105234 | /**** Cost of using this index has now been computed ****/ |
| 105235 | |
| 105236 | /* If there are additional constraints on this table that cannot |
| @@ -105247,29 +105357,29 @@ | |
| 105247 | ** tables that are not in outer loops. If notReady is used here instead |
| 105248 | ** of notValid, then a optimal index that depends on inner joins loops |
| 105249 | ** might be selected even when there exists an optimal index that has |
| 105250 | ** no such dependency. |
| 105251 | */ |
| 105252 | if( nRow>2 && cost<=p->cost.rCost ){ |
| 105253 | int k; /* Loop counter */ |
| 105254 | int nSkipEq = nEq; /* Number of == constraints to skip */ |
| 105255 | int nSkipRange = nBound; /* Number of < constraints to skip */ |
| 105256 | Bitmask thisTab; /* Bitmap for pSrc */ |
| 105257 | |
| 105258 | thisTab = getMask(pWC->pMaskSet, iCur); |
| 105259 | for(pTerm=pWC->a, k=pWC->nTerm; nRow>2 && k; k--, pTerm++){ |
| 105260 | if( pTerm->wtFlags & TERM_VIRTUAL ) continue; |
| 105261 | if( (pTerm->prereqAll & p->notValid)!=thisTab ) continue; |
| 105262 | if( pTerm->eOperator & (WO_EQ|WO_IN|WO_ISNULL) ){ |
| 105263 | if( nSkipEq ){ |
| 105264 | /* Ignore the first nEq equality matches since the index |
| 105265 | ** has already accounted for these */ |
| 105266 | nSkipEq--; |
| 105267 | }else{ |
| 105268 | /* Assume each additional equality match reduces the result |
| 105269 | ** set size by a factor of 10 */ |
| 105270 | nRow /= 10; |
| 105271 | } |
| 105272 | }else if( pTerm->eOperator & (WO_LT|WO_LE|WO_GT|WO_GE) ){ |
| 105273 | if( nSkipRange ){ |
| 105274 | /* Ignore the first nSkipRange range constraints since the index |
| 105275 | ** has already accounted for these */ |
| @@ -105279,43 +105389,38 @@ | |
| 105279 | ** set size by a factor of 3. Indexed range constraints reduce |
| 105280 | ** the search space by a larger factor: 4. We make indexed range |
| 105281 | ** more selective intentionally because of the subjective |
| 105282 | ** observation that indexed range constraints really are more |
| 105283 | ** selective in practice, on average. */ |
| 105284 | nRow /= 3; |
| 105285 | } |
| 105286 | }else if( pTerm->eOperator!=WO_NOOP ){ |
| 105287 | /* Any other expression lowers the output row count by half */ |
| 105288 | nRow /= 2; |
| 105289 | } |
| 105290 | } |
| 105291 | if( nRow<2 ) nRow = 2; |
| 105292 | } |
| 105293 | |
| 105294 | |
| 105295 | WHERETRACE(( |
| 105296 | "%s(%s):\n" |
| 105297 | " nEq=%d nInMul=%d rangeDiv=%d bSort=%d bLookup=%d wsFlags=0x%08x\n" |
| 105298 | " notReady=0x%llx log10N=%.1f nRow=%.1f cost=%.1f\n" |
| 105299 | " used=0x%llx nOrdered=%d nOBSat=%d\n", |
| 105300 | pSrc->pTab->zName, (pIdx ? pIdx->zName : "ipk"), |
| 105301 | nEq, nInMul, (int)rangeDiv, bSort, bLookup, wsFlags, |
| 105302 | p->notReady, log10N, nRow, cost, used, nOrdered, nOBSat |
| 105303 | )); |
| 105304 | |
| 105305 | /* If this index is the best we have seen so far, then record this |
| 105306 | ** index and its cost in the pCost structure. |
| 105307 | */ |
| 105308 | if( (!pIdx || wsFlags) |
| 105309 | && (cost<p->cost.rCost || (cost<=p->cost.rCost && nRow<p->cost.plan.nRow)) |
| 105310 | ){ |
| 105311 | p->cost.rCost = cost; |
| 105312 | p->cost.used = used; |
| 105313 | p->cost.plan.nRow = nRow; |
| 105314 | p->cost.plan.wsFlags = (wsFlags&wsFlagMask); |
| 105315 | p->cost.plan.nEq = nEq; |
| 105316 | p->cost.plan.nOBSat = nOBSat; |
| 105317 | p->cost.plan.u.pIdx = pIdx; |
| 105318 | } |
| 105319 | |
| 105320 | /* If there was an INDEXED BY clause, then only that one index is |
| 105321 | ** considered. */ |
| @@ -105333,21 +105438,19 @@ | |
| 105333 | ** SQLite outputs rows in in the absence of an ORDER BY clause. */ |
| 105334 | if( !p->pOrderBy && pParse->db->flags & SQLITE_ReverseOrder ){ |
| 105335 | p->cost.plan.wsFlags |= WHERE_REVERSE; |
| 105336 | } |
| 105337 | |
| 105338 | assert( p->pOrderBy || (p->cost.plan.wsFlags&WHERE_ORDERBY)==0 ); |
| 105339 | assert( p->cost.plan.u.pIdx==0 || (p->cost.plan.wsFlags&WHERE_ROWID_EQ)==0 ); |
| 105340 | assert( pSrc->pIndex==0 |
| 105341 | || p->cost.plan.u.pIdx==0 |
| 105342 | || p->cost.plan.u.pIdx==pSrc->pIndex |
| 105343 | ); |
| 105344 | |
| 105345 | WHERETRACE(("best index is: %s\n", |
| 105346 | ((p->cost.plan.wsFlags & WHERE_NOT_FULLSCAN)==0 ? "none" : |
| 105347 | p->cost.plan.u.pIdx ? p->cost.plan.u.pIdx->zName : "ipk") |
| 105348 | )); |
| 105349 | |
| 105350 | bestOrClauseIndex(p); |
| 105351 | bestAutomaticIndex(p); |
| 105352 | p->cost.plan.wsFlags |= eqTermMask; |
| 105353 | } |
| @@ -106071,11 +106174,11 @@ | |
| 106071 | ** should not have a NULL value stored in 'x'. If column 'x' is |
| 106072 | ** the first one after the nEq equality constraints in the index, |
| 106073 | ** this requires some special handling. |
| 106074 | */ |
| 106075 | if( (wctrlFlags&WHERE_ORDERBY_MIN)!=0 |
| 106076 | && (pLevel->plan.wsFlags&WHERE_ORDERBY) |
| 106077 | && (pIdx->nColumn>nEq) |
| 106078 | ){ |
| 106079 | /* assert( pOrderBy->nExpr==1 ); */ |
| 106080 | /* assert( pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq] ); */ |
| 106081 | isMinQuery = 1; |
| @@ -106892,12 +106995,12 @@ | |
| 106892 | continue; |
| 106893 | } |
| 106894 | sWBI.notReady = (isOptimal ? m : sWBI.notValid); |
| 106895 | if( sWBI.pSrc->pIndex==0 ) nUnconstrained++; |
| 106896 | |
| 106897 | WHERETRACE(("=== trying table %d with isOptimal=%d ===\n", |
| 106898 | j, isOptimal)); |
| 106899 | assert( sWBI.pSrc->pTab ); |
| 106900 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 106901 | if( IsVirtual(sWBI.pSrc->pTab) ){ |
| 106902 | sWBI.ppIdxInfo = &pWInfo->a[j].pIdxInfo; |
| 106903 | bestVirtualIndex(&sWBI); |
| @@ -106934,48 +107037,46 @@ | |
| 106934 | ** combination of INDEXED BY clauses are given. The error |
| 106935 | ** will be detected and relayed back to the application later. |
| 106936 | ** The NEVER() comes about because rule (2) above prevents |
| 106937 | ** An indexable full-table-scan from reaching rule (3). |
| 106938 | ** |
| 106939 | ** (4) The plan cost must be lower than prior plans or else the |
| 106940 | ** cost must be the same and the number of rows must be lower. |
| 106941 | */ |
| 106942 | if( (sWBI.cost.used&sWBI.notValid)==0 /* (1) */ |
| 106943 | && (bestJ<0 || (notIndexed&m)!=0 /* (2) */ |
| 106944 | || (bestPlan.plan.wsFlags & WHERE_NOT_FULLSCAN)==0 |
| 106945 | || (sWBI.cost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0) |
| 106946 | && (nUnconstrained==0 || sWBI.pSrc->pIndex==0 /* (3) */ |
| 106947 | || NEVER((sWBI.cost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0)) |
| 106948 | && (bestJ<0 || sWBI.cost.rCost<bestPlan.rCost /* (4) */ |
| 106949 | || (sWBI.cost.rCost<=bestPlan.rCost |
| 106950 | && sWBI.cost.plan.nRow<bestPlan.plan.nRow)) |
| 106951 | ){ |
| 106952 | WHERETRACE(("=== table %d is best so far" |
| 106953 | " with cost=%.1f, nRow=%.1f, nOBSat=%d\n", |
| 106954 | j, sWBI.cost.rCost, sWBI.cost.plan.nRow, |
| 106955 | sWBI.cost.plan.nOBSat)); |
| 106956 | bestPlan = sWBI.cost; |
| 106957 | bestJ = j; |
| 106958 | } |
| 106959 | if( doNotReorder ) break; |
| 106960 | } |
| 106961 | } |
| 106962 | assert( bestJ>=0 ); |
| 106963 | assert( sWBI.notValid & getMask(pMaskSet, pTabList->a[bestJ].iCursor) ); |
| 106964 | WHERETRACE(("*** Optimizer selects table %d for loop %d with:\n" |
| 106965 | " cost=%.1f, nRow=%.1f, nOBSat=%d wsFlags=0x%08x\n", |
| 106966 | bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow, |
| 106967 | bestPlan.plan.nOBSat, bestPlan.plan.wsFlags)); |
| 106968 | if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){ |
| 106969 | pWInfo->nOBSat = pOrderBy->nExpr; |
| 106970 | } |
| 106971 | if( (bestPlan.plan.wsFlags & WHERE_DISTINCT)!=0 ){ |
| 106972 | assert( pWInfo->eDistinct==0 ); |
| 106973 | pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; |
| 106974 | } |
| 106975 | andFlags &= bestPlan.plan.wsFlags; |
| 106976 | pLevel->plan = bestPlan.plan; |
| 106977 | testcase( bestPlan.plan.wsFlags & WHERE_INDEXED ); |
| 106978 | testcase( bestPlan.plan.wsFlags & WHERE_TEMP_INDEX ); |
| 106979 | if( bestPlan.plan.wsFlags & (WHERE_INDEXED|WHERE_TEMP_INDEX) ){ |
| 106980 | if( (wctrlFlags & WHERE_ONETABLE_ONLY) |
| 106981 | && (bestPlan.plan.wsFlags & WHERE_TEMP_INDEX)==0 |
| @@ -107013,15 +107114,22 @@ | |
| 107013 | } |
| 107014 | WHERETRACE(("*** Optimizer Finished ***\n")); |
| 107015 | if( pParse->nErr || db->mallocFailed ){ |
| 107016 | goto whereBeginError; |
| 107017 | } |
| 107018 | |
| 107019 | /* If the total query only selects a single row, then the ORDER BY |
| 107020 | ** clause is irrelevant. |
| 107021 | */ |
| 107022 | if( (andFlags & WHERE_UNIQUE)!=0 && pOrderBy ){ |
| 107023 | pWInfo->nOBSat = pOrderBy->nExpr; |
| 107024 | } |
| 107025 | |
| 107026 | /* If the caller is an UPDATE or DELETE statement that is requesting |
| 107027 | ** to use a one-pass algorithm, determine if this is appropriate. |
| @@ -107045,11 +107153,10 @@ | |
| 107045 | int iDb; /* Index of database containing table/index */ |
| 107046 | struct SrcList_item *pTabItem; |
| 107047 | |
| 107048 | pTabItem = &pTabList->a[pLevel->iFrom]; |
| 107049 | pTab = pTabItem->pTab; |
| 107050 | pLevel->iTabCur = pTabItem->iCursor; |
| 107051 | pWInfo->nRowOut *= pLevel->plan.nRow; |
| 107052 | iDb = sqlite3SchemaToIndex(db, pTab->pSchema); |
| 107053 | if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ |
| 107054 | /* Do nothing */ |
| 107055 | }else |
| @@ -114978,11 +115085,11 @@ | |
| 114978 | ** with various optimizations disabled to verify that the same answer |
| 114979 | ** is obtained in every case. |
| 114980 | */ |
| 114981 | case SQLITE_TESTCTRL_OPTIMIZATIONS: { |
| 114982 | sqlite3 *db = va_arg(ap, sqlite3*); |
| 114983 | db->dbOptFlags = (u16)(va_arg(ap, int) & 0xffff); |
| 114984 | break; |
| 114985 | } |
| 114986 | |
| 114987 | #ifdef SQLITE_N_KEYWORD |
| 114988 | /* sqlite3_test_control(SQLITE_TESTCTRL_ISKEYWORD, const char *zWord) |
| @@ -135232,11 +135339,11 @@ | |
| 135232 | /* |
| 135233 | ** Remove the entry with rowid=iDelete from the r-tree structure. |
| 135234 | */ |
| 135235 | static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ |
| 135236 | int rc; /* Return code */ |
| 135237 | RtreeNode *pLeaf; /* Leaf node containing record iDelete */ |
| 135238 | int iCell; /* Index of iDelete cell in pLeaf */ |
| 135239 | RtreeNode *pRoot; /* Root node of rtree structure */ |
| 135240 | |
| 135241 | |
| 135242 | /* Obtain a reference to the root node to initialise Rtree.iDepth */ |
| @@ -135435,11 +135542,11 @@ | |
| 135435 | ** (azData[2]..azData[argc-1]) contain a new record to insert into |
| 135436 | ** the r-tree structure. |
| 135437 | */ |
| 135438 | if( rc==SQLITE_OK && nData>1 ){ |
| 135439 | /* Insert the new record into the r-tree */ |
| 135440 | RtreeNode *pLeaf; |
| 135441 | |
| 135442 | /* Figure out the rowid of the new row. */ |
| 135443 | if( bHaveRowid==0 ){ |
| 135444 | rc = newRowid(pRtree, &cell.iRowid); |
| 135445 | } |
| 135446 |
| --- src/sqlite3.c | |
| +++ src/sqlite3.c | |
| @@ -673,11 +673,11 @@ | |
| 673 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 674 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 675 | */ |
| 676 | #define SQLITE_VERSION "3.7.15" |
| 677 | #define SQLITE_VERSION_NUMBER 3007015 |
| 678 | #define SQLITE_SOURCE_ID "2012-10-05 07:36:34 43155b1543bddbb84a8bc13a5b7344b228ddacb9" |
| 679 | |
| 680 | /* |
| 681 | ** CAPI3REF: Run-Time Library Version Numbers |
| 682 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 683 | ** |
| @@ -1421,10 +1421,21 @@ | |
| 1421 | ** that the VFS encountered an error while handling the [PRAGMA] and the |
| 1422 | ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] |
| 1423 | ** file control occurs at the beginning of pragma statement analysis and so |
| 1424 | ** it is able to override built-in [PRAGMA] statements. |
| 1425 | ** </ul> |
| 1426 | ** |
| 1427 | ** <li>[[SQLITE_FCNTL_BUSYHANDLER]] |
| 1428 | ** ^This file-control may be invoked by SQLite on the database file handle |
| 1429 | ** shortly after it is opened in order to provide a custom VFS with access |
| 1430 | ** to the connections busy-handler callback. The argument is of type (void **) |
| 1431 | ** - an array of two (void *) values. The first (void *) actually points |
| 1432 | ** to a function of type (int (*)(void *)). In order to invoke the connections |
| 1433 | ** busy-handler, this function should be invoked with the second (void *) in |
| 1434 | ** the array as the only argument. If it returns non-zero, then the operation |
| 1435 | ** should be retried. If it returns zero, the custom VFS should abandon the |
| 1436 | ** current operation. |
| 1437 | */ |
| 1438 | #define SQLITE_FCNTL_LOCKSTATE 1 |
| 1439 | #define SQLITE_GET_LOCKPROXYFILE 2 |
| 1440 | #define SQLITE_SET_LOCKPROXYFILE 3 |
| 1441 | #define SQLITE_LAST_ERRNO 4 |
| @@ -1436,10 +1447,11 @@ | |
| 1447 | #define SQLITE_FCNTL_PERSIST_WAL 10 |
| 1448 | #define SQLITE_FCNTL_OVERWRITE 11 |
| 1449 | #define SQLITE_FCNTL_VFSNAME 12 |
| 1450 | #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 |
| 1451 | #define SQLITE_FCNTL_PRAGMA 14 |
| 1452 | #define SQLITE_FCNTL_BUSYHANDLER 15 |
| 1453 | |
| 1454 | /* |
| 1455 | ** CAPI3REF: Mutex Handle |
| 1456 | ** |
| 1457 | ** The mutex module within SQLite defines [sqlite3_mutex] to be an |
| @@ -8381,10 +8393,13 @@ | |
| 8393 | SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); |
| 8394 | SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); |
| 8395 | SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree*); |
| 8396 | SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int); |
| 8397 | SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*); |
| 8398 | #if defined(SQLITE_HAS_CODEC) || defined(SQLITE_DEBUG) |
| 8399 | SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p); |
| 8400 | #endif |
| 8401 | SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); |
| 8402 | SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); |
| 8403 | SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int); |
| 8404 | SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); |
| 8405 | SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int); |
| @@ -9150,10 +9165,11 @@ | |
| 9165 | SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); |
| 9166 | SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); |
| 9167 | SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); |
| 9168 | SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); |
| 9169 | SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *); |
| 9170 | SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *); |
| 9171 | |
| 9172 | /* Functions used to truncate the database file. */ |
| 9173 | SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); |
| 9174 | |
| 9175 | #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) |
| @@ -25825,10 +25841,12 @@ | |
| 25841 | int prior = 0; |
| 25842 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) |
| 25843 | i64 newOffset; |
| 25844 | #endif |
| 25845 | TIMER_START; |
| 25846 | assert( cnt==(cnt&0x1ffff) ); |
| 25847 | cnt &= 0x1ffff; |
| 25848 | do{ |
| 25849 | #if defined(USE_PREAD) |
| 25850 | got = osPread(id->h, pBuf, cnt, offset); |
| 25851 | SimulateIOError( got = -1 ); |
| 25852 | #elif defined(USE_PREAD64) |
| @@ -25914,10 +25932,12 @@ | |
| 25932 | static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ |
| 25933 | int got; |
| 25934 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) |
| 25935 | i64 newOffset; |
| 25936 | #endif |
| 25937 | assert( cnt==(cnt&0x1ffff) ); |
| 25938 | cnt &= 0x1ffff; |
| 25939 | TIMER_START; |
| 25940 | #if defined(USE_PREAD) |
| 25941 | do{ got = osPwrite(id->h, pBuf, cnt, offset); }while( got<0 && errno==EINTR ); |
| 25942 | #elif defined(USE_PREAD64) |
| 25943 | do{ got = osPwrite64(id->h, pBuf, cnt, offset);}while( got<0 && errno==EINTR); |
| @@ -39558,10 +39578,25 @@ | |
| 39578 | } |
| 39579 | } |
| 39580 | } |
| 39581 | return rc; |
| 39582 | } |
| 39583 | |
| 39584 | /* |
| 39585 | ** Return a sanitized version of the sector-size of OS file pFile. The |
| 39586 | ** return value is guaranteed to lie between 32 and MAX_SECTOR_SIZE. |
| 39587 | */ |
| 39588 | SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *pFile){ |
| 39589 | int iRet = sqlite3OsSectorSize(pFile); |
| 39590 | if( iRet<32 ){ |
| 39591 | iRet = 512; |
| 39592 | }else if( iRet>MAX_SECTOR_SIZE ){ |
| 39593 | assert( MAX_SECTOR_SIZE>=512 ); |
| 39594 | iRet = MAX_SECTOR_SIZE; |
| 39595 | } |
| 39596 | return iRet; |
| 39597 | } |
| 39598 | |
| 39599 | /* |
| 39600 | ** Set the value of the Pager.sectorSize variable for the given |
| 39601 | ** pager based on the value returned by the xSectorSize method |
| 39602 | ** of the open database file. The sector size will be used used |
| @@ -39594,18 +39629,11 @@ | |
| 39629 | /* Sector size doesn't matter for temporary files. Also, the file |
| 39630 | ** may not have been opened yet, in which case the OsSectorSize() |
| 39631 | ** call will segfault. */ |
| 39632 | pPager->sectorSize = 512; |
| 39633 | }else{ |
| 39634 | pPager->sectorSize = sqlite3SectorSize(pPager->fd); |
| 39635 | } |
| 39636 | } |
| 39637 | |
| 39638 | /* |
| 39639 | ** Playback the journal and thus restore the database file to |
| @@ -40518,13 +40546,20 @@ | |
| 40546 | */ |
| 40547 | SQLITE_PRIVATE void sqlite3PagerSetBusyhandler( |
| 40548 | Pager *pPager, /* Pager object */ |
| 40549 | int (*xBusyHandler)(void *), /* Pointer to busy-handler function */ |
| 40550 | void *pBusyHandlerArg /* Argument to pass to xBusyHandler */ |
| 40551 | ){ |
| 40552 | pPager->xBusyHandler = xBusyHandler; |
| 40553 | pPager->pBusyHandlerArg = pBusyHandlerArg; |
| 40554 | |
| 40555 | if( isOpen(pPager->fd) ){ |
| 40556 | void **ap = (void **)&pPager->xBusyHandler; |
| 40557 | assert( ((int(*)(void *))(ap[0]))==xBusyHandler ); |
| 40558 | assert( ap[1]==pBusyHandlerArg ); |
| 40559 | sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_BUSYHANDLER, (void *)ap); |
| 40560 | } |
| 40561 | } |
| 40562 | |
| 40563 | /* |
| 40564 | ** Change the page size used by the Pager object. The new page size |
| 40565 | ** is passed in *pPageSize. |
| @@ -46815,11 +46850,11 @@ | |
| 46850 | ** sector boundary is synced; the part of the last frame that extends |
| 46851 | ** past the sector boundary is written after the sync. |
| 46852 | */ |
| 46853 | if( isCommit && (sync_flags & WAL_SYNC_TRANSACTIONS)!=0 ){ |
| 46854 | if( pWal->padToSectorBoundary ){ |
| 46855 | int sectorSize = sqlite3SectorSize(pWal->pWalFd); |
| 46856 | w.iSyncPoint = ((iOffset+sectorSize-1)/sectorSize)*sectorSize; |
| 46857 | while( iOffset<w.iSyncPoint ){ |
| 46858 | rc = walWriteOneFrame(&w, pLast, nTruncate, iOffset); |
| 46859 | if( rc ) return rc; |
| 46860 | iOffset += szFrame; |
| @@ -50227,10 +50262,28 @@ | |
| 50262 | ** Return the currently defined page size |
| 50263 | */ |
| 50264 | SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){ |
| 50265 | return p->pBt->pageSize; |
| 50266 | } |
| 50267 | |
| 50268 | #if defined(SQLITE_HAS_CODEC) || defined(SQLITE_DEBUG) |
| 50269 | /* |
| 50270 | ** This function is similar to sqlite3BtreeGetReserve(), except that it |
| 50271 | ** may only be called if it is guaranteed that the b-tree mutex is already |
| 50272 | ** held. |
| 50273 | ** |
| 50274 | ** This is useful in one special case in the backup API code where it is |
| 50275 | ** known that the shared b-tree mutex is held, but the mutex on the |
| 50276 | ** database handle that owns *p is not. In this case if sqlite3BtreeEnter() |
| 50277 | ** were to be called, it might collide with some other operation on the |
| 50278 | ** database handle that owns *p, causing undefined behaviour. |
| 50279 | */ |
| 50280 | SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){ |
| 50281 | assert( sqlite3_mutex_held(p->pBt->mutex) ); |
| 50282 | return p->pBt->pageSize - p->pBt->usableSize; |
| 50283 | } |
| 50284 | #endif /* SQLITE_HAS_CODEC || SQLITE_DEBUG */ |
| 50285 | |
| 50286 | #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM) |
| 50287 | /* |
| 50288 | ** Return the number of bytes of space at the end of every page that |
| 50289 | ** are intentually left unused. This is the "reserved" space that is |
| @@ -53284,11 +53337,11 @@ | |
| 53337 | btreeParseCellPtr(pPage, pCell, &info); |
| 53338 | if( info.iOverflow==0 ){ |
| 53339 | return SQLITE_OK; /* No overflow pages. Return without doing anything */ |
| 53340 | } |
| 53341 | if( pCell+info.iOverflow+3 > pPage->aData+pPage->maskPage ){ |
| 53342 | return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */ |
| 53343 | } |
| 53344 | ovflPgno = get4byte(&pCell[info.iOverflow]); |
| 53345 | assert( pBt->usableSize > 4 ); |
| 53346 | ovflPageSize = pBt->usableSize - 4; |
| 53347 | nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize; |
| @@ -53950,10 +54003,13 @@ | |
| 54003 | ** enough for all overflow cells. |
| 54004 | ** |
| 54005 | ** If aOvflSpace is set to a null pointer, this function returns |
| 54006 | ** SQLITE_NOMEM. |
| 54007 | */ |
| 54008 | #if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) |
| 54009 | #pragma optimize("", off) |
| 54010 | #endif |
| 54011 | static int balance_nonroot( |
| 54012 | MemPage *pParent, /* Parent page of siblings being balanced */ |
| 54013 | int iParentIdx, /* Index of "the page" in pParent */ |
| 54014 | u8 *aOvflSpace, /* page-size bytes of space for parent ovfl */ |
| 54015 | int isRoot, /* True if pParent is a root-page */ |
| @@ -54580,10 +54636,13 @@ | |
| 54636 | releasePage(apNew[i]); |
| 54637 | } |
| 54638 | |
| 54639 | return rc; |
| 54640 | } |
| 54641 | #if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) |
| 54642 | #pragma optimize("", on) |
| 54643 | #endif |
| 54644 | |
| 54645 | |
| 54646 | /* |
| 54647 | ** This function is called when the root page of a b-tree structure is |
| 54648 | ** overfull (has one or more overflow pages). |
| @@ -56558,17 +56617,20 @@ | |
| 56617 | const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc); |
| 56618 | int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest); |
| 56619 | const int nCopy = MIN(nSrcPgsz, nDestPgsz); |
| 56620 | const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz; |
| 56621 | #ifdef SQLITE_HAS_CODEC |
| 56622 | /* Use BtreeGetReserveNoMutex() for the source b-tree, as although it is |
| 56623 | ** guaranteed that the shared-mutex is held by this thread, handle |
| 56624 | ** p->pSrc may not actually be the owner. */ |
| 56625 | int nSrcReserve = sqlite3BtreeGetReserveNoMutex(p->pSrc); |
| 56626 | int nDestReserve = sqlite3BtreeGetReserve(p->pDest); |
| 56627 | #endif |
| 56628 | int rc = SQLITE_OK; |
| 56629 | i64 iOff; |
| 56630 | |
| 56631 | assert( sqlite3BtreeGetReserveNoMutex(p->pSrc)>=0 ); |
| 56632 | assert( p->bDestLocked ); |
| 56633 | assert( !isFatalError(p->rc) ); |
| 56634 | assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ); |
| 56635 | assert( zSrcData ); |
| 56636 | |
| @@ -91600,10 +91662,11 @@ | |
| 91662 | */ |
| 91663 | aFcntl[0] = 0; |
| 91664 | aFcntl[1] = zLeft; |
| 91665 | aFcntl[2] = zRight; |
| 91666 | aFcntl[3] = 0; |
| 91667 | db->busyHandler.nBusy = 0; |
| 91668 | rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); |
| 91669 | if( rc==SQLITE_OK ){ |
| 91670 | if( aFcntl[0] ){ |
| 91671 | int mem = ++pParse->nMem; |
| 91672 | sqlite3VdbeAddOp4(v, OP_String8, 0, mem, 0, aFcntl[0], 0); |
| @@ -102123,14 +102186,15 @@ | |
| 102186 | #define WHERE_NOT_FULLSCAN 0x100f3000 /* Does not do a full table scan */ |
| 102187 | #define WHERE_IN_ABLE 0x000f1000 /* Able to support an IN operator */ |
| 102188 | #define WHERE_TOP_LIMIT 0x00100000 /* x<EXPR or x<=EXPR constraint */ |
| 102189 | #define WHERE_BTM_LIMIT 0x00200000 /* x>EXPR or x>=EXPR constraint */ |
| 102190 | #define WHERE_BOTH_LIMIT 0x00300000 /* Both x>EXPR and x<EXPR */ |
| 102191 | #define WHERE_IDX_ONLY 0x00400000 /* Use index only - omit table */ |
| 102192 | #define WHERE_ORDERED 0x00800000 /* Output will appear in correct order */ |
| 102193 | #define WHERE_REVERSE 0x01000000 /* Scan in reverse order */ |
| 102194 | #define WHERE_UNIQUE 0x02000000 /* Selects no more than one row */ |
| 102195 | #define WHERE_ALL_UNIQUE 0x04000000 /* This and all prior have one row */ |
| 102196 | #define WHERE_VIRTUALTABLE 0x08000000 /* Use virtual-table processing */ |
| 102197 | #define WHERE_MULTI_OR 0x10000000 /* OR using multiple indices */ |
| 102198 | #define WHERE_TEMP_INDEX 0x20000000 /* Uses an ephemeral index */ |
| 102199 | #define WHERE_DISTINCT 0x40000000 /* Correct order for DISTINCT */ |
| 102200 | #define WHERE_COVER_SCAN 0x80000000 /* Full scan of a covering index */ |
| @@ -102154,10 +102218,21 @@ | |
| 102218 | sqlite3_index_info **ppIdxInfo; /* Index information passed to xBestIndex */ |
| 102219 | int i, n; /* Which loop is being coded; # of loops */ |
| 102220 | WhereLevel *aLevel; /* Info about outer loops */ |
| 102221 | WhereCost cost; /* Lowest cost query plan */ |
| 102222 | }; |
| 102223 | |
| 102224 | /* |
| 102225 | ** Return TRUE if the probe cost is less than the baseline cost |
| 102226 | */ |
| 102227 | static int compareCost(const WhereCost *pProbe, const WhereCost *pBaseline){ |
| 102228 | if( pProbe->rCost<pBaseline->rCost ) return 1; |
| 102229 | if( pProbe->rCost>pBaseline->rCost ) return 0; |
| 102230 | if( pProbe->plan.nOBSat>pBaseline->plan.nOBSat ) return 1; |
| 102231 | if( pProbe->plan.nRow<pBaseline->plan.nRow ) return 1; |
| 102232 | return 0; |
| 102233 | } |
| 102234 | |
| 102235 | /* |
| 102236 | ** Initialize a preallocated WhereClause structure. |
| 102237 | */ |
| 102238 | static void whereClauseInit( |
| @@ -103306,11 +103381,12 @@ | |
| 103381 | Table *pTab = pIdx->pTable; |
| 103382 | int i; |
| 103383 | if( pIdx->onError==OE_None ) return 0; |
| 103384 | for(i=nSkip; i<pIdx->nColumn; i++){ |
| 103385 | int j = pIdx->aiColumn[i]; |
| 103386 | assert( j>=0 && j<pTab->nCol ); |
| 103387 | if( pTab->aCol[j].notNull==0 ) return 0; |
| 103388 | } |
| 103389 | return 1; |
| 103390 | } |
| 103391 | |
| 103392 | /* |
| @@ -103370,11 +103446,12 @@ | |
| 103446 | int nEqCol /* Number of index columns with == */ |
| 103447 | ){ |
| 103448 | Bitmask mask = 0; /* Mask of unaccounted for pDistinct exprs */ |
| 103449 | int i; /* Iterator variable */ |
| 103450 | |
| 103451 | assert( pDistinct!=0 ); |
| 103452 | if( pIdx->zName==0 || pDistinct->nExpr>=BMS ) return 0; |
| 103453 | testcase( pDistinct->nExpr==BMS-1 ); |
| 103454 | |
| 103455 | /* Loop through all the expressions in the distinct list. If any of them |
| 103456 | ** are not simple column references, return early. Otherwise, test if the |
| 103457 | ** WHERE clause contains a "col=X" clause. If it does, the expression |
| @@ -103472,170 +103549,10 @@ | |
| 103549 | } |
| 103550 | |
| 103551 | return 0; |
| 103552 | } |
| 103553 | |
| 103554 | /* |
| 103555 | ** Prepare a crude estimate of the logarithm of the input value. |
| 103556 | ** The results need not be exact. This is only used for estimating |
| 103557 | ** the total cost of performing operations with O(logN) or O(NlogN) |
| 103558 | ** complexity. Because N is just a guess, it is no great tragedy if |
| @@ -103785,10 +103702,11 @@ | |
| 103702 | WHERETRACE(("... multi-index OR cost=%.9g nrow=%.9g\n", rTotal, nRow)); |
| 103703 | if( rTotal<p->cost.rCost ){ |
| 103704 | p->cost.rCost = rTotal; |
| 103705 | p->cost.used = used; |
| 103706 | p->cost.plan.nRow = nRow; |
| 103707 | p->cost.plan.nOBSat = p->i ? p->aLevel[p->i-1].plan.nOBSat : 0; |
| 103708 | p->cost.plan.wsFlags = flags; |
| 103709 | p->cost.plan.u.pTerm = pTerm; |
| 103710 | } |
| 103711 | } |
| 103712 | } |
| @@ -104327,11 +104245,14 @@ | |
| 104245 | }else{ |
| 104246 | p->cost.rCost = rCost; |
| 104247 | } |
| 104248 | p->cost.plan.u.pVtabIdx = pIdxInfo; |
| 104249 | if( pIdxInfo->orderByConsumed ){ |
| 104250 | p->cost.plan.wsFlags |= WHERE_ORDERED; |
| 104251 | p->cost.plan.nOBSat = nOrderBy; |
| 104252 | }else{ |
| 104253 | p->cost.plan.nOBSat = p->i ? p->aLevel[p->i-1].plan.nOBSat : 0; |
| 104254 | } |
| 104255 | p->cost.plan.nEq = 0; |
| 104256 | pIdxInfo->nOrderBy = nOrderBy; |
| 104257 | |
| 104258 | /* Try to find a more efficient access pattern by using multiple indexes |
| @@ -104750,20 +104671,26 @@ | |
| 104671 | WhereLevel *pLevel = &p->aLevel[p->i-1]; |
| 104672 | Index *pIdx; |
| 104673 | u8 sortOrder; |
| 104674 | for(i=p->i-1; i>=0; i--, pLevel--){ |
| 104675 | if( pLevel->iTabCur!=iTab ) continue; |
| 104676 | if( (pLevel->plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){ |
| 104677 | return 1; |
| 104678 | } |
| 104679 | if( (pLevel->plan.wsFlags & WHERE_ORDERED)==0 ){ |
| 104680 | return 0; |
| 104681 | } |
| 104682 | if( (pIdx = pLevel->plan.u.pIdx)!=0 ){ |
| 104683 | if( iCol<0 ){ |
| 104684 | sortOrder = 0; |
| 104685 | testcase( (pLevel->plan.wsFlags & WHERE_REVERSE)!=0 ); |
| 104686 | }else{ |
| 104687 | int n = pIdx->nColumn; |
| 104688 | for(j=0; j<n; j++){ |
| 104689 | if( iCol==pIdx->aiColumn[j] ) break; |
| 104690 | } |
| 104691 | if( j>=n ) return 0; |
| 104692 | sortOrder = pIdx->aSortOrder[j]; |
| 104693 | testcase( (pLevel->plan.wsFlags & WHERE_REVERSE)!=0 ); |
| 104694 | } |
| 104695 | }else{ |
| 104696 | if( iCol!=(-1) ) return 0; |
| @@ -104792,13 +104719,10 @@ | |
| 104719 | static int isOrderedTerm(WhereBestIdx *p, WhereTerm *pTerm, int *pbRev){ |
| 104720 | Expr *pExpr = pTerm->pExpr; |
| 104721 | assert( pExpr->op==TK_EQ ); |
| 104722 | assert( pExpr->pLeft!=0 && pExpr->pLeft->op==TK_COLUMN ); |
| 104723 | assert( pExpr->pRight!=0 ); |
| 104724 | if( pTerm->prereqRight==0 ){ |
| 104725 | return 1; /* RHS of the == is a constant */ |
| 104726 | } |
| 104727 | if( pExpr->pRight->op==TK_COLUMN |
| 104728 | && isOrderedColumn(p, pExpr->pRight->iTable, pExpr->pRight->iColumn, pbRev) |
| @@ -104808,10 +104732,177 @@ | |
| 104732 | |
| 104733 | /* If we cannot prove that the constraint is ordered, assume it is not */ |
| 104734 | return 0; |
| 104735 | } |
| 104736 | |
| 104737 | /* |
| 104738 | ** This routine decides if pIdx can be used to satisfy the ORDER BY |
| 104739 | ** clause, either in whole or in part. The return value is the |
| 104740 | ** cumulative number of terms in the ORDER BY clause that are satisfied |
| 104741 | ** by the index pIdx and other indices in outer loops. |
| 104742 | ** |
| 104743 | ** The table being queried has a cursor number of "base". pIdx is the |
| 104744 | ** index that is postulated for use to access the table. |
| 104745 | ** |
| 104746 | ** nEqCol is the number of columns of pIdx that are used as equality |
| 104747 | ** constraints and where the other side of the == is an ordered column |
| 104748 | ** or constant. An "order column" in the previous sentence means a column |
| 104749 | ** in table from an outer loop whose values will always appear in the |
| 104750 | ** correct order due to othre index, or because the outer loop generates |
| 104751 | ** a unique result. Any of the first nEqCol columns of pIdx may be missing |
| 104752 | ** from the ORDER BY clause and the match can still be a success. |
| 104753 | ** |
| 104754 | ** The *pbRev value is set to 0 order 1 depending on whether or not |
| 104755 | ** pIdx should be run in the forward order or in reverse order. |
| 104756 | */ |
| 104757 | static int isSortingIndex( |
| 104758 | WhereBestIdx *p, /* Best index search context */ |
| 104759 | Index *pIdx, /* The index we are testing */ |
| 104760 | int base, /* Cursor number for the table to be sorted */ |
| 104761 | int nEqCol, /* Number of index columns with ordered == constraints */ |
| 104762 | int wsFlags, /* Index usages flags */ |
| 104763 | int bOuterRev, /* True if outer loops scan in reverse order */ |
| 104764 | int *pbRev /* Set to 1 for reverse-order scan of pIdx */ |
| 104765 | ){ |
| 104766 | int i; /* Number of pIdx terms used */ |
| 104767 | int j; /* Number of ORDER BY terms satisfied */ |
| 104768 | int sortOrder = 0; /* XOR of index and ORDER BY sort direction */ |
| 104769 | int nTerm; /* Number of ORDER BY terms */ |
| 104770 | struct ExprList_item *pTerm; /* A term of the ORDER BY clause */ |
| 104771 | ExprList *pOrderBy; /* The ORDER BY clause */ |
| 104772 | Parse *pParse = p->pParse; /* Parser context */ |
| 104773 | sqlite3 *db = pParse->db; /* Database connection */ |
| 104774 | int nPriorSat; /* ORDER BY terms satisfied by outer loops */ |
| 104775 | int seenRowid = 0; /* True if an ORDER BY rowid term is seen */ |
| 104776 | int nEqOneRow; /* Idx columns that ref unique values */ |
| 104777 | |
| 104778 | if( p->i==0 ){ |
| 104779 | nPriorSat = 0; |
| 104780 | }else{ |
| 104781 | nPriorSat = p->aLevel[p->i-1].plan.nOBSat; |
| 104782 | if( OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return nPriorSat; |
| 104783 | } |
| 104784 | if( nEqCol==0 ){ |
| 104785 | if( p->i && (p->aLevel[p->i-1].plan.wsFlags & WHERE_ORDERED)==0 ){ |
| 104786 | return nPriorSat; |
| 104787 | } |
| 104788 | nEqOneRow = 0; |
| 104789 | }else if( p->i==0 || (p->aLevel[p->i-1].plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){ |
| 104790 | nEqOneRow = nEqCol; |
| 104791 | }else{ |
| 104792 | sortOrder = bOuterRev; |
| 104793 | nEqOneRow = -1; |
| 104794 | } |
| 104795 | pOrderBy = p->pOrderBy; |
| 104796 | assert( pOrderBy!=0 ); |
| 104797 | if( wsFlags & WHERE_COLUMN_IN ) return nPriorSat; |
| 104798 | if( pIdx->bUnordered ) return nPriorSat; |
| 104799 | nTerm = pOrderBy->nExpr; |
| 104800 | assert( nTerm>0 ); |
| 104801 | |
| 104802 | /* Argument pIdx must either point to a 'real' named index structure, |
| 104803 | ** or an index structure allocated on the stack by bestBtreeIndex() to |
| 104804 | ** represent the rowid index that is part of every table. */ |
| 104805 | assert( pIdx->zName || (pIdx->nColumn==1 && pIdx->aiColumn[0]==-1) ); |
| 104806 | |
| 104807 | /* Match terms of the ORDER BY clause against columns of |
| 104808 | ** the index. |
| 104809 | ** |
| 104810 | ** Note that indices have pIdx->nColumn regular columns plus |
| 104811 | ** one additional column containing the rowid. The rowid column |
| 104812 | ** of the index is also allowed to match against the ORDER BY |
| 104813 | ** clause. |
| 104814 | */ |
| 104815 | for(i=0,j=nPriorSat,pTerm=&pOrderBy->a[j]; j<nTerm; i++){ |
| 104816 | Expr *pExpr; /* The expression of the ORDER BY pTerm */ |
| 104817 | CollSeq *pColl; /* The collating sequence of pExpr */ |
| 104818 | int termSortOrder; /* Sort order for this term */ |
| 104819 | int iColumn; /* The i-th column of the index. -1 for rowid */ |
| 104820 | int iSortOrder; /* 1 for DESC, 0 for ASC on the i-th index term */ |
| 104821 | const char *zColl; /* Name of the collating sequence for i-th index term */ |
| 104822 | |
| 104823 | assert( i<=pIdx->nColumn ); |
| 104824 | pExpr = pTerm->pExpr; |
| 104825 | if( pExpr->op!=TK_COLUMN || pExpr->iTable!=base ){ |
| 104826 | /* Can not use an index sort on anything that is not a column in the |
| 104827 | ** left-most table of the FROM clause */ |
| 104828 | break; |
| 104829 | } |
| 104830 | pColl = sqlite3ExprCollSeq(pParse, pExpr); |
| 104831 | if( !pColl ){ |
| 104832 | pColl = db->pDfltColl; |
| 104833 | } |
| 104834 | if( pIdx->zName && i<pIdx->nColumn ){ |
| 104835 | iColumn = pIdx->aiColumn[i]; |
| 104836 | if( iColumn==pIdx->pTable->iPKey ){ |
| 104837 | iColumn = -1; |
| 104838 | } |
| 104839 | iSortOrder = pIdx->aSortOrder[i]; |
| 104840 | zColl = pIdx->azColl[i]; |
| 104841 | }else{ |
| 104842 | iColumn = -1; |
| 104843 | iSortOrder = 0; |
| 104844 | zColl = pColl->zName; |
| 104845 | } |
| 104846 | if( pExpr->iColumn!=iColumn || sqlite3StrICmp(pColl->zName, zColl) ){ |
| 104847 | /* Term j of the ORDER BY clause does not match column i of the index */ |
| 104848 | if( i<nEqCol ){ |
| 104849 | /* If an index column that is constrained by == fails to match an |
| 104850 | ** ORDER BY term, that is OK. Just ignore that column of the index |
| 104851 | */ |
| 104852 | continue; |
| 104853 | }else if( i==pIdx->nColumn ){ |
| 104854 | /* Index column i is the rowid. All other terms match. */ |
| 104855 | break; |
| 104856 | }else{ |
| 104857 | /* If an index column fails to match and is not constrained by == |
| 104858 | ** then the index cannot satisfy the ORDER BY constraint. |
| 104859 | */ |
| 104860 | return nPriorSat; |
| 104861 | } |
| 104862 | } |
| 104863 | assert( pIdx->aSortOrder!=0 || iColumn==-1 ); |
| 104864 | assert( pTerm->sortOrder==0 || pTerm->sortOrder==1 ); |
| 104865 | assert( iSortOrder==0 || iSortOrder==1 ); |
| 104866 | termSortOrder = iSortOrder ^ pTerm->sortOrder; |
| 104867 | if( i>nEqOneRow ){ |
| 104868 | if( termSortOrder!=sortOrder ){ |
| 104869 | /* Indices can only be used if all ORDER BY terms past the |
| 104870 | ** equality constraints have the correct DESC or ASC. */ |
| 104871 | break; |
| 104872 | } |
| 104873 | }else{ |
| 104874 | sortOrder = termSortOrder; |
| 104875 | } |
| 104876 | j++; |
| 104877 | pTerm++; |
| 104878 | if( iColumn<0 ){ |
| 104879 | seenRowid = 1; |
| 104880 | break; |
| 104881 | } |
| 104882 | } |
| 104883 | *pbRev = sortOrder; |
| 104884 | |
| 104885 | /* If there was an "ORDER BY rowid" term that matched, or it is only |
| 104886 | ** possible for a single row from this table to match, then skip over |
| 104887 | ** any additional ORDER BY terms dealing with this table. |
| 104888 | */ |
| 104889 | if( seenRowid || |
| 104890 | ( (wsFlags & WHERE_COLUMN_NULL)==0 |
| 104891 | && i>=pIdx->nColumn |
| 104892 | && indexIsUniqueNotNull(pIdx, nEqCol) |
| 104893 | ) |
| 104894 | ){ |
| 104895 | /* Advance j over additional ORDER BY terms associated with base */ |
| 104896 | WhereMaskSet *pMS = p->pWC->pMaskSet; |
| 104897 | Bitmask m = ~getMask(pMS, base); |
| 104898 | while( j<nTerm && (exprTableUsage(pMS, pOrderBy->a[j].pExpr)&m)==0 ){ |
| 104899 | j++; |
| 104900 | } |
| 104901 | } |
| 104902 | return j; |
| 104903 | } |
| 104904 | |
| 104905 | /* |
| 104906 | ** Find the best query plan for accessing a particular table. Write the |
| 104907 | ** best query plan and its cost into the p->cost. |
| 104908 | ** |
| @@ -104902,22 +104993,19 @@ | |
| 104993 | |
| 104994 | /* Loop over all indices looking for the best one to use |
| 104995 | */ |
| 104996 | for(; pProbe; pIdx=pProbe=pProbe->pNext){ |
| 104997 | const tRowcnt * const aiRowEst = pProbe->aiRowEst; |
| 104998 | WhereCost pc; /* Cost of using pProbe */ |
| 104999 | double log10N = (double)1; /* base-10 logarithm of nRow (inexact) */ |
| 105000 | int bRev = 2; /* 0=forward scan. 1=reverse. 2=undecided */ |
| 105001 | |
| 105002 | /* The following variables are populated based on the properties of |
| 105003 | ** index being evaluated. They are then used to determine the expected |
| 105004 | ** cost and number of rows returned. |
| 105005 | ** |
| 105006 | ** pc.plan.nEq: |
| 105007 | ** Number of equality terms that can be implemented using the index. |
| 105008 | ** In other words, the number of initial fields in the index that |
| 105009 | ** are used in == or IN or NOT NULL constraints of the WHERE clause. |
| 105010 | ** |
| 105011 | ** nInMul: |
| @@ -104960,11 +105048,11 @@ | |
| 105048 | ** bSort: |
| 105049 | ** Boolean. True if there is an ORDER BY clause that will require an |
| 105050 | ** external sort (i.e. scanning the index being evaluated will not |
| 105051 | ** correctly order records). |
| 105052 | ** |
| 105053 | ** bDist: |
| 105054 | ** Boolean. True if there is a DISTINCT clause that will require an |
| 105055 | ** external btree. |
| 105056 | ** |
| 105057 | ** bLookup: |
| 105058 | ** Boolean. True if a table lookup is required for each index entry |
| @@ -104979,132 +105067,148 @@ | |
| 105067 | ** both available in the index. |
| 105068 | ** |
| 105069 | ** SELECT a, b FROM tbl WHERE a = 1; |
| 105070 | ** SELECT a, b, c FROM tbl WHERE a = 1; |
| 105071 | */ |
| 105072 | int nOrdered; /* Number of ordered terms matching index */ |
| 105073 | int bInEst = 0; /* True if "x IN (SELECT...)" seen */ |
| 105074 | int nInMul = 1; /* Number of distinct equalities to lookup */ |
| 105075 | double rangeDiv = (double)1; /* Estimated reduction in search space */ |
| 105076 | int nBound = 0; /* Number of range constraints seen */ |
| 105077 | int bSort; /* True if external sort required */ |
| 105078 | int bDist; /* True if index cannot help with DISTINCT */ |
| 105079 | int bLookup = 0; /* True if not a covering index */ |
| 105080 | int nPriorSat; /* ORDER BY terms satisfied by outer loops */ |
| 105081 | int nOrderBy; /* Number of ORDER BY terms */ |
| 105082 | WhereTerm *pTerm; /* A single term of the WHERE clause */ |
| 105083 | #ifdef SQLITE_ENABLE_STAT3 |
| 105084 | WhereTerm *pFirstTerm = 0; /* First term matching the index */ |
| 105085 | #endif |
| 105086 | |
| 105087 | memset(&pc, 0, sizeof(pc)); |
| 105088 | nOrderBy = p->pOrderBy ? p->pOrderBy->nExpr : 0; |
| 105089 | if( p->i ){ |
| 105090 | nPriorSat = pc.plan.nOBSat = p->aLevel[p->i-1].plan.nOBSat; |
| 105091 | bSort = nPriorSat<nOrderBy; |
| 105092 | bDist = 0; |
| 105093 | }else{ |
| 105094 | nPriorSat = pc.plan.nOBSat = 0; |
| 105095 | bSort = nOrderBy>0; |
| 105096 | bDist = p->pDistinct!=0; |
| 105097 | } |
| 105098 | |
| 105099 | /* Determine the values of pc.plan.nEq and nInMul */ |
| 105100 | for(pc.plan.nEq=nOrdered=0; pc.plan.nEq<pProbe->nColumn; pc.plan.nEq++){ |
| 105101 | int j = pProbe->aiColumn[pc.plan.nEq]; |
| 105102 | pTerm = findTerm(pWC, iCur, j, p->notReady, eqTermMask, pIdx); |
| 105103 | if( pTerm==0 ) break; |
| 105104 | pc.plan.wsFlags |= (WHERE_COLUMN_EQ|WHERE_ROWID_EQ); |
| 105105 | testcase( pTerm->pWC!=pWC ); |
| 105106 | if( pTerm->eOperator & WO_IN ){ |
| 105107 | Expr *pExpr = pTerm->pExpr; |
| 105108 | pc.plan.wsFlags |= WHERE_COLUMN_IN; |
| 105109 | if( ExprHasProperty(pExpr, EP_xIsSelect) ){ |
| 105110 | /* "x IN (SELECT ...)": Assume the SELECT returns 25 rows */ |
| 105111 | nInMul *= 25; |
| 105112 | bInEst = 1; |
| 105113 | }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ |
| 105114 | /* "x IN (value, value, ...)" */ |
| 105115 | nInMul *= pExpr->x.pList->nExpr; |
| 105116 | } |
| 105117 | }else if( pTerm->eOperator & WO_ISNULL ){ |
| 105118 | pc.plan.wsFlags |= WHERE_COLUMN_NULL; |
| 105119 | if( pc.plan.nEq==nOrdered ) nOrdered++; |
| 105120 | }else if( bSort && pc.plan.nEq==nOrdered |
| 105121 | && isOrderedTerm(p,pTerm,&bRev) ){ |
| 105122 | nOrdered++; |
| 105123 | } |
| 105124 | #ifdef SQLITE_ENABLE_STAT3 |
| 105125 | if( pc.plan.nEq==0 && pProbe->aSample ) pFirstTerm = pTerm; |
| 105126 | #endif |
| 105127 | pc.used |= pTerm->prereqRight; |
| 105128 | } |
| 105129 | |
| 105130 | /* If the index being considered is UNIQUE, and there is an equality |
| 105131 | ** constraint for all columns in the index, then this search will find |
| 105132 | ** at most a single row. In this case set the WHERE_UNIQUE flag to |
| 105133 | ** indicate this to the caller. |
| 105134 | ** |
| 105135 | ** Otherwise, if the search may find more than one row, test to see if |
| 105136 | ** there is a range constraint on indexed column (pc.plan.nEq+1) that can be |
| 105137 | ** optimized using the index. |
| 105138 | */ |
| 105139 | if( pc.plan.nEq==pProbe->nColumn && pProbe->onError!=OE_None ){ |
| 105140 | testcase( pc.plan.wsFlags & WHERE_COLUMN_IN ); |
| 105141 | testcase( pc.plan.wsFlags & WHERE_COLUMN_NULL ); |
| 105142 | if( (pc.plan.wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0 ){ |
| 105143 | pc.plan.wsFlags |= WHERE_UNIQUE; |
| 105144 | if( p->i==0 || (p->aLevel[p->i-1].plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){ |
| 105145 | pc.plan.wsFlags |= WHERE_ALL_UNIQUE; |
| 105146 | } |
| 105147 | } |
| 105148 | }else if( pProbe->bUnordered==0 ){ |
| 105149 | int j; |
| 105150 | j = (pc.plan.nEq==pProbe->nColumn ? -1 : pProbe->aiColumn[pc.plan.nEq]); |
| 105151 | if( findTerm(pWC, iCur, j, p->notReady, WO_LT|WO_LE|WO_GT|WO_GE, pIdx) ){ |
| 105152 | WhereTerm *pTop, *pBtm; |
| 105153 | pTop = findTerm(pWC, iCur, j, p->notReady, WO_LT|WO_LE, pIdx); |
| 105154 | pBtm = findTerm(pWC, iCur, j, p->notReady, WO_GT|WO_GE, pIdx); |
| 105155 | whereRangeScanEst(pParse, pProbe, pc.plan.nEq, pBtm, pTop, &rangeDiv); |
| 105156 | if( pTop ){ |
| 105157 | nBound = 1; |
| 105158 | pc.plan.wsFlags |= WHERE_TOP_LIMIT; |
| 105159 | pc.used |= pTop->prereqRight; |
| 105160 | testcase( pTop->pWC!=pWC ); |
| 105161 | } |
| 105162 | if( pBtm ){ |
| 105163 | nBound++; |
| 105164 | pc.plan.wsFlags |= WHERE_BTM_LIMIT; |
| 105165 | pc.used |= pBtm->prereqRight; |
| 105166 | testcase( pBtm->pWC!=pWC ); |
| 105167 | } |
| 105168 | pc.plan.wsFlags |= (WHERE_COLUMN_RANGE|WHERE_ROWID_RANGE); |
| 105169 | } |
| 105170 | } |
| 105171 | |
| 105172 | /* If there is an ORDER BY clause and the index being considered will |
| 105173 | ** naturally scan rows in the required order, set the appropriate flags |
| 105174 | ** in pc.plan.wsFlags. Otherwise, if there is an ORDER BY clause but |
| 105175 | ** the index will scan rows in a different order, set the bSort |
| 105176 | ** variable. */ |
| 105177 | assert( bRev>=0 && bRev<=2 ); |
| 105178 | if( bSort ){ |
| 105179 | testcase( bRev==0 ); |
| 105180 | testcase( bRev==1 ); |
| 105181 | testcase( bRev==2 ); |
| 105182 | pc.plan.nOBSat = isSortingIndex(p, pProbe, iCur, nOrdered, |
| 105183 | pc.plan.wsFlags, bRev&1, &bRev); |
| 105184 | if( nPriorSat<pc.plan.nOBSat || (pc.plan.wsFlags & WHERE_UNIQUE)!=0 ){ |
| 105185 | pc.plan.wsFlags |= WHERE_ORDERED; |
| 105186 | } |
| 105187 | if( nOrderBy==pc.plan.nOBSat ){ |
| 105188 | bSort = 0; |
| 105189 | pc.plan.wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE; |
| 105190 | } |
| 105191 | if( bRev & 1 ) pc.plan.wsFlags |= WHERE_REVERSE; |
| 105192 | } |
| 105193 | |
| 105194 | /* If there is a DISTINCT qualifier and this index will scan rows in |
| 105195 | ** order of the DISTINCT expressions, clear bDist and set the appropriate |
| 105196 | ** flags in pc.plan.wsFlags. */ |
| 105197 | if( bDist |
| 105198 | && isDistinctIndex(pParse, pWC, pProbe, iCur, p->pDistinct, pc.plan.nEq) |
| 105199 | && (pc.plan.wsFlags & WHERE_COLUMN_IN)==0 |
| 105200 | ){ |
| 105201 | bDist = 0; |
| 105202 | pc.plan.wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_DISTINCT; |
| 105203 | } |
| 105204 | |
| 105205 | /* If currently calculating the cost of using an index (not the IPK |
| 105206 | ** index), determine if all required column data may be obtained without |
| 105207 | ** using the main table (i.e. if the index is a covering |
| 105208 | ** index for this query). If it is, set the WHERE_IDX_ONLY flag in |
| 105209 | ** pc.plan.wsFlags. Otherwise, set the bLookup variable to true. */ |
| 105210 | if( pIdx ){ |
| 105211 | Bitmask m = pSrc->colUsed; |
| 105212 | int j; |
| 105213 | for(j=0; j<pIdx->nColumn; j++){ |
| 105214 | int x = pIdx->aiColumn[j]; |
| @@ -105111,51 +105215,54 @@ | |
| 105215 | if( x<BMS-1 ){ |
| 105216 | m &= ~(((Bitmask)1)<<x); |
| 105217 | } |
| 105218 | } |
| 105219 | if( m==0 ){ |
| 105220 | pc.plan.wsFlags |= WHERE_IDX_ONLY; |
| 105221 | }else{ |
| 105222 | bLookup = 1; |
| 105223 | } |
| 105224 | } |
| 105225 | |
| 105226 | /* |
| 105227 | ** Estimate the number of rows of output. For an "x IN (SELECT...)" |
| 105228 | ** constraint, do not let the estimate exceed half the rows in the table. |
| 105229 | */ |
| 105230 | pc.plan.nRow = (double)(aiRowEst[pc.plan.nEq] * nInMul); |
| 105231 | if( bInEst && pc.plan.nRow*2>aiRowEst[0] ){ |
| 105232 | pc.plan.nRow = aiRowEst[0]/2; |
| 105233 | nInMul = (int)(pc.plan.nRow / aiRowEst[pc.plan.nEq]); |
| 105234 | } |
| 105235 | |
| 105236 | #ifdef SQLITE_ENABLE_STAT3 |
| 105237 | /* If the constraint is of the form x=VALUE or x IN (E1,E2,...) |
| 105238 | ** and we do not think that values of x are unique and if histogram |
| 105239 | ** data is available for column x, then it might be possible |
| 105240 | ** to get a better estimate on the number of rows based on |
| 105241 | ** VALUE and how common that value is according to the histogram. |
| 105242 | */ |
| 105243 | if( pc.plan.nRow>(double)1 && pc.plan.nEq==1 |
| 105244 | && pFirstTerm!=0 && aiRowEst[1]>1 ){ |
| 105245 | assert( (pFirstTerm->eOperator & (WO_EQ|WO_ISNULL|WO_IN))!=0 ); |
| 105246 | if( pFirstTerm->eOperator & (WO_EQ|WO_ISNULL) ){ |
| 105247 | testcase( pFirstTerm->eOperator==WO_EQ ); |
| 105248 | testcase( pFirstTerm->eOperator==WO_ISNULL ); |
| 105249 | whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, |
| 105250 | &pc.plan.nRow); |
| 105251 | }else if( bInEst==0 ){ |
| 105252 | assert( pFirstTerm->eOperator==WO_IN ); |
| 105253 | whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, |
| 105254 | &pc.plan.nRow); |
| 105255 | } |
| 105256 | } |
| 105257 | #endif /* SQLITE_ENABLE_STAT3 */ |
| 105258 | |
| 105259 | /* Adjust the number of output rows and downward to reflect rows |
| 105260 | ** that are excluded by range constraints. |
| 105261 | */ |
| 105262 | pc.plan.nRow = pc.plan.nRow/rangeDiv; |
| 105263 | if( pc.plan.nRow<1 ) pc.plan.nRow = 1; |
| 105264 | |
| 105265 | /* Experiments run on real SQLite databases show that the time needed |
| 105266 | ** to do a binary search to locate a row in a table or index is roughly |
| 105267 | ** log10(N) times the time to move from one row to the next row within |
| 105268 | ** a table or index. The actual times can vary, with the size of |
| @@ -105166,57 +105273,58 @@ | |
| 105273 | ** The ANALYZE command and the sqlite_stat1 and sqlite_stat3 tables do |
| 105274 | ** not give us data on the relative sizes of table and index records. |
| 105275 | ** So this computation assumes table records are about twice as big |
| 105276 | ** as index records |
| 105277 | */ |
| 105278 | if( (pc.plan.wsFlags&~(WHERE_REVERSE|WHERE_ORDERED))==WHERE_IDX_ONLY |
| 105279 | && (pWC->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 |
| 105280 | && sqlite3GlobalConfig.bUseCis |
| 105281 | && OptimizationEnabled(pParse->db, SQLITE_CoverIdxScan) |
| 105282 | ){ |
| 105283 | /* This index is not useful for indexing, but it is a covering index. |
| 105284 | ** A full-scan of the index might be a little faster than a full-scan |
| 105285 | ** of the table, so give this case a cost slightly less than a table |
| 105286 | ** scan. */ |
| 105287 | pc.rCost = aiRowEst[0]*3 + pProbe->nColumn; |
| 105288 | pc.plan.wsFlags |= WHERE_COVER_SCAN|WHERE_COLUMN_RANGE; |
| 105289 | }else if( (pc.plan.wsFlags & WHERE_NOT_FULLSCAN)==0 ){ |
| 105290 | /* The cost of a full table scan is a number of move operations equal |
| 105291 | ** to the number of rows in the table. |
| 105292 | ** |
| 105293 | ** We add an additional 4x penalty to full table scans. This causes |
| 105294 | ** the cost function to err on the side of choosing an index over |
| 105295 | ** choosing a full scan. This 4x full-scan penalty is an arguable |
| 105296 | ** decision and one which we expect to revisit in the future. But |
| 105297 | ** it seems to be working well enough at the moment. |
| 105298 | */ |
| 105299 | pc.rCost = aiRowEst[0]*4; |
| 105300 | pc.plan.wsFlags &= ~WHERE_IDX_ONLY; |
| 105301 | if( pIdx ) pc.plan.wsFlags &= ~WHERE_ORDERED; |
| 105302 | }else{ |
| 105303 | log10N = estLog(aiRowEst[0]); |
| 105304 | pc.rCost = pc.plan.nRow; |
| 105305 | if( pIdx ){ |
| 105306 | if( bLookup ){ |
| 105307 | /* For an index lookup followed by a table lookup: |
| 105308 | ** nInMul index searches to find the start of each index range |
| 105309 | ** + nRow steps through the index |
| 105310 | ** + nRow table searches to lookup the table entry using the rowid |
| 105311 | */ |
| 105312 | pc.rCost += (nInMul + pc.plan.nRow)*log10N; |
| 105313 | }else{ |
| 105314 | /* For a covering index: |
| 105315 | ** nInMul index searches to find the initial entry |
| 105316 | ** + nRow steps through the index |
| 105317 | */ |
| 105318 | pc.rCost += nInMul*log10N; |
| 105319 | } |
| 105320 | }else{ |
| 105321 | /* For a rowid primary key lookup: |
| 105322 | ** nInMult table searches to find the initial entry for each range |
| 105323 | ** + nRow steps through the table |
| 105324 | */ |
| 105325 | pc.rCost += nInMul*log10N; |
| 105326 | } |
| 105327 | } |
| 105328 | |
| 105329 | /* Add in the estimated cost of sorting the result. Actual experimental |
| 105330 | ** measurements of sorting performance in SQLite show that sorting time |
| @@ -105223,14 +105331,16 @@ | |
| 105331 | ** adds C*N*log10(N) to the cost, where N is the number of rows to be |
| 105332 | ** sorted and C is a factor between 1.95 and 4.3. We will split the |
| 105333 | ** difference and select C of 3.0. |
| 105334 | */ |
| 105335 | if( bSort ){ |
| 105336 | double m = estLog(pc.plan.nRow*(nOrderBy - pc.plan.nOBSat)/nOrderBy); |
| 105337 | m *= (double)(pc.plan.nOBSat ? 2 : 3); |
| 105338 | pc.rCost += pc.plan.nRow*m; |
| 105339 | } |
| 105340 | if( bDist ){ |
| 105341 | pc.rCost += pc.plan.nRow*estLog(pc.plan.nRow)*3; |
| 105342 | } |
| 105343 | |
| 105344 | /**** Cost of using this index has now been computed ****/ |
| 105345 | |
| 105346 | /* If there are additional constraints on this table that cannot |
| @@ -105247,29 +105357,29 @@ | |
| 105357 | ** tables that are not in outer loops. If notReady is used here instead |
| 105358 | ** of notValid, then a optimal index that depends on inner joins loops |
| 105359 | ** might be selected even when there exists an optimal index that has |
| 105360 | ** no such dependency. |
| 105361 | */ |
| 105362 | if( pc.plan.nRow>2 && pc.rCost<=p->cost.rCost ){ |
| 105363 | int k; /* Loop counter */ |
| 105364 | int nSkipEq = pc.plan.nEq; /* Number of == constraints to skip */ |
| 105365 | int nSkipRange = nBound; /* Number of < constraints to skip */ |
| 105366 | Bitmask thisTab; /* Bitmap for pSrc */ |
| 105367 | |
| 105368 | thisTab = getMask(pWC->pMaskSet, iCur); |
| 105369 | for(pTerm=pWC->a, k=pWC->nTerm; pc.plan.nRow>2 && k; k--, pTerm++){ |
| 105370 | if( pTerm->wtFlags & TERM_VIRTUAL ) continue; |
| 105371 | if( (pTerm->prereqAll & p->notValid)!=thisTab ) continue; |
| 105372 | if( pTerm->eOperator & (WO_EQ|WO_IN|WO_ISNULL) ){ |
| 105373 | if( nSkipEq ){ |
| 105374 | /* Ignore the first pc.plan.nEq equality matches since the index |
| 105375 | ** has already accounted for these */ |
| 105376 | nSkipEq--; |
| 105377 | }else{ |
| 105378 | /* Assume each additional equality match reduces the result |
| 105379 | ** set size by a factor of 10 */ |
| 105380 | pc.plan.nRow /= 10; |
| 105381 | } |
| 105382 | }else if( pTerm->eOperator & (WO_LT|WO_LE|WO_GT|WO_GE) ){ |
| 105383 | if( nSkipRange ){ |
| 105384 | /* Ignore the first nSkipRange range constraints since the index |
| 105385 | ** has already accounted for these */ |
| @@ -105279,43 +105389,38 @@ | |
| 105389 | ** set size by a factor of 3. Indexed range constraints reduce |
| 105390 | ** the search space by a larger factor: 4. We make indexed range |
| 105391 | ** more selective intentionally because of the subjective |
| 105392 | ** observation that indexed range constraints really are more |
| 105393 | ** selective in practice, on average. */ |
| 105394 | pc.plan.nRow /= 3; |
| 105395 | } |
| 105396 | }else if( pTerm->eOperator!=WO_NOOP ){ |
| 105397 | /* Any other expression lowers the output row count by half */ |
| 105398 | pc.plan.nRow /= 2; |
| 105399 | } |
| 105400 | } |
| 105401 | if( pc.plan.nRow<2 ) pc.plan.nRow = 2; |
| 105402 | } |
| 105403 | |
| 105404 | |
| 105405 | WHERETRACE(( |
| 105406 | "%s(%s):\n" |
| 105407 | " nEq=%d nInMul=%d rangeDiv=%d bSort=%d bLookup=%d wsFlags=0x%08x\n" |
| 105408 | " notReady=0x%llx log10N=%.1f nRow=%.1f cost=%.1f\n" |
| 105409 | " used=0x%llx nOrdered=%d nOBSat=%d\n", |
| 105410 | pSrc->pTab->zName, (pIdx ? pIdx->zName : "ipk"), |
| 105411 | pc.plan.nEq, nInMul, (int)rangeDiv, bSort, bLookup, pc.plan.wsFlags, |
| 105412 | p->notReady, log10N, pc.plan.nRow, pc.rCost, pc.used, nOrdered, |
| 105413 | pc.plan.nOBSat |
| 105414 | )); |
| 105415 | |
| 105416 | /* If this index is the best we have seen so far, then record this |
| 105417 | ** index and its cost in the p->cost structure. |
| 105418 | */ |
| 105419 | if( (!pIdx || pc.plan.wsFlags) && compareCost(&pc, &p->cost) ){ |
| 105420 | p->cost = pc; |
| 105421 | p->cost.plan.wsFlags &= wsFlagMask; |
| 105422 | p->cost.plan.u.pIdx = pIdx; |
| 105423 | } |
| 105424 | |
| 105425 | /* If there was an INDEXED BY clause, then only that one index is |
| 105426 | ** considered. */ |
| @@ -105333,21 +105438,19 @@ | |
| 105438 | ** SQLite outputs rows in in the absence of an ORDER BY clause. */ |
| 105439 | if( !p->pOrderBy && pParse->db->flags & SQLITE_ReverseOrder ){ |
| 105440 | p->cost.plan.wsFlags |= WHERE_REVERSE; |
| 105441 | } |
| 105442 | |
| 105443 | assert( p->pOrderBy || (p->cost.plan.wsFlags&WHERE_ORDERED)==0 ); |
| 105444 | assert( p->cost.plan.u.pIdx==0 || (p->cost.plan.wsFlags&WHERE_ROWID_EQ)==0 ); |
| 105445 | assert( pSrc->pIndex==0 |
| 105446 | || p->cost.plan.u.pIdx==0 |
| 105447 | || p->cost.plan.u.pIdx==pSrc->pIndex |
| 105448 | ); |
| 105449 | |
| 105450 | WHERETRACE(("best index is: %s\n", |
| 105451 | p->cost.plan.u.pIdx ? p->cost.plan.u.pIdx->zName : "ipk")); |
| 105452 | |
| 105453 | bestOrClauseIndex(p); |
| 105454 | bestAutomaticIndex(p); |
| 105455 | p->cost.plan.wsFlags |= eqTermMask; |
| 105456 | } |
| @@ -106071,11 +106174,11 @@ | |
| 106174 | ** should not have a NULL value stored in 'x'. If column 'x' is |
| 106175 | ** the first one after the nEq equality constraints in the index, |
| 106176 | ** this requires some special handling. |
| 106177 | */ |
| 106178 | if( (wctrlFlags&WHERE_ORDERBY_MIN)!=0 |
| 106179 | && (pLevel->plan.wsFlags&WHERE_ORDERED) |
| 106180 | && (pIdx->nColumn>nEq) |
| 106181 | ){ |
| 106182 | /* assert( pOrderBy->nExpr==1 ); */ |
| 106183 | /* assert( pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq] ); */ |
| 106184 | isMinQuery = 1; |
| @@ -106892,12 +106995,12 @@ | |
| 106995 | continue; |
| 106996 | } |
| 106997 | sWBI.notReady = (isOptimal ? m : sWBI.notValid); |
| 106998 | if( sWBI.pSrc->pIndex==0 ) nUnconstrained++; |
| 106999 | |
| 107000 | WHERETRACE(("=== trying table %d (%s) with isOptimal=%d ===\n", |
| 107001 | j, sWBI.pSrc->pTab->zName, isOptimal)); |
| 107002 | assert( sWBI.pSrc->pTab ); |
| 107003 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 107004 | if( IsVirtual(sWBI.pSrc->pTab) ){ |
| 107005 | sWBI.ppIdxInfo = &pWInfo->a[j].pIdxInfo; |
| 107006 | bestVirtualIndex(&sWBI); |
| @@ -106934,48 +107037,46 @@ | |
| 107037 | ** combination of INDEXED BY clauses are given. The error |
| 107038 | ** will be detected and relayed back to the application later. |
| 107039 | ** The NEVER() comes about because rule (2) above prevents |
| 107040 | ** An indexable full-table-scan from reaching rule (3). |
| 107041 | ** |
| 107042 | ** (4) The plan cost must be lower than prior plans, where "cost" |
| 107043 | ** is defined by the compareCost() function above. |
| 107044 | */ |
| 107045 | if( (sWBI.cost.used&sWBI.notValid)==0 /* (1) */ |
| 107046 | && (bestJ<0 || (notIndexed&m)!=0 /* (2) */ |
| 107047 | || (bestPlan.plan.wsFlags & WHERE_NOT_FULLSCAN)==0 |
| 107048 | || (sWBI.cost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0) |
| 107049 | && (nUnconstrained==0 || sWBI.pSrc->pIndex==0 /* (3) */ |
| 107050 | || NEVER((sWBI.cost.plan.wsFlags & WHERE_NOT_FULLSCAN)!=0)) |
| 107051 | && (bestJ<0 || compareCost(&sWBI.cost, &bestPlan)) /* (4) */ |
| 107052 | ){ |
| 107053 | WHERETRACE(("=== table %d (%s) is best so far\n" |
| 107054 | " cost=%.1f, nRow=%.1f, nOBSat=%d, wsFlags=%08x\n", |
| 107055 | j, sWBI.pSrc->pTab->zName, |
| 107056 | sWBI.cost.rCost, sWBI.cost.plan.nRow, |
| 107057 | sWBI.cost.plan.nOBSat, sWBI.cost.plan.wsFlags)); |
| 107058 | bestPlan = sWBI.cost; |
| 107059 | bestJ = j; |
| 107060 | } |
| 107061 | if( doNotReorder ) break; |
| 107062 | } |
| 107063 | } |
| 107064 | assert( bestJ>=0 ); |
| 107065 | assert( sWBI.notValid & getMask(pMaskSet, pTabList->a[bestJ].iCursor) ); |
| 107066 | WHERETRACE(("*** Optimizer selects table %d (%s) for loop %d with:\n" |
| 107067 | " cost=%.1f, nRow=%.1f, nOBSat=%d, wsFlags=0x%08x\n", |
| 107068 | bestJ, pTabList->a[bestJ].pTab->zName, |
| 107069 | pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow, |
| 107070 | bestPlan.plan.nOBSat, bestPlan.plan.wsFlags)); |
| 107071 | if( (bestPlan.plan.wsFlags & WHERE_DISTINCT)!=0 ){ |
| 107072 | assert( pWInfo->eDistinct==0 ); |
| 107073 | pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; |
| 107074 | } |
| 107075 | andFlags &= bestPlan.plan.wsFlags; |
| 107076 | pLevel->plan = bestPlan.plan; |
| 107077 | pLevel->iTabCur = pTabList->a[bestJ].iCursor; |
| 107078 | testcase( bestPlan.plan.wsFlags & WHERE_INDEXED ); |
| 107079 | testcase( bestPlan.plan.wsFlags & WHERE_TEMP_INDEX ); |
| 107080 | if( bestPlan.plan.wsFlags & (WHERE_INDEXED|WHERE_TEMP_INDEX) ){ |
| 107081 | if( (wctrlFlags & WHERE_ONETABLE_ONLY) |
| 107082 | && (bestPlan.plan.wsFlags & WHERE_TEMP_INDEX)==0 |
| @@ -107013,15 +107114,22 @@ | |
| 107114 | } |
| 107115 | WHERETRACE(("*** Optimizer Finished ***\n")); |
| 107116 | if( pParse->nErr || db->mallocFailed ){ |
| 107117 | goto whereBeginError; |
| 107118 | } |
| 107119 | if( nTabList ){ |
| 107120 | pLevel--; |
| 107121 | pWInfo->nOBSat = pLevel->plan.nOBSat; |
| 107122 | }else{ |
| 107123 | pWInfo->nOBSat = 0; |
| 107124 | } |
| 107125 | |
| 107126 | /* If the total query only selects a single row, then the ORDER BY |
| 107127 | ** clause is irrelevant. |
| 107128 | */ |
| 107129 | if( (andFlags & WHERE_UNIQUE)!=0 && pOrderBy ){ |
| 107130 | assert( nTabList==0 || (pLevel->plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ); |
| 107131 | pWInfo->nOBSat = pOrderBy->nExpr; |
| 107132 | } |
| 107133 | |
| 107134 | /* If the caller is an UPDATE or DELETE statement that is requesting |
| 107135 | ** to use a one-pass algorithm, determine if this is appropriate. |
| @@ -107045,11 +107153,10 @@ | |
| 107153 | int iDb; /* Index of database containing table/index */ |
| 107154 | struct SrcList_item *pTabItem; |
| 107155 | |
| 107156 | pTabItem = &pTabList->a[pLevel->iFrom]; |
| 107157 | pTab = pTabItem->pTab; |
| 107158 | pWInfo->nRowOut *= pLevel->plan.nRow; |
| 107159 | iDb = sqlite3SchemaToIndex(db, pTab->pSchema); |
| 107160 | if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ |
| 107161 | /* Do nothing */ |
| 107162 | }else |
| @@ -114978,11 +115085,11 @@ | |
| 115085 | ** with various optimizations disabled to verify that the same answer |
| 115086 | ** is obtained in every case. |
| 115087 | */ |
| 115088 | case SQLITE_TESTCTRL_OPTIMIZATIONS: { |
| 115089 | sqlite3 *db = va_arg(ap, sqlite3*); |
| 115090 | db->dbOptFlags = (u8)(va_arg(ap, int) & 0xff); |
| 115091 | break; |
| 115092 | } |
| 115093 | |
| 115094 | #ifdef SQLITE_N_KEYWORD |
| 115095 | /* sqlite3_test_control(SQLITE_TESTCTRL_ISKEYWORD, const char *zWord) |
| @@ -135232,11 +135339,11 @@ | |
| 135339 | /* |
| 135340 | ** Remove the entry with rowid=iDelete from the r-tree structure. |
| 135341 | */ |
| 135342 | static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ |
| 135343 | int rc; /* Return code */ |
| 135344 | RtreeNode *pLeaf = 0; /* Leaf node containing record iDelete */ |
| 135345 | int iCell; /* Index of iDelete cell in pLeaf */ |
| 135346 | RtreeNode *pRoot; /* Root node of rtree structure */ |
| 135347 | |
| 135348 | |
| 135349 | /* Obtain a reference to the root node to initialise Rtree.iDepth */ |
| @@ -135435,11 +135542,11 @@ | |
| 135542 | ** (azData[2]..azData[argc-1]) contain a new record to insert into |
| 135543 | ** the r-tree structure. |
| 135544 | */ |
| 135545 | if( rc==SQLITE_OK && nData>1 ){ |
| 135546 | /* Insert the new record into the r-tree */ |
| 135547 | RtreeNode *pLeaf = 0; |
| 135548 | |
| 135549 | /* Figure out the rowid of the new row. */ |
| 135550 | if( bHaveRowid==0 ){ |
| 135551 | rc = newRowid(pRtree, &cell.iRowid); |
| 135552 | } |
| 135553 |
+13
-1
| --- src/sqlite3.h | ||
| +++ src/sqlite3.h | ||
| @@ -107,11 +107,11 @@ | ||
| 107 | 107 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 108 | 108 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 109 | 109 | */ |
| 110 | 110 | #define SQLITE_VERSION "3.7.15" |
| 111 | 111 | #define SQLITE_VERSION_NUMBER 3007015 |
| 112 | -#define SQLITE_SOURCE_ID "2012-09-28 00:44:28 1e874629d7cf568368b912b295bd3001147d0b52" | |
| 112 | +#define SQLITE_SOURCE_ID "2012-10-05 07:36:34 43155b1543bddbb84a8bc13a5b7344b228ddacb9" | |
| 113 | 113 | |
| 114 | 114 | /* |
| 115 | 115 | ** CAPI3REF: Run-Time Library Version Numbers |
| 116 | 116 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 117 | 117 | ** |
| @@ -855,10 +855,21 @@ | ||
| 855 | 855 | ** that the VFS encountered an error while handling the [PRAGMA] and the |
| 856 | 856 | ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] |
| 857 | 857 | ** file control occurs at the beginning of pragma statement analysis and so |
| 858 | 858 | ** it is able to override built-in [PRAGMA] statements. |
| 859 | 859 | ** </ul> |
| 860 | +** | |
| 861 | +** <li>[[SQLITE_FCNTL_BUSYHANDLER]] | |
| 862 | +** ^This file-control may be invoked by SQLite on the database file handle | |
| 863 | +** shortly after it is opened in order to provide a custom VFS with access | |
| 864 | +** to the connections busy-handler callback. The argument is of type (void **) | |
| 865 | +** - an array of two (void *) values. The first (void *) actually points | |
| 866 | +** to a function of type (int (*)(void *)). In order to invoke the connections | |
| 867 | +** busy-handler, this function should be invoked with the second (void *) in | |
| 868 | +** the array as the only argument. If it returns non-zero, then the operation | |
| 869 | +** should be retried. If it returns zero, the custom VFS should abandon the | |
| 870 | +** current operation. | |
| 860 | 871 | */ |
| 861 | 872 | #define SQLITE_FCNTL_LOCKSTATE 1 |
| 862 | 873 | #define SQLITE_GET_LOCKPROXYFILE 2 |
| 863 | 874 | #define SQLITE_SET_LOCKPROXYFILE 3 |
| 864 | 875 | #define SQLITE_LAST_ERRNO 4 |
| @@ -870,10 +881,11 @@ | ||
| 870 | 881 | #define SQLITE_FCNTL_PERSIST_WAL 10 |
| 871 | 882 | #define SQLITE_FCNTL_OVERWRITE 11 |
| 872 | 883 | #define SQLITE_FCNTL_VFSNAME 12 |
| 873 | 884 | #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 |
| 874 | 885 | #define SQLITE_FCNTL_PRAGMA 14 |
| 886 | +#define SQLITE_FCNTL_BUSYHANDLER 15 | |
| 875 | 887 | |
| 876 | 888 | /* |
| 877 | 889 | ** CAPI3REF: Mutex Handle |
| 878 | 890 | ** |
| 879 | 891 | ** The mutex module within SQLite defines [sqlite3_mutex] to be an |
| 880 | 892 |
| --- src/sqlite3.h | |
| +++ src/sqlite3.h | |
| @@ -107,11 +107,11 @@ | |
| 107 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 108 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 109 | */ |
| 110 | #define SQLITE_VERSION "3.7.15" |
| 111 | #define SQLITE_VERSION_NUMBER 3007015 |
| 112 | #define SQLITE_SOURCE_ID "2012-09-28 00:44:28 1e874629d7cf568368b912b295bd3001147d0b52" |
| 113 | |
| 114 | /* |
| 115 | ** CAPI3REF: Run-Time Library Version Numbers |
| 116 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 117 | ** |
| @@ -855,10 +855,21 @@ | |
| 855 | ** that the VFS encountered an error while handling the [PRAGMA] and the |
| 856 | ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] |
| 857 | ** file control occurs at the beginning of pragma statement analysis and so |
| 858 | ** it is able to override built-in [PRAGMA] statements. |
| 859 | ** </ul> |
| 860 | */ |
| 861 | #define SQLITE_FCNTL_LOCKSTATE 1 |
| 862 | #define SQLITE_GET_LOCKPROXYFILE 2 |
| 863 | #define SQLITE_SET_LOCKPROXYFILE 3 |
| 864 | #define SQLITE_LAST_ERRNO 4 |
| @@ -870,10 +881,11 @@ | |
| 870 | #define SQLITE_FCNTL_PERSIST_WAL 10 |
| 871 | #define SQLITE_FCNTL_OVERWRITE 11 |
| 872 | #define SQLITE_FCNTL_VFSNAME 12 |
| 873 | #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 |
| 874 | #define SQLITE_FCNTL_PRAGMA 14 |
| 875 | |
| 876 | /* |
| 877 | ** CAPI3REF: Mutex Handle |
| 878 | ** |
| 879 | ** The mutex module within SQLite defines [sqlite3_mutex] to be an |
| 880 |
| --- src/sqlite3.h | |
| +++ src/sqlite3.h | |
| @@ -107,11 +107,11 @@ | |
| 107 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 108 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 109 | */ |
| 110 | #define SQLITE_VERSION "3.7.15" |
| 111 | #define SQLITE_VERSION_NUMBER 3007015 |
| 112 | #define SQLITE_SOURCE_ID "2012-10-05 07:36:34 43155b1543bddbb84a8bc13a5b7344b228ddacb9" |
| 113 | |
| 114 | /* |
| 115 | ** CAPI3REF: Run-Time Library Version Numbers |
| 116 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 117 | ** |
| @@ -855,10 +855,21 @@ | |
| 855 | ** that the VFS encountered an error while handling the [PRAGMA] and the |
| 856 | ** compilation of the PRAGMA fails with an error. ^The [SQLITE_FCNTL_PRAGMA] |
| 857 | ** file control occurs at the beginning of pragma statement analysis and so |
| 858 | ** it is able to override built-in [PRAGMA] statements. |
| 859 | ** </ul> |
| 860 | ** |
| 861 | ** <li>[[SQLITE_FCNTL_BUSYHANDLER]] |
| 862 | ** ^This file-control may be invoked by SQLite on the database file handle |
| 863 | ** shortly after it is opened in order to provide a custom VFS with access |
| 864 | ** to the connections busy-handler callback. The argument is of type (void **) |
| 865 | ** - an array of two (void *) values. The first (void *) actually points |
| 866 | ** to a function of type (int (*)(void *)). In order to invoke the connections |
| 867 | ** busy-handler, this function should be invoked with the second (void *) in |
| 868 | ** the array as the only argument. If it returns non-zero, then the operation |
| 869 | ** should be retried. If it returns zero, the custom VFS should abandon the |
| 870 | ** current operation. |
| 871 | */ |
| 872 | #define SQLITE_FCNTL_LOCKSTATE 1 |
| 873 | #define SQLITE_GET_LOCKPROXYFILE 2 |
| 874 | #define SQLITE_SET_LOCKPROXYFILE 3 |
| 875 | #define SQLITE_LAST_ERRNO 4 |
| @@ -870,10 +881,11 @@ | |
| 881 | #define SQLITE_FCNTL_PERSIST_WAL 10 |
| 882 | #define SQLITE_FCNTL_OVERWRITE 11 |
| 883 | #define SQLITE_FCNTL_VFSNAME 12 |
| 884 | #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 |
| 885 | #define SQLITE_FCNTL_PRAGMA 14 |
| 886 | #define SQLITE_FCNTL_BUSYHANDLER 15 |
| 887 | |
| 888 | /* |
| 889 | ** CAPI3REF: Mutex Handle |
| 890 | ** |
| 891 | ** The mutex module within SQLite defines [sqlite3_mutex] to be an |
| 892 |
+96
-29
| --- src/stash.c | ||
| +++ src/stash.c | ||
| @@ -155,10 +155,24 @@ | ||
| 155 | 155 | int stashid; /* ID of the new stash */ |
| 156 | 156 | int vid; /* Current checkout */ |
| 157 | 157 | |
| 158 | 158 | zComment = find_option("comment", "m", 1); |
| 159 | 159 | verify_all_options(); |
| 160 | + if( zComment==0 ){ | |
| 161 | + Blob prompt; /* Prompt for stash comment */ | |
| 162 | + Blob comment; /* User comment reply */ | |
| 163 | + blob_zero(&prompt); | |
| 164 | + blob_append(&prompt, | |
| 165 | + "\n" | |
| 166 | + "# Enter a description of what is being stashed. Lines beginning\n" | |
| 167 | + "# with \"#\" are ignored. Stash comments are plain text except.\n" | |
| 168 | + "# newlines are not preserved.\n", | |
| 169 | + -1); | |
| 170 | + prompt_for_user_comment(&comment, &prompt); | |
| 171 | + blob_reset(&prompt); | |
| 172 | + zComment = blob_str(&comment); | |
| 173 | + } | |
| 160 | 174 | stashid = db_lget_int("stash-next", 1); |
| 161 | 175 | db_lset_int("stash-next", stashid+1); |
| 162 | 176 | vid = db_lget_int("checkout", 0); |
| 163 | 177 | vfile_check_signature(vid, 0, 0); |
| 164 | 178 | db_multi_exec( |
| @@ -267,11 +281,18 @@ | ||
| 267 | 281 | } |
| 268 | 282 | |
| 269 | 283 | /* |
| 270 | 284 | ** Show the diffs associate with a single stash. |
| 271 | 285 | */ |
| 272 | -static void stash_diff(int stashid, const char *zDiffCmd, u64 diffFlags){ | |
| 286 | +static void stash_diff( | |
| 287 | + int stashid, /* The stash entry to diff */ | |
| 288 | + const char *zDiffCmd, /* Command used for diffing */ | |
| 289 | + const char *zBinGlob, /* GLOB pattern to determine binary files */ | |
| 290 | + int fBaseline, /* Diff against original baseline check-in if true */ | |
| 291 | + int fIncludeBinary, /* Do diffs against binary files */ | |
| 292 | + u64 diffFlags /* Other diff flags */ | |
| 293 | +){ | |
| 273 | 294 | Stmt q; |
| 274 | 295 | Blob empty; |
| 275 | 296 | blob_zero(&empty); |
| 276 | 297 | db_prepare(&q, |
| 277 | 298 | "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" |
| @@ -280,50 +301,66 @@ | ||
| 280 | 301 | ); |
| 281 | 302 | while( db_step(&q)==SQLITE_ROW ){ |
| 282 | 303 | int rid = db_column_int(&q, 0); |
| 283 | 304 | int isRemoved = db_column_int(&q, 1); |
| 284 | 305 | int isLink = db_column_int(&q, 3); |
| 306 | + int isBin1, isBin2; | |
| 285 | 307 | const char *zOrig = db_column_text(&q, 4); |
| 286 | 308 | const char *zNew = db_column_text(&q, 5); |
| 287 | 309 | char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig); |
| 288 | - Blob delta; | |
| 310 | + Blob delta, a, b, disk; | |
| 289 | 311 | if( rid==0 ){ |
| 290 | - db_ephemeral_blob(&q, 6, &delta); | |
| 312 | + db_ephemeral_blob(&q, 6, &a); | |
| 291 | 313 | fossil_print("ADDED %s\n", zNew); |
| 292 | 314 | diff_print_index(zNew, diffFlags); |
| 293 | - diff_file_mem(&empty, &delta, zNew, zDiffCmd, diffFlags); | |
| 315 | + isBin1 = 0; | |
| 316 | + isBin2 = fIncludeBinary ? 0 : looks_like_binary(&a); | |
| 317 | + diff_file_mem(&empty, &a, isBin1, isBin2, zNew, zDiffCmd, | |
| 318 | + zBinGlob, fIncludeBinary, diffFlags); | |
| 294 | 319 | }else if( isRemoved ){ |
| 295 | 320 | fossil_print("DELETE %s\n", zOrig); |
| 296 | - if( file_wd_islink(zOPath) ){ | |
| 297 | - blob_read_link(&delta, zOPath); | |
| 321 | + if( fBaseline==0 ){ | |
| 322 | + if( file_wd_islink(zOPath) ){ | |
| 323 | + blob_read_link(&a, zOPath); | |
| 324 | + }else{ | |
| 325 | + blob_read_from_file(&a, zOPath); | |
| 326 | + } | |
| 298 | 327 | }else{ |
| 299 | - blob_read_from_file(&delta, zOPath); | |
| 328 | + content_get(rid, &a); | |
| 300 | 329 | } |
| 301 | 330 | diff_print_index(zNew, diffFlags); |
| 302 | - diff_file_mem(&delta, &empty, zOrig, zDiffCmd, diffFlags); | |
| 331 | + isBin1 = fIncludeBinary ? 0 : looks_like_binary(&a); | |
| 332 | + isBin2 = 0; | |
| 333 | + diff_file_mem(&a, &empty, isBin1, isBin2, zOrig, zDiffCmd, | |
| 334 | + zBinGlob, fIncludeBinary, diffFlags); | |
| 303 | 335 | }else{ |
| 304 | - Blob a, b, disk; | |
| 305 | 336 | int isOrigLink = file_wd_islink(zOPath); |
| 306 | 337 | db_ephemeral_blob(&q, 6, &delta); |
| 307 | - if( isOrigLink ){ | |
| 308 | - blob_read_link(&disk, zOPath); | |
| 309 | - }else{ | |
| 310 | - blob_read_from_file(&disk, zOPath); | |
| 338 | + if( fBaseline==0 ){ | |
| 339 | + if( isOrigLink ){ | |
| 340 | + blob_read_link(&disk, zOPath); | |
| 341 | + }else{ | |
| 342 | + blob_read_from_file(&disk, zOPath); | |
| 343 | + } | |
| 311 | 344 | } |
| 312 | 345 | fossil_print("CHANGED %s\n", zNew); |
| 313 | 346 | if( !isOrigLink != !isLink ){ |
| 314 | 347 | diff_print_index(zNew, diffFlags); |
| 315 | 348 | diff_print_filenames(zOrig, zNew, diffFlags); |
| 316 | - printf("cannot compute difference between symlink and regular file\n"); | |
| 349 | + printf(DIFF_CANNOT_COMPUTE_SYMLINK); | |
| 317 | 350 | }else{ |
| 351 | + Blob *pBase = fBaseline ? &a : &disk; | |
| 318 | 352 | content_get(rid, &a); |
| 319 | 353 | blob_delta_apply(&a, &delta, &b); |
| 320 | - diff_file_mem(&disk, &b, zNew, zDiffCmd, diffFlags); | |
| 354 | + isBin1 = fIncludeBinary ? 0 : looks_like_binary(pBase); | |
| 355 | + isBin2 = fIncludeBinary ? 0 : looks_like_binary(&b); | |
| 356 | + diff_file_mem(fBaseline? &a : &disk, &b, isBin1, isBin2, zNew, | |
| 357 | + zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); | |
| 321 | 358 | blob_reset(&a); |
| 322 | 359 | blob_reset(&b); |
| 323 | 360 | } |
| 324 | - blob_reset(&disk); | |
| 361 | + if( !fBaseline ) blob_reset(&disk); | |
| 325 | 362 | } |
| 326 | 363 | blob_reset(&delta); |
| 327 | 364 | } |
| 328 | 365 | db_finalize(&q); |
| 329 | 366 | } |
| @@ -379,10 +416,14 @@ | ||
| 379 | 416 | ** fossil stash ls ?-l? |
| 380 | 417 | ** |
| 381 | 418 | ** List all changes sets currently stashed. Show information about |
| 382 | 419 | ** individual files in each changeset if --detail or -l is used. |
| 383 | 420 | ** |
| 421 | +** fossil stash show ?STASHID? ?DIFF-FLAGS? | |
| 422 | +** | |
| 423 | +** Show the content of a stash | |
| 424 | +** | |
| 384 | 425 | ** fossil stash pop |
| 385 | 426 | ** fossil stash apply ?STASHID? |
| 386 | 427 | ** |
| 387 | 428 | ** Apply STASHID or the most recently create stash to the current |
| 388 | 429 | ** working check-out. The "pop" command deletes that changeset from |
| @@ -404,19 +445,32 @@ | ||
| 404 | 445 | ** fossil stash diff ?STASHID? |
| 405 | 446 | ** fossil stash gdiff ?STASHID? |
| 406 | 447 | ** |
| 407 | 448 | ** Show diffs of the current working directory and what that |
| 408 | 449 | ** directory would be if STASHID were applied. |
| 450 | +** | |
| 451 | +** SUMMARY: | |
| 452 | +** fossil stash | |
| 453 | +** fossil stash save ?-m COMMENT? ?FILES...? | |
| 454 | +** fossil stash snapshot ?-m COMMENT? ?FILES...? | |
| 455 | +** fossil stash list|ls ?-l? ?--detail? | |
| 456 | +** fossil stash show ?STASHID? ?DIFF-OPTIONS? | |
| 457 | +** fossil stash pop | |
| 458 | +** fossil stash apply ?STASHID? | |
| 459 | +** fossil stash goto ?STASHID? | |
| 460 | +** fossil stash rm|drop ?STASHID? ?--all? | |
| 461 | +** fossil stash [g]diff ?STASHID? ?DIFF-OPTIONS? | |
| 409 | 462 | */ |
| 410 | 463 | void stash_cmd(void){ |
| 411 | 464 | const char *zDb; |
| 412 | 465 | const char *zCmd; |
| 413 | 466 | int nCmd; |
| 414 | 467 | int stashid; |
| 415 | 468 | |
| 416 | 469 | undo_capture_command_line(); |
| 417 | 470 | db_must_be_within_tree(); |
| 471 | + db_open_config(0); | |
| 418 | 472 | db_begin_transaction(); |
| 419 | 473 | zDb = db_name("localdb"); |
| 420 | 474 | db_multi_exec(zStashInit, zDb, zDb); |
| 421 | 475 | if( g.argc<=2 ){ |
| 422 | 476 | zCmd = "save"; |
| @@ -548,23 +602,36 @@ | ||
| 548 | 602 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN " |
| 549 | 603 | "(SELECT origname FROM stashfile WHERE stashid=%d)", |
| 550 | 604 | stashid); |
| 551 | 605 | undo_finish(); |
| 552 | 606 | }else |
| 553 | - if( memcmp(zCmd, "diff", nCmd)==0 ){ | |
| 554 | - const char *zDiffCmd = diff_command_external(0); | |
| 555 | - u64 diffFlags = diff_options(); | |
| 556 | - if( g.argc>4 ) usage("diff STASHID"); | |
| 557 | - stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); | |
| 558 | - stash_diff(stashid, zDiffCmd, diffFlags); | |
| 559 | - }else | |
| 560 | - if( memcmp(zCmd, "gdiff", nCmd)==0 ){ | |
| 561 | - const char *zDiffCmd = diff_command_external(1); | |
| 562 | - u64 diffFlags = diff_options(); | |
| 563 | - if( g.argc>4 ) usage("gdiff STASHID"); | |
| 564 | - stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); | |
| 565 | - stash_diff(stashid, zDiffCmd, diffFlags); | |
| 607 | + if( memcmp(zCmd, "diff", nCmd)==0 | |
| 608 | + || memcmp(zCmd, "gdiff", nCmd)==0 | |
| 609 | + || memcmp(zCmd, "show", nCmd)==0 | |
| 610 | + ){ | |
| 611 | + const char *zDiffCmd = 0; | |
| 612 | + const char *zBinGlob = 0; | |
| 613 | + int fIncludeBinary = 0; | |
| 614 | + u64 diffFlags; | |
| 615 | + | |
| 616 | + if( find_option("tk",0,0)!=0 ){ | |
| 617 | + db_close(0); | |
| 618 | + diff_tk((zCmd[0]=='s' ? "stash show" : "stash diff"), 3); | |
| 619 | + return; | |
| 620 | + } | |
| 621 | + if( find_option("internal","i",0)==0 ){ | |
| 622 | + zDiffCmd = diff_command_external(0); | |
| 623 | + } | |
| 624 | + diffFlags = diff_options(); | |
| 625 | + if( g.argc>4 ) usage(mprintf("%s STASHID", zCmd)); | |
| 626 | + if( zDiffCmd ){ | |
| 627 | + zBinGlob = diff_get_binary_glob(); | |
| 628 | + fIncludeBinary = diff_include_binary_files(); | |
| 629 | + } | |
| 630 | + stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); | |
| 631 | + stash_diff(stashid, zDiffCmd, zBinGlob, zCmd[0]=='s', fIncludeBinary, | |
| 632 | + diffFlags); | |
| 566 | 633 | }else |
| 567 | 634 | if( memcmp(zCmd, "help", nCmd)==0 ){ |
| 568 | 635 | g.argv[1] = "help"; |
| 569 | 636 | g.argv[2] = "stash"; |
| 570 | 637 | g.argc = 3; |
| 571 | 638 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -155,10 +155,24 @@ | |
| 155 | int stashid; /* ID of the new stash */ |
| 156 | int vid; /* Current checkout */ |
| 157 | |
| 158 | zComment = find_option("comment", "m", 1); |
| 159 | verify_all_options(); |
| 160 | stashid = db_lget_int("stash-next", 1); |
| 161 | db_lset_int("stash-next", stashid+1); |
| 162 | vid = db_lget_int("checkout", 0); |
| 163 | vfile_check_signature(vid, 0, 0); |
| 164 | db_multi_exec( |
| @@ -267,11 +281,18 @@ | |
| 267 | } |
| 268 | |
| 269 | /* |
| 270 | ** Show the diffs associate with a single stash. |
| 271 | */ |
| 272 | static void stash_diff(int stashid, const char *zDiffCmd, u64 diffFlags){ |
| 273 | Stmt q; |
| 274 | Blob empty; |
| 275 | blob_zero(&empty); |
| 276 | db_prepare(&q, |
| 277 | "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" |
| @@ -280,50 +301,66 @@ | |
| 280 | ); |
| 281 | while( db_step(&q)==SQLITE_ROW ){ |
| 282 | int rid = db_column_int(&q, 0); |
| 283 | int isRemoved = db_column_int(&q, 1); |
| 284 | int isLink = db_column_int(&q, 3); |
| 285 | const char *zOrig = db_column_text(&q, 4); |
| 286 | const char *zNew = db_column_text(&q, 5); |
| 287 | char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig); |
| 288 | Blob delta; |
| 289 | if( rid==0 ){ |
| 290 | db_ephemeral_blob(&q, 6, &delta); |
| 291 | fossil_print("ADDED %s\n", zNew); |
| 292 | diff_print_index(zNew, diffFlags); |
| 293 | diff_file_mem(&empty, &delta, zNew, zDiffCmd, diffFlags); |
| 294 | }else if( isRemoved ){ |
| 295 | fossil_print("DELETE %s\n", zOrig); |
| 296 | if( file_wd_islink(zOPath) ){ |
| 297 | blob_read_link(&delta, zOPath); |
| 298 | }else{ |
| 299 | blob_read_from_file(&delta, zOPath); |
| 300 | } |
| 301 | diff_print_index(zNew, diffFlags); |
| 302 | diff_file_mem(&delta, &empty, zOrig, zDiffCmd, diffFlags); |
| 303 | }else{ |
| 304 | Blob a, b, disk; |
| 305 | int isOrigLink = file_wd_islink(zOPath); |
| 306 | db_ephemeral_blob(&q, 6, &delta); |
| 307 | if( isOrigLink ){ |
| 308 | blob_read_link(&disk, zOPath); |
| 309 | }else{ |
| 310 | blob_read_from_file(&disk, zOPath); |
| 311 | } |
| 312 | fossil_print("CHANGED %s\n", zNew); |
| 313 | if( !isOrigLink != !isLink ){ |
| 314 | diff_print_index(zNew, diffFlags); |
| 315 | diff_print_filenames(zOrig, zNew, diffFlags); |
| 316 | printf("cannot compute difference between symlink and regular file\n"); |
| 317 | }else{ |
| 318 | content_get(rid, &a); |
| 319 | blob_delta_apply(&a, &delta, &b); |
| 320 | diff_file_mem(&disk, &b, zNew, zDiffCmd, diffFlags); |
| 321 | blob_reset(&a); |
| 322 | blob_reset(&b); |
| 323 | } |
| 324 | blob_reset(&disk); |
| 325 | } |
| 326 | blob_reset(&delta); |
| 327 | } |
| 328 | db_finalize(&q); |
| 329 | } |
| @@ -379,10 +416,14 @@ | |
| 379 | ** fossil stash ls ?-l? |
| 380 | ** |
| 381 | ** List all changes sets currently stashed. Show information about |
| 382 | ** individual files in each changeset if --detail or -l is used. |
| 383 | ** |
| 384 | ** fossil stash pop |
| 385 | ** fossil stash apply ?STASHID? |
| 386 | ** |
| 387 | ** Apply STASHID or the most recently create stash to the current |
| 388 | ** working check-out. The "pop" command deletes that changeset from |
| @@ -404,19 +445,32 @@ | |
| 404 | ** fossil stash diff ?STASHID? |
| 405 | ** fossil stash gdiff ?STASHID? |
| 406 | ** |
| 407 | ** Show diffs of the current working directory and what that |
| 408 | ** directory would be if STASHID were applied. |
| 409 | */ |
| 410 | void stash_cmd(void){ |
| 411 | const char *zDb; |
| 412 | const char *zCmd; |
| 413 | int nCmd; |
| 414 | int stashid; |
| 415 | |
| 416 | undo_capture_command_line(); |
| 417 | db_must_be_within_tree(); |
| 418 | db_begin_transaction(); |
| 419 | zDb = db_name("localdb"); |
| 420 | db_multi_exec(zStashInit, zDb, zDb); |
| 421 | if( g.argc<=2 ){ |
| 422 | zCmd = "save"; |
| @@ -548,23 +602,36 @@ | |
| 548 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN " |
| 549 | "(SELECT origname FROM stashfile WHERE stashid=%d)", |
| 550 | stashid); |
| 551 | undo_finish(); |
| 552 | }else |
| 553 | if( memcmp(zCmd, "diff", nCmd)==0 ){ |
| 554 | const char *zDiffCmd = diff_command_external(0); |
| 555 | u64 diffFlags = diff_options(); |
| 556 | if( g.argc>4 ) usage("diff STASHID"); |
| 557 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 558 | stash_diff(stashid, zDiffCmd, diffFlags); |
| 559 | }else |
| 560 | if( memcmp(zCmd, "gdiff", nCmd)==0 ){ |
| 561 | const char *zDiffCmd = diff_command_external(1); |
| 562 | u64 diffFlags = diff_options(); |
| 563 | if( g.argc>4 ) usage("gdiff STASHID"); |
| 564 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 565 | stash_diff(stashid, zDiffCmd, diffFlags); |
| 566 | }else |
| 567 | if( memcmp(zCmd, "help", nCmd)==0 ){ |
| 568 | g.argv[1] = "help"; |
| 569 | g.argv[2] = "stash"; |
| 570 | g.argc = 3; |
| 571 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -155,10 +155,24 @@ | |
| 155 | int stashid; /* ID of the new stash */ |
| 156 | int vid; /* Current checkout */ |
| 157 | |
| 158 | zComment = find_option("comment", "m", 1); |
| 159 | verify_all_options(); |
| 160 | if( zComment==0 ){ |
| 161 | Blob prompt; /* Prompt for stash comment */ |
| 162 | Blob comment; /* User comment reply */ |
| 163 | blob_zero(&prompt); |
| 164 | blob_append(&prompt, |
| 165 | "\n" |
| 166 | "# Enter a description of what is being stashed. Lines beginning\n" |
| 167 | "# with \"#\" are ignored. Stash comments are plain text except.\n" |
| 168 | "# newlines are not preserved.\n", |
| 169 | -1); |
| 170 | prompt_for_user_comment(&comment, &prompt); |
| 171 | blob_reset(&prompt); |
| 172 | zComment = blob_str(&comment); |
| 173 | } |
| 174 | stashid = db_lget_int("stash-next", 1); |
| 175 | db_lset_int("stash-next", stashid+1); |
| 176 | vid = db_lget_int("checkout", 0); |
| 177 | vfile_check_signature(vid, 0, 0); |
| 178 | db_multi_exec( |
| @@ -267,11 +281,18 @@ | |
| 281 | } |
| 282 | |
| 283 | /* |
| 284 | ** Show the diffs associate with a single stash. |
| 285 | */ |
| 286 | static void stash_diff( |
| 287 | int stashid, /* The stash entry to diff */ |
| 288 | const char *zDiffCmd, /* Command used for diffing */ |
| 289 | const char *zBinGlob, /* GLOB pattern to determine binary files */ |
| 290 | int fBaseline, /* Diff against original baseline check-in if true */ |
| 291 | int fIncludeBinary, /* Do diffs against binary files */ |
| 292 | u64 diffFlags /* Other diff flags */ |
| 293 | ){ |
| 294 | Stmt q; |
| 295 | Blob empty; |
| 296 | blob_zero(&empty); |
| 297 | db_prepare(&q, |
| 298 | "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" |
| @@ -280,50 +301,66 @@ | |
| 301 | ); |
| 302 | while( db_step(&q)==SQLITE_ROW ){ |
| 303 | int rid = db_column_int(&q, 0); |
| 304 | int isRemoved = db_column_int(&q, 1); |
| 305 | int isLink = db_column_int(&q, 3); |
| 306 | int isBin1, isBin2; |
| 307 | const char *zOrig = db_column_text(&q, 4); |
| 308 | const char *zNew = db_column_text(&q, 5); |
| 309 | char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig); |
| 310 | Blob delta, a, b, disk; |
| 311 | if( rid==0 ){ |
| 312 | db_ephemeral_blob(&q, 6, &a); |
| 313 | fossil_print("ADDED %s\n", zNew); |
| 314 | diff_print_index(zNew, diffFlags); |
| 315 | isBin1 = 0; |
| 316 | isBin2 = fIncludeBinary ? 0 : looks_like_binary(&a); |
| 317 | diff_file_mem(&empty, &a, isBin1, isBin2, zNew, zDiffCmd, |
| 318 | zBinGlob, fIncludeBinary, diffFlags); |
| 319 | }else if( isRemoved ){ |
| 320 | fossil_print("DELETE %s\n", zOrig); |
| 321 | if( fBaseline==0 ){ |
| 322 | if( file_wd_islink(zOPath) ){ |
| 323 | blob_read_link(&a, zOPath); |
| 324 | }else{ |
| 325 | blob_read_from_file(&a, zOPath); |
| 326 | } |
| 327 | }else{ |
| 328 | content_get(rid, &a); |
| 329 | } |
| 330 | diff_print_index(zNew, diffFlags); |
| 331 | isBin1 = fIncludeBinary ? 0 : looks_like_binary(&a); |
| 332 | isBin2 = 0; |
| 333 | diff_file_mem(&a, &empty, isBin1, isBin2, zOrig, zDiffCmd, |
| 334 | zBinGlob, fIncludeBinary, diffFlags); |
| 335 | }else{ |
| 336 | int isOrigLink = file_wd_islink(zOPath); |
| 337 | db_ephemeral_blob(&q, 6, &delta); |
| 338 | if( fBaseline==0 ){ |
| 339 | if( isOrigLink ){ |
| 340 | blob_read_link(&disk, zOPath); |
| 341 | }else{ |
| 342 | blob_read_from_file(&disk, zOPath); |
| 343 | } |
| 344 | } |
| 345 | fossil_print("CHANGED %s\n", zNew); |
| 346 | if( !isOrigLink != !isLink ){ |
| 347 | diff_print_index(zNew, diffFlags); |
| 348 | diff_print_filenames(zOrig, zNew, diffFlags); |
| 349 | printf(DIFF_CANNOT_COMPUTE_SYMLINK); |
| 350 | }else{ |
| 351 | Blob *pBase = fBaseline ? &a : &disk; |
| 352 | content_get(rid, &a); |
| 353 | blob_delta_apply(&a, &delta, &b); |
| 354 | isBin1 = fIncludeBinary ? 0 : looks_like_binary(pBase); |
| 355 | isBin2 = fIncludeBinary ? 0 : looks_like_binary(&b); |
| 356 | diff_file_mem(fBaseline? &a : &disk, &b, isBin1, isBin2, zNew, |
| 357 | zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); |
| 358 | blob_reset(&a); |
| 359 | blob_reset(&b); |
| 360 | } |
| 361 | if( !fBaseline ) blob_reset(&disk); |
| 362 | } |
| 363 | blob_reset(&delta); |
| 364 | } |
| 365 | db_finalize(&q); |
| 366 | } |
| @@ -379,10 +416,14 @@ | |
| 416 | ** fossil stash ls ?-l? |
| 417 | ** |
| 418 | ** List all changes sets currently stashed. Show information about |
| 419 | ** individual files in each changeset if --detail or -l is used. |
| 420 | ** |
| 421 | ** fossil stash show ?STASHID? ?DIFF-FLAGS? |
| 422 | ** |
| 423 | ** Show the content of a stash |
| 424 | ** |
| 425 | ** fossil stash pop |
| 426 | ** fossil stash apply ?STASHID? |
| 427 | ** |
| 428 | ** Apply STASHID or the most recently create stash to the current |
| 429 | ** working check-out. The "pop" command deletes that changeset from |
| @@ -404,19 +445,32 @@ | |
| 445 | ** fossil stash diff ?STASHID? |
| 446 | ** fossil stash gdiff ?STASHID? |
| 447 | ** |
| 448 | ** Show diffs of the current working directory and what that |
| 449 | ** directory would be if STASHID were applied. |
| 450 | ** |
| 451 | ** SUMMARY: |
| 452 | ** fossil stash |
| 453 | ** fossil stash save ?-m COMMENT? ?FILES...? |
| 454 | ** fossil stash snapshot ?-m COMMENT? ?FILES...? |
| 455 | ** fossil stash list|ls ?-l? ?--detail? |
| 456 | ** fossil stash show ?STASHID? ?DIFF-OPTIONS? |
| 457 | ** fossil stash pop |
| 458 | ** fossil stash apply ?STASHID? |
| 459 | ** fossil stash goto ?STASHID? |
| 460 | ** fossil stash rm|drop ?STASHID? ?--all? |
| 461 | ** fossil stash [g]diff ?STASHID? ?DIFF-OPTIONS? |
| 462 | */ |
| 463 | void stash_cmd(void){ |
| 464 | const char *zDb; |
| 465 | const char *zCmd; |
| 466 | int nCmd; |
| 467 | int stashid; |
| 468 | |
| 469 | undo_capture_command_line(); |
| 470 | db_must_be_within_tree(); |
| 471 | db_open_config(0); |
| 472 | db_begin_transaction(); |
| 473 | zDb = db_name("localdb"); |
| 474 | db_multi_exec(zStashInit, zDb, zDb); |
| 475 | if( g.argc<=2 ){ |
| 476 | zCmd = "save"; |
| @@ -548,23 +602,36 @@ | |
| 602 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN " |
| 603 | "(SELECT origname FROM stashfile WHERE stashid=%d)", |
| 604 | stashid); |
| 605 | undo_finish(); |
| 606 | }else |
| 607 | if( memcmp(zCmd, "diff", nCmd)==0 |
| 608 | || memcmp(zCmd, "gdiff", nCmd)==0 |
| 609 | || memcmp(zCmd, "show", nCmd)==0 |
| 610 | ){ |
| 611 | const char *zDiffCmd = 0; |
| 612 | const char *zBinGlob = 0; |
| 613 | int fIncludeBinary = 0; |
| 614 | u64 diffFlags; |
| 615 | |
| 616 | if( find_option("tk",0,0)!=0 ){ |
| 617 | db_close(0); |
| 618 | diff_tk((zCmd[0]=='s' ? "stash show" : "stash diff"), 3); |
| 619 | return; |
| 620 | } |
| 621 | if( find_option("internal","i",0)==0 ){ |
| 622 | zDiffCmd = diff_command_external(0); |
| 623 | } |
| 624 | diffFlags = diff_options(); |
| 625 | if( g.argc>4 ) usage(mprintf("%s STASHID", zCmd)); |
| 626 | if( zDiffCmd ){ |
| 627 | zBinGlob = diff_get_binary_glob(); |
| 628 | fIncludeBinary = diff_include_binary_files(); |
| 629 | } |
| 630 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 631 | stash_diff(stashid, zDiffCmd, zBinGlob, zCmd[0]=='s', fIncludeBinary, |
| 632 | diffFlags); |
| 633 | }else |
| 634 | if( memcmp(zCmd, "help", nCmd)==0 ){ |
| 635 | g.argv[1] = "help"; |
| 636 | g.argv[2] = "stash"; |
| 637 | g.argc = 3; |
| 638 |
+10
-3
| --- src/th_main.c | ||
| +++ src/th_main.c | ||
| @@ -221,13 +221,14 @@ | ||
| 221 | 221 | ** TH command: hasfeature STRING |
| 222 | 222 | ** |
| 223 | 223 | ** Return true if the fossil binary has the given compile-time feature |
| 224 | 224 | ** enabled. The set of features includes: |
| 225 | 225 | ** |
| 226 | -** "json" = FOSSIL_ENABLE_JSON | |
| 227 | -** "tcl" = FOSSIL_ENABLE_TCL | |
| 228 | -** "ssl" = FOSSIL_ENABLE_SSL | |
| 226 | +** "json" = FOSSIL_ENABLE_JSON | |
| 227 | +** "ssl" = FOSSIL_ENABLE_SSL | |
| 228 | +** "tcl" = FOSSIL_ENABLE_TCL | |
| 229 | +** "tclStubs" = FOSSIL_ENABLE_TCL_STUBS | |
| 229 | 230 | ** |
| 230 | 231 | */ |
| 231 | 232 | static int hasfeatureCmd( |
| 232 | 233 | Th_Interp *interp, |
| 233 | 234 | void *p, |
| @@ -256,10 +257,15 @@ | ||
| 256 | 257 | #endif |
| 257 | 258 | #if defined(FOSSIL_ENABLE_TCL) |
| 258 | 259 | else if( 0 == fossil_strnicmp( zArg, "tcl", 3 ) ){ |
| 259 | 260 | rc = 1; |
| 260 | 261 | } |
| 262 | +#endif | |
| 263 | +#if defined(FOSSIL_ENABLE_TCL_STUBS) | |
| 264 | + else if( 0 == fossil_strnicmp( zArg, "tclStubs", 8 ) ){ | |
| 265 | + rc = 1; | |
| 266 | + } | |
| 261 | 267 | #endif |
| 262 | 268 | if( g.thTrace ){ |
| 263 | 269 | Th_Trace("[hasfeature %#h] => %d<br />\n", argl[1], zArg, rc); |
| 264 | 270 | } |
| 265 | 271 | Th_SetResultInt(interp, rc); |
| @@ -447,10 +453,11 @@ | ||
| 447 | 453 | int i; |
| 448 | 454 | g.interp = Th_CreateInterp(&vtab); |
| 449 | 455 | th_register_language(g.interp); /* Basic scripting commands. */ |
| 450 | 456 | #ifdef FOSSIL_ENABLE_TCL |
| 451 | 457 | if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){ |
| 458 | + g.tcl.setup = db_get("tcl-setup", 0); /* Grab optional setup script. */ | |
| 452 | 459 | th_register_tcl(g.interp, &g.tcl); /* Tcl integration commands. */ |
| 453 | 460 | } |
| 454 | 461 | #endif |
| 455 | 462 | for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){ |
| 456 | 463 | if ( !aCommand[i].zName || !aCommand[i].xProc ) continue; |
| 457 | 464 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -221,13 +221,14 @@ | |
| 221 | ** TH command: hasfeature STRING |
| 222 | ** |
| 223 | ** Return true if the fossil binary has the given compile-time feature |
| 224 | ** enabled. The set of features includes: |
| 225 | ** |
| 226 | ** "json" = FOSSIL_ENABLE_JSON |
| 227 | ** "tcl" = FOSSIL_ENABLE_TCL |
| 228 | ** "ssl" = FOSSIL_ENABLE_SSL |
| 229 | ** |
| 230 | */ |
| 231 | static int hasfeatureCmd( |
| 232 | Th_Interp *interp, |
| 233 | void *p, |
| @@ -256,10 +257,15 @@ | |
| 256 | #endif |
| 257 | #if defined(FOSSIL_ENABLE_TCL) |
| 258 | else if( 0 == fossil_strnicmp( zArg, "tcl", 3 ) ){ |
| 259 | rc = 1; |
| 260 | } |
| 261 | #endif |
| 262 | if( g.thTrace ){ |
| 263 | Th_Trace("[hasfeature %#h] => %d<br />\n", argl[1], zArg, rc); |
| 264 | } |
| 265 | Th_SetResultInt(interp, rc); |
| @@ -447,10 +453,11 @@ | |
| 447 | int i; |
| 448 | g.interp = Th_CreateInterp(&vtab); |
| 449 | th_register_language(g.interp); /* Basic scripting commands. */ |
| 450 | #ifdef FOSSIL_ENABLE_TCL |
| 451 | if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){ |
| 452 | th_register_tcl(g.interp, &g.tcl); /* Tcl integration commands. */ |
| 453 | } |
| 454 | #endif |
| 455 | for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){ |
| 456 | if ( !aCommand[i].zName || !aCommand[i].xProc ) continue; |
| 457 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -221,13 +221,14 @@ | |
| 221 | ** TH command: hasfeature STRING |
| 222 | ** |
| 223 | ** Return true if the fossil binary has the given compile-time feature |
| 224 | ** enabled. The set of features includes: |
| 225 | ** |
| 226 | ** "json" = FOSSIL_ENABLE_JSON |
| 227 | ** "ssl" = FOSSIL_ENABLE_SSL |
| 228 | ** "tcl" = FOSSIL_ENABLE_TCL |
| 229 | ** "tclStubs" = FOSSIL_ENABLE_TCL_STUBS |
| 230 | ** |
| 231 | */ |
| 232 | static int hasfeatureCmd( |
| 233 | Th_Interp *interp, |
| 234 | void *p, |
| @@ -256,10 +257,15 @@ | |
| 257 | #endif |
| 258 | #if defined(FOSSIL_ENABLE_TCL) |
| 259 | else if( 0 == fossil_strnicmp( zArg, "tcl", 3 ) ){ |
| 260 | rc = 1; |
| 261 | } |
| 262 | #endif |
| 263 | #if defined(FOSSIL_ENABLE_TCL_STUBS) |
| 264 | else if( 0 == fossil_strnicmp( zArg, "tclStubs", 8 ) ){ |
| 265 | rc = 1; |
| 266 | } |
| 267 | #endif |
| 268 | if( g.thTrace ){ |
| 269 | Th_Trace("[hasfeature %#h] => %d<br />\n", argl[1], zArg, rc); |
| 270 | } |
| 271 | Th_SetResultInt(interp, rc); |
| @@ -447,10 +453,11 @@ | |
| 453 | int i; |
| 454 | g.interp = Th_CreateInterp(&vtab); |
| 455 | th_register_language(g.interp); /* Basic scripting commands. */ |
| 456 | #ifdef FOSSIL_ENABLE_TCL |
| 457 | if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){ |
| 458 | g.tcl.setup = db_get("tcl-setup", 0); /* Grab optional setup script. */ |
| 459 | th_register_tcl(g.interp, &g.tcl); /* Tcl integration commands. */ |
| 460 | } |
| 461 | #endif |
| 462 | for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){ |
| 463 | if ( !aCommand[i].zName || !aCommand[i].xProc ) continue; |
| 464 |
+242
-17
| --- src/th_tcl.c | ||
| +++ src/th_tcl.c | ||
| @@ -32,11 +32,11 @@ | ||
| 32 | 32 | ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 6)) |
| 33 | 33 | /* |
| 34 | 34 | ** Workaround NRE-specific issue in Tcl_EvalObjCmd (SF bug #3399564) by using |
| 35 | 35 | ** Tcl_EvalObjv instead of invoking the objProc directly. |
| 36 | 36 | */ |
| 37 | -#define USE_TCL_EVALOBJV 1 | |
| 37 | +# define USE_TCL_EVALOBJV 1 | |
| 38 | 38 | #endif |
| 39 | 39 | |
| 40 | 40 | /* |
| 41 | 41 | ** These macros are designed to reduce the redundant code required to marshal |
| 42 | 42 | ** arguments from TH1 to Tcl. |
| @@ -64,10 +64,94 @@ | ||
| 64 | 64 | ** Fetch the Tcl interpreter from the specified void pointer, cast to a Tcl |
| 65 | 65 | ** context. |
| 66 | 66 | */ |
| 67 | 67 | #define GET_CTX_TCL_INTERP(ctx) \ |
| 68 | 68 | ((struct TclContext *)(ctx))->interp |
| 69 | + | |
| 70 | +/* | |
| 71 | +** Define the Tcl shared library name, some exported function names, and some | |
| 72 | +** cross-platform macros for use with the Tcl stubs mechanism, when enabled. | |
| 73 | + */ | |
| 74 | +#if defined(USE_TCL_STUBS) | |
| 75 | +# if defined(_WIN32) | |
| 76 | +# define WIN32_LEAN_AND_MEAN | |
| 77 | +# include <windows.h> | |
| 78 | +# ifndef TCL_LIBRARY_NAME | |
| 79 | +# define TCL_LIBRARY_NAME "tcl86.dll\0" | |
| 80 | +# endif | |
| 81 | +# ifndef TCL_MINOR_OFFSET | |
| 82 | +# define TCL_MINOR_OFFSET (4) | |
| 83 | +# endif | |
| 84 | +# ifndef dlopen | |
| 85 | +# define dlopen(a,b) (void *)LoadLibrary((a)) | |
| 86 | +# endif | |
| 87 | +# ifndef dlsym | |
| 88 | +# define dlsym(a,b) GetProcAddress((HANDLE)(a),(b)) | |
| 89 | +# endif | |
| 90 | +# ifndef dlclose | |
| 91 | +# define dlclose(a) FreeLibrary((HANDLE)(a)) | |
| 92 | +# endif | |
| 93 | +# else | |
| 94 | +# include <dlfcn.h> | |
| 95 | +# if defined(__CYGWIN__) | |
| 96 | +# ifndef TCL_LIBRARY_NAME | |
| 97 | +# define TCL_LIBRARY_NAME "libtcl8.6.dll\0" | |
| 98 | +# endif | |
| 99 | +# ifndef TCL_MINOR_OFFSET | |
| 100 | +# define TCL_MINOR_OFFSET (8) | |
| 101 | +# endif | |
| 102 | +# elif defined(__APPLE__) | |
| 103 | +# ifndef TCL_LIBRARY_NAME | |
| 104 | +# define TCL_LIBRARY_NAME "libtcl8.6.dylib\0" | |
| 105 | +# endif | |
| 106 | +# ifndef TCL_MINOR_OFFSET | |
| 107 | +# define TCL_MINOR_OFFSET (8) | |
| 108 | +# endif | |
| 109 | +# else | |
| 110 | +# ifndef TCL_LIBRARY_NAME | |
| 111 | +# define TCL_LIBRARY_NAME "libtcl8.6.so\0" | |
| 112 | +# endif | |
| 113 | +# ifndef TCL_MINOR_OFFSET | |
| 114 | +# define TCL_MINOR_OFFSET (8) | |
| 115 | +# endif | |
| 116 | +# endif /* defined(__CYGWIN__) */ | |
| 117 | +# endif /* defined(_WIN32) */ | |
| 118 | +# ifndef TCL_FINDEXECUTABLE_NAME | |
| 119 | +# define TCL_FINDEXECUTABLE_NAME "_Tcl_FindExecutable" | |
| 120 | +# endif | |
| 121 | +# ifndef TCL_CREATEINTERP_NAME | |
| 122 | +# define TCL_CREATEINTERP_NAME "_Tcl_CreateInterp" | |
| 123 | +# endif | |
| 124 | +#endif /* defined(USE_TCL_STUBS) */ | |
| 125 | + | |
| 126 | +/* | |
| 127 | +** The function types for Tcl_FindExecutable and Tcl_CreateInterp are needed | |
| 128 | +** when the Tcl library is being loaded dynamically by a stubs-enabled | |
| 129 | +** application (i.e. the inverse of using a stubs-enabled package). These are | |
| 130 | +** the only Tcl API functions that MUST be called prior to being able to call | |
| 131 | +** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). | |
| 132 | + */ | |
| 133 | +typedef void (tcl_FindExecutableProc) (CONST char * argv0); | |
| 134 | +typedef Tcl_Interp *(tcl_CreateInterpProc) (void); | |
| 135 | + | |
| 136 | +/* | |
| 137 | +** The function types for the "hook" functions to be called before and after a | |
| 138 | +** TH1 command makes a call to evaluate a Tcl script. If the "pre" function | |
| 139 | +** returns anything but TH_OK, then evaluation of the Tcl script is skipped and | |
| 140 | +** that value is used as the return code. If the "post" function returns | |
| 141 | +** anything other than its rc argument, that will become the new return code | |
| 142 | +** for the command. | |
| 143 | + */ | |
| 144 | +typedef int (tcl_NotifyProc) ( | |
| 145 | + void *pContext, /* The context for this notification. */ | |
| 146 | + Th_Interp *interp, /* The TH1 interpreter being used. */ | |
| 147 | + void *ctx, /* The original TH1 command context. */ | |
| 148 | + int argc, /* Number of arguments for the TH1 command. */ | |
| 149 | + const char **argv, /* Array of arguments for the TH1 command. */ | |
| 150 | + int *argl, /* Array of lengths for the TH1 command arguments. */ | |
| 151 | + int rc /* Recommended notification return value. */ | |
| 152 | +); | |
| 69 | 153 | |
| 70 | 154 | /* |
| 71 | 155 | ** Creates and initializes a Tcl interpreter for use with the specified TH1 |
| 72 | 156 | ** interpreter. Stores the created Tcl interpreter in the Tcl context supplied |
| 73 | 157 | ** by the caller. This must be declared here because quite a few functions in |
| @@ -100,14 +184,54 @@ | ||
| 100 | 184 | /* |
| 101 | 185 | ** Tcl context information used by TH1. This structure definition has been |
| 102 | 186 | ** copied from and should be kept in sync with the one in "main.c". |
| 103 | 187 | */ |
| 104 | 188 | struct TclContext { |
| 105 | - int argc; | |
| 106 | - char **argv; | |
| 107 | - Tcl_Interp *interp; | |
| 189 | + int argc; /* Number of original arguments. */ | |
| 190 | + char **argv; /* Full copy of the original arguments. */ | |
| 191 | + void *library; /* The Tcl library module handle. */ | |
| 192 | + tcl_FindExecutableProc *xFindExecutable; /* Tcl_FindExecutable() pointer. */ | |
| 193 | + tcl_CreateInterpProc *xCreateInterp; /* Tcl_CreateInterp() pointer. */ | |
| 194 | + Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ | |
| 195 | + char *setup; /* The optional Tcl setup script. */ | |
| 196 | + tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */ | |
| 197 | + void *pPreContext; /* Optional, provided to xPreEval(). */ | |
| 198 | + tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */ | |
| 199 | + void *pPostContext; /* Optional, provided to xPostEval(). */ | |
| 108 | 200 | }; |
| 201 | + | |
| 202 | +/* | |
| 203 | +** This function calls the configured xPreEval or xPostEval functions, if any. | |
| 204 | +** May have arbitrary side-effects. This function returns the result of the | |
| 205 | +** called notification function or the value of the rc argument if there is no | |
| 206 | +** notification function configured. | |
| 207 | +*/ | |
| 208 | +static int notifyPreOrPostEval( | |
| 209 | + int bIsPost, | |
| 210 | + Th_Interp *interp, | |
| 211 | + void *ctx, | |
| 212 | + int argc, | |
| 213 | + const char **argv, | |
| 214 | + int *argl, | |
| 215 | + int rc | |
| 216 | +){ | |
| 217 | + struct TclContext *tclContext = (struct TclContext *)ctx; | |
| 218 | + tcl_NotifyProc *xNotifyProc; | |
| 219 | + | |
| 220 | + if ( !tclContext ){ | |
| 221 | + Th_ErrorMessage(interp, | |
| 222 | + "Invalid Tcl context", (const char *)"", 0); | |
| 223 | + return TH_ERROR; | |
| 224 | + } | |
| 225 | + xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval; | |
| 226 | + if ( xNotifyProc ){ | |
| 227 | + rc = xNotifyProc(bIsPost ? | |
| 228 | + tclContext->pPostContext : tclContext->pPreContext, | |
| 229 | + interp, ctx, argc, argv, argl, rc); | |
| 230 | + } | |
| 231 | + return rc; | |
| 232 | +} | |
| 109 | 233 | |
| 110 | 234 | /* |
| 111 | 235 | ** Syntax: |
| 112 | 236 | ** |
| 113 | 237 | ** tclEval arg ?arg ...? |
| @@ -119,24 +243,28 @@ | ||
| 119 | 243 | const char **argv, |
| 120 | 244 | int *argl |
| 121 | 245 | ){ |
| 122 | 246 | Tcl_Interp *tclInterp; |
| 123 | 247 | Tcl_Obj *objPtr; |
| 124 | - int rc; | |
| 248 | + int rc = TH_OK; | |
| 125 | 249 | int nResult; |
| 126 | 250 | const char *zResult; |
| 127 | 251 | |
| 128 | - if ( createTclInterp(interp, ctx)!=TH_OK ){ | |
| 252 | + if( createTclInterp(interp, ctx)!=TH_OK ){ | |
| 129 | 253 | return TH_ERROR; |
| 130 | 254 | } |
| 131 | 255 | if( argc<2 ){ |
| 132 | 256 | return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?"); |
| 133 | 257 | } |
| 134 | 258 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 135 | 259 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 136 | 260 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 137 | 261 | return TH_ERROR; |
| 262 | + } | |
| 263 | + rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 264 | + if( rc!=TH_OK ){ | |
| 265 | + return rc; | |
| 138 | 266 | } |
| 139 | 267 | Tcl_Preserve((ClientData)tclInterp); |
| 140 | 268 | if( argc==2 ){ |
| 141 | 269 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 142 | 270 | Tcl_IncrRefCount(objPtr); |
| @@ -152,10 +280,11 @@ | ||
| 152 | 280 | FREE_ARGV_TO_OBJV(); |
| 153 | 281 | } |
| 154 | 282 | zResult = getTclResult(tclInterp, &nResult); |
| 155 | 283 | Th_SetResult(interp, zResult, nResult); |
| 156 | 284 | Tcl_Release((ClientData)tclInterp); |
| 285 | + rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, rc); | |
| 157 | 286 | return rc; |
| 158 | 287 | } |
| 159 | 288 | |
| 160 | 289 | /* |
| 161 | 290 | ** Syntax: |
| @@ -170,24 +299,28 @@ | ||
| 170 | 299 | int *argl |
| 171 | 300 | ){ |
| 172 | 301 | Tcl_Interp *tclInterp; |
| 173 | 302 | Tcl_Obj *objPtr; |
| 174 | 303 | Tcl_Obj *resultObjPtr; |
| 175 | - int rc; | |
| 304 | + int rc = TH_OK; | |
| 176 | 305 | int nResult; |
| 177 | 306 | const char *zResult; |
| 178 | 307 | |
| 179 | - if ( createTclInterp(interp, ctx)!=TH_OK ){ | |
| 308 | + if( createTclInterp(interp, ctx)!=TH_OK ){ | |
| 180 | 309 | return TH_ERROR; |
| 181 | 310 | } |
| 182 | 311 | if( argc<2 ){ |
| 183 | 312 | return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?"); |
| 184 | 313 | } |
| 185 | 314 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 186 | 315 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 187 | 316 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 188 | 317 | return TH_ERROR; |
| 318 | + } | |
| 319 | + rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 320 | + if( rc!=TH_OK ){ | |
| 321 | + return rc; | |
| 189 | 322 | } |
| 190 | 323 | Tcl_Preserve((ClientData)tclInterp); |
| 191 | 324 | if( argc==2 ){ |
| 192 | 325 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 193 | 326 | Tcl_IncrRefCount(objPtr); |
| @@ -208,10 +341,11 @@ | ||
| 208 | 341 | zResult = getTclResult(tclInterp, &nResult); |
| 209 | 342 | } |
| 210 | 343 | Th_SetResult(interp, zResult, nResult); |
| 211 | 344 | if( rc==TCL_OK ) Tcl_DecrRefCount(resultObjPtr); |
| 212 | 345 | Tcl_Release((ClientData)tclInterp); |
| 346 | + rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, rc); | |
| 213 | 347 | return rc; |
| 214 | 348 | } |
| 215 | 349 | |
| 216 | 350 | /* |
| 217 | 351 | ** Syntax: |
| @@ -224,35 +358,39 @@ | ||
| 224 | 358 | int argc, |
| 225 | 359 | const char **argv, |
| 226 | 360 | int *argl |
| 227 | 361 | ){ |
| 228 | 362 | Tcl_Interp *tclInterp; |
| 229 | -#ifndef USE_TCL_EVALOBJV | |
| 363 | +#if !defined(USE_TCL_EVALOBJV) | |
| 230 | 364 | Tcl_Command command; |
| 231 | 365 | Tcl_CmdInfo cmdInfo; |
| 232 | 366 | #endif |
| 233 | - int rc; | |
| 367 | + int rc = TH_OK; | |
| 234 | 368 | int nResult; |
| 235 | 369 | const char *zResult; |
| 236 | -#ifndef USE_TCL_EVALOBJV | |
| 370 | +#if !defined(USE_TCL_EVALOBJV) | |
| 237 | 371 | Tcl_Obj *objPtr; |
| 238 | 372 | #endif |
| 239 | 373 | USE_ARGV_TO_OBJV(); |
| 240 | 374 | |
| 241 | - if ( createTclInterp(interp, ctx)!=TH_OK ){ | |
| 375 | + if( createTclInterp(interp, ctx)!=TH_OK ){ | |
| 242 | 376 | return TH_ERROR; |
| 243 | 377 | } |
| 244 | 378 | if( argc<2 ){ |
| 245 | 379 | return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?"); |
| 246 | 380 | } |
| 247 | 381 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 248 | 382 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 249 | 383 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 250 | 384 | return TH_ERROR; |
| 385 | + } | |
| 386 | + rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 387 | + if( rc!=TH_OK ){ | |
| 388 | + return rc; | |
| 251 | 389 | } |
| 252 | 390 | Tcl_Preserve((ClientData)tclInterp); |
| 253 | -#ifndef USE_TCL_EVALOBJV | |
| 391 | +#if !defined(USE_TCL_EVALOBJV) | |
| 254 | 392 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 255 | 393 | Tcl_IncrRefCount(objPtr); |
| 256 | 394 | command = Tcl_GetCommandFromObj(tclInterp, objPtr); |
| 257 | 395 | if( !command || Tcl_GetCommandInfoFromToken(command,&cmdInfo)==0 ){ |
| 258 | 396 | Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); |
| @@ -267,20 +405,21 @@ | ||
| 267 | 405 | return TH_ERROR; |
| 268 | 406 | } |
| 269 | 407 | Tcl_DecrRefCount(objPtr); |
| 270 | 408 | #endif |
| 271 | 409 | COPY_ARGV_TO_OBJV(); |
| 272 | -#ifdef USE_TCL_EVALOBJV | |
| 410 | +#if defined(USE_TCL_EVALOBJV) | |
| 273 | 411 | rc = Tcl_EvalObjv(tclInterp, objc, objv, 0); |
| 274 | 412 | #else |
| 275 | 413 | Tcl_ResetResult(tclInterp); |
| 276 | 414 | rc = cmdInfo.objProc(cmdInfo.objClientData, tclInterp, objc, objv); |
| 277 | 415 | #endif |
| 278 | 416 | FREE_ARGV_TO_OBJV(); |
| 279 | 417 | zResult = getTclResult(tclInterp, &nResult); |
| 280 | 418 | Th_SetResult(interp, zResult, nResult); |
| 281 | 419 | Tcl_Release((ClientData)tclInterp); |
| 420 | + rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, rc); | |
| 282 | 421 | return rc; |
| 283 | 422 | } |
| 284 | 423 | |
| 285 | 424 | /* |
| 286 | 425 | ** Syntax: |
| @@ -375,10 +514,77 @@ | ||
| 375 | 514 | /* Remove the Tcl integration commands. */ |
| 376 | 515 | for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){ |
| 377 | 516 | Th_RenameCommand(th1Interp, aCommand[i].zName, -1, NULL, 0); |
| 378 | 517 | } |
| 379 | 518 | } |
| 519 | + | |
| 520 | +/* | |
| 521 | +** When Tcl stubs support is enabled, attempts to dynamically load the Tcl | |
| 522 | +** shared library and fetch the function pointers necessary to create an | |
| 523 | +** interpreter and initialize the stubs mechanism; otherwise, simply setup | |
| 524 | +** the function pointers provided by the caller with the statically linked | |
| 525 | +** functions. | |
| 526 | + */ | |
| 527 | +static int loadTcl( | |
| 528 | + Th_Interp *interp, | |
| 529 | + void **pLibrary, | |
| 530 | + tcl_FindExecutableProc **pxFindExecutable, | |
| 531 | + tcl_CreateInterpProc **pxCreateInterp | |
| 532 | +){ | |
| 533 | +#if defined(USE_TCL_STUBS) | |
| 534 | + char fileName[] = TCL_LIBRARY_NAME; | |
| 535 | +#endif | |
| 536 | + if( !pLibrary || !pxFindExecutable || !pxCreateInterp ){ | |
| 537 | + Th_ErrorMessage(interp, | |
| 538 | + "Invalid Tcl loader argument(s)", (const char *)"", 0); | |
| 539 | + return TH_ERROR; | |
| 540 | + } | |
| 541 | +#if defined(USE_TCL_STUBS) | |
| 542 | + do { | |
| 543 | + void *library = dlopen(fileName, RTLD_NOW | RTLD_GLOBAL); | |
| 544 | + if( library ){ | |
| 545 | + tcl_FindExecutableProc *xFindExecutable; | |
| 546 | + tcl_CreateInterpProc *xCreateInterp; | |
| 547 | + const char *procName = TCL_FINDEXECUTABLE_NAME; | |
| 548 | + xFindExecutable = (tcl_FindExecutableProc *)dlsym(library, procName + 1); | |
| 549 | + if( !xFindExecutable ){ | |
| 550 | + xFindExecutable = (tcl_FindExecutableProc *)dlsym(library, procName); | |
| 551 | + } | |
| 552 | + if( !xFindExecutable ){ | |
| 553 | + Th_ErrorMessage(interp, | |
| 554 | + "Could not locate Tcl_FindExecutable", (const char *)"", 0); | |
| 555 | + dlclose(library); | |
| 556 | + return TH_ERROR; | |
| 557 | + } | |
| 558 | + procName = TCL_CREATEINTERP_NAME; | |
| 559 | + xCreateInterp = (tcl_CreateInterpProc *)dlsym(library, procName + 1); | |
| 560 | + if( !xCreateInterp ){ | |
| 561 | + xCreateInterp = (tcl_CreateInterpProc *)dlsym(library, procName); | |
| 562 | + } | |
| 563 | + if( !xCreateInterp ){ | |
| 564 | + Th_ErrorMessage(interp, | |
| 565 | + "Could not locate Tcl_CreateInterp", (const char *)"", 0); | |
| 566 | + dlclose(library); | |
| 567 | + return TH_ERROR; | |
| 568 | + } | |
| 569 | + *pLibrary = library; | |
| 570 | + *pxFindExecutable = xFindExecutable; | |
| 571 | + *pxCreateInterp = xCreateInterp; | |
| 572 | + return TH_OK; | |
| 573 | + } | |
| 574 | + } while( --fileName[TCL_MINOR_OFFSET]>'3' ); /* Tcl 8.4+ */ | |
| 575 | + Th_ErrorMessage(interp, | |
| 576 | + "Could not load Tcl shared library \"" TCL_LIBRARY_NAME "\"", | |
| 577 | + (const char *)"", 0); | |
| 578 | + return TH_ERROR; | |
| 579 | +#else | |
| 580 | + *pLibrary = 0; | |
| 581 | + *pxFindExecutable = Tcl_FindExecutable; | |
| 582 | + *pxCreateInterp = Tcl_CreateInterp; | |
| 583 | + return TH_OK; | |
| 584 | +#endif | |
| 585 | +} | |
| 380 | 586 | |
| 381 | 587 | /* |
| 382 | 588 | ** Sets the "argv0", "argc", and "argv" script variables in the Tcl interpreter |
| 383 | 589 | ** based on the supplied command line arguments. |
| 384 | 590 | */ |
| @@ -446,31 +652,41 @@ | ||
| 446 | 652 | struct TclContext *tclContext = (struct TclContext *)pContext; |
| 447 | 653 | int argc; |
| 448 | 654 | char **argv; |
| 449 | 655 | char *argv0 = 0; |
| 450 | 656 | Tcl_Interp *tclInterp; |
| 657 | + char *setup; | |
| 451 | 658 | |
| 452 | 659 | if ( !tclContext ){ |
| 453 | 660 | Th_ErrorMessage(interp, |
| 454 | 661 | "Invalid Tcl context", (const char *)"", 0); |
| 455 | 662 | return TH_ERROR; |
| 456 | 663 | } |
| 457 | 664 | if ( tclContext->interp ){ |
| 458 | 665 | return TH_OK; |
| 666 | + } | |
| 667 | + if( loadTcl(interp, &tclContext->library, &tclContext->xFindExecutable, | |
| 668 | + &tclContext->xCreateInterp)!=TH_OK ){ | |
| 669 | + return TH_ERROR; | |
| 459 | 670 | } |
| 460 | 671 | argc = tclContext->argc; |
| 461 | 672 | argv = tclContext->argv; |
| 462 | 673 | if( argc>0 && argv ){ |
| 463 | 674 | argv0 = argv[0]; |
| 464 | 675 | } |
| 465 | - Tcl_FindExecutable(argv0); | |
| 466 | - tclInterp = tclContext->interp = Tcl_CreateInterp(); | |
| 467 | - if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ | |
| 676 | + tclContext->xFindExecutable(argv0); | |
| 677 | + tclInterp = tclContext->xCreateInterp(); | |
| 678 | + if( !tclInterp || | |
| 679 | +#if defined(USE_TCL_STUBS) | |
| 680 | + !Tcl_InitStubs(tclInterp, "8.4", 0) || | |
| 681 | +#endif | |
| 682 | + Tcl_InterpDeleted(tclInterp) ){ | |
| 468 | 683 | Th_ErrorMessage(interp, |
| 469 | 684 | "Could not create Tcl interpreter", (const char *)"", 0); |
| 470 | 685 | return TH_ERROR; |
| 471 | 686 | } |
| 687 | + tclContext->interp = tclInterp; | |
| 472 | 688 | if( Tcl_Init(tclInterp)!=TCL_OK ){ |
| 473 | 689 | Th_ErrorMessage(interp, |
| 474 | 690 | "Tcl initialization error:", Tcl_GetStringResult(tclInterp), -1); |
| 475 | 691 | Tcl_DeleteInterp(tclInterp); |
| 476 | 692 | tclContext->interp = tclInterp = 0; |
| @@ -485,10 +701,19 @@ | ||
| 485 | 701 | } |
| 486 | 702 | /* Add the TH1 integration commands to Tcl. */ |
| 487 | 703 | Tcl_CallWhenDeleted(tclInterp, Th1DeleteProc, interp); |
| 488 | 704 | Tcl_CreateObjCommand(tclInterp, "th1Eval", Th1EvalObjCmd, interp, NULL); |
| 489 | 705 | Tcl_CreateObjCommand(tclInterp, "th1Expr", Th1ExprObjCmd, interp, NULL); |
| 706 | + /* If necessary, evaluate the custom Tcl setup script. */ | |
| 707 | + setup = tclContext->setup; | |
| 708 | + if( setup && Tcl_Eval(tclInterp, setup)!=TCL_OK ){ | |
| 709 | + Th_ErrorMessage(interp, | |
| 710 | + "Tcl setup script error:", Tcl_GetStringResult(tclInterp), -1); | |
| 711 | + Tcl_DeleteInterp(tclInterp); | |
| 712 | + tclContext->interp = tclInterp = 0; | |
| 713 | + return TH_ERROR; | |
| 714 | + } | |
| 490 | 715 | return TH_OK; |
| 491 | 716 | } |
| 492 | 717 | |
| 493 | 718 | /* |
| 494 | 719 | ** Register the Tcl language commands with interpreter interp. |
| 495 | 720 |
| --- src/th_tcl.c | |
| +++ src/th_tcl.c | |
| @@ -32,11 +32,11 @@ | |
| 32 | ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 6)) |
| 33 | /* |
| 34 | ** Workaround NRE-specific issue in Tcl_EvalObjCmd (SF bug #3399564) by using |
| 35 | ** Tcl_EvalObjv instead of invoking the objProc directly. |
| 36 | */ |
| 37 | #define USE_TCL_EVALOBJV 1 |
| 38 | #endif |
| 39 | |
| 40 | /* |
| 41 | ** These macros are designed to reduce the redundant code required to marshal |
| 42 | ** arguments from TH1 to Tcl. |
| @@ -64,10 +64,94 @@ | |
| 64 | ** Fetch the Tcl interpreter from the specified void pointer, cast to a Tcl |
| 65 | ** context. |
| 66 | */ |
| 67 | #define GET_CTX_TCL_INTERP(ctx) \ |
| 68 | ((struct TclContext *)(ctx))->interp |
| 69 | |
| 70 | /* |
| 71 | ** Creates and initializes a Tcl interpreter for use with the specified TH1 |
| 72 | ** interpreter. Stores the created Tcl interpreter in the Tcl context supplied |
| 73 | ** by the caller. This must be declared here because quite a few functions in |
| @@ -100,14 +184,54 @@ | |
| 100 | /* |
| 101 | ** Tcl context information used by TH1. This structure definition has been |
| 102 | ** copied from and should be kept in sync with the one in "main.c". |
| 103 | */ |
| 104 | struct TclContext { |
| 105 | int argc; |
| 106 | char **argv; |
| 107 | Tcl_Interp *interp; |
| 108 | }; |
| 109 | |
| 110 | /* |
| 111 | ** Syntax: |
| 112 | ** |
| 113 | ** tclEval arg ?arg ...? |
| @@ -119,24 +243,28 @@ | |
| 119 | const char **argv, |
| 120 | int *argl |
| 121 | ){ |
| 122 | Tcl_Interp *tclInterp; |
| 123 | Tcl_Obj *objPtr; |
| 124 | int rc; |
| 125 | int nResult; |
| 126 | const char *zResult; |
| 127 | |
| 128 | if ( createTclInterp(interp, ctx)!=TH_OK ){ |
| 129 | return TH_ERROR; |
| 130 | } |
| 131 | if( argc<2 ){ |
| 132 | return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?"); |
| 133 | } |
| 134 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 135 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 136 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 137 | return TH_ERROR; |
| 138 | } |
| 139 | Tcl_Preserve((ClientData)tclInterp); |
| 140 | if( argc==2 ){ |
| 141 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 142 | Tcl_IncrRefCount(objPtr); |
| @@ -152,10 +280,11 @@ | |
| 152 | FREE_ARGV_TO_OBJV(); |
| 153 | } |
| 154 | zResult = getTclResult(tclInterp, &nResult); |
| 155 | Th_SetResult(interp, zResult, nResult); |
| 156 | Tcl_Release((ClientData)tclInterp); |
| 157 | return rc; |
| 158 | } |
| 159 | |
| 160 | /* |
| 161 | ** Syntax: |
| @@ -170,24 +299,28 @@ | |
| 170 | int *argl |
| 171 | ){ |
| 172 | Tcl_Interp *tclInterp; |
| 173 | Tcl_Obj *objPtr; |
| 174 | Tcl_Obj *resultObjPtr; |
| 175 | int rc; |
| 176 | int nResult; |
| 177 | const char *zResult; |
| 178 | |
| 179 | if ( createTclInterp(interp, ctx)!=TH_OK ){ |
| 180 | return TH_ERROR; |
| 181 | } |
| 182 | if( argc<2 ){ |
| 183 | return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?"); |
| 184 | } |
| 185 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 186 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 187 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 188 | return TH_ERROR; |
| 189 | } |
| 190 | Tcl_Preserve((ClientData)tclInterp); |
| 191 | if( argc==2 ){ |
| 192 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 193 | Tcl_IncrRefCount(objPtr); |
| @@ -208,10 +341,11 @@ | |
| 208 | zResult = getTclResult(tclInterp, &nResult); |
| 209 | } |
| 210 | Th_SetResult(interp, zResult, nResult); |
| 211 | if( rc==TCL_OK ) Tcl_DecrRefCount(resultObjPtr); |
| 212 | Tcl_Release((ClientData)tclInterp); |
| 213 | return rc; |
| 214 | } |
| 215 | |
| 216 | /* |
| 217 | ** Syntax: |
| @@ -224,35 +358,39 @@ | |
| 224 | int argc, |
| 225 | const char **argv, |
| 226 | int *argl |
| 227 | ){ |
| 228 | Tcl_Interp *tclInterp; |
| 229 | #ifndef USE_TCL_EVALOBJV |
| 230 | Tcl_Command command; |
| 231 | Tcl_CmdInfo cmdInfo; |
| 232 | #endif |
| 233 | int rc; |
| 234 | int nResult; |
| 235 | const char *zResult; |
| 236 | #ifndef USE_TCL_EVALOBJV |
| 237 | Tcl_Obj *objPtr; |
| 238 | #endif |
| 239 | USE_ARGV_TO_OBJV(); |
| 240 | |
| 241 | if ( createTclInterp(interp, ctx)!=TH_OK ){ |
| 242 | return TH_ERROR; |
| 243 | } |
| 244 | if( argc<2 ){ |
| 245 | return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?"); |
| 246 | } |
| 247 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 248 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 249 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 250 | return TH_ERROR; |
| 251 | } |
| 252 | Tcl_Preserve((ClientData)tclInterp); |
| 253 | #ifndef USE_TCL_EVALOBJV |
| 254 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 255 | Tcl_IncrRefCount(objPtr); |
| 256 | command = Tcl_GetCommandFromObj(tclInterp, objPtr); |
| 257 | if( !command || Tcl_GetCommandInfoFromToken(command,&cmdInfo)==0 ){ |
| 258 | Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); |
| @@ -267,20 +405,21 @@ | |
| 267 | return TH_ERROR; |
| 268 | } |
| 269 | Tcl_DecrRefCount(objPtr); |
| 270 | #endif |
| 271 | COPY_ARGV_TO_OBJV(); |
| 272 | #ifdef USE_TCL_EVALOBJV |
| 273 | rc = Tcl_EvalObjv(tclInterp, objc, objv, 0); |
| 274 | #else |
| 275 | Tcl_ResetResult(tclInterp); |
| 276 | rc = cmdInfo.objProc(cmdInfo.objClientData, tclInterp, objc, objv); |
| 277 | #endif |
| 278 | FREE_ARGV_TO_OBJV(); |
| 279 | zResult = getTclResult(tclInterp, &nResult); |
| 280 | Th_SetResult(interp, zResult, nResult); |
| 281 | Tcl_Release((ClientData)tclInterp); |
| 282 | return rc; |
| 283 | } |
| 284 | |
| 285 | /* |
| 286 | ** Syntax: |
| @@ -375,10 +514,77 @@ | |
| 375 | /* Remove the Tcl integration commands. */ |
| 376 | for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){ |
| 377 | Th_RenameCommand(th1Interp, aCommand[i].zName, -1, NULL, 0); |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | /* |
| 382 | ** Sets the "argv0", "argc", and "argv" script variables in the Tcl interpreter |
| 383 | ** based on the supplied command line arguments. |
| 384 | */ |
| @@ -446,31 +652,41 @@ | |
| 446 | struct TclContext *tclContext = (struct TclContext *)pContext; |
| 447 | int argc; |
| 448 | char **argv; |
| 449 | char *argv0 = 0; |
| 450 | Tcl_Interp *tclInterp; |
| 451 | |
| 452 | if ( !tclContext ){ |
| 453 | Th_ErrorMessage(interp, |
| 454 | "Invalid Tcl context", (const char *)"", 0); |
| 455 | return TH_ERROR; |
| 456 | } |
| 457 | if ( tclContext->interp ){ |
| 458 | return TH_OK; |
| 459 | } |
| 460 | argc = tclContext->argc; |
| 461 | argv = tclContext->argv; |
| 462 | if( argc>0 && argv ){ |
| 463 | argv0 = argv[0]; |
| 464 | } |
| 465 | Tcl_FindExecutable(argv0); |
| 466 | tclInterp = tclContext->interp = Tcl_CreateInterp(); |
| 467 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 468 | Th_ErrorMessage(interp, |
| 469 | "Could not create Tcl interpreter", (const char *)"", 0); |
| 470 | return TH_ERROR; |
| 471 | } |
| 472 | if( Tcl_Init(tclInterp)!=TCL_OK ){ |
| 473 | Th_ErrorMessage(interp, |
| 474 | "Tcl initialization error:", Tcl_GetStringResult(tclInterp), -1); |
| 475 | Tcl_DeleteInterp(tclInterp); |
| 476 | tclContext->interp = tclInterp = 0; |
| @@ -485,10 +701,19 @@ | |
| 485 | } |
| 486 | /* Add the TH1 integration commands to Tcl. */ |
| 487 | Tcl_CallWhenDeleted(tclInterp, Th1DeleteProc, interp); |
| 488 | Tcl_CreateObjCommand(tclInterp, "th1Eval", Th1EvalObjCmd, interp, NULL); |
| 489 | Tcl_CreateObjCommand(tclInterp, "th1Expr", Th1ExprObjCmd, interp, NULL); |
| 490 | return TH_OK; |
| 491 | } |
| 492 | |
| 493 | /* |
| 494 | ** Register the Tcl language commands with interpreter interp. |
| 495 |
| --- src/th_tcl.c | |
| +++ src/th_tcl.c | |
| @@ -32,11 +32,11 @@ | |
| 32 | ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 6)) |
| 33 | /* |
| 34 | ** Workaround NRE-specific issue in Tcl_EvalObjCmd (SF bug #3399564) by using |
| 35 | ** Tcl_EvalObjv instead of invoking the objProc directly. |
| 36 | */ |
| 37 | # define USE_TCL_EVALOBJV 1 |
| 38 | #endif |
| 39 | |
| 40 | /* |
| 41 | ** These macros are designed to reduce the redundant code required to marshal |
| 42 | ** arguments from TH1 to Tcl. |
| @@ -64,10 +64,94 @@ | |
| 64 | ** Fetch the Tcl interpreter from the specified void pointer, cast to a Tcl |
| 65 | ** context. |
| 66 | */ |
| 67 | #define GET_CTX_TCL_INTERP(ctx) \ |
| 68 | ((struct TclContext *)(ctx))->interp |
| 69 | |
| 70 | /* |
| 71 | ** Define the Tcl shared library name, some exported function names, and some |
| 72 | ** cross-platform macros for use with the Tcl stubs mechanism, when enabled. |
| 73 | */ |
| 74 | #if defined(USE_TCL_STUBS) |
| 75 | # if defined(_WIN32) |
| 76 | # define WIN32_LEAN_AND_MEAN |
| 77 | # include <windows.h> |
| 78 | # ifndef TCL_LIBRARY_NAME |
| 79 | # define TCL_LIBRARY_NAME "tcl86.dll\0" |
| 80 | # endif |
| 81 | # ifndef TCL_MINOR_OFFSET |
| 82 | # define TCL_MINOR_OFFSET (4) |
| 83 | # endif |
| 84 | # ifndef dlopen |
| 85 | # define dlopen(a,b) (void *)LoadLibrary((a)) |
| 86 | # endif |
| 87 | # ifndef dlsym |
| 88 | # define dlsym(a,b) GetProcAddress((HANDLE)(a),(b)) |
| 89 | # endif |
| 90 | # ifndef dlclose |
| 91 | # define dlclose(a) FreeLibrary((HANDLE)(a)) |
| 92 | # endif |
| 93 | # else |
| 94 | # include <dlfcn.h> |
| 95 | # if defined(__CYGWIN__) |
| 96 | # ifndef TCL_LIBRARY_NAME |
| 97 | # define TCL_LIBRARY_NAME "libtcl8.6.dll\0" |
| 98 | # endif |
| 99 | # ifndef TCL_MINOR_OFFSET |
| 100 | # define TCL_MINOR_OFFSET (8) |
| 101 | # endif |
| 102 | # elif defined(__APPLE__) |
| 103 | # ifndef TCL_LIBRARY_NAME |
| 104 | # define TCL_LIBRARY_NAME "libtcl8.6.dylib\0" |
| 105 | # endif |
| 106 | # ifndef TCL_MINOR_OFFSET |
| 107 | # define TCL_MINOR_OFFSET (8) |
| 108 | # endif |
| 109 | # else |
| 110 | # ifndef TCL_LIBRARY_NAME |
| 111 | # define TCL_LIBRARY_NAME "libtcl8.6.so\0" |
| 112 | # endif |
| 113 | # ifndef TCL_MINOR_OFFSET |
| 114 | # define TCL_MINOR_OFFSET (8) |
| 115 | # endif |
| 116 | # endif /* defined(__CYGWIN__) */ |
| 117 | # endif /* defined(_WIN32) */ |
| 118 | # ifndef TCL_FINDEXECUTABLE_NAME |
| 119 | # define TCL_FINDEXECUTABLE_NAME "_Tcl_FindExecutable" |
| 120 | # endif |
| 121 | # ifndef TCL_CREATEINTERP_NAME |
| 122 | # define TCL_CREATEINTERP_NAME "_Tcl_CreateInterp" |
| 123 | # endif |
| 124 | #endif /* defined(USE_TCL_STUBS) */ |
| 125 | |
| 126 | /* |
| 127 | ** The function types for Tcl_FindExecutable and Tcl_CreateInterp are needed |
| 128 | ** when the Tcl library is being loaded dynamically by a stubs-enabled |
| 129 | ** application (i.e. the inverse of using a stubs-enabled package). These are |
| 130 | ** the only Tcl API functions that MUST be called prior to being able to call |
| 131 | ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). |
| 132 | */ |
| 133 | typedef void (tcl_FindExecutableProc) (CONST char * argv0); |
| 134 | typedef Tcl_Interp *(tcl_CreateInterpProc) (void); |
| 135 | |
| 136 | /* |
| 137 | ** The function types for the "hook" functions to be called before and after a |
| 138 | ** TH1 command makes a call to evaluate a Tcl script. If the "pre" function |
| 139 | ** returns anything but TH_OK, then evaluation of the Tcl script is skipped and |
| 140 | ** that value is used as the return code. If the "post" function returns |
| 141 | ** anything other than its rc argument, that will become the new return code |
| 142 | ** for the command. |
| 143 | */ |
| 144 | typedef int (tcl_NotifyProc) ( |
| 145 | void *pContext, /* The context for this notification. */ |
| 146 | Th_Interp *interp, /* The TH1 interpreter being used. */ |
| 147 | void *ctx, /* The original TH1 command context. */ |
| 148 | int argc, /* Number of arguments for the TH1 command. */ |
| 149 | const char **argv, /* Array of arguments for the TH1 command. */ |
| 150 | int *argl, /* Array of lengths for the TH1 command arguments. */ |
| 151 | int rc /* Recommended notification return value. */ |
| 152 | ); |
| 153 | |
| 154 | /* |
| 155 | ** Creates and initializes a Tcl interpreter for use with the specified TH1 |
| 156 | ** interpreter. Stores the created Tcl interpreter in the Tcl context supplied |
| 157 | ** by the caller. This must be declared here because quite a few functions in |
| @@ -100,14 +184,54 @@ | |
| 184 | /* |
| 185 | ** Tcl context information used by TH1. This structure definition has been |
| 186 | ** copied from and should be kept in sync with the one in "main.c". |
| 187 | */ |
| 188 | struct TclContext { |
| 189 | int argc; /* Number of original arguments. */ |
| 190 | char **argv; /* Full copy of the original arguments. */ |
| 191 | void *library; /* The Tcl library module handle. */ |
| 192 | tcl_FindExecutableProc *xFindExecutable; /* Tcl_FindExecutable() pointer. */ |
| 193 | tcl_CreateInterpProc *xCreateInterp; /* Tcl_CreateInterp() pointer. */ |
| 194 | Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ |
| 195 | char *setup; /* The optional Tcl setup script. */ |
| 196 | tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */ |
| 197 | void *pPreContext; /* Optional, provided to xPreEval(). */ |
| 198 | tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */ |
| 199 | void *pPostContext; /* Optional, provided to xPostEval(). */ |
| 200 | }; |
| 201 | |
| 202 | /* |
| 203 | ** This function calls the configured xPreEval or xPostEval functions, if any. |
| 204 | ** May have arbitrary side-effects. This function returns the result of the |
| 205 | ** called notification function or the value of the rc argument if there is no |
| 206 | ** notification function configured. |
| 207 | */ |
| 208 | static int notifyPreOrPostEval( |
| 209 | int bIsPost, |
| 210 | Th_Interp *interp, |
| 211 | void *ctx, |
| 212 | int argc, |
| 213 | const char **argv, |
| 214 | int *argl, |
| 215 | int rc |
| 216 | ){ |
| 217 | struct TclContext *tclContext = (struct TclContext *)ctx; |
| 218 | tcl_NotifyProc *xNotifyProc; |
| 219 | |
| 220 | if ( !tclContext ){ |
| 221 | Th_ErrorMessage(interp, |
| 222 | "Invalid Tcl context", (const char *)"", 0); |
| 223 | return TH_ERROR; |
| 224 | } |
| 225 | xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval; |
| 226 | if ( xNotifyProc ){ |
| 227 | rc = xNotifyProc(bIsPost ? |
| 228 | tclContext->pPostContext : tclContext->pPreContext, |
| 229 | interp, ctx, argc, argv, argl, rc); |
| 230 | } |
| 231 | return rc; |
| 232 | } |
| 233 | |
| 234 | /* |
| 235 | ** Syntax: |
| 236 | ** |
| 237 | ** tclEval arg ?arg ...? |
| @@ -119,24 +243,28 @@ | |
| 243 | const char **argv, |
| 244 | int *argl |
| 245 | ){ |
| 246 | Tcl_Interp *tclInterp; |
| 247 | Tcl_Obj *objPtr; |
| 248 | int rc = TH_OK; |
| 249 | int nResult; |
| 250 | const char *zResult; |
| 251 | |
| 252 | if( createTclInterp(interp, ctx)!=TH_OK ){ |
| 253 | return TH_ERROR; |
| 254 | } |
| 255 | if( argc<2 ){ |
| 256 | return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?"); |
| 257 | } |
| 258 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 259 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 260 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 261 | return TH_ERROR; |
| 262 | } |
| 263 | rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); |
| 264 | if( rc!=TH_OK ){ |
| 265 | return rc; |
| 266 | } |
| 267 | Tcl_Preserve((ClientData)tclInterp); |
| 268 | if( argc==2 ){ |
| 269 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 270 | Tcl_IncrRefCount(objPtr); |
| @@ -152,10 +280,11 @@ | |
| 280 | FREE_ARGV_TO_OBJV(); |
| 281 | } |
| 282 | zResult = getTclResult(tclInterp, &nResult); |
| 283 | Th_SetResult(interp, zResult, nResult); |
| 284 | Tcl_Release((ClientData)tclInterp); |
| 285 | rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, rc); |
| 286 | return rc; |
| 287 | } |
| 288 | |
| 289 | /* |
| 290 | ** Syntax: |
| @@ -170,24 +299,28 @@ | |
| 299 | int *argl |
| 300 | ){ |
| 301 | Tcl_Interp *tclInterp; |
| 302 | Tcl_Obj *objPtr; |
| 303 | Tcl_Obj *resultObjPtr; |
| 304 | int rc = TH_OK; |
| 305 | int nResult; |
| 306 | const char *zResult; |
| 307 | |
| 308 | if( createTclInterp(interp, ctx)!=TH_OK ){ |
| 309 | return TH_ERROR; |
| 310 | } |
| 311 | if( argc<2 ){ |
| 312 | return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?"); |
| 313 | } |
| 314 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 315 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 316 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 317 | return TH_ERROR; |
| 318 | } |
| 319 | rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); |
| 320 | if( rc!=TH_OK ){ |
| 321 | return rc; |
| 322 | } |
| 323 | Tcl_Preserve((ClientData)tclInterp); |
| 324 | if( argc==2 ){ |
| 325 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 326 | Tcl_IncrRefCount(objPtr); |
| @@ -208,10 +341,11 @@ | |
| 341 | zResult = getTclResult(tclInterp, &nResult); |
| 342 | } |
| 343 | Th_SetResult(interp, zResult, nResult); |
| 344 | if( rc==TCL_OK ) Tcl_DecrRefCount(resultObjPtr); |
| 345 | Tcl_Release((ClientData)tclInterp); |
| 346 | rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, rc); |
| 347 | return rc; |
| 348 | } |
| 349 | |
| 350 | /* |
| 351 | ** Syntax: |
| @@ -224,35 +358,39 @@ | |
| 358 | int argc, |
| 359 | const char **argv, |
| 360 | int *argl |
| 361 | ){ |
| 362 | Tcl_Interp *tclInterp; |
| 363 | #if !defined(USE_TCL_EVALOBJV) |
| 364 | Tcl_Command command; |
| 365 | Tcl_CmdInfo cmdInfo; |
| 366 | #endif |
| 367 | int rc = TH_OK; |
| 368 | int nResult; |
| 369 | const char *zResult; |
| 370 | #if !defined(USE_TCL_EVALOBJV) |
| 371 | Tcl_Obj *objPtr; |
| 372 | #endif |
| 373 | USE_ARGV_TO_OBJV(); |
| 374 | |
| 375 | if( createTclInterp(interp, ctx)!=TH_OK ){ |
| 376 | return TH_ERROR; |
| 377 | } |
| 378 | if( argc<2 ){ |
| 379 | return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?"); |
| 380 | } |
| 381 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 382 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 383 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 384 | return TH_ERROR; |
| 385 | } |
| 386 | rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); |
| 387 | if( rc!=TH_OK ){ |
| 388 | return rc; |
| 389 | } |
| 390 | Tcl_Preserve((ClientData)tclInterp); |
| 391 | #if !defined(USE_TCL_EVALOBJV) |
| 392 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 393 | Tcl_IncrRefCount(objPtr); |
| 394 | command = Tcl_GetCommandFromObj(tclInterp, objPtr); |
| 395 | if( !command || Tcl_GetCommandInfoFromToken(command,&cmdInfo)==0 ){ |
| 396 | Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); |
| @@ -267,20 +405,21 @@ | |
| 405 | return TH_ERROR; |
| 406 | } |
| 407 | Tcl_DecrRefCount(objPtr); |
| 408 | #endif |
| 409 | COPY_ARGV_TO_OBJV(); |
| 410 | #if defined(USE_TCL_EVALOBJV) |
| 411 | rc = Tcl_EvalObjv(tclInterp, objc, objv, 0); |
| 412 | #else |
| 413 | Tcl_ResetResult(tclInterp); |
| 414 | rc = cmdInfo.objProc(cmdInfo.objClientData, tclInterp, objc, objv); |
| 415 | #endif |
| 416 | FREE_ARGV_TO_OBJV(); |
| 417 | zResult = getTclResult(tclInterp, &nResult); |
| 418 | Th_SetResult(interp, zResult, nResult); |
| 419 | Tcl_Release((ClientData)tclInterp); |
| 420 | rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, rc); |
| 421 | return rc; |
| 422 | } |
| 423 | |
| 424 | /* |
| 425 | ** Syntax: |
| @@ -375,10 +514,77 @@ | |
| 514 | /* Remove the Tcl integration commands. */ |
| 515 | for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){ |
| 516 | Th_RenameCommand(th1Interp, aCommand[i].zName, -1, NULL, 0); |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /* |
| 521 | ** When Tcl stubs support is enabled, attempts to dynamically load the Tcl |
| 522 | ** shared library and fetch the function pointers necessary to create an |
| 523 | ** interpreter and initialize the stubs mechanism; otherwise, simply setup |
| 524 | ** the function pointers provided by the caller with the statically linked |
| 525 | ** functions. |
| 526 | */ |
| 527 | static int loadTcl( |
| 528 | Th_Interp *interp, |
| 529 | void **pLibrary, |
| 530 | tcl_FindExecutableProc **pxFindExecutable, |
| 531 | tcl_CreateInterpProc **pxCreateInterp |
| 532 | ){ |
| 533 | #if defined(USE_TCL_STUBS) |
| 534 | char fileName[] = TCL_LIBRARY_NAME; |
| 535 | #endif |
| 536 | if( !pLibrary || !pxFindExecutable || !pxCreateInterp ){ |
| 537 | Th_ErrorMessage(interp, |
| 538 | "Invalid Tcl loader argument(s)", (const char *)"", 0); |
| 539 | return TH_ERROR; |
| 540 | } |
| 541 | #if defined(USE_TCL_STUBS) |
| 542 | do { |
| 543 | void *library = dlopen(fileName, RTLD_NOW | RTLD_GLOBAL); |
| 544 | if( library ){ |
| 545 | tcl_FindExecutableProc *xFindExecutable; |
| 546 | tcl_CreateInterpProc *xCreateInterp; |
| 547 | const char *procName = TCL_FINDEXECUTABLE_NAME; |
| 548 | xFindExecutable = (tcl_FindExecutableProc *)dlsym(library, procName + 1); |
| 549 | if( !xFindExecutable ){ |
| 550 | xFindExecutable = (tcl_FindExecutableProc *)dlsym(library, procName); |
| 551 | } |
| 552 | if( !xFindExecutable ){ |
| 553 | Th_ErrorMessage(interp, |
| 554 | "Could not locate Tcl_FindExecutable", (const char *)"", 0); |
| 555 | dlclose(library); |
| 556 | return TH_ERROR; |
| 557 | } |
| 558 | procName = TCL_CREATEINTERP_NAME; |
| 559 | xCreateInterp = (tcl_CreateInterpProc *)dlsym(library, procName + 1); |
| 560 | if( !xCreateInterp ){ |
| 561 | xCreateInterp = (tcl_CreateInterpProc *)dlsym(library, procName); |
| 562 | } |
| 563 | if( !xCreateInterp ){ |
| 564 | Th_ErrorMessage(interp, |
| 565 | "Could not locate Tcl_CreateInterp", (const char *)"", 0); |
| 566 | dlclose(library); |
| 567 | return TH_ERROR; |
| 568 | } |
| 569 | *pLibrary = library; |
| 570 | *pxFindExecutable = xFindExecutable; |
| 571 | *pxCreateInterp = xCreateInterp; |
| 572 | return TH_OK; |
| 573 | } |
| 574 | } while( --fileName[TCL_MINOR_OFFSET]>'3' ); /* Tcl 8.4+ */ |
| 575 | Th_ErrorMessage(interp, |
| 576 | "Could not load Tcl shared library \"" TCL_LIBRARY_NAME "\"", |
| 577 | (const char *)"", 0); |
| 578 | return TH_ERROR; |
| 579 | #else |
| 580 | *pLibrary = 0; |
| 581 | *pxFindExecutable = Tcl_FindExecutable; |
| 582 | *pxCreateInterp = Tcl_CreateInterp; |
| 583 | return TH_OK; |
| 584 | #endif |
| 585 | } |
| 586 | |
| 587 | /* |
| 588 | ** Sets the "argv0", "argc", and "argv" script variables in the Tcl interpreter |
| 589 | ** based on the supplied command line arguments. |
| 590 | */ |
| @@ -446,31 +652,41 @@ | |
| 652 | struct TclContext *tclContext = (struct TclContext *)pContext; |
| 653 | int argc; |
| 654 | char **argv; |
| 655 | char *argv0 = 0; |
| 656 | Tcl_Interp *tclInterp; |
| 657 | char *setup; |
| 658 | |
| 659 | if ( !tclContext ){ |
| 660 | Th_ErrorMessage(interp, |
| 661 | "Invalid Tcl context", (const char *)"", 0); |
| 662 | return TH_ERROR; |
| 663 | } |
| 664 | if ( tclContext->interp ){ |
| 665 | return TH_OK; |
| 666 | } |
| 667 | if( loadTcl(interp, &tclContext->library, &tclContext->xFindExecutable, |
| 668 | &tclContext->xCreateInterp)!=TH_OK ){ |
| 669 | return TH_ERROR; |
| 670 | } |
| 671 | argc = tclContext->argc; |
| 672 | argv = tclContext->argv; |
| 673 | if( argc>0 && argv ){ |
| 674 | argv0 = argv[0]; |
| 675 | } |
| 676 | tclContext->xFindExecutable(argv0); |
| 677 | tclInterp = tclContext->xCreateInterp(); |
| 678 | if( !tclInterp || |
| 679 | #if defined(USE_TCL_STUBS) |
| 680 | !Tcl_InitStubs(tclInterp, "8.4", 0) || |
| 681 | #endif |
| 682 | Tcl_InterpDeleted(tclInterp) ){ |
| 683 | Th_ErrorMessage(interp, |
| 684 | "Could not create Tcl interpreter", (const char *)"", 0); |
| 685 | return TH_ERROR; |
| 686 | } |
| 687 | tclContext->interp = tclInterp; |
| 688 | if( Tcl_Init(tclInterp)!=TCL_OK ){ |
| 689 | Th_ErrorMessage(interp, |
| 690 | "Tcl initialization error:", Tcl_GetStringResult(tclInterp), -1); |
| 691 | Tcl_DeleteInterp(tclInterp); |
| 692 | tclContext->interp = tclInterp = 0; |
| @@ -485,10 +701,19 @@ | |
| 701 | } |
| 702 | /* Add the TH1 integration commands to Tcl. */ |
| 703 | Tcl_CallWhenDeleted(tclInterp, Th1DeleteProc, interp); |
| 704 | Tcl_CreateObjCommand(tclInterp, "th1Eval", Th1EvalObjCmd, interp, NULL); |
| 705 | Tcl_CreateObjCommand(tclInterp, "th1Expr", Th1ExprObjCmd, interp, NULL); |
| 706 | /* If necessary, evaluate the custom Tcl setup script. */ |
| 707 | setup = tclContext->setup; |
| 708 | if( setup && Tcl_Eval(tclInterp, setup)!=TCL_OK ){ |
| 709 | Th_ErrorMessage(interp, |
| 710 | "Tcl setup script error:", Tcl_GetStringResult(tclInterp), -1); |
| 711 | Tcl_DeleteInterp(tclInterp); |
| 712 | tclContext->interp = tclInterp = 0; |
| 713 | return TH_ERROR; |
| 714 | } |
| 715 | return TH_OK; |
| 716 | } |
| 717 | |
| 718 | /* |
| 719 | ** Register the Tcl language commands with interpreter interp. |
| 720 |
+1
-1
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -917,11 +917,11 @@ | ||
| 917 | 917 | const char *zThisTag = 0; /* Suppress links to this tag */ |
| 918 | 918 | const char *zThisUser = 0; /* Suppress links to this user */ |
| 919 | 919 | HQuery url; /* URL for various branch links */ |
| 920 | 920 | int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */ |
| 921 | 921 | int to_rid = name_to_typed_rid(P("to"),"ci"); /* to= for path timelines */ |
| 922 | - int noMerge = P("nomerge")!=0; /* Do not follow merge links */ | |
| 922 | + int noMerge = P("shortest")==0; /* Follow merge links if shorter */ | |
| 923 | 923 | int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */ |
| 924 | 924 | int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ |
| 925 | 925 | int pd_rid; |
| 926 | 926 | double rBefore, rAfter, rCirca; /* Boundary times */ |
| 927 | 927 | |
| 928 | 928 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -917,11 +917,11 @@ | |
| 917 | const char *zThisTag = 0; /* Suppress links to this tag */ |
| 918 | const char *zThisUser = 0; /* Suppress links to this user */ |
| 919 | HQuery url; /* URL for various branch links */ |
| 920 | int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */ |
| 921 | int to_rid = name_to_typed_rid(P("to"),"ci"); /* to= for path timelines */ |
| 922 | int noMerge = P("nomerge")!=0; /* Do not follow merge links */ |
| 923 | int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */ |
| 924 | int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ |
| 925 | int pd_rid; |
| 926 | double rBefore, rAfter, rCirca; /* Boundary times */ |
| 927 | |
| 928 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -917,11 +917,11 @@ | |
| 917 | const char *zThisTag = 0; /* Suppress links to this tag */ |
| 918 | const char *zThisUser = 0; /* Suppress links to this user */ |
| 919 | HQuery url; /* URL for various branch links */ |
| 920 | int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */ |
| 921 | int to_rid = name_to_typed_rid(P("to"),"ci"); /* to= for path timelines */ |
| 922 | int noMerge = P("shortest")==0; /* Follow merge links if shorter */ |
| 923 | int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */ |
| 924 | int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ |
| 925 | int pd_rid; |
| 926 | double rBefore, rAfter, rCirca; /* Boundary times */ |
| 927 | |
| 928 |
+9
-3
| --- src/update.c | ||
| +++ src/update.c | ||
| @@ -595,12 +595,13 @@ | ||
| 595 | 595 | */ |
| 596 | 596 | int historical_version_of_file( |
| 597 | 597 | const char *revision, /* The checkin containing the file */ |
| 598 | 598 | const char *file, /* Full treename of the file */ |
| 599 | 599 | Blob *content, /* Put the content here */ |
| 600 | - int *pIsLink, /* Set to true if file is link. */ | |
| 600 | + int *pIsLink, /* Set to true if file is link. */ | |
| 601 | 601 | int *pIsExe, /* Set to true if file is executable */ |
| 602 | + int *pIsBin, /* Set to true if file is binary */ | |
| 602 | 603 | int errCode /* Error code if file not found. Panic if 0. */ |
| 603 | 604 | ){ |
| 604 | 605 | Manifest *pManifest; |
| 605 | 606 | ManifestFile *pFile; |
| 606 | 607 | int rid=0; |
| @@ -617,15 +618,20 @@ | ||
| 617 | 618 | pManifest = manifest_get(rid, CFTYPE_MANIFEST); |
| 618 | 619 | |
| 619 | 620 | if( pManifest ){ |
| 620 | 621 | pFile = manifest_file_find(pManifest, file); |
| 621 | 622 | if( pFile ){ |
| 623 | + int rc; | |
| 622 | 624 | rid = uuid_to_rid(pFile->zUuid, 0); |
| 623 | 625 | if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE ); |
| 624 | 626 | if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK ); |
| 625 | 627 | manifest_destroy(pManifest); |
| 626 | - return content_get(rid, content); | |
| 628 | + rc = content_get(rid, content); | |
| 629 | + if( rc && pIsBin ){ | |
| 630 | + *pIsBin = looks_like_binary(content); | |
| 631 | + } | |
| 632 | + return rc; | |
| 627 | 633 | } |
| 628 | 634 | manifest_destroy(pManifest); |
| 629 | 635 | if( errCode<=0 ){ |
| 630 | 636 | fossil_fatal("file %s does not exist in checkin: %s", file, revision); |
| 631 | 637 | } |
| @@ -712,11 +718,11 @@ | ||
| 712 | 718 | int isLink = 0; |
| 713 | 719 | char *zFull; |
| 714 | 720 | zFile = db_column_text(&q, 0); |
| 715 | 721 | zFull = mprintf("%/%/", g.zLocalRoot, zFile); |
| 716 | 722 | errCode = historical_version_of_file(zRevision, zFile, &record, |
| 717 | - &isLink, &isExe,2); | |
| 723 | + &isLink, &isExe, 0, 2); | |
| 718 | 724 | if( errCode==2 ){ |
| 719 | 725 | if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){ |
| 720 | 726 | fossil_print("UNMANAGE: %s\n", zFile); |
| 721 | 727 | }else{ |
| 722 | 728 | undo_save(zFile); |
| 723 | 729 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -595,12 +595,13 @@ | |
| 595 | */ |
| 596 | int historical_version_of_file( |
| 597 | const char *revision, /* The checkin containing the file */ |
| 598 | const char *file, /* Full treename of the file */ |
| 599 | Blob *content, /* Put the content here */ |
| 600 | int *pIsLink, /* Set to true if file is link. */ |
| 601 | int *pIsExe, /* Set to true if file is executable */ |
| 602 | int errCode /* Error code if file not found. Panic if 0. */ |
| 603 | ){ |
| 604 | Manifest *pManifest; |
| 605 | ManifestFile *pFile; |
| 606 | int rid=0; |
| @@ -617,15 +618,20 @@ | |
| 617 | pManifest = manifest_get(rid, CFTYPE_MANIFEST); |
| 618 | |
| 619 | if( pManifest ){ |
| 620 | pFile = manifest_file_find(pManifest, file); |
| 621 | if( pFile ){ |
| 622 | rid = uuid_to_rid(pFile->zUuid, 0); |
| 623 | if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE ); |
| 624 | if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK ); |
| 625 | manifest_destroy(pManifest); |
| 626 | return content_get(rid, content); |
| 627 | } |
| 628 | manifest_destroy(pManifest); |
| 629 | if( errCode<=0 ){ |
| 630 | fossil_fatal("file %s does not exist in checkin: %s", file, revision); |
| 631 | } |
| @@ -712,11 +718,11 @@ | |
| 712 | int isLink = 0; |
| 713 | char *zFull; |
| 714 | zFile = db_column_text(&q, 0); |
| 715 | zFull = mprintf("%/%/", g.zLocalRoot, zFile); |
| 716 | errCode = historical_version_of_file(zRevision, zFile, &record, |
| 717 | &isLink, &isExe,2); |
| 718 | if( errCode==2 ){ |
| 719 | if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){ |
| 720 | fossil_print("UNMANAGE: %s\n", zFile); |
| 721 | }else{ |
| 722 | undo_save(zFile); |
| 723 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -595,12 +595,13 @@ | |
| 595 | */ |
| 596 | int historical_version_of_file( |
| 597 | const char *revision, /* The checkin containing the file */ |
| 598 | const char *file, /* Full treename of the file */ |
| 599 | Blob *content, /* Put the content here */ |
| 600 | int *pIsLink, /* Set to true if file is link. */ |
| 601 | int *pIsExe, /* Set to true if file is executable */ |
| 602 | int *pIsBin, /* Set to true if file is binary */ |
| 603 | int errCode /* Error code if file not found. Panic if 0. */ |
| 604 | ){ |
| 605 | Manifest *pManifest; |
| 606 | ManifestFile *pFile; |
| 607 | int rid=0; |
| @@ -617,15 +618,20 @@ | |
| 618 | pManifest = manifest_get(rid, CFTYPE_MANIFEST); |
| 619 | |
| 620 | if( pManifest ){ |
| 621 | pFile = manifest_file_find(pManifest, file); |
| 622 | if( pFile ){ |
| 623 | int rc; |
| 624 | rid = uuid_to_rid(pFile->zUuid, 0); |
| 625 | if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE ); |
| 626 | if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK ); |
| 627 | manifest_destroy(pManifest); |
| 628 | rc = content_get(rid, content); |
| 629 | if( rc && pIsBin ){ |
| 630 | *pIsBin = looks_like_binary(content); |
| 631 | } |
| 632 | return rc; |
| 633 | } |
| 634 | manifest_destroy(pManifest); |
| 635 | if( errCode<=0 ){ |
| 636 | fossil_fatal("file %s does not exist in checkin: %s", file, revision); |
| 637 | } |
| @@ -712,11 +718,11 @@ | |
| 718 | int isLink = 0; |
| 719 | char *zFull; |
| 720 | zFile = db_column_text(&q, 0); |
| 721 | zFull = mprintf("%/%/", g.zLocalRoot, zFile); |
| 722 | errCode = historical_version_of_file(zRevision, zFile, &record, |
| 723 | &isLink, &isExe, 0, 2); |
| 724 | if( errCode==2 ){ |
| 725 | if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){ |
| 726 | fossil_print("UNMANAGE: %s\n", zFile); |
| 727 | }else{ |
| 728 | undo_save(zFile); |
| 729 |
+1
-1
| --- test/th1-tcl2.txt | ||
| +++ test/th1-tcl2.txt | ||
| @@ -9,11 +9,11 @@ | ||
| 9 | 9 | # |
| 10 | 10 | tclInvoke set repository_name [repository 1] |
| 11 | 11 | proc doOut {msg} {puts $msg; puts \n} |
| 12 | 12 | doOut [tclEval { |
| 13 | 13 | package require sqlite3 |
| 14 | - sqlite3 db $repository_name | |
| 14 | + sqlite3 db $repository_name -readonly true | |
| 15 | 15 | set x [db eval {SELECT COUNT(*) FROM user;}] |
| 16 | 16 | db close |
| 17 | 17 | return $x |
| 18 | 18 | }] |
| 19 | 19 | </th1> |
| 20 | 20 |
| --- test/th1-tcl2.txt | |
| +++ test/th1-tcl2.txt | |
| @@ -9,11 +9,11 @@ | |
| 9 | # |
| 10 | tclInvoke set repository_name [repository 1] |
| 11 | proc doOut {msg} {puts $msg; puts \n} |
| 12 | doOut [tclEval { |
| 13 | package require sqlite3 |
| 14 | sqlite3 db $repository_name |
| 15 | set x [db eval {SELECT COUNT(*) FROM user;}] |
| 16 | db close |
| 17 | return $x |
| 18 | }] |
| 19 | </th1> |
| 20 |
| --- test/th1-tcl2.txt | |
| +++ test/th1-tcl2.txt | |
| @@ -9,11 +9,11 @@ | |
| 9 | # |
| 10 | tclInvoke set repository_name [repository 1] |
| 11 | proc doOut {msg} {puts $msg; puts \n} |
| 12 | doOut [tclEval { |
| 13 | package require sqlite3 |
| 14 | sqlite3 db $repository_name -readonly true |
| 15 | set x [db eval {SELECT COUNT(*) FROM user;}] |
| 16 | db close |
| 17 | return $x |
| 18 | }] |
| 19 | </th1> |
| 20 |
+22
-2
| --- win/Makefile.mingw | ||
| +++ win/Makefile.mingw | ||
| @@ -47,10 +47,14 @@ | ||
| 47 | 47 | # FOSSIL_ENABLE_SSL = 1 |
| 48 | 48 | |
| 49 | 49 | #### Enable scripting support via Tcl/Tk |
| 50 | 50 | # |
| 51 | 51 | # FOSSIL_ENABLE_TCL = 1 |
| 52 | + | |
| 53 | +#### Load Tcl using the stubs mechanism | |
| 54 | +# | |
| 55 | +# FOSSIL_ENABLE_TCL_STUBS = 1 | |
| 52 | 56 | |
| 53 | 57 | #### Use the Tcl source directory instead of the install directory? |
| 54 | 58 | # This is useful when Tcl has been compiled statically with MinGW. |
| 55 | 59 | # |
| 56 | 60 | FOSSIL_TCL_SOURCE = 1 |
| @@ -96,11 +100,15 @@ | ||
| 96 | 100 | TCLINCDIR = $(TCLDIR)/include |
| 97 | 101 | TCLLIBDIR = $(TCLDIR)/lib |
| 98 | 102 | |
| 99 | 103 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 100 | 104 | # |
| 105 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 106 | +LIBTCL = -ltclstub86 | |
| 107 | +else | |
| 101 | 108 | LIBTCL = -ltcl86 |
| 109 | +endif | |
| 102 | 110 | |
| 103 | 111 | #### C Compile and options for use in building executables that |
| 104 | 112 | # will run on the target platform. This is usually the same |
| 105 | 113 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 106 | 114 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -134,14 +142,22 @@ | ||
| 134 | 142 | ifdef FOSSIL_ENABLE_SSL |
| 135 | 143 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 136 | 144 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 137 | 145 | endif |
| 138 | 146 | |
| 139 | -# With Tcl support (statically linked) | |
| 147 | +# With Tcl support | |
| 140 | 148 | ifdef FOSSIL_ENABLE_TCL |
| 141 | -TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD | |
| 149 | +TCC += -DFOSSIL_ENABLE_TCL=1 | |
| 142 | 150 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 151 | +# Either statically linked or via stubs | |
| 152 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 153 | +TCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS | |
| 154 | +RCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS | |
| 155 | +else | |
| 156 | +TCC += -DSTATIC_BUILD | |
| 157 | +RCC += -DSTATIC_BUILD | |
| 158 | +endif | |
| 143 | 159 | endif |
| 144 | 160 | |
| 145 | 161 | # With JSON support |
| 146 | 162 | ifdef FOSSIL_ENABLE_JSON |
| 147 | 163 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -171,11 +187,15 @@ | ||
| 171 | 187 | |
| 172 | 188 | #### These libraries MUST appear in the same order as they do for Tcl |
| 173 | 189 | # or linking with it will not work (exact reason unknown). |
| 174 | 190 | # |
| 175 | 191 | ifdef FOSSIL_ENABLE_TCL |
| 192 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 193 | +LIB += -lkernel32 -lws2_32 | |
| 194 | +else | |
| 176 | 195 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 196 | +endif | |
| 177 | 197 | else |
| 178 | 198 | LIB += -lkernel32 -lws2_32 |
| 179 | 199 | endif |
| 180 | 200 | |
| 181 | 201 | #### Tcl shell for use in running the fossil test suite. This is only |
| 182 | 202 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -47,10 +47,14 @@ | |
| 47 | # FOSSIL_ENABLE_SSL = 1 |
| 48 | |
| 49 | #### Enable scripting support via Tcl/Tk |
| 50 | # |
| 51 | # FOSSIL_ENABLE_TCL = 1 |
| 52 | |
| 53 | #### Use the Tcl source directory instead of the install directory? |
| 54 | # This is useful when Tcl has been compiled statically with MinGW. |
| 55 | # |
| 56 | FOSSIL_TCL_SOURCE = 1 |
| @@ -96,11 +100,15 @@ | |
| 96 | TCLINCDIR = $(TCLDIR)/include |
| 97 | TCLLIBDIR = $(TCLDIR)/lib |
| 98 | |
| 99 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 100 | # |
| 101 | LIBTCL = -ltcl86 |
| 102 | |
| 103 | #### C Compile and options for use in building executables that |
| 104 | # will run on the target platform. This is usually the same |
| 105 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 106 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -134,14 +142,22 @@ | |
| 134 | ifdef FOSSIL_ENABLE_SSL |
| 135 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 136 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 137 | endif |
| 138 | |
| 139 | # With Tcl support (statically linked) |
| 140 | ifdef FOSSIL_ENABLE_TCL |
| 141 | TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD |
| 142 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 143 | endif |
| 144 | |
| 145 | # With JSON support |
| 146 | ifdef FOSSIL_ENABLE_JSON |
| 147 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -171,11 +187,15 @@ | |
| 171 | |
| 172 | #### These libraries MUST appear in the same order as they do for Tcl |
| 173 | # or linking with it will not work (exact reason unknown). |
| 174 | # |
| 175 | ifdef FOSSIL_ENABLE_TCL |
| 176 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 177 | else |
| 178 | LIB += -lkernel32 -lws2_32 |
| 179 | endif |
| 180 | |
| 181 | #### Tcl shell for use in running the fossil test suite. This is only |
| 182 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -47,10 +47,14 @@ | |
| 47 | # FOSSIL_ENABLE_SSL = 1 |
| 48 | |
| 49 | #### Enable scripting support via Tcl/Tk |
| 50 | # |
| 51 | # FOSSIL_ENABLE_TCL = 1 |
| 52 | |
| 53 | #### Load Tcl using the stubs mechanism |
| 54 | # |
| 55 | # FOSSIL_ENABLE_TCL_STUBS = 1 |
| 56 | |
| 57 | #### Use the Tcl source directory instead of the install directory? |
| 58 | # This is useful when Tcl has been compiled statically with MinGW. |
| 59 | # |
| 60 | FOSSIL_TCL_SOURCE = 1 |
| @@ -96,11 +100,15 @@ | |
| 100 | TCLINCDIR = $(TCLDIR)/include |
| 101 | TCLLIBDIR = $(TCLDIR)/lib |
| 102 | |
| 103 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 104 | # |
| 105 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 106 | LIBTCL = -ltclstub86 |
| 107 | else |
| 108 | LIBTCL = -ltcl86 |
| 109 | endif |
| 110 | |
| 111 | #### C Compile and options for use in building executables that |
| 112 | # will run on the target platform. This is usually the same |
| 113 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 114 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -134,14 +142,22 @@ | |
| 142 | ifdef FOSSIL_ENABLE_SSL |
| 143 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 144 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 145 | endif |
| 146 | |
| 147 | # With Tcl support |
| 148 | ifdef FOSSIL_ENABLE_TCL |
| 149 | TCC += -DFOSSIL_ENABLE_TCL=1 |
| 150 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 151 | # Either statically linked or via stubs |
| 152 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 153 | TCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS |
| 154 | RCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS |
| 155 | else |
| 156 | TCC += -DSTATIC_BUILD |
| 157 | RCC += -DSTATIC_BUILD |
| 158 | endif |
| 159 | endif |
| 160 | |
| 161 | # With JSON support |
| 162 | ifdef FOSSIL_ENABLE_JSON |
| 163 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -171,11 +187,15 @@ | |
| 187 | |
| 188 | #### These libraries MUST appear in the same order as they do for Tcl |
| 189 | # or linking with it will not work (exact reason unknown). |
| 190 | # |
| 191 | ifdef FOSSIL_ENABLE_TCL |
| 192 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 193 | LIB += -lkernel32 -lws2_32 |
| 194 | else |
| 195 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 196 | endif |
| 197 | else |
| 198 | LIB += -lkernel32 -lws2_32 |
| 199 | endif |
| 200 | |
| 201 | #### Tcl shell for use in running the fossil test suite. This is only |
| 202 |
+23
-3
| --- win/Makefile.mingw.mistachkin | ||
| +++ win/Makefile.mingw.mistachkin | ||
| @@ -46,11 +46,15 @@ | ||
| 46 | 46 | # |
| 47 | 47 | FOSSIL_ENABLE_SSL = 1 |
| 48 | 48 | |
| 49 | 49 | #### Enable scripting support via Tcl/Tk |
| 50 | 50 | # |
| 51 | -# FOSSIL_ENABLE_TCL = 1 | |
| 51 | +FOSSIL_ENABLE_TCL = 1 | |
| 52 | + | |
| 53 | +#### Load Tcl using the stubs mechanism | |
| 54 | +# | |
| 55 | +FOSSIL_ENABLE_TCL_STUBS = 1 | |
| 52 | 56 | |
| 53 | 57 | #### Use the Tcl source directory instead of the install directory? |
| 54 | 58 | # This is useful when Tcl has been compiled statically with MinGW. |
| 55 | 59 | # |
| 56 | 60 | FOSSIL_TCL_SOURCE = 1 |
| @@ -96,11 +100,15 @@ | ||
| 96 | 100 | TCLINCDIR = $(TCLDIR)/include |
| 97 | 101 | TCLLIBDIR = $(TCLDIR)/lib |
| 98 | 102 | |
| 99 | 103 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 100 | 104 | # |
| 105 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 106 | +LIBTCL = -ltclstub86 | |
| 107 | +else | |
| 101 | 108 | LIBTCL = -ltcl86 |
| 109 | +endif | |
| 102 | 110 | |
| 103 | 111 | #### C Compile and options for use in building executables that |
| 104 | 112 | # will run on the target platform. This is usually the same |
| 105 | 113 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 106 | 114 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -134,14 +142,22 @@ | ||
| 134 | 142 | ifdef FOSSIL_ENABLE_SSL |
| 135 | 143 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 136 | 144 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 137 | 145 | endif |
| 138 | 146 | |
| 139 | -# With Tcl support (statically linked) | |
| 147 | +# With Tcl support | |
| 140 | 148 | ifdef FOSSIL_ENABLE_TCL |
| 141 | -TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD | |
| 149 | +TCC += -DFOSSIL_ENABLE_TCL=1 | |
| 142 | 150 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 151 | +# Either statically linked or via stubs | |
| 152 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 153 | +TCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS | |
| 154 | +RCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS | |
| 155 | +else | |
| 156 | +TCC += -DSTATIC_BUILD | |
| 157 | +RCC += -DSTATIC_BUILD | |
| 158 | +endif | |
| 143 | 159 | endif |
| 144 | 160 | |
| 145 | 161 | # With JSON support |
| 146 | 162 | ifdef FOSSIL_ENABLE_JSON |
| 147 | 163 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -171,11 +187,15 @@ | ||
| 171 | 187 | |
| 172 | 188 | #### These libraries MUST appear in the same order as they do for Tcl |
| 173 | 189 | # or linking with it will not work (exact reason unknown). |
| 174 | 190 | # |
| 175 | 191 | ifdef FOSSIL_ENABLE_TCL |
| 192 | +ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 193 | +LIB += -lkernel32 -lws2_32 | |
| 194 | +else | |
| 176 | 195 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 196 | +endif | |
| 177 | 197 | else |
| 178 | 198 | LIB += -lkernel32 -lws2_32 |
| 179 | 199 | endif |
| 180 | 200 | |
| 181 | 201 | #### Tcl shell for use in running the fossil test suite. This is only |
| 182 | 202 |
| --- win/Makefile.mingw.mistachkin | |
| +++ win/Makefile.mingw.mistachkin | |
| @@ -46,11 +46,15 @@ | |
| 46 | # |
| 47 | FOSSIL_ENABLE_SSL = 1 |
| 48 | |
| 49 | #### Enable scripting support via Tcl/Tk |
| 50 | # |
| 51 | # FOSSIL_ENABLE_TCL = 1 |
| 52 | |
| 53 | #### Use the Tcl source directory instead of the install directory? |
| 54 | # This is useful when Tcl has been compiled statically with MinGW. |
| 55 | # |
| 56 | FOSSIL_TCL_SOURCE = 1 |
| @@ -96,11 +100,15 @@ | |
| 96 | TCLINCDIR = $(TCLDIR)/include |
| 97 | TCLLIBDIR = $(TCLDIR)/lib |
| 98 | |
| 99 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 100 | # |
| 101 | LIBTCL = -ltcl86 |
| 102 | |
| 103 | #### C Compile and options for use in building executables that |
| 104 | # will run on the target platform. This is usually the same |
| 105 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 106 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -134,14 +142,22 @@ | |
| 134 | ifdef FOSSIL_ENABLE_SSL |
| 135 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 136 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 137 | endif |
| 138 | |
| 139 | # With Tcl support (statically linked) |
| 140 | ifdef FOSSIL_ENABLE_TCL |
| 141 | TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD |
| 142 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 143 | endif |
| 144 | |
| 145 | # With JSON support |
| 146 | ifdef FOSSIL_ENABLE_JSON |
| 147 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -171,11 +187,15 @@ | |
| 171 | |
| 172 | #### These libraries MUST appear in the same order as they do for Tcl |
| 173 | # or linking with it will not work (exact reason unknown). |
| 174 | # |
| 175 | ifdef FOSSIL_ENABLE_TCL |
| 176 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 177 | else |
| 178 | LIB += -lkernel32 -lws2_32 |
| 179 | endif |
| 180 | |
| 181 | #### Tcl shell for use in running the fossil test suite. This is only |
| 182 |
| --- win/Makefile.mingw.mistachkin | |
| +++ win/Makefile.mingw.mistachkin | |
| @@ -46,11 +46,15 @@ | |
| 46 | # |
| 47 | FOSSIL_ENABLE_SSL = 1 |
| 48 | |
| 49 | #### Enable scripting support via Tcl/Tk |
| 50 | # |
| 51 | FOSSIL_ENABLE_TCL = 1 |
| 52 | |
| 53 | #### Load Tcl using the stubs mechanism |
| 54 | # |
| 55 | FOSSIL_ENABLE_TCL_STUBS = 1 |
| 56 | |
| 57 | #### Use the Tcl source directory instead of the install directory? |
| 58 | # This is useful when Tcl has been compiled statically with MinGW. |
| 59 | # |
| 60 | FOSSIL_TCL_SOURCE = 1 |
| @@ -96,11 +100,15 @@ | |
| 100 | TCLINCDIR = $(TCLDIR)/include |
| 101 | TCLLIBDIR = $(TCLDIR)/lib |
| 102 | |
| 103 | #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? |
| 104 | # |
| 105 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 106 | LIBTCL = -ltclstub86 |
| 107 | else |
| 108 | LIBTCL = -ltcl86 |
| 109 | endif |
| 110 | |
| 111 | #### C Compile and options for use in building executables that |
| 112 | # will run on the target platform. This is usually the same |
| 113 | # as BCC, unless you are cross-compiling. This C compiler builds |
| 114 | # the finished binary for fossil. The BCC compiler above is used |
| @@ -134,14 +142,22 @@ | |
| 142 | ifdef FOSSIL_ENABLE_SSL |
| 143 | TCC += -DFOSSIL_ENABLE_SSL=1 |
| 144 | RCC += -DFOSSIL_ENABLE_SSL=1 |
| 145 | endif |
| 146 | |
| 147 | # With Tcl support |
| 148 | ifdef FOSSIL_ENABLE_TCL |
| 149 | TCC += -DFOSSIL_ENABLE_TCL=1 |
| 150 | RCC += -DFOSSIL_ENABLE_TCL=1 |
| 151 | # Either statically linked or via stubs |
| 152 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 153 | TCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS |
| 154 | RCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS |
| 155 | else |
| 156 | TCC += -DSTATIC_BUILD |
| 157 | RCC += -DSTATIC_BUILD |
| 158 | endif |
| 159 | endif |
| 160 | |
| 161 | # With JSON support |
| 162 | ifdef FOSSIL_ENABLE_JSON |
| 163 | TCC += -DFOSSIL_ENABLE_JSON=1 |
| @@ -171,11 +187,15 @@ | |
| 187 | |
| 188 | #### These libraries MUST appear in the same order as they do for Tcl |
| 189 | # or linking with it will not work (exact reason unknown). |
| 190 | # |
| 191 | ifdef FOSSIL_ENABLE_TCL |
| 192 | ifdef FOSSIL_ENABLE_TCL_STUBS |
| 193 | LIB += -lkernel32 -lws2_32 |
| 194 | else |
| 195 | LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 |
| 196 | endif |
| 197 | else |
| 198 | LIB += -lkernel32 -lws2_32 |
| 199 | endif |
| 200 | |
| 201 | #### Tcl shell for use in running the fossil test suite. This is only |
| 202 |
+5
| --- win/fossil.rc | ||
| +++ win/fossil.rc | ||
| @@ -90,10 +90,15 @@ | ||
| 90 | 90 | #ifdef FOSSIL_ENABLE_SSL |
| 91 | 91 | VALUE "SslEnabled", "Yes, " OPENSSL_VERSION_TEXT "\0" |
| 92 | 92 | #endif |
| 93 | 93 | #ifdef FOSSIL_ENABLE_TCL |
| 94 | 94 | VALUE "TclEnabled", "Yes, Tcl " TCL_PATCH_LEVEL "\0" |
| 95 | +#ifdef FOSSIL_ENABLE_TCL_STUBS | |
| 96 | + VALUE "TclStubsEnabled", "Yes\0" | |
| 97 | +#else | |
| 98 | + VALUE "TclStubsEnabled", "No\0" | |
| 99 | +#endif | |
| 95 | 100 | #endif |
| 96 | 101 | #ifdef FOSSIL_ENABLE_JSON |
| 97 | 102 | VALUE "JsonEnabled", "Yes, cson\0" |
| 98 | 103 | #endif |
| 99 | 104 | END |
| 100 | 105 |
| --- win/fossil.rc | |
| +++ win/fossil.rc | |
| @@ -90,10 +90,15 @@ | |
| 90 | #ifdef FOSSIL_ENABLE_SSL |
| 91 | VALUE "SslEnabled", "Yes, " OPENSSL_VERSION_TEXT "\0" |
| 92 | #endif |
| 93 | #ifdef FOSSIL_ENABLE_TCL |
| 94 | VALUE "TclEnabled", "Yes, Tcl " TCL_PATCH_LEVEL "\0" |
| 95 | #endif |
| 96 | #ifdef FOSSIL_ENABLE_JSON |
| 97 | VALUE "JsonEnabled", "Yes, cson\0" |
| 98 | #endif |
| 99 | END |
| 100 |
| --- win/fossil.rc | |
| +++ win/fossil.rc | |
| @@ -90,10 +90,15 @@ | |
| 90 | #ifdef FOSSIL_ENABLE_SSL |
| 91 | VALUE "SslEnabled", "Yes, " OPENSSL_VERSION_TEXT "\0" |
| 92 | #endif |
| 93 | #ifdef FOSSIL_ENABLE_TCL |
| 94 | VALUE "TclEnabled", "Yes, Tcl " TCL_PATCH_LEVEL "\0" |
| 95 | #ifdef FOSSIL_ENABLE_TCL_STUBS |
| 96 | VALUE "TclStubsEnabled", "Yes\0" |
| 97 | #else |
| 98 | VALUE "TclStubsEnabled", "No\0" |
| 99 | #endif |
| 100 | #endif |
| 101 | #ifdef FOSSIL_ENABLE_JSON |
| 102 | VALUE "JsonEnabled", "Yes, cson\0" |
| 103 | #endif |
| 104 | END |
| 105 |