/ Check-in [3ed6eb2f]
Login
Overview
Comment:Merge the SQLITE_TESTCTRL_IMPOSTER changes from trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | ota-update
Files: files | file ages | folders
SHA1:3ed6eb2fab5d95709ef392170339e6dd5ba13971
User & Date: drh 2015-01-30 21:00:10
Context
2015-01-31
02:34
Merge in support for the index_xinfo pragma. check-in: f9b6dc77 user: drh tags: ota-update
2015-01-30
21:00
Merge the SQLITE_TESTCTRL_IMPOSTER changes from trunk. check-in: 3ed6eb2f user: drh tags: ota-update
20:59
Change SQLITE_TESTCTRL_INITMODE to SQLITE_TESTCTRL_IMPOSTER. Revise the order of parameters. Give it the ability to reset the schema parse table so that imposter tables can be erased. check-in: 42d56017 user: drh tags: trunk
16:36
Merge all recent trunk changes, and especially the SQLITE_TESTCTRL_INITMODE enhancement. check-in: 36436dde user: drh tags: ota-update
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

172
173
174
175
176
177
178






179
180
181
182
183
184
185
  ** written. For index b-trees, it is the root page of the associated
  ** table.  */
  if( isIndex ){
    HashElem *p;
    for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){
      Index *pIdx = (Index *)sqliteHashData(p);
      if( pIdx->tnum==(int)iRoot ){






        iTab = pIdx->pTable->tnum;
      }
    }
  }else{
    iTab = iRoot;
  }








>
>
>
>
>
>







172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  ** written. For index b-trees, it is the root page of the associated
  ** table.  */
  if( isIndex ){
    HashElem *p;
    for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){
      Index *pIdx = (Index *)sqliteHashData(p);
      if( pIdx->tnum==(int)iRoot ){
        if( iTab ){
          /* Two or more indexes share the same root page.  There must
          ** be imposter tables.  So just return true.  The assert is not
          ** useful in that case. */
          return 1;
        }
        iTab = pIdx->pTable->tnum;
      }
    }
  }else{
    iTab = iRoot;
  }

Changes to src/build.c.

1727
1728
1729
1730
1731
1732
1733
1734


1735
1736
1737
1738

1739
1740
1741
1742
1743
1744
1745
    }
    pPk->nKeyCol = j;
  }
  pPk->isCovering = 1;
  assert( pPk!=0 );
  nPk = pPk->nKeyCol;

  /* Make sure every column of the PRIMARY KEY is NOT NULL */


  for(i=0; i<nPk; i++){
    pTab->aCol[pPk->aiColumn[i]].notNull = 1;
  }
  pPk->uniqNotNull = 1;


  /* The root page of the PRIMARY KEY is the table root page */
  pPk->tnum = pTab->tnum;

  /* Update the in-memory representation of all UNIQUE indices by converting
  ** the final rowid column into one or more columns of the PRIMARY KEY.
  */







|
>
>
|
|
|
|
>







1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
    }
    pPk->nKeyCol = j;
  }
  pPk->isCovering = 1;
  assert( pPk!=0 );
  nPk = pPk->nKeyCol;

  /* Make sure every column of the PRIMARY KEY is NOT NULL.  (Except,
  ** do not enforce this for imposter tables.) */
  if( !db->init.imposterTable ){
    for(i=0; i<nPk; i++){
      pTab->aCol[pPk->aiColumn[i]].notNull = 1;
    }
    pPk->uniqNotNull = 1;
  }

  /* The root page of the PRIMARY KEY is the table root page */
  pPk->tnum = pTab->tnum;

  /* Update the in-memory representation of all UNIQUE indices by converting
  ** the final rowid column into one or more columns of the PRIMARY KEY.
  */

Changes to src/main.c.

3622
3623
3624
3625
3626
3627
3628
3629
3630
3631












3632
3633
3634

3635
3636
3637



3638
3639
3640
3641
3642
3643
3644
    ** not.
    */
    case SQLITE_TESTCTRL_ISINIT: {
      if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR;
      break;
    }

    /*   sqlite3_test_control(SQLITE_TESTCTRL_INITMODE, db, busy, iDb, newTnum);
    **
    ** Set the db->init.busy, db->init.iDb, and db->init.tnum fields.












    */
    case SQLITE_TESTCTRL_INITMODE: {
      sqlite3 *db = va_arg(ap, sqlite3*);

      db->init.busy = va_arg(ap,int);
      db->init.iDb = va_arg(ap,int);
      db->init.newTnum = va_arg(ap,int);



      break;
    }
  }
  va_end(ap);
#endif /* SQLITE_OMIT_BUILTIN_TEST */
  return rc;
}







|

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

|

>
|
<

>
>
>







3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648

3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
    ** not.
    */
    case SQLITE_TESTCTRL_ISINIT: {
      if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR;
      break;
    }

    /*   sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum);
    **
    ** This test control is used to create imposter tables.  "db" is a pointer
    ** to the database connection.  dbName is the database name (ex: "main" or
    ** "temp") which will receive the imposter.  "onOff" turns imposter mode on
    ** or off.  "tnum" is the root page of the b-tree to which the imposter
    ** table should connect.
    **
    ** Enable imposter mode only when the schema has already been parsed.  Then
    ** run a single CREATE TABLE statement to construct the imposter table in the
    ** parsed schema.  Then turn imposter mode back off again.
    **
    ** If onOff==0 and tnum>0 then reset the schema for all databases, causing
    ** the schema to be reparsed the next time it is needed.  This has the
    ** effect of erasing all imposter tables.
    */
    case SQLITE_TESTCTRL_IMPOSTER: {
      sqlite3 *db = va_arg(ap, sqlite3*);
      db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*));
      db->init.busy = db->init.imposterTable = va_arg(ap,int);

      db->init.newTnum = va_arg(ap,int);
      if( db->init.busy==0 && db->init.newTnum>0 ){
        sqlite3ResetAllSchemasOfConnection(db);
      }
      break;
    }
  }
  va_end(ap);
#endif /* SQLITE_OMIT_BUILTIN_TEST */
  return rc;
}

Changes to src/shell.c.

3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
....
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
      { "always",                SQLITE_TESTCTRL_ALWAYS                 },
      { "reserve",               SQLITE_TESTCTRL_RESERVE                },
      { "optimizations",         SQLITE_TESTCTRL_OPTIMIZATIONS          },
      { "iskeyword",             SQLITE_TESTCTRL_ISKEYWORD              },
      { "scratchmalloc",         SQLITE_TESTCTRL_SCRATCHMALLOC          },
      { "byteorder",             SQLITE_TESTCTRL_BYTEORDER              },
      { "never_corrupt",         SQLITE_TESTCTRL_NEVER_CORRUPT          },
      { "initmode",              SQLITE_TESTCTRL_INITMODE               },
    };
    int testctrl = -1;
    int rc = 0;
    int i, n;
    open_db(p, 0);

    /* convert testctrl text option to value. allow any unique prefix
................................................................................
          } else {
            fprintf(stderr,"Error: testctrl %s takes a single char * option\n",
                            azArg[1]);
          }
          break;
#endif

        case SQLITE_TESTCTRL_INITMODE:
          if( nArg==5 ){
            rc = sqlite3_test_control(testctrl, p->db, 
                          integerValue(azArg[2]),
                          integerValue(azArg[3]),
                          integerValue(azArg[4]));
          }else{
            fprintf(stderr,"Usage: .testctrl initmode fBusy iDb newTnum\n");
            rc = 1;
          }
          break;

        case SQLITE_TESTCTRL_BITVEC_TEST:         
        case SQLITE_TESTCTRL_FAULT_INSTALL:       
        case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: 







|







 







|


|



|







3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
....
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
      { "always",                SQLITE_TESTCTRL_ALWAYS                 },
      { "reserve",               SQLITE_TESTCTRL_RESERVE                },
      { "optimizations",         SQLITE_TESTCTRL_OPTIMIZATIONS          },
      { "iskeyword",             SQLITE_TESTCTRL_ISKEYWORD              },
      { "scratchmalloc",         SQLITE_TESTCTRL_SCRATCHMALLOC          },
      { "byteorder",             SQLITE_TESTCTRL_BYTEORDER              },
      { "never_corrupt",         SQLITE_TESTCTRL_NEVER_CORRUPT          },
      { "imposter",              SQLITE_TESTCTRL_IMPOSTER               },
    };
    int testctrl = -1;
    int rc = 0;
    int i, n;
    open_db(p, 0);

    /* convert testctrl text option to value. allow any unique prefix
................................................................................
          } else {
            fprintf(stderr,"Error: testctrl %s takes a single char * option\n",
                            azArg[1]);
          }
          break;
#endif

        case SQLITE_TESTCTRL_IMPOSTER:
          if( nArg==5 ){
            rc = sqlite3_test_control(testctrl, p->db, 
                          azArg[2],
                          integerValue(azArg[3]),
                          integerValue(azArg[4]));
          }else{
            fprintf(stderr,"Usage: .testctrl initmode dbName onoff tnum\n");
            rc = 1;
          }
          break;

        case SQLITE_TESTCTRL_BITVEC_TEST:         
        case SQLITE_TESTCTRL_FAULT_INSTALL:       
        case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: 

Changes to src/sqlite.h.in.

6261
6262
6263
6264
6265
6266
6267
6268
6269
6270
6271
6272
6273
6274
6275
#define SQLITE_TESTCTRL_LOCALTIME_FAULT         18
#define SQLITE_TESTCTRL_EXPLAIN_STMT            19  /* NOT USED */
#define SQLITE_TESTCTRL_NEVER_CORRUPT           20
#define SQLITE_TESTCTRL_VDBE_COVERAGE           21
#define SQLITE_TESTCTRL_BYTEORDER               22
#define SQLITE_TESTCTRL_ISINIT                  23
#define SQLITE_TESTCTRL_SORTER_MMAP             24
#define SQLITE_TESTCTRL_INITMODE                25
#define SQLITE_TESTCTRL_LAST                    25

/*
** CAPI3REF: SQLite Runtime Status
**
** ^This interface is used to retrieve runtime status information
** about the performance of SQLite, and optionally to reset various







|







6261
6262
6263
6264
6265
6266
6267
6268
6269
6270
6271
6272
6273
6274
6275
#define SQLITE_TESTCTRL_LOCALTIME_FAULT         18
#define SQLITE_TESTCTRL_EXPLAIN_STMT            19  /* NOT USED */
#define SQLITE_TESTCTRL_NEVER_CORRUPT           20
#define SQLITE_TESTCTRL_VDBE_COVERAGE           21
#define SQLITE_TESTCTRL_BYTEORDER               22
#define SQLITE_TESTCTRL_ISINIT                  23
#define SQLITE_TESTCTRL_SORTER_MMAP             24
#define SQLITE_TESTCTRL_IMPOSTER                25
#define SQLITE_TESTCTRL_LAST                    25

/*
** CAPI3REF: SQLite Runtime Status
**
** ^This interface is used to retrieve runtime status information
** about the performance of SQLite, and optionally to reset various

Changes to src/sqliteInt.h.

1083
1084
1085
1086
1087
1088
1089

1090
1091
1092
1093
1094
1095
1096
  int aLimit[SQLITE_N_LIMIT];   /* Limits */
  int nMaxSorterMmap;           /* Maximum size of regions mapped by sorter */
  struct sqlite3InitInfo {      /* Information used during initialization */
    int newTnum;                /* Rootpage of table being initialized */
    u8 iDb;                     /* Which db file is being initialized */
    u8 busy;                    /* TRUE if currently initializing */
    u8 orphanTrigger;           /* Last statement is orphaned TEMP trigger */

  } init;
  int nVdbeActive;              /* Number of VDBEs currently running */
  int nVdbeRead;                /* Number of active VDBEs that read or write */
  int nVdbeWrite;               /* Number of active VDBEs that read and write */
  int nVdbeExec;                /* Number of nested calls to VdbeExec() */
  int nExtension;               /* Number of loaded extensions */
  void **aExtension;            /* Array of shared library handles */







>







1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
  int aLimit[SQLITE_N_LIMIT];   /* Limits */
  int nMaxSorterMmap;           /* Maximum size of regions mapped by sorter */
  struct sqlite3InitInfo {      /* Information used during initialization */
    int newTnum;                /* Rootpage of table being initialized */
    u8 iDb;                     /* Which db file is being initialized */
    u8 busy;                    /* TRUE if currently initializing */
    u8 orphanTrigger;           /* Last statement is orphaned TEMP trigger */
    u8 imposterTable;           /* Building an imposter table */
  } init;
  int nVdbeActive;              /* Number of VDBEs currently running */
  int nVdbeRead;                /* Number of active VDBEs that read or write */
  int nVdbeWrite;               /* Number of active VDBEs that read and write */
  int nVdbeExec;                /* Number of nested calls to VdbeExec() */
  int nExtension;               /* Number of loaded extensions */
  void **aExtension;            /* Array of shared library handles */

Changes to src/test1.c.

5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
5925
....
5953
5954
5955
5956
5957
5958
5959
5960
5961

5962
5963
5964
5965
5966
5967
5968
5969
5970
5971
5972
5973
5974
5975
5976
5977
5978
){
  struct Verb {
    const char *zName;
    int i;
  } aVerb[] = {
    { "SQLITE_TESTCTRL_LOCALTIME_FAULT", SQLITE_TESTCTRL_LOCALTIME_FAULT }, 
    { "SQLITE_TESTCTRL_SORTER_MMAP",     SQLITE_TESTCTRL_SORTER_MMAP     }, 
    { "SQLITE_TESTCTRL_INITMODE",        SQLITE_TESTCTRL_INITMODE        },
  };
  int iVerb;
  int iFlag;
  int rc;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "VERB ARGS...");
................................................................................
      }
      if( getDbPointer(interp, Tcl_GetString(objv[2]), &db) ) return TCL_ERROR;
      if( Tcl_GetIntFromObj(interp, objv[3], &val) ) return TCL_ERROR;
      sqlite3_test_control(SQLITE_TESTCTRL_SORTER_MMAP, db, val);
      break;
    }

    case SQLITE_TESTCTRL_INITMODE: {
      int fBusy, iDb, newTnum;

      sqlite3 *db;
      if( objc!=6 ){
        Tcl_WrongNumArgs(interp, 2, objv, "DB fBusy iDb newTnum");
        return TCL_ERROR;
      }
      if( getDbPointer(interp, Tcl_GetString(objv[2]), &db) ) return TCL_ERROR;
      if( Tcl_GetIntFromObj(interp, objv[3], &fBusy) ) return TCL_ERROR;
      if( Tcl_GetIntFromObj(interp, objv[4], &iDb) ) return TCL_ERROR;
      if( Tcl_GetIntFromObj(interp, objv[5], &newTnum) ) return TCL_ERROR;
      sqlite3_test_control(SQLITE_TESTCTRL_INITMODE, db, fBusy, iDb, newTnum);
      break;
    }
  }

  Tcl_ResetResult(interp);
  return TCL_OK;
}







|







 







|
|
>


|



|
|
|
|







5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
5925
....
5953
5954
5955
5956
5957
5958
5959
5960
5961
5962
5963
5964
5965
5966
5967
5968
5969
5970
5971
5972
5973
5974
5975
5976
5977
5978
5979
){
  struct Verb {
    const char *zName;
    int i;
  } aVerb[] = {
    { "SQLITE_TESTCTRL_LOCALTIME_FAULT", SQLITE_TESTCTRL_LOCALTIME_FAULT }, 
    { "SQLITE_TESTCTRL_SORTER_MMAP",     SQLITE_TESTCTRL_SORTER_MMAP     }, 
    { "SQLITE_TESTCTRL_IMPOSTER",        SQLITE_TESTCTRL_IMPOSTER        },
  };
  int iVerb;
  int iFlag;
  int rc;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "VERB ARGS...");
................................................................................
      }
      if( getDbPointer(interp, Tcl_GetString(objv[2]), &db) ) return TCL_ERROR;
      if( Tcl_GetIntFromObj(interp, objv[3], &val) ) return TCL_ERROR;
      sqlite3_test_control(SQLITE_TESTCTRL_SORTER_MMAP, db, val);
      break;
    }

    case SQLITE_TESTCTRL_IMPOSTER: {
      int onOff, tnum;
      const char *zDbName;
      sqlite3 *db;
      if( objc!=6 ){
        Tcl_WrongNumArgs(interp, 2, objv, "DB dbName onOff tnum");
        return TCL_ERROR;
      }
      if( getDbPointer(interp, Tcl_GetString(objv[2]), &db) ) return TCL_ERROR;
      zDbName = Tcl_GetString(objv[3]);
      if( Tcl_GetIntFromObj(interp, objv[4], &onOff) ) return TCL_ERROR;
      if( Tcl_GetIntFromObj(interp, objv[5], &tnum) ) return TCL_ERROR;
      sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zDbName, onOff, tnum);
      break;
    }
  }

  Tcl_ResetResult(interp);
  return TCL_OK;
}

Name change from test/initmode.test to test/imposter1.test.

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
..
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
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file implements tests for SQLite library.
#
# The focus of this file is adding extra entries in the symbol table
# using sqlite3_test_control(SQLITE_TESTCTRL_INITMODE) and verifying that
# SQLite handles those as expected.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix initmode

# Create a bunch of data to sort against
#
do_test initmode-1.0 {
  execsql {
    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d NOT NULL);
    CREATE INDEX t1b ON t1(b);
    CREATE UNIQUE INDEX t1c ON t1(c);
    WITH RECURSIVE c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<30)
      INSERT INTO t1(a,b,c,d) SELECT i,1000+i,2000+i,3000+i FROM c;
  }
  set t1_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1'}]
  set t1a_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1a'}]
  set t1b_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1b'}]

  # Create a shadow table that uses the same b-tree as t1 but which does
  # not have the indexes
  #
  sqlite3_test_control SQLITE_TESTCTRL_INITMODE db 1 0 $t1_root
  db eval {CREATE TABLE xt1(a,b,c,d)}






  sqlite3_test_control SQLITE_TESTCTRL_INITMODE db 0 0 0

  # Create triggers to record changes to xt1.
  #
  db eval {
    CREATE TEMP TABLE chnglog(desc TEXT);
    CREATE TEMP TRIGGER xt1_del AFTER DELETE ON xt1 BEGIN
      INSERT INTO chnglog VALUES(
................................................................................
  }
} {}

# The xt1 table has separate xt1.rowid and xt1.a columns.  The xt1.rowid
# column corresponds to t1.rowid and t1.a, but the xt1.a column is always
# NULL
#
do_execsql_test initmode-1.1 {
  SELECT rowid FROM xt1 WHERE a IS NOT NULL;
} {}
do_execsql_test initmode-1.2 {
  SELECT a,b,c,d FROM t1 EXCEPT SELECT rowid,b,c,d FROM xt1;
  SELECT rowid,b,c,d FROM xt1 EXCEPT SELECT a,b,c,d FROM t1;
} {}


# Make changes via the xt1 shadow table.  This will not update the
# indexes on t1 nor check the uniqueness constraint on t1.c nor check
# the NOT NULL constraint on t1.d, resulting in a logically inconsistent
# database.
#
do_execsql_test initmode-1.3 {
  DELETE FROM xt1 WHERE rowid=5;
  INSERT INTO xt1(rowid,a,b,c,d) VALUES(99,'hello',1099,2022,NULL);
  SELECT * FROM chnglog ORDER BY rowid;
} [list \
  {DELETE t1: rowid=5, a=NULL, b=1005, c=2005, d=3005} \
  {INSERT t1:  rowid=99, a='hello', b=1099, c=2022, d=NULL} \
]

do_execsql_test initmode-1.4a {
  PRAGMA integrity_check;
} {/NULL value in t1.d/}
do_execsql_test initmode-1.4b {
  PRAGMA integrity_check;
} {/row # missing from index t1b/}








do_execsql_test initmode-1.4c {




  PRAGMA integrity_check;
} {/row # missing from index t1c/}
























finish_test







|





|



|








|
|

|


|

>
>
>
>
>
>
|







 







|


|










|








|


|


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



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

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
..
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
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file implements tests for SQLite library.
#
# The focus of this file is adding extra entries in the symbol table
# using sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER) and verifying that
# SQLite handles those as expected.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix imposter

# Create a bunch of data to sort against
#
do_test imposter-1.0 {
  execsql {
    CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d NOT NULL);
    CREATE INDEX t1b ON t1(b);
    CREATE UNIQUE INDEX t1c ON t1(c);
    WITH RECURSIVE c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<30)
      INSERT INTO t1(a,b,c,d) SELECT i,1000+i,2000+i,3000+i FROM c;
  }
  set t1_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1'}]
  set t1b_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1b'}]
  set t1c_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1c'}]

  # Create an imposter table that uses the same b-tree as t1 but which does
  # not have the indexes
  #
  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $t1_root
  db eval {CREATE TABLE xt1(a,b,c,d)}

  # And create an imposter table for the t1c index.
  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $t1c_root
  db eval {CREATE TABLE xt1c(c,rowid,PRIMARY KEY(c,rowid))WITHOUT ROWID;}

  # Go out of imposter mode for now.
  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0

  # Create triggers to record changes to xt1.
  #
  db eval {
    CREATE TEMP TABLE chnglog(desc TEXT);
    CREATE TEMP TRIGGER xt1_del AFTER DELETE ON xt1 BEGIN
      INSERT INTO chnglog VALUES(
................................................................................
  }
} {}

# The xt1 table has separate xt1.rowid and xt1.a columns.  The xt1.rowid
# column corresponds to t1.rowid and t1.a, but the xt1.a column is always
# NULL
#
do_execsql_test imposter-1.1 {
  SELECT rowid FROM xt1 WHERE a IS NOT NULL;
} {}
do_execsql_test imposter-1.2 {
  SELECT a,b,c,d FROM t1 EXCEPT SELECT rowid,b,c,d FROM xt1;
  SELECT rowid,b,c,d FROM xt1 EXCEPT SELECT a,b,c,d FROM t1;
} {}


# Make changes via the xt1 shadow table.  This will not update the
# indexes on t1 nor check the uniqueness constraint on t1.c nor check
# the NOT NULL constraint on t1.d, resulting in a logically inconsistent
# database.
#
do_execsql_test imposter-1.3 {
  DELETE FROM xt1 WHERE rowid=5;
  INSERT INTO xt1(rowid,a,b,c,d) VALUES(99,'hello',1099,2022,NULL);
  SELECT * FROM chnglog ORDER BY rowid;
} [list \
  {DELETE t1: rowid=5, a=NULL, b=1005, c=2005, d=3005} \
  {INSERT t1:  rowid=99, a='hello', b=1099, c=2022, d=NULL} \
]

do_execsql_test imposter-1.4a {
  PRAGMA integrity_check;
} {/NULL value in t1.d/}
do_execsql_test imposter-1.4b {
  PRAGMA integrity_check;
} {/row # missing from index t1b/}
do_execsql_test imposter-1.4c {
  PRAGMA integrity_check;
} {/row # missing from index t1c/}

# Cleanup the corruption.
# Then demonstrate that the xt1c imposter table can insert non-unique
# and NULL values into the UNIQUE index.
#
do_execsql_test imposter-2.0 {
  DELETE FROM t1;
  WITH RECURSIVE c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<10)
   INSERT INTO t1(a,b,c,d) SELECT i,i,i,i FROM c;
  UPDATE xt1c SET c=NULL WHERE rowid=5;
  PRAGMA integrity_check;
} {/row # missing from index t1c/}

do_execsql_test imposter-2.1 {
  DELETE FROM t1;
  WITH RECURSIVE c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<10)
   INSERT INTO t1(a,b,c,d) SELECT i,i,i,i FROM c;
  UPDATE xt1c SET c=99 WHERE rowid IN (5,7,9);
  SELECT c FROM t1 ORDER BY c;
} {1 2 3 4 6 8 10 99 99 99}
do_execsql_test imposter-2.2 {
  UPDATE xt1 SET c=99 WHERE rowid IN (5,7,9);
  PRAGMA integrity_check;
} {/non-unique entry in index t1c/}

# Erase the imposter tables
#
do_test imposter-3.1 {
  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 1
  db eval {
    DELETE FROM t1 WHERE rowid IN (5,7,9);
    PRAGMA integrity_check;
  }
} {ok}


finish_test