From b4480e942ff9480be5d60e5e1cca6b4d83181e2f Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 23 Mar 2011 16:03:11 +0000 Subject: [PATCH] Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API. FossilOrigin-Name: 1feaf2d35fd9ec777319717ae2c2929d66fe7baa --- ext/session/session1.test | 44 ++++++------ ext/session/session2.test | 124 ++++++++++++++++++++++++++++++---- ext/session/sessionfault.test | 20 +++--- ext/session/sqlite3session.c | 72 +++++++++++++++----- ext/session/sqlite3session.h | 43 ++++++++++-- ext/session/test_session.c | 18 ++++- manifest | 22 +++--- manifest.uuid | 2 +- 8 files changed, 260 insertions(+), 85 deletions(-) diff --git a/ext/session/session1.test b/ext/session/session1.test index 1d8b1921b9..582f802bea 100644 --- a/ext/session/session1.test +++ b/ext/session/session1.test @@ -88,14 +88,14 @@ do_test 2.1.1 { execsql { INSERT INTO t1 VALUES(3, 'Thonburi') } } {} do_changeset_test 2.1.2 S { - {INSERT t1 {} {i 1 t Sukhothai}} - {INSERT t1 {} {i 2 t Ayutthaya}} - {INSERT t1 {} {i 3 t Thonburi}} + {INSERT t1 0 {} {i 1 t Sukhothai}} + {INSERT t1 0 {} {i 2 t Ayutthaya}} + {INSERT t1 0 {} {i 3 t Thonburi}} } do_changeset_invert_test 2.1.3 S { - {DELETE t1 {i 1 t Sukhothai} {}} - {DELETE t1 {i 2 t Ayutthaya} {}} - {DELETE t1 {i 3 t Thonburi} {}} + {DELETE t1 0 {i 1 t Sukhothai} {}} + {DELETE t1 0 {i 2 t Ayutthaya} {}} + {DELETE t1 0 {i 3 t Thonburi} {}} } do_test 2.1.4 { S delete } {} @@ -105,14 +105,14 @@ do_test 2.2.1 { execsql { DELETE FROM t1 WHERE 1 } } {} do_changeset_test 2.2.2 S { - {DELETE t1 {i 1 t Sukhothai} {}} - {DELETE t1 {i 2 t Ayutthaya} {}} - {DELETE t1 {i 3 t Thonburi} {}} + {DELETE t1 0 {i 1 t Sukhothai} {}} + {DELETE t1 0 {i 2 t Ayutthaya} {}} + {DELETE t1 0 {i 3 t Thonburi} {}} } do_changeset_invert_test 2.2.3 S { - {INSERT t1 {} {i 1 t Sukhothai}} - {INSERT t1 {} {i 2 t Ayutthaya}} - {INSERT t1 {} {i 3 t Thonburi}} + {INSERT t1 0 {} {i 1 t Sukhothai}} + {INSERT t1 0 {} {i 2 t Ayutthaya}} + {INSERT t1 0 {} {i 3 t Thonburi}} } do_test 2.2.4 { S delete } {} @@ -131,19 +131,19 @@ do_test 2.3.1 { } {} do_changeset_test 2.3.2 S { - {INSERT t1 {} {i 10 t Sukhothai}} - {DELETE t1 {i 1 t Sukhothai} {}} - {UPDATE t1 {i 2 t Ayutthaya} {{} {} t Surin}} - {DELETE t1 {i 3 t Thonburi} {}} - {INSERT t1 {} {i 20 t Thapae}} + {INSERT t1 0 {} {i 10 t Sukhothai}} + {DELETE t1 0 {i 1 t Sukhothai} {}} + {UPDATE t1 0 {i 2 t Ayutthaya} {{} {} t Surin}} + {DELETE t1 0 {i 3 t Thonburi} {}} + {INSERT t1 0 {} {i 20 t Thapae}} } do_changeset_invert_test 2.3.3 S { - {DELETE t1 {i 10 t Sukhothai} {}} - {INSERT t1 {} {i 1 t Sukhothai}} - {UPDATE t1 {{} {} t Surin} {i 2 t Ayutthaya}} - {INSERT t1 {} {i 3 t Thonburi}} - {DELETE t1 {i 20 t Thapae} {}} + {DELETE t1 0 {i 10 t Sukhothai} {}} + {INSERT t1 0 {} {i 1 t Sukhothai}} + {UPDATE t1 0 {{} {} t Surin} {i 2 t Ayutthaya}} + {INSERT t1 0 {} {i 3 t Thonburi}} + {DELETE t1 0 {i 20 t Thapae} {}} } do_test 2.3.4 { S delete } {} diff --git a/ext/session/session2.test b/ext/session/session2.test index d0d8fa5617..d6cb284659 100644 --- a/ext/session/session2.test +++ b/ext/session/session2.test @@ -41,13 +41,13 @@ do_iterator_test 1.1 t1 { DELETE FROM t1 WHERE a = 'i'; INSERT INTO t1 VALUES('ii', 'two'); } { - {DELETE t1 {t i t one} {}} - {INSERT t1 {} {t ii t two}} + {DELETE t1 0 {t i t one} {}} + {INSERT t1 0 {} {t ii t two}} } do_iterator_test 1.2 t1 { INSERT INTO t1 VALUES(1.5, 99.9) } { - {INSERT t1 {} {f 1.5 f 99.9}} + {INSERT t1 0 {} {f 1.5 f 99.9}} } @@ -228,15 +228,15 @@ foreach {tn sql changeset} { INSERT INTO t1 VALUES(NULL); INSERT INTO t1 VALUES(456); } { - {INSERT t1 {} {i 456}} - {INSERT t1 {} {i 123}} + {INSERT t1 0 {} {i 456}} + {INSERT t1 0 {} {i 123}} } 2 { UPDATE t1 SET a = NULL; } { - {DELETE t1 {i 456} {}} - {DELETE t1 {i 123} {}} + {DELETE t1 0 {i 456} {}} + {DELETE t1 0 {i 123} {}} } 3 { DELETE FROM t1 } { } @@ -244,14 +244,14 @@ foreach {tn sql changeset} { 4 { INSERT INTO t3 VALUES(NULL, NULL) } { - {INSERT t3 {} {n {} i 1}} + {INSERT t3 0 {} {n {} i 1}} } 5 { INSERT INTO t2 VALUES(1, 2, NULL) } { } 6 { INSERT INTO t2 VALUES(1, NULL, 3) } { } 7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { } - 8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 {} {i 1 i 2 i 3}} } - 9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 {i 1 i 2 i 3} {}} } + 8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 0 {} {i 1 i 2 i 3}} } + 9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 0 {i 1 i 2 i 3} {}} } } { do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset @@ -269,18 +269,112 @@ do_execsql_test 5.0 { } foreach {tn sql changeset} { - 1 { INSERT INTO t1 VALUES(35) } { {INSERT t1 {} {i 35}} } - 2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 {} {i 36 i 37}} } + 1 { INSERT INTO t1 VALUES(35) } { {INSERT t1 0 {} {i 35}} } + 2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 {} {i 36 i 37}} } 3 { DELETE FROM t1 WHERE 1; UPDATE t2 SET x = 34; } { - {UPDATE t2 {i 36 i 37} {i 34 {} {}}} - {DELETE t1 {i 35} {}} + {UPDATE t2 0 {i 36 i 37} {i 34 {} {}}} + {DELETE t1 0 {i 35} {}} } } { do_iterator_test 5.$tn * $sql $changeset } -finish_test +#------------------------------------------------------------------------- +# The next block of tests verify that the "indirect" flag is set +# correctly within changesets. The indirect flag is set for a change +# if either of the following are true: +# +# * The sqlite3session_indirect() API has been used to set the session +# indirect flag to true, or +# * The change was made by a trigger. +# +# If the same row is updated more than once during a session, then the +# change is considered indirect only if all changes meet the criteria +# above. +# +test_reset +db function indirect [list S indirect] +do_execsql_test 6.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c); + + CREATE TABLE t2(x PRIMARY KEY, y); + CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN + INSERT INTO t2 VALUES(new.x+1, NULL); + END; +} + +do_iterator_test 6.1.1 * { + INSERT INTO t1 VALUES(1, 'one', 'i'); + SELECT indirect(1); + INSERT INTO t1 VALUES(2, 'two', 'ii'); + SELECT indirect(0); + INSERT INTO t1 VALUES(3, 'three', 'iii'); +} { + {INSERT t1 0 {} {i 1 t one t i}} + {INSERT t1 1 {} {i 2 t two t ii}} + {INSERT t1 0 {} {i 3 t three t iii}} +} + +do_iterator_test 6.1.2 * { + SELECT indirect(1); + UPDATE t1 SET c = 'I' WHERE a = 1; + SELECT indirect(0); +} { + {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}} +} +do_iterator_test 6.1.3 * { + SELECT indirect(1); + UPDATE t1 SET c = '.' WHERE a = 1; + SELECT indirect(0); + UPDATE t1 SET c = 'o' WHERE a = 1; +} { + {UPDATE t1 0 {i 1 {} {} t I} {{} {} {} {} t o}} +} +do_iterator_test 6.1.4 * { + SELECT indirect(0); + UPDATE t1 SET c = 'x' WHERE a = 1; + SELECT indirect(1); + UPDATE t1 SET c = 'i' WHERE a = 1; +} { + {UPDATE t1 0 {i 1 {} {} t o} {{} {} {} {} t i}} +} +do_iterator_test 6.1.4 * { + SELECT indirect(1); + UPDATE t1 SET c = 'y' WHERE a = 1; + SELECT indirect(1); + UPDATE t1 SET c = 'I' WHERE a = 1; +} { + {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}} +} + +do_iterator_test 6.1.5 * { + INSERT INTO t2 VALUES(1, 'x'); +} { + {INSERT t2 0 {} {i 1 t x}} + {INSERT t2 1 {} {i 2 n {}}} +} + +do_iterator_test 6.1.6 * { + SELECT indirect(1); + INSERT INTO t2 VALUES(3, 'x'); + SELECT indirect(0); + UPDATE t2 SET y = 'y' WHERE x>2; +} { + {INSERT t2 0 {} {i 3 t y}} + {INSERT t2 0 {} {i 4 t y}} +} + +do_iterator_test 6.1.7 * { + SELECT indirect(1); + DELETE FROM t2 WHERE x = 4; + SELECT indirect(0); + INSERT INTO t2 VALUES(4, 'new'); +} { + {UPDATE t2 0 {i 4 t y} {{} {} t new}} +} + +finish_test diff --git a/ext/session/sessionfault.test b/ext/session/sessionfault.test index c8ce9e48af..04ac8b8b88 100644 --- a/ext/session/sessionfault.test +++ b/ext/session/sessionfault.test @@ -38,7 +38,7 @@ db2 close # Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all # tables. # -do_faultsim_test pagerfault-1.1 -faults oom-* -prep { +do_faultsim_test 1.1 -faults oom-* -prep { catch {db2 close} catch {db close} faultsim_restore_and_reopen @@ -55,7 +55,7 @@ do_faultsim_test pagerfault-1.1 -faults oom-* -prep { if {$testrc==0} { compare_db db db2 } } -do_faultsim_test pagerfault-1.2 -faults oom-* -prep { +do_faultsim_test 1.2 -faults oom-* -prep { catch {db2 close} catch {db close} faultsim_restore_and_reopen @@ -82,7 +82,7 @@ do_faultsim_test pagerfault-1.2 -faults oom-* -prep { } #------------------------------------------------------------------------- -# The following block of tests - pagerfault-2.* - are designed to check +# The following block of tests - 2.* - are designed to check # the handling of faults in the sqlite3changeset_apply() function. # catch {db close} @@ -110,7 +110,7 @@ foreach {tn conflict_policy sql sql2} { } { proc xConflict args [list return $conflict_policy] - do_faultsim_test pagerfault-2.$tn -faults oom-transient -prep { + do_faultsim_test 2.$tn -faults oom-transient -prep { catch {db2 close} catch {db close} faultsim_restore_and_reopen @@ -132,7 +132,7 @@ foreach {tn conflict_policy sql sql2} { # resizing the session object hash-table from 256 to 512 buckets. This # is not an error, just a sub-optimal condition. # -do_faultsim_test pagerfault-3 -faults oom-* -prep { +do_faultsim_test 3 -faults oom-* -prep { catch {db2 close} catch {db close} faultsim_restore_and_reopen @@ -191,7 +191,7 @@ do_test 4.0 { faultsim_save_and_close db2 close -do_faultsim_test pagerfault-4 -faults oom-* -prep { +do_faultsim_test 4 -faults oom-* -prep { catch {db2 close} catch {db close} faultsim_restore_and_reopen @@ -234,7 +234,7 @@ set changeset [changeset_from_sql { }] db close -do_faultsim_test pagerfault-5 -faults oom* -body { +do_faultsim_test 5 -faults oom* -body { set ::inverse [sqlite3changeset_invert $::changeset] set {} {} } -test { @@ -243,9 +243,9 @@ do_faultsim_test pagerfault-5 -faults oom* -body { set x [list] sqlite3session_foreach c $::inverse { lappend x $c } foreach c { - {DELETE t1 {t xxx t yyy} {}} - {INSERT t1 {} {t string i 1}} - {UPDATE t1 {i 20 {} {}} {i 4 i 2}} + {DELETE t1 0 {t xxx t yyy} {}} + {INSERT t1 0 {} {t string i 1}} + {UPDATE t1 0 {i 20 {} {}} {i 4 i 2}} } { lappend y $c } if {$x != $y} { error "changeset no good" } } diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 3619e7c4b6..ff28487d3c 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -19,6 +19,7 @@ struct sqlite3_session { sqlite3 *db; /* Database handle session is attached to */ char *zDb; /* Name of database session is attached to */ int bEnable; /* True if currently recording */ + int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ int rc; /* Non-zero if an error has occurred */ sqlite3_session *pNext; /* Next session object on same db. */ @@ -37,6 +38,7 @@ struct sqlite3_changeset_iter { char *zTab; /* Current table */ int nCol; /* Number of columns in zTab */ int op; /* Current operation */ + int bIndirect; /* True if current change was indirect */ sqlite3_value **apValue; /* old.* and new.* values */ }; @@ -131,6 +133,7 @@ struct SessionTable { */ struct SessionChange { int bInsert; /* True if row was inserted this session */ + int bIndirect; /* True if this change is "indirect" */ int nRecord; /* Number of bytes in buffer aRecord[] */ u8 *aRecord; /* Buffer containing old.* record */ SessionChange *pNext; /* For hash-table collisions */ @@ -660,8 +663,6 @@ static void sessionPreupdateOneChange( SessionTable *pTab ){ sqlite3 *db = pSession->db; - SessionChange *pChange; - SessionChange *pC; int iHash; int bNullPk = 0; int rc = SQLITE_OK; @@ -679,7 +680,8 @@ static void sessionPreupdateOneChange( ** the hash table. Otherwise, set pChange to NULL. */ rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk); - if( bNullPk==0 ){ + if( rc==SQLITE_OK && bNullPk==0 ){ + SessionChange *pC; for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){ int bEqual; rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual); @@ -689,9 +691,11 @@ static void sessionPreupdateOneChange( /* Create a new change object containing all the old values (if ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK ** values (if this is an INSERT). */ + SessionChange *pChange; /* New change object */ int nByte; /* Number of bytes to allocate */ int i; /* Used to iterate through columns */ + assert( rc==SQLITE_OK ); pTab->nEntry++; /* Figure out how large an allocation is required */ @@ -732,6 +736,9 @@ static void sessionPreupdateOneChange( } if( rc==SQLITE_OK ){ /* Add the change back to the hash-table */ + if( pSession->bIndirect || sqlite3_preupdate_depth(pSession->db) ){ + pChange->bIndirect = 1; + } pChange->nRecord = nByte; pChange->bInsert = (op==SQLITE_INSERT); pChange->pNext = pTab->apChange[iHash]; @@ -739,6 +746,12 @@ static void sessionPreupdateOneChange( }else{ sqlite3_free(pChange); } + }else if( rc==SQLITE_OK && pC->bIndirect ){ + /* If the existing change is considered "indirect", but this current + ** change is "direct", mark the change object as direct. */ + if( sqlite3_preupdate_depth(pSession->db)==0 && pSession->bIndirect==0 ){ + pC->bIndirect = 0; + } } } @@ -1136,6 +1149,7 @@ static void sessionAppendUpdate( u8 *pCsr = p->aRecord; /* Used to iterate through old.* values */ sessionAppendByte(pBuf, SQLITE_UPDATE, pRc); + sessionAppendByte(pBuf, p->bIndirect, pRc); for(i=0; ibInsert ){ sessionAppendByte(&buf, SQLITE_INSERT, &rc); + sessionAppendByte(&buf, p->bIndirect, &rc); for(iCol=0; iColbInsert ){ /* A DELETE change */ sessionAppendByte(&buf, SQLITE_DELETE, &rc); + sessionAppendByte(&buf, p->bIndirect, &rc); sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); } if( rc==SQLITE_OK ){ @@ -1416,6 +1432,20 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable){ return ret; } +/* +** Enable or disable the session object passed as the first argument. +*/ +int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){ + int ret; + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); + if( bIndirect>=0 ){ + pSession->bIndirect = bIndirect; + } + ret = pSession->bIndirect; + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); + return ret; +} + /* ** Create an iterator used to iterate through the contents of a changeset. */ @@ -1548,6 +1578,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *p){ p->zTab = (char *)aChange; aChange += (strlen((char *)aChange) + 1); p->op = *(aChange++); + p->bIndirect = *(aChange++); sqlite3_free(p->apValue); nByte = sizeof(sqlite3_value *) * p->nCol * 2; p->apValue = (sqlite3_value **)sqlite3_malloc(nByte); @@ -1557,6 +1588,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *p){ memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); }else{ p->op = c; + p->bIndirect = *(aChange++); } if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ return (p->rc = SQLITE_CORRUPT); @@ -1587,11 +1619,13 @@ int sqlite3changeset_op( sqlite3_changeset_iter *pIter, /* Iterator handle */ const char **pzTab, /* OUT: Pointer to table name */ int *pnCol, /* OUT: Number of columns in table */ - int *pOp /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True if change is indirect */ ){ *pOp = pIter->op; *pnCol = pIter->nCol; *pzTab = pIter->zTab; + if( pbIndirect ) *pbIndirect = pIter->bIndirect; return SQLITE_OK; } @@ -1740,31 +1774,33 @@ int sqlite3changeset_invert( case SQLITE_INSERT: case SQLITE_DELETE: { int nByte; - u8 *aEnd = &aIn[i+1]; + u8 *aEnd = &aIn[i+2]; sessionReadRecord(&aEnd, nCol, 0); aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE); - nByte = aEnd - &aIn[i+1]; - memcpy(&aOut[i+1], &aIn[i+1], nByte); - i += 1 + nByte; + aOut[i+1] = aIn[i+1]; + nByte = aEnd - &aIn[i+2]; + memcpy(&aOut[i+2], &aIn[i+2], nByte); + i += 2 + nByte; break; } case SQLITE_UPDATE: { int nByte1; /* Size of old.* record in bytes */ int nByte2; /* Size of new.* record in bytes */ - u8 *aEnd = &aIn[i+1]; + u8 *aEnd = &aIn[i+2]; sessionReadRecord(&aEnd, nCol, 0); - nByte1 = aEnd - &aIn[i+1]; + nByte1 = aEnd - &aIn[i+2]; sessionReadRecord(&aEnd, nCol, 0); - nByte2 = aEnd - &aIn[i+1] - nByte1; + nByte2 = aEnd - &aIn[i+2] - nByte1; aOut[i] = SQLITE_UPDATE; - memcpy(&aOut[i+1], &aIn[i+1+nByte1], nByte2); - memcpy(&aOut[i+1+nByte2], &aIn[i+1], nByte1); + aOut[i+1] = aIn[i+1]; + memcpy(&aOut[i+2], &aIn[i+2+nByte1], nByte2); + memcpy(&aOut[i+2+nByte2], &aIn[i+2], nByte1); - i += 1 + nByte1 + nByte2; + i += 2 + nByte1 + nByte2; break; } @@ -2097,7 +2133,7 @@ static int sessionSeekToRow( int op; /* Changset operation (SQLITE_UPDATE etc.) */ const char *zDummy; /* Unused */ - sqlite3changeset_op(pIter, &zDummy, &nCol, &op); + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); rc = sessionBindRow(pIter, op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old, nCol, abPK, pSelect @@ -2160,7 +2196,7 @@ static int sessionConflictHandler( int op; const char *zDummy; - sqlite3changeset_op(pIter, &zDummy, &nCol, &op); + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA ); assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT ); @@ -2248,7 +2284,7 @@ static int sessionApplyOneOp( assert( p->azCol && p->abPK ); assert( !pbReplace || *pbReplace==0 ); - sqlite3changeset_op(pIter, &zDummy, &nCol, &op); + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); if( op==SQLITE_DELETE ){ @@ -2362,7 +2398,7 @@ int sqlite3changeset_apply( int bReplace = 0; int bRetry = 0; const char *zNew; - sqlite3changeset_op(pIter, &zNew, &nCol, &op); + sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0); if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ sqlite3_free(sApply.azCol); diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index d9d0ae1f54..ee0402bffa 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -90,6 +90,35 @@ void sqlite3session_delete(sqlite3_session *pSession); */ int sqlite3session_enable(sqlite3_session *pSession, int bEnable); +/* +** CAPI3REF: Set Or Clear the Indirect Change Flag +** +** Each change recorded by a session object is marked as either direct or +** indirect. A change is marked as indirect if either: +** +**
    +**
  • The session object "indirect" flag is set when the change is +** made, or +**
  • The change is made by an SQL trigger or foreign key action +** instead of directly as a result of a users SQL statement. +**
+** +** If a single row is affected by more than one operation within a session, +** then the change is considered indirect if all operations meet the criteria +** for an indirect change above, or direct otherwise. +** +** This function is used to set, clear or query the session object indirect +** flag. If the second argument passed to this function is zero, then the +** indirect flag is cleared. If it is greater than zero, the indirect flag +** is set. Passing a value less than zero does not modify the current value +** of the indirect flag, and may be used to query the current state of the +** indirect flag for the specified session object. +** +** The return value indicates the final state of the indirect flag: 0 if +** it is clear, or 1 if it is set. +*/ +int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); + /* ** CAPI3REF: Attach A Table To A Session Object ** @@ -292,10 +321,13 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter); ** affected by the current change. The buffer remains valid until either ** sqlite3changeset_next() is called on the iterator or until the ** conflict-handler function returns. If pnCol is not NULL, then *pnCol is -** set to the number of columns in the table affected by the change. Finally, -** if pOp is not NULL, then *pOp is set to one of [SQLITE_INSERT], -** [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the type of change that -** the iterator currently points to. +** set to the number of columns in the table affected by the change. If +** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change +** is an indirect change, or false (0) otherwise. See the documentation for +** [sqlite3session_indirect()] for a description of direct and indirect +** changes. Finally, if pOp is not NULL, then *pOp is set to one of +** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the +** type of change that the iterator currently points to. ** ** If no error occurs, SQLITE_OK is returned. If an error does occur, an ** SQLite error code is returned. The values of the output variables may not @@ -305,7 +337,8 @@ int sqlite3changeset_op( sqlite3_changeset_iter *pIter, /* Iterator object */ const char **pzTab, /* OUT: Pointer to table name */ int *pnCol, /* OUT: Number of columns in table */ - int *pOp /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True for an 'indirect' change */ ); /* diff --git a/ext/session/test_session.c b/ext/session/test_session.c index ecc07187d3..fee52d7f9c 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -17,6 +17,7 @@ static int test_session_error(Tcl_Interp *interp, int rc){ ** $session changeset ** $session delete ** $session enable BOOL +** $session indirect BOOL */ static int test_session_cmd( void *clientData, @@ -34,7 +35,8 @@ static int test_session_cmd( { "attach", 1, "TABLE", }, /* 0 */ { "changeset", 0, "", }, /* 1 */ { "delete", 0, "", }, /* 2 */ - { "enable", 1, "", }, /* 3 */ + { "enable", 1, "BOOL", }, /* 3 */ + { "indirect", 1, "BOOL", }, /* 4 */ { 0 } }; int iSub; @@ -88,6 +90,14 @@ static int test_session_cmd( Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); break; } + + case 4: { /* indirect */ + int val; + if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR; + val = sqlite3session_indirect(pSession, val); + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); + break; + } } return TCL_OK; @@ -205,7 +215,7 @@ static int test_conflict_handler( pEval = Tcl_DuplicateObj(p->pScript); Tcl_IncrRefCount(pEval); - sqlite3changeset_op(pIter, &zTab, &nCol, &op); + sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); /* Append the operation type. */ Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj( @@ -396,8 +406,9 @@ static int test_sqlite3session_foreach( Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */ Tcl_Obj *pOld; /* Vector of old.* values */ Tcl_Obj *pNew; /* Vector of new.* values */ + int bIndirect; - sqlite3changeset_op(pIter, &zTab, &nCol, &op); + sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); pVar = Tcl_NewObj(); Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( op==SQLITE_INSERT ? "INSERT" : @@ -405,6 +416,7 @@ static int test_sqlite3session_foreach( "DELETE", -1 )); Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); + Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); pOld = Tcl_NewObj(); if( op!=SQLITE_INSERT ){ diff --git a/manifest b/manifest index 20662eaa2f..1178e77cb3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sAPI\sfunction\ssqlite3_preupdate_depth(),\sfor\sdetermining\sthe\sdepth\sof\sthe\strigger\sstack\sfrom\swithin\sa\spre-update\scallback. -D 2011-03-22T18:45:30 +C Add\sthe\s"indirect\sflag"\sto\sthe\schangeset\sblob\sformat.\sAlso\sthe\ssqlite3session_indirect()\sAPI. +D 2011-03-23T16:03:12 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -99,13 +99,13 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024 -F ext/session/session1.test 1e8cda2cc8a60171dabc0fbec4124f9f7c943f23 -F ext/session/session2.test f993ee243db15025a7d9b1dae903fe3f82f04229 +F ext/session/session1.test bca38efbc34c0cdecc10e599246962860e3f664b +F ext/session/session2.test b0d305ee1397d7c17f9743126f636e97ddf88542 F ext/session/session_common.tcl fb91560b6dbd086010df8b3a137a452f1ac21a28 -F ext/session/sessionfault.test 495f87fb9bd764ae90d4b40d0c33a76d86d9063e -F ext/session/sqlite3session.c ec0f440ae73229ae8ae307a31e631b9d71991cd4 -F ext/session/sqlite3session.h 5055e21ca0cb6ddacd46b773a15d90bc0298b0a2 -F ext/session/test_session.c d31fbf5902d0cff5e5495a5ce62efda77b596f2b +F ext/session/sessionfault.test 4489a49d2d44c74c24251d5802b2cc011dbdac21 +F ext/session/sqlite3session.c 0c2e8f6a6d9872943edd04d0a19bf7b05db9df83 +F ext/session/sqlite3session.h 000c1ed86322d9d8e8118cd5ba815269aac608f2 +F ext/session/test_session.c 0fcfbd51e3f5885b958fbc8cdd8cba01b85b8b16 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F main.mk ae0868e05c76eaa8a0ae3d6927a949b1c8e810d7 @@ -923,7 +923,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 183c236e991faaabdc768e52e926c52cf4a7abc9 -R 3714f49d9e3ab391fe868f9d204fa8d5 +P bdea70895c2c686a4dd3f4bf0a475fd1501d9551 +R c02abbe676895f559af6fe54b071c0da U dan -Z 4e2f4fe62e6298f01b15895da5c35a23 +Z 27e4954529d298836a72824c8b085018 diff --git a/manifest.uuid b/manifest.uuid index cf0cd54d72..f85c8efaad 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bdea70895c2c686a4dd3f4bf0a475fd1501d9551 \ No newline at end of file +1feaf2d35fd9ec777319717ae2c2929d66fe7baa \ No newline at end of file