use super::ast::*;
use super::result::*;
use crate::datatypes::values::Value;
use crate::graph::core::pattern_matching::{
EdgeDirection, Pattern, PatternElement, PatternExecutor, PropertyMatcher,
};
use crate::graph::schema::{DirGraph, InternedKey};
use crate::graph::storage::GraphRead;
use rayon::prelude::*;
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, OnceLock, RwLock};
use std::time::Instant;
pub(super) const RAYON_THRESHOLD: usize = 256;
struct VectorScoreFilterSpec {
variable: String,
prop_name: String,
query_vec: Vec<f32>,
similarity_fn: fn(&[f32], &[f32]) -> f32,
threshold: f64,
greater_than: bool,
inclusive: bool,
}
struct DistanceFilterSpec {
variable: String,
lat_prop: String,
lon_prop: String,
center_lat: f64,
center_lon: f64,
threshold: f64,
less_than: bool,
inclusive: bool,
}
struct ContainsFilterSpec {
container_variable: String,
contained: ContainsTarget,
negated: bool,
}
enum ContainsTarget {
ConstantPoint(f64, f64),
Variable { name: String },
}
enum ResolvedSpatial {
Point(f64, f64),
Geometry(Arc<geo::Geometry<f64>>, Option<geo::Rect<f64>>),
}
type GeomWithBBox = (Arc<geo::Geometry<f64>>, Option<geo::Rect<f64>>);
struct NodeSpatialData {
geometry: Option<GeomWithBBox>,
location: Option<(f64, f64)>,
shapes: HashMap<String, GeomWithBBox>,
points: HashMap<String, (f64, f64)>,
}
struct ScoredRowRef {
score: f64,
index: usize,
}
impl PartialEq for ScoredRowRef {
fn eq(&self, other: &Self) -> bool {
self.index == other.index
}
}
impl Eq for ScoredRowRef {}
impl PartialOrd for ScoredRowRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ScoredRowRef {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.score
.partial_cmp(&self.score)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| other.index.cmp(&self.index))
}
}
struct VectorScoreCache {
prop_name: String,
query_vec: Vec<f32>,
similarity_fn: fn(&[f32], &[f32]) -> f32,
}
pub fn clause_display_name(clause: &Clause) -> String {
match clause {
Clause::Match(m) => {
let types: Vec<&str> = m
.patterns
.iter()
.flat_map(|p| p.elements.iter())
.filter_map(|e| {
if let PatternElement::Node(n) = e {
n.node_type.as_deref()
} else {
None
}
})
.collect();
if types.is_empty() {
"Match".into()
} else {
format!("Match :{}", types.join(", :"))
}
}
Clause::OptionalMatch(m) => {
let types: Vec<&str> = m
.patterns
.iter()
.flat_map(|p| p.elements.iter())
.filter_map(|e| {
if let PatternElement::Node(n) = e {
n.node_type.as_deref()
} else {
None
}
})
.collect();
if types.is_empty() {
"OptionalMatch".into()
} else {
format!("OptionalMatch :{}", types.join(", :"))
}
}
Clause::Where(_) => "Where".into(),
Clause::Return(_) => "Return".into(),
Clause::With(_) => "With".into(),
Clause::OrderBy(_) => "OrderBy".into(),
Clause::Skip(_) => "Skip".into(),
Clause::Limit(_) => "Limit".into(),
Clause::Unwind(_) => "Unwind".into(),
Clause::Union(_) => "Union".into(),
Clause::Create(_) => "Create".into(),
Clause::Set(_) => "Set".into(),
Clause::Delete(_) => "Delete".into(),
Clause::Remove(_) => "Remove".into(),
Clause::Merge(_) => "Merge".into(),
Clause::Call(_) => "Call".into(),
Clause::FusedOptionalMatchAggregate { .. } => "FusedOptionalMatchAggregate".into(),
Clause::FusedVectorScoreTopK { .. } => "FusedVectorScoreTopK".into(),
Clause::FusedMatchReturnAggregate { .. } => "FusedMatchReturnAggregate".into(),
Clause::FusedMatchWithAggregate { .. } => "FusedMatchWithAggregate".into(),
Clause::FusedOrderByTopK { .. } => "FusedOrderByTopK".into(),
Clause::FusedCountAll { .. } => "FusedCountAll".into(),
Clause::FusedCountByType { .. } => "FusedCountByType".into(),
Clause::FusedCountEdgesByType { .. } => "FusedCountEdgesByType".into(),
Clause::FusedCountTypedNode { node_type, .. } => {
format!("FusedCountTypedNode :{node_type}")
}
Clause::FusedCountTypedEdge { edge_type, .. } => {
format!("FusedCountTypedEdge :{edge_type}")
}
Clause::FusedCountAnchoredEdges {
anchor_idx,
anchor_direction,
edge_type,
..
} => {
let arrow = match anchor_direction {
petgraph::Direction::Outgoing => "→",
petgraph::Direction::Incoming => "←",
};
let t = edge_type.as_deref().unwrap_or("*");
format!("FusedCountAnchoredEdges (anchor#{anchor_idx} {arrow} :{t})")
}
Clause::FusedNodeScanAggregate { .. } => "FusedNodeScanAggregate".into(),
Clause::FusedNodeScanTopK { limit, .. } => format!("FusedNodeScanTopK (k={limit})"),
Clause::SpatialJoin {
container_type,
probe_type,
..
} => format!("SpatialJoin :{container_type} ⊇ :{probe_type}"),
}
}
pub struct CypherExecutor<'a> {
pub(super) graph: &'a DirGraph,
pub(super) params: &'a HashMap<String, Value>,
vs_cache: OnceLock<VectorScoreCache>,
pub(super) deadline: Option<Instant>,
max_rows: Option<usize>,
spatial_node_cache: RwLock<HashMap<usize, Option<NodeSpatialData>>>,
regex_cache: RwLock<HashMap<String, regex::Regex>>,
streaming: bool,
}
impl<'a> CypherExecutor<'a> {
pub fn with_params(
graph: &'a DirGraph,
params: &'a HashMap<String, Value>,
deadline: Option<Instant>,
) -> Self {
CypherExecutor {
graph,
params,
vs_cache: OnceLock::new(),
deadline,
max_rows: None,
spatial_node_cache: RwLock::new(HashMap::new()),
regex_cache: RwLock::new(HashMap::new()),
streaming: true,
}
}
pub fn with_max_rows(mut self, max_rows: Option<usize>) -> Self {
self.max_rows = max_rows;
self
}
pub fn with_streaming(mut self, streaming: bool) -> Self {
self.streaming = streaming;
self
}
#[inline]
pub(super) fn check_deadline(&self) -> Result<(), String> {
if let Some(dl) = self.deadline {
if Instant::now() > dl {
return Err(
"Query timed out. Hints: anchor the query with MATCH (n {id: ...}) \
or a pattern property matching an indexed column (e.g. \
MATCH (n {label: 'X'})). To allow a longer run, pass \
timeout_ms=N to cypher() or set kg.set_default_timeout(ms); \
timeout_ms=0 disables the deadline."
.to_string(),
);
}
}
Ok(())
}
pub fn execute(&self, query: &CypherQuery) -> Result<CypherResult, String> {
GraphRead::reset_arenas(&self.graph.graph);
let mut result_set = ResultSet::new();
let profiling = query.profile;
let mut profile_stats: Vec<ClauseStats> = Vec::new();
let mut skip_clause = vec![false; query.clauses.len()];
for (i, clause) in query.clauses.iter().enumerate() {
if skip_clause[i] {
continue;
}
self.check_deadline()?;
if i == 0
&& result_set.rows.is_empty()
&& matches!(
clause,
Clause::With(_) | Clause::Unwind(_) | Clause::Return(_)
)
{
result_set.rows.push(ResultRow::new());
}
if i > 0
&& result_set.rows.is_empty()
&& matches!(clause, Clause::Match(_) | Clause::OptionalMatch(_))
{
if profiling {
profile_stats.push(ClauseStats {
clause_name: clause_display_name(clause),
rows_in: 0,
rows_out: 0,
elapsed_us: 0,
});
}
continue;
}
let inline_where = if let Clause::Match(mc) = clause {
if result_set.rows.is_empty() && mc.patterns.len() == 1 {
if let Some(Clause::Where(w)) = query.clauses.get(i + 1) {
skip_clause[i + 1] = true;
Some(&w.predicate)
} else {
None
}
} else {
None
}
} else {
None
};
if self.streaming
&& !profiling
&& inline_where.is_none()
&& !matches!(clause, Clause::Match(_) | Clause::OptionalMatch(_))
{
match stream::pipeline::try_run_streaming(self, &query.clauses[i..], result_set)? {
stream::pipeline::StreamingOutcome::Absorbed(run) => {
for off in 1..run.absorbed {
if i + off < skip_clause.len() {
skip_clause[i + off] = true;
}
}
result_set = run.result;
continue;
}
stream::pipeline::StreamingOutcome::Bailed(rs) => {
result_set = rs;
}
}
}
if profiling {
let rows_in = result_set.rows.len();
let start = std::time::Instant::now();
result_set = if let Clause::Match(m) = clause {
self.execute_match(m, result_set, inline_where)?
} else {
self.execute_single_clause(clause, result_set)?
};
let elapsed = start.elapsed();
let name = if inline_where.is_some() {
format!("{} + Where (fused)", clause_display_name(clause))
} else {
clause_display_name(clause)
};
profile_stats.push(ClauseStats {
clause_name: name,
rows_in,
rows_out: result_set.rows.len(),
elapsed_us: elapsed.as_micros() as u64,
});
} else {
result_set = if let Clause::Match(m) = clause {
self.execute_match(m, result_set, inline_where)?
} else {
self.execute_single_clause(clause, result_set)?
};
}
}
let mut result = self.finalize_result(result_set)?;
result.stats = None;
if profiling {
result.profile = Some(profile_stats);
}
Ok(result)
}
pub fn execute_single_clause(
&self,
clause: &Clause,
result_set: ResultSet,
) -> Result<ResultSet, String> {
match clause {
Clause::Match(m) => self.execute_match(m, result_set, None),
Clause::OptionalMatch(m) => self.execute_optional_match(m, result_set),
Clause::Where(w) => self.execute_where(w, result_set),
Clause::Return(r) => self.execute_return(r, result_set),
Clause::With(w) => self.execute_with(w, result_set),
Clause::OrderBy(o) => self.execute_order_by(o, result_set),
Clause::Limit(l) => self.execute_limit(l, result_set),
Clause::Skip(s) => self.execute_skip(s, result_set),
Clause::Unwind(u) => self.execute_unwind(u, result_set),
Clause::Union(u) => self.execute_union(u, result_set),
Clause::FusedOptionalMatchAggregate {
match_clause,
with_clause,
} => self.execute_fused_optional_match_aggregate(match_clause, with_clause, result_set),
Clause::FusedVectorScoreTopK {
return_clause,
score_item_index,
descending,
limit,
} => self.execute_fused_vector_score_top_k(
return_clause,
*score_item_index,
*descending,
*limit,
result_set,
),
Clause::FusedOrderByTopK {
return_clause,
score_item_index,
descending,
limit,
sort_expression,
} => self.execute_fused_order_by_top_k(
return_clause,
*score_item_index,
*descending,
*limit,
sort_expression.as_ref(),
result_set,
),
Clause::FusedMatchReturnAggregate {
match_clause,
return_clause,
top_k,
candidate_emit,
distinct_count,
} => self.execute_fused_match_return_aggregate(
match_clause,
return_clause,
top_k,
candidate_emit,
*distinct_count,
result_set,
),
Clause::FusedMatchWithAggregate {
match_clause,
with_clause,
secondary_match,
top_k,
distinct_count,
} => self.execute_fused_match_with_aggregate(
match_clause,
with_clause,
secondary_match.as_ref(),
top_k.as_ref(),
*distinct_count,
result_set,
),
Clause::FusedCountAll { alias } => {
let count = self.graph.graph.node_count() as i64;
let mut projected = Bindings::with_capacity(1);
projected.insert(alias.clone(), Value::Int64(count));
Ok(ResultSet {
rows: vec![ResultRow::from_projected(projected)],
columns: vec![alias.clone()],
lazy_return_items: None,
})
}
Clause::FusedCountByType {
type_alias,
count_alias,
} => {
let mut result_rows = Vec::with_capacity(self.graph.type_indices.len());
for (node_type, indices) in self.graph.type_indices.iter() {
let mut projected = Bindings::with_capacity(2);
projected.insert(
type_alias.clone(),
Value::List(vec![Value::String(node_type.to_string())]),
);
projected.insert(count_alias.clone(), Value::Int64(indices.len() as i64));
result_rows.push(ResultRow::from_projected(projected));
}
Ok(ResultSet {
rows: result_rows,
columns: vec![type_alias.clone(), count_alias.clone()],
lazy_return_items: None,
})
}
Clause::FusedCountEdgesByType {
type_alias,
count_alias,
} => {
let counts = self.graph.get_edge_type_counts();
let mut result_rows = Vec::with_capacity(counts.len());
for (edge_type, count) in &counts {
let mut projected = Bindings::with_capacity(2);
projected.insert(type_alias.clone(), Value::String(edge_type.clone()));
projected.insert(count_alias.clone(), Value::Int64(*count as i64));
result_rows.push(ResultRow::from_projected(projected));
}
Ok(ResultSet {
rows: result_rows,
columns: vec![type_alias.clone(), count_alias.clone()],
lazy_return_items: None,
})
}
Clause::FusedCountTypedNode { node_type, alias } => {
let count = self
.graph
.type_indices
.get(node_type.as_str())
.map(|v| v.len() as i64)
.unwrap_or(0);
let mut projected = Bindings::with_capacity(1);
projected.insert(alias.clone(), Value::Int64(count));
Ok(ResultSet {
rows: vec![ResultRow::from_projected(projected)],
columns: vec![alias.clone()],
lazy_return_items: None,
})
}
Clause::FusedCountTypedEdge { edge_type, alias } => {
let counts = self.graph.get_edge_type_counts();
let count = counts.get(edge_type).copied().unwrap_or(0) as i64;
let mut projected = Bindings::with_capacity(1);
projected.insert(alias.clone(), Value::Int64(count));
Ok(ResultSet {
rows: vec![ResultRow::from_projected(projected)],
columns: vec![alias.clone()],
lazy_return_items: None,
})
}
Clause::FusedCountAnchoredEdges {
anchor_idx,
anchor_direction,
edge_type,
alias,
} => {
let idx = petgraph::graph::NodeIndex::new(*anchor_idx as usize);
let conn = edge_type.as_deref().map(InternedKey::from_str);
let count = self.graph.graph.count_edges_filtered(
idx,
*anchor_direction,
conn,
None,
self.deadline,
)? as i64;
let mut projected = Bindings::with_capacity(1);
projected.insert(alias.clone(), Value::Int64(count));
Ok(ResultSet {
rows: vec![ResultRow::from_projected(projected)],
columns: vec![alias.clone()],
lazy_return_items: None,
})
}
Clause::FusedNodeScanAggregate {
match_clause,
where_predicate,
return_clause,
} => self.execute_fused_node_scan_aggregate(
match_clause,
where_predicate.as_ref(),
return_clause,
),
Clause::FusedNodeScanTopK {
match_clause,
where_predicate,
return_clause,
sort_expression,
descending,
limit,
} => self.execute_fused_node_scan_top_k(
match_clause,
where_predicate.as_ref(),
return_clause,
sort_expression,
*descending,
*limit,
),
Clause::SpatialJoin {
container_var,
probe_var,
container_type,
probe_type,
probe_kind,
remainder,
} => self.execute_spatial_join(
container_var,
probe_var,
container_type,
probe_type,
*probe_kind,
remainder.as_ref(),
),
Clause::Call(c) => self.execute_call(c, result_set),
Clause::Create(_)
| Clause::Set(_)
| Clause::Delete(_)
| Clause::Remove(_)
| Clause::Merge(_) => {
Err("Mutation clauses cannot be executed in read-only mode".to_string())
}
}
}
pub(super) fn resolve_pattern_vars(&self, pattern: &Pattern, row: &ResultRow) -> Pattern {
let mut resolved = pattern.clone();
for element in &mut resolved.elements {
let props = match element {
PatternElement::Node(np) => &mut np.properties,
PatternElement::Edge(ep) => &mut ep.properties,
};
if let Some(props) = props {
for matcher in props.values_mut() {
match matcher {
PropertyMatcher::EqualsVar(name) => {
if let Some(val) = row.projected.get(name) {
if matches!(val, Value::Null) {
*matcher = PropertyMatcher::In(Vec::new());
} else {
*matcher = PropertyMatcher::Equals(val.clone());
}
} else {
*matcher = PropertyMatcher::In(Vec::new());
}
}
PropertyMatcher::EqualsNodeProp { var, prop } => {
let val = row
.node_bindings
.get(var)
.and_then(|idx| self.graph.graph.node_weight(*idx))
.map(|node| helpers::resolve_node_property(node, prop, self.graph));
match val {
Some(v) if !matches!(v, Value::Null) => {
*matcher = PropertyMatcher::Equals(v);
}
_ => {
*matcher = PropertyMatcher::In(Vec::new());
}
}
}
_ => {}
}
}
}
}
resolved
}
pub(super) fn pattern_has_vars(pattern: &Pattern) -> bool {
for element in &pattern.elements {
let props = match element {
PatternElement::Node(np) => &np.properties,
PatternElement::Edge(ep) => &ep.properties,
};
if let Some(props) = props {
for matcher in props.values() {
if matches!(
matcher,
PropertyMatcher::EqualsVar(_) | PropertyMatcher::EqualsNodeProp { .. }
) {
return true;
}
}
}
}
false
}
pub(super) fn execute_match(
&self,
clause: &MatchClause,
existing: ResultSet,
inline_where: Option<&Predicate>,
) -> Result<ResultSet, String> {
if let Some(pa) = clause.path_assignments.first() {
if pa.is_shortest_path {
return self.execute_shortest_path_match(clause, pa, existing);
}
}
let limit_hint = clause.limit_hint;
let pattern_limit = if inline_where.is_some() {
None
} else {
limit_hint
};
let mut result_rows = if existing.rows.is_empty() {
let mut all_rows = Vec::new();
for pattern in &clause.patterns {
if all_rows.is_empty() {
let executor = PatternExecutor::new_lightweight_with_params(
self.graph,
pattern_limit,
self.params,
)
.set_deadline(self.deadline)
.set_distinct_target(clause.distinct_node_hint.clone());
let matches = executor.execute(pattern)?;
if let Some(ref dedup_var) = clause.distinct_node_hint {
let mut seen = HashSet::with_capacity(matches.len().min(10000));
for m in matches {
let dominated = m
.bindings
.iter()
.find(|(name, _)| name == dedup_var)
.is_some_and(|(_, b)| match b {
crate::graph::core::pattern_matching::MatchBinding::Node {
index,
..
} => !seen.insert(*index),
crate::graph::core::pattern_matching::MatchBinding::NodeRef(
index,
) => !seen.insert(*index),
_ => false,
});
if !dominated {
all_rows.push(self.pattern_match_to_row(m));
}
}
} else {
for m in matches {
let row = self.pattern_match_to_row(m);
if let Some(pred) = inline_where {
match self.evaluate_predicate(pred, &row) {
Ok(true) => {} Ok(false) => continue, Err(e) => return Err(e), }
}
all_rows.push(row);
if let Some(limit) = limit_hint {
if all_rows.len() >= limit {
break;
}
}
}
}
if inline_where.is_none() {
if let Some(limit) = limit_hint {
all_rows.truncate(limit);
}
}
} else {
let has_vars = Self::pattern_has_vars(pattern);
let old_rows = std::mem::take(&mut all_rows);
let mut new_rows = Vec::with_capacity(old_rows.len());
for mut existing_row in old_rows {
let remaining = limit_hint.map(|l| l.saturating_sub(new_rows.len()));
if remaining == Some(0) {
break;
}
let resolved;
let pat = if has_vars {
resolved = self.resolve_pattern_vars(pattern, &existing_row);
&resolved
} else {
pattern
};
let executor = PatternExecutor::with_bindings_and_params(
self.graph,
remaining,
&existing_row.node_bindings,
self.params,
)
.set_deadline(self.deadline);
let matches = executor.execute(pat)?;
let compatible: Vec<_> = matches
.iter()
.filter(|m| self.bindings_compatible(&existing_row, m))
.collect();
let total = compatible.len();
for (i, m) in compatible.into_iter().enumerate() {
if i + 1 == total {
self.merge_match_into_row(&mut existing_row, m);
new_rows.push(existing_row);
break;
}
let mut new_row = existing_row.clone();
self.merge_match_into_row(&mut new_row, m);
new_rows.push(new_row);
if limit_hint.is_some_and(|l| new_rows.len() >= l) {
break;
}
}
if limit_hint.is_some_and(|l| new_rows.len() >= l) {
break;
}
}
all_rows = new_rows;
}
}
all_rows
} else {
let mut new_rows = Vec::with_capacity(existing.rows.len());
let transient_indexes: Vec<Option<transient_index::TransientEqIndex>> = clause
.patterns
.iter()
.map(|p| {
transient_index::TransientEqIndex::try_build(self.graph, p, existing.rows.len())
})
.collect();
for row in &existing.rows {
for (pi, pattern) in clause.patterns.iter().enumerate() {
let remaining = limit_hint.map(|l| l.saturating_sub(new_rows.len()));
if remaining == Some(0) {
break;
}
if let Some(idx) = &transient_indexes[pi] {
if !row.node_bindings.contains_key(idx.bind_var.as_str()) {
if let Some(probe) = idx.probe_value(row, self.graph) {
for &node_idx in idx.lookup(&probe) {
let mut new_row = row.clone();
new_row.node_bindings.insert(idx.bind_var.clone(), node_idx);
new_rows.push(new_row);
if limit_hint.is_some_and(|l| new_rows.len() >= l) {
break;
}
}
}
continue;
}
}
let resolved;
let pat = if Self::pattern_has_vars(pattern) {
resolved = self.resolve_pattern_vars(pattern, row);
&resolved
} else {
pattern
};
let executor = PatternExecutor::with_bindings_and_params(
self.graph,
remaining,
&row.node_bindings,
self.params,
)
.set_deadline(self.deadline);
let matches = executor.execute(pat)?;
for m in &matches {
if !self.bindings_compatible(row, m) {
continue;
}
let mut new_row = row.clone();
self.merge_match_into_row(&mut new_row, m);
new_rows.push(new_row);
if limit_hint.is_some_and(|l| new_rows.len() >= l) {
break;
}
}
}
if limit_hint.is_some_and(|l| new_rows.len() >= l) {
break;
}
}
new_rows
};
for pa in &clause.path_assignments {
if pa.is_shortest_path {
continue;
}
let vlp_edge_var: Option<String> =
clause.patterns.get(pa.pattern_index).and_then(|pat| {
pat.elements.iter().find_map(|elem| {
if let PatternElement::Edge(ep) = elem {
if ep.var_length.is_some() {
return ep.variable.clone();
}
}
None
})
});
for row in &mut result_rows {
let path_binding = if let Some(ref vlp_var) = vlp_edge_var {
row.path_bindings.get(vlp_var).cloned()
} else {
row.path_bindings.iter().next().map(|(_, pb)| pb.clone())
};
if let Some(pb) = path_binding {
row.path_bindings.insert(pa.variable.clone(), pb);
} else {
if let Some(pattern) = clause.patterns.get(pa.pattern_index) {
for elem in &pattern.elements {
if let PatternElement::Edge(ep) = elem {
if let Some(ref var) = ep.variable {
if let Some(eb) = row.edge_bindings.get(var) {
let conn_type = self
.graph
.graph
.edge_weight(eb.edge_index)
.map(|ed| {
ed.connection_type_str(&self.graph.interner)
.to_string()
})
.unwrap_or_default();
row.path_bindings.insert(
pa.variable.clone(),
crate::graph::languages::cypher::result::PathBinding {
source: eb.source,
hops: 1,
path: vec![(eb.target, conn_type)],
},
);
break;
}
} else {
let synth = self.synthesize_path_from_pattern(pattern, row);
if let Some(pb) = synth {
row.path_bindings.insert(pa.variable.clone(), pb);
}
break;
}
}
}
}
}
}
}
if let Some(max) = self.max_rows {
if result_rows.len() > max {
return Err(format!(
"Query produced {} rows, exceeding max_rows limit of {}. \
Add a LIMIT clause or increase max_rows.",
result_rows.len(),
max
));
}
}
Ok(ResultSet {
rows: result_rows,
columns: existing.columns,
lazy_return_items: None,
})
}
}
pub mod affected_tests;
pub mod call_clause;
pub mod expression;
pub mod helpers;
pub mod match_clause;
pub mod refresh_stats;
pub mod regex_cache;
pub mod return_clause;
pub mod rule_procedures;
pub mod scalar_functions;
pub mod shortest_path;
pub mod spatial_join;
pub mod stream;
#[cfg(test)]
pub mod tests;
pub mod transient_index;
pub mod where_clause;
pub mod write;
pub use helpers::return_item_column_name;
pub use write::{execute_mutable, is_mutation_query};