use indexmap::{IndexMap, IndexSet};
use crate::schema::{FieldType, Record, Scalar, ScalarKind, Schema};
use super::prune::satisfiable_set;
pub fn compatible_with(a: &Schema, b: &Schema) -> bool {
let sat_a = satisfiable_set(a);
let mut memo: IndexMap<(String, String), bool> = IndexMap::new();
sub(
a,
&FieldType::Ref(a.root().clone()),
b,
&FieldType::Ref(b.root().clone()),
&sat_a,
&mut memo,
)
}
pub fn equivalent(a: &Schema, b: &Schema) -> bool {
compatible_with(a, b) && compatible_with(b, a)
}
fn sub(
sa: &Schema,
ta: &FieldType,
sb: &Schema,
tb: &FieldType,
sat_a: &IndexSet<String>,
memo: &mut IndexMap<(String, String), bool>,
) -> bool {
match (ta, tb) {
(FieldType::Ref(ra), _) if !sat_a.contains(&ra.name) => true,
(_, FieldType::Any) => true,
(FieldType::Any, _) => false,
(FieldType::Scalar(a), FieldType::Scalar(b)) => scalar_sub(*a, *b),
(FieldType::Ref(ra), FieldType::Ref(rb)) => {
let key = (ra.name.clone(), rb.name.clone());
if let Some(&v) = memo.get(&key) {
return v;
}
memo.insert(key.clone(), true);
let reca = sa
.env()
.get(&ra.name)
.expect("Schema's own invariant: every Ref resolves within its env");
let recb = sb
.env()
.get(&rb.name)
.expect("Schema's own invariant: every Ref resolves within its env");
let result = record_sub(sa, reca, sb, recb, sat_a, memo);
memo.insert(key, result);
result
}
_ => false,
}
}
fn record_sub(
sa: &Schema,
a: &Record,
sb: &Schema,
b: &Record,
sat_a: &IndexSet<String>,
memo: &mut IndexMap<(String, String), bool>,
) -> bool {
for fa in a.fields() {
if fa.max == Some(0) {
continue; }
if fa.min == 0
&& let FieldType::Ref(r) = &fa.ty
&& !sat_a.contains(&r.name)
{
continue; }
let Some(fb) = b.field(&fa.label) else {
return false; };
if !(fb.min <= fa.min && le(fa.max, fb.max)) {
return false; }
if !sub(sa, &fa.ty, sb, &fb.ty, sat_a, memo) {
return false;
}
}
for fb in b.fields() {
if fb.min >= 1 {
match a.field(&fb.label) {
None => return false,
Some(fa) if fa.min < fb.min => return false,
_ => {}
}
}
}
true
}
fn le(x: Option<usize>, y: Option<usize>) -> bool {
match y {
None => true,
Some(y) => match x {
None => false,
Some(x) => x <= y,
},
}
}
fn scalar_sub(a: Scalar, b: Scalar) -> bool {
if a.is_nullable() && !b.is_nullable() {
return false;
}
if a.kind() == b.kind() {
return true;
}
a.kind() == ScalarKind::Integer && b.kind() == ScalarKind::Number
}