NULL values in a row of a unique index cause the row to be distinct.
Ticket #1301. More testing and optimization needs to be done on this before closing the ticket. (CVS 2526) FossilOrigin-Name: 06a71b162b032fc5b56d18919a784d4ee94dde7c
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
C Make\ssure\sthe\sString8\sopcode\salways\shas\sa\snon-null\sP3\sargument\sin\nthe\sforeign_key_list\spragma.\s\sTicket\s#1297.\s(CVS\s2525)
|
||||
D 2005-06-23T03:15:08
|
||||
C NULL\svalues\sin\sa\srow\sof\sa\sunique\sindex\scause\sthe\srow\sto\sbe\sdistinct.\nTicket\s#1301.\s\sMore\stesting\sand\soptimization\sneeds\sto\sbe\sdone\son\sthis\nbefore\sclosing\sthe\sticket.\s(CVS\s2526)
|
||||
D 2005-06-24T03:53:06
|
||||
F Makefile.in 64a6635ef44a98325e0cffe8d67669920a3dad47
|
||||
F Makefile.linux-gcc 06be33b2a9ad4f005a5f42b22c4a19dab3cbb5c7
|
||||
F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028
|
||||
@@ -32,16 +32,16 @@ F src/attach.c 3615dbe960cbee4aa5ea300b8a213dad36527b0f
|
||||
F src/auth.c 18c5a0befe20f3a58a41e3ddd78f372faeeefe1f
|
||||
F src/btree.c a167f412cf5b269bffba925ac55a1c0a2f749e29
|
||||
F src/btree.h 41a71ce027db9ddee72cb43df2316bbe3a1d92af
|
||||
F src/build.c b6db069ef2e1e1b21460857f498d45a9c0080fe8
|
||||
F src/build.c 3b64205934761976857ed3fe884854eb4c3b856a
|
||||
F src/callback.c 0910b611e0c158f107ee3ff86f8a371654971e2b
|
||||
F src/date.c 2134ef4388256e8247405178df8a61bd60dc180a
|
||||
F src/delete.c 4b68127f55971c7fb459146e0b6cf3bd70cfffe9
|
||||
F src/delete.c 9bb19ede439cf325bc6d6f5995b6393fb85b5162
|
||||
F src/experimental.c 50c1e3b34f752f4ac10c36f287db095c2b61766d
|
||||
F src/expr.c 4d6e26da200e0d08233df52fd8d07916d24a6926
|
||||
F src/func.c 301b81af2e831b2e929f0ba252739c32a0c756e5
|
||||
F src/hash.c 2b1b13f7400e179631c83a1be0c664608c8f021f
|
||||
F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84
|
||||
F src/insert.c 8c0868a975fe37366ed92e1b976853be96284607
|
||||
F src/insert.c d61752504f8a67e28a3bd45288051a587ba899cd
|
||||
F src/legacy.c d58ea507bce885298a2c8c3cbb0f4bff5d47830b
|
||||
F src/main.c 7d0293d9520688d47092ff48c1ed5254cd3c4474
|
||||
F src/md5.c 7ae1c39044b95de2f62e066f47bb1deb880a1070
|
||||
@@ -77,7 +77,7 @@ F src/update.c e96c7b342cd8903c672162f4cf84d2c737943347
|
||||
F src/utf.c bda5eb85039ef16f2d17004c1e18c96e1ab0a80c
|
||||
F src/util.c 1cdce9ae9fd17307e00848d63e3bc3300ca7c9fc
|
||||
F src/vacuum.c 829d9e1a6d7c094b80e0899686670932eafd768c
|
||||
F src/vdbe.c 4745b575d9f23a960da0ce8334f99b98ea855265
|
||||
F src/vdbe.c 5c1f7ccd6a75aa2cf211a9864ff511e15e86957c
|
||||
F src/vdbe.h 75e466d84d362b0c4498978a9d6b1e6bd32ecf3b
|
||||
F src/vdbeInt.h 4312faf41630a6c215924b6c7c2f39ebb1af8ffb
|
||||
F src/vdbeapi.c 5025a9163107e0a4964212d16e1c4defa13dc5c2
|
||||
@@ -211,7 +211,7 @@ F test/trigger5.test 619391a3e9fc194081d22cefd830d811e7badf83
|
||||
F test/trigger6.test 0e411654f122552da6590f0b4e6f781048a4a9b9
|
||||
F test/types.test f0a98d10c5ecc9865d19dc94486f9873afc6bb6b
|
||||
F test/types2.test 81dd1897be8ef4b5b73d0006e6076abe40610de3
|
||||
F test/unique.test 0e38d4cc7affeef2527720d1dafd1f6870f02f2b
|
||||
F test/unique.test 0253c4227a5dc533e312202ce21ecfad18058d18
|
||||
F test/update.test 7669ca789d62c258b678e8aa7a22a57eac10f2cf
|
||||
F test/utf16.test 5fb019e09601774743858ef7380b6c02103ff120
|
||||
F test/vacuum.test 5d4857ae2afc9c20d0edb8acc58bdc8d630126a9
|
||||
@@ -281,7 +281,7 @@ F www/tclsqlite.tcl 425be741b8ae664f55cb1ef2371aab0a75109cf9
|
||||
F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
|
||||
F www/version3.tcl a99cf5f6d8bd4d5537584a2b342f0fb9fa601d8b
|
||||
F www/whentouse.tcl 528299b8316726dbcc5548e9aa0648c8b1bd055b
|
||||
P affb0fa2e8c5ff497838ba3c2994cdb1f6f50c68
|
||||
R 42d4c8589393348bc0163ea0519a9b65
|
||||
P bcf62dc7a1e8e7a3180138cf9b8944eb8fbd5490
|
||||
R fa67143595d3d7217f6f8facb2f20078
|
||||
U drh
|
||||
Z 3f4d130920cda0cbea18523ee72fbcdb
|
||||
Z 382ef1453175c5aaab493342b42488e6
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
bcf62dc7a1e8e7a3180138cf9b8944eb8fbd5490
|
||||
06a71b162b032fc5b56d18919a784d4ee94dde7c
|
||||
+12
-6
@@ -22,7 +22,7 @@
|
||||
** COMMIT
|
||||
** ROLLBACK
|
||||
**
|
||||
** $Id: build.c,v 1.327 2005/06/14 02:12:46 drh Exp $
|
||||
** $Id: build.c,v 1.328 2005/06/24 03:53:06 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include <ctype.h>
|
||||
@@ -1973,7 +1973,6 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
|
||||
int addr1; /* Address of top of loop */
|
||||
int tnum; /* Root page of index */
|
||||
Vdbe *v; /* Generate code into this virtual machine */
|
||||
int isUnique; /* True for a unique index */
|
||||
|
||||
#ifndef SQLITE_OMIT_AUTHORIZATION
|
||||
if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0,
|
||||
@@ -2007,11 +2006,18 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
|
||||
sqlite3VdbeAddOp(v, OP_SetNumColumns, iTab, pTab->nCol);
|
||||
addr1 = sqlite3VdbeAddOp(v, OP_Rewind, iTab, 0);
|
||||
sqlite3GenerateIndexKey(v, pIndex, iTab);
|
||||
isUnique = pIndex->onError!=OE_None;
|
||||
sqlite3VdbeAddOp(v, OP_IdxInsert, iIdx, isUnique);
|
||||
if( isUnique ){
|
||||
sqlite3VdbeChangeP3(v, -1, "indexed columns are not unique", P3_STATIC);
|
||||
if( pIndex->onError!=OE_None ){
|
||||
int curaddr = sqlite3VdbeCurrentAddr(v);
|
||||
int addr2 = curaddr+4;
|
||||
sqlite3VdbeChangeP2(v, curaddr-1, addr2);
|
||||
sqlite3VdbeAddOp(v, OP_Rowid, iTab, 0);
|
||||
sqlite3VdbeAddOp(v, OP_AddImm, 1, 0);
|
||||
sqlite3VdbeAddOp(v, OP_IsUnique, iIdx, addr2);
|
||||
sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, OE_Abort,
|
||||
"indexed columns are not unique", P3_STATIC);
|
||||
assert( addr2==sqlite3VdbeCurrentAddr(v) );
|
||||
}
|
||||
sqlite3VdbeAddOp(v, OP_IdxInsert, iIdx, 0);
|
||||
sqlite3VdbeAddOp(v, OP_Next, iTab, addr1+1);
|
||||
sqlite3VdbeChangeP2(v, addr1, sqlite3VdbeCurrentAddr(v));
|
||||
sqlite3VdbeAddOp(v, OP_Close, iTab, 0);
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** in order to generate code for DELETE FROM statements.
|
||||
**
|
||||
** $Id: delete.c,v 1.106 2005/06/12 21:35:52 drh Exp $
|
||||
** $Id: delete.c,v 1.107 2005/06/24 03:53:06 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -442,6 +442,6 @@ void sqlite3GenerateIndexKey(
|
||||
sqlite3ColumnDefault(v, pTab, idx);
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp(v, OP_MakeRecord, pIdx->nColumn, (1<<24));
|
||||
sqlite3VdbeAddOp(v, OP_MakeIdxRec, pIdx->nColumn, 0);
|
||||
sqlite3IndexAffinityStr(v, pIdx);
|
||||
}
|
||||
|
||||
+3
-4
@@ -12,7 +12,7 @@
|
||||
** This file contains C code routines that are called by the parser
|
||||
** to handle INSERT statements in SQLite.
|
||||
**
|
||||
** $Id: insert.c,v 1.139 2005/06/12 21:35:52 drh Exp $
|
||||
** $Id: insert.c,v 1.140 2005/06/24 03:53:06 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
@@ -949,7 +949,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1);
|
||||
}
|
||||
}
|
||||
jumpInst1 = sqlite3VdbeAddOp(v, OP_MakeRecord, pIdx->nColumn, (1<<24));
|
||||
jumpInst1 = sqlite3VdbeAddOp(v, OP_MakeIdxRec, pIdx->nColumn, 0);
|
||||
sqlite3IndexAffinityStr(v, pIdx);
|
||||
|
||||
/* Find out what action to take in case there is an indexing conflict */
|
||||
@@ -1019,9 +1019,8 @@ void sqlite3GenerateConstraintChecks(
|
||||
}
|
||||
}
|
||||
contAddr = sqlite3VdbeCurrentAddr(v);
|
||||
assert( contAddr<(1<<24) );
|
||||
#if NULL_DISTINCT_FOR_UNIQUE
|
||||
sqlite3VdbeChangeP2(v, jumpInst1, contAddr | (1<<24));
|
||||
sqlite3VdbeChangeP2(v, jumpInst1, contAddr);
|
||||
#endif
|
||||
sqlite3VdbeChangeP2(v, jumpInst2, contAddr);
|
||||
}
|
||||
|
||||
+21
-42
@@ -43,7 +43,7 @@
|
||||
** in this file for details. If in doubt, do not deviate from existing
|
||||
** commenting and indentation practices when changing or adding code.
|
||||
**
|
||||
** $Id: vdbe.c,v 1.470 2005/06/22 02:36:37 drh Exp $
|
||||
** $Id: vdbe.c,v 1.471 2005/06/24 03:53:06 drh Exp $
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include "os.h"
|
||||
@@ -633,7 +633,7 @@ case OP_Return: { /* no-push */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: Halt P1 P2 *
|
||||
/* Opcode: Halt P1 P2 P3
|
||||
**
|
||||
** Exit immediately. All open cursors, Lists, Sorts, etc are closed
|
||||
** automatically.
|
||||
@@ -646,6 +646,8 @@ case OP_Return: { /* no-push */
|
||||
** then back out all changes that have occurred during this execution of the
|
||||
** VDBE, but do not rollback the transaction.
|
||||
**
|
||||
** If P3 is not null then it is an error message string.
|
||||
**
|
||||
** There is an implied "Halt 0 0 0" instruction inserted at the very end of
|
||||
** every program. So a jump past the last instruction of the program
|
||||
** is the same as executing Halt.
|
||||
@@ -2000,12 +2002,9 @@ op_column_out:
|
||||
** The original stack entries are popped from the stack if P1>0 but
|
||||
** remain on the stack if P1<0.
|
||||
**
|
||||
** The P2 argument is divided into two 16-bit words before it is processed.
|
||||
** If the hi-word is non-zero, then an extra integer is read from the stack
|
||||
** and appended to the record as a varint. If the low-word of P2 is not
|
||||
** zero and one or more of the entries are NULL, then jump to the value of
|
||||
** the low-word of P2. This feature can be used to skip a uniqueness test
|
||||
** on indices.
|
||||
** If P2 is not zero and one or more of the entries are NULL, then jump
|
||||
** to the address given by P2. This feature can be used to skip a
|
||||
** uniqueness test on indices.
|
||||
**
|
||||
** P3 may be a string that is P1 characters long. The nth character of the
|
||||
** string indicates the column affinity that should be used for the nth
|
||||
@@ -2019,7 +2018,17 @@ op_column_out:
|
||||
** 'o' = NONE.
|
||||
**
|
||||
** If P3 is NULL then all index fields have the affinity NONE.
|
||||
**
|
||||
** See also OP_MakeIdxRec
|
||||
*/
|
||||
/* Opcode: MakeRecordI P1 P2 P3
|
||||
**
|
||||
** This opcode works just OP_MakeRecord except that it reads an extra
|
||||
** integer from the stack (thus reading a total of abs(P1+1) entries)
|
||||
** and appends that extra integer to the end of the record as a varint.
|
||||
** This results in an index key.
|
||||
*/
|
||||
case OP_MakeIdxRec:
|
||||
case OP_MakeRecord: {
|
||||
/* Assuming the record contains N fields, the record format looks
|
||||
** like this:
|
||||
@@ -2057,8 +2066,8 @@ case OP_MakeRecord: {
|
||||
|
||||
leaveOnStack = ((pOp->p1<0)?1:0);
|
||||
nField = pOp->p1 * (leaveOnStack?-1:1);
|
||||
jumpIfNull = (pOp->p2 & 0x00FFFFFF);
|
||||
addRowid = ((pOp->p2>>24) & 0x0000FFFF)?1:0;
|
||||
jumpIfNull = pOp->p2;
|
||||
addRowid = pOp->opcode==OP_MakeIdxRec;
|
||||
zAffinity = pOp->p3;
|
||||
|
||||
pData0 = &pTos[1-nField];
|
||||
@@ -3444,17 +3453,12 @@ case OP_Next: { /* no-push */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: IdxInsert P1 P2 P3
|
||||
/* Opcode: IdxInsert P1 * *
|
||||
**
|
||||
** The top of the stack holds a SQL index key made using the
|
||||
** MakeIdxKey instruction. This opcode writes that key into the
|
||||
** index P1. Data for the entry is nil.
|
||||
**
|
||||
** If P2==1, then the key must be unique. If the key is not unique,
|
||||
** the program aborts with a SQLITE_CONSTRAINT error and the database
|
||||
** is rolled back. If P3 is not null, then it becomes part of the
|
||||
** error message returned with the SQLITE_CONSTRAINT.
|
||||
**
|
||||
** This instruction only works for indices. The equivalent instruction
|
||||
** for tables is OP_Insert.
|
||||
*/
|
||||
@@ -3466,35 +3470,10 @@ case OP_IdxInsert: { /* no-push */
|
||||
assert( i>=0 && i<p->nCursor );
|
||||
assert( p->apCsr[i]!=0 );
|
||||
assert( pTos->flags & MEM_Blob );
|
||||
assert( pOp->p2==0 );
|
||||
if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
|
||||
int nKey = pTos->n;
|
||||
const char *zKey = pTos->z;
|
||||
if( pOp->p2 ){
|
||||
int res;
|
||||
int len;
|
||||
|
||||
/* 'len' is the length of the key minus the rowid at the end */
|
||||
len = nKey - sqlite3VdbeIdxRowidLen(nKey, zKey);
|
||||
|
||||
rc = sqlite3BtreeMoveto(pCrsr, zKey, len, &res);
|
||||
if( rc!=SQLITE_OK ) goto abort_due_to_error;
|
||||
while( res!=0 && !sqlite3BtreeEof(pCrsr) ){
|
||||
int c;
|
||||
if( sqlite3VdbeIdxKeyCompare(pC, len, zKey, &c)==SQLITE_OK && c==0 ){
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
if( pOp->p3 && pOp->p3[0] ){
|
||||
sqlite3SetString(&p->zErrMsg, pOp->p3, (char*)0);
|
||||
}
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
if( res<0 ){
|
||||
sqlite3BtreeNext(pCrsr, &res);
|
||||
res = +1;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert( pC->isTable==0 );
|
||||
rc = sqlite3BtreeInsert(pCrsr, zKey, nKey, "", 0);
|
||||
assert( pC->deferredMoveto==0 );
|
||||
|
||||
+23
-2
@@ -12,7 +12,7 @@
|
||||
# focus of this file is testing the CREATE UNIQUE INDEX statement,
|
||||
# and primary keys, and the UNIQUE constraint on table columns
|
||||
#
|
||||
# $Id: unique.test,v 1.7 2003/08/05 13:13:39 drh Exp $
|
||||
# $Id: unique.test,v 1.8 2005/06/24 03:53:06 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -196,7 +196,28 @@ do_test unique-4.5 {
|
||||
SELECT * FROM t4
|
||||
}
|
||||
} {1 2 3 {} 2 {} {} 3 4 2 2 {}}
|
||||
integrity_check unique-4.6
|
||||
|
||||
# Ticket #1301. Any NULL value in a set of unique columns should
|
||||
# cause the rows to be distinct.
|
||||
#
|
||||
do_test unique-4.6 {
|
||||
catchsql {
|
||||
INSERT INTO t4 VALUES(NULL, 2, NULL);
|
||||
}
|
||||
} {0 {}}
|
||||
do_test unique-4.7 {
|
||||
execsql {SELECT * FROM t4}
|
||||
} {1 2 3 {} 2 {} {} 3 4 2 2 {} {} 2 {}}
|
||||
do_test unique-4.8 {
|
||||
catchsql {CREATE UNIQUE INDEX i4a ON t4(a,b)}
|
||||
} {0 {}}
|
||||
do_test unique-4.9 {
|
||||
catchsql {CREATE UNIQUE INDEX i4b ON t4(a,b,c)}
|
||||
} {0 {}}
|
||||
do_test unique-4.10 {
|
||||
catchsql {CREATE UNIQUE INDEX i4c ON t4(b)}
|
||||
} {1 {indexed columns are not unique}}
|
||||
integrity_check unique-4.99
|
||||
|
||||
# Test the error message generation logic. In particular, make sure we
|
||||
# do not overflow the static buffer used to generate the error message.
|
||||
|
||||
Reference in New Issue
Block a user