@@ -2443,6 +2443,102 @@ func TestLogicalReplicationGatewayRoute(t *testing.T) {
2443
2443
require .Empty (t , progress .Details .(* jobspb.Progress_LogicalReplication ).LogicalReplication .PartitionConnUris )
2444
2444
}
2445
2445
2446
+ // TestAlterExternalConnection tests that logical replication streams can
2447
+ // dynamically switch between different source nodes when the external
2448
+ // connection URI is updated. It verifies that data continues to replicate
2449
+ // correctly after the connection change.
2450
+ func TestAlterExternalConnection (t * testing.T ) {
2451
+ defer leaktest .AfterTest (t )()
2452
+ skip .UnderDeadlock (t )
2453
+ skip .UnderRace (t )
2454
+ defer log .Scope (t ).Close (t )
2455
+
2456
+ ctx := context .Background ()
2457
+ pollingInterval := 100 * time .Millisecond
2458
+
2459
+ clusterArgs := base.TestClusterArgs {
2460
+ ServerArgs : base.TestServerArgs {
2461
+ DefaultTestTenant : base .TestControlsTenantsExplicitly ,
2462
+ Knobs : base.TestingKnobs {
2463
+ JobsTestingKnobs : jobs .NewTestingKnobsWithShortIntervals (),
2464
+ Streaming : & sql.StreamingTestingKnobs {
2465
+ ExternalConnectionPollingInterval : & pollingInterval ,
2466
+ },
2467
+ },
2468
+ },
2469
+ }
2470
+
2471
+ activeLogicalSessionSQL := "SELECT count(*) > 0 FROM crdb_internal.node_sessions WHERE application_name like '$ internal repstream job id=%' AND status='ACTIVE'"
2472
+ countLogicalSessionSQL := "SELECT count(*) FROM crdb_internal.node_sessions WHERE application_name like '$ internal repstream job id=%' AND status='ACTIVE'"
2473
+ server , node0 , runners , dbNames := setupServerWithNumDBs (t , ctx , clusterArgs , 3 , 2 )
2474
+ defer server .Stopper ().Stop (ctx )
2475
+
2476
+ dbA := runners [0 ]
2477
+ dbB := runners [1 ]
2478
+
2479
+ dbANode0URL , cleanup := node0 .PGUrl (t , serverutils .DBName (dbNames [0 ]))
2480
+ defer cleanup ()
2481
+ dbANode0 := sqlutils .MakeSQLRunner (node0 .SQLConn (t , serverutils .DBName (dbNames [0 ])))
2482
+ node1 := server .Server (1 ).ApplicationLayer ()
2483
+ dbANode1URL , cleanup := node1 .PGUrl (t , serverutils .DBName (dbNames [0 ]))
2484
+ defer cleanup ()
2485
+ dbANode1 := sqlutils .MakeSQLRunner (node1 .SQLConn (t , serverutils .DBName (dbNames [0 ])))
2486
+
2487
+ q0 := dbANode0URL .Query ()
2488
+ q0 .Set (streamclient .RoutingModeKey , string (streamclient .RoutingModeGateway ))
2489
+ dbANode0URL .RawQuery = q0 .Encode ()
2490
+
2491
+ q1 := dbANode1URL .Query ()
2492
+ q1 .Set (streamclient .RoutingModeKey , string (streamclient .RoutingModeGateway ))
2493
+ dbANode1URL .RawQuery = q1 .Encode ()
2494
+
2495
+ // We want to make sure operations for cluster B is on seperate node from cluster A.
2496
+ node2 := server .Server (2 ).ApplicationLayer ()
2497
+ dbBNode2 := sqlutils .MakeSQLRunner (node2 .SQLConn (t , serverutils .DBName (dbNames [1 ])))
2498
+
2499
+ require .NotEqual (t , dbANode0URL .String (), dbANode1URL .String ())
2500
+
2501
+ externalConnName := "test_conn"
2502
+ dbBNode2 .Exec (t , fmt .Sprintf ("CREATE EXTERNAL CONNECTION '%s' AS '%s'" , externalConnName , dbANode0URL .String ()))
2503
+
2504
+ var jobID jobspb.JobID
2505
+ dbBNode2 .QueryRow (t , fmt .Sprintf (
2506
+ "CREATE LOGICAL REPLICATION STREAM FROM TABLE tab ON 'external://%s' INTO TABLE tab" ,
2507
+ externalConnName )).Scan (& jobID )
2508
+
2509
+ dbANode0 .Exec (t , "INSERT INTO tab VALUES (1, 'via_node_0')" )
2510
+
2511
+ now := node0 .Clock ().Now ()
2512
+ WaitUntilReplicatedTime (t , now , dbB , jobID )
2513
+
2514
+ dbANode0 .CheckQueryResults (t ,
2515
+ activeLogicalSessionSQL ,
2516
+ [][]string {{"true" }})
2517
+ dbANode1 .CheckQueryResults (t ,
2518
+ countLogicalSessionSQL ,
2519
+ [][]string {{"0" }})
2520
+
2521
+ dbBNode2 .CheckQueryResults (t , "SELECT * FROM tab WHERE pk = 1" , [][]string {
2522
+ {"1" , "via_node_0" },
2523
+ })
2524
+
2525
+ dbBNode2 .Exec (t , fmt .Sprintf ("ALTER EXTERNAL CONNECTION '%s' AS '%s'" , externalConnName , dbANode1URL .String ()))
2526
+ dbANode1 .CheckQueryResultsRetry (t ,
2527
+ activeLogicalSessionSQL ,
2528
+ [][]string {{"true" }})
2529
+ dbANode0 .CheckQueryResultsRetry (t ,
2530
+ countLogicalSessionSQL ,
2531
+ [][]string {{"0" }})
2532
+
2533
+ dbA .Exec (t , "INSERT INTO tab VALUES (2, 'via_node_1')" )
2534
+ now = node0 .Clock ().Now ()
2535
+ WaitUntilReplicatedTime (t , now , dbB , jobID )
2536
+
2537
+ dbBNode2 .CheckQueryResults (t , "SELECT * FROM tab WHERE pk = 2" , [][]string {
2538
+ {"2" , "via_node_1" },
2539
+ })
2540
+ }
2541
+
2446
2542
func TestMismatchColIDs (t * testing.T ) {
2447
2543
defer leaktest .AfterTest (t )()
2448
2544
skip .UnderDeadlock (t )
0 commit comments