blob: 5cd7040e454b5f479c825593e42930215d348f3a [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#
drh0e8003d2007-06-27 00:36:13 +000012# $Id: incrblob.test,v 1.12 2007/06/27 00:36:14 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
108foreach AutoVacuumMode [list 0 1] {
109
drh847d3ab2007-05-04 14:36:22 +0000110 if {$AutoVacuumMode>0} {
111 ifcapable !autovacuum {
112 break
113 }
114 }
115
danielk197720713f32007-05-03 11:43:33 +0000116 db close
117 file delete -force test.db test.db-journal
118
119 sqlite3 db test.db
120 execsql "PRAGMA auto_vacuum = $AutoVacuumMode"
121
122 do_test incrblob-2.$AutoVacuumMode.1 {
123 set ::str [string repeat abcdefghij 2900]
124 execsql {
125 BEGIN;
danielk19778cbadb02007-05-03 16:31:26 +0000126 CREATE TABLE blobs(k PRIMARY KEY, v BLOB, i INTEGER);
danielk197720713f32007-05-03 11:43:33 +0000127 DELETE FROM blobs;
danielk19778cbadb02007-05-03 16:31:26 +0000128 INSERT INTO blobs VALUES('one', $::str || randstr(500,500), 45);
danielk197720713f32007-05-03 11:43:33 +0000129 COMMIT;
130 }
131 expr [file size test.db]/1024
132 } [expr 31 + $AutoVacuumMode]
133
drh847d3ab2007-05-04 14:36:22 +0000134 ifcapable autovacuum {
135 do_test incrblob-2.$AutoVacuumMode.2 {
136 execsql {
137 PRAGMA auto_vacuum;
138 }
139 } $AutoVacuumMode
140 }
danielk197720713f32007-05-03 11:43:33 +0000141
142 do_test incrblob-2.$AutoVacuumMode.3 {
143 # Open and close the db to make sure the page cache is empty.
144 db close
145 sqlite3 db test.db
146
147 # Read the last 20 bytes of the blob via a blob handle.
148 set ::blob [db incrblob blobs v 1]
149 seek $::blob -20 end
150 set ::fragment [read $::blob]
151 close $::blob
152
153 # If the database is not in auto-vacuum mode, the whole of
154 # the overflow-chain must be scanned. In auto-vacuum mode,
155 # sqlite uses the ptrmap pages to avoid reading the other pages.
156 #
157 nRead db
158 } [expr $AutoVacuumMode ? 4 : 30]
159
danielk19778cbadb02007-05-03 16:31:26 +0000160 do_test incrblob-2.$AutoVacuumMode.4 {
danielk197720713f32007-05-03 11:43:33 +0000161 string range [db one {SELECT v FROM blobs}] end-19 end
162 } $::fragment
danielk19778cbadb02007-05-03 16:31:26 +0000163
164 do_test incrblob-2.$AutoVacuumMode.5 {
165 # Open and close the db to make sure the page cache is empty.
166 db close
167 sqlite3 db test.db
168
169 # Write the second-to-last 20 bytes of the blob via a blob handle.
170 #
171 set ::blob [db incrblob blobs v 1]
172 seek $::blob -40 end
173 puts -nonewline $::blob "1234567890abcdefghij"
174 flush $::blob
175
176 # If the database is not in auto-vacuum mode, the whole of
177 # the overflow-chain must be scanned. In auto-vacuum mode,
178 # sqlite uses the ptrmap pages to avoid reading the other pages.
179 #
180 nRead db
181 } [expr $AutoVacuumMode ? 4 : 30]
182
183 # Pages 1 (the write-counter) and 32 (the blob data) were written.
184 do_test incrblob-2.$AutoVacuumMode.6 {
185 close $::blob
186 nWrite db
187 } 2
188
189 do_test incrblob-2.$AutoVacuumMode.7 {
190 string range [db one {SELECT v FROM blobs}] end-39 end-20
191 } "1234567890abcdefghij"
192
193 do_test incrblob-2.$AutoVacuumMode.8 {
194 # Open and close the db to make sure the page cache is empty.
195 db close
196 sqlite3 db test.db
197
198 execsql { SELECT i FROM blobs }
199 } {45}
200
201 do_test incrblob-2.$AutoVacuumMode.9 {
202 nRead db
203 } [expr $AutoVacuumMode ? 4 : 30]
danielk197720713f32007-05-03 11:43:33 +0000204}
205
danielk19778cbadb02007-05-03 16:31:26 +0000206#------------------------------------------------------------------------
207# incrblob-3.*:
208#
209# Test the outcome of trying to write to a read-only blob handle.
210#
danielk19778cbadb02007-05-03 16:31:26 +0000211do_test incrblob-3.1 {
212 set ::blob [db incrblob -readonly blobs v 1]
213 seek $::blob -40 end
214 read $::blob 20
215} "1234567890abcdefghij"
216do_test incrblob-3.2 {
217 seek $::blob 0
218 set rc [catch {
219 puts -nonewline $::blob "helloworld"
220 } msg]
danielk1977f1819242007-05-03 18:14:10 +0000221 close $::blob
danielk19778cbadb02007-05-03 16:31:26 +0000222 list $rc $msg
223} "1 {channel \"$::blob\" wasn't opened for writing}"
224
danielk1977dcbb5d32007-05-04 18:36:44 +0000225do_test incrblob-3.3 {
226 set ::blob [db incrblob -readonly blobs v 1]
227 seek $::blob -40 end
228 read $::blob 20
229} "1234567890abcdefghij"
230do_test incrblob-3.4 {
231 set rc [catch {
232 sqlite3_blob_write $::blob 20 "qwertyuioplkjhgfds"
233 } msg]
234 list $rc $msg
235} {1 SQLITE_READONLY}
236catch {close $::blob}
237
danielk19778cbadb02007-05-03 16:31:26 +0000238#------------------------------------------------------------------------
239# incrblob-4.*:
240#
241# Try a couple of error conditions:
242#
243# 4.1 - Attempt to open a row that does not exist.
244# 4.2 - Attempt to open a column that does not exist.
245# 4.3 - Attempt to open a table that does not exist.
246# 4.4 - Attempt to open a database that does not exist.
247#
danielk1977f1819242007-05-03 18:14:10 +0000248# 4.5 - Attempt to open an integer
249# 4.6 - Attempt to open a real value
250# 4.7 - Attempt to open an SQL null
251#
252# 4.8 - Attempt to open an indexed column for writing
253# 4.9 - Attempt to open an indexed column for reading (this works)
254#
danielk19778cbadb02007-05-03 16:31:26 +0000255do_test incrblob-4.1 {
256 set rc [catch {
257 set ::blob [db incrblob blobs v 2]
258 } msg ]
259 list $rc $msg
260} {1 {no such rowid: 2}}
danielk19778cbadb02007-05-03 16:31:26 +0000261do_test incrblob-4.2 {
262 set rc [catch {
263 set ::blob [db incrblob blobs blue 1]
264 } msg ]
265 list $rc $msg
266} {1 {no such column: "blue"}}
danielk19778cbadb02007-05-03 16:31:26 +0000267do_test incrblob-4.3 {
268 set rc [catch {
269 set ::blob [db incrblob nosuchtable blue 1]
danielk1977f1819242007-05-03 18:14:10 +0000270 } msg ]
danielk19778cbadb02007-05-03 16:31:26 +0000271 list $rc $msg
272} {1 {no such table: main.nosuchtable}}
danielk19778cbadb02007-05-03 16:31:26 +0000273do_test incrblob-4.4 {
274 set rc [catch {
275 set ::blob [db incrblob nosuchdb blobs v 1]
276 } msg ]
277 list $rc $msg
278} {1 {no such table: nosuchdb.blobs}}
279
danielk1977f1819242007-05-03 18:14:10 +0000280do_test incrblob-4.5 {
281 set rc [catch {
282 set ::blob [db incrblob blobs i 1]
283 } msg ]
284 list $rc $msg
285} {1 {cannot open value of type integer}}
286do_test incrblob-4.6 {
287 execsql {
288 INSERT INTO blobs(k, v, i) VALUES(123, 567.765, NULL);
289 }
290 set rc [catch {
291 set ::blob [db incrblob blobs v 2]
292 } msg ]
293 list $rc $msg
294} {1 {cannot open value of type real}}
295do_test incrblob-4.7 {
296 set rc [catch {
297 set ::blob [db incrblob blobs i 2]
298 } msg ]
299 list $rc $msg
300} {1 {cannot open value of type null}}
danielk19778cbadb02007-05-03 16:31:26 +0000301
danielk1977f1819242007-05-03 18:14:10 +0000302do_test incrblob-4.8 {
303 execsql {
304 INSERT INTO blobs(k, v, i) VALUES(X'010203040506070809', 'hello', 'world');
305 }
306 set rc [catch {
307 set ::blob [db incrblob blobs k 3]
308 } msg ]
309 list $rc $msg
310} {1 {cannot open indexed column for writing}}
311
312do_test incrblob-4.9.1 {
313 set rc [catch {
314 set ::blob [db incrblob -readonly blobs k 3]
315 } msg]
316} {0}
317do_test incrblob-4.9.2 {
318 binary scan [read $::blob] c* c
319 close $::blob
320 set c
321} {1 2 3 4 5 6 7 8 9}
322
323#------------------------------------------------------------------------
324# incrblob-5.*:
325#
326# Test that opening a blob in an attached database works.
327#
328do_test incrblob-5.1 {
329 file delete -force test2.db test2.db-journal
330 set ::size [expr [file size [info script]]]
331 execsql {
332 ATTACH 'test2.db' AS aux;
333 CREATE TABLE aux.files(name, text);
334 INSERT INTO aux.files VALUES('this one', zeroblob($::size));
335 }
336 set fd [db incrblob aux files text 1]
337 set fd2 [open [info script]]
338 puts -nonewline $fd [read $fd2]
339 close $fd
340 close $fd2
341 set ::text [db one {select text from aux.files}]
342 string length $::text
343} [file size [info script]]
344do_test incrblob-5.2 {
345 set fd2 [open [info script]]
346 set ::data [read $fd2]
347 close $fd2
348 set ::data
349} $::text
350
351# free memory
352unset ::data
353unset ::text
354
355#------------------------------------------------------------------------
356# incrblob-6.*:
357#
358# Test that opening a blob for write-access is impossible if
359# another connection has the database RESERVED lock.
360#
361# Then test that blob writes that take place inside of a
362# transaction are not visible to external connections until
363# after the transaction is commited and the blob channel
364# closed.
365#
366do_test incrblob-6.1 {
367 sqlite3 db2 test.db
368 execsql {
369 BEGIN;
370 INSERT INTO blobs(k, v, i) VALUES('a', 'different', 'connection');
371 } db2
372} {}
373do_test incrblob-6.2 {
374 execsql {
375 SELECT rowid FROM blobs
376 }
377} {1 2 3}
378do_test incrblob-6.3 {
379 set rc [catch {
380 db incrblob blobs v 1
381 } msg]
382 list $rc $msg
383} {1 {database is locked}}
384do_test incrblob-6.4 {
385 set rc [catch {
386 db incrblob blobs v 3
387 } msg]
388 list $rc $msg
389} {1 {database is locked}}
390do_test incrblob-6.5 {
391 set ::blob [db incrblob -readonly blobs v 3]
392 read $::blob
393} {hello}
394do_test incrblob-6.6 {
395 close $::blob
396} {}
397
398do_test incrblob-6.7 {
399 set ::blob [db2 incrblob blobs i 4]
400 gets $::blob
401} {connection}
402do_test incrblob-6.8 {
403 tell $::blob
404} {10}
danielk1977f1819242007-05-03 18:14:10 +0000405do_test incrblob-6.9 {
406 seek $::blob 0
407 puts -nonewline $::blob "invocation"
408 flush $::blob
409} {}
410
411# At this point rollback or commit should be illegal (because
412# there is an open blob channel).
413do_test incrblob-6.10 {
414 catchsql {
415 ROLLBACK;
416 } db2
417} {1 {cannot rollback transaction - SQL statements in progress}}
418do_test incrblob-6.11 {
419 catchsql {
420 COMMIT;
421 } db2
422} {1 {cannot commit transaction - SQL statements in progress}}
423
424do_test incrblob-6.12 {
425 execsql {
426 SELECT * FROM blobs WHERE rowid = 4;
427 }
428} {}
429do_test incrblob-6.13 {
430 close $::blob
431 execsql {
432 COMMIT;
433 } db2
434} {}
435do_test incrblob-6.14 {
436 execsql {
437 SELECT * FROM blobs WHERE rowid = 4;
438 }
439} {a different invocation}
440db2 close
441
danielk1977dcbb5d32007-05-04 18:36:44 +0000442#-----------------------------------------------------------------------
443# The following tests verify the behaviour of the incremental IO
444# APIs in the following cases:
445#
446# 7.1 A row that containing an open blob is modified.
447#
448# 7.2 A CREATE TABLE requires that an overflow page that is part
449# of an open blob is moved.
450#
451# 7.3 An INCREMENTAL VACUUM moves an overflow page that is part
452# of an open blob.
453#
454# In the first case above, correct behaviour is for all subsequent
455# read/write operations on the blob-handle to return SQLITE_ABORT.
456# More accurately, blob-handles are invalidated whenever the table
457# they belong to is written to.
458#
459# The second two cases have no external effect. They are testing
460# that the internal cache of overflow page numbers is correctly
461# invalidated.
462#
463do_test incrblob-7.1.0 {
464 execsql {
465 BEGIN;
466 DROP TABLE blobs;
467 CREATE TABLE t1 (a, b, c, d BLOB);
468 INSERT INTO t1(a, b, c, d) VALUES(1, 2, 3, 4);
469 COMMIT;
470 }
471} {}
472
473foreach {tn arg} {1 "" 2 -readonly} {
474
475 execsql {
476 UPDATE t1 SET d = zeroblob(10000);
477 }
478
479 do_test incrblob-7.1.$tn.1 {
480 set ::b [eval db incrblob $arg t1 d 1]
481 binary scan [sqlite3_blob_read $::b 5000 5] c* c
482 set c
483 } {0 0 0 0 0}
484 do_test incrblob-7.1.$tn.2 {
485 execsql {
486 UPDATE t1 SET d = 15;
487 }
488 } {}
489 do_test incrblob-7.1.$tn.3 {
490 set rc [catch { sqlite3_blob_read $::b 5000 5 } msg]
491 list $rc $msg
492 } {1 SQLITE_ABORT}
493 do_test incrblob-7.1.$tn.4 {
494 execsql {
495 SELECT d FROM t1;
496 }
497 } {15}
498 do_test incrblob-7.1.$tn.5 {
499 set rc [catch { close $::b } msg]
500 list $rc $msg
501 } {0 {}}
502 do_test incrblob-7.1.$tn.6 {
503 execsql {
504 SELECT d FROM t1;
505 }
506 } {15}
507
508}
509
510set fd [open [info script]]
511set ::data [read $fd]
512close $fd
513
514db close
515file delete -force test.db test.db-journal
516sqlite3 db test.db
517
518do_test incrblob-7.2.1 {
519 execsql {
520 PRAGMA auto_vacuum = "incremental";
521 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); -- root@page3
522 INSERT INTO t1 VALUES(123, $::data);
523 }
524 set ::b [db incrblob -readonly t1 b 123]
525 read $::b
526} $::data
527do_test incrblob-7.2.2 {
528 execsql {
529 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); -- root@page4
530 }
531 seek $::b 0
532 read $::b
533} $::data
534do_test incrblob-7.2.3 {
535 close $::b
536 execsql {
537 SELECT rootpage FROM sqlite_master;
538 }
539} {3 4}
540
541set ::otherdata "[string range $::data 0 1000][string range $::data 1001 end]"
542do_test incrblob-7.3.1 {
543 execsql {
544 INSERT INTO t2 VALUES(456, $::otherdata);
545 }
546 set ::b [db incrblob -readonly t2 b 456]
547 read $::b
548} $::otherdata
549do_test incrblob-7.3.2 {
550 expr [file size test.db]/1024
551} 30
552do_test incrblob-7.3.3 {
553 execsql {
554 DELETE FROM t1 WHERE a = 123;
danielk1977a9808b32007-05-07 09:32:45 +0000555 PRAGMA INCREMENTAL_VACUUM(0);
danielk1977dcbb5d32007-05-04 18:36:44 +0000556 }
557 seek $::b 0
558 read $::b
559} $::otherdata
560
drh0e8003d2007-06-27 00:36:13 +0000561# Attempt to write on a read-only blob. Make sure the error code
562# gets set. Ticket #2464.
563#
564do_test incrblob-7.4 {
565 set rc [catch {sqlite3_blob_write $::b 10 HELLO} msg]
566 lappend rc $msg
567} {1 SQLITE_READONLY}
568do_test incrblob-7.5 {
569 sqlite3_errcode db
570} {SQLITE_READONLY}
571do_test incrblob-7.6 {
572 sqlite3_errmsg db
573} {attempt to write a readonly database}
danielk1977dcbb5d32007-05-04 18:36:44 +0000574
drh0e8003d2007-06-27 00:36:13 +0000575finish_test