#![allow(unreachable_pub)]
use std::sync::LazyLock;
use serde::{Deserialize, Serialize};
use crate::actions::{DomainMetadata, NUM_RECORDS};
use crate::engine_data::{GetData, RowVisitor, TypedGetData as _};
use crate::schema::{column_name, ColumnName, ColumnNamesAndTypes, DataType};
use crate::utils::require;
use crate::{KernelError, KernelResult};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RowTrackingDomainMetadata {
row_id_high_water_mark: i64,
}
pub(crate) const ROW_TRACKING_DOMAIN_NAME: &str = "delta.rowTracking";
pub const ROW_TRACKING_INITIAL_HIGH_WATER_MARK: i64 = -1;
impl RowTrackingDomainMetadata {
pub(crate) fn new(row_id_high_water_mark: i64) -> Self {
RowTrackingDomainMetadata {
row_id_high_water_mark,
}
}
pub(crate) fn high_water_mark(&self) -> i64 {
self.row_id_high_water_mark
}
pub(crate) fn initial() -> Self {
Self::new(ROW_TRACKING_INITIAL_HIGH_WATER_MARK)
}
}
pub(crate) fn parse_row_tracking_high_water_mark(configuration: &str) -> KernelResult<i64> {
Ok(serde_json::from_str::<RowTrackingDomainMetadata>(configuration)?.high_water_mark())
}
impl TryFrom<RowTrackingDomainMetadata> for DomainMetadata {
type Error = crate::KernelError;
fn try_from(metadata: RowTrackingDomainMetadata) -> KernelResult<Self> {
Ok(DomainMetadata::new(
ROW_TRACKING_DOMAIN_NAME.to_string(),
serde_json::to_string(&metadata)?,
))
}
}
pub(crate) struct RowTrackingVisitor {
pub(crate) row_id_high_water_mark: i64,
collect_base_row_ids: bool,
base_row_ids: Vec<i64>,
}
impl RowTrackingVisitor {
pub(crate) fn new(row_id_high_water_mark: Option<i64>) -> Self {
Self {
row_id_high_water_mark: row_id_high_water_mark
.unwrap_or(ROW_TRACKING_INITIAL_HIGH_WATER_MARK),
collect_base_row_ids: true,
base_row_ids: Vec::new(),
}
}
pub(crate) fn dry_run(row_id_high_water_mark: Option<i64>) -> Self {
Self {
collect_base_row_ids: false,
..Self::new(row_id_high_water_mark)
}
}
pub(crate) fn take_base_row_ids(&mut self) -> Vec<i64> {
std::mem::take(&mut self.base_row_ids)
}
}
impl RowVisitor for RowTrackingVisitor {
fn selected_column_names_and_types(&self) -> (&'static [ColumnName], &'static [DataType]) {
static NAMES_AND_TYPES: LazyLock<ColumnNamesAndTypes> = LazyLock::new(|| {
(
vec![column_name!("stats", NUM_RECORDS)],
vec![DataType::LONG],
)
.into()
});
NAMES_AND_TYPES.as_ref()
}
fn visit<'a>(&mut self, row_count: usize, getters: &[&'a dyn GetData<'a>]) -> KernelResult<()> {
require!(
getters.len() == 1,
KernelError::generic(format!(
"Wrong number of RowTrackingVisitor getters: {}",
getters.len()
))
);
if self.collect_base_row_ids {
self.base_row_ids.reserve(row_count);
}
for i in 0..row_count {
let num_records: i64 = getters[0].get_opt(i, NUM_RECORDS)?.ok_or_else(|| {
KernelError::InternalError(format!(
"{NUM_RECORDS} must be present in Add actions when row tracking is enabled."
))
})?;
if self.collect_base_row_ids {
self.base_row_ids.push(self.row_id_high_water_mark + 1);
}
self.row_id_high_water_mark += num_records;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use derive_more::Constructor;
use super::*;
use crate::engine_data::GetData;
use crate::unit_test_utils::assert_result_error_with_message;
#[derive(Constructor)]
struct MockGetData {
num_records_values: Vec<Option<i64>>,
}
impl<'a> GetData<'a> for MockGetData {
fn get_long(&'a self, row_index: usize, field_name: &str) -> KernelResult<Option<i64>> {
if field_name == NUM_RECORDS {
Ok(self.num_records_values.get(row_index).copied().flatten())
} else {
Ok(None)
}
}
}
fn create_getters(num_records_mock: &MockGetData) -> Vec<&dyn GetData<'_>> {
vec![num_records_mock]
}
#[test]
fn test_visit_basic_functionality() -> KernelResult<()> {
let mut visitor = RowTrackingVisitor::new(None);
let num_records_mock = MockGetData::new(vec![Some(10), Some(5), Some(20)]);
let getters = create_getters(&num_records_mock);
visitor.visit(3, &getters)?;
assert_eq!(visitor.take_base_row_ids(), vec![0, 10, 15]);
assert_eq!(visitor.row_id_high_water_mark, 34);
Ok(())
}
#[test]
fn test_visit_with_negative_high_water_mark() -> KernelResult<()> {
let mut visitor = RowTrackingVisitor::new(Some(-5));
let num_records_mock = MockGetData::new(vec![Some(3), Some(2)]);
let getters = create_getters(&num_records_mock);
visitor.visit(2, &getters)?;
assert_eq!(visitor.take_base_row_ids(), vec![-4, -1]);
assert_eq!(visitor.row_id_high_water_mark, 0);
Ok(())
}
#[test]
fn test_visit_with_zero_records() -> KernelResult<()> {
let mut visitor = RowTrackingVisitor::new(Some(10));
let num_records_mock = MockGetData::new(vec![Some(0), Some(0), Some(5)]);
let getters = create_getters(&num_records_mock);
visitor.visit(3, &getters)?;
assert_eq!(visitor.take_base_row_ids(), vec![11, 11, 11]);
assert_eq!(visitor.row_id_high_water_mark, 15);
Ok(())
}
#[test]
fn test_visit_empty_batch() -> KernelResult<()> {
let mut visitor = RowTrackingVisitor::new(Some(42));
let num_records_mock = MockGetData::new(vec![]);
let getters = create_getters(&num_records_mock);
visitor.visit(0, &getters)?;
assert!(visitor.take_base_row_ids().is_empty());
assert_eq!(visitor.row_id_high_water_mark, 42);
Ok(())
}
#[test]
fn test_visit_multiple_batches() -> KernelResult<()> {
let mut visitor = RowTrackingVisitor::new(Some(0));
let num_records_mock1 = MockGetData::new(vec![Some(10), Some(5)]);
let getters1 = create_getters(&num_records_mock1);
visitor.visit(2, &getters1)?;
let first_base_row_ids = visitor.take_base_row_ids();
let num_records_mock2 = MockGetData::new(vec![Some(3), Some(7), Some(2)]);
let getters2 = create_getters(&num_records_mock2);
visitor.visit(3, &getters2)?;
let second_base_row_ids = visitor.take_base_row_ids();
assert_eq!(first_base_row_ids, vec![1, 11]);
assert_eq!(second_base_row_ids, vec![16, 19, 26]);
assert_eq!(visitor.row_id_high_water_mark, 27);
Ok(())
}
#[test]
fn test_visit_wrong_getter_count() -> KernelResult<()> {
let mut visitor = RowTrackingVisitor::new(Some(0));
let wrong_getters: Vec<&dyn GetData<'_>> = vec![];
let result = visitor.visit(1, &wrong_getters);
assert_result_error_with_message(result, "Wrong number of RowTrackingVisitor getters");
Ok(())
}
#[test]
fn test_visit_missing_num_records() -> KernelResult<()> {
let mut visitor = RowTrackingVisitor::new(Some(0));
let num_records_mock = MockGetData::new(vec![None]); let getters = create_getters(&num_records_mock);
let result = visitor.visit(1, &getters);
assert_result_error_with_message(
result,
&format!("{NUM_RECORDS} must be present in Add actions when row tracking is enabled"),
);
Ok(())
}
#[test]
fn test_selected_column_names_and_types() {
let visitor = RowTrackingVisitor::new(Some(0));
let (names, types) = visitor.selected_column_names_and_types();
assert_eq!(names, (vec![column_name!("stats", NUM_RECORDS)]));
assert_eq!(types, vec![DataType::LONG]);
}
#[test]
fn test_serialization_roundtrip() -> KernelResult<()> {
let original = RowTrackingDomainMetadata::new(-42);
let json = serde_json::to_string(&original)?;
let deserialized: RowTrackingDomainMetadata = serde_json::from_str(&json)?;
assert_eq!(
original.row_id_high_water_mark,
deserialized.row_id_high_water_mark
);
Ok(())
}
}