1
0
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:
dan
2009-09-26 17:51:48 +00:00
parent 0ff297eaa7
commit 432cc5b9f3
6 changed files with 207 additions and 24 deletions

View File

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