Index: src/alter.c ================================================================== --- src/alter.c +++ src/alter.c @@ -815,17 +815,10 @@ if( !pTab ) goto exit_rename_column; /* 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 ); zDb = db->aDb[iSchema].zDbSName; @@ -849,15 +842,16 @@ if( !zNew ) goto exit_rename_column; assert( pNew->n>0 ); 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. */ if( pParse->pVdbe ){ sqlite3ChangeCookie(pParse, iSchema); @@ -896,14 +890,17 @@ /* ** 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 */ }; /* ** Add a new RenameToken object mapping parse tree element pPtr into ** token *pToken to the Parse object currently under construction. @@ -965,10 +962,18 @@ break; } } } +/* +** 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. ** ** For every TK_COLUMN node in the expression tree, search to see @@ -976,27 +981,35 @@ ** ALTER TABLE statement. If it is, then attach its associated ** RenameToken object to the list of RenameToken objects being ** 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; } /* ** 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; for(pToken=pBest->pNext; pToken; pToken=pToken->pNext){ @@ -1005,16 +1018,50 @@ for(pp=&pCtx->pList; *pp!=pBest; pp=&(*pp)->pNext); *pp = pBest->pNext; 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. ** ** This function is used internally by the ALTER TABLE RENAME COLUMN command. @@ -1032,18 +1079,20 @@ sqlite3_context *context, int NotUsed, 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; Parse sParse; Walker sWalker; @@ -1051,104 +1100,244 @@ char *zOut = 0; 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 ){ rc = SQLITE_NOMEM; }else{ 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.u.pRename = &sCtx; - - 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]); - } - 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{ - sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); - sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); - } - + sWalker.xSelectCallback = renameColumnSelectCb; + sWalker.u.pRename = &sCtx; + + sCtx.pTab = pTab; + if( rc!=SQLITE_OK ) goto renameColumnFunc_done; + if( sParse.pNewTable ){ + 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); + } + 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); + } + } + } + } + }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 ){ int nOut = nSql; memcpy(zOut, zSql, nSql); @@ -1178,20 +1367,32 @@ sqlite3DbFree(db, pBest); } 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); } /* Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -2173,11 +2173,16 @@ /* Make a copy of the entire SELECT statement that defines the view. ** This will force all the Expr.token.z values to be dynamically ** 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; /* Locate the end of the CREATE VIEW statement. Make sEnd point to ** the end. @@ -3687,11 +3692,12 @@ ** Append a new element to the given IdList. Create a new IdList if ** need be. ** ** 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) ); if( pList==0 ) return 0; } @@ -3705,10 +3711,13 @@ if( i<0 ){ sqlite3IdListDelete(db, pList); 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; } /* ** Delete an IdList. Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -1664,10 +1664,13 @@ assert( pList->nExpr>0 ); pItem = &pList->a[pList->nExpr-1]; 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); + } } } /* ** Set the ExprList.a[].zSpan element of the most recently added item Index: src/parse.y ================================================================== --- src/parse.y +++ src/parse.y @@ -906,13 +906,13 @@ %destructor idlist {sqlite3IdListDelete(pParse->db, $$);} 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 ///////////////////////////// // %type expr {Expr*} @@ -1449,20 +1449,20 @@ %type trigger_cmd {TriggerStep*} %destructor trigger_cmd {sqlite3DeleteTriggerStep(pParse->db, $$);} // 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). {A = sqlite3TriggerSelectStep(pParse->db, X, B, E); /*A-overwrites-X*/} Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -758,60 +758,62 @@ notValid(pParse, pNC, "non-deterministic functions", NC_IdxExpr|NC_PartIdx); } } + 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"; - } - sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()", zType, nId,zId); - pNC->nErr++; - is_agg = 0; - } + ); + 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; + } #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 ){ #ifndef SQLITE_OMIT_WINDOWFUNC if( pExpr->pWin ){ Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -3885,11 +3885,11 @@ # define sqlite3AutoincrementBegin(X) # define sqlite3AutoincrementEnd(X) #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*); SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, Token*, Select*, Expr*, IdList*); @@ -4047,16 +4047,16 @@ void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int); void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*); 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*); u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) Index: src/tokenize.c ================================================================== --- src/tokenize.c +++ src/tokenize.c @@ -695,13 +695,15 @@ ** structure built up in pParse->pNewTable. The calling code (see vtab.c) ** will take responsibility for freeing the Table structure. */ 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; pParse->pAinc = p->pNext; sqlite3DbFreeNN(db, p); Index: src/trigger.c ================================================================== --- src/trigger.c +++ src/trigger.c @@ -179,18 +179,20 @@ zName = sqlite3NameFromToken(db, pName); if( !zName || SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ 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); - } - goto trigger_cleanup; + 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; + } } /* Do not create a trigger on a system table */ if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ sqlite3ErrorMsg(pParse, "cannot create trigger on system table"); @@ -210,11 +212,11 @@ " trigger on table: %S", pTableName, 0); goto trigger_cleanup; } #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; const char *zDbTrig = isTemp ? db->aDb[1].zDbSName : zDb; if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER; @@ -244,12 +246,18 @@ pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName); pTrigger->pSchema = db->aDb[iDb].pSchema; 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; trigger_cleanup: sqlite3DbFree(db, zName); @@ -293,10 +301,18 @@ if( sqlite3FixTriggerStep(&sFix, pTrig->step_list) || sqlite3FixExpr(&sFix, pTrig->pWhen) ){ 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 */ if( !db->init.busy ){ @@ -335,11 +351,11 @@ } } triggerfinish_cleanup: sqlite3DeleteTrigger(db, pTrig); - assert( !pParse->pNewTrigger ); + assert( IN_RENAME_COLUMN || !pParse->pNewTrigger ); sqlite3DeleteTriggerStep(db, pStepList); } /* ** Duplicate a range of text from an SQL statement, then convert all @@ -410,26 +426,32 @@ ** ** The parser calls this routine when it sees an INSERT inside the ** 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 */ u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ Upsert *pUpsert, /* ON CONFLICT clauses for upsert */ 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; }else{ testcase( pColumn ); @@ -446,24 +468,32 @@ ** Construct a trigger step that implements an UPDATE statement and return ** a pointer to that trigger step. The parser calls this routine when it ** 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 */ u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */ 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); sqlite3ExprDelete(db, pWhere); return pTriggerStep; @@ -473,21 +503,27 @@ ** Construct a trigger step that implements a DELETE statement and return ** a pointer to that trigger step. The parser calls this routine when it ** 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); return pTriggerStep; } Index: test/altercol.test ================================================================== --- test/altercol.test +++ test/altercol.test @@ -23,10 +23,24 @@ # If SQLITE_OMIT_ALTERTABLE is defined, omit this file. ifcapable !altertable { finish_test 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)} @@ -63,10 +77,13 @@ 12 {CREATE TABLE t1(a, b, c); CREATE INDEX t1i ON t1(b+b+b+b, c) WHERE b>0} {{CREATE TABLE t1(a, d, c)} {CREATE INDEX t1i ON t1(d+d+d+d, c) WHERE d>0}} 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))} 16 {CREATE TABLE t1(a INTEGER, b INTEGER PRIMARY KEY, c BLOB)} @@ -120,28 +137,37 @@ #------------------------------------------------------------------------- # 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); } do_execsql_test 3.1 { ALTER TABLE t4 RENAME y TO abc; 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} +} #------------------------------------------------------------------------- # do_execsql_test 4.0 { CREATE TABLE c1(a, b, FOREIGN KEY (a, b) REFERENCES p1(c, d)); @@ -215,7 +241,229 @@ 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