Experimental opimizations to speed up FK constraint CASCADE and SET NULL action processing.
FossilOrigin-Name: 8c5dd6cc259e0cdaaddaa52ccfa96fee6b166906
This commit is contained in:
commit
1ac5fed3a7
17
manifest
17
manifest
@ -1,5 +1,5 @@
|
||||
C Fix\sthe\se_walauto.test\sscript\sso\sthat\sit\sworks\son\swindows.
|
||||
D 2014-12-16T12:46:38.635
|
||||
C Experimental\sopimizations\sto\sspeed\sup\sFK\sconstraint\sCASCADE\sand\sSET\sNULL\saction\sprocessing.
|
||||
D 2014-12-17T15:03:50.611
|
||||
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 e0444b61bed271a76840cbe6182df93a9baa3f12
|
||||
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 8f08203458321e6c19a263829de4cfc936274ab0
|
||||
F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749
|
||||
F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
|
||||
F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c
|
||||
@ -1232,7 +1233,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
|
||||
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
P ae43539e62e76676a3daf561b629a1b9b4e2d2c9
|
||||
R 6cb5309eecfa4835aff26790e68b0f80
|
||||
U drh
|
||||
Z 6fc407d3934d070a5bb755e43a0ba0ad
|
||||
P 7d092ebb6724c3c0fdc05dc94ca767d158933fb5 210cb2a6aaf780365064a26c0c99926bd6346e19
|
||||
R 2edc2fe620223f33e078e9395972a943
|
||||
U dan
|
||||
Z c2959ecd9d3a1afd4baaa55723226ea7
|
||||
|
@ -1 +1 @@
|
||||
7d092ebb6724c3c0fdc05dc94ca767d158933fb5
|
||||
8c5dd6cc259e0cdaaddaa52ccfa96fee6b166906
|
81
src/fkey.c
81
src/fkey.c
@ -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:
|
||||
@ -625,13 +629,9 @@ static void fkScanChildren(
|
||||
sqlite3ResolveExprNames(&sNameContext, pWhere);
|
||||
|
||||
/* Create VDBE to loop through the entries in pSrc that match the WHERE
|
||||
** clause. If the constraint is not deferred, throw an exception for
|
||||
** each row found. Otherwise, for deferred constraints, increment the
|
||||
** deferred constraint counter by nIncr for each row selected. */
|
||||
** clause. For each row found, increment either the deferred or immediate
|
||||
** foreign key constraint counter. */
|
||||
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 +810,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 +880,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 +939,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 +954,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 all child key
|
||||
** 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 +986,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 +1011,28 @@ 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, then any foreign key violations caused by
|
||||
** removing the parent key will be rectified by the action trigger.
|
||||
** So do not set the "may-abort" flag in this case.
|
||||
**
|
||||
** Note 1: If the FK is declared "ON UPDATE CASCADE", then the
|
||||
** may-abort flag will eventually be set on this statement anyway
|
||||
** (when this function is called as part of processing the UPDATE
|
||||
** within the action trigger).
|
||||
**
|
||||
** Note 2: At first glance it may seem like SQLite could simply omit
|
||||
** all OP_FkCounter related scans when either CASCADE or SET NULL
|
||||
** applies. The trouble starts if the CASCADE or SET NULL action
|
||||
** trigger causes other triggers or action rules attached to the
|
||||
** child table to fire. In these cases the fk constraint counters
|
||||
** might be set incorrectly if any OP_FkCounter related scans are
|
||||
** omitted. */
|
||||
if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){
|
||||
sqlite3MayAbort(pParse);
|
||||
}
|
||||
}
|
||||
pItem->zName = 0;
|
||||
sqlite3SrcListDelete(db, pSrc);
|
||||
|
@ -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 */
|
||||
|
||||
|
106
test/fkey8.test
Normal file
106
test/fkey8.test
Normal file
@ -0,0 +1,106 @@
|
||||
# 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);
|
||||
}
|
||||
5.3 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 ON DELETE SET DEFAULT);
|
||||
}
|
||||
|
||||
6.1 1 "UPDATE p1 SET a = ?" {
|
||||
CREATE TABLE p1(a PRIMARY KEY);
|
||||
CREATE TABLE c1(b REFERENCES p1 ON UPDATE SET NULL, c);
|
||||
}
|
||||
6.2 0 "UPDATE OR IGNORE p1 SET a = ?" {
|
||||
CREATE TABLE p1(a PRIMARY KEY);
|
||||
CREATE TABLE c1(b REFERENCES p1 ON UPDATE SET NULL, c);
|
||||
}
|
||||
6.3 1 "UPDATE OR IGNORE p1 SET a = ?" {
|
||||
CREATE TABLE p1(a PRIMARY KEY);
|
||||
CREATE TABLE c1(b REFERENCES p1 ON UPDATE CASCADE, c);
|
||||
}
|
||||
6.4 1 "UPDATE OR IGNORE p1 SET a = ?" {
|
||||
CREATE TABLE p1(a PRIMARY KEY);
|
||||
CREATE TABLE c1(b NOT NULL REFERENCES p1 ON UPDATE SET NULL, c);
|
||||
}
|
||||
|
||||
} {
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user