use rudb_common::{Error, Field, LogicalType, Result};
use rudb_plan::ColumnBinding;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Schema {
fields: Vec<Field>,
bindings: Vec<ColumnBinding>,
}
impl Schema {
pub fn new(fields: Vec<Field>, bindings: Vec<ColumnBinding>) -> Result<Self> {
if fields.len() != bindings.len() {
return Err(Error::internal(format!(
"a schema of {} fields and {} bindings",
fields.len(),
bindings.len()
)));
}
Ok(Self { fields, bindings })
}
#[must_use]
pub fn numbered(fields: Vec<Field>, table: u32) -> Self {
let bindings = (0..fields.len())
.map(|at| {
ColumnBinding::new(
table,
u32::try_from(at).expect("a schema this wide cannot be built"),
)
})
.collect();
Self { fields, bindings }
}
#[must_use]
pub fn empty() -> Self {
Self { fields: Vec::new(), bindings: Vec::new() }
}
#[must_use]
pub fn fields(&self) -> &[Field] {
&self.fields
}
#[must_use]
pub fn bindings(&self) -> &[ColumnBinding] {
&self.bindings
}
#[must_use]
pub fn width(&self) -> usize {
self.fields.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
#[must_use]
pub fn types(&self) -> Vec<LogicalType> {
self.fields.iter().map(|field| field.ty.clone()).collect()
}
#[must_use]
pub fn names(&self) -> Vec<String> {
self.fields.iter().map(|field| field.name.clone()).collect()
}
#[must_use]
pub fn position_of(&self, binding: ColumnBinding) -> Option<usize> {
self.bindings.iter().position(|held| *held == binding)
}
#[must_use]
pub fn concat(left: &Self, right: &Self) -> Self {
let mut fields = left.fields.clone();
fields.extend(right.fields.iter().cloned());
let mut bindings = left.bindings.clone();
bindings.extend(right.bindings.iter().copied());
Self { fields, bindings }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn two() -> Schema {
Schema::numbered(
vec![Field::new("a", LogicalType::Integer), Field::new("b", LogicalType::Varchar)],
7,
)
}
#[test]
fn a_numbered_schema_binds_its_columns_in_order() {
let schema = two();
assert_eq!(schema.position_of(ColumnBinding::new(7, 0)), Some(0));
assert_eq!(schema.position_of(ColumnBinding::new(7, 1)), Some(1));
assert_eq!(schema.position_of(ColumnBinding::new(7, 2)), None);
assert_eq!(schema.position_of(ColumnBinding::new(6, 0)), None);
}
#[test]
fn a_schema_reports_its_names_and_types_in_output_order() {
let schema = two();
assert_eq!(schema.names(), vec!["a".to_string(), "b".to_string()]);
assert_eq!(schema.types(), vec![LogicalType::Integer, LogicalType::Varchar]);
assert_eq!(schema.width(), 2);
}
#[test]
fn concatenating_keeps_both_sides_distinguishable() {
let left = Schema::numbered(vec![Field::new("id", LogicalType::Integer)], 0);
let right = Schema::numbered(vec![Field::new("id", LogicalType::Integer)], 1);
let joined = Schema::concat(&left, &right);
assert_eq!(joined.width(), 2);
assert_eq!(joined.position_of(ColumnBinding::new(0, 0)), Some(0));
assert_eq!(joined.position_of(ColumnBinding::new(1, 0)), Some(1));
}
#[test]
fn a_schema_whose_halves_disagree_is_caught() {
let error = Schema::new(vec![Field::new("a", LogicalType::Integer)], Vec::new())
.expect_err("one field and no bindings");
assert!(error.message().contains("1 fields and 0 bindings"), "{error}");
}
}