Added support for the INTEGER PRIMARY KEY column type. (CVS 333)

FossilOrigin-Name: 236a54d289e858a1e0505a20d907a2a40c01b521
diff --git a/src/build.c b/src/build.c
index c4db457..1d0654b 100644
--- a/src/build.c
+++ b/src/build.c
@@ -25,7 +25,7 @@
 **     ROLLBACK
 **     PRAGMA
 **
-** $Id: build.c,v 1.59 2001/12/15 02:35:59 drh Exp $
+** $Id: build.c,v 1.60 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -268,7 +268,7 @@
   }
   sqliteHashClear(&toDelete);
   for(pElem=sqliteHashFirst(&db->idxHash); pElem; pElem=sqliteHashNext(pElem)){
-    Table *pIndex = sqliteHashData(pElem);
+    Index *pIndex = sqliteHashData(pElem);
     if( pIndex->isDelete ){
       sqliteHashInsert(&toDelete, pIndex, 0, pIndex);
     }else{
@@ -311,7 +311,7 @@
   }
   sqliteHashClear(&toDelete);
   for(pElem=sqliteHashFirst(&db->idxHash); pElem; pElem=sqliteHashNext(pElem)){
-    Table *pIndex = sqliteHashData(pElem);
+    Index *pIndex = sqliteHashData(pElem);
     if( !pIndex->isCommit ){
       sqliteHashInsert(&toDelete, pIndex, 0, pIndex);
     }else{
@@ -425,6 +425,7 @@
   pTable->zName = zName;
   pTable->nCol = 0;
   pTable->aCol = 0;
+  pTable->iPKey = -1;
   pTable->pIndex = 0;
   pTable->isTemp = isTemp;
   if( pParse->pNewTable ) sqliteDeleteTable(db, pParse->pNewTable);
@@ -436,6 +437,7 @@
       pParse->schemaVerified = 1;
     }
     if( !isTemp ){
+      sqliteVdbeAddOp(v, OP_SetCookie, db->file_format, 1);
       sqliteVdbeAddOp(v, OP_OpenWrite, 0, 2);
       sqliteVdbeChangeP3(v, -1, MASTER_NAME, P3_STATIC);
     }
@@ -535,6 +537,56 @@
 }
 
 /*
+** Designate the PRIMARY KEY for the table.  pList is a list of names 
+** of columns that form the primary key.  If pList is NULL, then the
+** most recently added column of the table is the primary key.
+**
+** A table can have at most one primary key.  If the table already has
+** a primary key (and this is the second primary key) then create an
+** error.
+**
+** If the PRIMARY KEY is on a single column whose datatype is INTEGER,
+** then we will try to use that column as the row id.  (Exception:
+** For backwards compatibility with older databases, do not do this
+** if the file format version number is less than 1.)  Set the Table.iPKey
+** field of the table under construction to be the index of the
+** INTEGER PRIMARY KEY column.  Table.iPKey is set to -1 if there is
+** no INTEGER PRIMARY KEY.
+**
+** If the key is not an INTEGER PRIMARY KEY, then create a unique
+** index for the key.  No index is created for INTEGER PRIMARY KEYs.
+*/
+void sqliteAddPrimaryKey(Parse *pParse, IdList *pList){
+  Table *pTab = pParse->pNewTable;
+  char *zType = 0;
+  int iCol = -1;
+  if( pTab==0 ) return;
+  if( pTab->hasPrimKey ){
+    sqliteSetString(&pParse->zErrMsg, "table \"", pTab->zName, 
+        "\" has more than one primary key", 0);
+    pParse->nErr++;
+    return;
+  }
+  pTab->hasPrimKey = 1;
+  if( pList==0 ){
+    iCol = pTab->nCol - 1;
+  }else if( pList->nId==1 ){
+    for(iCol=0; iCol<pTab->nCol; iCol++){
+      if( sqliteStrICmp(pList->a[0].zName, pTab->aCol[iCol].zName)==0 ) break;
+    }
+  }
+  if( iCol>=0 && iCol<pTab->nCol ){
+    zType = pTab->aCol[iCol].zType;
+  }
+  if( pParse->db->file_format>=1 && 
+           zType && sqliteStrICmp(zType, "INTEGER")==0 ){
+    pTab->iPKey = iCol;
+  }else{
+    sqliteCreateIndex(pParse, 0, 0, pList, 1, 0, 0);
+  }
+}
+
+/*
 ** Come up with a new random value for the schema cookie.  Make sure
 ** the new value is different from the old.
 **
@@ -787,8 +839,8 @@
 
   /* If this index is created while re-reading the schema from sqlite_master
   ** but the table associated with this index is a temporary table, it can
-  ** only mean that the table this index is really associated with is one 
-  ** whose name is hidden behind a temporary table with the same name.
+  ** only mean that the table that this index is really associated with is
+  ** one whose name is hidden behind a temporary table with the same name.
   ** Since its table has been suppressed, we need to also suppress the
   ** index.
   */
diff --git a/src/delete.c b/src/delete.c
index 0b7a9c3..53e6e96 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle DELETE FROM statements.
 **
-** $Id: delete.c,v 1.21 2001/11/07 16:48:27 drh Exp $
+** $Id: delete.c,v 1.22 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -157,7 +157,12 @@
         int j;
         sqliteVdbeAddOp(v, OP_Recno, base, 0);
         for(j=0; j<pIdx->nColumn; j++){
-          sqliteVdbeAddOp(v, OP_Column, base, pIdx->aiColumn[j]);
+          int idx = pIdx->aiColumn[j];
+          if( idx==pTab->iPKey ){
+            sqliteVdbeAddOp(v, OP_Dup, j, 0);
+          }else{
+            sqliteVdbeAddOp(v, OP_Column, base, idx);
+          }
         }
         sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0);
         sqliteVdbeAddOp(v, OP_IdxDelete, base+i, 0);
diff --git a/src/expr.c b/src/expr.c
index fc7e7f8..37f4d28 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -12,7 +12,7 @@
 ** This file contains routines used for analyzing expressions and
 ** for generating VDBE code that evaluates expressions in SQLite.
 **
-** $Id: expr.c,v 1.34 2001/11/24 00:31:46 drh Exp $
+** $Id: expr.c,v 1.35 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -127,7 +127,12 @@
           if( sqliteStrICmp(pTab->aCol[j].zName, z)==0 ){
             cnt++;
             pExpr->iTable = i + pParse->nTab;
-            pExpr->iColumn = j;
+            if( j==pTab->iPKey ){
+              /* Substitute the record number for the INTEGER PRIMARY KEY */
+              pExpr->iColumn = -1;
+            }else{
+              pExpr->iColumn = j;
+            }
           }
         }
       }
@@ -190,7 +195,12 @@
           if( sqliteStrICmp(pTab->aCol[j].zName, zRight)==0 ){
             cnt++;
             pExpr->iTable = i + pParse->nTab;
-            pExpr->iColumn = j;
+            if( j==pTab->iPKey ){
+              /* Substitute the record number for the INTEGER PRIMARY KEY */
+              pExpr->iColumn = -1;
+            }else{
+              pExpr->iColumn = j;
+            }
           }
         }
       }
diff --git a/src/insert.c b/src/insert.c
index a3faa2c..d51a579 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle INSERT statements in SQLite.
 **
-** $Id: insert.c,v 1.26 2001/11/07 16:48:27 drh Exp $
+** $Id: insert.c,v 1.27 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -49,6 +49,7 @@
   int iCont, iBreak;    /* Beginning and end of the loop over srcTab */
   sqlite *db;           /* The main database structure */
   int openOp;           /* Opcode used to open cursors */
+  int keyColumn = -1;   /* Column that is the INTEGER PRIMARY KEY */
 
   if( pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup;
   db = pParse->db;
@@ -140,6 +141,9 @@
       for(j=0; j<pTab->nCol; j++){
         if( sqliteStrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){
           pColumn->a[i].idx = j;
+          if( j==pTab->iPKey ){
+            keyColumn = j;
+          }
           break;
         }
       }
@@ -152,6 +156,13 @@
     }
   }
 
+  /* If there is not IDLIST term but the table has an integer primary
+  ** key, the set the keyColumn variable to the primary key column.
+  */
+  if( pColumn==0 ){
+    keyColumn = pTab->iPKey;
+  }
+
   /* Open cursors into the table that is received the new data and
   ** all indices of that table.
   */
@@ -178,13 +189,41 @@
     iCont = sqliteVdbeCurrentAddr(v);
   }
 
-  /* Create a new entry in the table and fill it with data.
+  /* Push the record number for the new entry onto the stack.  The
+  ** record number is a randomly generate integer created by NewRecno
+  ** except when the table has an INTEGER PRIMARY KEY column, in which
+  ** case the record number is the same as that column.
   */
-  sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
+  if( keyColumn>=0 ){
+    if( srcTab>=0 ){
+      sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn);
+    }else{
+      sqliteExprCode(pParse, pList->a[keyColumn].pExpr);
+    }
+    sqliteVdbeAddOp(v, OP_AddImm, 0, 0);  /* Make sure ROWID is an integer */
+  }else{
+    sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
+  }
+
+  /* If there are indices, we'll need this record number again, so make
+  ** a copy.
+  */
   if( pTab->pIndex ){
     sqliteVdbeAddOp(v, OP_Dup, 0, 0);
   }
+
+  /* Push onto the stack data for all columns of the new entry, beginning
+  ** with the first column.
+  */
   for(i=0; i<pTab->nCol; i++){
+    if( i==pTab->iPKey ){
+      /* The value of the INTEGER PRIMARY KEY column is always a NULL.
+      ** Whenever this column is used, the record number will be substituted
+      ** in its place, so there is no point it it taking up space in
+      ** the data record. */
+      sqliteVdbeAddOp(v, OP_String, 0, 0);
+      continue;
+    }
     if( pColumn==0 ){
       j = i;
     }else{
@@ -201,10 +240,12 @@
       sqliteExprCode(pParse, pList->a[j].pExpr);
     }
   }
-  sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
-  sqliteVdbeAddOp(v, OP_Put, base, 0);
-  
 
+  /* Create the new record and put it into the database.
+  */
+  sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+  sqliteVdbeAddOp(v, OP_Put, base, keyColumn>=0);
+  
   /* Create appropriate entries for the new data row in all indices
   ** of the table.
   */
@@ -214,6 +255,11 @@
     }
     for(i=0; i<pIdx->nColumn; i++){
       int idx = pIdx->aiColumn[i];
+      if( idx==pTab->iPKey ){
+        /* Copy the record number in place of the INTEGER PRIMARY KEY column */
+        sqliteVdbeAddOp(v, OP_Dup, i, 0);
+        continue;
+      }
       if( pColumn==0 ){
         j = idx;
       }else{
diff --git a/src/main.c b/src/main.c
index d59c612..ce4efe5 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.51 2001/12/05 00:21:20 drh Exp $
+** $Id: main.c,v 1.52 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -25,7 +25,7 @@
 **
 ** Each callback contains the following information:
 **
-**     argv[0] = "meta" or "table" or "index"
+**     argv[0] = "file-format" or "schema-cookie" or "table" or "index"
 **     argv[1] = table or index name or meta statement type.
 **     argv[2] = root page number for table or index.  NULL for meta.
 **     argv[3] = SQL create statement for the table or index
@@ -42,13 +42,13 @@
 
   assert( argc==4 );
   switch( argv[0][0] ){
-    case 'm': {  /* Meta information */
-      if( strcmp(argv[1],"file-format")==0 ){
-        db->file_format = atoi(argv[3]);
-      }else if( strcmp(argv[1],"schema-cookie")==0 ){
-        db->schema_cookie = atoi(argv[3]);
-        db->next_cookie = db->schema_cookie;
-      }
+    case 'f': {  /* File format */
+      db->file_format = atoi(argv[3]);
+      break;
+    }
+    case 's': { /* Schema cookie */
+      db->schema_cookie = atoi(argv[3]);
+      db->next_cookie = db->schema_cookie;
       break;
     }
     case 'i':
@@ -156,44 +156,39 @@
   ** database scheme.
   */
   static VdbeOp initProg[] = {
-    { OP_Open,     0, 2,  0},
-    { OP_Rewind,   0, 31, 0},
-    { OP_Column,   0, 0,  0},           /* 2 */
-    { OP_String,   0, 0,  "meta"},
-    { OP_Ne,       0, 10, 0},
-    { OP_Column,   0, 0,  0},
-    { OP_Column,   0, 1,  0},
-    { OP_Column,   0, 3,  0},
-    { OP_Column,   0, 4,  0},
-    { OP_Callback, 4, 0,  0},
-    { OP_Next,     0, 2,  0},           /* 10 */
-    { OP_Rewind,   0, 31, 0},           /* 11 */
-    { OP_Column,   0, 0,  0},           /* 12 */
-    { OP_String,   0, 0,  "table"},
-    { OP_Ne,       0, 20, 0},
-    { OP_Column,   0, 0,  0},
-    { OP_Column,   0, 1,  0},
-    { OP_Column,   0, 3,  0},
-    { OP_Column,   0, 4,  0},
-    { OP_Callback, 4, 0,  0},
-    { OP_Next,     0, 12, 0},           /* 20 */
-    { OP_Rewind,   0, 31, 0},           /* 21 */
-    { OP_Column,   0, 0,  0},           /* 22 */
-    { OP_String,   0, 0,  "index"},
-    { OP_Ne,       0, 30, 0},
-    { OP_Column,   0, 0,  0},
-    { OP_Column,   0, 1,  0},
-    { OP_Column,   0, 3,  0},
-    { OP_Column,   0, 4,  0},
-    { OP_Callback, 4, 0,  0},
-    { OP_Next,     0, 22, 0},           /* 30 */
-    { OP_String,   0, 0,  "meta"},      /* 31 */
-    { OP_String,   0, 0,  "schema-cookie"},
-    { OP_String,   0, 0,  0},
-    { OP_ReadCookie,0,0,  0},
-    { OP_Callback, 4, 0,  0},
-    { OP_Close,    0, 0,  0},
-    { OP_Halt,     0, 0,  0},
+    { OP_Open,       0, 2,  0},
+    { OP_String,     0, 0,  "file-format"},
+    { OP_String,     0, 0,  0},
+    { OP_String,     0, 0,  0},
+    { OP_ReadCookie, 0, 1,  0},
+    { OP_Callback,   4, 0,  0},
+    { OP_String,     0, 0,  "schema_cookie"},
+    { OP_String,     0, 0,  0},
+    { OP_String,     0, 0,  0},
+    { OP_ReadCookie, 0, 0,  0},
+    { OP_Callback,   4, 0,  0},
+    { OP_Rewind,     0, 31, 0},
+    { OP_Column,     0, 0,  0},           /* 12 */
+    { OP_String,     0, 0,  "table"},
+    { OP_Ne,         0, 20, 0},
+    { OP_Column,     0, 0,  0},
+    { OP_Column,     0, 1,  0},
+    { OP_Column,     0, 3,  0},
+    { OP_Column,     0, 4,  0},
+    { OP_Callback,   4, 0,  0},
+    { OP_Next,       0, 12, 0},           /* 20 */
+    { OP_Rewind,     0, 31, 0},           /* 21 */
+    { OP_Column,     0, 0,  0},           /* 22 */
+    { OP_String,     0, 0,  "index"},
+    { OP_Ne,         0, 30, 0},
+    { OP_Column,     0, 0,  0},
+    { OP_Column,     0, 1,  0},
+    { OP_Column,     0, 3,  0},
+    { OP_Column,     0, 4,  0},
+    { OP_Callback,   4, 0,  0},
+    { OP_Next,       0, 22, 0},           /* 30 */
+    { OP_Close,      0, 0,  0},           /* 31 */
+    { OP_Halt,       0, 0,  0},
   };
 
   /* Create a virtual machine to run the initialization program.  Run
@@ -208,7 +203,10 @@
   rc = sqliteVdbeExec(vdbe, sqliteOpenCb, db, pzErrMsg, 
                       db->pBusyArg, db->xBusyCallback);
   sqliteVdbeDelete(vdbe);
-  if( rc==SQLITE_OK && db->file_format>1 && db->nTable>0 ){
+  if( rc==SQLITE_OK && db->nTable==0 ){
+    db->file_format = FILE_FORMAT;
+  }
+  if( rc==SQLITE_OK && db->file_format>FILE_FORMAT ){
     sqliteSetString(pzErrMsg, "unsupported file format", 0);
     rc = SQLITE_ERROR;
   }
@@ -282,9 +280,6 @@
     return 0;
   }
 
-  /* Assume file format 1 unless the database says otherwise */
-  db->file_format = 1;
-
   /* Attempt to read the schema */
   rc = sqliteInit(db, pzErrMsg);
   if( sqlite_malloc_failed ){
diff --git a/src/parse.y b/src/parse.y
index f2a1897..525ed4d 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.39 2001/12/16 20:05:06 drh Exp $
+** @(#) $Id: parse.y,v 1.40 2001/12/21 14:30:43 drh Exp $
 */
 %token_prefix TK_
 %token_type {Token}
@@ -138,7 +138,7 @@
 // UNIQUE constraints.
 //
 ccons ::= NOT NULL.                  {sqliteAddNotNull(pParse);}
-ccons ::= PRIMARY KEY sortorder.     {sqliteCreateIndex(pParse,0,0,0,1,0,0);}
+ccons ::= PRIMARY KEY sortorder.     {sqliteAddPrimaryKey(pParse, 0);}
 ccons ::= UNIQUE.                    {sqliteCreateIndex(pParse,0,0,0,1,0,0);}
 ccons ::= CHECK LP expr RP.
 
@@ -151,7 +151,7 @@
 conslist ::= conslist tcons.
 conslist ::= tcons.
 tcons ::= CONSTRAINT ids.
-tcons ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,1,0,0);}
+tcons ::= PRIMARY KEY LP idxlist(X) RP. {sqliteAddPrimaryKey(pParse,X);}
 tcons ::= UNIQUE LP idxlist(X) RP.      {sqliteCreateIndex(pParse,0,0,X,1,0,0);}
 tcons ::= CHECK expr.
 
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 03e0504..ef7021b 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.72 2001/12/05 00:21:20 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.73 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqlite.h"
 #include "hash.h"
@@ -31,6 +31,11 @@
 #define TEMP_PAGES   25
 
 /*
+** File format version number
+*/
+#define FILE_FORMAT 1
+
+/*
 ** Integers of known sizes.  These typedefs might change for architectures
 ** where the sizes very.  Preprocessor macros are available so that the
 ** types can be conveniently redefined at compile-type.  Like this:
@@ -213,7 +218,8 @@
   char *zName;     /* Name of this column */
   char *zDflt;     /* Default value of this column */
   char *zType;     /* Data type for this column */
-  int notNull;     /* True if there is a NOT NULL constraint */
+  u8 notNull;      /* True if there is a NOT NULL constraint */
+  u8 isPrimKey;    /* True if this column is an INTEGER PRIMARY KEY */
 };
 
 /*
@@ -224,12 +230,14 @@
   char *zName;     /* Name of the table */
   int nCol;        /* Number of columns in this table */
   Column *aCol;    /* Information about each column */
+  int iPKey;       /* Use this column as the record-number for each row */
   Index *pIndex;   /* List of SQL indexes on this table. */
   int tnum;        /* Page containing root for this table */
   u8 readOnly;     /* True if this table should not be written by the user */
   u8 isCommit;     /* True if creation of this table has been committed */
   u8 isDelete;     /* True if this table is being deleted */
   u8 isTemp;       /* True if stored in db->pBeTemp instead of db->pBe */
+  u8 hasPrimKey;   /* True if there exists a primary key */
 };
 
 /*
diff --git a/src/update.c b/src/update.c
index ce5d5b3..4c2bbd9 100644
--- a/src/update.c
+++ b/src/update.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle UPDATE statements.
 **
-** $Id: update.c,v 1.22 2001/11/21 02:21:12 drh Exp $
+** $Id: update.c,v 1.23 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -40,6 +40,8 @@
                          ** an expression for the i-th column of the table.
                          ** aXRef[i]==-1 if the i-th column is not changed. */
   int openOp;            /* Opcode used to open tables */
+  int chngRecno;         /* True if the record number is being changed */
+  Expr *pRecnoExpr;      /* Expression defining the new record number */
 
   if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup;
   db = pParse->db;
@@ -89,6 +91,7 @@
       goto update_cleanup;
     }
   }
+  chngRecno = 0;
   for(i=0; i<pChanges->nExpr; i++){
     if( sqliteExprResolveIds(pParse, pTabList, pChanges->a[i].pExpr) ){
       goto update_cleanup;
@@ -98,6 +101,10 @@
     }
     for(j=0; j<pTab->nCol; j++){
       if( sqliteStrICmp(pTab->aCol[j].zName, pChanges->a[i].zName)==0 ){
+        if( i==pTab->iPKey ){
+          chngRecno = 1;
+          pRecnoExpr = pChanges->a[i].pExpr;
+        }
         aXRef[j] = i;
         break;
       }
@@ -115,8 +122,12 @@
   ** key includes one of the columns named in pChanges.
   */
   for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
-    for(i=0; i<pIdx->nColumn; i++){
-      if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
+    if( chngRecno ){
+      i = 0;
+    }else {
+      for(i=0; i<pIdx->nColumn; i++){
+        if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
+      }
     }
     if( i<pIdx->nColumn ) nIdx++;
   }
@@ -125,8 +136,12 @@
     if( apIdx==0 ) goto update_cleanup;
   }
   for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
-    for(i=0; i<pIdx->nColumn; i++){
-      if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
+    if( chngRecno ){
+      i = 0;
+    }else{
+      for(i=0; i<pIdx->nColumn; i++){
+        if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
+      }
     }
     if( i<pIdx->nColumn ) apIdx[nIdx++] = pIdx;
   }
@@ -175,6 +190,7 @@
   ** the old data for each record to be updated because some columns
   ** might not change and we will need to copy the old value.
   ** Also, the old data is needed to delete the old index entires.
+  ** So make the cursor point at the old record.
   */
   end = sqliteVdbeMakeLabel(v);
   addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end);
@@ -193,9 +209,22 @@
     sqliteVdbeAddOp(v, OP_IdxDelete, base+i+1, 0);
   }
 
+  /* If changing the record number, remove the old record number
+  ** from the top of the stack and replace it with the new one.
+  */
+  if( chngRecno ){
+    sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+    sqliteExprCode(pParse, pRecnoExpr);
+    sqliteVdbeAddOp(v, OP_AddImm, 0, 0);
+  }
+
   /* Compute new data for this record.  
   */
   for(i=0; i<pTab->nCol; i++){
+    if( i==pTab->iPKey ){
+      sqliteVdbeAddOp(v, OP_Dup, i, 0);
+      continue;
+    }
     j = aXRef[i];
     if( j<0 ){
       sqliteVdbeAddOp(v, OP_Column, base, i);
@@ -204,13 +233,24 @@
     }
   }
 
+  /* If changing the record number, delete the hold record.
+  */
+  if( chngRecno ){
+    sqliteVdbeAddOp(v, OP_Delete, 0, 0);
+  }
+
   /* Insert new index entries that correspond to the new data
   */
   for(i=0; i<nIdx; i++){
     sqliteVdbeAddOp(v, OP_Dup, pTab->nCol, 0); /* The KEY */
     pIdx = apIdx[i];
     for(j=0; j<pIdx->nColumn; j++){
-      sqliteVdbeAddOp(v, OP_Dup, j+pTab->nCol-pIdx->aiColumn[j], 0);
+      int idx = pIdx->aiColumn[j];
+      if( idx==pTab->iPKey ){
+        sqliteVdbeAddOp(v, OP_Dup, j, 0);
+      }else{
+        sqliteVdbeAddOp(v, OP_Dup, j+pTab->nCol-idx, 0);
+      }
     }
     sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0);
     sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, pIdx->isUnique);
diff --git a/src/vdbe.c b/src/vdbe.c
index 85cca27..88cc6e9 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -30,7 +30,7 @@
 ** But other routines are also provided to help in building up
 ** a program instruction by instruction.
 **
-** $Id: vdbe.c,v 1.100 2001/11/13 19:35:15 drh Exp $
+** $Id: vdbe.c,v 1.101 2001/12/21 14:30:43 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -1654,7 +1654,10 @@
 
 /* Opcode: AddImm  P1 * *
 ** 
-** Add the value P1 to whatever is on top of the stack.
+** Add the value P1 to whatever is on top of the stack.  The result
+** is always an integer.
+**
+** To force the top of the stack to be an integer, just add 0.
 */
 case OP_AddImm: {
   int tos = p->tos;
@@ -2269,15 +2272,20 @@
   break;
 }
 
-/* Opcode: ReadCookie * * *
+/* Opcode: ReadCookie * P2 *
 **
-** Read the schema cookie from the database file and push it onto the
+** When P2==0, 
+** read the schema cookie from the database file and push it onto the
 ** stack.  The schema cookie is an integer that is used like a version
 ** number for the database schema.  Everytime the schema changes, the
 ** cookie changes to a new random value.  This opcode is used during
 ** initialization to read the initial cookie value so that subsequent
 ** database accesses can verify that the cookie has not changed.
 **
+** If P2>0, then read global database parameter number P2.  There is
+** a small fixed number of global database parameters.  P2==1 is the
+** database version number.  Other parameters are currently unused.
+**
 ** There must be a read-lock on the database (either a transaction
 ** must be started or there must be an open cursor) before
 ** executing this instruction.
@@ -2285,17 +2293,21 @@
 case OP_ReadCookie: {
   int i = ++p->tos;
   int aMeta[SQLITE_N_BTREE_META];
+  assert( pOp->p2<SQLITE_N_BTREE_META );
   VERIFY( if( NeedStack(p, p->tos) ) goto no_mem; )
   rc = sqliteBtreeGetMeta(pBt, aMeta);
-  aStack[i].i = aMeta[1];
+  aStack[i].i = aMeta[1+pOp->p2];
   aStack[i].flags = STK_Int;
   break;
 }
 
-/* Opcode: SetCookie P1 * *
+/* Opcode: SetCookie P1 P2 *
 **
-** This operation changes the value of the schema cookie on the database.
-** The new value is P1.
+** When P2==0,
+** this operation changes the value of the schema cookie on the database.
+** The new value is P1.  When P2>0, the value of global database parameter
+** number P2 is changed.  See ReadCookie for more information about
+** global database parametes.
 **
 ** The schema cookie changes its value whenever the database schema changes.
 ** That way, other processes can recognize when the schema has changed
@@ -2305,18 +2317,21 @@
 */
 case OP_SetCookie: {
   int aMeta[SQLITE_N_BTREE_META];
+  assert( pOp->p2<SQLITE_N_BTREE_META );
   rc = sqliteBtreeGetMeta(pBt, aMeta);
   if( rc==SQLITE_OK ){
-    aMeta[1] = pOp->p1;
+    aMeta[1+pOp->p2] = pOp->p1;
     rc = sqliteBtreeUpdateMeta(pBt, aMeta);
   }
   break;
 }
 
-/* Opcode: VerifyCookie P1 * *
+/* Opcode: VerifyCookie P1 P2 *
 **
-** Check the current value of the schema cookie and make sure it is
-** equal to P1.  If it is not, abort with an SQLITE_SCHEMA error.
+** Check the value of global database parameter number P2 and make
+** sure it is equal to P1.  P2==0 is the schema cookie.  P1==1 is
+** the database version.  If the values do not match, abort with
+** an SQLITE_SCHEMA error.
 **
 ** The cookie changes its value whenever the database schema changes.
 ** This operation is used to detect when that the cookie has changed
@@ -2328,8 +2343,9 @@
 */
 case OP_VerifyCookie: {
   int aMeta[SQLITE_N_BTREE_META];
+  assert( pOp->p2<SQLITE_N_BTREE_META );
   rc = sqliteBtreeGetMeta(pBt, aMeta);
-  if( rc==SQLITE_OK && aMeta[1]!=pOp->p1 ){
+  if( rc==SQLITE_OK && aMeta[1+pOp->p2]!=pOp->p1 ){
     sqliteSetString(pzErrMsg, "database schema has changed", 0);
     rc = SQLITE_SCHEMA;
   }
@@ -2613,7 +2629,7 @@
 **
 ** Get a new integer record number used as the key to a table.
 ** The record number is not previously used as a key in the database
-** table that cursor P1 points to.  The new record number pushed 
+** table that cursor P1 points to.  The new record number is pushed 
 ** onto the stack.
 */
 case OP_NewRecno: {
@@ -2666,13 +2682,16 @@
   break;
 }
 
-/* Opcode: Put P1 * *
+/* Opcode: Put P1 P2 *
 **
 ** Write an entry into the database file P1.  A new entry is
 ** created if it doesn't already exist or the data for an existing
 ** entry is overwritten.  The data is the value on the top of the
 ** stack.  The key is the next value down on the stack.  The stack
 ** is popped twice by this instruction.
+**
+** If P2==1 then overwriting is prohibited.  If a prior entry with
+** the same key exists, an SQLITE_CONSTRAINT exception is raised.
 */
 case OP_Put: {
   int tos = p->tos;
@@ -2691,6 +2710,16 @@
       iKey = bigEndian(aStack[nos].i);
       zKey = (char*)&iKey;
     }
+    if( pOp->p2 ){
+      int res;
+      rc = sqliteBtreeMoveto(p->aCsr[i].pCursor, zKey, nKey, &res);
+      if( res==0 && rc==SQLITE_OK ){
+        rc = SQLITE_CONSTRAINT;
+      }
+      if( rc!=SQLITE_OK ){
+        goto abort_due_to_error;
+      }
+    }
     rc = sqliteBtreeInsert(p->aCsr[i].pCursor, zKey, nKey,
                         zStack[tos], aStack[tos].n);
   }