Merge lp:~jameinel/u1db/put_and_update into lp:u1db
- put_and_update
- Merge into trunk
Proposed by
John A Meinel
Status: | Merged |
---|---|
Merged at revision: | 130 |
Proposed branch: | lp:~jameinel/u1db/put_and_update |
Merge into: | lp:u1db |
Diff against target: |
321 lines (+86/-50) 5 files modified
u1db/backends/__init__.py (+1/-4) u1db/backends/inmemory.py (+23/-23) u1db/backends/sqlite_backend.py (+15/-21) u1db/tests/test_backends.py (+45/-1) u1db/tests/test_sync.py (+2/-1) |
To merge this branch: | bzr merge lp:~jameinel/u1db/put_and_update |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Samuele Pedroni | Approve | ||
Review via email: mp+83776@code.launchpad.net |
Commit message
Description of the change
This updates some internal code (_put_and_
I think this fixes a bug Samuele observed with put after delete. I added a test for it, but I added the test after I had done the refactoring, so I'm not positive his original bug is fixed.
To post a comment you must log in.
lp:~jameinel/u1db/put_and_update
updated
- 132. By John A Meinel
-
Merge trunk and resolve a small conflict.
- 133. By John A Meinel
-
Remove the now-unnecessary pdb trace call.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'u1db/backends/__init__.py' |
2 | --- u1db/backends/__init__.py 2011-11-23 12:17:00 +0000 |
3 | +++ u1db/backends/__init__.py 2011-11-29 15:19:27 +0000 |
4 | @@ -77,13 +77,10 @@ |
5 | doc_vcr = VectorClockRev(doc.rev) |
6 | if cur_doc is None: |
7 | cur_vcr = VectorClockRev(None) |
8 | - cur_content = None |
9 | else: |
10 | cur_vcr = VectorClockRev(cur_doc.rev) |
11 | - cur_content = cur_doc.content |
12 | if doc_vcr.is_newer(cur_vcr): |
13 | - self._put_and_update_indexes(doc.doc_id, cur_content, doc.rev, |
14 | - doc.content) |
15 | + self._put_and_update_indexes(cur_doc, doc) |
16 | return 'inserted' |
17 | elif doc.rev == cur_doc.rev: |
18 | # magical convergence |
19 | |
20 | === modified file 'u1db/backends/inmemory.py' |
21 | --- u1db/backends/inmemory.py 2011-11-28 17:51:34 +0000 |
22 | +++ u1db/backends/inmemory.py 2011-11-29 15:19:27 +0000 |
23 | @@ -61,30 +61,28 @@ |
24 | def put_doc(self, doc): |
25 | if doc.doc_id is None: |
26 | raise errors.InvalidDocId() |
27 | - old_content = None |
28 | - if doc.doc_id in self._docs: |
29 | - if doc.doc_id in self._conflicts: |
30 | - raise errors.ConflictedDoc() |
31 | - old_rev, old_content = self._docs[doc.doc_id] |
32 | - if old_rev != doc.rev: |
33 | + if self._has_conflicts(doc.doc_id): |
34 | + raise errors.ConflictedDoc() |
35 | + old_doc = self._get_doc(doc.doc_id) |
36 | + if old_doc is not None: |
37 | + if old_doc.rev != doc.rev: |
38 | raise errors.RevisionConflict() |
39 | else: |
40 | if doc.rev is not None: |
41 | raise errors.RevisionConflict() |
42 | new_rev = self._allocate_doc_rev(doc.rev) |
43 | - self._put_and_update_indexes(doc.doc_id, old_content, new_rev, |
44 | - doc.content) |
45 | doc.rev = new_rev |
46 | + self._put_and_update_indexes(old_doc, doc) |
47 | return new_rev |
48 | |
49 | - def _put_and_update_indexes(self, doc_id, old_content, new_rev, content): |
50 | + def _put_and_update_indexes(self, old_doc, doc): |
51 | for index in self._indexes.itervalues(): |
52 | - if old_content is not None: |
53 | - index.remove_json(doc_id, old_content) |
54 | - if content not in (None, 'null'): |
55 | - index.add_json(doc_id, content) |
56 | - self._docs[doc_id] = (new_rev, content) |
57 | - self._transaction_log.append(doc_id) |
58 | + if old_doc is not None and old_doc.content is not None: |
59 | + index.remove_json(old_doc.doc_id, old_doc.content) |
60 | + if doc.content is not None: |
61 | + index.add_json(doc.doc_id, doc.content) |
62 | + self._docs[doc.doc_id] = (doc.rev, doc.content) |
63 | + self._transaction_log.append(doc.doc_id) |
64 | |
65 | def _get_doc(self, doc_id): |
66 | try: |
67 | @@ -111,7 +109,11 @@ |
68 | return result |
69 | |
70 | def resolve_doc(self, doc, conflicted_doc_revs): |
71 | - cur_rev, cur_content = self._docs[doc.doc_id] |
72 | + cur_doc = self._get_doc(doc.doc_id) |
73 | + if cur_doc is None: |
74 | + cur_rev = None |
75 | + else: |
76 | + cur_rev = cur_doc.rev |
77 | new_rev = self._ensure_maximal_rev(cur_rev, conflicted_doc_revs) |
78 | superseded_revs = set(conflicted_doc_revs) |
79 | remaining_conflicts = [] |
80 | @@ -120,16 +122,15 @@ |
81 | if c_rev in superseded_revs: |
82 | continue |
83 | remaining_conflicts.append((c_rev, c_doc)) |
84 | + doc.rev = new_rev |
85 | if cur_rev in superseded_revs: |
86 | - self._put_and_update_indexes(doc.doc_id, cur_content, new_rev, |
87 | - doc.content) |
88 | + self._put_and_update_indexes(cur_doc, doc) |
89 | else: |
90 | remaining_conflicts.append((new_rev, doc.content)) |
91 | if not remaining_conflicts: |
92 | del self._conflicts[doc.doc_id] |
93 | else: |
94 | self._conflicts[doc.doc_id] = remaining_conflicts |
95 | - doc.rev = new_rev |
96 | doc.has_conflicts = bool(remaining_conflicts) |
97 | |
98 | def delete_doc(self, doc): |
99 | @@ -169,12 +170,11 @@ |
100 | set(self._transaction_log[old_generation:])) |
101 | |
102 | def force_doc_sync_conflict(self, doc): |
103 | - my_doc_rev, my_content = self._docs[doc.doc_id] |
104 | + my_doc = self._get_doc(doc.doc_id) |
105 | self._conflicts.setdefault(doc.doc_id, []).append( |
106 | - (my_doc_rev, my_content)) |
107 | + (my_doc.rev, my_doc.content)) |
108 | doc.has_conflicts = True |
109 | - self._put_and_update_indexes(doc.doc_id, my_content, doc.rev, |
110 | - doc.content) |
111 | + self._put_and_update_indexes(my_doc, doc) |
112 | |
113 | |
114 | class InMemoryIndex(object): |
115 | |
116 | === modified file 'u1db/backends/sqlite_backend.py' |
117 | --- u1db/backends/sqlite_backend.py 2011-11-28 17:51:34 +0000 |
118 | +++ u1db/backends/sqlite_backend.py 2011-11-29 15:19:27 +0000 |
119 | @@ -253,22 +253,19 @@ |
120 | def put_doc(self, doc): |
121 | if doc.doc_id is None: |
122 | raise errors.InvalidDocId() |
123 | - old_content = None |
124 | with self._db_handle: |
125 | if self._has_conflicts(doc.doc_id): |
126 | raise errors.ConflictedDoc() |
127 | old_doc = self._get_doc(doc.doc_id) |
128 | if old_doc is not None: |
129 | - old_content = old_doc.content |
130 | if old_doc.rev != doc.rev: |
131 | raise errors.RevisionConflict() |
132 | else: |
133 | if doc.rev is not None: |
134 | raise errors.RevisionConflict() |
135 | new_rev = self._allocate_doc_rev(doc.rev) |
136 | - self._put_and_update_indexes(doc.doc_id, old_content, new_rev, |
137 | - doc.content) |
138 | doc.rev = new_rev |
139 | + self._put_and_update_indexes(old_doc, doc) |
140 | return new_rev |
141 | |
142 | def _expand_to_fields(self, doc_id, base_field, raw_doc, save_none): |
143 | @@ -300,7 +297,7 @@ |
144 | values.append((doc_id, subfield_name, val, len(values))) |
145 | return values |
146 | |
147 | - def _put_and_update_indexes(self, doc_id, old_doc, new_rev, doc): |
148 | + def _put_and_update_indexes(self, old_doc, doc): |
149 | """Actually insert a document into the database. |
150 | |
151 | This both updates the existing documents content, and any indexes that |
152 | @@ -333,10 +330,9 @@ |
153 | if self._has_conflicts(doc.doc_id): |
154 | raise errors.ConflictedDoc() |
155 | new_rev = self._allocate_doc_rev(doc.rev) |
156 | - self._put_and_update_indexes(doc.doc_id, |
157 | - old_doc.content, new_rev, None) |
158 | doc.rev = new_rev |
159 | doc.content = None |
160 | + self._put_and_update_indexes(old_doc, doc) |
161 | return new_rev |
162 | |
163 | def _get_conflicts(self, doc_id): |
164 | @@ -385,8 +381,7 @@ |
165 | c = self._db_handle.cursor() |
166 | self._add_conflict(c, doc.doc_id, my_doc.rev, my_doc.content) |
167 | doc.has_conflicts = True |
168 | - self._put_and_update_indexes(doc.doc_id, my_doc.content, doc.rev, |
169 | - doc.content) |
170 | + self._put_and_update_indexes(my_doc, doc) |
171 | |
172 | def resolve_doc(self, doc, conflicted_doc_revs): |
173 | with self._db_handle: |
174 | @@ -395,15 +390,14 @@ |
175 | superseded_revs = set(conflicted_doc_revs) |
176 | cur_conflicts = self._get_conflicts(doc.doc_id) |
177 | c = self._db_handle.cursor() |
178 | + doc.rev = new_rev |
179 | if cur_doc.rev in superseded_revs: |
180 | - self._put_and_update_indexes(doc.doc_id, cur_doc.content, |
181 | - new_rev, doc.content) |
182 | + self._put_and_update_indexes(cur_doc, doc) |
183 | else: |
184 | self._add_conflict(c, doc.doc_id, new_rev, doc.content) |
185 | deleting = [(doc.doc_id, c_rev) for c_rev in superseded_revs] |
186 | c.executemany("DELETE FROM conflicts" |
187 | " WHERE doc_id=? AND doc_rev=?", deleting) |
188 | - doc.rev = new_rev |
189 | doc.has_conflicts = self._has_conflicts(doc.doc_id) |
190 | |
191 | def create_index(self, index_name, index_expression): |
192 | @@ -559,28 +553,28 @@ |
193 | db_cursor.executemany( |
194 | "INSERT INTO document_fields VALUES (?, ?, ?)", values) |
195 | |
196 | - def _put_and_update_indexes(self, doc_id, old_doc, new_rev, doc): |
197 | + def _put_and_update_indexes(self, old_doc, doc): |
198 | c = self._db_handle.cursor() |
199 | - if doc: |
200 | - raw_doc = simplejson.loads(doc) |
201 | + if doc and doc.content: |
202 | + raw_doc = simplejson.loads(doc.content) |
203 | else: |
204 | raw_doc = {} |
205 | - if old_doc: |
206 | + if old_doc is not None: |
207 | c.execute("UPDATE document SET doc_rev=?, doc=? WHERE doc_id = ?", |
208 | - (new_rev, doc, doc_id)) |
209 | + (doc.rev, doc.content, doc.doc_id)) |
210 | c.execute("DELETE FROM document_fields WHERE doc_id = ?", |
211 | - (doc_id,)) |
212 | + (doc.doc_id,)) |
213 | else: |
214 | c.execute("INSERT INTO document VALUES (?, ?, ?)", |
215 | - (doc_id, new_rev, doc)) |
216 | + (doc.doc_id, doc.rev, doc.content)) |
217 | indexed_fields = self._get_indexed_fields() |
218 | if indexed_fields: |
219 | # It is expected that len(indexed_fields) is shorter than |
220 | # len(raw_doc) |
221 | # TODO: Handle nested indexed fields. |
222 | - self._update_indexes(doc_id, raw_doc, indexed_fields, c) |
223 | + self._update_indexes(doc.doc_id, raw_doc, indexed_fields, c) |
224 | c.execute("INSERT INTO transaction_log(doc_id) VALUES (?)", |
225 | - (doc_id,)) |
226 | + (doc.doc_id,)) |
227 | |
228 | def create_index(self, index_name, index_expression): |
229 | with self._db_handle: |
230 | |
231 | === modified file 'u1db/tests/test_backends.py' |
232 | --- u1db/tests/test_backends.py 2011-11-28 17:51:34 +0000 |
233 | +++ u1db/tests/test_backends.py 2011-11-29 15:19:27 +0000 |
234 | @@ -241,6 +241,30 @@ |
235 | [(doc1.rev, simple_doc), |
236 | (doc2.rev, nested_doc)]) |
237 | |
238 | + def test_resolve_doc_with_delete_conflict(self): |
239 | + doc1 = self.db.create_doc(simple_doc) |
240 | + self.db.delete_doc(doc1) |
241 | + doc2 = Document(doc1.doc_id, 'alternate:1', nested_doc) |
242 | + self.db.force_doc_sync_conflict(doc2) |
243 | + self.assertGetDocConflicts(self.db, doc1.doc_id, |
244 | + [(doc2.rev, nested_doc), |
245 | + (doc1.rev, None)]) |
246 | + self.db.resolve_doc(doc2, [doc1.rev, doc2.rev]) |
247 | + self.assertGetDocConflicts(self.db, doc1.doc_id, []) |
248 | + self.assertGetDoc(self.db, doc2.doc_id, doc2.rev, nested_doc, False) |
249 | + |
250 | + def test_resolve_doc_with_delete_to_delete(self): |
251 | + doc1 = self.db.create_doc(simple_doc) |
252 | + self.db.delete_doc(doc1) |
253 | + doc2 = Document(doc1.doc_id, 'alternate:1', nested_doc) |
254 | + self.db.force_doc_sync_conflict(doc2) |
255 | + self.assertGetDocConflicts(self.db, doc1.doc_id, |
256 | + [(doc2.rev, nested_doc), |
257 | + (doc1.rev, None)]) |
258 | + self.db.resolve_doc(doc1, [doc1.rev, doc2.rev]) |
259 | + self.assertGetDocConflicts(self.db, doc1.doc_id, []) |
260 | + self.assertGetDoc(self.db, doc1.doc_id, doc1.rev, None, False) |
261 | + |
262 | def test_get_docs_empty_list(self): |
263 | self.assertEqual([], self.db.get_docs([])) |
264 | |
265 | @@ -255,7 +279,8 @@ |
266 | doc1 = self.db.create_doc(orig_doc) |
267 | doc1_rev1 = doc1.rev |
268 | doc1.content = simple_doc |
269 | - doc1_rev2 = self.db.put_doc(doc1) |
270 | + self.db.put_doc(doc1) |
271 | + doc1_rev2 = doc1.rev |
272 | # Nothing is inserted, because the document is already superseded |
273 | doc = Document(doc1.doc_id, doc1_rev1, orig_doc) |
274 | state = self.db.put_doc_if_newer(doc) |
275 | @@ -287,6 +312,17 @@ |
276 | (doc1.rev, simple_doc)], |
277 | self.db.get_doc_conflicts(doc1.doc_id)) |
278 | |
279 | + def test_force_doc_sync_conflict_was_deleted(self): |
280 | + doc1 = self.db.create_doc(simple_doc) |
281 | + self.db.delete_doc(doc1) |
282 | + doc2 = Document(doc1.doc_id, 'alternate:1', nested_doc) |
283 | + self.db.force_doc_sync_conflict(doc2) |
284 | + self.assertTrue(doc2.has_conflicts) |
285 | + self.assertGetDoc(self.db, doc1.doc_id, 'alternate:1', nested_doc, True) |
286 | + self.assertEqual([('alternate:1', nested_doc), |
287 | + (doc1.rev, None)], |
288 | + self.db.get_doc_conflicts(doc1.doc_id)) |
289 | + |
290 | def test_get_sync_generation(self): |
291 | self.assertEqual(0, self.db.get_sync_generation('other-db')) |
292 | self.db.set_sync_generation('other-db', 2) |
293 | @@ -307,6 +343,14 @@ |
294 | self.db.delete_doc(doc) |
295 | self.assertEqual((2, set([doc.doc_id])), self.db.whats_changed(db_gen)) |
296 | |
297 | + def test_delete_then_put(self): |
298 | + doc = self.db.create_doc(simple_doc) |
299 | + self.db.delete_doc(doc) |
300 | + self.assertGetDoc(self.db, doc.doc_id, doc.rev, None, False) |
301 | + doc.content = nested_doc |
302 | + self.db.put_doc(doc) |
303 | + self.assertGetDoc(self.db, doc.doc_id, doc.rev, nested_doc, False) |
304 | + |
305 | def test_whats_changed_initial_database(self): |
306 | self.assertEqual((0, set()), self.db.whats_changed()) |
307 | |
308 | |
309 | === modified file 'u1db/tests/test_sync.py' |
310 | --- u1db/tests/test_sync.py 2011-11-28 14:58:52 +0000 |
311 | +++ u1db/tests/test_sync.py 2011-11-29 15:19:27 +0000 |
312 | @@ -338,7 +338,8 @@ |
313 | content1 = '{"key": "localval"}' |
314 | content2 = '{"key": "altval"}' |
315 | doc.content = content2 |
316 | - doc2_rev2 = self.db2.put_doc(doc) |
317 | + self.db2.put_doc(doc) |
318 | + doc2_rev2 = doc.rev |
319 | # Monkey patch so that after the local client has determined recent |
320 | # changes, we get another one, before sync finishes. |
321 | orig_wc = self.db1.whats_changed |
+1