use crate::types::{ErrorCode, GraphError, QueryPhase};
use super::expr::*;
use super::*;
pub(in crate::cypher::parser) fn parse_pattern_list(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<Vec<Pattern>> {
pair.into_inner()
.filter(|p| matches!(p.as_rule(), Rule::pattern_item))
.map(parse_pattern_item)
.collect()
}
pub(in crate::cypher::parser) fn parse_pattern_item(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<Pattern> {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::path_pattern => parse_path_pattern(inner),
Rule::pattern => parse_pattern(inner),
_ => Err(GraphError::syntax(format!(
"unexpected pattern item: {:?}",
inner.as_rule()
))),
}
}
pub(in crate::cypher::parser) fn parse_path_pattern(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<Pattern> {
let mut path_variable = None;
let mut pattern = None;
let mut mode = ShortestPathMode::None;
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::ident => path_variable = Some(strip_backticks(inner.as_str()).to_string()),
Rule::shortest_path_fn => {
mode = ShortestPathMode::Single;
for child in inner.into_inner() {
if child.as_rule() == Rule::pattern {
pattern = Some(parse_pattern(child)?);
}
}
}
Rule::all_shortest_paths_fn => {
mode = ShortestPathMode::All;
for child in inner.into_inner() {
if child.as_rule() == Rule::pattern {
pattern = Some(parse_pattern(child)?);
}
}
}
Rule::pattern => {
pattern = Some(parse_pattern(inner)?);
}
_ => {}
}
}
let mut pat = pattern
.ok_or_else(|| GraphError::syntax("missing pattern in path assignment".to_string()))?;
pat.path_variable = path_variable;
pat.shortest_path_mode = mode;
Ok(pat)
}
pub(in crate::cypher::parser) fn parse_pattern(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<Pattern> {
parse_pattern_inner(pair)
}
pub(in crate::cypher::parser) fn parse_pattern_inner(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<Pattern> {
let mut elements = Vec::new();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::node_pattern => elements.push(PatternElement::Node(parse_node_pattern(inner)?)),
Rule::rel_pattern => {
elements.push(PatternElement::Relationship(parse_rel_pattern(inner)?))
}
_ => {}
}
}
Ok(Pattern {
elements,
path_variable: None,
shortest_path_mode: ShortestPathMode::None,
})
}
pub(in crate::cypher::parser) fn parse_node_pattern(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<NodePattern> {
let mut variable = None;
let mut labels = Vec::new();
let mut properties = HashMap::new();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::ident => variable = Some(strip_backticks(inner.as_str()).to_string()),
Rule::label_spec => {
for child in inner.into_inner() {
if child.as_rule() == Rule::symbolic_name {
labels.push(child.as_str().to_string());
}
}
}
Rule::property_map => properties = parse_property_map(inner)?,
_ => {}
}
}
Ok(NodePattern {
variable,
labels,
properties,
})
}
pub(in crate::cypher::parser) fn parse_rel_pattern(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<RelPattern> {
let inner = pair.into_inner().next().unwrap();
let direction = match inner.as_rule() {
Rule::rel_right | Rule::rel_right_bare => RelDirection::Outgoing,
Rule::rel_left | Rule::rel_left_bare => RelDirection::Incoming,
Rule::rel_undirected | Rule::rel_undirected_bare | Rule::rel_both | Rule::rel_both_bare => {
RelDirection::Undirected
}
_ => unreachable!(),
};
let mut variable = None;
let mut rel_types = Vec::new();
let mut properties = HashMap::new();
let mut var_length = None;
for child in inner.into_inner() {
if child.as_rule() == Rule::rel_detail {
for detail in child.into_inner() {
match detail.as_rule() {
Rule::ident => variable = Some(strip_backticks(detail.as_str()).to_string()),
Rule::rel_type_spec => {
for rt in detail.into_inner() {
if rt.as_rule() == Rule::symbolic_name {
let t = rt.as_str().to_string();
if !rel_types.contains(&t) {
rel_types.push(t);
}
}
}
}
Rule::property_map => {
properties = parse_property_map(detail)?;
}
Rule::var_length => {
let (range, var_props) = parse_var_length(detail)?;
var_length = Some(range);
if !var_props.is_empty() {
properties.extend(var_props);
}
}
_ => {}
}
}
}
}
Ok(RelPattern {
variable,
rel_types,
properties,
direction,
var_length,
})
}
pub(in crate::cypher::parser) fn parse_var_length_bound(s: &str) -> crate::types::Result<u32> {
s.parse::<u32>().map_err(|_| {
GraphError::query(
QueryPhase::Parse,
ErrorCode::NumberOutOfRange,
format!("var-length hop count `{s}` is out of range (must fit in u32)"),
)
})
}
pub(in crate::cypher::parser) fn parse_var_length(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<((u32, u32), HashMap<String, Expr>)> {
let mut range = None;
let mut props = HashMap::new();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::int_range => {
let raw = inner.as_str().trim();
let nums: Vec<u32> = inner
.into_inner()
.filter(|p| p.as_rule() == Rule::integer)
.map(|p| parse_var_length_bound(p.as_str()))
.collect::<crate::types::Result<_>>()?;
range = Some(match nums.len() {
2 => (nums[0], nums[1]),
1 if raw.starts_with("..") => (1, nums[0]),
1 => (nums[0], 50),
_ => (1, 50),
});
}
Rule::fixed_length => {
let n = parse_var_length_bound(inner.as_str())?;
range = Some((n, n));
}
Rule::property_map => {
props = parse_property_map(inner)?;
}
_ => {}
}
}
Ok((range.unwrap_or((1, 50)), props))
}
pub(in crate::cypher::parser) fn parse_property_map(
pair: pest::iterators::Pair<Rule>,
) -> crate::types::Result<HashMap<String, Expr>> {
let mut map = HashMap::new();
for inner in pair.into_inner() {
if inner.as_rule() == Rule::property_pair {
let mut children = inner.into_inner();
let key = strip_backticks(children.next().unwrap().as_str()).to_string();
let value = parse_expr(children.next().unwrap())?;
map.insert(key, value);
}
}
Ok(map)
}