SQLite

Check-in [a438fa6c9a]
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
Timelines: family | ancestors | descendants | both | ota-update
Files: files | file ages | folders
SHA1: a438fa6c9ad2fb1d78ac747172d07455d6381387
User & Date: dan 2015-02-17 20:49:42.756
Context
2015-02-18
17:40
Fix a problem with OTA updates in the presence of database readers. (check-in: 144bb29ffc user: dan tags: ota-update)
2015-02-17
20:49
Improve test coverage of ota code a bit. (check-in: a438fa6c9a user: dan tags: ota-update)
2015-02-16
21:13
Add extra tests and fixes for ota. (check-in: e0b7151962 user: dan tags: ota-update)
Changes
Side-by-Side Diff 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
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

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
  }
}

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.$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} \
  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
                         {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 {
    
      if {$testrc==0} {
        sqlite3 db test.db
        faultsim_integrity_check
        set res [db eval $::sql]
        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!"
      }
    }
  }

        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
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







-
-
+
+




+
+
+
+
+
+
-
+
-
-
-



+
+
+
-
+

-
-
+
+
-
-
-
-
-
-
-
+
+
+
+

-
+



-
-
-
+
+
+
-
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
-
-
+
-
-
-
+
+

-
+


+


-
+
-
-
-
+
+
+
+
+
+







**     }
**   }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,
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 *pStmt = 0;
  sqlite3_stmt *aStmt[4] = {0, 0, 0, 0};
  int rc = SQLITE_OK;
  int rc2;
  char *zSql = 0;

  *peType = OTA_PK_NOTABLE;
  *piPk = 0;

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

  p->rc = prepareFreeAndCollectError(p->db, &aStmt[1], &p->zErrmsg, 
    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( 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( zSql==0 ){ rc = SQLITE_NOMEM; goto otaTableType_end; }
      break;
    }
  }
  rc = sqlite3_finalize(pStmt);
  pStmt = 0;
  if( rc ) return rc;
  if( zSql ){
      ));
      if( p->rc==SQLITE_OK ){
    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( 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( 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( p->rc==SQLITE_OK ){
    *peType = OTA_PK_NONE;                   /* (default) implicit ROWID */
    while( sqlite3_step(pStmt)==SQLITE_ROW ){
      if( sqlite3_column_int(pStmt,5)>0 ){
    while( sqlite3_step(aStmt[3])==SQLITE_ROW ){
      if( sqlite3_column_int(aStmt[3],5)>0 ){
        *peType = OTA_PK_IPK;                /* explicit IPK column */
        break;
        goto otaTableType_end;
      }
    }
    *peType = OTA_PK_NONE;
  }

otaTableType_end:
otaTableType_end: {
  sqlite3_free(zSql);
  rc2 = sqlite3_finalize(pStmt);
  return rc ? rc : rc2;
    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.
672
673
674
675
676
677
678
679

680
681
682
683
684
685
686
669
670
671
672
673
674
675

676
677
678
679
680
681
682
683







-
+







    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);
    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
    );

1099
1100
1101
1102
1103
1104
1105
1106

1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121



















1122
1123
1124
1125
1126
1127
1128
1096
1097
1098
1099
1100
1101
1102

1103
1104
1105
1106
1107
1108
1109
1110
1111
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







-
+















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







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

    while( p->rc==SQLITE_OK && pXInfo && SQLITE_ROW==sqlite3_step(pXInfo) ){
    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;