use crate::graph::GraphStorage;
use crate::traversal::optimizer::plan::Plan;
use crate::traversal::optimizer::rule::OptimizationRule;
use crate::traversal::step::GraphStep;
use std::collections::HashSet;
#[derive(Default)]
pub(crate) struct HasIdPushdownRule;
impl HasIdPushdownRule {
pub fn new() -> Self {
Self
}
}
impl<'g, S: GraphStorage + 'g> OptimizationRule<'g, S> for HasIdPushdownRule {
fn name(&self) -> &'static str {
"has_id_pushdown"
}
fn apply(&self, steps: &mut Plan<'g, S>) -> bool {
if steps.is_empty() {
return false;
}
let is_init = matches!(&steps[0], GraphStep::Init(name, _) if *name == "v" || *name == "e");
if !is_init {
return false;
}
let mut modified = false;
let mut idx = 1;
while idx < steps.len() {
match &steps[idx] {
GraphStep::Filter("has_id", filter_step) => {
let filter_ids = filter_step.ids().map(|ids| ids.to_vec());
if let Some(new_ids) = filter_ids {
if let GraphStep::Init(_, init_step) = steps.step_mut(0) {
match init_step.ids() {
None => {
init_step.set_ids(super::distinct(new_ids));
}
Some(existing) => {
let filter_set: HashSet<_> = new_ids.iter().collect();
let intersected: Vec<String> = existing
.iter()
.filter(|id| filter_set.contains(id))
.cloned()
.collect();
init_step.set_ids(intersected);
}
}
}
steps.absorb(idx, 0);
modified = true;
continue;
} else {
break;
}
}
GraphStep::Filter("has_label", _) => {
idx += 1;
}
_ => {
break;
}
}
}
modified
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::GraphSource;
#[test]
fn test_has_id_pushdown_v() {
let graph = GraphSource::tinkerpop_modern();
let g_lock = graph.read();
let g = g_lock.traversal();
let mut traversal = g.v(None).has_id("1");
let rule = HasIdPushdownRule::new();
let modified = rule.apply_to_steps(&mut traversal.steps);
assert!(modified);
assert_eq!(traversal.steps.len(), 1);
if let GraphStep::Init("v", step) = &traversal.steps[0] {
assert_eq!(step.ids(), Some(&["1".to_string()][..]));
} else {
panic!("Expected Init('v') step");
}
}
#[test]
fn test_has_id_pushdown_with_existing_ids() {
let graph = GraphSource::tinkerpop_modern();
let g_lock = graph.read();
let g = g_lock.traversal();
let mut traversal = g.v(vec!["1", "2"]).has_id("1");
let rule = HasIdPushdownRule::new();
let modified = rule.apply_to_steps(&mut traversal.steps);
assert!(modified);
assert_eq!(traversal.steps.len(), 1);
if let GraphStep::Init("v", step) = &traversal.steps[0] {
assert_eq!(step.ids(), Some(&["1".to_string()][..]));
} else {
panic!("Expected Init('v') step");
}
}
#[test]
fn test_has_id_pushdown_past_has_label() {
let graph = GraphSource::tinkerpop_modern();
let g_lock = graph.read();
let g = g_lock.traversal();
let mut traversal = g.v(None).has_label("person").has_id("1");
let rule = HasIdPushdownRule::new();
let modified = rule.apply_to_steps(&mut traversal.steps);
assert!(modified);
assert_eq!(traversal.steps.len(), 2);
if let GraphStep::Init("v", step) = &traversal.steps[0] {
assert_eq!(step.ids(), Some(&["1".to_string()][..]));
} else {
panic!("Expected Init('v') step");
}
assert_eq!(traversal.steps[1].get_name(), "has_label(\"person\")");
}
}