Index: ext/session/sqlite3session.h ================================================================== --- ext/session/sqlite3session.h +++ ext/session/sqlite3session.h @@ -13,19 +13,55 @@ typedef struct sqlite3_session sqlite3_session; typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; /* -** Create a session object. This session object will record changes to -** database zDb attached to connection db. +** Create a new session object attached to database handle db. If successful, +** a pointer to the new object is written to *ppSession and SQLITE_OK is +** returned. If an error occurs, *ppSession is set to NULL and an SQLite +** error code (e.g. [SQLITE_NOMEM]) is returned. +** +** It is possible to create multiple session objects attached to a single +** database handle. +** +** Session objects created using this function should be deleted using the +** [sqlite3session_delete()] function before the database handle that they +** are attached to is itself closed. If the database handle is closed before +** the session object is deleted, then the results of calling any session +** module function, including [sqlite3session_delete()] on the session object +** are undefined. +** +** Because the session module uses the [sqlite3_preupdate_hook()] API, it +** is not possible for an application to register a pre-update hook on a +** database handle that has one or more session objects attached. Nor is +** it possible to create a session object attached to a database handle for +** which a pre-update hook is already defined. The results of attempting +** either of these things are undefined. +** +** The session object will be used to create changesets for tables in +** database zDb, where zDb is either "main", or "temp", or the name of an +** attached database. It is not an error if database zDb does not exist +** to the database when the session object is created. */ int sqlite3session_create( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of db (e.g. "main") */ sqlite3_session **ppSession /* OUT: New session object */ ); +/* +** Delete a session object previously allocated using +** [sqlite3session_create()]. Once a session object has been deleted, the +** results of attempting to use pSession with any other session module +** function are undefined. +** +** Session objects must be deleted before the database handle to which they +** are attached is closed. Refer to the documentation for +** [sqlite3session_create()] for details. +*/ +void sqlite3session_delete(sqlite3_session *pSession); + /* ** Enable or disable the recording of changes by a session object. When ** enabled, a session object records changes made to the database. When ** disabled - it does not. A newly created session object is enabled. ** @@ -50,27 +86,27 @@ sqlite3_session *pSession, /* Session object */ const char *zTab /* Table name */ ); /* -** Obtain a changeset object containing all changes recorded by the -** session object passed as the first argument. +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset +** and *pnChangeset to the size of the changeset in bytes before returning +** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to +** zero and return an SQLite error code. ** -** It is the responsibility of the caller to eventually free the buffer -** using sqlite3_free(). +** Following a successful call to this function, it is the responsibility of +** the caller to eventually free the buffer that *ppChangeset points to using +** [sqlite3_free()]. */ int sqlite3session_changeset( sqlite3_session *pSession, /* Session object */ int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ void **ppChangeset /* OUT: Buffer containing changeset */ ); -/* -** Delete a session object previously allocated using sqlite3session_create(). -*/ -void sqlite3session_delete(sqlite3_session *pSession); - /* ** Create an iterator used to iterate through the contents of a changeset. */ int sqlite3changeset_start( sqlite3_changeset_iter **ppIter, Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -6364,12 +6364,12 @@ sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ ), void* ); SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); -SQLITE_EXPERIMENTAL int sqlite3_preupdate_modified(sqlite3 *, int, int *); SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *); +SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. */ Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -2844,13 +2844,13 @@ #endif break; } case DB_PREUPDATE: { - static const char *azSub[] = {"count", "hook", "modified", "old", 0}; + static const char *azSub[] = {"count", "hook", "new", "old", 0}; enum DbPreupdateSubCmd { - PRE_COUNT, PRE_HOOK, PRE_MODIFIED, PRE_OLD + PRE_COUNT, PRE_HOOK, PRE_NEW, PRE_OLD }; int iSub; if( objc<3 ){ Tcl_WrongNumArgs(interp, 2, objv, "SUB-COMMAND ?ARGS?"); @@ -2873,36 +2873,33 @@ } DbHookCmd(interp, pDb, (objc==4 ? objv[3] : 0), &pDb->pPreUpdateHook); break; } - case PRE_MODIFIED: + case PRE_NEW: case PRE_OLD: { int iIdx; + sqlite3_value *pValue; if( objc!=4 ){ Tcl_WrongNumArgs(interp, 3, objv, "INDEX"); return TCL_ERROR; } if( Tcl_GetIntFromObj(interp, objv[3], &iIdx) ){ return TCL_ERROR; } - if( iSub==PRE_MODIFIED ){ - int iRes; - rc = sqlite3_preupdate_modified(pDb->db, iIdx, &iRes); - if( rc==SQLITE_OK ) Tcl_SetObjResult(interp, Tcl_NewIntObj(iRes)); - }else{ - sqlite3_value *pValue; - assert( iSub==PRE_OLD ); - rc = sqlite3_preupdate_old(pDb->db, iIdx, &pValue); - if( rc==SQLITE_OK ){ - Tcl_Obj *pObj = Tcl_NewStringObj(sqlite3_value_text(pValue), -1); - Tcl_SetObjResult(interp, pObj); - } - } - - if( rc!=SQLITE_OK ){ + if( iSub==PRE_OLD ){ + rc = sqlite3_preupdate_old(pDb->db, iIdx, &pValue); + }else{ + assert( iSub==PRE_NEW ); + rc = sqlite3_preupdate_new(pDb->db, iIdx, &pValue); + } + + if( rc==SQLITE_OK ){ + Tcl_Obj *pObj = Tcl_NewStringObj(sqlite3_value_text(pValue), -1); + Tcl_SetObjResult(interp, pObj); + }else{ Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0); return TCL_ERROR; } } } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -491,11 +491,17 @@ sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx); /* If changing the rowid value, or if there are foreign key constraints ** to process, delete the old record. Otherwise, add a noop OP_Delete ** to invoke the pre-update hook. + ** + ** That (regNew==regnewRowid+1) is true is also important for the + ** pre-update hook. If hte caller invokes preupdate_new(), the returned + ** value is copied from memory cell (regNewRowid+1+iCol), where iCol + ** is the column index supplied by the user. */ + assert( regNew==regNewRowid+1 ); sqlite3VdbeAddOp3(v, OP_Delete, iCur, OPFLAG_ISUPDATE | ((hasFK || chngRowid) ? 0 : OPFLAG_ISNOOP), regNewRowid ); if( !pParse->nested ){ Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -3879,13 +3879,11 @@ /* Invoke the pre-update hook, if any */ if( db->xPreUpdateCallback && pOp->p4.z && (!(pOp->p5 & OPFLAG_ISUPDATE) || pC->rowidIsValid==0) ){ - sqlite3VdbePreUpdateHook(p, pC, - pC->rowidIsValid ? op : SQLITE_INSERT, zDb, zTbl, iKey, iKey - ); + sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, zTbl, iKey, pOp->p2); } if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = iKey; if( pData->flags & MEM_Null ){ @@ -3983,11 +3981,11 @@ if( db->xPreUpdateCallback && pOp->p4.z ){ assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) ); sqlite3VdbePreUpdateHook(p, pC, (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, zDb, zTbl, iKey, - (opflags & OPFLAG_ISUPDATE) ? aMem[pOp->p3].u.i : iKey + pOp->p3 ); } if( opflags & OPFLAG_ISNOOP ) break; Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -334,15 +334,19 @@ /* ** Structure used to store the context required by the ** sqlite3_preupdate_*() API functions. */ struct PreUpdate { + Vdbe *v; VdbeCursor *pCsr; /* Cursor to read old values from */ int op; /* One of SQLITE_INSERT, UPDATE, DELETE */ u8 *aRecord; /* old.* database record */ KeyInfo keyinfo; UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */ + UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ + int iNewReg; /* Register for new.* values */ + Mem *aNew; /* Array of new.* values */ }; /* ** Function prototypes */ @@ -398,11 +402,11 @@ int sqlite3VdbeCloseStatement(Vdbe *, int); void sqlite3VdbeFrameDelete(VdbeFrame*); int sqlite3VdbeFrameRestore(VdbeFrame *); void sqlite3VdbeMemStoreType(Mem *pMem); void sqlite3VdbePreUpdateHook( - Vdbe *, VdbeCursor *, int, const char*, const char*, i64, i64); + Vdbe *, VdbeCursor *, int, const char*, const char*, i64, int); #ifdef SQLITE_DEBUG void sqlite3VdbeMemPrepareToChange(Vdbe*,Mem*); #endif Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -1327,23 +1327,30 @@ int v = pVdbe->aCounter[op-1]; if( resetFlag ) pVdbe->aCounter[op-1] = 0; return v; } +/* +** 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; 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 ){ rc = SQLITE_MISUSE_BKPT; goto preupdate_old_out; } if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; goto preupdate_old_out; } + /* If the old.* record has not yet been loaded into memory, do so now. */ if( p->pUnpacked==0 ){ u32 nRecord; u8 *aRecord; rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRecord); @@ -1370,30 +1377,82 @@ preupdate_old_out: sqlite3Error(db, rc, 0); return sqlite3ApiExit(db, rc); } +/* +** This function is called from within a pre-update callback to retrieve +** the number of columns in the row being updated, deleted or inserted. +*/ int sqlite3_preupdate_count(sqlite3 *db){ PreUpdate *p = db->pPreUpdate; return (p ? p->pCsr->nField : 0); } -int sqlite3_preupdate_modified(sqlite3 *db, int iIdx, int *pbMod){ +/* +** This function is called from within a pre-update callback to retrieve +** a field of the row currently being updated or inserted. +*/ +int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ PreUpdate *p = db->pPreUpdate; int rc = SQLITE_OK; + Mem *pMem; - if( !p || p->op!=SQLITE_UPDATE ){ + if( !p || p->op==SQLITE_DELETE ){ rc = SQLITE_MISUSE_BKPT; - goto preupdate_mod_out; + goto preupdate_new_out; } if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; - goto preupdate_mod_out; + goto preupdate_new_out; + } + + if( p->op==SQLITE_INSERT ){ + /* For an INSERT, memory cell p->iNewReg contains the serialized record + ** that is being inserted. Deserialize it. */ + UnpackedRecord *pUnpack = p->pNewUnpacked; + if( !pUnpack ){ + Mem *pData = &p->v->aMem[p->iNewReg]; + rc = sqlite3VdbeMemExpandBlob(pData); + if( rc!=SQLITE_OK ) goto preupdate_new_out; + pUnpack = sqlite3VdbeRecordUnpack(&p->keyinfo, pData->n, pData->z, 0, 0); + if( !pUnpack ){ + rc = SQLITE_NOMEM; + goto preupdate_new_out; + } + p->pNewUnpacked = pUnpack; + } + if( iIdx>=pUnpack->nField ){ + pMem = (sqlite3_value *)columnNullValue(); + }else{ + pMem = &pUnpack->aMem[iIdx]; + sqlite3VdbeMemStoreType(pMem); + } + }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 + ** caller may modify the value text encoding. + */ + assert( p->op==SQLITE_UPDATE ); + if( !p->aNew ){ + p->aNew = (Mem *)sqlite3DbMallocZero(db, sizeof(Mem) * p->pCsr->nField); + if( !p->aNew ){ + rc = SQLITE_NOMEM; + goto preupdate_new_out; + } + } + pMem = &p->aNew[iIdx]; + if( pMem->flags==0 ){ + rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); + if( rc!=SQLITE_OK ) goto preupdate_new_out; + sqlite3VdbeMemStoreType(pMem); + } } - *pbMod = 1; + *ppValue = pMem; - preupdate_mod_out: + preupdate_new_out: sqlite3Error(db, rc, 0); return sqlite3ApiExit(db, rc); } Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -3174,19 +3174,28 @@ VdbeCursor *pCsr, /* Cursor to grab old.* values from */ int op, /* SQLITE_INSERT, UPDATE or DELETE */ const char *zDb, /* Database name */ const char *zTbl, /* Table name */ i64 iKey1, /* Initial key value */ - i64 iKey2 /* Final key value */ + int iReg /* Register for new.* record */ ){ sqlite3 *db = v->db; + i64 iKey2; PreUpdate preupdate; memset(&preupdate, 0, sizeof(PreUpdate)); + if( op==SQLITE_UPDATE ){ + iKey2 = v->aMem[iReg].u.i; + }else{ + iKey2 = iKey1; + } + + preupdate.v = v; preupdate.pCsr = pCsr; preupdate.op = op; + preupdate.iNewReg = iReg; preupdate.keyinfo.db = db; preupdate.keyinfo.enc = ENC(db); preupdate.keyinfo.nField = pCsr->nField; db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); @@ -3193,7 +3202,17 @@ db->pPreUpdate = 0; sqlite3DbFree(db, preupdate.aRecord); if( preupdate.pUnpacked ){ sqlite3VdbeDeleteUnpackedRecord(preupdate.pUnpacked); } + if( preupdate.pNewUnpacked ){ + sqlite3VdbeDeleteUnpackedRecord(preupdate.pNewUnpacked); + } + if( preupdate.aNew ){ + int i; + for(i=0; inField; i++){ + sqlite3VdbeMemRelease(&preupdate.aNew[i]); + } + sqlite3_free(preupdate.aNew); + } } Index: test/hook.test ================================================================== --- test/hook.test +++ test/hook.test @@ -403,14 +403,20 @@ } proc preupdate_hook {args} { set type [lindex $args 0] eval lappend ::preupdate $args - if {$type != "SQLITE_INSERT"} { + if {$type != "INSERT"} { for {set i 0} {$i < [db preupdate count]} {incr i} { lappend ::preupdate [db preupdate old $i] } + } + if {$type != "DELETE"} { + for {set i 0} {$i < [db preupdate count]} {incr i} { + set rc [catch { db preupdate new $i } v] + lappend ::preupdate $v + } } } db close forcedelete test.db @@ -431,40 +437,40 @@ INSERT INTO t3 VALUES(6, 36); } do_preupdate_test 7.1.1 { INSERT INTO t1 VALUES('x', 'y') -} {INSERT main t1 1 1} +} {INSERT main t1 1 1 x y} # 7.1.2.1 does not use the xfer optimization. 7.1.2.2 does. do_preupdate_test 7.1.2.1 { INSERT INTO t1 SELECT y, x FROM t2; -} {INSERT main t1 2 2 INSERT main t1 3 3} +} {INSERT main t1 2 2 b a INSERT main t1 3 3 d c} do_preupdate_test 7.1.2.2 { INSERT INTO t1 SELECT * FROM t2; -} {INSERT main t1 4 4 INSERT main t1 5 5} +} {INSERT main t1 4 4 a b INSERT main t1 5 5 c d} do_preupdate_test 7.1.3 { REPLACE INTO t1(rowid, a, b) VALUES(1, 1, 1); } { DELETE main t1 1 1 x y - INSERT main t1 1 1 + INSERT main t1 1 1 1 1 } do_preupdate_test 7.1.4 { REPLACE INTO t3 VALUES(4, NULL); } { DELETE main t3 1 1 4 16 - INSERT main t3 4 4 + INSERT main t3 4 4 4 {} } do_preupdate_test 7.1.5 { REPLACE INTO t3(rowid, i, j) VALUES(2, 6, NULL); } { DELETE main t3 2 2 5 25 DELETE main t3 3 3 6 36 - INSERT main t3 2 2 + INSERT main t3 2 2 6 {} } do_execsql_test 7.2.0 { SELECT rowid FROM t1 } {1 2 3 4 5} do_preupdate_test 7.2.1 { @@ -495,33 +501,33 @@ } do_preupdate_test 7.3.1 { UPDATE t2 SET y = y||y; } { - UPDATE main t2 1 1 a b - UPDATE main t2 2 2 c d + UPDATE main t2 1 1 a b a bb + UPDATE main t2 2 2 c d c dd } do_preupdate_test 7.3.2 { UPDATE t2 SET rowid = rowid-1; } { - UPDATE main t2 1 0 a bb - UPDATE main t2 2 1 c dd + UPDATE main t2 1 0 a bb a bb + UPDATE main t2 2 1 c dd c dd } do_preupdate_test 7.3.3 { UPDATE OR REPLACE t2 SET rowid = 1 WHERE x = 'a' } { DELETE main t2 1 1 c dd - UPDATE main t2 0 1 a bb + UPDATE main t2 0 1 a bb a bb } do_preupdate_test 7.3.4.1 { UPDATE OR REPLACE t3 SET i = 5 WHERE i = 6 } { DELETE main t3 2 2 5 25 - UPDATE main t3 3 3 6 36 + UPDATE main t3 3 3 6 36 5 36 } do_execsql_test 7.3.4.2 { INSERT INTO t3 VALUES(10, 100); SELECT rowid, * FROM t3; @@ -530,11 +536,11 @@ do_preupdate_test 7.3.5 { UPDATE OR REPLACE t3 SET rowid = 1, i = 5 WHERE j = 100; } { DELETE main t3 1 1 4 16 DELETE main t3 3 3 5 36 - UPDATE main t3 4 1 10 100 + UPDATE main t3 4 1 10 100 5 100 } do_execsql_test 7.4.1.0 { CREATE TABLE t4(a, b); INSERT INTO t4 VALUES('a', 1); @@ -575,11 +581,11 @@ } do_preupdate_test 7.4.2.1 { UPDATE t5 SET b = 4 WHERE a = 'c' } { DELETE main t5 1 1 a 1 - UPDATE main t5 3 3 c 3 + UPDATE main t5 3 3 c 3 c 4 } do_execsql_test 7.4.2.2 { INSERT INTO t5(rowid, a, b) VALUES(1, 'a', 1); } @@ -604,11 +610,11 @@ } do_preupdate_test 7.5.1.2 { UPDATE t7 SET b = 'five' } { - UPDATE main t7 2 2 three four {} + UPDATE main t7 2 2 three four {} three five {} } do_execsql_test 7.5.2.0 { CREATE TABLE t8(a, b); INSERT INTO t8 VALUES('one', 'two'); @@ -626,10 +632,10 @@ DELETE main t8 1 1 one two xxx } do_preupdate_test 7.5.2.2 { UPDATE t8 SET b = 'five' } { - UPDATE main t8 2 2 three four xxx + UPDATE main t8 2 2 three four xxx three five xxx } finish_test