use std::{borrow::Cow, fmt, marker::PhantomData};
use indexmap::IndexMap;
use crate::core::types::TypeId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FactKeyName {
ColumnName,
ColumnIndex,
ExpectedRows,
ActualRows,
ExpectedCount,
ActualCount,
LeftTypeId,
RightTypeId,
LeftDevice,
RightDevice,
TableRowCount,
TableColumnCount,
KeysCount,
DistinctKeysCount,
TypeId,
ValidationReason,
ExecutionBindingId,
ValidationDevice,
ValidationStream,
ControlPlaneScalarBytes,
}
impl fmt::Display for FactKeyName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_snake_case())
}
}
#[derive(Debug)]
pub(crate) struct FactKey<T> {
name: FactKeyName,
_marker: PhantomData<fn() -> T>,
}
impl<T> Clone for FactKey<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for FactKey<T> {}
impl<T> FactKey<T> {
pub(crate) const fn new(name: FactKeyName) -> Self {
Self { name, _marker: PhantomData }
}
pub(crate) const fn name(&self) -> FactKeyName {
self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FactValue {
Str(Cow<'static, str>),
Usize(usize),
I64(i64),
TypeId(TypeId),
DeviceId(i32),
U64(u64),
}
impl fmt::Display for FactValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FactValue::Str(s) => f.write_str(s),
FactValue::Usize(v) => write!(f, "{v}"),
FactValue::I64(v) => write!(f, "{v}"),
FactValue::TypeId(t) => write!(f, "{t}"),
FactValue::DeviceId(d) => write!(f, "{d}"),
FactValue::U64(v) => write!(f, "{v}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FactValueVariant {
Str,
Usize,
I64,
TypeId,
DeviceId,
U64,
}
impl FactValueVariant {
pub(crate) const fn of_value(value: &FactValue) -> Self {
match value {
FactValue::Str(_) => FactValueVariant::Str,
FactValue::Usize(_) => FactValueVariant::Usize,
FactValue::I64(_) => FactValueVariant::I64,
FactValue::TypeId(_) => FactValueVariant::TypeId,
FactValue::DeviceId(_) => FactValueVariant::DeviceId,
FactValue::U64(_) => FactValueVariant::U64,
}
}
}
impl FactKeyName {
pub const fn as_snake_case(self) -> &'static str {
match self {
FactKeyName::ColumnName => "column_name",
FactKeyName::ColumnIndex => "column_index",
FactKeyName::ExpectedRows => "expected_rows",
FactKeyName::ActualRows => "actual_rows",
FactKeyName::ExpectedCount => "expected_count",
FactKeyName::ActualCount => "actual_count",
FactKeyName::LeftTypeId => "left_type_id",
FactKeyName::RightTypeId => "right_type_id",
FactKeyName::LeftDevice => "left_device",
FactKeyName::RightDevice => "right_device",
FactKeyName::TableRowCount => "table_row_count",
FactKeyName::TableColumnCount => "table_column_count",
FactKeyName::KeysCount => "keys_count",
FactKeyName::DistinctKeysCount => "distinct_keys_count",
FactKeyName::TypeId => "type_id",
FactKeyName::ValidationReason => "validation_reason",
FactKeyName::ExecutionBindingId => "execution_binding_id",
FactKeyName::ValidationDevice => "validation_device",
FactKeyName::ValidationStream => "validation_stream",
FactKeyName::ControlPlaneScalarBytes => "control_plane_scalar_bytes",
}
}
pub fn from_snake_case(name: &str) -> Option<Self> {
Some(match name {
"column_name" => FactKeyName::ColumnName,
"column_index" => FactKeyName::ColumnIndex,
"expected_rows" => FactKeyName::ExpectedRows,
"actual_rows" => FactKeyName::ActualRows,
"expected_count" => FactKeyName::ExpectedCount,
"actual_count" => FactKeyName::ActualCount,
"left_type_id" => FactKeyName::LeftTypeId,
"right_type_id" => FactKeyName::RightTypeId,
"left_device" => FactKeyName::LeftDevice,
"right_device" => FactKeyName::RightDevice,
"table_row_count" => FactKeyName::TableRowCount,
"table_column_count" => FactKeyName::TableColumnCount,
"keys_count" => FactKeyName::KeysCount,
"distinct_keys_count" => FactKeyName::DistinctKeysCount,
"type_id" => FactKeyName::TypeId,
"validation_reason" => FactKeyName::ValidationReason,
"execution_binding_id" => FactKeyName::ExecutionBindingId,
"validation_device" => FactKeyName::ValidationDevice,
"validation_stream" => FactKeyName::ValidationStream,
"control_plane_scalar_bytes" => FactKeyName::ControlPlaneScalarBytes,
_ => return None,
})
}
pub(crate) const fn legal_variant(self) -> FactValueVariant {
match self {
FactKeyName::ColumnName => FactValueVariant::Str,
FactKeyName::ColumnIndex => FactValueVariant::Usize,
FactKeyName::ExpectedRows => FactValueVariant::Usize,
FactKeyName::ActualRows => FactValueVariant::Usize,
FactKeyName::ExpectedCount => FactValueVariant::Usize,
FactKeyName::ActualCount => FactValueVariant::Usize,
FactKeyName::LeftTypeId => FactValueVariant::TypeId,
FactKeyName::RightTypeId => FactValueVariant::TypeId,
FactKeyName::LeftDevice => FactValueVariant::DeviceId,
FactKeyName::RightDevice => FactValueVariant::DeviceId,
FactKeyName::TableRowCount => FactValueVariant::Usize,
FactKeyName::TableColumnCount => FactValueVariant::Usize,
FactKeyName::KeysCount => FactValueVariant::Usize,
FactKeyName::DistinctKeysCount => FactValueVariant::Usize,
FactKeyName::TypeId => FactValueVariant::TypeId,
FactKeyName::ValidationReason => FactValueVariant::Str,
FactKeyName::ExecutionBindingId => FactValueVariant::U64,
FactKeyName::ValidationDevice => FactValueVariant::DeviceId,
FactKeyName::ValidationStream => FactValueVariant::Usize,
FactKeyName::ControlPlaneScalarBytes => FactValueVariant::Usize,
}
}
}
pub(crate) trait IntoFactValue {
fn into_fact_value(self) -> FactValue;
}
impl IntoFactValue for &'static str {
fn into_fact_value(self) -> FactValue {
FactValue::Str(Cow::Borrowed(self))
}
}
impl IntoFactValue for String {
fn into_fact_value(self) -> FactValue {
FactValue::Str(Cow::Owned(self))
}
}
impl IntoFactValue for Cow<'static, str> {
fn into_fact_value(self) -> FactValue {
FactValue::Str(self)
}
}
impl IntoFactValue for usize {
fn into_fact_value(self) -> FactValue {
FactValue::Usize(self)
}
}
impl IntoFactValue for i64 {
fn into_fact_value(self) -> FactValue {
FactValue::I64(self)
}
}
impl IntoFactValue for u64 {
fn into_fact_value(self) -> FactValue {
FactValue::U64(self)
}
}
impl IntoFactValue for TypeId {
fn into_fact_value(self) -> FactValue {
FactValue::TypeId(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct DeviceIdFact(pub(crate) i32);
impl IntoFactValue for DeviceIdFact {
fn into_fact_value(self) -> FactValue {
FactValue::DeviceId(self.0)
}
}
pub(crate) const FACT_COLUMN_NAME: FactKey<Cow<'static, str>> = FactKey::new(FactKeyName::ColumnName);
pub(crate) const FACT_COLUMN_INDEX: FactKey<usize> = FactKey::new(FactKeyName::ColumnIndex);
pub(crate) const FACT_EXPECTED_ROWS: FactKey<usize> = FactKey::new(FactKeyName::ExpectedRows);
pub(crate) const FACT_ACTUAL_ROWS: FactKey<usize> = FactKey::new(FactKeyName::ActualRows);
pub(crate) const FACT_EXPECTED_COUNT: FactKey<usize> = FactKey::new(FactKeyName::ExpectedCount);
pub(crate) const FACT_ACTUAL_COUNT: FactKey<usize> = FactKey::new(FactKeyName::ActualCount);
pub(crate) const FACT_LEFT_TYPE_ID: FactKey<TypeId> = FactKey::new(FactKeyName::LeftTypeId);
pub(crate) const FACT_RIGHT_TYPE_ID: FactKey<TypeId> = FactKey::new(FactKeyName::RightTypeId);
pub(crate) const FACT_LEFT_DEVICE: FactKey<DeviceIdFact> = FactKey::new(FactKeyName::LeftDevice);
pub(crate) const FACT_RIGHT_DEVICE: FactKey<DeviceIdFact> = FactKey::new(FactKeyName::RightDevice);
pub(crate) const FACT_TABLE_ROW_COUNT: FactKey<usize> = FactKey::new(FactKeyName::TableRowCount);
pub(crate) const FACT_TABLE_COLUMN_COUNT: FactKey<usize> = FactKey::new(FactKeyName::TableColumnCount);
pub(crate) const FACT_KEYS_COUNT: FactKey<usize> = FactKey::new(FactKeyName::KeysCount);
pub(crate) const FACT_DISTINCT_KEYS_COUNT: FactKey<usize> = FactKey::new(FactKeyName::DistinctKeysCount);
pub(crate) const FACT_TYPE_ID: FactKey<TypeId> = FactKey::new(FactKeyName::TypeId);
pub(crate) const FACT_VALIDATION_REASON: FactKey<&'static str> = FactKey::new(FactKeyName::ValidationReason);
pub(crate) const FACT_EXECUTION_BINDING_ID: FactKey<u64> = FactKey::new(FactKeyName::ExecutionBindingId);
pub(crate) const FACT_VALIDATION_DEVICE: FactKey<DeviceIdFact> = FactKey::new(FactKeyName::ValidationDevice);
pub(crate) const FACT_VALIDATION_STREAM: FactKey<usize> = FactKey::new(FactKeyName::ValidationStream);
pub(crate) const FACT_CONTROL_PLANE_SCALAR_BYTES: FactKey<usize> = FactKey::new(FactKeyName::ControlPlaneScalarBytes);
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Facts {
inner: IndexMap<FactKeyName, FactValue>,
}
impl Facts {
pub(crate) fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn get(&self, key: FactKeyName) -> Option<&FactValue> {
self.inner.get(&key)
}
pub fn contains(&self, key: FactKeyName) -> bool {
self.inner.contains_key(&key)
}
pub fn iter(&self) -> impl Iterator<Item = (FactKeyName, &FactValue)> {
self.inner.iter().map(|(k, v)| (*k, v))
}
pub(crate) fn insert(&mut self, key: FactKeyName, value: FactValue) {
debug_assert_eq!(
FactValueVariant::of_value(&value),
key.legal_variant(),
"{key} bound to wrong FactValue variant"
);
self.inner.entry(key).or_insert(value);
}
}
impl fmt::Display for Facts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("{ ")?;
let mut first = true;
for (key, value) in self.iter() {
if !first {
f.write_str(", ")?;
}
first = false;
write!(f, "{key}: {value}")?;
}
f.write_str(" }")
}
}
#[cfg(test)]
mod tests {
use std::panic::{AssertUnwindSafe, catch_unwind};
use super::{FactKeyName, FactValue, Facts};
#[test]
fn insert_keeps_first_value_on_duplicate_key() {
let mut facts = Facts::new();
facts.insert(FactKeyName::ExpectedRows, FactValue::Usize(3));
facts.insert(FactKeyName::ExpectedRows, FactValue::Usize(99));
assert_eq!(facts.get(FactKeyName::ExpectedRows), Some(&FactValue::Usize(3)));
}
#[test]
fn insert_panics_on_wrong_value_variant() {
let panic = catch_unwind(AssertUnwindSafe(|| {
let mut facts = Facts::new();
facts.insert(FactKeyName::ExpectedRows, FactValue::Str("3".into()));
}));
assert!(panic.is_err(), "wrong FactValue variant must panic instead of smuggling numbers through strings");
}
}