diff --git a/manifest b/manifest index c1ffa1b827..a1be9219d7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sproblem\swhen\srenaming\san\sIPK\scolumn\sthat\sis\salso\spart\sof\sa\schild\skey. -D 2018-08-14T21:03:38.735 +C Have\sALTER\sTABLE\sRENAME\sCOLUMN\salso\sedit\strigger\sand\sview\sdefinitions. +D 2018-08-18T18:01:58.244 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -432,7 +432,7 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a -F src/alter.c 35512b6c704721442f9a2b0856d139699742c85b87130c975d9dd802f327df3c +F src/alter.c 3342dba26f9023b334e6ab634ef45d0d8b333023c80070a57f22f85f64bbeabb F src/analyze.c 3dc6b98cf007b005af89df165c966baaa48e8124f38c87b4d2b276fe7f0b9eb9 F src/attach.c 4bd5b92633671d3e8ce431153ebb1893b50335818423b5373f3f27969f79769a F src/auth.c 32a5bbe3b755169ab6c66311c5225a3cd4f75a46c041f7fb117e0cbb68055114 @@ -442,7 +442,7 @@ F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6 F src/btree.c 3f5e1a03db871e627bf5da21092bf7434ecfc5c5980bbd7d45eba13341340173 F src/btree.h febb2e817be499570b7a2e32a9bbb4b607a9234f6b84bb9ae84916d4806e96f2 F src/btreeInt.h 620ab4c7235f43572cf3ac2ac8723cbdf68073be4d29da24897c7b77dda5fd96 -F src/build.c a38f9e25a34cd5a8fd46dbb42c4f7a577f4968635021ddd2ebf2dbca59fad8b3 +F src/build.c a2e61e716e7d90e382d71818404472207024ecb94a44431ac9fcf1ac3b8c3066 F src/callback.c 36caff1e7eb7deb58572d59c41cee8f064a11d00297616995c5050ea0cfc1288 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c b157b01081f92442f8b0218ddb93ddce8ebddad36dbddeecfdd771561dd4f387 @@ -450,7 +450,7 @@ F src/date.c ebe1dc7c8a347117bb02570f1a931c62dd78f4a2b1b516f4837d45b7d6426957 F src/dbpage.c 4aa7f26198934dbd002e69418220eae3dbc71b010bbac32bd78faf86b52ce6c3 F src/dbstat.c edabb82611143727511a45ca0859b8cd037851ebe756ae3db289859dd18b6f91 F src/delete.c 107e28d3ef8bd72fd11953374ca9107cd74e8b09c3ded076a6048742d26ce7d2 -F src/expr.c 4a555ff68084360c133c5b9d985ae05d2cf914125d4c8e5614496dc071d318dd +F src/expr.c 4c1e40cdb1717b42e848835caf3e2b881f748cdcfabe3498e83634d1d2db5e26 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c f59253c0be4b1e9dfcb073b6d6d6ab83090ae50c08b5c113b76013c4b157cd6a F src/func.c 7c288b4ce309b5a8b8473514b88e1f8e69a80134509a8c0db8e39c858e367e7f @@ -487,7 +487,7 @@ F src/os_win.c 070cdbb400097c6cda54aa005356095afdc2f3ee691d17192c54724ef146a971 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 76d29b8a960dcb8b67210f095899d91e4a90673a6674ea58cfd1115b705a7fb9 F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388 -F src/parse.y 035b397bf1acbd3cb8293812b07c8aefce9f275d4de1442e113dbd0d2f733dce +F src/parse.y 57f533353a4945370a60f66aa7be284c8a627509baa02d707982f906e4851f7d F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170 F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880 @@ -496,14 +496,14 @@ F src/pragma.h bb83728944b42f6d409c77f5838a8edbdb0fe83046c5496ffc9602b40340a324 F src/prepare.c 117e27c6826a83f461986c0cfbb09c31fe004922ce23a61bf78d82a46b0958d9 F src/printf.c 7f6f3cba8e0c49c19e30a1ff4e9aeda6e06814dcbad4b664a69e1b6cb6e7e365 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 -F src/resolve.c b51a48f33da36e0c2dd1ea5f0d10197c3e938a54086a69efce064ae41e2254e1 +F src/resolve.c c58793195eaf2989b8d0688355cca15024cc792da155dffc44b85afae426c2d5 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c ae7396a314cc1bb1d767947cd57094e3a9ffcbb155ebc1b1c391e028c44a9a04 F src/shell.c.in 6e0aad854be738a5d0368940459399be211e9ac43aebe92bb9ed46cfe38d0e1f F src/sqlite.h.in c6451bb876adced3aba5b1682c6317d215c5eceaba21a6ce979e71a0b8d0bf95 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 9887b27e69c01e79c2cbe74ef73bf01af5b5703d6a7f0a4371e386d7249cb1c7 -F src/sqliteInt.h a7b305c23545c052829959f71fcc1f35f823a143a54c066695d071efedabdd94 +F src/sqliteInt.h 78e9b483adbdf928923a175f2c8470da89024b973b4b790486b6e8736b4c876f F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 @@ -561,9 +561,9 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 3177575b587f3d0491ca5c3e937663ca3e91bf86adf4f2e8e9f7d0ab98bc66d3 +F src/tokenize.c fe35cb947ec54fe9a96895359b777a9305356c2f3c2917a1d83a57603108e66c F src/treeview.c e7a7f90552bb418533cdd0309b5eb71d4effa50165b880fc8c2001e613577e5f -F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995 +F src/trigger.c f6760d78be400164c95732b4aabccab27072ec66e9db2ee33baf70e65e467af0 F src/update.c 345ce35eb1332eb4829857aa8b1f65a614b07dae91d0346c0dc2baacafbcc51b F src/upsert.c 47edd408cc73f8d3c00a140550d1ad180b407c146285947969dd09874802bf88 F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5 @@ -599,7 +599,7 @@ F test/alter.test b820ab9dcf85f8e3a65bc8326accb2f0c7be64ef F test/alter2.test 7ea05c7d92ac99349a802ef7ada17294dd647060 F test/alter3.test 4d79934d812eaeacc6f22781a080f8cfe012fdc3 F test/alter4.test b6d7b86860111864f6cddb54af313f5862dda23b -F test/altercol.test 8380bf54ea9eb849b783cb33eab988d4efd80c04f194485ff9f695ce3d643ac0 +F test/altercol.test 7e22d63b1adb3f66e695c5dfb32d102644943ceeb88bc81bf74fa6d65dca5eba F test/altermalloc.test e81ac9657ed25c6c5bb09bebfa5a047cd8e4acfc F test/amatch1.test b5ae7065f042b7f4c1c922933f4700add50cdb9f F test/analyze.test b3a9c67d00e1df7588a5b7be9a0292899f94fe8cac1f94a017277474ca2e59df @@ -1756,7 +1756,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f6d6b472713c21deace3fd52c0b0b6901d758af043c238f004fabe52b01f730c -R 8e9298efe4cb9b189013f3e469b1ce50 +P ad15486022209205c65fb5ffdbe30a7b99379170451e6aff4bab6e90b549d6c7 7fa1faeaff30b74b68ee6f4b363d837f21cf313d8262361c901bda884df139a2 +R 4b2d31defa56ef33764a237b1d928d0d +T +closed 7fa1faeaff30b74b68ee6f4b363d837f21cf313d8262361c901bda884df139a2 U dan -Z 288fdb9114eb61b0445f1cd0f818929a +Z 4f1d814f6f719b632fdfddd911dbafb8 diff --git a/manifest.uuid b/manifest.uuid index 392e31bd4b..dbcf910f4f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ad15486022209205c65fb5ffdbe30a7b99379170451e6aff4bab6e90b549d6c7 \ No newline at end of file +7908e8a4a3b9577211a5d3da9c4142c46e9d5872be4a6499ec053f2b547019b8 \ No newline at end of file diff --git a/src/alter.c b/src/alter.c index a07c248b3b..613293182b 100644 --- a/src/alter.c +++ b/src/alter.c @@ -817,13 +817,6 @@ void sqlite3AlterRenameColumn( /* Cannot alter a system table */ if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ) goto exit_rename_column; - /* Cannot rename columns of a virtual table */ - if( IsVirtual(pTab) ){ - sqlite3ErrorMsg(pParse, "cannot rename columns in a virtual table (%s)", - pTab->zName); - goto exit_rename_column; - } - /* Which schema holds the table to be altered */ iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iSchema>=0 ); @@ -851,11 +844,12 @@ void sqlite3AlterRenameColumn( bQuote = sqlite3Isquote(pNew->z[0]); sqlite3NestedParse(pParse, "UPDATE \"%w\".%s SET " - "sql = sqlite_rename_column(sql, %d, %d, %Q, %Q, %Q) " - "WHERE name NOT LIKE 'sqlite_%%' AND (" - " type = 'table' OR (type='index' AND tbl_name = %Q)" - ")", - zDb, MASTER_NAME, iCol, bQuote, zNew, pTab->zName, zOld, pTab->zName + "sql = sqlite_rename_column(sql, %Q, %Q, %d, %Q, %d) " + "WHERE name NOT LIKE 'sqlite_%%' AND (type != 'index' OR tbl_name = %Q)" + " AND sql NOT LIKE 'create virtual%%'", + zDb, MASTER_NAME, + zDb, pTab->zName, iCol, zNew, bQuote, + pTab->zName ); /* Drop and reload the database schema. */ @@ -898,10 +892,13 @@ struct RenameToken { ** The context of an ALTER TABLE RENAME COLUMN operation that gets passed ** down into the Walker. */ +typedef struct RenameCtx RenameCtx; struct RenameCtx { RenameToken *pList; /* List of tokens to overwrite */ int nList; /* Number of tokens in pList */ int iCol; /* Index of column being renamed */ + Table *pTab; /* Table being ALTERed */ + const char *zOld; /* Old column name */ }; /* @@ -967,6 +964,14 @@ static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){ } } +/* +** This is a Walker select callback. It does nothing. It is only required +** because without a dummy callback, sqlite3WalkExpr() and similar do not +** descend into sub-select statements. +*/ +static int renameColumnSelectCb(Walker *pWalker, Select *p){ + return WRC_Continue; +} /* ** This is a Walker expression callback. @@ -978,8 +983,16 @@ static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){ ** constructed in RenameCtx object at pWalker->u.pRename. */ static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ - struct RenameCtx *p = pWalker->u.pRename; - if( pExpr->op==TK_COLUMN && pExpr->iColumn==p->iCol ){ + RenameCtx *p = pWalker->u.pRename; + if( pExpr->op==TK_TRIGGER + && pExpr->iColumn==p->iCol + && pWalker->pParse->pTriggerTab==p->pTab + ){ + renameTokenFind(pWalker->pParse, p, (void*)pExpr); + }else if( pExpr->op==TK_COLUMN + && pExpr->iColumn==p->iCol + && p->pTab==pExpr->pTab + ){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); } return WRC_Continue; @@ -987,14 +1000,14 @@ static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ /* ** The RenameCtx contains a list of tokens that reference a column that -** is being renamed by an ALTER TABLE statement. Return the "first" +** is being renamed by an ALTER TABLE statement. Return the "last" ** RenameToken in the RenameCtx and remove that RenameToken from the -** RenameContext. "First" means the first RenameToken encountered when -** the input SQL from left to right. Repeated calls to this routine +** RenameContext. "Last" means the last RenameToken encountered when +** the input SQL is parsed from left to right. Repeated calls to this routine ** return all column name tokens in the order that they are encountered ** in the SQL statement. */ -static RenameToken *renameColumnTokenNext(struct RenameCtx *pCtx){ +static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ RenameToken *pBest = pCtx->pList; RenameToken *pToken; RenameToken **pp; @@ -1008,11 +1021,45 @@ static RenameToken *renameColumnTokenNext(struct RenameCtx *pCtx){ return pBest; } +/* +** An error occured while parsing or otherwise processing a database +** 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, Parse *pParse){ + const char *zT; + const char *zN; + char *zErr; + if( pParse->pNewTable ){ + zT = pParse->pNewTable->pSelect ? "view" : "table"; + zN = pParse->pNewTable->zName; + }else if( pParse->pNewIndex ){ + zT = "index"; + zN = pParse->pNewIndex->zName; + }else{ + assert( pParse->pNewTrigger ); + zT = "trigger"; + zN = pParse->pNewTrigger->zName; + } + zErr = sqlite3_mprintf("error processing %s %s: %s", zT, zN, pParse->zErrMsg); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); +} + /* ** SQL function: ** ** sqlite_rename_column(zSql, iCol, bQuote, zNew, zTable, zOld) ** +** 0. zSql: SQL statement to rewrite +** 1. Database: Database name (e.g. "main") +** 2. Table: Table name +** 3. iCol: Index of column to rename +** 4. zNew: New column name +** 5. 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. @@ -1034,14 +1081,16 @@ static void renameColumnFunc( sqlite3_value **argv ){ sqlite3 *db = sqlite3_context_db_handle(context); - struct RenameCtx sCtx; + RenameCtx sCtx; const char *zSql = (const char*)sqlite3_value_text(argv[0]); int nSql = sqlite3_value_bytes(argv[0]); - int bQuote = sqlite3_value_int(argv[2]); - const char *zNew = (const char*)sqlite3_value_text(argv[3]); - int nNew = sqlite3_value_bytes(argv[3]); - const char *zTable = (const char*)sqlite3_value_text(argv[4]); - const char *zOld = (const char*)sqlite3_value_text(argv[5]); + const char *zDb = (const char*)sqlite3_value_text(argv[1]); + const char *zTable = (const char*)sqlite3_value_text(argv[2]); + int iCol = sqlite3_value_int(argv[3]); + const char *zNew = (const char*)sqlite3_value_text(argv[4]); + int nNew = sqlite3_value_bytes(argv[4]); + int bQuote = sqlite3_value_int(argv[5]); + const char *zOld; int rc; char *zErr = 0; @@ -1053,26 +1102,54 @@ static void renameColumnFunc( char *zQuot = 0; /* Quoted version of zNew */ int nQuot = 0; /* Length of zQuot in bytes */ int i; + Table *pTab; if( zSql==0 ) return; if( zNew==0 ) return; if( zTable==0 ) return; - if( zOld==0 ) return; + if( iCol<0 ) return; + pTab = sqlite3FindTable(db, zTable, zDb); + if( pTab==0 || iCol>=pTab->nCol ) return; + zOld = pTab->aCol[iCol].zName; memset(&sCtx, 0, sizeof(sCtx)); - sCtx.iCol = sqlite3_value_int(argv[1]); - if( sCtx.iCol<0 ) return; + sCtx.iCol = ((iCol==pTab->iPKey) ? -1 : iCol); + /* Parse the SQL statement passed as the first argument. If no error + ** occurs and the parse does not result in a new table, index or + ** trigger object, the database must be corrupt. */ memset(&sParse, 0, sizeof(sParse)); sParse.eParseMode = PARSE_MODE_RENAME_COLUMN; sParse.db = db; sParse.nQueryLoop = 1; rc = sqlite3RunParser(&sParse, zSql, &zErr); - assert( sParse.pNewTable==0 || sParse.pNewIndex==0 ); + assert( sParse.zErrMsg==0 ); + assert( rc!=SQLITE_OK || zErr==0 ); + assert( (!!sParse.pNewTable)+(!!sParse.pNewIndex)+(!!sParse.pNewTrigger)<2 ); + sParse.zErrMsg = zErr; if( db->mallocFailed ) rc = SQLITE_NOMEM; - if( rc==SQLITE_OK && sParse.pNewTable==0 && sParse.pNewIndex==0 ){ + if( rc==SQLITE_OK + && sParse.pNewTable==0 && sParse.pNewIndex==0 && sParse.pNewTrigger==0 + ){ rc = SQLITE_CORRUPT_BKPT; } +#ifdef SQLITE_DEBUG + /* Ensure that all mappings in the Parse.pRename list really do map to + ** a part of the input string. */ + assert( sqlite3Strlen30(zSql)==nSql ); + if( rc==SQLITE_OK ){ + RenameToken *pToken; + for(pToken=sParse.pRename; pToken; pToken=pToken->pNext){ + assert( pToken->t.z>=zSql && &pToken->t.z[pToken->t.n]<=&zSql[nSql] ); + } + } +#endif + + /* Set zQuot to point to a buffer containing a quoted copy of the + ** identifier zNew. If the corresponding identifier in the original + ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to + ** point to zQuot so that all substitutions are made using the + ** quoted version of the new column name. */ if( rc==SQLITE_OK ){ zQuot = sqlite3_mprintf("\"%w\"", zNew); if( zQuot==0 ){ @@ -1081,72 +1158,184 @@ static void renameColumnFunc( nQuot = sqlite3Strlen30(zQuot); } } - - if( rc!=SQLITE_OK ){ - if( zErr ){ - sqlite3_result_error(context, zErr, -1); - }else{ - sqlite3_result_error_code(context, rc); - } - sqlite3DbFree(db, zErr); - goto renameColumnFunc_done; - } - if( bQuote ){ zNew = zQuot; nNew = nQuot; } -#ifdef SQLITE_DEBUG - assert( sqlite3Strlen30(zSql)==nSql ); - { - RenameToken *pToken; - for(pToken=sParse.pRename; pToken; pToken=pToken->pNext){ - assert( pToken->t.z>=zSql && &pToken->t.z[pToken->t.n]<=&zSql[nSql] ); - } - } -#endif - /* Find tokens that need to be replaced. */ memset(&sWalker, 0, sizeof(Walker)); sWalker.pParse = &sParse; sWalker.xExprCallback = renameColumnExprCb; + sWalker.xSelectCallback = renameColumnSelectCb; sWalker.u.pRename = &sCtx; + sCtx.pTab = pTab; + if( rc!=SQLITE_OK ) goto renameColumnFunc_done; if( sParse.pNewTable ){ - int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName); - FKey *pFKey; - for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){ - for(i=0; inCol; i++){ - if( bFKOnly==0 && pFKey->aCol[i].iFrom==sCtx.iCol ){ - renameTokenFind(&sParse, &sCtx, (void*)&pFKey->aCol[i]); + Select *pSelect = sParse.pNewTable->pSelect; + if( pSelect ){ + sParse.rc = SQLITE_OK; + sqlite3SelectPrep(&sParse, sParse.pNewTable->pSelect, 0); + rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); + if( rc==SQLITE_OK ){ + sqlite3WalkSelect(&sWalker, pSelect); + } + if( rc!=SQLITE_OK ) goto renameColumnFunc_done; + }else{ + /* A regular table */ + int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName); + FKey *pFKey; + assert( sParse.pNewTable->pSelect==0 ); + sCtx.pTab = sParse.pNewTable; + if( bFKOnly==0 ){ + renameTokenFind( + &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName + ); + if( sCtx.iCol<0 ){ + renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey); } - if( 0==sqlite3_stricmp(pFKey->zTo, zTable) - && 0==sqlite3_stricmp(pFKey->aCol[i].zCol, zOld) - ){ - renameTokenFind(&sParse, &sCtx, (void*)pFKey->aCol[i].zCol); + sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); + for(pIdx=sParse.pNewTable->pIndex; pIdx; pIdx=pIdx->pNext){ + sqlite3WalkExprList(&sWalker, pIdx->aColExpr); + } + } + + for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + for(i=0; inCol; i++){ + if( bFKOnly==0 && pFKey->aCol[i].iFrom==iCol ){ + renameTokenFind(&sParse, &sCtx, (void*)&pFKey->aCol[i]); + } + if( 0==sqlite3_stricmp(pFKey->zTo, zTable) + && 0==sqlite3_stricmp(pFKey->aCol[i].zCol, zOld) + ){ + renameTokenFind(&sParse, &sCtx, (void*)pFKey->aCol[i].zCol); + } } } } - if( bFKOnly==0 ){ - renameTokenFind( - &sParse, &sCtx, (void*)sParse.pNewTable->aCol[sCtx.iCol].zName - ); - assert( sCtx.iCol>=0 ); - if( sParse.pNewTable->iPKey==sCtx.iCol ){ - renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey); - sCtx.iCol = -1; - } - sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); - for(pIdx=sParse.pNewTable->pIndex; pIdx; pIdx=pIdx->pNext){ - sqlite3WalkExprList(&sWalker, pIdx->aColExpr); - } - } - }else{ + }else if( sParse.pNewIndex ){ sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + }else{ + /* A trigger */ + TriggerStep *pStep; + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = &sParse; + sParse.pTriggerTab = sqlite3FindTable(db, sParse.pNewTrigger->table, zDb); + sParse.eTriggerOp = sParse.pNewTrigger->op; + + /* Resolve symbols in WHEN clause */ + if( sParse.pNewTrigger->pWhen ){ + rc = sqlite3ResolveExprNames(&sNC, sParse.pNewTrigger->pWhen); + } + + for(pStep=sParse.pNewTrigger->step_list; + rc==SQLITE_OK && pStep; + pStep=pStep->pNext + ){ + if( pStep->pSelect ) sqlite3SelectPrep(&sParse, pStep->pSelect, &sNC); + if( pStep->zTarget ){ + Table *pTarget = sqlite3FindTable(db, pStep->zTarget, zDb); + if( pTarget==0 ){ + rc = SQLITE_ERROR; + }else{ + SrcList sSrc; + memset(&sSrc, 0, sizeof(sSrc)); + sSrc.nSrc = 1; + sSrc.a[0].zName = pStep->zTarget; + sSrc.a[0].pTab = pTarget; + sNC.pSrcList = &sSrc; + if( pStep->pWhere ){ + rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList); + } + if( pStep->pUpsert ){ + Upsert *pUpsert = pStep->pUpsert; + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); + } + if( rc==SQLITE_OK && pUpsert->pUpsertSet){ + ExprList *pUpsertSet = pUpsert->pUpsertSet; + rc = sqlite3ResolveExprListNames(&sNC, pUpsertSet); + if( rc==SQLITE_OK && pTarget==pTab ){ + for(i=0; inExpr; i++){ + char *zName = pUpsertSet->a[i].zName; + if( 0==sqlite3_stricmp(zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, (void*)zName); + } + } + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertWhere); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); + } + } + + if( rc==SQLITE_OK && pTarget==pTab ){ + if( pStep->pIdList ){ + for(i=0; ipIdList->nId; i++){ + char *zName = pStep->pIdList->a[i].zName; + if( 0==sqlite3_stricmp(zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, (void*)zName); + } + } + } + if( pStep->op==TK_UPDATE ){ + assert( pStep->pExprList ); + for(i=0; ipExprList->nExpr; i++){ + char *zName = pStep->pExprList->a[i].zName; + if( 0==sqlite3_stricmp(zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, (void*)zName); + } + } + } + } + } + } + } + + if( rc!=SQLITE_OK ) goto renameColumnFunc_done; + + /* Find tokens to edit in UPDATE OF clause */ + if( sParse.pTriggerTab==pTab && sParse.pNewTrigger->pColumns ){ + for(i=0; ipColumns->nId; i++){ + char *zName = sParse.pNewTrigger->pColumns->a[i].zName; + if( 0==sqlite3_stricmp(zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, (void*)zName); + } + } + } + + /* Find tokens to edit in WHEN clause */ + sqlite3WalkExpr(&sWalker, sParse.pNewTrigger->pWhen); + + /* Find tokens to edit in trigger steps */ + for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ + sqlite3WalkSelect(&sWalker, pStep->pSelect); + sqlite3WalkExpr(&sWalker, pStep->pWhere); + sqlite3WalkExprList(&sWalker, pStep->pExprList); + if( pStep->pUpsert ){ + Upsert *pUpsert = pStep->pUpsert; + sqlite3WalkExprList(&sWalker, pUpsert->pUpsertTarget); + sqlite3WalkExprList(&sWalker, pUpsert->pUpsertSet); + sqlite3WalkExpr(&sWalker, pUpsert->pUpsertWhere); + sqlite3WalkExpr(&sWalker, pUpsert->pUpsertTargetWhere); + } + } } + /* At this point sCtx.pList contains a list of RenameToken objects + ** corresponding to all tokens in the input SQL that must be replaced + ** with the new column name. All that remains is to construct and + ** return the edited SQL string. */ + assert( rc==SQLITE_OK ); assert( nQuot>=nNew ); zOut = sqlite3DbMallocZero(db, nSql + sCtx.nList*nQuot + 1); if( zOut ){ @@ -1180,16 +1369,28 @@ static void renameColumnFunc( sqlite3_result_text(context, zOut, -1, SQLITE_TRANSIENT); sqlite3DbFree(db, zOut); + }else{ + rc = SQLITE_NOMEM; } renameColumnFunc_done: + if( rc!=SQLITE_OK ){ + if( sParse.zErrMsg ){ + renameColumnParseError(context, &sParse); + }else{ + sqlite3_result_error_code(context, rc); + } + } + if( sParse.pVdbe ){ sqlite3VdbeFinalize(sParse.pVdbe); } sqlite3DeleteTable(db, sParse.pNewTable); if( sParse.pNewIndex ) sqlite3FreeIndex(db, sParse.pNewIndex); + sqlite3DeleteTrigger(db, sParse.pNewTrigger); renameTokenFree(db, sParse.pRename); renameTokenFree(db, sCtx.pList); + sqlite3DbFree(db, sParse.zErrMsg); sqlite3ParserReset(&sParse); sqlite3_free(zQuot); } diff --git a/src/build.c b/src/build.c index 31ef2e17d2..68fecd1039 100644 --- a/src/build.c +++ b/src/build.c @@ -2175,7 +2175,12 @@ void sqlite3CreateView( ** allocated rather than point to the input string - which means that ** they will persist after the current sqlite3_exec() call returns. */ - p->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + if( IN_RENAME_COLUMN ){ + p->pSelect = pSelect; + pSelect = 0; + }else{ + p->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + } p->pCheck = sqlite3ExprListDup(db, pCNames, EXPRDUP_REDUCE); if( db->mallocFailed ) goto create_view_fail; @@ -3689,7 +3694,8 @@ void *sqlite3ArrayAllocate( ** ** A new IdList is returned, or NULL if malloc() fails. */ -IdList *sqlite3IdListAppend(sqlite3 *db, IdList *pList, Token *pToken){ +IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token *pToken){ + sqlite3 *db = pParse->db; int i; if( pList==0 ){ pList = sqlite3DbMallocZero(db, sizeof(IdList) ); @@ -3707,6 +3713,9 @@ IdList *sqlite3IdListAppend(sqlite3 *db, IdList *pList, Token *pToken){ return 0; } pList->a[i].zName = sqlite3NameFromToken(db, pToken); + if( IN_RENAME_COLUMN && pList->a[i].zName ){ + sqlite3RenameToken(pParse, (void*)pList->a[i].zName, pToken); + } return pList; } diff --git a/src/expr.c b/src/expr.c index 769d198c22..e9b63cba83 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1666,6 +1666,9 @@ void sqlite3ExprListSetName( assert( pItem->zName==0 ); pItem->zName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n); if( dequote ) sqlite3Dequote(pItem->zName); + if( IN_RENAME_COLUMN ){ + sqlite3RenameToken(pParse, (void*)pItem->zName, pName); + } } } diff --git a/src/parse.y b/src/parse.y index 47e73feb8c..50bbd0207f 100644 --- a/src/parse.y +++ b/src/parse.y @@ -908,9 +908,9 @@ insert_cmd(A) ::= REPLACE. {A = OE_Replace;} idlist_opt(A) ::= . {A = 0;} idlist_opt(A) ::= LP idlist(X) RP. {A = X;} idlist(A) ::= idlist(A) COMMA nm(Y). - {A = sqlite3IdListAppend(pParse->db,A,&Y);} + {A = sqlite3IdListAppend(pParse,A,&Y);} idlist(A) ::= nm(Y). - {A = sqlite3IdListAppend(pParse->db,0,&Y); /*A-overwrites-Y*/} + {A = sqlite3IdListAppend(pParse,0,&Y); /*A-overwrites-Y*/} /////////////////////////// Expression Processing ///////////////////////////// // @@ -1451,16 +1451,16 @@ tridxby ::= NOT INDEXED. { // UPDATE trigger_cmd(A) ::= UPDATE(B) orconf(R) trnm(X) tridxby SET setlist(Y) where_opt(Z) scanpt(E). - {A = sqlite3TriggerUpdateStep(pParse->db, &X, Y, Z, R, B.z, E);} + {A = sqlite3TriggerUpdateStep(pParse, &X, Y, Z, R, B.z, E);} // INSERT trigger_cmd(A) ::= scanpt(B) insert_cmd(R) INTO trnm(X) idlist_opt(F) select(S) upsert(U) scanpt(Z). { - A = sqlite3TriggerInsertStep(pParse->db,&X,F,S,R,U,B,Z);/*A-overwrites-R*/ + A = sqlite3TriggerInsertStep(pParse,&X,F,S,R,U,B,Z);/*A-overwrites-R*/ } // DELETE trigger_cmd(A) ::= DELETE(B) FROM trnm(X) tridxby where_opt(Y) scanpt(E). - {A = sqlite3TriggerDeleteStep(pParse->db, &X, Y, B.z, E);} + {A = sqlite3TriggerDeleteStep(pParse, &X, Y, B.z, E);} // SELECT trigger_cmd(A) ::= scanpt(B) select(X) scanpt(E). diff --git a/src/resolve.c b/src/resolve.c index fc815eed64..b6c72759ff 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -760,56 +760,58 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } } + if( 0==IN_RENAME_COLUMN ){ #ifndef SQLITE_OMIT_WINDOWFUNC - assert( is_agg==0 || (pDef->funcFlags & SQLITE_FUNC_MINMAX) + assert( is_agg==0 || (pDef->funcFlags & SQLITE_FUNC_MINMAX) || (pDef->xValue==0 && pDef->xInverse==0) || (pDef->xValue && pDef->xInverse && pDef->xSFunc && pDef->xFinalize) - ); - if( pDef && pDef->xValue==0 && pExpr->pWin ){ - sqlite3ErrorMsg(pParse, - "%.*s() may not be used as a window function", nId, zId ); - pNC->nErr++; - }else if( - (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) - || (is_agg && (pDef->funcFlags & SQLITE_FUNC_WINDOW) && !pExpr->pWin) - || (is_agg && pExpr->pWin && (pNC->ncFlags & NC_AllowWin)==0) - ){ - const char *zType; - if( (pDef->funcFlags & SQLITE_FUNC_WINDOW) || pExpr->pWin ){ - zType = "window"; - }else{ - zType = "aggregate"; + if( pDef && pDef->xValue==0 && pExpr->pWin ){ + sqlite3ErrorMsg(pParse, + "%.*s() may not be used as a window function", nId, zId + ); + pNC->nErr++; + }else if( + (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) + || (is_agg && (pDef->funcFlags & SQLITE_FUNC_WINDOW) && !pExpr->pWin) + || (is_agg && pExpr->pWin && (pNC->ncFlags & NC_AllowWin)==0) + ){ + const char *zType; + if( (pDef->funcFlags & SQLITE_FUNC_WINDOW) || pExpr->pWin ){ + zType = "window"; + }else{ + zType = "aggregate"; + } + sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()",zType,nId,zId); + pNC->nErr++; + is_agg = 0; } - sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()", zType, nId,zId); - pNC->nErr++; - is_agg = 0; - } #else - if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){ - sqlite3ErrorMsg(pParse, "misuse of aggregate function %.*s()", nId,zId); - pNC->nErr++; - is_agg = 0; - } + if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){ + sqlite3ErrorMsg(pParse,"misuse of aggregate function %.*s()",nId,zId); + pNC->nErr++; + is_agg = 0; + } #endif - else if( no_such_func && pParse->db->init.busy==0 + else if( no_such_func && pParse->db->init.busy==0 && !IN_RENAME_COLUMN #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION - && pParse->explain==0 + && pParse->explain==0 #endif - ){ - sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId); - pNC->nErr++; - }else if( wrong_num_args ){ - sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()", - nId, zId); - pNC->nErr++; - } - if( is_agg ){ + ){ + sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId); + pNC->nErr++; + }else if( wrong_num_args ){ + sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()", + nId, zId); + pNC->nErr++; + } + if( is_agg ){ #ifndef SQLITE_OMIT_WINDOWFUNC - pNC->ncFlags &= ~(pExpr->pWin ? NC_AllowWin : NC_AllowAgg); + pNC->ncFlags &= ~(pExpr->pWin ? NC_AllowWin : NC_AllowAgg); #else - pNC->ncFlags &= ~NC_AllowAgg; + pNC->ncFlags &= ~NC_AllowAgg; #endif + } } sqlite3WalkExprList(pWalker, pList); if( is_agg ){ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index e7d86ba3be..9158a4ca36 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3887,7 +3887,7 @@ void sqlite3FreeIndex(sqlite3*, Index*); #endif void sqlite3Insert(Parse*, SrcList*, Select*, IdList*, int, Upsert*); void *sqlite3ArrayAllocate(sqlite3*,void*,int,int*,int*); -IdList *sqlite3IdListAppend(sqlite3*, IdList*, Token*); +IdList *sqlite3IdListAppend(Parse*, IdList*, Token*); int sqlite3IdListIndex(IdList*,const char*); SrcList *sqlite3SrcListEnlarge(sqlite3*, SrcList*, int, int); SrcList *sqlite3SrcListAppend(sqlite3*, SrcList*, Token*, Token*); @@ -4049,12 +4049,12 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, const char*,const char*); - TriggerStep *sqlite3TriggerInsertStep(sqlite3*,Token*, IdList*, + TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*, Select*,u8,Upsert*, const char*,const char*); - TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8, + TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,ExprList*, Expr*, u8, const char*,const char*); - TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*, + TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*, const char*,const char*); void sqlite3DeleteTrigger(sqlite3*, Trigger*); void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); diff --git a/src/tokenize.c b/src/tokenize.c index 685d10b252..02d21f1f58 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -697,9 +697,11 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ */ sqlite3DeleteTable(db, pParse->pNewTable); } + if( !IN_RENAME_COLUMN ){ + sqlite3DeleteTrigger(db, pParse->pNewTrigger); + } if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree); - sqlite3DeleteTrigger(db, pParse->pNewTrigger); sqlite3DbFree(db, pParse->pVList); while( pParse->pAinc ){ AutoincInfo *p = pParse->pAinc; diff --git a/src/trigger.c b/src/trigger.c index 044f256aeb..330e14ea72 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -181,14 +181,16 @@ void sqlite3BeginTrigger( goto trigger_cleanup; } assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),zName) ){ - if( !noErr ){ - sqlite3ErrorMsg(pParse, "trigger %T already exists", pName); - }else{ - assert( !db->init.busy ); - sqlite3CodeVerifySchema(pParse, iDb); + if( !IN_RENAME_COLUMN ){ + if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),zName) ){ + if( !noErr ){ + sqlite3ErrorMsg(pParse, "trigger %T already exists", pName); + }else{ + assert( !db->init.busy ); + sqlite3CodeVerifySchema(pParse, iDb); + } + goto trigger_cleanup; } - goto trigger_cleanup; } /* Do not create a trigger on a system table */ @@ -212,7 +214,7 @@ void sqlite3BeginTrigger( } #ifndef SQLITE_OMIT_AUTHORIZATION - { + if( !IN_RENAME_COLUMN ){ int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); int code = SQLITE_CREATE_TRIGGER; const char *zDb = db->aDb[iTabDb].zDbSName; @@ -246,8 +248,14 @@ void sqlite3BeginTrigger( pTrigger->pTabSchema = pTab->pSchema; pTrigger->op = (u8)op; pTrigger->tr_tm = tr_tm==TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER; - pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); - pTrigger->pColumns = sqlite3IdListDup(db, pColumns); + if( IN_RENAME_COLUMN ){ + pTrigger->pWhen = pWhen; + pWhen = 0; + }else{ + pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); + } + pTrigger->pColumns = pColumns; + pColumns = 0; assert( pParse->pNewTrigger==0 ); pParse->pNewTrigger = pTrigger; @@ -296,6 +304,14 @@ void sqlite3FinishTrigger( goto triggerfinish_cleanup; } +#ifndef SQLITE_OMIT_ALTERTABLE + if( IN_RENAME_COLUMN ){ + assert( !db->init.busy ); + pParse->pNewTrigger = pTrig; + pTrig = 0; + }else +#endif + /* if we are not initializing, ** build the sqlite_master entry */ @@ -337,7 +353,7 @@ void sqlite3FinishTrigger( triggerfinish_cleanup: sqlite3DeleteTrigger(db, pTrig); - assert( !pParse->pNewTrigger ); + assert( IN_RENAME_COLUMN || !pParse->pNewTrigger ); sqlite3DeleteTriggerStep(db, pStepList); } @@ -412,7 +428,7 @@ static TriggerStep *triggerStepAllocate( ** body of a trigger. */ TriggerStep *sqlite3TriggerInsertStep( - sqlite3 *db, /* The database connection */ + Parse *pParse, /* Parser */ Token *pTableName, /* Name of the table into which we insert */ IdList *pColumn, /* List of columns in pTableName to insert into */ Select *pSelect, /* A SELECT statement that supplies values */ @@ -421,13 +437,19 @@ TriggerStep *sqlite3TriggerInsertStep( const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ ){ + sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; assert(pSelect != 0 || db->mallocFailed); pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName, zStart, zEnd); if( pTriggerStep ){ - pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + if( IN_RENAME_COLUMN ){ + pTriggerStep->pSelect = pSelect; + pSelect = 0; + }else{ + pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + } pTriggerStep->pIdList = pColumn; pTriggerStep->pUpsert = pUpsert; pTriggerStep->orconf = orconf; @@ -448,7 +470,7 @@ TriggerStep *sqlite3TriggerInsertStep( ** sees an UPDATE statement inside the body of a CREATE TRIGGER. */ TriggerStep *sqlite3TriggerUpdateStep( - sqlite3 *db, /* The database connection */ + Parse *pParse, /* Parser */ Token *pTableName, /* Name of the table to be updated */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ @@ -456,12 +478,20 @@ TriggerStep *sqlite3TriggerUpdateStep( const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ ){ + sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; pTriggerStep = triggerStepAllocate(db, TK_UPDATE, pTableName, zStart, zEnd); if( pTriggerStep ){ - pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); - pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + if( IN_RENAME_COLUMN ){ + pTriggerStep->pExprList = pEList; + pTriggerStep->pWhere = pWhere; + pEList = 0; + pWhere = 0; + }else{ + pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); + pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + } pTriggerStep->orconf = orconf; } sqlite3ExprListDelete(db, pEList); @@ -475,17 +505,23 @@ TriggerStep *sqlite3TriggerUpdateStep( ** sees a DELETE statement inside the body of a CREATE TRIGGER. */ TriggerStep *sqlite3TriggerDeleteStep( - sqlite3 *db, /* Database connection */ + Parse *pParse, /* Parser */ Token *pTableName, /* The table from which rows are deleted */ Expr *pWhere, /* The WHERE clause */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ ){ + sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; pTriggerStep = triggerStepAllocate(db, TK_DELETE, pTableName, zStart, zEnd); if( pTriggerStep ){ - pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + if( IN_RENAME_COLUMN ){ + pTriggerStep->pWhere = pWhere; + pWhere = 0; + }else{ + pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + } pTriggerStep->orconf = OE_Default; } sqlite3ExprDelete(db, pWhere); diff --git a/test/altercol.test b/test/altercol.test index a82adeb039..de025ac61c 100644 --- a/test/altercol.test +++ b/test/altercol.test @@ -26,6 +26,20 @@ ifcapable !altertable { return } +# Drop all the tables and views in the 'main' database of database connect +# [db]. Sort the objects by name before dropping them. +# +proc drop_all_tables_and_views {db} { + set SQL { + SELECT name, type FROM sqlite_master + WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' + ORDER BY 1 + } + foreach {z t} [db eval $SQL] { + db eval "DROP $t $z" + } +} + foreach {tn before after} { 1 {CREATE TABLE t1(a INTEGER, b TEXT, c BLOB)} {CREATE TABLE t1(a INTEGER, d TEXT, c BLOB)} @@ -66,6 +80,9 @@ foreach {tn before after} { 13 {CREATE TABLE t1(a, b, c, FOREIGN KEY (b) REFERENCES t2)} {CREATE TABLE t1(a, d, c, FOREIGN KEY (d) REFERENCES t2)} + 14 {CREATE TABLE t1(a INTEGER, b TEXT, c BLOB, PRIMARY KEY(b))} + {CREATE TABLE t1(a INTEGER, d TEXT, c BLOB, PRIMARY KEY(d))} + 15 {CREATE TABLE t1(a INTEGER, b INTEGER, c BLOB, PRIMARY KEY(b))} {CREATE TABLE t1(a INTEGER, d INTEGER, c BLOB, PRIMARY KEY(d))} @@ -122,7 +139,10 @@ do_execsql_test -db db2 2.3 { SELECT biglongname FROM t3 } do_execsql_test 3.0 { CREATE TABLE t4(x, y, z); CREATE TRIGGER ttt AFTER INSERT ON t4 WHEN new.y<0 BEGIN - SELECT 1, 2, 3, 4; + SELECT x, y, z FROM t4; + DELETE FROM t4 WHERE y=32; + UPDATE t4 SET x=y+1, y=0 WHERE y=32; + INSERT INTO t4(x, y, z) SELECT 4, 5, 6 WHERE 0; END; INSERT INTO t4 VALUES(3, 2, 1); } @@ -132,14 +152,20 @@ do_execsql_test 3.1 { SELECT sql FROM sqlite_master WHERE name='t4'; } {{CREATE TABLE t4(x, abc, z)}} -db close -sqlite3 db test.db - do_execsql_test 3.2 { SELECT * FROM t4; } {3 2 1} -# do_execsql_test 3.3 { INSERT INTO t4 VALUES(6, 5, 4); } {} +do_execsql_test 3.3 { INSERT INTO t4 VALUES(6, 5, 4); } {} + +do_execsql_test 3.4 { SELECT sql FROM sqlite_master WHERE type='trigger' } { +{CREATE TRIGGER ttt AFTER INSERT ON t4 WHEN new.abc<0 BEGIN + SELECT x, abc, z FROM t4; + DELETE FROM t4 WHERE abc=32; + UPDATE t4 SET x=abc+1, abc=0 WHERE abc=32; + INSERT INTO t4(x, abc, z) SELECT 4, 5, 6 WHERE 0; + END} +} #------------------------------------------------------------------------- # @@ -217,5 +243,227 @@ do_execsql_test 6.3 { SELECT "where" FROM blob; } {} -finish_test +#------------------------------------------------------------------------- +# Triggers. +# +reset_db +do_execsql_test 7.0 { + CREATE TABLE c(x); + INSERT INTO c VALUES(0); + CREATE TABLE t6("col a", "col b", "col c"); + CREATE TRIGGER zzz AFTER UPDATE OF "col a", "col c" ON t6 BEGIN + UPDATE c SET x=x+1; + END; +} +do_execsql_test 7.1.1 { + INSERT INTO t6 VALUES(0, 0, 0); + UPDATE t6 SET "col c" = 1; + SELECT * FROM c; +} {1} + +do_execsql_test 7.1.2 { + ALTER TABLE t6 RENAME "col c" TO "col 3"; +} + +do_execsql_test 7.1.3 { + UPDATE t6 SET "col 3" = 0; + SELECT * FROM c; +} {2} + +#------------------------------------------------------------------------- +# Views. +# +reset_db +do_execsql_test 8.0 { + CREATE TABLE a1(x INTEGER, y TEXT, z BLOB, PRIMARY KEY(x)); + CREATE TABLE a2(a, b, c); + CREATE VIEW v1 AS SELECT x, y, z FROM a1; +} + +do_execsql_test 8.1 { + ALTER TABLE a1 RENAME y TO yyy; + SELECT sql FROM sqlite_master WHERE type='view'; +} {{CREATE VIEW v1 AS SELECT x, yyy, z FROM a1}} + +do_execsql_test 8.2.1 { + DROP VIEW v1; + CREATE VIEW v2 AS SELECT x, x+x, a, a+a FROM a1, a2; +} {} +do_execsql_test 8.2.2 { + ALTER TABLE a1 RENAME x TO xxx; +} +do_execsql_test 8.2.3 { + SELECT sql FROM sqlite_master WHERE type='view'; +} {{CREATE VIEW v2 AS SELECT xxx, xxx+xxx, a, a+a FROM a1, a2}} + +do_execsql_test 8.3.1 { + DROP TABLE a2; + DROP VIEW v2; + CREATE TABLE a2(a INTEGER PRIMARY KEY, b, c); + CREATE VIEW v2 AS SELECT xxx, xxx+xxx, a, a+a FROM a1, a2; +} {} +do_execsql_test 8.3.2 { + ALTER TABLE a1 RENAME xxx TO x; +} +do_execsql_test 8.3.3 { + SELECT sql FROM sqlite_master WHERE type='view'; +} {{CREATE VIEW v2 AS SELECT x, x+x, a, a+a FROM a1, a2}} + +do_execsql_test 8.4.0 { + CREATE TABLE b1(a, b, c); + CREATE TABLE b2(x, y, z); +} + +do_execsql_test 8.4.1 { + CREATE VIEW vvv AS SELECT c+c || coalesce(c, c) FROM b1, b2 WHERE x=c GROUP BY c HAVING c>0; + ALTER TABLE b1 RENAME c TO "a;b"; + SELECT sql FROM sqlite_master WHERE name='vvv'; +} {{CREATE VIEW vvv AS SELECT "a;b"+"a;b" || coalesce("a;b", "a;b") FROM b1, b2 WHERE x="a;b" GROUP BY "a;b" HAVING "a;b">0}} + +do_execsql_test 8.4.2 { + CREATE VIEW www AS SELECT b FROM b1 UNION ALL SELECT y FROM b2; + ALTER TABLE b1 RENAME b TO bbb; + SELECT sql FROM sqlite_master WHERE name='www'; +} {{CREATE VIEW www AS SELECT bbb FROM b1 UNION ALL SELECT y FROM b2}} + +db collate nocase {string compare} + +do_execsql_test 8.4.3 { + CREATE VIEW xxx AS SELECT a FROM b1 UNION SELECT x FROM b2 ORDER BY 1 COLLATE nocase; +} + +do_execsql_test 8.4.4 { + 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 + + set lSorted [list] + foreach sql $lSchema { + execsql $sql + lappend lSorted [string trim $sql] + } + set lSorted [lsort $lSorted] + + do_execsql_test $tn.$i.1 { + SELECT sql FROM sqlite_master WHERE sql!='' ORDER BY 1 + } $lSorted + + if {$i==1} { + db close + sqlite3 db test.db + } + + do_execsql_test $tn.$i.2 "ALTER TABLE t1 RENAME $old TO $new" + + do_execsql_test $tn.$i.3 { + SELECT sql FROM sqlite_master ORDER BY 1 + } [string map [list $old $new] $lSorted] + } +} + +foreach {tn old new lSchema} { + 1 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_) } + { CREATE TRIGGER AFTER INSERT ON t1 BEGIN + SELECT _x_ FROM t1; + END } + } + + 2 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_) } + { CREATE TABLE t2(c, d, e) } + { CREATE TRIGGER ttt AFTER INSERT ON t2 BEGIN + SELECT _x_ FROM t1; + END } + } + + 3 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_ INTEGER, PRIMARY KEY(_x_), CHECK(_x_>0)) } + { CREATE TABLE t2(c, d, e) } + { CREATE TRIGGER ttt AFTER UPDATE ON t1 BEGIN + INSERT INTO t2 VALUES(new.a, new.b, new._x_); + END } + } + + 4 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_ INTEGER, PRIMARY KEY(_x_), CHECK(_x_>0)) } + { CREATE TRIGGER ttt AFTER UPDATE ON t1 BEGIN + INSERT INTO t1 VALUES(new.a, new.b, new._x_) + ON CONFLICT (_x_) WHERE _x_>10 DO UPDATE SET _x_ = _x_+1; + END } + } +} { + do_rename_column_test 9.$tn $old $new $lSchema +} + +#------------------------------------------------------------------------- +# Test that views can be edited even if there are missing collation +# sequences or user defined functions. +# +reset_db + +foreach {tn old new lSchema} { + 1 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_) } + { CREATE VIEW s1 AS SELECT a, b, _x_ FROM t1 WHERE _x_='abc' COLLATE xyz } + } + + 2 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_) } + { CREATE VIEW v1 AS SELECT a, b, _x_ FROM t1 WHERE scalar(_x_) } + } + + 3 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_) } + { CREATE VIEW v1 AS SELECT a, b, _x_ FROM t1 WHERE _x_ = unicode(1, 2, 3) } + } + + 4 _x_ _xxx_ { + { CREATE TABLE t1(a, b, _x_) } + { CREATE VIRTUAL TABLE e1 USING echo(t1) } + } +} { + register_echo_module db + do_rename_column_test 10.$tn $old $new $lSchema +} + +#-------------------------------------------------------------------------- +# Test that if a view or trigger refers to a virtual table for which the +# module is not available, RENAME COLUMN cannot proceed. +# +reset_db +register_echo_module db +do_execsql_test 11.0 { + CREATE TABLE x1(a, b, c); + CREATE VIRTUAL TABLE e1 USING echo(x1); +} +db close +sqlite3 db test.db + +do_execsql_test 11.1 { + ALTER TABLE x1 RENAME b TO bbb; + SELECT sql FROM sqlite_master; +} { {CREATE TABLE x1(a, bbb, c)} {CREATE VIRTUAL TABLE e1 USING echo(x1)} } + +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}} + +finish_test