Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -1665,10 +1665,27 @@ } *pbMayReplace = seenReplace; VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace)); } + +/* +** Change the P5 operand on the last opcode (which should be an OP_MakeRecord) +** to be the number of columns in table pTab that must not be NULL-trimmed. +** +** Or if no columns of pTab may be NULL-trimmed, leave P5 at zero. +*/ +void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){ + u16 i; + + /* Records with omitted columns are only allowed for schema format + ** version 2 and later (SQLite version 3.1.4, 2005-02-20). */ + if( pTab->pSchema->file_format<2 ) return; + + for(i=pTab->nCol; i>1 && pTab->aCol[i-1].pDflt==0; i--){} + sqlite3VdbeChangeP5(v, i); +} /* ** This routine generates code to finish the INSERT or UPDATE operation ** that was started by a prior call to sqlite3GenerateConstraintChecks. ** A consecutive range of registers starting at regNewData contains the @@ -1725,10 +1742,11 @@ } if( !HasRowid(pTab) ) return; regData = regNewData + 1; regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); + sqlite3SetMakeRecordP5(v, pTab); if( !bAffinityDone ){ sqlite3TableAffinity(v, pTab, 0); sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); } if( pParse->nested ){ Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -3788,10 +3788,11 @@ void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int); int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int); void sqlite3ResolvePartIdxLabel(Parse*,int); void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int, u8,u8,int,int*,int*); +void sqlite3SetMakeRecordP5(Vdbe*,Table*); void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int); int sqlite3OpenTableAndIndices(Parse*, Table*, int, u8, int, u8*, int*, int*); void sqlite3BeginWriteOperation(Parse*, int, int); void sqlite3MultiWrite(Parse*); void sqlite3MayAbort(Parse*); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -2774,10 +2774,22 @@ do{ applyAffinity(pRec++, *(zAffinity++), encoding); assert( zAffinity[0]==0 || pRec<=pLast ); }while( zAffinity[0] ); } + + /* NULLs can be safely trimmed from the end of the record, as long as + ** as the schema format is 2 or more and none of the omitted columns + ** have a non-NULL default value. Also, the record must be left with + ** at least one field. If P5>0 then it will be one more than the + ** index of the right-most column with a non-NULL default value */ + if( pOp->p5 ){ + while( (pLast->flags & MEM_Null)!=0 && nField>pOp->p5 ){ + pLast--; + nField--; + } + } /* Loop through the elements that will make up the record to figure ** out how much space is required for the new record. */ pRec = pLast; Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -39,12 +39,11 @@ ** as an instance of the following structure: */ struct VdbeOp { u8 opcode; /* What operation to perform */ signed char p4type; /* One of the P4_xxx constants for p4 */ - u8 notUsed1; - u8 p5; /* Fifth parameter is an unsigned character */ + u16 p5; /* Fifth parameter is an unsigned 16-bit integer */ int p1; /* First operand */ int p2; /* Second parameter (often the jump destination) */ int p3; /* The third parameter */ union p4union { /* fourth parameter */ int i; /* Integer value if p4type==P4_INT32 */ @@ -192,11 +191,11 @@ void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); void sqlite3VdbeChangeOpcode(Vdbe*, u32 addr, u8); void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1); void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2); void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3); -void sqlite3VdbeChangeP5(Vdbe*, u8 P5); +void sqlite3VdbeChangeP5(Vdbe*, u16 P5); void sqlite3VdbeJumpHere(Vdbe*, int addr); int sqlite3VdbeChangeToNoop(Vdbe*, int addr); int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op); void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); void sqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type); Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -1658,10 +1658,11 @@ ** This function is called from within a pre-update callback to retrieve ** a field of the row currently being updated or deleted. */ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ PreUpdate *p = db->pPreUpdate; + Mem *pMem; int rc = SQLITE_OK; /* Test that this call is being made from within an SQLITE_DELETE or ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */ if( !p || p->op==SQLITE_INSERT ){ @@ -1691,21 +1692,18 @@ goto preupdate_old_out; } p->aRecord = aRec; } - if( iIdx>=p->pUnpacked->nField ){ + pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey1); + }else if( iIdx>=p->pUnpacked->nField ){ *ppValue = (sqlite3_value *)columnNullValue(); - }else{ - Mem *pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; - *ppValue = &p->pUnpacked->aMem[iIdx]; - if( iIdx==p->pTab->iPKey ){ - sqlite3VdbeMemSetInt64(pMem, p->iKey1); - }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ - if( pMem->flags & MEM_Int ){ - sqlite3VdbeMemRealify(pMem); - } + }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ + if( pMem->flags & MEM_Int ){ + sqlite3VdbeMemRealify(pMem); } } preupdate_old_out: sqlite3Error(db, rc); @@ -1774,17 +1772,15 @@ rc = SQLITE_NOMEM; goto preupdate_new_out; } p->pNewUnpacked = pUnpack; } - if( iIdx>=pUnpack->nField ){ + pMem = &pUnpack->aMem[iIdx]; + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + }else if( iIdx>=pUnpack->nField ){ pMem = (sqlite3_value *)columnNullValue(); - }else{ - pMem = &pUnpack->aMem[iIdx]; - if( iIdx==p->pTab->iPKey ){ - sqlite3VdbeMemSetInt64(pMem, p->iKey2); - } } }else{ /* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required ** value. Make a copy of the cell contents and return a pointer to it. ** It is not safe to return a pointer to the memory cell itself as the Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -781,11 +781,11 @@ sqlite3VdbeGetOp(p,addr)->p2 = val; } void sqlite3VdbeChangeP3(Vdbe *p, u32 addr, int val){ sqlite3VdbeGetOp(p,addr)->p3 = val; } -void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){ +void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){ assert( p->nOp>0 || p->db->mallocFailed ); if( p->nOp>0 ) p->aOp[p->nOp-1].p5 = p5; } /* @@ -4583,14 +4583,14 @@ ** structure itself, using sqlite3DbFree(). ** ** This function is used to free UnpackedRecord structures allocated by ** the vdbeUnpackRecord() function found in vdbeapi.c. */ -static void vdbeFreeUnpacked(sqlite3 *db, UnpackedRecord *p){ +static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ if( p ){ int i; - for(i=0; inField; i++){ + for(i=0; iaMem[i]; if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem); } sqlite3DbFree(db, p); } @@ -4645,16 +4645,16 @@ db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); db->pPreUpdate = 0; sqlite3DbFree(db, preupdate.aRecord); - vdbeFreeUnpacked(db, preupdate.pUnpacked); - vdbeFreeUnpacked(db, preupdate.pNewUnpacked); + vdbeFreeUnpacked(db, preupdate.keyinfo.nField+1, preupdate.pUnpacked); + vdbeFreeUnpacked(db, preupdate.keyinfo.nField+1, preupdate.pNewUnpacked); if( preupdate.aNew ){ int i; for(i=0; inField; i++){ sqlite3VdbeMemRelease(&preupdate.aNew[i]); } sqlite3DbFree(db, preupdate.aNew); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ Index: test/exclusive2.test ================================================================== --- test/exclusive2.test +++ test/exclusive2.test @@ -119,17 +119,17 @@ # do_test exclusive2-1.1 { execsql { BEGIN; CREATE TABLE t1(a, b); - INSERT INTO t1(a) VALUES(randstr(10, 400)); - INSERT INTO t1(a) VALUES(randstr(10, 400)); - INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; - INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; - INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; - INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; - INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; + INSERT INTO t1(a, b) VALUES(randstr(10, 400), 0); + INSERT INTO t1(a, b) VALUES(randstr(10, 400), 0); + INSERT INTO t1(a, b) SELECT randstr(10, 400), 0 FROM t1; + INSERT INTO t1(a, b) SELECT randstr(10, 400), 0 FROM t1; + INSERT INTO t1(a, b) SELECT randstr(10, 400), 0 FROM t1; + INSERT INTO t1(a, b) SELECT randstr(10, 400), 0 FROM t1; + INSERT INTO t1(a, b) SELECT randstr(10, 400), 0 FROM t1; COMMIT; SELECT count(*) FROM t1; } } {64} do_test exclusive2-1.2.1 { @@ -152,11 +152,11 @@ sqlite3 db2 test.db t1sig db2 } $::sig do_test exclusive2-1.5 { execsql { - UPDATE t1 SET b=a, a=NULL; + UPDATE t1 SET b=a, a=0; } db2 expr {[t1sig db2] eq $::sig} } 0 do_test exclusive2-1.6 { readPagerChangeCounter test.db Index: test/hook.test ================================================================== --- test/hook.test +++ test/hook.test @@ -851,7 +851,58 @@ # No preupdate callbacks for modifying sqlite_master. do_preupdate_test 8.1 { CREATE TABLE x1(x, y); } { } + +#------------------------------------------------------------------------- +reset_db +db preupdate hook preupdate_hook +do_execsql_test 9.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t2(a, b INTEGER PRIMARY KEY); +} +do_preupdate_test 9.1 { + INSERT INTO t1 VALUES(456, NULL, NULL); +} { + INSERT main t1 456 456 0 456 {} {} +} +do_execsql_test 9.2 { + ALTER TABLE t1 ADD COLUMN d; +} +do_preupdate_test 9.3 { + INSERT INTO t1(a, b, c) VALUES(457, NULL, NULL); +} { + INSERT main t1 457 457 0 457 {} {} {} +} +do_preupdate_test 9.4 { + DELETE FROM t1 WHERE a=456 +} { + DELETE main t1 456 456 0 456 {} {} {} +} +do_preupdate_test 9.5 { + INSERT INTO t2 DEFAULT VALUES; +} { + INSERT main t2 1 1 0 {} 1 +} +do_preupdate_test 9.6 { + INSERT INTO t1 DEFAULT VALUES; +} { + INSERT main t1 458 458 0 458 {} {} {} +} + + +do_execsql_test 10.0 { + CREATE TABLE t3(a, b INTEGER PRIMARY KEY); +} +do_preupdate_test 10.1 { + INSERT INTO t3 DEFAULT VALUES +} { + INSERT main t3 1 1 0 {} 1 +} +do_execsql_test 10.2 { SELECT * FROM t3 } {{} 1} +do_preupdate_test 10.3 { + DELETE FROM t3 WHERE b=1 +} {DELETE main t3 1 1 0 {} 1} + finish_test Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -459,11 +459,11 @@ sqlite3_memdebug_log start } } {^-+backtrace=.+$} { foreach {dummy cmdlinearg(backtrace)} [split $a =] break - sqlite3_memdebug_backtrace $value + sqlite3_memdebug_backtrace $cmdlinearg(backtrace) } {^-+binarylog=.+$} { foreach {dummy cmdlinearg(binarylog)} [split $a =] break set cmdlinearg(binarylog) [file normalize $cmdlinearg(binarylog)] }