1# This TCL script is the main driver script for the sqlite3_checker utility 2# program. 3# 4 5# Special case: 6# 7# sqlite3_checker --test FILENAME ARGS 8# 9# uses FILENAME in place of this script. 10# 11if {[lindex $argv 0]=="--test" && [llength $argv]>1} { 12 set ::argv0 [lindex $argv 1] 13 set argv [lrange $argv 2 end] 14 source $argv0 15 exit 0 16} 17 18# Emulate a TCL shell 19# 20proc tclsh {} { 21 set line {} 22 while {![eof stdin]} { 23 if {$line!=""} { 24 puts -nonewline "> " 25 } else { 26 puts -nonewline "% " 27 } 28 flush stdout 29 append line [gets stdin] 30 if {[info complete $line]} { 31 if {[catch {uplevel #0 $line} result]} { 32 puts stderr "Error: $result" 33 } elseif {$result!=""} { 34 puts $result 35 } 36 set line {} 37 } else { 38 append line \n 39 } 40 } 41} 42 43# Do an incremental integrity check of a single index 44# 45proc check_index {idxname batchsize bTrace} { 46 set i 0 47 set more 1 48 set nerr 0 49 set pct 00.0 50 set max [db one {SELECT nEntry FROM sqlite_btreeinfo('main') 51 WHERE name=$idxname}] 52 puts -nonewline "$idxname: $i of $max rows ($pct%)\r" 53 flush stdout 54 if {$bTrace} { 55 set sql {SELECT errmsg, current_key AS key, 56 CASE WHEN rowid=1 THEN scanner_sql END AS traceOut 57 FROM incremental_index_check($idxname) 58 WHERE after_key=$key 59 LIMIT $batchsize} 60 } else { 61 set sql {SELECT errmsg, current_key AS key, NULL AS traceOut 62 FROM incremental_index_check($idxname) 63 WHERE after_key=$key 64 LIMIT $batchsize} 65 } 66 while {$more} { 67 set more 0 68 db eval $sql { 69 set more 1 70 if {$errmsg!=""} { 71 incr nerr 72 puts "$idxname: key($key): $errmsg" 73 } elseif {$traceOut!=""} { 74 puts "$idxname: $traceOut" 75 } 76 incr i 77 78 } 79 set x [format {%.1f} [expr {($i*100.0)/$max}]] 80 if {$x!=$pct} { 81 puts -nonewline "$idxname: $i of $max rows ($pct%)\r" 82 flush stdout 83 set pct $x 84 } 85 } 86 puts "$idxname: $nerr errors out of $i entries" 87} 88 89# Print a usage message on standard error, then quit. 90# 91proc usage {} { 92 set argv0 [file rootname [file tail [info nameofexecutable]]] 93 puts stderr "Usage: $argv0 OPTIONS database-filename" 94 puts stderr { 95Do sanity checking on a live SQLite3 database file specified by the 96"database-filename" argument. 97 98Options: 99 100 --batchsize N Number of rows to check per transaction 101 102 --freelist Perform a freelist check 103 104 --index NAME Run a check of the index NAME 105 106 --summary Print summary information about the database 107 108 --table NAME Run a check of all indexes for table NAME 109 110 --tclsh Run the built-in TCL interpreter (for debugging) 111 112 --trace (Debugging only:) Output trace information on the scan 113 114 --version Show the version number of SQLite 115} 116 exit 1 117} 118 119set file_to_analyze {} 120append argv {} 121set bFreelistCheck 0 122set bSummary 0 123set zIndex {} 124set zTable {} 125set batchsize 1000 126set bAll 1 127set bTrace 0 128set argc [llength $argv] 129for {set i 0} {$i<$argc} {incr i} { 130 set arg [lindex $argv $i] 131 if {[regexp {^-+tclsh$} $arg]} { 132 tclsh 133 exit 0 134 } 135 if {[regexp {^-+version$} $arg]} { 136 sqlite3 mem :memory: 137 puts [mem one {SELECT sqlite_version()||' '||sqlite_source_id()}] 138 mem close 139 exit 0 140 } 141 if {[regexp {^-+freelist$} $arg]} { 142 set bFreelistCheck 1 143 set bAll 0 144 continue 145 } 146 if {[regexp {^-+summary$} $arg]} { 147 set bSummary 1 148 set bAll 0 149 continue 150 } 151 if {[regexp {^-+trace$} $arg]} { 152 set bTrace 1 153 continue 154 } 155 if {[regexp {^-+batchsize$} $arg]} { 156 incr i 157 if {$i>=$argc} { 158 puts stderr "missing argument on $arg" 159 exit 1 160 } 161 set batchsize [lindex $argv $i] 162 continue 163 } 164 if {[regexp {^-+index$} $arg]} { 165 incr i 166 if {$i>=$argc} { 167 puts stderr "missing argument on $arg" 168 exit 1 169 } 170 set zIndex [lindex $argv $i] 171 set bAll 0 172 continue 173 } 174 if {[regexp {^-+table$} $arg]} { 175 incr i 176 if {$i>=$argc} { 177 puts stderr "missing argument on $arg" 178 exit 1 179 } 180 set zTable [lindex $argv $i] 181 set bAll 0 182 continue 183 } 184 if {[regexp {^-} $arg]} { 185 puts stderr "Unknown option: $arg" 186 usage 187 } 188 if {$file_to_analyze!=""} { 189 usage 190 } else { 191 set file_to_analyze $arg 192 } 193} 194if {$file_to_analyze==""} usage 195 196# If a TCL script is specified on the command-line, then run that 197# script. 198# 199if {[file extension $file_to_analyze]==".tcl"} { 200 source $file_to_analyze 201 exit 0 202} 203 204set root_filename $file_to_analyze 205regexp {^file:(//)?([^?]*)} $file_to_analyze all x1 root_filename 206if {![file exists $root_filename]} { 207 puts stderr "No such file: $root_filename" 208 exit 1 209} 210if {![file readable $root_filename]} { 211 puts stderr "File is not readable: $root_filename" 212 exit 1 213} 214 215if {[catch {sqlite3 db $file_to_analyze} res]} { 216 puts stderr "Cannot open datababase $root_filename: $res" 217 exit 1 218} 219 220if {$bFreelistCheck || $bAll} { 221 puts -nonewline "freelist-check: " 222 flush stdout 223 db eval BEGIN 224 puts [db one {SELECT checkfreelist('main')}] 225 db eval END 226} 227if {$bSummary} { 228 set scale 0 229 set pgsz [db one {PRAGMA page_size}] 230 db eval {SELECT nPage*$pgsz AS sz, name, tbl_name 231 FROM sqlite_btreeinfo 232 WHERE type='index' 233 ORDER BY 1 DESC, name} { 234 if {$scale==0} { 235 if {$sz>10000000} { 236 set scale 1000000.0 237 set unit MB 238 } else { 239 set scale 1000.0 240 set unit KB 241 } 242 } 243 puts [format {%7.1f %s index %s of table %s} \ 244 [expr {$sz/$scale}] $unit $name $tbl_name] 245 } 246} 247if {$zIndex!=""} { 248 check_index $zIndex $batchsize $bTrace 249} 250if {$zTable!=""} { 251 foreach idx [db eval {SELECT name FROM sqlite_master 252 WHERE type='index' AND rootpage>0 253 AND tbl_name=$zTable}] { 254 check_index $idx $batchsize $bTrace 255 } 256} 257if {$bAll} { 258 set allidx [db eval {SELECT name FROM sqlite_btreeinfo('main') 259 WHERE type='index' AND rootpage>0 260 ORDER BY nEntry}] 261 foreach idx $allidx { 262 check_index $idx $batchsize $bTrace 263 } 264} 265