/ Check-in [e1530854]
Login

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

Overview
Comment:Extra tests and resulting bugfixes for btree cursors. (CVS 2106)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: e1530854c9004c25f5ffa21f9cfb9c44c83cc7f0
User & Date: danielk1977 2004-11-17 10:22:03
Context
2004-11-17
16:41
Add the ESCAPE clause to the LIKE operator. Not fully tested yet. (CVS 2107) check-in: 49268c2b user: danielk1977 tags: trunk
10:22
Extra tests and resulting bugfixes for btree cursors. (CVS 2106) check-in: e1530854 user: danielk1977 tags: trunk
2004-11-16
23:21
Clarify the LIMIT clause in the documentation. Ticket #1002. (CVS 2105) check-in: e05f52d9 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
....
3947
3948
3949
3950
3951
3952
3953












3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973

3974
3975
3976
3977




3978
3979
3980
3981
3982
3983
3984
....
4243
4244
4245
4246
4247
4248
4249

4250
4251
4252
4253
4254
4255
4256
....
4520
4521
4522
4523
4524
4525
4526
4527
4528

4529
4530
4531
4532
4533
4534
4535
....
4545
4546
4547
4548
4549
4550
4551




4552
4553
4554
4555
4556
4557
4558
....
4559
4560
4561
4562
4563
4564
4565



4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
....
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
....
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
....
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085



5086
5087
5088
5089
5090
5091
5092
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.219 2004/11/16 15:50:20 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
**
**     Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
**     "Sorting And Searching", pages 473-480. Addison-Wesley
**     Publishing Company, Reading, Massachusetts.
................................................................................
    int nCellCnt = 0;
    int iCell = -1;
    Pgno pgno = pCur->pPage->pgno;

    /* If the cursor is not valid, do not do anything with it. */
    if( !pCur->isValid ) continue;













    for(i=0; iCell<0 && i<nOld; i++){
      if( pgno==apCopy[i]->pgno ){
        iCell = nCellCnt + pCur->idx;
        break;
      }
      nCellCnt += (apCopy[i]->nCell + apCopy[i]->nOverflow) + (leafData?0:1);
    }

    if( pgno==pParent->pgno ){
      assert( !leafData );
      assert( iCell==-1 );
      if( pCur->idx>=nxDiv && pCur->idx<(nxDiv+nOld-1) ){
        for(i=0; i<=(pCur->idx-nxDiv); i++){
          iCell += (apCopy[i]->nCell + apCopy[i]->nOverflow + 1);
        }
      }
      if( pCur->idx>=(nxDiv+nOld-1) ){
        TRACE(("BALANCE: Cursor %p migrates from %d,%d to %d,%d\n", 
            pCur, pgno, pCur->idx, pgno, pCur->idx+(nNew-nOld)));
        pCur->idx += (nNew-nOld);

        pCur->info.nSize = 0;
      }
    }





    if( iCell>=0 ){
      int idxNew;
      Pgno pgnoNew;
      int x = 0;

      assert( iCell<nCell );
      while( cntNew[x]<=iCell ) x++;
................................................................................
  usableSize = pBt->usableSize;
  data = pPage->aData;
  hdr = pPage->hdrOffset;
  brk = get2byte(&data[hdr+5]);
  cdata = pChild->aData;
  memcpy(cdata, &data[hdr], pPage->cellOffset+2*pPage->nCell-hdr);
  memcpy(&cdata[brk], &data[brk], usableSize-brk);

  rc = initPage(pChild, pPage);
  if( rc ) return rc;
  memcpy(pChild->aOvfl, pPage->aOvfl, pPage->nOverflow*sizeof(pPage->aOvfl[0]));
  pChild->nOverflow = pPage->nOverflow;
  if( pChild->nOverflow ){
    pChild->nFree = 0;
  }
................................................................................
      if( rc!=SQLITE_NOMEM ){
        rc = SQLITE_CORRUPT;  /* bkpt-CORRUPT */
      }
      goto delete_out;
    }
    rc = sqlite3pager_write(leafCur.pPage->aData);
    if( rc ) goto delete_out;
    TRACE(("DELETE: table=%d delete internal from %d replace from leaf %d\n",
       pCur->pgnoRoot, pPage->pgno, leafCur.pPage->pgno));


    /* Drop the cell from the internal page. Make a copy of the cell from
    ** the leaf page into memory obtained from malloc(). Insert it into
    ** the internal page, at the position vacated by the delete. There
    ** are now two copies of the leaf-cell in the tree.
    */
    dropCell(pPage, idx, cellSizePtr(pPage, pCell));
................................................................................
    if( rc!=SQLITE_OK ) goto delete_out;
    put4byte(findOverflowCell(pPage, idx), pgnoChild);
    pPage->idxShift = 0;

    /* If there are any cursors that point to the leaf-cell, move them
    ** so that they point at internal cell. This is easiest done by
    ** calling BtreePrevious().




    */
    for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
      if( pCur2->pPage==leafCur.pPage && pCur2->idx==leafCur.idx ){
        int res;
        int delShiftSave = pCur2->delShift; 
        assert( leafCur.idx==0 );
        pCur2->delShift = 0;
................................................................................
        rc = sqlite3BtreePrevious(pCur2, &res);
        if( rc ) goto delete_out;
        assert( res==0 );
        assert( pCur2->pPage==pPage );
        assert( pCur2->idx==idx );
        pCur2->delShift = delShiftSave;
      }



    }

    /* Balance the internal page. Free the memory allocated for the 
    ** copy of the leaf cell. Then delete the cell from the leaf page.
    */
    rc = balance(pPage);
    sqliteFree(tempCell);
    if( rc ) goto delete_out;
    dropCell(leafCur.pPage, leafCur.idx, szNext);

    for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
      if( pCur2->pPage==leafCur.pPage && pCur2->idx>leafCur.idx ){
        TRACE(("DELETE: Cursor %p migrates from %d,%d to %d,%d\n", 
            pCur2, pPage->pgno, pCur2->idx, pPage->pgno, pCur2->idx-1));
        pCur2->idx--;
        pCur2->info.nSize = 0;
      }
    }

    rc = balance(leafCur.pPage);
    releaseTempCursor(&leafCur);
................................................................................
}

/*
** Print a disassembly of the given page on standard output.  This routine
** is used for debugging and testing only.
*/
#ifdef SQLITE_TEST
int sqlite3BtreePageDump(Btree *pBt, int pgno, int recursive){
  int rc;
  MemPage *pPage;
  int i, j, c;
  int nFree;
  u16 idx;
  int hdr;
  int nCell;
................................................................................
  unsigned char *data;
  char range[20];
  unsigned char payload[20];

  rc = getPage(pBt, (Pgno)pgno, &pPage);
  isInit = pPage->isInit;
  if( pPage->isInit==0 ){
    initPage(pPage, 0);
  }
  if( rc ){
    return rc;
  }
  hdr = pPage->hdrOffset;
  data = pPage->aData;
  c = data[hdr];
................................................................................
  }
  if( idx!=0 ){
    sqlite3DebugPrintf("ERROR: next freeblock index out of range: %d\n", idx);
  }
  if( recursive && !pPage->leaf ){
    for(i=0; i<nCell; i++){
      unsigned char *pCell = findCell(pPage, i);
      sqlite3BtreePageDump(pBt, get4byte(pCell), 1);
      idx = get2byte(pCell);
    }
    sqlite3BtreePageDump(pBt, get4byte(&data[hdr+8]), 1);
  }
  pPage->isInit = isInit;
  sqlite3pager_unref(data);
  fflush(stdout);
  return SQLITE_OK;



}
#endif

#ifdef SQLITE_TEST
/*
** Fill aResult[] with information about the entry and page that the
** cursor is pointing to.







|







 







>
>
>
>
>
>
>
>
>
>
>
>







<












>
|
|
|
<
>
>
>
>







 







>







 







|
|
>







 







>
>
>
>







 







>
>
>












|
|







 







|







 







|







 







|


|





>
>
>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
....
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972

3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988

3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
....
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
....
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
....
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
....
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
....
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
....
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
....
5094
5095
5096
5097
5098
5099
5100
5101
5102
5103
5104
5105
5106
5107
5108
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.220 2004/11/17 10:22:03 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
**
**     Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
**     "Sorting And Searching", pages 473-480. Addison-Wesley
**     Publishing Company, Reading, Massachusetts.
................................................................................
    int nCellCnt = 0;
    int iCell = -1;
    Pgno pgno = pCur->pPage->pgno;

    /* If the cursor is not valid, do not do anything with it. */
    if( !pCur->isValid ) continue;

    /* If the cursor pointed to one of the cells moved around during the
    ** balancing, then set variable iCell to the index of the cell in apCell.
    ** This is used by the block below to figure out where the cell was
    ** moved to, and adjust the cursor appropriately.
    **
    ** If the cursor points to the parent page, but the cell was not involved
    ** in the balance, then declare the cache of the cell-parse invalid, as a
    ** defragmentation may of occured during  the balance. Also, if the index
    ** of the cell is greater than that of the divider cells, then it may
    ** need to be adjusted (in case there are now more or less divider cells
    ** than there were before the balancing).
    */
    for(i=0; iCell<0 && i<nOld; i++){
      if( pgno==apCopy[i]->pgno ){
        iCell = nCellCnt + pCur->idx;
        break;
      }
      nCellCnt += (apCopy[i]->nCell + apCopy[i]->nOverflow) + (leafData?0:1);
    }

    if( pgno==pParent->pgno ){
      assert( !leafData );
      assert( iCell==-1 );
      if( pCur->idx>=nxDiv && pCur->idx<(nxDiv+nOld-1) ){
        for(i=0; i<=(pCur->idx-nxDiv); i++){
          iCell += (apCopy[i]->nCell + apCopy[i]->nOverflow + 1);
        }
      }
      if( pCur->idx>=(nxDiv+nOld-1) ){
        TRACE(("BALANCE: Cursor %p migrates from %d,%d to %d,%d\n", 
            pCur, pgno, pCur->idx, pgno, pCur->idx+(nNew-nOld)));
        pCur->idx += (nNew-nOld);
      }
      pCur->info.nSize = 0;
    }


    /* If iCell is greater than or equal to zero, then pCur points at a
    ** cell that was moved around during the balance. Figure out where
    ** the cell was moved to and adjust pCur to match.
    */
    if( iCell>=0 ){
      int idxNew;
      Pgno pgnoNew;
      int x = 0;

      assert( iCell<nCell );
      while( cntNew[x]<=iCell ) x++;
................................................................................
  usableSize = pBt->usableSize;
  data = pPage->aData;
  hdr = pPage->hdrOffset;
  brk = get2byte(&data[hdr+5]);
  cdata = pChild->aData;
  memcpy(cdata, &data[hdr], pPage->cellOffset+2*pPage->nCell-hdr);
  memcpy(&cdata[brk], &data[brk], usableSize-brk);
  assert( pChild->isInit==0 );
  rc = initPage(pChild, pPage);
  if( rc ) return rc;
  memcpy(pChild->aOvfl, pPage->aOvfl, pPage->nOverflow*sizeof(pPage->aOvfl[0]));
  pChild->nOverflow = pPage->nOverflow;
  if( pChild->nOverflow ){
    pChild->nFree = 0;
  }
................................................................................
      if( rc!=SQLITE_NOMEM ){
        rc = SQLITE_CORRUPT;  /* bkpt-CORRUPT */
      }
      goto delete_out;
    }
    rc = sqlite3pager_write(leafCur.pPage->aData);
    if( rc ) goto delete_out;
    TRACE(("DELETE: table=%d delete internal from %d,%d replace "
        "from leaf %d,%d\n", pCur->pgnoRoot, pPage->pgno, idx, 
        leafCur.pPage->pgno, leafCur.idx));

    /* Drop the cell from the internal page. Make a copy of the cell from
    ** the leaf page into memory obtained from malloc(). Insert it into
    ** the internal page, at the position vacated by the delete. There
    ** are now two copies of the leaf-cell in the tree.
    */
    dropCell(pPage, idx, cellSizePtr(pPage, pCell));
................................................................................
    if( rc!=SQLITE_OK ) goto delete_out;
    put4byte(findOverflowCell(pPage, idx), pgnoChild);
    pPage->idxShift = 0;

    /* If there are any cursors that point to the leaf-cell, move them
    ** so that they point at internal cell. This is easiest done by
    ** calling BtreePrevious().
    **
    ** Also, any cursors that point to the internal page have their
    ** cached parses invalidated, as the insertCell() above may have 
    ** caused a defragmation.
    */
    for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
      if( pCur2->pPage==leafCur.pPage && pCur2->idx==leafCur.idx ){
        int res;
        int delShiftSave = pCur2->delShift; 
        assert( leafCur.idx==0 );
        pCur2->delShift = 0;
................................................................................
        rc = sqlite3BtreePrevious(pCur2, &res);
        if( rc ) goto delete_out;
        assert( res==0 );
        assert( pCur2->pPage==pPage );
        assert( pCur2->idx==idx );
        pCur2->delShift = delShiftSave;
      }
      if( pCur2->pPage==pPage ){
        pCur2->info.nSize = 0;
      }
    }

    /* Balance the internal page. Free the memory allocated for the 
    ** copy of the leaf cell. Then delete the cell from the leaf page.
    */
    rc = balance(pPage);
    sqliteFree(tempCell);
    if( rc ) goto delete_out;
    dropCell(leafCur.pPage, leafCur.idx, szNext);

    for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
      if( pCur2->pPage==leafCur.pPage && pCur2->idx>leafCur.idx ){
        TRACE(("DELETE: Cursor %p migrates from %d,%d to %d,%d\n", pCur2, 
            leafCur.pPage->pgno,pCur2->idx,leafCur.pPage->pgno, pCur2->idx-1));
        pCur2->idx--;
        pCur2->info.nSize = 0;
      }
    }

    rc = balance(leafCur.pPage);
    releaseTempCursor(&leafCur);
................................................................................
}

/*
** Print a disassembly of the given page on standard output.  This routine
** is used for debugging and testing only.
*/
#ifdef SQLITE_TEST
static int btreePageDump(Btree *pBt, int pgno, int recursive, MemPage *pParent){
  int rc;
  MemPage *pPage;
  int i, j, c;
  int nFree;
  u16 idx;
  int hdr;
  int nCell;
................................................................................
  unsigned char *data;
  char range[20];
  unsigned char payload[20];

  rc = getPage(pBt, (Pgno)pgno, &pPage);
  isInit = pPage->isInit;
  if( pPage->isInit==0 ){
    initPage(pPage, pParent);
  }
  if( rc ){
    return rc;
  }
  hdr = pPage->hdrOffset;
  data = pPage->aData;
  c = data[hdr];
................................................................................
  }
  if( idx!=0 ){
    sqlite3DebugPrintf("ERROR: next freeblock index out of range: %d\n", idx);
  }
  if( recursive && !pPage->leaf ){
    for(i=0; i<nCell; i++){
      unsigned char *pCell = findCell(pPage, i);
      btreePageDump(pBt, get4byte(pCell), 1, pPage);
      idx = get2byte(pCell);
    }
    btreePageDump(pBt, get4byte(&data[hdr+8]), 1, pPage);
  }
  pPage->isInit = isInit;
  sqlite3pager_unref(data);
  fflush(stdout);
  return SQLITE_OK;
}
int sqlite3BtreePageDump(Btree *pBt, int pgno, int recursive){
  return btreePageDump(pBt, pgno, recursive, 0);
}
#endif

#ifdef SQLITE_TEST
/*
** Fill aResult[] with information about the entry and page that the
** cursor is pointing to.

Changes to test/btree8.test.

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
..
88
89
90
91
92
93
94
95
96




97
98
99
100
101
102
103
...
275
276
277
278
279
280
281
282






























































































































283
284
#
#***********************************************************************
# This file implements regression tests for SQLite library.  The
# focus of this script is btree database backend. Specifically,
# this file tests that existing cursors are correctly repositioned 
# when entries are inserted into or deleted from btrees.
#
# $Id: btree8.test,v 1.3 2004/11/16 15:50:21 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

# Test organization:
#
# btree-8.1.*: Test cursor persistence when inserting records into tables.
# btree-8.2.*: Test cursor persistence when deleting records from tables.
# btree-8.3.*: Test cursor persistence when inserting records into indices.
# btree-8.4.*: Test cursor persistence when deleting records from indices.
#


# Transform the number $num into a string of length $len by repeating the
# string representation of the number as many times as necessary. Repeats
# are seperated by a '.' character. Eg:
#
# [num_to_string 456 10] -> "456.456.45"
#
................................................................................
# Then, a record is inserted for each key value between 1 and 5000,
# including the values for which a record already exists (overwriting
# the original). After each record is inserted, the existing cursors
# are checked to ensure they still point at the same key-value.
#

# Open the database at the btree level and begin a transaction
do_test btree8-1.1 {
  set ::bt [btree_open test.db 100 0]




  btree_begin_transaction $::bt
  expr 0
} {0}

# For each element in the list $keys, insert an entry into the SQL table
# with the corresponding key value. Check that the cursor used to insert
# the key is left pointing to it after the insert. Then save this cursor
................................................................................
btree_close_cursor $::write_csr
btree_commit $::bt
if {$::nErr>0} { puts $::csr_list }
foreach csr $csr_list {
  btree_close_cursor $csr
}
set csr_list [list]































































































































finish_test








|











<







 







|

>
>
>
>







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

28
29
30
31
32
33
34
..
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
...
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
#
#***********************************************************************
# This file implements regression tests for SQLite library.  The
# focus of this script is btree database backend. Specifically,
# this file tests that existing cursors are correctly repositioned 
# when entries are inserted into or deleted from btrees.
#
# $Id: btree8.test,v 1.4 2004/11/17 10:22:04 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

# Test organization:
#
# btree-8.1.*: Test cursor persistence when inserting records into tables.
# btree-8.2.*: Test cursor persistence when deleting records from tables.
# btree-8.3.*: Test cursor persistence when inserting records into indices.
# btree-8.4.*: Test cursor persistence when deleting records from indices.
#


# Transform the number $num into a string of length $len by repeating the
# string representation of the number as many times as necessary. Repeats
# are seperated by a '.' character. Eg:
#
# [num_to_string 456 10] -> "456.456.45"
#
................................................................................
# Then, a record is inserted for each key value between 1 and 5000,
# including the values for which a record already exists (overwriting
# the original). After each record is inserted, the existing cursors
# are checked to ensure they still point at the same key-value.
#

# Open the database at the btree level and begin a transaction
do_test btree8-1.0 {
  set ::bt [btree_open test.db 100 0]
  expr 0
} {0}

do_test btree8-1.1 {
  btree_begin_transaction $::bt
  expr 0
} {0}

# For each element in the list $keys, insert an entry into the SQL table
# with the corresponding key value. Check that the cursor used to insert
# the key is left pointing to it after the insert. Then save this cursor
................................................................................
btree_close_cursor $::write_csr
btree_commit $::bt
if {$::nErr>0} { puts $::csr_list }
foreach csr $csr_list {
  btree_close_cursor $csr
}
set csr_list [list]

#------------------------------------------------------------------------
# Tests btree8.5.* also test the types of trees used for SQL indices. 
# This time, 300 entries of 150 bytes each are inserted into the btree (this
# produces a tree of height 3 - root page is the grandparent of the leaves).
# A cursor points at each entry. We check that all cursors retain there
# validity when:
#
# * Each entry is deleted (test cases btree-8.5.1.*)
# * An entry is inserted just after/before each existing key (test 
#   cases btree-8.5.2.*).
#

# Open a cursor on each entry in the tree in B-tree $bt, root-page $tnum.
# Return a list of the cursors.
#
proc open_cursors {bt tnum} {
  set c [btree_cursor $bt $tnum 0]
  set csr_list [list]
  for {btree_first $c} {![btree_eof $c]} {btree_next $c} {
    set c2 [btree_cursor $bt $tnum 0]
    btree_move_to $c2 [btree_key $c]
    lappend csr_list $c2
  }
  btree_close_cursor $c
  return $csr_list
}

# Close all cursors in the list $csr_list.
#
proc close_cursors {csr_list} { 
  foreach c $csr_list {
    btree_close_cursor $c
  }
}

# Check that the key for each cursor in csr_list matches the corresponding
# entry in key_list. If not, raise an exception.
#
proc check_cursors {key_list csr_list} {
  foreach k $key_list c $csr_list {
    if {[string compare $k [btree_key $c]]} {
      error "Csr key '[btree_key $c]' - should be '$k'"
    }
  }
}

# Set up the table used for the btree-8.5.* tests
do_test btree-8.5.0 {
  btree_begin_transaction $::bt
  set c [btree_cursor $::bt $::inum 1]
  for {set i 2} {$i<=600} {incr i 2} { 
    set key [num_to_string $i 150]
    lappend key_list $key
    btree_insert $c $key ""
  }
  btree_close_cursor $c
  btree_commit $::bt
} {}

# Test cases btree-8.5.1.* - Check that cursors survive DELETE operations.
set testnum 0
foreach key [lrange $::key_list 0 0] {
  incr testnum

  btree_begin_transaction $::bt

  # Open the 300 cursors.
  do_test btree-8.5.1.$testnum.1 {
    set ::csr_list [open_cursors $::bt $::inum]
    llength $::csr_list
  } {300}

   # Delete an entry.
   do_test btree-8.5.1.$testnum.2 {
     set c [btree_cursor $::bt $::inum 1]
     btree_move_to $c $::key
     btree_delete $c
     btree_close_cursor $c
   } {}
 
   # Check that all 300 cursors are Ok.
   do_test btree-8.5.1.$testnum.3 {
     catch {
       set e [lsearch $::key_list $::key]
       check_cursors [lreplace $::key_list $e $e ""] $::csr_list
     } msg
     set msg
   } {}

  close_cursors $::csr_list
  btree_rollback $::bt
}

# Test cases btree-8.5.2.* - Check that cursors survive INSERT operations.
set testnum 0
foreach key $::key_list {
  incr testnum

  btree_begin_transaction $::bt

  # Open the 300 cursors.
  do_test btree-8.5.2.$testnum.1 {
    set ::csr_list [open_cursors $::bt $::inum]
    llength $::csr_list
  } {300}

  # Insert new entries, one before the key, and one after.
  do_test btree-8.5.2.$testnum.2 {
    set c [btree_cursor $::bt $::inum 1]
    btree_insert $c "$::key$::key" ""
    btree_insert $c [string range $::key 0 end-1] ""
    btree_close_cursor $c
  } {}

  # Check that all 300 cursors are Ok.
  do_test btree-8.5.2.$testnum.3 {
    catch {
      check_cursors $::key_list $::csr_list
    } msg
    set msg
  } {}

  close_cursors $::csr_list
  btree_rollback $::bt
}

finish_test