use std::sync::{Arc, Mutex, Weak};
use vibesql_types::SqlValue;
pub const DEFAULT_CHANNEL_CAPACITY: usize = 1024;
#[derive(Debug, Clone, PartialEq)]
pub struct ChangeEventPk {
pub column: String,
pub value: SqlValue,
pub new_value: Option<SqlValue>,
}
impl ChangeEventPk {
pub fn single(column: impl Into<String>, value: SqlValue) -> Self {
Self { column: column.into(), value, new_value: None }
}
pub fn updated(column: impl Into<String>, old_value: SqlValue, new_value: SqlValue) -> Self {
Self { column: column.into(), value: old_value, new_value: Some(new_value) }
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChangeEvent {
Insert {
table_name: String,
row_index: usize,
pk: Option<ChangeEventPk>,
},
Update {
table_name: String,
row_index: usize,
pk: Option<ChangeEventPk>,
},
Delete {
table_name: String,
row_index: usize,
pk: Option<ChangeEventPk>,
},
}
impl ChangeEvent {
pub fn insert(table_name: impl Into<String>, row_index: usize) -> Self {
ChangeEvent::Insert { table_name: table_name.into(), row_index, pk: None }
}
pub fn insert_with_pk(
table_name: impl Into<String>,
row_index: usize,
pk: ChangeEventPk,
) -> Self {
ChangeEvent::Insert { table_name: table_name.into(), row_index, pk: Some(pk) }
}
pub fn update(table_name: impl Into<String>, row_index: usize) -> Self {
ChangeEvent::Update { table_name: table_name.into(), row_index, pk: None }
}
pub fn update_with_pk(
table_name: impl Into<String>,
row_index: usize,
pk: ChangeEventPk,
) -> Self {
ChangeEvent::Update { table_name: table_name.into(), row_index, pk: Some(pk) }
}
pub fn delete(table_name: impl Into<String>, row_index: usize) -> Self {
ChangeEvent::Delete { table_name: table_name.into(), row_index, pk: None }
}
pub fn delete_with_pk(
table_name: impl Into<String>,
row_index: usize,
pk: ChangeEventPk,
) -> Self {
ChangeEvent::Delete { table_name: table_name.into(), row_index, pk: Some(pk) }
}
pub fn table_name(&self) -> &str {
match self {
ChangeEvent::Insert { table_name, .. } => table_name,
ChangeEvent::Update { table_name, .. } => table_name,
ChangeEvent::Delete { table_name, .. } => table_name,
}
}
pub fn row_index(&self) -> usize {
match self {
ChangeEvent::Insert { row_index, .. } => *row_index,
ChangeEvent::Update { row_index, .. } => *row_index,
ChangeEvent::Delete { row_index, .. } => *row_index,
}
}
pub fn pk(&self) -> Option<&ChangeEventPk> {
match self {
ChangeEvent::Insert { pk, .. } => pk.as_ref(),
ChangeEvent::Update { pk, .. } => pk.as_ref(),
ChangeEvent::Delete { pk, .. } => pk.as_ref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecvError {
Empty,
Lagged(usize),
Closed,
}
struct ChannelState {
buffer: Vec<Option<ChangeEvent>>,
write_pos: usize,
total_sent: usize,
capacity: usize,
closed: bool,
}
impl ChannelState {
fn new(capacity: usize) -> Self {
Self { buffer: vec![None; capacity], write_pos: 0, total_sent: 0, capacity, closed: false }
}
}
#[derive(Clone)]
pub struct ChangeEventSender {
state: Arc<Mutex<ChannelState>>,
}
impl ChangeEventSender {
pub fn send(&self, event: ChangeEvent) -> usize {
let mut state = self.state.lock().unwrap();
let write_pos = state.write_pos;
let capacity = state.capacity;
state.buffer[write_pos] = Some(event);
state.write_pos = (write_pos + 1) % capacity;
state.total_sent += 1;
Arc::strong_count(&self.state) - 1 }
pub fn subscribe(&self) -> ChangeEventReceiver {
let state = self.state.lock().unwrap();
ChangeEventReceiver {
state: Arc::downgrade(&self.state),
read_pos: state.total_sent, }
}
}
impl std::fmt::Debug for ChangeEventSender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChangeEventSender").finish_non_exhaustive()
}
}
pub struct ChangeEventReceiver {
state: Weak<Mutex<ChannelState>>,
read_pos: usize,
}
impl ChangeEventReceiver {
pub fn try_recv(&mut self) -> Result<ChangeEvent, RecvError> {
let state_arc = self.state.upgrade().ok_or(RecvError::Closed)?;
let state = state_arc.lock().unwrap();
let oldest_available = state.total_sent.saturating_sub(state.capacity);
if self.read_pos < oldest_available {
let missed = oldest_available - self.read_pos;
self.read_pos = oldest_available;
return Err(RecvError::Lagged(missed));
}
if self.read_pos >= state.total_sent {
if state.closed {
return Err(RecvError::Closed);
}
return Err(RecvError::Empty);
}
let buffer_idx = self.read_pos % state.capacity;
let event = state.buffer[buffer_idx].clone().expect("Buffer slot should be filled");
self.read_pos += 1;
Ok(event)
}
pub fn recv_all(&mut self) -> Vec<ChangeEvent> {
let mut events = Vec::new();
loop {
match self.try_recv() {
Ok(event) => events.push(event),
Err(RecvError::Lagged(n)) => {
log::warn!("Change event receiver lagged, missed {} events", n);
}
Err(RecvError::Empty) | Err(RecvError::Closed) => break,
}
}
events
}
}
impl Clone for ChangeEventReceiver {
fn clone(&self) -> Self {
Self { state: self.state.clone(), read_pos: self.read_pos }
}
}
impl std::fmt::Debug for ChangeEventReceiver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChangeEventReceiver").field("read_pos", &self.read_pos).finish()
}
}
pub fn channel(capacity: usize) -> (ChangeEventSender, ChangeEventReceiver) {
let capacity = capacity.max(1); let state = Arc::new(Mutex::new(ChannelState::new(capacity)));
let sender = ChangeEventSender { state: state.clone() };
let receiver = ChangeEventReceiver { state: Arc::downgrade(&state), read_pos: 0 };
(sender, receiver)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_send_receive_single_event() {
let (sender, mut receiver) = channel(16);
sender.send(ChangeEvent::insert("users", 0));
let event = receiver.try_recv().unwrap();
assert!(
matches!(event, ChangeEvent::Insert { table_name, row_index: 0, .. } if table_name == "users")
);
}
#[test]
fn test_multiple_receivers() {
let (sender, mut rx1) = channel(16);
let mut rx2 = sender.subscribe();
sender.send(ChangeEvent::insert("users", 0));
assert!(rx1.try_recv().is_ok());
assert!(rx2.try_recv().is_ok());
}
#[test]
fn test_empty_when_no_events() {
let (_sender, mut receiver) = channel(16);
assert_eq!(receiver.try_recv(), Err(RecvError::Empty));
}
#[test]
fn test_lagged_receiver() {
let (sender, mut receiver) = channel(4);
for i in 0..10 {
sender.send(ChangeEvent::insert("users", i));
}
let result = receiver.try_recv();
assert!(matches!(result, Err(RecvError::Lagged(_))));
assert!(receiver.try_recv().is_ok());
}
#[test]
fn test_closed_channel() {
let (sender, mut receiver) = channel(16);
drop(sender);
assert_eq!(receiver.try_recv(), Err(RecvError::Closed));
}
#[test]
fn test_recv_all() {
let (sender, mut receiver) = channel(16);
sender.send(ChangeEvent::insert("users", 0));
sender.send(ChangeEvent::update("users", 0));
sender.send(ChangeEvent::delete("users", 0));
let events = receiver.recv_all();
assert_eq!(events.len(), 3);
assert!(matches!(events[0], ChangeEvent::Insert { .. }));
assert!(matches!(events[1], ChangeEvent::Update { .. }));
assert!(matches!(events[2], ChangeEvent::Delete { .. }));
}
#[test]
fn test_event_accessors() {
let event = ChangeEvent::insert("products", 42);
assert_eq!(event.table_name(), "products");
assert_eq!(event.row_index(), 42);
}
#[test]
fn test_new_subscriber_starts_from_current() {
let (sender, _rx1) = channel(16);
sender.send(ChangeEvent::insert("users", 0));
sender.send(ChangeEvent::insert("users", 1));
let mut rx2 = sender.subscribe();
assert_eq!(rx2.try_recv(), Err(RecvError::Empty));
sender.send(ChangeEvent::insert("users", 2));
let event = rx2.try_recv().unwrap();
assert_eq!(event.row_index(), 2);
}
}