blob: 434ac9aaa73bbf0f4961c28f514262af5bf9685d [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#
drh3aefaba2007-08-12 20:07:58 +000012# $Id: incrblob.test,v 1.13 2007/08/12 20:07:59 drh 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
danielk197732a0d8b2007-05-04 19:03:02 +000018ifcapable {!autovacuum || !pragma || !incrblob} {
19 finish_test
20 return
21}
22
danielk1977b4e9af92007-05-01 17:49:49 +000023do_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]
danielk1977ca306512007-06-15 15:08:08 +000033 string match incrblob_* $::blob
34} {1}
danielk1977b4e9af92007-05-01 17:49:49 +000035do_test incrblob-1.2.2 {
36 binary scan [read $::blob] c* data
37 set data
38} {1 2 3 4 5 6 7 8 9 10}
39do_test incrblob-1.2.3 {
40 seek $::blob 0
41 puts -nonewline $::blob "1234567890"
42 flush $::blob
43} {}
44do_test incrblob-1.2.4 {
45 seek $::blob 0
46 binary scan [read $::blob] c* data
47 set data
48} {49 50 51 52 53 54 55 56 57 48}
49do_test incrblob-1.2.5 {
50 close $::blob
51} {}
52do_test incrblob-1.2.6 {
53 execsql {
54 SELECT v FROM blobs WHERE rowid = 1;
55 }
56} {1234567890}
57
danielk1977d04417962007-05-02 13:16:30 +000058#--------------------------------------------------------------------
danielk197720713f32007-05-03 11:43:33 +000059# Test cases incrblob-1.3.X check that it is possible to read and write
danielk1977d04417962007-05-02 13:16:30 +000060# regions of a blob that lie on overflow pages.
danielk197720713f32007-05-03 11:43:33 +000061#
62do_test incrblob-1.3.1 {
danielk1977d04417962007-05-02 13:16:30 +000063 set ::str "[string repeat . 10000]"
64 execsql {
65 INSERT INTO blobs(rowid, k, v) VALUES(3, 'three', $::str);
66 }
67} {}
danielk1977b4e9af92007-05-01 17:49:49 +000068
danielk197720713f32007-05-03 11:43:33 +000069do_test incrblob-1.3.2 {
danielk1977d04417962007-05-02 13:16:30 +000070 set ::blob [db incrblob blobs v 3]
71 seek $::blob 8500
72 read $::blob 10
73} {..........}
danielk197720713f32007-05-03 11:43:33 +000074do_test incrblob-1.3.3 {
danielk1977d04417962007-05-02 13:16:30 +000075 seek $::blob 8500
76 puts -nonewline $::blob 1234567890
77} {}
danielk197720713f32007-05-03 11:43:33 +000078do_test incrblob-1.3.4 {
danielk1977d04417962007-05-02 13:16:30 +000079 seek $::blob 8496
80 read $::blob 10
81} {....123456}
danielk197720713f32007-05-03 11:43:33 +000082do_test incrblob-1.3.10 {
danielk1977d04417962007-05-02 13:16:30 +000083 close $::blob
84} {}
85
danielk1977b4e9af92007-05-01 17:49:49 +000086
danielk197720713f32007-05-03 11:43:33 +000087#------------------------------------------------------------------------
danielk19778cbadb02007-05-03 16:31:26 +000088# incrblob-2.*:
89#
90# Test that the following operations use ptrmap pages to reduce
91# unnecessary reads:
danielk197744e6c8d2007-05-03 13:11:32 +000092#
93# * Reading near the end of a blob,
danielk19778cbadb02007-05-03 16:31:26 +000094# * Writing near the end of a blob, and
95# * SELECT a column value that is located on an overflow page.
danielk197720713f32007-05-03 11:43:33 +000096#
97proc nRead {db} {
98 set bt [btree_from_db $db]
99 array set stats [btree_pager_stats $bt]
100 return $stats(read)
101}
danielk19778cbadb02007-05-03 16:31:26 +0000102proc nWrite {db} {
103 set bt [btree_from_db $db]
104 array set stats [btree_pager_stats $bt]
105 return $stats(write)
106}
danielk197720713f32007-05-03 11:43:33 +0000107
drh3aefaba2007-08-12 20:07:58 +0000108sqlite3_soft_heap_limit 0
109
danielk197720713f32007-05-03 11:43:33 +0000110foreach AutoVacuumMode [list 0 1] {
111
drh847d3ab2007-05-04 14:36:22 +0000112 if {$AutoVacuumMode>0} {
113 ifcapable !autovacuum {
114 break
115 }
116 }
117
danielk197720713f32007-05-03 11:43:33 +0000118 db close
119 file delete -force test.db test.db-journal
120
121 sqlite3 db test.db
122 execsql "PRAGMA auto_vacuum = $AutoVacuumMode"
123
124 do_test incrblob-2.$AutoVacuumMode.1 {
125 set ::str [string repeat abcdefghij 2900]
126 execsql {
127 BEGIN;
danielk19778cbadb02007-05-03 16:31:26 +0000128 CREATE TABLE blobs(k PRIMARY KEY, v BLOB, i INTEGER);
danielk197720713f32007-05-03 11:43:33 +0000129 DELETE FROM blobs;
danielk19778cbadb02007-05-03 16:31:26 +0000130 INSERT INTO blobs VALUES('one', $::str || randstr(500,500), 45);
danielk197720713f32007-05-03 11:43:33 +0000131 COMMIT;
132 }
133 expr [file size test.db]/1024
134 } [expr 31 + $AutoVacuumMode]
135
drh847d3ab2007-05-04 14:36:22 +0000136 ifcapable autovacuum {
137 do_test incrblob-2.$AutoVacuumMode.2 {
138 execsql {
139 PRAGMA auto_vacuum;
140 }
141 } $AutoVacuumMode
142 }
danielk197720713f32007-05-03 11:43:33 +0000143
144 do_test incrblob-2.$AutoVacuumMode.3 {
145 # Open and close the db to make sure the page cache is empty.
146 db close
147 sqlite3 db test.db
148
149 # Read the last 20 bytes of the blob via a blob handle.
150 set ::blob [db incrblob blobs v 1]
151 seek $::blob -20 end
152 set ::fragment [read $::blob]
153 close $::blob
154
155 # If the database is not in auto-vacuum mode, the whole of
156 # the overflow-chain must be scanned. In auto-vacuum mode,
157 # sqlite uses the ptrmap pages to avoid reading the other pages.
158 #
159 nRead db
160 } [expr $AutoVacuumMode ? 4 : 30]
161
danielk19778cbadb02007-05-03 16:31:26 +0000162 do_test incrblob-2.$AutoVacuumMode.4 {
danielk197720713f32007-05-03 11:43:33 +0000163 string range [db one {SELECT v FROM blobs}] end-19 end
164 } $::fragment
danielk19778cbadb02007-05-03 16:31:26 +0000165
166 do_test incrblob-2.$AutoVacuumMode.5 {
167 # Open and close the db to make sure the page cache is empty.
168 db close
169 sqlite3 db test.db
170
171 # Write the second-to-last 20 bytes of the blob via a blob handle.
172 #
173 set ::blob [db incrblob blobs v 1]
174 seek $::blob -40 end
175 puts -nonewline $::blob "1234567890abcdefghij"
176 flush $::blob
177
178 # If the database is not in auto-vacuum mode, the whole of
179 # the overflow-chain must be scanned. In auto-vacuum mode,
180 # sqlite uses the ptrmap pages to avoid reading the other pages.
181 #
182 nRead db
183 } [expr $AutoVacuumMode ? 4 : 30]
184
185 # Pages 1 (the write-counter) and 32 (the blob data) were written.
186 do_test incrblob-2.$AutoVacuumMode.6 {
187 close $::blob
188 nWrite db
189 } 2
190
191 do_test incrblob-2.$AutoVacuumMode.7 {
192 string range [db one {SELECT v FROM blobs}] end-39 end-20
193 } "1234567890abcdefghij"
194
195 do_test incrblob-2.$AutoVacuumMode.8 {
196 # Open and close the db to make sure the page cache is empty.
197 db close
198 sqlite3 db test.db
199
200 execsql { SELECT i FROM blobs }
201 } {45}
202
203 do_test incrblob-2.$AutoVacuumMode.9 {
204 nRead db
205 } [expr $AutoVacuumMode ? 4 : 30]
danielk197720713f32007-05-03 11:43:33 +0000206}
drh3aefaba2007-08-12 20:07:58 +0000207sqlite3_soft_heap_limit $soft_limit
danielk197720713f32007-05-03 11:43:33 +0000208
danielk19778cbadb02007-05-03 16:31:26 +0000209#------------------------------------------------------------------------
210# incrblob-3.*:
211#
212# Test the outcome of trying to write to a read-only blob handle.
213#
danielk19778cbadb02007-05-03 16:31:26 +0000214do_test incrblob-3.1 {
215 set ::blob [db incrblob -readonly blobs v 1]
216 seek $::blob -40 end
217 read $::blob 20
218} "1234567890abcdefghij"
219do_test incrblob-3.2 {
220 seek $::blob 0
221 set rc [catch {
222 puts -nonewline $::blob "helloworld"
223 } msg]
danielk1977f1819242007-05-03 18:14:10 +0000224 close $::blob
danielk19778cbadb02007-05-03 16:31:26 +0000225 list $rc $msg
226} "1 {channel \"$::blob\" wasn't opened for writing}"
227
danielk1977dcbb5d32007-05-04 18:36:44 +0000228do_test incrblob-3.3 {
229 set ::blob [db incrblob -readonly blobs v 1]
230 seek $::blob -40 end
231 read $::blob 20
232} "1234567890abcdefghij"
233do_test incrblob-3.4 {
234 set rc [catch {
235 sqlite3_blob_write $::blob 20 "qwertyuioplkjhgfds"
236 } msg]
237 list $rc $msg
238} {1 SQLITE_READONLY}
239catch {close $::blob}
240
danielk19778cbadb02007-05-03 16:31:26 +0000241#------------------------------------------------------------------------
242# incrblob-4.*:
243#
244# Try a couple of error conditions:
245#
246# 4.1 - Attempt to open a row that does not exist.
247# 4.2 - Attempt to open a column that does not exist.
248# 4.3 - Attempt to open a table that does not exist.
249# 4.4 - Attempt to open a database that does not exist.
250#
danielk1977f1819242007-05-03 18:14:10 +0000251# 4.5 - Attempt to open an integer
252# 4.6 - Attempt to open a real value
253# 4.7 - Attempt to open an SQL null
254#
255# 4.8 - Attempt to open an indexed column for writing
256# 4.9 - Attempt to open an indexed column for reading (this works)
257#
danielk19778cbadb02007-05-03 16:31:26 +0000258do_test incrblob-4.1 {
259 set rc [catch {
260 set ::blob [db incrblob blobs v 2]
261 } msg ]
262 list $rc $msg
263} {1 {no such rowid: 2}}
danielk19778cbadb02007-05-03 16:31:26 +0000264do_test incrblob-4.2 {
265 set rc [catch {
266 set ::blob [db incrblob blobs blue 1]
267 } msg ]
268 list $rc $msg
269} {1 {no such column: "blue"}}
danielk19778cbadb02007-05-03 16:31:26 +0000270do_test incrblob-4.3 {
271 set rc [catch {
272 set ::blob [db incrblob nosuchtable blue 1]
danielk1977f1819242007-05-03 18:14:10 +0000273 } msg ]
danielk19778cbadb02007-05-03 16:31:26 +0000274 list $rc $msg
275} {1 {no such table: main.nosuchtable}}
danielk19778cbadb02007-05-03 16:31:26 +0000276do_test incrblob-4.4 {
277 set rc [catch {
278 set ::blob [db incrblob nosuchdb blobs v 1]
279 } msg ]
280 list $rc $msg
281} {1 {no such table: nosuchdb.blobs}}
282
danielk1977f1819242007-05-03 18:14:10 +0000283do_test incrblob-4.5 {
284 set rc [catch {
285 set ::blob [db incrblob blobs i 1]
286 } msg ]
287 list $rc $msg
288} {1 {cannot open value of type integer}}
289do_test incrblob-4.6 {
290 execsql {
291 INSERT INTO blobs(k, v, i) VALUES(123, 567.765, NULL);
292 }
293 set rc [catch {
294 set ::blob [db incrblob blobs v 2]
295 } msg ]
296 list $rc $msg
297} {1 {cannot open value of type real}}
298do_test incrblob-4.7 {
299 set rc [catch {
300 set ::blob [db incrblob blobs i 2]
301 } msg ]
302 list $rc $msg
303} {1 {cannot open value of type null}}
danielk19778cbadb02007-05-03 16:31:26 +0000304
danielk1977f1819242007-05-03 18:14:10 +0000305do_test incrblob-4.8 {
306 execsql {
307 INSERT INTO blobs(k, v, i) VALUES(X'010203040506070809', 'hello', 'world');
308 }
309 set rc [catch {
310 set ::blob [db incrblob blobs k 3]
311 } msg ]
312 list $rc $msg
313} {1 {cannot open indexed column for writing}}
314
315do_test incrblob-4.9.1 {
316 set rc [catch {
317 set ::blob [db incrblob -readonly blobs k 3]
318 } msg]
319} {0}
320do_test incrblob-4.9.2 {
321 binary scan [read $::blob] c* c
322 close $::blob
323 set c
324} {1 2 3 4 5 6 7 8 9}
325
326#------------------------------------------------------------------------
327# incrblob-5.*:
328#
329# Test that opening a blob in an attached database works.
330#
331do_test incrblob-5.1 {
332 file delete -force test2.db test2.db-journal
333 set ::size [expr [file size [info script]]]
334 execsql {
335 ATTACH 'test2.db' AS aux;
336 CREATE TABLE aux.files(name, text);
337 INSERT INTO aux.files VALUES('this one', zeroblob($::size));
338 }
339 set fd [db incrblob aux files text 1]
340 set fd2 [open [info script]]
341 puts -nonewline $fd [read $fd2]
342 close $fd
343 close $fd2
344 set ::text [db one {select text from aux.files}]
345 string length $::text
346} [file size [info script]]
347do_test incrblob-5.2 {
348 set fd2 [open [info script]]
349 set ::data [read $fd2]
350 close $fd2
351 set ::data
352} $::text
353
354# free memory
355unset ::data
356unset ::text
357
358#------------------------------------------------------------------------
359# incrblob-6.*:
360#
361# Test that opening a blob for write-access is impossible if
362# another connection has the database RESERVED lock.
363#
364# Then test that blob writes that take place inside of a
365# transaction are not visible to external connections until
366# after the transaction is commited and the blob channel
367# closed.
368#
drh3aefaba2007-08-12 20:07:58 +0000369sqlite3_soft_heap_limit 0
danielk1977f1819242007-05-03 18:14:10 +0000370do_test incrblob-6.1 {
371 sqlite3 db2 test.db
372 execsql {
373 BEGIN;
374 INSERT INTO blobs(k, v, i) VALUES('a', 'different', 'connection');
375 } db2
376} {}
377do_test incrblob-6.2 {
378 execsql {
379 SELECT rowid FROM blobs
380 }
381} {1 2 3}
382do_test incrblob-6.3 {
383 set rc [catch {
384 db incrblob blobs v 1
385 } msg]
386 list $rc $msg
387} {1 {database is locked}}
388do_test incrblob-6.4 {
389 set rc [catch {
390 db incrblob blobs v 3
391 } msg]
392 list $rc $msg
393} {1 {database is locked}}
394do_test incrblob-6.5 {
395 set ::blob [db incrblob -readonly blobs v 3]
396 read $::blob
397} {hello}
398do_test incrblob-6.6 {
399 close $::blob
400} {}
401
402do_test incrblob-6.7 {
403 set ::blob [db2 incrblob blobs i 4]
404 gets $::blob
405} {connection}
406do_test incrblob-6.8 {
407 tell $::blob
408} {10}
danielk1977f1819242007-05-03 18:14:10 +0000409do_test incrblob-6.9 {
410 seek $::blob 0
411 puts -nonewline $::blob "invocation"
412 flush $::blob
413} {}
414
415# At this point rollback or commit should be illegal (because
416# there is an open blob channel).
417do_test incrblob-6.10 {
418 catchsql {
419 ROLLBACK;
420 } db2
421} {1 {cannot rollback transaction - SQL statements in progress}}
422do_test incrblob-6.11 {
423 catchsql {
424 COMMIT;
425 } db2
426} {1 {cannot commit transaction - SQL statements in progress}}
427
428do_test incrblob-6.12 {
429 execsql {
430 SELECT * FROM blobs WHERE rowid = 4;
431 }
432} {}
433do_test incrblob-6.13 {
434 close $::blob
435 execsql {
436 COMMIT;
437 } db2
438} {}
439do_test incrblob-6.14 {
440 execsql {
441 SELECT * FROM blobs WHERE rowid = 4;
442 }
443} {a different invocation}
444db2 close
drh3aefaba2007-08-12 20:07:58 +0000445sqlite3_soft_heap_limit $soft_limit
danielk1977f1819242007-05-03 18:14:10 +0000446
danielk1977dcbb5d32007-05-04 18:36:44 +0000447#-----------------------------------------------------------------------
448# The following tests verify the behaviour of the incremental IO
449# APIs in the following cases:
450#
451# 7.1 A row that containing an open blob is modified.
452#
453# 7.2 A CREATE TABLE requires that an overflow page that is part
454# of an open blob is moved.
455#
456# 7.3 An INCREMENTAL VACUUM moves an overflow page that is part
457# of an open blob.
458#
459# In the first case above, correct behaviour is for all subsequent
460# read/write operations on the blob-handle to return SQLITE_ABORT.
461# More accurately, blob-handles are invalidated whenever the table
462# they belong to is written to.
463#
464# The second two cases have no external effect. They are testing
465# that the internal cache of overflow page numbers is correctly
466# invalidated.
467#
468do_test incrblob-7.1.0 {
469 execsql {
470 BEGIN;
471 DROP TABLE blobs;
472 CREATE TABLE t1 (a, b, c, d BLOB);
473 INSERT INTO t1(a, b, c, d) VALUES(1, 2, 3, 4);
474 COMMIT;
475 }
476} {}
477
478foreach {tn arg} {1 "" 2 -readonly} {
479
480 execsql {
481 UPDATE t1 SET d = zeroblob(10000);
482 }
483
484 do_test incrblob-7.1.$tn.1 {
485 set ::b [eval db incrblob $arg t1 d 1]
486 binary scan [sqlite3_blob_read $::b 5000 5] c* c
487 set c
488 } {0 0 0 0 0}
489 do_test incrblob-7.1.$tn.2 {
490 execsql {
491 UPDATE t1 SET d = 15;
492 }
493 } {}
494 do_test incrblob-7.1.$tn.3 {
495 set rc [catch { sqlite3_blob_read $::b 5000 5 } msg]
496 list $rc $msg
497 } {1 SQLITE_ABORT}
498 do_test incrblob-7.1.$tn.4 {
499 execsql {
500 SELECT d FROM t1;
501 }
502 } {15}
503 do_test incrblob-7.1.$tn.5 {
504 set rc [catch { close $::b } msg]
505 list $rc $msg
506 } {0 {}}
507 do_test incrblob-7.1.$tn.6 {
508 execsql {
509 SELECT d FROM t1;
510 }
511 } {15}
512
513}
514
515set fd [open [info script]]
516set ::data [read $fd]
517close $fd
518
519db close
520file delete -force test.db test.db-journal
521sqlite3 db test.db
522
523do_test incrblob-7.2.1 {
524 execsql {
525 PRAGMA auto_vacuum = "incremental";
526 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); -- root@page3
527 INSERT INTO t1 VALUES(123, $::data);
528 }
529 set ::b [db incrblob -readonly t1 b 123]
530 read $::b
531} $::data
532do_test incrblob-7.2.2 {
533 execsql {
534 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); -- root@page4
535 }
536 seek $::b 0
537 read $::b
538} $::data
539do_test incrblob-7.2.3 {
540 close $::b
541 execsql {
542 SELECT rootpage FROM sqlite_master;
543 }
544} {3 4}
545
546set ::otherdata "[string range $::data 0 1000][string range $::data 1001 end]"
547do_test incrblob-7.3.1 {
548 execsql {
549 INSERT INTO t2 VALUES(456, $::otherdata);
550 }
551 set ::b [db incrblob -readonly t2 b 456]
552 read $::b
553} $::otherdata
554do_test incrblob-7.3.2 {
555 expr [file size test.db]/1024
556} 30
557do_test incrblob-7.3.3 {
558 execsql {
559 DELETE FROM t1 WHERE a = 123;
danielk1977a9808b32007-05-07 09:32:45 +0000560 PRAGMA INCREMENTAL_VACUUM(0);
danielk1977dcbb5d32007-05-04 18:36:44 +0000561 }
562 seek $::b 0
563 read $::b
564} $::otherdata
565
drh0e8003d2007-06-27 00:36:13 +0000566# Attempt to write on a read-only blob. Make sure the error code
567# gets set. Ticket #2464.
568#
569do_test incrblob-7.4 {
570 set rc [catch {sqlite3_blob_write $::b 10 HELLO} msg]
571 lappend rc $msg
572} {1 SQLITE_READONLY}
573do_test incrblob-7.5 {
574 sqlite3_errcode db
575} {SQLITE_READONLY}
576do_test incrblob-7.6 {
577 sqlite3_errmsg db
578} {attempt to write a readonly database}
danielk1977dcbb5d32007-05-04 18:36:44 +0000579
drh0e8003d2007-06-27 00:36:13 +0000580finish_test