]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Parse foreign key constraints and populate internal data structures
authordrh <drh@noemail.net>
Sat, 31 Aug 2002 18:53:06 +0000 (18:53 +0000)
committerdrh <drh@noemail.net>
Sat, 31 Aug 2002 18:53:06 +0000 (18:53 +0000)
appropriately.  Constraints are still not enforced. (CVS 738)

FossilOrigin-Name: 170711ca65dc894d0486b9d575edb8f1708250fb

13 files changed:
manifest
manifest.uuid
src/build.c
src/main.c
src/md5.c
src/parse.y
src/sqliteInt.h
src/tclsqlite.c
src/test1.c
src/test2.c
src/test3.c
test/fkey1.test [new file with mode: 0644]
test/table.test

index 4028bdc274545dc163e862fe185a1fce38e3c289..3a2855344ab53a2079c07473284b9a372fa0b762 100644 (file)
--- 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
index dd03f92421befaff21d97eefac01a7c2f24054dd..5fb922ae936e377ba9b87638f80f60314f3da1d7 100644 (file)
@@ -1 +1 @@
-5f51e13d56a58d7c263043cae9898d796017a369
\ No newline at end of file
+170711ca65dc894d0486b9d575edb8f1708250fb
\ No newline at end of file
index 4caad2d34da36be351c2c6d5cb46d2ba57f10e8f..4c1102d8fcb4a8434bcb4ccb4f206fed0c7da34a 100644 (file)
@@ -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 <ctype.h>
@@ -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; i<pTable->nCol; 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; i<pToCol->nId; 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; i<nCol; i++){
+      int j;
+      for(j=0; j<p->nCol; 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; i<nCol; i++){
+      int n = strlen(pToCol->a[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 
index 849ec55e3b16f6f5ae36e9c660ad05e08db716b6..4fa80f623827d45f8fde2597b4a2c973525d1fe0 100644 (file)
@@ -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);
 }
 
index 1fd70167b60c4a18d58465686475452d9138e578..a22f6d2dd6513f409e0e1bdb0cbdcb853efca640 100644 (file)
--- 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;
 }
 
index 061d1658744970fb1c9404f2ba50c19409b200f1..5a4242b18a3ae986e00b90bb533598881f086d90 100644 (file)
@@ -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 /////////////////////////
 //
index 3ab867c6038863d7613742bc5cdcc7045e1ebdae..6b40c87684a58a342da544f55811db28381a8929 100644 (file)
@@ -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);
index 466a78941da05ffb5782ee647bc036be64a3fa61..ed27499ee5343592b44b63626c32e42134e04f5c 100644 (file)
@@ -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;
 }
index 6bb76a7ee261a8242e2afd1e197262d780cf19ff..dda71544ea017ddf2e3778a3940c1e823be04f12 100644 (file)
@@ -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);
-  Tcl_LinkVar(interp, "sqlite_search_count", 
-      (char*)&sqlite_search_count, TCL_LINK_INT);
+  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
-  Tcl_CreateCommand(interp, "sqlite_malloc_fail", sqlite_malloc_fail, 0, 0);
-  Tcl_CreateCommand(interp, "sqlite_malloc_stat", sqlite_malloc_stat, 0, 0);
+     { "sqlite_malloc_fail",             (Tcl_CmdProc*)sqlite_malloc_fail    },
+     { "sqlite_malloc_stat",             (Tcl_CmdProc*)sqlite_malloc_stat    },
 #endif
-  Tcl_CreateCommand(interp, "sqlite_abort", sqlite_abort, 0, 0);
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+  Tcl_LinkVar(interp, "sqlite_search_count", 
+      (char*)&sqlite_search_count, TCL_LINK_INT);
   return TCL_OK;
 }
index fc7eb0cef9a64b2b8ce1903ba0c9d509ccda88a0..b583d2a83458110b01b98ee33f65a704856d9846 100644 (file)
@@ -13,7 +13,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test2.c,v 1.9 2002/08/12 12:29:57 drh Exp $
+** $Id: test2.c,v 1.10 2002/08/31 18:53:08 drh Exp $
 */
 #include "sqliteInt.h"
 #include "pager.h"
@@ -62,7 +62,7 @@ static int pager_open(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int nPage;
@@ -93,7 +93,7 @@ static int pager_close(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int rc;
@@ -120,7 +120,7 @@ static int pager_rollback(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int rc;
@@ -147,7 +147,7 @@ static int pager_commit(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int rc;
@@ -174,7 +174,7 @@ static int pager_ckpt_begin(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int rc;
@@ -201,7 +201,7 @@ static int pager_ckpt_rollback(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int rc;
@@ -228,7 +228,7 @@ static int pager_ckpt_commit(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int rc;
@@ -255,7 +255,7 @@ static int pager_stats(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   int i, *a;
@@ -288,7 +288,7 @@ static int pager_pagecount(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   char zBuf[100];
@@ -312,7 +312,7 @@ static int page_get(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   char zBuf[100];
@@ -346,7 +346,7 @@ static int page_lookup(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Pager *pPager;
   char zBuf[100];
@@ -376,7 +376,7 @@ static int page_unref(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   void *pPage;
   int rc;
@@ -403,7 +403,7 @@ static int page_read(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   char zBuf[100];
   void *pPage;
@@ -427,7 +427,7 @@ static int page_number(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   char zBuf[100];
   void *pPage;
@@ -451,7 +451,7 @@ static int page_write(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   void *pPage;
   int rc;
@@ -476,21 +476,30 @@ static int page_write(
 */
 int Sqlitetest2_Init(Tcl_Interp *interp){
   extern int sqlite_io_error_pending;
-  Tcl_CreateCommand(interp, "pager_open", pager_open, 0, 0);
-  Tcl_CreateCommand(interp, "pager_close", pager_close, 0, 0);
-  Tcl_CreateCommand(interp, "pager_commit", pager_commit, 0, 0);
-  Tcl_CreateCommand(interp, "pager_rollback", pager_rollback, 0, 0);
-  Tcl_CreateCommand(interp, "pager_ckpt_begin", pager_ckpt_begin, 0, 0);
-  Tcl_CreateCommand(interp, "pager_ckpt_commit", pager_ckpt_commit, 0, 0);
-  Tcl_CreateCommand(interp, "pager_ckpt_rollback", pager_ckpt_rollback, 0, 0);
-  Tcl_CreateCommand(interp, "pager_stats", pager_stats, 0, 0);
-  Tcl_CreateCommand(interp, "pager_pagecount", pager_pagecount, 0, 0);
-  Tcl_CreateCommand(interp, "page_get", page_get, 0, 0);
-  Tcl_CreateCommand(interp, "page_lookup", page_lookup, 0, 0);
-  Tcl_CreateCommand(interp, "page_unref", page_unref, 0, 0);
-  Tcl_CreateCommand(interp, "page_read", page_read, 0, 0);
-  Tcl_CreateCommand(interp, "page_write", page_write, 0, 0);
-  Tcl_CreateCommand(interp, "page_number", page_number, 0, 0);
+  static struct {
+    char *zName;
+    Tcl_CmdProc *xProc;
+  } aCmd[] = {
+    { "pager_open",              (Tcl_CmdProc*)pager_open          },
+    { "pager_close",             (Tcl_CmdProc*)pager_close         },
+    { "pager_commit",            (Tcl_CmdProc*)pager_commit        },
+    { "pager_rollback",          (Tcl_CmdProc*)pager_rollback      },
+    { "pager_ckpt_begin",        (Tcl_CmdProc*)pager_ckpt_begin    },
+    { "pager_ckpt_commit",       (Tcl_CmdProc*)pager_ckpt_commit   },
+    { "pager_ckpt_rollback",     (Tcl_CmdProc*)pager_ckpt_rollback },
+    { "pager_stats",             (Tcl_CmdProc*)pager_stats         },
+    { "pager_pagecount",         (Tcl_CmdProc*)pager_pagecount     },
+    { "page_get",                (Tcl_CmdProc*)page_get            },
+    { "page_lookup",             (Tcl_CmdProc*)page_lookup         },
+    { "page_unref",              (Tcl_CmdProc*)page_unref          },
+    { "page_read",               (Tcl_CmdProc*)page_read           },
+    { "page_write",              (Tcl_CmdProc*)page_write          },
+    { "page_number",             (Tcl_CmdProc*)page_number         },
+  };
+  int i;
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
   Tcl_LinkVar(interp, "sqlite_io_error_pending",
      (char*)&sqlite_io_error_pending, TCL_LINK_INT);
 #ifdef SQLITE_TEST
index 9402a01b25ea0a6719625251e09fd4c4431d96dc..e918a10b5a39a97be2f95c0437f29c6d85a7f5ea 100644 (file)
@@ -13,7 +13,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test3.c,v 1.18 2002/08/11 20:10:48 drh Exp $
+** $Id: test3.c,v 1.19 2002/08/31 18:53:08 drh Exp $
 */
 #include "sqliteInt.h"
 #include "pager.h"
@@ -57,7 +57,7 @@ static int btree_open(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc;
@@ -89,7 +89,7 @@ static int btree_close(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc;
@@ -116,7 +116,7 @@ static int btree_begin_transaction(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc;
@@ -143,7 +143,7 @@ static int btree_rollback(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc;
@@ -170,7 +170,7 @@ static int btree_commit(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc;
@@ -197,7 +197,7 @@ static int btree_create_table(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc, iTable;
@@ -227,7 +227,7 @@ static int btree_drop_table(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int iTable;
@@ -256,7 +256,7 @@ static int btree_clear_table(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int iTable;
@@ -285,7 +285,7 @@ static int btree_get_meta(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc;
@@ -319,7 +319,7 @@ static int btree_update_meta(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int rc;
@@ -354,7 +354,7 @@ static int btree_page_dump(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int iPage;
@@ -384,7 +384,7 @@ static int btree_tree_dump(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int iPage;
@@ -414,7 +414,7 @@ static int btree_pager_stats(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int i;
@@ -449,7 +449,7 @@ static int btree_pager_ref_dump(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
 
@@ -474,7 +474,7 @@ static int btree_integrity_check(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   char *zResult;
@@ -510,7 +510,7 @@ static int btree_cursor(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   Btree *pBt;
   int iTable;
@@ -546,7 +546,7 @@ static int btree_close_cursor(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -574,7 +574,7 @@ static int btree_move_to(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -608,7 +608,7 @@ static int btree_delete(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -637,7 +637,7 @@ static int btree_insert(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -666,7 +666,7 @@ static int btree_next(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -698,7 +698,7 @@ static int btree_first(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -730,7 +730,7 @@ static int btree_key(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -768,7 +768,7 @@ static int btree_data(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -806,7 +806,7 @@ static int btree_payload_size(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int n1, n2;
@@ -845,7 +845,7 @@ static int btree_cursor_dump(
   void *NotUsed,
   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   int argc,              /* Number of arguments */
-  char **argv            /* Text of each argument */
+  const char **argv      /* Text of each argument */
 ){
   BtCursor *pCur;
   int rc;
@@ -877,33 +877,42 @@ static int btree_cursor_dump(
 ** Register commands with the TCL interpreter.
 */
 int Sqlitetest3_Init(Tcl_Interp *interp){
-  Tcl_CreateCommand(interp, "btree_open", btree_open, 0, 0);
-  Tcl_CreateCommand(interp, "btree_close", btree_close, 0, 0);
-  Tcl_CreateCommand(interp, "btree_begin_transaction",
-      btree_begin_transaction, 0, 0);
-  Tcl_CreateCommand(interp, "btree_commit", btree_commit, 0, 0);
-  Tcl_CreateCommand(interp, "btree_rollback", btree_rollback, 0, 0);
-  Tcl_CreateCommand(interp, "btree_create_table", btree_create_table, 0, 0);
-  Tcl_CreateCommand(interp, "btree_drop_table", btree_drop_table, 0, 0);
-  Tcl_CreateCommand(interp, "btree_clear_table", btree_clear_table, 0, 0);
-  Tcl_CreateCommand(interp, "btree_get_meta", btree_get_meta, 0, 0);
-  Tcl_CreateCommand(interp, "btree_update_meta", btree_update_meta, 0, 0);
-  Tcl_CreateCommand(interp, "btree_page_dump", btree_page_dump, 0, 0);
-  Tcl_CreateCommand(interp, "btree_tree_dump", btree_tree_dump, 0, 0);
-  Tcl_CreateCommand(interp, "btree_pager_stats", btree_pager_stats, 0, 0);
-  Tcl_CreateCommand(interp, "btree_pager_ref_dump", btree_pager_ref_dump, 0, 0);
-  Tcl_CreateCommand(interp, "btree_cursor", btree_cursor, 0, 0);
-  Tcl_CreateCommand(interp, "btree_close_cursor", btree_close_cursor, 0, 0);
-  Tcl_CreateCommand(interp, "btree_move_to", btree_move_to, 0, 0);
-  Tcl_CreateCommand(interp, "btree_delete", btree_delete, 0, 0);
-  Tcl_CreateCommand(interp, "btree_insert", btree_insert, 0, 0);
-  Tcl_CreateCommand(interp, "btree_next", btree_next, 0, 0);
-  Tcl_CreateCommand(interp, "btree_key", btree_key, 0, 0);
-  Tcl_CreateCommand(interp, "btree_data", btree_data, 0, 0);
-  Tcl_CreateCommand(interp, "btree_payload_size", btree_payload_size, 0, 0);
-  Tcl_CreateCommand(interp, "btree_first", btree_first, 0, 0);
-  Tcl_CreateCommand(interp, "btree_cursor_dump", btree_cursor_dump, 0, 0);
-  Tcl_CreateCommand(interp, "btree_integrity_check", btree_integrity_check,0,0);
+  static struct {
+     char *zName;
+     Tcl_CmdProc *xProc;
+  } aCmd[] = {
+     { "btree_open",               (Tcl_CmdProc*)btree_open               },
+     { "btree_close",              (Tcl_CmdProc*)btree_close              },
+     { "btree_begin_transaction",  (Tcl_CmdProc*)btree_begin_transaction  },
+     { "btree_commit",             (Tcl_CmdProc*)btree_commit             },
+     { "btree_rollback",           (Tcl_CmdProc*)btree_rollback           },
+     { "btree_create_table",       (Tcl_CmdProc*)btree_create_table       },
+     { "btree_drop_table",         (Tcl_CmdProc*)btree_drop_table         },
+     { "btree_clear_table",        (Tcl_CmdProc*)btree_clear_table        },
+     { "btree_get_meta",           (Tcl_CmdProc*)btree_get_meta           },
+     { "btree_update_meta",        (Tcl_CmdProc*)btree_update_meta        },
+     { "btree_page_dump",          (Tcl_CmdProc*)btree_page_dump          },
+     { "btree_tree_dump",          (Tcl_CmdProc*)btree_tree_dump          },
+     { "btree_pager_stats",        (Tcl_CmdProc*)btree_pager_stats        },
+     { "btree_pager_ref_dump",     (Tcl_CmdProc*)btree_pager_ref_dump     },
+     { "btree_cursor",             (Tcl_CmdProc*)btree_cursor             },
+     { "btree_close_cursor",       (Tcl_CmdProc*)btree_close_cursor       },
+     { "btree_move_to",            (Tcl_CmdProc*)btree_move_to            },
+     { "btree_delete",             (Tcl_CmdProc*)btree_delete             },
+     { "btree_insert",             (Tcl_CmdProc*)btree_insert             },
+     { "btree_next",               (Tcl_CmdProc*)btree_next               },
+     { "btree_key",                (Tcl_CmdProc*)btree_key                },
+     { "btree_data",               (Tcl_CmdProc*)btree_data               },
+     { "btree_payload_size",       (Tcl_CmdProc*)btree_payload_size       },
+     { "btree_first",              (Tcl_CmdProc*)btree_first              },
+     { "btree_cursor_dump",        (Tcl_CmdProc*)btree_cursor_dump        },
+     { "btree_integrity_check",    (Tcl_CmdProc*)btree_integrity_check    },
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
   Tcl_LinkVar(interp, "pager_refinfo_enable", (char*)&pager_refinfo_enable,
      TCL_LINK_INT);
   Tcl_LinkVar(interp, "btree_native_byte_order",(char*)&btree_native_byte_order,
diff --git a/test/fkey1.test b/test/fkey1.test
new file mode 100644 (file)
index 0000000..a853457
--- /dev/null
@@ -0,0 +1,44 @@
+# 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 foreign keys.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Create a table and some data to work with.
+#
+do_test fkey1-1.0 {
+  execsql {
+    CREATE TABLE t1(
+      a INTEGER PRIMARY KEY,
+      b INTEGER
+           REFERENCES t1 ON DELETE CASCADE
+           REFERENCES t2,
+      c TEXT,
+      FOREIGN KEY (b,c) REFERENCES t2(x,y) ON UPDATE CASCADE
+    );
+  }
+} {}
+do_test fkey1-1.1 {
+  execsql {
+    CREATE TABLE t2(
+      x INTEGER PRIMARY KEY,
+      y TEXT
+    );
+  }
+} {}
+
+
+
+finish_test
index 7b1d9ae29688e2c77593d3b518de807de9fdcc12..8bed034a9b342d38c869e733e5eb81b80d02eef1 100644 (file)
@@ -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.19 2002/08/13 23:02:58 drh Exp $
+# $Id: table.test,v 1.20 2002/08/31 18:53:09 drh Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -433,6 +433,45 @@ do_test table-10.8 {
     );
   }
 } {0 {}}
+do_test table-10.9 {
+  catchsql {
+    DROP TABLE t6;
+    CREATE TABLE t6(a,b,c,
+      FOREIGN KEY (b,c) REFERENCES t4(x)
+    );
+  }
+} {1 {number of columns in foreign key does not match the number of columns in the referenced table}}
+do_test table-10.10 {
+  catchsql {DROP TABLE t6}
+  catchsql {
+    CREATE TABLE t6(a,b,c,
+      FOREIGN KEY (b,c) REFERENCES t4(x,y,z)
+    );
+  }
+} {1 {number of columns in foreign key does not match the number of columns in the referenced table}}
+do_test table-10.11 {
+  catchsql {DROP TABLE t6}
+  catchsql {
+    CREATE TABLE t6(a,b, c REFERENCES t4(x,y));
+  }
+} {1 {foreign key on c should reference only one column of table t4}}
+do_test table-10.12 {
+  catchsql {DROP TABLE t6}
+  catchsql {
+    CREATE TABLE t6(a,b,c,
+      FOREIGN KEY (b,x) REFERENCES t4(x,y)
+    );
+  }
+} {1 {unknown column "x" in foreign key definition}}
+do_test table-10.13 {
+  catchsql {DROP TABLE t6}
+  catchsql {
+    CREATE TABLE t6(a,b,c,
+      FOREIGN KEY (x,b) REFERENCES t4(x,y)
+    );
+  }
+} {1 {unknown column "x" in foreign key definition}}
+
 
 # Test for the "typeof" function.
 #