Merge lp:~jameinel/u1db/resolve_doc into lp:u1db
- resolve_doc
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Samuele Pedroni | Approve | ||
Review via email: mp+83402@code.launchpad.net |
Commit message
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.
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 |
looks good, test_resolve_doc was moved over twice though!