mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Fix a problem causing sqlite3changeset_invert() to effectively drop UPDATE changes.
FossilOrigin-Name: bb3e65d9724dcecdc54b4c9fb0448f95d14495ff
This commit is contained in:
@ -142,7 +142,7 @@ do_changeset_test 2.3.2 S {
|
||||
do_changeset_invert_test 2.3.3 S {
|
||||
{DELETE t1 0 X. {i 10 t Sukhothai} {}}
|
||||
{INSERT t1 0 X. {} {i 1 t Sukhothai}}
|
||||
{UPDATE t1 0 X. {{} {} t Surin} {i 2 t Ayutthaya}}
|
||||
{UPDATE t1 0 X. {i 2 t Surin} {{} {} t Ayutthaya}}
|
||||
{INSERT t1 0 X. {} {i 3 t Thonburi}}
|
||||
{DELETE t1 0 X. {i 20 t Thapae} {}}
|
||||
}
|
||||
@ -492,5 +492,18 @@ do_test 8.3 {
|
||||
} {1}
|
||||
do_test 8.4 { S delete } {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test 9.1 {
|
||||
CREATE TABLE t7(a, b, c, d, e PRIMARY KEY, f, g);
|
||||
INSERT INTO t7 VALUES(1, 1, 1, 1, 1, 1, 1);
|
||||
}
|
||||
do_test 9.2 {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql { UPDATE t7 SET b=2, d=2 }
|
||||
} {}
|
||||
do_changeset_test 9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}}
|
||||
S delete
|
||||
catch { db2 close }
|
||||
finish_test
|
||||
|
92
ext/session/session8.test
Normal file
92
ext/session/session8.test
Normal file
@ -0,0 +1,92 @@
|
||||
# 2011 July 13
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix session8
|
||||
|
||||
proc noop {args} {}
|
||||
|
||||
# Like [dbcksum] in tester.tcl. Except this version is not sensitive
|
||||
# to changes in the value of implicit IPK columns.
|
||||
#
|
||||
proc udbcksum {db dbname} {
|
||||
if {$dbname=="temp"} {
|
||||
set master sqlite_temp_master
|
||||
} else {
|
||||
set master $dbname.sqlite_master
|
||||
}
|
||||
set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
|
||||
set txt [$db eval "SELECT * FROM $master"]\n
|
||||
foreach tab $alltab {
|
||||
append txt [lsort [$db eval "SELECT * FROM $dbname.$tab"]]\n
|
||||
}
|
||||
return [md5 $txt]
|
||||
}
|
||||
|
||||
proc do_then_undo {tn sql} {
|
||||
set ck1 [udbcksum db main]
|
||||
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
db eval $sql
|
||||
|
||||
set ck2 [udbcksum db main]
|
||||
|
||||
set invert [sqlite3changeset_invert [S changeset]]
|
||||
S delete
|
||||
sqlite3changeset_apply db $invert noop
|
||||
|
||||
set ck3 [udbcksum db main]
|
||||
|
||||
set a [expr {$ck1==$ck2}]
|
||||
set b [expr {$ck1==$ck3}]
|
||||
uplevel [list do_test $tn.1 "set {} $a" 0]
|
||||
uplevel [list do_test $tn.2 "set {} $b" 1]
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES("abc", "xyz");
|
||||
}
|
||||
do_then_undo 1.2 { INSERT INTO t1 VALUES(3, 4); }
|
||||
do_then_undo 1.3 { DELETE FROM t1 WHERE b=2; }
|
||||
do_then_undo 1.4 { UPDATE t1 SET b = 3 WHERE a = 1; }
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
CREATE TABLE t2(a, b PRIMARY KEY);
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
INSERT INTO t2 VALUES('abc', 'xyz');
|
||||
}
|
||||
do_then_undo 1.2 { INSERT INTO t2 VALUES(3, 4); }
|
||||
do_then_undo 1.3 { DELETE FROM t2 WHERE b=2; }
|
||||
do_then_undo 1.4 { UPDATE t1 SET a = '123' WHERE b = 'xyz'; }
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
CREATE TABLE t3(a, b, c, d, e, PRIMARY KEY(c, e));
|
||||
INSERT INTO t3 VALUES('x', 45, 0.0, 'abcdef', 12);
|
||||
INSERT INTO t3 VALUES(45, 0.0, 'abcdef', 12, 'x');
|
||||
INSERT INTO t3 VALUES(0.0, 'abcdef', 12, 'x', 45);
|
||||
}
|
||||
|
||||
do_then_undo 3.2 { UPDATE t3 SET b=b||b WHERE e!='x' }
|
||||
do_then_undo 3.3 { UPDATE t3 SET a = 46 }
|
||||
|
||||
finish_test
|
||||
|
@ -2132,14 +2132,17 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
|
||||
*/
|
||||
int sqlite3changeset_invert(
|
||||
int nChangeset, /* Number of bytes in input */
|
||||
void *pChangeset, /* Input changeset */
|
||||
const void *pChangeset, /* Input changeset */
|
||||
int *pnInverted, /* OUT: Number of bytes in output changeset */
|
||||
void **ppInverted /* OUT: Inverse of pChangeset */
|
||||
){
|
||||
int rc = SQLITE_OK; /* Return value */
|
||||
u8 *aOut;
|
||||
u8 *aIn;
|
||||
int i;
|
||||
int nCol = 0;
|
||||
int nCol = 0; /* Number of cols in current table */
|
||||
u8 *abPK = 0; /* PK array for current table */
|
||||
sqlite3_value **apVal = 0; /* Space for values for UPDATE inversion */
|
||||
|
||||
/* Zero the output variables in case an error occurs. */
|
||||
*ppInverted = 0;
|
||||
@ -2163,10 +2166,13 @@ int sqlite3changeset_invert(
|
||||
** * A nul-terminated table name.
|
||||
*/
|
||||
int nByte = 1 + sessionVarintGet(&aIn[i+1], &nCol);
|
||||
abPK = &aIn[i+nByte];
|
||||
nByte += nCol;
|
||||
nByte += 1 + sqlite3Strlen30((char *)&aIn[i+nByte]);
|
||||
memcpy(&aOut[i], &aIn[i], nByte);
|
||||
i += nByte;
|
||||
sqlite3_free(apVal);
|
||||
apVal = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2185,40 +2191,82 @@ int sqlite3changeset_invert(
|
||||
}
|
||||
|
||||
case SQLITE_UPDATE: {
|
||||
int nByte1; /* Size of old.* record in bytes */
|
||||
int nByte2; /* Size of new.* record in bytes */
|
||||
u8 *aEnd = &aIn[i+2];
|
||||
int iCol;
|
||||
int nWrite = 0;
|
||||
u8 *aEnd = &aIn[i+2];
|
||||
|
||||
sessionReadRecord(&aEnd, nCol, 0);
|
||||
nByte1 = (int)(aEnd - &aIn[i+2]);
|
||||
sessionReadRecord(&aEnd, nCol, 0);
|
||||
nByte2 = (int)(aEnd - &aIn[i+2]) - nByte1;
|
||||
if( 0==apVal ){
|
||||
apVal = (sqlite3_value **)sqlite3_malloc(sizeof(apVal[0])*nCol*2);
|
||||
if( 0==apVal ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto finished_invert;
|
||||
}
|
||||
memset(apVal, 0, sizeof(apVal[0])*nCol*2);
|
||||
}
|
||||
|
||||
/* Read the old.* and new.* records for the update change. */
|
||||
rc = sessionReadRecord(&aEnd, nCol, &apVal[0]);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionReadRecord(&aEnd, nCol, &apVal[nCol]);
|
||||
}
|
||||
|
||||
/* Write the header for the new UPDATE change. Same as the original. */
|
||||
aOut[i] = SQLITE_UPDATE;
|
||||
aOut[i+1] = aIn[i+1];
|
||||
memcpy(&aOut[i+2], &aIn[i+2+nByte1], nByte2);
|
||||
memcpy(&aOut[i+2+nByte2], &aIn[i+2], nByte1);
|
||||
nWrite = 2;
|
||||
|
||||
i += 2 + nByte1 + nByte2;
|
||||
/* Write the new old.* record. Consists of the PK columns from the
|
||||
** original old.* record, and the other values from the original
|
||||
** new.* record. */
|
||||
for(iCol=0; rc==SQLITE_OK && iCol<nCol; iCol++){
|
||||
sqlite3_value *pVal = apVal[iCol + (abPK[iCol] ? 0 : nCol)];
|
||||
rc = sessionSerializeValue(&aOut[i+nWrite], pVal, &nWrite);
|
||||
}
|
||||
|
||||
/* Write the new new.* record. Consists of a copy of all values
|
||||
** from the original old.* record, except for the PK columns, which
|
||||
** are set to "undefined". */
|
||||
for(iCol=0; rc==SQLITE_OK && iCol<nCol; iCol++){
|
||||
sqlite3_value *pVal = (abPK[iCol] ? 0 : apVal[iCol]);
|
||||
rc = sessionSerializeValue(&aOut[i+nWrite], pVal, &nWrite);
|
||||
}
|
||||
|
||||
for(iCol=0; iCol<nCol*2; iCol++){
|
||||
sqlite3ValueFree(apVal[iCol]);
|
||||
}
|
||||
memset(apVal, 0, sizeof(apVal[0])*nCol*2);
|
||||
if( rc!=SQLITE_OK ){
|
||||
goto finished_invert;
|
||||
}
|
||||
|
||||
i += nWrite;
|
||||
assert( &aIn[i]==aEnd );
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
sqlite3_free(aOut);
|
||||
return SQLITE_CORRUPT;
|
||||
rc = SQLITE_CORRUPT;
|
||||
goto finished_invert;
|
||||
}
|
||||
}
|
||||
|
||||
assert( rc==SQLITE_OK );
|
||||
*pnInverted = nChangeset;
|
||||
*ppInverted = (void *)aOut;
|
||||
return SQLITE_OK;
|
||||
|
||||
finished_invert:
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_free(aOut);
|
||||
}
|
||||
sqlite3_free(apVal);
|
||||
return rc;
|
||||
}
|
||||
|
||||
typedef struct SessionApplyCtx SessionApplyCtx;
|
||||
struct SessionApplyCtx {
|
||||
sqlite3 *db;
|
||||
sqlite3_stmt *pDelete; /* DELETE statement */
|
||||
sqlite3_stmt *pUpdate; /* DELETE statement */
|
||||
sqlite3_stmt *pUpdate; /* UPDATE statement */
|
||||
sqlite3_stmt *pInsert; /* INSERT statement */
|
||||
sqlite3_stmt *pSelect; /* SELECT statement */
|
||||
int nCol; /* Size of azCol[] and abPK[] arrays */
|
||||
@ -2995,9 +3043,9 @@ static int sessionChangeMerge(
|
||||
pNew = 0;
|
||||
}
|
||||
}else if( op2==SQLITE_UPDATE ){ /* UPDATE + UPDATE */
|
||||
assert( op1==SQLITE_UPDATE );
|
||||
u8 *a1 = pExist->aRecord;
|
||||
u8 *a2 = aRec;
|
||||
assert( op1==SQLITE_UPDATE );
|
||||
sessionReadRecord(&a1, pTab->nCol, 0);
|
||||
sessionReadRecord(&a2, pTab->nCol, 0);
|
||||
pNew->op = SQLITE_UPDATE;
|
||||
|
@ -545,7 +545,7 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
|
||||
** changeset. If it is not, the results are undefined.
|
||||
*/
|
||||
int sqlite3changeset_invert(
|
||||
int nIn, void *pIn, /* Input changeset */
|
||||
int nIn, const void *pIn, /* Input changeset */
|
||||
int *pnOut, void **ppOut /* OUT: Inverse of input */
|
||||
);
|
||||
|
||||
|
Reference in New Issue
Block a user