use super::rows::{SnapshotRow, SnapshotRows};
use crate::error::{ReplicationError, Result};
use crate::prelude::*;
use crate::stream::{for_each_event_impl, EventSource, LogicalReplicationStream};
use crate::types::{ChangeEvent, XLogRecPtr};
#[must_use = "the replication stream is inside this value; consume it with \
run/finish/abandon to get it back"]
pub struct SnapshotEvents {
inner: SnapshotRows,
}
impl SnapshotEvents {
#[inline]
pub(super) fn new(inner: SnapshotRows) -> Self {
Self { inner }
}
#[inline]
pub fn stream(&self) -> &LogicalReplicationStream {
self.inner.stream()
}
pub async fn next_event(&mut self) -> Result<Option<ChangeEvent>> {
Ok(self.inner.next_row().await?.map(row_to_insert))
}
pub async fn for_each_event<F, Fut>(&mut self, handler: F) -> Result<()>
where
F: FnMut(ChangeEvent) -> Fut,
Fut: core::future::Future<Output = Result<()>>,
{
for_each_event_impl(self, handler).await
}
pub async fn run<F, Fut>(self, mut handler: F) -> Result<LogicalReplicationStream>
where
F: FnMut(ChangeEvent) -> Fut,
Fut: core::future::Future<Output = Result<()>>,
{
self.inner.run(move |row| handler(row_to_insert(row))).await
}
pub async fn finish(self) -> Result<LogicalReplicationStream> {
self.inner.finish().await
}
pub async fn abandon(self) -> Result<LogicalReplicationStream> {
self.inner.abandon().await
}
}
#[inline]
fn row_to_insert(row: SnapshotRow) -> ChangeEvent {
ChangeEvent::insert(
Arc::clone(&row.relation.namespace),
Arc::clone(&row.relation.relation_name),
row.relation.relation_id,
row.data,
row.lsn,
)
}
impl EventSource for SnapshotEvents {
#[inline]
async fn recv(&mut self) -> Result<ChangeEvent> {
match self.next_event().await? {
Some(event) => Ok(event),
None => Err(ReplicationError::Cancelled("snapshot complete".to_string())),
}
}
#[inline]
fn ack(&self, lsn: XLogRecPtr) {
self.inner
.stream()
.shared_lsn_feedback
.update_applied_lsn(lsn);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::snapshot::rows::SnapshotRows;
use crate::stream::{tests::create_test_stream, ReplicationStreamConfig};
use crate::types::{EventType, Lsn};
#[derive(serde::Deserialize)]
struct User {
id: i32,
}
const POINT: u64 = 0x1234_5678;
fn events(outcomes: Vec<Result<()>>) -> SnapshotEvents {
let config = ReplicationStreamConfig::builder("s", "p");
SnapshotEvents::new(SnapshotRows::mock(
create_test_stream(config),
outcomes,
Lsn::new(POINT),
))
}
#[tokio::test]
async fn maps_rows_to_inserts_stamped_with_the_consistent_point() {
let mut events = events(vec![Ok(())]);
let event = events.next_event().await.unwrap().unwrap();
assert_eq!(event.lsn, Lsn::new(POINT));
match &event.event_type {
EventType::Insert {
schema,
table,
relation_oid,
data,
} => {
assert_eq!(&**schema, "public");
assert_eq!(&**table, "users");
assert_eq!(*relation_oid, 42);
assert_eq!(
data.get("id").unwrap().as_str().unwrap(),
"1",
"the row payload must survive the row -> event mapping"
);
}
other => panic!("expected an Insert, got {other:?}"),
}
}
#[tokio::test]
async fn next_event_returns_none_at_the_end() {
let mut events = events(vec![]);
assert!(events.next_event().await.unwrap().is_none());
}
#[tokio::test]
async fn for_each_event_acks_the_consistent_point() {
let mut events = events(vec![Ok(()), Ok(())]);
let count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let seen = std::sync::Arc::clone(&count);
events
.for_each_event(move |_| {
let seen = std::sync::Arc::clone(&seen);
async move {
seen.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
})
.await
.unwrap();
assert_eq!(count.load(std::sync::atomic::Ordering::Relaxed), 2);
assert_eq!(
events.stream().shared_lsn_feedback.get_feedback_lsn().1,
POINT
);
}
#[tokio::test]
async fn event_source_reports_the_end_as_cancelled() {
let mut events = events(vec![]);
let err = EventSource::recv(&mut events).await.unwrap_err();
assert!(err.is_cancelled(), "{err:?}");
}
#[tokio::test]
async fn for_each_event_propagates_a_handler_error() {
let mut events = events(vec![Ok(())]);
let err = events
.for_each_event(|_| async { Err(ReplicationError::protocol("nope".to_string())) })
.await
.unwrap_err();
assert!(format!("{err}").contains("nope"), "{err}");
}
#[tokio::test]
async fn wal_router_dispatches_snapshot_rows_by_table() {
use crate::router::WalRouter;
let hits = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let counter = std::sync::Arc::clone(&hits);
let mut router = WalRouter::new();
router.on_insert("users", move |user: User| {
let counter = std::sync::Arc::clone(&counter);
async move {
assert!(
user.id > 0,
"snapshot rows must deserialize like streamed ones"
);
counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
});
let mut events = events(vec![Ok(()), Ok(()), Ok(())]);
router.run_snapshot(&mut events).await.unwrap();
assert_eq!(hits.load(std::sync::atomic::Ordering::Relaxed), 3);
}
#[tokio::test]
async fn wal_router_leaves_unrouted_tables_alone() {
use crate::router::WalRouter;
let mut router = WalRouter::new();
router.on_insert("other_table", |_: User| async { Ok(()) });
let mut events = events(vec![Ok(())]);
router.run_snapshot(&mut events).await.unwrap();
}
#[tokio::test]
async fn run_returns_the_stream_after_full_consumption() {
events(vec![Ok(())])
.run(|_| async { Ok(()) })
.await
.unwrap();
}
#[tokio::test]
async fn finish_before_completion_errors() {
let mut events = events(vec![Ok(()), Ok(())]);
events.next_event().await.unwrap();
match events.finish().await {
Ok(_) => panic!("a half-consumed snapshot must not yield the stream"),
Err(e) => assert!(format!("{e}").contains("not fully consumed"), "{e}"),
}
}
#[tokio::test]
async fn abandon_returns_the_stream() {
events(vec![Ok(())]).abandon().await.unwrap();
}
}