Merge lp:~thisfred/u1db/txid_in_get_sync_info into lp:u1db

Proposed by Eric Casteleijn
Status: Superseded
Proposed branch: lp:~thisfred/u1db/txid_in_get_sync_info
Merge into: lp:u1db
Diff against target: 1001 lines (+240/-172)
18 files modified
include/u1db/u1db_internal.h (+14/-8)
src/u1db.c (+55/-35)
src/u1db_http_sync_target.c (+23/-7)
src/u1db_sync_target.c (+18/-9)
u1db/__init__.py (+2/-2)
u1db/backends/__init__.py (+18/-11)
u1db/backends/inmemory.py (+10/-8)
u1db/backends/sqlite_backend.py (+9/-8)
u1db/remote/http_app.py (+3/-2)
u1db/remote/http_target.py (+1/-0)
u1db/sync.py (+8/-7)
u1db/tests/c_backend_wrapper.pyx (+30/-22)
u1db/tests/test_backends.py (+8/-35)
u1db/tests/test_c_backend.py (+1/-1)
u1db/tests/test_http_app.py (+3/-1)
u1db/tests/test_remote_sync_target.py (+1/-1)
u1db/tests/test_sqlite_backend.py (+1/-1)
u1db/tests/test_sync.py (+35/-14)
To merge this branch: bzr merge lp:~thisfred/u1db/txid_in_get_sync_info
Reviewer Review Type Date Requested Status
Ubuntu One hackers Pending
Review via email: mp+113801@code.launchpad.net

This proposal has been superseded by a proposal from 2012-07-06.

Description of the change

Added the target transaction id to the return parameters of get_sync_info, since we need it in sync() to be able to tell if the target diverged if it has the generation we expect.

To post a comment you must log in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/u1db/u1db_internal.h'
2--- include/u1db/u1db_internal.h 2012-07-03 15:48:59 +0000
3+++ include/u1db/u1db_internal.h 2012-07-06 20:51:26 +0000
4@@ -62,23 +62,25 @@
5 * Note that this is const char and memory will be
6 * managed by the sync_target, so it should *not*
7 * be freed.
8- * @param st_get (OUT) The database generation for this sync
9+ * @param st_gen (OUT) The database generation for this sync
10+ * target, matches st_replica_uid
11+ * @param st_trans_id (OUT) The database transaction id for this sync
12 * target, matches st_replica_uid
13 * @param source_gen (OUT) The last generation of source_replica_uid
14 * that st has synchronized with.
15- * @param trans_id (OUT) The transaction id associated with the
16+ * @param source_trans_id (OUT) The transaction id associated with the
17 * source generation, the memory must be freed by
18 * the caller.
19 */
20 int (*get_sync_info)(u1db_sync_target *st,
21 const char *source_replica_uid,
22- const char **st_replica_uid, int *st_gen, int *source_gen,
23- char **trans_id);
24+ const char **st_replica_uid, int *st_gen, char **st_trans_id,
25+ int *source_gen, char **source_trans_id);
26 /**
27 * Set the synchronization information about another replica.
28 *
29 * @param st Pass this sync_target to the function,
30- * eg st->get_sync_info(st, ...)
31+ * eg st->record_sync_info(st, ...)
32 * @param source_replica_uid The unique identifier for the source we
33 * want to synchronize from.
34 * @param source_gen The last generation of source_replica_uid
35@@ -223,9 +225,7 @@
36 * superseded.
37 */
38 int u1db__validate_source(u1database *db, const char *replica_uid,
39- int replica_gen, const char *replica_trans_id,
40- u1db_vectorclock *cur_vcr,
41- u1db_vectorclock *other_vcr, int *state);
42+ int replica_gen, const char *replica_trans_id);
43
44 /**
45 * Internal API, Get the global database rev.
46@@ -239,6 +239,12 @@
47 char **trans_id);
48
49 /**
50+ * Internal API, Get the transaction id for the db generation.
51+ */
52+int u1db__get_trans_id_for_gen(u1database *db, int generation,
53+ char **trans_id);
54+
55+/**
56 * Internal API, Validate generation and transaction id.
57 */
58 int u1db_validate_gen_and_trans_id(u1database *db, int generation,
59
60=== modified file 'src/u1db.c'
61--- src/u1db.c 2012-07-03 15:48:59 +0000
62+++ src/u1db.c 2012-07-06 20:51:26 +0000
63@@ -42,7 +42,6 @@
64 char **doc_rev);
65 static int generate_transaction_id(char buf[35]);
66
67-
68 static int
69 initialize(u1database *db)
70 {
71@@ -756,23 +755,17 @@
72
73 int
74 u1db__validate_source(u1database *db, const char *replica_uid, int replica_gen,
75- const char *replica_trans_id, u1db_vectorclock *cur,
76- u1db_vectorclock *other, int *state)
77+ const char *replica_trans_id)
78 {
79 int old_generation;
80 char *old_trans_id = NULL;
81 int status = U1DB_OK;
82
83- *state = U1DB_OK;
84 status = u1db__get_replica_gen_and_trans_id(
85 db, replica_uid, &old_generation, &old_trans_id);
86 if (status != U1DB_OK)
87 goto finish;
88 if (replica_gen < old_generation) {
89- if (u1db__vectorclock_is_newer(cur, other)) {
90- *state = U1DB_SUPERSEDED;
91- goto finish;
92- }
93 status = U1DB_INVALID_GENERATION;
94 goto finish;
95 }
96@@ -782,7 +775,6 @@
97 status = U1DB_INVALID_TRANSACTION_ID;
98 goto finish;
99 }
100- *state = U1DB_SUPERSEDED;
101 finish:
102 if (old_trans_id != NULL)
103 free(old_trans_id);
104@@ -830,15 +822,10 @@
105 }
106 if (replica_uid != NULL && replica_trans_id != NULL) {
107 status = u1db__validate_source(
108- db, replica_uid, replica_gen, replica_trans_id, stored_vc, new_vc,
109- state);
110+ db, replica_uid, replica_gen, replica_trans_id);
111 if (status != U1DB_OK) {
112 goto finish;
113 }
114- if (*state != U1DB_OK) {
115- status = u1db__get_generation(db, at_gen);
116- goto finish;
117- }
118 }
119 if (stored_doc_rev == NULL) {
120 status = U1DB_OK;
121@@ -1464,14 +1451,60 @@
122 *generation = sqlite3_column_int(statement, 0);
123 tmp = (const char *)sqlite3_column_text(statement, 1);
124 if (tmp == NULL) {
125+ *trans_id = strdup("");
126+ if (*trans_id == NULL) {
127+ status = U1DB_NOMEM;
128+ goto finish;
129+ }
130+ } else {
131+ *trans_id = strdup(tmp);
132+ if (*trans_id == NULL) {
133+ status = U1DB_NOMEM;
134+ goto finish;
135+ }
136+ }
137+ status = U1DB_OK;
138+ }
139+finish:
140+ sqlite3_finalize(statement);
141+ return status;
142+}
143+
144+int
145+u1db__get_trans_id_for_gen(u1database *db, int generation,
146+ char **trans_id)
147+{
148+ int status = U1DB_OK;
149+ sqlite3_stmt *statement;
150+ const char *tmp;
151+
152+ if (db == NULL) {
153+ return U1DB_INVALID_PARAMETER;
154+ }
155+ status = sqlite3_prepare_v2(db->sql_handle,
156+ "SELECT transaction_id FROM transaction_log WHERE generation = ?", -1,
157+ &statement, NULL);
158+ if (status != SQLITE_OK) { goto finish; }
159+ status = sqlite3_bind_int(statement, 1, generation);
160+ if (status != SQLITE_OK) { goto finish; }
161+ status = sqlite3_step(statement);
162+ if (status == SQLITE_DONE) {
163+ status = U1DB_INVALID_GENERATION;
164+ goto finish;
165+ } else if (status == SQLITE_ROW) {
166+ tmp = (const char *)sqlite3_column_text(statement, 0);
167+ if (tmp == NULL) {
168 *trans_id = NULL;
169 } else {
170 *trans_id = strdup(tmp);
171 if (*trans_id == NULL) {
172 status = U1DB_NOMEM;
173+ goto finish;
174 }
175 }
176+ status = U1DB_OK;
177 }
178+finish:
179 sqlite3_finalize(statement);
180 return status;
181 }
182@@ -1481,34 +1514,21 @@
183 const char *trans_id)
184 {
185 int status = U1DB_OK;
186- sqlite3_stmt *statement;
187+ char *known_trans_id = NULL;
188
189 if (generation == 0)
190 return status;
191- if (db == NULL) {
192- return U1DB_INVALID_PARAMETER;
193- }
194- status = sqlite3_prepare_v2(db->sql_handle,
195- "SELECT transaction_id FROM transaction_log WHERE generation = ?", -1,
196- &statement, NULL);
197- if (status != SQLITE_OK) { goto finish; }
198- status = sqlite3_bind_int(statement, 1, generation);
199- if (status != SQLITE_OK) { goto finish; }
200- status = sqlite3_step(statement);
201- if (status == SQLITE_DONE) {
202- status = U1DB_INVALID_GENERATION;
203+ status = u1db__get_trans_id_for_gen(db, generation, &known_trans_id);
204+ if (status != U1DB_OK)
205 goto finish;
206- } else if (status == SQLITE_ROW) {
207- // Note: We may want to handle the column containing NULL
208- if (strcmp(trans_id,
209- (const char *)sqlite3_column_text(statement, 0)) == 0) {
210+ if (strcmp(trans_id, known_trans_id) == 0) {
211 status = U1DB_OK;
212 goto finish;
213 }
214- status = U1DB_INVALID_TRANSACTION_ID;
215- }
216+ status = U1DB_INVALID_TRANSACTION_ID;
217 finish:
218- sqlite3_finalize(statement);
219+ if (known_trans_id != NULL)
220+ free(known_trans_id);
221 return status;
222 }
223
224
225=== modified file 'src/u1db_http_sync_target.c'
226--- src/u1db_http_sync_target.c 2012-07-03 15:48:59 +0000
227+++ src/u1db_http_sync_target.c 2012-07-06 20:51:26 +0000
228@@ -41,9 +41,10 @@
229 struct _http_request;
230
231 static int st_http_get_sync_info(u1db_sync_target *st,
232- const char *source_replica_uid,
233- const char **st_replica_uid, int *st_gen, int *source_gen,
234- char **trans_id);
235+ const char *source_replica_uid,
236+ const char **st_replica_uid, int *st_gen,
237+ char **st_trans_id, int *source_gen,
238+ char **trans_id);
239
240 static int st_http_record_sync_info(u1db_sync_target *st,
241 const char *source_replica_uid, int source_gen, const char *trans_id);
242@@ -366,15 +367,15 @@
243 }
244
245 static int
246-st_http_get_sync_info(u1db_sync_target *st,
247- const char *source_replica_uid,
248- const char **st_replica_uid, int *st_gen, int *source_gen,
249- char **trans_id)
250+st_http_get_sync_info(u1db_sync_target *st, const char *source_replica_uid,
251+ const char **st_replica_uid, int *st_gen,
252+ char **st_trans_id, int *source_gen, char **trans_id)
253 {
254 struct _http_state *state;
255 struct _http_request req = {0};
256 char *url = NULL;
257 const char *tmp = NULL;
258+ const char *tmp2 = NULL;
259 int status = U1DB_OK;
260 long http_code;
261 struct curl_slist *headers = NULL;
262@@ -481,6 +482,21 @@
263 goto finish;
264 }
265 *st_gen = json_object_get_int(obj);
266+ obj = json_object_object_get(json, "target_replica_transaction_id");
267+ if (obj == NULL) {
268+ status = U1DB_INVALID_HTTP_RESPONSE;
269+ goto finish;
270+ }
271+ tmp2 = json_object_get_string(obj);
272+ if (tmp2 == NULL) {
273+ *st_trans_id = NULL;
274+ } else {
275+ *st_trans_id = strdup(tmp2);
276+ if (*st_trans_id == NULL) {
277+ status = U1DB_NOMEM;
278+ goto finish;
279+ }
280+ }
281 obj = json_object_object_get(json, "source_replica_generation");
282 if (obj == NULL) {
283 status = U1DB_INVALID_HTTP_RESPONSE;
284
285=== modified file 'src/u1db_sync_target.c'
286--- src/u1db_sync_target.c 2012-07-03 15:48:59 +0000
287+++ src/u1db_sync_target.c 2012-07-06 20:51:26 +0000
288@@ -22,9 +22,10 @@
289
290
291 static int st_get_sync_info(u1db_sync_target *st,
292- const char *source_replica_uid,
293- const char **st_replica_uid, int *st_gen, int *source_gen,
294- char **source_trans_id);
295+ const char *source_replica_uid,
296+ const char **st_replica_uid, int *st_gen,
297+ char **st_trans_id, int *source_gen,
298+ char **source_trans_id);
299
300 static int st_record_sync_info(u1db_sync_target *st,
301 const char *source_replica_uid, int source_gen, const char *trans_id);
302@@ -105,8 +106,8 @@
303
304 static int
305 st_get_sync_info(u1db_sync_target *st, const char *source_replica_uid,
306- const char **st_replica_uid, int *st_gen, int *source_gen,
307- char **source_trans_id)
308+ const char **st_replica_uid, int *st_gen, char **st_trans_id,
309+ int *source_gen, char **source_trans_id)
310 {
311 int status = U1DB_OK;
312 u1database *db;
313@@ -128,7 +129,7 @@
314 status = u1db__get_replica_gen_and_trans_id(
315 db, source_replica_uid, source_gen, source_trans_id);
316 if (status != U1DB_OK) { goto finish; }
317- status = u1db__get_generation(db, st_gen);
318+ status = u1db__get_generation_info(db, st_gen, st_trans_id);
319 finish:
320 return status;
321 }
322@@ -594,6 +595,7 @@
323 char *local_target_trans_id = NULL;
324 char *target_trans_id_known_by_local = NULL;
325 char *local_trans_id_known_by_target = NULL;
326+ char *target_trans_id = NULL;
327 int target_gen, local_gen;
328 int local_gen_known_by_target, target_gen_known_by_local;
329
330@@ -608,14 +610,15 @@
331 if (status != U1DB_OK) { goto finish; }
332 // fprintf(stderr, "Local uid: %s\n", local_uid);
333 status = target->get_sync_info(
334- target, local_uid, &target_uid, &target_gen,
335+ target, local_uid, &target_uid, &target_gen, &target_trans_id,
336 &local_gen_known_by_target, &local_trans_id_known_by_target);
337 if (status != U1DB_OK) { goto finish; }
338 status = u1db_validate_gen_and_trans_id(
339 db, local_gen_known_by_target, local_trans_id_known_by_target);
340 if (status != U1DB_OK) { goto finish; }
341- status = u1db__get_replica_gen_and_trans_id(db, target_uid,
342- &target_gen_known_by_local, &target_trans_id_known_by_local);
343+ status = u1db__get_replica_gen_and_trans_id(
344+ db, target_uid, &target_gen_known_by_local,
345+ &target_trans_id_known_by_local);
346 if (status != U1DB_OK) { goto finish; }
347 local_target_trans_id = target_trans_id_known_by_local;
348 local_gen = local_gen_known_by_target;
349@@ -630,6 +633,9 @@
350 if (local_gen == local_gen_known_by_target
351 && target_gen == target_gen_known_by_local)
352 {
353+ if (strcmp(target_trans_id, target_trans_id_known_by_local) != 0) {
354+ status = U1DB_INVALID_TRANSACTION_ID;
355+ }
356 // We know status == U1DB_OK, and we can shortcut the rest of the
357 // logic, no need to look for more information.
358 goto finish;
359@@ -671,6 +677,9 @@
360 if (local_trans_id_known_by_target != NULL) {
361 free(local_trans_id_known_by_target);
362 }
363+ if (target_trans_id != NULL) {
364+ free(target_trans_id);
365+ }
366 if (local_target_trans_id != NULL) {
367 if (target_trans_id_known_by_local == local_target_trans_id) {
368 // Don't double free
369
370=== modified file 'u1db/__init__.py'
371--- u1db/__init__.py 2012-07-03 15:48:59 +0000
372+++ u1db/__init__.py 2012-07-06 20:51:26 +0000
373@@ -290,7 +290,7 @@
374 encountered during synchronization. If we've never synchronized
375 with the replica, this is (0, '').
376 """
377- raise NotImplementedError(self._replica_gen_and_trans_id)
378+ raise NotImplementedError(self._get_replica_gen_and_trans_id)
379
380 def _set_replica_gen_and_trans_id(self, other_replica_uid,
381 other_generation, other_transaction_id):
382@@ -536,7 +536,7 @@
383 :param source_replica_uid: Another replica which we might have
384 synchronized with in the past.
385 :return: (target_replica_uid, target_replica_generation,
386- source_replica_last_known_generation,
387+ target_trans_id, source_replica_last_known_generation,
388 source_replica_last_known_transaction_id)
389 """
390 raise NotImplementedError(self.get_sync_info)
391
392=== modified file 'u1db/backends/__init__.py'
393--- u1db/backends/__init__.py 2012-07-03 13:50:27 +0000
394+++ u1db/backends/__init__.py 2012-07-06 20:51:26 +0000
395@@ -102,6 +102,14 @@
396 result.append(doc)
397 return result
398
399+ def _get_trans_id_for_gen(self, generation):
400+ """Get the transaction id corresponding to a particular generation.
401+
402+ Raises an InvalidGeneration when the generation does not exist.
403+
404+ """
405+ raise NotImplementedError(self._get_trans_id_for_gen)
406+
407 def validate_gen_and_trans_id(self, generation, trans_id):
408 """Validate the generation and transaction id.
409
410@@ -109,10 +117,14 @@
411 InvalidTransactionId when it does but with a different transaction id.
412
413 """
414- raise NotImplementedError(self.validate_gen_and_trans_id)
415+ if generation == 0:
416+ return
417+ known_trans_id = self._get_trans_id_for_gen(generation)
418+ if known_trans_id != trans_id:
419+ raise errors.InvalidTransactionId
420
421 def _validate_source(self, other_replica_uid, other_generation,
422- other_transaction_id, cur_vcr, other_vcr):
423+ other_transaction_id):
424 """Validate the new generation and transaction id.
425
426 other_generation must be greater than what we have stored for this
427@@ -123,13 +135,11 @@
428 old_transaction_id) = self._get_replica_gen_and_trans_id(
429 other_replica_uid)
430 if other_generation < old_generation:
431- if cur_vcr.is_newer(other_vcr):
432- return 'superseded'
433 raise errors.InvalidGeneration
434 if other_generation > old_generation:
435- return 'ok'
436+ return
437 if other_transaction_id == old_transaction_id:
438- return 'superseded'
439+ return
440 raise errors.InvalidTransactionId
441
442 def _put_doc_if_newer(self, doc, save_conflict, replica_uid=None,
443@@ -141,11 +151,8 @@
444 else:
445 cur_vcr = VectorClockRev(cur_doc.rev)
446 if replica_uid is not None and replica_gen is not None:
447- state = self._validate_source(
448- replica_uid, replica_gen, replica_trans_id, cur_vcr,
449- doc_vcr)
450- if state != 'ok':
451- return state, self._get_generation()
452+ self._validate_source(
453+ replica_uid, replica_gen, replica_trans_id)
454 if doc_vcr.is_newer(cur_vcr):
455 rev = doc.rev
456 self._prune_conflicts(doc, doc_vcr)
457
458=== modified file 'u1db/backends/inmemory.py'
459--- u1db/backends/inmemory.py 2012-07-03 15:48:59 +0000
460+++ u1db/backends/inmemory.py 2012-07-06 20:51:26 +0000
461@@ -85,16 +85,16 @@
462 return len(self._transaction_log)
463
464 def _get_generation_info(self):
465+ if not self._transaction_log:
466+ return 0, ''
467 return len(self._transaction_log), self._transaction_log[-1][1]
468
469- def validate_gen_and_trans_id(self, generation, trans_id):
470+ def _get_trans_id_for_gen(self, generation):
471 if generation == 0:
472- return
473+ return ''
474 if generation > len(self._transaction_log):
475 raise errors.InvalidGeneration
476- if self._transaction_log[generation - 1][1] == trans_id:
477- return
478- raise errors.InvalidTransactionId
479+ return self._transaction_log[generation - 1][1]
480
481 def put_doc(self, doc):
482 if doc.doc_id is None:
483@@ -448,10 +448,12 @@
484 class InMemorySyncTarget(CommonSyncTarget):
485
486 def get_sync_info(self, source_replica_uid):
487- source_gen, trans_id = self._db._get_replica_gen_and_trans_id(
488+ source_gen, source_trans_id = self._db._get_replica_gen_and_trans_id(
489 source_replica_uid)
490- return (self._db._replica_uid, len(self._db._transaction_log),
491- source_gen, trans_id)
492+ my_gen, my_trans_id = self._db._get_generation_info()
493+ return (
494+ self._db._replica_uid, my_gen, my_trans_id, source_gen,
495+ source_trans_id)
496
497 def record_sync_info(self, source_replica_uid, source_replica_generation,
498 source_transaction_id):
499
500=== modified file 'u1db/backends/sqlite_backend.py'
501--- u1db/backends/sqlite_backend.py 2012-07-03 13:50:27 +0000
502+++ u1db/backends/sqlite_backend.py 2012-07-06 20:51:26 +0000
503@@ -271,12 +271,12 @@
504 'SELECT max(generation), transaction_id FROM transaction_log ')
505 val = c.fetchone()
506 if val[0] is None:
507- return(0, val[1])
508+ return(0, '')
509 return val
510
511- def validate_gen_and_trans_id(self, generation, trans_id):
512+ def _get_trans_id_for_gen(self, generation):
513 if generation == 0:
514- return
515+ return ''
516 c = self._db_handle.cursor()
517 c.execute(
518 'SELECT transaction_id FROM transaction_log WHERE generation = ?',
519@@ -284,8 +284,7 @@
520 val = c.fetchone()
521 if val is None:
522 raise errors.InvalidGeneration
523- if val[0] != trans_id:
524- raise errors.InvalidTransactionId
525+ return val[0]
526
527 def _get_transaction_log(self):
528 c = self._db_handle.cursor()
529@@ -788,10 +787,12 @@
530 class SQLiteSyncTarget(CommonSyncTarget):
531
532 def get_sync_info(self, source_replica_uid):
533- source_gen, trans_id = self._db._get_replica_gen_and_trans_id(
534+ source_gen, source_trans_id = self._db._get_replica_gen_and_trans_id(
535 source_replica_uid)
536- my_gen = self._db._get_generation()
537- return self._db._replica_uid, my_gen, source_gen, trans_id
538+ my_gen, my_trans_id = self._db._get_generation_info()
539+ return (
540+ self._db._replica_uid, my_gen, my_trans_id, source_gen,
541+ source_trans_id)
542
543 def record_sync_info(self, source_replica_uid, source_replica_generation,
544 source_replica_transaction_id):
545
546=== modified file 'u1db/remote/http_app.py'
547--- u1db/remote/http_app.py 2012-07-03 15:48:59 +0000
548+++ u1db/remote/http_app.py 2012-07-06 20:51:26 +0000
549@@ -309,9 +309,10 @@
550 result = self.target.get_sync_info(self.source_replica_uid)
551 self.responder.send_response_json(
552 target_replica_uid=result[0], target_replica_generation=result[1],
553+ target_replica_transaction_id=result[2],
554 source_replica_uid=self.source_replica_uid,
555- source_replica_generation=result[2],
556- source_transaction_id=result[3])
557+ source_replica_generation=result[3],
558+ source_transaction_id=result[4])
559
560 @http_method(generation=int,
561 content_as_args=True, no_query=True)
562
563=== modified file 'u1db/remote/http_target.py'
564--- u1db/remote/http_target.py 2012-06-19 02:35:49 +0000
565+++ u1db/remote/http_target.py 2012-07-06 20:51:26 +0000
566@@ -42,6 +42,7 @@
567 self._ensure_connection()
568 res, _ = self._request_json('GET', ['sync-from', source_replica_uid])
569 return (res['target_replica_uid'], res['target_replica_generation'],
570+ res['target_replica_transaction_id'],
571 res['source_replica_generation'], res['source_transaction_id'])
572
573 def record_sync_info(self, source_replica_uid, source_replica_generation,
574
575=== modified file 'u1db/sync.py'
576--- u1db/sync.py 2012-07-03 15:48:59 +0000
577+++ u1db/sync.py 2012-07-06 20:51:26 +0000
578@@ -18,6 +18,7 @@
579 from itertools import izip
580
581 import u1db
582+from u1db import errors
583
584
585 class Synchronizer(object):
586@@ -94,21 +95,21 @@
587 sync_target = self.sync_target
588 # get target identifier, its current generation,
589 # and its last-seen database generation for this source
590- (self.target_replica_uid, target_gen, target_my_gen,
591+ (self.target_replica_uid, target_gen, target_trans_id, target_my_gen,
592 target_my_trans_id) = sync_target.get_sync_info(
593 self.source._replica_uid)
594- # validate that the generation and transaction id the target knows
595- # about us are valid.
596+ # validate the generation and transaction id the target knows about us
597 self.source.validate_gen_and_trans_id(
598 target_my_gen, target_my_trans_id)
599 # what's changed since that generation and this current gen
600 my_gen, _, changes = self.source.whats_changed(target_my_gen)
601
602 # this source last-seen database generation for the target
603- (target_last_known_gen,
604- target_trans_id) = self.source._get_replica_gen_and_trans_id(
605- self.target_replica_uid)
606+ target_last_known_gen, target_last_known_trans_id = \
607+ self.source._get_replica_gen_and_trans_id(self.target_replica_uid)
608 if not changes and target_last_known_gen == target_gen:
609+ if target_trans_id != target_last_known_trans_id:
610+ raise errors.InvalidTransactionId
611 return my_gen
612 changed_doc_ids = [doc_id for doc_id, _, _ in changes]
613 # prepare to send all the changed docs
614@@ -123,7 +124,7 @@
615 # the target, return target synced-up-to gen
616 new_gen, new_trans_id = sync_target.sync_exchange(
617 docs_by_generation, self.source._replica_uid,
618- target_last_known_gen, target_trans_id,
619+ target_last_known_gen, target_last_known_trans_id,
620 self._insert_doc_from_target)
621 # record target synced-up-to generation including applying what we sent
622 self.source._set_replica_gen_and_trans_id(
623
624=== modified file 'u1db/tests/c_backend_wrapper.pyx'
625--- u1db/tests/c_backend_wrapper.pyx 2012-07-03 15:48:59 +0000
626+++ u1db/tests/c_backend_wrapper.pyx 2012-07-06 20:51:26 +0000
627@@ -81,9 +81,7 @@
628 void *context, u1db_doc_callback cb)
629 int u1db_put_doc(u1database *db, u1db_document *doc)
630 int u1db__validate_source(u1database *db, const_char_ptr replica_uid,
631- int replica_gen, const_char_ptr replica_trans_id,
632- u1db_vectorclock *cur_vcr,
633- u1db_vectorclock *other_vcr, int *state)
634+ int replica_gen, const_char_ptr replica_trans_id)
635 int u1db__put_doc_if_newer(u1database *db, u1db_document *doc,
636 int save_conflict, char *replica_uid,
637 int replica_gen, char *replica_trans_id,
638@@ -175,10 +173,10 @@
639
640 ctypedef int (*u1db__trace_callback)(void *context, const_char_ptr state)
641 ctypedef struct u1db_sync_target:
642- int (*get_sync_info)(u1db_sync_target *st,
643- char *source_replica_uid,
644- const_char_ptr *st_replica_uid, int *st_gen, int *source_gen,
645- char **source_trans_id) nogil
646+ int (*get_sync_info)(u1db_sync_target *st, char *source_replica_uid,
647+ const_char_ptr *st_replica_uid, int *st_gen,
648+ char **st_trans_id, int *source_gen,
649+ char **source_trans_id) nogil
650 int (*record_sync_info)(u1db_sync_target *st,
651 char *source_replica_uid, int source_gen, char *trans_id) nogil
652 int (*sync_exchange)(u1db_sync_target *st,
653@@ -206,6 +204,7 @@
654
655 int u1db__get_generation(u1database *, int *db_rev)
656 int u1db__get_generation_info(u1database *, int *db_rev, char **trans_id)
657+ int u1db__get_trans_id_for_gen(u1database *, int db_rev, char **trans_id)
658 int u1db_validate_gen_and_trans_id(u1database *, int db_rev,
659 const_char_ptr trans_id)
660 char *u1db__allocate_doc_id(u1database *)
661@@ -698,18 +697,25 @@
662 cdef const_char_ptr st_replica_uid = NULL
663 cdef int st_gen = 0, source_gen = 0, status
664 cdef char *trans_id = NULL
665+ cdef char *st_trans_id = NULL
666
667 self._check()
668 assert self._st.get_sync_info != NULL, "get_sync_info is NULL?"
669 with nogil:
670 status = self._st.get_sync_info(self._st, source_replica_uid,
671- &st_replica_uid, &st_gen, &source_gen, &trans_id)
672+ &st_replica_uid, &st_gen, &st_trans_id, &source_gen, &trans_id)
673 handle_status("get_sync_info", status)
674 res_trans_id = None
675+ res_st_trans_id = None
676 if trans_id != NULL:
677 res_trans_id = trans_id
678 free(trans_id)
679- return (safe_str(st_replica_uid), st_gen, source_gen, res_trans_id)
680+ if st_trans_id != NULL:
681+ res_st_trans_id = st_trans_id
682+ free(st_trans_id)
683+ return (
684+ safe_str(st_replica_uid), st_gen, res_st_trans_id, source_gen,
685+ res_trans_id)
686
687 def record_sync_info(self, source_replica_uid, source_gen, source_trans_id):
688 cdef int status
689@@ -941,26 +947,16 @@
690 u1db_put_doc(self._db, doc._doc))
691 return doc.rev
692
693- def _validate_source(self, replica_uid, replica_gen, replica_trans_id,
694- cur_vcr, other_vcr):
695+ def _validate_source(self, replica_uid, replica_gen, replica_trans_id):
696 cdef const_char_ptr c_uid, c_trans_id
697- cdef int c_gen, state = 0
698- cdef VectorClockRev cur
699- cdef VectorClockRev other
700+ cdef int c_gen = 0
701
702- cur = VectorClockRev(cur_vcr.as_str())
703- other = VectorClockRev(other_vcr.as_str())
704 c_uid = replica_uid
705 c_trans_id = replica_trans_id
706 c_gen = replica_gen
707 handle_status(
708 "invalid generation or transaction id",
709- u1db__validate_source(
710- self._db, c_uid, c_gen, c_trans_id, cur._clock, other._clock,
711- &state))
712- if state == U1DB_SUPERSEDED:
713- return 'superseded'
714- return 'ok'
715+ u1db__validate_source(self._db, c_uid, c_gen, c_trans_id))
716
717 def _put_doc_if_newer(self, CDocument doc, save_conflict, replica_uid=None,
718 replica_gen=None, replica_trans_id=None):
719@@ -1100,6 +1096,18 @@
720 "validate_gen_and_trans_id",
721 u1db_validate_gen_and_trans_id(self._db, generation, trans_id))
722
723+ def _get_trans_id_for_gen(self, generation):
724+ cdef char *trans_id = NULL
725+
726+ handle_status(
727+ "_get_trans_id_for_gen",
728+ u1db__get_trans_id_for_gen(self._db, generation, &trans_id))
729+ raw_trans_id = None
730+ if trans_id != NULL:
731+ raw_trans_id = trans_id
732+ free(trans_id)
733+ return raw_trans_id
734+
735 def _get_replica_gen_and_trans_id(self, replica_uid):
736 cdef int generation, status
737 cdef char *trans_id = NULL
738
739=== modified file 'u1db/tests/test_backends.py'
740--- u1db/tests/test_backends.py 2012-07-04 15:43:00 +0000
741+++ u1db/tests/test_backends.py 2012-07-06 20:51:26 +0000
742@@ -402,11 +402,12 @@
743
744 def test_put_doc_if_newer_same_generation_same_txid(self):
745 self.db._set_replica_gen_and_trans_id('other', 1, 'T-sid')
746- doc = self.make_document('doc_id', 'other:2', simple_doc)
747+ doc = self.db.create_doc(simple_doc)
748+ doc2 = self.make_document(doc.doc_id, 'other:1', simple_doc)
749 state, _ = self.db._put_doc_if_newer(
750 doc, save_conflict=False, replica_uid='other', replica_gen=1,
751 replica_trans_id='T-sid')
752- self.assertEqual('superseded', state)
753+ self.assertEqual('converged', state)
754
755 def test_put_doc_if_newer_wrong_transaction_id(self):
756 self.db._set_replica_gen_and_trans_id('other', 1, 'T-sid')
757@@ -422,10 +423,10 @@
758 doc_rev1 = doc.rev
759 doc.set_json(simple_doc)
760 self.db.put_doc(doc)
761- self.db._set_replica_gen_and_trans_id('other', 5, 'T-sid')
762+ self.db._set_replica_gen_and_trans_id('other', 3, 'T-sid')
763 older_doc = self.make_document(doc.doc_id, doc_rev1, simple_doc)
764 state, _ = self.db._put_doc_if_newer(
765- older_doc, save_conflict=False, replica_uid='other', replica_gen=3,
766+ older_doc, save_conflict=False, replica_uid='other', replica_gen=8,
767 replica_trans_id='T-irrelevant')
768 self.assertEqual('superseded', state)
769
770@@ -557,45 +558,17 @@
771
772 def test_validate_source_gen_and_trans_id_same(self):
773 self.db._set_replica_gen_and_trans_id('other', 1, 'T-sid')
774- v1 = vectorclock.VectorClockRev('other:1|self:1')
775- v2 = vectorclock.VectorClockRev('other:1|self:1')
776- self.assertEqual(
777- 'superseded',
778- self.db._validate_source('other', 1, 'T-sid', v1, v2))
779+ self.db._validate_source('other', 1, 'T-sid')
780
781 def test_validate_source_gen_newer(self):
782 self.db._set_replica_gen_and_trans_id('other', 1, 'T-sid')
783- v1 = vectorclock.VectorClockRev('other:1|self:1')
784- v2 = vectorclock.VectorClockRev('other:2|self:2')
785- self.assertEqual(
786- 'ok',
787- self.db._validate_source('other', 2, 'T-whatevs', v1, v2))
788+ self.db._validate_source('other', 2, 'T-whatevs')
789
790 def test_validate_source_wrong_txid(self):
791 self.db._set_replica_gen_and_trans_id('other', 1, 'T-sid')
792- v1 = vectorclock.VectorClockRev('other:1|self:1')
793- v2 = vectorclock.VectorClockRev('other:2|self:2')
794 self.assertRaises(
795 errors.InvalidTransactionId,
796- self.db._validate_source, 'other', 1, 'T-sad', v1, v2)
797-
798- def test_validate_source_gen_older_and_vcr_older(self):
799- self.db._set_replica_gen_and_trans_id('other', 1, 'T-sid')
800- self.db._set_replica_gen_and_trans_id('other', 2, 'T-sod')
801- v1 = vectorclock.VectorClockRev('other:1|self:1')
802- v2 = vectorclock.VectorClockRev('other:2|self:2')
803- self.assertEqual(
804- 'superseded',
805- self.db._validate_source('other', 1, 'T-sid', v2, v1))
806-
807- def test_validate_source_gen_older_vcr_newer(self):
808- self.db._set_replica_gen_and_trans_id('other', 1, 'T-sid')
809- self.db._set_replica_gen_and_trans_id('other', 2, 'T-sod')
810- v1 = vectorclock.VectorClockRev('other:1|self:1')
811- v2 = vectorclock.VectorClockRev('other:2|self:2')
812- self.assertRaises(
813- errors.InvalidGeneration,
814- self.db._validate_source, 'other', 1, 'T-sid', v1, v2)
815+ self.db._validate_source, 'other', 1, 'T-sad')
816
817
818 class LocalDatabaseWithConflictsTests(tests.DatabaseBaseTests):
819
820=== modified file 'u1db/tests/test_c_backend.py'
821--- u1db/tests/test_c_backend.py 2012-07-03 15:48:59 +0000
822+++ u1db/tests/test_c_backend.py 2012-07-06 20:51:26 +0000
823@@ -75,7 +75,7 @@
824
825 def test__get_generation_info(self):
826 db = c_backend_wrapper.CDatabase(':memory:')
827- self.assertEqual((0, None), db._get_generation_info())
828+ self.assertEqual((0, ''), db._get_generation_info())
829 db.create_doc(tests.simple_doc)
830 info = db._get_generation_info()
831 self.assertEqual(1, info[0])
832
833=== modified file 'u1db/tests/test_http_app.py'
834--- u1db/tests/test_http_app.py 2012-07-03 15:48:59 +0000
835+++ u1db/tests/test_http_app.py 2012-07-06 20:51:26 +0000
836@@ -697,6 +697,7 @@
837 self.assertEqual('application/json', resp.header('content-type'))
838 self.assertEqual(dict(target_replica_uid='db0',
839 target_replica_generation=0,
840+ target_replica_transaction_id='',
841 source_replica_uid='other-id',
842 source_replica_generation=1,
843 source_transaction_id='T-transid'),
844@@ -722,7 +723,8 @@
845 }
846
847 gens = []
848- _do_set_replica_gen_and_trans_id = self.db0._do_set_replica_gen_and_trans_id
849+ _do_set_replica_gen_and_trans_id = \
850+ self.db0._do_set_replica_gen_and_trans_id
851
852 def set_sync_generation_witness(other_uid, other_gen, other_trans_id):
853 gens.append((other_uid, other_gen))
854
855=== modified file 'u1db/tests/test_remote_sync_target.py'
856--- u1db/tests/test_remote_sync_target.py 2012-07-03 15:48:59 +0000
857+++ u1db/tests/test_remote_sync_target.py 2012-07-06 20:51:26 +0000
858@@ -173,7 +173,7 @@
859 db = self.request_state._create_database('test')
860 db._set_replica_gen_and_trans_id('other-id', 1, 'T-transid')
861 remote_target = self.getSyncTarget('test')
862- self.assertEqual(('test', 0, 1, 'T-transid'),
863+ self.assertEqual(('test', 0, '', 1, 'T-transid'),
864 remote_target.get_sync_info('other-id'))
865
866 def test_record_sync_info(self):
867
868=== modified file 'u1db/tests/test_sqlite_backend.py'
869--- u1db/tests/test_sqlite_backend.py 2012-06-15 19:41:07 +0000
870+++ u1db/tests/test_sqlite_backend.py 2012-07-06 20:51:26 +0000
871@@ -153,7 +153,7 @@
872 self.assertEqual(0, self.db._get_generation())
873
874 def test__get_generation_info(self):
875- self.assertEqual((0, None), self.db._get_generation_info())
876+ self.assertEqual((0, ''), self.db._get_generation_info())
877
878 def test_create_index(self):
879 self.db.create_index('test-idx', "key")
880
881=== modified file 'u1db/tests/test_sync.py'
882--- u1db/tests/test_sync.py 2012-07-04 16:03:39 +0000
883+++ u1db/tests/test_sync.py 2012-07-06 20:51:26 +0000
884@@ -143,18 +143,19 @@
885 self.assertIsNot(None, self.st)
886
887 def test_get_sync_info(self):
888- self.assertEqual(('test', 0, 0, ''), self.st.get_sync_info('other'))
889+ self.assertEqual(
890+ ('test', 0, '', 0, ''), self.st.get_sync_info('other'))
891
892 def test_create_doc_updates_sync_info(self):
893- self.assertEqual(('test', 0, 0, ''), self.st.get_sync_info('other'))
894+ self.assertEqual(
895+ ('test', 0, '', 0, ''), self.st.get_sync_info('other'))
896 self.db.create_doc(simple_doc)
897- self.assertEqual(('test', 1, 0, ''), self.st.get_sync_info('other'))
898+ self.assertEqual(1, self.st.get_sync_info('other')[1])
899
900 def test_record_sync_info(self):
901- self.assertEqual(('test', 0, 0, ''), self.st.get_sync_info('replica'))
902 self.st.record_sync_info('replica', 10, 'T-transid')
903- self.assertEqual(('test', 0, 10, 'T-transid'),
904- self.st.get_sync_info('replica'))
905+ self.assertEqual(
906+ ('test', 0, '', 10, 'T-transid'), self.st.get_sync_info('replica'))
907
908 def test_sync_exchange(self):
909 docs_by_gen = [
910@@ -168,7 +169,7 @@
911 last_trans_id = self.getLastTransId(self.db)
912 self.assertEqual(([], 1, last_trans_id),
913 (self.other_changes, new_gen, last_trans_id))
914- self.assertEqual(10, self.st.get_sync_info('replica')[2])
915+ self.assertEqual(10, self.st.get_sync_info('replica')[3])
916
917 def test_sync_exchange_deleted(self):
918 doc = self.db.create_doc('{}')
919@@ -184,7 +185,7 @@
920 last_trans_id = self.getLastTransId(self.db)
921 self.assertEqual(([], 2, last_trans_id),
922 (self.other_changes, new_gen, trans_id))
923- self.assertEqual(10, self.st.get_sync_info('replica')[2])
924+ self.assertEqual(10, self.st.get_sync_info('replica')[3])
925
926 def test_sync_exchange_push_many(self):
927 docs_by_gen = [
928@@ -200,7 +201,7 @@
929 last_trans_id = self.getLastTransId(self.db)
930 self.assertEqual(([], 2, last_trans_id),
931 (self.other_changes, new_gen, trans_id))
932- self.assertEqual(11, self.st.get_sync_info('replica')[2])
933+ self.assertEqual(11, self.st.get_sync_info('replica')[3])
934
935 def test_sync_exchange_refuses_conflicts(self):
936 doc = self.db.create_doc(simple_doc)
937@@ -378,7 +379,7 @@
938 last_trans_id = self.getLastTransId(self.db)
939 self.assertEqual(([], 1, last_trans_id),
940 (self.other_changes, new_gen, trans_id))
941- self.assertEqual(10, self.st.get_sync_info(db2._replica_uid)[2])
942+ self.assertEqual(10, self.st.get_sync_info(db2._replica_uid)[3])
943
944 def test__set_trace_hook(self):
945 called = []
946@@ -424,7 +425,8 @@
947 return db
948
949
950-def sync_via_synchronizer_and_http(test, db_source, db_target, trace_hook=None):
951+def sync_via_synchronizer_and_http(test, db_source, db_target,
952+ trace_hook=None):
953 if trace_hook:
954 test.skipTest("trace_hook unsupported over http")
955 path = test._http_at[db_target]
956@@ -988,6 +990,24 @@
957 self.assertRaises(
958 errors.InvalidTransactionId, self.sync, self.db1, db3)
959
960+ def test_sync_detects_rollback_and_divergence_in_source(self):
961+ self.db1.create_doc(tests.simple_doc, doc_id="divergent")
962+ self.sync(self.db1, self.db2)
963+ self.db1.create_doc(tests.simple_doc)
964+ self.db2._set_replica_gen_and_trans_id(
965+ self.db1._replica_uid, 2, 'T-madeup')
966+ self.assertRaises(
967+ errors.InvalidTransactionId, self.sync, self.db1, self.db2)
968+
969+ def test_sync_detects_rollback_and_divergence_in_target(self):
970+ self.db1.create_doc(tests.simple_doc, doc_id="divergent")
971+ self.sync(self.db1, self.db2)
972+ self.db2.create_doc(tests.simple_doc)
973+ self.db1._set_replica_gen_and_trans_id(
974+ self.db2._replica_uid, 2, 'T-madeup')
975+ self.assertRaises(
976+ errors.InvalidTransactionId, self.sync, self.db1, self.db2)
977+
978
979 class TestDbSync(tests.TestCaseWithServer):
980 """Test db.sync remote sync shortcut"""
981@@ -1032,7 +1052,8 @@
982 self.assertEqual(2, len(self.db2._get_transaction_log()))
983 progress1 = []
984 progress2 = []
985- _do_set_replica_gen_and_trans_id = self.db1._do_set_replica_gen_and_trans_id
986+ _do_set_replica_gen_and_trans_id = \
987+ self.db1._do_set_replica_gen_and_trans_id
988
989 def set_sync_generation_witness1(other_uid, other_gen, trans_id):
990 progress1.append((other_uid, other_gen,
991@@ -1040,8 +1061,8 @@
992 _do_set_replica_gen_and_trans_id(other_uid, other_gen, trans_id)
993 self.patch(self.db1, '_do_set_replica_gen_and_trans_id',
994 set_sync_generation_witness1)
995-
996- _do_set_replica_gen_and_trans_id2 = self.db2._do_set_replica_gen_and_trans_id
997+ _do_set_replica_gen_and_trans_id2 = \
998+ self.db2._do_set_replica_gen_and_trans_id
999
1000 def set_sync_generation_witness2(other_uid, other_gen, trans_id):
1001 progress2.append((other_uid, other_gen,

Subscribers

People subscribed via source and target branches