/ Check-in [195f3340]
Login

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

Overview
Comment:Add further tests for the code on this branch. Fix a problem in OOM handling.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | session-retry
Files: files | file ages | folders
SHA1:195f3340ee4d870420e4f58d44300bab5436b920
User & Date: dan 2016-03-31 10:50:26
Context
2016-03-31
15:08
Add another OOM test to this branch. Closed-Leaf check-in: 49763fc3 user: dan tags: session-retry
10:50
Add further tests for the code on this branch. Fix a problem in OOM handling. check-in: 195f3340 user: dan tags: session-retry
02:44
Enhanced comments describing the sessions file format. No changes to code. check-in: 84dc41e2 user: drh tags: session-retry
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/sessionG.test.

57
58
59
60
61
62
63
64















































































































65
66
  }
  execsql { SELECT * FROM t1 } db2
} {2 two 3 three 1 one 5 five 11 eleven}

do_test 1.3 {
  execsql { SELECT * FROM t1 }
} {2 two 3 three 1 one 11 eleven 12 five}
















































































































finish_test









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


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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
  }
  execsql { SELECT * FROM t1 } db2
} {2 two 3 three 1 one 5 five 11 eleven}

do_test 1.3 {
  execsql { SELECT * FROM t1 }
} {2 two 3 three 1 one 11 eleven 12 five}

#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2

do_test 2.1 {
  do_common_sql {
    CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c UNIQUE);
    INSERT INTO t1 VALUES(1, 1, 1);
    INSERT INTO t1 VALUES(2, 2, 2);
    INSERT INTO t1 VALUES(3, 3, 3);
  }
} {}

do_test 2.2.1 {
  # It is not possible to apply the changeset generated by the following
  # SQL, as none of the three updated rows may be updated as part of the
  # first pass.
  do_then_apply_sql {
    UPDATE t1 SET b=0 WHERE a=1;
    UPDATE t1 SET b=1 WHERE a=2;
    UPDATE t1 SET b=2 WHERE a=3;
    UPDATE t1 SET b=3 WHERE a=1;
  }
  db2 eval { SELECT a, b FROM t1 }
} {1 1 2 2 3 3}
do_test 2.2.2 { db eval { SELECT a, b FROM t1 } } {1 3 2 1 3 2}

#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2

do_test 3.1 {
  do_common_sql {
    CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c UNIQUE);
    INSERT INTO t1 VALUES(1, 1, 1);
    INSERT INTO t1 VALUES(2, 2, 2);
    INSERT INTO t1 VALUES(3, 3, 3);
  }
} {}

do_test 3.3 {
  do_then_apply_sql {
    UPDATE t1 SET b=4 WHERE a=3;
    UPDATE t1 SET b=3 WHERE a=2;
    UPDATE t1 SET b=2 WHERE a=1;
  }
  compare_db db db2
} {}

do_test 3.4 {
  do_then_apply_sql {
    UPDATE t1 SET b=1 WHERE a=1;
    UPDATE t1 SET b=2 WHERE a=2;
    UPDATE t1 SET b=3 WHERE a=3;
  }
  compare_db db db2
} {}

#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2

do_test 4.1 {
  do_common_sql {
    CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
    INSERT INTO t1 VALUES(1, 1);
    INSERT INTO t1 VALUES(2, 2);
    INSERT INTO t1 VALUES(3, 3);

    CREATE TABLE t2(a PRIMARY KEY, b UNIQUE);
    INSERT INTO t2 VALUES(1, 1);
    INSERT INTO t2 VALUES(2, 2);
    INSERT INTO t2 VALUES(3, 3);
  }
} {}

do_test 4.2 {
  do_then_apply_sql {
    UPDATE t1 SET b=4 WHERE a=3;
    UPDATE t1 SET b=3 WHERE a=2;
    UPDATE t1 SET b=2 WHERE a=1;

    UPDATE t2 SET b=0 WHERE a=1;
    UPDATE t2 SET b=1 WHERE a=2;
    UPDATE t2 SET b=2 WHERE a=3;
  }
  compare_db db db2
} {}

do_test 4.3 {
  do_then_apply_sql {
    UPDATE t1 SET b=1 WHERE a=1;
    UPDATE t1 SET b=2 WHERE a=2;
    UPDATE t1 SET b=3 WHERE a=3;

    UPDATE t2 SET b=3 WHERE a=3;
    UPDATE t2 SET b=2 WHERE a=2;
    UPDATE t2 SET b=1 WHERE a=1;
  }
  compare_db db db2
} {}

finish_test

Added ext/session/sessionfault2.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
# 2016 March 31
#
# 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 focus of this file is testing the session module.
#

if {![info exists testdir]} {
  set testdir [file join [file dirname [info script]] .. .. test]
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
set testprefix sessionfault2

do_execsql_test 1.0.0 {
  CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
  INSERT INTO t1 VALUES(1, 1);
  INSERT INTO t1 VALUES(2, 2);
  INSERT INTO t1 VALUES(3, 3);

  CREATE TABLE t2(a PRIMARY KEY, b UNIQUE);
  INSERT INTO t2 VALUES(1, 1);
  INSERT INTO t2 VALUES(2, 2);
  INSERT INTO t2 VALUES(3, 3);
}
faultsim_save_and_close

faultsim_restore_and_reopen
do_test 1.0.1 {
  set ::C [changeset_from_sql {
    UPDATE t1 SET b=4 WHERE a=3;
    UPDATE t1 SET b=3 WHERE a=2;
    UPDATE t1 SET b=2 WHERE a=1;
    UPDATE t2 SET b=0 WHERE a=1;
    UPDATE t2 SET b=1 WHERE a=2;
    UPDATE t2 SET b=2 WHERE a=3;
  }]
  set {} {}
} {}

proc xConflict args { return "OMIT" }

do_faultsim_test 1 -faults oom-p* -prep {
  faultsim_restore_and_reopen
} -body {
  sqlite3changeset_apply db $::C xConflict
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  faultsim_integrity_check

  catch { db eval ROLLBACK }
  set res [db eval {
    SELECT * FROM t1;
    SELECT * FROM t2;
  }]

  if {$testrc==0} {
    if {$res != "1 2 2 3 3 4 1 0 2 1 3 2"} { error "data error" }
  } else {
    if {
         $res != "1 2 2 3 3 4 1 0 2 1 3 2"
      && $res != "1 1 2 2 3 3 1 1 2 2 3 3"
    } { error "data error!! $res" }
  }
}

finish_test

Changes to ext/session/sqlite3session.c.

3848
3849
3850
3851
3852
3853
3854










3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866

3867







3868

3869
3870
3871





3872
3873
3874
3875
3876
3877
3878
3879
....
3964
3965
3966
3967
3968
3969
3970

3971
3972
3973
3974
3975
3976
3977
....
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072




4073
4074
4075
4076
4077
4078
4079
      );
    }
  }

  return rc;
}











static int sessionApplyOneWithRetry(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  sqlite3_changeset_iter *pIter,  /* Changeset iterator to read change from */
  SessionApplyCtx *pApply,        /* Apply context */
  int(*xConflict)(void*, int, sqlite3_changeset_iter*),
  void *pCtx                      /* First argument passed to xConflict */
){
  int bReplace = 0;
  int bRetry = 0;
  int rc;

  rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);









  if( rc==SQLITE_OK && bRetry ){

    rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, 0);
  }






  if( bReplace ){
    assert( pIter->op==SQLITE_INSERT );
    rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
    if( rc==SQLITE_OK ){
      rc = sessionBindRow(pIter, 
          sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
      sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
    }
................................................................................
  void *pCtx                      /* First argument passed to xConflict */
){
  int schemaMismatch = 0;
  int rc;                         /* Return code */
  const char *zTab = 0;           /* Name of current table */
  int nTab = 0;                   /* Result of sqlite3Strlen30(zTab) */
  SessionApplyCtx sApply;         /* changeset_apply() context object */


  assert( xConflict!=0 );

  pIter->in.bNoDiscard = 1;
  memset(&sApply, 0, sizeof(sApply));
  sqlite3_mutex_enter(sqlite3_db_mutex(db));
  rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
................................................................................
    /* If there is a schema mismatch on the current table, proceed to the
    ** next change. A log message has already been issued. */
    if( schemaMismatch ) continue;

    rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx);
  }

  if( rc==SQLITE_OK ){
    rc = sessionRetryConstraints(
        db, pIter->bPatchset, zTab, &sApply, xConflict, pCtx
    );
  }

  if( rc==SQLITE_OK ){
    rc = sqlite3changeset_finalize(pIter);
  }else{
    sqlite3changeset_finalize(pIter);
  }





  if( rc==SQLITE_OK ){
    int nFk, notUsed;
    sqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, &notUsed, 0);
    if( nFk!=0 ){
      int res = SQLITE_CHANGESET_ABORT;
      sqlite3_changeset_iter sIter;







>
>
>
>
>
>
>
>
>
>












>

>
>
>
>
>
>
>
|
>
|


>
>
>
>
>
|







 







>







 







<
<
|
<
<
<





>
>
>
>







3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
....
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
....
4080
4081
4082
4083
4084
4085
4086


4087



4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
      );
    }
  }

  return rc;
}

/*
** Attempt to apply the change that the iterator passed as the first argument
** currently points to to the database. If a conflict is encountered, invoke
** the conflict handler callback.
**
** The difference between this function and sessionApplyOne() is that this
** function handles the case where the conflict-handler is invoked and 
** returns SQLITE_CHANGESET_REPLACE - indicating that the change should be
** retried in some manner.
*/
static int sessionApplyOneWithRetry(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  sqlite3_changeset_iter *pIter,  /* Changeset iterator to read change from */
  SessionApplyCtx *pApply,        /* Apply context */
  int(*xConflict)(void*, int, sqlite3_changeset_iter*),
  void *pCtx                      /* First argument passed to xConflict */
){
  int bReplace = 0;
  int bRetry = 0;
  int rc;

  rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);
  assert( rc==SQLITE_OK || (bRetry==0 && bReplace==0) );

  /* If the bRetry flag is set, the change has not been applied due to an
  ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
  ** a row with the correct PK is present in the db, but one or more other
  ** fields do not contain the expected values) and the conflict handler 
  ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation,
  ** but pass NULL as the final argument so that sessionApplyOneOp() ignores
  ** the SQLITE_CHANGESET_DATA problem.  */
  if( bRetry ){
    assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
    rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
  }

  /* If the bReplace flag is set, the change is an INSERT that has not
  ** been performed because the database already contains a row with the
  ** specified primary key and the conflict handler returned
  ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row
  ** before reattempting the INSERT.  */
  else if( bReplace ){
    assert( pIter->op==SQLITE_INSERT );
    rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
    if( rc==SQLITE_OK ){
      rc = sessionBindRow(pIter, 
          sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
      sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
    }
................................................................................
  void *pCtx                      /* First argument passed to xConflict */
){
  int schemaMismatch = 0;
  int rc;                         /* Return code */
  const char *zTab = 0;           /* Name of current table */
  int nTab = 0;                   /* Result of sqlite3Strlen30(zTab) */
  SessionApplyCtx sApply;         /* changeset_apply() context object */
  int bPatchset;

  assert( xConflict!=0 );

  pIter->in.bNoDiscard = 1;
  memset(&sApply, 0, sizeof(sApply));
  sqlite3_mutex_enter(sqlite3_db_mutex(db));
  rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
................................................................................
    /* If there is a schema mismatch on the current table, proceed to the
    ** next change. A log message has already been issued. */
    if( schemaMismatch ) continue;

    rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx);
  }



  bPatchset = pIter->bPatchset;



  if( rc==SQLITE_OK ){
    rc = sqlite3changeset_finalize(pIter);
  }else{
    sqlite3changeset_finalize(pIter);
  }

  if( rc==SQLITE_OK ){
    rc = sessionRetryConstraints(db, bPatchset, zTab, &sApply, xConflict, pCtx);
  }

  if( rc==SQLITE_OK ){
    int nFk, notUsed;
    sqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, &notUsed, 0);
    if( nFk!=0 ){
      int res = SQLITE_CHANGESET_ABORT;
      sqlite3_changeset_iter sIter;