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

Proposed by Eric Casteleijn
Status: Merged
Approved by: Eric Casteleijn
Approved revision: 346
Merged at revision: 344
Proposed branch: lp:~thisfred/u1db/sync_integration_tests
Merge into: lp:u1db
Diff against target: 899 lines (+226/-130)
18 files modified
.bzrignore (+6/-0)
include/u1db/u1db_internal.h (+2/-2)
src/u1db.c (+1/-1)
src/u1db_http_sync_target.c (+8/-5)
src/u1db_sync_target.c (+23/-27)
u1db/__init__.py (+5/-2)
u1db/backends/inmemory.py (+4/-0)
u1db/errors.py (+4/-0)
u1db/remote/http_app.py (+15/-6)
u1db/remote/http_errors.py (+2/-0)
u1db/remote/http_target.py (+5/-2)
u1db/sync.py (+13/-7)
u1db/tests/c_backend_wrapper.pyx (+25/-17)
u1db/tests/test_backends.py (+1/-1)
u1db/tests/test_c_backend.py (+1/-1)
u1db/tests/test_http_app.py (+2/-1)
u1db/tests/test_remote_sync_target.py (+16/-16)
u1db/tests/test_sync.py (+93/-42)
To merge this branch: bzr merge lp:~thisfred/u1db/sync_integration_tests
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+111278@code.launchpad.net

Commit message

Added integration tests for the detection of a diverged source or target db during sync and made them pass.

Description of the change

Added integration tests for the detection of a diverged source or target db during sync and made them pass.

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

shoot, something's still not right with sync_exchange_doc_ids

Revision history for this message
Eric Casteleijn (thisfred) wrote :

fixed

Revision history for this message
John O'Brien (jdobrien) wrote :

I'm completely unqualified to mark this approved. but the changes look sound to me and I really like the formatting cleanup.

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 6/20/2012 9:27 PM, Eric Casteleijn wrote:
> Eric Casteleijn has proposed merging
> lp:~thisfred/u1db/sync_integration_tests into lp:u1db.
>
> Requested reviews: Ubuntu One hackers (ubuntuone-hackers) Related
> bugs: Bug #1006872 in U1DB: "sync_exchange should transmit the txid
> it thinks the target was at"
> https://bugs.launchpad.net/u1db/+bug/1006872
>
> For more details, see:
> https://code.launchpad.net/~thisfred/u1db/sync_integration_tests/+merge/111278
>
> Added integration tests for the detection of a diverged source or
> target db during sync and made them pass.
>

+ def test_sync_detects_rollback_in_source(self):
+ self.db1.create_doc(tests.simple_doc, doc_id="divergent")
+ self.sync(self.db1, self.db2)
+ # make db2 think it's synced with a much later version of db1
+ self.db2._set_sync_info(self.db1._replica_uid, 28, 'T-madeup')
+ self.assertRaises(
+ errors.InvalidGeneration, self.sync, self.db1, self.db2)

^- I think this is something that we eventually wanted to support, as
long as there wasn't a local change on source.

Otherwise, the diverged tests are certainly valid.

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk/kIZsACgkQJdeBCYSNAAPCqQCgy7rzs2uAYMKIZMGKzVyiHrcZ
O7kAn1apHU4pnrBBhC0vNqWysVe+ny7g
=6nPi
-----END PGP SIGNATURE-----

Revision history for this message
Eric Casteleijn (thisfred) wrote :

I think we should as well, but we need more information to do it: all this says is that the target knows a newer generation than the source knows of itself. That still doesn't mean it hasn't diverged. I believe we probably can just start the sync though, and put_doc_if_newer would complain at the point of divergence if any. What I need to think about though is how to determine which generation to start the sync from source to target at. Can we do better than 0 there?

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

as discussed _validate_source isn't triggered:

https://pastebin.canonical.com/69190/

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Ah, no: The code is triggered (just put a print in there) but all it does is shortcut the put_doc_if_newer, because what the target has is already strictly newer (this is the case where _validate_source returns 'superseded' rather than ok.) In the case where something is really wrong we raise an InvalidTransactionId, and that code is also exercised by the tests.

I'll try and change the relevant tests so that they detect that the put_doc_if_newer is broken off early, although I'm not sure that it's important, since it should not have any effect when it isn't (and indeed doesn't seem to, as your commenting out the check shows.)

342. By Eric Casteleijn

merged trunk, and fixed conflicts

343. By Eric Casteleijn

ignore

344. By Eric Casteleijn

fixed errors

345. By Eric Casteleijn

fixed errors

346. By Eric Casteleijn

merged lp:~pedronis/u1db/sync_integration_tests

Revision history for this message
Samuele Pedroni (pedronis) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2012-06-29 17:00:49 +0000
+++ .bzrignore 2012-07-04 16:16:18 +0000
@@ -15,3 +15,9 @@
15src/CMakeFiles15src/CMakeFiles
16src/Makefile16src/Makefile
17src/cmake_install.cmake17src/cmake_install.cmake
18CMakeCache.txt
19CMakeFiles
20CPackConfig.cmake
21CPackSourceConfig.cmake
22Makefile
23cmake_install.cmake
1824
=== modified file 'include/u1db/u1db_internal.h'
--- include/u1db/u1db_internal.h 2012-07-03 13:50:27 +0000
+++ include/u1db/u1db_internal.h 2012-07-04 16:16:18 +0000
@@ -127,8 +127,8 @@
127 const char *source_replica_uid, int n_docs,127 const char *source_replica_uid, int n_docs,
128 u1db_document **docs, int *generations,128 u1db_document **docs, int *generations,
129 const char **trans_ids, int *target_gen,129 const char **trans_ids, int *target_gen,
130 char **target_trans_id,130 char **target_trans_id, void *context,
131 void *context, u1db_doc_gen_callback cb);131 u1db_doc_gen_callback cb);
132 /**132 /**
133 * Create a sync_exchange state object.133 * Create a sync_exchange state object.
134 *134 *
135135
=== modified file 'src/u1db.c'
--- src/u1db.c 2012-07-03 13:50:27 +0000
+++ src/u1db.c 2012-07-04 16:16:18 +0000
@@ -1618,7 +1618,7 @@
1618{1618{
1619 int status;1619 int status;
1620 sqlite3_stmt *statement;1620 sqlite3_stmt *statement;
1621 const char *tmp;1621 const char *tmp = NULL;
16221622
1623 if (db == NULL || replica_uid == NULL || generation == NULL1623 if (db == NULL || replica_uid == NULL || generation == NULL
1624 || trans_id == NULL)1624 || trans_id == NULL)
16251625
=== modified file 'src/u1db_http_sync_target.c'
--- src/u1db_http_sync_target.c 2012-06-27 18:56:17 +0000
+++ src/u1db_http_sync_target.c 2012-07-04 16:16:18 +0000
@@ -773,7 +773,8 @@
773773
774774
775static int775static int
776init_temp_file(char tmpname[], FILE **temp_fd, int target_gen)776init_temp_file(char tmpname[], FILE **temp_fd, int target_gen,
777 char *target_trans_id)
777{778{
778 int status = U1DB_OK;779 int status = U1DB_OK;
779 *temp_fd = make_tempfile(tmpname);780 *temp_fd = make_tempfile(tmpname);
@@ -786,12 +787,14 @@
786 }787 }
787 // Spool all of the documents to a temporary file, so that it we can788 // Spool all of the documents to a temporary file, so that it we can
788 // determine Content-Length before we start uploading the data.789 // determine Content-Length before we start uploading the data.
789 fprintf(*temp_fd, "[\r\n{\"last_known_generation\": %d}", target_gen);790 fprintf(
791 *temp_fd,
792 "[\r\n{\"last_known_generation\": %d, \"last_known_trans_id\": \"%s\"}",
793 target_gen, target_trans_id);
790finish:794finish:
791 return status;795 return status;
792}796}
793797
794
795static int798static int
796finalize_and_send_temp_file(u1db_sync_target *st, FILE *temp_fd,799finalize_and_send_temp_file(u1db_sync_target *st, FILE *temp_fd,
797 const char *source_replica_uid,800 const char *source_replica_uid,
@@ -981,7 +984,7 @@
981 if (n_docs > 0 && (docs == NULL || generations == NULL)) {984 if (n_docs > 0 && (docs == NULL || generations == NULL)) {
982 return U1DB_INVALID_PARAMETER;985 return U1DB_INVALID_PARAMETER;
983 }986 }
984 status = init_temp_file(tmpname, &temp_fd, *target_gen);987 status = init_temp_file(tmpname, &temp_fd, *target_gen, *target_trans_id);
985 if (status != U1DB_OK) { goto finish; }988 if (status != U1DB_OK) { goto finish; }
986 for (i = 0; i < n_docs; ++i) {989 for (i = 0; i < n_docs; ++i) {
987 status = doc_to_tempfile(990 status = doc_to_tempfile(
@@ -1054,7 +1057,7 @@
1054 }1057 }
1055 status = u1db_get_replica_uid(source_db, &source_replica_uid);1058 status = u1db_get_replica_uid(source_db, &source_replica_uid);
1056 if (status != U1DB_OK) { goto finish; }1059 if (status != U1DB_OK) { goto finish; }
1057 status = init_temp_file(tmpname, &temp_fd, *target_gen);1060 status = init_temp_file(tmpname, &temp_fd, *target_gen, *target_trans_id);
1058 if (status != U1DB_OK) { goto finish; }1061 if (status != U1DB_OK) { goto finish; }
1059 state.num = n_doc_ids;1062 state.num = n_doc_ids;
1060 state.generations = generations;1063 state.generations = generations;
10611064
=== modified file 'src/u1db_sync_target.c'
--- src/u1db_sync_target.c 2012-07-03 13:50:27 +0000
+++ src/u1db_sync_target.c 2012-07-04 16:16:18 +0000
@@ -30,11 +30,11 @@
30 const char *source_replica_uid, int source_gen, const char *trans_id);30 const char *source_replica_uid, int source_gen, const char *trans_id);
3131
32static int st_sync_exchange(u1db_sync_target *st,32static int st_sync_exchange(u1db_sync_target *st,
33 const char *source_replica_uid, int n_docs,33 const char *source_replica_uid, int n_docs,
34 u1db_document **docs, int *generations,34 u1db_document **docs, int *generations,
35 const char **trans_ids, int *target_gen,35 const char **trans_ids, int *target_gen,
36 char **target_trans_id, void *context,36 char **target_trans_id, void *context,
37 u1db_doc_gen_callback cb);37 u1db_doc_gen_callback cb);
38static int st_sync_exchange_doc_ids(u1db_sync_target *st,38static int st_sync_exchange_doc_ids(u1db_sync_target *st,
39 u1database *source_db, int n_doc_ids,39 u1database *source_db, int n_doc_ids,
40 const char **doc_ids, int *generations,40 const char **doc_ids, int *generations,
@@ -370,7 +370,6 @@
370{370{
371 int status;371 int status;
372 struct _whats_changed_doc_ids_state state = {0};372 struct _whats_changed_doc_ids_state state = {0};
373 char *target_trans_id = NULL;
374 if (se == NULL) {373 if (se == NULL) {
375 return U1DB_INVALID_PARAMETER;374 return U1DB_INVALID_PARAMETER;
376 }375 }
@@ -396,9 +395,6 @@
396 se->gen_for_doc_ids = state.gen_for_doc_ids;395 se->gen_for_doc_ids = state.gen_for_doc_ids;
397 se->trans_ids_for_doc_ids = state.trans_ids_for_doc_ids;396 se->trans_ids_for_doc_ids = state.trans_ids_for_doc_ids;
398finish:397finish:
399 if (target_trans_id != NULL) {
400 free(target_trans_id);
401 }
402 return status;398 return status;
403}399}
404400
@@ -522,6 +518,9 @@
522 status = st->get_sync_exchange(st, source_replica_uid,518 status = st->get_sync_exchange(st, source_replica_uid,
523 *target_gen, &exchange);519 *target_gen, &exchange);
524 if (status != U1DB_OK) { goto finish; }520 if (status != U1DB_OK) { goto finish; }
521 status = u1db_validate_gen_and_trans_id(
522 exchange->db, *target_gen, *target_trans_id);
523 if (status != U1DB_OK) { goto finish; }
525 for (i = 0; i < n_docs; ++i) {524 for (i = 0; i < n_docs; ++i) {
526 status = u1db__sync_exchange_insert_doc_from_source(525 status = u1db__sync_exchange_insert_doc_from_source(
527 exchange, docs[i], generations[i], trans_ids[i]);526 exchange, docs[i], generations[i], trans_ids[i]);
@@ -530,14 +529,11 @@
530 status = u1db__sync_exchange_find_doc_ids_to_return(exchange);529 status = u1db__sync_exchange_find_doc_ids_to_return(exchange);
531 if (status != U1DB_OK) { goto finish; }530 if (status != U1DB_OK) { goto finish; }
532 status = u1db__sync_exchange_return_docs(exchange, context, cb);531 status = u1db__sync_exchange_return_docs(exchange, context, cb);
533 if (status != U1DB_OK) { goto finish; }
534 if (status == U1DB_OK) {
535 *target_gen = exchange->target_gen;
536 *target_trans_id = exchange->target_trans_id;
537 // We set this to NULL, because the caller is now responsible for it
538 exchange->target_trans_id = NULL;
539 }
540finish:532finish:
533 *target_gen = exchange->target_gen;
534 *target_trans_id = exchange->target_trans_id;
535 // We set this to NULL, because the caller is now responsible for it
536 exchange->target_trans_id = NULL;
541 st->finalize_sync_exchange(st, &exchange);537 st->finalize_sync_exchange(st, &exchange);
542 return status;538 return status;
543}539}
@@ -565,6 +561,9 @@
565 status = st->get_sync_exchange(st, source_replica_uid,561 status = st->get_sync_exchange(st, source_replica_uid,
566 *target_gen, &exchange);562 *target_gen, &exchange);
567 if (status != U1DB_OK) { goto finish; }563 if (status != U1DB_OK) { goto finish; }
564 status = u1db_validate_gen_and_trans_id(
565 exchange->db, *target_gen, *target_trans_id);
566 if (status != U1DB_OK) { goto finish; }
568 if (n_doc_ids > 0) {567 if (n_doc_ids > 0) {
569 status = get_and_insert_docs(source_db, exchange,568 status = get_and_insert_docs(source_db, exchange,
570 n_doc_ids, doc_ids, generations, trans_ids);569 n_doc_ids, doc_ids, generations, trans_ids);
@@ -573,15 +572,11 @@
573 status = u1db__sync_exchange_find_doc_ids_to_return(exchange);572 status = u1db__sync_exchange_find_doc_ids_to_return(exchange);
574 if (status != U1DB_OK) { goto finish; }573 if (status != U1DB_OK) { goto finish; }
575 status = u1db__sync_exchange_return_docs(exchange, context, cb);574 status = u1db__sync_exchange_return_docs(exchange, context, cb);
576 if (status != U1DB_OK) { goto finish; }575finish:
577 *target_gen = exchange->target_gen;576 *target_gen = exchange->target_gen;
578 if (status == U1DB_OK) {577 *target_trans_id = exchange->target_trans_id;
579 *target_gen = exchange->target_gen;578 // We set this to NULL, because the caller is now responsible for it
580 *target_trans_id = exchange->target_trans_id;579 exchange->target_trans_id = NULL;
581 // We set this to NULL, because the caller is now responsible for it
582 exchange->target_trans_id = NULL;
583 }
584finish:
585 st->finalize_sync_exchange(st, &exchange);580 st->finalize_sync_exchange(st, &exchange);
586 return status;581 return status;
587}582}
@@ -612,11 +607,12 @@
612 status = u1db_get_replica_uid(db, &local_uid);607 status = u1db_get_replica_uid(db, &local_uid);
613 if (status != U1DB_OK) { goto finish; }608 if (status != U1DB_OK) { goto finish; }
614 // fprintf(stderr, "Local uid: %s\n", local_uid);609 // fprintf(stderr, "Local uid: %s\n", local_uid);
615 status = target->get_sync_info(target, local_uid, &target_uid, &target_gen,610 status = target->get_sync_info(
616 &local_gen_known_by_target, &local_trans_id_known_by_target);611 target, local_uid, &target_uid, &target_gen,
612 &local_gen_known_by_target, &local_trans_id_known_by_target);
617 if (status != U1DB_OK) { goto finish; }613 if (status != U1DB_OK) { goto finish; }
618 status = u1db_validate_gen_and_trans_id(614 status = u1db_validate_gen_and_trans_id(
619 db, local_gen_known_by_target, local_trans_id_known_by_target);615 db, local_gen_known_by_target, local_trans_id_known_by_target);
620 if (status != U1DB_OK) { goto finish; }616 if (status != U1DB_OK) { goto finish; }
621 status = u1db__get_replica_gen_and_trans_id(db, target_uid,617 status = u1db__get_replica_gen_and_trans_id(db, target_uid,
622 &target_gen_known_by_local, &target_trans_id_known_by_local);618 &target_gen_known_by_local, &target_trans_id_known_by_local);
623619
=== modified file 'u1db/__init__.py'
--- u1db/__init__.py 2012-07-03 14:35:42 +0000
+++ u1db/__init__.py 2012-07-04 16:16:18 +0000
@@ -565,7 +565,8 @@
565 raise NotImplementedError(self.record_sync_info)565 raise NotImplementedError(self.record_sync_info)
566566
567 def sync_exchange(self, docs_by_generation, source_replica_uid,567 def sync_exchange(self, docs_by_generation, source_replica_uid,
568 last_known_generation, return_doc_cb):568 last_known_generation, last_known_trans_id,
569 return_doc_cb):
569 """Incorporate the documents sent from the source replica.570 """Incorporate the documents sent from the source replica.
570571
571 This is not meant to be called by client code directly, but is used as572 This is not meant to be called by client code directly, but is used as
@@ -589,7 +590,9 @@
589 id of their latest change.590 id of their latest change.
590 :param source_replica_uid: The source replica's identifier591 :param source_replica_uid: The source replica's identifier
591 :param last_known_generation: The last generation that the source592 :param last_known_generation: The last generation that the source
592 replica knows about this593 replica knows about this target replica
594 :param last_known_trans_id: The last transaction id that the source
595 replica knows about this target replica
593 :param: return_doc_cb(doc, gen): is a callback596 :param: return_doc_cb(doc, gen): is a callback
594 used to return documents to the source replica, it will597 used to return documents to the source replica, it will
595 be invoked in turn with Documents that have changed since598 be invoked in turn with Documents that have changed since
596599
=== modified file 'u1db/backends/inmemory.py'
--- u1db/backends/inmemory.py 2012-07-03 13:50:27 +0000
+++ u1db/backends/inmemory.py 2012-07-04 16:16:18 +0000
@@ -46,6 +46,10 @@
46 self._last_exchange_log = None46 self._last_exchange_log = None
47 self._factory = document_factory or Document47 self._factory = document_factory or Document
4848
49 def _set_replica_uid(self, replica_uid):
50 """Force the replica_uid to be set."""
51 self._replica_uid = replica_uid
52
49 def set_document_factory(self, factory):53 def set_document_factory(self, factory):
50 self._factory = factory54 self._factory = factory
5155
5256
=== modified file 'u1db/errors.py'
--- u1db/errors.py 2012-06-27 18:56:17 +0000
+++ u1db/errors.py 2012-07-04 16:16:18 +0000
@@ -46,10 +46,14 @@
46class InvalidTransactionId(U1DBError):46class InvalidTransactionId(U1DBError):
47 """Invalid transaction for generation."""47 """Invalid transaction for generation."""
4848
49 wire_description = "invalid transaction id"
50
4951
50class InvalidGeneration(U1DBError):52class InvalidGeneration(U1DBError):
51 """Generation was previously synced with a different transaction id."""53 """Generation was previously synced with a different transaction id."""
5254
55 wire_description = "invalid generation"
56
5357
54class ConflictedDoc(U1DBError):58class ConflictedDoc(U1DBError):
55 """The document is conflicted, you must call resolve before put()"""59 """The document is conflicted, you must call resolve before put()"""
5660
=== modified file 'u1db/remote/http_app.py'
--- u1db/remote/http_app.py 2012-06-27 18:35:52 +0000
+++ u1db/remote/http_app.py 2012-07-04 16:16:18 +0000
@@ -45,6 +45,12 @@
45 return False45 return False
4646
4747
48def none_or_str(expression):
49 if expression is None:
50 return None
51 return str(expression)
52
53
48class BadRequest(Exception):54class BadRequest(Exception):
49 """Bad request."""55 """Bad request."""
5056
@@ -316,12 +322,13 @@
316322
317 # Implements the same logic as LocalSyncTarget.sync_exchange323 # Implements the same logic as LocalSyncTarget.sync_exchange
318324
319 @http_method(last_known_generation=int,325 @http_method(last_known_generation=int, last_known_trans_id=none_or_str,
320 content_as_args=True)326 content_as_args=True)
321 def post_args(self, last_known_generation):327 def post_args(self, last_known_generation, last_known_trans_id=None):
322 self.sync_exch = self.sync_exchange_class(self.db,328 self.db.validate_gen_and_trans_id(
323 self.source_replica_uid,329 last_known_generation, last_known_trans_id)
324 last_known_generation)330 self.sync_exch = self.sync_exchange_class(
331 self.db, self.source_replica_uid, last_known_generation)
325332
326 @http_method(content_as_args=True)333 @http_method(content_as_args=True)
327 def post_stream_entry(self, id, rev, content, gen, trans_id):334 def post_stream_entry(self, id, rev, content, gen, trans_id):
@@ -329,17 +336,19 @@
329 self.sync_exch.insert_doc_from_source(doc, gen, trans_id)336 self.sync_exch.insert_doc_from_source(doc, gen, trans_id)
330337
331 def post_end(self):338 def post_end(self):
339
332 def send_doc(doc, gen, trans_id):340 def send_doc(doc, gen, trans_id):
333 entry = dict(id=doc.doc_id, rev=doc.rev, content=doc.get_json(),341 entry = dict(id=doc.doc_id, rev=doc.rev, content=doc.get_json(),
334 gen=gen, trans_id=trans_id)342 gen=gen, trans_id=trans_id)
335 self.responder.stream_entry(entry)343 self.responder.stream_entry(entry)
344
336 new_gen = self.sync_exch.find_changes_to_return()345 new_gen = self.sync_exch.find_changes_to_return()
337 self.responder.content_type = 'application/x-u1db-sync-stream'346 self.responder.content_type = 'application/x-u1db-sync-stream'
338 self.responder.start_response(200)347 self.responder.start_response(200)
339 self.responder.start_stream(),348 self.responder.start_stream(),
340 self.responder.stream_entry({"new_generation": new_gen,349 self.responder.stream_entry({"new_generation": new_gen,
341 "new_transaction_id": self.sync_exch.new_trans_id})350 "new_transaction_id": self.sync_exch.new_trans_id})
342 new_gen = self.sync_exch.return_docs(send_doc)351 self.sync_exch.return_docs(send_doc)
343 self.responder.end_stream()352 self.responder.end_stream()
344 self.responder.finish_response()353 self.responder.finish_response()
345354
346355
=== modified file 'u1db/remote/http_errors.py'
--- u1db/remote/http_errors.py 2012-04-24 13:40:36 +0000
+++ u1db/remote/http_errors.py 2012-07-04 16:16:18 +0000
@@ -29,6 +29,8 @@
29 (errors.DocumentDoesNotExist.wire_description, 404),29 (errors.DocumentDoesNotExist.wire_description, 404),
30 (errors.DocumentAlreadyDeleted.wire_description, 404),30 (errors.DocumentAlreadyDeleted.wire_description, 404),
31 (errors.RevisionConflict.wire_description, 409),31 (errors.RevisionConflict.wire_description, 409),
32 (errors.InvalidGeneration.wire_description, 409),
33 (errors.InvalidTransactionId.wire_description, 409),
32 (errors.Unavailable.wire_description, 503),34 (errors.Unavailable.wire_description, 503),
33# without matching exception35# without matching exception
34 (errors.DOCUMENT_DELETED, 404)36 (errors.DOCUMENT_DELETED, 404)
3537
=== modified file 'u1db/remote/http_target.py'
--- u1db/remote/http_target.py 2012-06-14 23:28:58 +0000
+++ u1db/remote/http_target.py 2012-07-04 16:16:18 +0000
@@ -80,7 +80,8 @@
80 return res80 return res
8181
82 def sync_exchange(self, docs_by_generations, source_replica_uid,82 def sync_exchange(self, docs_by_generations, source_replica_uid,
83 last_known_generation, return_doc_cb):83 last_known_generation, last_known_trans_id,
84 return_doc_cb):
84 self._ensure_connection()85 self._ensure_connection()
85 url = '%s/sync-from/%s' % (self._url.path, source_replica_uid)86 url = '%s/sync-from/%s' % (self._url.path, source_replica_uid)
86 self._conn.putrequest('POST', url)87 self._conn.putrequest('POST', url)
@@ -96,7 +97,9 @@
96 return len(entry)97 return len(entry)
9798
98 comma = ''99 comma = ''
99 size += prepare(last_known_generation=last_known_generation)100 size += prepare(
101 last_known_generation=last_known_generation,
102 last_known_trans_id=last_known_trans_id)
100 comma = ','103 comma = ','
101 for doc, gen, trans_id in docs_by_generations:104 for doc, gen, trans_id in docs_by_generations:
102 size += prepare(id=doc.doc_id, rev=doc.rev, content=doc.get_json(),105 size += prepare(id=doc.doc_id, rev=doc.rev, content=doc.get_json(),
103106
=== modified file 'u1db/sync.py'
--- u1db/sync.py 2012-07-03 13:50:27 +0000
+++ u1db/sync.py 2012-07-04 16:16:18 +0000
@@ -97,9 +97,11 @@
97 (self.target_replica_uid, target_gen, target_my_gen,97 (self.target_replica_uid, target_gen, target_my_gen,
98 target_my_trans_id) = sync_target.get_sync_info(98 target_my_trans_id) = sync_target.get_sync_info(
99 self.source._replica_uid)99 self.source._replica_uid)
100 # what's changed since that generation and this current gen100 # validate that the generation and transaction id the target knows
101 # about us are valid.
101 self.source.validate_gen_and_trans_id(102 self.source.validate_gen_and_trans_id(
102 target_my_gen, target_my_trans_id)103 target_my_gen, target_my_trans_id)
104 # what's changed since that generation and this current gen
103 my_gen, _, changes = self.source.whats_changed(target_my_gen)105 my_gen, _, changes = self.source.whats_changed(target_my_gen)
104106
105 # this source last-seen database generation for the target107 # this source last-seen database generation for the target
@@ -119,9 +121,10 @@
119121
120 # exchange documents and try to insert the returned ones with122 # exchange documents and try to insert the returned ones with
121 # the target, return target synced-up-to gen123 # the target, return target synced-up-to gen
122 new_gen, new_trans_id = sync_target.sync_exchange(docs_by_generation,124 new_gen, new_trans_id = sync_target.sync_exchange(
123 self.source._replica_uid, target_last_known_gen,125 docs_by_generation, self.source._replica_uid,
124 return_doc_cb=self._insert_doc_from_target)126 target_last_known_gen, target_trans_id,
127 self._insert_doc_from_target)
125 # record target synced-up-to generation including applying what we sent128 # record target synced-up-to generation including applying what we sent
126 self.source._set_replica_gen_and_trans_id(129 self.source._set_replica_gen_and_trans_id(
127 self.target_replica_uid, new_gen, new_trans_id)130 self.target_replica_uid, new_gen, new_trans_id)
@@ -262,9 +265,12 @@
262 self._trace_hook = None265 self._trace_hook = None
263266
264 def sync_exchange(self, docs_by_generations, source_replica_uid,267 def sync_exchange(self, docs_by_generations, source_replica_uid,
265 last_known_generation, return_doc_cb):268 last_known_generation, last_known_trans_id,
266 sync_exch = SyncExchange(self._db, source_replica_uid,269 return_doc_cb):
267 last_known_generation)270 self._db.validate_gen_and_trans_id(
271 last_known_generation, last_known_trans_id)
272 sync_exch = SyncExchange(
273 self._db, source_replica_uid, last_known_generation)
268 if self._trace_hook:274 if self._trace_hook:
269 sync_exch._set_trace_hook(self._trace_hook)275 sync_exch._set_trace_hook(self._trace_hook)
270 # 1st step: try to insert incoming docs and record progress276 # 1st step: try to insert incoming docs and record progress
271277
=== modified file 'u1db/tests/c_backend_wrapper.pyx'
--- u1db/tests/c_backend_wrapper.pyx 2012-07-03 13:50:27 +0000
+++ u1db/tests/c_backend_wrapper.pyx 2012-07-04 16:16:18 +0000
@@ -184,9 +184,9 @@
184 int (*sync_exchange)(u1db_sync_target *st,184 int (*sync_exchange)(u1db_sync_target *st,
185 char *source_replica_uid, int n_docs,185 char *source_replica_uid, int n_docs,
186 u1db_document **docs, int *generations,186 u1db_document **docs, int *generations,
187 const_char_ptr *trans_ids, int *target_gen,187 const_char_ptr *trans_ids,
188 char **target_trans_id, void *context,188 int *target_gen, char **target_trans_id,
189 u1db_doc_gen_callback cb) nogil189 void *context, u1db_doc_gen_callback cb) nogil
190 int (*sync_exchange_doc_ids)(u1db_sync_target *st,190 int (*sync_exchange_doc_ids)(u1db_sync_target *st,
191 u1database *source_db, int n_doc_ids,191 u1database *source_db, int n_doc_ids,
192 const_char_ptr *doc_ids, int *generations,192 const_char_ptr *doc_ids, int *generations,
@@ -713,6 +713,7 @@
713713
714 def record_sync_info(self, source_replica_uid, source_gen, source_trans_id):714 def record_sync_info(self, source_replica_uid, source_gen, source_trans_id):
715 cdef int status715 cdef int status
716
716 self._check()717 self._check()
717 assert self._st.record_sync_info != NULL, "record_sync_info is NULL?"718 assert self._st.record_sync_info != NULL, "record_sync_info is NULL?"
718 with nogil:719 with nogil:
@@ -725,12 +726,13 @@
725 return CSyncExchange(self, source_replica_uid, source_gen)726 return CSyncExchange(self, source_replica_uid, source_gen)
726727
727 def sync_exchange_doc_ids(self, source_db, doc_id_generations,728 def sync_exchange_doc_ids(self, source_db, doc_id_generations,
728 last_known_generation, return_doc_cb):729 last_known_generation, last_known_trans_id,
730 return_doc_cb):
729 cdef const_char_ptr *doc_ids731 cdef const_char_ptr *doc_ids
730 cdef int *generations732 cdef int *generations
731 cdef int num_doc_ids733 cdef int num_doc_ids
732 cdef int target_gen734 cdef int target_gen
733 cdef char *target_trans_id735 cdef char *target_trans_id = NULL
734 cdef int status736 cdef int status
735 cdef CDatabase sdb737 cdef CDatabase sdb
736738
@@ -755,31 +757,35 @@
755 generations[i] = gen757 generations[i] = gen
756 trans_ids[i] = trans_id758 trans_ids[i] = trans_id
757 target_gen = last_known_generation759 target_gen = last_known_generation
760 if last_known_trans_id is not None:
761 target_trans_id = last_known_trans_id
758 with nogil:762 with nogil:
759 status = self._st.sync_exchange_doc_ids(self._st, sdb._db,763 status = self._st.sync_exchange_doc_ids(self._st, sdb._db,
760 num_doc_ids, doc_ids, generations, trans_ids,764 num_doc_ids, doc_ids, generations, trans_ids,
761 &target_gen, &target_trans_id,765 &target_gen, &target_trans_id,
762 <void*>return_doc_cb, return_doc_cb_wrapper)766 <void*>return_doc_cb, return_doc_cb_wrapper)
763 handle_status("sync_exchange_doc_ids", status)767 handle_status("sync_exchange_doc_ids", status)
768 if target_trans_id != NULL:
769 res_trans_id = target_trans_id
764 finally:770 finally:
771 if target_trans_id != NULL:
772 free(target_trans_id)
765 if doc_ids != NULL:773 if doc_ids != NULL:
766 free(<void *>doc_ids)774 free(<void *>doc_ids)
767 if generations != NULL:775 if generations != NULL:
768 free(generations)776 free(generations)
769 if trans_ids != NULL:777 if trans_ids != NULL:
770 free(trans_ids)778 free(trans_ids)
771 if target_trans_id != NULL:
772 res_trans_id = target_trans_id
773 free(target_trans_id)
774 return target_gen, res_trans_id779 return target_gen, res_trans_id
775780
776 def sync_exchange(self, docs_by_generations, source_replica_uid,781 def sync_exchange(self, docs_by_generations, source_replica_uid,
777 last_known_generation, return_doc_cb):782 last_known_generation, last_known_trans_id,
783 return_doc_cb):
778 cdef CDocument cur_doc784 cdef CDocument cur_doc
779 cdef u1db_document **docs = NULL785 cdef u1db_document **docs = NULL
780 cdef int *generations = NULL786 cdef int *generations = NULL
781 cdef const_char_ptr *trans_ids = NULL787 cdef const_char_ptr *trans_ids = NULL
782 cdef char *trans_id = NULL788 cdef char *target_trans_id = NULL
783 cdef int i, count, status, target_gen789 cdef int i, count, status, target_gen
784790
785 self._check()791 self._check()
@@ -802,11 +808,13 @@
802 trans_ids[i] = docs_by_generations[i][2]808 trans_ids[i] = docs_by_generations[i][2]
803 docs[i] = cur_doc._doc809 docs[i] = cur_doc._doc
804 target_gen = last_known_generation810 target_gen = last_known_generation
811 if last_known_trans_id is not None:
812 target_trans_id = last_known_trans_id
805 with nogil:813 with nogil:
806 status = self._st.sync_exchange(self._st,814 status = self._st.sync_exchange(
807 source_replica_uid, count,815 self._st, source_replica_uid, count, docs, generations,
808 docs, generations, trans_ids, &target_gen, &trans_id,816 trans_ids, &target_gen, &target_trans_id,
809 <void *>return_doc_cb, return_doc_cb_wrapper)817 <void *>return_doc_cb, return_doc_cb_wrapper)
810 handle_status("sync_exchange", status)818 handle_status("sync_exchange", status)
811 finally:819 finally:
812 if docs != NULL:820 if docs != NULL:
@@ -815,9 +823,9 @@
815 free(generations)823 free(generations)
816 if trans_ids != NULL:824 if trans_ids != NULL:
817 free(trans_ids)825 free(trans_ids)
818 if trans_id != NULL:826 if target_trans_id != NULL:
819 res_trans_id = trans_id827 res_trans_id = target_trans_id
820 free(trans_id)828 free(target_trans_id)
821 return target_gen, res_trans_id829 return target_gen, res_trans_id
822830
823 def _set_trace_hook(self, cb):831 def _set_trace_hook(self, cb):
824832
=== modified file 'u1db/tests/test_backends.py'
--- u1db/tests/test_backends.py 2012-07-03 22:24:43 +0000
+++ u1db/tests/test_backends.py 2012-07-04 16:16:18 +0000
@@ -1556,7 +1556,7 @@
1556 docs_by_gen = [(doc_other, 10, 'T-sid')]1556 docs_by_gen = [(doc_other, 10, 'T-sid')]
1557 st.sync_exchange(1557 st.sync_exchange(
1558 docs_by_gen, 'other-replica', last_known_generation=0,1558 docs_by_gen, 'other-replica', last_known_generation=0,
1559 return_doc_cb=ignore)1559 last_known_trans_id=None, return_doc_cb=ignore)
1560 self.assertGetDoc(self.db, doc.doc_id, other_rev, new_content, False)1560 self.assertGetDoc(self.db, doc.doc_id, other_rev, new_content, False)
1561 self.assertEqual(1561 self.assertEqual(
1562 [doc_other], self.db.get_from_index('test-idx', 'altval'))1562 [doc_other], self.db.get_from_index('test-idx', 'altval'))
15631563
=== modified file 'u1db/tests/test_c_backend.py'
--- u1db/tests/test_c_backend.py 2012-07-03 13:50:27 +0000
+++ u1db/tests/test_c_backend.py 2012-07-04 16:16:18 +0000
@@ -359,7 +359,7 @@
359 returned.append((doc, gen, trans_id))359 returned.append((doc, gen, trans_id))
360360
361 val = self.st.sync_exchange_doc_ids(361 val = self.st.sync_exchange_doc_ids(
362 db2, [(doc2.doc_id, 1, 'T-sid')], 0, return_doc_cb)362 db2, [(doc2.doc_id, 1, 'T-sid')], 0, None, return_doc_cb)
363 last_trans_id = self.db._get_transaction_log()[-1][1]363 last_trans_id = self.db._get_transaction_log()[-1][1]
364 self.assertEqual(2, self.db._get_generation())364 self.assertEqual(2, self.db._get_generation())
365 self.assertEqual((2, last_trans_id), val)365 self.assertEqual((2, last_trans_id), val)
366366
=== modified file 'u1db/tests/test_http_app.py'
--- u1db/tests/test_http_app.py 2012-07-03 13:50:27 +0000
+++ u1db/tests/test_http_app.py 2012-07-04 16:16:18 +0000
@@ -1016,5 +1016,6 @@
1016 sync_exchange_class = MySyncExchange1016 sync_exchange_class = MySyncExchange
10171017
1018 sync_res = MySyncResource('foo', 'src', self.state, None)1018 sync_res = MySyncResource('foo', 'src', self.state, None)
1019 sync_res.post_args({'last_known_generation': 0}, '{}')1019 sync_res.post_args(
1020 {'last_known_generation': 0, 'last_known_trans_id': None}, '{}')
1020 self.assertIsInstance(sync_res.sync_exch, MySyncExchange)1021 self.assertIsInstance(sync_res.sync_exch, MySyncExchange)
10211022
=== modified file 'u1db/tests/test_remote_sync_target.py'
--- u1db/tests/test_remote_sync_target.py 2012-07-03 13:50:27 +0000
+++ u1db/tests/test_remote_sync_target.py 2012-07-04 16:16:18 +0000
@@ -195,9 +195,8 @@
195195
196 doc = self.make_document('doc-here', 'replica:1', '{"value": "here"}')196 doc = self.make_document('doc-here', 'replica:1', '{"value": "here"}')
197 new_gen, trans_id = remote_target.sync_exchange(197 new_gen, trans_id = remote_target.sync_exchange(
198 [(doc, 10, 'T-sid')],198 [(doc, 10, 'T-sid')], 'replica', last_known_generation=0,
199 'replica', last_known_generation=0,199 last_known_trans_id=None, return_doc_cb=receive_doc)
200 return_doc_cb=receive_doc)
201 self.assertEqual(1, new_gen)200 self.assertEqual(1, new_gen)
202 self.assertGetDoc(201 self.assertGetDoc(
203 db, 'doc-here', 'replica:1', '{"value": "here"}', False)202 db, 'doc-here', 'replica:1', '{"value": "here"}', False)
@@ -233,11 +232,12 @@
233 doc1 = self.make_document('doc-here', 'replica:1', '{"value": "here"}')232 doc1 = self.make_document('doc-here', 'replica:1', '{"value": "here"}')
234 doc2 = self.make_document('doc-here2', 'replica:1',233 doc2 = self.make_document('doc-here2', 'replica:1',
235 '{"value": "here2"}')234 '{"value": "here2"}')
236 self.assertRaises(errors.HTTPError, remote_target.sync_exchange,235 self.assertRaises(
237 [(doc1, 10, 'T-sid'),236 errors.HTTPError,
238 (doc2, 11, 'T-sud')237 remote_target.sync_exchange,
239 ], 'replica', last_known_generation=0,238 [(doc1, 10, 'T-sid'), (doc2, 11, 'T-sud')],
240 return_doc_cb=receive_doc)239 'replica', last_known_generation=0, last_known_trans_id=None,
240 return_doc_cb=receive_doc)
241 self.assertGetDoc(db, 'doc-here', 'replica:1', '{"value": "here"}',241 self.assertGetDoc(db, 'doc-here', 'replica:1', '{"value": "here"}',
242 False)242 False)
243 self.assertEqual(243 self.assertEqual(
@@ -246,9 +246,8 @@
246 # retry246 # retry
247 trigger_ids = []247 trigger_ids = []
248 new_gen, trans_id = remote_target.sync_exchange(248 new_gen, trans_id = remote_target.sync_exchange(
249 [(doc2, 11, 'T-sud')249 [(doc2, 11, 'T-sud')], 'replica', last_known_generation=0,
250 ], 'replica', last_known_generation=0,250 last_known_trans_id=None, return_doc_cb=receive_doc)
251 return_doc_cb=receive_doc)
252 self.assertGetDoc(db, 'doc-here2', 'replica:1', '{"value": "here2"}',251 self.assertGetDoc(db, 'doc-here2', 'replica:1', '{"value": "here2"}',
253 False)252 False)
254 self.assertEqual(253 self.assertEqual(
@@ -284,9 +283,10 @@
284 other_changes.append(283 other_changes.append(
285 (doc.doc_id, doc.rev, doc.get_json(), gen, trans_id))284 (doc.doc_id, doc.rev, doc.get_json(), gen, trans_id))
286285
287 self.assertRaises(errors.Unavailable, remote_target.sync_exchange,286 self.assertRaises(
288 [], 'replica', last_known_generation=0,287 errors.Unavailable, remote_target.sync_exchange, [], 'replica',
289 return_doc_cb=receive_doc)288 last_known_generation=0, last_known_trans_id=None,
289 return_doc_cb=receive_doc)
290 self.assertEqual(290 self.assertEqual(
291 (doc.doc_id, doc.rev, '{"value": "there"}', 1),291 (doc.doc_id, doc.rev, '{"value": "there"}', 1),
292 other_changes[0][:-1])292 other_changes[0][:-1])
@@ -303,8 +303,8 @@
303 (doc.doc_id, doc.rev, doc.get_json(), gen, trans_id))303 (doc.doc_id, doc.rev, doc.get_json(), gen, trans_id))
304304
305 new_gen, trans_id = remote_target.sync_exchange(305 new_gen, trans_id = remote_target.sync_exchange(
306 [], 'replica', last_known_generation=0,306 [], 'replica', last_known_generation=0, last_known_trans_id=None,
307 return_doc_cb=receive_doc)307 return_doc_cb=receive_doc)
308 self.assertEqual(1, new_gen)308 self.assertEqual(1, new_gen)
309 self.assertEqual(309 self.assertEqual(
310 (doc.doc_id, doc.rev, '{"value": "there"}', 1),310 (doc.doc_id, doc.rev, '{"value": "there"}', 1),
311311
=== modified file 'u1db/tests/test_sync.py'
--- u1db/tests/test_sync.py 2012-07-03 18:01:54 +0000
+++ u1db/tests/test_sync.py 2012-07-04 16:16:18 +0000
@@ -157,11 +157,12 @@
157 self.st.get_sync_info('replica'))157 self.st.get_sync_info('replica'))
158158
159 def test_sync_exchange(self):159 def test_sync_exchange(self):
160 doc = self.make_document('doc-id', 'replica:1', simple_doc)160 docs_by_gen = [
161 docs_by_gen = [(doc, 10, 'T-sid')]161 (self.make_document('doc-id', 'replica:1', simple_doc), 10,
162 new_gen, trans_id = self.st.sync_exchange(docs_by_gen, 'replica',162 'T-sid')]
163 last_known_generation=0,163 new_gen, trans_id = self.st.sync_exchange(
164 return_doc_cb=self.receive_doc)164 docs_by_gen, 'replica', last_known_generation=0,
165 last_known_trans_id=None, return_doc_cb=self.receive_doc)
165 self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False)166 self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False)
166 self.assertTransactionLog(['doc-id'], self.db)167 self.assertTransactionLog(['doc-id'], self.db)
167 last_trans_id = self.getLastTransId(self.db)168 last_trans_id = self.getLastTransId(self.db)
@@ -174,9 +175,9 @@
174 edit_rev = 'replica:1|' + doc.rev175 edit_rev = 'replica:1|' + doc.rev
175 docs_by_gen = [176 docs_by_gen = [
176 (self.make_document(doc.doc_id, edit_rev, None), 10, 'T-sid')]177 (self.make_document(doc.doc_id, edit_rev, None), 10, 'T-sid')]
177 new_gen, trans_id = self.st.sync_exchange(docs_by_gen, 'replica',178 new_gen, trans_id = self.st.sync_exchange(
178 last_known_generation=0,179 docs_by_gen, 'replica', last_known_generation=0,
179 return_doc_cb=self.receive_doc)180 last_known_trans_id=None, return_doc_cb=self.receive_doc)
180 self.assertGetDocIncludeDeleted(181 self.assertGetDocIncludeDeleted(
181 self.db, doc.doc_id, edit_rev, None, False)182 self.db, doc.doc_id, edit_rev, None, False)
182 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)183 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)
@@ -190,9 +191,9 @@
190 (self.make_document('doc-id', 'replica:1', simple_doc), 10, 'T-1'),191 (self.make_document('doc-id', 'replica:1', simple_doc), 10, 'T-1'),
191 (self.make_document('doc-id2', 'replica:1', nested_doc), 11,192 (self.make_document('doc-id2', 'replica:1', nested_doc), 11,
192 'T-2')]193 'T-2')]
193 new_gen, trans_id = self.st.sync_exchange(docs_by_gen, 'replica',194 new_gen, trans_id = self.st.sync_exchange(
194 last_known_generation=0,195 docs_by_gen, 'replica', last_known_generation=0,
195 return_doc_cb=self.receive_doc)196 last_known_trans_id=None, return_doc_cb=self.receive_doc)
196 self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False)197 self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False)
197 self.assertGetDoc(self.db, 'doc-id2', 'replica:1', nested_doc, False)198 self.assertGetDoc(self.db, 'doc-id2', 'replica:1', nested_doc, False)
198 self.assertTransactionLog(['doc-id', 'doc-id2'], self.db)199 self.assertTransactionLog(['doc-id', 'doc-id2'], self.db)
@@ -210,7 +211,7 @@
210 'T-sid')]211 'T-sid')]
211 new_gen, _ = self.st.sync_exchange(212 new_gen, _ = self.st.sync_exchange(
212 docs_by_gen, 'replica', last_known_generation=0,213 docs_by_gen, 'replica', last_known_generation=0,
213 return_doc_cb=self.receive_doc)214 last_known_trans_id=None, return_doc_cb=self.receive_doc)
214 self.assertTransactionLog([doc.doc_id], self.db)215 self.assertTransactionLog([doc.doc_id], self.db)
215 self.assertEqual(216 self.assertEqual(
216 (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1])217 (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1])
@@ -222,20 +223,21 @@
222 def test_sync_exchange_ignores_convergence(self):223 def test_sync_exchange_ignores_convergence(self):
223 doc = self.db.create_doc(simple_doc)224 doc = self.db.create_doc(simple_doc)
224 self.assertTransactionLog([doc.doc_id], self.db)225 self.assertTransactionLog([doc.doc_id], self.db)
226 gen, txid = self.db._get_generation_info()
225 docs_by_gen = [227 docs_by_gen = [
226 (self.make_document(doc.doc_id, doc.rev, simple_doc), 10, 'T-sid')]228 (self.make_document(doc.doc_id, doc.rev, simple_doc), 10, 'T-sid')]
227 new_gen, _ = self.st.sync_exchange(docs_by_gen, 'replica',229 new_gen, _ = self.st.sync_exchange(
228 last_known_generation=1,230 docs_by_gen, 'replica', last_known_generation=gen,
229 return_doc_cb=self.receive_doc)231 last_known_trans_id=txid, return_doc_cb=self.receive_doc)
230 self.assertTransactionLog([doc.doc_id], self.db)232 self.assertTransactionLog([doc.doc_id], self.db)
231 self.assertEqual(([], 1), (self.other_changes, new_gen))233 self.assertEqual(([], 1), (self.other_changes, new_gen))
232234
233 def test_sync_exchange_returns_new_docs(self):235 def test_sync_exchange_returns_new_docs(self):
234 doc = self.db.create_doc(simple_doc)236 doc = self.db.create_doc(simple_doc)
235 self.assertTransactionLog([doc.doc_id], self.db)237 self.assertTransactionLog([doc.doc_id], self.db)
236 new_gen, _ = self.st.sync_exchange([], 'other-replica',238 new_gen, _ = self.st.sync_exchange(
237 last_known_generation=0,239 [], 'other-replica', last_known_generation=0,
238 return_doc_cb=self.receive_doc)240 last_known_trans_id=None, return_doc_cb=self.receive_doc)
239 self.assertTransactionLog([doc.doc_id], self.db)241 self.assertTransactionLog([doc.doc_id], self.db)
240 self.assertEqual(242 self.assertEqual(
241 (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1])243 (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1])
@@ -248,9 +250,9 @@
248 doc = self.db.create_doc(simple_doc)250 doc = self.db.create_doc(simple_doc)
249 self.db.delete_doc(doc)251 self.db.delete_doc(doc)
250 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)252 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)
251 new_gen, _ = self.st.sync_exchange([], 'other-replica',253 new_gen, _ = self.st.sync_exchange(
252 last_known_generation=0,254 [], 'other-replica', last_known_generation=0,
253 return_doc_cb=self.receive_doc)255 last_known_trans_id=None, return_doc_cb=self.receive_doc)
254 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)256 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)
255 self.assertEqual(257 self.assertEqual(
256 (doc.doc_id, doc.rev, None, 2), self.other_changes[0][:-1])258 (doc.doc_id, doc.rev, None, 2), self.other_changes[0][:-1])
@@ -263,9 +265,9 @@
263 doc = self.db.create_doc(simple_doc)265 doc = self.db.create_doc(simple_doc)
264 doc2 = self.db.create_doc(nested_doc)266 doc2 = self.db.create_doc(nested_doc)
265 self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db)267 self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db)
266 new_gen, _ = self.st.sync_exchange([], 'other-replica',268 new_gen, _ = self.st.sync_exchange(
267 last_known_generation=0,269 [], 'other-replica', last_known_generation=0,
268 return_doc_cb=self.receive_doc)270 last_known_trans_id=None, return_doc_cb=self.receive_doc)
269 self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db)271 self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db)
270 self.assertEqual(2, new_gen)272 self.assertEqual(2, new_gen)
271 self.assertEqual(273 self.assertEqual(
@@ -285,9 +287,9 @@
285 docs_by_gen = [287 docs_by_gen = [
286 (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10,288 (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10,
287 'T-sid')]289 'T-sid')]
288 new_gen, _ = self.st.sync_exchange(docs_by_gen, 'other-replica',290 new_gen, _ = self.st.sync_exchange(
289 last_known_generation=0,291 docs_by_gen, 'other-replica', last_known_generation=0,
290 return_doc_cb=self.receive_doc)292 last_known_trans_id=None, return_doc_cb=self.receive_doc)
291 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)293 self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db)
292 self.assertEqual(([], 2), (self.other_changes, new_gen))294 self.assertEqual(([], 2), (self.other_changes, new_gen))
293295
@@ -311,15 +313,17 @@
311 'T-sid')]313 'T-sid')]
312 new_gen, _ = self.st.sync_exchange(314 new_gen, _ = self.st.sync_exchange(
313 docs_by_gen, 'other-replica', last_known_generation=0,315 docs_by_gen, 'other-replica', last_known_generation=0,
314 return_doc_cb=self.receive_doc)316 last_known_trans_id=None, return_doc_cb=self.receive_doc)
315 self.assertEqual(expected, [c[:-1] for c in self.other_changes])317 self.assertEqual(expected, [c[:-1] for c in self.other_changes])
316 self.assertEqual(3, new_gen)318 self.assertEqual(3, new_gen)
317319
318 def test_sync_exchange_with_concurrent_updates(self):320 def test_sync_exchange_with_concurrent_updates(self):
321
319 def after_whatschanged_cb(state):322 def after_whatschanged_cb(state):
320 if state != 'after whats_changed':323 if state != 'after whats_changed':
321 return324 return
322 self.db.create_doc('{"new": "doc"}')325 self.db.create_doc('{"new": "doc"}')
326
323 self.set_trace_hook(after_whatschanged_cb)327 self.set_trace_hook(after_whatschanged_cb)
324 doc = self.db.create_doc(simple_doc)328 doc = self.db.create_doc(simple_doc)
325 self.assertTransactionLog([doc.doc_id], self.db)329 self.assertTransactionLog([doc.doc_id], self.db)
@@ -327,9 +331,9 @@
327 docs_by_gen = [331 docs_by_gen = [
328 (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10,332 (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10,
329 'T-sid')]333 'T-sid')]
330 new_gen, _ = self.st.sync_exchange(docs_by_gen, 'other-replica',334 new_gen, _ = self.st.sync_exchange(
331 last_known_generation=0,335 docs_by_gen, 'other-replica', last_known_generation=0,
332 return_doc_cb=self.receive_doc)336 last_known_trans_id=None, return_doc_cb=self.receive_doc)
333 self.assertEqual(([], 2), (self.other_changes, new_gen))337 self.assertEqual(([], 2), (self.other_changes, new_gen))
334338
335 def test_sync_exchange_converged_handling(self):339 def test_sync_exchange_converged_handling(self):
@@ -338,9 +342,9 @@
338 (self.make_document('new', 'other:1', '{}'), 4, 'T-foo'),342 (self.make_document('new', 'other:1', '{}'), 4, 'T-foo'),
339 (self.make_document(doc.doc_id, doc.rev, doc.get_json()), 5,343 (self.make_document(doc.doc_id, doc.rev, doc.get_json()), 5,
340 'T-bar')]344 'T-bar')]
341 new_gen, _ = self.st.sync_exchange(docs_by_gen, 'other-replica',345 new_gen, _ = self.st.sync_exchange(
342 last_known_generation=0,346 docs_by_gen, 'other-replica', last_known_generation=0,
343 return_doc_cb=self.receive_doc)347 last_known_trans_id=None, return_doc_cb=self.receive_doc)
344 self.assertEqual(([], 2), (self.other_changes, new_gen))348 self.assertEqual(([], 2), (self.other_changes, new_gen))
345349
346 def test_sync_exchange_detect_incomplete_exchange(self):350 def test_sync_exchange_detect_incomplete_exchange(self):
@@ -354,10 +358,11 @@
354 'log_exception', lambda h, exc_info: None)358 'log_exception', lambda h, exc_info: None)
355 doc = self.db.create_doc(simple_doc)359 doc = self.db.create_doc(simple_doc)
356 self.assertTransactionLog([doc.doc_id], self.db)360 self.assertTransactionLog([doc.doc_id], self.db)
357 self.assertRaises((errors.U1DBError, errors.BrokenSyncStream),361 self.assertRaises(
358 self.st.sync_exchange, [], 'other-replica',362 (errors.U1DBError, errors.BrokenSyncStream),
359 last_known_generation=0,363 self.st.sync_exchange, [], 'other-replica',
360 return_doc_cb=self.receive_doc)364 last_known_generation=0, last_known_trans_id=None,
365 return_doc_cb=self.receive_doc)
361366
362 def test_sync_exchange_doc_ids(self):367 def test_sync_exchange_doc_ids(self):
363 sync_exchange_doc_ids = getattr(self.st, 'sync_exchange_doc_ids', None)368 sync_exchange_doc_ids = getattr(self.st, 'sync_exchange_doc_ids', None)
@@ -366,7 +371,7 @@
366 db2 = self.create_database('test2')371 db2 = self.create_database('test2')
367 doc = db2.create_doc(simple_doc)372 doc = db2.create_doc(simple_doc)
368 new_gen, trans_id = sync_exchange_doc_ids(373 new_gen, trans_id = sync_exchange_doc_ids(
369 db2, [(doc.doc_id, 10, 'T-sid')], 0,374 db2, [(doc.doc_id, 10, 'T-sid')], 0, None,
370 return_doc_cb=self.receive_doc)375 return_doc_cb=self.receive_doc)
371 self.assertGetDoc(self.db, doc.doc_id, doc.rev, simple_doc, False)376 self.assertGetDoc(self.db, doc.doc_id, doc.rev, simple_doc, False)
372 self.assertTransactionLog([doc.doc_id], self.db)377 self.assertTransactionLog([doc.doc_id], self.db)
@@ -382,7 +387,7 @@
382 called.append(state)387 called.append(state)
383388
384 self.set_trace_hook(cb)389 self.set_trace_hook(cb)
385 self.st.sync_exchange([], 'replica', 0, self.receive_doc)390 self.st.sync_exchange([], 'replica', 0, None, self.receive_doc)
386 self.st.record_sync_info('replica', 0, 'T-sid')391 self.st.record_sync_info('replica', 0, 'T-sid')
387 self.assertEqual(['before whats_changed',392 self.assertEqual(['before whats_changed',
388 'after whats_changed',393 'after whats_changed',
@@ -410,13 +415,19 @@
410def make_database_for_http_test(test, replica_uid):415def make_database_for_http_test(test, replica_uid):
411 if test.server is None:416 if test.server is None:
412 test.startServer()417 test.startServer()
413 return test.request_state._create_database(replica_uid)418 db = test.request_state._create_database(replica_uid)
419 try:
420 http_at = test._http_at
421 except AttributeError:
422 http_at = test._http_at = {}
423 http_at[db] = replica_uid
424 return db
414425
415426
416def sync_via_synchronizer_and_http(test, db_source, db_target, trace_hook=None):427def sync_via_synchronizer_and_http(test, db_source, db_target, trace_hook=None):
417 if trace_hook:428 if trace_hook:
418 test.skipTest("trace_hook unsupported over http")429 test.skipTest("trace_hook unsupported over http")
419 path = db_target._replica_uid430 path = test._http_at[db_target]
420 target = http_target.HTTPSyncTarget.connect(test.getURL(path))431 target = http_target.HTTPSyncTarget.connect(test.getURL(path))
421 return sync.Synchronizer(db_source, target).sync()432 return sync.Synchronizer(db_source, target).sync()
422433
@@ -937,6 +948,46 @@
937948
938 self.sync(self.db1, self.db2, trace_hook=put_hook)949 self.sync(self.db1, self.db2, trace_hook=put_hook)
939950
951 def test_sync_detects_rollback_in_source(self):
952 self.db1.create_doc(tests.simple_doc, doc_id="divergent")
953 self.sync(self.db1, self.db2)
954 # make db2 think it's synced with a much later version of db1
955 self.db2._set_replica_gen_and_trans_id(
956 self.db1._replica_uid, 28, 'T-madeup')
957 self.assertRaises(
958 errors.InvalidGeneration, self.sync, self.db1, self.db2)
959
960 def test_sync_detects_rollback_in_target(self):
961 self.db1.create_doc(tests.simple_doc, doc_id="divergent")
962 self.sync(self.db1, self.db2)
963 # make db1 think it's synced with a much later version of db2
964 self.db1._set_replica_gen_and_trans_id(
965 self.db2._replica_uid, 28, 'T-madeup')
966 self.assertRaises(
967 errors.InvalidGeneration, self.sync, self.db1, self.db2)
968
969 def test_sync_detects_diverged_source(self):
970 self.db1.create_doc(tests.simple_doc, doc_id="divergent")
971 self.sync(self.db1, self.db2)
972 # create a new db with the same replica as the source, and add
973 # a document, so it will have a different txid for the same generation.
974 db3 = self.create_database('test3')
975 db3.create_doc(tests.nested_doc, doc_id="divergent")
976 db3._set_replica_uid(self.db1._replica_uid)
977 self.assertRaises(
978 errors.InvalidTransactionId, self.sync, db3, self.db2)
979
980 def test_sync_detects_diverged_target(self):
981 self.db1.create_doc(tests.simple_doc, doc_id="divergent")
982 self.sync(self.db1, self.db2)
983 # create a new db with the same replica as the target, and add
984 # a document, so it will have a different txid for the same generation.
985 db3 = self.create_database('test3')
986 db3.create_doc(tests.nested_doc, doc_id="divergent")
987 db3._set_replica_uid(self.db2._replica_uid)
988 self.assertRaises(
989 errors.InvalidTransactionId, self.sync, self.db1, db3)
990
940991
941class TestDbSync(tests.TestCaseWithServer):992class TestDbSync(tests.TestCaseWithServer):
942 """Test db.sync remote sync shortcut"""993 """Test db.sync remote sync shortcut"""

Subscribers

People subscribed via source and target branches