Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | 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) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
06a71b162b032fc5b56d18919a784d4e |
User & Date: | drh 2005-06-24 03:53:06.000 |
Context
2005-06-25
| ||
18:42 | Remove the blob(), text() and numeric() functions added in (2524) and replace them with the standard CAST operator. Ticket #1287. (CVS 2527) (check-in: 17631785f9 user: drh tags: trunk) | |
2005-06-24
| ||
03:53 | 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) (check-in: 06a71b162b user: drh tags: trunk) | |
2005-06-23
| ||
03:15 | Make sure the String8 opcode always has a non-null P3 argument in the foreign_key_list pragma. Ticket #1297. (CVS 2525) (check-in: bcf62dc7a1 user: drh tags: trunk) | |
Changes
Changes to src/build.c.
︙ | ︙ | |||
18 19 20 21 22 23 24 | ** CREATE INDEX ** DROP INDEX ** creating ID lists ** BEGIN TRANSACTION ** COMMIT ** ROLLBACK ** | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | ** CREATE INDEX ** DROP INDEX ** creating ID lists ** BEGIN TRANSACTION ** COMMIT ** ROLLBACK ** ** $Id: build.c,v 1.328 2005/06/24 03:53:06 drh Exp $ */ #include "sqliteInt.h" #include <ctype.h> /* ** This routine is called when a new SQL statement is beginning to ** be parsed. Initialize the pParse structure as needed. |
︙ | ︙ | |||
1969 1970 1971 1972 1973 1974 1975 | static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ Table *pTab = pIndex->pTable; /* The table that is indexed */ int iTab = pParse->nTab; /* Btree cursor used for pTab */ int iIdx = pParse->nTab+1; /* Btree cursor used for pIndex */ int addr1; /* Address of top of loop */ int tnum; /* Root page of index */ Vdbe *v; /* Generate code into this virtual machine */ | < | 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 | static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ Table *pTab = pIndex->pTable; /* The table that is indexed */ int iTab = pParse->nTab; /* Btree cursor used for pTab */ int iIdx = pParse->nTab+1; /* Btree cursor used for pIndex */ int addr1; /* Address of top of loop */ int tnum; /* Root page of index */ Vdbe *v; /* Generate code into this virtual machine */ #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0, pParse->db->aDb[pIndex->iDb].zName ) ){ return; } #endif |
︙ | ︙ | |||
2003 2004 2005 2006 2007 2008 2009 | sqlite3VdbeOp3(v, OP_OpenWrite, iIdx, tnum, (char*)&pIndex->keyInfo, P3_KEYINFO); sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0); sqlite3VdbeAddOp(v, OP_OpenRead, iTab, pTab->tnum); sqlite3VdbeAddOp(v, OP_SetNumColumns, iTab, pTab->nCol); addr1 = sqlite3VdbeAddOp(v, OP_Rewind, iTab, 0); sqlite3GenerateIndexKey(v, pIndex, iTab); | | > > > > | | > | > > | 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 | sqlite3VdbeOp3(v, OP_OpenWrite, iIdx, tnum, (char*)&pIndex->keyInfo, P3_KEYINFO); sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0); sqlite3VdbeAddOp(v, OP_OpenRead, iTab, pTab->tnum); sqlite3VdbeAddOp(v, OP_SetNumColumns, iTab, pTab->nCol); addr1 = sqlite3VdbeAddOp(v, OP_Rewind, iTab, 0); sqlite3GenerateIndexKey(v, pIndex, iTab); 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); sqlite3VdbeAddOp(v, OP_Close, iIdx, 0); } /* |
︙ | ︙ |
Changes to src/delete.c.
︙ | ︙ | |||
8 9 10 11 12 13 14 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains C code routines that are called by the parser ** in order to generate code for DELETE FROM statements. ** | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** 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.107 2005/06/24 03:53:06 drh Exp $ */ #include "sqliteInt.h" /* ** Look up every table that is named in pSrc. If any table is not found, ** add an error message to pParse->zErrMsg and return NULL. If all tables ** are found, return a pointer to the last table. |
︙ | ︙ | |||
438 439 440 441 442 443 444 | if( idx==pTab->iPKey ){ sqlite3VdbeAddOp(v, OP_Dup, j, 0); }else{ sqlite3VdbeAddOp(v, OP_Column, iCur, idx); sqlite3ColumnDefault(v, pTab, idx); } } | | | 438 439 440 441 442 443 444 445 446 447 | if( idx==pTab->iPKey ){ sqlite3VdbeAddOp(v, OP_Dup, j, 0); }else{ sqlite3VdbeAddOp(v, OP_Column, iCur, idx); sqlite3ColumnDefault(v, pTab, idx); } } sqlite3VdbeAddOp(v, OP_MakeIdxRec, pIdx->nColumn, 0); sqlite3IndexAffinityStr(v, pIdx); } |
Changes to src/insert.c.
︙ | ︙ | |||
8 9 10 11 12 13 14 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle INSERT statements in SQLite. ** | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle INSERT statements in SQLite. ** ** $Id: insert.c,v 1.140 2005/06/24 03:53:06 drh Exp $ */ #include "sqliteInt.h" /* ** Set P3 of the most recently inserted opcode to a column affinity ** string for index pIdx. A column affinity string has one character ** for each column in the table, according to the affinity of the column: |
︙ | ︙ | |||
945 946 947 948 949 950 951 | int idx = pIdx->aiColumn[i]; if( idx==pTab->iPKey ){ sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol+1, 1); }else{ sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1); } } | | | 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 | int idx = pIdx->aiColumn[i]; if( idx==pTab->iPKey ){ sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol+1, 1); }else{ sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1); } } jumpInst1 = sqlite3VdbeAddOp(v, OP_MakeIdxRec, pIdx->nColumn, 0); sqlite3IndexAffinityStr(v, pIdx); /* Find out what action to take in case there is an indexing conflict */ onError = pIdx->onError; if( onError==OE_None ) continue; /* pIdx is not a UNIQUE index */ if( overrideError!=OE_Default ){ onError = overrideError; |
︙ | ︙ | |||
1015 1016 1017 1018 1019 1020 1021 | sqlite3VdbeAddOp(v, OP_MoveGe, base, 0); } seenReplace = 1; break; } } contAddr = sqlite3VdbeCurrentAddr(v); | < | | 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 | sqlite3VdbeAddOp(v, OP_MoveGe, base, 0); } seenReplace = 1; break; } } contAddr = sqlite3VdbeCurrentAddr(v); #if NULL_DISTINCT_FOR_UNIQUE sqlite3VdbeChangeP2(v, jumpInst1, contAddr); #endif sqlite3VdbeChangeP2(v, jumpInst2, contAddr); } } /* ** This routine generates code to finish the INSERT or UPDATE operation |
︙ | ︙ |
Changes to src/vdbe.c.
︙ | ︙ | |||
39 40 41 42 43 44 45 | ** ** Various scripts scan this source file in order to generate HTML ** documentation, headers files, or other derived files. The formatting ** of the code in this file is, therefore, important. See other comments ** in this file for details. If in doubt, do not deviate from existing ** commenting and indentation practices when changing or adding code. ** | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | ** ** Various scripts scan this source file in order to generate HTML ** documentation, headers files, or other derived files. The formatting ** of the code in this file is, therefore, important. See other comments ** 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.471 2005/06/24 03:53:06 drh Exp $ */ #include "sqliteInt.h" #include "os.h" #include <ctype.h> #include "vdbeInt.h" /* |
︙ | ︙ | |||
629 630 631 632 633 634 635 | case OP_Return: { /* no-push */ assert( p->returnDepth>0 ); p->returnDepth--; pc = p->returnStack[p->returnDepth] - 1; break; } | | > > | 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 | case OP_Return: { /* no-push */ assert( p->returnDepth>0 ); p->returnDepth--; pc = p->returnStack[p->returnDepth] - 1; break; } /* Opcode: Halt P1 P2 P3 ** ** Exit immediately. All open cursors, Lists, Sorts, etc are closed ** automatically. ** ** P1 is the result code returned by sqlite3_exec(), sqlite3_reset(), ** or sqlite3_finalize(). For a normal halt, this should be SQLITE_OK (0). ** For errors, it can be some other value. If P1!=0 then P2 will determine ** whether or not to rollback the current transaction. Do not rollback ** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort, ** 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. */ case OP_Halt: { /* no-push */ p->pTos = pTos; |
︙ | ︙ | |||
1996 1997 1998 1999 2000 2001 2002 | ** sqlite3VdbeRecordCompare function will correctly compare two encoded ** records. Refer to source code comments for the details of the record ** format. ** ** The original stack entries are popped from the stack if P1>0 but ** remain on the stack if P1<0. ** | < < < | | | > > > > > > > > > > | 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 | ** sqlite3VdbeRecordCompare function will correctly compare two encoded ** records. Refer to source code comments for the details of the record ** format. ** ** The original stack entries are popped from the stack if P1>0 but ** remain on the stack if P1<0. ** ** 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 ** field of the index key (i.e. the first character of P3 corresponds to the ** lowest element on the stack). ** ** The mapping from character to affinity is as follows: ** 'n' = NUMERIC. ** 'i' = INTEGER. ** 't' = TEXT. ** '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: ** ** ------------------------------------------------------------------------ ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | ** ------------------------------------------------------------------------ |
︙ | ︙ | |||
2053 2054 2055 2056 2057 2058 2059 | int nField; /* Number of fields in the record */ int jumpIfNull; /* Jump here if non-zero and any entries are NULL. */ int addRowid; /* True to append a rowid column at the end */ char *zAffinity; /* The affinity string for the record */ leaveOnStack = ((pOp->p1<0)?1:0); nField = pOp->p1 * (leaveOnStack?-1:1); | | | | 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 | int nField; /* Number of fields in the record */ int jumpIfNull; /* Jump here if non-zero and any entries are NULL. */ int addRowid; /* True to append a rowid column at the end */ char *zAffinity; /* The affinity string for the record */ leaveOnStack = ((pOp->p1<0)?1:0); nField = pOp->p1 * (leaveOnStack?-1:1); jumpIfNull = pOp->p2; addRowid = pOp->opcode==OP_MakeIdxRec; zAffinity = pOp->p3; pData0 = &pTos[1-nField]; assert( pData0>=p->aStack ); containsNull = 0; /* Loop through the elements that will make up the record to figure |
︙ | ︙ | |||
3440 3441 3442 3443 3444 3445 3446 | }else{ pC->nullRow = 1; } pC->rowidIsValid = 0; break; } | | < < < < < > < < < < < < < < < < < < < < < < < < < < < < < < < < | 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 | }else{ pC->nullRow = 1; } pC->rowidIsValid = 0; break; } /* 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. ** ** This instruction only works for indices. The equivalent instruction ** for tables is OP_Insert. */ case OP_IdxInsert: { /* no-push */ int i = pOp->p1; Cursor *pC; BtCursor *pCrsr; assert( pTos>=p->aStack ); 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; assert( pC->isTable==0 ); rc = sqlite3BtreeInsert(pCrsr, zKey, nKey, "", 0); assert( pC->deferredMoveto==0 ); pC->cacheValid = 0; } Release(pTos); pTos--; |
︙ | ︙ |
Changes to test/unique.test.
︙ | ︙ | |||
8 9 10 11 12 13 14 | # May you share freely, never taking more than you give. # #*********************************************************************** # This file implements regression tests for SQLite library. The # focus of this file is testing the CREATE UNIQUE INDEX statement, # and primary keys, and the UNIQUE constraint on table columns # | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # May you share freely, never taking more than you give. # #*********************************************************************** # This file implements regression tests for SQLite library. The # 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.8 2005/06/24 03:53:06 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Try to create a table with two primary keys. # (This is allowed in SQLite even that it is not valid SQL) # |
︙ | ︙ | |||
192 193 194 195 196 197 198 | } } {0 {}} do_test unique-4.5 { execsql { SELECT * FROM t4 } } {1 2 3 {} 2 {} {} 3 4 2 2 {}} | > > > > > > > > > > > > > > > > > > > > > | | 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | } } {0 {}} do_test unique-4.5 { execsql { SELECT * FROM t4 } } {1 2 3 {} 2 {} {} 3 4 2 2 {}} # 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. # do_test unique-5.1 { execsql { CREATE TABLE t5( |
︙ | ︙ |