aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordan <dan@noemail.net>2018-08-09 20:47:01 +0000
committerdan <dan@noemail.net>2018-08-09 20:47:01 +0000
commitcf8f2895424da7f73a96515abf3e42ef480eec2e (patch)
treea420a500591a0264e081a5f78f555d23b0717aa8 /src
parentd98f53249c363a48757500cf8918e160bc0ec8c1 (diff)
downloadsqlite-cf8f2895424da7f73a96515abf3e42ef480eec2e.tar.gz
sqlite-cf8f2895424da7f73a96515abf3e42ef480eec2e.zip
Experimental implementation of ALTER TABLE ... RENAME COLUMN. Still buggy.
FossilOrigin-Name: fa0fc01eb48a864f0a3d43f9b805d5ed2e530846ee0c34fcbc2eabd9e5696277
Diffstat (limited to 'src')
-rw-r--r--src/alter.c318
-rw-r--r--src/auth.c2
-rw-r--r--src/build.c237
-rw-r--r--src/parse.y11
-rw-r--r--src/resolve.c7
-rw-r--r--src/sqliteInt.h32
-rw-r--r--src/tokenize.c2
-rw-r--r--src/vtab.c4
8 files changed, 478 insertions, 135 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 */
diff --git a/src/auth.c b/src/auth.c
index a708d0c24..918ff46c3 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -207,7 +207,7 @@ int sqlite3AuthCheck(
/* Don't do any authorization checks if the database is initialising
** or if the parser is being invoked from within sqlite3_declare_vtab.
*/
- if( db->init.busy || IN_DECLARE_VTAB ){
+ if( db->init.busy || IN_SPECIAL_PARSE ){
return SQLITE_OK;
}
diff --git a/src/build.c b/src/build.c
index 62d0ff14d..eab9d441d 100644
--- a/src/build.c
+++ b/src/build.c
@@ -439,7 +439,7 @@ Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){
/*
** Reclaim the memory used by an index
*/
-static void freeIndex(sqlite3 *db, Index *p){
+void sqlite3FreeIndex(sqlite3 *db, Index *p){
#ifndef SQLITE_OMIT_ANALYZE
sqlite3DeleteIndexSamples(db, p);
#endif
@@ -479,7 +479,7 @@ void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){
p->pNext = pIndex->pNext;
}
}
- freeIndex(db, pIndex);
+ sqlite3FreeIndex(db, pIndex);
}
db->mDbFlags |= DBFLAG_SchemaChange;
}
@@ -625,7 +625,7 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){
assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
assert( pOld==pIndex || pOld==0 );
}
- freeIndex(db, pIndex);
+ sqlite3FreeIndex(db, pIndex);
}
/* Delete any foreign keys attached to this table. */
@@ -913,7 +913,7 @@ void sqlite3StartTable(
** and types will be used, so there is no need to test for namespace
** collisions.
*/
- if( !IN_DECLARE_VTAB ){
+ if( !IN_SPECIAL_PARSE ){
char *zDb = db->aDb[iDb].zDbSName;
if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
goto begin_table_error;
@@ -1072,6 +1072,7 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
}
z = sqlite3DbMallocRaw(db, pName->n + pType->n + 2);
if( z==0 ) return;
+ if( IN_RENAME_COLUMN ) sqlite3RenameToken(pParse, (void*)z, pName);
memcpy(z, pName->z, pName->n);
z[pName->n] = 0;
sqlite3Dequote(z);
@@ -2761,6 +2762,9 @@ void sqlite3CreateForeignKey(
pFromCol->a[i].zName);
goto fk_end;
}
+ if( IN_RENAME_COLUMN ){
+ sqlite3MoveRenameToken(pParse, &pFKey->aCol[i], &pFromCol->a[i]);
+ }
}
}
if( pToCol ){
@@ -3099,20 +3103,22 @@ void sqlite3CreateIndex(
if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
goto exit_create_index;
}
- if( !db->init.busy ){
- if( sqlite3FindTable(db, zName, 0)!=0 ){
- sqlite3ErrorMsg(pParse, "there is already a table named %s", zName);
- goto exit_create_index;
+ if( !IN_RENAME_COLUMN ){
+ if( !db->init.busy ){
+ if( sqlite3FindTable(db, zName, 0)!=0 ){
+ sqlite3ErrorMsg(pParse, "there is already a table named %s", zName);
+ goto exit_create_index;
+ }
}
- }
- if( sqlite3FindIndex(db, zName, pDb->zDbSName)!=0 ){
- if( !ifNotExist ){
- sqlite3ErrorMsg(pParse, "index %s already exists", zName);
- }else{
- assert( !db->init.busy );
- sqlite3CodeVerifySchema(pParse, iDb);
+ if( sqlite3FindIndex(db, zName, pDb->zDbSName)!=0 ){
+ if( !ifNotExist ){
+ sqlite3ErrorMsg(pParse, "index %s already exists", zName);
+ }else{
+ assert( !db->init.busy );
+ sqlite3CodeVerifySchema(pParse, iDb);
+ }
+ goto exit_create_index;
}
- goto exit_create_index;
}
}else{
int n;
@@ -3128,13 +3134,13 @@ void sqlite3CreateIndex(
** The following statement converts "sqlite3_autoindex..." into
** "sqlite3_butoindex..." in order to make the names distinct.
** The "vtab_err.test" test demonstrates the need of this statement. */
- if( IN_DECLARE_VTAB ) zName[7]++;
+ if( IN_SPECIAL_PARSE ) zName[7]++;
}
/* Check for authorization to create an index.
*/
#ifndef SQLITE_OMIT_AUTHORIZATION
- {
+ if( !IN_RENAME_COLUMN ){
const char *zDb = pDb->zDbSName;
if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iDb), 0, zDb) ){
goto exit_create_index;
@@ -3221,7 +3227,12 @@ void sqlite3CreateIndex(
** TODO: Issue a warning if the table primary key is used as part of the
** index key.
*/
- for(i=0, pListItem=pList->a; i<pList->nExpr; i++, pListItem++){
+ pListItem = pList->a;
+ if( IN_RENAME_COLUMN ){
+ pIndex->aColExpr = pList;
+ pList = 0;
+ }
+ for(i=0; i<pIndex->nKeyCol; i++, pListItem++){
Expr *pCExpr; /* The i-th index expression */
int requestedSortOrder; /* ASC or DESC on the i-th expression */
const char *zColl; /* Collation sequence name */
@@ -3237,12 +3248,8 @@ void sqlite3CreateIndex(
goto exit_create_index;
}
if( pIndex->aColExpr==0 ){
- ExprList *pCopy = sqlite3ExprListDup(db, pList, 0);
- pIndex->aColExpr = pCopy;
- if( !db->mallocFailed ){
- assert( pCopy!=0 );
- pListItem = &pCopy->a[i];
- }
+ pIndex->aColExpr = pList;
+ pList = 0;
}
j = XN_EXPR;
pIndex->aiColumn[i] = XN_EXPR;
@@ -3381,98 +3388,101 @@ void sqlite3CreateIndex(
}
}
- /* Link the new Index structure to its table and to the other
- ** in-memory database structures.
- */
- assert( pParse->nErr==0 );
- if( db->init.busy ){
- Index *p;
- assert( !IN_DECLARE_VTAB );
- assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
- p = sqlite3HashInsert(&pIndex->pSchema->idxHash,
- pIndex->zName, pIndex);
- if( p ){
- assert( p==pIndex ); /* Malloc must have failed */
- sqlite3OomFault(db);
- goto exit_create_index;
- }
- db->mDbFlags |= DBFLAG_SchemaChange;
- if( pTblName!=0 ){
- pIndex->tnum = db->init.newTnum;
- }
- }
-
- /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the
- ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then
- ** emit code to allocate the index rootpage on disk and make an entry for
- ** the index in the sqlite_master table and populate the index with
- ** content. But, do not do this if we are simply reading the sqlite_master
- ** table to parse the schema, or if this index is the PRIMARY KEY index
- ** of a WITHOUT ROWID table.
- **
- ** If pTblName==0 it means this index is generated as an implied PRIMARY KEY
- ** or UNIQUE index in a CREATE TABLE statement. Since the table
- ** has just been created, it contains no data and the index initialization
- ** step can be skipped.
- */
- else if( HasRowid(pTab) || pTblName!=0 ){
- Vdbe *v;
- char *zStmt;
- int iMem = ++pParse->nMem;
-
- v = sqlite3GetVdbe(pParse);
- if( v==0 ) goto exit_create_index;
+ if( !IN_RENAME_COLUMN ){
- sqlite3BeginWriteOperation(pParse, 1, iDb);
-
- /* Create the rootpage for the index using CreateIndex. But before
- ** doing so, code a Noop instruction and store its address in
- ** Index.tnum. This is required in case this index is actually a
- ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In
- ** that case the convertToWithoutRowidTable() routine will replace
- ** the Noop with a Goto to jump over the VDBE code generated below. */
- pIndex->tnum = sqlite3VdbeAddOp0(v, OP_Noop);
- sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, iMem, BTREE_BLOBKEY);
-
- /* Gather the complete text of the CREATE INDEX statement into
- ** the zStmt variable
+ /* Link the new Index structure to its table and to the other
+ ** in-memory database structures.
*/
- if( pStart ){
- int n = (int)(pParse->sLastToken.z - pName->z) + pParse->sLastToken.n;
- if( pName->z[n-1]==';' ) n--;
- /* A named index with an explicit CREATE INDEX statement */
- zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
- onError==OE_None ? "" : " UNIQUE", n, pName->z);
- }else{
- /* An automatic index created by a PRIMARY KEY or UNIQUE constraint */
- /* zStmt = sqlite3MPrintf(""); */
- zStmt = 0;
+ assert( pParse->nErr==0 );
+ if( db->init.busy ){
+ Index *p;
+ assert( !IN_SPECIAL_PARSE );
+ assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
+ p = sqlite3HashInsert(&pIndex->pSchema->idxHash,
+ pIndex->zName, pIndex);
+ if( p ){
+ assert( p==pIndex ); /* Malloc must have failed */
+ sqlite3OomFault(db);
+ goto exit_create_index;
+ }
+ db->mDbFlags |= DBFLAG_SchemaChange;
+ if( pTblName!=0 ){
+ pIndex->tnum = db->init.newTnum;
+ }
}
- /* Add an entry in sqlite_master for this index
+ /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the
+ ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then
+ ** emit code to allocate the index rootpage on disk and make an entry for
+ ** the index in the sqlite_master table and populate the index with
+ ** content. But, do not do this if we are simply reading the sqlite_master
+ ** table to parse the schema, or if this index is the PRIMARY KEY index
+ ** of a WITHOUT ROWID table.
+ **
+ ** If pTblName==0 it means this index is generated as an implied PRIMARY KEY
+ ** or UNIQUE index in a CREATE TABLE statement. Since the table
+ ** has just been created, it contains no data and the index initialization
+ ** step can be skipped.
*/
- sqlite3NestedParse(pParse,
- "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);",
- db->aDb[iDb].zDbSName, MASTER_NAME,
- pIndex->zName,
- pTab->zName,
- iMem,
- zStmt
- );
- sqlite3DbFree(db, zStmt);
+ else if( HasRowid(pTab) || pTblName!=0 ){
+ Vdbe *v;
+ char *zStmt;
+ int iMem = ++pParse->nMem;
+
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto exit_create_index;
+
+ sqlite3BeginWriteOperation(pParse, 1, iDb);
+
+ /* Create the rootpage for the index using CreateIndex. But before
+ ** doing so, code a Noop instruction and store its address in
+ ** Index.tnum. This is required in case this index is actually a
+ ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In
+ ** that case the convertToWithoutRowidTable() routine will replace
+ ** the Noop with a Goto to jump over the VDBE code generated below. */
+ pIndex->tnum = sqlite3VdbeAddOp0(v, OP_Noop);
+ sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, iMem, BTREE_BLOBKEY);
+
+ /* Gather the complete text of the CREATE INDEX statement into
+ ** the zStmt variable
+ */
+ if( pStart ){
+ int n = (int)(pParse->sLastToken.z - pName->z) + pParse->sLastToken.n;
+ if( pName->z[n-1]==';' ) n--;
+ /* A named index with an explicit CREATE INDEX statement */
+ zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
+ onError==OE_None ? "" : " UNIQUE", n, pName->z);
+ }else{
+ /* An automatic index created by a PRIMARY KEY or UNIQUE constraint */
+ /* zStmt = sqlite3MPrintf(""); */
+ zStmt = 0;
+ }
- /* Fill the index with data and reparse the schema. Code an OP_Expire
- ** to invalidate all pre-compiled statements.
- */
- if( pTblName ){
- sqlite3RefillIndex(pParse, pIndex, iMem);
- sqlite3ChangeCookie(pParse, iDb);
- sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
- sqlite3VdbeAddOp2(v, OP_Expire, 0, 1);
- }
+ /* Add an entry in sqlite_master for this index
+ */
+ sqlite3NestedParse(pParse,
+ "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);",
+ db->aDb[iDb].zDbSName, MASTER_NAME,
+ pIndex->zName,
+ pTab->zName,
+ iMem,
+ zStmt
+ );
+ sqlite3DbFree(db, zStmt);
+
+ /* Fill the index with data and reparse the schema. Code an OP_Expire
+ ** to invalidate all pre-compiled statements.
+ */
+ if( pTblName ){
+ sqlite3RefillIndex(pParse, pIndex, iMem);
+ sqlite3ChangeCookie(pParse, iDb);
+ sqlite3VdbeAddParseSchemaOp(v, iDb,
+ sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
+ sqlite3VdbeAddOp2(v, OP_Expire, 0, 1);
+ }
- sqlite3VdbeJumpHere(v, pIndex->tnum);
+ sqlite3VdbeJumpHere(v, pIndex->tnum);
+ }
}
/* When adding an index to the list of indices for a table, make
@@ -3496,10 +3506,15 @@ void sqlite3CreateIndex(
}
pIndex = 0;
}
+ else if( IN_RENAME_COLUMN ){
+ assert( pParse->pNewIndex==0 );
+ pParse->pNewIndex = pIndex;
+ pIndex = 0;
+ }
/* Clean up before exiting */
exit_create_index:
- if( pIndex ) freeIndex(db, pIndex);
+ if( pIndex ) sqlite3FreeIndex(db, pIndex);
sqlite3ExprDelete(db, pPIWhere);
sqlite3ExprListDelete(db, pList);
sqlite3SrcListDelete(db, pTblName);
diff --git a/src/parse.y b/src/parse.y
index ea2052934..752f30a77 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -940,6 +940,7 @@ idlist(A) ::= nm(Y).
if( p->u.zToken[0]=='"' ) p->flags |= EP_DblQuoted;
sqlite3Dequote(p->u.zToken);
}
+ if( IN_RENAME_COLUMN ) sqlite3RenameToken(pParse, (void*)p, &t);
#if SQLITE_MAX_EXPR_DEPTH>0
p->nHeight = 1;
#endif
@@ -955,6 +956,7 @@ expr(A) ::= JOIN_KW(X). {A=tokenExpr(pParse,TK_ID,X); /*A-overwrites-X*/}
expr(A) ::= nm(X) DOT nm(Y). {
Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &X, 1);
Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &Y, 1);
+ if( IN_RENAME_COLUMN ) sqlite3RenameToken(pParse, (void*)temp2, &Y);
A = sqlite3PExpr(pParse, TK_DOT, temp1, temp2);
}
expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). {
@@ -962,6 +964,7 @@ expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). {
Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &Y, 1);
Expr *temp3 = sqlite3ExprAlloc(pParse->db, TK_ID, &Z, 1);
Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3);
+ if( IN_RENAME_COLUMN ) sqlite3RenameToken(pParse, (void*)temp3, &Z);
A = sqlite3PExpr(pParse, TK_DOT, temp1, temp4);
}
term(A) ::= NULL|FLOAT|BLOB(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/}
@@ -1308,6 +1311,9 @@ uniqueflag(A) ::= . {A = OE_None;}
pIdToken->n, pIdToken->z);
}
sqlite3ExprListSetName(pParse, p, pIdToken, 1);
+ if( IN_RENAME_COLUMN ){
+ sqlite3RenameToken(pParse, (void*)&(p->a[p->nExpr-1]), pIdToken);
+ }
return p;
}
} // end %include
@@ -1532,8 +1538,13 @@ add_column_fullname ::= fullname(X). {
disableLookaside(pParse);
sqlite3AlterBeginAddColumn(pParse, X);
}
+cmd ::= ALTER TABLE fullname(X) RENAME kwcolumn_opt nm(Y) TO nm(Z). {
+ sqlite3AlterRenameColumn(pParse, X, &Y, &Z);
+}
+
kwcolumn_opt ::= .
kwcolumn_opt ::= COLUMNKW.
+
%endif SQLITE_OMIT_ALTERTABLE
//////////////////////// CREATE VIRTUAL TABLE ... /////////////////////////////
diff --git a/src/resolve.c b/src/resolve.c
index 5c1dd09be..fc815eed6 100644
--- a/src/resolve.c
+++ b/src/resolve.c
@@ -668,12 +668,15 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
if( pRight->op==TK_ID ){
zDb = 0;
zTable = pExpr->pLeft->u.zToken;
- zColumn = pRight->u.zToken;
}else{
assert( pRight->op==TK_DOT );
zDb = pExpr->pLeft->u.zToken;
zTable = pRight->pLeft->u.zToken;
- zColumn = pRight->pRight->u.zToken;
+ pRight = pRight->pRight;
+ }
+ zColumn = pRight->u.zToken;
+ if( IN_RENAME_COLUMN ){
+ sqlite3MoveRenameToken(pParse, (void*)pExpr, (void*)pRight);
}
}
return lookupName(pParse, zDb, zTable, zColumn, pNC, pExpr);
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 6304680ab..12812f042 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -1087,6 +1087,7 @@ typedef struct NameContext NameContext;
typedef struct Parse Parse;
typedef struct PreUpdate PreUpdate;
typedef struct PrintfArguments PrintfArguments;
+typedef struct RenameToken RenameToken;
typedef struct RowSet RowSet;
typedef struct Savepoint Savepoint;
typedef struct Select Select;
@@ -3087,8 +3088,10 @@ struct Parse {
ynVar nVar; /* Number of '?' variables seen in the SQL so far */
u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */
u8 explain; /* True if the EXPLAIN flag is found on the query */
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_ALTERTABLE)
+ u8 eParseMode; /* PARSE_MODE_XXX constant */
+#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
- u8 declareVtab; /* True if inside sqlite3_declare_vtab() */
int nVtabLock; /* Number of virtual tables to lock */
#endif
int nHeight; /* Expression tree height of current sub-select */
@@ -3099,6 +3102,7 @@ struct Parse {
Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */
const char *zTail; /* All SQL text past the last semicolon parsed */
Table *pNewTable; /* A table being constructed by CREATE TABLE */
+ Index *pNewIndex; /* An index being constructed by CREATE INDEX */
Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */
const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */
#ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -3109,8 +3113,15 @@ struct Parse {
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
With *pWith; /* Current WITH clause, or NULL */
With *pWithToFree; /* Free this WITH object at the end of the parse */
+#ifndef SQLITE_OMIT_ALTERTABLE
+ RenameToken *pRename;
+#endif
};
+#define PARSE_MODE_NORMAL 0
+#define PARSE_MODE_DECLARE_VTAB 1
+#define PARSE_MODE_RENAME_COLUMN 2
+
/*
** Sizes and pointers of various parts of the Parse object.
*/
@@ -3125,7 +3136,19 @@ struct Parse {
#ifdef SQLITE_OMIT_VIRTUALTABLE
#define IN_DECLARE_VTAB 0
#else
- #define IN_DECLARE_VTAB (pParse->declareVtab)
+ #define IN_DECLARE_VTAB (pParse->eParseMode==PARSE_MODE_DECLARE_VTAB)
+#endif
+
+#if defined(SQLITE_OMIT_ALTERTABLE)
+ #define IN_RENAME_COLUMN 0
+#else
+ #define IN_RENAME_COLUMN (pParse->eParseMode==PARSE_MODE_RENAME_COLUMN)
+#endif
+
+#if defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_OMIT_ALTERTABLE)
+ #define IN_SPECIAL_PARSE 0
+#else
+ #define IN_SPECIAL_PARSE (pParse->eParseMode!=PARSE_MODE_NORMAL)
#endif
/*
@@ -3409,6 +3432,7 @@ struct Walker {
Select *pSelect; /* HAVING to WHERE clause ctx */
struct WindowRewrite *pRewrite; /* Window rewrite context */
struct WhereConst *pConst; /* WHERE clause constants */
+ struct RenameCtx *pRename; /* RENAME COLUMN context */
} u;
};
@@ -3844,6 +3868,7 @@ void sqlite3CreateView(Parse*,Token*,Token*,Token*,ExprList*,Select*,int,int);
void sqlite3DropTable(Parse*, SrcList*, int, int);
void sqlite3CodeDropTable(Parse*, Table*, int, int);
void sqlite3DeleteTable(sqlite3*, Table*);
+void sqlite3FreeIndex(sqlite3*, Index*);
#ifndef SQLITE_OMIT_AUTOINCREMENT
void sqlite3AutoincrementBegin(Parse *pParse);
void sqlite3AutoincrementEnd(Parse *pParse);
@@ -4188,6 +4213,7 @@ void sqlite3RootPageMoved(sqlite3*, int, int, int);
void sqlite3Reindex(Parse*, Token*, Token*);
void sqlite3AlterFunctions(void);
void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
+void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*);
int sqlite3GetToken(const unsigned char *, int *);
void sqlite3NestedParse(Parse*, const char*, ...);
void sqlite3ExpirePreparedStatements(sqlite3*, int);
@@ -4203,6 +4229,8 @@ int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
void sqlite3ColumnDefault(Vdbe *, Table *, int, int);
void sqlite3AlterFinishAddColumn(Parse *, Token *);
void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
+void sqlite3RenameToken(Parse*, void*, Token*);
+void sqlite3MoveRenameToken(Parse*, void *pTo, void *pFrom);
CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*);
char sqlite3AffinityType(const char*, Column*);
void sqlite3Analyze(Parse*, Token*, Token*);
diff --git a/src/tokenize.c b/src/tokenize.c
index 6cdff74d3..685d10b25 100644
--- a/src/tokenize.c
+++ b/src/tokenize.c
@@ -690,7 +690,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
sqlite3_free(pParse->apVtabLock);
#endif
- if( !IN_DECLARE_VTAB ){
+ if( !IN_SPECIAL_PARSE ){
/* If the pParse->declareVtab flag is set, do not delete any table
** structure built up in pParse->pNewTable. The calling code (see vtab.c)
** will take responsibility for freeing the Table structure.
diff --git a/src/vtab.c b/src/vtab.c
index c9ce83890..7b459be7b 100644
--- a/src/vtab.c
+++ b/src/vtab.c
@@ -758,7 +758,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
assert( IsVirtual(pTab) );
memset(&sParse, 0, sizeof(sParse));
- sParse.declareVtab = 1;
+ sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
sParse.db = db;
sParse.nQueryLoop = 1;
if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr)
@@ -799,7 +799,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
sqlite3DbFree(db, zErr);
rc = SQLITE_ERROR;
}
- sParse.declareVtab = 0;
+ sParse.eParseMode = PARSE_MODE_NORMAL;
if( sParse.pVdbe ){
sqlite3VdbeFinalize(sParse.pVdbe);