1717from pprint import pprint
1818import pytest
1919
20+ def create_db ():
21+ c = connect (":memory:" )
22+ c .execute ("CREATE TABLE foo (a INTEGER PRIMARY KEY NOT NULL, b INTEGER) STRICT;" )
23+ c .execute ("SELECT crsql_as_crr('foo')" )
24+ c .commit ()
25+ return c
26+
27+ def get_site_id (c ):
28+ return c .execute ("SELECT crsql_site_id()" ).fetchone ()[0 ]
29+
30+ def sync_left_to_right (l , r , since ):
31+ changes = l .execute (
32+ "SELECT * FROM crsql_changes WHERE db_version > ?" , (since ,))
33+ for change in changes :
34+ r .execute (
35+ "INSERT INTO crsql_changes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" , change )
36+ r .commit ()
2037
2138# The idea here is that we are using an upsert to create a row that has never existing in our db
2239# In metadata tables or otherwise
@@ -237,68 +254,162 @@ def test_delete_previously_deleted():
237254# - single
238255# - pko
239256def test_change_primary_key_to_something_new ():
240- c = connect (":memory:" )
241- c .execute ("CREATE TABLE foo (a INTEGER PRIMARY KEY NOT NULL, b INTEGER) STRICT;" )
242- c .execute ("SELECT crsql_as_crr('foo')" )
243- c .commit ()
257+ c1 = create_db ()
258+ c2 = create_db ()
244259
245- c .execute ("INSERT INTO foo VALUES (1, 2)" )
246- c .execute ("UPDATE foo SET a = 2 WHERE a = 1" )
260+ # First step: Insert initial row
261+ c1 .execute ("INSERT INTO foo VALUES (1, 2)" )
262+ c1 .commit ()
263+ sync_left_to_right (c1 , c2 , 0 )
247264
248- changes = c .execute (
265+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
266+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
267+
268+ c1 .execute ("UPDATE foo SET a = 2 WHERE a = 1" )
269+ c1 .commit ()
270+ sync_left_to_right (c1 , c2 , 1 )
271+
272+ changes = c1 .execute (
249273 "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
250274 # pk 1 was deleted so has a CL of 2
251275 # pk 2 was created so has a CL of 1 and also has the `b` column data as that was moved!
252- assert (changes == [(b'\x01 \t \x02 ' , 'b' , 1 ),
253- (b'\x01 \t \x01 ' , '-1' , 2 ), (b'\x01 \t \x02 ' , '-1' , 1 )])
276+ assert (changes == [(b'\x01 \t \x01 ' , '-1' , 2 ),
277+ (b'\x01 \t \x02 ' , '-1' , 1 ), (b'\x01 \t \x02 ' , 'b' , 1 )])
278+
279+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
280+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
281+
282+ close (c1 )
283+ close (c2 )
254284
255285
256286# Previously existing thing should get bumped to re-existing
257287# Previously existing means we have metadata for the row but it is not a live row in the base tables.
258288def test_change_primary_key_to_previously_existing ():
259- c = connect (":memory:" )
260- c .execute ("CREATE TABLE foo (a INTEGER PRIMARY KEY NOT NULL, b INTEGER) STRICT;" )
261- c .execute ("SELECT crsql_as_crr('foo')" )
262- c .commit ()
289+ c1 = create_db ()
290+ c2 = create_db ()
263291
264- c .execute ("INSERT INTO foo VALUES (1, 2)" )
265- c .execute ("INSERT INTO foo VALUES (2, 3)" )
266- c .commit ()
267- c .execute ("DELETE FROM foo WHERE a = 2" )
268- c .execute ("UPDATE foo SET a = 2 WHERE a = 1" )
292+ c1 .execute ("INSERT INTO foo VALUES (1, 2)" )
293+ c1 .execute ("INSERT INTO foo VALUES (2, 3)" )
294+ c1 .commit ()
295+ sync_left_to_right (c1 , c2 , 0 )
269296
270- changes = c .execute (
297+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
298+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
299+
300+ c1 .execute ("DELETE FROM foo WHERE a = 2" )
301+ c1 .execute ("UPDATE foo SET a = 2 WHERE a = 1" )
302+ c1 .commit ()
303+ sync_left_to_right (c1 , c2 , 1 )
304+
305+ changes = c1 .execute (
306+ "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
307+ changes2 = c2 .execute (
271308 "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
272309 # pk 1 was deleted so has a CL of 2
273310 # pk 2 was resurrected so has a CL of 3
274- assert (changes == [(b'\x01 \t \x02 ' , 'b' , 3 ),
275- (b'\x01 \t \x01 ' , '-1' , 2 ), (b'\x01 \t \x02 ' , '-1' , 3 )])
311+ assert (changes == [(b'\x01 \t \x01 ' , '-1' , 2 ), (b'\x01 \t \x02 ' , '-1' , 3 ),
312+ (b'\x01 \t \x02 ' , 'b' , 3 )])
313+ assert (changes2 == changes )
314+
315+ # Verify both nodes have same data after final sync
316+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
317+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
276318
277- # try changing to and away from 1 again to ensure we aren't stuck at 2
319+ close (c1 )
320+ close (c2 )
278321
279322
280323# Changing to something currently existing is an update that replaces the thing on conflict
281324def test_change_primary_key_to_currently_existing ():
282- c = connect (":memory:" )
283- c .execute ("CREATE TABLE foo (a INTEGER PRIMARY KEY NOT NULL, b INTEGER) STRICT;" )
284- c .execute ("SELECT crsql_as_crr('foo')" )
285- c .commit ()
325+ c1 = create_db ()
326+ c2 = create_db ()
286327
287- c .execute ("INSERT INTO foo VALUES (1, 2)" )
288- c .execute ("INSERT INTO foo VALUES (2, 3)" )
289- c .commit ()
290- c .execute ("UPDATE OR REPLACE foo SET a = 2 WHERE a = 1" )
291- c .commit ()
328+ c1 .execute ("INSERT INTO foo VALUES (1, 2)" )
329+ c1 .execute ("INSERT INTO foo VALUES (2, 3)" )
330+ c1 .commit ()
331+ sync_left_to_right (c1 , c2 , 0 )
292332
293- changes = c .execute (
333+ changes = c1 .execute (
334+ "SELECT pk, cid, val, cl FROM crsql_changes" ).fetchall ()
335+ changes2 = c2 .execute (
336+ "SELECT pk, cid, val, cl FROM crsql_changes" ).fetchall ()
337+ assert (changes == [(b'\x01 \t \x01 ' , 'b' , 2 , 1 ), (b'\x01 \t \x02 ' , 'b' , 3 , 1 )])
338+ assert (changes2 == changes )
339+
340+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
341+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
342+
343+ c1 .execute ("UPDATE OR REPLACE foo SET a = 2 WHERE a = 1" )
344+ c1 .commit ()
345+ sync_left_to_right (c1 , c2 , 1 )
346+
347+ changes = c1 .execute (
348+ "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
349+ changes2 = c2 .execute (
294350 "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
295351 # pk 2 is alive as we `update or replaced` to it
296- # and it is alive at version 3 given it is a re-insertion of the currently existing row
352+ # and it is alive at version 3 given it iassert (changes2 == changes)s a re-insertion of the currently existing row
297353 # pk 1 is dead (cl of 2) given we mutated / updated away from it. E.g.,
298354 # set a = 2 where a = 1
299- assert (changes == [(b'\x01 \t \x02 ' , 'b' , 1 ),
300- (b'\x01 \t \x01 ' , '-1' , 2 ), (b'\x01 \t \x02 ' , '-1' , 1 )])
355+ assert (changes == [(b'\x01 \t \x01 ' , '-1' , 2 ), (b'\x01 \t \x02 ' , '-1' , 1 ),
356+ (b'\x01 \t \x02 ' , 'b' , 1 )])
357+ # TODO: The change from second node is missing the sentinel row for the
358+ # existing row because we skip inserts if cl hasn't changed and we assume
359+ # an existing row has a cl of 1.
360+ # assert (changes2 == changes)
361+
362+ # Verify both nodes have same data after final sync
363+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
364+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
365+
366+ close (c1 )
367+ close (c2 )
368+
369+
370+ # Changing to the primary key of a row that was created in another db.
371+ def test_change_primary_key_from_another_db ():
372+ c1 = create_db ()
373+ c2 = create_db ()
374+
375+ c1 .execute ("INSERT INTO foo VALUES (1, 2)" )
376+ c1 .execute ("INSERT INTO foo VALUES (2, 3)" )
377+ c1 .commit ()
378+ sync_left_to_right (c1 , c2 , 0 )
379+
380+ changes = c1 .execute (
381+ "SELECT pk, cid, val, cl FROM crsql_changes" ).fetchall ()
382+ changes2 = c2 .execute (
383+ "SELECT pk, cid, val, cl FROM crsql_changes" ).fetchall ()
384+ assert (changes == [(b'\x01 \t \x01 ' , 'b' , 2 , 1 ), (b'\x01 \t \x02 ' , 'b' , 3 , 1 )])
385+ assert (changes2 == changes )
386+
387+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
388+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
389+
390+ c2 .execute ("UPDATE OR REPLACE foo SET a = 3 WHERE a = 1" )
391+ c2 .commit ()
392+ assert (c2 .execute ("SELECT crsql_db_version()" ).fetchone ()[0 ] == 2 )
393+ sync_left_to_right (c2 , c1 , 1 )
394+
395+ changes = c2 .execute (
396+ "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
397+ changes1 = c1 .execute (
398+ "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
399+ # pk 2 is alive as we `update or replaced` to it
400+ # and it is alive at version 3 given it iassert (changes2 == changes)s a re-insertion of the currently existing row
401+ # pk 1 is dead (cl of 2) given we mutated / updated away from it. E.g.,
402+ # set a = 2 where a = 1
403+ assert (changes == [(b'\x01 \t \x02 ' , 'b' , 1 ), (b'\x01 \t \x01 ' , '-1' , 2 ), (b'\x01 \t \x03 ' , '-1' , 1 ),
404+ (b'\x01 \t \x03 ' , 'b' , 1 )])
405+ # assert (changes2 == changes)
406+
407+ # Verify both nodes have same data after final sync
408+ assert (c1 .execute ("SELECT * FROM foo" ).fetchall () ==
409+ c2 .execute ("SELECT * FROM foo" ).fetchall ())
301410
411+ close (c1 )
412+ close (c2 )
302413
303414def test_change_primary_key_away_from_thing_with_large_length ():
304415 c = connect (":memory:" )
@@ -318,8 +429,8 @@ def test_change_primary_key_away_from_thing_with_large_length():
318429 "SELECT pk, cid, cl FROM crsql_changes" ).fetchall ()
319430 # first time existing for 2
320431 # third deletion for 1
321- assert (changes == [(b'\x01 \t \x02 ' , 'b ' , 1 ),
322- ( b' \x01 \t \x01 ' , '-1' , 6 ), (b'\x01 \t \x02 ' , '-1 ' , 1 )])
432+ assert (changes == [(b'\x01 \t \x01 ' , '-1' , 6 ), ( b' \x01 \t \x02 ' , '-1 ' , 1 ),
433+ (b'\x01 \t \x02 ' , 'b ' , 1 ), ])
323434
324435
325436# Test inserting something for which we have delete records for but no actual row
0 commit comments