Permalink
Browse files

support for auto-reconcilliation of couch conflicts, with associated …

…unit tests
  • Loading branch information...
1 parent 905b30e commit aaf4fef03bda097f941c6655212a6d689289015c @kolosy kolosy committed Nov 16, 2009
Showing with 60 additions and 13 deletions.
  1. +19 −7 Tests/Autoreconcile/CouchAutoreconcileTest.cs
  2. +8 −3 src/CouchDatabase.cs
  3. +16 −3 src/CouchDocument.cs
  4. +17 −0 src/IReconcilingDocument.cs
@@ -27,10 +27,10 @@ private class Car : CouchDocument
public Car()
{
- // This constructor is needed by Divan
+ ReconcileBy = ReconcileStrategy.AutoMergeFields;
}
- public Car(string make, string model, int hps)
+ public Car(string make, string model, int hps): this()
{
Make = make;
Model = model;
@@ -107,7 +107,6 @@ public void ShouldCauseConflict()
public void ShouldHandleConflict()
{
var doc = new Car("Hoopty", "Type R", 5);
- doc.ReconcileBy = ReconcileStrategy.AutoMergeFields;
doc = db.SaveDocument(doc) as Car;
var rev = doc.Rev;
@@ -116,7 +115,7 @@ public void ShouldHandleConflict()
doc.Rev = rev;
db.SaveDocument(doc);
- Assert.That(doc.Rev.StartsWith("3"), "Incorrect revision");
+ Assert.That(doc.Rev.StartsWith("4"), "Incorrect revision");
}
[Test]
@@ -125,7 +124,9 @@ public void ShouldResolveConflict()
var doc = new Car("Hoopty", "Type R", 5);
doc = db.SaveDocument(doc) as Car;
- var doc2 = new Car("Slightly Better", "Type R", 6);
+ var doc2 = db.GetDocument<Car>(doc.Id);
+ doc2.Make = "Slightly Better";
+ doc2.HorsePowers = 6;
doc2.Id = doc.Id;
doc2.Rev = doc.Rev;
@@ -134,14 +135,25 @@ public void ShouldResolveConflict()
doc2 = db.SaveDocument(doc2) as Car;
- Assert.That(doc2.Rev.StartsWith("3"), "Incorrect revision");
+ Assert.That(doc2.Rev.StartsWith("4"), "Incorrect revision");
Assert.AreEqual("Slightly Better", doc2.Make);
Assert.AreEqual("Type S", doc2.Model);
Assert.AreEqual(6, doc2.HorsePowers);
}
+ [Test, ExpectedException(ExceptionType = typeof(CouchConflictException))]
+ public void ShouldCauseConflictOnNewDoc()
+ {
+ var doc = new Car("Hoopty", "Type R", 5);
+ doc = db.SaveDocument(doc) as Car;
+
+ var doc2 = new Car("Some other", "Car", 1000000);
+ doc2.Id = doc.Id;
+ db.SaveDocument(doc2);
+ }
+
private CouchServer server;
private CouchDatabase db;
- private const string DbName = "divan_linq_unit_tests";
+ private const string DbName = "divan_reconcile_unit_tests";
}
}
View
@@ -222,16 +222,19 @@ public ICouchDocument SaveDocument(ICouchDocument document)
try
{
if (document.Id == null)
- {
- savedDoc = CreateDocument(document);
- }
+ CreateDocument(document);
+
savedDoc = WriteDocument(document);
}
catch (CouchConflictException ex)
{
if (reconcilingDoc == null)
throw;
+ // can't handle a brand-new document
+ if (String.IsNullOrEmpty(reconcilingDoc.Rev))
+ throw;
+
switch (reconcilingDoc.ReconcileBy)
{
case ReconcileStrategy.None:
@@ -241,6 +244,8 @@ public ICouchDocument SaveDocument(ICouchDocument document)
SaveDocument(reconcilingDoc);
break;
}
+
+ savedDoc = reconcilingDoc;
}
reconcilingDoc.SaveCommited();
View
@@ -106,6 +106,15 @@ public virtual void ReadJson(JObject obj)
#endregion
+ private bool EqualFields(object v1, object v2)
+ {
+ if (v1 == null)
+ return v2 == null;
+ if (v2 == null)
+ return false;
+ return v1.Equals(v2);
+ }
+
/// <summary>
/// Automatically reconcile the database copy with the target instance. This method
/// uses reflection to perform the reconcilliation, and as such won't perform as well
@@ -118,14 +127,17 @@ protected void AutoReconcile(ICouchDocument databaseCopy)
var fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (var field in fields)
// if we haven't changed the field,
- if (field.GetValue(sourceData) == field.GetValue(this))
+ if (EqualFields(field.GetValue(sourceData), field.GetValue(this)))
field.SetValue(this, field.GetValue(databaseCopy));
foreach (var prop in properties)
if (!prop.CanWrite || prop.GetIndexParameters().Length > 0)
continue;
- else if (prop.GetValue(sourceData, null) == prop.GetValue(this, null))
+ else if (EqualFields(prop.GetValue(sourceData, null), prop.GetValue(this, null)))
prop.SetValue(this, prop.GetValue(databaseCopy, null), null);
+
+ // this is non-negotiable
+ Rev = databaseCopy.Rev;
}
protected CouchDocument AutoClone()
@@ -166,7 +178,8 @@ public void SaveCommited()
break;
}
- sourceData.ReconcileBy = ReconcileStrategy.None;
+ if (sourceData != null)
+ sourceData.ReconcileBy = ReconcileStrategy.None;
}
/// <summary>
@@ -0,0 +1,17 @@
+namespace Divan
+{
+ public interface IReconcilingDocument: ICouchDocument
+ {
+ ReconcileStrategy ReconcileBy { get; set; }
+ void SaveCommited();
+
+ /// <summary>
+ /// Called by the runtime when a conflict is detected during save. The supplied parameter
+ /// is the database copy of the document being saved.
+ /// </summary>
+ /// <param name="databaseCopy"></param>
+ void Reconcile(ICouchDocument databaseCopy);
+
+ IReconcilingDocument GetDatabaseCopy(CouchDatabase db);
+ }
+}

0 comments on commit aaf4fef

Please sign in to comment.