use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Column {
pub name: String,
pub data_type: String,
pub nullable: bool,
pub primary_key: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableSchema {
pub name: String,
pub columns: Vec<Column>,
}
impl TableSchema {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
columns: Vec::new(),
}
}
pub fn column(
mut self,
name: &str,
data_type: &str,
nullable: bool,
primary_key: bool,
) -> Self {
self.columns.push(Column {
name: name.to_string(),
data_type: data_type.to_string(),
nullable,
primary_key,
});
self
}
pub fn primary_keys(&self) -> Vec<&Column> {
self.columns.iter().filter(|c| c.primary_key).collect()
}
pub fn column_names(&self) -> String {
self.columns
.iter()
.map(|c| c.name.as_str())
.collect::<Vec<_>>()
.join(", ")
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SchemaRegistry {
pub tables: HashMap<String, TableSchema>,
}
impl SchemaRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, table: TableSchema) {
self.tables.insert(table.name.clone(), table);
}
pub fn get(&self, name: &str) -> Option<&TableSchema> {
let lower = name.to_lowercase();
self.tables
.values()
.find(|t| t.name.to_lowercase() == lower)
}
pub fn table_names(&self) -> Vec<&str> {
self.tables.keys().map(|s| s.as_str()).collect()
}
pub fn to_schema_text(&self) -> String {
let mut out = String::new();
for table in self.tables.values() {
out.push_str(&format!("TABLE {}\n", table.name));
for col in &table.columns {
let pk = if col.primary_key { " PRIMARY KEY" } else { "" };
let null = if col.nullable { " NULL" } else { " NOT NULL" };
out.push_str(&format!(" {} {}{}{}\n", col.name, col.data_type, null, pk));
}
out.push('\n');
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_registry() -> SchemaRegistry {
let mut reg = SchemaRegistry::new();
reg.register(
TableSchema::new("users")
.column("id", "INTEGER", false, true)
.column("name", "VARCHAR(255)", false, false)
.column("email", "VARCHAR(255)", true, false),
);
reg.register(
TableSchema::new("orders")
.column("id", "INTEGER", false, true)
.column("user_id", "INTEGER", false, false)
.column("total", "DECIMAL(10,2)", false, false)
.column("created_at", "TIMESTAMP", false, false),
);
reg
}
#[test]
fn test_schema_building() {
let table = TableSchema::new("users")
.column("id", "INTEGER", false, true)
.column("name", "VARCHAR(255)", false, false);
assert_eq!(table.columns.len(), 2);
assert_eq!(table.primary_keys().len(), 1);
}
#[test]
fn test_column_names() {
let table = TableSchema::new("t")
.column("a", "INT", false, false)
.column("b", "TEXT", false, false);
assert_eq!(table.column_names(), "a, b");
}
#[test]
fn test_registry_lookup() {
let reg = make_registry();
assert!(reg.get("users").is_some());
assert!(reg.get("Users").is_some()); assert!(reg.get("nonexistent").is_none());
}
#[test]
fn test_registry_table_names() {
let reg = make_registry();
let names = reg.table_names();
assert_eq!(names.len(), 2);
}
#[test]
fn test_schema_text() {
let reg = make_registry();
let text = reg.to_schema_text();
assert!(text.contains("TABLE users"));
assert!(text.contains("TABLE orders"));
assert!(text.contains("PRIMARY KEY"));
}
}