Merge lp:~jameinel/u1db/whats_changed_999574 into lp:u1db
- whats_changed_999574
- Merge into trunk
Proposed by
John A Meinel
Status: | Superseded |
---|---|
Proposed branch: | lp:~jameinel/u1db/whats_changed_999574 |
Merge into: | lp:u1db |
Diff against target: |
1344 lines (+305/-210) 21 files modified
include/u1db/u1db.h (+8/-7) include/u1db/u1db_internal.h (+13/-6) src/u1db.c (+28/-15) src/u1db_http_sync_target.c (+19/-6) src/u1db_sync_target.c (+26/-13) u1db/__init__.py (+26/-15) u1db/backends/__init__.py (+1/-1) u1db/backends/dbschema.sql (+5/-5) u1db/backends/inmemory.py (+20/-15) u1db/backends/sqlite_backend.py (+23/-15) u1db/remote/http_app.py (+5/-3) u1db/remote/http_target.py (+5/-3) u1db/sync.py (+11/-11) u1db/tests/__init__.py (+4/-0) u1db/tests/c_backend_wrapper.pyx (+34/-32) u1db/tests/test_backends.py (+27/-16) u1db/tests/test_c_backend.py (+2/-1) u1db/tests/test_http_app.py (+11/-10) u1db/tests/test_https.py (+2/-2) u1db/tests/test_remote_sync_target.py (+6/-6) u1db/tests/test_sync.py (+29/-28) |
To merge this branch: | bzr merge lp:~jameinel/u1db/whats_changed_999574 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu One hackers | Pending | ||
Review via email: mp+106339@code.launchpad.net |
This proposal has been superseded by a proposal from 2012-05-18.
Commit message
Description of the change
This is hopefully a small patch that is easier to review.
It just updates the whats_changed api so that it returns the transaction_id along with the database generation. Hopefully I updated enough of the docs to match it.
It does mean that we have to expose the transaction ids a bit in the test suite, for now I just went with '_get_transacti
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.h' |
2 | --- include/u1db/u1db.h 2012-05-17 15:13:42 +0000 |
3 | +++ include/u1db/u1db.h 2012-05-18 09:33:18 +0000 |
4 | @@ -198,15 +198,16 @@ |
5 | * get all changes in the database. The integer will be updated |
6 | * to point at the current generation. |
7 | * @param cb A callback function. This will be called passing in 'context', |
8 | - * and a document identifier for each document that has been modified. |
9 | - * The doc_id string is transient, so callers must copy it to |
10 | - * their own memory if they want to keep it. |
11 | - * If a document is changed more than once, it is currently |
12 | - * undefined whether this will call cb() once per change, or just |
13 | - * once per doc_id. |
14 | + * and a document identifier for each document that has been |
15 | + * modified. This includes the generation and associated |
16 | + * transaction id for each change. If a document is modified more |
17 | + * than once, only the most recent change will be given. |
18 | + * Note that the strings passed are transient, so must be copied |
19 | + * if callers want to use them after they return. |
20 | * @param context Opaque context, passed back to the caller. |
21 | */ |
22 | -int u1db_whats_changed(u1database *db, int *gen, void *context, u1db_doc_id_gen_callback cb); |
23 | +int u1db_whats_changed(u1database *db, int *gen, void *context, |
24 | + u1db_trans_info_callback cb); |
25 | |
26 | |
27 | /** |
28 | |
29 | === modified file 'include/u1db/u1db_internal.h' |
30 | --- include/u1db/u1db_internal.h 2012-05-17 15:13:42 +0000 |
31 | +++ include/u1db/u1db_internal.h 2012-05-18 09:33:18 +0000 |
32 | @@ -65,10 +65,14 @@ |
33 | * target, matches st_replica_uid |
34 | * @param source_gen (OUT) The last generation of source_replica_uid |
35 | * that st has synchronized with. |
36 | + * @param trans_id (OUT) The transaction id associated with the |
37 | + * source generation, the memory must be freed by |
38 | + * the caller. |
39 | */ |
40 | int (*get_sync_info)(u1db_sync_target *st, |
41 | const char *source_replica_uid, |
42 | - const char **st_replica_uid, int *st_gen, int *source_gen); |
43 | + const char **st_replica_uid, int *st_gen, int *source_gen, |
44 | + char **trans_id); |
45 | /** |
46 | * Set the synchronization information about another replica. |
47 | * |
48 | @@ -78,9 +82,10 @@ |
49 | * want to synchronize from. |
50 | * @param source_gen The last generation of source_replica_uid |
51 | * that st has synchronized with. |
52 | + * @param trans_id The transaction id associated with source_gen |
53 | */ |
54 | int (*record_sync_info)(u1db_sync_target *st, |
55 | - const char *source_replica_uid, int source_gen); |
56 | + const char *source_replica_uid, int source_gen, const char *trans_id); |
57 | |
58 | /** |
59 | * Send documents to the target, and receive the response. |
60 | @@ -249,16 +254,18 @@ |
61 | * @param replica_uid The identifier for the other database |
62 | * @param generation (OUT) The last generation that we know we synchronized |
63 | * with the other database. |
64 | + * @param trans_id (OUT) The transaction id associated with the generation. |
65 | + * Callers must free the data. |
66 | */ |
67 | -int u1db__get_sync_generation(u1database *db, const char *replica_uid, |
68 | - int *generation); |
69 | +int u1db__get_sync_gen_info(u1database *db, const char *replica_uid, |
70 | + int *generation, char **trans_id); |
71 | |
72 | /** |
73 | * Set the known sync generation for another replica. |
74 | * |
75 | */ |
76 | -int u1db__set_sync_generation(u1database *db, const char *replica_uid, |
77 | - int generation); |
78 | +int u1db__set_sync_info(u1database *db, const char *replica_uid, |
79 | + int generation, const char *trans_id); |
80 | |
81 | /** |
82 | * Internal sync api, get the stored information about another machine. |
83 | |
84 | === modified file 'src/u1db.c' |
85 | --- src/u1db.c 2012-05-17 15:26:01 +0000 |
86 | +++ src/u1db.c 2012-05-18 09:33:18 +0000 |
87 | @@ -810,7 +810,7 @@ |
88 | (stored_doc_rev != NULL)); |
89 | } |
90 | if (status == U1DB_OK && replica_uid != NULL) { |
91 | - status = u1db__set_sync_generation(db, replica_uid, replica_gen); |
92 | + status = u1db__set_sync_info(db, replica_uid, replica_gen, "T-sid"); |
93 | } |
94 | if (status == U1DB_OK && at_gen != NULL) { |
95 | status = u1db__get_generation(db, at_gen); |
96 | @@ -1108,7 +1108,7 @@ |
97 | |
98 | int |
99 | u1db_whats_changed(u1database *db, int *gen, void *context, |
100 | - int (*cb)(void *, const char *doc_id, int gen)) |
101 | + u1db_trans_info_callback cb) |
102 | { |
103 | int status; |
104 | sqlite3_stmt *statement; |
105 | @@ -1116,8 +1116,8 @@ |
106 | return -1; // Bad parameters |
107 | } |
108 | status = sqlite3_prepare_v2(db->sql_handle, |
109 | - "SELECT max(generation) as g, doc_id FROM transaction_log" |
110 | - " WHERE generation > ?" |
111 | + "SELECT max(generation) as g, doc_id, transaction_id" |
112 | + " FROM transaction_log WHERE generation > ?" |
113 | " GROUP BY doc_id ORDER BY g", |
114 | -1, &statement, NULL); |
115 | if (status != SQLITE_OK) { |
116 | @@ -1131,13 +1131,15 @@ |
117 | status = sqlite3_step(statement); |
118 | while (status == SQLITE_ROW) { |
119 | int local_gen; |
120 | - char *doc_id; |
121 | + const char *doc_id; |
122 | + const char *trans_id; |
123 | local_gen = sqlite3_column_int(statement, 0); |
124 | if (local_gen > *gen) { |
125 | *gen = local_gen; |
126 | } |
127 | - doc_id = (char *)sqlite3_column_text(statement, 1); |
128 | - cb(context, doc_id, local_gen); |
129 | + doc_id = (const char *)sqlite3_column_text(statement, 1); |
130 | + trans_id = (const char *)sqlite3_column_text(statement, 2); |
131 | + cb(context, doc_id, local_gen, trans_id); |
132 | status = sqlite3_step(statement); |
133 | } |
134 | if (status == SQLITE_DONE) { |
135 | @@ -1315,18 +1317,22 @@ |
136 | |
137 | |
138 | int |
139 | -u1db__get_sync_generation(u1database *db, const char *replica_uid, |
140 | - int *generation) |
141 | +u1db__get_sync_gen_info(u1database *db, const char *replica_uid, |
142 | + int *generation, char **trans_id) |
143 | { |
144 | int status; |
145 | sqlite3_stmt *statement; |
146 | + const char *tmp; |
147 | |
148 | - if (db == NULL || replica_uid == NULL || generation == NULL) { |
149 | + if (db == NULL || replica_uid == NULL || generation == NULL |
150 | + || trans_id == NULL) |
151 | + { |
152 | return U1DB_INVALID_PARAMETER; |
153 | } |
154 | status = sqlite3_prepare_v2(db->sql_handle, |
155 | - "SELECT known_generation FROM sync_log WHERE replica_uid = ?", -1, |
156 | - &statement, NULL); |
157 | + "SELECT known_generation, known_transaction_id" |
158 | + " FROM sync_log WHERE replica_uid = ?", |
159 | + -1, &statement, NULL); |
160 | if (status != SQLITE_OK) { goto finish; } |
161 | status = sqlite3_bind_text(statement, 1, replica_uid, -1, SQLITE_TRANSIENT); |
162 | if (status != SQLITE_OK) { goto finish; } |
163 | @@ -1334,10 +1340,17 @@ |
164 | if (status == SQLITE_DONE) { |
165 | status = SQLITE_OK; |
166 | *generation = 0; |
167 | + *trans_id = strdup(""); |
168 | } else if (status == SQLITE_ROW) { |
169 | *generation = sqlite3_column_int(statement, 0); |
170 | + // Note: We may want to handle the column containing NULL |
171 | + tmp = (const char *)sqlite3_column_text(statement, 1); |
172 | + *trans_id = strdup(tmp); |
173 | status = SQLITE_OK; |
174 | } |
175 | + if (*trans_id == NULL) { |
176 | + status = U1DB_NOMEM; |
177 | + } |
178 | finish: |
179 | sqlite3_finalize(statement); |
180 | return status; |
181 | @@ -1345,8 +1358,8 @@ |
182 | |
183 | |
184 | int |
185 | -u1db__set_sync_generation(u1database *db, const char *replica_uid, |
186 | - int generation) |
187 | +u1db__set_sync_info(u1database *db, const char *replica_uid, |
188 | + int generation, const char *trans_id) |
189 | { |
190 | int status; |
191 | sqlite3_stmt *statement; |
192 | @@ -1364,7 +1377,7 @@ |
193 | if (status != SQLITE_OK) { goto finish; } |
194 | status = sqlite3_bind_int(statement, 2, generation); |
195 | if (status != SQLITE_OK) { goto finish; } |
196 | - status = sqlite3_bind_text(statement, 3, "", -1, SQLITE_TRANSIENT); |
197 | + status = sqlite3_bind_text(statement, 3, trans_id, -1, SQLITE_TRANSIENT); |
198 | if (status != SQLITE_OK) { goto finish; } |
199 | status = sqlite3_step(statement); |
200 | if (status == SQLITE_DONE) { |
201 | |
202 | === modified file 'src/u1db_http_sync_target.c' |
203 | --- src/u1db_http_sync_target.c 2012-05-09 16:09:04 +0000 |
204 | +++ src/u1db_http_sync_target.c 2012-05-18 09:33:18 +0000 |
205 | @@ -34,17 +34,18 @@ |
206 | |
207 | static int st_http_get_sync_info(u1db_sync_target *st, |
208 | const char *source_replica_uid, |
209 | - const char **st_replica_uid, int *st_gen, int *source_gen); |
210 | + const char **st_replica_uid, int *st_gen, int *source_gen, |
211 | + char **trans_id); |
212 | |
213 | static int st_http_record_sync_info(u1db_sync_target *st, |
214 | - const char *source_replica_uid, int source_gen); |
215 | + const char *source_replica_uid, int source_gen, const char *trans_id); |
216 | |
217 | static int st_http_get_sync_exchange(u1db_sync_target *st, |
218 | const char *source_replica_uid, |
219 | int source_gen, |
220 | u1db_sync_exchange **exchange); |
221 | static int st_http_sync_exchange(u1db_sync_target *st, |
222 | - const char *source_replica_uid, |
223 | + const char *source_replica_uid, |
224 | int n_docs, u1db_document **docs, |
225 | int *generations, int *target_gen, void *context, |
226 | u1db_doc_gen_callback cb); |
227 | @@ -355,7 +356,8 @@ |
228 | static int |
229 | st_http_get_sync_info(u1db_sync_target *st, |
230 | const char *source_replica_uid, |
231 | - const char **st_replica_uid, int *st_gen, int *source_gen) |
232 | + const char **st_replica_uid, int *st_gen, int *source_gen, |
233 | + char **trans_id) |
234 | { |
235 | struct _http_state *state; |
236 | struct _http_request req = {0}; |
237 | @@ -451,6 +453,15 @@ |
238 | goto finish; |
239 | } |
240 | *source_gen = json_object_get_int(obj); |
241 | + obj = json_object_object_get(json, "source_transaction_id"); |
242 | + if (obj == NULL) { |
243 | + status = U1DB_INVALID_HTTP_RESPONSE; |
244 | + goto finish; |
245 | + } |
246 | + *trans_id = strdup(json_object_get_string(obj)); |
247 | + if (*trans_id == NULL) { |
248 | + status = U1DB_NOMEM; |
249 | + } |
250 | finish: |
251 | if (req.header_buffer != NULL) { |
252 | free(req.header_buffer); |
253 | @@ -508,7 +519,7 @@ |
254 | |
255 | static int |
256 | st_http_record_sync_info(u1db_sync_target *st, |
257 | - const char *source_replica_uid, int source_gen) |
258 | + const char *source_replica_uid, int source_gen, const char *trans_id) |
259 | { |
260 | struct _http_state *state; |
261 | struct _http_request req = {0}; |
262 | @@ -537,6 +548,8 @@ |
263 | goto finish; |
264 | } |
265 | json_object_object_add(json, "generation", json_object_new_int(source_gen)); |
266 | + json_object_object_add(json, "transaction_id", |
267 | + json_object_new_string(trans_id)); |
268 | raw_body = json_object_to_json_string(json); |
269 | raw_len = strlen(raw_body); |
270 | req.state = state; |
271 | @@ -561,7 +574,7 @@ |
272 | status = curl_easy_setopt(state->curl, CURLOPT_INFILESIZE_LARGE, |
273 | (curl_off_t)req.num_put_bytes); |
274 | if (status != CURLE_OK) { goto finish; } |
275 | - status = maybe_sign_url(st, "PUT", url, &headers); |
276 | + status = maybe_sign_url(st, "PUT", url, &headers); |
277 | if (status != U1DB_OK) { goto finish; } |
278 | |
279 | // Now actually send the data |
280 | |
281 | === modified file 'src/u1db_sync_target.c' |
282 | --- src/u1db_sync_target.c 2012-05-17 13:25:42 +0000 |
283 | +++ src/u1db_sync_target.c 2012-05-18 09:33:18 +0000 |
284 | @@ -23,10 +23,11 @@ |
285 | |
286 | static int st_get_sync_info(u1db_sync_target *st, |
287 | const char *source_replica_uid, |
288 | - const char **st_replica_uid, int *st_gen, int *source_gen); |
289 | + const char **st_replica_uid, int *st_gen, int *source_gen, |
290 | + char **trans_id); |
291 | |
292 | static int st_record_sync_info(u1db_sync_target *st, |
293 | - const char *source_replica_uid, int source_gen); |
294 | + const char *source_replica_uid, int source_gen, const char *trans_id); |
295 | |
296 | static int st_sync_exchange(u1db_sync_target *st, |
297 | const char *source_replica_uid, int n_docs, |
298 | @@ -100,7 +101,8 @@ |
299 | |
300 | static int |
301 | st_get_sync_info(u1db_sync_target *st, const char *source_replica_uid, |
302 | - const char **st_replica_uid, int *st_gen, int *source_gen) |
303 | + const char **st_replica_uid, int *st_gen, int *source_gen, |
304 | + char **trans_id) |
305 | { |
306 | int status = U1DB_OK; |
307 | u1database *db; |
308 | @@ -119,7 +121,8 @@ |
309 | db = (u1database *)st->implementation; |
310 | status = u1db_get_replica_uid(db, st_replica_uid); |
311 | if (status != U1DB_OK) { goto finish; } |
312 | - status = u1db__get_sync_generation(db, source_replica_uid, source_gen); |
313 | + status = u1db__get_sync_gen_info(db, source_replica_uid, source_gen, |
314 | + trans_id); |
315 | if (status != U1DB_OK) { goto finish; } |
316 | status = u1db__get_generation(db, st_gen); |
317 | finish: |
318 | @@ -129,7 +132,7 @@ |
319 | |
320 | static int |
321 | st_record_sync_info(u1db_sync_target *st, const char *source_replica_uid, |
322 | - int source_gen) |
323 | + int source_gen, const char *trans_id) |
324 | { |
325 | int status; |
326 | u1database *db; |
327 | @@ -141,7 +144,7 @@ |
328 | if (status != U1DB_OK) { goto finish; } |
329 | } |
330 | db = (u1database *)st->implementation; |
331 | - status = u1db__set_sync_generation(db, source_replica_uid, source_gen); |
332 | + status = u1db__set_sync_info(db, source_replica_uid, source_gen, trans_id); |
333 | finish: |
334 | return status; |
335 | } |
336 | @@ -301,7 +304,8 @@ |
337 | // Callback for whats_changed to map the callback into the sync_exchange |
338 | // doc_ids_to_return array. |
339 | static int |
340 | -whats_changed_to_doc_ids(void *context, const char *doc_id, int gen) |
341 | +whats_changed_to_doc_ids(void *context, const char *doc_id, int gen, |
342 | + const char *trans_id) |
343 | { |
344 | struct lh_entry *e; |
345 | struct _whats_changed_doc_ids_state *state; |
346 | @@ -543,6 +547,8 @@ |
347 | struct _whats_changed_doc_ids_state to_send_state = {0}; |
348 | struct _return_doc_state return_doc_state = {0}; |
349 | const char *target_uid, *local_uid; |
350 | + char *target_trans_id_known_by_local = NULL; |
351 | + char *local_trans_id_known_by_target = NULL; |
352 | int target_gen, local_gen; |
353 | int local_gen_known_by_target, target_gen_known_by_local; |
354 | |
355 | @@ -556,10 +562,10 @@ |
356 | if (status != U1DB_OK) { goto finish; } |
357 | // fprintf(stderr, "Local uid: %s\n", local_uid); |
358 | status = target->get_sync_info(target, local_uid, &target_uid, &target_gen, |
359 | - &local_gen_known_by_target); |
360 | + &local_gen_known_by_target, &local_trans_id_known_by_target); |
361 | if (status != U1DB_OK) { goto finish; } |
362 | - status = u1db__get_sync_generation(db, target_uid, |
363 | - &target_gen_known_by_local); |
364 | + status = u1db__get_sync_gen_info(db, target_uid, |
365 | + &target_gen_known_by_local, &target_trans_id_known_by_local); |
366 | if (status != U1DB_OK) { goto finish; } |
367 | local_gen = local_gen_known_by_target; |
368 | |
369 | @@ -589,17 +595,24 @@ |
370 | if (status != U1DB_OK) { goto finish; } |
371 | // Now we successfully sent and received docs, make sure we record the |
372 | // current remote generation |
373 | - status = u1db__set_sync_generation(db, target_uid, |
374 | - target_gen_known_by_local); |
375 | + status = u1db__set_sync_info(db, target_uid, target_gen_known_by_local, |
376 | + "T-sid"); |
377 | if (status != U1DB_OK) { goto finish; } |
378 | if (return_doc_state.num_inserted > 0 && |
379 | ((*local_gen_before_sync + return_doc_state.num_inserted) |
380 | == local_gen)) |
381 | { |
382 | - status = target->record_sync_info(target, local_uid, local_gen); |
383 | + status = target->record_sync_info(target, local_uid, local_gen, |
384 | + "T-sid"); |
385 | if (status != U1DB_OK) { goto finish; } |
386 | } |
387 | finish: |
388 | + if (local_trans_id_known_by_target != NULL) { |
389 | + free(local_trans_id_known_by_target); |
390 | + } |
391 | + if (target_trans_id_known_by_local != NULL) { |
392 | + free(target_trans_id_known_by_local); |
393 | + } |
394 | if (to_send_state.doc_ids_to_return != NULL) { |
395 | int i; |
396 | |
397 | |
398 | === modified file 'u1db/__init__.py' |
399 | --- u1db/__init__.py 2012-05-17 13:22:20 +0000 |
400 | +++ u1db/__init__.py 2012-05-18 09:33:18 +0000 |
401 | @@ -57,11 +57,12 @@ |
402 | |
403 | :param old_generation: The generation of the database in the old |
404 | state. |
405 | - :return: (cur_generation, [(doc_id, generation),...]) |
406 | + :return: (cur_generation, [(doc_id, generation, trans_id),...]) |
407 | The current generation of the database, and a list of of |
408 | changed documents since old_generation, represented by tuples |
409 | - with for each document its doc_id and the generation corresponding |
410 | - to the last intervening change and sorted by generation |
411 | + with for each document its doc_id and the generation and |
412 | + transaction id corresponding to the last intervening change and |
413 | + sorted by generation (old changes first) |
414 | """ |
415 | raise NotImplementedError(self.whats_changed) |
416 | |
417 | @@ -216,29 +217,35 @@ |
418 | from u1db.remote.http_target import HTTPSyncTarget |
419 | return Synchronizer(self, HTTPSyncTarget(url)).sync() |
420 | |
421 | - def _get_sync_generation(self, other_replica_uid): |
422 | - """Return the last known database generation of the other db replica. |
423 | + def _get_sync_gen_info(self, other_replica_uid): |
424 | + """Return the last known information about the other db replica. |
425 | |
426 | When you do a synchronization with another replica, the Database keeps |
427 | - track of what generation the other database replica was at. |
428 | - This way we only have to request data that is newer. |
429 | + track of what generation the other database replica was at, and what |
430 | + the associated transaction id was. This is used to determine what data |
431 | + needs to be sent, and if two databases are claiming to be the same |
432 | + replica. |
433 | |
434 | :param other_replica_uid: The identifier for the other replica. |
435 | - :return: The generation we encountered during synchronization. If we've |
436 | - never synchronized with the replica, this is 0. |
437 | + :return: (gen, trans_id) The generation and transaction id we |
438 | + encountered during synchronization. If we've never synchronized |
439 | + with the replica, this is (0, ''). |
440 | """ |
441 | - raise NotImplementedError(self._get_sync_generation) |
442 | + raise NotImplementedError(self._get_sync_gen_info) |
443 | |
444 | - def _set_sync_generation(self, other_replica_uid, other_generation): |
445 | + def _set_sync_info(self, other_replica_uid, other_generation, |
446 | + other_transaction_id): |
447 | """Set the last-known generation for the other database replica. |
448 | |
449 | We have just performed some synchronization, and we want to track what |
450 | - generation the other replica was at. See also get_sync_generation. |
451 | + generation the other replica was at. See also _get_sync_gen_info. |
452 | :param other_replica_uid: The U1DB identifier for the other replica. |
453 | :param other_generation: The generation number for the other replica. |
454 | + :param other_transaction_id: The transaction id associated with the |
455 | + generation. |
456 | :return: None |
457 | """ |
458 | - raise NotImplementedError(self._set_sync_generation) |
459 | + raise NotImplementedError(self._set_sync_info) |
460 | |
461 | def _put_doc_if_newer(self, doc, save_conflict, replica_uid=None, |
462 | replica_gen=None): |
463 | @@ -338,11 +345,13 @@ |
464 | :param source_replica_uid: Another replica which we might have |
465 | synchronized with in the past. |
466 | :return: (target_replica_uid, target_replica_generation, |
467 | - source_replica_last_known_generation) |
468 | + source_replica_last_known_generation, |
469 | + source_replica_last_known_transaction_id) |
470 | """ |
471 | raise NotImplementedError(self.get_sync_info) |
472 | |
473 | - def record_sync_info(self, source_replica_uid, source_replica_generation): |
474 | + def record_sync_info(self, source_replica_uid, source_replica_generation, |
475 | + source_replica_transaction_id): |
476 | """Record tip information for another replica. |
477 | |
478 | After sync_exchange has been processed, the caller will have |
479 | @@ -358,6 +367,8 @@ |
480 | :param source_replica_uid: The identifier for the source replica. |
481 | :param source_replica_generation: |
482 | The database generation for the source replica. |
483 | + :param source_replica_transaction_id: The transaction id associated |
484 | + with the source replica generation. |
485 | :return: None |
486 | """ |
487 | raise NotImplementedError(self.record_sync_info) |
488 | |
489 | === modified file 'u1db/backends/__init__.py' |
490 | --- u1db/backends/__init__.py 2012-05-17 14:44:25 +0000 |
491 | +++ u1db/backends/__init__.py 2012-05-18 09:33:18 +0000 |
492 | @@ -115,7 +115,7 @@ |
493 | if save_conflict: |
494 | self._force_doc_sync_conflict(doc) |
495 | if replica_uid is not None and replica_gen is not None: |
496 | - self._do_set_sync_generation(replica_uid, replica_gen) |
497 | + self._do_set_sync_info(replica_uid, replica_gen, 'T-sid') |
498 | return state, self._get_generation() |
499 | |
500 | def _ensure_maximal_rev(self, cur_rev, extra_revs): |
501 | |
502 | === modified file 'u1db/backends/dbschema.sql' |
503 | --- u1db/backends/dbschema.sql 2012-05-17 13:59:05 +0000 |
504 | +++ u1db/backends/dbschema.sql 2012-05-18 09:33:18 +0000 |
505 | @@ -1,17 +1,17 @@ |
506 | -- Database schema |
507 | CREATE TABLE transaction_log ( |
508 | generation INTEGER PRIMARY KEY AUTOINCREMENT, |
509 | - doc_id TEXT, |
510 | - transaction_id TEXT |
511 | + doc_id TEXT NOT NULL, |
512 | + transaction_id TEXT NOT NULL |
513 | ); |
514 | CREATE TABLE document ( |
515 | doc_id TEXT PRIMARY KEY, |
516 | - doc_rev TEXT, |
517 | + doc_rev TEXT NOT NULL, |
518 | content TEXT |
519 | ); |
520 | CREATE TABLE document_fields ( |
521 | - doc_id TEXT, |
522 | - field_name TEXT, |
523 | + doc_id TEXT NOT NULL, |
524 | + field_name TEXT NOT NULL, |
525 | value TEXT |
526 | ); |
527 | CREATE INDEX document_fields_field_value_doc_idx |
528 | |
529 | === modified file 'u1db/backends/inmemory.py' |
530 | --- u1db/backends/inmemory.py 2012-05-17 14:44:25 +0000 |
531 | +++ u1db/backends/inmemory.py 2012-05-18 09:33:18 +0000 |
532 | @@ -45,16 +45,20 @@ |
533 | # may be closing it, while another wants to inspect the results. |
534 | pass |
535 | |
536 | - def _get_sync_generation(self, other_replica_uid): |
537 | - return self._other_generations.get(other_replica_uid, 0) |
538 | - |
539 | - def _set_sync_generation(self, other_replica_uid, other_generation): |
540 | - self._do_set_sync_generation(other_replica_uid, other_generation) |
541 | - |
542 | - def _do_set_sync_generation(self, other_replica_uid, other_generation): |
543 | + def _get_sync_gen_info(self, other_replica_uid): |
544 | + return self._other_generations.get(other_replica_uid, (0, '')) |
545 | + |
546 | + def _set_sync_info(self, other_replica_uid, other_generation, |
547 | + other_transaction_id): |
548 | + self._do_set_sync_info(other_replica_uid, other_generation, |
549 | + other_transaction_id) |
550 | + |
551 | + def _do_set_sync_info(self, other_replica_uid, other_generation, |
552 | + other_transaction_id): |
553 | # TODO: to handle race conditions, we may want to check if the current |
554 | # value is greater than this new value. |
555 | - self._other_generations[other_replica_uid] = other_generation |
556 | + self._other_generations[other_replica_uid] = (other_generation, |
557 | + other_transaction_id) |
558 | |
559 | def get_sync_target(self): |
560 | return InMemorySyncTarget(self) |
561 | @@ -203,7 +207,7 @@ |
562 | generation = cur_generation |
563 | for doc_id, trans_id in reversed(relevant_tail): |
564 | if doc_id not in seen: |
565 | - changes.append((doc_id, generation)) |
566 | + changes.append((doc_id, generation, trans_id)) |
567 | seen.add(doc_id) |
568 | generation -= 1 |
569 | changes.reverse() |
570 | @@ -334,12 +338,13 @@ |
571 | class InMemorySyncTarget(CommonSyncTarget): |
572 | |
573 | def get_sync_info(self, source_replica_uid): |
574 | - source_gen = self._db._get_sync_generation(source_replica_uid) |
575 | - return ( |
576 | - self._db._replica_uid, len(self._db._transaction_log), source_gen) |
577 | + source_gen, trans_id = self._db._get_sync_gen_info(source_replica_uid) |
578 | + return (self._db._replica_uid, len(self._db._transaction_log), |
579 | + source_gen, trans_id) |
580 | |
581 | - def record_sync_info(self, source_replica_uid, source_replica_generation): |
582 | + def record_sync_info(self, source_replica_uid, source_replica_generation, |
583 | + source_transaction_id): |
584 | if self._trace_hook: |
585 | self._trace_hook('record_sync_info') |
586 | - self._db._set_sync_generation(source_replica_uid, |
587 | - source_replica_generation) |
588 | + self._db._set_sync_info(source_replica_uid, source_replica_generation, |
589 | + source_transaction_id) |
590 | |
591 | === modified file 'u1db/backends/sqlite_backend.py' |
592 | --- u1db/backends/sqlite_backend.py 2012-05-17 15:13:42 +0000 |
593 | +++ u1db/backends/sqlite_backend.py 2012-05-18 09:33:18 +0000 |
594 | @@ -355,16 +355,17 @@ |
595 | |
596 | def whats_changed(self, old_generation=0): |
597 | c = self._db_handle.cursor() |
598 | - c.execute("SELECT generation, doc_id FROM transaction_log" |
599 | + c.execute("SELECT generation, doc_id, transaction_id" |
600 | + " FROM transaction_log" |
601 | " WHERE generation > ? ORDER BY generation DESC", |
602 | (old_generation,)) |
603 | results = c.fetchall() |
604 | cur_gen = old_generation |
605 | seen = set() |
606 | changes = [] |
607 | - for generation, doc_id in results: |
608 | + for generation, doc_id, trans_id in results: |
609 | if doc_id not in seen: |
610 | - changes.append((doc_id, generation)) |
611 | + changes.append((doc_id, generation, trans_id)) |
612 | seen.add(doc_id) |
613 | if changes: |
614 | cur_gen = changes[0][1] # max generation |
615 | @@ -409,26 +410,32 @@ |
616 | this_doc.has_conflicts = True |
617 | return [this_doc] + conflict_docs |
618 | |
619 | - def _get_sync_generation(self, other_replica_uid): |
620 | + def _get_sync_gen_info(self, other_replica_uid): |
621 | c = self._db_handle.cursor() |
622 | - c.execute("SELECT known_generation FROM sync_log" |
623 | + c.execute("SELECT known_generation, known_transaction_id FROM sync_log" |
624 | " WHERE replica_uid = ?", |
625 | (other_replica_uid,)) |
626 | val = c.fetchone() |
627 | if val is None: |
628 | other_gen = 0 |
629 | + trans_id = '' |
630 | else: |
631 | other_gen = val[0] |
632 | - return other_gen |
633 | + trans_id = val[1] |
634 | + return other_gen, trans_id |
635 | |
636 | - def _set_sync_generation(self, other_replica_uid, other_generation): |
637 | + def _set_sync_info(self, other_replica_uid, other_generation, |
638 | + other_transaction_id): |
639 | with self._db_handle: |
640 | - self._do_set_sync_generation(other_replica_uid, other_generation) |
641 | + self._do_set_sync_info(other_replica_uid, other_generation, |
642 | + other_transaction_id) |
643 | |
644 | - def _do_set_sync_generation(self, other_replica_uid, other_generation): |
645 | + def _do_set_sync_info(self, other_replica_uid, other_generation, |
646 | + other_transaction_id): |
647 | c = self._db_handle.cursor() |
648 | c.execute("INSERT OR REPLACE INTO sync_log VALUES (?, ?, ?)", |
649 | - (other_replica_uid, other_generation, '')) |
650 | + (other_replica_uid, other_generation, |
651 | + other_transaction_id)) |
652 | |
653 | def _put_doc_if_newer(self, doc, save_conflict, replica_uid=None, |
654 | replica_gen=None): |
655 | @@ -608,15 +615,16 @@ |
656 | class SQLiteSyncTarget(CommonSyncTarget): |
657 | |
658 | def get_sync_info(self, source_replica_uid): |
659 | - source_gen = self._db._get_sync_generation(source_replica_uid) |
660 | + source_gen, trans_id = self._db._get_sync_gen_info(source_replica_uid) |
661 | my_gen = self._db._get_generation() |
662 | - return self._db._replica_uid, my_gen, source_gen |
663 | + return self._db._replica_uid, my_gen, source_gen, trans_id |
664 | |
665 | - def record_sync_info(self, source_replica_uid, source_replica_generation): |
666 | + def record_sync_info(self, source_replica_uid, source_replica_generation, |
667 | + source_replica_transaction_id): |
668 | if self._trace_hook: |
669 | self._trace_hook('record_sync_info') |
670 | - self._db._set_sync_generation(source_replica_uid, |
671 | - source_replica_generation) |
672 | + self._db._set_sync_info(source_replica_uid, source_replica_generation, |
673 | + source_replica_transaction_id) |
674 | |
675 | |
676 | class SQLitePartialExpandDatabase(SQLiteDatabase): |
677 | |
678 | === modified file 'u1db/remote/http_app.py' |
679 | --- u1db/remote/http_app.py 2012-05-15 13:01:47 +0000 |
680 | +++ u1db/remote/http_app.py 2012-05-18 09:33:18 +0000 |
681 | @@ -290,12 +290,14 @@ |
682 | self.responder.send_response_json(target_replica_uid=result[0], |
683 | target_replica_generation=result[1], |
684 | source_replica_uid=self.source_replica_uid, |
685 | - source_replica_generation=result[2]) |
686 | + source_replica_generation=result[2], |
687 | + source_transaction_id=result[3]) |
688 | |
689 | @http_method(generation=int, |
690 | content_as_args=True, no_query=True) |
691 | - def put(self, generation): |
692 | - self.target.record_sync_info(self.source_replica_uid, generation) |
693 | + def put(self, generation, transaction_id): |
694 | + self.target.record_sync_info(self.source_replica_uid, generation, |
695 | + transaction_id) |
696 | self.responder.send_response_json(ok=True) |
697 | |
698 | # Implements the same logic as LocalSyncTarget.sync_exchange |
699 | |
700 | === modified file 'u1db/remote/http_target.py' |
701 | --- u1db/remote/http_target.py 2012-05-13 09:56:59 +0000 |
702 | +++ u1db/remote/http_target.py 2012-05-18 09:33:18 +0000 |
703 | @@ -42,12 +42,14 @@ |
704 | self._ensure_connection() |
705 | res, _ = self._request_json('GET', ['sync-from', source_replica_uid]) |
706 | return (res['target_replica_uid'], res['target_replica_generation'], |
707 | - res['source_replica_generation']) |
708 | + res['source_replica_generation'], res['source_transaction_id']) |
709 | |
710 | - def record_sync_info(self, source_replica_uid, source_replica_generation): |
711 | + def record_sync_info(self, source_replica_uid, source_replica_generation, |
712 | + source_transaction_id): |
713 | self._ensure_connection() |
714 | self._request_json('PUT', ['sync-from', source_replica_uid], {}, |
715 | - {'generation': source_replica_generation}) |
716 | + {'generation': source_replica_generation, |
717 | + 'transaction_id': source_transaction_id}) |
718 | |
719 | def _parse_sync_stream(self, data, return_doc_cb): |
720 | parts = data.splitlines() # one at a time |
721 | |
722 | === modified file 'u1db/sync.py' |
723 | --- u1db/sync.py 2012-05-17 13:36:08 +0000 |
724 | +++ u1db/sync.py 2012-05-18 09:33:18 +0000 |
725 | @@ -86,28 +86,28 @@ |
726 | if (cur_gen == start_generation + self.num_inserted |
727 | and self.num_inserted > 0): |
728 | self.sync_target.record_sync_info(self.source._replica_uid, |
729 | - cur_gen) |
730 | + cur_gen, 'T-sid') |
731 | |
732 | def sync(self, callback=None): |
733 | """Synchronize documents between source and target.""" |
734 | sync_target = self.sync_target |
735 | # get target identifier, its current generation, |
736 | # and its last-seen database generation for this source |
737 | - (self.target_replica_uid, target_gen, |
738 | - target_my_gen) = sync_target.get_sync_info(self.source._replica_uid) |
739 | + (self.target_replica_uid, target_gen, target_my_gen, |
740 | + target_my_trans_id) = sync_target.get_sync_info(self.source._replica_uid) |
741 | # what's changed since that generation and this current gen |
742 | my_gen, changes = self.source.whats_changed(target_my_gen) |
743 | |
744 | # this source last-seen database generation for the target |
745 | - target_last_known_gen = self.source._get_sync_generation( |
746 | + target_last_known_gen, target_trans_id = self.source._get_sync_gen_info( |
747 | self.target_replica_uid) |
748 | if not changes and target_last_known_gen == target_gen: |
749 | return my_gen |
750 | - changed_doc_ids = [doc_id for doc_id, _ in changes] |
751 | + changed_doc_ids = [doc_id for doc_id, _, _ in changes] |
752 | # prepare to send all the changed docs |
753 | docs_to_send = self.source.get_docs(changed_doc_ids, |
754 | check_for_conflicts=False) |
755 | - docs_by_generation = zip(docs_to_send, (gen for _, gen in changes)) |
756 | + docs_by_generation = zip(docs_to_send, (gen for _, gen, _ in changes)) |
757 | |
758 | # exchange documents and try to insert the returned ones with |
759 | # the target, return target synced-up-to gen |
760 | @@ -115,7 +115,7 @@ |
761 | self.source._replica_uid, target_last_known_gen, |
762 | return_doc_cb=self._insert_doc_from_target) |
763 | # record target synced-up-to generation including applying what we sent |
764 | - self.source._set_sync_generation(self.target_replica_uid, new_gen) |
765 | + self.source._set_sync_info(self.target_replica_uid, new_gen, 'T-sid') |
766 | |
767 | # if gapless record current reached generation with target |
768 | self._record_sync_info_with_the_target(my_gen) |
769 | @@ -206,10 +206,10 @@ |
770 | self.new_gen = gen |
771 | seen_ids = self.seen_ids |
772 | # changed docs that weren't superseded by or converged with |
773 | - self.changes_to_return = [(doc_id, gen) for (doc_id, gen) in changes |
774 | - if doc_id not in seen_ids or |
775 | - # there was a subsequent update |
776 | - seen_ids.get(doc_id) < gen] |
777 | + self.changes_to_return = [(doc_id, gen) for (doc_id, gen, _) in changes |
778 | + # there was a subsequent update |
779 | + if doc_id not in seen_ids or |
780 | + seen_ids.get(doc_id) < gen] |
781 | return self.new_gen |
782 | |
783 | def return_docs(self, return_doc_cb): |
784 | |
785 | === modified file 'u1db/tests/__init__.py' |
786 | --- u1db/tests/__init__.py 2012-05-17 15:13:42 +0000 |
787 | +++ u1db/tests/__init__.py 2012-05-18 09:33:18 +0000 |
788 | @@ -177,6 +177,10 @@ |
789 | seen_transactions.add(transaction_id) |
790 | self.assertEqual(doc_ids, just_ids) |
791 | |
792 | + def getLastTransId(self, db): |
793 | + """Return the transaction id for the last database update.""" |
794 | + return self.db._get_transaction_log()[-1][-1] |
795 | + |
796 | |
797 | class ServerStateForTests(server_state.ServerState): |
798 | """Used in the test suite, so we don't have to touch disk, etc.""" |
799 | |
800 | === modified file 'u1db/tests/c_backend_wrapper.pyx' |
801 | --- u1db/tests/c_backend_wrapper.pyx 2012-05-17 15:13:42 +0000 |
802 | +++ u1db/tests/c_backend_wrapper.pyx 2012-05-18 09:33:18 +0000 |
803 | @@ -61,8 +61,6 @@ |
804 | ctypedef int (*u1db_key_callback)(void *context, char *key) |
805 | ctypedef int (*u1db_doc_gen_callback)(void *context, |
806 | u1db_document *doc, int gen) |
807 | - ctypedef int (*u1db_doc_id_gen_callback)(void *context, |
808 | - const_char_ptr doc_id, int gen) |
809 | ctypedef int (*u1db_trans_info_callback)(void *context, |
810 | const_char_ptr doc_id, int gen, const_char_ptr trans_id) |
811 | |
812 | @@ -85,7 +83,7 @@ |
813 | int n_revs, const_char_ptr *revs) |
814 | int u1db_delete_doc(u1database *db, u1db_document *doc) |
815 | int u1db_whats_changed(u1database *db, int *gen, void *context, |
816 | - u1db_doc_id_gen_callback cb) |
817 | + u1db_trans_info_callback cb) |
818 | int u1db__get_transaction_log(u1database *db, void *context, |
819 | u1db_trans_info_callback cb) |
820 | int u1db_get_doc_conflicts(u1database *db, char *doc_id, void *context, |
821 | @@ -156,9 +154,10 @@ |
822 | ctypedef struct u1db_sync_target: |
823 | int (*get_sync_info)(u1db_sync_target *st, |
824 | char *source_replica_uid, |
825 | - const_char_ptr *st_replica_uid, int *st_gen, int *source_gen) nogil |
826 | + const_char_ptr *st_replica_uid, int *st_gen, int *source_gen, |
827 | + char **source_trans_id) nogil |
828 | int (*record_sync_info)(u1db_sync_target *st, |
829 | - char *source_replica_uid, int source_gen) nogil |
830 | + char *source_replica_uid, int source_gen, char *trans_id) nogil |
831 | int (*sync_exchange)(u1db_sync_target *st, |
832 | char *source_replica_uid, int n_docs, |
833 | u1db_document **docs, int *generations, |
834 | @@ -191,10 +190,10 @@ |
835 | char *content, int has_conflicts) |
836 | int u1db__generate_hex_uuid(char *) |
837 | |
838 | - int u1db__get_sync_generation(u1database *db, char *replica_uid, |
839 | - int *generation) |
840 | - int u1db__set_sync_generation(u1database *db, char *replica_uid, |
841 | - int generation) |
842 | + int u1db__get_sync_gen_info(u1database *db, char *replica_uid, |
843 | + int *generation, char **trans_id) |
844 | + int u1db__set_sync_info(u1database *db, char *replica_uid, int generation, |
845 | + char *trans_id) |
846 | int u1db__sync_get_machine_info(u1database *db, char *other_replica_uid, |
847 | int *other_db_rev, char **my_replica_uid, |
848 | int *my_db_rev) |
849 | @@ -253,14 +252,6 @@ |
850 | from sqlite3 import dbapi2 |
851 | |
852 | |
853 | -cdef int _append_doc_gen_to_list(void *context, const_char_ptr doc_id, |
854 | - int generation) with gil: |
855 | - a_list = <object>(context) |
856 | - doc = doc_id |
857 | - a_list.append((doc, generation)) |
858 | - return 0 |
859 | - |
860 | - |
861 | cdef int _append_trans_info_to_list(void *context, const_char_ptr doc_id, |
862 | int generation, |
863 | const_char_ptr trans_id) with gil: |
864 | @@ -665,22 +656,27 @@ |
865 | def get_sync_info(self, source_replica_uid): |
866 | cdef const_char_ptr st_replica_uid = NULL |
867 | cdef int st_gen = 0, source_gen = 0, status |
868 | + cdef char *trans_id = NULL |
869 | |
870 | self._check() |
871 | assert self._st.get_sync_info != NULL, "get_sync_info is NULL?" |
872 | with nogil: |
873 | status = self._st.get_sync_info(self._st, source_replica_uid, |
874 | - &st_replica_uid, &st_gen, &source_gen) |
875 | + &st_replica_uid, &st_gen, &source_gen, &trans_id) |
876 | handle_status("get_sync_info", status) |
877 | - return (safe_str(st_replica_uid), st_gen, source_gen) |
878 | + res_trans_id = None |
879 | + if trans_id != NULL: |
880 | + res_trans_id = trans_id |
881 | + free(trans_id) |
882 | + return (safe_str(st_replica_uid), st_gen, source_gen, res_trans_id) |
883 | |
884 | - def record_sync_info(self, source_replica_uid, source_gen): |
885 | + def record_sync_info(self, source_replica_uid, source_gen, source_trans_id): |
886 | cdef int status |
887 | self._check() |
888 | assert self._st.record_sync_info != NULL, "record_sync_info is NULL?" |
889 | with nogil: |
890 | status = self._st.record_sync_info(self._st, source_replica_uid, |
891 | - source_gen) |
892 | + source_gen, source_trans_id) |
893 | handle_status("record_sync_info", status) |
894 | |
895 | def _get_sync_exchange(self, source_replica_uid, source_gen): |
896 | @@ -952,7 +948,7 @@ |
897 | c_generation = generation |
898 | handle_status("whats_changed", |
899 | u1db_whats_changed(self._db, &c_generation, <void*>a_list, |
900 | - _append_doc_gen_to_list)) |
901 | + _append_trans_info_to_list)) |
902 | return c_generation, a_list |
903 | |
904 | def _get_transaction_log(self): |
905 | @@ -968,16 +964,22 @@ |
906 | u1db__get_generation(self._db, &generation)) |
907 | return generation |
908 | |
909 | - def _get_sync_generation(self, replica_uid): |
910 | - cdef int generation |
911 | - |
912 | - handle_status("_get_sync_generation", |
913 | - u1db__get_sync_generation(self._db, replica_uid, &generation)) |
914 | - return generation |
915 | - |
916 | - def _set_sync_generation(self, replica_uid, generation): |
917 | - handle_status("_set_sync_generation", |
918 | - u1db__set_sync_generation(self._db, replica_uid, generation)) |
919 | + def _get_sync_gen_info(self, replica_uid): |
920 | + cdef int generation, status |
921 | + cdef char *trans_id = NULL |
922 | + |
923 | + status = u1db__get_sync_gen_info(self._db, replica_uid, &generation, |
924 | + &trans_id) |
925 | + handle_status("_get_sync_gen_info", status) |
926 | + raw_trans_id = None |
927 | + if trans_id != NULL: |
928 | + raw_trans_id = trans_id |
929 | + free(trans_id) |
930 | + return generation, raw_trans_id |
931 | + |
932 | + def _set_sync_info(self, replica_uid, generation, trans_id): |
933 | + handle_status("_set_sync_info", |
934 | + u1db__set_sync_info(self._db, replica_uid, generation, trans_id)) |
935 | |
936 | def _sync_exchange(self, docs_info, from_replica_uid, from_machine_rev, |
937 | last_known_rev): |
938 | |
939 | === modified file 'u1db/tests/test_backends.py' |
940 | --- u1db/tests/test_backends.py 2012-05-17 14:44:25 +0000 |
941 | +++ u1db/tests/test_backends.py 2012-05-18 09:33:18 +0000 |
942 | @@ -296,19 +296,19 @@ |
943 | |
944 | def test_put_doc_if_newer_replica_uid(self): |
945 | doc1 = self.db.create_doc(simple_doc) |
946 | - self.db._set_sync_generation('other', 1) |
947 | + self.db._set_sync_info('other', 1, 'T-sid') |
948 | doc2 = self.make_document(doc1.doc_id, doc1.rev + '|other:1', |
949 | nested_doc) |
950 | self.assertEqual('inserted', |
951 | self.db._put_doc_if_newer(doc2, save_conflict=False, |
952 | replica_uid='other', replica_gen=2)[0]) |
953 | - self.assertEqual(2, self.db._get_sync_generation('other')) |
954 | + self.assertEqual(2, self.db._get_sync_gen_info('other')[0]) |
955 | # Compare to the old rev, should be superseded |
956 | doc2 = self.make_document(doc1.doc_id, doc1.rev, nested_doc) |
957 | self.assertEqual('superseded', |
958 | self.db._put_doc_if_newer(doc2, save_conflict=False, |
959 | replica_uid='other', replica_gen=3)[0]) |
960 | - self.assertEqual(3, self.db._get_sync_generation('other')) |
961 | + self.assertEqual(3, self.db._get_sync_gen_info('other')[0]) |
962 | # A conflict that isn't saved still records the sync gen, because we |
963 | # don't need to see it again |
964 | doc2 = self.make_document(doc1.doc_id, doc1.rev + '|fourth:1', |
965 | @@ -316,12 +316,13 @@ |
966 | self.assertEqual('conflicted', |
967 | self.db._put_doc_if_newer(doc2, save_conflict=False, |
968 | replica_uid='other', replica_gen=4)[0]) |
969 | - self.assertEqual(4, self.db._get_sync_generation('other')) |
970 | + self.assertEqual(4, self.db._get_sync_gen_info('other')[0]) |
971 | |
972 | - def test__get_sync_generation(self): |
973 | - self.assertEqual(0, self.db._get_sync_generation('other-db')) |
974 | - self.db._set_sync_generation('other-db', 2) |
975 | - self.assertEqual(2, self.db._get_sync_generation('other-db')) |
976 | + def test__get_sync_gen_info(self): |
977 | + self.assertEqual((0, ''), self.db._get_sync_gen_info('other-db')) |
978 | + self.db._set_sync_info('other-db', 2, 'T-transaction') |
979 | + self.assertEqual((2, 'T-transaction'), |
980 | + self.db._get_sync_gen_info('other-db')) |
981 | |
982 | def test_put_updates_transaction_log(self): |
983 | doc = self.db.create_doc(simple_doc) |
984 | @@ -329,13 +330,17 @@ |
985 | doc.content = '{"something": "else"}' |
986 | self.db.put_doc(doc) |
987 | self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) |
988 | - self.assertEqual((2, [(doc.doc_id, 2)]), self.db.whats_changed()) |
989 | + last_trans_id = self.getLastTransId(self.db) |
990 | + self.assertEqual((2, [(doc.doc_id, 2, last_trans_id)]), |
991 | + self.db.whats_changed()) |
992 | |
993 | def test_delete_updates_transaction_log(self): |
994 | doc = self.db.create_doc(simple_doc) |
995 | db_gen, _ = self.db.whats_changed() |
996 | self.db.delete_doc(doc) |
997 | - self.assertEqual((2, [(doc.doc_id, 2)]), self.db.whats_changed(db_gen)) |
998 | + last_trans_id = self.getLastTransId(self.db) |
999 | + self.assertEqual((2, [(doc.doc_id, 2, last_trans_id)]), |
1000 | + self.db.whats_changed(db_gen)) |
1001 | |
1002 | def test_whats_changed_initial_database(self): |
1003 | self.assertEqual((0, []), self.db.whats_changed()) |
1004 | @@ -344,7 +349,9 @@ |
1005 | doc = self.db.create_doc(simple_doc) |
1006 | doc.content = '{"new": "contents"}' |
1007 | self.db.put_doc(doc) |
1008 | - self.assertEqual((2, [(doc.doc_id, 2)]), self.db.whats_changed()) |
1009 | + last_trans_id = self.getLastTransId(self.db) |
1010 | + self.assertEqual((2, [(doc.doc_id, 2, last_trans_id)]), |
1011 | + self.db.whats_changed()) |
1012 | self.assertEqual((2, []), self.db.whats_changed(2)) |
1013 | |
1014 | def test_whats_changed_returns_last_edits_ascending(self): |
1015 | @@ -352,15 +359,19 @@ |
1016 | doc1 = self.db.create_doc(simple_doc) |
1017 | doc.content = '{"new": "contents"}' |
1018 | self.db.delete_doc(doc1) |
1019 | + delete_trans_id = self.getLastTransId(self.db) |
1020 | self.db.put_doc(doc) |
1021 | - self.assertEqual((4, [(doc1.doc_id, 3), (doc.doc_id, 4)]), |
1022 | + put_trans_id = self.getLastTransId(self.db) |
1023 | + self.assertEqual((4, [(doc1.doc_id, 3, delete_trans_id), |
1024 | + (doc.doc_id, 4, put_trans_id)]), |
1025 | self.db.whats_changed()) |
1026 | |
1027 | - def test_whats_changed_doesnt_includ_old_gen(self): |
1028 | + def test_whats_changed_doesnt_include_old_gen(self): |
1029 | self.db.create_doc(simple_doc) |
1030 | self.db.create_doc(simple_doc) |
1031 | doc2 = self.db.create_doc(simple_doc) |
1032 | - self.assertEqual((3, [(doc2.doc_id, 3)]), |
1033 | + last_trans_id = self.getLastTransId(self.db) |
1034 | + self.assertEqual((3, [(doc2.doc_id, 3, last_trans_id)]), |
1035 | self.db.whats_changed(2)) |
1036 | |
1037 | |
1038 | @@ -568,7 +579,7 @@ |
1039 | |
1040 | def test_put_doc_if_newer_replica_uid(self): |
1041 | doc1 = self.db.create_doc(simple_doc) |
1042 | - self.db._set_sync_generation('other', 1) |
1043 | + self.db._set_sync_info('other', 1, 'T-sid') |
1044 | doc2 = self.make_document(doc1.doc_id, doc1.rev + '|other:1', |
1045 | nested_doc) |
1046 | self.db._put_doc_if_newer(doc2, save_conflict=True, |
1047 | @@ -579,7 +590,7 @@ |
1048 | self.assertEqual('conflicted', |
1049 | self.db._put_doc_if_newer(doc2, save_conflict=True, |
1050 | replica_uid='other', replica_gen=3)[0]) |
1051 | - self.assertEqual(3, self.db._get_sync_generation('other')) |
1052 | + self.assertEqual(3, self.db._get_sync_gen_info('other')[0]) |
1053 | |
1054 | def test_put_refuses_to_update_conflicted(self): |
1055 | doc1 = self.db.create_doc(simple_doc) |
1056 | |
1057 | === modified file 'u1db/tests/test_c_backend.py' |
1058 | --- u1db/tests/test_c_backend.py 2012-05-17 13:30:51 +0000 |
1059 | +++ u1db/tests/test_c_backend.py 2012-05-18 09:33:18 +0000 |
1060 | @@ -201,7 +201,8 @@ |
1061 | exc.insert_doc_from_source(doc, 10) |
1062 | self.assertGetDoc(self.db, 'doc-id', 'replica:1', tests.simple_doc, |
1063 | False) |
1064 | - self.assertEqual(10, self.db._get_sync_generation('source-uid')) |
1065 | + self.assertEqual((10, 'T-sid'), |
1066 | + self.db._get_sync_gen_info('source-uid')) |
1067 | self.assertEqual(['doc-id'], exc.get_seen_ids()) |
1068 | |
1069 | def test_sync_exchange_conflicted_doc(self): |
1070 | |
1071 | === modified file 'u1db/tests/test_http_app.py' |
1072 | --- u1db/tests/test_http_app.py 2012-05-17 13:36:08 +0000 |
1073 | +++ u1db/tests/test_http_app.py 2012-05-18 09:33:18 +0000 |
1074 | @@ -664,24 +664,26 @@ |
1075 | simplejson.loads(resp.body)) |
1076 | |
1077 | def test_get_sync_info(self): |
1078 | - self.db0._set_sync_generation('other-id', 1) |
1079 | + self.db0._set_sync_info('other-id', 1, 'T-transid') |
1080 | resp = self.app.get('/db0/sync-from/other-id') |
1081 | self.assertEqual(200, resp.status) |
1082 | self.assertEqual('application/json', resp.header('content-type')) |
1083 | self.assertEqual(dict(target_replica_uid='db0', |
1084 | target_replica_generation=0, |
1085 | source_replica_uid='other-id', |
1086 | - source_replica_generation=1), |
1087 | + source_replica_generation=1, |
1088 | + source_transaction_id='T-transid'), |
1089 | simplejson.loads(resp.body)) |
1090 | |
1091 | def test_record_sync_info(self): |
1092 | resp = self.app.put('/db0/sync-from/other-id', |
1093 | - params='{"generation": 2}', |
1094 | - headers={'content-type': 'application/json'}) |
1095 | + params='{"generation": 2, "transaction_id": "T-transid"}', |
1096 | + headers={'content-type': 'application/json'}) |
1097 | self.assertEqual(200, resp.status) |
1098 | self.assertEqual('application/json', resp.header('content-type')) |
1099 | self.assertEqual({'ok': True}, simplejson.loads(resp.body)) |
1100 | - self.assertEqual(self.db0._get_sync_generation('other-id'), 2) |
1101 | + self.assertEqual((2, 'T-transid'), |
1102 | + self.db0._get_sync_gen_info('other-id')) |
1103 | |
1104 | def test_sync_exchange_send(self): |
1105 | entries = { |
1106 | @@ -692,16 +694,15 @@ |
1107 | } |
1108 | |
1109 | gens = [] |
1110 | - _do_set_sync_generation = self.db0._do_set_sync_generation |
1111 | - def set_sync_generation_witness(other_uid, other_gen): |
1112 | + _do_set_sync_info = self.db0._do_set_sync_info |
1113 | + def set_sync_generation_witness(other_uid, other_gen, other_trans_id): |
1114 | gens.append((other_uid, other_gen)) |
1115 | - _do_set_sync_generation(other_uid, other_gen) |
1116 | + _do_set_sync_info(other_uid, other_gen, other_trans_id) |
1117 | self.assertGetDoc(self.db0, entries[other_gen]['id'], |
1118 | entries[other_gen]['rev'], |
1119 | entries[other_gen]['content'], False) |
1120 | |
1121 | - self.patch(self.db0, '_do_set_sync_generation', |
1122 | - set_sync_generation_witness) |
1123 | + self.patch(self.db0, '_do_set_sync_info', set_sync_generation_witness) |
1124 | |
1125 | args = dict(last_known_generation=0) |
1126 | body = ("[\r\n" + |
1127 | |
1128 | === modified file 'u1db/tests/test_https.py' |
1129 | --- u1db/tests/test_https.py 2012-05-17 15:13:31 +0000 |
1130 | +++ u1db/tests/test_https.py 2012-05-18 09:33:18 +0000 |
1131 | @@ -83,8 +83,8 @@ |
1132 | db = self.request_state._create_database('test') |
1133 | self.patch(http_client, 'CA_CERTS', self.cacert_pem) |
1134 | remote_target = self.getSyncTarget('localhost', 'test') |
1135 | - remote_target.record_sync_info('other-id', 2) |
1136 | - self.assertEqual(db._get_sync_generation('other-id'), 2) |
1137 | + remote_target.record_sync_info('other-id', 2, 'T-id') |
1138 | + self.assertEqual((2, 'T-id'), db._get_sync_gen_info('other-id')) |
1139 | |
1140 | def test_cannot_verify_cert(self): |
1141 | if not sys.platform.startswith('linux'): |
1142 | |
1143 | === modified file 'u1db/tests/test_remote_sync_target.py' |
1144 | --- u1db/tests/test_remote_sync_target.py 2012-05-17 13:36:08 +0000 |
1145 | +++ u1db/tests/test_remote_sync_target.py 2012-05-18 09:33:18 +0000 |
1146 | @@ -164,17 +164,17 @@ |
1147 | def test_get_sync_info(self): |
1148 | self.startServer() |
1149 | db = self.request_state._create_database('test') |
1150 | - db._set_sync_generation('other-id', 1) |
1151 | + db._set_sync_info('other-id', 1, 'T-transid') |
1152 | remote_target = self.getSyncTarget('test') |
1153 | - self.assertEqual(('test', 0, 1), |
1154 | + self.assertEqual(('test', 0, 1, 'T-transid'), |
1155 | remote_target.get_sync_info('other-id')) |
1156 | |
1157 | def test_record_sync_info(self): |
1158 | self.startServer() |
1159 | db = self.request_state._create_database('test') |
1160 | remote_target = self.getSyncTarget('test') |
1161 | - remote_target.record_sync_info('other-id', 2) |
1162 | - self.assertEqual(db._get_sync_generation('other-id'), 2) |
1163 | + remote_target.record_sync_info('other-id', 2, 'T-transid') |
1164 | + self.assertEqual((2, 'T-transid'), db._get_sync_gen_info('other-id')) |
1165 | |
1166 | def test_sync_exchange_send(self): |
1167 | self.startServer() |
1168 | @@ -222,7 +222,7 @@ |
1169 | return_doc_cb=receive_doc) |
1170 | self.assertGetDoc(db, 'doc-here', 'replica:1', '{"value": "here"}', |
1171 | False) |
1172 | - self.assertEqual(10, db._get_sync_generation('replica')) |
1173 | + self.assertEqual((10, 'T-sid'), db._get_sync_gen_info('replica')) |
1174 | self.assertEqual([], other_changes) |
1175 | # retry |
1176 | trigger_ids = [] |
1177 | @@ -232,7 +232,7 @@ |
1178 | return_doc_cb=receive_doc) |
1179 | self.assertGetDoc(db, 'doc-here2', 'replica:1', '{"value": "here2"}', |
1180 | False) |
1181 | - self.assertEqual(11, db._get_sync_generation('replica')) |
1182 | + self.assertEqual((11, 'T-sid'), db._get_sync_gen_info('replica')) |
1183 | self.assertEqual(2, new_gen) |
1184 | # bounced back to us |
1185 | self.assertEqual([('doc-here', 'replica:1', '{"value": "here"}', 1)], |
1186 | |
1187 | === modified file 'u1db/tests/test_sync.py' |
1188 | --- u1db/tests/test_sync.py 2012-05-17 14:44:25 +0000 |
1189 | +++ u1db/tests/test_sync.py 2012-05-18 09:33:18 +0000 |
1190 | @@ -141,17 +141,18 @@ |
1191 | self.assertIsNot(None, self.st) |
1192 | |
1193 | def test_get_sync_info(self): |
1194 | - self.assertEqual(('test', 0, 0), self.st.get_sync_info('other')) |
1195 | + self.assertEqual(('test', 0, 0, ''), self.st.get_sync_info('other')) |
1196 | |
1197 | def test_create_doc_updates_sync_info(self): |
1198 | - self.assertEqual(('test', 0, 0), self.st.get_sync_info('other')) |
1199 | + self.assertEqual(('test', 0, 0, ''), self.st.get_sync_info('other')) |
1200 | doc = self.db.create_doc(simple_doc) |
1201 | - self.assertEqual(('test', 1, 0), self.st.get_sync_info('other')) |
1202 | + self.assertEqual(('test', 1, 0, ''), self.st.get_sync_info('other')) |
1203 | |
1204 | def test_record_sync_info(self): |
1205 | - self.assertEqual(('test', 0, 0), self.st.get_sync_info('replica')) |
1206 | - self.st.record_sync_info('replica', 10) |
1207 | - self.assertEqual(('test', 0, 10), self.st.get_sync_info('replica')) |
1208 | + self.assertEqual(('test', 0, 0, ''), self.st.get_sync_info('replica')) |
1209 | + self.st.record_sync_info('replica', 10, 'T-transid') |
1210 | + self.assertEqual(('test', 0, 10, 'T-transid'), |
1211 | + self.st.get_sync_info('replica')) |
1212 | |
1213 | def test_sync_exchange(self): |
1214 | docs_by_gen = [ |
1215 | @@ -162,7 +163,7 @@ |
1216 | self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False) |
1217 | self.assertTransactionLog(['doc-id'], self.db) |
1218 | self.assertEqual(([], 1), (self.other_changes, new_gen)) |
1219 | - self.assertEqual(10, self.st.get_sync_info('replica')[-1]) |
1220 | + self.assertEqual(10, self.st.get_sync_info('replica')[2]) |
1221 | |
1222 | def test_sync_exchange_deleted(self): |
1223 | doc = self.db.create_doc('{}') |
1224 | @@ -175,7 +176,7 @@ |
1225 | self.assertGetDoc(self.db, doc.doc_id, edit_rev, None, False) |
1226 | self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) |
1227 | self.assertEqual(([], 2), (self.other_changes, new_gen)) |
1228 | - self.assertEqual(10, self.st.get_sync_info('replica')[-1]) |
1229 | + self.assertEqual(10, self.st.get_sync_info('replica')[2]) |
1230 | |
1231 | def test_sync_exchange_push_many(self): |
1232 | docs_by_gen = [ |
1233 | @@ -188,7 +189,7 @@ |
1234 | self.assertGetDoc(self.db, 'doc-id2', 'replica:1', nested_doc, False) |
1235 | self.assertTransactionLog(['doc-id', 'doc-id2'], self.db) |
1236 | self.assertEqual(([], 2), (self.other_changes, new_gen)) |
1237 | - self.assertEqual(11, self.st.get_sync_info('replica')[-1]) |
1238 | + self.assertEqual(11, self.st.get_sync_info('replica')[2]) |
1239 | |
1240 | def test_sync_exchange_refuses_conflicts(self): |
1241 | doc = self.db.create_doc(simple_doc) |
1242 | @@ -345,7 +346,7 @@ |
1243 | self.assertGetDoc(self.db, doc.doc_id, doc.rev, simple_doc, False) |
1244 | self.assertTransactionLog([doc.doc_id], self.db) |
1245 | self.assertEqual(([], 1), (self.other_changes, new_gen)) |
1246 | - self.assertEqual(10, self.st.get_sync_info(db2._replica_uid)[-1]) |
1247 | + self.assertEqual(10, self.st.get_sync_info(db2._replica_uid)[2]) |
1248 | |
1249 | def test__set_trace_hook(self): |
1250 | called = [] |
1251 | @@ -353,7 +354,7 @@ |
1252 | called.append(state) |
1253 | self.set_trace_hook(cb) |
1254 | self.st.sync_exchange([], 'replica', 0, self.receive_doc) |
1255 | - self.st.record_sync_info('replica', 0) |
1256 | + self.st.record_sync_info('replica', 0, 'T-sid') |
1257 | self.assertEqual(['before whats_changed', |
1258 | 'after whats_changed', |
1259 | 'before get_docs', |
1260 | @@ -410,8 +411,8 @@ |
1261 | |
1262 | def test_sync_tracks_db_generation_of_other(self): |
1263 | self.assertEqual(0, self.sync(self.db1, self.db2)) |
1264 | - self.assertEqual(0, self.db1._get_sync_generation('test2')) |
1265 | - self.assertEqual(0, self.db2._get_sync_generation('test1')) |
1266 | + self.assertEqual((0, ''), self.db1._get_sync_gen_info('test2')) |
1267 | + self.assertEqual((0, ''), self.db2._get_sync_gen_info('test1')) |
1268 | self.assertLastExchangeLog(self.db2, |
1269 | {'receive': {'docs': [], 'last_known_gen': 0}, |
1270 | 'return': {'docs': [], 'last_gen': 0}}) |
1271 | @@ -420,8 +421,8 @@ |
1272 | doc = self.db1.create_doc(simple_doc) |
1273 | self.assertEqual(1, self.sync(self.db1, self.db2)) |
1274 | self.assertGetDoc(self.db2, doc.doc_id, doc.rev, simple_doc, False) |
1275 | - self.assertEqual(1, self.db1._get_sync_generation('test2')) |
1276 | - self.assertEqual(1, self.db2._get_sync_generation('test1')) |
1277 | + self.assertEqual(1, self.db1._get_sync_gen_info('test2')[0]) |
1278 | + self.assertEqual(1, self.db2._get_sync_gen_info('test1')[0]) |
1279 | self.assertLastExchangeLog(self.db2, |
1280 | {'receive': {'docs': [(doc.doc_id, doc.rev)], |
1281 | 'source_uid': 'test1', |
1282 | @@ -433,8 +434,8 @@ |
1283 | self.db1.create_index('test-idx', ['key']) |
1284 | self.assertEqual(0, self.sync(self.db1, self.db2)) |
1285 | self.assertGetDoc(self.db1, doc.doc_id, doc.rev, simple_doc, False) |
1286 | - self.assertEqual(1, self.db1._get_sync_generation('test2')) |
1287 | - self.assertEqual(1, self.db2._get_sync_generation('test1')) |
1288 | + self.assertEqual(1, self.db1._get_sync_gen_info('test2')[0]) |
1289 | + self.assertEqual(1, self.db2._get_sync_gen_info('test1')[0]) |
1290 | self.assertLastExchangeLog(self.db2, |
1291 | {'receive': {'docs': [], 'last_known_gen': 0}, |
1292 | 'return': {'docs': [(doc.doc_id, doc.rev)], |
1293 | @@ -459,11 +460,11 @@ |
1294 | {'receive': {'docs': [], 'last_known_gen': 0}, |
1295 | 'return': {'docs': [(doc.doc_id, doc.rev)], |
1296 | 'last_gen': 1}}) |
1297 | - self.assertEqual(1, self.db1._get_sync_generation('test2')) |
1298 | + self.assertEqual(1, self.db1._get_sync_gen_info('test2')[0]) |
1299 | # c2 should not have gotten a '_record_sync_info' call, because the |
1300 | # local database had been updated more than just by the messages |
1301 | # returned from c2. |
1302 | - self.assertEqual(0, self.db2._get_sync_generation('test1')) |
1303 | + self.assertEqual((0, ''), self.db2._get_sync_gen_info('test1')) |
1304 | |
1305 | def test_sync_doesnt_update_other_if_nothing_pulled(self): |
1306 | doc = self.db1.create_doc(simple_doc) |
1307 | @@ -474,7 +475,7 @@ |
1308 | self.assertEqual(1, self.sync(self.db1, self.db2, |
1309 | trace_hook=no_record_sync_info)) |
1310 | self.assertEqual(1, |
1311 | - self.db2._get_sync_generation(self.db1._replica_uid)) |
1312 | + self.db2._get_sync_gen_info(self.db1._replica_uid)[0]) |
1313 | |
1314 | def test_sync_ignores_convergence(self): |
1315 | doc = self.db1.create_doc(simple_doc) |
1316 | @@ -707,20 +708,20 @@ |
1317 | self.assertEqual(2, len(self.db2._get_transaction_log())) |
1318 | progress1 = [] |
1319 | progress2 = [] |
1320 | - _do_set_sync_generation1 = self.db1._do_set_sync_generation |
1321 | - def set_sync_generation_witness1(other_uid, other_gen): |
1322 | + _do_set_sync_info = self.db1._do_set_sync_info |
1323 | + def set_sync_generation_witness1(other_uid, other_gen, trans_id): |
1324 | progress1.append((other_uid, other_gen, |
1325 | [d for d, t in self.db1._get_transaction_log()[2:]])) |
1326 | - _do_set_sync_generation1(other_uid, other_gen) |
1327 | - self.patch(self.db1, '_do_set_sync_generation', |
1328 | + _do_set_sync_info(other_uid, other_gen, trans_id) |
1329 | + self.patch(self.db1, '_do_set_sync_info', |
1330 | set_sync_generation_witness1) |
1331 | |
1332 | - _do_set_sync_generation2 = self.db2._do_set_sync_generation |
1333 | - def set_sync_generation_witness2(other_uid, other_gen): |
1334 | + _do_set_sync_info2 = self.db2._do_set_sync_info |
1335 | + def set_sync_generation_witness2(other_uid, other_gen, trans_id): |
1336 | progress2.append((other_uid, other_gen, |
1337 | [d for d, t in self.db2._get_transaction_log()[2:]])) |
1338 | - _do_set_sync_generation2(other_uid, other_gen) |
1339 | - self.patch(self.db2, '_do_set_sync_generation', |
1340 | + _do_set_sync_info2(other_uid, other_gen, trans_id) |
1341 | + self.patch(self.db2, '_do_set_sync_info', |
1342 | set_sync_generation_witness2) |
1343 | |
1344 | db2_url = self.getURL('test2') |