1: # 2007 August 21
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: # The focus of this file is testing some specific characteristics of the
13: # IO traffic generated by SQLite (making sure SQLite is not writing out
14: # more database pages than it has to, stuff like that).
15: #
16:
17: set testdir [file dirname $argv0]
18: source $testdir/tester.tcl
19:
20: db close
21: sqlite3_simulate_device
22: sqlite3 db test.db -vfs devsym
23:
24: # Test summary:
25: #
26: # io-1.* - Test that quick-balance does not journal pages unnecessarily.
27: #
28: # io-2.* - Test the "atomic-write optimization".
29: #
30: # io-3.* - Test the IO traffic enhancements triggered when the
31: # IOCAP_SEQUENTIAL device capability flag is set (no
32: # fsync() calls on the journal file).
33: #
34: # io-4.* - Test the IO traffic enhancements triggered when the
35: # IOCAP_SAFE_APPEND device capability flag is set (fewer
36: # fsync() calls on the journal file, no need to set nRec
37: # field in the single journal header).
38: #
39: # io-5.* - Test that the default page size is selected and used
40: # correctly.
41: #
42:
43: set ::nWrite 0
44: proc nWrite {db} {
45: set bt [btree_from_db $db]
46: db_enter $db
47: array set stats [btree_pager_stats $bt]
48: db_leave $db
49: set res [expr $stats(write) - $::nWrite]
50: set ::nWrite $stats(write)
51: set res
52: }
53:
54: set ::nSync 0
55: proc nSync {} {
56: set res [expr {$::sqlite_sync_count - $::nSync}]
57: set ::nSync $::sqlite_sync_count
58: set res
59: }
60:
61: do_test io-1.1 {
62: execsql {
63: PRAGMA auto_vacuum = OFF;
64: PRAGMA page_size = 1024;
65: CREATE TABLE abc(a,b);
66: }
67: nWrite db
68: } {2}
69:
70: # Insert into the table 4 records of aproximately 240 bytes each.
71: # This should completely fill the root-page of the table. Each
72: # INSERT causes 2 db pages to be written - the root-page of "abc"
73: # and page 1 (db change-counter page).
74: do_test io-1.2 {
75: set ret [list]
76: execsql { INSERT INTO abc VALUES(1,randstr(230,230)); }
77: lappend ret [nWrite db]
78: execsql { INSERT INTO abc VALUES(2,randstr(230,230)); }
79: lappend ret [nWrite db]
80: execsql { INSERT INTO abc VALUES(3,randstr(230,230)); }
81: lappend ret [nWrite db]
82: execsql { INSERT INTO abc VALUES(4,randstr(230,230)); }
83: lappend ret [nWrite db]
84: } {2 2 2 2}
85:
86: # Insert another 240 byte record. This causes two leaf pages
87: # to be added to the root page of abc. 4 pages in total
88: # are written to the db file - the two leaf pages, the root
89: # of abc and the change-counter page.
90: do_test io-1.3 {
91: execsql { INSERT INTO abc VALUES(5,randstr(230,230)); }
92: nWrite db
93: } {4}
94:
95: # Insert another 3 240 byte records. After this, the tree consists of
96: # the root-node, which is close to empty, and two leaf pages, both of
97: # which are full.
98: do_test io-1.4 {
99: set ret [list]
100: execsql { INSERT INTO abc VALUES(6,randstr(230,230)); }
101: lappend ret [nWrite db]
102: execsql { INSERT INTO abc VALUES(7,randstr(230,230)); }
103: lappend ret [nWrite db]
104: execsql { INSERT INTO abc VALUES(8,randstr(230,230)); }
105: lappend ret [nWrite db]
106: } {2 2 2}
107:
108: # This insert should use the quick-balance trick to add a third leaf
109: # to the b-tree used to store table abc. It should only be necessary to
110: # write to 3 pages to do this: the change-counter, the root-page and
111: # the new leaf page.
112: do_test io-1.5 {
113: execsql { INSERT INTO abc VALUES(9,randstr(230,230)); }
114: nWrite db
115: } {3}
116:
117: ifcapable atomicwrite {
118:
119: #----------------------------------------------------------------------
120: # Test cases io-2.* test the atomic-write optimization.
121: #
122: do_test io-2.1 {
123: execsql { DELETE FROM abc; VACUUM; }
124: } {}
125:
126: # Clear the write and sync counts.
127: nWrite db ; nSync
128:
129: # The following INSERT updates 2 pages and requires 4 calls to fsync():
130: #
131: # 1) The directory in which the journal file is created,
132: # 2) The journal file (to sync the page data),
133: # 3) The journal file (to sync the journal file header),
134: # 4) The database file.
135: #
136: do_test io-2.2 {
137: execsql { INSERT INTO abc VALUES(1, 2) }
138: list [nWrite db] [nSync]
139: } {2 4}
140:
141: # Set the device-characteristic mask to include the SQLITE_IOCAP_ATOMIC,
142: # then do another INSERT similar to the one in io-2.2. This should
143: # only write 1 page and require a single fsync().
144: #
145: # The single fsync() is the database file. Only one page is reported as
146: # written because page 1 - the change-counter page - is written using
147: # an out-of-band method that bypasses the write counter.
148: #
149: sqlite3_simulate_device -char atomic
150: do_test io-2.3 {
151: execsql { INSERT INTO abc VALUES(3, 4) }
152: list [nWrite db] [nSync]
153: } {1 1}
154:
155: # Test that the journal file is not created and the change-counter is
156: # updated when the atomic-write optimization is used.
157: #
158: do_test io-2.4.1 {
159: execsql {
160: BEGIN;
161: INSERT INTO abc VALUES(5, 6);
162: }
163: sqlite3 db2 test.db -vfs devsym
164: execsql { SELECT * FROM abc } db2
165: } {1 2 3 4}
166: do_test io-2.4.2 {
167: file exists test.db-journal
168: } {0}
169: do_test io-2.4.3 {
170: execsql { COMMIT }
171: execsql { SELECT * FROM abc } db2
172: } {1 2 3 4 5 6}
173: db2 close
174:
175: # Test that the journal file is created and sync()d if the transaction
176: # modifies more than one database page, even if the IOCAP_ATOMIC flag
177: # is set.
178: #
179: do_test io-2.5.1 {
180: execsql { CREATE TABLE def(d, e) }
181: nWrite db ; nSync
182: execsql {
183: BEGIN;
184: INSERT INTO abc VALUES(7, 8);
185: }
186: file exists test.db-journal
187: } {0}
188: do_test io-2.5.2 {
189: execsql { INSERT INTO def VALUES('a', 'b'); }
190: file exists test.db-journal
191: } {1}
192: do_test io-2.5.3 {
193: execsql { COMMIT }
194: list [nWrite db] [nSync]
195: } {3 4}
196:
197: # Test that the journal file is created and sync()d if the transaction
198: # modifies a single database page and also appends a page to the file.
199: # Internally, this case is handled differently to the one above. The
200: # journal file is not actually created until the 'COMMIT' statement
201: # is executed.
202: #
203: # Changed 2010-03-27: The size of the database is now stored in
204: # bytes 28..31 and so when a page is added to the database, page 1
205: # is immediately modified and the journal file immediately comes into
206: # existance. To fix this test, the BEGIN is changed into a a
207: # BEGIN IMMEDIATE and the INSERT is omitted.
208: #
209: do_test io-2.6.1 {
210: execsql {
211: BEGIN IMMEDIATE;
212: -- INSERT INTO abc VALUES(9, randstr(1000,1000));
213: }
214: file exists test.db-journal
215: } {0}
216: do_test io-2.6.2 {
217: # Create a file at "test.db-journal". This will prevent SQLite from
218: # opening the journal for exclusive access. As a result, the COMMIT
219: # should fail with SQLITE_CANTOPEN and the transaction rolled back.
220: #
221: file mkdir test.db-journal
222: catchsql {
223: INSERT INTO abc VALUES(9, randstr(1000,1000));
224: COMMIT
225: }
226: } {1 {unable to open database file}}
227: do_test io-2.6.3 {
228: forcedelete test.db-journal
229: catchsql { COMMIT }
230: } {0 {}}
231: do_test io-2.6.4 {
232: execsql { SELECT * FROM abc }
233: } {1 2 3 4 5 6 7 8}
234:
235: # Test that if the database modification is part of multi-file commit,
236: # the journal file is always created. In this case, the journal file
237: # is created during execution of the COMMIT statement, so we have to
238: # use the same technique to check that it is created as in the above
239: # block.
240: forcedelete test2.db test2.db-journal
241: ifcapable attach {
242: do_test io-2.7.1 {
243: execsql {
244: ATTACH 'test2.db' AS aux;
245: PRAGMA aux.page_size = 1024;
246: CREATE TABLE aux.abc2(a, b);
247: BEGIN;
248: INSERT INTO abc VALUES(9, 10);
249: }
250: file exists test.db-journal
251: } {0}
252: do_test io-2.7.2 {
253: execsql { INSERT INTO abc2 SELECT * FROM abc }
254: file exists test2.db-journal
255: } {0}
256: do_test io-2.7.3 {
257: execsql { SELECT * FROM abc UNION ALL SELECT * FROM abc2 }
258: } {1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10}
259: do_test io-2.7.4 {
260: file mkdir test2.db-journal
261: catchsql { COMMIT }
262: } {1 {unable to open database file}}
263: do_test io-2.7.5 {
264: forcedelete test2.db-journal
265: catchsql { COMMIT }
266: } {1 {cannot commit - no transaction is active}}
267: do_test io-2.7.6 {
268: execsql { SELECT * FROM abc UNION ALL SELECT * FROM abc2 }
269: } {1 2 3 4 5 6 7 8}
270: }
271:
272: # Try an explicit ROLLBACK before the journal file is created.
273: #
274: do_test io-2.8.1 {
275: execsql {
276: BEGIN;
277: DELETE FROM abc;
278: }
279: file exists test.db-journal
280: } {0}
281: do_test io-2.8.2 {
282: execsql { SELECT * FROM abc }
283: } {}
284: do_test io-2.8.3 {
285: execsql {
286: ROLLBACK;
287: SELECT * FROM abc;
288: }
289: } {1 2 3 4 5 6 7 8}
290:
291: # Test that the atomic write optimisation is not enabled if the sector
292: # size is larger than the page-size.
293: #
294: do_test io-2.9.1 {
295: db close
296: sqlite3 db test.db
297: sqlite3_simulate_device -char atomic -sectorsize 2048
298: execsql {
299: BEGIN;
300: INSERT INTO abc VALUES(9, 10);
301: }
302: file exists test.db-journal
303: } {1}
304: do_test io-2.9.2 {
305: execsql { ROLLBACK; }
306: db close
307: forcedelete test.db test.db-journal
308: sqlite3 db test.db -vfs devsym
309: execsql {
310: PRAGMA auto_vacuum = OFF;
311: PRAGMA page_size = 2048;
312: CREATE TABLE abc(a, b);
313: }
314: execsql {
315: BEGIN;
316: INSERT INTO abc VALUES(9, 10);
317: }
318: file exists test.db-journal
319: } {0}
320: do_test io-2.9.3 {
321: execsql { COMMIT }
322: } {}
323:
324: # Test a couple of the more specific IOCAP_ATOMIC flags
325: # (i.e IOCAP_ATOMIC2K etc.).
326: #
327: do_test io-2.10.1 {
328: sqlite3_simulate_device -char atomic1k
329: execsql {
330: BEGIN;
331: INSERT INTO abc VALUES(11, 12);
332: }
333: file exists test.db-journal
334: } {1}
335: do_test io-2.10.2 {
336: execsql { ROLLBACK }
337: sqlite3_simulate_device -char atomic2k
338: execsql {
339: BEGIN;
340: INSERT INTO abc VALUES(11, 12);
341: }
342: file exists test.db-journal
343: } {0}
344: do_test io-2.10.3 {
345: execsql { ROLLBACK }
346: } {}
347:
348: do_test io-2.11.0 {
349: execsql {
350: PRAGMA locking_mode = exclusive;
351: PRAGMA locking_mode;
352: }
353: } {exclusive exclusive}
354: do_test io-2.11.1 {
355: execsql {
356: INSERT INTO abc VALUES(11, 12);
357: }
358: file exists test.db-journal
359: } {0}
360:
361: do_test io-2.11.2 {
362: execsql {
363: PRAGMA locking_mode = normal;
364: INSERT INTO abc VALUES(13, 14);
365: }
366: file exists test.db-journal
367: } {0}
368:
369: } ;# /* ifcapable atomicwrite */
370:
371: #----------------------------------------------------------------------
372: # Test cases io-3.* test the IOCAP_SEQUENTIAL optimization.
373: #
374: sqlite3_simulate_device -char sequential -sectorsize 0
375: ifcapable pager_pragmas {
376: do_test io-3.1 {
377: db close
378: forcedelete test.db test.db-journal
379: sqlite3 db test.db -vfs devsym
380: db eval {
381: PRAGMA auto_vacuum=OFF;
382: }
383: # File size might be 1 due to the hack to work around ticket #3260.
384: # Search for #3260 in os_unix.c for additional information.
385: expr {[file size test.db]>1}
386: } {0}
387: do_test io-3.2 {
388: execsql { CREATE TABLE abc(a, b) }
389: nSync
390: execsql {
391: PRAGMA temp_store = memory;
392: PRAGMA cache_size = 10;
393: BEGIN;
394: INSERT INTO abc VALUES('hello', 'world');
395: INSERT INTO abc SELECT * FROM abc;
396: INSERT INTO abc SELECT * FROM abc;
397: INSERT INTO abc SELECT * FROM abc;
398: INSERT INTO abc SELECT * FROM abc;
399: INSERT INTO abc SELECT * FROM abc;
400: INSERT INTO abc SELECT * FROM abc;
401: INSERT INTO abc SELECT * FROM abc;
402: INSERT INTO abc SELECT * FROM abc;
403: INSERT INTO abc SELECT * FROM abc;
404: INSERT INTO abc SELECT * FROM abc;
405: INSERT INTO abc SELECT * FROM abc;
406: }
407: # File has grown - showing there was a cache-spill - but there
408: # have been no calls to fsync(). The file is probably about 30KB.
409: # But some VFS implementations (symbian) buffer writes so the actual
410: # size may be a little less than that. So this test case just tests
411: # that the file is now greater than 20000 bytes in size.
412: list [expr [file size test.db]>20000] [nSync]
413: } {1 0}
414: do_test io-3.3 {
415: # The COMMIT requires a single fsync() - to the database file.
416: execsql { COMMIT }
417: list [file size test.db] [nSync]
418: } {39936 1}
419: }
420:
421: #----------------------------------------------------------------------
422: # Test cases io-4.* test the IOCAP_SAFE_APPEND optimization.
423: #
424: sqlite3_simulate_device -char safe_append
425:
426: # With the SAFE_APPEND flag set, simple transactions require 3, rather
427: # than 4, calls to fsync(). The fsync() calls are on:
428: #
429: # 1) The directory in which the journal file is created, (unix only)
430: # 2) The journal file (to sync the page data),
431: # 3) The database file.
432: #
433: # Normally, when the SAFE_APPEND flag is not set, there is another fsync()
434: # on the journal file between steps (2) and (3) above.
435: #
436: set expected_sync_count 2
437: if {$::tcl_platform(platform)=="unix"} {
438: ifcapable dirsync {
439: incr expected_sync_count
440: }
441: }
442:
443: do_test io-4.1 {
444: execsql { DELETE FROM abc }
445: nSync
446: execsql { INSERT INTO abc VALUES('a', 'b') }
447: nSync
448: } $expected_sync_count
449:
450: # With SAFE_APPEND set, the nRec field of the journal file header should
451: # be set to 0xFFFFFFFF before the first journal sync. The nRec field
452: # occupies bytes 8-11 of the journal file.
453: #
454: do_test io-4.2.1 {
455: execsql { BEGIN }
456: execsql { INSERT INTO abc VALUES('c', 'd') }
457: file exists test.db-journal
458: } {1}
459: if {$::tcl_platform(platform)=="unix"} {
460: do_test io-4.2.2 {
461: hexio_read test.db-journal 8 4
462: } {FFFFFFFF}
463: }
464: do_test io-4.2.3 {
465: execsql { COMMIT }
466: nSync
467: } $expected_sync_count
468: sqlite3_simulate_device -char safe_append
469:
470: # With SAFE_APPEND set, there should only ever be one journal-header
471: # written to the database, even though the sync-mode is "full".
472: #
473: do_test io-4.3.1 {
474: execsql {
475: INSERT INTO abc SELECT * FROM abc;
476: INSERT INTO abc SELECT * FROM abc;
477: INSERT INTO abc SELECT * FROM abc;
478: INSERT INTO abc SELECT * FROM abc;
479: INSERT INTO abc SELECT * FROM abc;
480: INSERT INTO abc SELECT * FROM abc;
481: INSERT INTO abc SELECT * FROM abc;
482: INSERT INTO abc SELECT * FROM abc;
483: INSERT INTO abc SELECT * FROM abc;
484: INSERT INTO abc SELECT * FROM abc;
485: INSERT INTO abc SELECT * FROM abc;
486: }
487: expr {[file size test.db]/1024}
488: } {43}
489: ifcapable pager_pragmas {
490: do_test io-4.3.2 {
491: execsql {
492: PRAGMA synchronous = full;
493: PRAGMA cache_size = 10;
494: PRAGMA synchronous;
495: }
496: } {2}
497: }
498: do_test io-4.3.3 {
499: execsql {
500: BEGIN;
501: UPDATE abc SET a = 'x';
502: }
503: file exists test.db-journal
504: } {1}
505: if {$tcl_platform(platform) != "symbian"} {
506: # This test is not run on symbian because the file-buffer makes it
507: # difficult to predict the exact size of the file as reported by
508: # [file size].
509: do_test io-4.3.4 {
510: # The UPDATE statement in the statement above modifies 41 pages
511: # (all pages in the database except page 1 and the root page of
512: # abc). Because the cache_size is set to 10, this must have required
513: # at least 4 cache-spills. If there were no journal headers written
514: # to the journal file after the cache-spill, then the size of the
515: # journal file is give by:
516: #
517: # <jrnl file size> = <jrnl header size> + nPage * (<page-size> + 8)
518: #
519: # If the journal file contains additional headers, this formula
520: # will not predict the size of the journal file.
521: #
522: file size test.db-journal
523: } [expr 512 + (1024+8)*41]
524: }
525:
526: #----------------------------------------------------------------------
527: # Test cases io-5.* test that the default page size is selected and
528: # used correctly.
529: #
530: set tn 0
531: foreach {char sectorsize pgsize} {
532: {} 512 1024
533: {} 1024 1024
534: {} 2048 2048
535: {} 8192 8192
536: {} 16384 8192
537: {atomic} 512 8192
538: {atomic512} 512 1024
539: {atomic2K} 512 2048
540: {atomic2K} 4096 4096
541: {atomic2K atomic} 512 8192
542: {atomic64K} 512 1024
543: } {
544: incr tn
545: if {$pgsize>$::SQLITE_MAX_PAGE_SIZE} continue
546: db close
547: forcedelete test.db test.db-journal
548: sqlite3_simulate_device -char $char -sectorsize $sectorsize
549: sqlite3 db test.db -vfs devsym
550: db eval {
551: PRAGMA auto_vacuum=OFF;
552: }
553: ifcapable !atomicwrite {
554: if {[regexp {^atomic} $char]} continue
555: }
556: do_test io-5.$tn {
557: execsql {
558: CREATE TABLE abc(a, b, c);
559: }
560: expr {[file size test.db]/2}
561: } $pgsize
562: }
563:
564: sqlite3_simulate_device -char {} -sectorsize 0
565: finish_test
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>