pub mod conformance;
pub mod engine;
pub mod error;
pub mod format;
use engine::{ColumnSchema, ComparePlan, DiffRow, Engine, TableRef, TableSchema};
use error::Result;
pub fn build_plan(
src: &TableSchema,
dst: &TableSchema,
cols: Option<&[String]>,
key: &str,
) -> Result<ComparePlan> {
let col_names: Vec<String> = match cols {
Some(cols) => cols.to_vec(),
None => src
.columns
.iter()
.filter(|c| c.name != key)
.map(|c| c.name.clone())
.collect(),
};
let mut shared_columns = Vec::new();
for col in &col_names {
let src_col: Option<&ColumnSchema> = src.columns.iter().find(|c| c.name == *col);
let dst_col: Option<&ColumnSchema> = dst.columns.iter().find(|c| c.name == *col);
match (src_col, dst_col) {
(Some(src_col), Some(dst_col)) => {
if src_col.ty != dst_col.ty {
return Err(error::VeritableError::Schema(format!(
"Column {} has different types in source and destination",
col
)));
}
shared_columns.push(src_col.clone());
}
_ => {
return Err(error::VeritableError::Schema(format!(
"Column {} not found in both source and destination",
col
)));
}
}
}
let key_column: &ColumnSchema =
src.columns.iter().find(|c| c.name == key).ok_or_else(|| {
error::VeritableError::Schema(format!("Key column {} not found in source", key))
})?;
Ok(ComparePlan {
key: key_column.clone(),
columns: shared_columns,
})
}
pub fn plan(
src: &dyn Engine,
src_t: &TableRef,
dst: &dyn Engine,
dst_t: &TableRef,
key: &str,
) -> Result<ComparePlan> {
let src_schema = src.introspect(src_t)?;
let dst_schema = dst.introspect(dst_t)?;
build_plan(&src_schema, &dst_schema, None, key)
}
pub fn joindiff(
_engine: &dyn Engine,
_a: &TableRef,
_b: &TableRef,
_plan: &ComparePlan,
) -> Result<Vec<DiffRow>> {
todo!()
}
pub fn hashdiff(
_src: &dyn Engine,
_dst: &dyn Engine,
_src_t: &TableRef,
_dst_t: &TableRef,
_plan: &ComparePlan,
_segment_size: usize,
_leaf_threshold: usize,
) -> Result<Vec<DiffRow>> {
todo!()
}
#[cfg(test)]
mod tests {
use super::*;
use engine::LogicalType;
fn col(name: &str, ty: LogicalType) -> ColumnSchema {
ColumnSchema {
name: name.into(),
ty,
nullable: true,
default_value: None,
primary_key: false,
}
}
#[test]
fn none_columns_defaults_to_all_non_key() {
let src = TableSchema {
columns: vec![
col("id", LogicalType::Int),
col("a", LogicalType::String),
col("b", LogicalType::Boolean),
],
};
let dst = TableSchema {
columns: vec![
col("id", LogicalType::Int),
col("a", LogicalType::String),
col("b", LogicalType::Boolean),
],
};
let p = build_plan(&src, &dst, None, "id").unwrap();
assert_eq!(p.key.name, "id");
let names: Vec<&str> = p.columns.iter().map(|c| c.name.as_str()).collect();
assert_eq!(names, ["a", "b"]);
}
#[test]
fn default_column_missing_in_dst_is_error() {
let src = TableSchema {
columns: vec![col("id", LogicalType::Int), col("a", LogicalType::String)],
};
let dst = TableSchema {
columns: vec![col("id", LogicalType::Int)],
};
assert!(build_plan(&src, &dst, None, "id").is_err());
}
#[test]
fn mismatched_types_are_an_error() {
let src = TableSchema {
columns: vec![col("id", LogicalType::Int), col("a", LogicalType::String)],
};
let dst = TableSchema {
columns: vec![col("id", LogicalType::Int), col("a", LogicalType::Boolean)],
};
assert!(build_plan(&src, &dst, None, "id").is_err());
}
}