AryaWu/sqlite
0
1# 2007 May 12#3# The author disclaims copyright to this source code. In place of4# a legal notice, here is a blessing:5#6# May you do good and not evil.7# May you find forgiveness for yourself and forgive others.8# May you share freely, never taking more than you give.9#10#***********************************************************************11#12# $Id: incrblob.test,v 1.24 2009/06/19 22:23:42 drh Exp $13#14 15set testdir [file dirname $argv0]16source $testdir/tester.tcl17 18ifcapable {!autovacuum || !pragma || !incrblob} {19 finish_test20 return21}22 23do_test incrblob-1.1 {24 execsql {25 CREATE TABLE blobs(k PRIMARY KEY, v BLOB);26 INSERT INTO blobs VALUES('one', X'0102030405060708090A');27 INSERT INTO blobs VALUES('two', X'0A090807060504030201');28 }29} {}30 31do_test incrblob-1.2.1 {32 set ::blob [db incrblob blobs v 1]33 string match incrblob_* $::blob34} {1}35unset -nocomplain data36do_test incrblob-1.2.2 {37 binary scan [read $::blob] c* data38 set data39} {1 2 3 4 5 6 7 8 9 10}40do_test incrblob-1.2.3 {41 seek $::blob 042 puts -nonewline $::blob "1234567890"43 flush $::blob44} {}45do_test incrblob-1.2.4 {46 seek $::blob 047 binary scan [read $::blob] c* data48 set data49} {49 50 51 52 53 54 55 56 57 48}50do_test incrblob-1.2.5 {51 close $::blob52} {}53do_test incrblob-1.2.6 {54 execsql {55 SELECT v FROM blobs WHERE rowid = 1;56 }57} {1234567890}58 59#--------------------------------------------------------------------60# Test cases incrblob-1.3.X check that it is possible to read and write61# regions of a blob that lie on overflow pages.62#63do_test incrblob-1.3.1 {64 set ::str "[string repeat . 10000]"65 execsql {66 INSERT INTO blobs(rowid, k, v) VALUES(3, 'three', $::str);67 }68} {}69 70do_test incrblob-1.3.2 {71 set ::blob [db incrblob blobs v 3]72 seek $::blob 850073 read $::blob 1074} {..........}75do_test incrblob-1.3.3 {76 seek $::blob 850077 puts -nonewline $::blob 123456789078} {}79do_test incrblob-1.3.4 {80 seek $::blob 849681 read $::blob 1082} {....123456}83do_test incrblob-1.3.10 {84 close $::blob85} {}86 87#------------------------------------------------------------------------88# incrblob-2.*: 89#90# Test that the following operations use ptrmap pages to reduce91# unnecessary reads:92#93# * Reading near the end of a blob,94# * Writing near the end of a blob, and95# * SELECT a column value that is located on an overflow page.96#97proc nRead {db} {98 set bt [btree_from_db $db]99 db_enter $db100 array set stats [btree_pager_stats $bt]101 db_leave $db102 return $stats(read)103}104proc nWrite {db} {105 set bt [btree_from_db $db]106 db_enter $db107 array set stats [btree_pager_stats $bt]108 db_leave $db109 return $stats(write)110}111 112sqlite3_soft_heap_limit 0113 114foreach AutoVacuumMode [list 0 1] {115 116 if {$AutoVacuumMode>0} {117 ifcapable !autovacuum {118 break119 }120 }121 122 db close123 forcedelete test.db test.db-journal124 125 sqlite3 db test.db126 execsql "PRAGMA mmap_size = 0"127 execsql "PRAGMA auto_vacuum = $AutoVacuumMode"128 129 # Extra value added to size answers130 set ib2_extra 0131 if {$AutoVacuumMode} {incr ib2_extra}132 if {[nonzero_reserved_bytes]} {incr ib2_extra}133 134 do_test incrblob-2.$AutoVacuumMode.1 {135 set ::str [string repeat abcdefghij 2900]136 execsql {137 BEGIN;138 CREATE TABLE blobs(k PRIMARY KEY, v BLOB, i INTEGER);139 DELETE FROM blobs;140 INSERT INTO blobs VALUES('one', $::str || randstr(500,500), 45);141 COMMIT;142 }143 expr [file size test.db]/1024144 } [expr 31 + $ib2_extra]145 146 ifcapable autovacuum {147 do_test incrblob-2.$AutoVacuumMode.2 {148 execsql {149 PRAGMA auto_vacuum;150 }151 } $AutoVacuumMode152 }153 154 do_test incrblob-2.$AutoVacuumMode.3 {155 # Open and close the db to make sure the page cache is empty.156 db close157 sqlite3 db test.db158 execsql "PRAGMA mmap_size = 0"159 160 # Read the last 20 bytes of the blob via a blob handle.161 set ::blob [db incrblob blobs v 1]162 seek $::blob -20 end163 set ::fragment [read $::blob]164 close $::blob165 166 # If the database is not in auto-vacuum mode, the whole of167 # the overflow-chain must be scanned. In auto-vacuum mode,168 # sqlite uses the ptrmap pages to avoid reading the other pages.169 #170 nRead db171 } [expr $AutoVacuumMode ? 4 : 30+$ib2_extra]172 173 do_test incrblob-2.$AutoVacuumMode.4 {174 string range [db one {SELECT v FROM blobs}] end-19 end175 } $::fragment176 177 do_test incrblob-2.$AutoVacuumMode.5 {178 # Open and close the db to make sure the page cache is empty.179 db close180 sqlite3 db test.db181 execsql "PRAGMA mmap_size = 0"182 183 # Write the second-to-last 20 bytes of the blob via a blob handle.184 #185 set ::blob [db incrblob blobs v 1]186 seek $::blob -40 end187 puts -nonewline $::blob "1234567890abcdefghij"188 flush $::blob189 190 # If the database is not in auto-vacuum mode, the whole of191 # the overflow-chain must be scanned. In auto-vacuum mode,192 # sqlite uses the ptrmap pages to avoid reading the other pages.193 #194 nRead db195 } [expr $AutoVacuumMode ? 4 : 30 + $ib2_extra]196 197 # Pages 1 (the write-counter) and 32 (the blob data) were written.198 do_test incrblob-2.$AutoVacuumMode.6 {199 close $::blob200 nWrite db201 } 2202 203 do_test incrblob-2.$AutoVacuumMode.7 {204 string range [db one {SELECT v FROM blobs}] end-39 end-20205 } "1234567890abcdefghij"206 207 do_test incrblob-2.$AutoVacuumMode.8 {208 # Open and close the db to make sure the page cache is empty.209 db close210 sqlite3 db test.db211 execsql { PRAGMA mmap_size = 0 }212 213 execsql { SELECT i FROM blobs } 214 } {45}215 216 do_test incrblob-2.$AutoVacuumMode.9 {217 nRead db218 } [expr $AutoVacuumMode ? 4 : 30 + $ib2_extra]219}220sqlite3_soft_heap_limit $cmdlinearg(soft-heap-limit)221 222#------------------------------------------------------------------------223# incrblob-3.*: 224#225# Test the outcome of trying to write to a read-only blob handle.226#227do_test incrblob-3.1 {228 set ::blob [db incrblob -readonly blobs v 1]229 seek $::blob -40 end230 read $::blob 20231} "1234567890abcdefghij"232do_test incrblob-3.2 {233 seek $::blob 0234 set rc [catch {235 puts -nonewline $::blob "helloworld"236 } msg]237 close $::blob238 list $rc $msg239} "1 {channel \"$::blob\" wasn't opened for writing}"240 241do_test incrblob-3.3 {242 set ::blob [db incrblob -readonly blobs v 1]243 seek $::blob -40 end244 read $::blob 20245} "1234567890abcdefghij"246do_test incrblob-3.4 {247 set rc [catch {248 sqlite3_blob_write $::blob 20 "qwertyuioplkjhgfds" 249 } msg]250 list $rc $msg251} {1 SQLITE_READONLY}252catch {close $::blob}253 254#------------------------------------------------------------------------255# incrblob-4.*: 256#257# Try a couple of error conditions:258#259# 4.1 - Attempt to open a row that does not exist.260# 4.2 - Attempt to open a column that does not exist.261# 4.3 - Attempt to open a table that does not exist.262# 4.4 - Attempt to open a database that does not exist.263#264# 4.5 - Attempt to open an integer265# 4.6 - Attempt to open a real value266# 4.7 - Attempt to open an SQL null267#268# 4.8 - Attempt to open an indexed column for writing269# 4.9 - Attempt to open an indexed column for reading (this works)270#271# 4.11 - Attempt to open a column of a view.272# 4.12 - Attempt to open a column of a virtual table.273#274do_test incrblob-4.1 {275 set rc [catch {276 set ::blob [db incrblob blobs v 2]277 } msg ] 278 list $rc $msg279} {1 {no such rowid: 2}}280do_test incrblob-4.2 {281 set rc [catch {282 set ::blob [db incrblob blobs blue 1]283 } msg ] 284 list $rc $msg285} {1 {no such column: "blue"}}286do_test incrblob-4.3 {287 set rc [catch {288 set ::blob [db incrblob nosuchtable blue 1]289 } msg ]290 list $rc $msg291} {1 {no such table: main.nosuchtable}}292do_test incrblob-4.4 {293 set rc [catch {294 set ::blob [db incrblob nosuchdb blobs v 1]295 } msg ] 296 list $rc $msg297} {1 {no such table: nosuchdb.blobs}}298 299do_test incrblob-4.5 {300 set rc [catch {301 set ::blob [db incrblob blobs i 1]302 } msg ] 303 list $rc $msg304} {1 {cannot open value of type integer}}305do_test incrblob-4.6 {306 execsql {307 INSERT INTO blobs(k, v, i) VALUES(123, 567.765, NULL);308 }309 set rc [catch {310 set ::blob [db incrblob blobs v 2]311 } msg ] 312 list $rc $msg313} {1 {cannot open value of type real}}314do_test incrblob-4.7 {315 set rc [catch {316 set ::blob [db incrblob blobs i 2]317 } msg ] 318 list $rc $msg319} {1 {cannot open value of type null}}320 321do_test incrblob-4.8.1 {322 execsql {323 INSERT INTO blobs(k, v, i) VALUES(X'010203040506070809', 'hello', 'world');324 }325 set rc [catch {326 set ::blob [db incrblob blobs k 3]327 } msg ] 328 list $rc $msg329} {1 {cannot open indexed column for writing}}330do_test incrblob-4.8.2 {331 execsql {332 CREATE TABLE t3(a INTEGER PRIMARY KEY, b);333 INSERT INTO t3 VALUES(1, 2);334 }335 set rc [catch {336 set ::blob [db incrblob -readonly t3 a 1]337 } msg ] 338 list $rc $msg339} {1 {cannot open value of type null}}340do_test incrblob-4.8.3 {341 set rc [catch {342 set ::blob [db incrblob -readonly t3 rowid 1]343 } msg ] 344 list $rc $msg345} {1 {no such column: "rowid"}}346 347do_test incrblob-4.9.1 {348 set rc [catch {349 set ::blob [db incrblob -readonly blobs k 3]350 } msg]351} {0}352do_test incrblob-4.9.2 {353 binary scan [read $::blob] c* c354 close $::blob355 set c356} {1 2 3 4 5 6 7 8 9}357 358do_test incrblob-4.10 {359 set ::blob [db incrblob -readonly blobs k 3]360 set rc [catch { sqlite3_blob_read $::blob 10 100 } msg]361 list $rc $msg362} {1 SQLITE_ERROR}363do_test incrblob-4.10.2 {364 close $::blob365} {}366 367ifcapable view {368 do_test incrblob-4.11 {369 execsql { CREATE VIEW blobs_view AS SELECT k, v, i FROM blobs }370 set rc [catch { db incrblob blobs_view v 3 } msg]371 list $rc $msg372 } {1 {cannot open view: blobs_view}}373}374ifcapable vtab {375 register_echo_module [sqlite3_connection_pointer db]376 do_test incrblob-4.12 {377 execsql { CREATE VIRTUAL TABLE blobs_echo USING echo(blobs) }378 set rc [catch { db incrblob blobs_echo v 3 } msg]379 list $rc $msg380 } {1 {cannot open virtual table: blobs_echo}}381}382 383 384#------------------------------------------------------------------------385# incrblob-5.*: 386#387# Test that opening a blob in an attached database works.388#389ifcapable attach {390 do_test incrblob-5.1 {391 forcedelete test2.db test2.db-journal392 set ::size [expr [file size $::cmdlinearg(INFO_SCRIPT)]]393 execsql {394 ATTACH 'test2.db' AS aux;395 CREATE TABLE aux.files(name, text);396 INSERT INTO aux.files VALUES('this one', zeroblob($::size));397 }398 set fd [db incrblob aux files text 1]399 fconfigure $fd -translation binary400 set fd2 [open $::cmdlinearg(INFO_SCRIPT)]401 fconfigure $fd2 -translation binary402 puts -nonewline $fd [read $fd2]403 close $fd404 close $fd2405 set ::text [db one {select text from aux.files}]406 string length $::text407 } [file size $::cmdlinearg(INFO_SCRIPT)]408 do_test incrblob-5.2 {409 set fd2 [open $::cmdlinearg(INFO_SCRIPT)]410 fconfigure $fd2 -translation binary411 set ::data [read $fd2]412 close $fd2413 set ::data414 } $::text415}416 417# free memory418unset -nocomplain ::data419unset -nocomplain ::text420 421#------------------------------------------------------------------------422# incrblob-6.*: 423#424# Test that opening a blob for write-access is impossible if425# another connection has the database RESERVED lock.426#427# Then test that blob writes that take place inside of a428# transaction are not visible to external connections until429# after the transaction is commited and the blob channel 430# closed.431#432# This test does not work with the "memsubsys1" configuration.433# Permutation memsubsys1 configures a very small static allocation 434# for use as page-cache memory. This causes SQLite to upgrade435# to an exclusive lock when writing earlier than usual, which436# makes some of these tests fail.437#438sqlite3_soft_heap_limit 0439if {[permutation] != "memsubsys1"} {440 do_test incrblob-6.1 {441 sqlite3 db2 test.db442 execsql {443 BEGIN;444 INSERT INTO blobs(k, v, i) VALUES('a', 'different', 'connection');445 } db2446 } {}447 do_test incrblob-6.2 {448 execsql {449 SELECT rowid FROM blobs ORDER BY rowid450 }451 } {1 2 3}452 do_test incrblob-6.3 {453 set rc [catch {454 db incrblob blobs v 1455 } msg]456 list $rc $msg457 } {1 {database is locked}}458 do_test incrblob-6.4 {459 set rc [catch {460 db incrblob blobs v 3461 } msg]462 list $rc $msg463 } {1 {database is locked}}464 do_test incrblob-6.5 {465 set ::blob [db incrblob -readonly blobs v 3]466 read $::blob467 } {hello}468 do_test incrblob-6.6 {469 close $::blob470 } {}471 472 do_test incrblob-6.7 {473 set ::blob [db2 incrblob blobs i 4]474 gets $::blob475 } {connection}476 do_test incrblob-6.8 {477 tell $::blob478 } {10}479 do_test incrblob-6.9 {480 seek $::blob 0481 puts -nonewline $::blob "invocation"482 flush $::blob483 } {}484 485 # At this point commit should be illegal (because 486 # there is an open blob channel).487 #488 do_test incrblob-6.11 {489 catchsql {490 COMMIT;491 } db2492 } {1 {cannot commit transaction - SQL statements in progress}}493 494 do_test incrblob-6.12 {495 execsql {496 SELECT * FROM blobs WHERE rowid = 4;497 }498 } {}499 do_test incrblob-6.13 {500 close $::blob501 } {}502 do_test incrblob-6.14 {503 catchsql {504 COMMIT;505 } db2506 } {0 {}}507 do_test incrblob-6.15 {508 execsql {509 SELECT * FROM blobs WHERE rowid = 4;510 }511 } {a different invocation}512 db2 close513}514sqlite3_soft_heap_limit $cmdlinearg(soft-heap-limit)515 516#-----------------------------------------------------------------------517# The following tests verify the behavior of the incremental IO518# APIs in the following cases:519#520# 7.1 A row that containing an open blob is modified.521#522# 7.2 A CREATE TABLE requires that an overflow page that is part523# of an open blob is moved.524#525# 7.3 An INCREMENTAL VACUUM moves an overflow page that is part526# of an open blob.527#528# In the first case above, correct behavior is for all subsequent529# read/write operations on the blob-handle to return SQLITE_ABORT.530# More accurately, blob-handles are invalidated whenever the table531# they belong to is written to.532#533# The second two cases have no external effect. They are testing534# that the internal cache of overflow page numbers is correctly535# invalidated.536#537do_test incrblob-7.1.0 {538 execsql {539 BEGIN;540 DROP TABLE blobs;541 CREATE TABLE t1 (a, b, c, d BLOB);542 INSERT INTO t1(a, b, c, d) VALUES(1, 2, 3, 4);543 COMMIT;544 }545} {}546 547foreach {tn arg} {1 "" 2 -readonly} {548 549 execsql {550 UPDATE t1 SET d = zeroblob(10000);551 }552 553 do_test incrblob-7.1.$tn.1 {554 set ::b [eval db incrblob $arg t1 d 1]555 binary scan [sqlite3_blob_read $::b 5000 5] c* c556 set c557 } {0 0 0 0 0}558 do_test incrblob-7.1.$tn.2 {559 execsql {560 UPDATE t1 SET d = 15;561 }562 } {}563 do_test incrblob-7.1.$tn.3 {564 set rc [catch { sqlite3_blob_read $::b 5000 5 } msg]565 list $rc $msg566 } {1 SQLITE_ABORT}567 do_test incrblob-7.1.$tn.4 {568 execsql {569 SELECT d FROM t1;570 }571 } {15}572 do_test incrblob-7.1.$tn.5 {573 set rc [catch { close $::b } msg]574 list $rc $msg575 } {0 {}}576 do_test incrblob-7.1.$tn.6 {577 execsql {578 SELECT d FROM t1;579 }580 } {15}581 582}583 584set fd [open $::cmdlinearg(INFO_SCRIPT)]585fconfigure $fd -translation binary586set ::data [read $fd 14000]587close $fd588 589db close590forcedelete test.db test.db-journal591sqlite3 db test.db592 593do_test incrblob-7.2.1 {594 execsql {595 PRAGMA auto_vacuum = "incremental";596 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); -- root@page3597 INSERT INTO t1 VALUES(123, $::data);598 }599 set ::b [db incrblob -readonly t1 b 123]600 fconfigure $::b -translation binary601 read $::b602} $::data603do_test incrblob-7.2.2 {604 execsql {605 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); -- root@page4606 }607 seek $::b 0608 read $::b609} $::data610do_test incrblob-7.2.3 {611 close $::b612 execsql {613 SELECT rootpage FROM sqlite_master;614 }615} {3 4}616 617set ::otherdata "[string range $::data 0 1000][string range $::data 1001 end]"618do_test incrblob-7.3.1 {619 execsql {620 INSERT INTO t2 VALUES(456, $::otherdata);621 }622 set ::b [db incrblob -readonly t2 b 456]623 fconfigure $::b -translation binary624 read $::b625} $::otherdata626do_test incrblob-7.3.2 {627 expr [file size test.db]/1024628} 30629do_test incrblob-7.3.3 {630 execsql {631 DELETE FROM t1 WHERE a = 123;632 PRAGMA INCREMENTAL_VACUUM(0);633 }634 seek $::b 0635 read $::b636} $::otherdata637 638# Attempt to write on a read-only blob. Make sure the error code639# gets set. Ticket #2464.640#641do_test incrblob-7.4 {642 set rc [catch {sqlite3_blob_write $::b 10 HELLO} msg]643 lappend rc $msg644} {1 SQLITE_READONLY}645do_test incrblob-7.5 {646 sqlite3_errcode db647} {SQLITE_READONLY}648do_test incrblob-7.6 {649 sqlite3_errmsg db650} {attempt to write a readonly database}651 652# Test that if either the "offset" or "amount" arguments to653# sqlite3_blob_write() are less than zero, SQLITE_ERROR is returned.654# 655do_test incrblob-8.1 {656 execsql { INSERT INTO t1 VALUES(314159, 'sqlite') }657 set ::b [db incrblob t1 b 314159]658 fconfigure $::b -translation binary659 set rc [catch {sqlite3_blob_write $::b 10 HELLO -1} msg]660 lappend rc $msg661} {1 SQLITE_ERROR}662do_test incrblob-8.2 {663 sqlite3_errcode db664} {SQLITE_ERROR}665do_test incrblob-8.3 {666 set rc [catch {sqlite3_blob_write $::b -1 HELLO 5} msg]667 lappend rc $msg668} {1 SQLITE_ERROR}669do_test incrblob-8.4 {670 sqlite3_errcode db671} {SQLITE_ERROR}672do_test incrblob-8.5 {673 execsql {SELECT b FROM t1 WHERE a = 314159}674} {sqlite}675do_test incrblob-8.6 {676 set rc [catch {sqlite3_blob_write $::b 0 etilqs 6} msg]677 lappend rc $msg678} {0 {}}679do_test incrblob-8.7 {680 execsql {SELECT b FROM t1 WHERE a = 314159}681} {etilqs}682 683# The following test case exposes an instance in the blob code where684# an error message was set using a call similar to sqlite3_mprintf(zErr),685# where zErr is an arbitrary string. This is no good if the string contains686# characters that can be mistaken for printf() formatting directives.687#688do_test incrblob-9.1 {689 list [catch { db incrblob t1 "A tricky column name %s%s" 1 } msg] $msg690} {1 {no such column: "A tricky column name %s%s"}}691 692 693finish_test694 