| /* |
| ** 2005 February 15 |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** May you do good and not evil. |
| ** May you find forgiveness for yourself and forgive others. |
| ** May you share freely, never taking more than you give. |
| ** |
| ************************************************************************* |
| ** This file contains C code routines that used to generate VDBE code |
| ** that implements the ALTER TABLE command. |
| */ |
| #include "sqliteInt.h" |
| |
| /* |
| ** The code in this file only exists if we are not omitting the |
| ** ALTER TABLE logic from the build. |
| */ |
| #ifndef SQLITE_OMIT_ALTERTABLE |
| |
| /* |
| ** Parameter zName is the name of a table that is about to be altered |
| ** (either with ALTER TABLE ... RENAME TO or ALTER TABLE ... ADD COLUMN). |
| ** If the table is a system table, this function leaves an error message |
| ** in pParse->zErr (system tables may not be altered) and returns non-zero. |
| ** |
| ** Or, if zName is not a system table, zero is returned. |
| */ |
| static int isAlterableTable(Parse *pParse, Table *pTab){ |
| if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| || (pTab->tabFlags & TF_Eponymous)!=0 |
| || ( (pTab->tabFlags & TF_Shadow)!=0 |
| && sqlite3ReadOnlyShadowTables(pParse->db) |
| ) |
| #endif |
| ){ |
| sqlite3ErrorMsg(pParse, "table %s may not be altered", pTab->zName); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* |
| ** Generate code to verify that the schemas of database zDb and, if |
| ** bTemp is not true, database "temp", can still be parsed. This is |
| ** called at the end of the generation of an ALTER TABLE ... RENAME ... |
| ** statement to ensure that the operation has not rendered any schema |
| ** objects unusable. |
| */ |
| static void renameTestSchema( |
| Parse *pParse, /* Parse context */ |
| const char *zDb, /* Name of db to verify schema of */ |
| int bTemp, /* True if this is the temp db */ |
| const char *zWhen, /* "when" part of error message */ |
| int bNoDQS /* Do not allow DQS in the schema */ |
| ){ |
| pParse->colNamesSet = 1; |
| sqlite3NestedParse(pParse, |
| "SELECT 1 " |
| "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " |
| "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" |
| " AND sql NOT LIKE 'create virtual%%'" |
| " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %d)=NULL ", |
| zDb, |
| zDb, bTemp, zWhen, bNoDQS |
| ); |
| |
| if( bTemp==0 ){ |
| sqlite3NestedParse(pParse, |
| "SELECT 1 " |
| "FROM temp." LEGACY_SCHEMA_TABLE " " |
| "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" |
| " AND sql NOT LIKE 'create virtual%%'" |
| " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %d)=NULL ", |
| zDb, zWhen, bNoDQS |
| ); |
| } |
| } |
| |
| /* |
| ** Generate VM code to replace any double-quoted strings (but not double-quoted |
| ** identifiers) within the "sql" column of the sqlite_schema table in |
| ** database zDb with their single-quoted equivalents. If argument bTemp is |
| ** not true, similarly update all SQL statements in the sqlite_schema table |
| ** of the temp db. |
| */ |
| static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){ |
| sqlite3NestedParse(pParse, |
| "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE |
| " SET sql = sqlite_rename_quotefix(%Q, sql)" |
| "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" |
| " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb |
| ); |
| if( bTemp==0 ){ |
| sqlite3NestedParse(pParse, |
| "UPDATE temp." LEGACY_SCHEMA_TABLE |
| " SET sql = sqlite_rename_quotefix('temp', sql)" |
| "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" |
| " AND sql NOT LIKE 'create virtual%%'" |
| ); |
| } |
| } |
| |
| /* |
| ** Generate code to reload the schema for database iDb. And, if iDb!=1, for |
| ** the temp database as well. |
| */ |
| static void renameReloadSchema(Parse *pParse, int iDb, u16 p5){ |
| Vdbe *v = pParse->pVdbe; |
| if( v ){ |
| sqlite3ChangeCookie(pParse, iDb); |
| sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0, p5); |
| if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0, p5); |
| } |
| } |
| |
| /* |
| ** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" |
| ** command. |
| */ |
| void sqlite3AlterRenameTable( |
| Parse *pParse, /* Parser context. */ |
| SrcList *pSrc, /* The table to rename. */ |
| Token *pName /* The new table name. */ |
| ){ |
| int iDb; /* Database that contains the table */ |
| char *zDb; /* Name of database iDb */ |
| Table *pTab; /* Table being renamed */ |
| char *zName = 0; /* NULL-terminated version of pName */ |
| sqlite3 *db = pParse->db; /* Database connection */ |
| int nTabName; /* Number of UTF-8 characters in zTabName */ |
| const char *zTabName; /* Original name of the table */ |
| Vdbe *v; |
| VTable *pVTab = 0; /* Non-zero if this is a v-tab with an xRename() */ |
| |
| if( NEVER(db->mallocFailed) ) goto exit_rename_table; |
| assert( pSrc->nSrc==1 ); |
| assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); |
| |
| pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); |
| if( !pTab ) goto exit_rename_table; |
| iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); |
| zDb = db->aDb[iDb].zDbSName; |
| |
| /* Get a NULL terminated version of the new table name. */ |
| zName = sqlite3NameFromToken(db, pName); |
| if( !zName ) goto exit_rename_table; |
| |
| /* Check that a table or index named 'zName' does not already exist |
| ** in database iDb. If so, this is an error. |
| */ |
| if( sqlite3FindTable(db, zName, zDb) |
| || sqlite3FindIndex(db, zName, zDb) |
| || sqlite3IsShadowTableOf(db, pTab, zName) |
| ){ |
| sqlite3ErrorMsg(pParse, |
| "there is already another table or index with this name: %s", zName); |
| goto exit_rename_table; |
| } |
| |
| /* Make sure it is not a system table being altered, or a reserved name |
| ** that the table is being renamed to. |
| */ |
| if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){ |
| goto exit_rename_table; |
| } |
| if( SQLITE_OK!=sqlite3CheckObjectName(pParse,zName,"table",zName) ){ |
| goto exit_rename_table; |
| } |
| |
| #ifndef SQLITE_OMIT_VIEW |
| if( IsView(pTab) ){ |
| sqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName); |
| goto exit_rename_table; |
| } |
| #endif |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| /* Invoke the authorization callback. */ |
| if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){ |
| goto exit_rename_table; |
| } |
| #endif |
| |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| if( sqlite3ViewGetColumnNames(pParse, pTab) ){ |
| goto exit_rename_table; |
| } |
| if( IsVirtual(pTab) ){ |
| pVTab = sqlite3GetVTable(db, pTab); |
| if( pVTab->pVtab->pModule->xRename==0 ){ |
| pVTab = 0; |
| } |
| } |
| #endif |
| |
| /* Begin a transaction for database iDb. Then modify the schema cookie |
| ** (since the ALTER TABLE modifies the schema). Call sqlite3MayAbort(), |
| ** as the scalar functions (e.g. sqlite_rename_table()) invoked by the |
| ** nested SQL may raise an exception. */ |
| v = sqlite3GetVdbe(pParse); |
| if( v==0 ){ |
| goto exit_rename_table; |
| } |
| sqlite3MayAbort(pParse); |
| |
| /* figure out how many UTF-8 characters are in zName */ |
| zTabName = pTab->zName; |
| nTabName = sqlite3Utf8CharLen(zTabName, -1); |
| |
| /* Rewrite all CREATE TABLE, INDEX, TRIGGER or VIEW statements in |
| ** the schema to use the new table name. */ |
| sqlite3NestedParse(pParse, |
| "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " |
| "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, %d) " |
| "WHERE (type!='index' OR tbl_name=%Q COLLATE nocase)" |
| "AND name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" |
| , zDb, zDb, zTabName, zName, (iDb==1), zTabName |
| ); |
| |
| /* Update the tbl_name and name columns of the sqlite_schema table |
| ** as required. */ |
| sqlite3NestedParse(pParse, |
| "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET " |
| "tbl_name = %Q, " |
| "name = CASE " |
| "WHEN type='table' THEN %Q " |
| "WHEN name LIKE 'sqliteX_autoindex%%' ESCAPE 'X' " |
| " AND type='index' THEN " |
| "'sqlite_autoindex_' || %Q || substr(name,%d+18) " |
| "ELSE name END " |
| "WHERE tbl_name=%Q COLLATE nocase AND " |
| "(type='table' OR type='index' OR type='trigger');", |
| zDb, |
| zName, zName, zName, |
| nTabName, zTabName |
| ); |
| |
| #ifndef SQLITE_OMIT_AUTOINCREMENT |
| /* If the sqlite_sequence table exists in this database, then update |
| ** it with the new table name. |
| */ |
| if( sqlite3FindTable(db, "sqlite_sequence", zDb) ){ |
| sqlite3NestedParse(pParse, |
| "UPDATE \"%w\".sqlite_sequence set name = %Q WHERE name = %Q", |
| zDb, zName, pTab->zName); |
| } |
| #endif |
| |
| /* If the table being renamed is not itself part of the temp database, |
| ** edit view and trigger definitions within the temp database |
| ** as required. */ |
| if( iDb!=1 ){ |
| sqlite3NestedParse(pParse, |
| "UPDATE sqlite_temp_schema SET " |
| "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), " |
| "tbl_name = " |
| "CASE WHEN tbl_name=%Q COLLATE nocase AND " |
| " sqlite_rename_test(%Q, sql, type, name, 1, 'after rename', 0) " |
| "THEN %Q ELSE tbl_name END " |
| "WHERE type IN ('view', 'trigger')" |
| , zDb, zTabName, zName, zTabName, zDb, zName); |
| } |
| |
| /* If this is a virtual table, invoke the xRename() function if |
| ** one is defined. The xRename() callback will modify the names |
| ** of any resources used by the v-table implementation (including other |
| ** SQLite tables) that are identified by the name of the virtual table. |
| */ |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| if( pVTab ){ |
| int i = ++pParse->nMem; |
| sqlite3VdbeLoadString(v, i, zName); |
| sqlite3VdbeAddOp4(v, OP_VRename, i, 0, 0,(const char*)pVTab, P4_VTAB); |
| } |
| #endif |
| |
| renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); |
| renameTestSchema(pParse, zDb, iDb==1, "after rename", 0); |
| |
| exit_rename_table: |
| sqlite3SrcListDelete(db, pSrc); |
| sqlite3DbFree(db, zName); |
| } |
| |
| /* |
| ** Write code that will raise an error if the table described by |
| ** zDb and zTab is not empty. |
| */ |
| static void sqlite3ErrorIfNotEmpty( |
| Parse *pParse, /* Parsing context */ |
| const char *zDb, /* Schema holding the table */ |
| const char *zTab, /* Table to check for empty */ |
| const char *zErr /* Error message text */ |
| ){ |
| sqlite3NestedParse(pParse, |
| "SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"", |
| zErr, zDb, zTab |
| ); |
| } |
| |
| /* |
| ** This function is called after an "ALTER TABLE ... ADD" statement |
| ** has been parsed. Argument pColDef contains the text of the new |
| ** column definition. |
| ** |
| ** The Table structure pParse->pNewTable was extended to include |
| ** the new column during parsing. |
| */ |
| void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ |
| Table *pNew; /* Copy of pParse->pNewTable */ |
| Table *pTab; /* Table being altered */ |
| int iDb; /* Database number */ |
| const char *zDb; /* Database name */ |
| const char *zTab; /* Table name */ |
| char *zCol; /* Null-terminated column definition */ |
| Column *pCol; /* The new column */ |
| Expr *pDflt; /* Default value for the new column */ |
| sqlite3 *db; /* The database connection; */ |
| Vdbe *v; /* The prepared statement under construction */ |
| int r1; /* Temporary registers */ |
| |
| db = pParse->db; |
| assert( db->pParse==pParse ); |
| if( pParse->nErr ) return; |
| assert( db->mallocFailed==0 ); |
| pNew = pParse->pNewTable; |
| assert( pNew ); |
| |
| assert( sqlite3BtreeHoldsAllMutexes(db) ); |
| iDb = sqlite3SchemaToIndex(db, pNew->pSchema); |
| zDb = db->aDb[iDb].zDbSName; |
| zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ |
| pCol = &pNew->aCol[pNew->nCol-1]; |
| pDflt = sqlite3ColumnExpr(pNew, pCol); |
| pTab = sqlite3FindTable(db, zTab, zDb); |
| assert( pTab ); |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| /* Invoke the authorization callback. */ |
| if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){ |
| return; |
| } |
| #endif |
| |
| |
| /* Check that the new column is not specified as PRIMARY KEY or UNIQUE. |
| ** If there is a NOT NULL constraint, then the default value for the |
| ** column must not be NULL. |
| */ |
| if( pCol->colFlags & COLFLAG_PRIMKEY ){ |
| sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column"); |
| return; |
| } |
| if( pNew->pIndex ){ |
| sqlite3ErrorMsg(pParse, |
| "Cannot add a UNIQUE column"); |
| return; |
| } |
| if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){ |
| /* If the default value for the new column was specified with a |
| ** literal NULL, then set pDflt to 0. This simplifies checking |
| ** for an SQL NULL default below. |
| */ |
| assert( pDflt==0 || pDflt->op==TK_SPAN ); |
| if( pDflt && pDflt->pLeft->op==TK_NULL ){ |
| pDflt = 0; |
| } |
| assert( IsOrdinaryTable(pNew) ); |
| if( (db->flags&SQLITE_ForeignKeys) && pNew->u.tab.pFKey && pDflt ){ |
| sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, |
| "Cannot add a REFERENCES column with non-NULL default value"); |
| } |
| if( pCol->notNull && !pDflt ){ |
| sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, |
| "Cannot add a NOT NULL column with default value NULL"); |
| } |
| |
| |
| /* Ensure the default expression is something that sqlite3ValueFromExpr() |
| ** can handle (i.e. not CURRENT_TIME etc.) |
| */ |
| if( pDflt ){ |
| sqlite3_value *pVal = 0; |
| int rc; |
| rc = sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_BLOB, &pVal); |
| assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); |
| if( rc!=SQLITE_OK ){ |
| assert( db->mallocFailed == 1 ); |
| return; |
| } |
| if( !pVal ){ |
| sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, |
| "Cannot add a column with non-constant default"); |
| } |
| sqlite3ValueFree(pVal); |
| } |
| }else if( pCol->colFlags & COLFLAG_STORED ){ |
| sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, "cannot add a STORED column"); |
| } |
| |
| |
| /* Modify the CREATE TABLE statement. */ |
| zCol = sqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n); |
| if( zCol ){ |
| char *zEnd = &zCol[pColDef->n-1]; |
| while( zEnd>zCol && (*zEnd==';' || sqlite3Isspace(*zEnd)) ){ |
| *zEnd-- = '\0'; |
| } |
| /* substr() operations on characters, but addColOffset is in bytes. So we |
| ** have to use printf() to translate between these units: */ |
| assert( IsOrdinaryTable(pTab) ); |
| assert( IsOrdinaryTable(pNew) ); |
| sqlite3NestedParse(pParse, |
| "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " |
| "sql = printf('%%.%ds, ',sql) || %Q" |
| " || substr(sql,1+length(printf('%%.%ds',sql))) " |
| "WHERE type = 'table' AND name = %Q", |
| zDb, pNew->u.tab.addColOffset, zCol, pNew->u.tab.addColOffset, |
| zTab |
| ); |
| sqlite3DbFree(db, zCol); |
| } |
| |
| v = sqlite3GetVdbe(pParse); |
| if( v ){ |
| /* Make sure the schema version is at least 3. But do not upgrade |
| ** from less than 3 to 4, as that will corrupt any preexisting DESC |
| ** index. |
| */ |
| r1 = sqlite3GetTempReg(pParse); |
| sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT); |
| sqlite3VdbeUsesBtree(v, iDb); |
| sqlite3VdbeAddOp2(v, OP_AddImm, r1, -2); |
| sqlite3VdbeAddOp2(v, OP_IfPos, r1, sqlite3VdbeCurrentAddr(v)+2); |
| VdbeCoverage(v); |
| sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, 3); |
| sqlite3ReleaseTempReg(pParse, r1); |
| |
| /* Reload the table definition */ |
| renameReloadSchema(pParse, iDb, INITFLAG_AlterAdd); |
| |
| /* Verify that constraints are still satisfied */ |
| if( pNew->pCheck!=0 |
| || (pCol->notNull && (pCol->colFlags & COLFLAG_GENERATED)!=0) |
| ){ |
| sqlite3NestedParse(pParse, |
| "SELECT CASE WHEN quick_check GLOB 'CHECK*'" |
| " THEN raise(ABORT,'CHECK constraint failed')" |
| " ELSE raise(ABORT,'NOT NULL constraint failed')" |
| " END" |
| " FROM pragma_quick_check(%Q,%Q)" |
| " WHERE quick_check GLOB 'CHECK*' OR quick_check GLOB 'NULL*'", |
| zTab, zDb |
| ); |
| } |
| } |
| } |
| |
| /* |
| ** This function is called by the parser after the table-name in |
| ** an "ALTER TABLE <table-name> ADD" statement is parsed. Argument |
| ** pSrc is the full-name of the table being altered. |
| ** |
| ** This routine makes a (partial) copy of the Table structure |
| ** for the table being altered and sets Parse.pNewTable to point |
| ** to it. Routines called by the parser as the column definition |
| ** is parsed (i.e. sqlite3AddColumn()) add the new Column data to |
| ** the copy. The copy of the Table structure is deleted by tokenize.c |
| ** after parsing is finished. |
| ** |
| ** Routine sqlite3AlterFinishAddColumn() will be called to complete |
| ** coding the "ALTER TABLE ... ADD" statement. |
| */ |
| void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ |
| Table *pNew; |
| Table *pTab; |
| int iDb; |
| int i; |
| int nAlloc; |
| sqlite3 *db = pParse->db; |
| |
| /* Look up the table being altered. */ |
| assert( pParse->pNewTable==0 ); |
| assert( sqlite3BtreeHoldsAllMutexes(db) ); |
| if( db->mallocFailed ) goto exit_begin_add_column; |
| pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); |
| if( !pTab ) goto exit_begin_add_column; |
| |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| if( IsVirtual(pTab) ){ |
| sqlite3ErrorMsg(pParse, "virtual tables may not be altered"); |
| goto exit_begin_add_column; |
| } |
| #endif |
| |
| /* Make sure this is not an attempt to ALTER a view. */ |
| if( IsView(pTab) ){ |
| sqlite3ErrorMsg(pParse, "Cannot add a column to a view"); |
| goto exit_begin_add_column; |
| } |
| if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){ |
| goto exit_begin_add_column; |
| } |
| |
| sqlite3MayAbort(pParse); |
| assert( IsOrdinaryTable(pTab) ); |
| assert( pTab->u.tab.addColOffset>0 ); |
| iDb = sqlite3SchemaToIndex(db, pTab->pSchema); |
| |
| /* Put a copy of the Table struct in Parse.pNewTable for the |
| ** sqlite3AddColumn() function and friends to modify. But modify |
| ** the name by adding an "sqlite_altertab_" prefix. By adding this |
| ** prefix, we insure that the name will not collide with an existing |
| ** table because user table are not allowed to have the "sqlite_" |
| ** prefix on their name. |
| */ |
| pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table)); |
| if( !pNew ) goto exit_begin_add_column; |
| pParse->pNewTable = pNew; |
| pNew->nTabRef = 1; |
| pNew->nCol = pTab->nCol; |
| assert( pNew->nCol>0 ); |
| nAlloc = (((pNew->nCol-1)/8)*8)+8; |
| assert( nAlloc>=pNew->nCol && nAlloc%8==0 && nAlloc-pNew->nCol<8 ); |
| pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nAlloc); |
| pNew->zName = sqlite3MPrintf(db, "sqlite_altertab_%s", pTab->zName); |
| if( !pNew->aCol || !pNew->zName ){ |
| assert( db->mallocFailed ); |
| goto exit_begin_add_column; |
| } |
| memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol); |
| for(i=0; i<pNew->nCol; i++){ |
| Column *pCol = &pNew->aCol[i]; |
| pCol->zCnName = sqlite3DbStrDup(db, pCol->zCnName); |
| pCol->hName = sqlite3StrIHash(pCol->zCnName); |
| } |
| assert( IsOrdinaryTable(pNew) ); |
| pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pTab->u.tab.pDfltList, 0); |
| pNew->pSchema = db->aDb[iDb].pSchema; |
| pNew->u.tab.addColOffset = pTab->u.tab.addColOffset; |
| assert( pNew->nTabRef==1 ); |
| |
| exit_begin_add_column: |
| sqlite3SrcListDelete(db, pSrc); |
| return; |
| } |
| |
| /* |
| ** Parameter pTab is the subject of an ALTER TABLE ... RENAME COLUMN |
| ** command. This function checks if the table is a view or virtual |
| ** table (columns of views or virtual tables may not be renamed). If so, |
| ** it loads an error message into pParse and returns non-zero. |
| ** |
| ** Or, if pTab is not a view or virtual table, zero is returned. |
| */ |
| #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) |
| static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ |
| const char *zType = 0; |
| #ifndef SQLITE_OMIT_VIEW |
| if( IsView(pTab) ){ |
| zType = "view"; |
| } |
| #endif |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| if( IsVirtual(pTab) ){ |
| zType = "virtual table"; |
| } |
| #endif |
| if( zType ){ |
| sqlite3ErrorMsg(pParse, "cannot %s %s \"%s\"", |
| (bDrop ? "drop column from" : "rename columns of"), |
| zType, pTab->zName |
| ); |
| return 1; |
| } |
| return 0; |
| } |
| #else /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */ |
| # define isRealTable(x,y,z) (0) |
| #endif |
| |
| /* |
| ** Handles the following parser reduction: |
| ** |
| ** cmd ::= ALTER TABLE pSrc RENAME COLUMN pOld TO pNew |
| */ |
| void sqlite3AlterRenameColumn( |
| Parse *pParse, /* Parsing context */ |
| SrcList *pSrc, /* Table being altered. pSrc->nSrc==1 */ |
| Token *pOld, /* Name of column being changed */ |
| Token *pNew /* New column name */ |
| ){ |
| sqlite3 *db = pParse->db; /* Database connection */ |
| Table *pTab; /* Table being updated */ |
| int iCol; /* Index of column being renamed */ |
| char *zOld = 0; /* Old column name */ |
| char *zNew = 0; /* New column name */ |
| const char *zDb; /* Name of schema containing the table */ |
| int iSchema; /* Index of the schema */ |
| int bQuote; /* True to quote the new name */ |
| |
| /* Locate the table to be altered */ |
| pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); |
| if( !pTab ) goto exit_rename_column; |
| |
| /* Cannot alter a system table */ |
| if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column; |
| if( SQLITE_OK!=isRealTable(pParse, pTab, 0) ) 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; |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| /* Invoke the authorization callback. */ |
| if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){ |
| goto exit_rename_column; |
| } |
| #endif |
| |
| /* Make sure the old name really is a column name in the table to be |
| ** altered. Set iCol to be the index of the column being renamed */ |
| zOld = sqlite3NameFromToken(db, pOld); |
| if( !zOld ) goto exit_rename_column; |
| for(iCol=0; iCol<pTab->nCol; iCol++){ |
| if( 0==sqlite3StrICmp(pTab->aCol[iCol].zCnName, zOld) ) break; |
| } |
| if( iCol==pTab->nCol ){ |
| sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pOld); |
| goto exit_rename_column; |
| } |
| |
| /* Ensure the schema contains no double-quoted strings */ |
| renameTestSchema(pParse, zDb, iSchema==1, "", 0); |
| renameFixQuotes(pParse, zDb, iSchema==1); |
| |
| /* Do the rename operation using a recursive UPDATE statement that |
| ** uses the sqlite_rename_column() SQL function to compute the new |
| ** CREATE statement text for the sqlite_schema table. |
| */ |
| sqlite3MayAbort(pParse); |
| zNew = sqlite3NameFromToken(db, pNew); |
| if( !zNew ) goto exit_rename_column; |
| assert( pNew->n>0 ); |
| bQuote = sqlite3Isquote(pNew->z[0]); |
| sqlite3NestedParse(pParse, |
| "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " |
| "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) " |
| "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' " |
| " AND (type != 'index' OR tbl_name = %Q)", |
| zDb, |
| zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1, |
| pTab->zName |
| ); |
| |
| sqlite3NestedParse(pParse, |
| "UPDATE temp." LEGACY_SCHEMA_TABLE " SET " |
| "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, 1) " |
| "WHERE type IN ('trigger', 'view')", |
| zDb, pTab->zName, iCol, zNew, bQuote |
| ); |
| |
| /* Drop and reload the database schema. */ |
| renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename); |
| renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1); |
| |
| exit_rename_column: |
| sqlite3SrcListDelete(db, pSrc); |
| sqlite3DbFree(db, zOld); |
| sqlite3DbFree(db, zNew); |
| return; |
| } |
| |
| /* |
| ** Each RenameToken object maps an element of the parse tree into |
| ** the token that generated that element. The parse tree element |
| ** might be one of: |
| ** |
| ** * A pointer to an Expr that represents an ID |
| ** * The name of a table column in Column.zName |
| ** |
| ** A list of RenameToken objects can be constructed during parsing. |
| ** Each new object is created by sqlite3RenameTokenMap(). |
| ** As the parse tree is transformed, the sqlite3RenameTokenRemap() |
| ** routine is used to keep the mapping current. |
| ** |
| ** After the parse finishes, renameTokenFind() routine can be used |
| ** to look up the actual token value that created some element in |
| ** the parse tree. |
| */ |
| struct RenameToken { |
| const void *p; /* Parse tree element created by token t */ |
| Token t; /* The token that created parse tree element p */ |
| RenameToken *pNext; /* Next is a list of all RenameToken objects */ |
| }; |
| |
| /* |
| ** 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 */ |
| }; |
| |
| #ifdef SQLITE_DEBUG |
| /* |
| ** This function is only for debugging. It performs two tasks: |
| ** |
| ** 1. Checks that pointer pPtr does not already appear in the |
| ** rename-token list. |
| ** |
| ** 2. Dereferences each pointer in the rename-token list. |
| ** |
| ** The second is most effective when debugging under valgrind or |
| ** address-sanitizer or similar. If any of these pointers no longer |
| ** point to valid objects, an exception is raised by the memory-checking |
| ** tool. |
| ** |
| ** The point of this is to prevent comparisons of invalid pointer values. |
| ** Even though this always seems to work, it is undefined according to the |
| ** C standard. Example of undefined comparison: |
| ** |
| ** sqlite3_free(x); |
| ** if( x==y ) ... |
| ** |
| ** Technically, as x no longer points into a valid object or to the byte |
| ** following a valid object, it may not be used in comparison operations. |
| */ |
| static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ |
| assert( pParse==pParse->db->pParse ); |
| assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); |
| if( pParse->nErr==0 ){ |
| const RenameToken *p; |
| u32 i = 1; |
| for(p=pParse->pRename; p; p=p->pNext){ |
| if( p->p ){ |
| assert( p->p!=pPtr ); |
| i += *(u8*)(p->p) | 1; |
| } |
| } |
| assert( i>0 ); |
| } |
| } |
| #else |
| # define renameTokenCheckAll(x,y) |
| #endif |
| |
| /* |
| ** Remember that the parser tree element pPtr was created using |
| ** the token pToken. |
| ** |
| ** In other words, construct a new RenameToken object and add it |
| ** to the list of RenameToken objects currently being built up |
| ** in pParse->pRename. |
| ** |
| ** The pPtr argument is returned so that this routine can be used |
| ** with tail recursion in tokenExpr() routine, for a small performance |
| ** improvement. |
| */ |
| const void *sqlite3RenameTokenMap( |
| Parse *pParse, |
| const void *pPtr, |
| const Token *pToken |
| ){ |
| RenameToken *pNew; |
| assert( pPtr || pParse->db->mallocFailed ); |
| renameTokenCheckAll(pParse, pPtr); |
| if( ALWAYS(pParse->eParseMode!=PARSE_MODE_UNMAP) ){ |
| pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken)); |
| if( pNew ){ |
| pNew->p = pPtr; |
| pNew->t = *pToken; |
| pNew->pNext = pParse->pRename; |
| pParse->pRename = pNew; |
| } |
| } |
| |
| return pPtr; |
| } |
| |
| /* |
| ** It is assumed that there is already a RenameToken object associated |
| ** with parse tree element pFrom. This function remaps the associated token |
| ** to parse tree element pTo. |
| */ |
| void sqlite3RenameTokenRemap(Parse *pParse, const void *pTo, const void *pFrom){ |
| RenameToken *p; |
| renameTokenCheckAll(pParse, pTo); |
| for(p=pParse->pRename; p; p=p->pNext){ |
| if( p->p==pFrom ){ |
| p->p = pTo; |
| break; |
| } |
| } |
| } |
| |
| /* |
| ** Walker callback used by sqlite3RenameExprUnmap(). |
| */ |
| static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ |
| Parse *pParse = pWalker->pParse; |
| sqlite3RenameTokenRemap(pParse, 0, (const void*)pExpr); |
| if( ExprUseYTab(pExpr) ){ |
| sqlite3RenameTokenRemap(pParse, 0, (const void*)&pExpr->y.pTab); |
| } |
| return WRC_Continue; |
| } |
| |
| /* |
| ** Iterate through the Select objects that are part of WITH clauses attached |
| ** to select statement pSelect. |
| */ |
| static void renameWalkWith(Walker *pWalker, Select *pSelect){ |
| With *pWith = pSelect->pWith; |
| if( pWith ){ |
| Parse *pParse = pWalker->pParse; |
| int i; |
| With *pCopy = 0; |
| assert( pWith->nCte>0 ); |
| if( (pWith->a[0].pSelect->selFlags & SF_Expanded)==0 ){ |
| /* Push a copy of the With object onto the with-stack. We use a copy |
| ** here as the original will be expanded and resolved (flags SF_Expanded |
| ** and SF_Resolved) below. And the parser code that uses the with-stack |
| ** fails if the Select objects on it have already been expanded and |
| ** resolved. */ |
| pCopy = sqlite3WithDup(pParse->db, pWith); |
| pCopy = sqlite3WithPush(pParse, pCopy, 1); |
| } |
| for(i=0; i<pWith->nCte; i++){ |
| Select *p = pWith->a[i].pSelect; |
| NameContext sNC; |
| memset(&sNC, 0, sizeof(sNC)); |
| sNC.pParse = pParse; |
| if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC); |
| if( sNC.pParse->db->mallocFailed ) return; |
| sqlite3WalkSelect(pWalker, p); |
| sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols); |
| } |
| if( pCopy && pParse->pWith==pCopy ){ |
| pParse->pWith = pCopy->pOuter; |
| } |
| } |
| } |
| |
| /* |
| ** Unmap all tokens in the IdList object passed as the second argument. |
| */ |
| static void unmapColumnIdlistNames( |
| Parse *pParse, |
| const IdList *pIdList |
| ){ |
| int ii; |
| assert( pIdList!=0 ); |
| for(ii=0; ii<pIdList->nId; ii++){ |
| sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName); |
| } |
| } |
| |
| /* |
| ** Walker callback used by sqlite3RenameExprUnmap(). |
| */ |
| static int renameUnmapSelectCb(Walker *pWalker, Select *p){ |
| Parse *pParse = pWalker->pParse; |
| int i; |
| if( pParse->nErr ) return WRC_Abort; |
| testcase( p->selFlags & SF_View ); |
| testcase( p->selFlags & SF_CopyCte ); |
| if( p->selFlags & (SF_View|SF_CopyCte) ){ |
| return WRC_Prune; |
| } |
| if( ALWAYS(p->pEList) ){ |
| ExprList *pList = p->pEList; |
| for(i=0; i<pList->nExpr; i++){ |
| if( pList->a[i].zEName && pList->a[i].fg.eEName==ENAME_NAME ){ |
| sqlite3RenameTokenRemap(pParse, 0, (void*)pList->a[i].zEName); |
| } |
| } |
| } |
| if( ALWAYS(p->pSrc) ){ /* Every Select as a SrcList, even if it is empty */ |
| SrcList *pSrc = p->pSrc; |
| for(i=0; i<pSrc->nSrc; i++){ |
| sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName); |
| if( pSrc->a[i].fg.isUsing==0 ){ |
| sqlite3WalkExpr(pWalker, pSrc->a[i].u3.pOn); |
| }else{ |
| unmapColumnIdlistNames(pParse, pSrc->a[i].u3.pUsing); |
| } |
| } |
| } |
| |
| renameWalkWith(pWalker, p); |
| return WRC_Continue; |
| } |
| |
| /* |
| ** Remove all nodes that are part of expression pExpr from the rename list. |
| */ |
| void sqlite3RenameExprUnmap(Parse *pParse, Expr *pExpr){ |
| u8 eMode = pParse->eParseMode; |
| Walker sWalker; |
| memset(&sWalker, 0, sizeof(Walker)); |
| sWalker.pParse = pParse; |
| sWalker.xExprCallback = renameUnmapExprCb; |
| sWalker.xSelectCallback = renameUnmapSelectCb; |
| pParse->eParseMode = PARSE_MODE_UNMAP; |
| sqlite3WalkExpr(&sWalker, pExpr); |
| pParse->eParseMode = eMode; |
| } |
| |
| /* |
| ** Remove all nodes that are part of expression-list pEList from the |
| ** rename list. |
| */ |
| void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){ |
| if( pEList ){ |
| int i; |
| Walker sWalker; |
| memset(&sWalker, 0, sizeof(Walker)); |
| sWalker.pParse = pParse; |
| sWalker.xExprCallback = renameUnmapExprCb; |
| sqlite3WalkExprList(&sWalker, pEList); |
| for(i=0; i<pEList->nExpr; i++){ |
| if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) ){ |
| sqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zEName); |
| } |
| } |
| } |
| } |
| |
| /* |
| ** Free the list of RenameToken objects given in the second argument |
| */ |
| static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ |
| RenameToken *pNext; |
| RenameToken *p; |
| for(p=pToken; p; p=pNext){ |
| pNext = p->pNext; |
| sqlite3DbFree(db, p); |
| } |
| } |
| |
| /* |
| ** Search the Parse object passed as the first argument for a RenameToken |
| ** object associated with parse tree element pPtr. If found, return a pointer |
| ** to it. Otherwise, return NULL. |
| ** |
| ** If the second argument passed to this function is not NULL and a matching |
| ** RenameToken object is found, remove it from the Parse object and add it to |
| ** the list maintained by the RenameCtx object. |
| */ |
| static RenameToken *renameTokenFind( |
| Parse *pParse, |
| struct RenameCtx *pCtx, |
| const void *pPtr |
| ){ |
| RenameToken **pp; |
| if( NEVER(pPtr==0) ){ |
| return 0; |
| } |
| for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){ |
| if( (*pp)->p==pPtr ){ |
| RenameToken *pToken = *pp; |
| if( pCtx ){ |
| *pp = pToken->pNext; |
| pToken->pNext = pCtx->pList; |
| pCtx->pList = pToken; |
| pCtx->nList++; |
| } |
| return pToken; |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| ** 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){ |
| if( p->selFlags & (SF_View|SF_CopyCte) ){ |
| testcase( p->selFlags & SF_View ); |
| testcase( p->selFlags & SF_CopyCte ); |
| return WRC_Prune; |
| } |
| renameWalkWith(pWalker, p); |
| return WRC_Continue; |
| } |
| |
| /* |
| ** This is a Walker expression callback. |
| ** |
| ** For every TK_COLUMN node in the expression tree, search to see |
| ** if the column being references is the column being renamed by an |
| ** 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){ |
| 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 |
| && ALWAYS(ExprUseYTab(pExpr)) |
| && p->pTab==pExpr->y.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 "last" |
| ** RenameToken in the RenameCtx and remove that RenameToken from the |
| ** 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(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; |
| } |
| |
| /* |
| ** An error occurred 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, |
| const char *zWhen, |
| sqlite3_value *pType, |
| sqlite3_value *pObject, |
| Parse *pParse |
| ){ |
| const char *zT = (const char*)sqlite3_value_text(pType); |
| const char *zN = (const char*)sqlite3_value_text(pObject); |
| char *zErr; |
| |
| zErr = sqlite3MPrintf(pParse->db, "error in %s %s%s%s: %s", |
| zT, zN, (zWhen[0] ? " " : ""), zWhen, |
| pParse->zErrMsg |
| ); |
| sqlite3_result_error(pCtx, zErr, -1); |
| sqlite3DbFree(pParse->db, zErr); |
| } |
| |
| /* |
| ** For each name in the the expression-list pEList (i.e. each |
| ** pEList->a[i].zName) that matches the string in zOld, extract the |
| ** corresponding rename-token from Parse object pParse and add it |
| ** to the RenameCtx pCtx. |
| */ |
| static void renameColumnElistNames( |
| Parse *pParse, |
| RenameCtx *pCtx, |
| const ExprList *pEList, |
| const char *zOld |
| ){ |
| if( pEList ){ |
| int i; |
| for(i=0; i<pEList->nExpr; i++){ |
| const char *zName = pEList->a[i].zEName; |
| if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) |
| && ALWAYS(zName!=0) |
| && 0==sqlite3_stricmp(zName, zOld) |
| ){ |
| renameTokenFind(pParse, pCtx, (const void*)zName); |
| } |
| } |
| } |
| } |
| |
| /* |
| ** For each name in the the id-list pIdList (i.e. each pIdList->a[i].zName) |
| ** that matches the string in zOld, extract the corresponding rename-token |
| ** from Parse object pParse and add it to the RenameCtx pCtx. |
| */ |
| static void renameColumnIdlistNames( |
| Parse *pParse, |
| RenameCtx *pCtx, |
| const IdList *pIdList, |
| const char *zOld |
| ){ |
| if( pIdList ){ |
| int i; |
| for(i=0; i<pIdList->nId; i++){ |
| const char *zName = pIdList->a[i].zName; |
| if( 0==sqlite3_stricmp(zName, zOld) ){ |
| renameTokenFind(pParse, pCtx, (const void*)zName); |
| } |
| } |
| } |
| } |
| |
| |
| /* |
| ** Parse the SQL statement zSql using Parse object (*p). The Parse object |
| ** is initialized by this function before it is used. |
| */ |
| static int renameParseSql( |
| Parse *p, /* Memory to use for Parse object */ |
| const char *zDb, /* Name of schema SQL belongs to */ |
| sqlite3 *db, /* Database handle */ |
| const char *zSql, /* SQL to parse */ |
| int bTemp /* True if SQL is from temp schema */ |
| ){ |
| int rc; |
| |
| sqlite3ParseObjectInit(p, db); |
| if( zSql==0 ){ |
| return SQLITE_NOMEM; |
| } |
| if( sqlite3StrNICmp(zSql,"CREATE ",7)!=0 ){ |
| return SQLITE_CORRUPT_BKPT; |
| } |
| db->init.iDb = bTemp ? 1 : sqlite3FindDbName(db, zDb); |
| p->eParseMode = PARSE_MODE_RENAME; |
| p->db = db; |
| p->nQueryLoop = 1; |
| rc = sqlite3RunParser(p, zSql); |
| if( db->mallocFailed ) rc = SQLITE_NOMEM; |
| if( rc==SQLITE_OK |
| && NEVER(p->pNewTable==0 && p->pNewIndex==0 && p->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. */ |
| if( rc==SQLITE_OK ){ |
| int nSql = sqlite3Strlen30(zSql); |
| RenameToken *pToken; |
| for(pToken=p->pRename; pToken; pToken=pToken->pNext){ |
| assert( pToken->t.z>=zSql && &pToken->t.z[pToken->t.n]<=&zSql[nSql] ); |
| } |
| } |
| #endif |
| |
| db->init.iDb = 0; |
| return rc; |
| } |
| |
| /* |
| ** This function edits SQL statement zSql, replacing each token identified |
| ** by the linked list pRename with the text of zNew. If argument bQuote is |
| ** true, then zNew is always quoted first. If no error occurs, the result |
| ** is loaded into context object pCtx as the result. |
| ** |
| ** Or, if an error occurs (i.e. an OOM condition), an error is left in |
| ** pCtx and an SQLite error code returned. |
| */ |
| static int renameEditSql( |
| sqlite3_context *pCtx, /* Return result here */ |
| RenameCtx *pRename, /* Rename context */ |
| const char *zSql, /* SQL statement to edit */ |
| const char *zNew, /* New token text */ |
| int bQuote /* True to always quote token */ |
| ){ |
| i64 nNew = sqlite3Strlen30(zNew); |
| i64 nSql = sqlite3Strlen30(zSql); |
| sqlite3 *db = sqlite3_context_db_handle(pCtx); |
| int rc = SQLITE_OK; |
| char *zQuot = 0; |
| char *zOut; |
| i64 nQuot = 0; |
| char *zBuf1 = 0; |
| char *zBuf2 = 0; |
| |
| if( zNew ){ |
| /* 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. */ |
| zQuot = sqlite3MPrintf(db, "\"%w\" ", zNew); |
| if( zQuot==0 ){ |
| return SQLITE_NOMEM; |
| }else{ |
| nQuot = sqlite3Strlen30(zQuot)-1; |
| } |
| |
| assert( nQuot>=nNew ); |
| zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1); |
| }else{ |
| zOut = (char*)sqlite3DbMallocZero(db, (nSql*2+1) * 3); |
| if( zOut ){ |
| zBuf1 = &zOut[nSql*2+1]; |
| zBuf2 = &zOut[nSql*4+2]; |
| } |
| } |
| |
| /* At this point pRename->pList contains a list of RenameToken objects |
| ** corresponding to all tokens in the input SQL that must be replaced |
| ** with the new column name, or with single-quoted versions of themselves. |
| ** All that remains is to construct and return the edited SQL string. */ |
| if( zOut ){ |
| int nOut = nSql; |
| memcpy(zOut, zSql, nSql); |
| while( pRename->pList ){ |
| int iOff; /* Offset of token to replace in zOut */ |
| u32 nReplace; |
| const char *zReplace; |
| RenameToken *pBest = renameColumnTokenNext(pRename); |
| |
| if( zNew ){ |
| if( bQuote==0 && sqlite3IsIdChar(*pBest->t.z) ){ |
| nReplace = nNew; |
| zReplace = zNew; |
| }else{ |
| nReplace = nQuot; |
| zReplace = zQuot; |
| if( pBest->t.z[pBest->t.n]=='"' ) nReplace++; |
| } |
| }else{ |
| /* Dequote the double-quoted token. Then requote it again, this time |
| ** using single quotes. If the character immediately following the |
| ** original token within the input SQL was a single quote ('), then |
| ** add another space after the new, single-quoted version of the |
| ** token. This is so that (SELECT "string"'alias') maps to |
| ** (SELECT 'string' 'alias'), and not (SELECT 'string''alias'). */ |
| memcpy(zBuf1, pBest->t.z, pBest->t.n); |
| zBuf1[pBest->t.n] = 0; |
| sqlite3Dequote(zBuf1); |
| sqlite3_snprintf(nSql*2, zBuf2, "%Q%s", zBuf1, |
| pBest->t.z[pBest->t.n]=='\'' ? " " : "" |
| ); |
| zReplace = zBuf2; |
| nReplace = sqlite3Strlen30(zReplace); |
| } |
| |
| 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(pCtx, zOut, -1, SQLITE_TRANSIENT); |
| sqlite3DbFree(db, zOut); |
| }else{ |
| rc = SQLITE_NOMEM; |
| } |
| |
| sqlite3_free(zQuot); |
| return rc; |
| } |
| |
| /* |
| ** Set all pEList->a[].fg.eEName fields in the expression-list to val. |
| */ |
| static void renameSetENames(ExprList *pEList, int val){ |
| if( pEList ){ |
| int i; |
| for(i=0; i<pEList->nExpr; i++){ |
| assert( val==ENAME_NAME || pEList->a[i].fg.eEName==ENAME_NAME ); |
| pEList->a[i].fg.eEName = val; |
| } |
| } |
| } |
| |
| /* |
| ** Resolve all symbols in the trigger at pParse->pNewTrigger, assuming |
| ** it was read from the schema of database zDb. Return SQLITE_OK if |
| ** successful. Otherwise, return an SQLite error code and leave an error |
| ** message in the Parse object. |
| */ |
| static int renameResolveTrigger(Parse *pParse){ |
| sqlite3 *db = pParse->db; |
| Trigger *pNew = pParse->pNewTrigger; |
| TriggerStep *pStep; |
| NameContext sNC; |
| int rc = SQLITE_OK; |
| |
| memset(&sNC, 0, sizeof(sNC)); |
| sNC.pParse = pParse; |
| assert( pNew->pTabSchema ); |
| pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, |
| db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName |
| ); |
| pParse->eTriggerOp = pNew->op; |
| /* ALWAYS() because if the table of the trigger does not exist, the |
| ** error would have been hit before this point */ |
| if( ALWAYS(pParse->pTriggerTab) ){ |
| rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab); |
| } |
| |
| /* Resolve symbols in WHEN clause */ |
| if( rc==SQLITE_OK && pNew->pWhen ){ |
| rc = sqlite3ResolveExprNames(&sNC, pNew->pWhen); |
| } |
| |
| for(pStep=pNew->step_list; rc==SQLITE_OK && pStep; pStep=pStep->pNext){ |
| if( pStep->pSelect ){ |
| sqlite3SelectPrep(pParse, pStep->pSelect, &sNC); |
| if( pParse->nErr ) rc = pParse->rc; |
| } |
| if( rc==SQLITE_OK && pStep->zTarget ){ |
| SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep); |
| if( pSrc ){ |
| Select *pSel = sqlite3SelectNew( |
| pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 |
| ); |
| if( pSel==0 ){ |
| pStep->pExprList = 0; |
| pSrc = 0; |
| rc = SQLITE_NOMEM; |
| }else{ |
| /* pStep->pExprList contains an expression-list used for an UPDATE |
| ** statement. So the a[].zEName values are the RHS of the |
| ** "<col> = <expr>" clauses of the UPDATE statement. So, before |
| ** running SelectPrep(), change all the eEName values in |
| ** pStep->pExprList to ENAME_SPAN (from their current value of |
| ** ENAME_NAME). This is to prevent any ids in ON() clauses that are |
| ** part of pSrc from being incorrectly resolved against the |
| ** a[].zEName values as if they were column aliases. */ |
| renameSetENames(pStep->pExprList, ENAME_SPAN); |
| sqlite3SelectPrep(pParse, pSel, 0); |
| renameSetENames(pStep->pExprList, ENAME_NAME); |
| rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK; |
| assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList ); |
| assert( pSrc==pSel->pSrc ); |
| if( pStep->pExprList ) pSel->pEList = 0; |
| pSel->pSrc = 0; |
| sqlite3SelectDelete(db, pSel); |
| } |
| if( pStep->pFrom ){ |
| int i; |
| for(i=0; i<pStep->pFrom->nSrc && rc==SQLITE_OK; i++){ |
| SrcItem *p = &pStep->pFrom->a[i]; |
| if( p->pSelect ){ |
| sqlite3SelectPrep(pParse, p->pSelect, 0); |
| } |
| } |
| } |
| |
| if( db->mallocFailed ){ |
| rc = SQLITE_NOMEM; |
| } |
| sNC.pSrcList = pSrc; |
| if( rc==SQLITE_OK && pStep->pWhere ){ |
| rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList); |
| } |
| assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) ); |
| if( pStep->pUpsert && rc==SQLITE_OK ){ |
| Upsert *pUpsert = pStep->pUpsert; |
| pUpsert->pUpsertSrc = pSrc; |
| sNC.uNC.pUpsert = pUpsert; |
| sNC.ncFlags = NC_UUpsert; |
| rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); |
| if( rc==SQLITE_OK ){ |
| ExprList *pUpsertSet = pUpsert->pUpsertSet; |
| rc = sqlite3ResolveExprListNames(&sNC, pUpsertSet); |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertWhere); |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); |
| } |
| sNC.ncFlags = 0; |
| } |
| sNC.pSrcList = 0; |
| sqlite3SrcListDelete(db, pSrc); |
| }else{ |
| rc = SQLITE_NOMEM; |
| } |
| } |
| } |
| return rc; |
| } |
| |
| /* |
| ** Invoke sqlite3WalkExpr() or sqlite3WalkSelect() on all Select or Expr |
| ** objects that are part of the trigger passed as the second argument. |
| */ |
| static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){ |
| TriggerStep *pStep; |
| |
| /* Find tokens to edit in WHEN clause */ |
| sqlite3WalkExpr(pWalker, pTrigger->pWhen); |
| |
| /* Find tokens to edit in trigger steps */ |
| for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ |
| sqlite3WalkSelect(pWalker, pStep->pSelect); |
| sqlite3WalkExpr(pWalker, pStep->pWhere); |
| sqlite3WalkExprList(pWalker, pStep->pExprList); |
| if( pStep->pUpsert ){ |
| Upsert *pUpsert = pStep->pUpsert; |
| sqlite3WalkExprList(pWalker, pUpsert->pUpsertTarget); |
| sqlite3WalkExprList(pWalker, pUpsert->pUpsertSet); |
| sqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere); |
| sqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere); |
| } |
| if( pStep->pFrom ){ |
| int i; |
| for(i=0; i<pStep->pFrom->nSrc; i++){ |
| sqlite3WalkSelect(pWalker, pStep->pFrom->a[i].pSelect); |
| } |
| } |
| } |
| } |
| |
| /* |
| ** Free the contents of Parse object (*pParse). Do not free the memory |
| ** occupied by the Parse object itself. |
| */ |
| static void renameParseCleanup(Parse *pParse){ |
| sqlite3 *db = pParse->db; |
| Index *pIdx; |
| if( pParse->pVdbe ){ |
| sqlite3VdbeFinalize(pParse->pVdbe); |
| } |
| sqlite3DeleteTable(db, pParse->pNewTable); |
| while( (pIdx = pParse->pNewIndex)!=0 ){ |
| pParse->pNewIndex = pIdx->pNext; |
| sqlite3FreeIndex(db, pIdx); |
| } |
| sqlite3DeleteTrigger(db, pParse->pNewTrigger); |
| sqlite3DbFree(db, pParse->zErrMsg); |
| renameTokenFree(db, pParse->pRename); |
| sqlite3ParseObjectReset(pParse); |
| } |
| |
| /* |
| ** SQL function: |
| ** |
| ** sqlite_rename_column(SQL,TYPE,OBJ,DB,TABLE,COL,NEWNAME,QUOTE,TEMP) |
| ** |
| ** 0. zSql: SQL statement to rewrite |
| ** 1. type: Type of object ("table", "view" etc.) |
| ** 2. object: Name of object |
| ** 3. Database: Database name (e.g. "main") |
| ** 4. Table: Table name |
| ** 5. iCol: Index of column to rename |
| ** 6. zNew: New column name |
| ** 7. bQuote: Non-zero if the new column name should be quoted. |
| ** 8. bTemp: True if zSql comes from temp schema |
| ** |
| ** 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. |
| ** It is only accessible to SQL created using sqlite3NestedParse(). It is |
| ** not reachable from ordinary SQL passed into sqlite3_prepare() unless the |
| ** SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test setting is enabled. |
| */ |
| static void renameColumnFunc( |
| sqlite3_context *context, |
| int NotUsed, |
| sqlite3_value **argv |
| ){ |
| sqlite3 *db = sqlite3_context_db_handle(context); |
| RenameCtx sCtx; |
| const char *zSql = (const char*)sqlite3_value_text(argv[0]); |
| const char *zDb = (const char*)sqlite3_value_text(argv[3]); |
| const char *zTable = (const char*)sqlite3_value_text(argv[4]); |
| int iCol = sqlite3_value_int(argv[5]); |
| const char *zNew = (const char*)sqlite3_value_text(argv[6]); |
| int bQuote = sqlite3_value_int(argv[7]); |
| int bTemp = sqlite3_value_int(argv[8]); |
| const char *zOld; |
| int rc; |
| Parse sParse; |
| Walker sWalker; |
| Index *pIdx; |
| int i; |
| Table *pTab; |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| sqlite3_xauth xAuth = db->xAuth; |
| #endif |
| |
| UNUSED_PARAMETER(NotUsed); |
| if( zSql==0 ) return; |
| if( zTable==0 ) return; |
| if( zNew==0 ) return; |
| if( iCol<0 ) return; |
| sqlite3BtreeEnterAll(db); |
| pTab = sqlite3FindTable(db, zTable, zDb); |
| if( pTab==0 || iCol>=pTab->nCol ){ |
| sqlite3BtreeLeaveAll(db); |
| return; |
| } |
| zOld = pTab->aCol[iCol].zCnName; |
| memset(&sCtx, 0, sizeof(sCtx)); |
| sCtx.iCol = ((iCol==pTab->iPKey) ? -1 : iCol); |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| db->xAuth = 0; |
| #endif |
| rc = renameParseSql(&sParse, zDb, db, zSql, bTemp); |
| |
| /* 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 ){ |
| if( IsView(sParse.pNewTable) ){ |
| Select *pSelect = sParse.pNewTable->u.view.pSelect; |
| pSelect->selFlags &= ~SF_View; |
| sParse.rc = SQLITE_OK; |
| sqlite3SelectPrep(&sParse, pSelect, 0); |
| rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); |
| if( rc==SQLITE_OK ){ |
| sqlite3WalkSelect(&sWalker, pSelect); |
| } |
| if( rc!=SQLITE_OK ) goto renameColumnFunc_done; |
| }else if( IsOrdinaryTable(sParse.pNewTable) ){ |
| /* A regular table */ |
| int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName); |
| FKey *pFKey; |
| sCtx.pTab = sParse.pNewTable; |
| if( bFKOnly==0 ){ |
| if( iCol<sParse.pNewTable->nCol ){ |
| renameTokenFind( |
| &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zCnName |
| ); |
| } |
| 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(pIdx=sParse.pNewIndex; pIdx; pIdx=pIdx->pNext){ |
| sqlite3WalkExprList(&sWalker, pIdx->aColExpr); |
| } |
| #ifndef SQLITE_OMIT_GENERATED_COLUMNS |
| for(i=0; i<sParse.pNewTable->nCol; i++){ |
| Expr *pExpr = sqlite3ColumnExpr(sParse.pNewTable, |
| &sParse.pNewTable->aCol[i]); |
| sqlite3WalkExpr(&sWalker, pExpr); |
| } |
| #endif |
| } |
| |
| assert( IsOrdinaryTable(sParse.pNewTable) ); |
| for(pFKey=sParse.pNewTable->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ |
| for(i=0; i<pFKey->nCol; 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; |
| rc = renameResolveTrigger(&sParse); |
| if( rc!=SQLITE_OK ) goto renameColumnFunc_done; |
| |
| for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ |
| if( pStep->zTarget ){ |
| Table *pTarget = sqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb); |
| if( pTarget==pTab ){ |
| if( pStep->pUpsert ){ |
| ExprList *pUpsertSet = pStep->pUpsert->pUpsertSet; |
| renameColumnElistNames(&sParse, &sCtx, pUpsertSet, zOld); |
| } |
| renameColumnIdlistNames(&sParse, &sCtx, pStep->pIdList, zOld); |
| renameColumnElistNames(&sParse, &sCtx, pStep->pExprList, zOld); |
| } |
| } |
| } |
| |
| |
| /* Find tokens to edit in UPDATE OF clause */ |
| if( sParse.pTriggerTab==pTab ){ |
| renameColumnIdlistNames(&sParse, &sCtx,sParse.pNewTrigger->pColumns,zOld); |
| } |
| |
| /* Find tokens to edit in various expressions and selects */ |
| renameWalkTrigger(&sWalker, sParse.pNewTrigger); |
| } |
| |
| assert( rc==SQLITE_OK ); |
| rc = renameEditSql(context, &sCtx, zSql, zNew, bQuote); |
| |
| renameColumnFunc_done: |
| if( rc!=SQLITE_OK ){ |
| if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ |
| sqlite3_result_value(context, argv[0]); |
| }else if( sParse.zErrMsg ){ |
| renameColumnParseError(context, "", argv[1], argv[2], &sParse); |
| }else{ |
| sqlite3_result_error_code(context, rc); |
| } |
| } |
| |
| renameParseCleanup(&sParse); |
| renameTokenFree(db, sCtx.pList); |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| db->xAuth = xAuth; |
| #endif |
| sqlite3BtreeLeaveAll(db); |
| } |
| |
| /* |
| ** Walker expression callback used by "RENAME TABLE". |
| */ |
| static int renameTableExprCb(Walker *pWalker, Expr *pExpr){ |
| RenameCtx *p = pWalker->u.pRename; |
| if( pExpr->op==TK_COLUMN |
| && ALWAYS(ExprUseYTab(pExpr)) |
| && p->pTab==pExpr->y.pTab |
| ){ |
| renameTokenFind(pWalker->pParse, p, (void*)&pExpr->y.pTab); |
| } |
| return WRC_Continue; |
| } |
| |
| /* |
| ** Walker select callback used by "RENAME TABLE". |
| */ |
| static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ |
| int i; |
| RenameCtx *p = pWalker->u.pRename; |
| SrcList *pSrc = pSelect->pSrc; |
| if( pSelect->selFlags & (SF_View|SF_CopyCte) ){ |
| testcase( pSelect->selFlags & SF_View ); |
| testcase( pSelect->selFlags & SF_CopyCte ); |
| return WRC_Prune; |
| } |
| if( NEVER(pSrc==0) ){ |
| assert( pWalker->pParse->db->mallocFailed ); |
| return WRC_Abort; |
| } |
| for(i=0; i<pSrc->nSrc; i++){ |
| SrcItem *pItem = &pSrc->a[i]; |
| if( pItem->pTab==p->pTab ){ |
| renameTokenFind(pWalker->pParse, p, pItem->zName); |
| } |
| } |
| renameWalkWith(pWalker, pSelect); |
| |
| return WRC_Continue; |
| } |
| |
| |
| /* |
| ** This C function implements an SQL user function that is used by SQL code |
| ** generated by the ALTER TABLE ... RENAME command to modify the definition |
| ** of any foreign key constraints that use the table being renamed as the |
| ** parent table. It is passed three arguments: |
| ** |
| ** 0: The database containing the table being renamed. |
| ** 1. type: Type of object ("table", "view" etc.) |
| ** 2. object: Name of object |
| ** 3: The complete text of the schema statement being modified, |
| ** 4: The old name of the table being renamed, and |
| ** 5: The new name of the table being renamed. |
| ** 6: True if the schema statement comes from the temp db. |
| ** |
| ** It returns the new schema statement. For example: |
| ** |
| ** sqlite_rename_table('main', 'CREATE TABLE t1(a REFERENCES t2)','t2','t3',0) |
| ** -> 'CREATE TABLE t1(a REFERENCES t3)' |
| */ |
| static void renameTableFunc( |
| sqlite3_context *context, |
| int NotUsed, |
| sqlite3_value **argv |
| ){ |
| sqlite3 *db = sqlite3_context_db_handle(context); |
| const char *zDb = (const char*)sqlite3_value_text(argv[0]); |
| const char *zInput = (const char*)sqlite3_value_text(argv[3]); |
| const char *zOld = (const char*)sqlite3_value_text(argv[4]); |
| const char *zNew = (const char*)sqlite3_value_text(argv[5]); |
| int bTemp = sqlite3_value_int(argv[6]); |
| UNUSED_PARAMETER(NotUsed); |
| |
| if( zInput && zOld && zNew ){ |
| Parse sParse; |
| int rc; |
| int bQuote = 1; |
| RenameCtx sCtx; |
| Walker sWalker; |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| sqlite3_xauth xAuth = db->xAuth; |
| db->xAuth = 0; |
| #endif |
| |
| sqlite3BtreeEnterAll(db); |
| |
| memset(&sCtx, 0, sizeof(RenameCtx)); |
| sCtx.pTab = sqlite3FindTable(db, zOld, zDb); |
| memset(&sWalker, 0, sizeof(Walker)); |
| sWalker.pParse = &sParse; |
| sWalker.xExprCallback = renameTableExprCb; |
| sWalker.xSelectCallback = renameTableSelectCb; |
| sWalker.u.pRename = &sCtx; |
| |
| rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); |
| |
| if( rc==SQLITE_OK ){ |
| int isLegacy = (db->flags & SQLITE_LegacyAlter); |
| if( sParse.pNewTable ){ |
| Table *pTab = sParse.pNewTable; |
| |
| if( IsView(pTab) ){ |
| if( isLegacy==0 ){ |
| Select *pSelect = pTab->u.view.pSelect; |
| NameContext sNC; |
| memset(&sNC, 0, sizeof(sNC)); |
| sNC.pParse = &sParse; |
| |
| assert( pSelect->selFlags & SF_View ); |
| pSelect->selFlags &= ~SF_View; |
| sqlite3SelectPrep(&sParse, pTab->u.view.pSelect, &sNC); |
| if( sParse.nErr ){ |
| rc = sParse.rc; |
| }else{ |
| sqlite3WalkSelect(&sWalker, pTab->u.view.pSelect); |
| } |
| } |
| }else{ |
| /* Modify any FK definitions to point to the new table. */ |
| #ifndef SQLITE_OMIT_FOREIGN_KEY |
| if( (isLegacy==0 || (db->flags & SQLITE_ForeignKeys)) |
| && !IsVirtual(pTab) |
| ){ |
| FKey *pFKey; |
| assert( IsOrdinaryTable(pTab) ); |
| for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ |
| if( sqlite3_stricmp(pFKey->zTo, zOld)==0 ){ |
| renameTokenFind(&sParse, &sCtx, (void*)pFKey->zTo); |
| } |
| } |
| } |
| #endif |
| |
| /* If this is the table being altered, fix any table refs in CHECK |
| ** expressions. Also update the name that appears right after the |
| ** "CREATE [VIRTUAL] TABLE" bit. */ |
| if( sqlite3_stricmp(zOld, pTab->zName)==0 ){ |
| sCtx.pTab = pTab; |
| if( isLegacy==0 ){ |
| sqlite3WalkExprList(&sWalker, pTab->pCheck); |
| } |
| renameTokenFind(&sParse, &sCtx, pTab->zName); |
| } |
| } |
| } |
| |
| else if( sParse.pNewIndex ){ |
| renameTokenFind(&sParse, &sCtx, sParse.pNewIndex->zName); |
| if( isLegacy==0 ){ |
| sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); |
| } |
| } |
| |
| #ifndef SQLITE_OMIT_TRIGGER |
| else{ |
| Trigger *pTrigger = sParse.pNewTrigger; |
| TriggerStep *pStep; |
| if( 0==sqlite3_stricmp(sParse.pNewTrigger->table, zOld) |
| && sCtx.pTab->pSchema==pTrigger->pTabSchema |
| ){ |
| renameTokenFind(&sParse, &sCtx, sParse.pNewTrigger->table); |
| } |
| |
| if( isLegacy==0 ){ |
| rc = renameResolveTrigger(&sParse); |
| if( rc==SQLITE_OK ){ |
| renameWalkTrigger(&sWalker, pTrigger); |
| for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ |
| if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ |
| renameTokenFind(&sParse, &sCtx, pStep->zTarget); |
| } |
| if( pStep->pFrom ){ |
| int i; |
| for(i=0; i<pStep->pFrom->nSrc; i++){ |
| SrcItem *pItem = &pStep->pFrom->a[i]; |
| if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ |
| renameTokenFind(&sParse, &sCtx, pItem->zName); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| #endif |
| } |
| |
| if( rc==SQLITE_OK ){ |
| rc = renameEditSql(context, &sCtx, zInput, zNew, bQuote); |
| } |
| if( rc!=SQLITE_OK ){ |
| if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ |
| sqlite3_result_value(context, argv[3]); |
| }else if( sParse.zErrMsg ){ |
| renameColumnParseError(context, "", argv[1], argv[2], &sParse); |
| }else{ |
| sqlite3_result_error_code(context, rc); |
| } |
| } |
| |
| renameParseCleanup(&sParse); |
| renameTokenFree(db, sCtx.pList); |
| sqlite3BtreeLeaveAll(db); |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| db->xAuth = xAuth; |
| #endif |
| } |
| |
| return; |
| } |
| |
| static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){ |
| if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){ |
| renameTokenFind(pWalker->pParse, pWalker->u.pRename, (const void*)pExpr); |
| } |
| return WRC_Continue; |
| } |
| |
| /* SQL function: sqlite_rename_quotefix(DB,SQL) |
| ** |
| ** Rewrite the DDL statement "SQL" so that any string literals that use |
| ** double-quotes use single quotes instead. |
| ** |
| ** Two arguments must be passed: |
| ** |
| ** 0: Database name ("main", "temp" etc.). |
| ** 1: SQL statement to edit. |
| ** |
| ** The returned value is the modified SQL statement. For example, given |
| ** the database schema: |
| ** |
| ** CREATE TABLE t1(a, b, c); |
| ** |
| ** SELECT sqlite_rename_quotefix('main', |
| ** 'CREATE VIEW v1 AS SELECT "a", "string" FROM t1' |
| ** ); |
| ** |
| ** returns the string: |
| ** |
| ** CREATE VIEW v1 AS SELECT "a", 'string' FROM t1 |
| ** |
| ** If there is a error in the input SQL, then raise an error, except |
| ** if PRAGMA writable_schema=ON, then just return the input string |
| ** unmodified following an error. |
| */ |
| static void renameQuotefixFunc( |
| sqlite3_context *context, |
| int NotUsed, |
| sqlite3_value **argv |
| ){ |
| sqlite3 *db = sqlite3_context_db_handle(context); |
| char const *zDb = (const char*)sqlite3_value_text(argv[0]); |
| char const *zInput = (const char*)sqlite3_value_text(argv[1]); |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| sqlite3_xauth xAuth = db->xAuth; |
| db->xAuth = 0; |
| #endif |
| |
| sqlite3BtreeEnterAll(db); |
| |
| UNUSED_PARAMETER(NotUsed); |
| if( zDb && zInput ){ |
| int rc; |
| Parse sParse; |
| rc = renameParseSql(&sParse, zDb, db, zInput, 0); |
| |
| if( rc==SQLITE_OK ){ |
| RenameCtx sCtx; |
| Walker sWalker; |
| |
| /* Walker to find tokens that need to be replaced. */ |
| memset(&sCtx, 0, sizeof(RenameCtx)); |
| memset(&sWalker, 0, sizeof(Walker)); |
| sWalker.pParse = &sParse; |
| sWalker.xExprCallback = renameQuotefixExprCb; |
| sWalker.xSelectCallback = renameColumnSelectCb; |
| sWalker.u.pRename = &sCtx; |
| |
| if( sParse.pNewTable ){ |
| if( IsView(sParse.pNewTable) ){ |
| Select *pSelect = sParse.pNewTable->u.view.pSelect; |
| pSelect->selFlags &= ~SF_View; |
| sParse.rc = SQLITE_OK; |
| sqlite3SelectPrep(&sParse, pSelect, 0); |
| rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); |
| if( rc==SQLITE_OK ){ |
| sqlite3WalkSelect(&sWalker, pSelect); |
| } |
| }else{ |
| int i; |
| sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); |
| #ifndef SQLITE_OMIT_GENERATED_COLUMNS |
| for(i=0; i<sParse.pNewTable->nCol; i++){ |
| sqlite3WalkExpr(&sWalker, |
| sqlite3ColumnExpr(sParse.pNewTable, |
| &sParse.pNewTable->aCol[i])); |
| } |
| #endif /* SQLITE_OMIT_GENERATED_COLUMNS */ |
| } |
| }else if( sParse.pNewIndex ){ |
| sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); |
| sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); |
| }else{ |
| #ifndef SQLITE_OMIT_TRIGGER |
| rc = renameResolveTrigger(&sParse); |
| if( rc==SQLITE_OK ){ |
| renameWalkTrigger(&sWalker, sParse.pNewTrigger); |
| } |
| #endif /* SQLITE_OMIT_TRIGGER */ |
| } |
| |
| if( rc==SQLITE_OK ){ |
| rc = renameEditSql(context, &sCtx, zInput, 0, 0); |
| } |
| renameTokenFree(db, sCtx.pList); |
| } |
| if( rc!=SQLITE_OK ){ |
| if( sqlite3WritableSchema(db) && rc==SQLITE_ERROR ){ |
| sqlite3_result_value(context, argv[1]); |
| }else{ |
| sqlite3_result_error_code(context, rc); |
| } |
| } |
| renameParseCleanup(&sParse); |
| } |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| db->xAuth = xAuth; |
| #endif |
| |
| sqlite3BtreeLeaveAll(db); |
| } |
| |
| /* Function: sqlite_rename_test(DB,SQL,TYPE,NAME,ISTEMP,WHEN,DQS) |
| ** |
| ** An SQL user function that checks that there are no parse or symbol |
| ** resolution problems in a CREATE TRIGGER|TABLE|VIEW|INDEX statement. |
| ** After an ALTER TABLE .. RENAME operation is performed and the schema |
| ** reloaded, this function is called on each SQL statement in the schema |
| ** to ensure that it is still usable. |
| ** |
| ** 0: Database name ("main", "temp" etc.). |
| ** 1: SQL statement. |
| ** 2: Object type ("view", "table", "trigger" or "index"). |
| ** 3: Object name. |
| ** 4: True if object is from temp schema. |
| ** 5: "when" part of error message. |
| ** 6: True to disable the DQS quirk when parsing SQL. |
| ** |
| ** The return value is computed as follows: |
| ** |
| ** A. If an error is seen and not in PRAGMA writable_schema=ON mode, |
| ** then raise the error. |
| ** B. Else if a trigger is created and the the table that the trigger is |
| ** attached to is in database zDb, then return 1. |
| ** C. Otherwise return NULL. |
| */ |
| static void renameTableTest( |
| sqlite3_context *context, |
| int NotUsed, |
| sqlite3_value **argv |
| ){ |
| sqlite3 *db = sqlite3_context_db_handle(context); |
| char const *zDb = (const char*)sqlite3_value_text(argv[0]); |
| char const *zInput = (const char*)sqlite3_value_text(argv[1]); |
| int bTemp = sqlite3_value_int(argv[4]); |
| int isLegacy = (db->flags & SQLITE_LegacyAlter); |
| char const *zWhen = (const char*)sqlite3_value_text(argv[5]); |
| int bNoDQS = sqlite3_value_int(argv[6]); |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| sqlite3_xauth xAuth = db->xAuth; |
| db->xAuth = 0; |
| #endif |
| |
| UNUSED_PARAMETER(NotUsed); |
| |
| if( zDb && zInput ){ |
| int rc; |
| Parse sParse; |
| int flags = db->flags; |
| if( bNoDQS ) db->flags &= ~(SQLITE_DqsDML|SQLITE_DqsDDL); |
| rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); |
| db->flags |= (flags & (SQLITE_DqsDML|SQLITE_DqsDDL)); |
| if( rc==SQLITE_OK ){ |
| if( isLegacy==0 && sParse.pNewTable && IsView(sParse.pNewTable) ){ |
| NameContext sNC; |
| memset(&sNC, 0, sizeof(sNC)); |
| sNC.pParse = &sParse; |
| sqlite3SelectPrep(&sParse, sParse.pNewTable->u.view.pSelect, &sNC); |
| if( sParse.nErr ) rc = sParse.rc; |
| } |
| |
| else if( sParse.pNewTrigger ){ |
| if( isLegacy==0 ){ |
| rc = renameResolveTrigger(&sParse); |
| } |
| if( rc==SQLITE_OK ){ |
| int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema); |
| int i2 = sqlite3FindDbName(db, zDb); |
| if( i1==i2 ){ |
| /* Handle output case B */ |
| sqlite3_result_int(context, 1); |
| } |
| } |
| } |
| } |
| |
| if( rc!=SQLITE_OK && zWhen && !sqlite3WritableSchema(db) ){ |
| /* Output case A */ |
| renameColumnParseError(context, zWhen, argv[2], argv[3],&sParse); |
| } |
| renameParseCleanup(&sParse); |
| } |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| db->xAuth = xAuth; |
| #endif |
| } |
| |
| /* |
| ** The implementation of internal UDF sqlite_drop_column(). |
| ** |
| ** Arguments: |
| ** |
| ** argv[0]: An integer - the index of the schema containing the table |
| ** argv[1]: CREATE TABLE statement to modify. |
| ** argv[2]: An integer - the index of the column to remove. |
| ** |
| ** The value returned is a string containing the CREATE TABLE statement |
| ** with column argv[2] removed. |
| */ |
| static void dropColumnFunc( |
| sqlite3_context *context, |
| int NotUsed, |
| sqlite3_value **argv |
| ){ |
| sqlite3 *db = sqlite3_context_db_handle(context); |
| int iSchema = sqlite3_value_int(argv[0]); |
| const char *zSql = (const char*)sqlite3_value_text(argv[1]); |
| int iCol = sqlite3_value_int(argv[2]); |
| const char *zDb = db->aDb[iSchema].zDbSName; |
| int rc; |
| Parse sParse; |
| RenameToken *pCol; |
| Table *pTab; |
| const char *zEnd; |
| char *zNew = 0; |
| |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| sqlite3_xauth xAuth = db->xAuth; |
| db->xAuth = 0; |
| #endif |
| |
| UNUSED_PARAMETER(NotUsed); |
| rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1); |
| if( rc!=SQLITE_OK ) goto drop_column_done; |
| pTab = sParse.pNewTable; |
| if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){ |
| /* This can happen if the sqlite_schema table is corrupt */ |
| rc = SQLITE_CORRUPT_BKPT; |
| goto drop_column_done; |
| } |
| |
| pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); |
| if( iCol<pTab->nCol-1 ){ |
| RenameToken *pEnd; |
| pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); |
| zEnd = (const char*)pEnd->t.z; |
| }else{ |
| assert( IsOrdinaryTable(pTab) ); |
| zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; |
| while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; |
| } |
| |
| zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd); |
| sqlite3_result_text(context, zNew, -1, SQLITE_TRANSIENT); |
| sqlite3_free(zNew); |
| |
| drop_column_done: |
| renameParseCleanup(&sParse); |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| db->xAuth = xAuth; |
| #endif |
| if( rc!=SQLITE_OK ){ |
| sqlite3_result_error_code(context, rc); |
| } |
| } |
| |
| /* |
| ** This function is called by the parser upon parsing an |
| ** |
| ** ALTER TABLE pSrc DROP COLUMN pName |
| ** |
| ** statement. Argument pSrc contains the possibly qualified name of the |
| ** table being edited, and token pName the name of the column to drop. |
| */ |
| void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ |
| sqlite3 *db = pParse->db; /* Database handle */ |
| Table *pTab; /* Table to modify */ |
| int iDb; /* Index of db containing pTab in aDb[] */ |
| const char *zDb; /* Database containing pTab ("main" etc.) */ |
| char *zCol = 0; /* Name of column to drop */ |
| int iCol; /* Index of column zCol in pTab->aCol[] */ |
| |
| /* Look up the table being altered. */ |
| assert( pParse->pNewTable==0 ); |
| assert( sqlite3BtreeHoldsAllMutexes(db) ); |
| if( NEVER(db->mallocFailed) ) goto exit_drop_column; |
| pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); |
| if( !pTab ) goto exit_drop_column; |
| |
| /* Make sure this is not an attempt to ALTER a view, virtual table or |
| ** system table. */ |
| if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_drop_column; |
| if( SQLITE_OK!=isRealTable(pParse, pTab, 1) ) goto exit_drop_column; |
| |
| /* Find the index of the column being dropped. */ |
| zCol = sqlite3NameFromToken(db, pName); |
| if( zCol==0 ){ |
| assert( db->mallocFailed ); |
| goto exit_drop_column; |
| } |
| iCol = sqlite3ColumnIndex(pTab, zCol); |
| if( iCol<0 ){ |
| sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pName); |
| goto exit_drop_column; |
| } |
| |
| /* Do not allow the user to drop a PRIMARY KEY column or a column |
| ** constrained by a UNIQUE constraint. */ |
| if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){ |
| sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"", |
| (pTab->aCol[iCol].colFlags&COLFLAG_PRIMKEY) ? "PRIMARY KEY" : "UNIQUE", |
| zCol |
| ); |
| goto exit_drop_column; |
| } |
| |
| /* Do not allow the number of columns to go to zero */ |
| if( pTab->nCol<=1 ){ |
| sqlite3ErrorMsg(pParse, "cannot drop column \"%s\": no other columns exist",zCol); |
| goto exit_drop_column; |
| } |
| |
| /* Edit the sqlite_schema table */ |
| iDb = sqlite3SchemaToIndex(db, pTab->pSchema); |
| assert( iDb>=0 ); |
| zDb = db->aDb[iDb].zDbSName; |
| #ifndef SQLITE_OMIT_AUTHORIZATION |
| /* Invoke the authorization callback. */ |
| if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ |
| goto exit_drop_column; |
| } |
| #endif |
| renameTestSchema(pParse, zDb, iDb==1, "", 0); |
| renameFixQuotes(pParse, zDb, iDb==1); |
| sqlite3NestedParse(pParse, |
| "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " |
| "sql = sqlite_drop_column(%d, sql, %d) " |
| "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)" |
| , zDb, iDb, iCol, pTab->zName |
| ); |
| |
| /* Drop and reload the database schema. */ |
| renameReloadSchema(pParse, iDb, INITFLAG_AlterDrop); |
| renameTestSchema(pParse, zDb, iDb==1, "after drop column", 1); |
| |
| /* Edit rows of table on disk */ |
| if( pParse->nErr==0 && (pTab->aCol[iCol].colFlags & COLFLAG_VIRTUAL)==0 ){ |
| int i; |
| int addr; |
| int reg; |
| int regRec; |
| Index *pPk = 0; |
| int nField = 0; /* Number of non-virtual columns after drop */ |
| int iCur; |
| Vdbe *v = sqlite3GetVdbe(pParse); |
| iCur = pParse->nTab++; |
| sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); |
| addr = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); |
| reg = ++pParse->nMem; |
| if( HasRowid(pTab) ){ |
| sqlite3VdbeAddOp2(v, OP_Rowid, iCur, reg); |
| pParse->nMem += pTab->nCol; |
| }else{ |
| pPk = sqlite3PrimaryKeyIndex(pTab); |
| pParse->nMem += pPk->nColumn; |
| for(i=0; i<pPk->nKeyCol; i++){ |
| sqlite3VdbeAddOp3(v, OP_Column, iCur, i, reg+i+1); |
| } |
| nField = pPk->nKeyCol; |
| } |
| regRec = ++pParse->nMem; |
| for(i=0; i<pTab->nCol; i++){ |
| if( i!=iCol && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ |
| int regOut; |
| if( pPk ){ |
| int iPos = sqlite3TableColumnToIndex(pPk, i); |
| int iColPos = sqlite3TableColumnToIndex(pPk, iCol); |
| if( iPos<pPk->nKeyCol ) continue; |
| regOut = reg+1+iPos-(iPos>iColPos); |
| }else{ |
| regOut = reg+1+nField; |
| } |
| if( i==pTab->iPKey ){ |
| sqlite3VdbeAddOp2(v, OP_Null, 0, regOut); |
| }else{ |
| sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); |
| } |
| nField++; |
| } |
| } |
| if( nField==0 ){ |
| /* dbsqlfuzz 5f09e7bcc78b4954d06bf9f2400d7715f48d1fef */ |
| pParse->nMem++; |
| sqlite3VdbeAddOp2(v, OP_Null, 0, reg+1); |
| nField = 1; |
| } |
| sqlite3VdbeAddOp3(v, OP_MakeRecord, reg+1, nField, regRec); |
| if( pPk ){ |
| sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iCur, regRec, reg+1, pPk->nKeyCol); |
| }else{ |
| sqlite3VdbeAddOp3(v, OP_Insert, iCur, regRec, reg); |
| } |
| sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); |
| |
| sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+1); VdbeCoverage(v); |
| sqlite3VdbeJumpHere(v, addr); |
| } |
| |
| exit_drop_column: |
| sqlite3DbFree(db, zCol); |
| sqlite3SrcListDelete(db, pSrc); |
| } |
| |
| /* |
| ** Register built-in functions used to help implement ALTER TABLE |
| */ |
| void sqlite3AlterFunctions(void){ |
| static FuncDef aAlterTableFuncs[] = { |
| INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc), |
| INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc), |
| INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), |
| INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), |
| INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), |
| }; |
| sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); |
| } |
| #endif /* SQLITE_ALTER_TABLE */ |