blob: 40c4e215088c0b34ecee8053d6c3db760b3c266b [file] [log] [blame]
danielk1977b4e9af92007-05-01 17:49:49 +00001# 2007 May 1
2#
3# The author disclaims copyright to this source code. In place of
4# 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#
danielk1977dcbb5d32007-05-04 18:36:44 +000012# $Id: incrblob.test,v 1.8 2007/05/04 18:36:46 danielk1977 Exp $
danielk197720713f32007-05-03 11:43:33 +000013#
danielk1977b4e9af92007-05-01 17:49:49 +000014
15set testdir [file dirname $argv0]
16source $testdir/tester.tcl
17
18do_test incrblob-1.1 {
19 execsql {
20 CREATE TABLE blobs(k PRIMARY KEY, v BLOB);
21 INSERT INTO blobs VALUES('one', X'0102030405060708090A');
22 INSERT INTO blobs VALUES('two', X'0A090807060504030201');
23 }
24} {}
25
26do_test incrblob-1.2.1 {
27 set ::blob [db incrblob blobs v 1]
28} {incrblob_1}
29do_test incrblob-1.2.2 {
30 binary scan [read $::blob] c* data
31 set data
32} {1 2 3 4 5 6 7 8 9 10}
33do_test incrblob-1.2.3 {
34 seek $::blob 0
35 puts -nonewline $::blob "1234567890"
36 flush $::blob
37} {}
38do_test incrblob-1.2.4 {
39 seek $::blob 0
40 binary scan [read $::blob] c* data
41 set data
42} {49 50 51 52 53 54 55 56 57 48}
43do_test incrblob-1.2.5 {
44 close $::blob
45} {}
46do_test incrblob-1.2.6 {
47 execsql {
48 SELECT v FROM blobs WHERE rowid = 1;
49 }
50} {1234567890}
51
danielk1977d04417962007-05-02 13:16:30 +000052#--------------------------------------------------------------------
danielk197720713f32007-05-03 11:43:33 +000053# Test cases incrblob-1.3.X check that it is possible to read and write
danielk1977d04417962007-05-02 13:16:30 +000054# regions of a blob that lie on overflow pages.
danielk197720713f32007-05-03 11:43:33 +000055#
56do_test incrblob-1.3.1 {
danielk1977d04417962007-05-02 13:16:30 +000057 set ::str "[string repeat . 10000]"
58 execsql {
59 INSERT INTO blobs(rowid, k, v) VALUES(3, 'three', $::str);
60 }
61} {}
danielk1977b4e9af92007-05-01 17:49:49 +000062
danielk197720713f32007-05-03 11:43:33 +000063do_test incrblob-1.3.2 {
danielk1977d04417962007-05-02 13:16:30 +000064 set ::blob [db incrblob blobs v 3]
65 seek $::blob 8500
66 read $::blob 10
67} {..........}
danielk197720713f32007-05-03 11:43:33 +000068do_test incrblob-1.3.3 {
danielk1977d04417962007-05-02 13:16:30 +000069 seek $::blob 8500
70 puts -nonewline $::blob 1234567890
71} {}
danielk197720713f32007-05-03 11:43:33 +000072do_test incrblob-1.3.4 {
danielk1977d04417962007-05-02 13:16:30 +000073 seek $::blob 8496
74 read $::blob 10
75} {....123456}
danielk197720713f32007-05-03 11:43:33 +000076do_test incrblob-1.3.10 {
danielk1977d04417962007-05-02 13:16:30 +000077 close $::blob
78} {}
79
danielk1977b4e9af92007-05-01 17:49:49 +000080
danielk197720713f32007-05-03 11:43:33 +000081#------------------------------------------------------------------------
danielk19778cbadb02007-05-03 16:31:26 +000082# incrblob-2.*:
83#
84# Test that the following operations use ptrmap pages to reduce
85# unnecessary reads:
danielk197744e6c8d2007-05-03 13:11:32 +000086#
87# * Reading near the end of a blob,
danielk19778cbadb02007-05-03 16:31:26 +000088# * Writing near the end of a blob, and
89# * SELECT a column value that is located on an overflow page.
danielk197720713f32007-05-03 11:43:33 +000090#
91proc nRead {db} {
92 set bt [btree_from_db $db]
93 array set stats [btree_pager_stats $bt]
94 return $stats(read)
95}
danielk19778cbadb02007-05-03 16:31:26 +000096proc nWrite {db} {
97 set bt [btree_from_db $db]
98 array set stats [btree_pager_stats $bt]
99 return $stats(write)
100}
danielk197720713f32007-05-03 11:43:33 +0000101
102foreach AutoVacuumMode [list 0 1] {
103
drh847d3ab2007-05-04 14:36:22 +0000104 if {$AutoVacuumMode>0} {
105 ifcapable !autovacuum {
106 break
107 }
108 }
109
danielk197720713f32007-05-03 11:43:33 +0000110 db close
111 file delete -force test.db test.db-journal
112
113 sqlite3 db test.db
114 execsql "PRAGMA auto_vacuum = $AutoVacuumMode"
115
116 do_test incrblob-2.$AutoVacuumMode.1 {
117 set ::str [string repeat abcdefghij 2900]
118 execsql {
119 BEGIN;
danielk19778cbadb02007-05-03 16:31:26 +0000120 CREATE TABLE blobs(k PRIMARY KEY, v BLOB, i INTEGER);
danielk197720713f32007-05-03 11:43:33 +0000121 DELETE FROM blobs;
danielk19778cbadb02007-05-03 16:31:26 +0000122 INSERT INTO blobs VALUES('one', $::str || randstr(500,500), 45);
danielk197720713f32007-05-03 11:43:33 +0000123 COMMIT;
124 }
125 expr [file size test.db]/1024
126 } [expr 31 + $AutoVacuumMode]
127
drh847d3ab2007-05-04 14:36:22 +0000128 ifcapable autovacuum {
129 do_test incrblob-2.$AutoVacuumMode.2 {
130 execsql {
131 PRAGMA auto_vacuum;
132 }
133 } $AutoVacuumMode
134 }
danielk197720713f32007-05-03 11:43:33 +0000135
136 do_test incrblob-2.$AutoVacuumMode.3 {
137 # Open and close the db to make sure the page cache is empty.
138 db close
139 sqlite3 db test.db
140
141 # Read the last 20 bytes of the blob via a blob handle.
142 set ::blob [db incrblob blobs v 1]
143 seek $::blob -20 end
144 set ::fragment [read $::blob]
145 close $::blob
146
147 # If the database is not in auto-vacuum mode, the whole of
148 # the overflow-chain must be scanned. In auto-vacuum mode,
149 # sqlite uses the ptrmap pages to avoid reading the other pages.
150 #
151 nRead db
152 } [expr $AutoVacuumMode ? 4 : 30]
153
danielk19778cbadb02007-05-03 16:31:26 +0000154 do_test incrblob-2.$AutoVacuumMode.4 {
danielk197720713f32007-05-03 11:43:33 +0000155 string range [db one {SELECT v FROM blobs}] end-19 end
156 } $::fragment
danielk19778cbadb02007-05-03 16:31:26 +0000157
158 do_test incrblob-2.$AutoVacuumMode.5 {
159 # Open and close the db to make sure the page cache is empty.
160 db close
161 sqlite3 db test.db
162
163 # Write the second-to-last 20 bytes of the blob via a blob handle.
164 #
165 set ::blob [db incrblob blobs v 1]
166 seek $::blob -40 end
167 puts -nonewline $::blob "1234567890abcdefghij"
168 flush $::blob
169
170 # If the database is not in auto-vacuum mode, the whole of
171 # the overflow-chain must be scanned. In auto-vacuum mode,
172 # sqlite uses the ptrmap pages to avoid reading the other pages.
173 #
174 nRead db
175 } [expr $AutoVacuumMode ? 4 : 30]
176
177 # Pages 1 (the write-counter) and 32 (the blob data) were written.
178 do_test incrblob-2.$AutoVacuumMode.6 {
179 close $::blob
180 nWrite db
181 } 2
182
183 do_test incrblob-2.$AutoVacuumMode.7 {
184 string range [db one {SELECT v FROM blobs}] end-39 end-20
185 } "1234567890abcdefghij"
186
187 do_test incrblob-2.$AutoVacuumMode.8 {
188 # Open and close the db to make sure the page cache is empty.
189 db close
190 sqlite3 db test.db
191
192 execsql { SELECT i FROM blobs }
193 } {45}
194
195 do_test incrblob-2.$AutoVacuumMode.9 {
196 nRead db
197 } [expr $AutoVacuumMode ? 4 : 30]
danielk197720713f32007-05-03 11:43:33 +0000198}
199
danielk19778cbadb02007-05-03 16:31:26 +0000200#------------------------------------------------------------------------
201# incrblob-3.*:
202#
203# Test the outcome of trying to write to a read-only blob handle.
204#
danielk19778cbadb02007-05-03 16:31:26 +0000205do_test incrblob-3.1 {
206 set ::blob [db incrblob -readonly blobs v 1]
207 seek $::blob -40 end
208 read $::blob 20
209} "1234567890abcdefghij"
210do_test incrblob-3.2 {
211 seek $::blob 0
212 set rc [catch {
213 puts -nonewline $::blob "helloworld"
214 } msg]
danielk1977f1819242007-05-03 18:14:10 +0000215 close $::blob
danielk19778cbadb02007-05-03 16:31:26 +0000216 list $rc $msg
217} "1 {channel \"$::blob\" wasn't opened for writing}"
218
danielk1977dcbb5d32007-05-04 18:36:44 +0000219do_test incrblob-3.3 {
220 set ::blob [db incrblob -readonly blobs v 1]
221 seek $::blob -40 end
222 read $::blob 20
223} "1234567890abcdefghij"
224do_test incrblob-3.4 {
225 set rc [catch {
226 sqlite3_blob_write $::blob 20 "qwertyuioplkjhgfds"
227 } msg]
228 list $rc $msg
229} {1 SQLITE_READONLY}
230catch {close $::blob}
231
danielk19778cbadb02007-05-03 16:31:26 +0000232#------------------------------------------------------------------------
233# incrblob-4.*:
234#
235# Try a couple of error conditions:
236#
237# 4.1 - Attempt to open a row that does not exist.
238# 4.2 - Attempt to open a column that does not exist.
239# 4.3 - Attempt to open a table that does not exist.
240# 4.4 - Attempt to open a database that does not exist.
241#
danielk1977f1819242007-05-03 18:14:10 +0000242# 4.5 - Attempt to open an integer
243# 4.6 - Attempt to open a real value
244# 4.7 - Attempt to open an SQL null
245#
246# 4.8 - Attempt to open an indexed column for writing
247# 4.9 - Attempt to open an indexed column for reading (this works)
248#
danielk19778cbadb02007-05-03 16:31:26 +0000249do_test incrblob-4.1 {
250 set rc [catch {
251 set ::blob [db incrblob blobs v 2]
252 } msg ]
253 list $rc $msg
254} {1 {no such rowid: 2}}
danielk19778cbadb02007-05-03 16:31:26 +0000255do_test incrblob-4.2 {
256 set rc [catch {
257 set ::blob [db incrblob blobs blue 1]
258 } msg ]
259 list $rc $msg
260} {1 {no such column: "blue"}}
danielk19778cbadb02007-05-03 16:31:26 +0000261do_test incrblob-4.3 {
262 set rc [catch {
263 set ::blob [db incrblob nosuchtable blue 1]
danielk1977f1819242007-05-03 18:14:10 +0000264 } msg ]
danielk19778cbadb02007-05-03 16:31:26 +0000265 list $rc $msg
266} {1 {no such table: main.nosuchtable}}
danielk19778cbadb02007-05-03 16:31:26 +0000267do_test incrblob-4.4 {
268 set rc [catch {
269 set ::blob [db incrblob nosuchdb blobs v 1]
270 } msg ]
271 list $rc $msg
272} {1 {no such table: nosuchdb.blobs}}
273
danielk1977f1819242007-05-03 18:14:10 +0000274do_test incrblob-4.5 {
275 set rc [catch {
276 set ::blob [db incrblob blobs i 1]
277 } msg ]
278 list $rc $msg
279} {1 {cannot open value of type integer}}
280do_test incrblob-4.6 {
281 execsql {
282 INSERT INTO blobs(k, v, i) VALUES(123, 567.765, NULL);
283 }
284 set rc [catch {
285 set ::blob [db incrblob blobs v 2]
286 } msg ]
287 list $rc $msg
288} {1 {cannot open value of type real}}
289do_test incrblob-4.7 {
290 set rc [catch {
291 set ::blob [db incrblob blobs i 2]
292 } msg ]
293 list $rc $msg
294} {1 {cannot open value of type null}}
danielk19778cbadb02007-05-03 16:31:26 +0000295
danielk1977f1819242007-05-03 18:14:10 +0000296do_test incrblob-4.8 {
297 execsql {
298 INSERT INTO blobs(k, v, i) VALUES(X'010203040506070809', 'hello', 'world');
299 }
300 set rc [catch {
301 set ::blob [db incrblob blobs k 3]
302 } msg ]
303 list $rc $msg
304} {1 {cannot open indexed column for writing}}
305
306do_test incrblob-4.9.1 {
307 set rc [catch {
308 set ::blob [db incrblob -readonly blobs k 3]
309 } msg]
310} {0}
311do_test incrblob-4.9.2 {
312 binary scan [read $::blob] c* c
313 close $::blob
314 set c
315} {1 2 3 4 5 6 7 8 9}
316
317#------------------------------------------------------------------------
318# incrblob-5.*:
319#
320# Test that opening a blob in an attached database works.
321#
322do_test incrblob-5.1 {
323 file delete -force test2.db test2.db-journal
324 set ::size [expr [file size [info script]]]
325 execsql {
326 ATTACH 'test2.db' AS aux;
327 CREATE TABLE aux.files(name, text);
328 INSERT INTO aux.files VALUES('this one', zeroblob($::size));
329 }
330 set fd [db incrblob aux files text 1]
331 set fd2 [open [info script]]
332 puts -nonewline $fd [read $fd2]
333 close $fd
334 close $fd2
335 set ::text [db one {select text from aux.files}]
336 string length $::text
337} [file size [info script]]
338do_test incrblob-5.2 {
339 set fd2 [open [info script]]
340 set ::data [read $fd2]
341 close $fd2
342 set ::data
343} $::text
344
345# free memory
346unset ::data
347unset ::text
348
349#------------------------------------------------------------------------
350# incrblob-6.*:
351#
352# Test that opening a blob for write-access is impossible if
353# another connection has the database RESERVED lock.
354#
355# Then test that blob writes that take place inside of a
356# transaction are not visible to external connections until
357# after the transaction is commited and the blob channel
358# closed.
359#
360do_test incrblob-6.1 {
361 sqlite3 db2 test.db
362 execsql {
363 BEGIN;
364 INSERT INTO blobs(k, v, i) VALUES('a', 'different', 'connection');
365 } db2
366} {}
367do_test incrblob-6.2 {
368 execsql {
369 SELECT rowid FROM blobs
370 }
371} {1 2 3}
372do_test incrblob-6.3 {
373 set rc [catch {
374 db incrblob blobs v 1
375 } msg]
376 list $rc $msg
377} {1 {database is locked}}
378do_test incrblob-6.4 {
379 set rc [catch {
380 db incrblob blobs v 3
381 } msg]
382 list $rc $msg
383} {1 {database is locked}}
384do_test incrblob-6.5 {
385 set ::blob [db incrblob -readonly blobs v 3]
386 read $::blob
387} {hello}
388do_test incrblob-6.6 {
389 close $::blob
390} {}
391
392do_test incrblob-6.7 {
393 set ::blob [db2 incrblob blobs i 4]
394 gets $::blob
395} {connection}
396do_test incrblob-6.8 {
397 tell $::blob
398} {10}
danielk1977f1819242007-05-03 18:14:10 +0000399do_test incrblob-6.9 {
400 seek $::blob 0
401 puts -nonewline $::blob "invocation"
402 flush $::blob
403} {}
404
405# At this point rollback or commit should be illegal (because
406# there is an open blob channel).
407do_test incrblob-6.10 {
408 catchsql {
409 ROLLBACK;
410 } db2
411} {1 {cannot rollback transaction - SQL statements in progress}}
412do_test incrblob-6.11 {
413 catchsql {
414 COMMIT;
415 } db2
416} {1 {cannot commit transaction - SQL statements in progress}}
417
418do_test incrblob-6.12 {
419 execsql {
420 SELECT * FROM blobs WHERE rowid = 4;
421 }
422} {}
423do_test incrblob-6.13 {
424 close $::blob
425 execsql {
426 COMMIT;
427 } db2
428} {}
429do_test incrblob-6.14 {
430 execsql {
431 SELECT * FROM blobs WHERE rowid = 4;
432 }
433} {a different invocation}
434db2 close
435
danielk1977dcbb5d32007-05-04 18:36:44 +0000436#-----------------------------------------------------------------------
437# The following tests verify the behaviour of the incremental IO
438# APIs in the following cases:
439#
440# 7.1 A row that containing an open blob is modified.
441#
442# 7.2 A CREATE TABLE requires that an overflow page that is part
443# of an open blob is moved.
444#
445# 7.3 An INCREMENTAL VACUUM moves an overflow page that is part
446# of an open blob.
447#
448# In the first case above, correct behaviour is for all subsequent
449# read/write operations on the blob-handle to return SQLITE_ABORT.
450# More accurately, blob-handles are invalidated whenever the table
451# they belong to is written to.
452#
453# The second two cases have no external effect. They are testing
454# that the internal cache of overflow page numbers is correctly
455# invalidated.
456#
457do_test incrblob-7.1.0 {
458 execsql {
459 BEGIN;
460 DROP TABLE blobs;
461 CREATE TABLE t1 (a, b, c, d BLOB);
462 INSERT INTO t1(a, b, c, d) VALUES(1, 2, 3, 4);
463 COMMIT;
464 }
465} {}
466
467foreach {tn arg} {1 "" 2 -readonly} {
468
469 execsql {
470 UPDATE t1 SET d = zeroblob(10000);
471 }
472
473 do_test incrblob-7.1.$tn.1 {
474 set ::b [eval db incrblob $arg t1 d 1]
475 binary scan [sqlite3_blob_read $::b 5000 5] c* c
476 set c
477 } {0 0 0 0 0}
478 do_test incrblob-7.1.$tn.2 {
479 execsql {
480 UPDATE t1 SET d = 15;
481 }
482 } {}
483 do_test incrblob-7.1.$tn.3 {
484 set rc [catch { sqlite3_blob_read $::b 5000 5 } msg]
485 list $rc $msg
486 } {1 SQLITE_ABORT}
487 do_test incrblob-7.1.$tn.4 {
488 execsql {
489 SELECT d FROM t1;
490 }
491 } {15}
492 do_test incrblob-7.1.$tn.5 {
493 set rc [catch { close $::b } msg]
494 list $rc $msg
495 } {0 {}}
496 do_test incrblob-7.1.$tn.6 {
497 execsql {
498 SELECT d FROM t1;
499 }
500 } {15}
501
502}
503
504set fd [open [info script]]
505set ::data [read $fd]
506close $fd
507
508db close
509file delete -force test.db test.db-journal
510sqlite3 db test.db
511
512do_test incrblob-7.2.1 {
513 execsql {
514 PRAGMA auto_vacuum = "incremental";
515 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); -- root@page3
516 INSERT INTO t1 VALUES(123, $::data);
517 }
518 set ::b [db incrblob -readonly t1 b 123]
519 read $::b
520} $::data
521do_test incrblob-7.2.2 {
522 execsql {
523 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); -- root@page4
524 }
525 seek $::b 0
526 read $::b
527} $::data
528do_test incrblob-7.2.3 {
529 close $::b
530 execsql {
531 SELECT rootpage FROM sqlite_master;
532 }
533} {3 4}
534
535set ::otherdata "[string range $::data 0 1000][string range $::data 1001 end]"
536do_test incrblob-7.3.1 {
537 execsql {
538 INSERT INTO t2 VALUES(456, $::otherdata);
539 }
540 set ::b [db incrblob -readonly t2 b 456]
541 read $::b
542} $::otherdata
543do_test incrblob-7.3.2 {
544 expr [file size test.db]/1024
545} 30
546do_test incrblob-7.3.3 {
547 execsql {
548 DELETE FROM t1 WHERE a = 123;
549 INCREMENTAL VACUUM;
550 }
551 seek $::b 0
552 read $::b
553} $::otherdata
554
danielk1977f1819242007-05-03 18:14:10 +0000555finish_test
danielk1977dcbb5d32007-05-04 18:36:44 +0000556