diff --git a/manifest b/manifest
index f3b2d09a56..9cc1bfddd0 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Version\s3.1.6\s(CVS\s2392)
-D 2005-03-17T04:01:24
+C Add\sthe\sALTER\sTABLE\s...\sADD\sCOLUMN\scommand.\s(CVS\s2393)
+D 2005-03-17T05:03:39
F Makefile.in 5c00d0037104de2a50ac7647a5f12769795957a3
F Makefile.linux-gcc 06be33b2a9ad4f005a5f42b22c4a19dab3cbb5c7
F README a01693e454a00cc117967e3f9fdab2d4d52e9bc1
@@ -27,12 +27,12 @@ F sqlite.pc.in 30552343140c53304c2a658c080fbe810cd09ca2
F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc
F sqlite3.def dbaeb20c153e1d366e8f421b55a573f5dfc00863
F sqlite3.pc.in 985b9bf34192a549d7d370e0f0b6b34a4f61369a
-F src/alter.c 6dab3d91aa4bf5c24e874145a2a547070c8c1883
+F src/alter.c 85a23fc9a565c0e065d05d1d89744fcc6dee1c65
F src/attach.c 3615dbe960cbee4aa5ea300b8a213dad36527b0f
F src/auth.c 18c5a0befe20f3a58a41e3ddd78f372faeeefe1f
F src/btree.c 1d9b2179ccac13970c883da6ae3758cc72978bb0
F src/btree.h 2e2cc923224649337d7217df0dd32b06673ca180
-F src/build.c 66bf13d2d478d43b9b490bebf07ea62524d80993
+F src/build.c a5688630ba792987dca4daa62aae5ea30d1fad67
F src/date.c f3d1f5cd1503dabf426a198f3ebef5afbc122a7f
F src/delete.c d70d54a84695de92efc05b9db7d3684cd21d9094
F src/experimental.c 50c1e3b34f752f4ac10c36f287db095c2b61766d
@@ -54,14 +54,14 @@ F src/os_win.c bddeae1c3299be0fbe47077dd4e98b786a067f71
F src/os_win.h 41a946bea10f61c158ce8645e7646b29d44f122b
F src/pager.c 26a642c1238615bdea53d458ab6a4df7ca070a08
F src/pager.h 70d496f372163abb6340f474288c4bb9ea962cf7
-F src/parse.y 5c8336e7df0d843976c2aa0f63010d479d9cdba5
+F src/parse.y 10c0ace9efce31d5a06e03488a4284b9d97abc56
F src/pragma.c 4b20dbc0f4b97f412dc511853d3d0c2e0d4adedc
F src/printf.c 3d20b21cfecadacecac3fb7274e746cb81d3d357
F src/random.c eff68e3f257e05e81eae6c4d50a51eb88beb4ff3
F src/select.c 69e6093d52e871a243477e9746f820456538dd03
F src/shell.c 25b3217d7c64e6497225439d261a253a23efff26
F src/sqlite.h.in c85f6bad9ca7de29f505fe886646cfff7df4c55e
-F src/sqliteInt.h b59243adc43f0326ca7d8ce0b7ebd3cc70bd670d
+F src/sqliteInt.h 9b2aa887e79b2ecadc24f0b30363b9ec1e5b51e3
F src/table.c 25b3ff2b39b7d87e8d4a5da0713d68dfc06cbee9
F src/tclsqlite.c 29e56ecdb94c4066dbe6b088d12cc2404bc9597e
F src/test1.c dd3e4961f3b9b235a68d4af5d77a06eb7be73184
@@ -86,6 +86,7 @@ F tclinstaller.tcl 046e3624671962dc50f0481d7c25b38ef803eb42
F test/all.test 7f0988442ab811dfa41793b5b550f5828ce316f3
F test/alter.test 3a20ce14c3989f7e2e75da50797065c2e56f838b
F test/alter2.test 60ba0a7057dc71ad630a1cc7c487104346849d50
+F test/alter3.test ed4b8d4f1ce429d313221cc827d9a18392e51620
F test/attach.test e6bda19cc954fd84836fadbd70d80134cb17918a
F test/attach2.test 6f3a3a3a7f5be40388dd4d805e0e0712718dca9d
F test/attach3.test c05c70b933afbde0901dab9da3e66ee842c09f38
@@ -259,7 +260,7 @@ F www/faq.tcl 1e348dec52dc0f21f4216fd6918c69c56daa4cfd
F www/fileformat.tcl 900c95b9633abc3dcfc384d9ddd8eb4876793059
F www/formatchng.tcl bfbf14dbf5181e771d06da7797767b0200b36d8a
F www/index.tcl a53c9b092ab528cc4b9755ccd78e23ca796bafe0
-F www/lang.tcl 0e5aeb09864b9ae0746d8afaa6377193f1553486
+F www/lang.tcl 21cb28bee6a522ab9a376e33f15a048638d6db97
F www/lockingv3.tcl f59b19d6c8920a931f096699d6faaf61c05db55f
F www/mingw.tcl d96b451568c5d28545fefe0c80bee3431c73f69c
F www/nulls.tcl ec35193f92485b87b90a994a01d0171b58823fcf
@@ -276,7 +277,7 @@ F www/tclsqlite.tcl e73f8f8e5f20e8277619433f7970060ab01088fc
F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
F www/version3.tcl 092a01f5ef430d2c4acc0ae558d74c4bb89638a0
F www/whentouse.tcl 3e522a06ad41992023c80ca29a048ae2331ca5bd
-P 3f45cf3516be9919fe2c0673d6f445fa83d42126
-R ecdf32cee3676fed33ab83ee414d13b7
-U drh
-Z f31b75bb74fc3f7add0a909524577db3
+P 6a3f4e4be6f294f756ed882f220f579252735f3f
+R 6d3cd1539c542074a3e7518c7b23a719
+U danielk1977
+Z c3ddc11776f2cb70a8848ec00f552873
diff --git a/manifest.uuid b/manifest.uuid
index 9c20bef6a1..765cbc7d4b 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-6a3f4e4be6f294f756ed882f220f579252735f3f
\ No newline at end of file
+94185dd4f7e2e941c74a521488d1212a75927218
\ No newline at end of file
diff --git a/src/alter.c b/src/alter.c
index 2ed4a7afec..c01219f211 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
/*
** The code in this file only exists if we are not omitting the
@@ -166,6 +167,78 @@ void sqlite3AlterFunctions(sqlite3 *db){
}
}
+/*
+** 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 @@ void sqlite3AlterRenameTable(
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 @@ void sqlite3AlterRenameTable(
"(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 @@ zName,
** 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 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; inCol; 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 15fc2b6729..d1f2fef739 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
@@ -1413,7 +1413,12 @@ static char *createTableStmt(Table *p){
** "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 @@ void sqlite3EndTable(Parse *pParse, Token *pEnd, Select *pSelect){
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 @@ void sqlite3CreateView(
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 2157f15d71..ad7fb2f985 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 @@ create_table ::= CREATE(X) temp(T) TABLE nm(Y) dbnm(Z). {
%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 @@ columnlist ::= column.
// 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 ::= NOT NULL onconf(R). {sqlite3AddNotNull(pParse, R);}
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 @@ init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE. {A = 0;}
// 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 ::= REINDEX nm(X) dbnm(Y). {sqlite3Reindex(pParse, &X, &Y);}
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 174c0d2a86..f5e79a9510 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 @@ struct Table {
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 sqlite3AddPrimaryKey(Parse*, ExprList*, int, int);
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 sqlite3ExpirePreparedStatements(sqlite3*);
void sqlite3CodeSubselect(Parse *, Expr *);
int sqlite3SelectResolve(Parse *, Select *, NameContext *);
void sqlite3ColumnDefault(Vdbe *, Table *, int);
+void sqlite3AlterFinishAddColumn(Parse *, Token *);
+void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
#endif
diff --git a/test/alter3.test b/test/alter3.test
new file mode 100644
index 0000000000..4102a6d338
--- /dev/null
+++ b/test/alter3.test
@@ -0,0 +1,333 @@
+# 2005 February 19
+#
+# 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 implements regression tests for SQLite library. The
+# focus of this script is testing that SQLite can handle a subtle
+# file format change that may be used in the future to implement
+# "ALTER TABLE ... ADD COLUMN".
+#
+# $Id: alter3.test,v 1.1 2005/03/17 05:03:41 danielk1977 Exp $
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Test Organisation:
+# ------------------
+#
+# alter3-1.*: Test that ALTER TABLE correctly modifies the CREATE TABLE sql.
+# alter3-2.*: Test error messages.
+# alter3-3.*: Test adding columns with default value NULL.
+# alter3-4.*: Test adding columns with default values other than NULL.
+# alter3-5.*: Test adding columns to tables in ATTACHed databases.
+# alter3-6.*: Test that temp triggers are not accidentally dropped.
+# alter3-7.*: Test that VACUUM resets the file-format.
+#
+
+# This procedure returns the value of the file-format in file 'test.db'.
+#
+proc get_file_format {{fname test.db}} {
+ set bt [btree_open $fname 10 0]
+ set meta [btree_get_meta $bt]
+ btree_close $bt
+ lindex $meta 2
+}
+
+do_test alter3-1.1 {
+ execsql {
+ CREATE TABLE abc(a, b, c);
+ SELECT sql FROM sqlite_master;
+ }
+} {{CREATE TABLE abc(a, b, c)}}
+do_test alter3-1.2 {
+ execsql {ALTER TABLE abc ADD d INTEGER;}
+ execsql {
+ SELECT sql FROM sqlite_master;
+ }
+} {{CREATE TABLE abc(a, b, c, d INTEGER)}}
+do_test alter3-1.3 {
+ execsql {ALTER TABLE abc ADD e}
+ execsql {
+ SELECT sql FROM sqlite_master;
+ }
+} {{CREATE TABLE abc(a, b, c, d INTEGER, e)}}
+do_test alter3-1.4 {
+ execsql {
+ CREATE TABLE main.t1(a, b);
+ ALTER TABLE t1 ADD c;
+ SELECT sql FROM sqlite_master WHERE tbl_name = 't1';
+ }
+} {{CREATE TABLE t1(a, b, c)}}
+do_test alter3-1.5 {
+ execsql {
+ ALTER TABLE t1 ADD d CHECK (a>d);
+ SELECT sql FROM sqlite_master WHERE tbl_name = 't1';
+ }
+} {{CREATE TABLE t1(a, b, c, d CHECK (a>d))}}
+do_test alter3-1.6 {
+ execsql {
+ CREATE TABLE t2(a, b, UNIQUE(a, b));
+ ALTER TABLE t2 ADD c REFERENCES t1(c) ;
+ SELECT sql FROM sqlite_master WHERE tbl_name = 't2' AND type = 'table';
+ }
+} {{CREATE TABLE t2(a, b, c REFERENCES t1(c), UNIQUE(a, b))}}
+do_test alter3-1.7 {
+ execsql {
+ CREATE TABLE t3(a, b, UNIQUE(a, b));
+ ALTER TABLE t3 ADD COLUMN c VARCHAR(10, 20);
+ SELECT sql FROM sqlite_master WHERE tbl_name = 't3' AND type = 'table';
+ }
+} {{CREATE TABLE t3(a, b, c VARCHAR(10, 20), UNIQUE(a, b))}}
+do_test alter3-1.99 {
+ execsql {
+ DROP TABLE abc;
+ DROP TABLE t1;
+ DROP TABLE t2;
+ DROP TABLE t3;
+ }
+} {}
+
+do_test alter3-2.1 {
+ execsql {
+ CREATE TABLE t1(a, b);
+ }
+ catchsql {
+ ALTER TABLE t1 ADD c PRIMARY KEY;
+ }
+} {1 {Cannot add a PRIMARY KEY column}}
+do_test alter3-2.2 {
+ catchsql {
+ ALTER TABLE t1 ADD c UNIQUE
+ }
+} {1 {Cannot add a UNIQUE column}}
+do_test alter3-2.3 {
+ catchsql {
+ ALTER TABLE t1 ADD b VARCHAR(10)
+ }
+} {1 {duplicate column name: b}}
+do_test alter3-2.3 {
+ catchsql {
+ ALTER TABLE t1 ADD c NOT NULL;
+ }
+} {1 {Cannot add a NOT NULL column with default value NULL}}
+do_test alter3-2.4 {
+ catchsql {
+ ALTER TABLE t1 ADD c NOT NULL DEFAULT 10;
+ }
+} {0 {}}
+do_test alter3-2.5 {
+ execsql {
+ CREATE VIEW v1 AS SELECT * FROM t1;
+ }
+ catchsql {
+ alter table v1 add column d;
+ }
+} {1 {Cannot add a column to a view}}
+do_test alter3-2.6 {
+ catchsql {
+ alter table t1 add column d DEFAULT CURRENT_TIME;
+ }
+} {1 {Cannot add a column with non-constant default}}
+do_test alter3-2.99 {
+ execsql {
+ DROP TABLE t1;
+ }
+} {}
+
+do_test alter3-3.1 {
+ execsql {
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1, 100);
+ INSERT INTO t1 VALUES(2, 300);
+ SELECT * FROM t1;
+ }
+} {1 100 2 300}
+do_test alter3-3.1 {
+ execsql {
+ PRAGMA schema_version = 10;
+ }
+} {}
+do_test alter3-3.2 {
+ execsql {
+ ALTER TABLE t1 ADD c;
+ SELECT * FROM t1;
+ }
+} {1 100 {} 2 300 {}}
+do_test alter3-3.3 {
+ get_file_format
+} {3}
+do_test alter3-3.4 {
+ execsql {
+ PRAGMA schema_version;
+ }
+} {11}
+
+do_test alter3-4.1 {
+ db close
+ file delete -force test.db
+ set ::DB [sqlite3 db test.db]
+ execsql {
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1, 100);
+ INSERT INTO t1 VALUES(2, 300);
+ SELECT * FROM t1;
+ }
+} {1 100 2 300}
+do_test alter3-4.1 {
+ execsql {
+ PRAGMA schema_version = 20;
+ }
+} {}
+do_test alter3-4.2 {
+ execsql {
+ ALTER TABLE t1 ADD c DEFAULT 'hello world';
+ SELECT * FROM t1;
+ }
+} {1 100 {hello world} 2 300 {hello world}}
+do_test alter3-4.3 {
+ get_file_format
+} {3}
+do_test alter3-4.4 {
+ execsql {
+ PRAGMA schema_version;
+ }
+} {21}
+do_test alter3-4.99 {
+ execsql {
+ DROP TABLE t1;
+ }
+} {}
+
+do_test alter3-5.1 {
+ file delete -force test2.db
+ file delete -force test2.db-journal
+ execsql {
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1, 'one');
+ INSERT INTO t1 VALUES(2, 'two');
+ ATTACH 'test2.db' AS aux;
+ CREATE TABLE aux.t1 AS SELECT * FROM t1;
+ PRAGMA aux.schema_version = 30;
+ SELECT sql FROM aux.sqlite_master;
+ }
+} {{CREATE TABLE t1(a,b)}}
+do_test alter3-5.2 {
+ execsql {
+ ALTER TABLE aux.t1 ADD COLUMN c VARCHAR(128);
+ SELECT sql FROM aux.sqlite_master;
+ }
+} {{CREATE TABLE t1(a,b, c VARCHAR(128))}}
+do_test alter3-5.3 {
+ execsql {
+ SELECT * FROM aux.t1;
+ }
+} {1 one {} 2 two {}}
+do_test alter3-5.4 {
+ execsql {
+ PRAGMA aux.schema_version;
+ }
+} {31}
+do_test alter3-5.5 {
+ list [get_file_format test2.db] [get_file_format]
+} {2 3}
+do_test alter3-5.6 {
+ execsql {
+ ALTER TABLE aux.t1 ADD COLUMN d DEFAULT 1000;
+ SELECT sql FROM aux.sqlite_master;
+ }
+} {{CREATE TABLE t1(a,b, c VARCHAR(128), d DEFAULT 1000)}}
+do_test alter3-5.7 {
+ execsql {
+ SELECT * FROM aux.t1;
+ }
+} {1 one {} 1000 2 two {} 1000}
+do_test alter3-5.8 {
+ execsql {
+ PRAGMA aux.schema_version;
+ }
+} {32}
+do_test alter3-5.9 {
+ execsql {
+ SELECT * FROM t1;
+ }
+} {1 one 2 two}
+do_test alter3-5.99 {
+ execsql {
+ DROP TABLE aux.t1;
+ DROP TABLE t1;
+ }
+} {}
+
+#----------------------------------------------------------------
+# Test that the table schema is correctly reloaded when a column
+# is added to a table.
+#
+ifcapable trigger {
+ do_test alter3-6.1 {
+ execsql {
+ CREATE TABLE t1(a, b);
+ CREATE TABLE log(trig, a, b);
+
+ CREATE TRIGGER t1_a AFTER INSERT ON t1 BEGIN
+ INSERT INTO log VALUES('a', new.a, new.b);
+ END;
+ CREATE TEMP TRIGGER t1_b AFTER INSERT ON t1 BEGIN
+ INSERT INTO log VALUES('b', new.a, new.b);
+ END;
+
+ INSERT INTO t1 VALUES(1, 2);
+ SELECT * FROM log;
+ }
+ } {b 1 2 a 1 2}
+ do_test alter3-6.2 {
+ execsql {
+ ALTER TABLE t1 ADD COLUMN c DEFAULT 'c';
+ INSERT INTO t1(a, b) VALUES(3, 4);
+ SELECT * FROM log;
+ }
+ } {b 1 2 a 1 2 b 3 4 a 3 4}
+}
+
+ifcapable vacuum {
+ do_test alter3-7.1 {
+ execsql {
+ VACUUM;
+ }
+ get_file_format
+ } {1}
+ do_test alter3-7.2 {
+ execsql {
+ CREATE TABLE abc(a, b, c);
+ ALTER TABLE abc ADD d DEFAULT NULL;
+ }
+ get_file_format
+ } {2}
+ do_test alter3-7.3 {
+ execsql {
+ ALTER TABLE abc ADD e DEFAULT 10;
+ }
+ get_file_format
+ } {3}
+ do_test alter3-7.4 {
+ execsql {
+ ALTER TABLE abc ADD f DEFAULT NULL;
+ }
+ get_file_format
+ } {3}
+ do_test alter3-7.5 {
+ execsql {
+ VACUUM;
+ }
+ get_file_format
+ } {1}
+}
+
+finish_test
+
diff --git a/www/lang.tcl b/www/lang.tcl
index 54195578ec..f3ba0a757c 100644
--- a/www/lang.tcl
+++ b/www/lang.tcl
@@ -1,7 +1,7 @@
#
# Run this Tcl script to generate the lang-*.html files.
#
-set rcsid {$Id: lang.tcl,v 1.84 2005/02/19 12:44:16 drh Exp $}
+set rcsid {$Id: lang.tcl,v 1.85 2005/03/17 05:03:40 danielk1977 Exp $}
source common.tcl
if {[llength $argv]>0} {
@@ -139,16 +139,23 @@ proc Section {name label} {
Section {ALTER TABLE} altertable
Syntax {sql-statement} {
-ALTER TABLE [ .] RENAME TO
+ALTER TABLE [ .]
+} {alteration} {
+RENAME TO
+} {alteration} {
+ADD [COLUMN]
}
puts {
SQLite's version of the ALTER TABLE command allows the user to
-rename an existing table. The table identified by
-[database-name.]table-name is renamed to
-new-table-name. This command cannot be used to move a
-table between attached databases, only to rename a table within
-the same database.
+rename or add a new column to an existing table. It is not possible
+to remove a column from a table.
+
+
+The RENAME TO syntax is used to rename the table identified by
+[database-name.]table-name to new-table-name. This command
+cannot be used to move a table between attached databases, only to rename
+a table within the same database.
If the table being renamed has triggers or indices, then these remain
attached to the table after it has been renamed. However, if there are
@@ -157,6 +164,18 @@ the table being renamed, these are not automatically modified to use the new
table name. If this is required, the triggers or view definitions must be
dropped and recreated to use the new table name by hand.
+
+The ADD [COLUMN] syntax is used to add a new column to an existing table.
+The new column is always appended to the end of the list of existing columns.
+Column-def may take any of the forms permissable in a CREATE TABLE
+statement, with the following restrictions:
+
+- The column may not have a PRIMARY KEY or UNIQUE constraint.
+- The column may not have a default value of CURRENT_TIME, CURRENT_DATE
+ or CURRENT_TIMESTAMP.
+- If a NOT NULL constraint is specified, then the column must have a
+ default value other than NULL.
+
}
Section {ATTACH DATABASE} attach