use crate::ast::ddl::IndexMethod;
#[derive(Debug, Clone)]
pub struct IndexMetadata {
pub index_id: u32,
pub name: String,
pub catalog_name: String,
pub namespace_name: String,
pub table: String,
pub columns: Vec<String>,
pub column_indices: Vec<usize>,
pub unique: bool,
pub method: Option<IndexMethod>,
pub options: Vec<(String, String)>,
}
impl IndexMetadata {
pub fn new(
index_id: u32,
name: impl Into<String>,
table: impl Into<String>,
columns: Vec<String>,
) -> Self {
Self {
index_id,
name: name.into(),
catalog_name: "default".to_string(),
namespace_name: "default".to_string(),
table: table.into(),
columns,
column_indices: Vec::new(),
unique: false,
method: None,
options: Vec::new(),
}
}
pub fn with_column_indices(mut self, indices: Vec<usize>) -> Self {
self.column_indices = indices;
self
}
pub fn with_unique(mut self, unique: bool) -> Self {
self.unique = unique;
self
}
pub fn with_method(mut self, method: IndexMethod) -> Self {
self.method = Some(method);
self
}
pub fn with_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.options.push((key.into(), value.into()));
self
}
pub fn with_options(mut self, options: Vec<(String, String)>) -> Self {
self.options = options;
self
}
pub fn get_option(&self, key: &str) -> Option<&str> {
self.options
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
pub fn covers_column(&self, column: &str) -> bool {
self.columns.iter().any(|c| c == column)
}
pub fn is_single_column(&self) -> bool {
self.columns.len() == 1
}
pub fn first_column(&self) -> Option<&str> {
self.columns.first().map(|s| s.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_index_metadata_new() {
let index = IndexMetadata::new(1, "idx_users_id", "users", vec!["id".into()]);
assert_eq!(index.index_id, 1);
assert_eq!(index.name, "idx_users_id");
assert_eq!(index.table, "users");
assert_eq!(index.columns, vec!["id"]);
assert!(index.column_indices.is_empty());
assert!(!index.unique);
assert!(index.method.is_none());
assert!(index.options.is_empty());
}
#[test]
fn test_index_metadata_with_column_indices() {
let index =
IndexMetadata::new(1, "idx", "table", vec!["col".into()]).with_column_indices(vec![2]);
assert_eq!(index.column_indices, vec![2]);
}
#[test]
fn test_index_metadata_with_unique() {
let index = IndexMetadata::new(1, "idx", "table", vec!["col".into()]).with_unique(true);
assert!(index.unique);
}
#[test]
fn test_index_metadata_composite() {
let index = IndexMetadata::new(
5,
"idx_composite",
"orders",
vec!["user_id".into(), "order_date".into()],
)
.with_column_indices(vec![0, 3])
.with_unique(true);
assert_eq!(index.index_id, 5);
assert_eq!(index.columns.len(), 2);
assert_eq!(index.column_indices, vec![0, 3]);
assert!(index.unique);
assert!(!index.is_single_column());
}
#[test]
fn test_index_metadata_with_method() {
let index = IndexMetadata::new(1, "idx_users_name", "users", vec!["name".into()])
.with_method(IndexMethod::BTree);
assert_eq!(index.method, Some(IndexMethod::BTree));
}
#[test]
fn test_index_metadata_hnsw_with_options() {
let index = IndexMetadata::new(1, "idx_items_embedding", "items", vec!["embedding".into()])
.with_method(IndexMethod::Hnsw)
.with_option("m", "16")
.with_option("ef_construction", "200");
assert_eq!(index.method, Some(IndexMethod::Hnsw));
assert_eq!(index.options.len(), 2);
assert_eq!(index.get_option("m"), Some("16"));
assert_eq!(index.get_option("ef_construction"), Some("200"));
assert_eq!(index.get_option("nonexistent"), None);
}
#[test]
fn test_index_metadata_with_options_bulk() {
let options = vec![
("m".to_string(), "32".to_string()),
("ef_construction".to_string(), "400".to_string()),
];
let index = IndexMetadata::new(1, "idx", "table", vec!["col".into()]).with_options(options);
assert_eq!(index.options.len(), 2);
assert_eq!(index.get_option("m"), Some("32"));
}
#[test]
fn test_covers_column() {
let index = IndexMetadata::new(1, "idx", "table", vec!["a".into(), "b".into()]);
assert!(index.covers_column("a"));
assert!(index.covers_column("b"));
assert!(!index.covers_column("c"));
}
#[test]
fn test_is_single_column() {
let single = IndexMetadata::new(1, "idx", "table", vec!["a".into()]);
let composite = IndexMetadata::new(2, "idx", "table", vec!["a".into(), "b".into()]);
assert!(single.is_single_column());
assert!(!composite.is_single_column());
}
#[test]
fn test_first_column() {
let index = IndexMetadata::new(1, "idx", "table", vec!["first".into(), "second".into()]);
let empty = IndexMetadata::new(2, "idx", "table", vec![]);
assert_eq!(index.first_column(), Some("first"));
assert_eq!(empty.first_column(), None);
}
}