Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API.
FossilOrigin-Name: 1feaf2d35fd9ec777319717ae2c2929d66fe7baa
This commit is contained in:
parent
1e7a2d4315
commit
b4480e942f
@ -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 } {}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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" }
|
||||
}
|
||||
|
@ -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; i<sqlite3_column_count(pStmt); i++){
|
||||
int bChanged = 0;
|
||||
int nAdvance;
|
||||
@ -1366,6 +1380,7 @@ int sqlite3session_changeset(
|
||||
int iCol;
|
||||
if( p->bInsert ){
|
||||
sessionAppendByte(&buf, SQLITE_INSERT, &rc);
|
||||
sessionAppendByte(&buf, p->bIndirect, &rc);
|
||||
for(iCol=0; iCol<nCol; iCol++){
|
||||
sessionAppendCol(&buf, pSel, iCol, &rc);
|
||||
}
|
||||
@ -1375,6 +1390,7 @@ int sqlite3session_changeset(
|
||||
}else if( !p->bInsert ){
|
||||
/* 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);
|
||||
|
@ -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:
|
||||
**
|
||||
** <ul>
|
||||
** <li> The session object "indirect" flag is set when the change is
|
||||
** made, or
|
||||
** <li> The change is made by an SQL trigger or foreign key action
|
||||
** instead of directly as a result of a users SQL statement.
|
||||
** </ul>
|
||||
**
|
||||
** 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 */
|
||||
);
|
||||
|
||||
/*
|
||||
|
@ -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 ){
|
||||
|
22
manifest
22
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
|
||||
|
@ -1 +1 @@
|
||||
bdea70895c2c686a4dd3f4bf0a475fd1501d9551
|
||||
1feaf2d35fd9ec777319717ae2c2929d66fe7baa
|
Loading…
Reference in New Issue
Block a user