/ Check-in [1d9468d2]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

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

Overview
Comment:Add a new row type to RBU (a peer of insert, update and delete) - "delete then insert".
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 1d9468d2427d2c9b7240b364554ac85a0b62fa44
User & Date: dan 2016-03-08 15:52:43
Context
2016-03-08
17:44
Modify the memjournal.c code to make it a bit smaller. Closed-Leaf check-in: d99ac415 user: dan tags: memjournal-exp
16:02
Remove an unused goto label in the RBU extension. check-in: 2179a106 user: drh tags: trunk
15:52
Add a new row type to RBU (a peer of insert, update and delete) - "delete then insert". check-in: 1d9468d2 user: dan tags: trunk
15:47
Add the SQLITE_DEFAULT_SYNCHRONOUS and SQLITE_DEFAULT_WAL_SYNCHRONOUS compile-time options. Automatically switch to the WAL_SYNCHRONOUS setting when first opening a WAL-mode database if the synchronous setting has not been previously set by the application. check-in: 5a847a67 user: drh tags: trunk
2016-03-07
17:39
Add a new row type to RBU (a peer of insert, update and delete) - "delete then insert". Closed-Leaf check-in: 169311c8 user: dan tags: rbu-delete-then-insert
Changes
Hide Diffs Unified Diffs Show Whitespace Changes Patch

Added ext/rbu/rbuC.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
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
# 2016 March 7
#
# 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 RBU focused on the REPLACE operation (rbu_control column
# contains integer value 2).
#

source [file join [file dirname [info script]] rbu_common.tcl]
set ::testprefix rbuC

#-------------------------------------------------------------------------
# This test is actually of an UPDATE directive. Just to establish that
# these work with UNIQUE indexes before preceding to REPLACE.
#
do_execsql_test 1.0 {
  CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
  INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
}

forcedelete rbu.db
do_execsql_test 1.1 {
  ATTACH 'rbu.db' AS rbu;
  CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
  INSERT INTO data_t1 VALUES(1, 'a', 'b', 'c', '.xxx');
}

do_test 1.2 {
  step_rbu test.db rbu.db
} {SQLITE_DONE}

do_execsql_test 1.3 {
  SELECT * FROM t1
} {
  1 a b c
}

#-------------------------------------------------------------------------
#
foreach {tn schema} {
  1 {
    CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
    CREATE INDEX t1a ON t1(a);
  }
  2 {
    CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE);
    CREATE INDEX t1a ON t1(a);
  }
  3 {
    CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE) WITHOUT ROWID;
    CREATE INDEX t1a ON t1(a);
  }
} {
  reset_db
  forcedelete rbu.db
  execsql $schema

  do_execsql_test 2.$tn.0 {
    INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
    INSERT INTO t1 VALUES(2, 'b', 'c', 'd');
    INSERT INTO t1 VALUES(3, 'c', 'd', 'e');
  }
  
  do_execsql_test 2.$tn.1 {
    ATTACH 'rbu.db' AS rbu;
    CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
    INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
    INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
    INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
  }
  
  do_test 2.$tn.2 {
    step_rbu test.db rbu.db
  } {SQLITE_DONE}
  
  do_execsql_test 2.$tn.3 {
    SELECT * FROM t1 ORDER BY i
  } {
    1 1 2 3
    2 b c d
    3 c d e
    4 d e f
  }
  
  integrity_check 2.$tn.4
}

foreach {tn schema} {
  1 {
    CREATE TABLE t1(a, b, c UNIQUE);
    CREATE INDEX t1a ON t1(a);
  }

  2 {
    CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
  }
} {
  if {$tn==2} { ifcapable !fts5 break }
  reset_db
  forcedelete rbu.db
  execsql $schema

  do_execsql_test 3.$tn.0 {
    INSERT INTO t1 VALUES('a', 'b', 'c');
    INSERT INTO t1 VALUES('b', 'c', 'd');
    INSERT INTO t1 VALUES('c', 'd', 'e');
  }
  
  do_execsql_test 3.$tn.1 {
    ATTACH 'rbu.db' AS rbu;
    CREATE TABLE rbu.data_t1(rbu_rowid, a, b, c, rbu_control);
    INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
    INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
    INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
  }
  
  do_test 3.$tn.2 {
    step_rbu test.db rbu.db
  } {SQLITE_DONE}
  
  do_execsql_test 3.$tn.3 {
    SELECT rowid, * FROM t1 ORDER BY 1
  } {
    1 1 2 3
    2 b c d
    3 c d e
    4 d e f
  }
  
  integrity_check 3.$tn.4
}



finish_test

Changes to ext/rbu/sqlite3rbu.c.

276
277
278
279
280
281
282

283
284
285
286

287
288
289
290
291
292
293
....
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915

1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
....
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
....
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516

2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
....
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569

2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588

2589
2590

2591
2592
2593
2594
2595
2596
2597
....
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
....
2631
2632
2633
2634
2635
2636
2637



































2638
2639
2640
2641
2642
2643

2644
2645
2646
2647
2648
2649
2650

/*
** Within the RBU_STAGE_OAL stage, each call to sqlite3rbu_step() performs
** one of the following operations.
*/
#define RBU_INSERT     1          /* Insert on a main table b-tree */
#define RBU_DELETE     2          /* Delete a row from a main table b-tree */

#define RBU_IDX_DELETE 3          /* Delete a row from an aux. index b-tree */
#define RBU_IDX_INSERT 4          /* Insert on an aux. index b-tree */
#define RBU_UPDATE     5          /* Update a row in a main table b-tree */



/*
** A single step of an incremental checkpoint - frame iWalFrame of the wal
** file should be copied to page iDbPage of the database file.
*/
struct RbuFrame {
  u32 iDbPage;
................................................................................
          zSql = sqlite3_mprintf(
              "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' ORDER BY %s%s",
              zCollist, p->zStateDb, pIter->zDataTbl,
              zCollist, zLimit
          );
        }else{
          zSql = sqlite3_mprintf(
              "SELECT %s, rbu_control FROM '%q' "
              "WHERE typeof(rbu_control)='integer' AND rbu_control!=1 "
              "UNION ALL "
              "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "

              "ORDER BY %s%s",
              zCollist, pIter->zDataTbl, 
              zCollist, p->zStateDb, pIter->zDataTbl, 
              zCollist, zLimit
          );
        }
        p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, zSql);
      }

      sqlite3_free(zImposterCols);
................................................................................
            , (pIter->eType==RBU_PK_EXTERNAL ? ", 0 AS rbu_rowid" : "")
            , pIter->zDataTbl
        );

        rbuMPrintfExec(p, p->dbMain,
            "CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" "
            "BEGIN "
            "  SELECT rbu_tmp_insert(2, %s);"
            "END;"

            "CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" "
            "BEGIN "
            "  SELECT rbu_tmp_insert(2, %s);"
            "END;"

            "CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" "
            "BEGIN "
            "  SELECT rbu_tmp_insert(3, %s);"
            "END;",
            zWrite, zTbl, zOldlist,
            zWrite, zTbl, zOldlist,
            zWrite, zTbl, zNewlist
        );

        if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){
................................................................................
static int rbuStepType(sqlite3rbu *p, const char **pzMask){
  int iCol = p->objiter.nCol;     /* Index of rbu_control column */
  int res = 0;                    /* Return value */

  switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
    case SQLITE_INTEGER: {
      int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
      if( iVal==0 ){
        res = RBU_INSERT;
      }else if( iVal==1 ){
        res = RBU_DELETE;
      }else if( iVal==2 ){

        res = RBU_IDX_DELETE;
      }else if( iVal==3 ){
        res = RBU_IDX_INSERT;
      }
      break;
    }

    case SQLITE_TEXT: {
      const unsigned char *z = sqlite3_column_text(p->objiter.pSelect, iCol);
      if( z==0 ){
................................................................................
  assert( 0==sqlite3_stricmp(zName, zCol) );
}
#else
# define assertColumnName(x,y,z)
#endif

/*
** This function does the work for an sqlite3rbu_step() call.
**
** The object-iterator (p->objiter) currently points to a valid object,
** and the input cursor (p->objiter.pSelect) currently points to a valid
** input row. Perform whatever processing is required and return.
**
** If no  error occurs, SQLITE_OK is returned. Otherwise, an error code
** and message is left in the RBU handle and a copy of the error code
** returned.
*/
static int rbuStep(sqlite3rbu *p){

  RbuObjIter *pIter = &p->objiter;
  const char *zMask = 0;
  int i;
  int eType = rbuStepType(p, &zMask);

  if( eType ){
    assert( eType!=RBU_UPDATE || pIter->zIdx==0 );

    if( pIter->zIdx==0 && eType==RBU_IDX_DELETE ){
      rbuBadControlError(p);
    }
    else if( 
        eType==RBU_INSERT 
     || eType==RBU_DELETE
     || eType==RBU_IDX_DELETE 
     || eType==RBU_IDX_INSERT
    ){
      sqlite3_value *pVal;
      sqlite3_stmt *pWriter;


      assert( eType!=RBU_UPDATE );

      assert( eType!=RBU_DELETE || pIter->zIdx==0 );

      if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
        pWriter = pIter->pDelete;
      }else{
        pWriter = pIter->pInsert;
      }
................................................................................
        ** to write a NULL into the IPK column. That is not permitted.  */
        if( eType==RBU_INSERT 
         && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i] 
         && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
        ){
          p->rc = SQLITE_MISMATCH;
          p->zErrmsg = sqlite3_mprintf("datatype mismatch");
          goto step_out;
        }

        if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
          continue;
        }

        pVal = sqlite3_column_value(pIter->pSelect, i);
        p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
        if( p->rc ) goto step_out;
      }
      if( pIter->zIdx==0
       && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) 
      ){
        /* For a virtual table, or a table with no primary key, the 
        ** SELECT statement is:
        **
................................................................................
        pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
        p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
      }
      if( p->rc==SQLITE_OK ){
        sqlite3_step(pWriter);
        p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
      }



































    }else{
      sqlite3_value *pVal;
      sqlite3_stmt *pUpdate = 0;
      assert( eType==RBU_UPDATE );
      rbuGetUpdateStmt(p, pIter, zMask, &pUpdate);
      if( pUpdate ){

        for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){
          char c = zMask[pIter->aiSrcOrder[i]];
          pVal = sqlite3_column_value(pIter->pSelect, i);
          if( pIter->abTblPk[i] || c!='.' ){
            p->rc = sqlite3_bind_value(pUpdate, i+1, pVal);
          }
        }







>
|
|
<

>







 







|
<

|
>

|
|







 







|




|




|







 







|
|
<
|
<
>
|
<
|







 







|
|
|
<
<
<
<
<
<

<
>

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

<
>







 







|








|







 







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





>







276
277
278
279
280
281
282
283
284
285

286
287
288
289
290
291
292
293
294
....
1906
1907
1908
1909
1910
1911
1912
1913

1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
....
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
....
2506
2507
2508
2509
2510
2511
2512
2513
2514

2515

2516
2517

2518
2519
2520
2521
2522
2523
2524
2525
....
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560






2561

2562
2563
















2564
2565
2566
2567

2568
2569
2570
2571
2572
2573
2574
2575
....
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
....
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664

/*
** Within the RBU_STAGE_OAL stage, each call to sqlite3rbu_step() performs
** one of the following operations.
*/
#define RBU_INSERT     1          /* Insert on a main table b-tree */
#define RBU_DELETE     2          /* Delete a row from a main table b-tree */
#define RBU_REPLACE    3          /* Delete and then insert a row */
#define RBU_IDX_DELETE 4          /* Delete a row from an aux. index b-tree */
#define RBU_IDX_INSERT 5          /* Insert on an aux. index b-tree */


#define RBU_UPDATE     6          /* Update a row in a main table b-tree */

/*
** A single step of an incremental checkpoint - frame iWalFrame of the wal
** file should be copied to page iDbPage of the database file.
*/
struct RbuFrame {
  u32 iDbPage;
................................................................................
          zSql = sqlite3_mprintf(
              "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' ORDER BY %s%s",
              zCollist, p->zStateDb, pIter->zDataTbl,
              zCollist, zLimit
          );
        }else{
          zSql = sqlite3_mprintf(
              "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "

              "UNION ALL "
              "SELECT %s, rbu_control FROM '%q' "
              "WHERE typeof(rbu_control)='integer' AND rbu_control!=1 "
              "ORDER BY %s%s",
              zCollist, p->zStateDb, pIter->zDataTbl, 
              zCollist, pIter->zDataTbl, 
              zCollist, zLimit
          );
        }
        p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, zSql);
      }

      sqlite3_free(zImposterCols);
................................................................................
            , (pIter->eType==RBU_PK_EXTERNAL ? ", 0 AS rbu_rowid" : "")
            , pIter->zDataTbl
        );

        rbuMPrintfExec(p, p->dbMain,
            "CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" "
            "BEGIN "
            "  SELECT rbu_tmp_insert(3, %s);"
            "END;"

            "CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" "
            "BEGIN "
            "  SELECT rbu_tmp_insert(3, %s);"
            "END;"

            "CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" "
            "BEGIN "
            "  SELECT rbu_tmp_insert(4, %s);"
            "END;",
            zWrite, zTbl, zOldlist,
            zWrite, zTbl, zOldlist,
            zWrite, zTbl, zNewlist
        );

        if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){
................................................................................
static int rbuStepType(sqlite3rbu *p, const char **pzMask){
  int iCol = p->objiter.nCol;     /* Index of rbu_control column */
  int res = 0;                    /* Return value */

  switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
    case SQLITE_INTEGER: {
      int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
      switch( iVal ){
        case 0: res = RBU_INSERT;     break;

        case 1: res = RBU_DELETE;     break;

        case 2: res = RBU_REPLACE;    break;
        case 3: res = RBU_IDX_DELETE; break;

        case 4: res = RBU_IDX_INSERT; break;
      }
      break;
    }

    case SQLITE_TEXT: {
      const unsigned char *z = sqlite3_column_text(p->objiter.pSelect, iCol);
      if( z==0 ){
................................................................................
  assert( 0==sqlite3_stricmp(zName, zCol) );
}
#else
# define assertColumnName(x,y,z)
#endif

/*
** Argument eType must be one of RBU_INSERT, RBU_DELETE, RBU_IDX_INSERT or
** RBU_IDX_DELETE. This function performs the work of a single
** sqlite3rbu_step() call for the type of operation specified by eType.






*/

static void rbuStepOneOp(sqlite3rbu *p, int eType){
  RbuObjIter *pIter = &p->objiter;
















  sqlite3_value *pVal;
  sqlite3_stmt *pWriter;
  int i;


  assert( p->rc==SQLITE_OK );
  assert( eType!=RBU_DELETE || pIter->zIdx==0 );

  if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
    pWriter = pIter->pDelete;
  }else{
    pWriter = pIter->pInsert;
  }
................................................................................
    ** to write a NULL into the IPK column. That is not permitted.  */
    if( eType==RBU_INSERT 
     && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i] 
     && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
    ){
      p->rc = SQLITE_MISMATCH;
      p->zErrmsg = sqlite3_mprintf("datatype mismatch");
      return;
    }

    if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
      continue;
    }

    pVal = sqlite3_column_value(pIter->pSelect, i);
    p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
    if( p->rc ) return;
  }
  if( pIter->zIdx==0
   && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) 
  ){
    /* For a virtual table, or a table with no primary key, the 
    ** SELECT statement is:
    **
................................................................................
    pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
    p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
  }
  if( p->rc==SQLITE_OK ){
    sqlite3_step(pWriter);
    p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
  }
}

/*
** This function does the work for an sqlite3rbu_step() call.
**
** The object-iterator (p->objiter) currently points to a valid object,
** and the input cursor (p->objiter.pSelect) currently points to a valid
** input row. Perform whatever processing is required and return.
**
** If no  error occurs, SQLITE_OK is returned. Otherwise, an error code
** and message is left in the RBU handle and a copy of the error code
** returned.
*/
static int rbuStep(sqlite3rbu *p){
  RbuObjIter *pIter = &p->objiter;
  const char *zMask = 0;
  int eType = rbuStepType(p, &zMask);

  if( eType ){
    assert( eType==RBU_INSERT     || eType==RBU_DELETE
         || eType==RBU_REPLACE    || eType==RBU_IDX_DELETE
         || eType==RBU_IDX_INSERT || eType==RBU_UPDATE
    );
    assert( eType!=RBU_UPDATE || pIter->zIdx==0 );

    if( pIter->zIdx==0 && eType==RBU_IDX_DELETE ){
      rbuBadControlError(p);
    }
    else if( eType==RBU_REPLACE ){
      if( pIter->zIdx==0 ) rbuStepOneOp(p, RBU_DELETE);
      if( p->rc==SQLITE_OK ) rbuStepOneOp(p, RBU_INSERT);
    }
    else if( eType!=RBU_UPDATE ){
      rbuStepOneOp(p, eType);
    }
    else{
      sqlite3_value *pVal;
      sqlite3_stmt *pUpdate = 0;
      assert( eType==RBU_UPDATE );
      rbuGetUpdateStmt(p, pIter, zMask, &pUpdate);
      if( pUpdate ){
        int i;
        for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){
          char c = zMask[pIter->aiSrcOrder[i]];
          pVal = sqlite3_column_value(pIter->pSelect, i);
          if( pIter->abTblPk[i] || c!='.' ){
            p->rc = sqlite3_bind_value(pUpdate, i+1, pVal);
          }
        }