SQLite4
Check-in [9e00823074]
Not logged in

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

Overview
Comment:Add sub-transactions to btree module.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 9e00823074163c12c9cef8ea61d29136fa857b2e
User & Date: dan 2013-10-30 18:37:28
Context
2013-10-30
19:57
Btree fixes related to multiple client tests. check-in: 58f7282211 user: dan tags: trunk
18:37
Add sub-transactions to btree module. check-in: 9e00823074 user: dan tags: trunk
2013-10-29
17:46
Add code to prevent database writers from overwriting portions of the log that might be required by present or future database readers or recoverers. check-in: 407a82adbf user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to lsm-test/README.

29
30
31
32
33
34
35

36
37
38
39
              clients are still running) whereas lsmtest2.c tests recovery
              from a system or power failure.

  lsmtest9.c: More data tests. These focus on testing that calling
              lsm_work(nMerge=1) to compact the database does not corrupt it.
              In other words, that databases containing block-redirects
              can be read and written.












>




29
30
31
32
33
34
35
36
37
38
39
40
              clients are still running) whereas lsmtest2.c tests recovery
              from a system or power failure.

  lsmtest9.c: More data tests. These focus on testing that calling
              lsm_work(nMerge=1) to compact the database does not corrupt it.
              In other words, that databases containing block-redirects
              can be read and written.





Changes to src/bt.h.

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
** Begin(): If (L>=iLevel), this is a no-op. Otherwise, open the read and/or
**          write transactions required to set L to iLevel.
**
** Commit(): If (L<=iLevel), this is a no-op. Otherwise, close as many
**           transactions as required to set L to iLevel. Commit any write
**           transactions closed by this action.
**
** Rollback(): If (L<=iLevel), this is a no-op. Otherwise, close as many
**             transactions as required to set L to iLevel. Roll back any 
**             write transactions closed by this action.
**
** Revert(): Revert(i) is the same as Rollback(i) except that if i>=2, then
**           following the rollback the innermost nested write transaction
**           is also rolled back (but not closed).
**
**    SAVEPOINT one;              -- Begin(2)
**      SAVEPOINT two;            -- Begin(3)
**      ROLLBACK TO one;          -- Revert(2)
**      SAVEPOINT two;            -- Begin(3)
**        SAVEPOINT three;        -- Begin(4)
**        ROLLBACK TO three;      -- Revert(4)
**        RELEASE three;          -- Commit(3)
**    RELEASE one;                -- Commit(1)
**
** The TransactionLevel() method returns the current transaction level.
*/
int sqlite4BtBegin(bt_db*, int iLevel);
int sqlite4BtCommit(bt_db*, int iLevel);
int sqlite4BtRevert(bt_db*, int iLevel);
int sqlite4BtRollback(bt_db*, int iLevel);
int sqlite4BtTransactionLevel(bt_db*);

/*
** Open and close a database cursor.
*/
int sqlite4BtCsrOpen(bt_db*, int nExtra, bt_cursor **ppCsr);







|
|
|
|
<
<
<



|


|







<







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
** Begin(): If (L>=iLevel), this is a no-op. Otherwise, open the read and/or
**          write transactions required to set L to iLevel.
**
** Commit(): If (L<=iLevel), this is a no-op. Otherwise, close as many
**           transactions as required to set L to iLevel. Commit any write
**           transactions closed by this action.
**
** Rollback(): If (L<=iLevel), this is a no-op. Otherwise, rollback and 
**             close as many transactions as required to set L to iLevel. 
**             Then, if iLevel is greater than or equal to 2, rollback
**             (but do not close) the innermost remaining sub-transaction.



**
**    SAVEPOINT one;              -- Begin(2)
**      SAVEPOINT two;            -- Begin(3)
**      ROLLBACK TO one;          -- Rollback(2)
**      SAVEPOINT two;            -- Begin(3)
**        SAVEPOINT three;        -- Begin(4)
**        ROLLBACK TO three;      -- Rollback(5)
**        RELEASE three;          -- Commit(3)
**    RELEASE one;                -- Commit(1)
**
** The TransactionLevel() method returns the current transaction level.
*/
int sqlite4BtBegin(bt_db*, int iLevel);
int sqlite4BtCommit(bt_db*, int iLevel);

int sqlite4BtRollback(bt_db*, int iLevel);
int sqlite4BtTransactionLevel(bt_db*);

/*
** Open and close a database cursor.
*/
int sqlite4BtCsrOpen(bt_db*, int nExtra, bt_cursor **ppCsr);

Changes to src/bt_pager.c.

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
..
80
81
82
83
84
85
86


87
88
89
90
91
92
93
...
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
...
382
383
384
385
386
387
388


389
390
391
392
393
394
395
...
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464

465
466
467
468
469
470
471
...
476
477
478
479
480
481
482
483
484
485
486








































487
488
489
490
491
492
493
...
500
501
502
503
504
505
506






507
508
509
510
511
512
513
...
531
532
533
534
535
536
537
538




539

540



541


542
543
544

545
546
547
548
549
550
551
...
621
622
623
624
625
626
627








628
629
630
631
632
633
634
635
636
637
638
639
640

/* By default auto-checkpoint is 1000 */
#define BT_DEFAULT_AUTOCKPT 1000

typedef struct BtPageHash BtPageHash;
typedef struct BtDbhdr BtDbhdr;




/*
** Hash table for page references currently in memory. Manipulated using
** the following functions:
**
**     btHashAdd()
**     btHashRemove()
**     btHashSearch()
................................................................................
**     btHashClear()
*/
struct BtPageHash {
  int nEntry;                     /* Number of entries in hash table */
  int nHash;                      /* Size of aHash[] array */
  BtPage **aHash;                 /* Hash array */
};


















struct BtPage {
  BtPager *pPager;                /* Pager object that owns this page handle */
  u32 pgno;                       /* Current page number */
  int nRef;                       /* Number of references to this page */
  int flags;                      /* Mask of BTPAGE_XXX flags */
  u8 *aData;                      /* Pointer to current data */
  BtPage *pNextHash;              /* Next entry with same hash key */
  BtPage *pNextDirty;             /* Next page in BtPager.pDirty list */

};

/*
** Candidate values for BtPage.flags
*/
#define BT_PAGE_DIRTY 0x0001      /* Set for pages in BtPager.pDirty list */

................................................................................
  int nFile;                      /* Length of string zFile in bytes */
  BtPageHash hash;                /* Hash table */
  BtPage *pDirty;                 /* List of all dirty pages */
  int nTotalRef;                  /* Total number of outstanding page refs */
  int nAutoCkpt;                  /* Auto-checkpoint when log is this large */
  int bDoAutoCkpt;                /* Do auto-checkpoint after next unlock */
  BtDbhdr dbhdr;


};


/**************************************************************************
** Interface to BtPageHash object.
*/

................................................................................

static int btCleanup(BtLock *pLock){
  BtPager *p = (BtPager*)pLock;
  int rc = sqlite4BtLogClose(p->pLog, 1);
  p->pLog = 0;
  return rc;
}
























































/*

















































** Close a pager database handle.
*/
int sqlite4BtPagerClose(BtPager *p){
  int i;
  int rc;

  rc = sqlite4BtLockDisconnect((BtLock*)p, btCheckpoint, btCleanup);

  if( p->btl.pFd ){
    p->btl.pVfs->xClose(p->btl.pFd);
  }


  btPurgeCache(p);
  sqlite4BtLogClose(p->pLog, 0);
  sqlite4_free(p->btl.pEnv, p->zFile);

  sqlite4_free(p->btl.pEnv, p);
  return rc;
}

/*
** Return a pointer to the nExtra bytes of space allocated by PagerNew().
*/
................................................................................
** Commit the current write transaction to disk.
*/
static int btCommitTransaction(BtPager *p){
  int rc = SQLITE4_OK;
  BtPage *pPg;
  BtPage *pNext;
  assert( p->iTransactionLevel>=2 );



  for(pPg=p->pDirty; pPg; pPg=pNext){
    pNext = pPg->pNextDirty;
    pPg->flags &= ~(BT_PAGE_DIRTY);
    pPg->pNextDirty = 0;
    if( rc==SQLITE4_OK ){
      int bCommit = (pNext==0);
................................................................................
  }

  *ppPg = pRet;
  return rc;
}

/*
** Roll back the current write transaction.
*/
static int btRollbackTransaction(BtPager *p){
  int rc = SQLITE4_OK;
  BtPage *pPg;
  BtPage *pNext;

  assert( p->iTransactionLevel>=2 );


  /* Load the old db header from disk */
  rc = p->btl.pVfs->xRead(p->btl.pFd, 0, &p->dbhdr, sizeof(p->dbhdr));

  /* Loop through all dirty pages in memory. Discard those with nRef==0.
  ** Reload data from disk for any others.  */
  for(pPg=p->pDirty; pPg; pPg=pNext){
................................................................................
      btHashRemove(p, pPg);
      btFreePage(p, pPg);
    }else if( rc==SQLITE4_OK && (pPg->pgno<=p->dbhdr.nPg) ){
      rc = btLoadPageData(p, pPg);
    }
  }
  p->pDirty = 0;
  sqlite4BtLogSnapshotEndWrite(p->pLog);

  return rc;
}









































/*
** Transactions. These methods are more or less the same as their 
** counterparts in bt.h.
*/
int sqlite4BtPagerBegin(BtPager *p, int iLevel){
  int rc = SQLITE4_OK;
................................................................................
    }

    /* Open a write transaction if one is required */
    if( rc==SQLITE4_OK && p->iTransactionLevel<2 && iLevel>=2 ){
      rc = btOpenWriteTransaction(p);
    }







    if( rc==SQLITE4_OK ){
      assert( p->iTransactionLevel>=1 && iLevel>=p->iTransactionLevel );
      p->iTransactionLevel = iLevel;
    }
  }

  return rc;
................................................................................
}

int sqlite4BtPagerRollback(BtPager *p, int iLevel){
  int rc = SQLITE4_OK;

  assert( p->btl.pFd );
  if( p->iTransactionLevel>=iLevel ){
    assert( iLevel<=1 );          /* TODO: Fix this! */




    if( p->iTransactionLevel>=2 ){

      rc = btRollbackTransaction(p);



    }


    p->iTransactionLevel = iLevel;
    if( iLevel==0 ){
      rc = btCloseReadTransaction(p);

    }
  }

  return rc;
}

int sqlite4BtPagerRevert(BtPager *p, int iLevel){
................................................................................
    pRet->nRef++;
  }
  *ppPg = pRet;
  return rc;
}

int sqlite4BtPageWrite(BtPage *pPg){








  if( (pPg->flags & BT_PAGE_DIRTY)==0 ){
    pPg->flags |= BT_PAGE_DIRTY;
    pPg->pNextDirty = pPg->pPager->pDirty;
    pPg->pPager->pDirty = pPg;
  }
  return SQLITE4_OK;
}

/*
** Decrement the refcount on page pPg. Also, indicate that page pPg is
** no longer in use.
*/
int sqlite4BtPageTrim(BtPage *pPg){







>
>
>







 







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









>







 







>
>







 








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

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



<








>



>







 







>
>







 







|







>







 







<



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







 







>
>
>
>
>
>







 







<
>
>
>
>

>
|
>
>
>
|
>
>


|
>







 







>
>
>
>
>
>
>
>





|







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
...
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
264
265
266
267
268
269
270
271
272
273
274
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
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
...
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
...
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
...
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
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
...
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
...
707
708
709
710
711
712
713

714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
...
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834

/* By default auto-checkpoint is 1000 */
#define BT_DEFAULT_AUTOCKPT 1000

typedef struct BtPageHash BtPageHash;
typedef struct BtDbhdr BtDbhdr;

typedef struct BtSavepoint BtSavepoint;
typedef struct BtSavepage BtSavepage;

/*
** Hash table for page references currently in memory. Manipulated using
** the following functions:
**
**     btHashAdd()
**     btHashRemove()
**     btHashSearch()
................................................................................
**     btHashClear()
*/
struct BtPageHash {
  int nEntry;                     /* Number of entries in hash table */
  int nHash;                      /* Size of aHash[] array */
  BtPage **aHash;                 /* Hash array */
};

/*
** There is one object of this type for each open sub-transaction. Stored
** in the BtPager.aSavepoint[] array.
*/
struct BtSavepoint {
  int iLevel;                     /* Transaction level value (always >2) */
  BtSavepage *pSavepage;          /* First in linked list of saved data */
};

struct BtSavepage {
  BtPage *pPg;                    /* Pointer to page this object belongs to */
  u8 *aData;                      /* Saved data */
  BtSavepage *pNext;              /* Next saved page in the same savepoint */
  int iSavepoint;                 /* Transaction number of savepoint */
  BtSavepage *pNextSavepage;      /* Next saved page on the same BtPage */
};

struct BtPage {
  BtPager *pPager;                /* Pager object that owns this page handle */
  u32 pgno;                       /* Current page number */
  int nRef;                       /* Number of references to this page */
  int flags;                      /* Mask of BTPAGE_XXX flags */
  u8 *aData;                      /* Pointer to current data */
  BtPage *pNextHash;              /* Next entry with same hash key */
  BtPage *pNextDirty;             /* Next page in BtPager.pDirty list */
  BtSavepage *pSavepage;          /* List of saved page images */
};

/*
** Candidate values for BtPage.flags
*/
#define BT_PAGE_DIRTY 0x0001      /* Set for pages in BtPager.pDirty list */

................................................................................
  int nFile;                      /* Length of string zFile in bytes */
  BtPageHash hash;                /* Hash table */
  BtPage *pDirty;                 /* List of all dirty pages */
  int nTotalRef;                  /* Total number of outstanding page refs */
  int nAutoCkpt;                  /* Auto-checkpoint when log is this large */
  int bDoAutoCkpt;                /* Do auto-checkpoint after next unlock */
  BtDbhdr dbhdr;
  BtSavepoint *aSavepoint;        /* Savepoint array */
  int nSavepoint;                 /* Number of entries in aSavepoint array */
};


/**************************************************************************
** Interface to BtPageHash object.
*/

................................................................................

static int btCleanup(BtLock *pLock){
  BtPager *p = (BtPager*)pLock;
  int rc = sqlite4BtLogClose(p->pLog, 1);
  p->pLog = 0;
  return rc;
}

static int btOpenSavepoints(BtPager *p, int iLevel){
  int rc = SQLITE4_OK;            /* Return code */
  int nReq = iLevel - 2;          /* Required number of savepoints */

  if( nReq>p->nSavepoint ){
    BtSavepoint *aNew;
    int nByte = (nReq * sizeof(BtSavepoint));

    aNew = sqlite4_realloc(p->btl.pEnv, p->aSavepoint, nByte);
    if( aNew ){
      int i;
      for(i=p->nSavepoint; i<nReq; i++){
        aNew[i].pSavepage = 0;
        aNew[i].iLevel = i+3;
      }
      p->aSavepoint = aNew;
      p->nSavepoint = nReq;
    }else{
      rc = btErrorBkpt(SQLITE4_NOMEM);
    }
  }

  return rc;
}

static void btCloseSavepoints(BtPager *p, int iLevel){
  int nReq = MAX(iLevel - 2, 0);
  if( nReq<p->nSavepoint ){
    int i;
    for(i=p->nSavepoint-1; i>=nReq; i--){
      BtSavepoint *pSavepoint = &p->aSavepoint[i];
      BtSavepage *pSavepage;
      BtSavepage *pNext;

      /* Loop through each of the BtSavepage objects associated with this
      ** savepoint. Detach them from the BtPage objects and free all
      ** allocated memory.  */
      for(pSavepage=pSavepoint->pSavepage; pSavepage; pSavepage=pNext){
        BtPage *pPg = pSavepage->pPg;
        pNext = pSavepage->pNext;

        /* Detach the BtSavepage from its BtPage object */
        assert( pSavepage==pPg->pSavepage );
        pPg->pSavepage = pSavepage->pNextSavepage;

        /* Free associated memory allocations */
        sqlite4_free(p->btl.pEnv, pSavepage->aData);
        sqlite4_free(p->btl.pEnv, pSavepage);
      }
    }

    p->nSavepoint = nReq;
  }
}

/*
** If it has not already been added, add page pPg to the innermost 
** savepoint.
*/
static int btAddToSavepoint(BtPager *p, BtPage *pPg){
  int rc = SQLITE4_OK;
  BtSavepage *pSavepage;
  int iLevel = p->iTransactionLevel;

  /* Check if there is already a BtSavepage for this page associated with
  ** the innermost open savepoint. If so, this function becomes a no-op.  */
  for(pSavepage=pPg->pSavepage; pSavepage; pSavepage=pSavepage->pNextSavepage){
    if( pSavepage->iSavepoint==iLevel ) break;
  }

  if( pSavepage==0 ){
    pSavepage = sqlite4_malloc(p->btl.pEnv, sizeof(BtSavepage));
    if( pSavepage==0 ){
      rc = btErrorBkpt(SQLITE4_NOMEM);
    }else{
      memset(pSavepage, 0, sizeof(BtSavepage));
    }

    if( rc==SQLITE4_OK && (1 || (pPg->flags & BT_PAGE_DIRTY)) ){
      pSavepage->aData = (u8*)sqlite4_malloc(p->btl.pEnv, p->dbhdr.pgsz);
      if( pSavepage->aData==0 ){
        sqlite4_free(p->btl.pEnv, pSavepage);
        rc = btErrorBkpt(SQLITE4_NOMEM);
      }else{
        memcpy(pSavepage->aData, pPg->aData, p->dbhdr.pgsz);
      }
    }

    if( rc==SQLITE4_OK ){
      pSavepage->pPg = pPg;
      pSavepage->iSavepoint = iLevel;

      pSavepage->pNextSavepage = pPg->pSavepage;
      pPg->pSavepage = pSavepage;

      assert( p->aSavepoint[iLevel-3].iLevel==iLevel ); 
      pSavepage->pNext = p->aSavepoint[iLevel-3].pSavepage;
      p->aSavepoint[iLevel-3].pSavepage = pSavepage;
    }
  }

  return rc;
}

/*
** Close a pager database handle.
*/
int sqlite4BtPagerClose(BtPager *p){

  int rc;

  rc = sqlite4BtLockDisconnect((BtLock*)p, btCheckpoint, btCleanup);

  if( p->btl.pFd ){
    p->btl.pVfs->xClose(p->btl.pFd);
  }

  btCloseSavepoints(p, 0);
  btPurgeCache(p);
  sqlite4BtLogClose(p->pLog, 0);
  sqlite4_free(p->btl.pEnv, p->zFile);
  sqlite4_free(p->btl.pEnv, p->aSavepoint);
  sqlite4_free(p->btl.pEnv, p);
  return rc;
}

/*
** Return a pointer to the nExtra bytes of space allocated by PagerNew().
*/
................................................................................
** Commit the current write transaction to disk.
*/
static int btCommitTransaction(BtPager *p){
  int rc = SQLITE4_OK;
  BtPage *pPg;
  BtPage *pNext;
  assert( p->iTransactionLevel>=2 );

  btCloseSavepoints(p, 2);

  for(pPg=p->pDirty; pPg; pPg=pNext){
    pNext = pPg->pNextDirty;
    pPg->flags &= ~(BT_PAGE_DIRTY);
    pPg->pNextDirty = 0;
    if( rc==SQLITE4_OK ){
      int bCommit = (pNext==0);
................................................................................
  }

  *ppPg = pRet;
  return rc;
}

/*
** Roll back, but do not close, the current write transaction. 
*/
static int btRollbackTransaction(BtPager *p){
  int rc = SQLITE4_OK;
  BtPage *pPg;
  BtPage *pNext;

  assert( p->iTransactionLevel>=2 );
  btCloseSavepoints(p, 2);

  /* Load the old db header from disk */
  rc = p->btl.pVfs->xRead(p->btl.pFd, 0, &p->dbhdr, sizeof(p->dbhdr));

  /* Loop through all dirty pages in memory. Discard those with nRef==0.
  ** Reload data from disk for any others.  */
  for(pPg=p->pDirty; pPg; pPg=pNext){
................................................................................
      btHashRemove(p, pPg);
      btFreePage(p, pPg);
    }else if( rc==SQLITE4_OK && (pPg->pgno<=p->dbhdr.nPg) ){
      rc = btLoadPageData(p, pPg);
    }
  }
  p->pDirty = 0;


  return rc;
}

static int btRollbackSavepoints(BtPager *p, int iLevel){
  int nReq = iLevel - 2;

  assert( nReq>=1 );
  if( nReq<=p->nSavepoint ){
    int i;
    for(i=p->nSavepoint-1; i>=(nReq-1); i--){
      BtSavepoint *pSavepoint = &p->aSavepoint[i];
      BtSavepage *pSavepage;
      BtSavepage *pNext;

      /* Loop through each of the BtSavepage objects associated with this
      ** savepoint. Detach them from the BtPage objects and free all
      ** allocated memory.  */
      for(pSavepage=pSavepoint->pSavepage; pSavepage; pSavepage=pNext){
        BtPage *pPg = pSavepage->pPg;
        pNext = pSavepage->pNext;

        /* Detach the BtSavepage from its BtPage object */
        assert( pSavepage==pPg->pSavepage );
        pPg->pSavepage = pSavepage->pNextSavepage;

        /* Restore the page data */
        memcpy(pPg->aData, pSavepage->aData, p->dbhdr.pgsz);

        /* Free associated memory allocations */
        assert( pSavepage->aData ); /* temp */
        sqlite4_free(p->btl.pEnv, pSavepage->aData);
        sqlite4_free(p->btl.pEnv, pSavepage);
      }

      pSavepoint->pSavepage = 0;
    }

    p->nSavepoint = nReq;
  }

  return SQLITE4_OK;
}

/*
** Transactions. These methods are more or less the same as their 
** counterparts in bt.h.
*/
int sqlite4BtPagerBegin(BtPager *p, int iLevel){
  int rc = SQLITE4_OK;
................................................................................
    }

    /* Open a write transaction if one is required */
    if( rc==SQLITE4_OK && p->iTransactionLevel<2 && iLevel>=2 ){
      rc = btOpenWriteTransaction(p);
    }

    /* Open any required savepoints */
    if( rc==SQLITE4_OK ){
      rc = btOpenSavepoints(p, iLevel);
    }

    /* If nothing has gone wrong, update BtPager.iTransactionLevel */
    if( rc==SQLITE4_OK ){
      assert( p->iTransactionLevel>=1 && iLevel>=p->iTransactionLevel );
      p->iTransactionLevel = iLevel;
    }
  }

  return rc;
................................................................................
}

int sqlite4BtPagerRollback(BtPager *p, int iLevel){
  int rc = SQLITE4_OK;

  assert( p->btl.pFd );
  if( p->iTransactionLevel>=iLevel ){


    /* If a write transaction is open and the requested level is 2 or
    ** lower, rollback the outermost write transaction. If the requested
    ** level is less than 2, also drop the WRITER lock.  */
    if( p->iTransactionLevel>=2 ){
      if( iLevel<=2 ){
        rc = btRollbackTransaction(p);
        if( iLevel<2 ) sqlite4BtLogSnapshotEndWrite(p->pLog);
      }else{
        rc = btRollbackSavepoints(p, iLevel);
      }
    }

    p->iTransactionLevel = iLevel;
    if( iLevel==0 ){
      int rc2 = btCloseReadTransaction(p);
      if( rc==SQLITE4_OK ) rc2 = rc;
    }
  }

  return rc;
}

int sqlite4BtPagerRevert(BtPager *p, int iLevel){
................................................................................
    pRet->nRef++;
  }
  *ppPg = pRet;
  return rc;
}

int sqlite4BtPageWrite(BtPage *pPg){
  int rc = SQLITE4_OK;
  BtPager *p = pPg->pPager;

  /* If there are savepoints open, add this page to the innermost savepoint */
  if( p->nSavepoint>0 ){
    rc = btAddToSavepoint(p, pPg);
  }

  if( (pPg->flags & BT_PAGE_DIRTY)==0 ){
    pPg->flags |= BT_PAGE_DIRTY;
    pPg->pNextDirty = pPg->pPager->pDirty;
    pPg->pPager->pDirty = pPg;
  }
  return rc;
}

/*
** Decrement the refcount on page pPg. Also, indicate that page pPg is
** no longer in use.
*/
int sqlite4BtPageTrim(BtPage *pPg){

Changes to src/kvbt.c.

73
74
75
76
77
78
79

80
81
82
83
84
85
86

87
88
89
90
91
92
93
...
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  pKVStore->iTransLevel = sqlite4BtTransactionLevel(p->pDb);
  return rc;
}

/*
** Revert a transaction back to what it was when it started.
*/

static int btRevert(KVStore *pKVStore, int iLevel){
  KVBt *p = (KVBt *)pKVStore;
  int rc;
  rc = sqlite4BtRevert(p->pDb, iLevel);
  pKVStore->iTransLevel = sqlite4BtTransactionLevel(p->pDb);
  return rc;
}


/*
** Implementation of the xReplace(X, aKey, nKey, aData, nData) method.
*/
static int btReplace(
  KVStore *pKVStore,
  const KVByteArray *aKey, KVSize nKey,
................................................................................
    btData,                       /* xData */
    btReset,                      /* xReset */
    btCloseCursor,                /* xCloseCursor */
    btBegin,                      /* xBegin */
    btCommitPhaseOne,             /* xCommitPhaseOne */
    btCommitPhaseTwo,             /* xCommitPhaseTwo */
    btRollback,                   /* xRollback */
    btRevert,                     /* xRevert */
    btClose,                      /* xClose */
    btControl,                    /* xControl */
    btGetMeta,                    /* xGetMeta */
    btPutMeta,                    /* xPutMeta */
    btGetMethod                   /* xGetMethod */
  };








>







>







 







|







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
  pKVStore->iTransLevel = sqlite4BtTransactionLevel(p->pDb);
  return rc;
}

/*
** Revert a transaction back to what it was when it started.
*/
#if 0
static int btRevert(KVStore *pKVStore, int iLevel){
  KVBt *p = (KVBt *)pKVStore;
  int rc;
  rc = sqlite4BtRevert(p->pDb, iLevel);
  pKVStore->iTransLevel = sqlite4BtTransactionLevel(p->pDb);
  return rc;
}
#endif

/*
** Implementation of the xReplace(X, aKey, nKey, aData, nData) method.
*/
static int btReplace(
  KVStore *pKVStore,
  const KVByteArray *aKey, KVSize nKey,
................................................................................
    btData,                       /* xData */
    btReset,                      /* xReset */
    btCloseCursor,                /* xCloseCursor */
    btBegin,                      /* xBegin */
    btCommitPhaseOne,             /* xCommitPhaseOne */
    btCommitPhaseTwo,             /* xCommitPhaseTwo */
    btRollback,                   /* xRollback */
    0,                            /* xRevert */
    btClose,                      /* xClose */
    btControl,                    /* xControl */
    btGetMeta,                    /* xGetMeta */
    btPutMeta,                    /* xPutMeta */
    btGetMethod                   /* xGetMethod */
  };

Added test/simple4.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
# 2013 September 24
#
# 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.
#
#***********************************************************************
# The tests in this file were used while developing the SQLite 4 code. 
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix simple3

db close
forcedelete test.db
sqlite4 db file:test.db?kv=bt

do_execsql_test 1.0 {
  CREATE TABLE t1(a, b);
  INSERT INTO t1 VALUES(1, 2);
  INSERT INTO t1 VALUES(3, 4);
}

do_execsql_test 1.1 {
  BEGIN;
    INSERT INTO t1 VALUES(5, 6);
    SAVEPOINT one;
      INSERT INTO t1 VALUES(7, 8);
}

do_execsql_test 1.2 { SELECT * FROM t1 }                   {1 2 3 4 5 6 7 8}
do_execsql_test 1.3 { ROLLBACK TO one; SELECT * FROM t1; } {1 2 3 4 5 6}
do_execsql_test 1.4 { ROLLBACK; SELECT * FROM t1; }        {1 2 3 4}

finish_test