xref: /sqlite-3.40.0/test/incrblob.test (revision dfdcf2c5)
1# 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#
12# $Id: incrblob.test,v 1.16 2007/09/03 16:45:36 drh Exp $
13#
14
15set testdir [file dirname $argv0]
16source $testdir/tester.tcl
17
18ifcapable {!autovacuum || !pragma || !incrblob} {
19  finish_test
20  return
21}
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_* $::blob
34} {1}
35do_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
58#--------------------------------------------------------------------
59# Test cases incrblob-1.3.X check that it is possible to read and write
60# regions of a blob that lie on overflow pages.
61#
62do_test incrblob-1.3.1 {
63  set ::str "[string repeat . 10000]"
64  execsql {
65    INSERT INTO blobs(rowid, k, v) VALUES(3, 'three', $::str);
66  }
67} {}
68
69do_test incrblob-1.3.2 {
70  set ::blob [db incrblob blobs v 3]
71  seek $::blob 8500
72  read $::blob 10
73} {..........}
74do_test incrblob-1.3.3 {
75  seek $::blob 8500
76  puts -nonewline $::blob 1234567890
77} {}
78do_test incrblob-1.3.4 {
79  seek $::blob 8496
80  read $::blob 10
81} {....123456}
82do_test incrblob-1.3.10 {
83  close $::blob
84} {}
85
86
87#------------------------------------------------------------------------
88# incrblob-2.*:
89#
90# Test that the following operations use ptrmap pages to reduce
91# unnecessary reads:
92#
93#     * Reading near the end of a blob,
94#     * Writing near the end of a blob, and
95#     * 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 $db
100  array set stats [btree_pager_stats $bt]
101  db_leave $db
102  return $stats(read)
103}
104proc nWrite {db} {
105  set bt [btree_from_db $db]
106  db_enter $db
107  array set stats [btree_pager_stats $bt]
108  db_leave $db
109  return $stats(write)
110}
111
112sqlite3_soft_heap_limit 0
113
114foreach AutoVacuumMode [list 0 1] {
115
116  if {$AutoVacuumMode>0} {
117    ifcapable !autovacuum {
118      break
119    }
120  }
121
122  db close
123  file delete -force test.db test.db-journal
124
125  sqlite3 db test.db
126  execsql "PRAGMA auto_vacuum = $AutoVacuumMode"
127
128  do_test incrblob-2.$AutoVacuumMode.1 {
129    set ::str [string repeat abcdefghij 2900]
130    execsql {
131      BEGIN;
132      CREATE TABLE blobs(k PRIMARY KEY, v BLOB, i INTEGER);
133      DELETE FROM blobs;
134      INSERT INTO blobs VALUES('one', $::str || randstr(500,500), 45);
135      COMMIT;
136    }
137    expr [file size test.db]/1024
138  } [expr 31 + $AutoVacuumMode]
139
140  ifcapable autovacuum {
141    do_test incrblob-2.$AutoVacuumMode.2 {
142      execsql {
143        PRAGMA auto_vacuum;
144      }
145    } $AutoVacuumMode
146  }
147
148  do_test incrblob-2.$AutoVacuumMode.3 {
149    # Open and close the db to make sure the page cache is empty.
150    db close
151    sqlite3 db test.db
152
153    # Read the last 20 bytes of the blob via a blob handle.
154    set ::blob [db incrblob blobs v 1]
155    seek $::blob -20 end
156    set ::fragment [read $::blob]
157    close $::blob
158
159    # If the database is not in auto-vacuum mode, the whole of
160    # the overflow-chain must be scanned. In auto-vacuum mode,
161    # sqlite uses the ptrmap pages to avoid reading the other pages.
162    #
163    nRead db
164  } [expr $AutoVacuumMode ? 4 : 30]
165
166  do_test incrblob-2.$AutoVacuumMode.4 {
167    string range [db one {SELECT v FROM blobs}] end-19 end
168  } $::fragment
169
170  do_test incrblob-2.$AutoVacuumMode.5 {
171    # Open and close the db to make sure the page cache is empty.
172    db close
173    sqlite3 db test.db
174
175    # Write the second-to-last 20 bytes of the blob via a blob handle.
176    #
177    set ::blob [db incrblob blobs v 1]
178    seek $::blob -40 end
179    puts -nonewline $::blob "1234567890abcdefghij"
180    flush $::blob
181
182    # If the database is not in auto-vacuum mode, the whole of
183    # the overflow-chain must be scanned. In auto-vacuum mode,
184    # sqlite uses the ptrmap pages to avoid reading the other pages.
185    #
186    nRead db
187  } [expr $AutoVacuumMode ? 4 : 30]
188
189  # Pages 1 (the write-counter) and 32 (the blob data) were written.
190  do_test incrblob-2.$AutoVacuumMode.6 {
191    close $::blob
192    nWrite db
193  } 2
194
195  do_test incrblob-2.$AutoVacuumMode.7 {
196    string range [db one {SELECT v FROM blobs}] end-39 end-20
197  } "1234567890abcdefghij"
198
199  do_test incrblob-2.$AutoVacuumMode.8 {
200    # Open and close the db to make sure the page cache is empty.
201    db close
202    sqlite3 db test.db
203
204    execsql { SELECT i FROM blobs }
205  } {45}
206
207  do_test incrblob-2.$AutoVacuumMode.9 {
208    nRead db
209  } [expr $AutoVacuumMode ? 4 : 30]
210}
211sqlite3_soft_heap_limit $soft_limit
212
213#------------------------------------------------------------------------
214# incrblob-3.*:
215#
216# Test the outcome of trying to write to a read-only blob handle.
217#
218do_test incrblob-3.1 {
219  set ::blob [db incrblob -readonly blobs v 1]
220  seek $::blob -40 end
221  read $::blob 20
222} "1234567890abcdefghij"
223do_test incrblob-3.2 {
224  seek $::blob 0
225  set rc [catch {
226    puts -nonewline $::blob "helloworld"
227  } msg]
228  close $::blob
229  list $rc $msg
230} "1 {channel \"$::blob\" wasn't opened for writing}"
231
232do_test incrblob-3.3 {
233  set ::blob [db incrblob -readonly blobs v 1]
234  seek $::blob -40 end
235  read $::blob 20
236} "1234567890abcdefghij"
237do_test incrblob-3.4 {
238  set rc [catch {
239    sqlite3_blob_write $::blob 20 "qwertyuioplkjhgfds"
240  } msg]
241  list $rc $msg
242} {1 SQLITE_READONLY}
243catch {close $::blob}
244
245#------------------------------------------------------------------------
246# incrblob-4.*:
247#
248# Try a couple of error conditions:
249#
250#     4.1 - Attempt to open a row that does not exist.
251#     4.2 - Attempt to open a column that does not exist.
252#     4.3 - Attempt to open a table that does not exist.
253#     4.4 - Attempt to open a database that does not exist.
254#
255#     4.5 - Attempt to open an integer
256#     4.6 - Attempt to open a real value
257#     4.7 - Attempt to open an SQL null
258#
259#     4.8 - Attempt to open an indexed column for writing
260#     4.9 - Attempt to open an indexed column for reading (this works)
261#
262do_test incrblob-4.1 {
263  set rc [catch {
264    set ::blob [db incrblob blobs v 2]
265  } msg ]
266  list $rc $msg
267} {1 {no such rowid: 2}}
268do_test incrblob-4.2 {
269  set rc [catch {
270    set ::blob [db incrblob blobs blue 1]
271  } msg ]
272  list $rc $msg
273} {1 {no such column: "blue"}}
274do_test incrblob-4.3 {
275  set rc [catch {
276    set ::blob [db incrblob nosuchtable blue 1]
277  } msg ]
278  list $rc $msg
279} {1 {no such table: main.nosuchtable}}
280do_test incrblob-4.4 {
281  set rc [catch {
282    set ::blob [db incrblob nosuchdb blobs v 1]
283  } msg ]
284  list $rc $msg
285} {1 {no such table: nosuchdb.blobs}}
286
287do_test incrblob-4.5 {
288  set rc [catch {
289    set ::blob [db incrblob blobs i 1]
290  } msg ]
291  list $rc $msg
292} {1 {cannot open value of type integer}}
293do_test incrblob-4.6 {
294  execsql {
295    INSERT INTO blobs(k, v, i) VALUES(123, 567.765, NULL);
296  }
297  set rc [catch {
298    set ::blob [db incrblob blobs v 2]
299  } msg ]
300  list $rc $msg
301} {1 {cannot open value of type real}}
302do_test incrblob-4.7 {
303  set rc [catch {
304    set ::blob [db incrblob blobs i 2]
305  } msg ]
306  list $rc $msg
307} {1 {cannot open value of type null}}
308
309do_test incrblob-4.8 {
310  execsql {
311    INSERT INTO blobs(k, v, i) VALUES(X'010203040506070809', 'hello', 'world');
312  }
313  set rc [catch {
314    set ::blob [db incrblob blobs k 3]
315  } msg ]
316  list $rc $msg
317} {1 {cannot open indexed column for writing}}
318
319do_test incrblob-4.9.1 {
320  set rc [catch {
321    set ::blob [db incrblob -readonly blobs k 3]
322  } msg]
323} {0}
324do_test incrblob-4.9.2 {
325  binary scan [read $::blob] c* c
326  close $::blob
327  set c
328} {1 2 3 4 5 6 7 8 9}
329
330do_test incrblob-4.10 {
331  set ::blob [db incrblob -readonly blobs k 3]
332  set rc [catch { sqlite3_blob_read $::blob 10 100 } msg]
333  list $rc $msg
334} {1 SQLITE_ERROR}
335do_test incrblob-4.11 {
336  close $::blob
337} {}
338
339#------------------------------------------------------------------------
340# incrblob-5.*:
341#
342#     Test that opening a blob in an attached database works.
343#
344do_test incrblob-5.1 {
345  file delete -force test2.db test2.db-journal
346  set ::size [expr [file size [info script]]]
347  execsql {
348    ATTACH 'test2.db' AS aux;
349    CREATE TABLE aux.files(name, text);
350    INSERT INTO aux.files VALUES('this one', zeroblob($::size));
351  }
352  set fd  [db incrblob aux files text 1]
353  fconfigure $fd -translation binary
354  set fd2 [open [info script]]
355  fconfigure $fd2 -translation binary
356  puts -nonewline $fd [read $fd2]
357  close $fd
358  close $fd2
359  set ::text [db one {select text from aux.files}]
360  string length $::text
361} [file size [info script]]
362do_test incrblob-5.2 {
363  set fd2 [open [info script]]
364  fconfigure $fd2 -translation binary
365  set ::data [read $fd2]
366  close $fd2
367  set ::data
368} $::text
369
370# free memory
371unset ::data
372unset ::text
373
374#------------------------------------------------------------------------
375# incrblob-6.*:
376#
377#     Test that opening a blob for write-access is impossible if
378#     another connection has the database RESERVED lock.
379#
380#     Then test that blob writes that take place inside of a
381#     transaction are not visible to external connections until
382#     after the transaction is commited and the blob channel
383#     closed.
384#
385sqlite3_soft_heap_limit 0
386do_test incrblob-6.1 {
387  sqlite3 db2 test.db
388  execsql {
389    BEGIN;
390    INSERT INTO blobs(k, v, i) VALUES('a', 'different', 'connection');
391  } db2
392} {}
393do_test incrblob-6.2 {
394  execsql {
395    SELECT rowid FROM blobs
396  }
397} {1 2 3}
398do_test incrblob-6.3 {
399  set rc [catch {
400    db incrblob blobs v 1
401  } msg]
402  list $rc $msg
403} {1 {database is locked}}
404do_test incrblob-6.4 {
405  set rc [catch {
406    db incrblob blobs v 3
407  } msg]
408  list $rc $msg
409} {1 {database is locked}}
410do_test incrblob-6.5 {
411  set ::blob [db incrblob -readonly blobs v 3]
412  read $::blob
413} {hello}
414do_test incrblob-6.6 {
415  close $::blob
416} {}
417
418do_test incrblob-6.7 {
419  set ::blob [db2 incrblob blobs i 4]
420  gets $::blob
421} {connection}
422do_test incrblob-6.8 {
423  tell $::blob
424} {10}
425do_test incrblob-6.9 {
426  seek $::blob 0
427  puts -nonewline $::blob "invocation"
428  flush $::blob
429} {}
430
431# At this point rollback or commit should be illegal (because
432# there is an open blob channel).
433do_test incrblob-6.10 {
434  catchsql {
435    ROLLBACK;
436  } db2
437} {1 {cannot rollback transaction - SQL statements in progress}}
438do_test incrblob-6.11 {
439  catchsql {
440    COMMIT;
441  } db2
442} {1 {cannot commit transaction - SQL statements in progress}}
443
444do_test incrblob-6.12 {
445  execsql {
446    SELECT * FROM blobs WHERE rowid = 4;
447  }
448} {}
449do_test incrblob-6.13 {
450  close $::blob
451  execsql {
452    COMMIT;
453  } db2
454} {}
455do_test incrblob-6.14 {
456  execsql {
457    SELECT * FROM blobs WHERE rowid = 4;
458  }
459} {a different invocation}
460db2 close
461sqlite3_soft_heap_limit $soft_limit
462
463#-----------------------------------------------------------------------
464# The following tests verify the behaviour of the incremental IO
465# APIs in the following cases:
466#
467#     7.1 A row that containing an open blob is modified.
468#
469#     7.2 A CREATE TABLE requires that an overflow page that is part
470#         of an open blob is moved.
471#
472#     7.3 An INCREMENTAL VACUUM moves an overflow page that is part
473#         of an open blob.
474#
475# In the first case above, correct behaviour is for all subsequent
476# read/write operations on the blob-handle to return SQLITE_ABORT.
477# More accurately, blob-handles are invalidated whenever the table
478# they belong to is written to.
479#
480# The second two cases have no external effect. They are testing
481# that the internal cache of overflow page numbers is correctly
482# invalidated.
483#
484do_test incrblob-7.1.0 {
485  execsql {
486    BEGIN;
487    DROP TABLE blobs;
488    CREATE TABLE t1 (a, b, c, d BLOB);
489    INSERT INTO t1(a, b, c, d) VALUES(1, 2, 3, 4);
490    COMMIT;
491  }
492} {}
493
494foreach {tn arg} {1 "" 2 -readonly} {
495
496  execsql {
497    UPDATE t1 SET d = zeroblob(10000);
498  }
499
500  do_test incrblob-7.1.$tn.1 {
501    set ::b [eval db incrblob $arg t1 d 1]
502    binary scan [sqlite3_blob_read $::b 5000 5] c* c
503    set c
504  } {0 0 0 0 0}
505  do_test incrblob-7.1.$tn.2 {
506    execsql {
507      UPDATE t1 SET d = 15;
508    }
509  } {}
510  do_test incrblob-7.1.$tn.3 {
511    set rc [catch { sqlite3_blob_read $::b 5000 5 } msg]
512    list $rc $msg
513  } {1 SQLITE_ABORT}
514  do_test incrblob-7.1.$tn.4 {
515    execsql {
516      SELECT d FROM t1;
517    }
518  } {15}
519  do_test incrblob-7.1.$tn.5 {
520    set rc [catch { close $::b } msg]
521    list $rc $msg
522  } {0 {}}
523  do_test incrblob-7.1.$tn.6 {
524    execsql {
525      SELECT d FROM t1;
526    }
527  } {15}
528
529}
530
531set fd [open [info script]]
532fconfigure $fd -translation binary
533set ::data [read $fd 14000]
534close $fd
535
536db close
537file delete -force test.db test.db-journal
538sqlite3 db test.db
539
540do_test incrblob-7.2.1 {
541  execsql {
542    PRAGMA auto_vacuum = "incremental";
543    CREATE TABLE t1(a INTEGER PRIMARY KEY, b);        -- root@page3
544    INSERT INTO t1 VALUES(123, $::data);
545  }
546  set ::b [db incrblob -readonly t1 b 123]
547  read $::b
548} $::data
549do_test incrblob-7.2.2 {
550  execsql {
551    CREATE TABLE t2(a INTEGER PRIMARY KEY, b);        -- root@page4
552  }
553  seek $::b 0
554  read $::b
555} $::data
556do_test incrblob-7.2.3 {
557  close $::b
558  execsql {
559    SELECT rootpage FROM sqlite_master;
560  }
561} {3 4}
562
563set ::otherdata "[string range $::data 0 1000][string range $::data 1001 end]"
564do_test incrblob-7.3.1 {
565  execsql {
566    INSERT INTO t2 VALUES(456, $::otherdata);
567  }
568  set ::b [db incrblob -readonly t2 b 456]
569  read $::b
570} $::otherdata
571do_test incrblob-7.3.2 {
572  expr [file size test.db]/1024
573} 30
574do_test incrblob-7.3.3 {
575  execsql {
576    DELETE FROM t1 WHERE a = 123;
577    PRAGMA INCREMENTAL_VACUUM(0);
578  }
579  seek $::b 0
580  read $::b
581} $::otherdata
582
583# Attempt to write on a read-only blob.  Make sure the error code
584# gets set.  Ticket #2464.
585#
586do_test incrblob-7.4 {
587  set rc [catch {sqlite3_blob_write $::b 10 HELLO} msg]
588  lappend rc $msg
589} {1 SQLITE_READONLY}
590do_test incrblob-7.5 {
591  sqlite3_errcode db
592} {SQLITE_READONLY}
593do_test incrblob-7.6 {
594  sqlite3_errmsg db
595} {attempt to write a readonly database}
596
597finish_test
598