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

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
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+83776@code.launchpad.net

Description of the change

This updates some internal code (_put_and_update_indexes) to use Document objects.

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.

Revision history for this message
Samuele Pedroni (pedronis) wrote :

+1

review: Approve

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

Subscribers

People subscribed via source and target branches