/ Check-in [ed0a31be]
Login

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

Overview
Comment:Add further tests for deferred page allocation. And fixes for the same.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | begin-concurrent
Files: files | file ages | folders
SHA1: ed0a31be726e60115a5dd73d4ed580201b400ab7
User & Date: dan 2015-08-22 17:28:55
Wiki:begin-concurrent
Context
2015-08-22
20:32
Fix a problem to do with detecting unlocked transaction conflicts if another client restarts the wal while the transaction is running. check-in: e3968b25 user: dan tags: begin-concurrent
17:28
Add further tests for deferred page allocation. And fixes for the same. check-in: ed0a31be user: dan tags: begin-concurrent
07:56
Merge further trunk changes. check-in: c2327a3b user: dan tags: begin-concurrent
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

435
436
437
438
439
440
441


442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
...
475
476
477
478
479
480
481




482
483

484
485
486
487

488
489
490
491
492
493
494
495
496
497
498
499
...
531
532
533
534
535
536
537
538

539
540
541
542
543
544
545
546
...
558
559
560
561
562
563
564
565


566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586










































587
588
589
590
591
592
593
....
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029





1030
1031
1032
1033
1034

1035


1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050


1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062

1063
1064
1065
1066
1067
1068
1069
1070
1071
....
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
....
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
....
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
....
3837
3838
3839
3840
3841
3842
3843

3844
3845
3846
3847
3848
3849
3850
....
3909
3910
3911
3912
3913
3914
3915

3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927

3928
3929
3930
3931
3932

3933
3934
3935


3936

3937
3938
3939
3940
3941

3942
3943
3944
3945
3946
3947

3948
3949
3950
3951
3952

3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963

3964
3965
3966
3967
3968
3969
3970
....
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002

4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
....
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
....
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
....
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
....
5813
5814
5815
5816
5817
5818
5819
5820
5821
5822
5823
5824
5825
5826
5827
....
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
....
9922
9923
9924
9925
9926
9927
9928

















9929
9930
9931
9932
9933
9934
9935
9936
9937
      pLock->eLock = READ_LOCK;
    }
  }
}

#endif /* SQLITE_OMIT_SHARED_CACHE */



/*
** The following structure stores the in-memory pointer map used for newly
** allocated pages in UNLOCKED transactions. Such pages are always allocated 
** in a contiguous block (from the end of the file) starting with page
** BtreePtrmap.iFirst.
** 
** The page number for the parent page iFirst is stored in aPtr[0]. For
** (iFirst+1), aPtr[1]. A zero value indicates that the page has not
** been allocated.
**
**
*/

typedef struct RollbackEntry RollbackEntry;
typedef struct PtrmapEntry PtrmapEntry;
struct PtrmapEntry {
  Pgno parent;
  u8 eType;
};
struct RollbackEntry {
  Pgno pgno;
  Pgno parent;
  u8 eType;
};

struct BtreePtrmap {
  Pgno iFirst;                    /* First new page number aPtr[0] */

  int nPtrAlloc;                  /* Allocated size of aPtr[] array */
  PtrmapEntry *aPtr;              /* Array of parent page numbers */

  int nSvpt;                      /* Used size of aSvpt[] array */
................................................................................
  int *aSvpt;                     /* First aRollback[] entry for savepoint i */

  int nRollback;                  /* Used size of aRollback[] array */
  int nRollbackAlloc;             /* Allocated size of aRollback[] array */
  RollbackEntry *aRollback;       /* Array of rollback entries */
};





static int btreePtrmapStore(
  BtreePtrmap *pMap, 

  Pgno pgno, 
  u8 eType, 
  Pgno parent
){

  if( pgno>=pMap->iFirst ){
    int iEntry = pgno - pMap->iFirst;

    /* Grow the aPtr[] array if required */
    if( iEntry>=pMap->nPtrAlloc ){
      int nNew = pMap->nPtrAlloc ? pMap->nPtrAlloc*2 : 16;
      PtrmapEntry *aNew = (PtrmapEntry*)sqlite3_realloc(
          pMap->aPtr, nNew*sizeof(PtrmapEntry)
      );
      if( aNew==0 ){
        return SQLITE_NOMEM;
      }else{
................................................................................

  return SQLITE_OK;
}

/*
** Open savepoint iSavepoint, if it is not already open.
*/
static int btreePtrmapBegin(BtreePtrmap *pMap, int nSvpt){

  if( nSvpt<pMap->nSvpt ){
    int i;
    if( nSvpt>=pMap->nSvptAlloc ){
      int nNew = pMap->nSvptAlloc ? pMap->nSvptAlloc*2 : 16;
      int *aNew = sqlite3_realloc(pMap->aSvpt, sizeof(int) * nNew);
      if( aNew==0 ){
        return SQLITE_NOMEM;
      }else{
................................................................................
  return SQLITE_OK;
}

/*
** Rollback (if op==SAVEPOINT_ROLLBACK) or release (if op==SAVEPOINT_RELEASE)
** savepoint iSvpt.
*/
static void btreePtrmapEnd(BtreePtrmap *pMap, int op, int iSvpt){


  assert( op==SAVEPOINT_ROLLBACK || op==SAVEPOINT_RELEASE );
  assert( iSvpt>=0 || (iSvpt==-1 && op==SAVEPOINT_ROLLBACK) );
  if( iSvpt<0 ){
    pMap->nSvpt = 0;
    pMap->nRollback = 0;
    memset(pMap->aPtr, 0, sizeof(Pgno) * pMap->nPtrAlloc);
  }else if( iSvpt<pMap->nSvpt ){
    if( op==SAVEPOINT_ROLLBACK ){
      int ii;
      for(ii=pMap->nRollback-1; ii>=pMap->aSvpt[iSvpt]; ii--){
        RollbackEntry *p = &pMap->aRollback[ii];
        PtrmapEntry *pEntry = &pMap->aPtr[p->pgno - pMap->iFirst];
        pEntry->parent = p->parent;
        pEntry->eType = p->eType;
      }
    }
    pMap->nSvpt = iSvpt + (op==SAVEPOINT_ROLLBACK);
    pMap->nRollback = pMap->aSvpt[iSvpt];
  }
}












































static void releasePage(MemPage *pPage);  /* Forward reference */

/*
***** This routine is used inside of assert() only ****
**
** Verify that the cursor holds the mutex on its BtShared
................................................................................
static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
  DbPage *pDbPage;  /* The pointer map page */
  u8 *pPtrmap;      /* The pointer map data */
  Pgno iPtrmap;     /* The pointer map page number */
  int offset;       /* Offset in pointer map page */
  int rc;           /* Return code from subfunctions */

  assert( sqlite3_mutex_held(pBt->mutex) );
  if( *pRC ) return;






  if( pBt->pMap ){
    *pRC = btreePtrmapStore(pBt->pMap, key, eType, parent);
  }else{
    /* The master-journal page number must never be used as a ptr map page */
    assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );




    assert( pBt->autoVacuum );
    if( key==0 ){
      *pRC = SQLITE_CORRUPT_BKPT;
      return;
    }
    iPtrmap = PTRMAP_PAGENO(pBt, key);
    rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage);
    if( rc!=SQLITE_OK ){
      *pRC = rc;
      return;
    }
    offset = PTRMAP_PTROFFSET(iPtrmap, key);
    if( offset<0 ){
      *pRC = SQLITE_CORRUPT_BKPT;
    }else{


      assert( offset <= (int)pBt->usableSize-5 );
      pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);

      if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
        TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent));
        *pRC= rc = sqlite3PagerWrite(pDbPage);
        if( rc==SQLITE_OK ){
          pPtrmap[offset] = eType;
          put4byte(&pPtrmap[offset+1], parent);
        }
      }
    }

    sqlite3PagerUnref(pDbPage);
  }
}

/*
** Read an entry from the pointer map.
**
** This routine retrieves the pointer map entry for page 'key', writing
** the type and parent page number to *pEType and *pPgno respectively.
................................................................................
}
u32 sqlite3BtreeLastPage(Btree *p){
  assert( sqlite3BtreeHoldsMutex(p) );
  assert( ((p->pBt->nPage)&0x8000000)==0 );
  return btreePagecount(p->pBt);
}

#ifdef SQLITE_ENABLE_UNLOCKED
/*
** This function is called before allocating or freeing a b-tree page. If
** the current transaction is UNLOCKED, it allocates the BtreePtrmap 
** structure and zeroes the nFree/iTrunk fields in the database header
** on page 1.
*/
static int allocatePtrmap(BtShared *pBt){
  int rc = SQLITE_OK;
  if( pBt->pMap==0 && sqlite3PagerIsUnlocked(pBt->pPager) ){
    /* If this is an unlocked transaction, set the header values
    ** identifying the size of the free-list and the page number
    ** of the first trunk page to zero. */
    BtreePtrmap *pMap = sqlite3_malloc(sizeof(BtreePtrmap));
    if( pMap==0 ){
      rc = SQLITE_NOMEM;
    }else{
      memset(&pBt->pPage1->aData[32], 0, sizeof(u32)*2);
      memset(pMap, 0, sizeof(BtreePtrmap));
      pMap->iFirst = btreePagecount(pBt) + 1;
      pBt->pMap = pMap;
    }
  }
  return rc;
}

/*
** Free a pointer-map allocated by allocatePtrmap.
*/
static void deletePtrmap(BtShared *pBt){
  BtreePtrmap *pMap = pBt->pMap;
  if( pMap ){
    sqlite3_free(pMap->aRollback);
    sqlite3_free(pMap->aPtr);
    sqlite3_free(pMap->aSvpt);
    sqlite3_free(pMap);
    pBt->pMap = 0;
  }
}
#else
#define allocatePtrmap(x) SQLITE_OK
#define deletePtrmap(x) 
#endif

/*
** Get a page from the pager and initialize it.
**
** If pCur!=0 then the page is being fetched as part of a moveToChild()
** call.  Do additional sanity checking on the page in this case.
** And if the fetch fails, this routine must decrement pCur->iPage.
**
................................................................................
        int exFlag = (p->db->bUnlocked && !ISAUTOVACUUM) ? -1 : (wrflag>1);
        int bSubjInMem = sqlite3TempInMemory(p->db);
        assert( p->db->bUnlocked==0 || wrflag==1 );
        rc = sqlite3PagerBegin(pBt->pPager, exFlag, bSubjInMem);
        if( rc==SQLITE_OK ){
          rc = newDatabase(pBt);
        }
        if( rc==SQLITE_OK ){
          rc = allocatePtrmap(pBt);
        }
      }
    }
  
    if( rc!=SQLITE_OK ){
      unlockBtreeIfUnused(pBt);
    }
................................................................................
  if( rc==SQLITE_OK && wrflag ){
    /* This call makes sure that the pager has the correct number of
    ** open savepoints. If the second parameter is greater than 0 and
    ** the sub-journal is not already open, then it will be opened here.
    */
    int nSavepoint = p->db->nSavepoint;
    rc = sqlite3PagerOpenSavepoint(pBt->pPager, nSavepoint);
    if( pBt->pMap && rc==SQLITE_OK && nSavepoint ){
      rc = btreePtrmapBegin(pBt->pMap, nSavepoint);
    }
  }

  btreeIntegrity(p);
  sqlite3BtreeLeave(p);
  return rc;
}
................................................................................
  return rc;
}

#else /* ifndef SQLITE_OMIT_AUTOVACUUM */
# define setChildPtrmaps(x) SQLITE_OK
#endif


/*
** The b-tree handle passed as the only argument is about to commit an
** UNLOCKED transaction. At this point it is guaranteed that this is 
** possible - the wal WRITER lock is held and it is known that there are 
** no conflicts with committed transactions.
*/
static int btreeFixUnlocked(Btree *p){
................................................................................
        nCurrent = MAX(nPage, nHPage);

        for(iPg=pMap->iFirst; iPg<=iLast && rc==SQLITE_OK; iPg++){
          MemPage *pPg = 0;
          Pgno iNew;              /* New page number for pPg */
          PtrmapEntry *pEntry;    /* Pointer map entry for page iPg */


          pEntry = &pMap->aPtr[iPg - pMap->iFirst];
          if( pEntry->eType==PTRMAP_FREEPAGE ){
            MemPage *pFree = 0;
            Pgno dummy;
            rc = allocateBtreePage(pBt, &pFree, &dummy, iPg, BTALLOC_EXACT);
            releasePage(pFree);
            assert( rc!=SQLITE_OK || dummy==iPg );
          }else{
            btreeGetPage(pBt, iPg, &pPg, 0);
            assert( sqlite3PagerIswriteable(pPg->pDbPage) );
            assert( sqlite3PagerPageRefcount(pPg->pDbPage)==1 );
            iNew = ++nCurrent;

            rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent, iNew, 1);
            releasePageNotNull(pPg);
          }
        }
        sqlite3PagerSetDbsize(pPager, nCurrent);


        nFree = get4byte(&p1[36]);
        nFinal = MAX(nCurrent-nFree, nHPage);




        for(iPg=nFinal+1; rc==SQLITE_OK && iPg<nCurrent; iPg++){
          Pgno iNew;              /* New page number for pPg */
          MemPage *pFree;
          PtrmapEntry *pEntry;    /* Pointer map entry for page iPg */


          pEntry = &pMap->aPtr[iPg - pMap->iFirst];
          if( pEntry->eType==PTRMAP_FREEPAGE ){
            rc = allocateBtreePage(pBt, &pFree, &iNew, iPg, BTALLOC_EXACT);
            releasePage(pFree);
          }else{
            rc = allocateBtreePage(pBt, &pFree, &iNew, nFinal, BTALLOC_LE);

            releasePage(pFree);
            if( rc==SQLITE_OK ){
              MemPage *pPg = 0;
              btreeGetPage(pBt, iPg, &pPg, 0);
              rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent,iNew,1);

            }
          }
        }
        put4byte(&p1[28], nFinal);
      }
    }
    sqlite3PagerSetDbsize(pPager, nFinal);
  }

  return rc;
}


/*
** This routine does the first phase of a two-phase commit.  This routine
** causes a rollback journal to be created (if it does not already exist)
** and populated with enough information so that if a power loss occurs
** the database can be restored to its original state by playing back
** the journal.  Then the contents of the journal are flushed out to
................................................................................
int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
  int rc = SQLITE_OK;
  if( p->inTrans==TRANS_WRITE ){
    BtShared *pBt = p->pBt;
    sqlite3BtreeEnter(p);

#ifndef SQLITE_OMIT_AUTOVACUUM
    /* Figure out if this is a commit of an UNLOCKED transaction that 
    ** requires a snapshot upgrade. If so, skip any auto-vacuum 
    ** processing.  */
    if( pBt->autoVacuum ){
      assert( sqlite3PagerIsUnlocked(pBt->pPager)==0 );

      rc = autoVacuumCommit(pBt);
      if( rc!=SQLITE_OK ){
        sqlite3BtreeLeave(p);
        return rc;
      }
    }
    if( pBt->bDoTruncate ){
      sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
    }
#endif
    if( rc==SQLITE_OK && sqlite3PagerIsUnlocked(pBt->pPager) ){
      rc = btreeFixUnlocked(p);
    }
    if( rc==SQLITE_OK ){
      rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
    }
    sqlite3BtreeLeave(p);
  }
................................................................................
    /* Set the current transaction state to TRANS_NONE and unlock the 
    ** pager if this call closed the only read or write transaction.  */
    p->inTrans = TRANS_NONE;
    unlockBtreeIfUnused(pBt);
  }

  /* If this was an UNLOCKED transaction, delete the pBt->pMap object */
  deletePtrmap(pBt);
  btreeIntegrity(p);
}

/*
** Commit the transaction currently in progress.
**
** This routine implements the second phase of a 2-phase commit.  The
................................................................................
  assert( pBt->inTransaction==TRANS_WRITE );
  /* At the pager level, a statement transaction is a savepoint with
  ** an index greater than all savepoints created explicitly using
  ** SQL statements. It is illegal to open, release or rollback any
  ** such savepoints while the statement transaction savepoint is active.
  */
  rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement);
  if( rc==SQLITE_OK && pBt->pMap ){
    rc = btreePtrmapBegin(pBt->pMap, iStatement);
  }
  sqlite3BtreeLeave(p);
  return rc;
}

/*
** The second argument to this function, op, is always SAVEPOINT_ROLLBACK
................................................................................
int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
  int rc = SQLITE_OK;
  if( p && p->inTrans==TRANS_WRITE ){
    BtShared *pBt = p->pBt;
    assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );
    assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) );
    sqlite3BtreeEnter(p);
    if( pBt->pMap ) btreePtrmapEnd(pBt->pMap, op, iSavepoint);
    rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint);
    if( rc==SQLITE_OK ){
      if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){
        pBt->nPage = 0;
      }
      rc = newDatabase(pBt);
      pBt->nPage = get4byte(28 + pBt->pPage1->aData);
................................................................................
  assert( eMode==BTALLOC_ANY || (nearby>0 && REQUIRE_PTRMAP ) );
  pPage1 = pBt->pPage1;
  mxPage = btreePagecount(pBt);
  /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36
  ** stores stores the total number of pages on the freelist. */
  n = get4byte(&pPage1->aData[36]);
  testcase( n==mxPage-1 );
  if( sqlite3PagerIsUnlocked(pBt->pPager)==0 && n>=mxPage ){
    return SQLITE_CORRUPT_BKPT;
  }

  /* Ensure page 1 is writable. This function will either change the number
  ** of pages in the free-list or the size of the database file. Since both
  ** of these operations involve modifying page 1 header fields, page 1
  ** will definitely be written by this transaction. If this is an UNLOCKED
................................................................................
    u32 nSearch = 0;   /* Count of the number of search attempts */
    
    /* If eMode==BTALLOC_EXACT and a query of the pointer-map
    ** shows that the page 'nearby' is somewhere on the free-list, then
    ** the entire-list will be searched for that page.
    */
    if( eMode==BTALLOC_EXACT ){
      assert( ISAUTOVACUUM==!sqlite3PagerIsUnlocked(pBt->pPager) );
      if( ISAUTOVACUUM ){
        if( nearby<=mxPage ){
          u8 eType;
          assert( nearby>0 );
          assert( pBt->autoVacuum );
          rc = ptrmapGet(pBt, nearby, &eType, 0);
          if( rc ) return rc;
................................................................................
}

/*
** Return the size of the header added to each page by this module.
*/
int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }


















int sqlite3BtreeExclusiveLock(Btree *p){
  int rc;
  BtShared *pBt = p->pBt;
  assert( p->inTrans==TRANS_WRITE && pBt->pPage1 );
  sqlite3BtreeEnter(p);
  rc = sqlite3PagerExclusiveLock(pBt->pPager, pBt->pPage1->pDbPage);
  sqlite3BtreeLeave(p);
  return rc;
}







>
>

|
|
|
|
<
<
<
<
<
<

<











<







 







>
>
>
>

<
>
|



>



|
|







 







|
>
|







 







|
>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<


>
>
>
>
>

|
<
<
<
>
|
>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
>
>
|
|

|
|
|
|
|
|
|
|
|
>
|
<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
|







 







|
|







 







>







 







>












>





>



>
>
|
>
|




>






>





>











>







 







<
<
<

<
>










|







 







|







 







|
|







 







|







 







|







 







|







 







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









435
436
437
438
439
440
441
442
443
444
445
446
447
448






449

450
451
452
453
454
455
456
457
458
459
460

461
462
463
464
465
466
467
...
469
470
471
472
473
474
475
476
477
478
479
480

481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
...
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
...
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
....
1064
1065
1066
1067
1068
1069
1070

1071
1072
1073
1074
1075
1076
1077
1078
1079



1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097

1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113

1114
1115
1116
1117
1118
1119
1120
....
2117
2118
2119
2120
2121
2122
2123












































2124
2125
2126
2127
2128
2129
2130
....
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
....
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
....
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
....
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
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
....
4007
4008
4009
4010
4011
4012
4013



4014

4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
....
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
....
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
....
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
....
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
....
5849
5850
5851
5852
5853
5854
5855
5856
5857
5858
5859
5860
5861
5862
5863
....
9935
9936
9937
9938
9939
9940
9941
9942
9943
9944
9945
9946
9947
9948
9949
9950
9951
9952
9953
9954
9955
9956
9957
9958
9959
9960
9961
9962
9963
9964
9965
9966
9967
      pLock->eLock = READ_LOCK;
    }
  }
}

#endif /* SQLITE_OMIT_SHARED_CACHE */


#ifdef SQLITE_ENABLE_UNLOCKED
/*
** The following structure - BtreePtrmap - stores the in-memory pointer map
** used for newly allocated pages in UNLOCKED transactions. Such pages are
** always allocated in a contiguous block (from the end of the file) starting
** with page BtreePtrmap.iFirst.






*/

typedef struct RollbackEntry RollbackEntry;
typedef struct PtrmapEntry PtrmapEntry;
struct PtrmapEntry {
  Pgno parent;
  u8 eType;
};
struct RollbackEntry {
  Pgno pgno;
  Pgno parent;
  u8 eType;
};

struct BtreePtrmap {
  Pgno iFirst;                    /* First new page number aPtr[0] */

  int nPtrAlloc;                  /* Allocated size of aPtr[] array */
  PtrmapEntry *aPtr;              /* Array of parent page numbers */

  int nSvpt;                      /* Used size of aSvpt[] array */
................................................................................
  int *aSvpt;                     /* First aRollback[] entry for savepoint i */

  int nRollback;                  /* Used size of aRollback[] array */
  int nRollbackAlloc;             /* Allocated size of aRollback[] array */
  RollbackEntry *aRollback;       /* Array of rollback entries */
};

/*
** If page number pgno is greater than or equal to BtreePtrmap.iFirst, 
** store an entry for it in the pointer-map structure.
*/
static int btreePtrmapStore(

  BtShared *pBt,
  Pgno pgno,
  u8 eType, 
  Pgno parent
){
  BtreePtrmap *pMap = pBt->pMap;
  if( pgno>=pMap->iFirst ){
    int iEntry = pgno - pMap->iFirst;

    /* Grow the aPtr[] array as required */
    while( iEntry>=pMap->nPtrAlloc ){
      int nNew = pMap->nPtrAlloc ? pMap->nPtrAlloc*2 : 16;
      PtrmapEntry *aNew = (PtrmapEntry*)sqlite3_realloc(
          pMap->aPtr, nNew*sizeof(PtrmapEntry)
      );
      if( aNew==0 ){
        return SQLITE_NOMEM;
      }else{
................................................................................

  return SQLITE_OK;
}

/*
** Open savepoint iSavepoint, if it is not already open.
*/
static int btreePtrmapBegin(BtShared *pBt, int nSvpt){
  BtreePtrmap *pMap = pBt->pMap;
  if( pMap && nSvpt<pMap->nSvpt ){
    int i;
    if( nSvpt>=pMap->nSvptAlloc ){
      int nNew = pMap->nSvptAlloc ? pMap->nSvptAlloc*2 : 16;
      int *aNew = sqlite3_realloc(pMap->aSvpt, sizeof(int) * nNew);
      if( aNew==0 ){
        return SQLITE_NOMEM;
      }else{
................................................................................
  return SQLITE_OK;
}

/*
** Rollback (if op==SAVEPOINT_ROLLBACK) or release (if op==SAVEPOINT_RELEASE)
** savepoint iSvpt.
*/
static void btreePtrmapEnd(BtShared *pBt, int op, int iSvpt){
  BtreePtrmap *pMap = pBt->pMap;
  if( pMap ){
    assert( op==SAVEPOINT_ROLLBACK || op==SAVEPOINT_RELEASE );
    assert( iSvpt>=0 || (iSvpt==-1 && op==SAVEPOINT_ROLLBACK) );
    if( iSvpt<0 ){
      pMap->nSvpt = 0;
      pMap->nRollback = 0;
      memset(pMap->aPtr, 0, sizeof(Pgno) * pMap->nPtrAlloc);
    }else if( iSvpt<pMap->nSvpt ){
      if( op==SAVEPOINT_ROLLBACK ){
        int ii;
        for(ii=pMap->nRollback-1; ii>=pMap->aSvpt[iSvpt]; ii--){
          RollbackEntry *p = &pMap->aRollback[ii];
          PtrmapEntry *pEntry = &pMap->aPtr[p->pgno - pMap->iFirst];
          pEntry->parent = p->parent;
          pEntry->eType = p->eType;
        }
      }
      pMap->nSvpt = iSvpt + (op==SAVEPOINT_ROLLBACK);
      pMap->nRollback = pMap->aSvpt[iSvpt];
    }
  }
}

/*
** This function is called after an UNLOCKED transaction is opened on the
** database. It allocates the BtreePtrmap structure used to track pointers
** to allocated pages and zeroes the nFree/iTrunk fields in the database 
** header on page 1.
*/
static int btreePtrmapAllocate(BtShared *pBt){
  int rc = SQLITE_OK;
  BtreePtrmap *pMap = sqlite3_malloc(sizeof(BtreePtrmap));
  assert( pBt->pMap==0 && sqlite3PagerIsUnlocked(pBt->pPager) );
  if( pMap==0 ){
    rc = SQLITE_NOMEM;
  }else{
    memset(&pBt->pPage1->aData[32], 0, sizeof(u32)*2);
    memset(pMap, 0, sizeof(BtreePtrmap));
    pMap->iFirst = pBt->nPage + 1;
    pBt->pMap = pMap;
  }
  return rc;
}

/*
** Free any BtreePtrmap structure allocated by an earlier call to
** btreePtrmapAllocate().
*/
static void btreePtrmapDelete(BtShared *pBt){
  BtreePtrmap *pMap = pBt->pMap;
  if( pMap ){
    sqlite3_free(pMap->aRollback);
    sqlite3_free(pMap->aPtr);
    sqlite3_free(pMap->aSvpt);
    sqlite3_free(pMap);
    pBt->pMap = 0;
  }
}
#else
# define btreePtrmapAllocate(x) SQLITE_OK
# define btreePtrmapDelete(x) 
# define btreePtrmapBegin(x,y)  SQLITE_OK
# define btreePtrmapEnd(x,y,z) 
#endif

static void releasePage(MemPage *pPage);  /* Forward reference */

/*
***** This routine is used inside of assert() only ****
**
** Verify that the cursor holds the mutex on its BtShared
................................................................................
static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
  DbPage *pDbPage;  /* The pointer map page */
  u8 *pPtrmap;      /* The pointer map data */
  Pgno iPtrmap;     /* The pointer map page number */
  int offset;       /* Offset in pointer map page */
  int rc;           /* Return code from subfunctions */


  if( *pRC ) return;

  assert( sqlite3_mutex_held(pBt->mutex) );
  /* The master-journal page number is never added to a pointer-map page */
  assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );

#ifdef SQLITE_ENABLE_UNLOCKED
  if( pBt->pMap ){
    *pRC = btreePtrmapStore(pBt, key, eType, parent);



    return;
  }
#endif

  assert( pBt->autoVacuum );
  if( key==0 ){
    *pRC = SQLITE_CORRUPT_BKPT;
    return;
  }
  iPtrmap = PTRMAP_PAGENO(pBt, key);
  rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage);
  if( rc!=SQLITE_OK ){
    *pRC = rc;
    return;
  }
  offset = PTRMAP_PTROFFSET(iPtrmap, key);
  if( offset<0 ){
    *pRC = SQLITE_CORRUPT_BKPT;

    goto ptrmap_exit;
  }
  assert( offset <= (int)pBt->usableSize-5 );
  pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);

  if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
    TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent));
    *pRC= rc = sqlite3PagerWrite(pDbPage);
    if( rc==SQLITE_OK ){
      pPtrmap[offset] = eType;
      put4byte(&pPtrmap[offset+1], parent);
    }
  }

ptrmap_exit:
  sqlite3PagerUnref(pDbPage);

}

/*
** Read an entry from the pointer map.
**
** This routine retrieves the pointer map entry for page 'key', writing
** the type and parent page number to *pEType and *pPgno respectively.
................................................................................
}
u32 sqlite3BtreeLastPage(Btree *p){
  assert( sqlite3BtreeHoldsMutex(p) );
  assert( ((p->pBt->nPage)&0x8000000)==0 );
  return btreePagecount(p->pBt);
}













































/*
** Get a page from the pager and initialize it.
**
** If pCur!=0 then the page is being fetched as part of a moveToChild()
** call.  Do additional sanity checking on the page in this case.
** And if the fetch fails, this routine must decrement pCur->iPage.
**
................................................................................
        int exFlag = (p->db->bUnlocked && !ISAUTOVACUUM) ? -1 : (wrflag>1);
        int bSubjInMem = sqlite3TempInMemory(p->db);
        assert( p->db->bUnlocked==0 || wrflag==1 );
        rc = sqlite3PagerBegin(pBt->pPager, exFlag, bSubjInMem);
        if( rc==SQLITE_OK ){
          rc = newDatabase(pBt);
        }
        if( rc==SQLITE_OK && sqlite3PagerIsUnlocked(pBt->pPager) ){
          rc = btreePtrmapAllocate(pBt);
        }
      }
    }
  
    if( rc!=SQLITE_OK ){
      unlockBtreeIfUnused(pBt);
    }
................................................................................
  if( rc==SQLITE_OK && wrflag ){
    /* This call makes sure that the pager has the correct number of
    ** open savepoints. If the second parameter is greater than 0 and
    ** the sub-journal is not already open, then it will be opened here.
    */
    int nSavepoint = p->db->nSavepoint;
    rc = sqlite3PagerOpenSavepoint(pBt->pPager, nSavepoint);
    if( rc==SQLITE_OK && nSavepoint ){
      rc = btreePtrmapBegin(pBt, nSavepoint);
    }
  }

  btreeIntegrity(p);
  sqlite3BtreeLeave(p);
  return rc;
}
................................................................................
  return rc;
}

#else /* ifndef SQLITE_OMIT_AUTOVACUUM */
# define setChildPtrmaps(x) SQLITE_OK
#endif

#ifdef SQLITE_ENABLE_UNLOCKED
/*
** The b-tree handle passed as the only argument is about to commit an
** UNLOCKED transaction. At this point it is guaranteed that this is 
** possible - the wal WRITER lock is held and it is known that there are 
** no conflicts with committed transactions.
*/
static int btreeFixUnlocked(Btree *p){
................................................................................
        nCurrent = MAX(nPage, nHPage);

        for(iPg=pMap->iFirst; iPg<=iLast && rc==SQLITE_OK; iPg++){
          MemPage *pPg = 0;
          Pgno iNew;              /* New page number for pPg */
          PtrmapEntry *pEntry;    /* Pointer map entry for page iPg */

          if( iPg==PENDING_BYTE_PAGE(pBt) ) continue;
          pEntry = &pMap->aPtr[iPg - pMap->iFirst];
          if( pEntry->eType==PTRMAP_FREEPAGE ){
            MemPage *pFree = 0;
            Pgno dummy;
            rc = allocateBtreePage(pBt, &pFree, &dummy, iPg, BTALLOC_EXACT);
            releasePage(pFree);
            assert( rc!=SQLITE_OK || dummy==iPg );
          }else{
            btreeGetPage(pBt, iPg, &pPg, 0);
            assert( sqlite3PagerIswriteable(pPg->pDbPage) );
            assert( sqlite3PagerPageRefcount(pPg->pDbPage)==1 );
            iNew = ++nCurrent;
            if( iNew==PENDING_BYTE_PAGE(pBt) ) iNew = ++nCurrent;
            rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent, iNew, 1);
            releasePageNotNull(pPg);
          }
        }
        sqlite3PagerSetDbsize(pPager, nCurrent);
        assert( nCurrent!=PENDING_BYTE_PAGE(pBt) );

        nFree = get4byte(&p1[36]);
        nFinal = MAX(nCurrent-nFree, nHPage);
        if( nCurrent>PENDING_BYTE_PAGE(pBt) && nFinal<=PENDING_BYTE_PAGE(pBt) ){
          nFinal--;
        }

        for(iPg=nFinal+1; rc==SQLITE_OK && iPg<=nCurrent; iPg++){
          Pgno iNew;              /* New page number for pPg */
          MemPage *pFree;
          PtrmapEntry *pEntry;    /* Pointer map entry for page iPg */

          if( iPg==PENDING_BYTE_PAGE(pBt) ) continue;
          pEntry = &pMap->aPtr[iPg - pMap->iFirst];
          if( pEntry->eType==PTRMAP_FREEPAGE ){
            rc = allocateBtreePage(pBt, &pFree, &iNew, iPg, BTALLOC_EXACT);
            releasePage(pFree);
          }else{
            rc = allocateBtreePage(pBt, &pFree, &iNew, nFinal, BTALLOC_LE);
            assert( rc!=SQLITE_OK || iNew<=nFinal );
            releasePage(pFree);
            if( rc==SQLITE_OK ){
              MemPage *pPg = 0;
              btreeGetPage(pBt, iPg, &pPg, 0);
              rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent,iNew,1);
              releasePage(pPg);
            }
          }
        }
        put4byte(&p1[28], nFinal);
      }
    }
    sqlite3PagerSetDbsize(pPager, nFinal);
  }

  return rc;
}
#endif

/*
** This routine does the first phase of a two-phase commit.  This routine
** causes a rollback journal to be created (if it does not already exist)
** and populated with enough information so that if a power loss occurs
** the database can be restored to its original state by playing back
** the journal.  Then the contents of the journal are flushed out to
................................................................................
int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
  int rc = SQLITE_OK;
  if( p->inTrans==TRANS_WRITE ){
    BtShared *pBt = p->pBt;
    sqlite3BtreeEnter(p);

#ifndef SQLITE_OMIT_AUTOVACUUM



    if( pBt->autoVacuum ){

      assert( ISUNLOCKED==0 );
      rc = autoVacuumCommit(pBt);
      if( rc!=SQLITE_OK ){
        sqlite3BtreeLeave(p);
        return rc;
      }
    }
    if( pBt->bDoTruncate ){
      sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
    }
#endif
    if( rc==SQLITE_OK && ISUNLOCKED ){
      rc = btreeFixUnlocked(p);
    }
    if( rc==SQLITE_OK ){
      rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
    }
    sqlite3BtreeLeave(p);
  }
................................................................................
    /* Set the current transaction state to TRANS_NONE and unlock the 
    ** pager if this call closed the only read or write transaction.  */
    p->inTrans = TRANS_NONE;
    unlockBtreeIfUnused(pBt);
  }

  /* If this was an UNLOCKED transaction, delete the pBt->pMap object */
  btreePtrmapDelete(pBt);
  btreeIntegrity(p);
}

/*
** Commit the transaction currently in progress.
**
** This routine implements the second phase of a 2-phase commit.  The
................................................................................
  assert( pBt->inTransaction==TRANS_WRITE );
  /* At the pager level, a statement transaction is a savepoint with
  ** an index greater than all savepoints created explicitly using
  ** SQL statements. It is illegal to open, release or rollback any
  ** such savepoints while the statement transaction savepoint is active.
  */
  rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement);
  if( rc==SQLITE_OK ){
    rc = btreePtrmapBegin(pBt, iStatement);
  }
  sqlite3BtreeLeave(p);
  return rc;
}

/*
** The second argument to this function, op, is always SAVEPOINT_ROLLBACK
................................................................................
int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
  int rc = SQLITE_OK;
  if( p && p->inTrans==TRANS_WRITE ){
    BtShared *pBt = p->pBt;
    assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );
    assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) );
    sqlite3BtreeEnter(p);
    btreePtrmapEnd(pBt, op, iSavepoint);
    rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint);
    if( rc==SQLITE_OK ){
      if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){
        pBt->nPage = 0;
      }
      rc = newDatabase(pBt);
      pBt->nPage = get4byte(28 + pBt->pPage1->aData);
................................................................................
  assert( eMode==BTALLOC_ANY || (nearby>0 && REQUIRE_PTRMAP ) );
  pPage1 = pBt->pPage1;
  mxPage = btreePagecount(pBt);
  /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36
  ** stores stores the total number of pages on the freelist. */
  n = get4byte(&pPage1->aData[36]);
  testcase( n==mxPage-1 );
  if( ISUNLOCKED==0 && n>=mxPage ){
    return SQLITE_CORRUPT_BKPT;
  }

  /* Ensure page 1 is writable. This function will either change the number
  ** of pages in the free-list or the size of the database file. Since both
  ** of these operations involve modifying page 1 header fields, page 1
  ** will definitely be written by this transaction. If this is an UNLOCKED
................................................................................
    u32 nSearch = 0;   /* Count of the number of search attempts */
    
    /* If eMode==BTALLOC_EXACT and a query of the pointer-map
    ** shows that the page 'nearby' is somewhere on the free-list, then
    ** the entire-list will be searched for that page.
    */
    if( eMode==BTALLOC_EXACT ){
      assert( ISAUTOVACUUM!=ISUNLOCKED );
      if( ISAUTOVACUUM ){
        if( nearby<=mxPage ){
          u8 eType;
          assert( nearby>0 );
          assert( pBt->autoVacuum );
          rc = ptrmapGet(pBt, nearby, &eType, 0);
          if( rc ) return rc;
................................................................................
}

/*
** Return the size of the header added to each page by this module.
*/
int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }

/*
** This function is called to ensure that all locks required to commit the
** current write-transaction to the database file are held. If the db is
** in rollback mode, this means the EXCLUSIVE lock on the database file.
**
** Or, if this is an UNLOCKED transaction on a wal-mode database, the WRITER
** lock on the wal file. In this case this function also checks that the
** UNLOCKED transaction can be safely committed (does not commit with any
** other transaction committed since it was opened).
**
** SQLITE_OK is returned if successful. SQLITE_BUSY if the required locks
** cannot be obtained due to a conflicting lock. If the locks cannot be
** obtained for an UNLOCKED transaction due to a conflict with an already
** committed transaction, SQLITE_BUSY_SNAPSHOT is returned. Otherwise, if
** some other error (OOM, IO, etc.) occurs, the relevant SQLite error code
** is returned.
*/
int sqlite3BtreeExclusiveLock(Btree *p){
  int rc;
  BtShared *pBt = p->pBt;
  assert( p->inTrans==TRANS_WRITE && pBt->pPage1 );
  sqlite3BtreeEnter(p);
  rc = sqlite3PagerExclusiveLock(pBt->pPager, pBt->pPage1->pDbPage);
  sqlite3BtreeLeave(p);
  return rc;
}

Changes to src/btreeInt.h.

660
661
662
663
664
665
666
667
668
669
670
671

672
673
674
675
676
677
678
#ifndef SQLITE_OMIT_AUTOVACUUM
#define ISAUTOVACUUM (pBt->autoVacuum)
#else
#define ISAUTOVACUUM 0
#endif

#ifdef SQLITE_ENABLE_UNLOCKED
# define REQUIRE_PTRMAP (ISAUTOVACUUM || pBt->pMap)
#else
# define REQUIRE_PTRMAP ISAUTOVACUUM
#endif



/*
** This structure is passed around through all the sanity checking routines
** in order to keep track of some global state information.
**
** The aRef[] array is allocated so that there is 1 bit for each page in
** the database. As the integrity-check proceeds, for each page used in







|

|


>







660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
#ifndef SQLITE_OMIT_AUTOVACUUM
#define ISAUTOVACUUM (pBt->autoVacuum)
#else
#define ISAUTOVACUUM 0
#endif

#ifdef SQLITE_ENABLE_UNLOCKED
# define ISUNLOCKED (pBt->pMap!=0)
#else
# define ISUNLOCKED 0
#endif

#define REQUIRE_PTRMAP (ISAUTOVACUUM || ISUNLOCKED)

/*
** This structure is passed around through all the sanity checking routines
** in order to keep track of some global state information.
**
** The aRef[] array is allocated so that there is 1 bit for each page in
** the database. As the integrity-check proceeds, for each page used in

Changes to test/unlocked2.test.

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
...
220
221
222
223
224
225
226
227

































228
229
      ROLLBACK;
      BEGIN UNLOCKED;
        INSERT INTO t1 VALUES(randomblob(1500));
    }
    sql2 {
      INSERT INTO t2 VALUES(8);
    }
    breakpoint
    sql1 {
      COMMIT;
    }
  } {}

  do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
}
................................................................................
      INSERT INTO t2 VALUES(randomblob(1500));
      DELETE FROM t2 WHERE rowid IN (1, 2);
    }

    sql1 COMMIT
  } {}
}


































finish_test








<







 








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


64
65
66
67
68
69
70

71
72
73
74
75
76
77
...
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
      ROLLBACK;
      BEGIN UNLOCKED;
        INSERT INTO t1 VALUES(randomblob(1500));
    }
    sql2 {
      INSERT INTO t2 VALUES(8);
    }

    sql1 {
      COMMIT;
    }
  } {}

  do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
}
................................................................................
      INSERT INTO t2 VALUES(randomblob(1500));
      DELETE FROM t2 WHERE rowid IN (1, 2);
    }

    sql1 COMMIT
  } {}
}

#-------------------------------------------------------------------------
#
do_multiclient_test tn {
  do_test 5.$tn.1 {
    sql1 {
      PRAGMA journal_mode = wal;
      CREATE TABLE t1(x);
      CREATE TABLE t2(x);
      INSERT INTO t1 VALUES(randomblob(1500));
      PRAGMA page_count;
    }
  } {wal 4}

  do_test 5.$tn.2 {
    sql1 {
      BEGIN UNLOCKED;
        INSERT INTO t2 VALUES(randomblob(1500));
        PRAGMA page_count;
    }
  } {5}

  do_test 5.$tn.3 {
    sql2 { 
      DELETE FROM t1;
      PRAGMA freelist_count;
      PRAGMA page_count;
    }
  } {1 4}

  do_test 5.$tn.4 { sql1 COMMIT } {}
  do_test 5.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
}

finish_test

Added test/unlocked3.test.





























































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
# 2015 July 26
#
# 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.
#
#***********************************************************************
#
# Tests for transactions started with BEGIN UNLOCKED. The tests in this
# file focus on testing that deferred page allocation works properly.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
set ::testprefix unlocked3

if {$AUTOVACUUM} { finish_test ; return }

proc create_schema {} {
  db eval {
    PRAGMA journal_mode = wal;

    CREATE TABLE t1(x, y);
    CREATE TABLE t2(x, y);
    CREATE TABLE t3(x, y);
    CREATE TABLE t4(x, y);

    CREATE INDEX i1 ON t1(y, x);
    CREATE INDEX i2 ON t2(y, x);
    CREATE INDEX i3 ON t3(y, x);
    CREATE INDEX i4 ON t4(y, x);
  }
}

proc do_sql_op {iTbl iOp} {
  set db "db$iTbl"

  switch $iOp {
    "i" {
      set sql "
        WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<10)
        INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM cnt;
      "
    }

    "d" {
      set sql "
        DELETE FROM t$iTbl WHERE rowid IN (
          SELECT rowid FROM t$iTbl ORDER BY 1 ASC LIMIT 10
        )
      "
    }

    default {
      error "bad iOp parameter: $iOp"
    }
  }

  $db eval $sql
}


set DBLIST {db1 db2 db3 db4} 

create_schema
foreach {tn oplist} {
  1 {1i   2i   3i   4i} 
  2 {1iii 2iii 3iii 4iii}
  3 {1d   2d   3d   4d} 
  . -----------------------
  4 {1i}
  5 {1d 2i}
  . -----------------------
  6 {1iii 2iii 3iii 4iii}
  7 {1di  2id  3iii 4ddd}
  8 {1iii 2iii 3iii 4iii}
} {
  if {[string range $oplist 0 0]=="-"} {
    reset_db
    create_schema
    continue
  }
  foreach db $DBLIST { sqlite3 $db test.db }

  do_test 1.$tn {
    foreach db $DBLIST { $db eval "BEGIN UNLOCKED" } 

    foreach op $oplist {
      set iTbl [string range $op 0 0]
      foreach char [split [string range $op 1 end] {}] {
        do_sql_op $iTbl $char
      }
    }

    foreach db $DBLIST { $db eval "COMMIT" }
    db eval {PRAGMA integrity_check}
  } {ok}

  foreach db $DBLIST { 
    if {$db=="db2"} breakpoint
    $db close 
  }
}

finish_test