use crate::executor::eval::eval;
use crate::executor::{ExecutionError, Params, Record, ScalarFnLookup, Value};
use crate::parser::ast::Expression;
use cypherlite_core::{LabelRegistry, PropertyValue};
use cypherlite_storage::StorageEngine;
pub fn execute_index_scan(
variable: &str,
label_id: u32,
prop_key: &str,
lookup_value: &Expression,
engine: &StorageEngine,
params: &Params,
scalar_fns: &dyn ScalarFnLookup,
) -> Result<Vec<Record>, ExecutionError> {
let empty_record = Record::new();
let val = eval(lookup_value, &empty_record, engine, params, scalar_fns)?;
let pv = PropertyValue::try_from(val).map_err(|e| ExecutionError {
message: format!("invalid index lookup value: {}", e),
})?;
let prop_key_id = match engine.prop_key_id(prop_key) {
Some(id) => id,
None => {
return Ok(vec![]);
}
};
let node_ids = engine.scan_nodes_by_property(label_id, prop_key_id, &pv);
let records = node_ids
.into_iter()
.map(|nid| {
let mut record = Record::new();
record.insert(variable.to_string(), Value::Node(nid));
record
})
.collect();
Ok(records)
}
#[cfg(test)]
mod tests {
use super::*;
use cypherlite_core::{DatabaseConfig, SyncMode};
use cypherlite_storage::StorageEngine;
use tempfile::tempdir;
use crate::parser::ast::*;
fn test_engine(dir: &std::path::Path) -> StorageEngine {
let config = DatabaseConfig {
path: dir.join("test.cyl"),
wal_sync_mode: SyncMode::Normal,
..Default::default()
};
StorageEngine::open(config).expect("open")
}
#[test]
fn test_index_scan_with_index() {
let dir = tempdir().expect("tempdir");
let mut engine = test_engine(dir.path());
let person_label = engine.get_or_create_label("Person");
let name_key = engine.get_or_create_prop_key("name");
let _n1 = engine.create_node(
vec![person_label],
vec![(name_key, PropertyValue::String("Alice".into()))],
);
let _n2 = engine.create_node(
vec![person_label],
vec![(name_key, PropertyValue::String("Bob".into()))],
);
engine
.index_manager_mut()
.create_index("idx_person_name".into(), person_label, name_key)
.expect("create index");
let nodes: Vec<(cypherlite_core::NodeId, Vec<(u32, PropertyValue)>)> = engine
.scan_nodes_by_label(person_label)
.iter()
.map(|n| (n.node_id, n.properties.clone()))
.collect();
for (nid, props) in &nodes {
for (pk, v) in props {
if *pk == name_key {
if let Some(idx) = engine
.index_manager_mut()
.find_index_mut(person_label, name_key)
{
idx.insert(v, *nid);
}
}
}
}
let params = Params::new();
let lookup = Expression::Literal(Literal::String("Alice".into()));
let records = execute_index_scan("n", person_label, "name", &lookup, &engine, ¶ms, &())
.expect("should succeed");
assert_eq!(records.len(), 1);
assert!(matches!(records[0].get("n"), Some(Value::Node(_))));
}
#[test]
fn test_index_scan_without_index_falls_back() {
let dir = tempdir().expect("tempdir");
let mut engine = test_engine(dir.path());
let person_label = engine.get_or_create_label("Person");
let name_key = engine.get_or_create_prop_key("name");
engine.create_node(
vec![person_label],
vec![(name_key, PropertyValue::String("Alice".into()))],
);
engine.create_node(
vec![person_label],
vec![(name_key, PropertyValue::String("Bob".into()))],
);
let params = Params::new();
let lookup = Expression::Literal(Literal::String("Alice".into()));
let records = execute_index_scan("n", person_label, "name", &lookup, &engine, ¶ms, &())
.expect("should succeed");
assert_eq!(records.len(), 1);
}
#[test]
fn test_index_scan_unknown_property_key() {
let dir = tempdir().expect("tempdir");
let mut engine = test_engine(dir.path());
let person_label = engine.get_or_create_label("Person");
let params = Params::new();
let lookup = Expression::Literal(Literal::String("Alice".into()));
let records = execute_index_scan(
"n",
person_label,
"nonexistent",
&lookup,
&engine,
¶ms,
&(),
)
.expect("should succeed");
assert!(records.is_empty());
}
}