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