mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-12 13:01:09 +03:00
When ALTER TABLE RENAME TO is used to change the name of a table that is the parent table of a foreign key constraint, modify that foreign key constraint to use the new table name.
FossilOrigin-Name: b4a10c39e726dc190e9597e382baddc034294114
This commit is contained in:
145
src/alter.c
145
src/alter.c
@@ -85,6 +85,67 @@ static void renameTableFunc(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** This C function implements an SQL user function that is used by SQL code
|
||||
** generated by the ALTER TABLE ... RENAME command to modify the definition
|
||||
** of any foreign key constraints that use the table being renamed as the
|
||||
** parent table. It is passed three arguments:
|
||||
**
|
||||
** 1) The complete text of the CREATE TABLE statement being modified,
|
||||
** 2) The old name of the table being renamed, and
|
||||
** 3) The new name of the table being renamed.
|
||||
**
|
||||
** It returns the new CREATE TABLE statement. For example:
|
||||
**
|
||||
** sqlite_rename_parent('CREATE TABLE t1(a REFERENCES t2)', 't2', 't3')
|
||||
** -> 'CREATE TABLE t1(a REFERENCES t3)'
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
static void renameParentFunc(
|
||||
sqlite3_context *context,
|
||||
int NotUsed,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||
char *zOutput = 0;
|
||||
char *zResult;
|
||||
unsigned char const *zInput = sqlite3_value_text(argv[0]);
|
||||
unsigned char const *zOld = sqlite3_value_text(argv[1]);
|
||||
unsigned char const *zNew = sqlite3_value_text(argv[2]);
|
||||
|
||||
unsigned const char *z; /* Pointer to token */
|
||||
int n; /* Length of token z */
|
||||
int token; /* Type of token */
|
||||
|
||||
for(z=zInput; *z; z=z+n){
|
||||
n = sqlite3GetToken(z, &token);
|
||||
if( token==TK_REFERENCES ){
|
||||
char *zParent;
|
||||
do {
|
||||
z += n;
|
||||
n = sqlite3GetToken(z, &token);
|
||||
}while( token==TK_SPACE );
|
||||
|
||||
zParent = sqlite3DbStrNDup(db, (const char *)z, n);
|
||||
sqlite3Dequote(zParent);
|
||||
if( 0==sqlite3StrICmp((const char *)zOld, zParent) ){
|
||||
char *zOut = sqlite3MPrintf(db, "%s%.*s\"%w\"",
|
||||
(zOutput?zOutput:""), z-zInput, zInput, (const char *)zNew
|
||||
);
|
||||
sqlite3DbFree(db, zOutput);
|
||||
zOutput = zOut;
|
||||
zInput = &z[n];
|
||||
}
|
||||
sqlite3DbFree(db, zParent);
|
||||
}
|
||||
}
|
||||
|
||||
zResult = sqlite3MPrintf(db, "%s%s", (zOutput?zOutput:""), zInput),
|
||||
sqlite3_result_text(context, zResult, -1, SQLITE_DYNAMIC);
|
||||
sqlite3DbFree(db, zOutput);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
/* This function is used by SQL generated to implement the
|
||||
** ALTER TABLE command. The first argument is the text of a CREATE TRIGGER
|
||||
@@ -172,6 +233,52 @@ void sqlite3AlterFunctions(sqlite3 *db){
|
||||
sqlite3CreateFunc(db, "sqlite_rename_trigger", 2, SQLITE_UTF8, 0,
|
||||
renameTriggerFunc, 0, 0);
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
sqlite3CreateFunc(db, "sqlite_rename_parent", 3, SQLITE_UTF8, 0,
|
||||
renameParentFunc, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is used to create the text of expressions of the form:
|
||||
**
|
||||
** name=<constant1> OR name=<constant2> OR ...
|
||||
**
|
||||
** If argument zWhere is NULL, then a pointer string containing the text
|
||||
** "name=<constant>" is returned, where <constant> is the quoted version
|
||||
** of the string passed as argument zConstant. The returned buffer is
|
||||
** allocated using sqlite3DbMalloc(). It is the responsibility of the
|
||||
** caller to ensure that it is eventually freed.
|
||||
**
|
||||
** If argument zWhere is not NULL, then the string returned is
|
||||
** "<where> OR name=<constant>", where <where> is the contents of zWhere.
|
||||
** In this case zWhere is passed to sqlite3DbFree() before returning.
|
||||
**
|
||||
*/
|
||||
static char *whereOrName(sqlite3 *db, char *zWhere, char *zConstant){
|
||||
char *zNew;
|
||||
if( !zWhere ){
|
||||
zNew = sqlite3MPrintf(db, "name=%Q", zConstant);
|
||||
}else{
|
||||
zNew = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, zConstant);
|
||||
sqlite3DbFree(db, zWhere);
|
||||
}
|
||||
return zNew;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate the text of a WHERE expression which can be used to select all
|
||||
** tables that have foreign key constraints that refer to table pTab (i.e.
|
||||
** constraints for which pTab is the parent table) from the sqlite_master
|
||||
** table.
|
||||
*/
|
||||
static char *whereForeignKeys(Parse *pParse, Table *pTab){
|
||||
FKey *p;
|
||||
char *zWhere = 0;
|
||||
for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
|
||||
zWhere = whereOrName(pParse->db, zWhere, p->pFrom->zName);
|
||||
}
|
||||
return zWhere;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -183,7 +290,6 @@ void sqlite3AlterFunctions(sqlite3 *db){
|
||||
static char *whereTempTriggers(Parse *pParse, Table *pTab){
|
||||
Trigger *pTrig;
|
||||
char *zWhere = 0;
|
||||
char *tmp = 0;
|
||||
const Schema *pTempSchema = pParse->db->aDb[1].pSchema; /* Temp db schema */
|
||||
|
||||
/* If the table is not located in the temp-db (in which case NULL is
|
||||
@@ -195,13 +301,7 @@ static char *whereTempTriggers(Parse *pParse, Table *pTab){
|
||||
sqlite3 *db = pParse->db;
|
||||
for(pTrig=sqlite3TriggerList(pParse, pTab); pTrig; pTrig=pTrig->pNext){
|
||||
if( pTrig->pSchema==pTempSchema ){
|
||||
if( !zWhere ){
|
||||
zWhere = sqlite3MPrintf(db, "name=%Q", pTrig->zName);
|
||||
}else{
|
||||
tmp = zWhere;
|
||||
zWhere = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, pTrig->zName);
|
||||
sqlite3DbFree(db, tmp);
|
||||
}
|
||||
zWhere = whereOrName(db, zWhere, pTrig->zName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +339,7 @@ static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Drop the table and index from the internal schema */
|
||||
/* Drop the table and index from the internal schema. */
|
||||
sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
|
||||
|
||||
/* Reload the table, index and permanent trigger schemas. */
|
||||
@@ -370,6 +470,21 @@ void sqlite3AlterRenameTable(
|
||||
zTabName = pTab->zName;
|
||||
nTabName = sqlite3Utf8CharLen(zTabName, -1);
|
||||
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
if( db->flags&SQLITE_ForeignKeys ){
|
||||
/* If foreign-key support is enabled, rewrite the CREATE TABLE
|
||||
** statements corresponding to all child tables of foreign key constraints
|
||||
** for which the renamed table is the parent table. */
|
||||
if( (zWhere=whereForeignKeys(pParse, pTab))!=0 ){
|
||||
sqlite3NestedParse(pParse,
|
||||
"UPDATE sqlite_master SET "
|
||||
"sql = sqlite_rename_parent(sql, %Q, %Q) "
|
||||
"WHERE %s;", zTabName, zName, zWhere);
|
||||
sqlite3DbFree(db, zWhere);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Modify the sqlite_master table to use the new table name. */
|
||||
sqlite3NestedParse(pParse,
|
||||
"UPDATE %Q.%s SET "
|
||||
@@ -421,6 +536,18 @@ void sqlite3AlterRenameTable(
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
if( db->flags&SQLITE_ForeignKeys ){
|
||||
FKey *p;
|
||||
for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
|
||||
Table *pFrom = p->pFrom;
|
||||
if( pFrom!=pTab ){
|
||||
reloadTableSchema(pParse, p->pFrom, pFrom->zName);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Drop and reload the internal table schema. */
|
||||
reloadTableSchema(pParse, pTab, zName);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user