From adbca9cfdef3522651863ffbf29d48d4014101d0 Mon Sep 17 00:00:00 2001 From: drh Date: Thu, 27 Sep 2001 15:11:53 +0000 Subject: [PATCH] Fixed the support of UNIQUE and PRIMARY KEY. (CVS 268) FossilOrigin-Name: 116fdad06868acf6aca9e75c2c3497c0511a42c3 --- manifest | 31 ++++---- manifest.uuid | 2 +- src/build.c | 187 +++++++++++++++++++++-------------------------- src/main.c | 80 ++++++++++---------- src/parse.y | 10 +-- src/shell.c | 3 +- src/sqliteInt.h | 3 +- src/vdbe.c | 118 +++++++++++++++--------------- src/vdbe.h | 7 +- test/index.test | 10 ++- test/table.test | 4 +- test/tester.tcl | 11 ++- test/unique.test | 33 +++++++++ 13 files changed, 261 insertions(+), 238 deletions(-) create mode 100644 test/unique.test diff --git a/manifest b/manifest index f36f581880..173f3c521c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Added\sbasic\ssupport\sfor\senforcement\sof\sUNIQUE\son\sindices\sand\sprimary\nkeys.\s\sSupport\sfor\saddition\sconstraints\sis\sto\sfollow.\s(CVS\s267) -D 2001-09-27T03:22:33 +C Fixed\sthe\ssupport\sof\sUNIQUE\sand\sPRIMARY\sKEY.\s(CVS\s268) +D 2001-09-27T15:11:54 F Makefile.in fe9d96d6a7b04b3000a24692c2a3761840bbbf97 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958 F VERSION 17fadc361fb942d644f92116388409c937c9fa79 @@ -10,26 +10,26 @@ F doc/report1.txt a031aaf37b185e4fa540223cb516d3bccec7eeac F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6 F src/btree.c a4a88dfef2072cedfdac09f3a51b7d70b017b9b4 F src/btree.h 57d653ef5137b91f2a068aaf71a2905468dd2cb7 -F src/build.c 501c96f8224f1dd6c87c53c1ca7de62c3a98d5bb +F src/build.c 64a7325c1471087ada81588d6ace6d294b28cd1d F src/delete.c 81002d889aae874decf507627207c5d1b3599dc2 F src/expr.c 343a515a4abaf60e9e26c7412aa8c43fd3eae97d F src/hash.c bf36fb4cba114015123b0050f137d2c4553778a1 F src/hash.h 5f6e7c04c46ed015ab4e01797c2049b4af5b006d F src/insert.c 0552c2a4b5fd359e9ed5d1e314d5e89093802c8e -F src/main.c 00ff61d82189ad23fe2f2e6c355951f514cb1b5c +F src/main.c 87cabd64c99af66ba95f06a7dcd870eec65d89af F src/md5.c 52f677bfc590e09f71d07d7e327bd59da738d07c F src/os.c 45376582c41dc8829330816d56b8e9e6cd1b7972 F src/os.h 0f478e2fef5ec1612f94b59b163d4807d4c77d6d F src/pager.c 0fe02b63a89d8eebb42ad30529d0c7cc918ecb94 F src/pager.h a0d4c5ae271914aa07b62aee0707997d6932b6ca -F src/parse.y a136e0a24ce434e52c79a7eaa669cf9c8249e4db +F src/parse.y 7a61488cb52da8b3da094aadb391b42d59a25602 F src/printf.c b1e22a47be8cdf707815647239991e08e8cb69f9 F src/random.c 708a23f69f40d6f2ae5ce1a04e6a4055d4a6ecec F src/select.c 7d90a6464906419fde96c0707a4cf4f3280db318 -F src/shell.c 8e573138074e0b9526fca59b3eac22bdf18ecc03 +F src/shell.c 977ec6b6479c8b8b6e05323d5026a6f4bd73aa54 F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e F src/sqlite.h.in 08151912b382ded315b5c8fc6288d9d7a9332aa4 -F src/sqliteInt.h 35f42e624f11924a56a5501e30a670e1b92f62bb +F src/sqliteInt.h 3ead85324704b79b2ae6799d6af3e5fd710756d9 F src/table.c abd0adbe0fee39d995287b3bcccd908d174dfcac F src/tclsqlite.c 04a35d04f06046acc3944121dc6c36717f7f36d5 F src/test1.c e4b31f62ea71963cbae44338acf477a04fc8fc49 @@ -38,8 +38,8 @@ F src/test3.c 4a0d7b882fdae731dbb759f512ad867122452f96 F src/tokenize.c 2ab07b85fde38d8fa2b4e73417b93e94f9cf8f5f F src/update.c 0449af173b5f2f0b26e2f0e4545ee0e0429763cb F src/util.c 4da3be37d0fd3c640d2d3033503768afdc8e5387 -F src/vdbe.c d3cf685bd9c823445a4b4a57f7cb01718169f464 -F src/vdbe.h dc1d441494ba560a1ff464e1c56beb8ca03844fc +F src/vdbe.c 173892798e1698605fafb24d29e26e3a5644ddf5 +F src/vdbe.h c543a58f52fb654c90dd31d0d0c31309f4d838de F src/where.c cce952b6a2459ac2296e3432876a4252d2fe3b87 F test/all.test a2320eb40b462f25bd3e33115b1cabf3791450dd F test/bigrow.test a35f2de9948b24e427fb292c35947795efe182d0 @@ -50,7 +50,7 @@ F test/delete.test 5ebb114582457428b3e0e30b21b477fedcb85609 F test/expr.test b3475005ea19d53bf8c4573fb6e4a4498be5b434 F test/func.test dfee65686b8ba06071c2f007243a25c96ce82cf2 F test/in.test 9323681388be301dc73f370b4cd62c5a33f79d1e -F test/index.test e43e952b482c2afe938f1f31b71e2b33d43893a9 +F test/index.test 6076f29d09a4f26a2efa38b03b8cc338b8662f0e F test/insert.test a5c122aa726f1cef6f07d6767e8fd6f220994c11 F test/insert2.test 252d7130d8cc20f649b31a4f503cd87e660abda8 F test/lock.test a9641cdc282214563a2fb0233735b09cc2fdd8f2 @@ -69,11 +69,12 @@ F test/select4.test 29a2ffb187f3d8b6ca42a0a6b619e9cabe12e228 F test/select5.test 00a240e3311b6c4ff0f27a252ad33811211fa8d8 F test/sort.test 462c1161eee1abaa7cc93990e0b34d5fdb70ce19 F test/subselect.test 335d3dad8d585726c447dfee8d9c4f7383c76b78 -F test/table.test 52fdca1632580fb638c7b7dd14f4d37ecc09f994 +F test/table.test 3ef4254d62ece31a3872ab11cdaec846f6fa8fd1 F test/tableapi.test 162840153191a91a7dce6395f2334f9aef713b37 F test/tclsqlite.test a57bb478d7e9f0b2c927f92e161f391e2896631a -F test/tester.tcl 957cd92fe8645b829da175d94b7ddb7ea68dac39 +F test/tester.tcl c7ddeebc14cc841abb37134cd5d40c1e3ad367c1 F test/trans.test 855337b8a178c73c433fcf8ee88e4b2f5efff0d9 +F test/unique.test 23056f0705755bc503ff543e79b79a5c91d35850 F test/update.test b320ea22899e80b32b4d21c54591eb7a6ba4d6bd F test/vacuum.test 8acf8669f3b627e54149b25165b034aa06c2432e F test/where.test 43d5ac94da3f3722375307f948884dc79b326a91 @@ -99,7 +100,7 @@ F www/speed.tcl 91b53f9403a62bb322dc1f85a81531309bcfb41c F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f F www/tclsqlite.tcl 13d50723f583888fc80ae1a38247c0ab415066fa F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad -P 0e9cfcd53e16f96fc181def1d0b2d0ea7f7df73f -R d903e186383659ad85dc50397b26ad49 +P 34c42967f3d52dfb65d9f31db4f6995d098ec1f7 +R 738ff05ebc1672d0941631329247a92e U drh -Z 4633481d5649b488b3df47e886de0708 +Z b9a4ce432fac1a7969450da73881d616 diff --git a/manifest.uuid b/manifest.uuid index 01640a3521..35300e0adc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -34c42967f3d52dfb65d9f31db4f6995d098ec1f7 \ No newline at end of file +116fdad06868acf6aca9e75c2c3497c0511a42c3 \ No newline at end of file diff --git a/src/build.c b/src/build.c index 84e612ac41..70f0c7e2d3 100644 --- a/src/build.c +++ b/src/build.c @@ -25,7 +25,7 @@ ** ROLLBACK ** PRAGMA ** -** $Id: build.c,v 1.42 2001/09/27 03:22:33 drh Exp $ +** $Id: build.c,v 1.43 2001/09/27 15:11:54 drh Exp $ */ #include "sqliteInt.h" #include @@ -343,6 +343,7 @@ void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName){ Table *pTable; char *zName; sqlite *db = pParse->db; + Vdbe *v; pParse->sFirstToken = *pStart; zName = sqliteTableNameFromToken(pName); @@ -370,13 +371,13 @@ void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName){ pTable->pIndex = 0; if( pParse->pNewTable ) sqliteDeleteTable(db, pParse->pNewTable); pParse->pNewTable = pTable; - if( !pParse->initFlag && (db->flags & SQLITE_InTrans)==0 ){ - Vdbe *v = sqliteGetVdbe(pParse); - if( v ){ + if( !pParse->initFlag && (v = sqliteGetVdbe(pParse))!=0 ){ + if( (db->flags & SQLITE_InTrans)==0 ){ sqliteVdbeAddOp(v, OP_Transaction, 0, 0, 0, 0); sqliteVdbeAddOp(v, OP_VerifyCookie, db->schema_cookie, 0, 0, 0); pParse->schemaVerified = 1; } + sqliteVdbeAddOp(v, OP_OpenWrite, 0, 2, MASTER_NAME, 0); } } @@ -491,47 +492,28 @@ void sqliteEndTable(Parse *pParse, Token *pEnd){ */ if( pParse->initFlag ){ p->tnum = pParse->newTnum; - if( p->pIndex ){ - p->pIndex->tnum = pParse->newKnum; - } } /* If not initializing, then create a record for the new table ** in the SQLITE_MASTER table of the database. */ if( !pParse->initFlag ){ - int n, base; + int n, addr; Vdbe *v; v = sqliteGetVdbe(pParse); if( v==0 ) return; n = (int)pEnd->z - (int)pParse->sFirstToken.z + 1; - sqliteVdbeAddOp(v, OP_OpenWrite, 0, 2, MASTER_NAME, 0); sqliteVdbeAddOp(v, OP_NewRecno, 0, 0, 0, 0); sqliteVdbeAddOp(v, OP_String, 0, 0, "table", 0); sqliteVdbeAddOp(v, OP_String, 0, 0, p->zName, 0); sqliteVdbeAddOp(v, OP_String, 0, 0, p->zName, 0); - sqliteVdbeAddOp(v, OP_CreateTable, 0, 0, 0, 0); - sqliteVdbeTableRootAddr(v, &p->tnum); - if( p->pIndex ){ - /* If the table has a primary key, create an index in the database - ** for that key and record the root page of the index in the "knum" - ** column of of the SQLITE_MASTER table. - */ - Index *pIndex = p->pIndex; - assert( pIndex->pNext==0 ); - assert( pIndex->tnum==0 ); - sqliteVdbeAddOp(v, OP_CreateIndex, 0, 0, 0, 0), - sqliteVdbeIndexRootAddr(v, &pIndex->tnum); - }else{ - /* If the table does not have a primary key, the "knum" column is - ** fill with a NULL value. - */ - sqliteVdbeAddOp(v, OP_Null, 0, 0, 0, 0); - } - base = sqliteVdbeAddOp(v, OP_String, 0, 0, 0, 0); - sqliteVdbeChangeP3(v, base, pParse->sFirstToken.z, n); - sqliteVdbeAddOp(v, OP_MakeRecord, 6, 0, 0, 0); + addr = sqliteVdbeAddOp(v, OP_CreateTable, 0, 0, 0, 0); + sqliteVdbeChangeP3(v, addr, (char *)&p->tnum, -1); + p->tnum = 0; + addr = sqliteVdbeAddOp(v, OP_String, 0, 0, 0, 0); + sqliteVdbeChangeP3(v, addr, pParse->sFirstToken.z, n); + sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0, 0, 0); sqliteVdbeAddOp(v, OP_Put, 0, 0, 0, 0); changeCookie(db); sqliteVdbeAddOp(v, OP_SetCookie, db->next_cookie, 0, 0, 0); @@ -634,11 +616,13 @@ void sqliteDropTable(Parse *pParse, Token *pName){ /* ** Create a new index for an SQL table. pIndex is the name of the index ** and pTable is the name of the table that is to be indexed. Both will -** be NULL for a primary key. In that case, use pParse->pNewTable as the -** table to be indexed. +** be NULL for a primary key or an index that is created to satisfy a +** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable +** as the table to be indexed. ** ** pList is a list of columns to be indexed. pList will be NULL if the -** most recently added column of the table is labeled as the primary key. +** most recently added column of the table is the primary key or has +** the UNIQUE constraint. */ void sqliteCreateIndex( Parse *pParse, /* All information about this parse */ @@ -679,8 +663,8 @@ void sqliteCreateIndex( /* ** Find the name of the index. Make sure there is not already another ** index or table with the same name. If pName==0 it means that we are - ** dealing with a primary key, which has no name, so this step can be - ** skipped. + ** dealing with a primary key or UNIQUE constraint. We have to invent our + ** own name. */ if( pName ){ zName = sqliteTableNameFromToken(pName); @@ -698,8 +682,13 @@ void sqliteCreateIndex( goto exit_create_index; } }else{ + char zBuf[30]; + int n; + Index *pLoop; + for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){} + sprintf(zBuf,"%d)",n); zName = 0; - sqliteSetString(&zName, pTab->zName, " (primary key)", 0); + sqliteSetString(&zName, "(", pTab->zName, " autoindex ", zBuf, 0); if( zName==0 ) goto exit_create_index; } @@ -746,16 +735,12 @@ void sqliteCreateIndex( } /* Link the new Index structure to its table and to the other - ** in-memory database structures. Note that primary key indices - ** do not appear in the index hash table. + ** in-memory database structures. */ - if( pParse->explain==0 ){ - if( pName!=0 ){ - char *zName = pIndex->zName;; - sqliteHashInsert(&db->idxHash, zName, strlen(zName)+1, pIndex); - } - pIndex->pNext = pTab->pIndex; - pTab->pIndex = pIndex; + pIndex->pNext = pTab->pIndex; + pTab->pIndex = pIndex; + if( !pParse->explain ){ + sqliteHashInsert(&db->idxHash, pIndex->zName, strlen(zName)+1, pIndex); db->flags |= SQLITE_InternChanges; } @@ -763,7 +748,7 @@ void sqliteCreateIndex( ** "sqlite_master" table on the disk. So do not write to the disk ** again. Extract the table number from the pParse->newTnum field. */ - if( pParse->initFlag ){ + if( pParse->initFlag && pTable!=0 ){ pIndex->tnum = pParse->newTnum; } @@ -778,79 +763,75 @@ void sqliteCreateIndex( ** we don't want to recreate it. ** ** If pTable==0 it means this index is generated as a primary key - ** and those does not have a CREATE INDEX statement to add to the - ** master table. Also, since primary keys are created at the same - ** time as tables, the table will be empty so there is no need to - ** initialize the index. Hence, skip all the code generation if - ** pTable==0. + ** or UNIQUE constraint of a CREATE TABLE statement. The code generator + ** for CREATE TABLE will have already opened cursor 0 for writing to + ** the sqlite_master table and will take care of closing that cursor + ** for us in the end. So those steps are skipped when pTable==0 */ - else if( pParse->initFlag==0 && pTable!=0 ){ - static VdbeOp addTable[] = { - { OP_OpenWrite, 2, 2, MASTER_NAME}, - { OP_NewRecno, 2, 0, 0}, - { OP_String, 0, 0, "index"}, - { OP_String, 0, 0, 0}, /* 3 */ - { OP_String, 0, 0, 0}, /* 4 */ - { OP_CreateIndex, 1, 0, 0}, - { OP_Dup, 0, 0, 0}, - { OP_OpenWrite, 1, 0, 0}, /* 7 */ - { OP_Null, 0, 0, 0}, - { OP_String, 0, 0, 0}, /* 9 */ - { OP_MakeRecord, 6, 0, 0}, - { OP_Put, 2, 0, 0}, - { OP_SetCookie, 0, 0, 0}, /* 12 */ - { OP_Close, 2, 0, 0}, - }; + else if( pParse->initFlag==0 ){ int n; - Vdbe *v = pParse->pVdbe; + Vdbe *v; int lbl1, lbl2; int i; + int addr; v = sqliteGetVdbe(pParse); if( v==0 ) goto exit_create_index; - if( pTable!=0 && (db->flags & SQLITE_InTrans)==0 ){ - sqliteVdbeAddOp(v, OP_Transaction, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_VerifyCookie, db->schema_cookie, 0, 0, 0); - pParse->schemaVerified = 1; + if( pTable!=0 ){ + if( (db->flags & SQLITE_InTrans)==0 ){ + sqliteVdbeAddOp(v, OP_Transaction, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_VerifyCookie, db->schema_cookie, 0, 0, 0); + pParse->schemaVerified = 1; + } + sqliteVdbeAddOp(v, OP_OpenWrite, 0, 2, MASTER_NAME, 0); + } + sqliteVdbeAddOp(v, OP_NewRecno, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_String, 0, 0, "index", 0); + sqliteVdbeAddOp(v, OP_String, 0, 0, pIndex->zName, 0); + sqliteVdbeAddOp(v, OP_String, 0, 0, pTab->zName, 0); + addr = sqliteVdbeAddOp(v, OP_CreateIndex, 0, 0, 0, 0); + sqliteVdbeChangeP3(v, addr, (char*)&pIndex->tnum, -1); + pIndex->tnum = 0; + if( pTable ){ + sqliteVdbeAddOp(v, OP_Dup, 0, 0, 0, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, 1, 0, 0, 0); } if( pStart && pEnd ){ - int base; n = (int)pEnd->z - (int)pStart->z + 1; - base = sqliteVdbeAddOpList(v, ArraySize(addTable), addTable); - sqliteVdbeChangeP3(v, base+3, pIndex->zName, 0); - sqliteVdbeChangeP3(v, base+4, pTab->zName, 0); - sqliteVdbeIndexRootAddr(v, &pIndex->tnum); - sqliteVdbeChangeP3(v, base+7, pIndex->zName, 0); - sqliteVdbeChangeP3(v, base+9, pStart->z, n); - changeCookie(db); - sqliteVdbeChangeP1(v, base+12, db->next_cookie); + addr = sqliteVdbeAddOp(v, OP_String, 0, 0, "", 0); + sqliteVdbeChangeP3(v, addr, pStart->z, n); + }else{ + sqliteVdbeAddOp(v, OP_Null, 0, 0, 0, 0); } - sqliteVdbeAddOp(v, OP_Open, 0, pTab->tnum, pTab->zName, 0); - lbl1 = sqliteVdbeMakeLabel(v); - lbl2 = sqliteVdbeMakeLabel(v); - sqliteVdbeAddOp(v, OP_Rewind, 0, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Next, 0, lbl2, 0, lbl1); - sqliteVdbeAddOp(v, OP_Recno, 0, 0, 0, 0); - for(i=0; inColumn; i++){ - sqliteVdbeAddOp(v, OP_Column, 0, pIndex->aiColumn[i], 0, 0); + sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Put, 0, 0, 0, 0); + if( pTable ){ + sqliteVdbeAddOp(v, OP_Open, 2, pTab->tnum, pTab->zName, 0); + lbl1 = sqliteVdbeMakeLabel(v); + lbl2 = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_Rewind, 2, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Next, 2, lbl2, 0, lbl1); + sqliteVdbeAddOp(v, OP_Recno, 2, 0, 0, 0); + for(i=0; inColumn; i++){ + sqliteVdbeAddOp(v, OP_Column, 2, pIndex->aiColumn[i], 0, 0); + } + sqliteVdbeAddOp(v, OP_MakeIdxKey, pIndex->nColumn, 0, 0, 0); + sqliteVdbeAddOp(v, OP_PutIdx, 1, pIndex->isUnique, 0, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, lbl1, 0, 0); + sqliteVdbeAddOp(v, OP_Noop, 0, 0, 0, lbl2); + sqliteVdbeAddOp(v, OP_Close, 2, 0, 0, 0); } - sqliteVdbeAddOp(v, OP_MakeIdxKey, pIndex->nColumn, 0, 0, 0); - sqliteVdbeAddOp(v, OP_PutIdx, 1, pIndex->isUnique, 0, 0); - sqliteVdbeAddOp(v, OP_Goto, 0, lbl1, 0, 0); - sqliteVdbeAddOp(v, OP_Noop, 0, 0, 0, lbl2); sqliteVdbeAddOp(v, OP_Close, 1, 0, 0, 0); - sqliteVdbeAddOp(v, OP_Close, 0, 0, 0, 0); - if( pTable!=0 && (db->flags & SQLITE_InTrans)==0 ){ - sqliteVdbeAddOp(v, OP_Commit, 0, 0, 0, 0); + if( pTable!=0 ){ + changeCookie(db); + sqliteVdbeAddOp(v, OP_SetCookie, db->next_cookie, 0, 0, 0); + sqliteVdbeAddOp(v, OP_Close, 0, 0, 0, 0); + if( (db->flags & SQLITE_InTrans)==0 ){ + sqliteVdbeAddOp(v, OP_Commit, 0, 0, 0, 0); + } } } - /* Reclaim memory on an EXPLAIN call. - */ - if( pParse->explain ){ - sqliteFree(pIndex); - } - /* Clean up before exiting */ exit_create_index: sqliteIdListDelete(pList); diff --git a/src/main.c b/src/main.c index bfdbaf9ee9..5fcc32cccc 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.41 2001/09/23 20:17:55 drh Exp $ +** $Id: main.c,v 1.42 2001/09/27 15:11:54 drh Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -26,8 +26,7 @@ ** argv[0] = "meta" 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] = root page number of primary key for tables or NULL. -** argv[4] = SQL create statement for the table or index +** argv[3] = SQL create statement for the table or index ** */ static int sqliteOpenCb(void *pDb, int argc, char **argv, char **azColName){ @@ -38,25 +37,33 @@ static int sqliteOpenCb(void *pDb, int argc, char **argv, char **azColName){ /* TODO: Do some validity checks on all fields. In particular, ** make sure fields do not contain NULLs. */ - assert( argc==5 ); + assert( argc==4 ); switch( argv[0][0] ){ case 'm': { /* Meta information */ if( strcmp(argv[1],"file-format")==0 ){ - db->file_format = atoi(argv[4]); + db->file_format = atoi(argv[3]); }else if( strcmp(argv[1],"schema-cookie")==0 ){ - db->schema_cookie = atoi(argv[4]); + db->schema_cookie = atoi(argv[3]); db->next_cookie = db->schema_cookie; } break; } case 'i': case 't': { /* CREATE TABLE and CREATE INDEX statements */ - memset(&sParse, 0, sizeof(sParse)); - sParse.db = db; - sParse.initFlag = 1; - sParse.newTnum = atoi(argv[2]); - sParse.newKnum = argv[3] ? atoi(argv[3]) : 0; - nErr = sqliteRunParser(&sParse, argv[4], 0); + if( argv[3] && argv[3][0] ){ + memset(&sParse, 0, sizeof(sParse)); + sParse.db = db; + sParse.initFlag = 1; + sParse.newTnum = atoi(argv[2]); + nErr = sqliteRunParser(&sParse, argv[3], 0); + }else{ + Index *pIndex = sqliteFindIndex(db, argv[1]); + if( pIndex==0 || pIndex->tnum!=0 ){ + nErr++; + }else{ + pIndex->tnum = atoi(argv[2]); + } + } break; } default: { @@ -92,8 +99,7 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){ " type text,\n" " name text,\n" " tbl_name text,\n" - " tnum integer,\n" - " knum integer,\n" + " rootpage integer,\n" " sql text\n" ")" ; @@ -107,8 +113,7 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){ ** type text, -- Either "table" or "index" or "meta" ** name text, -- Name of table or index ** tbl_name text, -- Associated table - ** tnum integer, -- The integer page number of root page - ** knum integer, -- Root page of primary key, or NULL + ** rootpage integer, -- The integer page number of root page ** sql text -- The CREATE statement for this object ** ); ** @@ -136,47 +141,43 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){ static VdbeOp initProg[] = { { OP_Open, 0, 2, 0}, { OP_Rewind, 0, 0, 0}, - { OP_Next, 0, 13, 0}, /* 2 */ + { OP_Next, 0, 12, 0}, /* 2 */ { OP_Column, 0, 0, 0}, { OP_String, 0, 0, "meta"}, { OP_Ne, 0, 2, 0}, { OP_Column, 0, 0, 0}, { OP_Column, 0, 1, 0}, { OP_Column, 0, 3, 0}, - { OP_Null, 0, 0, 0}, - { OP_Column, 0, 5, 0}, - { OP_Callback, 5, 0, 0}, + { OP_Column, 0, 4, 0}, + { OP_Callback, 4, 0, 0}, { OP_Goto, 0, 2, 0}, - { OP_Rewind, 0, 0, 0}, /* 13 */ - { OP_Next, 0, 25, 0}, /* 14 */ + { OP_Rewind, 0, 0, 0}, /* 12 */ + { OP_Next, 0, 23, 0}, /* 13 */ { OP_Column, 0, 0, 0}, { OP_String, 0, 0, "table"}, - { OP_Ne, 0, 14, 0}, + { OP_Ne, 0, 13, 0}, { OP_Column, 0, 0, 0}, { OP_Column, 0, 1, 0}, { OP_Column, 0, 3, 0}, { OP_Column, 0, 4, 0}, - { OP_Column, 0, 5, 0}, - { OP_Callback, 5, 0, 0}, - { OP_Goto, 0, 14, 0}, - { OP_Rewind, 0, 0, 0}, /* 25 */ - { OP_Next, 0, 37, 0}, /* 26 */ + { OP_Callback, 4, 0, 0}, + { OP_Goto, 0, 13, 0}, + { OP_Rewind, 0, 0, 0}, /* 23 */ + { OP_Next, 0, 34, 0}, /* 24 */ { OP_Column, 0, 0, 0}, { OP_String, 0, 0, "index"}, - { OP_Ne, 0, 26, 0}, + { OP_Ne, 0, 24, 0}, { OP_Column, 0, 0, 0}, { OP_Column, 0, 1, 0}, { OP_Column, 0, 3, 0}, - { OP_Null, 0, 0, 0}, - { OP_Column, 0, 5, 0}, - { OP_Callback, 5, 0, 0}, - { OP_Goto, 0, 26, 0}, - { OP_String, 0, 0, "meta"}, /* 37 */ + { OP_Column, 0, 4, 0}, + { OP_Callback, 4, 0, 0}, + { OP_Goto, 0, 24, 0}, + { OP_String, 0, 0, "meta"}, /* 34 */ { OP_String, 0, 0, "schema-cookie"}, { OP_Null, 0, 0, 0}, - { OP_Null, 0, 0, 0}, { OP_ReadCookie,0,0, 0}, - { OP_Callback, 5, 0, 0}, + { OP_Callback, 4, 0, 0}, { OP_Close, 0, 0, 0}, { OP_Halt, 0, 0, 0}, }; @@ -203,10 +204,9 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){ azArg[0] = "table"; azArg[1] = MASTER_NAME; azArg[2] = "2"; - azArg[3] = 0; - azArg[4] = master_schema; - azArg[5] = 0; - sqliteOpenCb(db, 5, azArg, 0); + azArg[3] = master_schema; + azArg[4] = 0; + sqliteOpenCb(db, 4, azArg, 0); pTab = sqliteFindTable(db, MASTER_NAME); if( pTab ){ pTab->readOnly = 1; diff --git a/src/parse.y b/src/parse.y index e07054ca58..b5519895de 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.31 2001/09/27 03:22:33 drh Exp $ +** @(#) $Id: parse.y,v 1.32 2001/09/27 15:11:54 drh Exp $ */ %token_prefix TK_ %token_type {Token} @@ -125,7 +125,7 @@ carg ::= DEFAULT NULL. // ccons ::= NOT NULL. ccons ::= PRIMARY KEY sortorder. {sqliteCreateIndex(pParse,0,0,0,1,0,0);} -ccons ::= UNIQUE. +ccons ::= UNIQUE. {sqliteCreateIndex(pParse,0,0,0,1,0,0);} ccons ::= CHECK LP expr RP. // For the time being, the only constraint we care about is the primary @@ -138,10 +138,10 @@ conslist ::= conslist tcons. conslist ::= tcons. tcons ::= CONSTRAINT ids. tcons ::= PRIMARY KEY LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,1,0,0);} -tcons ::= UNIQUE LP idlist RP. +tcons ::= UNIQUE LP idxlist(X) RP. {sqliteCreateIndex(pParse,0,0,X,1,0,0);} tcons ::= CHECK expr. -idlist ::= idlist COMMA ids. -idlist ::= ids. +// idlist ::= idlist COMMA ids. +// idlist ::= ids. // The next command format is dropping tables. // diff --git a/src/shell.c b/src/shell.c index 59c5f6a0c5..e93f284e47 100644 --- a/src/shell.c +++ b/src/shell.c @@ -12,7 +12,7 @@ ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. ** -** $Id: shell.c,v 1.33 2001/09/16 00:13:27 drh Exp $ +** $Id: shell.c,v 1.34 2001/09/27 15:11:54 drh Exp $ */ #include #include @@ -648,6 +648,7 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){ " type text,\n" " name text,\n" " tbl_name text,\n" + " rootpage integer,\n" " sql text\n" ")"; new_argv[1] = 0; diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 40323e4de8..5d1d7438fb 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.55 2001/09/27 03:22:33 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.56 2001/09/27 15:11:54 drh Exp $ */ #include "sqlite.h" #include "hash.h" @@ -444,6 +444,7 @@ void sqliteExprCode(Parse*, Expr*); void sqliteExprIfTrue(Parse*, Expr*, int); void sqliteExprIfFalse(Parse*, Expr*, int); Table *sqliteFindTable(sqlite*,char*); +Index *sqliteFindIndex(sqlite*,char*); void sqliteCopy(Parse*, Token*, Token*, Token*); void sqliteVacuum(Parse*, Token*); int sqliteGlobCompare(const unsigned char*,const unsigned char*); diff --git a/src/vdbe.c b/src/vdbe.c index 49a1eb55cd..546b7bf86f 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.78 2001/09/27 03:22:34 drh Exp $ +** $Id: vdbe.c,v 1.79 2001/09/27 15:11:54 drh Exp $ */ #include "sqliteInt.h" #include @@ -202,8 +202,6 @@ struct Vdbe { Agg agg; /* Aggregate information */ int nSet; /* Number of sets allocated */ Set *aSet; /* An array of sets */ - int *pTableRoot; /* Write root page no. for new tables to this addr */ - int *pIndexRoot; /* Write root page no. for new indices to this addr */ int nFetch; /* Number of OP_Fetch instructions executed */ }; @@ -226,24 +224,6 @@ void sqliteVdbeTrace(Vdbe *p, FILE *trace){ p->trace = trace; } -/* -** Cause the next OP_CreateTable or OP_CreateIndex instruction that executes -** to write the page number of the root page for the new table or index it -** creates into the memory location *pAddr. -** -** The pointer to the place to write the page number is cleared after -** the OP_Create* statement. If OP_Create* is executed and the pointer -** is NULL, an error results. Hence the address can only be used once. -** If the root address fields are set but OP_Create* operations never -** execute, that too is an error. -*/ -void sqliteVdbeTableRootAddr(Vdbe *p, int *pAddr){ - p->pTableRoot = pAddr; -} -void sqliteVdbeIndexRootAddr(Vdbe *p, int *pAddr){ - p->pIndexRoot = pAddr; -} - /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. @@ -288,6 +268,7 @@ int sqliteVdbeAddOp(Vdbe *p, int op, int p1, int p2, const char *p3, int lbl){ p->aOp[i].p2 = p2; if( p3 && p3[0] ){ p->aOp[i].p3 = sqliteStrDup(p3); + p->aOp[i].p3dyn = 1; }else{ p->aOp[i].p3 = 0; } @@ -367,10 +348,28 @@ void sqliteVdbeChangeP1(Vdbe *p, int addr, int val){ ** This routine is useful when a large program is loaded from a ** static array using sqliteVdbeAddOpList but we want to make a ** few minor changes to the program. +** +** If n>=0 then the P3 operand is dynamic, meaning that a copy of +** the string is made into memory obtained from sqliteMalloc(). +** A value of n==0 means copy bytes of zP3 up to and including the +** first null byte. If n>0 then copy n+1 bytes of zP3. +** +** If n<0 then zP3 is assumed to be a pointer to a static string. +** P3 is made to point directly to this string without any copying. */ -void sqliteVdbeChangeP3(Vdbe *p, int addr, const char *zP3, int n){ +void sqliteVdbeChangeP3(Vdbe *p, int addr, char *zP3, int n){ if( p && addr>=0 && p->nOp>addr && zP3 ){ - sqliteSetNString(&p->aOp[addr].p3, zP3, n, 0); + Op *pOp = &p->aOp[addr]; + if( pOp->p3 && pOp->p3dyn ){ + sqliteFree(pOp->p3); + } + if( n<0 ){ + pOp->p3 = zP3; + pOp->p3dyn = 0; + }else{ + sqliteSetNString(&p->aOp[addr].p3, zP3, n, 0); + pOp->p3dyn = 1; + } } } @@ -384,10 +383,15 @@ void sqliteVdbeChangeP3(Vdbe *p, int addr, const char *zP3, int n){ ** resolve to be a single actual quote character within the string. */ void sqliteVdbeDequoteP3(Vdbe *p, int addr){ - char *z; + Op *pOp; if( addr<0 || addr>=p->nOp ) return; - z = p->aOp[addr].p3; - if( z ) sqliteDequote(z); + pOp = &p->aOp[addr]; + if( pOp->p3==0 || pOp->p3[0]==0 ) return; + if( !pOp->p3dyn ){ + pOp->p3 = sqliteStrDup(pOp->p3); + pOp->p3dyn = 1; + } + if( pOp->p3 ) sqliteDequote(pOp->p3); } /* @@ -398,8 +402,14 @@ void sqliteVdbeDequoteP3(Vdbe *p, int addr){ void sqliteVdbeCompressSpace(Vdbe *p, int addr){ char *z; int i, j; + Op *pOp; if( addr<0 || addr>=p->nOp ) return; - z = p->aOp[addr].p3; + pOp = &p->aOp[addr]; + if( !pOp->p3dyn ){ + pOp->p3 = sqliteStrDup(pOp->p3); + pOp->p3dyn = 1; + } + z = pOp->p3; if( z==0 ) return; i = j = 0; while( isspace(z[i]) ){ i++; } @@ -747,8 +757,6 @@ static void Cleanup(Vdbe *p){ sqliteFree(p->aSet); p->aSet = 0; p->nSet = 0; - p->pTableRoot = 0; - p->pIndexRoot = 0; } /* @@ -763,7 +771,9 @@ void sqliteVdbeDelete(Vdbe *p){ p->nOp = 0; } for(i=0; inOp; i++){ - sqliteFree(p->aOp[i].p3); + if( p->aOp[i].p3dyn ){ + sqliteFree(p->aOp[i].p3); + } } sqliteFree(p->aOp); sqliteFree(p->aLabel); @@ -2618,7 +2628,7 @@ case OP_BeginIdx: { if( Stringify(p, tos) ) goto no_mem; if( pCrsr->zKey ) sqliteFree(pCrsr->zKey); pCrsr->nKey = aStack[tos].n; - pCrsr->zKey = sqliteMalloc( pCrsr->nKey ); + pCrsr->zKey = sqliteMalloc( pCrsr->nKey+1 ); if( pCrsr->zKey==0 ) goto no_mem; memcpy(pCrsr->zKey, zStack[tos], aStack[tos].n); pCrsr->zKey[aStack[tos].n] = 0; @@ -2774,17 +2784,15 @@ case OP_Clear: { break; } -/* Opcode: CreateTable * * * +/* Opcode: CreateTable * * P3 ** ** Allocate a new table in the main database file. Push the page number ** for the root page of the new table onto the stack. ** -** The root page number is also written to a memory location which has -** be set up by the parser. The difference between CreateTable and -** CreateIndex is that each writes its root page number into a different -** memory location. This writing of the page number into a memory location -** is used by the SQL parser to record the page number in its internal -** data structures. +** The root page number is also written to a memory location that P3 +** points to. This is the mechanism is used to write the root page +** number into the parser's internal data structures that describe the +** new table. ** ** See also: CreateIndex */ @@ -2792,31 +2800,26 @@ case OP_CreateTable: { int i = ++p->tos; int pgno; VERIFY( if( NeedStack(p, p->tos) ) goto no_mem; ) - if( p->pTableRoot==0 ){ - rc = SQLITE_INTERNAL; - goto abort_due_to_error; - } + assert( pOp->p3!=0 && pOp->p3dyn==0 ); rc = sqliteBtreeCreateTable(pBt, &pgno); if( rc==SQLITE_OK ){ aStack[i].i = pgno; aStack[i].flags = STK_Int; - *p->pTableRoot = pgno; - p->pTableRoot = 0; + *(u32*)pOp->p3 = pgno; + pOp->p3 = 0; } break; } -/* Opcode: CreateIndex P1 * * +/* Opcode: CreateIndex * * P3 ** ** Allocate a new Index in the main database file. Push the page number ** for the root page of the new table onto the stack. ** -** The root page number is also written to a memory location which has -** be set up by the parser. The difference between CreateTable and -** CreateIndex is that each writes its root page number into a different -** memory location. This writing of the page number into a memory location -** is used by the SQL parser to record the page number in its internal -** data structures. +** The root page number is also written to a memory location that P3 +** points to. This is the mechanism is used to write the root page +** number into the parser's internal data structures that describe the +** new index. ** ** See also: CreateTable */ @@ -2824,16 +2827,13 @@ case OP_CreateIndex: { int i = ++p->tos; int pgno; VERIFY( if( NeedStack(p, p->tos) ) goto no_mem; ) - if( p->pIndexRoot==0 ){ - rc = SQLITE_INTERNAL; - goto abort_due_to_error; - } + assert( pOp->p3!=0 && pOp->p3dyn==0 ); rc = sqliteBtreeCreateTable(pBt, &pgno); if( rc==SQLITE_OK ){ aStack[i].i = pgno; aStack[i].flags = STK_Int; - *p->pIndexRoot = pgno; - p->pIndexRoot = 0; + *(u32*)pOp->p3 = pgno; + pOp->p3 = 0; } break; } @@ -3865,10 +3865,6 @@ default: { cleanup: Cleanup(p); - if( (p->pTableRoot || p->pIndexRoot) && rc==SQLITE_OK ){ - rc = SQLITE_INTERNAL; - sqliteSetString(pzErrMsg, "table or index root page not set", 0); - } if( rc!=SQLITE_OK ){ closeAllCursors(p); sqliteBtreeRollback(pBt); diff --git a/src/vdbe.h b/src/vdbe.h index c1975bd172..211d6ede5b 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -15,7 +15,7 @@ ** or VDBE. The VDBE implements an abstract machine that runs a ** simple program to access and modify the underlying database. ** -** $Id: vdbe.h,v 1.24 2001/09/23 02:35:53 drh Exp $ +** $Id: vdbe.h,v 1.25 2001/09/27 15:11:55 drh Exp $ */ #ifndef _SQLITE_VDBE_H_ #define _SQLITE_VDBE_H_ @@ -38,6 +38,7 @@ struct VdbeOp { int p1; /* First operand */ int p2; /* Second parameter (often the jump destination) */ char *p3; /* Third parameter */ + int p3dyn; /* True if p3 is malloced. False if it is static */ }; typedef struct VdbeOp VdbeOp; @@ -187,12 +188,10 @@ typedef struct VdbeOp VdbeOp; */ Vdbe *sqliteVdbeCreate(sqlite*); void sqliteVdbeCreateCallback(Vdbe*, int*); -void sqliteVdbeTableRootAddr(Vdbe*, int*); -void sqliteVdbeIndexRootAddr(Vdbe*, int*); int sqliteVdbeAddOp(Vdbe*,int,int,int,const char*,int); int sqliteVdbeAddOpList(Vdbe*, int nOp, VdbeOp const *aOp); void sqliteVdbeChangeP1(Vdbe*, int addr, int P1); -void sqliteVdbeChangeP3(Vdbe*, int addr, const char *zP1, int N); +void sqliteVdbeChangeP3(Vdbe*, int addr, char *zP1, int N); void sqliteVdbeDequoteP3(Vdbe*, int addr); int sqliteVdbeMakeLabel(Vdbe*); void sqliteVdbeDelete(Vdbe*); diff --git a/test/index.test b/test/index.test index b0d385bd15..f61d42de25 100644 --- a/test/index.test +++ b/test/index.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing the CREATE INDEX statement. # -# $Id: index.test,v 1.13 2001/09/17 20:25:58 drh Exp $ +# $Id: index.test,v 1.14 2001/09/27 15:11:55 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -203,9 +203,11 @@ do_test index-7.2 { execsql {SELECT f1 FROM test1 WHERE f2=65536} } {16} do_test index-7.3 { - set code [execsql {EXPLAIN SELECT f1 FROM test1 WHERE f2=65536}] - expr {[lsearch $code {test1 (primary key)}]>0} -} {1} + execsql { + SELECT name FROM sqlite_master + WHERE type='index' AND tbl_name='test1' + } +} {{(test1 autoindex 1)}} do_test index-7.4 { execsql {DROP table test1} execsql {SELECT name FROM sqlite_master WHERE type!='meta'} diff --git a/test/table.test b/test/table.test index 5e06e64b71..9c41c1fcc9 100644 --- a/test/table.test +++ b/test/table.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing the CREATE TABLE statement. # -# $Id: table.test,v 1.12 2001/09/16 00:13:28 drh Exp $ +# $Id: table.test,v 1.13 2001/09/27 15:11:55 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -163,7 +163,7 @@ set big_table \ )} do_test table-3.1 { execsql $big_table - execsql {SELECT sql FROM sqlite_master WHERE type!='meta'} + execsql {SELECT sql FROM sqlite_master WHERE type=='table'} } \{$big_table\} do_test table-3.2 { set v [catch {execsql {CREATE TABLE BIG(xyz foo)}} msg] diff --git a/test/tester.tcl b/test/tester.tcl index 18e887040f..582c3c00db 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -11,7 +11,7 @@ # This file implements some common TCL routines used for regression # testing the SQLite library # -# $Id: tester.tcl,v 1.19 2001/09/20 01:44:43 drh Exp $ +# $Id: tester.tcl,v 1.20 2001/09/27 15:11:55 drh Exp $ # Make sure tclsqlite was compiled correctly. Abort now with an # error message if not. @@ -174,6 +174,15 @@ proc execsql {sql {db db}} { return [$db eval $sql] } +# Execute SQL and catch exceptions. +# +proc catchsql {sql {db db}} { + # puts "SQL = $sql" + set r [catch {$db eval $sql} msg] + lappend r $msg + return $r +} + # Another procedure to execute SQL. This one includes the field # names in the returned list. # diff --git a/test/unique.test b/test/unique.test new file mode 100644 index 0000000000..e2eb697892 --- /dev/null +++ b/test/unique.test @@ -0,0 +1,33 @@ +# 2001 September 27 +# +# 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 file is testing the CREATE UNIQUE INDEX statement, +# and primary keys, and the UNIQUE constraint on table columns +# +# $Id: unique.test,v 1.1 2001/09/27 15:11:55 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Try to create a table with two primary keys. +# (This is allowed in SQLite even that it is not valid SQL) +# +do_test unique-1.1 { + catchsql { + CREATE TABLE t1( + a int PRIMARY KEY, + b int PRIMARY KEY, + c text + ); + } +} {0 {}} + +finish_test