SQLite

Check-in [9780829a77]
Login

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

Overview
Comment:Fix further REUSE_SCHEMA issues.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | reuse-schema1
Files: files | file ages | folders
SHA3-256: 9780829a77a49b199a55e56d21a51d33fee50321621337afabe5a6f977a65eee
User & Date: dan 2018-11-16 17:04:58.793
Context
2018-11-17
18:11
Fix further problems with the feature on this branch. (check-in: 6d7a7e5faf user: dan tags: reuse-schema1)
2018-11-16
17:04
Fix further REUSE_SCHEMA issues. (check-in: 9780829a77 user: dan tags: reuse-schema1)
2018-11-15
21:20
Fix some problems with the feature on this branch. Many problems remain. (check-in: 31b6aee755 user: dan tags: reuse-schema1)
Changes
Side-by-Side Diff Ignore Whitespace Patch
Changes to src/alter.c.
102
103
104
105
106
107
108
109

110
111
112
113
114
115
116
102
103
104
105
106
107
108

109
110
111
112
113
114
115
116







-
+







  savedDbFlags = db->mDbFlags;  
  if( NEVER(db->mallocFailed) ) goto exit_rename_table;
  assert( pSrc->nSrc==1 );
  assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );

  pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]);
  if( !pTab ) goto exit_rename_table;
  iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
  iDb = sqlite3SchemaToIndex2(pParse->db, pTab->pSchema, 0);
  zDb = db->aDb[iDb].zDbSName;
  db->mDbFlags |= DBFLAG_PreferBuiltin;

  /* Get a NULL terminated version of the new table name. */
  zName = sqlite3NameFromToken(db, pName);
  if( !zName ) goto exit_rename_table;

272
273
274
275
276
277
278
279

280
281
282
283
284
285
286
272
273
274
275
276
277
278

279
280
281
282
283
284
285
286







-
+








  db = pParse->db;
  if( pParse->nErr || db->mallocFailed ) return;
  pNew = pParse->pNewTable;
  assert( pNew );

  assert( sqlite3BtreeHoldsAllMutexes(db) );
  iDb = sqlite3SchemaToIndex(db, pNew->pSchema);
  iDb = sqlite3SchemaToIndex2(db, pNew->pSchema, 0);
  zDb = db->aDb[iDb].zDbSName;
  zTab = &pNew->zName[16];  /* Skip the "sqlite_altertab_" prefix on the name */
  pCol = &pNew->aCol[pNew->nCol-1];
  pDflt = pCol->pDflt;
  pTab = sqlite3FindTable(db, zTab, zDb);
  assert( pTab );

425
426
427
428
429
430
431
432

433
434
435
436
437
438
439
425
426
427
428
429
430
431

432
433
434
435
436
437
438
439







-
+







    goto exit_begin_add_column;
  }
  if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){
    goto exit_begin_add_column;
  }

  assert( pTab->addColOffset>0 );
  iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
  iDb = sqlite3SchemaToIndex2(db, pTab->pSchema, 0);

  /* Put a copy of the Table struct in Parse.pNewTable for the
  ** sqlite3AddColumn() function and friends to modify.  But modify
  ** the name by adding an "sqlite_altertab_" prefix.  By adding this
  ** prefix, we insure that the name will not collide with an existing
  ** table because user table are not allowed to have the "sqlite_"
  ** prefix on their name.
526
527
528
529
530
531
532
533

534
535
536
537
538
539
540
526
527
528
529
530
531
532

533
534
535
536
537
538
539
540







-
+







  if( !pTab ) goto exit_rename_column;

  /* Cannot alter a system table */
  if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ) goto exit_rename_column;
  if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column;

  /* Which schema holds the table to be altered */  
  iSchema = sqlite3SchemaToIndex(db, pTab->pSchema);
  iSchema = sqlite3SchemaToIndex2(db, pTab->pSchema, 0);
  assert( iSchema>=0 );
  zDb = db->aDb[iSchema].zDbSName;

#ifndef SQLITE_OMIT_AUTHORIZATION
  /* Invoke the authorization callback. */
  if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
    goto exit_rename_column;
1054
1055
1056
1057
1058
1059
1060
1061

1062
1063
1064
1065
1066
1067
1068
1054
1055
1056
1057
1058
1059
1060

1061
1062
1063
1064
1065
1066
1067
1068







-
+







  NameContext sNC;
  int rc = SQLITE_OK;

  memset(&sNC, 0, sizeof(sNC));
  sNC.pParse = pParse;
  assert( pNew->pTabSchema );
  pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, 
      db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName
      db->aDb[sqlite3SchemaToIndex2(db, pNew->pTabSchema, 0)].zDbSName
  );
  pParse->eTriggerOp = pNew->op;
  /* ALWAYS() because if the table of the trigger does not exist, the
  ** error would have been hit before this point */
  if( ALWAYS(pParse->pTriggerTab) ){
    rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab);
  }
1571
1572
1573
1574
1575
1576
1577
1578

1579
1580
1581
1582
1583
1584
1585
1571
1572
1573
1574
1575
1576
1577

1578
1579
1580
1581
1582
1583
1584
1585







-
+







      }

      else if( sParse.pNewTrigger ){
        if( isLegacy==0 ){
          rc = renameResolveTrigger(&sParse, bTemp ? 0 : zDb);
        }
        if( rc==SQLITE_OK ){
          int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema);
          int i1 = sqlite3SchemaToIndex2(db, sParse.pNewTrigger->pTabSchema, 0);
          int i2 = sqlite3FindDbName(db, zDb);
          if( i1==i2 ) sqlite3_result_int(context, 1);
        }
      }
    }

    if( rc!=SQLITE_OK ){
Changes to src/analyze.c.
975
976
977
978
979
980
981

982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995

996
997
998
999
1000
1001
1002







+













-








/*
** Generate code to do an analysis of all indices associated with
** a single table.
*/
static void analyzeOneTable(
  Parse *pParse,   /* Parser context */
  int iDb,         /* Database that contains table pTab */
  Table *pTab,     /* Table whose indices are to be analyzed */
  Index *pOnlyIdx, /* If not NULL, only analyze this one index */
  int iStatCur,    /* Index of VdbeCursor that writes the sqlite_stat1 table */
  int iMem,        /* Available memory locations begin here */
  int iTab         /* Next available cursor */
){
  sqlite3 *db = pParse->db;    /* Database handle */
  Index *pIdx;                 /* An index to being analyzed */
  int iIdxCur;                 /* Cursor open on index being analyzed */
  int iTabCur;                 /* Table cursor */
  Vdbe *v;                     /* The virtual machine being built up */
  int i;                       /* Loop counter */
  int jZeroRows = -1;          /* Jump from here if number of rows is zero */
  int iDb;                     /* Index of database containing pTab */
  u8 needTableCnt = 1;         /* True to count the table */
  int regNewRowid = iMem++;    /* Rowid for the inserted record */
  int regStat4 = iMem++;       /* Register to hold Stat4Accum object */
  int regChng = iMem++;        /* Index of changed index field */
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
  int regRowid = iMem++;       /* Rowid argument passed to stat_push() */
#endif
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1019
1020
1021
1022
1023
1024
1025

1026
1027
1028
1029
1030
1031
1032







-







    return;
  }
  if( sqlite3_strlike("sqlite\\_%", pTab->zName, '\\')==0 ){
    /* Do not gather statistics on system tables */
    return;
  }
  assert( sqlite3BtreeHoldsAllMutexes(db) );
  iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
  assert( iDb>=0 );
  assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
#ifndef SQLITE_OMIT_AUTHORIZATION
  if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0,
      db->aDb[iDb].zDbSName ) ){
    return;
  }
1114
1115
1116
1117
1118
1119
1120
1121

1122
1123
1124
1125
1126
1127
1128
1113
1114
1115
1116
1117
1118
1119

1120
1121
1122
1123
1124
1125
1126
1127







-
+







    /* Make sure there are enough memory cells allocated to accommodate 
    ** the regPrev array and a trailing rowid (the rowid slot is required
    ** when building a record to insert into the sample column of 
    ** the sqlite_stat4 table.  */
    pParse->nMem = MAX(pParse->nMem, regPrev+nColTest);

    /* Open a read-only cursor on the index being analyzed. */
    assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) );
    assert( db->aDb[iDb].pSchema==pIdx->pSchema );
    sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb);
    sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
    VdbeComment((v, "%s", pIdx->zName));

    /* Invoke the stat_init() function. The arguments are:
    ** 
    **    (1) the number of columns in the index including the rowid
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352

1353
1354
1355
1356
1357
1358
1359
1360
1361
1362

1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377



1378
1379
1380
1381
1382
1383
1384
1334
1335
1336
1337
1338
1339
1340

1341
1342
1343
1344
1345
1346
1347
1348
1349

1350
1351
1352
1353
1354
1355
1356
1357
1358
1359

1360

1361
1362
1363
1364

1365
1366
1367
1368
1369
1370
1371
1372

1373
1374
1375
1376
1377
1378
1379
1380
1381
1382







-









-
+









-
+
-




-








-
+
+
+







  sqlite3 *db = pParse->db;
  Schema *pSchema = db->aDb[iDb].pSchema;    /* Schema of database iDb */
  HashElem *k;
  int iStatCur;
  int iMem;
  int iTab;

  sqlite3SchemaWritable(pParse, iDb);
  sqlite3BeginWriteOperation(pParse, 0, iDb);
  iStatCur = pParse->nTab;
  pParse->nTab += 3;
  openStatTable(pParse, iDb, iStatCur, 0, 0);
  iMem = pParse->nMem+1;
  iTab = pParse->nTab;
  assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
  for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){
    Table *pTab = (Table*)sqliteHashData(k);
    analyzeOneTable(pParse, pTab, 0, iStatCur, iMem, iTab);
    analyzeOneTable(pParse, iDb, pTab, 0, iStatCur, iMem, iTab);
  }
  loadAnalysis(pParse, iDb);
}

/*
** Generate code that will do an analysis of a single table in
** a database.  If pOnlyIdx is not NULL then it is a single index
** in pTab that should be analyzed.
*/
static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){
static void analyzeTable(Parse *pParse, int iDb, Table *pTab, Index *pOnlyIdx){
  int iDb;
  int iStatCur;

  assert( pTab!=0 );
  assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
  iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
  sqlite3BeginWriteOperation(pParse, 0, iDb);
  iStatCur = pParse->nTab;
  pParse->nTab += 3;
  if( pOnlyIdx ){
    openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx");
  }else{
    openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl");
  }
  analyzeOneTable(pParse, pTab, pOnlyIdx, iStatCur,pParse->nMem+1,pParse->nTab);
  analyzeOneTable(pParse, iDb, pTab, 
      pOnlyIdx, iStatCur, pParse->nMem+1, pParse->nTab
  );
  loadAnalysis(pParse, iDb);
}

/*
** Generate code for the ANALYZE command.  The parser calls this routine
** when it recognizes an ANALYZE command.
**
1421
1422
1423
1424
1425
1426
1427
1428

1429
1430

1431
1432
1433
1434
1435
1436
1437
1419
1420
1421
1422
1423
1424
1425

1426
1427

1428
1429
1430
1431
1432
1433
1434
1435







-
+

-
+







    /* Form 3: Analyze the table or index named as an argument */
    iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pTableName);
    if( iDb>=0 ){
      zDb = pName2->n ? db->aDb[iDb].zDbSName : 0;
      z = sqlite3NameFromToken(db, pTableName);
      if( z ){
        if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){
          analyzeTable(pParse, pIdx->pTable, pIdx);
          analyzeTable(pParse, iDb, pIdx->pTable, pIdx);
        }else if( (pTab = sqlite3LocateTable(pParse, 0, z, zDb))!=0 ){
          analyzeTable(pParse, pTab, 0);
          analyzeTable(pParse, iDb, pTab, 0);
        }
        sqlite3DbFree(db, z);
      }
    }
  }
  if( db->nSqlExec==0 && (v = sqlite3GetVdbe(pParse))!=0 ){
    sqlite3VdbeAddOp0(v, OP_Expire);
Changes to src/build.c.
262
263
264
265
266
267
268
269



270
271
272
273
274
275
276
262
263
264
265
266
267
268

269
270
271
272
273
274
275
276
277
278







-
+
+
+







  if( zSql==0 ){
    return;   /* A malloc must have failed */
  }
  pParse->nested++;
  memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ);
  memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ);
  sqlite3RunParser(pParse, zSql, &zErrMsg);
  sqlite3DbFree(db, zErrMsg);
  if( zErrMsg ){
    sqlite3ErrorMsg(pParse, "%z", zErrMsg);
  }
  sqlite3DbFree(db, zSql);
  memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ);
  pParse->nested--;
}

#if SQLITE_USER_AUTHENTICATION
/*
3531
3532
3533
3534
3535
3536
3537
3538

3539
3540
3541
3542
3543
3544
3545
3533
3534
3535
3536
3537
3538
3539

3540
3541
3542
3543
3544
3545
3546
3547







-
+







    }
  }

  /* When adding an index to the list of indices for a table, make
  ** sure all indices labeled OE_Replace come after all those labeled
  ** OE_Ignore.  This is necessary for the correct constraint check
  ** processing (in sqlite3GenerateConstraintChecks()) as part of
  ** UPDATE and INSERT statements.  
  ** UPDATE and INSERT statements.
  */
  if( db->init.busy || pTblName==0 ){
    if( onError!=OE_Replace || pTab->pIndex==0
         || pTab->pIndex->onError==OE_Replace){
      pIndex->pNext = pTab->pIndex;
      pTab->pIndex = pIndex;
    }else{
Changes to src/delete.c.
659
660
661
662
663
664
665
666

667
668
669
670
671
672
673
659
660
661
662
663
664
665

666
667
668
669
670
671
672
673







-
+







**   then it identifies an index cursor (from within array of cursors
**   starting at iIdxCur) that already points to the index entry to be deleted.
**   Except, this optimization is disabled if there are BEFORE triggers since
**   the trigger body might have moved the cursor.
*/
void sqlite3GenerateRowDelete(
  Parse *pParse,     /* Parsing context */
  int iDb,
  int iDb,           /* Database containing pTab */
  Table *pTab,       /* Table containing the row to be deleted */
  Trigger *pTrigger, /* List of triggers to (potentially) fire */
  int iDataCur,      /* Cursor from which column data is extracted */
  int iIdxCur,       /* First index cursor */
  int iPk,           /* First memory cell containing the PRIMARY KEY */
  i16 nPk,           /* Number of PRIMARY KEY memory cells */
  u8 count,          /* If non-zero, increment the row change counter */
Changes to src/insert.c.
1033
1034
1035
1036
1037
1038
1039
1040
1041



1042
1043
1044
1045
1046
1047
1048
1033
1034
1035
1036
1037
1038
1039


1040
1041
1042
1043
1044
1045
1046
1047
1048
1049







-
-
+
+
+







      sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
      sqlite3MayAbort(pParse);
    }else
#endif
    {
      int isReplace;    /* Set to true if constraints may cause a replace */
      int bUseSeek;     /* True to use OPFLAG_SEEKRESULT */
      sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
          regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert
      sqlite3GenerateConstraintChecks(pParse, iDb, pTab, aRegIdx, iDataCur, 
          iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 
          0, pUpsert
      );
      sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0);

      /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE
      ** constraints or (b) there are no triggers and this table is not a
      ** parent table in a foreign key constraint. It is safe to set the
      ** flag in the second case as if any REPLACE constraint is hit, an
1270
1271
1272
1273
1274
1275
1276

1277
1278
1279
1280
1281
1282
1283
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285







+







** Which action to take is determined by the overrideError parameter.
** Or if overrideError==OE_Default, then the pParse->onError parameter
** is used.  Or if pParse->onError==OE_Default then the onError value
** for the constraint is used.
*/
void sqlite3GenerateConstraintChecks(
  Parse *pParse,       /* The parser context */
  int iDb,             /* Databse that contains pTab */
  Table *pTab,         /* The table being inserted or updated */
  int *aRegIdx,        /* Use register aRegIdx[i] for index i.  0 for unused */
  int iDataCur,        /* Canonical data cursor (main table or PK index) */
  int iIdxCur,         /* First index cursor */
  int regNewData,      /* First register in a range holding values to insert */
  int regOldData,      /* Previous content.  0 for INSERTs */
  u8 pkChng,           /* Non-zero if the rowid or PRIMARY KEY changed */
1553
1554
1555
1556
1557
1558
1559
1560
1561


1562
1563
1564
1565
1566
1567
1568
1555
1556
1557
1558
1559
1560
1561


1562
1563
1564
1565
1566
1567
1568
1569
1570







-
-
+
+







        */
        Trigger *pTrigger = 0;
        if( db->flags&SQLITE_RecTriggers ){
          pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
        }
        if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){
          sqlite3MultiWrite(pParse);
          sqlite3GenerateRowDelete(pParse, 0, pTab, pTrigger, iDataCur, iIdxCur,
                                   regNewData, 1, 0, OE_Replace, 1, -1);
          sqlite3GenerateRowDelete(pParse, iDb, pTab, pTrigger, iDataCur, 
                                  iIdxCur, regNewData, 1, 0, OE_Replace, 1, -1);
        }else{
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
          assert( HasRowid(pTab) );
          /* This OP_Delete opcode fires the pre-update-hook only. It does
          ** not modify the b-tree. It is more efficient to let the coming
          ** OP_Insert replace the existing entry than it is to delete the
          ** existing entry and then insert a new one. */
1802
1803
1804
1805
1806
1807
1808
1809

1810
1811
1812
1813
1814
1815
1816
1804
1805
1806
1807
1808
1809
1810

1811
1812
1813
1814
1815
1816
1817
1818







-
+







        assert( onError==OE_Replace );
        if( db->flags&SQLITE_RecTriggers ){
          pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
        }
        if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){
          sqlite3MultiWrite(pParse);
        }
        sqlite3GenerateRowDelete(pParse, 0, pTab, pTrigger, iDataCur, iIdxCur,
        sqlite3GenerateRowDelete(pParse, iDb, pTab, pTrigger, iDataCur, iIdxCur,
            regR, nPkField, 0, OE_Replace,
            (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur);
        seenReplace = 1;
        break;
      }
    }
    if( pUpIdx==pIdx ){
Changes to src/sqliteInt.h.
4032
4033
4034
4035
4036
4037
4038
4039

4040
4041
4042
4043
4044
4045
4046
4032
4033
4034
4035
4036
4037
4038

4039
4040
4041
4042
4043
4044
4045
4046







-
+







#endif
void sqlite3GenerateRowDelete(
    Parse*,int,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int);
void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int);
int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int);
void sqlite3ResolvePartIdxLabel(Parse*,int);
int sqlite3ExprReferencesUpdatedColumn(Expr*,int*,int);
void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int,
void sqlite3GenerateConstraintChecks(Parse*,int,Table*,int*,int,int,int,int,
                                     u8,u8,int,int*,int*,Upsert*);
#ifdef SQLITE_ENABLE_NULL_TRIM
  void sqlite3SetMakeRecordP5(Vdbe*,Table*);
#else
# define sqlite3SetMakeRecordP5(A,B)
#endif
void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int);
Changes to src/update.c.
706
707
708
709
710
711
712
713
714
715



716
717
718
719
720
721
722
706
707
708
709
710
711
712



713
714
715
716
717
718
719
720
721
722







-
-
-
+
+
+







  }

  if( !isView ){
    int addr1 = 0;        /* Address of jump instruction */

    /* Do constraint checks. */
    assert( regOldRowid>0 );
    sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
        regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace,
        aXRef, 0);
    sqlite3GenerateConstraintChecks(pParse, iDb, pTab, aRegIdx, iDataCur, 
        iIdxCur, regNewRowid, regOldRowid, chngKey, onError, labelContinue, 
        &bReplace, aXRef, 0);

    /* Do FK constraint checks. */
    if( hasFK ){
      sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey);
    }

    /* Delete the index entries associated with the current record.  */
Changes to test/reuse1.test.
117
118
119
120
121
122
123


124
125
126
127
128
129
130
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132







+
+







  } {aux1 aux2 aux3}

  do_execsql_test 2.2 {
    SELECT * FROM aux.ft_content;
  } {1 aux1 2 aux2 3 aux3}
}

#-------------------------------------------------------------------------
#
reset_db
forcedelete test.db2
do_execsql_test 3.0 {
  CREATE TABLE t1(a PRIMARY KEY, b, c);
  CREATE VIEW v1 AS SELECT * FROM t1;
  CREATE TRIGGER v1_ins INSTEAD OF INSERT ON v1 BEGIN
    INSERT INTO t1 VALUES(new.a, new.b, new.c);
193
194
195
196
197
198
199

200














































































































201
202

195
196
197
198
199
200
201
202

203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315







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


+
do_execsql_test 3.13.2 {
  INSERT INTO aux.v1 VALUES('x', 'y', 'z');
}
do_execsql_test 3.13.3 {
  SELECT * FROM v1;
} {1 2 3 x y z}

#-------------------------------------------------------------------------

#
reset_db
forcedelete test.db2
do_execsql_test 4.0 {
  CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE);
  CREATE TABLE del(a, b, c);
  CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN
    INSERT INTO del VALUES(old.a, old.b, old.c);
  END;
}
forcecopy test.db test.db2

db close
sqlite3 db test.db -reuse-schema 1
execsql { 
  ATTACH 'test.db2' AS aux;
  PRAGMA recursive_triggers = 1;
}

do_execsql_test 4.1 {
  INSERT INTO main.t1 VALUES(1, 2, 3);
  INSERT INTO aux.t1 VALUES(4, 5, 6);
}

do_execsql_test 4.2.1 {
  INSERT OR REPLACE INTO aux.t1 VALUES('a', 'b', 6);
  SELECT * FROM aux.t1;
} {a b 6}
do_execsql_test 4.2.2 { SELECT * FROM aux.del  } {4 5 6}
do_execsql_test 4.2.3 { SELECT * FROM main.del } {}

do_execsql_test 4.3.1 {
  INSERT INTO aux.t1 VALUES('x', 'y', 'z');
  UPDATE OR REPLACE aux.t1 SET c='z' WHERE a='a';
} {}
do_execsql_test 4.3.2 { SELECT * FROM aux.del  } {4 5 6 x y z}
do_execsql_test 4.3.3 { SELECT * FROM main.del } {}

#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 5.0 {
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
  CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c);
  CREATE INDEX i1 ON t1(b);
  INSERT INTO t1 VALUES(1, 2, 3), (4, 5, 6);
  ANALYZE;
  PRAGMA writable_schema = 1;
  DELETE FROM sqlite_stat1;
}
db close
forcecopy test.db test.db2
sqlite3 db test.db -reuse-schema 1
execsql { ATTACH 'test.db2' AS aux }

foreach {tn sql} {
  1 { CREATE TABLE t3(x) }
  2 { DROP TABLE t2 }
  3 { CREATE INDEX i2 ON t2(b) }
  4 { DROP INDEX i1 }
  5 { ALTER TABLE t1 ADD COLUMN d }
  6 { ALTER TABLE t1 RENAME TO t3 }
  7 { ALTER TABLE t1 RENAME c TO d }
} {
  do_catchsql_test 5.1.$tn $sql {1 {attempt to modify read-only schema}}
}

do_execsql_test 5.2.1 { ANALYZE aux.t1 } {}
do_execsql_test 5.2.2 { SELECT * FROM aux.sqlite_stat1  } {t1 i1 {2 1}}
do_execsql_test 5.2.3 { SELECT * FROM main.sqlite_stat1 } {}

do_test 5.3.0 {
  sqlite3 db2 test.db2
  db2 eval { 
    PRAGMA writable_schema = 1;
    DELETE FROM sqlite_stat1;
  }
} {}

do_execsql_test 5.3.1 { SELECT * FROM aux.sqlite_stat1  } {}
do_execsql_test 5.3.2 { ANALYZE aux } {}
do_execsql_test 5.3.3 { SELECT * FROM aux.sqlite_stat1  } {t1 i1 {2 1}}
do_execsql_test 5.3.4 { SELECT * FROM main.sqlite_stat1 } {}

#-------------------------------------------------------------------------
# Attempting to run ANALYZE when the required sqlite_statXX functions
# are missing is an error (because it would modify the database schema).
#
reset_db
do_execsql_test 5.4 {
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
  CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c);
  CREATE INDEX i1 ON t1(b);
  INSERT INTO t1 VALUES(1, 2, 3), (4, 5, 6);
}
db close
sqlite3 db test.db -reuse-schema 1
foreach {tn sql} {
  1 { ANALYZE }
  2 { ANALYZE t1 }
  3 { ANALYZE i1 }
  4 { ANALYZE main }
  5 { ANALYZE main.t1 }
  6 { ANALYZE main.i1 }
} {
  do_catchsql_test 5.4.$tn $sql {1 {attempt to modify read-only schema}}
}



finish_test