]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Experimental opimizations to speed up FK constraint CASCADE and SET NULL action proce...
authordan <dan@noemail.net>
Tue, 16 Dec 2014 20:13:30 +0000 (20:13 +0000)
committerdan <dan@noemail.net>
Tue, 16 Dec 2014 20:13:30 +0000 (20:13 +0000)
FossilOrigin-Name: 35a20a5f22245c70faa51965951e8cc011defa93

manifest
manifest.uuid
src/fkey.c
src/vdbeaux.c
test/fkey8.test [new file with mode: 0644]

index 012153429fa54ecc25b48064c334b52e56072d59..d7fc48ce456384d7362c877030a7db6ccade8d65 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Enhanced\s"stress2"\stesting\sin\sthe\sthreadtest3.c\stest\sprogram.
-D 2014-12-16T00:20:07.236
+C Experimental\sopimizations\sto\sspeed\sup\sFK\sconstraint\sCASCADE\sand\sSET\sNULL\saction\sprocessing.
+D 2014-12-16T20:13:30.440
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 6c4f961fa91d0b4fa121946a19f9e5eac2f2f809
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -184,7 +184,7 @@ F src/date.c 93594514aae68de117ca4a2a0d6cc63eddf26744
 F src/delete.c 0750b1eb4d96cd3fb2c798599a3a7c85e92f1417
 F src/expr.c 00da3072f362b06f39ce4052baa1d4ce2bb36d1c
 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
-F src/fkey.c da985ae673efef2c712caef825a5d2edb087ead7
+F src/fkey.c eab56799b0339b04c258233b0f462b6e9317f90f
 F src/func.c 6d3c4ebd72aa7923ce9b110a7dc15f9b8c548430
 F src/global.c 6ded36dda9466fc1c9a3c5492ded81d79bf3977d
 F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
@@ -295,7 +295,7 @@ F src/vdbe.c 1a9e671c9cfc259e4d2affc71f7df4a4c00a842c
 F src/vdbe.h 6fc69d9c5e146302c56e163cb4b31d1ee64a18c3
 F src/vdbeInt.h 9bb69ff2447c34b6ccc58b34ec35b615f86ead78
 F src/vdbeapi.c 4bc511a46b9839392ae0e90844a71dc96d9dbd71
-F src/vdbeaux.c 6f7f39c3fcf0f5923758df8561bb5d843908a553
+F src/vdbeaux.c 07ef87c6d4b5abdf13ff33babb10205702fdab0a
 F src/vdbeblob.c 4af4bfb71f6df7778397b4a0ebc1879793276778
 F src/vdbemem.c 31d8eabb0cd78bfeab4e5124c7363c3e9e54db9f
 F src/vdbesort.c c150803a3e98fbc68bd07772cbbd4328a0a7212d
@@ -504,6 +504,7 @@ F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d
 F test/fkey5.test 8a1fde4e7721ae00b05b3178888833726ca2df8d
 F test/fkey6.test abb59f866c1b44926fd02d1fdd217d831fe04f48
 F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13
+F test/fkey8.test 2d58cfb990cdd56b2fbac9f4ae54ee53968b3e06
 F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749
 F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
 F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c
@@ -1232,8 +1233,10 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 5b1b697040116048e464b3ebab8395fe088e389a 5648af96d8e2521c5b0cca19f1358374d032394d
-R 64bec50dd05bb00af986c569d5edb044
-T +closed 5648af96d8e2521c5b0cca19f1358374d032394d
-U drh
-Z 1075019e3631a6f364d3a72648c7dd6d
+P ae43539e62e76676a3daf561b629a1b9b4e2d2c9
+R ea6275c5fe67bcf602365c1ede77fced
+T *branch * experimental-fk-actions
+T *sym-experimental-fk-actions *
+T -sym-trunk *
+U dan
+Z b6352d484674a0d7b3ae6d8d076cd106
index 209ef7a57a651b3648ddcedf4d309ea4d5c5fd1b..859f607ae01fe134e4fd172303fcc672ca474ef6 100644 (file)
@@ -1 +1 @@
-ae43539e62e76676a3daf561b629a1b9b4e2d2c9
\ No newline at end of file
+35a20a5f22245c70faa51965951e8cc011defa93
\ No newline at end of file
index e816bd95daf30bcfa985eb89785c1e0c95d37a7a..7ea48f5058950964a422ecacc75c134cdb7260c5 100644 (file)
@@ -437,7 +437,7 @@ static void fkLookupParent(
         OE_Abort, 0, P4_STATIC, P5_ConstraintFK);
   }else{
     if( nIncr>0 && pFKey->isDeferred==0 ){
-      sqlite3ParseToplevel(pParse)->mayAbort = 1;
+      sqlite3MayAbort(pParse);
     }
     sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
   }
@@ -509,6 +509,10 @@ static Expr *exprTableColumn(
 ** code for an SQL UPDATE operation, this function may be called twice -
 ** once to "delete" the old row and once to "insert" the new row.
 **
+** Parameter nIncr is passed -1 when inserting a row (as this may decrease
+** the number of FK violations in the db) or +1 when deleting one (as this
+** may increase the number of FK constraint problems).
+**
 ** The code generated by this function scans through the rows in the child
 ** table that correspond to the parent table row being deleted or inserted.
 ** For each child row found, one of the following actions is taken:
@@ -629,9 +633,6 @@ static void fkScanChildren(
   ** each row found. Otherwise, for deferred constraints, increment the
   ** deferred constraint counter by nIncr for each row selected.  */
   pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0);
-  if( nIncr>0 && pFKey->isDeferred==0 ){
-    sqlite3ParseToplevel(pParse)->mayAbort = 1;
-  }
   sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
   if( pWInfo ){
     sqlite3WhereEnd(pWInfo);
@@ -810,6 +811,24 @@ static int fkParentIsModified(
   return 0;
 }
 
+/*
+** Return true if the parser passed as the first argument is being
+** used to code a trigger that is really a "SET NULL" action belonging
+** to trigger pFKey.
+*/
+static int isSetNullAction(Parse *pParse, FKey *pFKey){
+  Parse *pTop = sqlite3ParseToplevel(pParse);
+  if( pTop->pTriggerPrg ){
+    Trigger *p = pTop->pTriggerPrg->pTrigger;
+    if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull)
+     || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull)
+    ){
+      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 
@@ -862,7 +881,7 @@ void sqlite3FkCheck(
     int *aiCol;
     int iCol;
     int i;
-    int isIgnore = 0;
+    int bIgnore = 0;
 
     if( aChange 
      && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0
@@ -921,7 +940,7 @@ void sqlite3FkCheck(
         int rcauth;
         char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName;
         rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb);
-        isIgnore = (rcauth==SQLITE_IGNORE);
+        bIgnore = (rcauth==SQLITE_IGNORE);
       }
 #endif
     }
@@ -936,12 +955,18 @@ void sqlite3FkCheck(
       /* A row is being removed from the child table. Search for the parent.
       ** If the parent does not exist, removing the child row resolves an 
       ** outstanding foreign key constraint violation. */
-      fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1,isIgnore);
+      fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore);
     }
-    if( regNew!=0 ){
+    if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){
       /* A row is being added to the child table. If a parent row cannot
-      ** be found, adding the child row has violated the FK constraint. */ 
-      fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1,isIgnore);
+      ** be found, adding the child row has violated the FK constraint. 
+      **
+      ** If this operation is being performed as part of a trigger program
+      ** that is actually a "SET NULL" action belonging to this very 
+      ** foreign key, then omit this scan altogether. As the child keys
+      ** values are guaranteed to be NULL, it is not possible for adding
+      ** this row to cause an FK violation. */
+      fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore);
     }
 
     sqlite3DbFree(db, aiFree);
@@ -962,8 +987,8 @@ void sqlite3FkCheck(
      && !pParse->pToplevel && !pParse->isMultiWrite 
     ){
       assert( regOld==0 && regNew!=0 );
-      /* Inserting a single row into a parent table cannot cause an immediate
-      ** foreign key violation. So do nothing in this case.  */
+      /* Inserting a single row into a parent table cannot cause (or fix)
+      ** an immediate foreign key violation. So do nothing in this case.  */
       continue;
     }
 
@@ -987,13 +1012,15 @@ void sqlite3FkCheck(
         fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1);
       }
       if( regOld!=0 ){
-        /* If there is a RESTRICT action configured for the current operation
-        ** on the parent table of this FK, then throw an exception 
-        ** immediately if the FK constraint is violated, even if this is a
-        ** deferred trigger. That's what RESTRICT means. To defer checking
-        ** the constraint, the FK should specify NO ACTION (represented
-        ** using OE_None). NO ACTION is the default.  */
+        int eAction = pFKey->aAction[aChange!=0];
         fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1);
+        /* If this is a deferred FK constraint, or a CASCADE or SET NULL
+        ** action applies, do not set the may-abort flag on this statement.
+        ** The flag may be set on this statement for some other reason, but
+        ** not as a result of this FK constraint. */
+        if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){
+          sqlite3MayAbort(pParse);
+        }
       }
       pItem->zName = 0;
       sqlite3SrcListDelete(db, pSrc);
index 7900bd52ac4a6fff89450f126e46d7af557fec9e..03c229c994a0bd08f621411933a234f4bc956bb4 100644 (file)
@@ -396,6 +396,7 @@ static Op *opIterNext(VdbeOpIter *p){
 */
 int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
   int hasAbort = 0;
+  int hasFkCounter = 0;
   Op *pOp;
   VdbeOpIter sIter;
   memset(&sIter, 0, sizeof(sIter));
@@ -404,15 +405,17 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
   while( (pOp = opIterNext(&sIter))!=0 ){
     int opcode = pOp->opcode;
     if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename 
-#ifndef SQLITE_OMIT_FOREIGN_KEY
-     || (opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1) 
-#endif
      || ((opcode==OP_Halt || opcode==OP_HaltIfNull) 
       && ((pOp->p1&0xff)==SQLITE_CONSTRAINT && pOp->p2==OE_Abort))
     ){
       hasAbort = 1;
       break;
     }
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+    if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){
+      hasFkCounter = 1;
+    }
+#endif
   }
   sqlite3DbFree(v->db, sIter.apSub);
 
@@ -421,7 +424,7 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
   ** through all opcodes and hasAbort may be set incorrectly. Return
   ** true for this case to prevent the assert() in the callers frame
   ** from failing.  */
-  return ( v->db->mallocFailed || hasAbort==mayAbort );
+  return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter );
 }
 #endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */
 
diff --git a/test/fkey8.test b/test/fkey8.test
new file mode 100644 (file)
index 0000000..e9f3093
--- /dev/null
@@ -0,0 +1,85 @@
+# 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 fkey8
+
+ifcapable {!foreignkey} {
+  finish_test
+  return
+}
+do_execsql_test 1.0 { PRAGMA foreign_keys = 1; }
+
+
+foreach {tn use_stmt sql schema} {
+  1   1 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1);
+  }
+
+  2.1     0 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE);
+  }
+  2.2   0 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1 ON DELETE SET NULL);
+  }
+  2.3   1 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1 ON DELETE SET DEFAULT);
+  }
+
+  3   1 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE);
+    CREATE TRIGGER ct1 AFTER DELETE ON c1 BEGIN
+      INSERT INTO p1 VALUES('x');
+    END;
+  }
+
+  4   1 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE, c PRIMARY KEY);
+    CREATE TABLE cc1(d REFERENCES c1);
+  }
+
+  5.1   0 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE, c PRIMARY KEY);
+    CREATE TABLE cc1(d REFERENCES c1 ON DELETE CASCADE);
+  }
+  5.2   0 "DELETE FROM p1" {
+    CREATE TABLE p1(a PRIMARY KEY);
+    CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE, c PRIMARY KEY);
+    CREATE TABLE cc1(d REFERENCES c1 ON DELETE SET NULL);
+  }
+
+} {
+  drop_all_tables
+  do_test 1.$tn {
+    execsql $schema
+    set stmt [sqlite3_prepare_v2 db $sql -1 dummy]
+    set ret [uses_stmt_journal $stmt]
+    sqlite3_finalize $stmt
+    set ret
+  } $use_stmt
+}
+
+
+
+
+finish_test