/ Check-in [39cdfa53]
Login

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

Overview
Comment:Remove some unreachable code in sqlite3session.c. Add test cases.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 39cdfa5324ae91bfbbac733b1e3e2d33ca883340
User & Date: dan 2011-03-21 19:41:30
Context
2011-03-22
02:03
Fix a couple typos for consistency in sessions documentation. check-in: 510198f1 user: shaneh tags: sessions
2011-03-21
19:41
Remove some unreachable code in sqlite3session.c. Add test cases. check-in: 39cdfa53 user: dan tags: sessions
17:17
Merge in the sqlite3_db_config() enhancements for enabling and disabling FKs and triggers from trunk. check-in: 2b3c8b9d user: drh tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/session1.test.

184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
    if {![info exists O($key)]} {error "no such option: $key"}
  }
  array set O $args

  sqlite3session S db main
  foreach t $O(-tables) { S attach $t }
  execsql $O(-sql)

  set ::xConflict [list]
  sqlite3changeset_apply db2 [S changeset] xConflict

  set conflicts [list]
  foreach c $O(-conflicts) {
    lappend conflicts $c
  }







<







184
185
186
187
188
189
190

191
192
193
194
195
196
197
    if {![info exists O($key)]} {error "no such option: $key"}
  }
  array set O $args

  sqlite3session S db main
  foreach t $O(-tables) { S attach $t }
  execsql $O(-sql)

  set ::xConflict [list]
  sqlite3changeset_apply db2 [S changeset] xConflict

  set conflicts [list]
  foreach c $O(-conflicts) {
    lappend conflicts $c
  }

Changes to ext/session/session2.test.

146
147
148
149
150
151
152







153
154
155
156
157
158
159

160
161
162
163
164
165
166
167
168
169
...
178
179
180
181
182
183
184

185
186
187
188
189

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
  15 {
    INSERT INTO %T1% VALUES(1, 1);
    INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%;
    INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%;
    INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%;
    INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%;
  }







}

test_reset
do_common_sql {
  CREATE TABLE t1(a PRIMARY KEY, b);
  CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
  CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));

}

foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] {
  do_then_apply_sql $sql
  do_test 2.$tn { compare_db db db2 } {}
}

# The following block of tests is similar to the last, except that the
# session object is recording changes made to an attached database. The
# main database contains a table of the same name as the table being
................................................................................
    CREATE TABLE t1(a, b PRIMARY KEY);
    CREATE TABLE t2(x, y, z);
    CREATE TABLE t3(a);

    CREATE TABLE aux.t1(a PRIMARY KEY, b);
    CREATE TABLE aux.t2(a, b INTEGER PRIMARY KEY);
    CREATE TABLE aux.t3(a, b, c, PRIMARY KEY(a, b));

  }
  execsql {
    CREATE TABLE t1(a PRIMARY KEY, b);
    CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
    CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));

  } db2
} {}

proc xTrace {args} { puts $args }

foreach {tn sql} [
  string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3} $set_of_tests
] {
  do_then_apply_sql $sql aux
  do_test 3.$tn { compare_db db3 db2 } {}
}
catch {db3 close}


#-------------------------------------------------------------------------
# The following tests verify that NULL values in primary key columns are
# handled correctly by the session module.







>
>
>
>
>
>
>







>


|







 







>





>






|


|







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
...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  15 {
    INSERT INTO %T1% VALUES(1, 1);
    INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%;
    INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%;
    INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%;
    INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%;
  }

  16 {
    INSERT INTO %T4% VALUES('abc', 'def');
    INSERT INTO %T4% VALUES('def', 'abc');
  }
  17 { UPDATE %T4% SET b = 1 }
  18 { DELETE FROM %T4% WHERE 1 }
}

test_reset
do_common_sql {
  CREATE TABLE t1(a PRIMARY KEY, b);
  CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
  CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
  CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
}

foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3 %T4% t4} $set_of_tests] {
  do_then_apply_sql $sql
  do_test 2.$tn { compare_db db db2 } {}
}

# The following block of tests is similar to the last, except that the
# session object is recording changes made to an attached database. The
# main database contains a table of the same name as the table being
................................................................................
    CREATE TABLE t1(a, b PRIMARY KEY);
    CREATE TABLE t2(x, y, z);
    CREATE TABLE t3(a);

    CREATE TABLE aux.t1(a PRIMARY KEY, b);
    CREATE TABLE aux.t2(a, b INTEGER PRIMARY KEY);
    CREATE TABLE aux.t3(a, b, c, PRIMARY KEY(a, b));
    CREATE TABLE aux.t4(a, b, PRIMARY KEY(b, a));
  }
  execsql {
    CREATE TABLE t1(a PRIMARY KEY, b);
    CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
    CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
    CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
  } db2
} {}

proc xTrace {args} { puts $args }

foreach {tn sql} [
  string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3 %T4% aux.t4} $set_of_tests
] {
  do_then_apply_sql $sql aux
  do_test 3.$tn { compare_db db2 db3 } {}
}
catch {db3 close}


#-------------------------------------------------------------------------
# The following tests verify that NULL values in primary key columns are
# handled correctly by the session module.

Changes to ext/session/session_common.tcl.

79
80
81
82
83
84
85



86

87
88
89
90
91
92
93
#
proc compare_db {db1 db2} {

  set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
  set lot1 [$db1 eval $sql]
  set lot2 [$db2 eval $sql]




  if {$lot1 != $lot2} { error "databases contain different tables" }


  foreach tbl $lot1 {
    set col1 [list]
    set col2 [list]

    $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
    $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name }







>
>
>
|
>







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#
proc compare_db {db1 db2} {

  set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
  set lot1 [$db1 eval $sql]
  set lot2 [$db2 eval $sql]

  if {$lot1 != $lot2} { 
    puts $lot1
    puts $lot2
    error "databases contain different tables" 
  }

  foreach tbl $lot1 {
    set col1 [list]
    set col2 [list]

    $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
    $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name }

Changes to ext/session/sessionfault.test.

77
78
79
80
81
82
83
84




















































85
  if {$testrc==0} { 
    sqlite3changeset_apply db2 [S changeset] xConflict
    compare_db db db2 
  }
  catch { S delete }
  faultsim_integrity_check
}





















































finish_test








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

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
  if {$testrc==0} { 
    sqlite3changeset_apply db2 [S changeset] xConflict
    compare_db db db2 
  }
  catch { S delete }
  faultsim_integrity_check
}

catch { db close }
catch { db2 close }
forcedelete test.db2 test.db
sqlite3 db2 test.db2
sqlite3 db test.db

proc xConflict {op tbl type args} {
  if { $type=="CONFLICT" || $type=="DATA" } {
    return "REPLACE"
  }
  return "OMIT"
}

do_test 3.0 {
  execsql {
    PRAGMA encoding = 'utf16';
    CREATE TABLE t1(a PRIMARY KEY, b);
    INSERT INTO t1 VALUES(5, 32);
  }
  execsql {
    PRAGMA encoding = 'utf16';
    CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
    INSERT INTO t1 VALUES(1, 2);
    INSERT INTO t1 VALUES(2, 4);
    INSERT INTO t1 VALUES(4, 16);
  } db2
} {}

faultsim_save_and_close
db2 close

do_faultsim_test pagerfault-3 -faults oom-transient -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
  sqlite3 db2 test.db2
  sqlite3session S db main
  S attach t1
  execsql {
    INSERT INTO t1 VALUES(1, 45);
    INSERT INTO t1 VALUES(2, 55);
    INSERT INTO t1 VALUES(3, 55);
    UPDATE t1 SET a = 4 WHERE a = 5;
  }
} -body {
  sqlite3changeset_apply db2 [S changeset] xConflict
} -test {
  catch { S delete }
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  if {$testrc==0} { compare_db db db2 }
}

finish_test

Changes to ext/session/sqlite3session.c.

548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
...
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
....
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
....
1992
1993
1994
1995
1996
1997
1998



































1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028

2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042

2043
2044
2045
2046
2047
2048
2049
....
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
....
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204

2205
2206
2207
2208
2209
2210
2211
....
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289

2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
....
2365
2366
2367
2368
2369
2370
2371

2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
  sqlite3_stmt *pStmt;
  int rc;
  int nByte;
  int nDbCol = 0;
  int nThis;
  int i;
  u8 *pAlloc;
  u8 *pFree = 0;
  char **azCol;
  u8 *abPK;

  assert( pazCol || pabPK );

  nThis = strlen(zThis);
  zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
  if( !zPragma ) return SQLITE_NOMEM;

  rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
  sqlite3_free(zPragma);
................................................................................
    nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
    pAlloc = sqlite3_malloc(nByte);
    if( pAlloc==0 ){
      rc = SQLITE_NOMEM;
    }
  }
  if( rc==SQLITE_OK ){
    pFree = pAlloc;
    if( pazCol ){
      azCol = (char **)pAlloc;
      pAlloc = (u8 *)&azCol[nCol];
    }
    if( pabPK ){
      abPK = (u8 *)pAlloc;
      pAlloc = &abPK[nCol];
    }
    if( pzTab ){
      memcpy(pAlloc, zThis, nThis+1);
      *pzTab = (char *)pAlloc;
      pAlloc += nThis+1;
    }
  
    i = 0;
    while( SQLITE_ROW==sqlite3_step(pStmt) ){
      int nName = sqlite3_column_bytes(pStmt, 1);
      const unsigned char *zName = sqlite3_column_text(pStmt, 1);
      if( zName==0 ) break;
      if( pazCol ){
        memcpy(pAlloc, zName, nName+1);
        azCol[i] = (char *)pAlloc;
        pAlloc += nName+1;
      }
      if( pabPK ) abPK[i] = sqlite3_column_int(pStmt, 5);
      i++;
    }
    rc = sqlite3_reset(pStmt);
  
  }

  /* If successful, populate the output variables. Otherwise, zero them and
  ** free any allocation made. An error code will be returned in this case.
  */
  if( rc==SQLITE_OK ){
    if( pazCol ) *pazCol = (const char **)azCol;
    if( pabPK ) *pabPK = abPK;
  }else{
    if( pazCol ) *pazCol = 0;
    if( pabPK ) *pabPK = 0;
    if( pzTab ) *pzTab = 0;
    sqlite3_free(pFree);
  }
  sqlite3_finalize(pStmt);
  return rc;
}

/*
** This function is only called from within a pre-update handler for a
................................................................................
** Formulate a statement to DELETE a row from database db. Assuming a table
** structure like this:
**
**     CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
**
** The DELETE statement looks like this:
**
**     DELETE FROM x WHERE a = :1 AND c = :3 AND :5 OR (b IS :2 AND d IS :4)
**
** Variable :5 (nCol+1) is a boolean. It should be set to 0 if we require
** matching b and d values, or 1 otherwise. The second case comes up if the
** conflict handler is invoked with NOTFOUND and returns CHANGESET_REPLACE.
**
** If successful, SQLITE_OK is returned and SessionApplyCtx.pDelete is left
** pointing to the prepared version of the SQL statement.
................................................................................

  if( rc==SQLITE_OK ){
    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
  }
  sqlite3_free(buf.aBuf);
  return rc;
}




































/*
** SQL statement pSelect is as generated by the sessionSelectRow() function.
** This function binds the primary key values from the change that changeset
** iterator pIter points to to the SELECT and attempts to seek to the table
** entry. If a row is found, the SELECT statement left pointing at the row 
** and SQLITE_ROW is returned. Otherwise, if no row is found and no error
** has occured, the statement is reset and SQLITE_OK is returned. If an
** error occurs, an SQLite error code is returned.
**
** If the iterator currently points to an INSERT record, bind values from the
** new.* record to the SELECT statement. Or, if it points to a DELETE, bind
** values from the old.* record. If the changeset iterator points to an
** UPDATE, bind values from the new.* record, but use old.* values in place
** of any undefined new.* values.
*/
static int sessionSeekToRow(
  sqlite3 *db,                    /* Database handle */
  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
  u8 *abPK,                       /* Primary key flags array */
  sqlite3_stmt *pSelect           /* SELECT statement from sessionSelectRow() */
){
  int rc = SQLITE_OK;             /* Return code */
  int i;                          /* Used to iterate through table columns */
  int nCol;                       /* Number of columns in table */
  int op;                         /* Changset operation (SQLITE_UPDATE etc.) */
  const char *zDummy;             /* Unused */

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);


  for(i=0; rc==SQLITE_OK && i<nCol; i++){
    if( abPK[i] ){
      sqlite3_value *pVal = 0;
      if( op!=SQLITE_DELETE ){
        rc = sqlite3changeset_new(pIter, i, &pVal);
      }
      if( pVal==0 ){
        rc = sqlite3changeset_old(pIter, i, &pVal);
      }
      if( rc==SQLITE_OK ){
        rc = sqlite3_bind_value(pSelect, i+1, pVal);
      }
    }
  }


  if( rc==SQLITE_OK ){
    rc = sqlite3_step(pSelect);
    if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
  }

  return rc;
................................................................................
  assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
  assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );

  /* Bind the new.* PRIMARY KEY values to the SELECT statement. */
  if( pbReplace ){
    rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
  }else{
    rc = SQLITE_DONE;
  }

  if( rc==SQLITE_ROW ){
    /* There exists another row with the new.* primary key. */
    pIter->pConflict = p->pSelect;
    res = xConflict(pCtx, eType, pIter);
    pIter->pConflict = 0;
    rc = sqlite3_reset(p->pSelect);
  }else{
    /* No other row with the new.* primary key. */
    rc = sqlite3_reset(p->pSelect);
    if( rc==SQLITE_OK ){
      res = xConflict(pCtx, eType+1, pIter);
      if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
    }
  }
................................................................................
  assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
  assert( p->azCol && p->abPK );
  assert( !pbReplace || *pbReplace==0 );

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);

  if( op==SQLITE_DELETE ){
    int i;

    /* Bind values to the DELETE statement. */
    for(i=0; rc==SQLITE_OK && i<nCol; i++){
      sqlite3_value *pVal;
      rc = sqlite3changeset_old(pIter, i, &pVal);
      if( rc==SQLITE_OK ){
        rc = sqlite3_bind_value(p->pDelete, i+1, pVal);
      }
    }

    if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){
      rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
    }
    if( rc!=SQLITE_OK ) return rc;

    sqlite3_step(p->pDelete);
    rc = sqlite3_reset(p->pDelete);
................................................................................
      ** Otherwise, if there is no primary key match, it is a NOTFOUND. */

      rc = sessionConflictHandler(
          SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
      );

    }else if( rc==SQLITE_CONSTRAINT ){
      /* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if
      ** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT 
      ** otherwise. */
      int bPKChange = 0;

      /* Check if the PK has been modified. */
      rc = SQLITE_OK;
      for(i=0; i<nCol && rc==SQLITE_OK; i++){
        if( p->abPK[i] ){
          sqlite3_value *pNew;
          rc = sqlite3changeset_new(pIter, i, &pNew);
          if( rc==SQLITE_OK && pNew ){
            bPKChange = 1;
            break;
          }
        }
      }

      rc = sessionConflictHandler(SQLITE_CHANGESET_CONFLICT, 
          p, pIter, xConflict, pCtx, (bPKChange ? pbReplace : 0)
      );
    }

  }else{
    int i;
    assert( op==SQLITE_INSERT );
    for(i=0; rc==SQLITE_OK && i<nCol; i++){
      sqlite3_value *pVal;
      rc = sqlite3changeset_new(pIter, i, &pVal);
      if( rc==SQLITE_OK ){
        rc = sqlite3_bind_value(p->pInsert, i+1, pVal);
      }
    }

    if( rc!=SQLITE_OK ) return rc;

    sqlite3_step(p->pInsert);
    rc = sqlite3_reset(p->pInsert);
    if( rc==SQLITE_CONSTRAINT && xConflict ){
      rc = sessionConflictHandler(
          SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
      );
    }
  }

  return rc;
................................................................................
    rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, &bRetry);

    if( rc==SQLITE_OK && bRetry ){
      rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, 0);
    }

    if( bReplace ){

      rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
      if( rc==SQLITE_OK ){
        int i;
        for(i=0; i<sApply.nCol; i++){
          if( sApply.abPK[i] ){
            sqlite3_value *pVal;
            rc = sqlite3changeset_new(pIter, i, &pVal);
            if( rc==SQLITE_OK && pVal==0 ){
              rc = sqlite3changeset_old(pIter, i, &pVal);
            }
            if( rc==SQLITE_OK ){
              rc = sqlite3_bind_value(sApply.pDelete, i+1, pVal);
            }
          }
        }
        sqlite3_bind_int(sApply.pDelete, sApply.nCol+1, 1);
      }
      if( rc==SQLITE_OK ){
        sqlite3_step(sApply.pDelete);
        rc = sqlite3_reset(sApply.pDelete);
      }
      if( rc==SQLITE_OK ){







<
|


|







 







<
<
|
|
<
<
|
|
<











<
|
|
|
<
|










|
|

|
|

|







 







|







 







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











|
<
|
<








<





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







 







|








|







 







<


<
<
<
<
<
<
<
>







 







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|




<

<
<
<
<
<
<
<
>




|







 







>


<
|
<
<
|
<
<
<
<
<
<
<
<







548
549
550
551
552
553
554

555
556
557
558
559
560
561
562
563
564
565
...
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
....
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
....
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037

2038

2039
2040
2041
2042
2043
2044
2045
2046

2047
2048
2049
2050
2051

2052
2053
2054












2055
2056
2057
2058
2059
2060
2061
2062
....
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
....
2201
2202
2203
2204
2205
2206
2207

2208
2209







2210
2211
2212
2213
2214
2215
2216
2217
....
2256
2257
2258
2259
2260
2261
2262
2263

















2264
2265
2266
2267
2268
2269

2270







2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
....
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356

2357


2358








2359
2360
2361
2362
2363
2364
2365
  sqlite3_stmt *pStmt;
  int rc;
  int nByte;
  int nDbCol = 0;
  int nThis;
  int i;
  u8 *pAlloc;

  char **azCol = 0;
  u8 *abPK;

  assert( pazCol && pabPK );

  nThis = strlen(zThis);
  zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
  if( !zPragma ) return SQLITE_NOMEM;

  rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
  sqlite3_free(zPragma);
................................................................................
    nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
    pAlloc = sqlite3_malloc(nByte);
    if( pAlloc==0 ){
      rc = SQLITE_NOMEM;
    }
  }
  if( rc==SQLITE_OK ){


    azCol = (char **)pAlloc;
    pAlloc = (u8 *)&azCol[nCol];


    abPK = (u8 *)pAlloc;
    pAlloc = &abPK[nCol];

    if( pzTab ){
      memcpy(pAlloc, zThis, nThis+1);
      *pzTab = (char *)pAlloc;
      pAlloc += nThis+1;
    }
  
    i = 0;
    while( SQLITE_ROW==sqlite3_step(pStmt) ){
      int nName = sqlite3_column_bytes(pStmt, 1);
      const unsigned char *zName = sqlite3_column_text(pStmt, 1);
      if( zName==0 ) break;

      memcpy(pAlloc, zName, nName+1);
      azCol[i] = (char *)pAlloc;
      pAlloc += nName+1;

      abPK[i] = sqlite3_column_int(pStmt, 5);
      i++;
    }
    rc = sqlite3_reset(pStmt);
  
  }

  /* If successful, populate the output variables. Otherwise, zero them and
  ** free any allocation made. An error code will be returned in this case.
  */
  if( rc==SQLITE_OK ){
    *pazCol = (const char **)azCol;
    *pabPK = abPK;
  }else{
    *pazCol = 0;
    *pabPK = 0;
    if( pzTab ) *pzTab = 0;
    sqlite3_free(azCol);
  }
  sqlite3_finalize(pStmt);
  return rc;
}

/*
** This function is only called from within a pre-update handler for a
................................................................................
** Formulate a statement to DELETE a row from database db. Assuming a table
** structure like this:
**
**     CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
**
** The DELETE statement looks like this:
**
**     DELETE FROM x WHERE a = :1 AND c = :3 AND (:5 OR b IS :2 AND d IS :4)
**
** Variable :5 (nCol+1) is a boolean. It should be set to 0 if we require
** matching b and d values, or 1 otherwise. The second case comes up if the
** conflict handler is invoked with NOTFOUND and returns CHANGESET_REPLACE.
**
** If successful, SQLITE_OK is returned and SessionApplyCtx.pDelete is left
** pointing to the prepared version of the SQL statement.
................................................................................

  if( rc==SQLITE_OK ){
    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
  }
  sqlite3_free(buf.aBuf);
  return rc;
}

/*
** Iterator pIter must point to an SQLITE_INSERT entry. This function 
** transfers new.* values from the current iterator entry to statement
** pStmt. The table being inserted into has nCol columns.
**
** New.* value $i 0 from the iterator is bound to variable ($i+1) of 
** statement pStmt. If parameter abPK is NULL, all values from 0 to (nCol-1)
** are transfered to the statement. Otherwise, if abPK is not NULL, it points
** to an array nCol elements in size. In this case only those values for 
** which abPK[$i] is true are read from the iterator and bound to the 
** statement.
**
** An SQLite error code is returned if an error occurs. Otherwise, SQLITE_OK.
*/
static int sessionBindValues(
  sqlite3_changeset_iter *pIter,  /* Iterator to read values from */
  int(*xIterValue)(sqlite3_changeset_iter *, int, sqlite3_value **),
  int nCol,                       /* Number of columns */
  u8 *abPK,                       /* If not NULL, bind only if true */
  sqlite3_stmt *pStmt             /* Bind values to this statement */
){
  int i;
  int rc = SQLITE_OK;
  for(i=0; rc==SQLITE_OK && i<nCol; i++){
    if( !abPK || abPK[i] ){
      sqlite3_value *pVal;
      rc = xIterValue(pIter, i, &pVal);
      if( rc==SQLITE_OK ){
        rc = sqlite3_bind_value(pStmt, i+1, pVal);
      }
    }
  }
  return rc;
}

/*
** SQL statement pSelect is as generated by the sessionSelectRow() function.
** This function binds the primary key values from the change that changeset
** iterator pIter points to to the SELECT and attempts to seek to the table
** entry. If a row is found, the SELECT statement left pointing at the row 
** and SQLITE_ROW is returned. Otherwise, if no row is found and no error
** has occured, the statement is reset and SQLITE_OK is returned. If an
** error occurs, an SQLite error code is returned.
**
** If the iterator currently points to an INSERT record, bind values from the
** new.* record to the SELECT statement. Or, if it points to a DELETE or

** UPDATE, bind values from the old.* record. 

*/
static int sessionSeekToRow(
  sqlite3 *db,                    /* Database handle */
  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
  u8 *abPK,                       /* Primary key flags array */
  sqlite3_stmt *pSelect           /* SELECT statement from sessionSelectRow() */
){
  int rc = SQLITE_OK;             /* Return code */

  int nCol;                       /* Number of columns in table */
  int op;                         /* Changset operation (SQLITE_UPDATE etc.) */
  const char *zDummy;             /* Unused */

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);

  rc = sessionBindValues(pIter, 
      op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old,
      nCol, abPK, pSelect












  );

  if( rc==SQLITE_OK ){
    rc = sqlite3_step(pSelect);
    if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
  }

  return rc;
................................................................................
  assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
  assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );

  /* Bind the new.* PRIMARY KEY values to the SELECT statement. */
  if( pbReplace ){
    rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
  }else{
    rc = SQLITE_OK;
  }

  if( rc==SQLITE_ROW ){
    /* There exists another row with the new.* primary key. */
    pIter->pConflict = p->pSelect;
    res = xConflict(pCtx, eType, pIter);
    pIter->pConflict = 0;
    rc = sqlite3_reset(p->pSelect);
  }else if( rc==SQLITE_OK ){
    /* No other row with the new.* primary key. */
    rc = sqlite3_reset(p->pSelect);
    if( rc==SQLITE_OK ){
      res = xConflict(pCtx, eType+1, pIter);
      if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
    }
  }
................................................................................
  assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
  assert( p->azCol && p->abPK );
  assert( !pbReplace || *pbReplace==0 );

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);

  if( op==SQLITE_DELETE ){


    /* Bind values to the DELETE statement. */







    rc = sessionBindValues(pIter, sqlite3changeset_old, nCol, 0, p->pDelete);
    if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){
      rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
    }
    if( rc!=SQLITE_OK ) return rc;

    sqlite3_step(p->pDelete);
    rc = sqlite3_reset(p->pDelete);
................................................................................
      ** Otherwise, if there is no primary key match, it is a NOTFOUND. */

      rc = sessionConflictHandler(
          SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
      );

    }else if( rc==SQLITE_CONSTRAINT ){
      /* This is always a CONSTRAINT conflict. */

















      rc = sessionConflictHandler(
          SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0
      );
    }

  }else{

    assert( op==SQLITE_INSERT );







    rc = sessionBindValues(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
    if( rc!=SQLITE_OK ) return rc;

    sqlite3_step(p->pInsert);
    rc = sqlite3_reset(p->pInsert);
    if( rc==SQLITE_CONSTRAINT ){
      rc = sessionConflictHandler(
          SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
      );
    }
  }

  return rc;
................................................................................
    rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, &bRetry);

    if( rc==SQLITE_OK && bRetry ){
      rc = sessionApplyOneOp(pIter, &sApply, 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 = sessionBindValues(pIter, 


            sqlite3changeset_new, sApply.nCol, sApply.abPK, sApply.pDelete);








        sqlite3_bind_int(sApply.pDelete, sApply.nCol+1, 1);
      }
      if( rc==SQLITE_OK ){
        sqlite3_step(sApply.pDelete);
        rc = sqlite3_reset(sApply.pDelete);
      }
      if( rc==SQLITE_OK ){