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 |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
e1530854c9004c25f5ffa21f9cfb9c44 |
User & Date: | danielk1977 2004-11-17 10:22:03.000 |
Context
2004-11-17
| ||
16:41 | Add the ESCAPE clause to the LIKE operator. Not fully tested yet. (CVS 2107) (check-in: 49268c2b7a user: danielk1977 tags: trunk) | |
10:22 | Extra tests and resulting bugfixes for btree cursors. (CVS 2106) (check-in: e1530854c9 user: danielk1977 tags: trunk) | |
2004-11-16
| ||
23:21 | Clarify the LIMIT clause in the documentation. Ticket #1002. (CVS 2105) (check-in: e05f52d907 user: drh tags: trunk) | |
Changes
Changes to src/btree.c.
1 2 3 4 5 6 7 8 9 10 11 | /* ** 2004 April 6 ** ** The author disclaims copyright to this source code. In place of ** 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. ** ************************************************************************* | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* ** 2004 April 6 ** ** The author disclaims copyright to this source code. In place of ** 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. |
︙ | ︙ | |||
3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 | 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); } | > > > > > > > > > > > > < > | | | | > > > | 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 | 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++; |
︙ | ︙ | |||
4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 | 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; } | > | 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 | 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; } |
︙ | ︙ | |||
4520 4521 4522 4523 4524 4525 4526 | if( rc!=SQLITE_NOMEM ){ rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ } goto delete_out; } rc = sqlite3pager_write(leafCur.pPage->aData); if( rc ) goto delete_out; | | | > | 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 | 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)); |
︙ | ︙ | |||
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 | 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 ){ | > > > > > > > | | | 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 | 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); |
︙ | ︙ | |||
4984 4985 4986 4987 4988 4989 4990 | } /* ** Print a disassembly of the given page on standard output. This routine ** is used for debugging and testing only. */ #ifdef SQLITE_TEST | | | | 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 | } /* ** 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; int isInit; 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]; |
︙ | ︙ | |||
5070 5071 5072 5073 5074 5075 5076 | } 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); | | | > > > | 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 | } 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 | # #*********************************************************************** # 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. # | | < | 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 | # #*********************************************************************** # 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" # |
︙ | ︙ | |||
88 89 90 91 92 93 94 | # 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 | | > > > > | 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | # 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 |
︙ | ︙ | |||
275 276 277 278 279 280 281 282 283 284 | 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 | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | 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 |