From 4a32431ce7c94176cb146d3776e192b45fe92d57 Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 21 Dec 2001 14:30:42 +0000 Subject: [PATCH] Added support for the INTEGER PRIMARY KEY column type. (CVS 333) FossilOrigin-Name: 236a54d289e858a1e0505a20d907a2a40c01b521 --- VERSION | 2 +- manifest | 33 ++++++------ manifest.uuid | 2 +- src/build.c | 62 +++++++++++++++++++++-- src/delete.c | 9 +++- src/expr.c | 16 ++++-- src/insert.c | 58 ++++++++++++++++++--- src/main.c | 97 +++++++++++++++++------------------ src/parse.y | 6 +-- src/sqliteInt.h | 12 ++++- src/update.c | 52 ++++++++++++++++--- src/vdbe.c | 59 ++++++++++++++++------ test/intpkey.test | 125 ++++++++++++++++++++++++++++++++++++++++++++++ test/unique.test | 11 +++- 14 files changed, 432 insertions(+), 112 deletions(-) create mode 100644 test/intpkey.test diff --git a/VERSION b/VERSION index ebf14b4698..ccbccc3dc6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.8 +2.2.0 diff --git a/manifest b/manifest index 6e339f88a0..84223197e9 100644 --- a/manifest +++ b/manifest @@ -1,9 +1,9 @@ -C Added\sthe\sability\sto\ssay\sthings\slike\s"SELECT\srowid,\s*\sFROM\stable1;"\s(CVS\s332) -D 2001-12-16T20:05:05 +C Added\ssupport\sfor\sthe\sINTEGER\sPRIMARY\sKEY\scolumn\stype.\s(CVS\s333) +D 2001-12-21T14:30:43 F Makefile.in 352fed589f09dd94347e0bb391d047118ebd6105 F Makefile.template 0fbf0ee1fe38183d760170a13e91fffec64e73f5 F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0 -F VERSION 380c16915706a90410eb148b6266d51f0d49ad8b +F VERSION 353ee68ca2468dce6464d331044643d350fd256f F aclocal.m4 11faa843caa38fd451bc6aeb43e248d1723a269d F config.guess f38b1e93d1e0fa6f5a6913e9e7b12774b9232588 F config.log 6a73d03433669b10a3f0c221198c3f26b9413914 @@ -21,35 +21,35 @@ F publish.sh cb0f8f7bcb65b8360d0f6668a216a9ac9d5da892 F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6 F src/btree.c c3c36b3b5f07c3efdabf76df9ea423086b1ce142 F src/btree.h 8767bd4ecf841c4999b7aee6876906bd607546e7 -F src/build.c 5127f737837a9d2a8cb4b998dbab505c08b8f06a -F src/delete.c 5d93a21c1388cfb1359bda01c072f25583a2f4f2 -F src/expr.c 6b25c5bb1e750af2e2217c0134a7aa1fc0b11444 +F src/build.c 36b3bf95bb2f0bdf1d19436b8af5125997c95735 +F src/delete.c f7690efc09ad6a2f1f3f0490e1b0cbb676bb95cf +F src/expr.c ef1c365c5d558fa691878830501d3c36ed7edb25 F src/hash.c 6f1a7712ae3aac8351662969aec5693740a2fbf7 F src/hash.h a5f5b3ce2d086a172c5879b0b06a27a82eac9fac -F src/insert.c 3526be771a01035198bef28d8f370cbcab94f46d -F src/main.c e5fa4773e6684b81fc0bcd9d9ae4578d56660c0c +F src/insert.c 18353ee08ee29241e147187c63ade3669b0af007 +F src/main.c 00a9f5603e130fc0b1a05f731731c9c99ebdc2dc F src/md5.c 52f677bfc590e09f71d07d7e327bd59da738d07c F src/os.c 07882cde5c61f26751b8ee76fd84726c1f7e453c F src/os.h 00a18e0ae1139a64f1d3ead465ae2b9ff43f3db2 F src/pager.c dde0eb5bf9af0ac0ff8a4429b2bee2aec2194ec9 F src/pager.h f78d064c780855ff70beacbeba0e2324471b26fe -F src/parse.y 23ff6728eb2f5d3052fe8b1c311bfd55d9fb0944 +F src/parse.y c62f32e332c291612a3a2e856ab48b636c82ee03 F src/printf.c 300a90554345751f26e1fc0c0333b90a66110a1d F src/random.c 2a9cc2c9716d14815fd4c2accf89d87a1143e46b F src/select.c 76a8fafb29935865ddbef263ee90f1398d950d8b F src/shell.c 407095aaeeae78f42deb3e846b1ad77f8ed3b4ef F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e F src/sqlite.h.in 934de9112747ad8d8e7d5fec44876246b24ca5a3 -F src/sqliteInt.h 3990eeee362d1fb48dff841b56b7fe39577ea590 +F src/sqliteInt.h 0b1e8ba2738440e2f06a4e01bb89230492bc203b F src/table.c c89698bd5bb4b8d14722d6ee7e9be014c383d24a F src/tclsqlite.c b82e4faeae89fdb7304b3c970979ade299336a1f F src/test1.c 41eabe255970ef947263b94145c9b2766bab8675 F src/test2.c e9f99aa5ee73872819259d6612c11e55e1644321 F src/test3.c d6775f95fd91f5b3cf0e2382a28e5aaeb68f745b F src/tokenize.c 830e9ef684334070a26583d94770bb869e2727bf -F src/update.c 365f6fafe75f6816a598e76031b0a757d91c003d +F src/update.c 9c266e5c9d1beba74475fd2fb8078dc3d5b23182 F src/util.c 13dcd870ee0e424f5427e8178480ca1b1833a706 -F src/vdbe.c f1afb7a82016be2cb4cea24cf98dbb5af0ea7214 +F src/vdbe.c 49227b52911dcc6811d5c71d36024172feb22195 F src/vdbe.h cd4c8647051a0c22c0e133c375f1cd17bb8b1e06 F src/where.c 05d27a01e53c20b8cd10589b7e789b2a64367988 F test/all.test 2a51e5395ac7c2c539689b123b9782a05e3837fe @@ -65,6 +65,7 @@ F test/in.test 9323681388be301dc73f370b4cd62c5a33f79d1e F test/index.test c8a471243bbf878974b99baf5badd59407237cf3 F test/insert.test a5c122aa726f1cef6f07d6767e8fd6f220994c11 F test/insert2.test d6901ca931e308fea7fca8c95ebe7dc957cc9fc2 +F test/intpkey.test 79be8360e6f0a5506b513f3ab4399da797cd8b3e F test/ioerr.test 57d9bffaca18b34f9e976f786eadc2591d6efc6a F test/limit.test a930f3eba2a7691c8397ccab33710b931589566a F test/lock.test 19593689260c419efe7ced55b1418653a4b7bcd1 @@ -89,7 +90,7 @@ F test/tclsqlite.test feca0f2b23ba51d202d67d71e10ba7a8a1621f82 F test/temptable.test 37acd9e39781c2ff7cff2ba741b6b27ce020a44a F test/tester.tcl 96db1b49157388edb57e11bf33285e3811a897e4 F test/trans.test 855337b8a178c73c433fcf8ee88e4b2f5efff0d9 -F test/unique.test ef1f67607a7109e9c0842cd8557550fb121d7ec6 +F test/unique.test 07776624b82221a80c8b4138ce0dd8b0853bb3ea F test/update.test 3cf1ca0565f678063c2dfa9a7948d2d66ae1a778 F test/vacuum.test 8acf8669f3b627e54149b25165b034aa06c2432e F test/where.test 20b19475fe894b86b06d2979592260dd16beeb17 @@ -117,7 +118,7 @@ F www/speed.tcl 83457b2bf6bb430900bd48ca3dd98264d9a916a5 F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279 F www/tclsqlite.tcl 880ef67cb4f2797b95bf1368fc4e0d8ca0fda956 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P e8595579a5218aa3f344f967a23ac52ea89daca1 -R 0e3b3ee92605e01cba0081d64199478c +P ffbdd43f5de62e7bf81631c83473aca29c3a6c98 +R 56465183b3eaba81097d58ab4fff365b U drh -Z 9f8a16e7e900ed26b3f50785c874bb79 +Z 8ca8d028861bf23d8008c82aec39ab1e diff --git a/manifest.uuid b/manifest.uuid index e6f44a8236..09d004ee09 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ffbdd43f5de62e7bf81631c83473aca29c3a6c98 \ No newline at end of file +236a54d289e858a1e0505a20d907a2a40c01b521 \ No newline at end of file diff --git a/src/build.c b/src/build.c index c4db4573de..1d0654b1ba 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 @@ -268,7 +268,7 @@ void sqliteCommitInternalChanges(sqlite *db){ } 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 @@ void sqliteRollbackInternalChanges(sqlite *db){ } 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 @@ void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName, int isTemp){ 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 @@ void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName, int isTemp){ 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); } @@ -534,6 +536,56 @@ void sqliteAddDefaultValue(Parse *pParse, Token *pVal, int minusFlag){ sqliteDequote(*pz); } +/* +** 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; iColnCol; iCol++){ + if( sqliteStrICmp(pList->a[0].zName, pTab->aCol[iCol].zName)==0 ) break; + } + } + if( iCol>=0 && iColnCol ){ + 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 @@ void sqliteCreateIndex( /* 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 0b7a9c386f..53e6e96000 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 @@ void sqliteDeleteFrom( int j; sqliteVdbeAddOp(v, OP_Recno, base, 0); for(j=0; jnColumn; 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 fc7e7f83e8..37f4d28e1b 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 @@ int sqliteExprResolveIds(Parse *pParse, IdList *pTabList, Expr *pExpr){ 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 @@ int sqliteExprResolveIds(Parse *pParse, IdList *pTabList, Expr *pExpr){ 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 a3faa2c41d..d51a5791f6 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 @@ void sqliteInsert( 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 @@ void sqliteInsert( for(j=0; jnCol; 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 @@ void sqliteInsert( } } + /* 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 @@ void sqliteInsert( 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. + */ + 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. */ - sqliteVdbeAddOp(v, OP_NewRecno, base, 0); 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; inCol; 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 @@ void sqliteInsert( 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 @@ void sqliteInsert( } for(i=0; inColumn; 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 d59c612af9..ce4efe5881 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 @@ static int sqliteOpenCb(void *pDb, int argc, char **argv, char **azColName){ 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 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){ ** 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 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){ 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 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){ 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 f2a1897fd3..525ed4d3fe 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 @@ carg ::= DEFAULT NULL. // 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 COMMA tcons. 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 03e0504d92..ef7021bd43 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" @@ -30,6 +30,11 @@ #define MAX_PAGES 100 #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 @@ -213,7 +218,8 @@ struct Column { 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 @@ struct Table { 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 ce5d5b3467..4c2bbd96b3 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 @@ void sqliteUpdate( ** 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 @@ void sqliteUpdate( goto update_cleanup; } } + chngRecno = 0; for(i=0; inExpr; i++){ if( sqliteExprResolveIds(pParse, pTabList, pChanges->a[i].pExpr) ){ goto update_cleanup; @@ -98,6 +101,10 @@ void sqliteUpdate( } for(j=0; jnCol; 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 @@ void sqliteUpdate( ** key includes one of the columns named in pChanges. */ for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - for(i=0; inColumn; i++){ - if( aXRef[pIdx->aiColumn[i]]>=0 ) break; + if( chngRecno ){ + i = 0; + }else { + for(i=0; inColumn; i++){ + if( aXRef[pIdx->aiColumn[i]]>=0 ) break; + } } if( inColumn ) nIdx++; } @@ -125,8 +136,12 @@ void sqliteUpdate( if( apIdx==0 ) goto update_cleanup; } for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - for(i=0; inColumn; i++){ - if( aXRef[pIdx->aiColumn[i]]>=0 ) break; + if( chngRecno ){ + i = 0; + }else{ + for(i=0; inColumn; i++){ + if( aXRef[pIdx->aiColumn[i]]>=0 ) break; + } } if( inColumn ) apIdx[nIdx++] = pIdx; } @@ -175,6 +190,7 @@ void sqliteUpdate( ** 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 @@ void sqliteUpdate( 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; inCol; 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 @@ void sqliteUpdate( } } + /* 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; inCol, 0); /* The KEY */ pIdx = apIdx[i]; for(j=0; jnColumn; 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 85cca27d1b..88cc6e9c37 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 @@ -1654,7 +1654,10 @@ case OP_ShiftRight: { /* 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 @@ case OP_Rollback: { 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_Rollback: { case OP_ReadCookie: { int i = ++p->tos; int aMeta[SQLITE_N_BTREE_META]; + assert( pOp->p2tos) ) 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_ReadCookie: { */ case OP_SetCookie: { int aMeta[SQLITE_N_BTREE_META]; + assert( pOp->p2p1; + 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_SetCookie: { */ case OP_VerifyCookie: { int aMeta[SQLITE_N_BTREE_META]; + assert( pOp->p2p1 ){ + if( rc==SQLITE_OK && aMeta[1+pOp->p2]!=pOp->p1 ){ sqliteSetString(pzErrMsg, "database schema has changed", 0); rc = SQLITE_SCHEMA; } @@ -2613,7 +2629,7 @@ case OP_Found: { ** ** 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 @@ case OP_NewRecno: { 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 @@ case OP_Put: { 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); } diff --git a/test/intpkey.test b/test/intpkey.test new file mode 100644 index 0000000000..9868b7326c --- /dev/null +++ b/test/intpkey.test @@ -0,0 +1,125 @@ +# 2001 September 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 implements regression tests for SQLite library. +# +# This file implements tests for the special processing associated +# with INTEGER PRIMARY KEY columns. +# +# $Id: intpkey.test,v 1.1 2001/12/21 14:30:44 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Create a table with a primary key and a datatype other than +# integer +# +do_test intpkey-1.0 { + execsql { + CREATE TABLE t1(a TEXT PRIMARY KEY, b, c); + } +} {} + +# There should be an index associated with the primary key +# +do_test intpkey-1.1 { + execsql { + SELECT name FROM sqlite_master + WHERE type='index' AND tbl_name='t1'; + } +} {{(t1 autoindex 1)}} + +# Now create a table with an integer primary key and verify that +# there is no associated index. +# +do_test intpkey-1.2 { + execsql { + DROP TABLE t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + SELECT name FROM sqlite_master + WHERE type='index' AND tbl_name='t1'; + } +} {} + +# Insert some records into the new table. Specify the primary key +# and verify that the key is used as the record number. +# +do_test intpkey-1.3 { + execsql { + INSERT INTO t1 VALUES(5,'hello','world'); + } +} {} +do_test intpkey-1.4 { + execsql { + SELECT * FROM t1; + } +} {5 hello world} +do_test intpkey-1.5 { + execsql { + SELECT rowid, * FROM t1; + } +} {5 5 hello world} + +# Attempting to insert a duplicate primary key should give a constraint +# failure. +# +do_test intpkey-1.6 { + set r [catch {execsql { + INSERT INTO t1 VALUES(5,'second','entry'); + }} msg] + lappend r $msg +} {1 {constraint failed}} +do_test intpkey-1.7 { + execsql { + SELECT rowid, * FROM t1; + } +} {5 5 hello world} +do_test intpkey-1.8 { + set r [catch {execsql { + INSERT INTO t1 VALUES(6,'second','entry'); + }} msg] + lappend r $msg +} {0 {}} +do_test intpkey-1.9 { + execsql { + SELECT rowid, * FROM t1; + } +} {5 5 hello world 6 6 second entry} + +# A ROWID is automatically generated for new records that do not specify +# the integer primary key. +# +do_test intpkey-1.10 { + execsql { + INSERT INTO t1(b,c) VALUES('one','two'); + SELECT b FROM t1 ORDER BY b; + } +} {hello one second} + +# Try to change the ROWID for the new entry. +# +do_test intpkey-1.11 { + execsql { + UPDATE t1 SET a=7 WHERE b='one'; + SELECT * FROM t1; + } +} {5 hello world 6 second entry 7 one two} + +# Make sure SELECT statements are able to use the primary key column +# as an index. +# +do_test intpkey-1.12 { + execsql { + SELECT * FROM t1 WHERE a==7; + } +} {7 one two} + + +finish_test diff --git a/test/unique.test b/test/unique.test index c41203dfa5..146ddf0285 100644 --- a/test/unique.test +++ b/test/unique.test @@ -12,7 +12,7 @@ # focus of this file is testing the CREATE UNIQUE INDEX statement, # and primary keys, and the UNIQUE constraint on table columns # -# $Id: unique.test,v 1.2 2001/09/27 23:57:06 drh Exp $ +# $Id: unique.test,v 1.3 2001/12/21 14:30:44 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -28,6 +28,15 @@ do_test unique-1.1 { c text ); } +} {1 {table "t1" has more than one primary key}} +do_test unique-1.1b { + catchsql { + CREATE TABLE t1( + a int PRIMARY KEY, + b int UNIQUE, + c text + ); + } } {0 {}} do_test unique-1.2 { catchsql {