Merge lp:~chipaca/u1db/autoresolver into lp:u1db

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 296
Merged at revision: 297
Proposed branch: lp:~chipaca/u1db/autoresolver
Merge into: lp:u1db
Diff against target: 206 lines (+61/-20)
4 files modified
src/u1db.c (+25/-17)
u1db/backends/__init__.py (+8/-1)
u1db/tests/test_backends.py (+13/-2)
u1db/tests/test_sync.py (+15/-0)
To merge this branch: bzr merge lp:~chipaca/u1db/autoresolver
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+106372@code.launchpad.net

Description of the change

Automerging of revisions with the same content.

To post a comment you must log in.
Revision history for this message
Samuele Pedroni (pedronis) wrote :

this needs direct put_doc_if_newer level unit tests

review: Needs Fixing
lp:~chipaca/u1db/autoresolver updated
294. By John Lenton

add a unit test to put_doc_if_newer

Revision history for this message
John Lenton (chipaca) wrote :

Like this?

lp:~chipaca/u1db/autoresolver updated
295. By John Lenton

a bit of C code cleanup

296. By John Lenton

checked the vector clock is strictly greater on content-based conflict resolution. Also, got rid of spurious prunes.

Revision history for this message
Samuele Pedroni (pedronis) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/u1db.c'
2--- src/u1db.c 2012-05-18 15:12:42 +0000
3+++ src/u1db.c 2012-05-18 16:40:23 +0000
4@@ -726,31 +726,26 @@
5 const char *replica_uid, int replica_gen,
6 const char *replica_trans_id, int *state, int *at_gen)
7 {
8- const char *stored_content = NULL, *stored_doc_rev = NULL;
9+ const char *stored_content = NULL;
10+ const char *stored_doc_rev = NULL;
11+ const char *local_replica_uid = NULL;
12 int status = U1DB_INVALID_PARAMETER, store = 0;
13 int stored_content_len;
14- sqlite3_stmt *statement;
15+ sqlite3_stmt *statement = NULL;
16+ u1db_vectorclock *stored_vc = NULL, *new_vc = NULL;
17
18 if (db == NULL || doc == NULL || state == NULL || doc->doc_rev == NULL) {
19 return U1DB_INVALID_PARAMETER;
20 }
21
22 status = u1db__is_doc_id_valid(doc->doc_id);
23- if (status != U1DB_OK) {
24- return status;
25- }
26+ if (status != SQLITE_OK) { goto finish; }
27 status = sqlite3_exec(db->sql_handle, "BEGIN", NULL, NULL, NULL);
28- if (status != SQLITE_OK) {
29- return status;
30- }
31+ if (status != SQLITE_OK) { goto finish; }
32 stored_content = NULL;
33 status = lookup_doc(db, doc->doc_id, &stored_doc_rev, &stored_content,
34 &stored_content_len, &statement);
35- if (status != SQLITE_OK) {
36- sqlite3_exec(db->sql_handle, "ROLLBACK", NULL, NULL, NULL);
37- sqlite3_finalize(statement);
38- return status;
39- }
40+ if (status != SQLITE_OK) { goto finish; }
41 if (stored_doc_rev == NULL) {
42 status = U1DB_OK;
43 *state = U1DB_INSERTED;
44@@ -760,7 +755,6 @@
45 *state = U1DB_CONVERGED;
46 store = 0;
47 } else {
48- u1db_vectorclock *stored_vc = NULL, *new_vc = NULL;
49 // TODO: u1db__vectorclock_from_str returns NULL if there is an error
50 // in the vector clock, or if we run out of memory... Probably
51 // shouldn't be U1DB_NOMEM
52@@ -772,7 +766,6 @@
53 new_vc = u1db__vectorclock_from_str(doc->doc_rev);
54 if (new_vc == NULL) {
55 status = U1DB_NOMEM;
56- u1db__free_vectorclock(&stored_vc);
57 goto finish;
58 }
59 if (u1db__vectorclock_is_newer(new_vc, stored_vc)) {
60@@ -785,6 +778,21 @@
61 store = 0;
62 status = U1DB_OK;
63 *state = U1DB_SUPERSEDED;
64+ } else if ((doc->content == NULL && stored_content == NULL)
65+ || (doc->content != NULL && stored_content != NULL
66+ && strcmp(doc->content, stored_content) == 0)) {
67+ // The contents have converged by divine intervention!
68+ status = u1db__vectorclock_maximize(new_vc, stored_vc);
69+ if (status != SQLITE_OK) { goto finish; }
70+ status = u1db_get_replica_uid(db, &local_replica_uid);
71+ if (status != SQLITE_OK) { goto finish; }
72+ status = u1db__vectorclock_increment(new_vc, local_replica_uid);
73+ if (status != SQLITE_OK) { goto finish; }
74+ free(doc->doc_rev);
75+ status = u1db__vectorclock_as_str(new_vc, &doc->doc_rev);
76+ if (status != SQLITE_OK) { goto finish; }
77+ store = 1;
78+ *state = U1DB_SUPERSEDED;
79 } else {
80 // TODO: Handle the case where the vc strings are not identical,
81 // but they are functionally equivalent.
82@@ -801,8 +809,6 @@
83 }
84 }
85 }
86- u1db__free_vectorclock(&stored_vc);
87- u1db__free_vectorclock(&new_vc);
88 }
89 if (status == U1DB_OK && store) {
90 status = write_doc(db, doc->doc_id, doc->doc_rev,
91@@ -823,6 +829,8 @@
92 } else {
93 sqlite3_exec(db->sql_handle, "ROLLBACK", NULL, NULL, NULL);
94 }
95+ u1db__free_vectorclock(&stored_vc);
96+ u1db__free_vectorclock(&new_vc);
97 return status;
98 }
99
100
101=== modified file 'u1db/backends/__init__.py'
102--- u1db/backends/__init__.py 2012-05-18 10:52:37 +0000
103+++ u1db/backends/__init__.py 2012-05-18 16:40:23 +0000
104@@ -14,7 +14,7 @@
105 # You should have received a copy of the GNU Lesser General Public License
106 # along with u1db. If not, see <http://www.gnu.org/licenses/>.
107
108-""""""
109+"""Abstract classes and common implementations for the backends."""
110
111 import re
112 import uuid
113@@ -110,6 +110,13 @@
114 # so we should send it back, and we should not generate a
115 # conflict
116 state = 'superseded'
117+ elif cur_doc.content == doc.content:
118+ # the documents have been edited to the same thing at both ends
119+ doc_vcr.maximize(cur_vcr)
120+ doc_vcr.increment(self._replica_uid)
121+ doc.rev = doc_vcr.as_str()
122+ self._put_and_update_indexes(cur_doc, doc)
123+ state = 'superseded'
124 else:
125 state = 'conflicted'
126 if save_conflict:
127
128=== modified file 'u1db/tests/test_backends.py'
129--- u1db/tests/test_backends.py 2012-05-18 15:12:42 +0000
130+++ u1db/tests/test_backends.py 2012-05-18 16:40:23 +0000
131@@ -279,6 +279,17 @@
132 self.assertEqual('superseded', state)
133 self.assertGetDoc(self.db, doc1.doc_id, doc1_rev2, simple_doc, False)
134
135+ def test_put_doc_if_newer_automerge(self):
136+ doc1 = self.db.create_doc(simple_doc)
137+ rev = doc1.rev
138+ doc = self.make_document(doc1.doc_id, "whatever:1", doc1.content)
139+ state, _ = self.db._put_doc_if_newer(doc, save_conflict=False)
140+ self.assertEqual('superseded', state)
141+ doc2 = self.db.get_doc(doc1.doc_id)
142+ v2 = vectorclock.VectorClockRev(doc2.rev)
143+ self.assertTrue(v2.is_newer(vectorclock.VectorClockRev("whatever:1")))
144+ self.assertTrue(v2.is_newer(vectorclock.VectorClockRev(rev)))
145+
146 def test_put_doc_if_newer_already_converged(self):
147 orig_doc = '{"new": "doc"}'
148 doc1 = self.db.create_doc(orig_doc)
149@@ -314,7 +325,7 @@
150 # A conflict that isn't saved still records the sync gen, because we
151 # don't need to see it again
152 doc2 = self.make_document(doc1.doc_id, doc1.rev + '|fourth:1',
153- nested_doc)
154+ '{}')
155 self.assertEqual('conflicted',
156 self.db._put_doc_if_newer(doc2, save_conflict=False,
157 replica_uid='other', replica_gen=4,
158@@ -591,7 +602,7 @@
159 replica_trans_id='T-id2')
160 # Conflict vs the current update
161 doc2 = self.make_document(doc1.doc_id, doc1.rev + '|third:3',
162- nested_doc)
163+ '{}')
164 self.assertEqual('conflicted',
165 self.db._put_doc_if_newer(doc2, save_conflict=True,
166 replica_uid='other', replica_gen=3,
167
168=== modified file 'u1db/tests/test_sync.py'
169--- u1db/tests/test_sync.py 2012-05-17 17:28:19 +0000
170+++ u1db/tests/test_sync.py 2012-05-18 16:40:23 +0000
171@@ -23,6 +23,7 @@
172 errors,
173 sync,
174 tests,
175+ vectorclock,
176 )
177 from u1db.backends import (
178 inmemory,
179@@ -397,6 +398,7 @@
180 class DatabaseSyncTests(tests.DatabaseBaseTests):
181
182 scenarios = sync_scenarios
183+ sync = None # set by scenarios
184
185 def setUp(self):
186 super(DatabaseSyncTests, self).setUp()
187@@ -417,6 +419,19 @@
188 {'receive': {'docs': [], 'last_known_gen': 0},
189 'return': {'docs': [], 'last_gen': 0}})
190
191+ def test_sync_autoresolves(self):
192+ doc1 = self.db1.create_doc(simple_doc, doc_id='doc')
193+ rev1 = doc1.rev
194+ doc2 = self.db2.create_doc(simple_doc, doc_id='doc')
195+ rev2 = doc2.rev
196+ self.sync(self.db1, self.db2)
197+ doc = self.db1.get_doc('doc')
198+ self.assertFalse(doc.has_conflicts)
199+ self.assertEqual(doc.rev, self.db2.get_doc('doc').rev)
200+ v = vectorclock.VectorClockRev(doc.rev)
201+ self.assertTrue(v.is_newer(vectorclock.VectorClockRev(rev1)))
202+ self.assertTrue(v.is_newer(vectorclock.VectorClockRev(rev2)))
203+
204 def test_sync_puts_changes(self):
205 doc = self.db1.create_doc(simple_doc)
206 self.assertEqual(1, self.sync(self.db1, self.db2))

Subscribers

People subscribed via source and target branches