use std::fmt;
use crate::ModelError;
pub const MAX_CATALOG_TABLES: usize = 256;
pub const MAX_CATALOG_COLUMNS: usize = 256;
pub const MAX_CATALOG_NAME_BYTES: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CatalogColumnType {
Utf8,
FixedBytes {
len: u32,
},
UInt64,
Boolean,
}
#[derive(Clone, PartialEq, Eq)]
pub struct CatalogColumn {
name: String,
logical_type: CatalogColumnType,
nullable: bool,
}
impl CatalogColumn {
pub fn try_new(
name: String,
logical_type: CatalogColumnType,
nullable: bool,
) -> Result<Self, ModelError> {
if name.is_empty() || name.len() > MAX_CATALOG_NAME_BYTES {
return Err(ModelError::Bounds("name"));
}
Ok(Self {
name,
logical_type,
nullable,
})
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn logical_type(&self) -> CatalogColumnType {
self.logical_type
}
#[must_use]
pub const fn nullable(&self) -> bool {
self.nullable
}
}
impl fmt::Debug for CatalogColumn {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CatalogColumn")
.field("logical_type", &self.logical_type)
.field("nullable", &self.nullable)
.finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct CatalogTable {
name: String,
columns: Vec<CatalogColumn>,
}
impl CatalogTable {
pub fn try_new(name: String, columns: Vec<CatalogColumn>) -> Result<Self, ModelError> {
if name.is_empty() || name.len() > MAX_CATALOG_NAME_BYTES {
return Err(ModelError::Bounds("name"));
}
if columns.is_empty() || columns.len() > MAX_CATALOG_COLUMNS {
return Err(ModelError::Bounds("columns"));
}
Ok(Self { name, columns })
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn columns(&self) -> &[CatalogColumn] {
&self.columns
}
}
impl fmt::Debug for CatalogTable {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CatalogTable")
.field("columns", &self.columns.len())
.finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct CatalogReply {
tables: Vec<CatalogTable>,
}
impl CatalogReply {
pub fn try_new(tables: Vec<CatalogTable>) -> Result<Self, ModelError> {
if tables.len() > MAX_CATALOG_TABLES {
return Err(ModelError::Bounds("tables"));
}
if tables
.windows(2)
.any(|pair| pair[0].name() >= pair[1].name())
{
return Err(ModelError::Order("tables"));
}
Ok(Self { tables })
}
#[must_use]
pub fn tables(&self) -> &[CatalogTable] {
&self.tables
}
}
impl fmt::Debug for CatalogReply {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CatalogReply")
.field("tables", &self.tables.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn column(name: &str) -> CatalogColumn {
CatalogColumn::try_new(name.to_owned(), CatalogColumnType::Utf8, false).unwrap()
}
fn table(name: &str) -> CatalogTable {
CatalogTable::try_new(name.to_owned(), vec![column("id")]).unwrap()
}
#[test]
fn reply_refuses_unsorted_or_duplicate_tables() {
assert_eq!(
CatalogReply::try_new(vec![table("b"), table("a")]),
Err(ModelError::Order("tables"))
);
assert_eq!(
CatalogReply::try_new(vec![table("a"), table("a")]),
Err(ModelError::Order("tables"))
);
assert_eq!(
CatalogReply::try_new(vec![table("a"), table("b")])
.unwrap()
.tables()
.len(),
2
);
}
#[test]
fn table_and_column_refuse_empty_names() {
assert_eq!(
CatalogTable::try_new(String::new(), vec![column("id")]),
Err(ModelError::Bounds("name"))
);
assert_eq!(
CatalogTable::try_new("t".to_owned(), vec![]),
Err(ModelError::Bounds("columns"))
);
assert_eq!(
CatalogColumn::try_new(String::new(), CatalogColumnType::Boolean, true),
Err(ModelError::Bounds("name"))
);
}
}