Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Avoid checking if an insert or delete has "fixed" an outstanding FK constraint violation if the constraint counter indicates that the database contains no such violations. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
519144ac437b5842e4213f0e81e05c70 |
User & Date: | dan 2009-09-25 17:03:14.000 |
Context
2009-09-26
| ||
17:51 | When ALTER TABLE RENAME TO is used to change the name of a table that is the parent table of a foreign key constraint, modify that foreign key constraint to use the new table name. (check-in: b4a10c39e7 user: dan tags: trunk) | |
2009-09-25
| ||
17:03 | Avoid checking if an insert or delete has "fixed" an outstanding FK constraint violation if the constraint counter indicates that the database contains no such violations. (check-in: 519144ac43 user: dan tags: trunk) | |
12:00 | Fix comments in fkey2.c to reflect the immediate-constraint-counter approach. (check-in: 9fd54b0aa7 user: dan tags: trunk) | |
Changes
Changes to src/fkey.c.
︙ | ︙ | |||
305 306 307 308 309 310 311 | int nIncr /* Increment constraint counter by this */ ){ int i; /* Iterator variable */ Vdbe *v = sqlite3GetVdbe(pParse); /* Vdbe to add code to */ int iCur = pParse->nTab - 1; /* Cursor number to use */ int iOk = sqlite3VdbeMakeLabel(v); /* jump here if parent key found */ | > > > > | | | > > > | 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | int nIncr /* Increment constraint counter by this */ ){ int i; /* Iterator variable */ Vdbe *v = sqlite3GetVdbe(pParse); /* Vdbe to add code to */ int iCur = pParse->nTab - 1; /* Cursor number to use */ int iOk = sqlite3VdbeMakeLabel(v); /* jump here if parent key found */ /* If nIncr is less than zero, then check at runtime if there are any ** outstanding constraints to resolve. If there are not, there is no need ** to check if deleting this row resolves any outstanding violations. ** ** Check if any of the key columns in the child table row are NULL. If ** any are, then the constraint is considered satisfied. No need to ** search for a matching row in the parent table. */ if( nIncr<0 ){ sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, iOk); } for(i=0; i<pFKey->nCol; i++){ int iReg = aiCol[i] + regData + 1; sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); } if( pIdx==0 ){ /* If pIdx is NULL, then the parent key is the INTEGER PRIMARY KEY |
︙ | ︙ | |||
365 366 367 368 369 370 371 | sqlite3HaltConstraint( pParse, OE_Abort, "foreign key constraint failed", P4_STATIC ); }else{ if( nIncr>0 && pFKey->isDeferred==0 ){ sqlite3ParseToplevel(pParse)->mayAbort = 1; } | | | 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | sqlite3HaltConstraint( pParse, OE_Abort, "foreign key constraint failed", P4_STATIC ); }else{ if( nIncr>0 && pFKey->isDeferred==0 ){ sqlite3ParseToplevel(pParse)->mayAbort = 1; } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); } sqlite3VdbeResolveLabel(v, iOk); } /* ** This function is called to generate code executed when a row is deleted |
︙ | ︙ | |||
413 414 415 416 417 418 419 420 421 422 423 424 425 426 | int nIncr /* Amount to increment deferred counter by */ ){ sqlite3 *db = pParse->db; /* Database handle */ int i; /* Iterator variable */ Expr *pWhere = 0; /* WHERE clause to scan with */ NameContext sNameContext; /* Context used to resolve WHERE clause */ WhereInfo *pWInfo; /* Context used by sqlite3WhereXXX() */ /* Create an Expr object representing an SQL expression like: ** ** <parent-key1> = <child-key1> AND <parent-key2> = <child-key2> ... ** ** The collation sequence used for the comparison should be that of ** the parent key columns. The affinity of the parent key column should | > > > > > > | 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 | int nIncr /* Amount to increment deferred counter by */ ){ sqlite3 *db = pParse->db; /* Database handle */ int i; /* Iterator variable */ Expr *pWhere = 0; /* WHERE clause to scan with */ NameContext sNameContext; /* Context used to resolve WHERE clause */ WhereInfo *pWInfo; /* Context used by sqlite3WhereXXX() */ int iFkIfZero = 0; /* Address of OP_FkIfZero */ Vdbe *v = sqlite3GetVdbe(pParse); if( nIncr<0 ){ iFkIfZero = sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, 0); } /* Create an Expr object representing an SQL expression like: ** ** <parent-key1> = <child-key1> AND <parent-key2> = <child-key2> ... ** ** The collation sequence used for the comparison should be that of ** the parent key columns. The affinity of the parent key column should |
︙ | ︙ | |||
473 474 475 476 477 478 479 | sqlite3HaltConstraint( pParse, OE_Abort, "foreign key constraint failed", P4_STATIC ); }else{ if( nIncr>0 && pFKey->isDeferred==0 ){ sqlite3ParseToplevel(pParse)->mayAbort = 1; } | | > > > | 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | sqlite3HaltConstraint( pParse, OE_Abort, "foreign key constraint failed", P4_STATIC ); }else{ if( nIncr>0 && pFKey->isDeferred==0 ){ sqlite3ParseToplevel(pParse)->mayAbort = 1; } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); } if( pWInfo ){ sqlite3WhereEnd(pWInfo); } /* Clean up the WHERE clause constructed above. */ sqlite3ExprDelete(db, pWhere); if( iFkIfZero ){ sqlite3VdbeJumpHere(v, iFkIfZero); } } /* ** This function returns a pointer to the head of a linked list of FK ** constraints for which table pTab is the parent table. For example, ** given the following schema: ** |
︙ | ︙ |
Changes to src/test1.c.
︙ | ︙ | |||
4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 | /* ** Register commands with the TCL interpreter. */ int Sqlitetest1_Init(Tcl_Interp *interp){ extern int sqlite3_search_count; extern int sqlite3_interrupt_count; extern int sqlite3_open_file_count; extern int sqlite3_sort_count; extern int sqlite3_current_time; #if SQLITE_OS_UNIX && defined(__APPLE__) extern int sqlite3_hostid_num; #endif | > | 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 | /* ** Register commands with the TCL interpreter. */ int Sqlitetest1_Init(Tcl_Interp *interp){ extern int sqlite3_search_count; extern int sqlite3_found_count; extern int sqlite3_interrupt_count; extern int sqlite3_open_file_count; extern int sqlite3_sort_count; extern int sqlite3_current_time; #if SQLITE_OS_UNIX && defined(__APPLE__) extern int sqlite3_hostid_num; #endif |
︙ | ︙ | |||
5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 | } for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){ Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, aObjCmd[i].clientData, 0); } Tcl_LinkVar(interp, "sqlite_search_count", (char*)&sqlite3_search_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_sort_count", (char*)&sqlite3_sort_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite3_max_blobsize", (char*)&sqlite3_max_blobsize, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_like_count", (char*)&sqlite3_like_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_interrupt_count", | > > | 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 | } for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){ Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, aObjCmd[i].clientData, 0); } Tcl_LinkVar(interp, "sqlite_search_count", (char*)&sqlite3_search_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_found_count", (char*)&sqlite3_found_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_sort_count", (char*)&sqlite3_sort_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite3_max_blobsize", (char*)&sqlite3_max_blobsize, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_like_count", (char*)&sqlite3_like_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_interrupt_count", |
︙ | ︙ |
Changes to src/vdbe.c.
︙ | ︙ | |||
94 95 96 97 98 99 100 101 102 103 104 105 106 107 | static void updateMaxBlobsize(Mem *p){ if( (p->flags & (MEM_Str|MEM_Blob))!=0 && p->n>sqlite3_max_blobsize ){ sqlite3_max_blobsize = p->n; } } #endif /* ** Test a register to see if it exceeds the current maximum blob size. ** If it does, record the new maximum blob size. */ #if defined(SQLITE_TEST) && !defined(SQLITE_OMIT_BUILTIN_TEST) # define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P) #else | > > > > > > > > > > > | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | static void updateMaxBlobsize(Mem *p){ if( (p->flags & (MEM_Str|MEM_Blob))!=0 && p->n>sqlite3_max_blobsize ){ sqlite3_max_blobsize = p->n; } } #endif /* ** The next global variable is incremented each type the OP_Found opcode ** is executed. This is used to test whether or not the foreign key ** operation implemented using OP_FkIsZero is working. This variable ** has no function other than to help verify the correct operation of the ** library. */ #ifdef SQLITE_TEST int sqlite3_found_count = 0; #endif /* ** Test a register to see if it exceeds the current maximum blob size. ** If it does, record the new maximum blob size. */ #if defined(SQLITE_TEST) && !defined(SQLITE_OMIT_BUILTIN_TEST) # define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P) #else |
︙ | ︙ | |||
3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 | case OP_NotFound: /* jump, in3 */ case OP_Found: { /* jump, in3 */ int alreadyExists; VdbeCursor *pC; int res; UnpackedRecord *pIdxKey; char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*3 + 7]; alreadyExists = 0; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); if( ALWAYS(pC->pCursor!=0) ){ | > > > > | 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 | case OP_NotFound: /* jump, in3 */ case OP_Found: { /* jump, in3 */ int alreadyExists; VdbeCursor *pC; int res; UnpackedRecord *pIdxKey; char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*3 + 7]; #ifdef SQLITE_TEST sqlite3_found_count++; #endif alreadyExists = 0; assert( pOp->p1>=0 && pOp->p1<p->nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); if( ALWAYS(pC->pCursor!=0) ){ |
︙ | ︙ | |||
4898 4899 4900 4901 4902 4903 4904 | } #endif /* #ifndef SQLITE_OMIT_TRIGGER */ #ifndef SQLITE_OMIT_FOREIGN_KEY /* Opcode: FkCounter P1 P2 * * * ** | | | | | | | > > > > > > > > > > > > > > > > > > > > | 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 | } #endif /* #ifndef SQLITE_OMIT_TRIGGER */ #ifndef SQLITE_OMIT_FOREIGN_KEY /* Opcode: FkCounter P1 P2 * * * ** ** Increment a "constraint counter" by P2 (P2 may be negative or positive). ** If P1 is non-zero, the database constraint counter is incremented ** (deferred foreign key constraints). Otherwise, if P1 is zero, the ** statement counter is incremented (immediate foreign key constraints). */ case OP_FkCounter: { if( pOp->p1 ){ db->nDeferredCons += pOp->p2; }else{ p->nFkConstraint += pOp->p2; } break; } /* Opcode: FkIfZero P1 P2 * * * ** ** This opcode tests if a foreign key constraint-counter is currently zero. ** If so, jump to instruction P2. Otherwise, fall through to the next ** instruction. ** ** If P1 is non-zero, then the jump is taken if the database constraint-counter ** is zero (the one that counts deferred constraint violations). If P1 is ** zero, the jump is taken if the statement constraint-counter is zero ** (immediate foreign key constraint violations). */ case OP_FkIfZero: { /* jump */ if( pOp->p1 ){ if( db->nDeferredCons==0 ) pc = pOp->p2-1; }else{ if( p->nFkConstraint==0 ) pc = pOp->p2-1; } break; } #endif /* #ifndef SQLITE_OMIT_FOREIGN_KEY */ #ifndef SQLITE_OMIT_AUTOINCREMENT /* Opcode: MemMax P1 P2 * * * |
︙ | ︙ |
Changes to src/vdbeaux.c.
︙ | ︙ | |||
337 338 339 340 341 342 343 | memset(&sIter, 0, sizeof(sIter)); sIter.v = v; while( (pOp = opIterNext(&sIter))!=0 ){ int opcode = pOp->opcode; if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename #ifndef SQLITE_OMIT_FOREIGN_KEY | | | 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 | memset(&sIter, 0, sizeof(sIter)); sIter.v = v; 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==SQLITE_CONSTRAINT && pOp->p2==OE_Abort)) ){ hasAbort = 1; break; } |
︙ | ︙ |
Changes to test/fkey2.test.
︙ | ︙ | |||
56 57 58 59 60 61 62 63 64 65 66 67 68 69 | # fkey2-12.*: Test RESTRICT actions. # # fkey2-13.*: Test that FK processing is performed when a row is REPLACED by # an UPDATE or INSERT statement. # # fkey2-14.*: Test the ALTER TABLE and DROP TABLE commands. # # fkey2-genfkey.*: Tests that were used with the shell tool .genfkey # command. Recycled to test the built-in implementation. # proc drop_all_tables {{db db}} { set tbls [execsql {SELECT name FROM sqlite_master WHERE type = 'table'}] | > > > > > | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | # fkey2-12.*: Test RESTRICT actions. # # fkey2-13.*: Test that FK processing is performed when a row is REPLACED by # an UPDATE or INSERT statement. # # fkey2-14.*: Test the ALTER TABLE and DROP TABLE commands. # # fkey2-15.*: Test that if there are no (known) outstanding foreign key # constraint violations in the database, inserting into a parent # table or deleting from a child table does not cause SQLite # to check if this has repaired an outstanding violation. # # fkey2-genfkey.*: Tests that were used with the shell tool .genfkey # command. Recycled to test the built-in implementation. # proc drop_all_tables {{db db}} { set tbls [execsql {SELECT name FROM sqlite_master WHERE type = 'table'}] |
︙ | ︙ | |||
87 88 89 90 91 92 93 94 95 96 97 98 99 100 | CREATE TABLE t7(a, b INTEGER PRIMARY KEY); CREATE TABLE t8(c REFERENCES t7 /D/, d); CREATE TABLE t9(a REFERENCES nosuchtable, b); CREATE TABLE t10(a REFERENCES t9(c) /D/, b); } set FkeySimpleTests { 1.1 "INSERT INTO t2 VALUES(1, 3)" {1 {foreign key constraint failed}} 1.2 "INSERT INTO t1 VALUES(1, 2)" {0 {}} 1.3 "INSERT INTO t2 VALUES(1, 3)" {0 {}} 1.4 "INSERT INTO t2 VALUES(2, 4)" {1 {foreign key constraint failed}} 1.5 "INSERT INTO t2 VALUES(NULL, 4)" {0 {}} 1.6 "UPDATE t2 SET c=2 WHERE d=4" {1 {foreign key constraint failed}} | > > | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | CREATE TABLE t7(a, b INTEGER PRIMARY KEY); CREATE TABLE t8(c REFERENCES t7 /D/, d); CREATE TABLE t9(a REFERENCES nosuchtable, b); CREATE TABLE t10(a REFERENCES t9(c) /D/, b); } set FkeySimpleTests { 1.1 "INSERT INTO t2 VALUES(1, 3)" {1 {foreign key constraint failed}} 1.2 "INSERT INTO t1 VALUES(1, 2)" {0 {}} 1.3 "INSERT INTO t2 VALUES(1, 3)" {0 {}} 1.4 "INSERT INTO t2 VALUES(2, 4)" {1 {foreign key constraint failed}} 1.5 "INSERT INTO t2 VALUES(NULL, 4)" {0 {}} 1.6 "UPDATE t2 SET c=2 WHERE d=4" {1 {foreign key constraint failed}} |
︙ | ︙ | |||
764 765 766 767 768 769 770 | # TABLE" commands work as expected wrt foreign key constraints. # # fkey2-14.1*: ALTER TABLE ADD COLUMN # fkey2-14.2*: ALTER TABLE RENAME TABLE # fkey2-14.3*: DROP TABLE # drop_all_tables | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 | # TABLE" commands work as expected wrt foreign key constraints. # # fkey2-14.1*: ALTER TABLE ADD COLUMN # fkey2-14.2*: ALTER TABLE RENAME TABLE # fkey2-14.3*: DROP TABLE # drop_all_tables do_test fkey2-14.1.1 { # Adding a column with a REFERENCES clause is not supported. execsql { CREATE TABLE t1(a PRIMARY KEY); CREATE TABLE t2(a, b); } catchsql { ALTER TABLE t2 ADD COLUMN c REFERENCES t1 } } {0 {}} do_test fkey2-14.1.2 { catchsql { ALTER TABLE t2 ADD COLUMN d DEFAULT NULL REFERENCES t1 } } {0 {}} do_test fkey2-14.1.3 { catchsql { ALTER TABLE t2 ADD COLUMN e REFERENCES t1 DEFAULT NULL} } {0 {}} do_test fkey2-14.1.4 { catchsql { ALTER TABLE t2 ADD COLUMN f REFERENCES t1 DEFAULT 'text'} } {1 {Cannot add a REFERENCES column with non-NULL default value}} do_test fkey2-14.1.5 { catchsql { ALTER TABLE t2 ADD COLUMN g DEFAULT CURRENT_TIME REFERENCES t1 } } {1 {Cannot add a REFERENCES column with non-NULL default value}} do_test fkey2-14.1.6 { execsql { PRAGMA foreign_keys = off; ALTER TABLE t2 ADD COLUMN h DEFAULT 'text' REFERENCES t1; PRAGMA foreign_keys = on; SELECT sql FROM sqlite_master WHERE name='t2'; } } {{CREATE TABLE t2(a, b, c REFERENCES t1, d DEFAULT NULL REFERENCES t1, e REFERENCES t1 DEFAULT NULL, h DEFAULT 'text' REFERENCES t1)}} #------------------------------------------------------------------------- # The following tests, fkey2-15.*, test that unnecessary FK related scans # and lookups are avoided when the constraint counters are zero. # drop_all_tables proc execsqlS {zSql} { set ::sqlite_search_count 0 set ::sqlite_found_count 0 set res [uplevel [list execsql $zSql]] concat [expr $::sqlite_found_count + $::sqlite_search_count] $res } do_test fkey2-15.1.1 { execsql { CREATE TABLE pp(a PRIMARY KEY, b); CREATE TABLE cc(x, y REFERENCES pp DEFERRABLE INITIALLY DEFERRED); INSERT INTO pp VALUES(1, 'one'); INSERT INTO pp VALUES(2, 'two'); INSERT INTO cc VALUES('neung', 1); INSERT INTO cc VALUES('song', 2); } } {} do_test fkey2-15.1.2 { execsqlS { INSERT INTO pp VALUES(3, 'three') } } {0} do_test fkey2-15.1.3 { execsql { BEGIN; INSERT INTO cc VALUES('see', 4); -- Violates deferred constraint } execsqlS { INSERT INTO pp VALUES(5, 'five') } } {2} do_test fkey2-15.1.4 { execsql { DELETE FROM cc WHERE x = 'see' } execsqlS { INSERT INTO pp VALUES(6, 'six') } } {0} do_test fkey2-15.1.5 { execsql COMMIT } {} do_test fkey2-15.1.6 { execsql BEGIN execsqlS { DELETE FROM cc WHERE x = 'neung'; ROLLBACK; } } {1} do_test fkey2-15.1.7 { execsql { BEGIN; DELETE FROM pp WHERE a = 2; } execsqlS { DELETE FROM cc WHERE x = 'neung'; ROLLBACK; } } {2} #------------------------------------------------------------------------- # The following block of tests, those prefixed with "fkey2-genfkey.", are # the same tests that were used to test the ".genfkey" command provided # by the shell tool. So these tests show that the built-in foreign key # implementation is more or less compatible with the triggers generated # by genfkey. |
︙ | ︙ |