diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 2ff0bc56fe..ae3139badc 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -1279,6 +1279,19 @@ static void sessionUpdateDeleteWhere( } } + +typedef struct SessionApplyCtx SessionApplyCtx; +struct SessionApplyCtx { + sqlite3 *db; + sqlite3_stmt *pDelete; /* DELETE statement */ + sqlite3_stmt *pUpdate; /* DELETE statement */ + sqlite3_stmt *pInsert; /* INSERT statement */ + sqlite3_stmt *pSelect; /* SELECT statement */ + int nCol; /* Size of azCol[] and abPK[] arrays */ + const char **azCol; /* Array of column names */ + u8 *abPK; /* Boolean array - true if column is in PK */ +}; + /* ** Formulate a statement to DELETE a row from database db. Assuming a table ** structure like this: @@ -1296,25 +1309,19 @@ static void sessionUpdateDeleteWhere( static int sessionDeleteRow( sqlite3 *db, /* Database handle */ const char *zTab, /* Table name */ - int nCol, /* Number of entries in azCol and abPK */ - const char **azCol, /* Column names */ - u8 *abPK, /* True for PK columns */ - sqlite3_stmt **ppStmt /* OUT: Compiled SELECT statement. */ + SessionApplyCtx *p /* Session changeset-apply context */ ){ int rc = SQLITE_OK; - if( *ppStmt==0 ){ - SessionBuffer buf = {0, 0, 0}; + SessionBuffer buf = {0, 0, 0}; - sessionAppendStr(&buf, "DELETE FROM ", &rc); - sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, "DELETE FROM ", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionUpdateDeleteWhere(&buf, p->nCol, p->azCol, p->abPK, &rc); - sessionUpdateDeleteWhere(&buf, nCol, azCol, abPK, &rc); - - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0); - } - sqlite3_free(buf.aBuf); + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0); } + sqlite3_free(buf.aBuf); return rc; } @@ -1351,149 +1358,344 @@ static int sessionDeleteRow( static int sessionUpdateRow( sqlite3 *db, /* Database handle */ const char *zTab, /* Table name */ - int nCol, /* Number of entries in azCol and abPK */ - const char **azCol, /* Column names */ - u8 *abPK, /* True for PK columns */ - sqlite3_stmt **ppStmt /* OUT: Compiled SELECT statement. */ + SessionApplyCtx *p /* Session changeset-apply context */ ){ int rc = SQLITE_OK; - if( *ppStmt==0 ){ - int i; - const char *zSep = ""; - SessionBuffer buf = {0, 0, 0}; + int i; + const char *zSep = ""; + SessionBuffer buf = {0, 0, 0}; - /* Append "UPDATE tbl SET " */ - sessionAppendStr(&buf, "UPDATE ", &rc); - sessionAppendIdent(&buf, zTab, &rc); - sessionAppendStr(&buf, " SET ", &rc); + /* Append "UPDATE tbl SET " */ + sessionAppendStr(&buf, "UPDATE ", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " SET ", &rc); - /* Append the assignments */ - for(i=0; inCol; i++){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " = CASE WHEN ?", &rc); + sessionAppendInteger(&buf, i*3+2, &rc); + sessionAppendStr(&buf, " THEN ?", &rc); + sessionAppendInteger(&buf, i*3+3, &rc); + sessionAppendStr(&buf, " ELSE ", &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " END", &rc); + zSep = ", "; } + /* Append the PK part of the WHERE clause */ + sessionAppendStr(&buf, " WHERE ", &rc); + for(i=0; inCol; i++){ + if( p->abPK[i] ){ + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, i*3+1, &rc); + sessionAppendStr(&buf, " AND ", &rc); + } + } + + /* Append the non-PK part of the WHERE clause */ + sessionAppendStr(&buf, " (?", &rc); + sessionAppendInteger(&buf, p->nCol*3+1, &rc); + sessionAppendStr(&buf, " OR 1", &rc); + for(i=0; inCol; i++){ + if( !p->abPK[i] ){ + sessionAppendStr(&buf, " AND (?", &rc); + sessionAppendInteger(&buf, i*3+2, &rc); + sessionAppendStr(&buf, "=0 OR ", &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, i*3+1, &rc); + sessionAppendStr(&buf, ")", &rc); + } + } + sessionAppendStr(&buf, ")", &rc); + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0); + } + sqlite3_free(buf.aBuf); + return rc; } static int sessionSelectRow( sqlite3 *db, /* Database handle */ const char *zTab, /* Table name */ - int nCol, /* Number of entries in azCol and abPK */ - const char **azCol, /* Column names */ - u8 *abPK, /* True for PK columns */ - sqlite3_stmt **ppStmt /* OUT: Compiled SELECT statement. */ + SessionApplyCtx *p /* Session changeset-apply context */ ){ int rc = SQLITE_OK; - if( *ppStmt==0 ){ - int i; - const char *zSep = ""; - SessionBuffer buf = {0, 0, 0}; - - sessionAppendStr(&buf, "SELECT * FROM ", &rc); - sessionAppendIdent(&buf, zTab, &rc); - sessionAppendStr(&buf, " WHERE ", &rc); - - for(i=0; inCol; i++){ + if( p->abPK[i] ){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, i+1, &rc); + zSep = " AND "; } - - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0); - } - sqlite3_free(buf.aBuf); } + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pSelect, 0); + } + sqlite3_free(buf.aBuf); return rc; } -static int sessionConstraintConflict( +static int sessionInsertRow( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + SessionApplyCtx *p /* Session changeset-apply context */ +){ + int rc = SQLITE_OK; + int i; + SessionBuffer buf = {0, 0, 0}; + + sessionAppendStr(&buf, "INSERT INTO main.", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " VALUES(?", &rc); + for(i=1; inCol; i++){ + sessionAppendStr(&buf, ", ?", &rc); + } + sessionAppendStr(&buf, ")", &rc); + + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); + } + sqlite3_free(buf.aBuf); + return rc; +} + +static int sessionSeekToRow( sqlite3 *db, /* Database handle */ sqlite3_changeset_iter *pIter, /* Changeset iterator */ u8 *abPK, /* Primary key flags array */ - sqlite3_stmt *pSelect, /* SELECT statement from sessionSelectRow() */ - int(*xConflict)(void *, int, sqlite3_changeset_iter*), - void *pCtx + sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */ ){ - int res; - int rc; + int rc = SQLITE_OK; + int i; int nCol; int op; const char *zDummy; sqlite3changeset_op(pIter, &zDummy, &nCol, &op); - assert( op==SQLITE_UPDATE || op==SQLITE_INSERT ); - /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ - for(i=0; idb, pIter, p->abPK, p->pSelect); + }else{ + rc = SQLITE_DONE; + } + + if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ - pIter->pConflict = pSelect; - res = xConflict(pCtx, SQLITE_CHANGESET_CONFLICT, pIter); + pIter->pConflict = p->pSelect; + res = xConflict(pCtx, eType, pIter); pIter->pConflict = 0; - sqlite3_reset(pSelect); + rc = sqlite3_reset(p->pSelect); }else{ /* No other row with the new.* primary key. */ - rc = sqlite3_reset(pSelect); + rc = sqlite3_reset(p->pSelect); if( rc==SQLITE_OK ){ - res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter); + res = xConflict(pCtx, eType+1, pIter); + if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; + } + } + + if( rc==SQLITE_OK ){ + switch( res ){ + case SQLITE_CHANGESET_REPLACE: + if( pbReplace ) *pbReplace = 1; + break; + + case SQLITE_CHANGESET_OMIT: + break; + + case SQLITE_CHANGESET_ABORT: + rc = SQLITE_ABORT; + break; + + default: + rc = SQLITE_MISUSE; + break; + } + } + + return rc; +} + +static int sessionApplyOneOp( + sqlite3_changeset_iter *pIter, + SessionApplyCtx *p, + int(*xConflict)(void *, int, sqlite3_changeset_iter *), + void *pCtx, + int *pbReplace, + int *pbRetry +){ + const char *zDummy; + int op; + int nCol; + int rc = SQLITE_OK; + + assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect ); + assert( p->azCol && p->abPK ); + assert( !pbReplace || *pbReplace==0 ); + + sqlite3changeset_op(pIter, &zDummy, &nCol, &op); + + if( op==SQLITE_DELETE ){ + int i; + + /* Bind values to the DELETE statement. */ + for(i=0; rc==SQLITE_OK && ipDelete, i+1, pVal); + } + } + if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0); + if( rc!=SQLITE_OK ) return rc; + + sqlite3_step(p->pDelete); + rc = sqlite3_reset(p->pDelete); + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry + ); + }else if( rc==SQLITE_CONSTRAINT ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0 + ); + } + + }else if( op==SQLITE_UPDATE ){ + int i; + + /* Bind values to the UPDATE statement. */ + for(i=0; rc==SQLITE_OK && ipUpdate, i*3+1, pOld); + sqlite3_bind_int(p->pUpdate, i*3+2, !!pNew); + if( pNew ) sqlite3_bind_value(p->pUpdate, i*3+3, pNew); + } + } + if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0); + if( rc!=SQLITE_OK ) return rc; + + /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict, + ** the result will be SQLITE_OK with 0 rows modified. */ + sqlite3_step(p->pUpdate); + rc = sqlite3_reset(p->pUpdate); + + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ + /* A NOTFOUND or DATA error. Search the table to see if it contains + ** a row with a matching primary key. If so, this is a DATA conflict. + ** Otherwise, if there is no primary key match, it is a NOTFOUND. */ + + rc = sessionConflictHandler( + SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry + ); + + }else if( rc==SQLITE_CONSTRAINT ){ + /* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if + ** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT + ** otherwise. */ + int bPKChange = 0; + + /* Check if the PK has been modified. */ + rc = SQLITE_OK; + for(i=0; iabPK[i] ){ + sqlite3_value *pNew; + rc = sqlite3changeset_new(pIter, i, &pNew); + if( rc==SQLITE_OK && pNew ){ + bPKChange = 1; + break; + } + } + } + + rc = sessionConflictHandler(SQLITE_CHANGESET_CONFLICT, + p, pIter, xConflict, pCtx, (bPKChange ? pbReplace : 0) + ); + } + + }else{ + int i; + assert( op==SQLITE_INSERT ); + for(i=0; rc==SQLITE_OK && ipInsert, i+1, pVal); + } + } + if( rc!=SQLITE_OK ) return rc; + + sqlite3_step(p->pInsert); + rc = sqlite3_reset(p->pInsert); + if( rc==SQLITE_CONSTRAINT && xConflict ){ + rc = sessionConflictHandler( + SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace + ); } } @@ -1511,209 +1713,90 @@ int sqlite3changeset_apply( ), void *pCtx ){ - sqlite3_changeset_iter *pIter; + sqlite3_changeset_iter *pIter = 0; int rc; int rc2; const char *zTab = 0; /* Name of current table */ int nTab = 0; /* Result of strlen(zTab) */ - int nCol = 0; /* Number of columns in table zTab */ - const char **azCol = 0; /* Array of column names */ - u8 *abPK = 0; /* Boolean array - true if column is in PK */ - sqlite3_stmt *pDelete = 0; /* DELETE statement */ - sqlite3_stmt *pUpdate = 0; /* DELETE statement */ - sqlite3_stmt *pInsert = 0; /* INSERT statement */ - sqlite3_stmt *pSelect = 0; /* SELECT statement */ - - rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); - if( rc!=SQLITE_OK ) return rc; + SessionApplyCtx sApply; + memset(&sApply, 0, sizeof(sApply)); sqlite3changeset_start(&pIter, nChangeset, pChangeset); - while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ + + rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ + int nCol; int op; - const char *zThis; - sqlite3changeset_op(pIter, &zThis, &nCol, &op); - if( zTab==0 || sqlite3_strnicmp(zThis, zTab, nTab+1) ){ - sqlite3_free(azCol); - rc = sessionTableInfo(db, zThis, nCol, &zTab, &azCol, &abPK); - nTab = strlen(zTab); + int bReplace = 0; + int bRetry = 0; + const char *zNew; + sqlite3changeset_op(pIter, &zNew, &nCol, &op); - sqlite3_finalize(pDelete); - sqlite3_finalize(pUpdate); - sqlite3_finalize(pInsert); - sqlite3_finalize(pSelect); - pSelect = pUpdate = pInsert = pDelete = 0; + if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ + sqlite3_free(sApply.azCol); + sqlite3_finalize(sApply.pDelete); + sqlite3_finalize(sApply.pUpdate); + sqlite3_finalize(sApply.pInsert); + sqlite3_finalize(sApply.pSelect); + memset(&sApply, 0, sizeof(sApply)); + sApply.db = db; + sApply.nCol = nCol; - if( (rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect)) - || (rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate)) - || (rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete)) + rc = sessionTableInfo(db, zNew, nCol, &zTab, &sApply.azCol, &sApply.abPK); + + if( rc!=SQLITE_OK + || (rc = sessionSelectRow(db, zTab, &sApply)) + || (rc = sessionUpdateRow(db, zTab, &sApply)) + || (rc = sessionDeleteRow(db, zTab, &sApply)) + || (rc = sessionInsertRow(db, zTab, &sApply)) ){ break; } + + nTab = strlen(zTab); } - if( op==SQLITE_DELETE ){ - int res; - int i; - rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete); - for(i=0; rc==SQLITE_OK && ipConflict = pSelect; - res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter); - pIter->pConflict = 0; - sqlite3_reset(pSelect); - }else{ - rc = sqlite3_reset(pSelect); - if( rc==SQLITE_OK ){ - res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter); - } - } - - }else if( rc==SQLITE_CONSTRAINT ){ - res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter); - rc = SQLITE_OK; - } - - if( rc!=SQLITE_OK ) break; - - }else if( op==SQLITE_UPDATE ){ - int i; - int res; - rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate); - for(i=0; rc==SQLITE_OK && ipConflict = pSelect; - res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter); - pIter->pConflict = 0; - sqlite3_reset(pSelect); - }else{ - rc = sqlite3_reset(pSelect); - if( rc==SQLITE_OK ){ - res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter); - } - } - }else if( rc==SQLITE_CONSTRAINT ){ - /* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if - ** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT - ** otherwise. */ - int bPKChange = 0; - - /* Check if the PK has been modified. */ - rc = SQLITE_OK; - for(i=0; iinterp; + int ret = 0; /* Return value */ int op; /* SQLITE_UPDATE, DELETE or INSERT */ const char *zTab; /* Name of table conflict is on */ @@ -257,9 +269,27 @@ static int test_conflict_handler( if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){ Tcl_BackgroundError(interp); + }else{ + Tcl_Obj *pRes = Tcl_GetObjResult(interp); + if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){ + ret = SQLITE_CHANGESET_OMIT; + }else if( test_obj_eq_string(pRes, "REPLACE") ){ + ret = SQLITE_CHANGESET_REPLACE; + }else if( test_obj_eq_string(pRes, "ABORT") ){ + ret = SQLITE_CHANGESET_ABORT; + }else{ + Tcl_IncrRefCount(pRes); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "unrecognized conflict handler return: \"", + Tcl_GetString(pRes), "\"", 0 + ); + Tcl_DecrRefCount(pRes); + Tcl_BackgroundError(interp); + } } + Tcl_DecrRefCount(pEval); - return SQLITE_CHANGESET_OMIT; + return ret; } /* diff --git a/manifest b/manifest index 38e0a86e95..89b9043680 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\ssome\sissues\swith\sUPDATE\schanges\sin\sthe\ssession\smodule. -D 2011-03-12T17:22:46 +C Fix\shandling\sof\sreturn\svalues\sfrom\sthe\sconflict\shandler.\sDocument\sthe\sconflict\shandler\sarguments\sand\sreturn\scodes\sin\ssqlite3session.h. +D 2011-03-14T19:49:23 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -98,9 +98,9 @@ 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/sqlite3session.c af63d87b8787c19b4a4b681f77331a9cc13d67af -F ext/session/sqlite3session.h 3246613b20857e58f7419e4e26dbe9161677aff0 -F ext/session/test_session.c 1b4f278d0ae164e2d02c11f5e1f2df3a2567ba41 +F ext/session/sqlite3session.c 111a988b4734b7419f23bb07e45bf5e991510270 +F ext/session/sqlite3session.h 55ca208bddbc1284c83427f423748eb720f5c68f +F ext/session/test_session.c 2559ef68e421c7fb83e2c19ef08a17343b70d535 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F main.mk ae0868e05c76eaa8a0ae3d6927a949b1c8e810d7 @@ -639,7 +639,7 @@ F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532 F test/selectB.test f305cc6660804cb239aab4e2f26b0e288b59958b F test/selectC.test f9bf1bc4581b5b8158caa6e4e4f682acb379fb25 F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c -F test/session1.test 6ec522f3491fbdbf8a5b6c27f50b9e35f24e3dba +F test/session1.test edbd6078b86b3f6337d779552671125fbd885e19 F test/shared.test b9114eaea7e748a3a4c8ff7b9ca806c8f95cef3e F test/shared2.test 7f6ad2d857d0f4e5d6a0b9a897b5e56a6b6ea18c F test/shared3.test d69bdd5f156580876c5345652d21dc2092e85962 @@ -913,7 +913,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 2b19be7bf753c7dd12e1c3b384981a3ea1bc8145 -R 222ffce3b378b2ba18950183bb251367 +P 57862efe718fdc93401998f9058511292a0e1a50 +R 17f2f6e54c1cfa1876ed6a58e92b5c4b U dan -Z b642b18410bb67d03067201d5612ecd6 +Z e9add8546b195a8b303123ffee5a5609 diff --git a/manifest.uuid b/manifest.uuid index b6d1059b89..f68965ee4d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -57862efe718fdc93401998f9058511292a0e1a50 \ No newline at end of file +cbbb274e500237dbf7155a51d4f9c17583d704ea \ No newline at end of file diff --git a/test/session1.test b/test/session1.test index 5706b77bc5..46b9d3442d 100644 --- a/test/session1.test +++ b/test/session1.test @@ -148,7 +148,9 @@ do_changeset_invert_test 2.4.3 S {} do_test 2.4.4 { S delete } {} #------------------------------------------------------------------------- -# Test the application of simple changesets. +# Test the application of simple changesets. These tests also test that +# the conflict callback is invoked correctly. For these tests, the +# conflict callback always returns OMIT. # db close forcedelete test.db test.db2 @@ -160,6 +162,8 @@ proc xConflict {args} { return "" } +proc bgerror {args} { set ::background_error $args } + proc do_conflict_test {tn args} { set O(-tables) [list] set O(-sql) [list] @@ -183,6 +187,9 @@ proc do_conflict_test {tn args} { lappend conflicts $c } + after 1 {set go 1} + vwait go + uplevel do_test $tn [list { set ::xConflict }] [list $conflicts] S delete } @@ -279,10 +286,126 @@ do_conflict_test 3.3.3 -tables t4 -sql { {UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}} {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}} } - do_db2_test 3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12} do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12} +#------------------------------------------------------------------------- +# This next block of tests verifies that values returned by the conflict +# handler are intepreted correctly. The following cases are tested: +# +# Test case Operation Conflict Return Code +# UPDATE DATA OMIT +# UPDATE DATA REPLACE +# + +proc test_reset {} { + db close + db2 close + forcedelete test.db test.db2 + sqlite3 db test.db + sqlite3 db2 test.db2 +} + +proc xConflict {args} { + lappend ::xConflict $args + return $::conflict_return +} + +foreach {tn conflict_return after} { + 1 OMIT {1 2 value1 4 5 7 7 8 9 10 x x} + 2 REPLACE {1 2 value1 4 5 value2 10 8 9} +} { + test_reset + + do_test 4.$tn.1 { + foreach db {db db2} { + execsql { + CREATE TABLE t1(a, b, c, PRIMARY KEY(a)); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); + INSERT INTO t1 VALUES(7, 8, 9); + } $db + } + execsql { + REPLACE INTO t1 VALUES(4, 5, 7); + REPLACE INTO t1 VALUES(10, 'x', 'x'); + } db2 + } {} + + do_conflict_test 4.$tn.2 -tables t1 -sql { + UPDATE t1 SET c = 'value1' WHERE a = 1; -- no conflict + UPDATE t1 SET c = 'value2' WHERE a = 4; -- DATA conflict + UPDATE t1 SET a = 10 WHERE a = 7; -- CONFLICT conflict + } -conflicts { + {UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}} + {UPDATE t1 CONFLICT {i 7 {} {} {} {}} {i 10 {} {} {} {}} {i 10 t x t x}} + } + + do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after +} + +foreach {tn conflict_return} { + 1 OMIT + 2 REPLACE +} { + test_reset + + do_test 5.$tn.1 { + # Create an identical schema in both databases. + set schema { + CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y)); + } + execsql $schema db + execsql $schema db2 + + # Add some rows to [db2]. These rows will cause conflicts later + # on when the changeset from [db] is applied to it. + execsql { + INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii'); + INSERT INTO "'foolish name'" VALUES('one', 'two', 'i'); + INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii'); + } db2 + + } {} + + do_conflict_test 5.$tn.2 -tables {{'foolish name'}} -sql { + INSERT INTO "'foolish name'" VALUES('one', 'two', 2); + } -conflicts { + {INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}} + } + + set res(REPLACE) {one one ii one two 2 two two ii} + set res(OMIT) {one one ii one two i two two ii} + do_db2_test 5.$tn.3 { + SELECT * FROM "'foolish name'" ORDER BY x, y + } $res($conflict_return) + + + do_test 5.$tn.1 { + set schema { + CREATE TABLE d1("z""z" PRIMARY KEY, y); + INSERT INTO d1 VALUES(1, 'one'); + INSERT INTO d1 VALUES(2, 'two'); + } + execsql $schema db + execsql $schema db2 + + execsql { + UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2; + } db2 + + } {} + + do_conflict_test 5.$tn.2 -tables d1 -sql { + DELETE FROM d1 WHERE "z""z" = 2; + } -conflicts { + {DELETE d1 DATA {i 2 t two} {i 2 t TWO}} + } + + set res(REPLACE) {1 one} + set res(OMIT) {1 one 2 TWO} + do_db2_test 5.$tn.3 "SELECT * FROM d1" $res($conflict_return) +} catch { db2 close } finish_test