From: drh <> Date: Mon, 26 Dec 2022 17:02:15 +0000 (+0000) Subject: Prevent the sessions rebaser from generating changesets containing UPDATE records... X-Git-Tag: version-3.40.1~7 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9940e04c67b9253d5d239e24fc154c24b4b2944b;p=thirdparty%2Fsqlite.git Prevent the sessions rebaser from generating changesets containing UPDATE records for which non-PK fields are present in the old.* but not the new.* record. Also update the changeset iterator to work around such changesets. FossilOrigin-Name: 629dbe254346dc0b78025bb73def853bd725201244baa35cf169cf425930e184 --- diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index a892804b49..fd06f3b4d6 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -3327,6 +3327,22 @@ static int sessionChangesetNextOne( if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE; else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT; } + + /* If this is an UPDATE that is part of a changeset, then check that + ** there are no fields in the old.* record that are not (a) PK fields, + ** or (b) also present in the new.* record. + ** + ** Such records are technically corrupt, but the rebaser was at one + ** point generating them. Under most circumstances this is benign, but + ** can cause spurious SQLITE_RANGE errors when applying the changeset. */ + if( p->bPatchset==0 && p->op==SQLITE_UPDATE){ + for(i=0; inCol; i++){ + if( p->abPK[i]==0 && p->apValue[i+p->nCol]==0 ){ + sqlite3ValueFree(p->apValue[i]); + p->apValue[i] = 0; + } + } + } } return SQLITE_ROW; @@ -5523,7 +5539,7 @@ static void sessionAppendPartialUpdate( if( !pIter->abPK[i] && a1[0] ) bData = 1; memcpy(pOut, a1, n1); pOut += n1; - }else if( a2[0]!=0xFF ){ + }else if( a2[0]!=0xFF && a1[0] ){ bData = 1; memcpy(pOut, a2, n2); pOut += n2; diff --git a/ext/session/test_session.c b/ext/session/test_session.c index f2fc77eee3..c1feb7802c 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -98,6 +98,19 @@ int sql_exec_changeset( } /************************************************************************/ + +#ifdef SQLITE_DEBUG +static int sqlite3_test_changeset(int, void *, char **); +static void assert_changeset_is_ok(int n, void *p){ + int rc = 0; + char *z = 0; + rc = sqlite3_test_changeset(n, p, &z); + assert( z==0 ); +} +#else +# define assert_changeset_is_ok(n,p) +#endif + /* ** Tclcmd: sql_exec_changeset DB SQL */ @@ -127,6 +140,7 @@ static int SQLITE_TCLAPI test_sql_exec_changeset( return TCL_ERROR; } + assert_changeset_is_ok(nChangeset, pChangeset); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset)); sqlite3_free(pChangeset); return TCL_OK; @@ -295,6 +309,7 @@ static int SQLITE_TCLAPI test_session_cmd( } } if( rc==SQLITE_OK ){ + assert_changeset_is_ok(o.n, o.p); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); } sqlite3_free(o.p); @@ -953,6 +968,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_invert( if( rc!=SQLITE_OK ){ rc = test_session_error(interp, rc, 0); }else{ + assert_changeset_is_ok(sOut.n, sOut.p); Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); } sqlite3_free(sOut.p); @@ -1001,6 +1017,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat( if( rc!=SQLITE_OK ){ rc = test_session_error(interp, rc, 0); }else{ + assert_changeset_is_ok(sOut.n, sOut.p); Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); } sqlite3_free(sOut.p); @@ -1236,6 +1253,7 @@ static int SQLITE_TCLAPI test_rebaser_cmd( } if( rc==SQLITE_OK ){ + assert_changeset_is_ok(sOut.n, sOut.p); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n)); } sqlite3_free(sOut.p); @@ -1282,6 +1300,99 @@ static int SQLITE_TCLAPI test_sqlite3rebaser_create( return TCL_OK; } +/* +** +*/ +static int sqlite3_test_changeset( + int nChangeset, + void *pChangeset, + char **pzErr +){ + sqlite3_changeset_iter *pIter = 0; + char *zErr = 0; + int rc = SQLITE_OK; + int bPatch = (nChangeset>0 && ((char*)pChangeset)[0]=='P'); + + rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); + if( rc==SQLITE_OK ){ + int rc2; + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ + unsigned char *aPk = 0; + int nCol = 0; + int op = 0; + const char *zTab = 0; + + sqlite3changeset_pk(pIter, &aPk, &nCol); + sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); + + if( op==SQLITE_UPDATE ){ + int iCol; + for(iCol=0; iCol