use postg::config::Config;
use postg::engine::Postg;
use std::time::Duration;
use tokio::time::sleep;
#[tokio::test]
#[ignore]
async fn test_vanilla_logical_replication() {
let tmp = tempfile::TempDir::new().unwrap();
let data_dir_a = tmp.path().join("node_a");
let data_dir_b = tmp.path().join("node_b");
let config_a = Config {
data_dir: data_dir_a,
temporary: false,
..Config::default()
};
let mut node_a = Postg::start(config_a).await.unwrap();
let config_b = Config {
data_dir: data_dir_b,
temporary: false,
..Config::default()
};
let mut node_b = Postg::start(config_b).await.unwrap();
let pool_a = sqlx::PgPool::connect(&node_a.connection_string())
.await
.unwrap();
let pool_b = sqlx::PgPool::connect(&node_b.connection_string())
.await
.unwrap();
let schema_sql = "CREATE TABLE messages (id serial PRIMARY KEY, content text NOT NULL);";
sqlx::query(schema_sql).execute(&pool_a).await.unwrap();
sqlx::query(schema_sql).execute(&pool_b).await.unwrap();
sqlx::query("CREATE PUBLICATION pub_messages FOR TABLE messages;")
.execute(&pool_a)
.await
.unwrap();
let sub_sql = format!(
"CREATE SUBSCRIPTION sub_messages CONNECTION '{}' PUBLICATION pub_messages;",
node_a.connection_string()
);
sqlx::query(&sub_sql).execute(&pool_b).await.unwrap();
sleep(Duration::from_millis(500)).await;
sqlx::query("INSERT INTO messages (content) VALUES ('hello from A')")
.execute(&pool_a)
.await
.unwrap();
let mut replicated = false;
for _ in 0..20 {
let count: (i64,) =
sqlx::query_as("SELECT COUNT(*) FROM messages WHERE content = 'hello from A'")
.fetch_one(&pool_b)
.await
.unwrap();
if count.0 == 1 {
replicated = true;
break;
}
sleep(Duration::from_millis(100)).await;
}
assert!(replicated, "Data from Node A did not replicate to Node B");
sqlx::query("INSERT INTO messages (id, content) VALUES (100, 'hello from B')")
.execute(&pool_b)
.await
.unwrap();
sleep(Duration::from_millis(500)).await;
let count_a: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM messages")
.fetch_one(&pool_a)
.await
.unwrap();
assert_eq!(
count_a.0, 1,
"Node A received a row from Node B, but replication should be one-way"
);
sqlx::query("UPDATE messages SET content = 'updated from A' WHERE content = 'hello from A'")
.execute(&pool_a)
.await
.unwrap();
let mut update_replicated = false;
for _ in 0..20 {
let content: (String,) = sqlx::query_as("SELECT content FROM messages WHERE id = 1")
.fetch_one(&pool_b)
.await
.unwrap();
if content.0 == "updated from A" {
update_replicated = true;
break;
}
sleep(Duration::from_millis(200)).await;
}
assert!(
update_replicated,
"UPDATE from Node A did not replicate to Node B"
);
sqlx::query("DELETE FROM messages WHERE id = 1")
.execute(&pool_a)
.await
.unwrap();
let mut delete_replicated = false;
for _ in 0..20 {
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM messages WHERE id = 1")
.fetch_one(&pool_b)
.await
.unwrap();
if count.0 == 0 {
delete_replicated = true;
break;
}
sleep(Duration::from_millis(200)).await;
}
assert!(
delete_replicated,
"DELETE from Node A did not replicate to Node B"
);
pool_b.close().await;
node_b.stop().await.unwrap();
sqlx::query("INSERT INTO messages (id, content) VALUES (200, 'inserted while B was offline')")
.execute(&pool_a)
.await
.unwrap();
let config_b_restarted = node_b.config().clone();
node_b = Postg::start(config_b_restarted)
.await
.expect("Failed to restart Node B");
let pool_b = sqlx::PgPool::connect(&node_b.connection_string())
.await
.unwrap();
let mut offline_sync = false;
for _ in 0..40 {
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM messages WHERE id = 200")
.fetch_one(&pool_b)
.await
.unwrap_or((0,));
if count.0 == 1 {
offline_sync = true;
break;
}
sleep(Duration::from_millis(200)).await;
}
if !offline_sync {
let log_content = std::fs::read_to_string(node_b.config().data_dir.join("postgres.log"))
.unwrap_or_default();
println!("Node B Postgres Log:\n{}", log_content);
}
assert!(
offline_sync,
"Node B did not sync missed data after coming back online"
);
pool_a.close().await;
pool_b.close().await;
node_a.stop().await.unwrap();
node_b.stop().await.unwrap();
}