From: drh Date: Fri, 1 Aug 2014 01:40:33 +0000 (+0000) Subject: Enhance the PRAGMA integrity_check command to detect UNIQUE and NOT NULL X-Git-Tag: version-3.8.6~42 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cefc87fca5bbfd6bd8f918373de15efd9fe5b413;p=thirdparty%2Fsqlite.git Enhance the PRAGMA integrity_check command to detect UNIQUE and NOT NULL constraint violations. FossilOrigin-Name: 9abcf2698c09f4f6a44a68e74f9f6b538f3253d6 --- diff --git a/manifest b/manifest index 7cd7515a7e..f13081bfc1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Refactoring:\s\sChange\s"pIndex->onError!=OE_None"\sto\suse\sa\smacro:\n"IsUniqueIndex(pIndex)".\s\sEasier\sto\sunderstand\sthat\sway. -D 2014-07-31T22:59:04.121 +C Enhance\sthe\sPRAGMA\sintegrity_check\scommand\sto\sdetect\sUNIQUE\sand\sNOT\sNULL\nconstraint\sviolations. +D 2014-08-01T01:40:33.869 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 5eb79e334a5de69c87740edd56af6527dd219308 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -216,7 +216,7 @@ F src/parse.y 22d6a074e5f5a7258947a1dc55a9bf946b765dd0 F src/pcache.c d8eafac28290d4bb80332005435db44991d07fc2 F src/pcache.h a5e4f5d9f5d592051d91212c5949517971ae6222 F src/pcache1.c 102e6f5a2fbc646154463eb856d1fd716867b64c -F src/pragma.c f9268bd5fa072afb3a174149129859727efb4326 +F src/pragma.c d4a33151f057e35e5a2024adf8e41d2817b5c105 F src/prepare.c 677521ab7132615a8a26107a1d1c3132f44ae337 F src/printf.c af06f66927919730f03479fed6ae9854f73419f4 F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece @@ -283,7 +283,7 @@ F src/update.c 01564b3c430f6c7b0a35afaf7aba7987206fa3a5 F src/utf.c a0314e637768a030e6e84a957d0c4f6ba910cc05 F src/util.c 3076bdd51cdbf60a6e2e57fada745be37133c73e F src/vacuum.c 3728d74919d4fb1356f9e9a13e27773db60b7179 -F src/vdbe.c f87f77b0049cbef1fa68b331c3551d82b4d9fba4 +F src/vdbe.c 115e08834b883964d9a480f685d8601826c5792a F src/vdbe.h c63fad052c9e7388d551e556e119c0bcf6bebdf8 F src/vdbeInt.h f5513f2b5ac1e2c5128996c7ea23add256a301df F src/vdbeapi.c 24e40422382beb774daab11fe9fe9d37e8a04949 @@ -754,7 +754,7 @@ F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025 F test/percentile.test b98fc868d71eb5619d42a1702e9ab91718cbed54 F test/permutations.test bc474bafb022cc5014ef3a9c3d5ab61d6d6f587c -F test/pragma.test adb21a90875bc54a880fa939c4d7c46598905aa0 +F test/pragma.test 19d0241a007bcdd77fc2606ec60fc60357e7fc8b F test/pragma2.test aea7b3d82c76034a2df2b38a13745172ddc0bc13 F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552 F test/printf2.test bed79b4c3e5da08ba88ad637c0bf62586843cfb1 @@ -1185,7 +1185,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 1361450a9dfe9476e8df98f370a3695752252245 -R 114928731acdabb64615a67d8eb69386 +P e75b26ee357bb3d3c1a539b05d633ebf314726d7 +R 50d518176c08d9fef67243df4085599b U drh -Z 0cd7191a5ffc11f0c0a8249763204863 +Z ae546b77e53655a859301e9ce422b440 diff --git a/manifest.uuid b/manifest.uuid index 0455b18c12..087d6ad801 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e75b26ee357bb3d3c1a539b05d633ebf314726d7 \ No newline at end of file +9abcf2698c09f4f6a44a68e74f9f6b538f3253d6 \ No newline at end of file diff --git a/src/pragma.c b/src/pragma.c index a4a1b2a250..b37499f7bd 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1908,28 +1908,76 @@ void sqlite3Pragma( pParse->nMem = MAX(pParse->nMem, 8+j); sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v); loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1); + /* Verify that all NOT NULL columns really are NOT NULL */ + for(j=0; jnCol; j++){ + char *zErr; + int jmp2, jmp3; + if( j==pTab->iPKey ) continue; + if( pTab->aCol[j].notNull==0 ) continue; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ + zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, + pTab->aCol[j].zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); + jmp3 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v); + sqlite3VdbeAddOp0(v, OP_Halt); + sqlite3VdbeJumpHere(v, jmp2); + sqlite3VdbeJumpHere(v, jmp3); + } + /* Validate index entries for the current row */ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - int jmp2, jmp3, jmp4; + int jmp2, jmp3, jmp4, jmp5; + int ckUniq = sqlite3VdbeMakeLabel(v); if( pPk==pIdx ) continue; r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3, pPrior, r1); pPrior = pIdx; sqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1); /* increment entry count */ - jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, 0, r1, + /* Verify that an index entry exists for the current table row */ + jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1, pIdx->nColumn); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, "row ", P4_STATIC); sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); - sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, " missing from index ", - P4_STATIC); + sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, + " missing from index ", P4_STATIC); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); - sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, pIdx->zName, P4_TRANSIENT); + jmp5 = sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, + pIdx->zName, P4_TRANSIENT); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); jmp4 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v); sqlite3VdbeAddOp0(v, OP_Halt); - sqlite3VdbeJumpHere(v, jmp4); sqlite3VdbeJumpHere(v, jmp2); + /* For UNIQUE indexes, verify that only one entry exists with the + ** current key. The entry is unique if (1) any column is NULL + ** or (2) the next entry has a different key */ + if( IsUniqueIndex(pIdx) ){ + int uniqOk = sqlite3VdbeMakeLabel(v); + int jmp6; + int kk; + for(kk=0; kknKeyCol; kk++){ + int iCol = pIdx->aiColumn[kk]; + assert( iCol>=0 && iColnCol ); + if( pTab->aCol[iCol].notNull ) continue; + sqlite3VdbeAddOp2(v, OP_IsNull, r1+kk, uniqOk); + VdbeCoverage(v); + } + jmp6 = sqlite3VdbeAddOp1(v, OP_Next, iIdxCur+j); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Goto, 0, uniqOk); + sqlite3VdbeJumpHere(v, jmp6); + sqlite3VdbeAddOp4Int(v, OP_IdxGT, iIdxCur+j, uniqOk, r1, + pIdx->nKeyCol); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, + "non-unique entry in index ", P4_STATIC); + sqlite3VdbeAddOp2(v, OP_Goto, 0, jmp5); + sqlite3VdbeResolveLabel(v, uniqOk); + } + sqlite3VdbeJumpHere(v, jmp4); sqlite3ResolvePartIdxLabel(pParse, jmp3); } sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v); diff --git a/src/vdbe.c b/src/vdbe.c index 52f0f3aa74..07514951a4 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -3747,9 +3747,9 @@ case OP_Seek: { /* in2 */ ** is a prefix of any entry in P1 then a jump is made to P2 and ** P1 is left pointing at the matching entry. ** -** This operation leaves the cursor in a state where it cannot be -** advanced in either direction. In other words, the Next and Prev -** opcodes do not work after this operation. +** This operation leaves the cursor in a state where it can be +** advanced in the forward direction. The Next instruction will work, +** but not the Prev instruction. ** ** See also: NotFound, NoConflict, NotExists. SeekGe */ @@ -3816,7 +3816,7 @@ case OP_Found: { /* jump, in3 */ pC = p->apCsr[pOp->p1]; assert( pC!=0 ); #ifdef SQLITE_DEBUG - pC->seekOp = 0; + pC->seekOp = pOp->opcode; #endif pIn3 = &aMem[pOp->p3]; assert( pC->pCursor!=0 ); @@ -4673,7 +4673,7 @@ case OP_Next: /* jump */ ** The Prev opcode is only used after SeekLT, SeekLE, and Last. */ assert( pOp->opcode!=OP_Next || pOp->opcode!=OP_NextIfOpen || pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE - || pC->seekOp==OP_Rewind ); + || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found); assert( pOp->opcode!=OP_Prev || pOp->opcode!=OP_PrevIfOpen || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE || pC->seekOp==OP_Last ); diff --git a/test/pragma.test b/test/pragma.test index 8f54e695d7..539d867366 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -431,7 +431,32 @@ Page 6 is never used} {row 1 missing from index i2}} db eval {PRAGMA integrity_check} } {ok} } -#exit + +# Verify that PRAGMA integrity_check catches UNIQUE and NOT NULL +# constraint violations. +# +do_execsql_test pragma-3.20 { + CREATE TABLE t1(a,b); + CREATE INDEX t1a ON t1(a); + INSERT INTO t1 VALUES(1,1),(2,2),(3,3),(2,4),(NULL,5),(NULL,6); + PRAGMA writable_schema=ON; + UPDATE sqlite_master SET sql='CREATE UNIQUE INDEX t1a ON t1(a)' + WHERE name='t1a'; + UPDATE sqlite_master SET sql='CREATE TABLE t1(a NOT NULL,b)' + WHERE name='t1'; + PRAGMA writable_schema=OFF; + ALTER TABLE t1 RENAME TO t1x; + PRAGMA integrity_check; +} {{non-unique entry in index t1a} {NULL value in t1x.a} {non-unique entry in index t1a} {NULL value in t1x.a}} +do_execsql_test pragma-3.21 { + PRAGMA integrity_check(3); +} {{non-unique entry in index t1a} {NULL value in t1x.a} {non-unique entry in index t1a}} +do_execsql_test pragma-3.22 { + PRAGMA integrity_check(2); +} {{non-unique entry in index t1a} {NULL value in t1x.a}} +do_execsql_test pragma-3.21 { + PRAGMA integrity_check(1); +} {{non-unique entry in index t1a}} # Test modifying the cache_size of an attached database. ifcapable pager_pragmas&&attach {