SQLite

Changes On Branch appendvfs_fix
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch appendvfs_fix Excluding Merge-Ins

This is equivalent to a diff from aea12399bf to 25c3186aa1

2021-03-16
14:26
Fix recent breakage of the appendvfs extension. (check-in: 7dbbe5b34e user: drh tags: trunk)
14:12
Comment and stylistic changes to the appendvfs.c implementation. (Closed-Leaf check-in: 25c3186aa1 user: drh tags: appendvfs_fix)
11:11
Fix a problem with ALTER TABLE RENAME COLUMN when used on a schema that features generated columns. Fix for [a753a856]. (check-in: 0e255b2687 user: dan tags: trunk)
07:06
Fix assert typo. (check-in: 3aedf818c1 user: larrybr tags: appendvfs_fix)
06:50
Merge from trunk. (check-in: eae8236f3b user: larrybr tags: appendvfs_fix)
2021-03-15
19:34
Merge version 3.35.1 into the reuse-schema branch. (check-in: 0fe60a63ae user: drh tags: reuse-schema)
16:53
Version 3.35.1 (check-in: aea12399bf user: drh tags: trunk, release, version-3.35.1)
15:12
Improvements to the built-in ".dump" documentation in the CLI. (check-in: 0915f969f4 user: drh tags: trunk)

Changes to ext/misc/appendvfs.c.
34
35
36
37
38
39
40
41
42


43
44
45
46
47
48
49
34
35
36
37
38
39
40


41
42
43
44
45
46
47
48
49







-
-
+
+







**
**  (4)  If none of the above apply and the SQLITE_OPEN_CREATE flag is
**       set, then a new database is appended to the already existing file.
**
**  (5)  Otherwise, SQLITE_CANTOPEN is returned.
**
** To avoid unnecessary complications with the PENDING_BYTE, the size of
** the file containing the database is limited to 1GB. (1000013824 bytes)
** This VFS will not read or write past the 1GB mark.  This restriction
** the file containing the database is limited to 1GiB. (1073741824 bytes)
** This VFS will not read or write past the 1GiB mark.  This restriction
** might be lifted in future versions.  For now, if you need a larger
** database, then keep it in a separate file.
**
** If the file being opened is a plain database (not an appended one), then
** this shim is a pass-through into the default underlying VFS. (rule 3)
**/
#include "sqlite3ext.h"
64
65
66
67
68
69
70
71

72
73
74

75
76
77


78


79
80
81
82
83
84
85
86
87
88
89
90
91
92

93
94
95
96
97

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114



























115
116
117





118
119
120

121
122

123
124


125
126
127
128
129
130
131
64
65
66
67
68
69
70

71
72
73

74
75


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

94





95

















96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122



123
124
125
126
127
128


129


130


131
132
133
134
135
136
137
138
139







-
+


-
+

-
-
+
+

+
+













-
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+

-
-
+
-
-
+
-
-
+
+







#define APND_MARK_FOS_SZ      8
#define APND_MARK_SIZE       (APND_MARK_PREFIX_SZ+APND_MARK_FOS_SZ)

/*
** Maximum size of the combined prefix + database + append-mark.  This
** must be less than 0x40000000 to avoid locking issues on Windows.
*/
#define APND_MAX_SIZE  (65536*15259)
#define APND_MAX_SIZE  (0x40000000)

/*
** Size of storage page upon which to align appendvfs portion.
** Try to align the database to an even multiple of APND_ROUNDUP bytes.
*/
#ifndef APND_ROUNDUP_BITS
#define APND_ROUNDUP_BITS 12
#ifndef APND_ROUNDUP
#define APND_ROUNDUP 4096
#endif
#define APND_ALIGN_MASK         ((sqlite3_int64)(APND_ROUNDUP-1))
#define APND_START_ROUNDUP(fsz) (((fsz)+APND_ALIGN_MASK) & ~APND_ALIGN_MASK)

/*
** Forward declaration of objects used by this utility
*/
typedef struct sqlite3_vfs ApndVfs;
typedef struct ApndFile ApndFile;

/* Access to a lower-level VFS that (might) implement dynamic loading,
** access to randomness, etc.
*/
#define ORIGVFS(p)  ((sqlite3_vfs*)((p)->pAppData))
#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1))

/* Invariants for an open appendvfs file:
/* An open appendvfs file
 * Once an appendvfs file is opened, it will be in one of three states:
 * State 0: Never written. Underlying file (if any) is unaltered.
 * State 1: Append mark is persisted, content write is in progress.
 * State 2: Append mark is persisted, content writes are complete.
 * 
**
 * State 0 is persistent in the sense that nothing will have been done
 * to the underlying file, including any attempt to convert it to an
 * appendvfs file.
 *
 * State 1 is normally transitory. However, if a write operation ends
 * abnormally (disk full, power loss, process kill, etc.), then State 1
 * may be persistent on disk with an incomplete content write-out. This
 * is logically equivalent to an interrupted write to an ordinary file,
 * where some unknown portion of to-be-written data is persisted while
 * the remainder is not. Database integrity in such cases is maintained
 * (or not) by the same measures available for ordinary file access.
 *
 * State 2 is persistent under normal circumstances (when there is no
 * abnormal termination of a write operation such that data provided
 * to the underlying VFS write method has not yet reached storage.)
 *
 * In order to maintain the state invariant, the append mark is written
** An instance of this structure describes the appended database file.
** A separate sqlite3_file object is always appended. The appended
** sqlite3_file object (which can be accessed using ORIGFILE()) describes
** the entire file, including the prefix, the database, and the
** append-mark.
**
** The structure of an AppendVFS database is like this:
**
**   +-------------+---------+----------+-------------+
**   | prefix-file | padding | database | append-mark |
**   +-------------+---------+----------+-------------+
**                           ^          ^
**                           |          |
**                         iPgOne      iMark
**
**
** "prefix file" -  file onto which the database has been appended.
** "padding"     -  zero or more bytes inserted so that "database"
**                  starts on an APND_ROUNDUP boundary
** "database"    -  The SQLite database file
** "append-mark" -  The 25-byte "Start-Of-SQLite3-NNNNNNNN" that indicates
**                  the offset from the start of prefix-file to the start
**                  of "database".
**
** The size of the database is iMark - iPgOne.
**
** The NNNNNNNN in the "Start-Of-SQLite3-NNNNNNNN" suffix is the value
 * in advance of content writes where any part of such content would
 * overwrite an existing (or yet to be written) append mark.
 */
** of iPgOne stored as a big-ending 64-bit integer.
**
** iMark will be the size of the underlying file minus 25 (APND_MARKSIZE).
** Or, iMark is -1 to indicate that it has not yet been written.
*/
struct ApndFile {
  /* Access to IO methods of the underlying file */
  sqlite3_file base;
  sqlite3_file base;        /* Subclass.  MUST BE FIRST! */
  /* File offset to beginning of appended content (unchanging) */
  sqlite3_int64 iPgOne;
  sqlite3_int64 iPgOne;     /* Offset to the start of the database */
  /* File offset of written append-mark, or -1 if unwritten */
  sqlite3_int64 iMark;
  sqlite3_int64 iMark;      /* Offset of the append mark.  -1 if unwritten */
  /* Always followed by another sqlite3_file that describes the whole file */
};

/*
** Methods for ApndFile
*/
static int apndClose(sqlite3_file*);
static int apndRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
247
248
249
250
251
252
253
254

255
256
257
258
259
260
261
255
256
257
258
259
260
261

262
263
264
265
266
267
268
269







-
+







){
  sqlite_int64 iPgOne = paf->iPgOne;
  unsigned char a[APND_MARK_SIZE];
  int i = APND_MARK_FOS_SZ;
  int rc;
  assert(pFile == ORIGFILE(paf));
  memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ);
  while (--i >= 0) {
  while( --i >= 0 ){
    a[APND_MARK_PREFIX_SZ+i] = (unsigned char)(iPgOne & 0xff);
    iPgOne >>= 8;
  }
  iWriteEnd += paf->iPgOne;
  if( SQLITE_OK==(rc = pFile->pMethods->xWrite
                  (pFile, a, APND_MARK_SIZE, iWriteEnd)) ){
    paf->iMark = iWriteEnd;
275
276
277
278
279
280
281
282

283
284
285
286
287
288
289
290
291
292
293
294
295

296
297
298
299
300
301
302
303
283
284
285
286
287
288
289

290

291
292
293
294
295
296
297
298
299
300
301

302

303
304
305
306
307
308
309







-
+
-











-
+
-







  ApndFile *paf = (ApndFile *)pFile;
  sqlite_int64 iWriteEnd = iOfst + iAmt;
  if( iWriteEnd>=APND_MAX_SIZE ) return SQLITE_FULL;
  pFile = ORIGFILE(pFile);
  /* If append-mark is absent or will be overwritten, write it. */
  if( paf->iMark < 0 || paf->iPgOne + iWriteEnd > paf->iMark ){
    int rc = apndWriteMark(paf, pFile, iWriteEnd);
    if( SQLITE_OK!=rc )
    if( SQLITE_OK!=rc ) return rc;
      return rc;
  }
  return pFile->pMethods->xWrite(pFile, zBuf, iAmt, paf->iPgOne+iOfst);
}

/*
** Truncate an apnd-file.
*/
static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){
  ApndFile *paf = (ApndFile *)pFile;
  pFile = ORIGFILE(pFile);
  /* The append mark goes out first so truncate failure does not lose it. */
  if( SQLITE_OK!=apndWriteMark(paf, pFile, size) )
  if( SQLITE_OK!=apndWriteMark(paf, pFile, size) ) return SQLITE_IOERR;
    return SQLITE_IOERR;
  /* Truncate underlying file just past append mark */
  return pFile->pMethods->xTruncate(pFile, paf->iMark+APND_MARK_SIZE);
}

/*
** Sync an apnd-file.
*/
405
406
407
408
409
410
411
412

413

414
415
416
417
418
419
420
411
412
413
414
415
416
417

418
419
420
421
422
423
424
425
426
427







-
+

+







static int apndFetch(
  sqlite3_file *pFile,
  sqlite3_int64 iOfst,
  int iAmt,
  void **pp
){
  ApndFile *p = (ApndFile *)pFile;
  if( p->iMark < 0 || iOfst+iAmt > p->iMark)
  if( p->iMark < 0 || iOfst+iAmt > p->iMark ){
    return SQLITE_IOERR; /* Cannot read what is not yet there. */
  }
  pFile = ORIGFILE(pFile);
  return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp);
}

/* Release a memory-mapped page */
static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
  ApndFile *p = (ApndFile *)pFile;
457
458
459
460
461
462
463
464
465
466
467




468

469
470




471

472
473
474
475
476
477
478
464
465
466
467
468
469
470




471
472
473
474
475
476


477
478
479
480
481
482
483
484
485
486
487
488
489







-
-
-
-
+
+
+
+

+
-
-
+
+
+
+

+







** Return true iff it is such. Parameter sz is the file's size.
*/
static int apndIsAppendvfsDatabase(sqlite3_int64 sz, sqlite3_file *pFile){
  int rc;
  char zHdr[16];
  sqlite3_int64 iMark = apndReadMark(sz, pFile);
  if( iMark>=0 ){
    /* If file has right end-marker, the expected odd size, and the
     * SQLite DB type marker where the end-marker puts it, then it
     * is an appendvfs database (to be treated as such.)
     */
    /* If file has the correct end-marker, the expected odd size, and the
    ** SQLite DB type marker where the end-marker puts it, then it
    ** is an appendvfs database.
    */
    rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), iMark);
    if( SQLITE_OK==rc
    if( SQLITE_OK==rc && memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0
        && (sz & 0x1ff)== APND_MARK_SIZE && sz>=512+APND_MARK_SIZE )
     && memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0
     && (sz & 0x1ff) == APND_MARK_SIZE
     && sz>=512+APND_MARK_SIZE
    ){
      return 1; /* It's an appendvfs database */
    }
  }
  return 0;
}

/*
** Check to see if the file is an ordinary SQLite database file.
** Return true iff so. Parameter sz is the file's size.
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502

503
504
505
506
507
508
509
510



511
512
513
514
515
516





517
518
519

520
521


522
523
524




525
526

527


528
529
530





531
532
533
534
535
536
537



538
539
540
541
542

543

544
545
546
547
548
549
550







551
552

553
554
555
556
557
558
559
497
498
499
500
501
502
503





504
505
506
507

508
509
510
511
512
513



514
515
516
517
518

519


520
521
522
523
524
525


526

527
528
529



530
531
532
533
534

535

536
537
538


539
540
541
542
543
544
545





546
547
548

549
550
551

552
553
554







555
556
557
558
559
560
561


562
563
564
565
566
567
568
569







-
-
-
-
-




-
+





-
-
-
+
+
+


-

-
-
+
+
+
+
+

-
-
+
-

+
+
-
-
-
+
+
+
+

-
+
-
+
+

-
-
+
+
+
+
+


-
-
-
-
-
+
+
+
-



-
+

+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+







  ){
    return 0;
  }else{
    return 1;
  }
}

/* Round-up used to get appendvfs portion to begin at a page boundary. */
#define APND_ALIGN_MASK(nbits) ((1<<nbits)-1)
#define APND_START_ROUNDUP(fsz, nbits) \
 ( ((fsz)+APND_ALIGN_MASK(nbits)) & ~(sqlite3_int64)APND_ALIGN_MASK(nbits) )

/*
** Open an apnd file handle.
*/
static int apndOpen(
  sqlite3_vfs *pVfs,
  sqlite3_vfs *pApndVfs,
  const char *zName,
  sqlite3_file *pFile,
  int flags,
  int *pOutFlags
){
  ApndFile *p;
  sqlite3_file *pSubFile;
  sqlite3_vfs *pSubVfs;
  ApndFile *pApndFile = (ApndFile*)pFile;
  sqlite3_file *pBaseFile = ORIGFILE(pFile);
  sqlite3_vfs *pBaseVfs = ORIGVFS(pApndVfs);
  int rc;
  sqlite3_int64 sz;
  pSubVfs = ORIGVFS(pVfs);
  if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
    /* The appendvfs is not to be used for transient or temporary databases. */
    return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
    /* The appendvfs is not to be used for transient or temporary databases.
    ** Just use the base VFS open to initialize the given file object and
    ** open the underlying file. (Appendvfs is then unused for this file.)
    */
    return pBaseVfs->xOpen(pBaseVfs, zName, pFile, flags, pOutFlags);
  }
  p = (ApndFile*)pFile;
  memset(p, 0, sizeof(*p));
  memset(pApndFile, 0, sizeof(ApndFile));
  pSubFile = ORIGFILE(pFile);
  pFile->pMethods = &apnd_io_methods;
  pApndFile->iMark = -1;    /* Append mark not yet written */

  rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags);
  if( rc ) goto apnd_open_done;
  rc = pSubFile->pMethods->xFileSize(pSubFile, &sz);
  rc = pBaseVfs->xOpen(pBaseVfs, zName, pBaseFile, flags, pOutFlags);
  if( rc==SQLITE_OK ){
    rc = pBaseFile->pMethods->xFileSize(pBaseFile, &sz);
  }
  if( rc ){
    pSubFile->pMethods->xClose(pSubFile);
    pBaseFile->pMethods->xClose(pBaseFile);
    goto apnd_open_done;
    pFile->pMethods = 0;
    return rc;
  }
  if( apndIsOrdinaryDatabaseFile(sz, pSubFile) ){
    memmove(pFile, pSubFile, pSubVfs->szOsFile);
  if( apndIsOrdinaryDatabaseFile(sz, pBaseFile) ){
    /* The file being opened appears to be just an ordinary DB. Copy
    ** the base dispatch-table so this instance mimics the base VFS. 
    */
    memmove(pApndFile, pBaseFile, pBaseVfs->szOsFile);
    return SQLITE_OK;
  }
  /* Record that append mark has not been written until seen otherwise. */
  p->iMark = -1;
  p->iPgOne = apndReadMark(sz, pFile);
  if( p->iPgOne>=0 ){
    /* Append mark was found, infer its offset */
  pApndFile->iPgOne = apndReadMark(sz, pFile);
  if( pApndFile->iPgOne>=0 ){
    pApndFile->iMark = sz - APND_MARK_SIZE; /* Append mark found */
    p->iMark = sz - p->iPgOne - APND_MARK_SIZE;
    return SQLITE_OK;
  }
  if( (flags & SQLITE_OPEN_CREATE)==0 ){
    pSubFile->pMethods->xClose(pSubFile);
    pBaseFile->pMethods->xClose(pBaseFile);
    rc = SQLITE_CANTOPEN;
    pFile->pMethods = 0;
  }
  /* Round newly added appendvfs location to #define'd page boundary. 
   * Note that nothing has yet been written to the underlying file.
   * The append mark will be written along with first content write.
   * Until then, the p->iMark value indicates it is not yet written.
   */
  p->iPgOne = APND_START_ROUNDUP(sz, APND_ROUNDUP_BITS);
  }else{
    /* Round newly added appendvfs location to #define'd page boundary. 
    ** Note that nothing has yet been written to the underlying file.
    ** The append mark will be written along with first content write.
    ** Until then, paf->iMark value indicates it is not yet written.
    */
    pApndFile->iPgOne = APND_START_ROUNDUP(sz);
apnd_open_done:
  if( rc ) pFile->pMethods = 0;
  }
  return rc;
}

/*
** Delete an apnd file.
** For an appendvfs, this could mean delete the appendvfs portion,
** leaving the appendee as it was before it gained an appendvfs.
Changes to test/avfs.test.
16
17
18
19
20
21
22



23
24
25
26
27
28
29
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32







+
+
+







# avfs-1.1. Test that the DB can be read with correct content upon reopen.
# avfs-1.2. Test that an appendvfs DB can be added to a simple text file.
# avfs-1.3. Test that the DB can be read with correct content upon reopen.
# avfs-1.4. Test that appended DB is aligned to default page boundary.
# avfs-2.1. Test that the simple text file retains its initial text.
# avfs-3.1. Test that the appendvfs can grow and shrink, remaining intact.
# avfs-3.2. Test that appendvfs is intact after grow/shrink/close/reopen.
# avfs-3.3. Test that appendvfs can grow by many pages and be written.
# avfs-3.4. Test that grown appendvfs can be reopened and appear intact.
# avfs-3.5. Test that much grown appendvfs can shrink and reopen intact.
# avfs-4.1. Test shell's ability to append to a non-appendvfs file.
# avfs-4.2. Test shell's ability to append to empty or nonexistent file.
# avfs-4.3. Test shell's ability to reopen and alter an appendvfs file.
# avfs-5.1. Test appendvfs refusal to open too-tiny DB appended onto ZLF.
# avfs-5.2. Test appendvfs refusal to open too-tiny DB appended on other.
# ...
# (more to come)
155
156
157
158
159
160
161

162
163
164
165
166

167
168
169
170
171
172
173
174
175
176
177
178
179

180
181
182
183
184
185
186
158
159
160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182

183
184
185
186
187
188
189
190







+




-
+












-
+







    set ::result "Appendee changed."
  } else {
    set ::result "Appendee intact."
  }
} {Appendee intact.}

# Set of repeatable random integers for a couple tests.
set ::nrint 50000
proc rint {v} {
  return [::tcl::mathfunc::int [expr $v * 100000]]
}
array set ::randints [list 0 [rint [::tcl::mathfunc::srand 0]]]
for {set i 1} {$i < 10000} {incr i} {
for {set i 1} {$i < $::nrint} {incr i} {
  set ::randints($i) [rint [::tcl::mathfunc::rand]]
}

do_test 3.1 {
  set results {}
  sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1
  adb eval {
    DROP TABLE t1;
    PRAGMA cache_size=10;
    CREATE TABLE ri (i INTEGER);
    BEGIN;
  }
  for {set i 0} {$i < 10000} {incr i} {
  for {set i 0} {$i < $::nrint} {incr i} {
    set r $::randints($i)
    set s $::randints([incr i])
    set t $::randints([incr i])
    set u $::randints([incr i])
    set v $::randints([incr i])
    adb eval {
      INSERT INTO ri VALUES ($r),($s),($t),($u),($v)
199
200
201
202
203
204
205
206

207
208

209
210

211
212
213
214
215
216
217
218
219
220






















































221
222
223
224
225
226
227
203
204
205
206
207
208
209

210
211

212
213

214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285







-
+

-
+

-
+










+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    VACUUM;
    SELECT integrity_check as ic FROM pragma_integrity_check();
    SELECT count(*) as ic FROM ri;
  } { lappend qr $ic }
  adb close
  set adaSz [file size $::fa]
  set adba [expr ($adbSz + 0.1)/$adaSz]
  # lappend results $adbSz $adaSz
  # lappend results $adba
  set results [concat $results [lrange $qr 0 2]]
  lappend results [expr {$adba > 10.0 && $adba < 20.0}]
  lappend results [expr {$adba > 10.0}]
  set ::result [join $results " | "]
} {ok | 10000 | ok | ok | 1}
} "ok | $::nrint | ok | ok | 1"

do_test 3.2 {
  set results {}
  sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1
  adb eval {
    SELECT integrity_check as ic FROM pragma_integrity_check();
  } { lappend results $ic }
  adb close
  set ::result [join $results " | "]
} {ok}

# avfs-3.3. Test that appendvfs can grow by many pages and be written.
do_test 3.3 {
  set results {}
  sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1
  set npages 300
  adb eval { BEGIN }
  while {$npages > 0} {
    adb eval { INSERT INTO ri VALUES (randomblob(1500)) }
    incr npages -1
  }
  adb eval { COMMIT }
  adb eval {
    SELECT integrity_check as ic FROM pragma_integrity_check();
  } { lappend results $ic }
  adb close
  set adaSzr [expr [file size $::fa] / 300.0 / 1500 ]
  set okSzr [expr $adaSzr > 1.0 && $adaSzr < 1.3 ]
  lappend results $okSzr
  set ::result [join $results " | "]
} {ok | 1}

# avfs-3.4. Test that grown appendvfs can be reopened and appear intact.
do_test 3.4 {
  set results {}
  sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1
  adb eval {
    SELECT integrity_check as ic FROM pragma_integrity_check();
  } { lappend results $ic }
  adb close
  set ::result $ic
} {ok}

# avfs-3.5. Test that much grown appendvfs can shrink and reopen intact.
do_test 3.5 {
  set results {}
  set adbsz [file size $::fa]
  sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1
  adb eval {
    DELETE FROM ri WHERE rowid % 8 <> 0;
    SELECT integrity_check as ic FROM pragma_integrity_check();
    VACUUM;
    SELECT integrity_check as ic FROM pragma_integrity_check();
  } { lappend results $ic }
  adb close
  set adasz [file size $::fa]
  lappend results [expr {$adbsz/$adasz > 5}]
  sqlite3 adb "file:$::fa?mode=rw$::vf" -uri 1
  adb eval {
    SELECT integrity_check as ic FROM pragma_integrity_check();
  } { lappend results $ic }
  adb close
  set ::result [join $results " | "]
} {ok | ok | 1 | ok}

set ::cliDoesAr [shellDoesAr]

do_test 4.1 {
  set shdo "sh_app1.sql"
  set shod "sh_app1.adb"
  forcedelete $shdo $shod
328
329
330
331
332
333
334
335

336
337
386
387
388
389
390
391
392

393
394
395







-
+


  }
  forcedelete $fake
  set ::result $res
} {Open failed.}

forcedelete $::fa $::fza

unset -nocomplain ::fa ::fza ::tlo ::result ::randints ::cliDoesAr
unset -nocomplain ::fa ::fza ::tlo ::result ::randints ::nrint ::cliDoesAr

finish_test