use crate::index::error::IndexError;
use crate::value::Value;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum ElementType {
Vertex,
Edge,
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
pub enum IndexType {
#[default]
BTree,
Unique,
RTree,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct IndexSpec {
pub name: String,
pub element_type: ElementType,
pub label: Option<String>,
pub property: String,
pub index_type: IndexType,
}
impl IndexSpec {
fn auto_name(
element_type: ElementType,
label: Option<&str>,
property: &str,
index_type: IndexType,
) -> String {
let prefix = match index_type {
IndexType::BTree => "idx",
IndexType::Unique => "uniq",
IndexType::RTree => "rtree",
};
let elem = match element_type {
ElementType::Vertex => "v",
ElementType::Edge => "e",
};
match label {
Some(l) => format!("{}_{}_{}{}", prefix, l, property, elem),
None => format!("{}_{}{}", prefix, property, elem),
}
}
}
#[derive(Clone, Debug)]
pub struct IndexBuilder {
element_type: ElementType,
label: Option<String>,
property: Option<String>,
index_type: IndexType,
name: Option<String>,
}
impl IndexBuilder {
pub fn vertex() -> Self {
Self {
element_type: ElementType::Vertex,
label: None,
property: None,
index_type: IndexType::BTree,
name: None,
}
}
pub fn edge() -> Self {
Self {
element_type: ElementType::Edge,
label: None,
property: None,
index_type: IndexType::BTree,
name: None,
}
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn property(mut self, property: impl Into<String>) -> Self {
self.property = Some(property.into());
self
}
pub fn unique(mut self) -> Self {
self.index_type = IndexType::Unique;
self
}
pub fn rtree(mut self) -> Self {
self.index_type = IndexType::RTree;
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn build(self) -> Result<IndexSpec, IndexError> {
let property = self.property.ok_or(IndexError::MissingProperty)?;
let name = self.name.unwrap_or_else(|| {
IndexSpec::auto_name(
self.element_type,
self.label.as_deref(),
&property,
self.index_type,
)
});
Ok(IndexSpec {
name,
element_type: self.element_type,
label: self.label,
property,
index_type: self.index_type,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum IndexPredicate {
Eq(Value),
Neq(Value),
Lt(Value),
Lte(Value),
Gt(Value),
Gte(Value),
Between {
start: Value,
end: Value,
start_inclusive: bool,
end_inclusive: bool,
},
Within(Vec<Value>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_builder_vertex_basic() {
let spec = IndexBuilder::vertex()
.label("person")
.property("age")
.build()
.unwrap();
assert_eq!(spec.element_type, ElementType::Vertex);
assert_eq!(spec.label, Some("person".to_string()));
assert_eq!(spec.property, "age");
assert_eq!(spec.index_type, IndexType::BTree);
assert!(spec.name.starts_with("idx_"));
}
#[test]
fn index_builder_edge_basic() {
let spec = IndexBuilder::edge()
.label("knows")
.property("since")
.build()
.unwrap();
assert_eq!(spec.element_type, ElementType::Edge);
assert_eq!(spec.label, Some("knows".to_string()));
assert_eq!(spec.property, "since");
assert_eq!(spec.index_type, IndexType::BTree);
}
#[test]
fn index_builder_unique() {
let spec = IndexBuilder::vertex()
.label("user")
.property("email")
.unique()
.build()
.unwrap();
assert_eq!(spec.index_type, IndexType::Unique);
assert!(spec.name.starts_with("uniq_"));
}
#[test]
fn index_builder_custom_name() {
let spec = IndexBuilder::vertex()
.label("person")
.property("age")
.name("my_custom_index")
.build()
.unwrap();
assert_eq!(spec.name, "my_custom_index");
}
#[test]
fn index_builder_no_label() {
let spec = IndexBuilder::vertex()
.property("created_at")
.build()
.unwrap();
assert_eq!(spec.label, None);
assert_eq!(spec.property, "created_at");
}
#[test]
fn index_builder_missing_property() {
let result = IndexBuilder::vertex().label("person").build();
assert!(matches!(result, Err(IndexError::MissingProperty)));
}
#[test]
fn index_spec_auto_name_btree_with_label() {
let name =
IndexSpec::auto_name(ElementType::Vertex, Some("person"), "age", IndexType::BTree);
assert_eq!(name, "idx_person_agev");
}
#[test]
fn index_spec_auto_name_unique_with_label() {
let name = IndexSpec::auto_name(
ElementType::Vertex,
Some("user"),
"email",
IndexType::Unique,
);
assert_eq!(name, "uniq_user_emailv");
}
#[test]
fn index_spec_auto_name_without_label() {
let name = IndexSpec::auto_name(ElementType::Edge, None, "weight", IndexType::BTree);
assert_eq!(name, "idx_weighte");
}
#[test]
fn element_type_equality() {
assert_eq!(ElementType::Vertex, ElementType::Vertex);
assert_eq!(ElementType::Edge, ElementType::Edge);
assert_ne!(ElementType::Vertex, ElementType::Edge);
}
#[test]
fn index_type_equality() {
assert_eq!(IndexType::BTree, IndexType::BTree);
assert_eq!(IndexType::Unique, IndexType::Unique);
assert_ne!(IndexType::BTree, IndexType::Unique);
}
#[test]
fn index_type_default_is_btree() {
assert_eq!(IndexType::default(), IndexType::BTree);
}
#[test]
fn index_predicate_eq() {
let pred = IndexPredicate::Eq(Value::Int(42));
assert!(matches!(pred, IndexPredicate::Eq(Value::Int(42))));
}
#[test]
fn index_predicate_between() {
let pred = IndexPredicate::Between {
start: Value::Int(10),
end: Value::Int(20),
start_inclusive: true,
end_inclusive: false,
};
match pred {
IndexPredicate::Between {
start,
end,
start_inclusive,
end_inclusive,
} => {
assert_eq!(start, Value::Int(10));
assert_eq!(end, Value::Int(20));
assert!(start_inclusive);
assert!(!end_inclusive);
}
_ => panic!("Expected Between"),
}
}
#[test]
fn index_predicate_within() {
let pred = IndexPredicate::Within(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
match pred {
IndexPredicate::Within(values) => {
assert_eq!(values.len(), 3);
}
_ => panic!("Expected Within"),
}
}
#[test]
fn index_spec_clone() {
let spec = IndexBuilder::vertex()
.label("person")
.property("age")
.build()
.unwrap();
let cloned = spec.clone();
assert_eq!(spec, cloned);
}
#[test]
fn index_builder_clone() {
let builder = IndexBuilder::vertex().label("person").property("age");
let cloned = builder.clone();
let spec1 = builder.build().unwrap();
let spec2 = cloned.build().unwrap();
assert_eq!(spec1, spec2);
}
}