1pub mod conformance;
2pub mod engine;
3pub mod error;
4pub mod format;
5
6use engine::{ColumnSchema, ComparePlan, DiffRow, Engine, TableRef, TableSchema};
7use error::Result;
8
9pub fn build_plan(
12 src: &TableSchema,
13 dst: &TableSchema,
14 cols: Option<&[String]>,
15 key: &str,
16) -> Result<ComparePlan> {
17 let col_names: Vec<String> = match cols {
19 Some(cols) => cols.to_vec(),
20 None => src
21 .columns
22 .iter()
23 .filter(|c| c.name != key)
24 .map(|c| c.name.clone())
25 .collect(),
26 };
27
28 let mut shared_columns = Vec::new();
29 for col in &col_names {
30 let src_col: Option<&ColumnSchema> = src.columns.iter().find(|c| c.name == *col);
31 let dst_col: Option<&ColumnSchema> = dst.columns.iter().find(|c| c.name == *col);
32 match (src_col, dst_col) {
33 (Some(src_col), Some(dst_col)) => {
34 if src_col.ty != dst_col.ty {
35 return Err(error::VeritableError::Schema(format!(
36 "Column {} has different types in source and destination",
37 col
38 )));
39 }
40 shared_columns.push(src_col.clone());
41 }
42 _ => {
43 return Err(error::VeritableError::Schema(format!(
44 "Column {} not found in both source and destination",
45 col
46 )));
47 }
48 }
49 }
50
51 let key_column: &ColumnSchema =
53 src.columns.iter().find(|c| c.name == key).ok_or_else(|| {
54 error::VeritableError::Schema(format!("Key column {} not found in source", key))
55 })?;
56 Ok(ComparePlan {
57 key: key_column.clone(),
58 columns: shared_columns,
59 })
60}
61
62pub fn plan(
64 src: &dyn Engine,
65 src_t: &TableRef,
66 dst: &dyn Engine,
67 dst_t: &TableRef,
68 key: &str,
69) -> Result<ComparePlan> {
70 let src_schema = src.introspect(src_t)?;
71 let dst_schema = dst.introspect(dst_t)?;
72 build_plan(&src_schema, &dst_schema, None, key)
73}
74
75pub fn joindiff(
77 _engine: &dyn Engine,
78 _a: &TableRef,
79 _b: &TableRef,
80 _plan: &ComparePlan,
81) -> Result<Vec<DiffRow>> {
82 todo!()
83}
84
85pub fn hashdiff(
86 _src: &dyn Engine,
87 _dst: &dyn Engine,
88 _src_t: &TableRef,
89 _dst_t: &TableRef,
90 _plan: &ComparePlan,
91 _segment_size: usize,
92 _leaf_threshold: usize,
93) -> Result<Vec<DiffRow>> {
94 todo!()
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use engine::LogicalType;
101
102 fn col(name: &str, ty: LogicalType) -> ColumnSchema {
103 ColumnSchema {
104 name: name.into(),
105 ty,
106 nullable: true,
107 default_value: None,
108 primary_key: false,
109 }
110 }
111
112 #[test]
113 fn none_columns_defaults_to_all_non_key() {
114 let src = TableSchema {
115 columns: vec![
116 col("id", LogicalType::Int),
117 col("a", LogicalType::String),
118 col("b", LogicalType::Boolean),
119 ],
120 };
121 let dst = TableSchema {
122 columns: vec![
123 col("id", LogicalType::Int),
124 col("a", LogicalType::String),
125 col("b", LogicalType::Boolean),
126 ],
127 };
128 let p = build_plan(&src, &dst, None, "id").unwrap();
129 assert_eq!(p.key.name, "id");
130 let names: Vec<&str> = p.columns.iter().map(|c| c.name.as_str()).collect();
131 assert_eq!(names, ["a", "b"]);
132 }
133
134 #[test]
135 fn default_column_missing_in_dst_is_error() {
136 let src = TableSchema {
137 columns: vec![col("id", LogicalType::Int), col("a", LogicalType::String)],
138 };
139 let dst = TableSchema {
140 columns: vec![col("id", LogicalType::Int)],
141 };
142 assert!(build_plan(&src, &dst, None, "id").is_err());
143 }
144
145 #[test]
146 fn mismatched_types_are_an_error() {
147 let src = TableSchema {
148 columns: vec![col("id", LogicalType::Int), col("a", LogicalType::String)],
149 };
150 let dst = TableSchema {
151 columns: vec![col("id", LogicalType::Int), col("a", LogicalType::Boolean)],
152 };
153 assert!(build_plan(&src, &dst, None, "id").is_err());
154 }
155}