Add the ALTER TABLE ... ADD COLUMN command. (CVS 2393)
FossilOrigin-Name: 94185dd4f7e2e941c74a521488d1212a75927218
diff --git a/src/alter.c b/src/alter.c
index 2ed4a7a..c01219f 100644
--- a/src/alter.c
+++ b/src/alter.c
@@ -12,9 +12,10 @@
** This file contains C code routines that used to generate VDBE code
** that implements the ALTER TABLE command.
**
-** $Id: alter.c,v 1.2 2005/02/15 21:36:18 drh Exp $
+** $Id: alter.c,v 1.3 2005/03/17 05:03:39 danielk1977 Exp $
*/
#include "sqliteInt.h"
+#include <ctype.h>
/*
** The code in this file only exists if we are not omitting the
@@ -167,6 +168,78 @@
}
/*
+** Generate the text of a WHERE expression which can be used to select all
+** temporary triggers on table pTab from the sqlite_temp_master table. If
+** table pTab has no temporary triggers, or is itself stored in the
+** temporary database, NULL is returned.
+*/
+static char *whereTempTriggers(Parse *pParse, Table *pTab){
+ Trigger *pTrig;
+ char *zWhere = 0;
+ char *tmp = 0;
+ if( pTab->iDb!=1 ){
+ for( pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext ){
+ if( pTrig->iDb==1 ){
+ if( !zWhere ){
+ zWhere = sqlite3MPrintf("name=%Q", pTrig->name);
+ }else{
+ tmp = zWhere;
+ zWhere = sqlite3MPrintf("%s OR name=%Q", zWhere, pTrig->name);
+ sqliteFree(tmp);
+ }
+ }
+ }
+ }
+ return zWhere;
+}
+
+/*
+** Generate code to drop and reload the internal representation of table
+** pTab from the database, including triggers and temporary triggers.
+** Argument zName is the name of the table in the database schema at
+** the time the generated code is executed. This can be different from
+** pTab->zName if this function is being called to code part of an
+** "ALTER TABLE RENAME TO" statement.
+*/
+static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){
+ Vdbe *v;
+ char *zWhere;
+ int iDb;
+#ifndef SQLITE_OMIT_TRIGGER
+ Trigger *pTrig;
+#endif
+
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) return;
+ iDb = pTab->iDb;
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* Drop any table triggers from the internal schema. */
+ for(pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext){
+ assert( pTrig->iDb==iDb || pTrig->iDb==1 );
+ sqlite3VdbeOp3(v, OP_DropTrigger, pTrig->iDb, 0, pTrig->name, 0);
+ }
+#endif
+
+ /* Drop the table and index from the internal schema */
+ sqlite3VdbeOp3(v, OP_DropTable, iDb, 0, pTab->zName, 0);
+
+ /* Reload the table, index and permanent trigger schemas. */
+ zWhere = sqlite3MPrintf("tbl_name=%Q", zName);
+ if( !zWhere ) return;
+ sqlite3VdbeOp3(v, OP_ParseSchema, iDb, 0, zWhere, P3_DYNAMIC);
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* Now, if the table is not stored in the temp database, reload any temp
+ ** triggers. Don't use IN(...) in case SQLITE_OMIT_SUBQUERY is defined.
+ */
+ if( (zWhere=whereTempTriggers(pParse, pTab)) ){
+ sqlite3VdbeOp3(v, OP_ParseSchema, 1, 0, zWhere, P3_DYNAMIC);
+ }
+#endif
+}
+
+/*
** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
** command.
*/
@@ -179,11 +252,10 @@
char *zDb; /* Name of database iDb */
Table *pTab; /* Table being renamed */
char *zName = 0; /* NULL-terminated version of pName */
- char *zWhere = 0; /* Where clause of schema elements to reparse */
sqlite3 *db = pParse->db; /* Database connection */
Vdbe *v;
#ifndef SQLITE_OMIT_TRIGGER
- char *zTempTrig = 0; /* Where clause to locate temp triggers */
+ char *zWhere = 0; /* Where clause to locate temp triggers */
#endif
assert( pSrc->nSrc==1 );
@@ -255,7 +327,7 @@
"(type='table' OR type='index' OR type='trigger');",
zDb, SCHEMA_TABLE(iDb), zName, zName, zName,
#ifndef SQLITE_OMIT_TRIGGER
-zName,
+ zName,
#endif
zName, strlen(pTab->zName), pTab->zName
);
@@ -276,59 +348,204 @@
** table. Don't do this if the table being ALTERed is itself located in
** the temp database.
*/
- if( iDb!=1 ){
- Trigger *pTrig;
- char *tmp = 0;
- for( pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext ){
- if( pTrig->iDb==1 ){
- if( !zTempTrig ){
- zTempTrig =
- sqlite3MPrintf("type = 'trigger' AND (name=%Q", pTrig->name);
- }else{
- tmp = zTempTrig;
- zTempTrig = sqlite3MPrintf("%s OR name=%Q", zTempTrig, pTrig->name);
- sqliteFree(tmp);
- }
- }
- }
- if( zTempTrig ){
- tmp = zTempTrig;
- zTempTrig = sqlite3MPrintf("%s)", zTempTrig);
- sqliteFree(tmp);
- sqlite3NestedParse(pParse,
- "UPDATE sqlite_temp_master SET "
- "sql = sqlite_rename_trigger(sql, %Q), "
- "tbl_name = %Q "
- "WHERE %s;", zName, zName, zTempTrig);
- }
+ if( (zWhere=whereTempTriggers(pParse, pTab)) ){
+ sqlite3NestedParse(pParse,
+ "UPDATE sqlite_temp_master SET "
+ "sql = sqlite_rename_trigger(sql, %Q), "
+ "tbl_name = %Q "
+ "WHERE %s;", zName, zName, zWhere);
+ sqliteFree(zWhere);
}
#endif
- /* Drop the elements of the in-memory schema that refered to the table
- ** renamed and load the new versions from the database.
- */
- if( pParse->nErr==0 ){
-#ifndef SQLITE_OMIT_TRIGGER
- Trigger *pTrig;
- for( pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext ){
- assert( pTrig->iDb==iDb || pTrig->iDb==1 );
- sqlite3VdbeOp3(v, OP_DropTrigger, pTrig->iDb, 0, pTrig->name, 0);
- }
-#endif
- sqlite3VdbeOp3(v, OP_DropTable, iDb, 0, pTab->zName, 0);
- zWhere = sqlite3MPrintf("tbl_name=%Q", zName);
- sqlite3VdbeOp3(v, OP_ParseSchema, iDb, 0, zWhere, P3_DYNAMIC);
-#ifndef SQLITE_OMIT_TRIGGER
- if( zTempTrig ){
- sqlite3VdbeOp3(v, OP_ParseSchema, 1, 0, zTempTrig, P3_DYNAMIC);
- }
- }else{
- sqliteFree(zTempTrig);
-#endif
- }
+ /* Drop and reload the internal table schema. */
+ reloadTableSchema(pParse, pTab, zName);
exit_rename_table:
sqlite3SrcListDelete(pSrc);
sqliteFree(zName);
}
+
+
+/*
+** 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 */
+ Vdbe *v;
+
+ if( pParse->nErr ) return;
+ pNew = pParse->pNewTable;
+ assert( pNew );
+
+ iDb = pNew->iDb;
+ zDb = pParse->db->aDb[iDb].zName;
+ zTab = pNew->zName;
+ pCol = &pNew->aCol[pNew->nCol-1];
+ pDflt = pCol->pDflt;
+ pTab = sqlite3FindTable(pParse->db, zTab, zDb);
+ assert( pTab );
+
+ /* 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.
+ */
+ if( pDflt && pDflt->op==TK_NULL ){
+ pDflt = 0;
+ }
+
+ /* 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->isPrimKey ){
+ sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column");
+ return;
+ }
+ if( pNew->pIndex ){
+ sqlite3ErrorMsg(pParse, "Cannot add a UNIQUE column");
+ return;
+ }
+ if( pCol->notNull && !pDflt ){
+ sqlite3ErrorMsg(pParse,
+ "Cannot add a NOT NULL column with default value NULL");
+ return;
+ }
+
+ /* Ensure the default expression is something that sqlite3ValueFromExpr()
+ ** can handle (i.e. not CURRENT_TIME etc.)
+ */
+ if( pDflt ){
+ sqlite3_value *pVal;
+ if( sqlite3ValueFromExpr(pDflt, SQLITE_UTF8, SQLITE_AFF_NONE, &pVal) ){
+ /* malloc() has failed */
+ return;
+ }
+ if( !pVal ){
+ sqlite3ErrorMsg(pParse, "Cannot add a column with non-constant default");
+ return;
+ }
+ sqlite3ValueFree(pVal);
+ }
+
+ /* Modify the CREATE TABLE statement. */
+ zCol = sqliteStrNDup(pColDef->z, pColDef->n);
+ if( zCol ){
+ char *zEnd = &zCol[pColDef->n-1];
+ while( zEnd>zCol && *zEnd==';' || isspace(*(unsigned char *)zEnd) ){
+ *zEnd-- = '\0';
+ }
+ sqlite3NestedParse(pParse,
+ "UPDATE %Q.%s SET "
+ "sql = substr(sql,0,%d) || ', ' || %Q || substr(sql,%d,length(sql)) "
+ "WHERE type = 'table' AND name = %Q",
+ zDb, SCHEMA_TABLE(iDb), pNew->addColOffset, zCol, pNew->addColOffset+1,
+ zTab
+ );
+ sqliteFree(zCol);
+ }
+
+ /* If the default value of the new column is NULL, then set the file
+ ** format to 2. If the default value of the new column is not NULL,
+ ** the file format becomes 3.
+ */
+ if( (v=sqlite3GetVdbe(pParse)) ){
+ int f = (pDflt?3:2);
+
+ /* Only set the file format to $f if it is currently less than $f. */
+ sqlite3VdbeAddOp(v, OP_ReadCookie, iDb, 1);
+ sqlite3VdbeAddOp(v, OP_Integer, f, 0);
+ sqlite3VdbeAddOp(v, OP_Ge, 0, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp(v, OP_Integer, f, 0);
+ sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 1);
+ }
+
+ /* Reload the schema of the modified table. */
+ reloadTableSchema(pParse, pTab, pTab->zName);
+}
+
+
+/*
+** 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;
+ Vdbe *v;
+ int iDb;
+ int i;
+ int nAlloc;
+
+ /* Look up the table being altered. */
+ assert( !pParse->pNewTable );
+ pTab = sqlite3LocateTable(pParse, pSrc->a[0].zName, pSrc->a[0].zDatabase);
+ if( !pTab ) goto exit_begin_add_column;
+
+ /* Make sure this is not an attempt to ALTER a view. */
+ if( pTab->pSelect ){
+ sqlite3ErrorMsg(pParse, "Cannot add a column to a view");
+ goto exit_begin_add_column;
+ }
+
+ assert( pTab->addColOffset>0 );
+ iDb = pTab->iDb;
+
+ /* Put a copy of the Table struct in Parse.pNewTable for the
+ ** sqlite3AddColumn() function and friends to modify.
+ */
+ pNew = (Table *)sqliteMalloc(sizeof(Table));
+ if( !pNew ) goto exit_begin_add_column;
+ pParse->pNewTable = pNew;
+ pNew->nCol = pTab->nCol;
+ nAlloc = ((pNew->nCol)/8)+8;
+ pNew->aCol = (Column *)sqliteMalloc(sizeof(Column)*nAlloc);
+ pNew->zName = sqliteStrDup(pTab->zName);
+ if( !pNew->aCol || !pNew->zName ){
+ 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->zName = sqliteStrDup(pCol->zName);
+ pCol->zType = 0;
+ pCol->pDflt = 0;
+ }
+ pNew->iDb = iDb;
+ pNew->addColOffset = pTab->addColOffset;
+
+ /* Begin a transaction and increment the schema cookie. */
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) goto exit_begin_add_column;
+ sqlite3ChangeCookie(pParse->db, v, iDb);
+
+exit_begin_add_column:
+ sqlite3SrcListDelete(pSrc);
+ return;
+}
#endif /* SQLITE_ALTER_TABLE */
+
diff --git a/src/build.c b/src/build.c
index 15fc2b6..d1f2fef 100644
--- a/src/build.c
+++ b/src/build.c
@@ -22,7 +22,7 @@
** COMMIT
** ROLLBACK
**
-** $Id: build.c,v 1.313 2005/03/16 12:15:21 danielk1977 Exp $
+** $Id: build.c,v 1.314 2005/03/17 05:03:39 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@@ -1413,7 +1413,12 @@
** "CREATE TABLE ... AS SELECT ..." statement. The column names of
** the new table will match the result set of the SELECT.
*/
-void sqlite3EndTable(Parse *pParse, Token *pEnd, Select *pSelect){
+void sqlite3EndTable(
+ Parse *pParse, /* Parse context */
+ Token *pCons, /* The ',' token after the last column defn. */
+ Token *pEnd, /* The final ')' token in the CREATE TABLE */
+ Select *pSelect /* Select from a "CREATE ... AS SELECT" */
+){
Table *p;
sqlite3 *db = pParse->db;
@@ -1567,6 +1572,14 @@
pParse->pNewTable = 0;
db->nTable++;
db->flags |= SQLITE_InternChanges;
+
+#ifndef SQLITE_OMIT_ALTERTABLE
+ if( !p->pSelect ){
+ assert( !pSelect && pCons && pEnd );
+ if( pCons->z==0 ) pCons = pEnd;
+ p->addColOffset = 13 + (pCons->z - pParse->sNameToken.z);
+ }
+#endif
}
}
@@ -1629,7 +1642,7 @@
sEnd.n = 1;
/* Use sqlite3EndTable() to add the view to the SQLITE_MASTER table */
- sqlite3EndTable(pParse, &sEnd, 0);
+ sqlite3EndTable(pParse, 0, &sEnd, 0);
return;
}
#endif /* SQLITE_OMIT_VIEW */
diff --git a/src/parse.y b/src/parse.y
index 2157f15..ad7fb2f 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -14,7 +14,7 @@
** the parser. Lemon will also generate a header file containing
** numeric codes for all of the tokens.
**
-** @(#) $Id: parse.y,v 1.168 2005/03/16 12:15:21 danielk1977 Exp $
+** @(#) $Id: parse.y,v 1.169 2005/03/17 05:03:40 danielk1977 Exp $
*/
%token_prefix TK_
%token_type {Token}
@@ -114,11 +114,11 @@
%type temp {int}
temp(A) ::= TEMP. {A = 1;}
temp(A) ::= . {A = 0;}
-create_table_args ::= LP columnlist conslist_opt RP(X). {
- sqlite3EndTable(pParse,&X,0);
+create_table_args ::= LP columnlist conslist_opt(X) RP(Y). {
+ sqlite3EndTable(pParse,&X,&Y,0);
}
create_table_args ::= AS select(S). {
- sqlite3EndTable(pParse,0,S);
+ sqlite3EndTable(pParse,0,0,S);
sqlite3SelectDelete(S);
}
columnlist ::= columnlist COMMA column.
@@ -128,8 +128,15 @@
// column. The type is always just "text". But the code will accept
// an elaborate typename. Perhaps someday we'll do something with it.
//
-column ::= columnid type carglist.
-columnid ::= nm(X). {sqlite3AddColumn(pParse,&X);}
+column(A) ::= columnid(X) type carglist. {
+ A.z = X.z;
+ A.n = (pParse->sLastToken.z-X.z) + pParse->sLastToken.n;
+}
+columnid(A) ::= nm(X). {
+ sqlite3AddColumn(pParse,&X);
+ A = X;
+}
+
// An IDENTIFIER can be a generic identifier, or one of several
// keywords. Any non-standard keyword can also be an identifier.
@@ -223,7 +230,7 @@
ccons ::= PRIMARY KEY sortorder onconf(R) autoinc(I).
{sqlite3AddPrimaryKey(pParse,0,R,I);}
ccons ::= UNIQUE onconf(R). {sqlite3CreateIndex(pParse,0,0,0,0,R,0,0);}
-ccons ::= CHECK LP expr RP onconf.
+ccons ::= CHECK LP expr(X) RP onconf. {sqlite3ExprDelete(X);}
ccons ::= REFERENCES nm(T) idxlist_opt(TA) refargs(R).
{sqlite3CreateForeignKey(pParse,0,&T,TA,R);}
ccons ::= defer_subclause(D). {sqlite3DeferForeignKey(pParse,D);}
@@ -263,8 +270,8 @@
// For the time being, the only constraint we care about is the primary
// key and UNIQUE. Both create indices.
//
-conslist_opt ::= .
-conslist_opt ::= COMMA conslist.
+conslist_opt(A) ::= . {A.n = 0; A.z = 0;}
+conslist_opt(A) ::= COMMA(X) conslist. {A = X;}
conslist ::= conslist COMMA tcons.
conslist ::= conslist tcons.
conslist ::= tcons.
@@ -975,4 +982,12 @@
cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
sqlite3AlterRenameTable(pParse,X,&Z);
}
+cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column(Y). {
+ sqlite3AlterFinishAddColumn(pParse, &Y);
+}
+add_column_fullname ::= fullname(X). {
+ sqlite3AlterBeginAddColumn(pParse, X);
+}
+kwcolumn_opt ::= .
+kwcolumn_opt ::= COLUMNKW.
%endif
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 174c0d2..f5e79a9 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
-** @(#) $Id: sqliteInt.h,v 1.372 2005/03/09 12:26:51 danielk1977 Exp $
+** @(#) $Id: sqliteInt.h,v 1.373 2005/03/17 05:03:40 danielk1977 Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
@@ -611,6 +611,9 @@
Trigger *pTrigger; /* List of SQL triggers on this table */
FKey *pFKey; /* Linked list of all foreign keys in this table */
char *zColAff; /* String defining the affinity of each column */
+#ifndef SQLITE_OMIT_ALTERTABLE
+ int addColOffset; /* Offset in CREATE TABLE statement to add a new column */
+#endif
};
/*
@@ -1367,7 +1370,7 @@
void sqlite3AddColumnType(Parse*,Token*,Token*);
void sqlite3AddDefaultValue(Parse*,Expr*);
void sqlite3AddCollateType(Parse*, const char*, int);
-void sqlite3EndTable(Parse*,Token*,Select*);
+void sqlite3EndTable(Parse*,Token*,Token*,Select*);
#ifndef SQLITE_OMIT_VIEW
void sqlite3CreateView(Parse*,Token*,Token*,Token*,Select*,int);
@@ -1554,5 +1557,7 @@
void sqlite3CodeSubselect(Parse *, Expr *);
int sqlite3SelectResolve(Parse *, Select *, NameContext *);
void sqlite3ColumnDefault(Vdbe *, Table *, int);
+void sqlite3AlterFinishAddColumn(Parse *, Token *);
+void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
#endif