| # This TCL script is the main driver script for the sqlite3_checker utility |
| # program. |
| # |
| |
| # Special case: |
| # |
| # sqlite3_checker --test FILENAME ARGS |
| # |
| # uses FILENAME in place of this script. |
| # |
| if {[lindex $argv 0]=="--test" && [llength $argv]>1} { |
| set ::argv0 [lindex $argv 1] |
| set argv [lrange $argv 2 end] |
| source $argv0 |
| exit 0 |
| } |
| |
| # Emulate a TCL shell |
| # |
| proc tclsh {} { |
| set line {} |
| while {![eof stdin]} { |
| if {$line!=""} { |
| puts -nonewline "> " |
| } else { |
| puts -nonewline "% " |
| } |
| flush stdout |
| append line [gets stdin] |
| if {[info complete $line]} { |
| if {[catch {uplevel #0 $line} result]} { |
| puts stderr "Error: $result" |
| } elseif {$result!=""} { |
| puts $result |
| } |
| set line {} |
| } else { |
| append line \n |
| } |
| } |
| } |
| |
| # Do an incremental integrity check of a single index |
| # |
| proc check_index {idxname batchsize bTrace} { |
| set i 0 |
| set more 1 |
| set nerr 0 |
| set pct 00.0 |
| set max [db one {SELECT nEntry FROM sqlite_btreeinfo('main') |
| WHERE name=$idxname}] |
| puts -nonewline "$idxname: $i of $max rows ($pct%)\r" |
| flush stdout |
| if {$bTrace} { |
| set sql {SELECT errmsg, current_key AS key, |
| CASE WHEN rowid=1 THEN scanner_sql END AS traceOut |
| FROM incremental_index_check($idxname) |
| WHERE after_key=$key |
| LIMIT $batchsize} |
| } else { |
| set sql {SELECT errmsg, current_key AS key, NULL AS traceOut |
| FROM incremental_index_check($idxname) |
| WHERE after_key=$key |
| LIMIT $batchsize} |
| } |
| while {$more} { |
| set more 0 |
| db eval $sql { |
| set more 1 |
| if {$errmsg!=""} { |
| incr nerr |
| puts "$idxname: key($key): $errmsg" |
| } elseif {$traceOut!=""} { |
| puts "$idxname: $traceOut" |
| } |
| incr i |
| |
| } |
| set x [format {%.1f} [expr {($i*100.0)/$max}]] |
| if {$x!=$pct} { |
| puts -nonewline "$idxname: $i of $max rows ($pct%)\r" |
| flush stdout |
| set pct $x |
| } |
| } |
| puts "$idxname: $nerr errors out of $i entries" |
| } |
| |
| # Print a usage message on standard error, then quit. |
| # |
| proc usage {} { |
| set argv0 [file rootname [file tail [info nameofexecutable]]] |
| puts stderr "Usage: $argv0 OPTIONS database-filename" |
| puts stderr { |
| Do sanity checking on a live SQLite3 database file specified by the |
| "database-filename" argument. |
| |
| Options: |
| |
| --batchsize N Number of rows to check per transaction |
| |
| --freelist Perform a freelist check |
| |
| --index NAME Run a check of the index NAME |
| |
| --summary Print summary information about the database |
| |
| --table NAME Run a check of all indexes for table NAME |
| |
| --tclsh Run the built-in TCL interpreter (for debugging) |
| |
| --trace (Debugging only:) Output trace information on the scan |
| |
| --version Show the version number of SQLite |
| } |
| exit 1 |
| } |
| |
| set file_to_analyze {} |
| append argv {} |
| set bFreelistCheck 0 |
| set bSummary 0 |
| set zIndex {} |
| set zTable {} |
| set batchsize 1000 |
| set bAll 1 |
| set bTrace 0 |
| set argc [llength $argv] |
| for {set i 0} {$i<$argc} {incr i} { |
| set arg [lindex $argv $i] |
| if {[regexp {^-+tclsh$} $arg]} { |
| tclsh |
| exit 0 |
| } |
| if {[regexp {^-+version$} $arg]} { |
| sqlite3 mem :memory: |
| puts [mem one {SELECT sqlite_version()||' '||sqlite_source_id()}] |
| mem close |
| exit 0 |
| } |
| if {[regexp {^-+freelist$} $arg]} { |
| set bFreelistCheck 1 |
| set bAll 0 |
| continue |
| } |
| if {[regexp {^-+summary$} $arg]} { |
| set bSummary 1 |
| set bAll 0 |
| continue |
| } |
| if {[regexp {^-+trace$} $arg]} { |
| set bTrace 1 |
| continue |
| } |
| if {[regexp {^-+batchsize$} $arg]} { |
| incr i |
| if {$i>=$argc} { |
| puts stderr "missing argument on $arg" |
| exit 1 |
| } |
| set batchsize [lindex $argv $i] |
| continue |
| } |
| if {[regexp {^-+index$} $arg]} { |
| incr i |
| if {$i>=$argc} { |
| puts stderr "missing argument on $arg" |
| exit 1 |
| } |
| set zIndex [lindex $argv $i] |
| set bAll 0 |
| continue |
| } |
| if {[regexp {^-+table$} $arg]} { |
| incr i |
| if {$i>=$argc} { |
| puts stderr "missing argument on $arg" |
| exit 1 |
| } |
| set zTable [lindex $argv $i] |
| set bAll 0 |
| continue |
| } |
| if {[regexp {^-} $arg]} { |
| puts stderr "Unknown option: $arg" |
| usage |
| } |
| if {$file_to_analyze!=""} { |
| usage |
| } else { |
| set file_to_analyze $arg |
| } |
| } |
| if {$file_to_analyze==""} usage |
| |
| # If a TCL script is specified on the command-line, then run that |
| # script. |
| # |
| if {[file extension $file_to_analyze]==".tcl"} { |
| source $file_to_analyze |
| exit 0 |
| } |
| |
| set root_filename $file_to_analyze |
| regexp {^file:(//)?([^?]*)} $file_to_analyze all x1 root_filename |
| if {![file exists $root_filename]} { |
| puts stderr "No such file: $root_filename" |
| exit 1 |
| } |
| if {![file readable $root_filename]} { |
| puts stderr "File is not readable: $root_filename" |
| exit 1 |
| } |
| |
| if {[catch {sqlite3 db $file_to_analyze} res]} { |
| puts stderr "Cannot open datababase $root_filename: $res" |
| exit 1 |
| } |
| |
| if {$bFreelistCheck || $bAll} { |
| puts -nonewline "freelist-check: " |
| flush stdout |
| db eval BEGIN |
| puts [db one {SELECT checkfreelist('main')}] |
| db eval END |
| } |
| if {$bSummary} { |
| set scale 0 |
| set pgsz [db one {PRAGMA page_size}] |
| db eval {SELECT nPage*$pgsz AS sz, name, tbl_name |
| FROM sqlite_btreeinfo |
| WHERE type='index' |
| ORDER BY 1 DESC, name} { |
| if {$scale==0} { |
| if {$sz>10000000} { |
| set scale 1000000.0 |
| set unit MB |
| } else { |
| set scale 1000.0 |
| set unit KB |
| } |
| } |
| puts [format {%7.1f %s index %s of table %s} \ |
| [expr {$sz/$scale}] $unit $name $tbl_name] |
| } |
| } |
| if {$zIndex!=""} { |
| check_index $zIndex $batchsize $bTrace |
| } |
| if {$zTable!=""} { |
| foreach idx [db eval {SELECT name FROM sqlite_master |
| WHERE type='index' AND rootpage>0 |
| AND tbl_name=$zTable}] { |
| check_index $idx $batchsize $bTrace |
| } |
| } |
| if {$bAll} { |
| set allidx [db eval {SELECT name FROM sqlite_btreeinfo('main') |
| WHERE type='index' AND rootpage>0 |
| ORDER BY nEntry}] |
| foreach idx $allidx { |
| check_index $idx $batchsize $bTrace |
| } |
| } |