@@ -2454,3 +2454,145 @@ def test_dynamic_column_addition_user_config():
2454
2454
db_pid = get_db_replicator_pid (cfg , "test_replication" )
2455
2455
if db_pid :
2456
2456
kill_process (db_pid )
2457
+
2458
+
2459
+ def test_ignore_deletes ():
2460
+ # Create a temporary config file with ignore_deletes=True
2461
+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.yaml' , delete = False ) as temp_config_file :
2462
+ config_file = temp_config_file .name
2463
+
2464
+ # Read the original config
2465
+ with open (CONFIG_FILE , 'r' ) as original_config :
2466
+ config_data = yaml .safe_load (original_config )
2467
+
2468
+ # Add ignore_deletes=True
2469
+ config_data ['ignore_deletes' ] = True
2470
+
2471
+ # Write to the temp file
2472
+ yaml .dump (config_data , temp_config_file )
2473
+
2474
+ try :
2475
+ cfg = config .Settings ()
2476
+ cfg .load (config_file )
2477
+
2478
+ # Verify the ignore_deletes option was set
2479
+ assert cfg .ignore_deletes is True
2480
+
2481
+ mysql = mysql_api .MySQLApi (
2482
+ database = None ,
2483
+ mysql_settings = cfg .mysql ,
2484
+ )
2485
+
2486
+ ch = clickhouse_api .ClickhouseApi (
2487
+ database = TEST_DB_NAME ,
2488
+ clickhouse_settings = cfg .clickhouse ,
2489
+ )
2490
+
2491
+ prepare_env (cfg , mysql , ch )
2492
+
2493
+ # Create a table with a composite primary key
2494
+ mysql .execute (f'''
2495
+ CREATE TABLE `{ TEST_TABLE_NAME } ` (
2496
+ departments int(11) NOT NULL,
2497
+ termine int(11) NOT NULL,
2498
+ data varchar(255) NOT NULL,
2499
+ PRIMARY KEY (departments,termine)
2500
+ )
2501
+ ''' )
2502
+
2503
+ # Insert initial records
2504
+ mysql .execute (f"INSERT INTO `{ TEST_TABLE_NAME } ` (departments, termine, data) VALUES (10, 20, 'data1');" , commit = True )
2505
+ mysql .execute (f"INSERT INTO `{ TEST_TABLE_NAME } ` (departments, termine, data) VALUES (30, 40, 'data2');" , commit = True )
2506
+ mysql .execute (f"INSERT INTO `{ TEST_TABLE_NAME } ` (departments, termine, data) VALUES (50, 60, 'data3');" , commit = True )
2507
+
2508
+ # Run the replicator with ignore_deletes=True
2509
+ run_all_runner = RunAllRunner (cfg_file = config_file )
2510
+ run_all_runner .run ()
2511
+
2512
+ # Wait for replication to complete
2513
+ assert_wait (lambda : TEST_DB_NAME in ch .get_databases ())
2514
+ ch .execute_command (f'USE `{ TEST_DB_NAME } `' )
2515
+ assert_wait (lambda : TEST_TABLE_NAME in ch .get_tables ())
2516
+ assert_wait (lambda : len (ch .select (TEST_TABLE_NAME )) == 3 )
2517
+
2518
+ # Delete some records from MySQL
2519
+ mysql .execute (f"DELETE FROM `{ TEST_TABLE_NAME } ` WHERE departments=10;" , commit = True )
2520
+ mysql .execute (f"DELETE FROM `{ TEST_TABLE_NAME } ` WHERE departments=30;" , commit = True )
2521
+
2522
+ # Wait a moment to ensure replication processes the events
2523
+ time .sleep (5 )
2524
+
2525
+ # Verify records are NOT deleted in ClickHouse (since ignore_deletes=True)
2526
+ # The count should still be 3
2527
+ assert len (ch .select (TEST_TABLE_NAME )) == 3 , "Deletions were processed despite ignore_deletes=True"
2528
+
2529
+ # Insert a new record and verify it's added
2530
+ mysql .execute (f"INSERT INTO `{ TEST_TABLE_NAME } ` (departments, termine, data) VALUES (70, 80, 'data4');" , commit = True )
2531
+ assert_wait (lambda : len (ch .select (TEST_TABLE_NAME )) == 4 )
2532
+
2533
+ # Verify the new record is correctly added
2534
+ result = ch .select (TEST_TABLE_NAME , where = "departments=70 AND termine=80" )
2535
+ assert len (result ) == 1
2536
+ assert result [0 ]['data' ] == 'data4'
2537
+
2538
+ # Clean up
2539
+ run_all_runner .stop ()
2540
+
2541
+ # Verify no errors occurred
2542
+ assert_wait (lambda : 'stopping db_replicator' in read_logs (TEST_DB_NAME ))
2543
+ assert ('Traceback' not in read_logs (TEST_DB_NAME ))
2544
+
2545
+ # Additional tests for persistence after restart
2546
+
2547
+ # 1. Remove all entries from table in MySQL
2548
+ mysql .execute (f"DELETE FROM `{ TEST_TABLE_NAME } ` WHERE 1=1;" , commit = True )
2549
+
2550
+ # Add a new row in MySQL before starting the replicator
2551
+ mysql .execute (f"INSERT INTO `{ TEST_TABLE_NAME } ` (departments, termine, data) VALUES (110, 120, 'offline_data');" , commit = True )
2552
+
2553
+ # 2. Wait 5 seconds
2554
+ time .sleep (5 )
2555
+
2556
+ # 3. Remove binlog directory (similar to prepare_env, but without removing tables)
2557
+ if os .path .exists (cfg .binlog_replicator .data_dir ):
2558
+ shutil .rmtree (cfg .binlog_replicator .data_dir )
2559
+ os .mkdir (cfg .binlog_replicator .data_dir )
2560
+
2561
+
2562
+ # 4. Create and run a new runner
2563
+ new_runner = RunAllRunner (cfg_file = config_file )
2564
+ new_runner .run ()
2565
+
2566
+ # 5. Ensure it has all the previous data (should still be 4 records from before + 1 new offline record)
2567
+ assert_wait (lambda : TEST_DB_NAME in ch .get_databases ())
2568
+ ch .execute_command (f'USE `{ TEST_DB_NAME } `' )
2569
+ assert_wait (lambda : TEST_TABLE_NAME in ch .get_tables ())
2570
+ assert_wait (lambda : len (ch .select (TEST_TABLE_NAME )) == 5 )
2571
+
2572
+ # Verify we still have all the old data
2573
+ assert len (ch .select (TEST_TABLE_NAME , where = "departments=10 AND termine=20" )) == 1
2574
+ assert len (ch .select (TEST_TABLE_NAME , where = "departments=30 AND termine=40" )) == 1
2575
+ assert len (ch .select (TEST_TABLE_NAME , where = "departments=50 AND termine=60" )) == 1
2576
+ assert len (ch .select (TEST_TABLE_NAME , where = "departments=70 AND termine=80" )) == 1
2577
+
2578
+ # Verify the offline data was replicated
2579
+ assert len (ch .select (TEST_TABLE_NAME , where = "departments=110 AND termine=120" )) == 1
2580
+ offline_data = ch .select (TEST_TABLE_NAME , where = "departments=110 AND termine=120" )[0 ]
2581
+ assert offline_data ['data' ] == 'offline_data'
2582
+
2583
+ # 6. Insert new data and verify it gets added to existing data
2584
+ mysql .execute (f"INSERT INTO `{ TEST_TABLE_NAME } ` (departments, termine, data) VALUES (90, 100, 'data5');" , commit = True )
2585
+ assert_wait (lambda : len (ch .select (TEST_TABLE_NAME )) == 6 )
2586
+
2587
+ # Verify the combined old and new data
2588
+ result = ch .select (TEST_TABLE_NAME , where = "departments=90 AND termine=100" )
2589
+ assert len (result ) == 1
2590
+ assert result [0 ]['data' ] == 'data5'
2591
+
2592
+ # Make sure we have all 6 records (4 original + 1 offline + 1 new one)
2593
+ assert len (ch .select (TEST_TABLE_NAME )) == 6
2594
+
2595
+ new_runner .stop ()
2596
+ finally :
2597
+ # Clean up the temporary config file
2598
+ os .unlink (config_file )
0 commit comments