sqlite3_checker.tcl 6.0 KB

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