diff --git a/manifest b/manifest index e283ebdf5d..60cf6a27a2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Rearrange\sthe\sorder\sof\sconditions\sin\san\s"if"\sstatement\sto\sfacilitate\stesting. -D 2013-09-04T18:14:53.187 +C When\spreparing\san\sUPDATE\sstatement,\savoid\sgenerating\sVDBE\scode\sfor\sthose\sforeign\skey\srelated\sactions\sand\sconstraint\schecks\sthat\smay\sbe\sseen\sto\sbe\sunnecessary\sby\sconsidering\sthe\ssubset\sof\stable\scolumns\spotentially\smodified\sby\sthe\sUPDATE. +D 2013-09-05T18:40:29.445 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -171,16 +171,16 @@ F src/callback.c d7e46f40c3cf53c43550b7da7a1d0479910b62cc F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac F src/ctime.c ea4b7f3623a0fcb1146e7f245d7410033e86859c F src/date.c 067a81c9942c497aafd2c260e13add8a7d0c7dd4 -F src/delete.c 2317c814866d9aa71fea16b3faf4fdd4d6a49b94 +F src/delete.c 2dc64ca360b7d7da481183ea920a813a0c203c97 F src/expr.c 4d89bd03a04fcdb5ff71d86b4e0cc7d3230797b8 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb -F src/fkey.c 914a6bbd987d857c41ac9d244efa6641f36faadb +F src/fkey.c be866cd8c4fa6cae98ba33109578fd1a3311ee5b F src/func.c 5b064acd303b3e74f019ab551d423ff6cace4023 F src/global.c 5caf4deab621abb45b4c607aad1bd21c20aac759 F src/hash.c ac3470bbf1ca4ae4e306a8ecb0fdf1731810ffe4 F src/hash.h 8890a25af81fb85a9ad7790d32eedab4b994da22 F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08 -F src/insert.c a66bcdc956145369c1a876709f47f69476973e15 +F src/insert.c a271771db927873c850da8928d6ee9fc058fb9fe F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12 F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b @@ -221,7 +221,7 @@ F src/shell.c d920a891ca09b8bd262cced7fb0ab9d723f7a747 F src/sqlite.h.in ec40aa958a270416fb04b4f72210357bf163d2c5 F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0 F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc -F src/sqliteInt.h 600086a5082e2291b0aeeefcfbb546f2bbda67b2 +F src/sqliteInt.h a083fc4effb15c15946a36919793c311bc247b57 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e @@ -273,7 +273,7 @@ F src/test_vfstrace.c 34b544e80ba7fb77be15395a609c669df2e660a2 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/tokenize.c 70061085a51f2f4fc15ece94f32c03bcb78e63b2 F src/trigger.c 5c0ea9b8755e7c5e1a700f3e27ac4f8d92dd221e -F src/update.c 7d9d38e4f341ada7d79035ea969cdefb8b9014d1 +F src/update.c f5182157f5d0d0a97bc5f5e3c9bdba0dfbe08f08 F src/utf.c 6fc6c88d50448c469c5c196acf21617a24f90269 F src/util.c f566b5138099a2df8533b190d0dcc74b7dfbe0c9 F src/vacuum.c d9c5759f4c5a438bb43c2086f72c5d2edabc36c8 @@ -462,6 +462,7 @@ F test/fkey3.test 5ec899d12b13bcf1e9ef40eff7fb692fdb91392e F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d F test/fkey5.test 0bf64f2d19ad80433ca0b24edbf604a18b353d5f F test/fkey6.test c555f7fc45d842cc84b0d3ff93951ce2b8c25fc8 +F test/fkey7.test e31d0e71a41c1d29349a16448d6c420e2c53a8fc F test/fkey_malloc.test bb74c9cb8f8fceed03b58f8a7ef2df98520bbd51 F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c @@ -717,7 +718,7 @@ F test/pagesize.test 1dd51367e752e742f58e861e65ed7390603827a0 F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025 F test/percentile.test b98fc868d71eb5619d42a1702e9ab91718cbed54 -F test/permutations.test 9cd3bba6f1e8658442d0dcfcef6c2e5efd131bdc +F test/permutations.test 72f4f8881d388163ddbbeec0a6ed812e863ea2e6 F test/pragma.test 5e7de6c32a5d764f09437d2025f07e4917b9e178 F test/pragma2.test 224f0381f9411a78ae685cac24c13656a62021b7 F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552 @@ -1110,7 +1111,7 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac -P f929e9b41f2f69b086be3dd48fe557adbba7ae5c -R cc5fb62c722092188f8662d393b9b48e -U drh -Z 3d3da6aaad5a89de084de232785827d1 +P 8462fb43c275a70db59c4339650225deeadeef00 +R d3b1cfd48ce8f1b63476cb1b87cb1fe9 +U dan +Z ebec4296d6724df2f506ca7d4f2e487f diff --git a/manifest.uuid b/manifest.uuid index 05fd8fd0c5..1ea65c845e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8462fb43c275a70db59c4339650225deeadeef00 \ No newline at end of file +e940b5de49baa1d6a4cf859fbbc0e0df86ac5dbf \ No newline at end of file diff --git a/src/delete.c b/src/delete.c index af64afc65c..50a35cd165 100644 --- a/src/delete.c +++ b/src/delete.c @@ -536,7 +536,7 @@ void sqlite3GenerateRowDelete( /* Do FK processing. This call checks that any FK constraints that ** refer to this table (i.e. constraints attached to other tables) ** are not violated by deleting this row. */ - sqlite3FkCheck(pParse, pTab, iOld, 0); + sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0); } /* Delete the index and table entries. Skip this step if pTab is really @@ -553,7 +553,7 @@ void sqlite3GenerateRowDelete( /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just deleted. */ - sqlite3FkActions(pParse, pTab, 0, iOld); + sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0); /* Invoke AFTER DELETE trigger programs. */ sqlite3CodeRowTrigger(pParse, pTrigger, diff --git a/src/fkey.c b/src/fkey.c index bb59c656f6..1947c2ee20 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -682,6 +682,70 @@ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ } } + +/* +** The second argument points to an FKey object representing a foreign key +** for which pTab is the child table. An UPDATE statement against pTab +** is currently being processed. For each column of the table that is +** actually updated, the corresponding element in the aChange[] array +** is zero or greater (if a column is unmodified the corresponding element +** is set to -1). If the rowid column is modified by the UPDATE statement +** the bChngRowid argument is non-zero. +** +** This function returns true if any of the columns that are part of the +** child key for FK constraint *p are modified. +*/ +static int fkChildIsModified( + Table *pTab, /* Table being updated */ + FKey *p, /* Foreign key for which pTab is the child */ + int *aChange, /* Array indicating modified columns */ + int bChngRowid /* True if rowid is modified by this update */ +){ + int i; + for(i=0; inCol; i++){ + int iChildKey = p->aCol[i].iFrom; + if( aChange[iChildKey]>=0 ) return 1; + if( iChildKey==pTab->iPKey && bChngRowid ) return 1; + } + return 0; +} + +/* +** The second argument points to an FKey object representing a foreign key +** for which pTab is the parent table. An UPDATE statement against pTab +** is currently being processed. For each column of the table that is +** actually updated, the corresponding element in the aChange[] array +** is zero or greater (if a column is unmodified the corresponding element +** is set to -1). If the rowid column is modified by the UPDATE statement +** the bChngRowid argument is non-zero. +** +** This function returns true if any of the columns that are part of the +** parent key for FK constraint *p are modified. +*/ +static int fkParentIsModified( + Table *pTab, + FKey *p, + int *aChange, + int bChngRowid +){ + int i; + for(i=0; inCol; i++){ + char *zKey = p->aCol[i].zCol; + int iKey; + for(iKey=0; iKeynCol; iKey++){ + if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){ + Column *pCol = &pTab->aCol[iKey]; + if( zKey ){ + if( 0==sqlite3StrICmp(pCol->zName, zKey) ) return 1; + }else if( pCol->colFlags & COLFLAG_PRIMKEY ){ + return 1; + } + } + } + } + return 0; +} + /* ** This function is called when inserting, deleting or updating a row of ** table pTab to generate VDBE code to perform foreign key constraint @@ -706,7 +770,9 @@ void sqlite3FkCheck( Parse *pParse, /* Parse context */ Table *pTab, /* Row is being deleted from this table */ int regOld, /* Previous row data is stored here */ - int regNew /* New row data is stored here */ + int regNew, /* New row data is stored here */ + int *aChange, /* Array indicating UPDATEd columns (or 0) */ + int bChngRowid /* True if rowid is UPDATEd */ ){ sqlite3 *db = pParse->db; /* Database handle */ FKey *pFKey; /* Used to iterate through FKs */ @@ -734,6 +800,13 @@ void sqlite3FkCheck( int i; int isIgnore = 0; + if( aChange + && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 + && fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0 + ){ + continue; + } + /* Find the parent table of this foreign key. Also find a unique index ** on the parent key columns in the parent table. If either of these ** schema items cannot be located, set an error in pParse and return @@ -816,6 +889,10 @@ void sqlite3FkCheck( SrcList *pSrc; int *aiCol = 0; + if( aChange && fkParentIsModified(pTab, pFKey, aChange, bChngRowid)==0 ){ + continue; + } + if( !pFKey->isDeferred && !(db->flags & SQLITE_DeferFKs) && !pParse->pToplevel && !pParse->isMultiWrite ){ @@ -889,6 +966,7 @@ u32 sqlite3FkOldmask( return mask; } + /* ** This function is called before generating code to update or delete a ** row contained in table pTab. If the operation is a DELETE, then @@ -918,32 +996,16 @@ int sqlite3FkRequired( }else{ /* This is an UPDATE. Foreign key processing is only required if the ** operation modifies one or more child or parent key columns. */ - int i; FKey *p; /* Check if any child key columns are being modified. */ for(p=pTab->pFKey; p; p=p->pNextFrom){ - for(i=0; inCol; i++){ - int iChildKey = p->aCol[i].iFrom; - if( aChange[iChildKey]>=0 ) return 1; - if( iChildKey==pTab->iPKey && chngRowid ) return 1; - } + if( fkChildIsModified(pTab, p, aChange, chngRowid) ) return 1; } /* Check if any parent key columns are being modified. */ for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ - for(i=0; inCol; i++){ - char *zKey = p->aCol[i].zCol; - int iKey; - for(iKey=0; iKeynCol; iKey++){ - Column *pCol = &pTab->aCol[iKey]; - if( (zKey ? !sqlite3StrICmp(pCol->zName, zKey) - : (pCol->colFlags & COLFLAG_PRIMKEY)!=0) ){ - if( aChange[iKey]>=0 ) return 1; - if( iKey==pTab->iPKey && chngRowid ) return 1; - } - } - } + if( fkParentIsModified(pTab, p, aChange, chngRowid) ) return 1; } } } @@ -1169,7 +1231,9 @@ void sqlite3FkActions( Parse *pParse, /* Parse context */ Table *pTab, /* Table being updated or deleted from */ ExprList *pChanges, /* Change-list for UPDATE, NULL for DELETE */ - int regOld /* Address of array containing old row */ + int regOld, /* Address of array containing old row */ + int *aChange, /* Array indicating UPDATEd columns (or 0) */ + int bChngRowid /* True if rowid is UPDATEd */ ){ /* If foreign-key support is enabled, iterate through all FKs that ** refer to table pTab. If there is an action associated with the FK @@ -1178,9 +1242,11 @@ void sqlite3FkActions( if( pParse->db->flags&SQLITE_ForeignKeys ){ FKey *pFKey; /* Iterator variable */ for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){ - Trigger *pAction = fkActionTrigger(pParse, pTab, pFKey, pChanges); - if( pAction ){ - sqlite3CodeRowTriggerDirect(pParse, pAction, pTab, regOld, OE_Abort, 0); + if( aChange==0 || fkParentIsModified(pTab, pFKey, aChange, bChngRowid) ){ + Trigger *pAct = fkActionTrigger(pParse, pTab, pFKey, pChanges); + if( pAct ){ + sqlite3CodeRowTriggerDirect(pParse, pAct, pTab, regOld, OE_Abort, 0); + } } } } diff --git a/src/insert.c b/src/insert.c index 1c2cabb938..32cfb83fa8 100644 --- a/src/insert.c +++ b/src/insert.c @@ -1031,7 +1031,7 @@ void sqlite3Insert( sqlite3GenerateConstraintChecks(pParse, pTab, baseCur, regIns, aRegIdx, keyColumn>=0, 0, onError, endOfLoop, &isReplace ); - sqlite3FkCheck(pParse, pTab, 0, regIns); + sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); sqlite3CompleteInsertion( pParse, pTab, baseCur, regIns, aRegIdx, 0, appendFlag, isReplace==0 ); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 49264238b8..d74903fda1 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3209,18 +3209,18 @@ const char *sqlite3JournalModename(int); ** provided (enforcement of FK constraints requires the triggers sub-system). */ #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - void sqlite3FkCheck(Parse*, Table*, int, int); + void sqlite3FkCheck(Parse*, Table*, int, int, int*, int); void sqlite3FkDropTable(Parse*, SrcList *, Table*); - void sqlite3FkActions(Parse*, Table*, ExprList*, int); + void sqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int); int sqlite3FkRequired(Parse*, Table*, int*, int); u32 sqlite3FkOldmask(Parse*, Table*); FKey *sqlite3FkReferences(Table *); #else - #define sqlite3FkActions(a,b,c,d) + #define sqlite3FkActions(a,b,c,d,e,f) #define sqlite3FkCheck(a,b,c,d) #define sqlite3FkDropTable(a,b,c) - #define sqlite3FkOldmask(a,b) 0 - #define sqlite3FkRequired(a,b,c,d) 0 + #define sqlite3FkOldmask(a,b) 0 + #define sqlite3FkRequired(a,b,c,d,e,f) 0 #endif #ifndef SQLITE_OMIT_FOREIGN_KEY void sqlite3FkDelete(sqlite3 *, Table*); diff --git a/src/update.c b/src/update.c index 5077f64e35..0f760a4609 100644 --- a/src/update.c +++ b/src/update.c @@ -488,7 +488,7 @@ void sqlite3Update( /* Do FK constraint checks. */ if( hasFK ){ - sqlite3FkCheck(pParse, pTab, regOldRowid, 0); + sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngRowid); } /* Delete the index entries associated with the current record. */ @@ -502,7 +502,7 @@ void sqlite3Update( sqlite3VdbeJumpHere(v, j1); if( hasFK ){ - sqlite3FkCheck(pParse, pTab, 0, regNewRowid); + sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngRowid); } /* Insert the new index entries and the new record. */ @@ -512,7 +512,7 @@ void sqlite3Update( ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just updated. */ if( hasFK ){ - sqlite3FkActions(pParse, pTab, pChanges, regOldRowid); + sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngRowid); } } diff --git a/test/fkey7.test b/test/fkey7.test new file mode 100644 index 0000000000..c2682edbe5 --- /dev/null +++ b/test/fkey7.test @@ -0,0 +1,54 @@ +# 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 +set testprefix fkey7 + +ifcapable {!foreignkey} { + finish_test + return +} + +do_execsql_test 1.1 { + PRAGMA foreign_keys = 1; + + CREATE TABLE s1(a PRIMARY KEY, b); + CREATE TABLE par(a, b REFERENCES s1, c UNIQUE, PRIMARY KEY(a)); + + CREATE TABLE c1(a, b REFERENCES par); + CREATE TABLE c2(a, b REFERENCES par); + CREATE TABLE c3(a, b REFERENCES par(c)); +} + +proc auth {op tbl args} { + if {$op == "SQLITE_READ"} { set ::tbls($tbl) 1 } + return "SQLITE_OK" +} +db auth auth +db cache size 0 +proc do_tblsread_test {tn sql tbllist} { + array unset ::tbls + uplevel [list execsql $sql] + uplevel [list do_test $tn {lsort [array names ::tbls]} $tbllist] +} + +do_tblsread_test 1.2 { UPDATE par SET b=? WHERE a=? } {par s1} +do_tblsread_test 1.3 { UPDATE par SET a=? WHERE b=? } {c1 c2 par} +do_tblsread_test 1.4 { UPDATE par SET c=? WHERE b=? } {c3 par} +do_tblsread_test 1.5 { UPDATE par SET a=?,b=?,c=? WHERE b=? } {c1 c2 c3 par s1} + + +finish_test diff --git a/test/permutations.test b/test/permutations.test index 47c4665cdd..7468b89673 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -509,7 +509,7 @@ test_suite "utf16" -description { } -files { alter.test alter3.test analyze.test analyze3.test analyze4.test analyze5.test analyze6.test - analyze7.test analyze8.test analyze9.test analyzeA.test + analyze7.test analyze8.test analyze9.test analyzeA.test analyzeB.test auth.test bind.test blob.test capi2.test capi3.test collate1.test collate2.test collate3.test collate4.test collate5.test collate6.test conflict.test date.test delete.test expr.test fkey1.test func.test