Hello Patrick,
On Sep 8, 2011, at 9:59 , Lukas Zeller wrote:
PS: I'm in the code now :-)
Done, but not tested beyond not interfering with normal operation in my context.
Here's the patch, I'm currently shuffling my repos around so I haven't pushed
it yet.
Best Regards,
Lukas
---- the patch ----
From 25f7b2bf8187544cf357a13214f65b2902f14bc8 Mon Sep 17 00:00:00 2001
From: Lukas Zeller <luz(a)plan44.ch>
Date: Thu, 8 Sep 2011 16:41:44 +0200
Subject: [PATCH] engine: added more merge options for DB implementations to
ask engine for.
Up to now, only DB_DataMerged (207) had a special meaning when
returned by the DB backend for a server add.
Now the following options exist for server add operations:
- DB backend returns DB_DataMerged (207):
This means that the backend has augmented the item
with additional data, either from an external source,
or by merging with another item in the sync set.
The backend must return the localID for the augmented
item. In case the augmented item was created by
combining with a existing item already in the sync set,
the localID of that existing item must be returned
(and not a new ID!)
The client will be updated with the augmented
version.
Additionally, if the new version
was created by merge with an existing item in the
sync set, a delete is sent to the client to
remove the old (pre-merge) version of the item
from the client.
- DB backend returns DB_DataReplaced (208):
This means that the backend has stored the item,
but doing so, an existing item from the sync set
was replaced.
The backend must return the localID for the
replaced item which must also become the localID
of the newly stored version.
The client item will not be updated (as the item
was stored as received), but otherwise the processing
is the same as described for DB_DataMerged (207) above.
Additionally, for both server and client, add and replace,
the following new option exists:
- DB backend returns DB_Conflict (409)
This means that the backend did NOT store the item,
but has detected a need to merge the current version
stored in the backend with the new version.
The engine will read the current version,
merge it with the incoming version, and issue
an updateItem operation on the backend with
the merge result.
In server add case, after the merge the same
processing occurs as for DB_DataMerged (see above).
---
src/DB_interfaces/api_db/dbapi.cpp | 11 ++--
src/sysync/customimplds.cpp | 91 ++++++++++++++++++++++++++++++++---
src/sysync/customimplds.h | 2 +
src/sysync/localengineds.cpp | 13 +++--
src/sysync/localengineds.h | 5 +-
src/sysync_SDK/Sources/syerror.h | 11 ++++-
src/sysync_SDK/Sources/sync_dbapi.h | 22 +++++++--
7 files changed, 128 insertions(+), 27 deletions(-)
diff --git a/src/DB_interfaces/api_db/dbapi.cpp b/src/DB_interfaces/api_db/dbapi.cpp
index a4296ef..de6a04b 100755
--- a/src/DB_interfaces/api_db/dbapi.cpp
+++ b/src/DB_interfaces/api_db/dbapi.cpp
@@ -1889,7 +1889,7 @@ TSyError TDB_Api::InsertItem( cAppCharP aItemData, cAppCharP
parentID,
InsItemSFunc p= (InsItemSFunc)dm->ds.dsData.str.InsertItem;
TSyError err= p( fContext, aItemData, &a );
- if (!err || err==DB_DataMerged) {
+ if (!err || err==DB_DataMerged || err==DB_DataReplaced || err==DB_Conflict) {
Assign_ItemID( newID, a, parentID );
} // if
@@ -1902,8 +1902,10 @@ TSyError TDB_Api::InsertItem( cAppCharP aItemData, TDB_Api_Str
&newItemID )
{
TDB_Api_ItemID nID;
TSyError err= InsertItem( aItemData, "",nID );
- if (!err ||
- err==DB_DataMerged) GetItemID( nID, newItemID );
+ if (!err || err==DB_DataMerged || err==DB_DataReplaced || err==DB_Conflict) {
+ GetItemID( nID, newItemID );
+ } // if
+
return err;
} // InsertItem
@@ -1920,8 +1922,7 @@ TSyError TDB_Api::InsertItemAsKey( KeyH aItemKey, cAppCharP
parentID,
InsItemKFunc p= (InsItemKFunc)dm->ds.dsData.key.InsertItemAsKey;
TSyError err= p( fContext, aItemKey, &a );
- if (!err ||
- err==DB_DataMerged) {
+ if (!err || err==DB_DataMerged || err==DB_DataReplaced || err==DB_Conflict) {
Assign_ItemID( newID, a, parentID );
} // if
diff --git a/src/sysync/customimplds.cpp b/src/sysync/customimplds.cpp
index 497f554..f4092d4 100755
--- a/src/sysync/customimplds.cpp
+++ b/src/sysync/customimplds.cpp
@@ -2720,6 +2720,41 @@ localstatus TCustomImplDS::implProcessMap(cAppCharP aRemoteID,
cAppCharP aLocalI
+/// helper to merge database version of an item with the passed version of the same item
+TMultiFieldItem *TCustomImplDS::mergeWithDatabaseVersion(TSyncItem *aSyncItemP)
+{
+ TStatusCommand dummy(fSessionP);
+ TMultiFieldItem *dbVersionItemP = (TMultiFieldItem
*)newItemForRemote(aSyncItemP->getTypeID());
+ if (!dbVersionItemP) return NULL;
+ // - set IDs
+ dbVersionItemP->setLocalID(aSyncItemP->getLocalID());
+ dbVersionItemP->setRemoteID(aSyncItemP->getRemoteID());
+ // - result is always a replace (item exists in DB)
+ dbVersionItemP->setSyncOp(sop_wants_replace);
+ // - try to get from DB
+ bool ok=logicRetrieveItemByID(*dbVersionItemP,dummy);
+ if (ok && dummy.getStatusCode()!=404) {
+ // item found in DB, merge with original item
+ bool changedNewVersion, changedDBVersion;
+ aSyncItemP->mergeWith(*dbVersionItemP, changedNewVersion, changedDBVersion,
this);
+ PDEBUGPRINTFX(DBG_DATA,(
+ "Merged incoming item (winning,relevant,%smodified) with version from database
(loosing,to-be-replaced,%smodified)",
+ changedNewVersion ? "" : "NOT ",
+ changedDBVersion ? "" : "NOT "
+ ));
+ }
+ else {
+ // no item found, we cannot force a conflict
+ PDEBUGPRINTFX(DBG_ERROR,("Could not retrieve database version of item, DB status
code = %hd",dummy.getStatusCode()));
+ delete dbVersionItemP;
+ dbVersionItemP=NULL;
+ return NULL;
+ }
+ return dbVersionItemP;
+} // TCustomImplDS::mergeWithDatabaseVersion
+
+
+
/// process item (according to operation: add/delete/replace - and for future:
copy/move)
/// @note data items will be sent only after StartWrite()
bool TCustomImplDS::implProcessItem(
@@ -2740,6 +2775,7 @@ bool TCustomImplDS::implProcessItem(
// %%% bool RemoteIDKnown=false;
TMapContainer::iterator mappos;
TSyncOperation sop=sop_none;
+ TMultiFieldItem *augmentedItemP = NULL;
TP_DEFIDX(li);
TP_SWITCH(li,fSessionP->fTPInfo,TP_database);
@@ -2795,15 +2831,28 @@ bool TCustomImplDS::implProcessItem(
}
// add item and retrieve new localID for it
sta = apiAddItem(*myitemP,localID);
+ myitemP->setLocalID(localID.c_str()); // possibly following operations need to
be based on new localID returned by add
+ // check for backend asking engine to do a merge
+ if (sta==DB_Conflict) {
+ // DB has detected item conflicts with data already stored in the database and
+ // request merging current data from the backend with new data before storing.
+ augmentedItemP = mergeWithDatabaseVersion(myitemP);
+ if (augmentedItemP==NULL)
+ sta = DB_Error; // no item found, DB error
+ else {
+ sta = apiUpdateItem(*augmentedItemP); // store augmented version back to DB
+ // in server case, further process like backend merge (but no need to fetch
again, we just keep augmentedItemP)
+ if (IS_SERVER && sta==LOCERR_OK) sta = DB_DataMerged;
+ }
+ }
if (IS_SERVER) {
#ifdef SYSYNC_SERVER
- if (sta==DB_DataMerged) {
+ if (sta==DB_DataMerged || sta==DB_DataReplaced) {
// while adding, data was merged with pre-existing data from...
// ..either data external from the sync set, such as augmenting a contact
with info from a third-party lookup
// ..or another item pre-existing in the sync set.
PDEBUGPRINTFX(DBG_DATA,("Database adapter indicates that added item was
merged with pre-existing data (status 207)"));
- myitemP->setLocalID(localID.c_str()); // following searches need to be
based on new localID returned by add
- // check if the item resulting from merrge is known by the client already (in
it's pre-merge form, that is)
+ // check if the item resulting from merge is known by the client already (in
it's pre-merge form, that is)
TMapContainer::iterator conflictingMapPos = findMapByLocalID(localID.c_str(),
mapentry_normal);
bool remoteAlreadyKnowsItem = conflictingMapPos!=fMapTable.end();
// also check if we have a (pre-merge) operation pending for that item
already
@@ -2841,14 +2890,30 @@ bool TCustomImplDS::implProcessItem(
}
}
}
- // now create a replace command to update the item added from the client with
the merge result
- // - this is like forcing a conflict, i.e. this loads the item by
local/remoteid and adds it to
- // the to-be-sent list of the server.
- forceConflict(myitemP);
+ // if backend has not replaced, but merely merged data, we're done.
Otherwise, client needs to be updated with
+ // merged/augmented version of the data
+ if (sta!=DB_DataReplaced) {
+ // now create a replace command to update the item added from the client
with the merge result
+ // - this is like forcing a conflict, i.e. this loads the item by
local/remoteid and adds it to
+ // the to-be-sent list of the server.
+ if (augmentedItemP) {
+ // augmented version was created in engine, just add that version to the
list of items to be sent
+ SendItemAsServer(augmentedItemP); // takes ownership of augmentedItemP
+ augmentedItemP = NULL;
+ }
+ else {
+ // augmented version was created in backend, fetch it now and add to list
of items to be sent
+ augmentedItemP = (TMultiFieldItem
*)SendDBVersionOfItemAsServer(myitemP);
+ }
+ }
sta = LOCERR_OK; // otherwise, treat as ok
}
#endif
} // server
+ // - we don't need the augmented item any more if it still exists at this
point
+ if (augmentedItemP) {
+ delete augmentedItemP; augmentedItemP = NULL;
+ }
if (sta!=LOCERR_OK) {
aStatusCommand.setStatusCode(sta);
ok=false;
@@ -2878,6 +2943,18 @@ bool TCustomImplDS::implProcessItem(
myitemP->setLocalID(localID.c_str());
// update item
sta = apiUpdateItem(*myitemP);
+ if (sta==DB_Conflict) {
+ // DB has detected item conflicts with data already stored in the database
and
+ // request merging current data from the backend with new data before
storing.
+ augmentedItemP = mergeWithDatabaseVersion(myitemP);
+ if (augmentedItemP==NULL)
+ sta = DB_Error; // no item found, DB error
+ else {
+ sta = apiUpdateItem(*augmentedItemP); // store augmented version back to
DB
+ delete augmentedItemP; // forget now
+ }
+ }
+ // now check final status
if (sta!=LOCERR_OK) {
aStatusCommand.setStatusCode(sta);
ok=false;
diff --git a/src/sysync/customimplds.h b/src/sysync/customimplds.h
index d8884d0..9587eb9 100755
--- a/src/sysync/customimplds.h
+++ b/src/sysync/customimplds.h
@@ -772,6 +772,8 @@ protected:
// - Queue the data needed for finalisation (usually - relational link updates)
// as a item copy with only finalisation-required fields
void queueForFinalisation(TMultiFieldItem *aItemP);
+ /// helper to merge database version of an item with the passed version of the same
item
+ TMultiFieldItem *mergeWithDatabaseVersion(TSyncItem *aSyncItemP);
public:
// - get last to-remote sync time
lineartime_t getPreviousToRemoteSyncCmpRef(void) { return fPreviousToRemoteSyncCmpRef;
};
diff --git a/src/sysync/localengineds.cpp b/src/sysync/localengineds.cpp
index 4cb65bd..0e269bd 100644
--- a/src/sysync/localengineds.cpp
+++ b/src/sysync/localengineds.cpp
@@ -5144,8 +5144,9 @@ bool TLocalEngineDS::engProcessSyncOpItem(
// Server Case
// ===========
-// helper to force a conflict
-TSyncItem *TLocalEngineDS::forceConflict(TSyncItem *aSyncItemP)
+// helper to cause database version of an item (as identified by aSyncItemP's ID) to
be sent to client
+// (aka "force a conflict")
+TSyncItem *TLocalEngineDS::SendDBVersionOfItemAsServer(TSyncItem *aSyncItemP)
{
TStatusCommand dummy(fSessionP);
// - create new item
@@ -5171,7 +5172,7 @@ TSyncItem *TLocalEngineDS::forceConflict(TSyncItem *aSyncItemP)
conflictingItemP=NULL;
}
return conflictingItemP;
-} // TLocalEngineDS::forceConflict
+} // TLocalEngineDS::SendDBVersionOfItemAsServer
@@ -5302,7 +5303,7 @@ bool TLocalEngineDS::engProcessRemoteItemAsServer(
// Note: a forced conflict can still occur even if item is rejected
// (this has the effect of unconditionally letting the server item win)
if (fForceConflict && syncop!=sop_add) {
- conflictingItemP = forceConflict(aSyncItemP);
+ conflictingItemP = SendDBVersionOfItemAsServer(aSyncItemP);
// Note: conflictingitem is always a replace
if (conflictingItemP) {
if (syncop==sop_delete) {
@@ -5393,7 +5394,7 @@ bool TLocalEngineDS::engProcessRemoteItemAsServer(
if (!echoItemP) conflictingItemP = getConflictingItemByRemoteID(aSyncItemP); // do
not check conflicts if we have already created an echo
// - check if we must force the conflict
if (!conflictingItemP && fForceConflict) {
- conflictingItemP=forceConflict(aSyncItemP);
+ conflictingItemP=SendDBVersionOfItemAsServer(aSyncItemP);
}
if (conflictingItemP) {
// conflict only if other party has replace
@@ -5506,7 +5507,7 @@ bool TLocalEngineDS::engProcessRemoteItemAsServer(
if (!echoItemP) conflictingItemP = getConflictingItemByRemoteID(aSyncItemP);
// - check if we must force the conflict
if (!conflictingItemP && fForceConflict) {
- conflictingItemP=forceConflict(aSyncItemP);
+ conflictingItemP=SendDBVersionOfItemAsServer(aSyncItemP);
}
bool deleteconflict=false;
if (conflictingItemP) {
diff --git a/src/sysync/localengineds.h b/src/sysync/localengineds.h
index 0c918f3..7d34917 100755
--- a/src/sysync/localengineds.h
+++ b/src/sysync/localengineds.h
@@ -1118,8 +1118,9 @@ protected:
void adjustLocalIDforSize(string &aLocalID, sInt32 maxguidsize, sInt32
prefixsize);
/// for received GUIDs (Map command), obtain real GUID (might be temp GUID due to
maxguidsize restrictions)
void obtainRealLocalID(string &aLocalID);
- /// helper to force a conflict (i.e. have a particular item in the sync set)
- TSyncItem *forceConflict(TSyncItem *aSyncItemP);
+ /// helper to cause database version of an item (as identified by aSyncItemP's ID)
to be sent to client
+ /// (aka "force a conflict")
+ TSyncItem *SendDBVersionOfItemAsServer(TSyncItem *aSyncItemP);
#endif // SYSYNC_SERVER
/// helper to save resume state either at end of request or explicitly at reception of
a "suspend"
SUPERDS_VIRTUAL localstatus engSaveSuspendState(bool aAnyway);
diff --git a/src/sysync_SDK/Sources/syerror.h b/src/sysync_SDK/Sources/syerror.h
index fd7a351..84c68b6 100644
--- a/src/sysync_SDK/Sources/syerror.h
+++ b/src/sysync_SDK/Sources/syerror.h
@@ -38,21 +38,28 @@ enum TSyErrorEnum {
/** no content / end of file / end of iteration / empty/NULL value */
DB_NoContent = 204,
- /** external data has been merged */
+
+ /** while adding item, additional data (from external source or other syncset item) has
been merged */
DB_DataMerged = 207,
+ /** adding item has replaced an existing item */
+ DB_DataReplaced = 208,
+
/** not authorized */
DB_Unauthorized = 401,
/** forbidden / access denied */
DB_Forbidden = 403,
/** object not found / unassigned field */
DB_NotFound = 404,
- /** command not allowed (possibly: only at this time)*/
+ /** command not allowed (possibly: only at this time) */
DB_NotAllowed = 405,
/** proxy authentication required */
DB_ProxyAuth = 407,
+ /** conflict, item was not stored because it requires merge (on part of the engine)
first */
+ DB_Conflict = 409,
+
/** item already exists */
DB_AlreadyExists = 418,
diff --git a/src/sysync_SDK/Sources/sync_dbapi.h b/src/sysync_SDK/Sources/sync_dbapi.h
index 71964a6..192d330 100755
--- a/src/sysync_SDK/Sources/sync_dbapi.h
+++ b/src/sysync_SDK/Sources/sync_dbapi.h
@@ -876,11 +876,23 @@ _ENTRY_ TSyError StartDataWrite( CContext aContext );
* @param <aID> Database key of the new dataset.
*
* @return error code
- * - LOCERR_OK ( =0 ), if successful
- * - DB_DataMerged ( =207 ), if successful, but "ReadItem"
requested to
- * inform about updates
- * - DB_Forbidden ( =403 ), if \<aItemData> can't be resolved
- * - DB_Full ( =420 ), if not enough space in the DB
+ * - LOCERR_OK ( =0 ), if successful
+ * - DB_DataMerged ( =207 ), if successful, but item actually stored
+ * was updated with data from
+ * external source or another
+ * sync set item. Engine will
+ * request updated version
+ * using ReadItem.
+ * (server add case only)
+ * - DB_DataReplaced ( =208 ), if successful, but item added replaces
+ * another item that already
+ * existed in the sync set.
+ * (server add case only)
+ * - DB_Conflict ( =409 ), if database requests engine to merge
+ * existing data with the
+ * to-be stored data first.
+ * - DB_Forbidden ( =403 ), if \<aItemData> can't be resolved
+ * - DB_Full ( =420 ), if not enough space in the DB
* - ... or any other SyncML error code, see Reference Manual
*
* NOTE: The memory for \<aItemID> must be allocated locally.
--
1.7.5.4+GitX