diff options
author | dan <dan@noemail.net> | 2018-08-09 20:47:01 +0000 |
---|---|---|
committer | dan <dan@noemail.net> | 2018-08-09 20:47:01 +0000 |
commit | cf8f2895424da7f73a96515abf3e42ef480eec2e (patch) | |
tree | a420a500591a0264e081a5f78f555d23b0717aa8 /src/alter.c | |
parent | d98f53249c363a48757500cf8918e160bc0ec8c1 (diff) | |
download | sqlite-cf8f2895424da7f73a96515abf3e42ef480eec2e.tar.gz sqlite-cf8f2895424da7f73a96515abf3e42ef480eec2e.zip |
Experimental implementation of ALTER TABLE ... RENAME COLUMN. Still buggy.
FossilOrigin-Name: fa0fc01eb48a864f0a3d43f9b805d5ed2e530846ee0c34fcbc2eabd9e5696277
Diffstat (limited to 'src/alter.c')
-rw-r--r-- | src/alter.c | 318 |
1 files changed, 302 insertions, 16 deletions
diff --git a/src/alter.c b/src/alter.c index 2d7a5d659..c4dc00bd2 100644 --- a/src/alter.c +++ b/src/alter.c @@ -226,22 +226,6 @@ static void renameTriggerFunc( #endif /* !SQLITE_OMIT_TRIGGER */ /* -** Register built-in functions used to help implement ALTER TABLE -*/ -void sqlite3AlterFunctions(void){ - static FuncDef aAlterTableFuncs[] = { - FUNCTION(sqlite_rename_table, 2, 0, 0, renameTableFunc), -#ifndef SQLITE_OMIT_TRIGGER - FUNCTION(sqlite_rename_trigger, 2, 0, 0, renameTriggerFunc), -#endif -#ifndef SQLITE_OMIT_FOREIGN_KEY - FUNCTION(sqlite_rename_parent, 3, 0, 0, renameParentFunc), -#endif - }; - sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); -} - -/* ** This function is used to create the text of expressions of the form: ** ** name=<constant1> OR name=<constant2> OR ... @@ -805,4 +789,306 @@ exit_begin_add_column: sqlite3SrcListDelete(db, pSrc); return; } + +void sqlite3AlterRenameColumn( + Parse *pParse, + SrcList *pSrc, + Token *pOld, + Token *pNew +){ + sqlite3 *db = pParse->db; + Table *pTab; /* Table being updated */ + int iCol; /* Index of column being renamed */ + char *zOld = 0; + char *zNew = 0; + const char *zDb; + int iSchema; + + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( !pTab ) goto exit_rename_column; + iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iSchema>=0 ); + zDb = db->aDb[iSchema].zDbSName; + + zOld = sqlite3NameFromToken(db, pOld); + if( !zOld ) goto exit_rename_column; + for(iCol=0; iCol<pTab->nCol; iCol++){ + if( 0==sqlite3StrICmp(pTab->aCol[iCol].zName, zOld) ) break; + } + if( iCol==pTab->nCol ){ + sqlite3ErrorMsg(pParse, "no such column: \"%s\"", zOld); + goto exit_rename_column; + } + + zNew = sqlite3NameFromToken(db, pNew); + if( !zNew ) goto exit_rename_column; + + sqlite3NestedParse(pParse, + "UPDATE \"%w\".%s SET " + "sql = sqlite_rename_column(sql, %d, %Q) " + "WHERE type IN ('table', 'index') AND tbl_name = %Q AND sql!=''", + zDb, MASTER_NAME, iCol, zNew, pTab->zName + ); + + /* Drop and reload the internal table schema. */ + reloadTableSchema(pParse, pTab, pTab->zName); + + exit_rename_column: + sqlite3SrcListDelete(db, pSrc); + sqlite3DbFree(db, zOld); + sqlite3DbFree(db, zNew); + return; +} + +struct RenameToken { + void *p; + Token t; + RenameToken *pNext; +}; + +struct RenameCtx { + RenameToken *pList; /* List of tokens to overwrite */ + int nList; /* Number of tokens in pList */ + int iCol; /* Index of column being renamed */ +}; + +void sqlite3RenameToken(Parse *pParse, void *pPtr, Token *pToken){ + RenameToken *pNew; + pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken)); + if( pNew ){ + pNew->p = pPtr; + pNew->t = *pToken; + pNew->pNext = pParse->pRename; + pParse->pRename = pNew; + } +} + +void sqlite3MoveRenameToken(Parse *pParse, void *pTo, void *pFrom){ + RenameToken *p; + for(p=pParse->pRename; p; p=p->pNext){ + if( p->p==pFrom ){ + p->p = pTo; + break; + } + } + assert( p ); +} + +static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ + RenameToken *pNext; + RenameToken *p; + for(p=pToken; p; p=pNext){ + pNext = p->pNext; + sqlite3DbFree(db, p); + } +} + +static RenameToken *renameTokenFind(Parse *pParse, void *pPtr){ + RenameToken **pp; + for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){ + if( (*pp)->p==pPtr ){ + RenameToken *pToken = *pp; + *pp = pToken->pNext; + pToken->pNext = 0; + return pToken; + } + } + return 0; +} + +static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ + struct RenameCtx *p = pWalker->u.pRename; + if( pExpr->op==TK_COLUMN && pExpr->iColumn==p->iCol ){ + RenameToken *pTok = renameTokenFind(pWalker->pParse, (void*)pExpr); + if( pTok ){ + pTok->pNext = p->pList; + p->pList = pTok; + p->nList++; + } + } + return WRC_Continue; +} + +static RenameToken *renameColumnTokenNext(struct RenameCtx *pCtx){ + RenameToken *pBest = pCtx->pList; + RenameToken *pToken; + RenameToken **pp; + + for(pToken=pBest->pNext; pToken; pToken=pToken->pNext){ + if( pToken->t.z>pBest->t.z ) pBest = pToken; + } + for(pp=&pCtx->pList; *pp!=pBest; pp=&(*pp)->pNext); + *pp = pBest->pNext; + + return pBest; +} + +static void renameColumnFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + struct RenameCtx sCtx; + const char *zSql = sqlite3_value_text(argv[0]); + int nSql = sqlite3_value_bytes(argv[0]); + const char *zNew = sqlite3_value_text(argv[2]); + int nNew = sqlite3_value_bytes(argv[2]); + int rc; + char *zErr = 0; + Parse sParse; + Walker sWalker; + Table *pTab; + Index *pIdx; + char *zOut = 0; + + char *zQuot = 0; /* Quoted version of zNew */ + int nQuot = 0; /* Length of zQuot in bytes */ + int i; + + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.iCol = sqlite3_value_int(argv[1]); + + 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 ); + if( rc==SQLITE_OK && sParse.pNewTable==0 && sParse.pNewIndex==0 ){ + rc = SQLITE_CORRUPT_BKPT; + } + + 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); + sqlite3_free(zQuot); + return; + } + + for(i=0; i<nNew; i++){ + if( sqlite3IsIdChar(zNew[i])==0 ){ + zNew = zQuot; + nNew = nQuot; + break; + } + } + +#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 ){ + FKey *pFKey; + sCtx.pList = renameTokenFind( + &sParse, (void*)sParse.pNewTable->aCol[sCtx.iCol].zName + ); + sCtx.nList = 1; + 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; i<pFKey->nCol; i++){ + if( pFKey->aCol[i].iFrom==sCtx.iCol ){ + RenameToken *pTok = renameTokenFind(&sParse, (void*)&pFKey->aCol[i]); + if( pTok ){ + pTok->pNext = sCtx.pList; + sCtx.pList = pTok; + sCtx.nList++; + } + } + } + } + }else{ + sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); + sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + } + + zOut = sqlite3DbMallocZero(db, nSql + sCtx.nList*nNew + 1); + if( zOut ){ + int nOut = nSql; + memcpy(zOut, zSql, nSql); + while( sCtx.pList ){ + int iOff; /* Offset of token to replace in zOut */ + RenameToken *pBest = renameColumnTokenNext(&sCtx); + + int nReplace; + const char *zReplace; + if( sqlite3IsIdChar(*pBest->t.z) ){ + nReplace = nNew; + zReplace = zNew; + }else{ + nReplace = nQuot; + zReplace = zQuot; + } + + iOff = pBest->t.z - zSql; + if( pBest->t.n!=nReplace ){ + memmove(&zOut[iOff + nReplace], &zOut[iOff + pBest->t.n], + nOut - (iOff + pBest->t.n) + ); + nOut += nReplace - pBest->t.n; + zOut[nOut] = '\0'; + } + memcpy(&zOut[iOff], zReplace, nReplace); + sqlite3DbFree(db, pBest); + } + + sqlite3_result_text(context, zOut, -1, SQLITE_TRANSIENT); + sqlite3DbFree(db, zOut); + } + + if( sParse.pVdbe ){ + sqlite3VdbeFinalize(sParse.pVdbe); + } + sqlite3DeleteTable(db, sParse.pNewTable); + if( sParse.pNewIndex ) sqlite3FreeIndex(db, sParse.pNewIndex); + renameTokenFree(db, sParse.pRename); + sqlite3ParserReset(&sParse); + sqlite3_free(zQuot); +} + +/* +** Register built-in functions used to help implement ALTER TABLE +*/ +void sqlite3AlterFunctions(void){ + static FuncDef aAlterTableFuncs[] = { + FUNCTION(sqlite_rename_table, 2, 0, 0, renameTableFunc), + FUNCTION(sqlite_rename_column, 3, 0, 0, renameColumnFunc), +#ifndef SQLITE_OMIT_TRIGGER + FUNCTION(sqlite_rename_trigger, 2, 0, 0, renameTriggerFunc), +#endif +#ifndef SQLITE_OMIT_FOREIGN_KEY + FUNCTION(sqlite_rename_parent, 3, 0, 0, renameParentFunc), +#endif + }; + sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); +} #endif /* SQLITE_ALTER_TABLE */ |