/ 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 Unified Diffs Ignore Whitespace Patch

Changes to ext/ota/otafault.test.

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
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
178
179
180
181
if {![info exists testdir]} {
  set testdir [file join [file dirname [info script]] .. .. test]
}
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
set ::testprefix otafault

do_test 1.1 {



  forcedelete ota.db
  execsql {
    PRAGMA encoding = utf16;





    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
    CREATE INDEX t1cb ON t1(c, b);
    INSERT INTO t1 VALUES(1, 1, 1);
    INSERT INTO t1 VALUES(2, 2, 2);
    INSERT INTO t1 VALUES(3, 3, 3);








    CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
    CREATE INDEX t2cb ON t1(c, b);
    INSERT INTO t2 VALUES('a', 'a', 'a');
    INSERT INTO t2 VALUES('b', 'b', 'b');
    INSERT INTO t2 VALUES('c', 'c', 'c');

    ATTACH 'ota.db' AS ota;
    CREATE TABLE ota.data_t1(a, b, c, ota_control);
    CREATE TABLE ota.data_t2(a, b, c, ota_control);













    INSERT INTO data_t1 VALUES(2, NULL, NULL, 1);
    INSERT INTO data_t1 VALUES(3, 'three', NULL, '.x.');
    INSERT INTO data_t1 VALUES(4, 4, 4, 0);


    INSERT INTO data_t2 VALUES('b', NULL, NULL, 1);
    INSERT INTO data_t2 VALUES('c', 'see', NULL, '.x.');
    INSERT INTO data_t2 VALUES('d', 'd', 'd', 0);







  }

  db close

  forcecopy test.db test.db.bak
  forcecopy ota.db ota.db.bak
} {}

sqlite3_shutdown
set lookaside_config [sqlite3_config_lookaside 0 0]
sqlite3_initialize
autoinstall_test_functions

foreach {tn f reslist} {
  1 oom-tra*  {
    {0 SQLITE_DONE} 
    {1 {SQLITE_NOMEM - out of memory}} 
    {1 SQLITE_NOMEM} 
    {1 SQLITE_IOERR_NOMEM} 
    {1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
  }

  2 ioerr-*  {
    {0 SQLITE_DONE} 
    {1 {SQLITE_IOERR - disk I/O error}}
    {1 SQLITE_IOERR}
    {1 SQLITE_IOERR_WRITE}
    {1 SQLITE_IOERR_READ}
    {1 SQLITE_IOERR_FSYNC}
    {1 {SQLITE_ERROR - SQL logic error or missing database}}
    {1 {SQLITE_ERROR - unable to open database: ota.db}}
    {1 {SQLITE_IOERR - unable to open database: ota.db}}
  }
} {







  do_faultsim_test 2 -faults $::f -prep {
    catch { db close }
    forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
    forcecopy test.db.bak test.db
    forcecopy ota.db.bak  ota.db
  } -body {
    sqlite3ota ota test.db ota.db
    while {[ota step]=="SQLITE_OK"} {}
    ota close
  } -test {
    faultsim_test_result {*}$::reslist
    if {$testrc==0} {
      sqlite3 db test.db
      faultsim_integrity_check
      set res [db eval {
        SELECT * FROM t1 UNION ALL SELECT * FROM t2;
      }]
      set expected [list {*}{
        1 1 1   3 three 3   4 4 4
        a a a   c see c     d d d
      }]
  
      if {$res != $expected} {
        puts ""
        puts "res: $res"
        puts "exp: $expected"
        error "data not as expected!"
      }
    }
  }
}

catch {db close}
sqlite3_shutdown
sqlite3_config_lookaside {*}$lookaside_config
sqlite3_initialize
autoinstall_test_functions

proc copy_if_exists {src target} {
  if {[file exists $src]} {
    forcecopy $src $target
  } else {
    forcedelete $target
  }
}

for {set iStep 0} {$iStep<=21} {incr iStep} {

  forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal

  copy_if_exists test.db.bak test.db
  copy_if_exists ota.db.bak ota.db

  sqlite3ota ota test.db ota.db
  for {set x 0} {$x < $::iStep} {incr x} { ota step }
  ota close

  copy_if_exists test.db test.db.bak.2
  copy_if_exists test.db-wal test.db.bak.2-wal
  copy_if_exists test.db-oal test.db.bak.2-oal
  copy_if_exists ota.db ota.db.bak.2

  do_faultsim_test 3.$iStep -faults oom-trans* -prep {
    catch { db close }
    forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
    copy_if_exists test.db.bak.2 test.db
    copy_if_exists test.db.bak.2-wal test.db-wal
    copy_if_exists test.db.bak.2-oal test.db-oal
    copy_if_exists ota.db.bak.2  ota.db
  } -body {
    sqlite3ota ota test.db ota.db
    while {[ota step] == "SQLITE_OK"} {}
    ota close
  } -test {
    faultsim_test_result {0 SQLITE_DONE} \
                         {1 {SQLITE_NOMEM - out of memory}} \
                         {1 SQLITE_NOMEM} \
                         {1 SQLITE_IOERR_NOMEM} \
                         {1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
  
    if {$testrc==0} {
      sqlite3 db test.db
      faultsim_integrity_check
      set res [db eval {
        SELECT * FROM t1 UNION ALL SELECT * FROM t2;
      }]
      set expected [list {*}{
        1 1 1   3 three 3   4 4 4
        a a a   c see c     d d d
      }]
  
      if {$res != $expected} {
        puts ""
        puts "res: $res"
        puts "exp: $expected"
        error "data not as expected!"
      }
    }
  }

}

finish_test








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





>
>
>
>
>
>
>
>

|




<
<

>
>
>
>

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

<
<
<
>
>
>
>
>
>
>

>




<

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

<
<
<
<
<
|
|

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




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
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
178
179
180
181
182
183
184
185
186
187
188
if {![info exists testdir]} {
  set testdir [file join [file dirname [info script]] .. .. test]
}
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
set ::testprefix otafault

proc copy_if_exists {src target} {
  if {[file exists $src]} {
    forcecopy $src $target
  } else {
    forcedelete $target


  }
}

foreach {tn2 setup sql expect} {
  1 {
    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
    CREATE INDEX t1cb ON t1(c, b);
    INSERT INTO t1 VALUES(1, 1, 1);
    INSERT INTO t1 VALUES(2, 2, 2);
    INSERT INTO t1 VALUES(3, 3, 3);

    CREATE TABLE ota.data_t1(a, b, c, ota_control);
    INSERT INTO data_t1 VALUES(2, NULL, NULL, 1);
    INSERT INTO data_t1 VALUES(3, 'three', NULL, '.x.');
    INSERT INTO data_t1 VALUES(4, 4, 4, 0);
  } {SELECT * FROM t1} {1 1 1   3 three 3   4 4 4}

  2 {
    CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
    CREATE INDEX t2cb ON t2(c, b);
    INSERT INTO t2 VALUES('a', 'a', 'a');
    INSERT INTO t2 VALUES('b', 'b', 'b');
    INSERT INTO t2 VALUES('c', 'c', 'c');



    CREATE TABLE ota.data_t2(a, b, c, ota_control);
    INSERT INTO data_t2 VALUES('b', NULL, NULL, 1);
    INSERT INTO data_t2 VALUES('c', 'see', NULL, '.x.');
    INSERT INTO data_t2 VALUES('d', 'd', 'd', 0);
  } {SELECT * FROM t2} {a a a   c see c     d d d}

  3 {
    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
    CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID;
    CREATE INDEX t1cb ON t1(c, b);
    CREATE INDEX t2cb ON t2(c, b);

    CREATE TABLE ota.data_t1(a, b, c, ota_control);
    CREATE TABLE ota.data_t2(a, b, c, ota_control);
    INSERT INTO data_t1 VALUES(1, 2, 3, 0);

    INSERT INTO data_t2 VALUES(4, 5, 6, 0);
  } {SELECT * FROM t1 UNION ALL SELECT * FROM t2} {1 2 3 4 5 6}




} {
  catch {db close}
  forcedelete ota.db test.db
  sqlite3 db test.db
  execsql {
    PRAGMA encoding = utf16;
    ATTACH 'ota.db' AS ota;
  }
  execsql $setup
  db close

  forcecopy test.db test.db.bak
  forcecopy ota.db ota.db.bak







  foreach {tn f reslist} {
    1 oom-tra*  {
      {0 SQLITE_DONE} 
      {1 {SQLITE_NOMEM - out of memory}} 
      {1 SQLITE_NOMEM} 
      {1 SQLITE_IOERR_NOMEM} 
      {1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
    }
  
    2 ioerr-*  {
      {0 SQLITE_DONE} 
      {1 {SQLITE_IOERR - disk I/O error}}
      {1 SQLITE_IOERR}
      {1 SQLITE_IOERR_WRITE}
      {1 SQLITE_IOERR_READ}
      {1 SQLITE_IOERR_FSYNC}
      {1 {SQLITE_ERROR - SQL logic error or missing database}}
      {1 {SQLITE_ERROR - unable to open database: ota.db}}
      {1 {SQLITE_IOERR - unable to open database: ota.db}}
    }
  } {

    catch {db close}
    sqlite3_shutdown
    set lookaside_config [sqlite3_config_lookaside 0 0]
    sqlite3_initialize
    autoinstall_test_functions

    do_faultsim_test 2.$tn2 -faults $::f -prep {
      catch { db close }
      forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
      forcecopy test.db.bak test.db
      forcecopy ota.db.bak  ota.db
    } -body {
      sqlite3ota ota test.db ota.db
      while {[ota step]=="SQLITE_OK"} {}
      ota close
    } -test {
      faultsim_test_result {*}$::reslist
      if {$testrc==0} {
        sqlite3 db test.db
        faultsim_integrity_check
        set res [db eval $::sql]







        if {$res != [list {*}$::expect]} {
          puts ""
          puts "res: $res"
          puts "exp: $expect"
          error "data not as expected!"
        }
      }
    }


    catch {db close}
    sqlite3_shutdown
    sqlite3_config_lookaside {*}$lookaside_config
    sqlite3_initialize
    autoinstall_test_functions







  }

  for {set iStep 0} {$iStep<=21} {incr iStep} {
  
    forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
  
    copy_if_exists test.db.bak test.db
    copy_if_exists ota.db.bak ota.db
  
    sqlite3ota ota test.db ota.db
    for {set x 0} {$x < $::iStep} {incr x} { ota step }
    ota close
  
    copy_if_exists test.db test.db.bak.2
    copy_if_exists test.db-wal test.db.bak.2-wal
    copy_if_exists test.db-oal test.db.bak.2-oal
    copy_if_exists ota.db ota.db.bak.2
  
    do_faultsim_test 3.$tn.$iStep -faults $::f -prep {
      catch { db close }
      forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
      copy_if_exists test.db.bak.2 test.db
      copy_if_exists test.db.bak.2-wal test.db-wal
      copy_if_exists test.db.bak.2-oal test.db-oal
      copy_if_exists ota.db.bak.2  ota.db
    } -body {
      sqlite3ota ota test.db ota.db
      while {[ota step] == "SQLITE_OK"} {}
      ota close
    } -test {
      faultsim_test_result {*}$::reslist




    
      if {$testrc==0} {
        sqlite3 db test.db
        faultsim_integrity_check
        set res [db eval $::sql]







        if {$res != [list {*}$::expect]} {
          puts ""
          puts "res: $res"
          puts "exp: $expected"
          error "data not as expected!"
        }
      }
    }
  }
}

finish_test

Changes to ext/ota/sqlite3ota.c.

569
570
571
572
573
574
575
576
577
578
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
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
...
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
....
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
....
1115
1116
1117
1118
1119
1120
1121



















1122
1123
1124
1125
1126
1127
1128
**     }
**   }else if( "PRAGMA table_info()" lists one or more "pk" columns ){
**     return OTA_PK_IPK
**   }else{
**     return OTA_PK_NONE
**   }
*/
static int otaTableType(
  sqlite3 *db,
  const char *zTab,
  int *peType,
  int *piPk
){
  sqlite3_stmt *pStmt = 0;
  int rc = SQLITE_OK;
  int rc2;
  char *zSql = 0;




  *peType = OTA_PK_NOTABLE;
  *piPk = 0;
  zSql = sqlite3_mprintf(
          "SELECT (sql LIKE 'create virtual%%')"
          "  FROM main.sqlite_master"
          " WHERE name=%Q", zTab);
  if( zSql==0 ) return SQLITE_NOMEM;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  sqlite3_free(zSql);
  zSql = 0;
  if( pStmt==0 ) goto otaTableType_end;
  if( sqlite3_step(pStmt)!=SQLITE_ROW ){
     goto otaTableType_end;                    /* no such table */
  }
  if( sqlite3_column_int(pStmt,0) ){
    *peType = OTA_PK_VTAB;                     /* virtual table */
    goto otaTableType_end;
  }
  rc = sqlite3_finalize(pStmt);
  if( rc ) return rc;
  zSql = sqlite3_mprintf("PRAGMA index_list=%Q",zTab);
  if( zSql==0 ) return SQLITE_NOMEM;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  sqlite3_free(zSql);
  zSql = 0;
  if( pStmt==0 ) goto otaTableType_end;
  while( sqlite3_step(pStmt)==SQLITE_ROW ){
    const u8 *zOrig = sqlite3_column_text(pStmt,3);
    if( zOrig && zOrig[0]=='p' ){
      zSql = sqlite3_mprintf("SELECT rootpage FROM main.sqlite_master"
                             " WHERE name=%Q", sqlite3_column_text(pStmt,1));
      if( zSql==0 ){ rc = SQLITE_NOMEM; goto otaTableType_end; }
      break;
    }
  }
  rc = sqlite3_finalize(pStmt);
  pStmt = 0;
  if( rc ) return rc;
  if( zSql ){
    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
    sqlite3_free(zSql);
    zSql = 0;
    if( pStmt==0 ) goto otaTableType_end;
    if( sqlite3_step(pStmt)==SQLITE_ROW ){
      *piPk = sqlite3_column_int(pStmt, 0);
      *peType = OTA_PK_EXTERNAL;             /* external PK index */
    }else{
      *peType = OTA_PK_WITHOUT_ROWID;        /* WITHOUT ROWID table */
    }
  }else{
    zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab);
    if( zSql==0 ) return SQLITE_NOMEM;
    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
    sqlite3_free(zSql);
    zSql = 0;
    if( pStmt==0 ) goto otaTableType_end;
    *peType = OTA_PK_NONE;                   /* (default) implicit ROWID */
    while( sqlite3_step(pStmt)==SQLITE_ROW ){
      if( sqlite3_column_int(pStmt,5)>0 ){
        *peType = OTA_PK_IPK;                /* explicit IPK column */
        break;
      }
    }
  }

otaTableType_end:
  sqlite3_free(zSql);
  rc2 = sqlite3_finalize(pStmt);
  return rc ? rc : rc2;
}


/*
** If they are not already populated, populate the pIter->azTblCol[],
** pIter->abTblPk[], pIter->nTblCol and pIter->bRowid variables according to
** the table (not index) that the iterator currently points to.
................................................................................
    int i;                        /* for() loop iterator variable */
    int rc2;                      /* sqlite3_finalize() return value */
    int bOtaRowid = 0;            /* If input table has column "ota_rowid" */
    int iOrder = 0;

    /* Figure out the type of table this step will deal with. */
    assert( pIter->eType==0 );
    p->rc = otaTableType(p->db, pIter->zTbl, &pIter->eType, &pIter->iPkTnum);
    if( p->rc ) return p->rc;

    assert( pIter->eType==OTA_PK_NONE || pIter->eType==OTA_PK_IPK 
         || pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_WITHOUT_ROWID
         || pIter->eType==OTA_PK_VTAB
    );

................................................................................
        }
        break;
      }
    }
    rc = sqlite3_finalize(pXList);
    if( p->rc==SQLITE_OK ) p->rc = rc;

    while( p->rc==SQLITE_OK && pXInfo && SQLITE_ROW==sqlite3_step(pXInfo) ){
      if( sqlite3_column_int(pXInfo, 5) ){
        /* int iCid = sqlite3_column_int(pXInfo, 0); */
        const char *zCol = (const char*)sqlite3_column_text(pXInfo, 2);
        const char *zDesc = sqlite3_column_int(pXInfo, 3) ? " DESC" : "";
        z = otaMPrintf(p, "%z%s\"%w\"%s", z, zSep, zCol, zDesc);
        zSep = ", ";
      }
................................................................................
    z = otaMPrintf(p, "%z)", z);
    rc = sqlite3_finalize(pXInfo);
    if( p->rc==SQLITE_OK ) p->rc = rc;
  }
  return z;
}




















static void otaCreateImposterTable2(sqlite3ota *p, OtaObjIter *pIter){
  if( p->rc==SQLITE_OK && pIter->eType==OTA_PK_EXTERNAL ){
    int tnum = pIter->iPkTnum;    /* Root page of PK index */
    sqlite3_stmt *pQuery = 0;     /* SELECT name ... WHERE rootpage = $tnum */
    const char *zIdx = 0;         /* Name of PK index */
    sqlite3_stmt *pXInfo = 0;     /* PRAGMA main.index_xinfo = $zIdx */
    int rc;







|
|




|
|
|
|
>
>
>



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







 







|







 







|







 







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







569
570
571
572
573
574
575
576
577
578
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
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
...
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
....
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
....
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
**     }
**   }else if( "PRAGMA table_info()" lists one or more "pk" columns ){
**     return OTA_PK_IPK
**   }else{
**     return OTA_PK_NONE
**   }
*/
static void otaTableType(
  sqlite3ota *p,
  const char *zTab,
  int *peType,
  int *piPk
){
  /*
  ** 0) SELECT count(*) FROM sqlite_master where name=%Q AND IsVirtual(%Q)
  ** 1) PRAGMA index_list = ?
  ** 2) SELECT count(*) FROM sqlite_master where name=%Q 
  ** 3) PRAGMA table_info = ?
  */
  sqlite3_stmt *aStmt[4] = {0, 0, 0, 0};

  *peType = OTA_PK_NOTABLE;
  *piPk = 0;

  assert( p->rc==SQLITE_OK );
  p->rc = prepareFreeAndCollectError(p->db, &aStmt[0], &p->zErrmsg, 
    sqlite3_mprintf(
          "SELECT (sql LIKE 'create virtual%%')"
          "  FROM sqlite_master"
          " WHERE name=%Q", zTab
  ));
  if( p->rc!=SQLITE_OK || sqlite3_step(aStmt[0])!=SQLITE_ROW ){
    /* Either an error, or no such table. */
    goto otaTableType_end;
  }
  if( sqlite3_column_int(aStmt[0], 0) ){
    *peType = OTA_PK_VTAB;                     /* virtual table */
    goto otaTableType_end;
  }

  p->rc = prepareFreeAndCollectError(p->db, &aStmt[1], &p->zErrmsg, 
    sqlite3_mprintf("PRAGMA index_list=%Q",zTab)
  );
  if( p->rc ) goto otaTableType_end;
  while( sqlite3_step(aStmt[1])==SQLITE_ROW ){
    const u8 *zOrig = sqlite3_column_text(aStmt[1], 3);
    const u8 *zIdx = sqlite3_column_text(aStmt[1], 1);
    if( zOrig && zIdx && zOrig[0]=='p' ){
      p->rc = prepareFreeAndCollectError(p->db, &aStmt[2], &p->zErrmsg, 
          sqlite3_mprintf(
            "SELECT rootpage FROM sqlite_master WHERE name = %Q", zIdx
      ));
      if( p->rc==SQLITE_OK ){
        if( sqlite3_step(aStmt[2])==SQLITE_ROW ){
          *piPk = sqlite3_column_int(aStmt[2], 0);
          *peType = OTA_PK_EXTERNAL;
        }else{
          *peType = OTA_PK_WITHOUT_ROWID;
        }
      }
      goto otaTableType_end;
    }
  }

  p->rc = prepareFreeAndCollectError(p->db, &aStmt[3], &p->zErrmsg, 
    sqlite3_mprintf("PRAGMA table_info=%Q",zTab)
  );
  if( p->rc==SQLITE_OK ){
    while( sqlite3_step(aStmt[3])==SQLITE_ROW ){
      if( sqlite3_column_int(aStmt[3],5)>0 ){
        *peType = OTA_PK_IPK;                /* explicit IPK column */
        goto otaTableType_end;
      }
    }
    *peType = OTA_PK_NONE;
  }

otaTableType_end: {
    int i;
    for(i=0; i<sizeof(aStmt)/sizeof(aStmt[0]); i++){
      int rc2 = sqlite3_finalize(aStmt[i]);
      if( p->rc==SQLITE_OK ) p->rc = rc2;
    }
  }






}


/*
** If they are not already populated, populate the pIter->azTblCol[],
** pIter->abTblPk[], pIter->nTblCol and pIter->bRowid variables according to
** the table (not index) that the iterator currently points to.
................................................................................
    int i;                        /* for() loop iterator variable */
    int rc2;                      /* sqlite3_finalize() return value */
    int bOtaRowid = 0;            /* If input table has column "ota_rowid" */
    int iOrder = 0;

    /* Figure out the type of table this step will deal with. */
    assert( pIter->eType==0 );
    otaTableType(p, pIter->zTbl, &pIter->eType, &pIter->iPkTnum);
    if( p->rc ) return p->rc;

    assert( pIter->eType==OTA_PK_NONE || pIter->eType==OTA_PK_IPK 
         || pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_WITHOUT_ROWID
         || pIter->eType==OTA_PK_VTAB
    );

................................................................................
        }
        break;
      }
    }
    rc = sqlite3_finalize(pXList);
    if( p->rc==SQLITE_OK ) p->rc = rc;

    while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){
      if( sqlite3_column_int(pXInfo, 5) ){
        /* int iCid = sqlite3_column_int(pXInfo, 0); */
        const char *zCol = (const char*)sqlite3_column_text(pXInfo, 2);
        const char *zDesc = sqlite3_column_int(pXInfo, 3) ? " DESC" : "";
        z = otaMPrintf(p, "%z%s\"%w\"%s", z, zSep, zCol, zDesc);
        zSep = ", ";
      }
................................................................................
    z = otaMPrintf(p, "%z)", z);
    rc = sqlite3_finalize(pXInfo);
    if( p->rc==SQLITE_OK ) p->rc = rc;
  }
  return z;
}

/*
** This function creates the second imposter table used when writing to
** a table b-tree where the table has an external primary key. If the
** iterator passed as the second argument does not currently point to
** a table (not index) with an external primary key, this function is a
** no-op. 
**
** Assuming the iterator does point to a table with an external PK, this
** function creates a WITHOUT ROWID imposter table named "ota_imposter2"
** used to access that PK index. For example, if the target table is
** declared as follows:
**
**   CREATE TABLE t1(a, b TEXT, c REAL, PRIMARY KEY(b, c));
**
** then the imposter table schema is:
**
**   CREATE TABLE ota_imposter2(c1 TEXT, c2 REAL, id INTEGER) WITHOUT ROWID;
**
*/
static void otaCreateImposterTable2(sqlite3ota *p, OtaObjIter *pIter){
  if( p->rc==SQLITE_OK && pIter->eType==OTA_PK_EXTERNAL ){
    int tnum = pIter->iPkTnum;    /* Root page of PK index */
    sqlite3_stmt *pQuery = 0;     /* SELECT name ... WHERE rootpage = $tnum */
    const char *zIdx = 0;         /* Name of PK index */
    sqlite3_stmt *pXInfo = 0;     /* PRAGMA main.index_xinfo = $zIdx */
    int rc;