/ Check-in [a438fa6c]
Login

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

Overview
Comment:Improve test coverage of ota code a bit.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | ota-update
Files: files | file ages | folders
SHA1:a438fa6c9ad2fb1d78ac747172d07455d6381387
User & Date: dan 2015-02-17 20:49:42
Context
2015-02-18
17:40
Fix a problem with OTA updates in the presence of database readers. check-in: 144bb29f user: dan tags: ota-update
2015-02-17
20:49
Improve test coverage of ota code a bit. check-in: a438fa6c user: dan tags: ota-update
2015-02-16
21:13
Add extra tests and fixes for ota. check-in: e0b71519 user: dan tags: ota-update
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/ota/otafault.test.

    13     13   if {![info exists testdir]} {
    14     14     set testdir [file join [file dirname [info script]] .. .. test]
    15     15   }
    16     16   source $testdir/tester.tcl
    17     17   source $testdir/malloc_common.tcl
    18     18   set ::testprefix otafault
    19     19   
    20         -do_test 1.1 {
    21         -  forcedelete ota.db
    22         -  execsql {
    23         -    PRAGMA encoding = utf16;
    24         -    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
    25         -    CREATE INDEX t1cb ON t1(c, b);
    26         -    INSERT INTO t1 VALUES(1, 1, 1);
    27         -    INSERT INTO t1 VALUES(2, 2, 2);
    28         -    INSERT INTO t1 VALUES(3, 3, 3);
    29         -    CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
    30         -    CREATE INDEX t2cb ON t1(c, b);
    31         -    INSERT INTO t2 VALUES('a', 'a', 'a');
    32         -    INSERT INTO t2 VALUES('b', 'b', 'b');
    33         -    INSERT INTO t2 VALUES('c', 'c', 'c');
    34         -
    35         -    ATTACH 'ota.db' AS ota;
    36         -    CREATE TABLE ota.data_t1(a, b, c, ota_control);
    37         -    CREATE TABLE ota.data_t2(a, b, c, ota_control);
    38         -
    39         -    INSERT INTO data_t1 VALUES(2, NULL, NULL, 1);
    40         -    INSERT INTO data_t1 VALUES(3, 'three', NULL, '.x.');
    41         -    INSERT INTO data_t1 VALUES(4, 4, 4, 0);
    42         -
    43         -    INSERT INTO data_t2 VALUES('b', NULL, NULL, 1);
    44         -    INSERT INTO data_t2 VALUES('c', 'see', NULL, '.x.');
    45         -    INSERT INTO data_t2 VALUES('d', 'd', 'd', 0);
    46         -  }
    47         -  db close
    48         -
    49         -  forcecopy test.db test.db.bak
    50         -  forcecopy ota.db ota.db.bak
    51         -} {}
    52         -
    53         -sqlite3_shutdown
    54         -set lookaside_config [sqlite3_config_lookaside 0 0]
    55         -sqlite3_initialize
    56         -autoinstall_test_functions
    57         -
    58         -foreach {tn f reslist} {
    59         -  1 oom-tra*  {
    60         -    {0 SQLITE_DONE} 
    61         -    {1 {SQLITE_NOMEM - out of memory}} 
    62         -    {1 SQLITE_NOMEM} 
    63         -    {1 SQLITE_IOERR_NOMEM} 
    64         -    {1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
    65         -  }
    66         -  2 ioerr-*  {
    67         -    {0 SQLITE_DONE} 
    68         -    {1 {SQLITE_IOERR - disk I/O error}}
    69         -    {1 SQLITE_IOERR}
    70         -    {1 SQLITE_IOERR_WRITE}
    71         -    {1 SQLITE_IOERR_READ}
    72         -    {1 SQLITE_IOERR_FSYNC}
    73         -    {1 {SQLITE_ERROR - SQL logic error or missing database}}
    74         -    {1 {SQLITE_ERROR - unable to open database: ota.db}}
    75         -    {1 {SQLITE_IOERR - unable to open database: ota.db}}
    76         -  }
    77         -} {
    78         -  do_faultsim_test 2 -faults $::f -prep {
    79         -    catch { db close }
    80         -    forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
    81         -    forcecopy test.db.bak test.db
    82         -    forcecopy ota.db.bak  ota.db
    83         -  } -body {
    84         -    sqlite3ota ota test.db ota.db
    85         -    while {[ota step]=="SQLITE_OK"} {}
    86         -    ota close
    87         -  } -test {
    88         -    faultsim_test_result {*}$::reslist
    89         -    if {$testrc==0} {
    90         -      sqlite3 db test.db
    91         -      faultsim_integrity_check
    92         -      set res [db eval {
    93         -        SELECT * FROM t1 UNION ALL SELECT * FROM t2;
    94         -      }]
    95         -      set expected [list {*}{
    96         -        1 1 1   3 three 3   4 4 4
    97         -        a a a   c see c     d d d
    98         -      }]
    99         -  
   100         -      if {$res != $expected} {
   101         -        puts ""
   102         -        puts "res: $res"
   103         -        puts "exp: $expected"
   104         -        error "data not as expected!"
   105         -      }
   106         -    }
   107         -  }
   108         -}
   109         -
   110         -catch {db close}
   111         -sqlite3_shutdown
   112         -sqlite3_config_lookaside {*}$lookaside_config
   113         -sqlite3_initialize
   114         -autoinstall_test_functions
   115         -
   116     20   proc copy_if_exists {src target} {
   117     21     if {[file exists $src]} {
   118     22       forcecopy $src $target
   119     23     } else {
   120     24       forcedelete $target
   121     25     }
   122     26   }
   123     27   
   124         -for {set iStep 0} {$iStep<=21} {incr iStep} {
           28  +foreach {tn2 setup sql expect} {
           29  +  1 {
           30  +    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
           31  +    CREATE INDEX t1cb ON t1(c, b);
           32  +    INSERT INTO t1 VALUES(1, 1, 1);
           33  +    INSERT INTO t1 VALUES(2, 2, 2);
           34  +    INSERT INTO t1 VALUES(3, 3, 3);
           35  +
           36  +    CREATE TABLE ota.data_t1(a, b, c, ota_control);
           37  +    INSERT INTO data_t1 VALUES(2, NULL, NULL, 1);
           38  +    INSERT INTO data_t1 VALUES(3, 'three', NULL, '.x.');
           39  +    INSERT INTO data_t1 VALUES(4, 4, 4, 0);
           40  +  } {SELECT * FROM t1} {1 1 1   3 three 3   4 4 4}
           41  +
           42  +  2 {
           43  +    CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
           44  +    CREATE INDEX t2cb ON t2(c, b);
           45  +    INSERT INTO t2 VALUES('a', 'a', 'a');
           46  +    INSERT INTO t2 VALUES('b', 'b', 'b');
           47  +    INSERT INTO t2 VALUES('c', 'c', 'c');
           48  +
           49  +    CREATE TABLE ota.data_t2(a, b, c, ota_control);
           50  +    INSERT INTO data_t2 VALUES('b', NULL, NULL, 1);
           51  +    INSERT INTO data_t2 VALUES('c', 'see', NULL, '.x.');
           52  +    INSERT INTO data_t2 VALUES('d', 'd', 'd', 0);
           53  +  } {SELECT * FROM t2} {a a a   c see c     d d d}
           54  +
           55  +  3 {
           56  +    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
           57  +    CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
           58  +    CREATE INDEX t1cb ON t1(c, b);
           59  +    CREATE INDEX t2cb ON t2(c, b);
           60  +
           61  +    CREATE TABLE ota.data_t1(a, b, c, ota_control);
           62  +    CREATE TABLE ota.data_t2(a, b, c, ota_control);
           63  +    INSERT INTO data_t1 VALUES(1, 2, 3, 0);
           64  +    INSERT INTO data_t2 VALUES(4, 5, 6, 0);
           65  +  } {SELECT * FROM t1 UNION ALL SELECT * FROM t2} {1 2 3 4 5 6}
           66  +
           67  +} {
           68  +  catch {db close}
           69  +  forcedelete ota.db test.db
           70  +  sqlite3 db test.db
           71  +  execsql {
           72  +    PRAGMA encoding = utf16;
           73  +    ATTACH 'ota.db' AS ota;
           74  +  }
           75  +  execsql $setup
           76  +  db close
           77  +
           78  +  forcecopy test.db test.db.bak
           79  +  forcecopy ota.db ota.db.bak
           80  +
           81  +  foreach {tn f reslist} {
           82  +    1 oom-tra*  {
           83  +      {0 SQLITE_DONE} 
           84  +      {1 {SQLITE_NOMEM - out of memory}} 
           85  +      {1 SQLITE_NOMEM} 
           86  +      {1 SQLITE_IOERR_NOMEM} 
           87  +      {1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
           88  +    }
           89  +  
           90  +    2 ioerr-*  {
           91  +      {0 SQLITE_DONE} 
           92  +      {1 {SQLITE_IOERR - disk I/O error}}
           93  +      {1 SQLITE_IOERR}
           94  +      {1 SQLITE_IOERR_WRITE}
           95  +      {1 SQLITE_IOERR_READ}
           96  +      {1 SQLITE_IOERR_FSYNC}
           97  +      {1 {SQLITE_ERROR - SQL logic error or missing database}}
           98  +      {1 {SQLITE_ERROR - unable to open database: ota.db}}
           99  +      {1 {SQLITE_IOERR - unable to open database: ota.db}}
          100  +    }
          101  +  } {
   125    102   
   126         -  forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
          103  +    catch {db close}
          104  +    sqlite3_shutdown
          105  +    set lookaside_config [sqlite3_config_lookaside 0 0]
          106  +    sqlite3_initialize
          107  +    autoinstall_test_functions
   127    108   
   128         -  copy_if_exists test.db.bak test.db
   129         -  copy_if_exists ota.db.bak ota.db
          109  +    do_faultsim_test 2.$tn2 -faults $::f -prep {
          110  +      catch { db close }
          111  +      forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
          112  +      forcecopy test.db.bak test.db
          113  +      forcecopy ota.db.bak  ota.db
          114  +    } -body {
          115  +      sqlite3ota ota test.db ota.db
          116  +      while {[ota step]=="SQLITE_OK"} {}
          117  +      ota close
          118  +    } -test {
          119  +      faultsim_test_result {*}$::reslist
          120  +      if {$testrc==0} {
          121  +        sqlite3 db test.db
          122  +        faultsim_integrity_check
          123  +        set res [db eval $::sql]
          124  +        if {$res != [list {*}$::expect]} {
          125  +          puts ""
          126  +          puts "res: $res"
          127  +          puts "exp: $expect"
          128  +          error "data not as expected!"
          129  +        }
          130  +      }
          131  +    }
   130    132   
   131         -  sqlite3ota ota test.db ota.db
   132         -  for {set x 0} {$x < $::iStep} {incr x} { ota step }
   133         -  ota close
          133  +    catch {db close}
          134  +    sqlite3_shutdown
          135  +    sqlite3_config_lookaside {*}$lookaside_config
          136  +    sqlite3_initialize
          137  +    autoinstall_test_functions
          138  +
   134    139   
   135         -  copy_if_exists test.db test.db.bak.2
   136         -  copy_if_exists test.db-wal test.db.bak.2-wal
   137         -  copy_if_exists test.db-oal test.db.bak.2-oal
   138         -  copy_if_exists ota.db ota.db.bak.2
          140  +  }
   139    141   
   140         -  do_faultsim_test 3.$iStep -faults oom-trans* -prep {
   141         -    catch { db close }
          142  +  for {set iStep 0} {$iStep<=21} {incr iStep} {
          143  +  
   142    144       forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
   143         -    copy_if_exists test.db.bak.2 test.db
   144         -    copy_if_exists test.db.bak.2-wal test.db-wal
   145         -    copy_if_exists test.db.bak.2-oal test.db-oal
   146         -    copy_if_exists ota.db.bak.2  ota.db
   147         -  } -body {
          145  +  
          146  +    copy_if_exists test.db.bak test.db
          147  +    copy_if_exists ota.db.bak ota.db
          148  +  
   148    149       sqlite3ota ota test.db ota.db
   149         -    while {[ota step] == "SQLITE_OK"} {}
          150  +    for {set x 0} {$x < $::iStep} {incr x} { ota step }
   150    151       ota close
   151         -  } -test {
   152         -    faultsim_test_result {0 SQLITE_DONE} \
   153         -                         {1 {SQLITE_NOMEM - out of memory}} \
   154         -                         {1 SQLITE_NOMEM} \
   155         -                         {1 SQLITE_IOERR_NOMEM} \
   156         -                         {1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
          152  +  
          153  +    copy_if_exists test.db test.db.bak.2
          154  +    copy_if_exists test.db-wal test.db.bak.2-wal
          155  +    copy_if_exists test.db-oal test.db.bak.2-oal
          156  +    copy_if_exists ota.db ota.db.bak.2
   157    157     
   158         -    if {$testrc==0} {
   159         -      sqlite3 db test.db
   160         -      faultsim_integrity_check
   161         -      set res [db eval {
   162         -        SELECT * FROM t1 UNION ALL SELECT * FROM t2;
   163         -      }]
   164         -      set expected [list {*}{
   165         -        1 1 1   3 three 3   4 4 4
   166         -        a a a   c see c     d d d
   167         -      }]
   168         -  
   169         -      if {$res != $expected} {
   170         -        puts ""
   171         -        puts "res: $res"
   172         -        puts "exp: $expected"
   173         -        error "data not as expected!"
          158  +    do_faultsim_test 3.$tn.$iStep -faults $::f -prep {
          159  +      catch { db close }
          160  +      forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
          161  +      copy_if_exists test.db.bak.2 test.db
          162  +      copy_if_exists test.db.bak.2-wal test.db-wal
          163  +      copy_if_exists test.db.bak.2-oal test.db-oal
          164  +      copy_if_exists ota.db.bak.2  ota.db
          165  +    } -body {
          166  +      sqlite3ota ota test.db ota.db
          167  +      while {[ota step] == "SQLITE_OK"} {}
          168  +      ota close
          169  +    } -test {
          170  +      faultsim_test_result {*}$::reslist
          171  +    
          172  +      if {$testrc==0} {
          173  +        sqlite3 db test.db
          174  +        faultsim_integrity_check
          175  +        set res [db eval $::sql]
          176  +        if {$res != [list {*}$::expect]} {
          177  +          puts ""
          178  +          puts "res: $res"
          179  +          puts "exp: $expected"
          180  +          error "data not as expected!"
          181  +        }
   174    182         }
   175    183       }
   176    184     }
   177         -
   178    185   }
   179    186   
   180    187   finish_test
   181    188   

Changes to ext/ota/sqlite3ota.c.

   569    569   **     }
   570    570   **   }else if( "PRAGMA table_info()" lists one or more "pk" columns ){
   571    571   **     return OTA_PK_IPK
   572    572   **   }else{
   573    573   **     return OTA_PK_NONE
   574    574   **   }
   575    575   */
   576         -static int otaTableType(
   577         -  sqlite3 *db,
          576  +static void otaTableType(
          577  +  sqlite3ota *p,
   578    578     const char *zTab,
   579    579     int *peType,
   580    580     int *piPk
   581    581   ){
   582         -  sqlite3_stmt *pStmt = 0;
   583         -  int rc = SQLITE_OK;
   584         -  int rc2;
   585         -  char *zSql = 0;
          582  +  /*
          583  +  ** 0) SELECT count(*) FROM sqlite_master where name=%Q AND IsVirtual(%Q)
          584  +  ** 1) PRAGMA index_list = ?
          585  +  ** 2) SELECT count(*) FROM sqlite_master where name=%Q 
          586  +  ** 3) PRAGMA table_info = ?
          587  +  */
          588  +  sqlite3_stmt *aStmt[4] = {0, 0, 0, 0};
   586    589   
   587    590     *peType = OTA_PK_NOTABLE;
   588    591     *piPk = 0;
   589         -  zSql = sqlite3_mprintf(
          592  +
          593  +  assert( p->rc==SQLITE_OK );
          594  +  p->rc = prepareFreeAndCollectError(p->db, &aStmt[0], &p->zErrmsg, 
          595  +    sqlite3_mprintf(
   590    596             "SELECT (sql LIKE 'create virtual%%')"
   591         -          "  FROM main.sqlite_master"
   592         -          " WHERE name=%Q", zTab);
   593         -  if( zSql==0 ) return SQLITE_NOMEM;
   594         -  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   595         -  sqlite3_free(zSql);
   596         -  zSql = 0;
   597         -  if( pStmt==0 ) goto otaTableType_end;
   598         -  if( sqlite3_step(pStmt)!=SQLITE_ROW ){
   599         -     goto otaTableType_end;                    /* no such table */
          597  +          "  FROM sqlite_master"
          598  +          " WHERE name=%Q", zTab
          599  +  ));
          600  +  if( p->rc!=SQLITE_OK || sqlite3_step(aStmt[0])!=SQLITE_ROW ){
          601  +    /* Either an error, or no such table. */
          602  +    goto otaTableType_end;
   600    603     }
   601         -  if( sqlite3_column_int(pStmt,0) ){
          604  +  if( sqlite3_column_int(aStmt[0], 0) ){
   602    605       *peType = OTA_PK_VTAB;                     /* virtual table */
   603    606       goto otaTableType_end;
   604    607     }
   605         -  rc = sqlite3_finalize(pStmt);
   606         -  if( rc ) return rc;
   607         -  zSql = sqlite3_mprintf("PRAGMA index_list=%Q",zTab);
   608         -  if( zSql==0 ) return SQLITE_NOMEM;
   609         -  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   610         -  sqlite3_free(zSql);
   611         -  zSql = 0;
   612         -  if( pStmt==0 ) goto otaTableType_end;
   613         -  while( sqlite3_step(pStmt)==SQLITE_ROW ){
   614         -    const u8 *zOrig = sqlite3_column_text(pStmt,3);
   615         -    if( zOrig && zOrig[0]=='p' ){
   616         -      zSql = sqlite3_mprintf("SELECT rootpage FROM main.sqlite_master"
   617         -                             " WHERE name=%Q", sqlite3_column_text(pStmt,1));
   618         -      if( zSql==0 ){ rc = SQLITE_NOMEM; goto otaTableType_end; }
   619         -      break;
   620         -    }
   621         -  }
   622         -  rc = sqlite3_finalize(pStmt);
   623         -  pStmt = 0;
   624         -  if( rc ) return rc;
   625         -  if( zSql ){
   626         -    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   627         -    sqlite3_free(zSql);
   628         -    zSql = 0;
   629         -    if( pStmt==0 ) goto otaTableType_end;
   630         -    if( sqlite3_step(pStmt)==SQLITE_ROW ){
   631         -      *piPk = sqlite3_column_int(pStmt, 0);
   632         -      *peType = OTA_PK_EXTERNAL;             /* external PK index */
   633         -    }else{
   634         -      *peType = OTA_PK_WITHOUT_ROWID;        /* WITHOUT ROWID table */
   635         -    }
   636         -  }else{
   637         -    zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab);
   638         -    if( zSql==0 ) return SQLITE_NOMEM;
   639         -    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
   640         -    sqlite3_free(zSql);
   641         -    zSql = 0;
   642         -    if( pStmt==0 ) goto otaTableType_end;
   643         -    *peType = OTA_PK_NONE;                   /* (default) implicit ROWID */
   644         -    while( sqlite3_step(pStmt)==SQLITE_ROW ){
   645         -      if( sqlite3_column_int(pStmt,5)>0 ){
          608  +
          609  +  p->rc = prepareFreeAndCollectError(p->db, &aStmt[1], &p->zErrmsg, 
          610  +    sqlite3_mprintf("PRAGMA index_list=%Q",zTab)
          611  +  );
          612  +  if( p->rc ) goto otaTableType_end;
          613  +  while( sqlite3_step(aStmt[1])==SQLITE_ROW ){
          614  +    const u8 *zOrig = sqlite3_column_text(aStmt[1], 3);
          615  +    const u8 *zIdx = sqlite3_column_text(aStmt[1], 1);
          616  +    if( zOrig && zIdx && zOrig[0]=='p' ){
          617  +      p->rc = prepareFreeAndCollectError(p->db, &aStmt[2], &p->zErrmsg, 
          618  +          sqlite3_mprintf(
          619  +            "SELECT rootpage FROM sqlite_master WHERE name = %Q", zIdx
          620  +      ));
          621  +      if( p->rc==SQLITE_OK ){
          622  +        if( sqlite3_step(aStmt[2])==SQLITE_ROW ){
          623  +          *piPk = sqlite3_column_int(aStmt[2], 0);
          624  +          *peType = OTA_PK_EXTERNAL;
          625  +        }else{
          626  +          *peType = OTA_PK_WITHOUT_ROWID;
          627  +        }
          628  +      }
          629  +      goto otaTableType_end;
          630  +    }
          631  +  }
          632  +
          633  +  p->rc = prepareFreeAndCollectError(p->db, &aStmt[3], &p->zErrmsg, 
          634  +    sqlite3_mprintf("PRAGMA table_info=%Q",zTab)
          635  +  );
          636  +  if( p->rc==SQLITE_OK ){
          637  +    while( sqlite3_step(aStmt[3])==SQLITE_ROW ){
          638  +      if( sqlite3_column_int(aStmt[3],5)>0 ){
   646    639           *peType = OTA_PK_IPK;                /* explicit IPK column */
   647         -        break;
          640  +        goto otaTableType_end;
   648    641         }
   649    642       }
          643  +    *peType = OTA_PK_NONE;
   650    644     }
   651    645   
   652         -otaTableType_end:
   653         -  sqlite3_free(zSql);
   654         -  rc2 = sqlite3_finalize(pStmt);
   655         -  return rc ? rc : rc2;
          646  +otaTableType_end: {
          647  +    int i;
          648  +    for(i=0; i<sizeof(aStmt)/sizeof(aStmt[0]); i++){
          649  +      int rc2 = sqlite3_finalize(aStmt[i]);
          650  +      if( p->rc==SQLITE_OK ) p->rc = rc2;
          651  +    }
          652  +  }
   656    653   }
   657    654   
   658    655   
   659    656   /*
   660    657   ** If they are not already populated, populate the pIter->azTblCol[],
   661    658   ** pIter->abTblPk[], pIter->nTblCol and pIter->bRowid variables according to
   662    659   ** the table (not index) that the iterator currently points to.
................................................................................
   672    669       int i;                        /* for() loop iterator variable */
   673    670       int rc2;                      /* sqlite3_finalize() return value */
   674    671       int bOtaRowid = 0;            /* If input table has column "ota_rowid" */
   675    672       int iOrder = 0;
   676    673   
   677    674       /* Figure out the type of table this step will deal with. */
   678    675       assert( pIter->eType==0 );
   679         -    p->rc = otaTableType(p->db, pIter->zTbl, &pIter->eType, &pIter->iPkTnum);
          676  +    otaTableType(p, pIter->zTbl, &pIter->eType, &pIter->iPkTnum);
   680    677       if( p->rc ) return p->rc;
   681    678   
   682    679       assert( pIter->eType==OTA_PK_NONE || pIter->eType==OTA_PK_IPK 
   683    680            || pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_WITHOUT_ROWID
   684    681            || pIter->eType==OTA_PK_VTAB
   685    682       );
   686    683   
................................................................................
  1099   1096           }
  1100   1097           break;
  1101   1098         }
  1102   1099       }
  1103   1100       rc = sqlite3_finalize(pXList);
  1104   1101       if( p->rc==SQLITE_OK ) p->rc = rc;
  1105   1102   
  1106         -    while( p->rc==SQLITE_OK && pXInfo && SQLITE_ROW==sqlite3_step(pXInfo) ){
         1103  +    while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){
  1107   1104         if( sqlite3_column_int(pXInfo, 5) ){
  1108   1105           /* int iCid = sqlite3_column_int(pXInfo, 0); */
  1109   1106           const char *zCol = (const char*)sqlite3_column_text(pXInfo, 2);
  1110   1107           const char *zDesc = sqlite3_column_int(pXInfo, 3) ? " DESC" : "";
  1111   1108           z = otaMPrintf(p, "%z%s\"%w\"%s", z, zSep, zCol, zDesc);
  1112   1109           zSep = ", ";
  1113   1110         }
................................................................................
  1115   1112       z = otaMPrintf(p, "%z)", z);
  1116   1113       rc = sqlite3_finalize(pXInfo);
  1117   1114       if( p->rc==SQLITE_OK ) p->rc = rc;
  1118   1115     }
  1119   1116     return z;
  1120   1117   }
  1121   1118   
         1119  +/*
         1120  +** This function creates the second imposter table used when writing to
         1121  +** a table b-tree where the table has an external primary key. If the
         1122  +** iterator passed as the second argument does not currently point to
         1123  +** a table (not index) with an external primary key, this function is a
         1124  +** no-op. 
         1125  +**
         1126  +** Assuming the iterator does point to a table with an external PK, this
         1127  +** function creates a WITHOUT ROWID imposter table named "ota_imposter2"
         1128  +** used to access that PK index. For example, if the target table is
         1129  +** declared as follows:
         1130  +**
         1131  +**   CREATE TABLE t1(a, b TEXT, c REAL, PRIMARY KEY(b, c));
         1132  +**
         1133  +** then the imposter table schema is:
         1134  +**
         1135  +**   CREATE TABLE ota_imposter2(c1 TEXT, c2 REAL, id INTEGER) WITHOUT ROWID;
         1136  +**
         1137  +*/
  1122   1138   static void otaCreateImposterTable2(sqlite3ota *p, OtaObjIter *pIter){
  1123   1139     if( p->rc==SQLITE_OK && pIter->eType==OTA_PK_EXTERNAL ){
  1124   1140       int tnum = pIter->iPkTnum;    /* Root page of PK index */
  1125   1141       sqlite3_stmt *pQuery = 0;     /* SELECT name ... WHERE rootpage = $tnum */
  1126   1142       const char *zIdx = 0;         /* Name of PK index */
  1127   1143       sqlite3_stmt *pXInfo = 0;     /* PRAGMA main.index_xinfo = $zIdx */
  1128   1144       int rc;