Merge lp:~jameinel/u1db/resolve_doc into lp:u1db

Proposed by John A Meinel
Status: Merged
Approved by: Samuele Pedroni
Approved revision: 126
Merged at revision: 127
Proposed branch: lp:~jameinel/u1db/resolve_doc
Merge into: lp:u1db
Diff against target: 310 lines (+112/-100)
6 files modified
u1db/__init__.py (+7/-2)
u1db/backends/inmemory.py (+10/-8)
u1db/backends/sqlite_backend.py (+9/-8)
u1db/tests/__init__.py (+14/-0)
u1db/tests/test_backends.py (+72/-0)
u1db/tests/test_sync.py (+0/-82)
To merge this branch: bzr merge lp:~jameinel/u1db/resolve_doc
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+83402@code.launchpad.net

Description of the change

Change resolve_doc to take a Document and a list of revisions that it supersedes. Then mutate the doc directly rather than returning another doc.

This also moves the tests from test_sync into test_backends.

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

looks good, test_resolve_doc was moved over twice though!

review: Approve
lp:~jameinel/u1db/resolve_doc updated
126. By John A Meinel

Remove the duplicate test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'u1db/__init__.py'
2--- u1db/__init__.py 2011-11-23 13:46:56 +0000
3+++ u1db/__init__.py 2011-11-25 15:43:24 +0000
4@@ -187,8 +187,9 @@
5 """
6 raise NotImplementedError(self.get_doc_conflicts)
7
8- def resolve_doc(self, doc_id, doc, conflicted_doc_revs):
9+ def resolve_doc(self, doc, conflicted_doc_revs):
10 """Mark a document as no longer conflicted.
11+
12 We take the list of revisions that the client knows about that it is
13 superseding. This may be a different list from the actual current
14 conflicts, in which case only those are removed as conflicted. This
15@@ -196,7 +197,11 @@
16 supplied information. (sync could have happened in the background from
17 the time you GET_DOC_CONFLICTS until the point where you RESOLVE)
18
19- :return: (new_rev, still_conflicted)
20+ :param doc: A Document with the new content to be inserted.
21+ :param conflicted_doc_revs: A list of revisions that the new content
22+ supersedes.
23+ :return: None, doc will be updated with the new revision and
24+ has_conflict flags.
25 """
26 raise NotImplementedError(self.resolve_doc)
27
28
29=== modified file 'u1db/backends/inmemory.py'
30--- u1db/backends/inmemory.py 2011-11-23 12:39:32 +0000
31+++ u1db/backends/inmemory.py 2011-11-25 15:43:24 +0000
32@@ -110,25 +110,27 @@
33 result.extend(self._conflicts[doc_id])
34 return result
35
36- def resolve_doc(self, doc_id, doc, conflicted_doc_revs):
37- cur_rev, cur_doc = self._docs[doc_id]
38+ def resolve_doc(self, doc, conflicted_doc_revs):
39+ cur_rev, cur_content = self._docs[doc.doc_id]
40 new_rev = self._ensure_maximal_rev(cur_rev, conflicted_doc_revs)
41 superseded_revs = set(conflicted_doc_revs)
42 remaining_conflicts = []
43- cur_conflicts = self._conflicts[doc_id]
44+ cur_conflicts = self._conflicts[doc.doc_id]
45 for c_rev, c_doc in cur_conflicts:
46 if c_rev in superseded_revs:
47 continue
48 remaining_conflicts.append((c_rev, c_doc))
49 if cur_rev in superseded_revs:
50- self._put_and_update_indexes(doc_id, cur_doc, new_rev, doc)
51+ self._put_and_update_indexes(doc.doc_id, cur_content, new_rev,
52+ doc.content)
53 else:
54- remaining_conflicts.append((new_rev, doc))
55+ remaining_conflicts.append((new_rev, doc.content))
56 if not remaining_conflicts:
57- del self._conflicts[doc_id]
58+ del self._conflicts[doc.doc_id]
59 else:
60- self._conflicts[doc_id] = remaining_conflicts
61- return new_rev, bool(remaining_conflicts)
62+ self._conflicts[doc.doc_id] = remaining_conflicts
63+ doc.rev = new_rev
64+ doc.has_conflicts = bool(remaining_conflicts)
65
66 def delete_doc(self, doc_id, doc_rev):
67 if doc_id not in self._docs:
68
69=== modified file 'u1db/backends/sqlite_backend.py'
70--- u1db/backends/sqlite_backend.py 2011-11-23 13:46:56 +0000
71+++ u1db/backends/sqlite_backend.py 2011-11-25 15:43:24 +0000
72@@ -381,22 +381,23 @@
73 self._put_and_update_indexes(doc.doc_id, my_doc.content, doc.rev,
74 doc.content)
75
76- def resolve_doc(self, doc_id, doc, conflicted_doc_revs):
77+ def resolve_doc(self, doc, conflicted_doc_revs):
78 with self._db_handle:
79- cur_doc = self._get_doc(doc_id)
80+ cur_doc = self._get_doc(doc.doc_id)
81 new_rev = self._ensure_maximal_rev(cur_doc.rev, conflicted_doc_revs)
82 superseded_revs = set(conflicted_doc_revs)
83- cur_conflicts = self._get_conflicts(doc_id)
84+ cur_conflicts = self._get_conflicts(doc.doc_id)
85 c = self._db_handle.cursor()
86 if cur_doc.rev in superseded_revs:
87- self._put_and_update_indexes(doc_id, cur_doc.content,
88- new_rev, doc)
89+ self._put_and_update_indexes(doc.doc_id, cur_doc.content,
90+ new_rev, doc.content)
91 else:
92- self._add_conflict(c, doc_id, new_rev, doc)
93- deleting = [(doc_id, c_rev) for c_rev in superseded_revs]
94+ self._add_conflict(c, doc.doc_id, new_rev, doc.content)
95+ deleting = [(doc.doc_id, c_rev) for c_rev in superseded_revs]
96 c.executemany("DELETE FROM conflicts"
97 " WHERE doc_id=? AND doc_rev=?", deleting)
98- return new_rev, self._has_conflicts(doc_id)
99+ doc.rev = new_rev
100+ doc.has_conflicts = self._has_conflicts(doc.doc_id)
101
102 def create_index(self, index_name, index_expression):
103 with self._db_handle:
104
105=== modified file 'u1db/tests/__init__.py'
106--- u1db/tests/__init__.py 2011-11-22 11:09:00 +0000
107+++ u1db/tests/__init__.py 2011-11-25 15:43:24 +0000
108@@ -56,6 +56,20 @@
109 has_conflicts=has_conflicts)
110 self.assertEqual(exp_doc, db.get_doc(doc_id))
111
112+ def assertGetDocConflicts(self, db, doc_id, conflicts):
113+ """Assert what conflicts are stored for a given doc_id.
114+
115+ :param conflicts: A list of (doc_Rev, content) pairs.
116+ The first item must match the first item returned from the
117+ database, however the rest can be returned in any order.
118+ """
119+ if conflicts:
120+ conflicts = conflicts[:1] + sorted(conflicts[1:])
121+ actual = db.get_doc_conflicts(doc_id)
122+ if actual:
123+ actual = actual[:1] + sorted(actual[1:])
124+ self.assertEqual(conflicts, actual)
125+
126
127 def multiply_scenarios(a_scenarios, b_scenarios):
128 """Create the cross-product of scenarios."""
129
130=== modified file 'u1db/tests/test_backends.py'
131--- u1db/tests/test_backends.py 2011-11-23 13:46:56 +0000
132+++ u1db/tests/test_backends.py 2011-11-25 15:43:24 +0000
133@@ -18,6 +18,7 @@
134 Document,
135 errors,
136 tests,
137+ vectorclock,
138 )
139
140
141@@ -108,6 +109,77 @@
142 sorted(self.db.get_docs([doc1.doc_id, doc2.doc_id],
143 check_for_conflicts=False)))
144
145+ def test_resolve_doc(self):
146+ doc = self.db.create_doc(simple_doc)
147+ alt_doc = Document(doc.doc_id, 'alternate:1', nested_doc)
148+ self.db.force_doc_sync_conflict(alt_doc)
149+ self.assertEqual([('alternate:1', nested_doc),
150+ (doc.rev, simple_doc)],
151+ self.db.get_doc_conflicts(doc.doc_id))
152+ orig_rev = doc.rev
153+ self.db.resolve_doc(doc, [alt_doc.rev, doc.rev])
154+ self.assertNotEqual(orig_rev, doc.rev)
155+ self.assertFalse(doc.has_conflicts)
156+ self.assertGetDoc(self.db, doc.doc_id, doc.rev, simple_doc, False)
157+ self.assertEqual([], self.db.get_doc_conflicts(doc.doc_id))
158+
159+ def test_resolve_doc_picks_biggest_vcr(self):
160+ doc1 = self.db.create_doc(simple_doc)
161+ doc2 = Document(doc1.doc_id, 'alternate:1', nested_doc)
162+ self.db.force_doc_sync_conflict(doc2)
163+ self.assertGetDocConflicts(self.db, doc1.doc_id,
164+ [(doc2.rev, nested_doc),
165+ (doc1.rev, simple_doc)])
166+ orig_doc1_rev = doc1.rev
167+ self.db.resolve_doc(doc1, [doc2.rev, doc1.rev])
168+ self.assertFalse(doc1.has_conflicts)
169+ self.assertNotEqual(orig_doc1_rev, doc1.rev)
170+ self.assertGetDoc(self.db, doc1.doc_id, doc1.rev, simple_doc, False)
171+ self.assertGetDocConflicts(self.db, doc1.doc_id, [])
172+ vcr_1 = vectorclock.VectorClockRev(orig_doc1_rev)
173+ vcr_2 = vectorclock.VectorClockRev(doc2.rev)
174+ vcr_new = vectorclock.VectorClockRev(doc1.rev)
175+ self.assertTrue(vcr_new.is_newer(vcr_1))
176+ self.assertTrue(vcr_new.is_newer(vcr_2))
177+
178+ def test_resolve_doc_partial_not_winning(self):
179+ doc1 = self.db.create_doc(simple_doc)
180+ doc2 = Document(doc1.doc_id, 'alternate:1', nested_doc)
181+ self.db.force_doc_sync_conflict(doc2)
182+ self.assertGetDocConflicts(self.db, doc1.doc_id,
183+ [(doc2.rev, nested_doc),
184+ (doc1.rev, simple_doc)])
185+ content3 = '{"key": "valin3"}'
186+ doc3 = Document(doc1.doc_id, 'third:1', content3)
187+ self.db.force_doc_sync_conflict(doc3)
188+ self.assertGetDocConflicts(self.db, doc1.doc_id,
189+ [(doc3.rev, content3),
190+ (doc1.rev, simple_doc),
191+ (doc2.rev, nested_doc)])
192+ self.db.resolve_doc(doc1, [doc2.rev, doc1.rev])
193+ self.assertTrue(doc1.has_conflicts)
194+ self.assertGetDoc(self.db, doc1.doc_id, doc3.rev, content3, True)
195+ self.assertGetDocConflicts(self.db, doc1.doc_id,
196+ [(doc3.rev, content3),
197+ (doc1.rev, simple_doc)])
198+
199+ def test_resolve_doc_partial_winning(self):
200+ doc1 = self.db.create_doc(simple_doc)
201+ doc2 = Document(doc1.doc_id, 'alternate:1', nested_doc)
202+ self.db.force_doc_sync_conflict(doc2)
203+ content3 = '{"key": "valin3"}'
204+ doc3 = Document(doc1.doc_id, 'third:1', content3)
205+ self.db.force_doc_sync_conflict(doc3)
206+ self.assertGetDocConflicts(self.db, doc1.doc_id,
207+ [(doc3.rev, content3),
208+ (doc1.rev, simple_doc),
209+ (doc2.rev, nested_doc)])
210+ self.db.resolve_doc(doc1, [doc3.rev, doc1.rev])
211+ self.assertTrue(doc1.has_conflicts)
212+ self.assertGetDocConflicts(self.db, doc1.doc_id,
213+ [(doc1.rev, simple_doc),
214+ (doc2.rev, nested_doc)])
215+
216 def test_get_docs_empty_list(self):
217 self.assertEqual([], self.db.get_docs([]))
218
219
220=== modified file 'u1db/tests/test_sync.py'
221--- u1db/tests/test_sync.py 2011-11-22 11:09:00 +0000
222+++ u1db/tests/test_sync.py 2011-11-25 15:43:24 +0000
223@@ -422,87 +422,5 @@
224 (doc1.rev, simple_doc)],
225 self.db1.get_doc_conflicts(doc1.doc_id))
226
227- def test_resolve_doc(self):
228- doc1 = self.db1.create_doc(simple_doc)
229- doc_id = doc1.doc_id
230- content1 = '{"key": "altval"}'
231- doc2 = self.db2.create_doc(content1, doc_id=doc_id)
232- self.sync(self.db1, self.db2)
233- self.assertEqual([(doc2.rev, content1),
234- (doc1.rev, simple_doc)],
235- self.db1.get_doc_conflicts(doc_id))
236- new_rev, has_conflicts = self.db1.resolve_doc(doc_id, simple_doc,
237- [doc2.rev, doc1.rev])
238- self.assertFalse(has_conflicts)
239- self.assertGetDoc(self.db1, doc_id, new_rev, simple_doc, False)
240- self.assertEqual([], self.db1.get_doc_conflicts(doc_id))
241-
242- def test_resolve_doc_picks_biggest_vcr(self):
243- doc = self.db1.create_doc(simple_doc)
244- doc_id = doc.doc_id
245- doc1_rev = self.db1.put_doc(doc)
246- contents2 = '{"key": "altval"}'
247- doc2 = self.db2.create_doc(contents2, doc_id=doc_id)
248- doc2_rev = self.db2.put_doc(doc2)
249- self.sync(self.db1, self.db2)
250- self.assertEqual([(doc2_rev, contents2),
251- (doc1_rev, simple_doc)],
252- self.db1.get_doc_conflicts(doc_id))
253- new_rev, has_conflicts = self.db1.resolve_doc(doc_id, simple_doc,
254- [doc2_rev, doc1_rev])
255- self.assertFalse(has_conflicts)
256- self.assertGetDoc(self.db1, doc_id, new_rev, simple_doc, False)
257- self.assertEqual([], self.db1.get_doc_conflicts(doc_id))
258- vcr_1 = vectorclock.VectorClockRev(doc1_rev)
259- vcr_2 = vectorclock.VectorClockRev(doc2_rev)
260- vcr_new = vectorclock.VectorClockRev(new_rev)
261- self.assertTrue(vcr_new.is_newer(vcr_1))
262- self.assertTrue(vcr_new.is_newer(vcr_2))
263-
264- def test_resolve_doc_partial_not_winning(self):
265- doc1 = self.db1.create_doc(simple_doc)
266- doc_id = doc1.doc_id
267- content2 = '{"key": "valin2"}'
268- doc2 = self.db2.create_doc(content2, doc_id=doc_id)
269- self.sync(self.db1, self.db2)
270- self.assertEqual([(doc2.rev, content2),
271- (doc1.rev, simple_doc)],
272- self.db1.get_doc_conflicts(doc_id))
273- self.db3 = self.create_database('test3')
274- content3 = '{"key": "valin3"}'
275- doc3 = self.db3.create_doc(content3, doc_id=doc_id)
276- self.sync(self.db1, self.db3)
277- self.assertEqual([(doc3.rev, content3),
278- (doc1.rev, simple_doc),
279- (doc2.rev, content2)],
280- self.db1.get_doc_conflicts(doc_id))
281- new_rev, has_conflicts = self.db1.resolve_doc(doc_id, simple_doc,
282- [doc2.rev, doc1.rev])
283- self.assertTrue(has_conflicts)
284- self.assertGetDoc(self.db1, doc_id, doc3.rev, content3, True)
285- self.assertEqual([(doc3.rev, content3), (new_rev, simple_doc)],
286- self.db1.get_doc_conflicts(doc_id))
287-
288- def test_resolve_doc_partial_winning(self):
289- doc1 = self.db1.create_doc(simple_doc)
290- doc_id = doc1.doc_id
291- content2 = '{"key": "valin2"}'
292- doc2 = self.db2.create_doc(content2, doc_id=doc_id)
293- self.sync(self.db1, self.db2)
294- self.db3 = self.create_database('test3')
295- content3 = '{"key": "valin3"}'
296- doc3 = self.db3.create_doc(content3, doc_id=doc_id)
297- self.sync(self.db1, self.db3)
298- self.assertEqual([(doc3.rev, content3),
299- (doc1.rev, simple_doc),
300- (doc2.rev, content2)],
301- self.db1.get_doc_conflicts(doc_id))
302- new_rev, has_conflicts = self.db1.resolve_doc(doc_id, simple_doc,
303- [doc3.rev, doc1.rev])
304- self.assertTrue(has_conflicts)
305- self.assertEqual([(new_rev, simple_doc),
306- (doc2.rev, content2)],
307- self.db1.get_doc_conflicts(doc_id))
308-
309
310 load_tests = tests.load_with_scenarios

Subscribers

People subscribed via source and target branches