diff --git a/manifest b/manifest index 4028bdc274..3a2855344a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Version\s2.7.1\s(CVS\s737) -D 2002-08-31T17:02:43 +C Parse\sforeign\skey\sconstraints\sand\spopulate\sinternal\sdata\sstructures\nappropriately.\s\sConstraints\sare\sstill\snot\senforced.\s(CVS\s738) +D 2002-08-31T18:53:06 F Makefile.in 420fada882179cb72ffd07313f3fd693f9f06640 F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -20,7 +20,7 @@ F spec.template 238f7db425a78dc1bb7682e56e3834c7270a3f5e F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea F src/btree.c 9e21606581a5a4a5b18ad304d7a4f433101f1538 F src/btree.h 0ca6c2631338df62e4f7894252d9347ae234eda9 -F src/build.c b367b4a839f978c0225d984e327287852835948e +F src/build.c 0116afe4f67687206364c4d1e88dc07aefc661de F src/delete.c c9f59ee217e062eb9de7b64b76b5cfff42b2f028 F src/encode.c 346b12b46148506c32038524b95c4631ab46d760 F src/expr.c ee027b908a1e157fc21644121811fa6ec1eec798 @@ -28,25 +28,25 @@ F src/func.c e45cd908b9b723d9b91473d09e12c23f786b3fc2 F src/hash.c 6a6236b89c8c060c65dabd300a1c8ce7c10edb72 F src/hash.h cd0433998bc1a3759d244e1637fe5a3c13b53bf8 F src/insert.c a2f5455009904476b43ec5304a181b505235f72f -F src/main.c 26a31201133c93b6065e11b49a83f5b987642e96 -F src/md5.c 0ae1f3e2cac92d06fc6246d1b4b8f61a2fe66d3b +F src/main.c 46d6a88070974360918cdfd1241b1906c6e189ce +F src/md5.c fe4f9c9c6f71dfc26af8da63e4d04489b1430565 F src/os.c 00d10655e1dc9a52b4aabca58c8d8e45048057b0 F src/os.h 3009379b06941e7796a9812d1b6cbc59b26248c8 F src/pager.c 4b0169e91b34f6ff91e8feb57545c43e4d6eb370 F src/pager.h 6991c9c2dc5e4c7f2df4d4ba47d1c6458f763a32 -F src/parse.y 1b180e14b6346e323bd4279469748716f412cc1c +F src/parse.y 818b03a73f6b3b8b284b515c5b1d9998d4663dc3 F src/printf.c 5c50fc1da75c8f5bf432b1ad17d91d6653acd167 F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe F src/select.c 6cd3673edbb36a8f8027341093085e01c04dd3d4 F src/shell.c 9e9a6eb6bca07f01e6472a603f908a0127ea50ff F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e F src/sqlite.h.in d3999a9c6374675779058d6cfe5431131618e92b -F src/sqliteInt.h 4d42c8685693ecf9d99edf52c9a404da2b2df7fd +F src/sqliteInt.h 62177a08d332148b1d69cd040840aac45ad86a42 F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63 -F src/tclsqlite.c c502819c209011659e1bbb428cbac5670cce7f79 -F src/test1.c 456cb080db85056be723e770435d9509afc3a83a -F src/test2.c 279057a854359665b89122070ac1fc472acce1b2 -F src/test3.c b99d5ab68ee672f1fbb00520723b5c21bac35822 +F src/tclsqlite.c e6c6de6ce41614b9ae82161ed998308070a5790d +F src/test1.c a46e9f61915b32787c5d5a05a4b92e4dacc437d9 +F src/test2.c 5fa694d130b3309e3f9c852f0a437750fcb5a006 +F src/test3.c 540fa7fc3cb3732517b779b5f90ad9cc4303d0ab F src/threadtest.c 72bce0a284647314847bbea44616ceb056bfb77f F src/tokenize.c 62c98842447effe92eba9622bb2f9a2a8a4b97ad F src/trigger.c c90a292a4bef25e478fd5deda6d300319be6a023 @@ -64,6 +64,7 @@ F test/conflict.test 0173a12a257f73bac2c9d53ad44cac9b15ea517e F test/copy.test 55d60a4d5ed342a0fa08b7cd07d46d43ea0d0d7f F test/delete.test 5821a95a66061ae09723a88938f23d10d8a881ad F test/expr.test dea1cd62684a8bf116426447c948f5e8fb2c84b6 +F test/fkey1.test 33c850201a6ec35f0b370daf4e57f44456f1b35d F test/format3.test cbb168d446152fcf1dd85be299ad2d6cd507da4e F test/func.test bed7ae7a3482df05db0f5eed2debdf25ac2d07fc F test/in.test e59461f1702b7387880bf08a0ce6bb777925d282 @@ -97,7 +98,7 @@ F test/select5.test c2a6c4a003316ee42cbbd689eebef8fdce0db2ac F test/select6.test efb8d0c07a440441db87db2c4ade6904e1407e85 F test/sort.test 876b76c5a837af5bead713146c7c65f85e84fbf5 F test/subselect.test f0fea8cf9f386d416d64d152e3c65f9116d0f50f -F test/table.test dedb4d3a73340d811e309672ca14537daa542fb1 +F test/table.test 10508e5e53fb7971b9fa6acb29d85748e545745c F test/tableapi.test 3c80421a889e1d106df16e5800fa787f0d2914a6 F test/tclsqlite.test 6f4b9760681c7dbca52a18d0ab46a1679cdc79b9 F test/temptable.test 9ed7ec0288f887e132de66d90c428ad109105f67 @@ -149,7 +150,7 @@ F www/speed.tcl a20a792738475b68756ea7a19321600f23d1d803 F www/sqlite.tcl ae3dcfb077e53833b59d4fcc94d8a12c50a44098 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P b7f788fcc4a26ae42196a209d2e94672321dc154 -R c5c569997115d1f2c07a9211654c4fdd +P 5f51e13d56a58d7c263043cae9898d796017a369 +R 5f57a541da1cc0eea6b8b3a2339bca70 U drh -Z 53b53adb384374736e4596208d092298 +Z 53eaaf70a55382e9bd46cf1caade9346 diff --git a/manifest.uuid b/manifest.uuid index dd03f92421..5fb922ae93 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5f51e13d56a58d7c263043cae9898d796017a369 \ No newline at end of file +170711ca65dc894d0486b9d575edb8f1708250fb \ No newline at end of file diff --git a/src/build.c b/src/build.c index 4caad2d34d..4c1102d8fc 100644 --- a/src/build.c +++ b/src/build.c @@ -25,7 +25,7 @@ ** ROLLBACK ** PRAGMA ** -** $Id: build.c,v 1.110 2002/08/24 18:24:53 drh Exp $ +** $Id: build.c,v 1.111 2002/08/31 18:53:06 drh Exp $ */ #include "sqliteInt.h" #include @@ -152,6 +152,7 @@ void sqliteResetInternalSchema(sqlite *db){ Hash temp1; Hash temp2; + sqliteHashClear(&db->aFKey); temp1 = db->tblHash; temp2 = db->trigHash; sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0); @@ -194,8 +195,10 @@ void sqliteCommitInternalChanges(sqlite *db){ ** Table. No changes are made to disk by this routine. ** ** This routine just deletes the data structure. It does not unlink -** the table data structure from the hash table. But it does destroy -** memory structures of the indices associated with the table. +** the table data structure from the hash table. Nor does it remove +** foreign keys from the sqlite.aFKey hash table. But it does destroy +** memory structures of the indices and foreign keys associated with +** the table. ** ** Indices associated with the table are unlinked from the "db" ** data structure if db!=NULL. If db==NULL, indices attached to @@ -205,16 +208,33 @@ void sqliteCommitInternalChanges(sqlite *db){ void sqliteDeleteTable(sqlite *db, Table *pTable){ int i; Index *pIndex, *pNext; + FKey *pFKey, *pNextFKey; + if( pTable==0 ) return; + + /* Delete all indices associated with this table + */ + for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){ + pNext = pIndex->pNext; + sqliteDeleteIndex(db, pIndex); + } + + /* Delete all foreign keys associated with this table. The keys + ** should have already been unlinked from the db->aFKey hash table + */ + for(pFKey=pTable->pFKey; pFKey; pFKey=pNextFKey){ + pNextFKey = pFKey->pNextFrom; + assert( sqliteHashFind(&db->aFKey,pFKey->zTo,strlen(pFKey->zTo)+1)!=pFKey ); + sqliteFree(pFKey); + } + + /* Delete the Table structure itself. + */ for(i=0; inCol; i++){ sqliteFree(pTable->aCol[i].zName); sqliteFree(pTable->aCol[i].zDflt); sqliteFree(pTable->aCol[i].zType); } - for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){ - pNext = pIndex->pNext; - sqliteDeleteIndex(db, pIndex); - } sqliteFree(pTable->zName); sqliteFree(pTable->aCol); sqliteSelectDelete(pTable->pSelect); @@ -223,13 +243,26 @@ void sqliteDeleteTable(sqlite *db, Table *pTable){ /* ** Unlink the given table from the hash tables and the delete the -** table structure with all its indices. +** table structure with all its indices and foreign keys. */ static void sqliteUnlinkAndDeleteTable(sqlite *db, Table *p){ Table *pOld; + FKey *pF1, *pF2; assert( db!=0 ); pOld = sqliteHashInsert(&db->tblHash, p->zName, strlen(p->zName)+1, 0); assert( pOld==0 || pOld==p ); + for(pF1=p->pFKey; pF1; pF1=pF1->pNextFrom){ + int nTo = strlen(pF1->zTo) + 1; + pF2 = sqliteHashFind(&db->aFKey, pF1->zTo, nTo); + if( pF2==pF1 ){ + sqliteHashInsert(&db->aFKey, pF1->zTo, nTo, pF1->pNextTo); + }else{ + while( pF2 && pF2->pNextTo!=pF1 ){ pF2=pF2->pNextTo; } + if( pF2 ){ + pF2->pNextTo = pF1->pNextTo; + } + } + } sqliteDeleteTable(db, p); } @@ -739,11 +772,17 @@ void sqliteEndTable(Parse *pParse, Token *pEnd, Select *pSelect){ assert( pParse->nameClash==0 || pParse->initFlag==1 ); if( pParse->explain==0 && pParse->nameClash==0 ){ Table *pOld; + FKey *pFKey; pOld = sqliteHashInsert(&db->tblHash, p->zName, strlen(p->zName)+1, p); if( pOld ){ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ return; } + for(pFKey=p->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + int nTo = strlen(pFKey->zTo) + 1; + pFKey->pNextTo = sqliteHashFind(&db->aFKey, pFKey->zTo, nTo); + sqliteHashInsert(&db->aFKey, pFKey->zTo, nTo, pFKey); + } pParse->pNewTable = 0; db->nTable++; db->flags |= SQLITE_InternChanges; @@ -1141,6 +1180,137 @@ void sqliteAddIdxKeyType(Vdbe *v, Index *pIdx){ sqliteFree(zType); } +/* +** This routine is called to create a new foreign key on the table +** currently under construction. pFromCol determines which columns +** in the current table point to the foreign key. If pFromCol==0 then +** connect the key to the last column inserted. pTo is the name of +** the table referred to. pToCol is a list of tables in the other +** pTo table that the foreign key points to. flags contains all +** information about the conflict resolution algorithms specified +** in the ON DELETE, ON UPDATE and ON INSERT clauses. +** +** An FKey structure is created and added to the table currently +** under construction in the pParse->pNewTable field. The new FKey +** is not linked into db->aFKey at this point - that does not happen +** until sqliteEndTable(). +** +** The foreign key is set for IMMEDIATE processing. A subsequent call +** to sqliteDeferForeignKey() might change this to DEFERRED. +*/ +void sqliteCreateForeignKey( + Parse *pParse, /* Parsing context */ + IdList *pFromCol, /* Columns in this table that point to other table */ + Token *pTo, /* Name of the other table */ + IdList *pToCol, /* Columns in the other table */ + int flags /* Conflict resolution algorithms. */ +){ + Table *p = pParse->pNewTable; + int nByte; + int i; + int nCol; + char *z; + FKey *pFKey = 0; + + assert( pTo!=0 ); + if( p==0 || pParse->nErr ) goto fk_end; + if( pFromCol==0 ){ + int iCol = p->nCol-1; + if( iCol<0 ) goto fk_end; + if( pToCol && pToCol->nId!=1 ){ + sqliteSetNString(&pParse->zErrMsg, "foreign key on ", -1, + p->aCol[iCol].zName, -1, + " should reference only one column of table ", -1, + pTo->z, pTo->n, 0); + pParse->nErr++; + goto fk_end; + } + nCol = 1; + }else if( pToCol && pToCol->nId!=pFromCol->nId ){ + sqliteSetString(&pParse->zErrMsg, + "number of columns in foreign key does not match the number of " + "columns in the referenced table", 0); + pParse->nErr++; + goto fk_end; + }else{ + nCol = pFromCol->nId; + } + nByte = sizeof(*pFKey) + nCol*sizeof(pFKey->aCol[0]) + pTo->n + 1; + if( pToCol ){ + for(i=0; inId; i++){ + nByte += strlen(pToCol->a[i].zName) + 1; + } + } + pFKey = sqliteMalloc( nByte ); + if( pFKey==0 ) goto fk_end; + pFKey->pFrom = p; + pFKey->pNextFrom = p->pFKey; + pFKey->zTo = z = (char*)&pFKey[1]; + memcpy(z, pTo->z, pTo->n); + z[pTo->n] = 0; + z += pTo->n+1; + pFKey->pNextTo = 0; + pFKey->nCol = nCol; + pFKey->aCol = (struct sColMap*)z; + z += sizeof(struct sColMap)*nCol; + if( pFromCol==0 ){ + pFKey->aCol[0].iFrom = p->nCol-1; + }else{ + for(i=0; inCol; j++){ + if( sqliteStrICmp(p->aCol[j].zName, pFromCol->a[i].zName)==0 ){ + pFKey->aCol[i].iFrom = j; + break; + } + } + if( j>=p->nCol ){ + sqliteSetString(&pParse->zErrMsg, "unknown column \"", + pFromCol->a[i].zName, "\" in foreign key definition", 0); + pParse->nErr++; + goto fk_end; + } + } + } + if( pToCol ){ + for(i=0; ia[i].zName); + pFKey->aCol[i].zCol = z; + memcpy(z, pToCol->a[i].zName, n); + z[n] = 0; + z += n+1; + } + } + pFKey->isDeferred = 0; + pFKey->deleteConf = flags & 0xff; + pFKey->updateConf = (flags >> 8 ) & 0xff; + pFKey->insertConf = (flags >> 16 ) & 0xff; + + /* Link the foreign key to the table as the last step. + */ + p->pFKey = pFKey; + pFKey = 0; + +fk_end: + sqliteFree(pFKey); + sqliteIdListDelete(pFromCol); + sqliteIdListDelete(pToCol); +} + +/* +** This routine is called when an INITIALLY IMMEDIATE or INITIALLY DEFERRED +** clause is seen as part of a foreign key definition. The isDeferred +** parameter is 1 for INITIALLY DEFERRED and 0 for INITIALLY IMMEDIATE. +** The behavior of the most recently created foreign key is adjusted +** accordingly. +*/ +void sqliteDeferForeignKey(Parse *pParse, int isDeferred){ + Table *pTab; + FKey *pFKey; + if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return; + pFKey->isDeferred = isDeferred; +} + /* ** 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 diff --git a/src/main.c b/src/main.c index 849ec55e3b..4fa80f6238 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.99 2002/08/29 23:59:48 drh Exp $ +** $Id: main.c,v 1.100 2002/08/31 18:53:06 drh Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -354,6 +354,7 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){ sqliteHashInit(&db->idxHash, SQLITE_HASH_STRING, 0); sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0); sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1); + sqliteHashInit(&db->aFKey, SQLITE_HASH_STRING, 1); sqliteRegisterBuiltinFunctions(db); db->onError = OE_Default; db->priorNewRowid = 0; @@ -466,6 +467,7 @@ void sqlite_close(sqlite *db){ } } sqliteHashClear(&db->aFunc); + sqliteHashClear(&db->aFKey); sqliteFree(db); } diff --git a/src/md5.c b/src/md5.c index 1fd70167b6..a22f6d2dd6 100644 --- a/src/md5.c +++ b/src/md5.c @@ -293,7 +293,7 @@ static void DigestToBase16(unsigned char *digest, char *zBuf){ ** A TCL command for md5. The argument is the text to be hashed. The ** Result is the hash in base64. */ -static int md5_cmd(ClientData cd, Tcl_Interp *interp, int argc, char **argv){ +static int md5_cmd(void*cd, Tcl_Interp *interp, int argc, const char **argv){ MD5Context ctx; unsigned char digest[16]; @@ -313,7 +313,7 @@ static int md5_cmd(ClientData cd, Tcl_Interp *interp, int argc, char **argv){ ** A TCL command to take the md5 hash of a file. The argument is the ** name of the file. */ -static int md5file_cmd(ClientData cd, Tcl_Interp*interp, int argc, char **argv){ +static int md5file_cmd(void*cd, Tcl_Interp*interp, int argc, const char **argv){ FILE *in; MD5Context ctx; unsigned char digest[16]; @@ -347,8 +347,8 @@ static int md5file_cmd(ClientData cd, Tcl_Interp*interp, int argc, char **argv){ ** Register the two TCL commands above with the TCL interpreter. */ int Md5_Init(Tcl_Interp *interp){ - Tcl_CreateCommand(interp, "md5", md5_cmd, 0, 0); - Tcl_CreateCommand(interp, "md5file", md5file_cmd, 0, 0); + Tcl_CreateCommand(interp, "md5", (Tcl_CmdProc*)md5_cmd, 0, 0); + Tcl_CreateCommand(interp, "md5file", (Tcl_CmdProc*)md5file_cmd, 0, 0); return TCL_OK; } diff --git a/src/parse.y b/src/parse.y index 061d165874..5a4242b18a 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.82 2002/08/24 18:24:54 drh Exp $ +** @(#) $Id: parse.y,v 1.83 2002/08/31 18:53:07 drh Exp $ */ %token_prefix TK_ %token_type {Token} @@ -169,31 +169,38 @@ ccons ::= NOT NULL onconf(R). {sqliteAddNotNull(pParse, R);} ccons ::= PRIMARY KEY sortorder onconf(R). {sqliteAddPrimaryKey(pParse,0,R);} ccons ::= UNIQUE onconf(R). {sqliteCreateIndex(pParse,0,0,0,R,0,0);} ccons ::= CHECK LP expr RP onconf. -ccons ::= references. -ccons ::= defer_subclause. +ccons ::= REFERENCES nm(T) idxlist_opt(TA) refargs(R). + {sqliteCreateForeignKey(pParse,0,&T,TA,R);} +ccons ::= defer_subclause(D). {sqliteDeferForeignKey(pParse,D);} ccons ::= COLLATE id(C). { sqliteAddCollateType(pParse, sqliteCollateType(pParse, &C)); } -// A REFERENCES clause is parsed but the current implementation does not -// do anything with it. +// The next group of rules parses the arguments to a REFERENCES clause +// that determine if the referential integrity checking is deferred or +// or immediate and which determine what action to take if a ref-integ +// check fails. // -references ::= REFERENCES nm LP idxlist RP refargs. -references ::= REFERENCES nm refargs. -refargs ::= . -refargs ::= refargs refarg. -refarg ::= MATCH nm. -refarg ::= ON DELETE refact. -refarg ::= ON UPDATE refact. -refact ::= SET NULL. -refact ::= SET DEFAULT. -refact ::= CASCADE. -refact ::= RESTRICT. -defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt. -defer_subclause ::= DEFERRABLE init_deferred_pred_opt. -init_deferred_pred_opt ::= . -init_deferred_pred_opt ::= INITIALLY DEFERRED. -init_deferred_pred_opt ::= INITIALLY IMMEDIATE. +%type refargs {int} +refargs(A) ::= . { A = OE_Restrict * 0x010101; } +refargs(A) ::= refargs(X) refarg(Y). { A = (X & Y.mask) | Y.value; } +%type refarg {struct {int value; int mask;}} +refarg(A) ::= MATCH nm. { A.value = 0; A.mask = 0x000000; } +refarg(A) ::= ON DELETE refact(X). { A.value = X; A.mask = 0x0000ff; } +refarg(A) ::= ON UPDATE refact(X). { A.value = X<<8; A.mask = 0x00ff00; } +refarg(A) ::= ON INSERT refact(X). { A.value = X<<16; A.mask = 0xff0000; } +%type refact {int} +refact(A) ::= SET NULL. { A = OE_SetNull; } +refact(A) ::= SET DEFAULT. { A = OE_SetDflt; } +refact(A) ::= CASCADE. { A = OE_Cascade; } +refact(A) ::= RESTRICT. { A = OE_Restrict; } +%type defer_subclause {int} +defer_subclause(A) ::= NOT DEFERRABLE init_deferred_pred_opt(X). {A = X;} +defer_subclause(A) ::= DEFERRABLE init_deferred_pred_opt(X). {A = X;} +%type init_deferred_pred_opt {int} +init_deferred_pred_opt(A) ::= . {A = 0;} +init_deferred_pred_opt(A) ::= INITIALLY DEFERRED. {A = 1;} +init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE. {A = 0;} // For the time being, the only constraint we care about is the primary // key and UNIQUE. Both create indices. @@ -209,9 +216,14 @@ tcons ::= PRIMARY KEY LP idxlist(X) RP onconf(R). tcons ::= UNIQUE LP idxlist(X) RP onconf(R). {sqliteCreateIndex(pParse,0,0,X,R,0,0);} tcons ::= CHECK expr onconf. -tcons ::= FOREIGN KEY LP idxlist RP references defer_subclause_opt. -defer_subclause_opt ::= . -defer_subclause_opt ::= defer_subclause. +tcons ::= FOREIGN KEY LP idxlist(FA) RP + REFERENCES nm(T) idxlist_opt(TA) refargs(R) defer_subclause_opt(D). { + sqliteCreateForeignKey(pParse, FA, &T, TA, R); + sqliteDeferForeignKey(pParse, D); +} +%type defer_subclause_opt {int} +defer_subclause_opt(A) ::= . {A = 0;} +defer_subclause_opt(A) ::= defer_subclause(X). {A = X;} // The following is a non-standard extension that allows us to declare the // default behavior when there is a constraint conflict. @@ -677,13 +689,15 @@ uniqueflag(A) ::= . { A = OE_None; } %type idxlist {IdList*} %destructor idxlist {sqliteIdListDelete($$);} +%type idxlist_opt {IdList*} +%destructor idxlist_opt {sqliteIdListDelete($$);} %type idxitem {Token} -idxlist(A) ::= idxlist(X) COMMA idxitem(Y). - {A = sqliteIdListAppend(X,&Y);} -idxlist(A) ::= idxitem(Y). - {A = sqliteIdListAppend(0,&Y);} -idxitem(A) ::= nm(X). {A = X;} +idxlist_opt(A) ::= . {A = 0;} +idxlist_opt(A) ::= LP idxlist(X) RP. {A = X;} +idxlist(A) ::= idxlist(X) COMMA idxitem(Y). {A = sqliteIdListAppend(X,&Y);} +idxlist(A) ::= idxitem(Y). {A = sqliteIdListAppend(0,&Y);} +idxitem(A) ::= nm(X). {A = X;} ///////////////////////////// The DROP INDEX command ///////////////////////// // diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 3ab867c603..6b40c87684 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.144 2002/08/28 03:00:59 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.145 2002/08/31 18:53:07 drh Exp $ */ #include "sqlite.h" #include "hash.h" @@ -175,6 +175,7 @@ typedef struct FuncDef FuncDef; typedef struct Trigger Trigger; typedef struct TriggerStep TriggerStep; typedef struct TriggerStack TriggerStack; +typedef struct FKey FKey; /* ** Each database is an instance of the following structure. @@ -206,6 +207,7 @@ struct sqlite { Hash idxHash; /* All (named) indices indexed by name */ Hash trigHash; /* All triggers indexed by name */ Hash aFunc; /* All functions that can be in SQL exprs */ + Hash aFKey; /* Foreign keys indexed by to-table */ int lastRowid; /* ROWID of most recent insert */ int priorNewRowid; /* Last randomly generated ROWID */ int onError; /* Default conflict algorithm */ @@ -330,10 +332,52 @@ struct Table { u8 hasPrimKey; /* True if there exists a primary key */ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ Trigger *pTrigger; /* List of SQL triggers on this table */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ }; /* -** SQLite supports 5 different ways to resolve a contraint +** Each foreign key constraint is an instance of the following structure. +** +** A foreign key is associated with two tables. The "from" table is +** the table that contains the REFERENCES clause that creates the foreign +** key. The "to" table is the table that is named in the REFERENCES clause. +** Consider this example: +** +** CREATE TABLE ex1( +** a INTEGER PRIMARY KEY, +** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) +** ); +** +** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** +** Each REFERENCES clause generates an instance of the following structure +** which is attached to the from-table. The to-table need not exist when +** the from-table is created. The existance of the to-table is not checked +** until an attempt is made to insert data into the from-table. +** +** The sqlite.aFKey hash table stores pointers to to this structure +** given the name of a to-table. For each to-table, all foreign keys +** associated with that table are on a linked list using the FKey.pNextTo +** field. +*/ +struct FKey { + Table *pFrom; /* The table that constains the REFERENCES clause */ + FKey *pNextFrom; /* Next foreign key in pFrom */ + char *zTo; /* Name of table that the key points to */ + FKey *pNextTo; /* Next foreign key that points to zTo */ + int nCol; /* Number of columns in this key */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ + } *aCol; /* One entry for each of nCol column s */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 updateConf; /* How to resolve conflicts that occur on UPDATE */ + u8 deleteConf; /* How to resolve conflicts that occur on DELETE */ + u8 insertConf; /* How to resolve conflicts that occur on INSERT */ +}; + +/* +** SQLite supports many different ways to resolve a contraint ** error. ROLLBACK processing means that a constraint violation ** causes the operation in process to fail and for the current transaction ** to be rolled back. ABORT processing means the operation in process @@ -346,6 +390,13 @@ struct Table { ** is returned. REPLACE means that preexisting database rows that caused ** a UNIQUE constraint violation are removed so that the new insert or ** update can proceed. Processing continues and no error is reported. +** +** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. +** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the +** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign +** key is set to NULL. CASCADE means that a DELETE or UPDATE of the +** referenced table row is propagated into the row that holds the +** foreign key. ** ** The following there symbolic values are used to record which type ** of action to take. @@ -356,7 +407,13 @@ struct Table { #define OE_Fail 3 /* Stop the operation but leave all prior changes */ #define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */ #define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */ -#define OE_Default 9 /* Do whatever the default action is */ + +#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ +#define OE_SetNull 7 /* Set the foreign key value to NULL */ +#define OE_SetDflt 8 /* Set the foreign key value to its default */ +#define OE_Cascade 9 /* Cascade the changes */ + +#define OE_Default 99 /* Do whatever the default action is */ /* ** Each SQL index is represented in memory by an @@ -947,3 +1004,5 @@ TriggerStep *sqliteTriggerUpdateStep(Token*, ExprList*, Expr*, int); TriggerStep *sqliteTriggerDeleteStep(Token*, Expr*); void sqliteDeleteTrigger(Trigger*); int sqliteJoinType(Parse*, Token*, Token*, Token*); +void sqliteCreateForeignKey(Parse*, IdList*, Token*, IdList*, int); +void sqliteDeferForeignKey(Parse*, int); diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 466a78941d..ed27499ee5 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -11,7 +11,7 @@ ************************************************************************* ** A TCL Interface to SQLite ** -** $Id: tclsqlite.c,v 1.39 2002/07/15 20:58:48 drh Exp $ +** $Id: tclsqlite.c,v 1.40 2002/08/31 18:53:08 drh Exp $ */ #ifndef NO_TCL /* Omit this whole file if TCL is unavailable */ @@ -643,13 +643,13 @@ static int DbMain(void *cd, Tcl_Interp *interp, int argc, char **argv){ */ int Sqlite_Init(Tcl_Interp *interp){ Tcl_InitStubs(interp, "8.0", 0); - Tcl_CreateCommand(interp, "sqlite", DbMain, 0, 0); + Tcl_CreateCommand(interp, "sqlite", (Tcl_CmdProc*)DbMain, 0, 0); Tcl_PkgProvide(interp, "sqlite", "2.0"); return TCL_OK; } int Tclsqlite_Init(Tcl_Interp *interp){ Tcl_InitStubs(interp, "8.0", 0); - Tcl_CreateCommand(interp, "sqlite", DbMain, 0, 0); + Tcl_CreateCommand(interp, "sqlite", (Tcl_CmdProc*)DbMain, 0, 0); Tcl_PkgProvide(interp, "sqlite", "2.0"); return TCL_OK; } diff --git a/src/test1.c b/src/test1.c index 6bb76a7ee2..dda71544ea 100644 --- a/src/test1.c +++ b/src/test1.c @@ -13,7 +13,7 @@ ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test1.c,v 1.12 2002/07/10 21:26:01 drh Exp $ +** $Id: test1.c,v 1.13 2002/08/31 18:53:08 drh Exp $ */ #include "sqliteInt.h" #include "tcl.h" @@ -238,7 +238,7 @@ static void sqliteExecFunc(sqlite_func *context, int argc, const char **argv){ ** sqlite_create_function function while a query is in progress in order ** to test the SQLITE_MISUSE detection logic. */ -static int sqlite_test_create_function( +static int test_create_function( void *NotUsed, Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ int argc, /* Number of arguments */ @@ -288,7 +288,7 @@ static void countFinalize(sqlite_func *context){ ** sqlite_create_aggregate function while a query is in progress in order ** to test the SQLITE_MISUSE detection logic. */ -static int sqlite_test_create_aggregate( +static int test_create_aggregate( void *NotUsed, Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ int argc, /* Number of arguments */ @@ -484,7 +484,7 @@ static void testFunc(sqlite_func *context, int argc, const char **argv){ ** ** Register the test SQL function on the database DB under the name NAME. */ -static int sqlite_register_test_function( +static int test_register_func( void *NotUsed, Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ int argc, /* Number of arguments */ @@ -511,27 +511,33 @@ static int sqlite_register_test_function( */ int Sqlitetest1_Init(Tcl_Interp *interp){ extern int sqlite_search_count; - Tcl_CreateCommand(interp, "sqlite_mprintf_int", sqlite_mprintf_int, 0, 0); - Tcl_CreateCommand(interp, "sqlite_mprintf_str", sqlite_mprintf_str, 0, 0); - Tcl_CreateCommand(interp, "sqlite_mprintf_double", sqlite_mprintf_double,0,0); - Tcl_CreateCommand(interp, "sqlite_open", sqlite_test_open, 0, 0); - Tcl_CreateCommand(interp, "sqlite_last_insert_rowid", test_last_rowid, 0, 0); - Tcl_CreateCommand(interp, "sqlite_exec_printf", test_exec_printf, 0, 0); - Tcl_CreateCommand(interp, "sqlite_get_table_printf", test_get_table_printf, - 0, 0); - Tcl_CreateCommand(interp, "sqlite_close", sqlite_test_close, 0, 0); - Tcl_CreateCommand(interp, "sqlite_create_function", - sqlite_test_create_function, 0, 0); - Tcl_CreateCommand(interp, "sqlite_create_aggregate", - sqlite_test_create_aggregate, 0, 0); - Tcl_CreateCommand(interp, "sqlite_register_test_function", - sqlite_register_test_function, 0, 0); + static struct { + char *zName; + Tcl_CmdProc *xProc; + } aCmd[] = { + { "sqlite_mprintf_int", (Tcl_CmdProc*)sqlite_mprintf_int }, + { "sqlite_mprintf_str", (Tcl_CmdProc*)sqlite_mprintf_str }, + { "sqlite_mprintf_double", (Tcl_CmdProc*)sqlite_mprintf_double }, + { "sqlite_open", (Tcl_CmdProc*)sqlite_test_open }, + { "sqlite_last_insert_rowid", (Tcl_CmdProc*)test_last_rowid }, + { "sqlite_exec_printf", (Tcl_CmdProc*)test_exec_printf }, + { "sqlite_get_table_printf", (Tcl_CmdProc*)test_get_table_printf }, + { "sqlite_close", (Tcl_CmdProc*)sqlite_test_close }, + { "sqlite_create_function", (Tcl_CmdProc*)test_create_function }, + { "sqlite_create_aggregate", (Tcl_CmdProc*)test_create_aggregate }, + { "sqlite_register_test_function", (Tcl_CmdProc*)test_register_func }, + { "sqlite_abort", (Tcl_CmdProc*)sqlite_abort }, +#ifdef MEMORY_DEBUG + { "sqlite_malloc_fail", (Tcl_CmdProc*)sqlite_malloc_fail }, + { "sqlite_malloc_stat", (Tcl_CmdProc*)sqlite_malloc_stat }, +#endif + }; + int i; + + for(i=0; i