From 0ca3e24b2ee1114f0dac2442540f584ab99e2db0 Mon Sep 17 00:00:00 2001 From: drh Date: Tue, 29 Jan 2002 23:07:02 +0000 Subject: [PATCH] The new ON CONFLICT logic is in and passes the legacy tests. But the new capabilities have not been tested and are likely broken. (CVS 356) FossilOrigin-Name: ac8a4189e2a0c41161ee359db25de94435420368 --- manifest | 24 +++--- manifest.uuid | 2 +- src/build.c | 3 +- src/delete.c | 56 +++++++++---- src/insert.c | 199 +++++++++++++++++++++++++++++----------------- src/sqliteInt.h | 6 +- src/update.c | 116 ++++++++++++++------------- src/vdbe.c | 93 ++++++++++++++-------- test/intpkey.test | 3 +- 9 files changed, 305 insertions(+), 197 deletions(-) diff --git a/manifest b/manifest index eedd784ec7..ea170d3457 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Beginning\sto\sinsert\sthe\sinfrastructure\sfor\sON\sCONFLICT\sclauses.\s(CVS\s355) -D 2002-01-29T18:41:24 +C The\snew\sON\sCONFLICT\slogic\sis\sin\sand\spasses\sthe\slegacy\stests.\s\sBut\sthe\nnew\scapabilities\shave\snot\sbeen\stested\sand\sare\slikely\sbroken.\s(CVS\s356) +D 2002-01-29T23:07:02 F Makefile.in 9fa4277413bf1d9cf91365f07d4108d7d87ed2af F Makefile.template 3e26a3b9e7aee1b811deaf673e8d8973bdb3f22d F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0 @@ -21,12 +21,12 @@ F sqlite.1 2e2bb0529ef468ade9e4322bd609d0695fb9ded9 F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6 F src/btree.c c796e387da340cb628dc1e41f684fc20253f561e F src/btree.h 9ead7f54c270d8a554e59352ca7318fdaf411390 -F src/build.c c5023252c4d0ed19067f2118639b8d9f14f3f91a -F src/delete.c 2d1abc228be80a3c41f29f3312534dd0c9cd5066 +F src/build.c c55881f270b1a77d1025dcfba7c87db46d43a9d0 +F src/delete.c 4cdb6d2e94e2eb1b1aa79eefafd4669d43c249d6 F src/expr.c 4cae8bf44d5732182e5e8c25b4552c05ea55593e F src/hash.c 8f7c740ef2eaaa8decfa8751f2be30680b123e46 F src/hash.h a5f5b3ce2d086a172c5879b0b06a27a82eac9fac -F src/insert.c 475316610462d1b6881f45565bbebf3209f1088c +F src/insert.c 35c3e17bf5f8ef3a9cdd95f7cfdbf3d94cd598c2 F src/main.c 0205771a6c31a9858ff131fc1e797b589afb76bf F src/md5.c 52f677bfc590e09f71d07d7e327bd59da738d07c F src/os.c c615faa4d23e742e0650e0751a6ad2a18438ad53 @@ -40,16 +40,16 @@ F src/select.c fc11d5a8c2bae1b62d8028ffb111c773ad6bf161 F src/shell.c c102dfe388c7618a668c944ff157c49cb48f28e3 F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e F src/sqlite.h.in f57074c84a2c112a5093ba7a9d9636aa9cacc87c -F src/sqliteInt.h f4dc7f359549f70cd0fd6c68ae054d6986ad3afc +F src/sqliteInt.h 60c0945eb4159c44adec9aadadb61dfd931d29e9 F src/table.c c89698bd5bb4b8d14722d6ee7e9be014c383d24a F src/tclsqlite.c b9cf346e95291cb4c4f1bf5ac1d77db6b8ad023d F src/test1.c 33efd350dca27c52c58c553c04fd3a6a51f13c1f F src/test2.c e9f99aa5ee73872819259d6612c11e55e1644321 F src/test3.c d6775f95fd91f5b3cf0e2382a28e5aaeb68f745b F src/tokenize.c 1199b96a82d5c41509b5e24fc9faa1852b7f3135 -F src/update.c 2ece9c433968f9268d93bcc2f0ece409ab4dc296 +F src/update.c 5ffd4bbd380f1fa99da184f28416e6dcf8b5508e F src/util.c 8f8973dd55a6ec63be9632fc5de86965c99d6327 -F src/vdbe.c 5e51f9dfc34ee329117f59c28410ea9d4224402a +F src/vdbe.c abd60d37361eaaa3b94d016cd2a9f31bd8d57620 F src/vdbe.h 5b1bd518126fc5a30e6ea13fe11de931b32c4b59 F src/where.c 2dda39367f193194e4c7d2e0dcab31527d9d8aba F test/all.test 2a51e5395ac7c2c539689b123b9782a05e3837fe @@ -65,7 +65,7 @@ F test/in.test c09312672e3f0709fa02c8e2e9cd8fb4bd6269aa F test/index.test c8a471243bbf878974b99baf5badd59407237cf3 F test/insert.test a5c122aa726f1cef6f07d6767e8fd6f220994c11 F test/insert2.test d6901ca931e308fea7fca8c95ebe7dc957cc9fc2 -F test/intpkey.test d6c7f42679b3f87da3933f5dddac92c822dd89c5 +F test/intpkey.test ce3de8326082929667cf356855426519cfe2f5c7 F test/ioerr.test 57d9bffaca18b34f9e976f786eadc2591d6efc6a F test/limit.test a930f3eba2a7691c8397ccab33710b931589566a F test/lock.test 19593689260c419efe7ced55b1418653a4b7bcd1 @@ -119,7 +119,7 @@ F www/speed.tcl 83457b2bf6bb430900bd48ca3dd98264d9a916a5 F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279 F www/tclsqlite.tcl 829b393d1ab187fd7a5e978631b3429318885c49 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P af3bb80810c6cd302a884a106cc70591a738e102 -R 82f94ecaa281bc36c2caa4b6f3c6c838 +P e00a9ff8f99dd58f7cb19a6195fac21f4c8b4af9 +R 1ca39242f5cf0f094b73bd233e3fcc93 U drh -Z 16309c459073f336b26332a0e349efd1 +Z 0835a3278f559e60402c3a41f932d0d4 diff --git a/manifest.uuid b/manifest.uuid index 8b9c80c5d4..ba959099f7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e00a9ff8f99dd58f7cb19a6195fac21f4c8b4af9 \ No newline at end of file +ac8a4189e2a0c41161ee359db25de94435420368 \ No newline at end of file diff --git a/src/build.c b/src/build.c index 473b55e781..7eabe18a18 100644 --- a/src/build.c +++ b/src/build.c @@ -25,7 +25,7 @@ ** ROLLBACK ** PRAGMA ** -** $Id: build.c,v 1.67 2002/01/29 18:41:25 drh Exp $ +** $Id: build.c,v 1.68 2002/01/29 23:07:02 drh Exp $ */ #include "sqliteInt.h" #include @@ -866,6 +866,7 @@ void sqliteCreateIndex( int hideName = 0; /* Do not put table name in the hash table */ if( pParse->nErr || sqlite_malloc_failed ) goto exit_create_index; + if( onError==OE_Default ) onError = OE_Abort; /* ** Find the table that is to be indexed. Return early if not found. diff --git a/src/delete.c b/src/delete.c index 7d19b2b0e3..15e2ea3f9d 100644 --- a/src/delete.c +++ b/src/delete.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle DELETE FROM statements. ** -** $Id: delete.c,v 1.24 2002/01/29 18:41:25 drh Exp $ +** $Id: delete.c,v 1.25 2002/01/29 23:07:02 drh Exp $ */ #include "sqliteInt.h" @@ -200,26 +200,50 @@ void sqliteGenerateRowDelete( Vdbe *v, /* Generate code into this VDBE */ Table *pTab, /* Table containing the row to be deleted */ int base /* Cursor number for the table */ +){ + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); + sqliteGenerateRowIndexDelete(v, pTab, base, 0); + sqliteVdbeAddOp(v, OP_Delete, base, 0); +} + +/* +** This routine generates VDBE code that causes the deletion of all +** index entries associated with a single row of a single table. +** +** The VDBE must be in a particular state when this routine is called. +** These are the requirements: +** +** 1. A read/write cursor pointing to pTab, the table containing the row +** to be deleted, must be opened as cursor number "base". +** +** 2. Read/write cursors for all indices of pTab must be open as +** cursor number base+i for the i-th index. +** +** 3. The "base" cursor must be pointing to the row that is to be +** deleted. +*/ +void sqliteGenerateRowIndexDelete( + Vdbe *v, /* Generate code into this VDBE */ + Table *pTab, /* Table containing the row to be deleted */ + int base, /* Cursor number for the table */ + char *aIdxUsed /* Only delete if aIdxUsed!=0 && aIdxUsed[i]!=0 */ ){ int i; Index *pIdx; - sqliteVdbeAddOp(v, OP_MoveTo, base, 0); - if( pTab->pIndex ){ - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - int j; - sqliteVdbeAddOp(v, OP_Recno, base, 0); - for(j=0; jnColumn; j++){ - int idx = pIdx->aiColumn[j]; - if( idx==pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Dup, j, 0); - }else{ - sqliteVdbeAddOp(v, OP_Column, base, idx); - } + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + int j; + if( aIdxUsed!=0 && aIdxUsed[i-1]==0 ) continue; + sqliteVdbeAddOp(v, OP_Recno, base, 0); + for(j=0; jnColumn; j++){ + int idx = pIdx->aiColumn[j]; + if( idx==pTab->iPKey ){ + sqliteVdbeAddOp(v, OP_Dup, j, 0); + }else{ + sqliteVdbeAddOp(v, OP_Column, base, idx); } - sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); - sqliteVdbeAddOp(v, OP_IdxDelete, base+i, 0); } + sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); + sqliteVdbeAddOp(v, OP_IdxDelete, base+i, 0); } - sqliteVdbeAddOp(v, OP_Delete, base, 0); } diff --git a/src/insert.c b/src/insert.c index f9b8b1791c..1a01fafd95 100644 --- a/src/insert.c +++ b/src/insert.c @@ -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.34 2002/01/29 18:41:25 drh Exp $ +** $Id: insert.c,v 1.35 2002/01/29 23:07:02 drh Exp $ */ #include "sqliteInt.h" @@ -51,6 +51,7 @@ void sqliteInsert( sqlite *db; /* The main database structure */ int openOp; /* Opcode used to open cursors */ int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ + int endOfLoop; /* Label for the end of the insertion loop */ if( pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup; db = pParse->db; @@ -201,7 +202,9 @@ void sqliteInsert( /* Push the record number for the new entry onto the stack. The ** record number is a randomly generate integer created by NewRecno ** except when the table has an INTEGER PRIMARY KEY column, in which - ** case the record number is the same as that column. + ** case the record number is the same as that column. May a copy + ** because sqliteGenerateConstraintChecks() requires two copies of + ** the record number. */ if( keyColumn>=0 ){ if( srcTab>=0 ){ @@ -213,13 +216,7 @@ void sqliteInsert( }else{ sqliteVdbeAddOp(v, OP_NewRecno, base, 0); } - - /* If there are indices, we'll need the new record number again, so make - ** a copy. - */ - if( pTab->pIndex ){ - sqliteVdbeAddOp(v, OP_Dup, 0, 0); - } + sqliteVdbeAddOp(v, OP_Dup, 0, 0); /* Push onto the stack, data for all columns of the new entry, beginning ** with the first column. @@ -250,45 +247,12 @@ void sqliteInsert( } } - /* Create the new record and put it into the database. + /* Generate code to check constraints and generate index keys and + ** do the insertion. */ - sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); - sqliteVdbeAddOp(v, OP_PutIntKey, base, keyColumn>=0); - - /* Create appropriate entries for the new data row in all indices - ** of the table. - */ - for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ - if( pIdx->pNext ){ - sqliteVdbeAddOp(v, OP_Dup, 0, 0); - } - for(i=0; inColumn; i++){ - int idx = pIdx->aiColumn[i]; - if( idx==pTab->iPKey ){ - /* Copy the record number in place of the INTEGER PRIMARY KEY column */ - sqliteVdbeAddOp(v, OP_Dup, i, 0); - continue; - } - if( pColumn==0 ){ - j = idx; - }else{ - for(j=0; jnId; j++){ - if( pColumn->a[j].idx==idx ) break; - } - } - if( pColumn && j>=pColumn->nId ){ - sqliteVdbeAddOp(v, OP_String, 0, 0); - sqliteVdbeChangeP3(v, -1, pTab->aCol[idx].zDflt, P3_STATIC); - }else if( srcTab>=0 ){ - sqliteVdbeAddOp(v, OP_Column, srcTab, idx); - }else{ - sqliteExprCode(pParse, pList->a[j].pExpr); - } - } - sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); - sqliteVdbeAddOp(v, OP_IdxPut, idx+base, pIdx->isUnique); - } - + endOfLoop = sqliteVdbeMakeLabel(v); + sqliteGenerateConstraintChecks(pParse, pTab, base, 0,1,onError,endOfLoop,0); + sqliteCompleteInsertion(pParse, pTab, base, 0, 1); /* If inserting from a SELECT, keep a count of the number of ** rows inserted. @@ -299,6 +263,7 @@ void sqliteInsert( /* The bottom of the loop, if the data source is a SELECT statement */ + sqliteVdbeResolveLabel(v, endOfLoop); if( srcTab>=0 ){ sqliteVdbeAddOp(v, OP_Next, srcTab, iCont); sqliteVdbeResolveLabel(v, iBreak); @@ -331,17 +296,33 @@ insert_cleanup: sqliteIdListDelete(pColumn); } -#if 0 /* ** Generate code to do a constraint check prior to an INSERT or an UPDATE. ** ** When this routine is called, the stack contains (from bottom to top) -** the recno of the row to be updated and each column of new data beginning -** with the first column. The code generated by this routine pushes addition -** entries onto the stack which are the keys for new index entries for -** the new record. The order of index keys is the same as the order of -** the indices on the pTable->pIndex list. A key is only created for -** index i if aIdxUsed!=0 and aIdxUsed[i]!=0. +** the following values: +** +** 1. The recno of the row to be updated before it is updated. +** +** 2. The recno of the row after the update. (This is usually the +** same as (1) but can be different if an UPDATE changes an +** INTEGER PRIMARY KEY column.) +** +** 3. The data in the first column of the entry after the update. +** +** i. Data from middle columns... +** +** N. The data in the last column of the entry after the update. +** +** The old recno shown as entry (1) above is omitted if the recnoChng +** parameter is 0. recnoChange is true if the record number is changing +** and false if not. +** +** The code generated by this routine pushes additional entries onto +** the stack which are the keys for new index entries for the new record. +** The order of index keys is the same as the order of the indices on +** the pTable->pIndex list. A key is only created for index i if +** aIdxUsed!=0 and aIdxUsed[i]!=0. ** ** This routine also generates code to check constraints. NOT NULL, ** CHECK, and UNIQUE constraints are all checked. If a constraint fails, @@ -391,6 +372,7 @@ void sqliteGenerateConstraintChecks( Table *pTab, /* the table into which we are inserting */ int base, /* Index of a read/write cursor pointing at pTab */ char *aIdxUsed, /* Which indices are used. NULL means all are used */ + int recnoChng, /* True if the record number will change */ int overrideError, /* Override onError to this if not OE_Default */ int ignoreDest, /* Jump to this label on an OE_Ignore resolution */ int isUpdate /* True for UPDATE, False for INSERT */ @@ -401,18 +383,26 @@ void sqliteGenerateConstraintChecks( int onError; int addr; int extra; - char *pToFree = 0; - int seenIgnore = 0; + int iCur; + Index *pIdx; + int seenReplace = 0; + int jumpInst; + int contAddr; v = sqliteGetVdbe(pParse); assert( v!=0 ); nCol = pTab->nCol; + recnoChng = (recnoChng!=0); /* Must be either 1 or 0 */ /* Test all NOT NULL constraints. */ for(i=0; iiPKey ){ + /* Fix me: Make sure the INTEGER PRIMARY KEY is not NULL. */ + continue; + } onError = pTab->aCol[i].notNull; - if( i==iPKey || onError==OE_None ) continue; + if( onError==OE_None ) continue; if( overrideError!=OE_Default ){ onError = overrideError; } @@ -427,8 +417,8 @@ void sqliteGenerateConstraintChecks( break; } case OE_Ignore: { - sqliteVdbeAddOp(v, OP_Pop, nCol+1, 0); - sqliteVdbeAddOp(v, OP_GoTo, 0, ignoreDest); + sqliteVdbeAddOp(v, OP_Pop, nCol+1+recnoChng, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest); break; } case OE_Replace: { @@ -437,9 +427,7 @@ void sqliteGenerateConstraintChecks( sqliteVdbeAddOp(v, OP_Push, nCol-i, 0); break; } - default: { - CANT_HAPPEN; - } + default: assert(0); } } @@ -448,20 +436,44 @@ void sqliteGenerateConstraintChecks( /* Test all UNIQUE constraints. Add index records as we go. */ + if( recnoChng && pTab->iPKey>=0 && pTab->keyConf!=OE_Replace + && overrideError!=OE_Replace ){ + sqliteVdbeAddOp(v, OP_Dup, nCol, 1); + jumpInst = sqliteVdbeAddOp(v, OP_NotExists, base, 0); + onError = pTab->keyConf; + if( overrideError!=OE_Default ){ + onError = overrideError; + } + switch( onError ){ + case OE_Abort: { + sqliteVdbeAddOp(v, OP_Halt, SQLITE_CONSTRAINT, 0); + break; + } + case OE_Ignore: { + sqliteVdbeAddOp(v, OP_Pop, nCol+2, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest); + break; + } + default: assert(0); + } + contAddr = sqliteVdbeCurrentAddr(v); + sqliteVdbeChangeP2(v, jumpInst, contAddr); + if( isUpdate ){ + sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1); + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); + } + } extra = 0; for(extra=(-1), iCur=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, iCur++){ - int jumpInst; - int contAddr; - if( aIdxUsed && aIdxUsed[iCur]==0 ) continue; extra++; sqliteVdbeAddOp(v, OP_Dup, nCol+extra, 1); for(i=0; inColumn; i++){ int idx = pIdx->aiColumn[i]; if( idx==pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol+1, 0); + sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol+1, 1); }else{ - sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 0); + sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1); } } sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); @@ -470,31 +482,68 @@ void sqliteGenerateConstraintChecks( if( overrideError!=OE_Default ){ onError = overrideError; } - jumpInst = sqliteVdbeAddOp(v, OP_IsUnique, iCur, 0); + sqliteVdbeAddOp(v, OP_Dup, extra+nCol+2, 1); + jumpInst = sqliteVdbeAddOp(v, OP_IsUnique, base+iCur+1, 0); switch( onError ){ case OE_Abort: { sqliteVdbeAddOp(v, OP_Halt, SQLITE_CONSTRAINT, 0); break; } case OE_Ignore: { - sqliteVdbeAddOp(v, OP_Pop, nCol+extra+2, 0); + assert( seenReplace==0 ); + sqliteVdbeAddOp(v, OP_Pop, nCol+extra+2+recnoChng, 0); sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest); - seenIgnore = 1; break; } case OE_Replace: { - assert( seenIgnore==0 ); + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); sqliteGenerateRowDelete(v, pTab, base); if( isUpdate ){ - sqliteVdbeAddOp(v, OP_Dup, nCol+extra+2, 1); - sqliteVdbeAddOp(v, OP_Moveto, base, 0); + sqliteVdbeAddOp(v, OP_Dup, nCol+extra+recnoChng, 1); + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); } + seenReplace = 1; break; } - default: CANT_HAPPEN; + default: assert(0); } contAddr = sqliteVdbeCurrentAddr(v); sqliteVdbeChangeP2(v, jumpInst, contAddr); } } -#endif + +/* +** This routine generates code to finish the INSERT or UPDATE operation +** that was started by a prior call to sqliteGenerateConstraintChecks. +** The stack must contain keys for all active indices followed by data +** and the recno for the new entry. This routine creates the new +** entries in all indices and in the main table. +** +** The arguments to this routine should be the same as the first five +** arguments to sqliteGenerateConstraintChecks. +*/ +void sqliteCompleteInsertion( + Parse *pParse, /* The parser context */ + Table *pTab, /* the table into which we are inserting */ + int base, /* Index of a read/write cursor pointing at pTab */ + char *aIdxUsed, /* Which indices are used. NULL means all are used */ + int recnoChng /* True if the record number changed */ +){ + int i; + Vdbe *v; + int nIdx; + Index *pIdx; + + v = sqliteGetVdbe(pParse); + assert( v!=0 ); + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){} + for(i=nIdx-1; i>=0; i--){ + if( aIdxUsed && aIdxUsed[i]==0 ) continue; + sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 0); + } + sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, base, 0); + if( recnoChng ){ + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + } +} diff --git a/src/sqliteInt.h b/src/sqliteInt.h index ca4e7616ea..c6e11d64a3 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.80 2002/01/29 18:41:25 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.81 2002/01/29 23:07:02 drh Exp $ */ #include "sqlite.h" #include "hash.h" @@ -547,3 +547,7 @@ void sqliteCommitTransaction(Parse*); void sqliteRollbackTransaction(Parse*); char *sqlite_mprintf(const char *, ...); int sqliteExprIsConstant(Expr*); +void sqliteGenerateRowDelete(Vdbe*, Table*, int); +void sqliteGenerateRowIndexDelete(Vdbe*, Table*, int, char*); +void sqliteGenerateConstraintChecks(Parse*,Table*,int,char*,int,int,int,int); +void sqliteCompleteInsertion(Parse*, Table*, int, char*, int); diff --git a/src/update.c b/src/update.c index e4d170456a..6dff1fdb74 100644 --- a/src/update.c +++ b/src/update.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle UPDATE statements. ** -** $Id: update.c,v 1.29 2002/01/29 18:41:25 drh Exp $ +** $Id: update.c,v 1.30 2002/01/29 23:07:02 drh Exp $ */ #include "sqliteInt.h" @@ -29,20 +29,23 @@ void sqliteUpdate( int i, j; /* Loop counters */ Table *pTab; /* The table to be updated */ IdList *pTabList = 0; /* List containing only pTab */ - int end, addr; /* A couple of addresses in the generated code */ + int addr; /* VDBE instruction address of the start of the loop */ WhereInfo *pWInfo; /* Information about the WHERE clause */ Vdbe *v; /* The virtual database engine */ Index *pIdx; /* For looping over indices */ int nIdx; /* Number of indices that need updating */ + int nIdxTotal; /* Total number of indices */ int base; /* Index of first available table cursor */ sqlite *db; /* The database structure */ Index **apIdx = 0; /* An array of indices that need updating too */ + char *aIdxUsed = 0; /* aIdxUsed[i] if the i-th index is used */ int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the ** an expression for the i-th column of the table. ** aXRef[i]==-1 if the i-th column is not changed. */ int openOp; /* Opcode used to open tables */ int chngRecno; /* True if the record number is being changed */ Expr *pRecnoExpr; /* Expression defining the new record number */ + int openAll; /* True if all indices need to be opened */ if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup; db = pParse->db; @@ -123,7 +126,7 @@ void sqliteUpdate( ** key includes one of the columns named in pChanges or if the record ** number of the original table entry is changing. */ - for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + for(nIdx=nIdxTotal=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdxTotal++){ if( chngRecno ){ i = 0; }else { @@ -133,11 +136,12 @@ void sqliteUpdate( } if( inColumn ) nIdx++; } - if( nIdx>0 ){ - apIdx = sqliteMalloc( sizeof(Index*) * nIdx ); + if( nIdxTotal>0 ){ + apIdx = sqliteMalloc( sizeof(Index*) * nIdx + nIdxTotal ); if( apIdx==0 ) goto update_cleanup; + aIdxUsed = (char*)&apIdx[nIdx]; } - for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + for(nIdx=j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ if( chngRecno ){ i = 0; }else{ @@ -145,7 +149,12 @@ void sqliteUpdate( if( aXRef[pIdx->aiColumn[i]]>=0 ) break; } } - if( inColumn ) apIdx[nIdx++] = pIdx; + if( inColumn ){ + apIdx[nIdx++] = pIdx; + aIdxUsed[j] = 1; + }else{ + aIdxUsed[j] = 0; + } } /* Begin generating code. @@ -178,14 +187,30 @@ void sqliteUpdate( } /* Rewind the list of records that need to be updated and - ** open every index that needs updating. + ** open every index that needs updating. Note that if any + ** index could potentially invoke a REPLACE conflict resolution + ** action, then we need to open all indices because we might need + ** to be deleting some records. */ sqliteVdbeAddOp(v, OP_ListRewind, 0, 0); base = pParse->nTab; openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite; sqliteVdbeAddOp(v, openOp, base, pTab->tnum); - for(i=0; itnum); + if( onError==OE_Replace ){ + openAll = 1; + }else{ + openAll = 0; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->onError==OE_Replace ){ + openAll = 1; + break; + } + } + } + for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + if( openAll || aIdxUsed[i] ){ + sqliteVdbeAddOp(v, openOp, base+i+1, pIdx->tnum); + } } /* Loop over every record that needs updating. We have to load @@ -194,42 +219,28 @@ void sqliteUpdate( ** Also, the old data is needed to delete the old index entires. ** So make the cursor point at the old record. */ - end = sqliteVdbeMakeLabel(v); - addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end); + addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0); sqliteVdbeAddOp(v, OP_Dup, 0, 0); sqliteVdbeAddOp(v, OP_MoveTo, base, 0); - /* Delete the old indices for the current record. - */ - for(i=0; inColumn; j++){ - int x = pIdx->aiColumn[j]; - if( x==pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Dup, j, 0); - }else{ - sqliteVdbeAddOp(v, OP_Column, base, x); - } - } - sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); - sqliteVdbeAddOp(v, OP_IdxDelete, base+i+1, 0); - } - - /* If changing the record number, remove the old record number - ** from the top of the stack and replace it with the new one. + /* If the record number will change, push the record number as it + ** will be after the update. (The old record number is currently + ** on top of the stack.) */ if( chngRecno ){ - sqliteVdbeAddOp(v, OP_Pop, 1, 0); - sqliteExprCode(pParse, pRecnoExpr); - sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); + if( pTab->iPKey<0 || (j = aXRef[pTab->iPKey])<0 ){ + sqliteVdbeAddOp(v, OP_Dup, 0, 0); + }else{ + sqliteExprCode(pParse, pChanges->a[j].pExpr); + sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); + } } /* Compute new data for this record. */ for(i=0; inCol; i++){ if( i==pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Dup, i, 0); + sqliteVdbeAddOp(v, OP_String, 0, 0); continue; } j = aXRef[i]; @@ -240,35 +251,26 @@ void sqliteUpdate( } } + /* Do constraint checks + */ + sqliteGenerateConstraintChecks(pParse, pTab, base, aIdxUsed, chngRecno, + onError, addr,1); + + /* Delete the old indices for the current record. + */ + sqliteGenerateRowIndexDelete(v, pTab, base, aIdxUsed); + /* If changing the record number, delete the old record. */ if( chngRecno ){ sqliteVdbeAddOp(v, OP_Delete, 0, 0); } - /* Insert new index entries that correspond to the new data + /* Create the new index entries and the new record. */ - for(i=0; inCol, 0); /* The KEY */ - pIdx = apIdx[i]; - for(j=0; jnColumn; j++){ - int idx = pIdx->aiColumn[j]; - if( idx==pTab->iPKey ){ - sqliteVdbeAddOp(v, OP_Dup, j, 0); - }else{ - sqliteVdbeAddOp(v, OP_Dup, j+pTab->nCol-idx, 0); - } - } - sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); - sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, pIdx->isUnique); - } + sqliteCompleteInsertion(pParse, pTab, base, aIdxUsed, chngRecno); - /* Write the new data back into the database. - */ - sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); - sqliteVdbeAddOp(v, OP_PutIntKey, base, 0); - - /* Increment the count of rows affected by the update + /* Increment the row counter */ if( db->flags & SQLITE_CountRows ){ sqliteVdbeAddOp(v, OP_AddImm, 1, 0); @@ -278,7 +280,7 @@ void sqliteUpdate( ** all record selected by the WHERE clause have been updated. */ sqliteVdbeAddOp(v, OP_Goto, 0, addr); - sqliteVdbeResolveLabel(v, end); + sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v)); sqliteVdbeAddOp(v, OP_ListReset, 0, 0); if( (db->flags & SQLITE_InTrans)==0 ){ sqliteVdbeAddOp(v, OP_Commit, 0, 0); diff --git a/src/vdbe.c b/src/vdbe.c index 2e5251284d..40b5e5d02c 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -30,7 +30,7 @@ ** But other routines are also provided to help in building up ** a program instruction by instruction. ** -** $Id: vdbe.c,v 1.109 2002/01/29 18:41:25 drh Exp $ +** $Id: vdbe.c,v 1.110 2002/01/29 23:07:02 drh Exp $ */ #include "sqliteInt.h" #include @@ -1299,9 +1299,7 @@ case OP_Pull: { case OP_Push: { int from = p->tos; int to = p->tos - pOp->p1; - int i; - Stack ts; - char *tz; + VERIFY( if( to<0 ) goto not_enough_stack; ) if( aStack[to].flags & STK_Dyn ){ sqliteFree(zStack[to]); @@ -2756,35 +2754,53 @@ case OP_Found: { /* Opcode: IsUnique P1 P2 * ** -** The top of the stack is an index key created using MakeIdxKey. If -** there does not exist an entry in P1 that exactly matches the top of -** the stack, then jump immediately to P2. If there are no entries -** in P1 that match all but the last four bytes of the top of the stack -** then also jump to P2. The index key on the top of the stack is -** unchanged. +** The top of the stack is an integer record number. Call this +** record number R. The next on the stack is an index key created +** using MakeIdxKey. Call it K. This instruction pops R from the +** stack but it leaves K unchanged. ** -** If there is an entry in P1 which differs from the index key on the -** top of the stack only in the last four bytes, then do not jump. -** Instead, push the last four bytes of the existing P1 entry onto the -** stack and fall through. This new stack element is the record number -** of an existing entry this preventing the index key on the stack from -** being a unique key. +** P1 is an index. So all but the last four bytes of K are an +** index string. The last four bytes of K are a record number. +** +** This instruction asks if there is an entry in P1 where the +** index string matches K but the record number is different +** from R. If there is no such entry, then there is an immediate +** jump to P2. If any entry does exist where the index string +** matches K but the record number is not R, then the record +** number for that entry is pushed onto the stack and control +** falls through to the next instruction. ** ** See also: Distinct, NotFound, NotExists */ case OP_IsUnique: { int i = pOp->p1; int tos = p->tos; + int nos = tos-1; BtCursor *pCrsr; + int R; - VERIFY( if( tos<0 ) goto not_enough_stack; ) + /* Pop the value R off the top of the stack + */ + VERIFY( if( nos<0 ) goto not_enough_stack; ) + Integerify(p, tos); + R = aStack[tos].i; + POPSTACK; if( VERIFY( i>=0 && inCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){ int res, rc; - int v1, v2; - char *zKey = zStack[tos]; - int nKey = aStack[tos].n; - if( Stringify(p, tos) ) goto no_mem; - assert( aStack[tos].n >= 4 ); + int v; /* The record number on the P1 entry that matches K */ + char *zKey; /* The value of K */ + int nKey; /* Number of bytes in K */ + + /* Make sure K is a string and make zKey point to K + */ + if( Stringify(p, nos) ) goto no_mem; + zKey = zStack[nos]; + nKey = aStack[nos].n; + assert( nKey >= 4 ); + + /* Search for an entry in P1 where all but the last four bytes match K. + ** If there is no such entry, jump immediately to P2. + */ rc = sqliteBtreeMoveto(pCrsr, zKey, nKey-4, &res); if( rc!=SQLITE_OK ) goto abort_due_to_error; if( res<0 ){ @@ -2800,15 +2816,27 @@ case OP_IsUnique: { pc = pOp->p2 - 1; break; } - sqliteBtreeKey(pCrsr, nKey - 4, 4, (char*)&v1); - memcpy((char*)&v2, &zKey[nKey-4], 4); - if( v1==v2 ){ + + /* At this point, pCrsr is pointing to an entry in P1 where all but + ** the last for bytes of the key match K. Check to see if the last + ** four bytes of the key are different from R. If the last four + ** bytes equal R then jump immediately to P2. + */ + sqliteBtreeKey(pCrsr, nKey - 4, 4, (char*)&v); + v = keyToInt(v); + if( v==R ){ pc = pOp->p2 - 1; break; } - tos = ++p->tos; + + /* The last four bytes of the key are different from R. Convert the + ** last four bytes of the key into an integer and push it onto the + ** stack. (These bytes are the record number of an entry that + ** violates a UNIQUE constraint.) + */ + p->tos++; VERIFY( if( NeedStack(p, p->tos) ) goto no_mem; ) - aStack[tos].i = keyToInt(v1); + aStack[tos].i = v; aStack[tos].flags = STK_Int; } break; @@ -2830,14 +2858,13 @@ case OP_IsUnique: { case OP_NotExists: { int i = pOp->p1; int tos = p->tos; - int alreadyExists = 0; - Cursor *pC; + BtCursor *pCrsr; VERIFY( if( tos<0 ) goto not_enough_stack; ) - if( VERIFY( i>=0 && inCursor && ) (pC = &p->aCsr[i])->pCursor!=0 ){ + if( VERIFY( i>=0 && inCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){ int res, rx, iKey; assert( aStack[tos].flags & STK_Int ); iKey = intToKey(aStack[tos].i); - rx = sqliteBtreeMoveto(pC->pCursor, (char*)&iKey, sizeof(int), &res); + rx = sqliteBtreeMoveto(pCrsr, (char*)&iKey, sizeof(int), &res); if( rx!=SQLITE_OK || res!=0 ){ pc = pOp->p2 - 1; } @@ -2910,7 +2937,7 @@ case OP_NewRecno: { break; } -/* Opcode: PutIK P1 P2 * +/* Opcode: PutIntKey P1 P2 * ** ** Write an entry into the database file P1. A new entry is ** created if it doesn't already exist or the data for an existing @@ -2921,7 +2948,7 @@ case OP_NewRecno: { ** If P2==1 then overwriting is prohibited. If a prior entry with ** the same key exists, an SQLITE_CONSTRAINT exception is raised. */ -/* Opcode: PutSK P1 P2 * +/* Opcode: PutStrKey P1 P2 * ** ** Write an entry into the database file P1. A new entry is ** created if it doesn't already exist or the data for an existing diff --git a/test/intpkey.test b/test/intpkey.test index c3adfd0cf7..d28efd4726 100644 --- a/test/intpkey.test +++ b/test/intpkey.test @@ -13,7 +13,7 @@ # This file implements tests for the special processing associated # with INTEGER PRIMARY KEY columns. # -# $Id: intpkey.test,v 1.6 2002/01/16 21:00:28 drh Exp $ +# $Id: intpkey.test,v 1.7 2002/01/29 23:07:02 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -179,6 +179,7 @@ do_test intpkey-2.1.4 { SELECT * FROM t1 WHERE b>='y' AND rowid<10 } } {-3 y z} + do_test intpkey-2.2 { execsql { UPDATE t1 SET a=8 WHERE b=='y';