use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display};
use kimberlite_store::TableId;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TableName(String);
impl TableName {
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Debug for TableName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TableName({:?})", self.0)
}
}
impl Display for TableName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for TableName {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for TableName {
fn from(s: String) -> Self {
Self::new(s)
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ColumnName(String);
impl ColumnName {
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Debug for ColumnName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ColumnName({:?})", self.0)
}
}
impl Display for ColumnName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for ColumnName {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for ColumnName {
fn from(s: String) -> Self {
Self::new(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DataType {
TinyInt,
SmallInt,
Integer,
BigInt,
Real,
Decimal {
precision: u8,
scale: u8,
},
Text,
Bytes,
Boolean,
Date,
Time,
Timestamp,
Uuid,
Json,
}
impl Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataType::TinyInt => write!(f, "TINYINT"),
DataType::SmallInt => write!(f, "SMALLINT"),
DataType::Integer => write!(f, "INTEGER"),
DataType::BigInt => write!(f, "BIGINT"),
DataType::Real => write!(f, "REAL"),
DataType::Decimal { precision, scale } => write!(f, "DECIMAL({precision},{scale})"),
DataType::Text => write!(f, "TEXT"),
DataType::Bytes => write!(f, "BYTES"),
DataType::Boolean => write!(f, "BOOLEAN"),
DataType::Date => write!(f, "DATE"),
DataType::Time => write!(f, "TIME"),
DataType::Timestamp => write!(f, "TIMESTAMP"),
DataType::Uuid => write!(f, "UUID"),
DataType::Json => write!(f, "JSON"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnDef {
pub name: ColumnName,
pub data_type: DataType,
pub nullable: bool,
}
impl ColumnDef {
pub fn new(name: impl Into<ColumnName>, data_type: DataType) -> Self {
Self {
name: name.into(),
data_type,
nullable: true,
}
}
pub fn not_null(mut self) -> Self {
self.nullable = false;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexDef {
pub index_id: u64,
pub name: String,
pub columns: Vec<ColumnName>,
}
impl IndexDef {
pub fn new(index_id: u64, name: impl Into<String>, columns: Vec<ColumnName>) -> Self {
Self {
index_id,
name: name.into(),
columns,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableDef {
pub table_id: TableId,
pub columns: Vec<ColumnDef>,
pub primary_key: Vec<ColumnName>,
pub indexes: Vec<IndexDef>,
}
impl TableDef {
pub fn new(table_id: TableId, columns: Vec<ColumnDef>, primary_key: Vec<ColumnName>) -> Self {
for pk_col in &primary_key {
debug_assert!(
columns.iter().any(|c| &c.name == pk_col),
"primary key column '{pk_col}' not found in columns"
);
}
Self {
table_id,
columns,
primary_key,
indexes: Vec::new(),
}
}
pub fn with_index(mut self, index: IndexDef) -> Self {
self.indexes.push(index);
self
}
pub fn indexes(&self) -> &[IndexDef] {
&self.indexes
}
pub fn find_index_for_column(&self, column: &ColumnName) -> Option<&IndexDef> {
self.indexes
.iter()
.find(|idx| !idx.columns.is_empty() && &idx.columns[0] == column)
}
pub fn find_column(&self, name: &ColumnName) -> Option<(usize, &ColumnDef)> {
self.columns
.iter()
.enumerate()
.find(|(_, c)| &c.name == name)
}
pub fn is_primary_key(&self, name: &ColumnName) -> bool {
self.primary_key.contains(name)
}
pub fn primary_key_position(&self, name: &ColumnName) -> Option<usize> {
self.primary_key.iter().position(|pk| pk == name)
}
pub fn primary_key_indices(&self) -> Vec<usize> {
self.primary_key
.iter()
.filter_map(|pk| self.find_column(pk).map(|(idx, _)| idx))
.collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct Schema {
tables: BTreeMap<TableName, TableDef>,
}
impl Schema {
pub fn new() -> Self {
Self::default()
}
pub fn add_table(&mut self, name: impl Into<TableName>, def: TableDef) {
self.tables.insert(name.into(), def);
}
pub fn get_table(&self, name: &TableName) -> Option<&TableDef> {
self.tables.get(name)
}
pub fn table_names(&self) -> impl Iterator<Item = &TableName> {
self.tables.keys()
}
pub fn all_tables(&self) -> impl Iterator<Item = (&TableName, &TableDef)> {
self.tables.iter()
}
pub fn len(&self) -> usize {
self.tables.len()
}
pub fn is_empty(&self) -> bool {
self.tables.is_empty()
}
}
#[derive(Debug, Default)]
pub struct SchemaBuilder {
schema: Schema,
}
impl SchemaBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn table(
mut self,
name: impl Into<TableName>,
table_id: TableId,
columns: Vec<ColumnDef>,
primary_key: Vec<ColumnName>,
) -> Self {
let def = TableDef::new(table_id, columns, primary_key);
self.schema.add_table(name, def);
self
}
pub fn build(self) -> Schema {
self.schema
}
}