pub trait DatabaseIntrospection {
fn get_table_list(&self) -> Vec<TableInfo>;
fn get_table_description(&self, table_name: &str) -> Vec<ColumnInfo>;
fn get_constraints(&self, table_name: &str) -> Vec<ConstraintInfo>;
fn get_indexes(&self, table_name: &str) -> Vec<IndexInfo>;
fn get_primary_key_column(&self, table_name: &str) -> Option<String>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableInfo {
pub name: String,
pub table_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnInfo {
pub name: String,
pub data_type: String,
pub nullable: bool,
pub default: Option<String>,
pub primary_key: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstraintInfo {
pub name: String,
pub constraint_type: String,
pub columns: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexInfo {
pub name: String,
pub columns: Vec<String>,
pub unique: bool,
}
#[cfg(test)]
mod tests {
use super::{ColumnInfo, ConstraintInfo, DatabaseIntrospection, IndexInfo, TableInfo};
struct FakeIntrospection;
impl DatabaseIntrospection for FakeIntrospection {
fn get_table_list(&self) -> Vec<TableInfo> {
vec![
TableInfo {
name: "widgets".to_string(),
table_type: "table".to_string(),
},
TableInfo {
name: "widgets_view".to_string(),
table_type: "view".to_string(),
},
]
}
fn get_table_description(&self, table_name: &str) -> Vec<ColumnInfo> {
match table_name {
"widgets" => vec![
ColumnInfo {
name: "id".to_string(),
data_type: "INTEGER".to_string(),
nullable: false,
default: None,
primary_key: true,
},
ColumnInfo {
name: "slug".to_string(),
data_type: "TEXT".to_string(),
nullable: false,
default: Some("''".to_string()),
primary_key: false,
},
],
_ => Vec::new(),
}
}
fn get_constraints(&self, table_name: &str) -> Vec<ConstraintInfo> {
match table_name {
"widgets" => vec![ConstraintInfo {
name: "widgets_slug_key".to_string(),
constraint_type: "unique".to_string(),
columns: vec!["slug".to_string()],
}],
_ => Vec::new(),
}
}
fn get_indexes(&self, table_name: &str) -> Vec<IndexInfo> {
match table_name {
"widgets" => vec![IndexInfo {
name: "widgets_slug_idx".to_string(),
columns: vec!["slug".to_string()],
unique: false,
}],
_ => Vec::new(),
}
}
fn get_primary_key_column(&self, table_name: &str) -> Option<String> {
(table_name == "widgets").then(|| "id".to_string())
}
}
#[test]
fn introspection_lists_tables() {
let introspection = FakeIntrospection;
let tables = introspection.get_table_list();
assert_eq!(tables.len(), 2);
assert_eq!(tables[0].name, "widgets");
assert_eq!(tables[1].table_type, "view");
}
#[test]
fn introspection_describes_table_columns() {
let introspection = FakeIntrospection;
let columns = introspection.get_table_description("widgets");
assert_eq!(columns.len(), 2);
assert!(columns[0].primary_key);
assert_eq!(columns[1].default.as_deref(), Some("''"));
}
#[test]
fn introspection_reports_constraints() {
let introspection = FakeIntrospection;
let constraints = introspection.get_constraints("widgets");
assert_eq!(constraints.len(), 1);
assert_eq!(constraints[0].constraint_type, "unique");
assert_eq!(constraints[0].columns, vec!["slug"]);
}
#[test]
fn introspection_reports_indexes() {
let introspection = FakeIntrospection;
let indexes = introspection.get_indexes("widgets");
assert_eq!(indexes.len(), 1);
assert_eq!(indexes[0].name, "widgets_slug_idx");
assert!(!indexes[0].unique);
}
#[test]
fn introspection_reports_primary_key_column() {
let introspection = FakeIntrospection;
assert_eq!(
introspection.get_primary_key_column("widgets").as_deref(),
Some("id")
);
assert_eq!(introspection.get_primary_key_column("missing"), None);
}
#[test]
fn info_structs_are_cloneable_and_comparable() {
let table = TableInfo {
name: "widgets".to_string(),
table_type: "table".to_string(),
};
let cloned = table.clone();
assert_eq!(table, cloned);
}
}