/ Check-in [a0e06d2c]
Login

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

Overview
Comment:After modifying and reparsing the schema as part of an ALTER TABLE RENAME COLUMN, check that no new schema errors have been introduced (e.g. ambiguous column names in views) before committing the operation.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | alter-table-rename-column
Files: files | file ages | folders
SHA3-256:a0e06d2c5e3abb3f300491e7651bb177a436899efd4506de9239359096b6a9e7
User & Date: dan 2018-08-24 17:55:49
Context
2018-08-24
20:10
Fix a problem with renaming a column that occurs as an "excluded.colname" construction in an UPSERT that is part of a trigger program. check-in: bb2f7234 user: dan tags: alter-table-rename-column
17:55
After modifying and reparsing the schema as part of an ALTER TABLE RENAME COLUMN, check that no new schema errors have been introduced (e.g. ambiguous column names in views) before committing the operation. check-in: a0e06d2c user: dan tags: alter-table-rename-column
16:04
Avoid incorrectly replacing tokens that refer to a column being renamed via an alias. For example, do not overwrite "xyz" when "a" is renamed in "CREATE VIEW v1 AS SELECT a AS xyz FROM tbl WHERE xyz=1" check-in: ad072a83 user: dan tags: alter-table-rename-column
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/alter.c.

887
888
889
890
891
892
893











894
895
896
897
898
899
900
....
1066
1067
1068
1069
1070
1071
1072

1073
1074
1075
1076
1077
1078
1079
1080
1081



1082
1083
1084
1085
1086
1087
1088
....
1136
1137
1138
1139
1140
1141
1142
1143



1144
1145
1146
1147
1148
1149
1150
....
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
  );

  /* Drop and reload the database schema. */
  if( pParse->pVdbe ){
    sqlite3ChangeCookie(pParse, iSchema);
    sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iSchema, 0);
  }












 exit_rename_column:
  sqlite3SrcListDelete(db, pSrc);
  sqlite3DbFree(db, zOld);
  sqlite3DbFree(db, zNew);
  return;
}
................................................................................
** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an
** ALTER TABLE RENAME COLUMN program. The error message emitted by the
** sub-routine is currently stored in pParse->zErrMsg. This function
** adds context to the error message and then stores it in pCtx.
*/
static void renameColumnParseError(
  sqlite3_context *pCtx, 

  sqlite3_value *pType,
  sqlite3_value *pObject,
  Parse *pParse
){
  const char *zT = (const char*)sqlite3_value_text(pType);
  const char *zN = (const char*)sqlite3_value_text(pObject);
  char *zErr;

  zErr = sqlite3_mprintf("error processing %s %s: %s", zT, zN, pParse->zErrMsg);



  sqlite3_result_error(pCtx, zErr, -1);
  sqlite3_free(zErr);
}

/*
** For each name in the the expression-list pEList (i.e. each
** pEList->a[i].zName) that matches the string in zOld, extract the 
................................................................................
**   0. zSql:     SQL statement to rewrite
**   1. type:     Type of object ("table", "view" etc.)
**   2. object:   Name of object
**   3. Database: Database name (e.g. "main")
**   4. Table:    Table name
**   5. iCol:     Index of column to rename
**   6. zNew:     New column name
**   7. bQuote:   True if the new column name should be quoted



**
** Do a column rename operation on the CREATE statement given in zSql.
** The iCol-th column (left-most is 0) of table zTable is renamed from zCol
** into zNew.  The name should be quoted if bQuote is true.
**
** This function is used internally by the ALTER TABLE RENAME COLUMN command.
** Though accessible to application code, it is not intended for use by
................................................................................
  }else{
    rc = SQLITE_NOMEM;
  }

renameColumnFunc_done:
  if( rc!=SQLITE_OK ){
    if( sParse.zErrMsg ){
      renameColumnParseError(context, argv[1], argv[2], &sParse);
    }else{
      sqlite3_result_error_code(context, rc);
    }
  }

  if( sParse.pVdbe ){
    sqlite3VdbeFinalize(sParse.pVdbe);







>
>
>
>
>
>
>
>
>
>
>







 







>








|
>
>
>







 







|
>
>
>







 







|







887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
....
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
....
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
....
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
  );

  /* Drop and reload the database schema. */
  if( pParse->pVdbe ){
    sqlite3ChangeCookie(pParse, iSchema);
    sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iSchema, 0);
  }

  sqlite3NestedParse(pParse, 
      "SELECT 1 "
      "FROM \"%w\".%s "
      "WHERE name NOT LIKE 'sqlite_%%' AND (type != 'index' OR tbl_name = %Q)"
      " AND sql NOT LIKE 'create virtual%%'"
      " AND sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, -1)=0 ",
      zDb, MASTER_NAME, 
      pTab->zName,
      zDb, pTab->zName, iCol, zNew
  );

 exit_rename_column:
  sqlite3SrcListDelete(db, pSrc);
  sqlite3DbFree(db, zOld);
  sqlite3DbFree(db, zNew);
  return;
}
................................................................................
** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an
** ALTER TABLE RENAME COLUMN program. The error message emitted by the
** sub-routine is currently stored in pParse->zErrMsg. This function
** adds context to the error message and then stores it in pCtx.
*/
static void renameColumnParseError(
  sqlite3_context *pCtx, 
  int bPost,
  sqlite3_value *pType,
  sqlite3_value *pObject,
  Parse *pParse
){
  const char *zT = (const char*)sqlite3_value_text(pType);
  const char *zN = (const char*)sqlite3_value_text(pObject);
  char *zErr;

  zErr = sqlite3_mprintf("error in %s %s%s: %s", 
      zT, zN, (bPost ? " after rename" : ""),
      pParse->zErrMsg
  );
  sqlite3_result_error(pCtx, zErr, -1);
  sqlite3_free(zErr);
}

/*
** For each name in the the expression-list pEList (i.e. each
** pEList->a[i].zName) that matches the string in zOld, extract the 
................................................................................
**   0. zSql:     SQL statement to rewrite
**   1. type:     Type of object ("table", "view" etc.)
**   2. object:   Name of object
**   3. Database: Database name (e.g. "main")
**   4. Table:    Table name
**   5. iCol:     Index of column to rename
**   6. zNew:     New column name
**   7. bQuote:   Non-zero if the new column name should be quoted. Negative
**                if this function is being called to check that the schema
**                can still be parsed and symbols resolved after the column
**                has been renamed.
**
** Do a column rename operation on the CREATE statement given in zSql.
** The iCol-th column (left-most is 0) of table zTable is renamed from zCol
** into zNew.  The name should be quoted if bQuote is true.
**
** This function is used internally by the ALTER TABLE RENAME COLUMN command.
** Though accessible to application code, it is not intended for use by
................................................................................
  }else{
    rc = SQLITE_NOMEM;
  }

renameColumnFunc_done:
  if( rc!=SQLITE_OK ){
    if( sParse.zErrMsg ){
      renameColumnParseError(context, (bQuote<0), argv[1], argv[2], &sParse);
    }else{
      sqlite3_result_error_code(context, rc);
    }
  }

  if( sParse.pVdbe ){
    sqlite3VdbeFinalize(sParse.pVdbe);

Changes to src/vdbe.c.

5750
5751
5752
5753
5754
5755
5756

5757
5758
5759
5760
5761
5762
5763

#ifndef SQLITE_OMIT_ALTERTABLE
  if( pOp->p4.z==0 ){
    sqlite3SchemaClear(db->aDb[iDb].pSchema);
    db->mDbFlags &= ~DBFLAG_SchemaKnownOk;
    rc = sqlite3InitOne(db, iDb, &p->zErrMsg, INITFLAG_AlterTable);
    db->mDbFlags |= DBFLAG_SchemaChange;

  }else
#endif
  {
    zMaster = MASTER_NAME;
    initData.db = db;
    initData.iDb = pOp->p1;
    initData.pzErrMsg = &p->zErrMsg;







>







5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
5762
5763
5764

#ifndef SQLITE_OMIT_ALTERTABLE
  if( pOp->p4.z==0 ){
    sqlite3SchemaClear(db->aDb[iDb].pSchema);
    db->mDbFlags &= ~DBFLAG_SchemaKnownOk;
    rc = sqlite3InitOne(db, iDb, &p->zErrMsg, INITFLAG_AlterTable);
    db->mDbFlags |= DBFLAG_SchemaChange;
    p->expired = 0;
  }else
#endif
  {
    zMaster = MASTER_NAME;
    initData.db = db;
    initData.iDb = pOp->p1;
    initData.pzErrMsg = &p->zErrMsg;

Changes to test/altercol.test.

337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
...
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
...
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
...
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
...
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
...
645
646
647
648
649
650
651
652





















653
  ALTER TABLE b2 RENAME x TO hello;
  SELECT sql FROM sqlite_master WHERE name='xxx';
} {{CREATE VIEW xxx AS SELECT a FROM b1 UNION SELECT hello FROM b2 ORDER BY 1 COLLATE nocase}}

do_catchsql_test 8.4.5 {
  CREATE VIEW zzz AS SELECT george, ringo FROM b1;
  ALTER TABLE b1 RENAME a TO aaa;
} {1 {error processing view zzz: no such column: george}}

#-------------------------------------------------------------------------
# More triggers.
#
proc do_rename_column_test {tn old new lSchema} {
  for {set i 0} {$i < 2} {incr i} {
    drop_all_tables_and_views db
................................................................................

do_execsql_test 11.2 {
  CREATE VIEW v1 AS SELECT e1.*, x1.c FROM e1, x1;
}

do_catchsql_test 11.3 {
  ALTER TABLE x1 RENAME c TO ccc;
} {1 {error processing view v1: no such module: echo}}

#-------------------------------------------------------------------------
# Test some error conditions:
#
#   1. Renaming a column of a system table,
#   2. Renaming a column of a VIEW,
#   3. Renaming a column of a virtual table.
................................................................................
  CREATE TRIGGER tr1 AFTER INSERT ON x1 BEGIN
    SELECT * FROM nosuchtable;
  END;
}

do_catchsql_test 13.1.2 {
  ALTER TABLE x1 RENAME COLUMN t TO ttt;
} {1 {error processing trigger tr1: no such table: main.nosuchtable}}

do_execsql_test 13.1.3 {
  DROP TRIGGER tr1;
  CREATE INDEX x1i ON x1(i);
  SELECT sql FROM sqlite_master WHERE name='x1i';
} {{CREATE INDEX x1i ON x1(i)}}

................................................................................
do_execsql_test 13.1.4 {
  PRAGMA writable_schema = 1;
  UPDATE sqlite_master SET sql = 'CREATE INDEX x1i ON x1(j)' WHERE name='x1i';
} {}

do_catchsql_test 13.1.5 {
  ALTER TABLE x1 RENAME COLUMN t TO ttt;
} {1 {error processing index x1i: no such column: j}}

do_execsql_test 13.1.6 {
  UPDATE sqlite_master SET sql = '' WHERE name='x1i';
} {}

do_catchsql_test 13.1.7 {
  ALTER TABLE x1 RENAME COLUMN t TO ttt;
................................................................................
  do_execsql_test 13.2.$tn.1 "
    DROP TRIGGER IF EXISTS tr1;
    $trigger
  "

  do_catchsql_test 13.2.$tn.2 {
    ALTER TABLE x1 RENAME COLUMN t TO ttt;
  } "1 {error processing trigger tr1: $error}"
}

#-------------------------------------------------------------------------
# Passing invalid parameters directly to sqlite_rename_column().
#
do_execsql_test 14.1 {
  CREATE TABLE ddd(sql, type, object, db, tbl, icol, znew, bquote);
................................................................................
  CREATE VIEW vvv AS SELECT a AS d FROM xxx WHERE d=0;
  ALTER TABLE xxx RENAME a TO xyz;
}

do_execsql_test 15.2 {
  SELECT sql FROM sqlite_master WHERE type='view';
} {{CREATE VIEW vvv AS SELECT xyz AS d FROM xxx WHERE d=0}}






















finish_test







|







 







|







 







|







 







|







 







|







 








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

337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
...
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
...
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
...
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
...
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
...
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
  ALTER TABLE b2 RENAME x TO hello;
  SELECT sql FROM sqlite_master WHERE name='xxx';
} {{CREATE VIEW xxx AS SELECT a FROM b1 UNION SELECT hello FROM b2 ORDER BY 1 COLLATE nocase}}

do_catchsql_test 8.4.5 {
  CREATE VIEW zzz AS SELECT george, ringo FROM b1;
  ALTER TABLE b1 RENAME a TO aaa;
} {1 {error in view zzz: no such column: george}}

#-------------------------------------------------------------------------
# More triggers.
#
proc do_rename_column_test {tn old new lSchema} {
  for {set i 0} {$i < 2} {incr i} {
    drop_all_tables_and_views db
................................................................................

do_execsql_test 11.2 {
  CREATE VIEW v1 AS SELECT e1.*, x1.c FROM e1, x1;
}

do_catchsql_test 11.3 {
  ALTER TABLE x1 RENAME c TO ccc;
} {1 {error in view v1: no such module: echo}}

#-------------------------------------------------------------------------
# Test some error conditions:
#
#   1. Renaming a column of a system table,
#   2. Renaming a column of a VIEW,
#   3. Renaming a column of a virtual table.
................................................................................
  CREATE TRIGGER tr1 AFTER INSERT ON x1 BEGIN
    SELECT * FROM nosuchtable;
  END;
}

do_catchsql_test 13.1.2 {
  ALTER TABLE x1 RENAME COLUMN t TO ttt;
} {1 {error in trigger tr1: no such table: main.nosuchtable}}

do_execsql_test 13.1.3 {
  DROP TRIGGER tr1;
  CREATE INDEX x1i ON x1(i);
  SELECT sql FROM sqlite_master WHERE name='x1i';
} {{CREATE INDEX x1i ON x1(i)}}

................................................................................
do_execsql_test 13.1.4 {
  PRAGMA writable_schema = 1;
  UPDATE sqlite_master SET sql = 'CREATE INDEX x1i ON x1(j)' WHERE name='x1i';
} {}

do_catchsql_test 13.1.5 {
  ALTER TABLE x1 RENAME COLUMN t TO ttt;
} {1 {error in index x1i: no such column: j}}

do_execsql_test 13.1.6 {
  UPDATE sqlite_master SET sql = '' WHERE name='x1i';
} {}

do_catchsql_test 13.1.7 {
  ALTER TABLE x1 RENAME COLUMN t TO ttt;
................................................................................
  do_execsql_test 13.2.$tn.1 "
    DROP TRIGGER IF EXISTS tr1;
    $trigger
  "

  do_catchsql_test 13.2.$tn.2 {
    ALTER TABLE x1 RENAME COLUMN t TO ttt;
  } "1 {error in trigger tr1: $error}"
}

#-------------------------------------------------------------------------
# Passing invalid parameters directly to sqlite_rename_column().
#
do_execsql_test 14.1 {
  CREATE TABLE ddd(sql, type, object, db, tbl, icol, znew, bquote);
................................................................................
  CREATE VIEW vvv AS SELECT a AS d FROM xxx WHERE d=0;
  ALTER TABLE xxx RENAME a TO xyz;
}

do_execsql_test 15.2 {
  SELECT sql FROM sqlite_master WHERE type='view';
} {{CREATE VIEW vvv AS SELECT xyz AS d FROM xxx WHERE d=0}}

#-------------------------------------------------------------------------
#
do_execsql_test 16.0 {
  CREATE TABLE t1(a,b,c);
  CREATE TABLE t2(d,e,f);
  INSERT INTO t1 VALUES(1,2,3);
  INSERT INTO t2 VALUES(4,5,6);
  CREATE VIEW v4 AS SELECT a, d FROM t1, t2;
  SELECT * FROM v4;
} {1 4}

do_catchsql_test 16.1 {
  ALTER TABLE t2 RENAME d TO a;
} {1 {error in view v4 after rename: ambiguous column name: a}}

do_execsql_test 16.2 {
  SELECT * FROM v4;
} {1 4}

reset_db

finish_test