use vibesql_types::SqlValue;
const APPEND_MODE_THRESHOLD: usize = 3;
#[derive(Debug, Clone)]
pub struct AppendModeTracker {
last_pk_value: Option<Vec<SqlValue>>,
append_mode: bool,
append_streak: usize,
}
impl AppendModeTracker {
pub fn new() -> Self {
Self { last_pk_value: None, append_mode: false, append_streak: 0 }
}
pub fn update(&mut self, pk_values: &[SqlValue]) {
if let Some(last_pk) = &self.last_pk_value {
if pk_values > last_pk.as_slice() {
self.append_streak += 1;
if self.append_streak >= APPEND_MODE_THRESHOLD {
self.append_mode = true;
}
} else {
self.append_mode = false;
self.append_streak = 0;
}
}
self.last_pk_value = Some(pk_values.to_vec());
}
pub fn is_active(&self) -> bool {
self.append_mode
}
pub fn reset(&mut self) {
self.last_pk_value = None;
self.append_mode = false;
self.append_streak = 0;
}
#[cfg(test)]
pub(crate) fn append_streak(&self) -> usize {
self.append_streak
}
#[cfg(test)]
pub(crate) fn last_pk_value(&self) -> Option<&Vec<SqlValue>> {
self.last_pk_value.as_ref()
}
}
impl Default for AppendModeTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_tracker_not_active() {
let tracker = AppendModeTracker::new();
assert!(!tracker.is_active());
assert_eq!(tracker.append_streak(), 0);
assert_eq!(tracker.last_pk_value(), None);
}
#[test]
fn test_first_insert_no_streak() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Integer(1)]);
assert!(!tracker.is_active());
assert_eq!(tracker.append_streak(), 0);
assert_eq!(tracker.last_pk_value(), Some(&vec![SqlValue::Integer(1)]));
}
#[test]
fn test_sequential_inserts_build_streak() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Integer(1)]);
assert_eq!(tracker.append_streak(), 0);
assert!(!tracker.is_active());
tracker.update(&[SqlValue::Integer(2)]);
assert_eq!(tracker.append_streak(), 1);
assert!(!tracker.is_active());
tracker.update(&[SqlValue::Integer(3)]);
assert_eq!(tracker.append_streak(), 2);
assert!(!tracker.is_active());
tracker.update(&[SqlValue::Integer(4)]);
assert_eq!(tracker.append_streak(), 3);
assert!(tracker.is_active());
}
#[test]
fn test_non_sequential_resets_append_mode() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Integer(1)]);
tracker.update(&[SqlValue::Integer(2)]);
tracker.update(&[SqlValue::Integer(3)]);
tracker.update(&[SqlValue::Integer(4)]);
assert!(tracker.is_active());
assert_eq!(tracker.append_streak(), 3);
tracker.update(&[SqlValue::Integer(2)]);
assert!(!tracker.is_active());
assert_eq!(tracker.append_streak(), 0);
}
#[test]
fn test_equal_pk_resets_append_mode() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Integer(1)]);
tracker.update(&[SqlValue::Integer(2)]);
tracker.update(&[SqlValue::Integer(3)]);
tracker.update(&[SqlValue::Integer(4)]);
assert!(tracker.is_active());
tracker.update(&[SqlValue::Integer(4)]);
assert!(!tracker.is_active());
assert_eq!(tracker.append_streak(), 0);
}
#[test]
fn test_reset_clears_state() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Integer(1)]);
tracker.update(&[SqlValue::Integer(2)]);
tracker.update(&[SqlValue::Integer(3)]);
tracker.update(&[SqlValue::Integer(4)]);
assert!(tracker.is_active());
tracker.reset();
assert!(!tracker.is_active());
assert_eq!(tracker.append_streak(), 0);
assert_eq!(tracker.last_pk_value(), None);
}
#[test]
fn test_composite_primary_key() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Integer(1), SqlValue::Integer(100)]);
tracker.update(&[SqlValue::Integer(1), SqlValue::Integer(101)]);
tracker.update(&[SqlValue::Integer(1), SqlValue::Integer(102)]);
tracker.update(&[SqlValue::Integer(1), SqlValue::Integer(103)]);
assert!(tracker.is_active());
assert_eq!(tracker.append_streak(), 3);
}
#[test]
fn test_string_primary_keys() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Varchar(arcstr::ArcStr::from("Alice"))]);
tracker.update(&[SqlValue::Varchar(arcstr::ArcStr::from("Bob"))]);
tracker.update(&[SqlValue::Varchar(arcstr::ArcStr::from("Charlie"))]);
tracker.update(&[SqlValue::Varchar(arcstr::ArcStr::from("David"))]);
assert!(tracker.is_active());
}
#[test]
fn test_continues_after_activation() {
let mut tracker = AppendModeTracker::new();
tracker.update(&[SqlValue::Integer(1)]);
tracker.update(&[SqlValue::Integer(2)]);
tracker.update(&[SqlValue::Integer(3)]);
tracker.update(&[SqlValue::Integer(4)]);
assert!(tracker.is_active());
tracker.update(&[SqlValue::Integer(100)]);
assert!(tracker.is_active());
assert_eq!(tracker.append_streak(), 4); }
}