use crate::expr::match_plan::{BindingDef, BindingId, BindingKind};
use crate::gql::ast::{
EdgePattern, Ident, MatchClause, MatchItem, NodePattern, OptionalBlock, PathPattern,
PathSearchKind,
};
use crate::gql::lower::naming;
use crate::syn::error::{SyntaxError, bail, syntax_error};
pub(super) struct BindingInfo {
pub(super) name: String,
pub(super) kind: BindingKind,
pub(super) user_named: bool,
pub(super) optional_depth: u32,
}
pub(super) struct Registry {
bindings: Vec<BindingInfo>,
hidden_edges: u32,
hidden_nodes: u32,
current_depth: u32,
}
impl Registry {
fn new() -> Self {
Registry {
bindings: Vec::new(),
hidden_edges: 0,
hidden_nodes: 0,
current_depth: 0,
}
}
pub(super) fn bindings(&self) -> &[BindingInfo] {
&self.bindings
}
pub(super) fn into_defs(self) -> Vec<BindingDef> {
self.bindings
.into_iter()
.map(|b| BindingDef {
name: b.name,
kind: b.kind,
user_named: b.user_named,
})
.collect()
}
pub(super) fn resolve(&self, ident: &Ident) -> Result<BindingId, SyntaxError> {
self.lookup(&ident.name).ok_or_else(|| {
syntax_error!(
"Unknown variable `{}`",
ident.name,
@ident.span => "variables must be declared in the MATCH pattern"
)
})
}
pub(super) fn kind(&self, id: BindingId) -> BindingKind {
self.bindings[id as usize].kind
}
pub(super) fn name(&self, id: BindingId) -> &str {
&self.bindings[id as usize].name
}
pub(super) fn optional_depth(&self, id: BindingId) -> u32 {
self.bindings[id as usize].optional_depth
}
fn lookup(&self, name: &str) -> Option<BindingId> {
self.bindings.iter().position(|b| b.name == name).map(|i| i as BindingId)
}
fn declare(&mut self, name: String, kind: BindingKind, user_named: bool) -> BindingId {
let id = self.bindings.len() as BindingId;
self.bindings.push(BindingInfo {
name,
kind,
user_named,
optional_depth: self.current_depth,
});
id
}
fn declare_hidden_node(&mut self) -> BindingId {
let name = format!("__v{}", self.hidden_nodes);
self.hidden_nodes += 1;
self.declare(name, BindingKind::Node, false)
}
fn next_hidden_edge_name(&mut self) -> String {
let name = format!("__e{}", self.hidden_edges);
self.hidden_edges += 1;
name
}
pub(super) fn try_lookup(&self, name: &str) -> Option<BindingId> {
self.lookup(name)
}
pub(super) fn resolve_node_ref(&self, ident: &Ident) -> Result<BindingId, SyntaxError> {
let id = self.resolve(ident)?;
match self.kind(id) {
BindingKind::Node => Ok(id),
other => Err(kind_mismatch(ident, other, BindingKind::Node)),
}
}
pub(super) fn declare_new_node(
&mut self,
var: Option<&Ident>,
) -> Result<BindingId, SyntaxError> {
match var {
None => Ok(self.declare_hidden_node()),
Some(ident) => {
naming::validate_var(ident)?;
if self.lookup(&ident.name).is_some() {
return Err(syntax_error!(
"Variable `{}` is already bound and cannot be created by INSERT",
ident.name,
@ident.span => "use a fresh variable for a new node, or drop the label and \
properties to reference the bound one as an edge endpoint"
));
}
Ok(self.declare(ident.name.clone(), BindingKind::Node, true))
}
}
}
pub(super) fn declare_new_edge(
&mut self,
var: Option<&Ident>,
) -> Result<BindingId, SyntaxError> {
match var {
None => {
let name = self.next_hidden_edge_name();
Ok(self.declare(name, BindingKind::Edge, false))
}
Some(ident) => {
naming::validate_var(ident)?;
if self.lookup(&ident.name).is_some() {
return Err(syntax_error!(
"Variable `{}` is already bound and cannot be created by INSERT",
ident.name,
@ident.span => "use a fresh variable for a new edge"
));
}
Ok(self.declare(ident.name.clone(), BindingKind::Edge, true))
}
}
}
}
pub(super) struct PatternBindings {
pub(super) path_var: Option<BindingId>,
pub(super) start: BindingId,
pub(super) steps: Vec<(BindingId, BindingId)>,
pub(super) node_equalities: Vec<(BindingId, BindingId)>,
}
pub(super) struct ClauseBindings<'ast> {
pub(super) clause: &'ast MatchClause,
pub(super) optional_group: Option<u32>,
pub(super) patterns: Vec<PatternBindings>,
}
struct GroupCounter(u32);
impl GroupCounter {
fn next(&mut self) -> u32 {
let id = self.0;
self.0 += 1;
id
}
}
pub(super) struct Analyzer {
registry: Registry,
groups: GroupCounter,
}
impl Analyzer {
pub(super) fn new() -> Self {
Analyzer {
registry: Registry::new(),
groups: GroupCounter(0),
}
}
pub(super) fn read<'ast>(
&mut self,
item: &'ast MatchItem,
) -> Result<Vec<ClauseBindings<'ast>>, SyntaxError> {
let mut out = Vec::new();
analyze_items(
&mut self.registry,
std::slice::from_ref(item),
None,
&mut self.groups,
&mut out,
)?;
Ok(out)
}
pub(super) fn registry(&self) -> &Registry {
&self.registry
}
pub(super) fn registry_mut(&mut self) -> &mut Registry {
&mut self.registry
}
pub(super) fn into_registry(self) -> Registry {
self.registry
}
}
fn analyze_items<'ast>(
registry: &mut Registry,
items: &'ast [MatchItem],
group: Option<u32>,
groups: &mut GroupCounter,
out: &mut Vec<ClauseBindings<'ast>>,
) -> Result<(), SyntaxError> {
for item in items {
match item {
MatchItem::Match(clause) => {
let optional = group.is_some();
let block_leading = optional && out.last().map(|c| c.optional_group) != Some(group);
let patterns = analyze_clause(registry, clause, optional, block_leading)?;
out.push(ClauseBindings {
clause,
optional_group: group,
patterns,
});
}
MatchItem::Optional(block) => analyze_optional(registry, block, groups, out)?,
}
}
Ok(())
}
fn analyze_optional<'ast>(
registry: &mut Registry,
block: &'ast OptionalBlock,
groups: &mut GroupCounter,
out: &mut Vec<ClauseBindings<'ast>>,
) -> Result<(), SyntaxError> {
let group = groups.next();
registry.current_depth += 1;
let result = analyze_items(registry, &block.items, Some(group), groups, out);
registry.current_depth -= 1;
result
}
fn analyze_clause(
registry: &mut Registry,
clause: &MatchClause,
optional: bool,
block_leading: bool,
) -> Result<Vec<PatternBindings>, SyntaxError> {
if clause.patterns.is_empty() {
return Err(syntax_error!(
"Internal error: MATCH clause without a pattern",
@clause.span
));
}
let mut patterns = Vec::with_capacity(clause.patterns.len());
for (index, pattern) in clause.patterns.iter().enumerate() {
let context = if index == 0 && !optional {
AnchorContext::MandatoryLeading
} else if index == 0 && block_leading {
AnchorContext::OptionalBlockLeading
} else {
AnchorContext::Inner
};
patterns.push(analyze_pattern(registry, pattern, context)?);
}
Ok(patterns)
}
fn analyze_pattern(
registry: &mut Registry,
pattern: &PathPattern,
context: AnchorContext,
) -> Result<PatternBindings, SyntaxError> {
if !pattern_is_anchorable(registry, pattern) {
bail!(
"Cannot choose a starting table for this pattern: label at least one node or reuse a \
variable bound by an earlier pattern",
@pattern.start.span => "label a node: `(n:label)`, or reuse an earlier variable"
);
}
if !pattern_is_realisable(registry, pattern, context) {
bail!(
"This MATCH pattern shape is not supported yet",
@pattern.start.span => "anchor the pattern on a labelled start node `(n:label)`, a \
single labelled edge, or a node variable bound by an earlier pattern used as the \
pattern's start"
);
}
validate_path_prefix(pattern)?;
let mut used: Vec<BindingId> = Vec::new();
let mut node_equalities: Vec<(BindingId, BindingId)> = Vec::new();
let start = declare_node(registry, &pattern.start, &mut used, &mut node_equalities)?;
let mut steps = Vec::with_capacity(pattern.steps.len());
for step in &pattern.steps {
let edge = declare_edge(registry, &step.edge)?;
let node = declare_node(registry, &step.node, &mut used, &mut node_equalities)?;
steps.push((edge, node));
}
let path_var = match &pattern.path_var {
None => None,
Some(ident) => {
naming::validate_var(ident)?;
if registry.lookup(&ident.name).is_some() {
return Err(repeated_path_variable(ident));
}
Some(registry.declare(ident.name.clone(), BindingKind::Path, true))
}
};
Ok(PatternBindings {
path_var,
start,
steps,
node_equalities,
})
}
fn validate_path_prefix(pattern: &PathPattern) -> Result<(), SyntaxError> {
let Some(prefix) = pattern.prefix.as_ref() else {
return Ok(());
};
let single_segment =
matches!(pattern.steps.as_slice(), [step] if step.edge.quantifier.is_some());
if !single_segment {
bail!(
"A path search prefix requires a single variable-length segment, e.g. \
`(a)-[:edge]->{{1,3}}(b)`",
@prefix.span => "quantify exactly one edge (`{{m,n}}`, `+`, `*`)"
);
}
if matches!(prefix.kind, PathSearchKind::All) {
return Ok(());
}
let [step] = pattern.steps.as_slice() else {
return Ok(());
};
if same_node_variable(&pattern.start, &step.node) {
bail!(
"A path search does not support a pattern whose start and end are the same node",
@prefix.span => "use distinct start and end nodes"
);
}
Ok(())
}
fn same_node_variable(a: &NodePattern, b: &NodePattern) -> bool {
match (a.var.as_ref(), b.var.as_ref()) {
(Some(a), Some(b)) => a.name == b.name,
_ => false,
}
}
fn pattern_is_anchorable(registry: &Registry, pattern: &PathPattern) -> bool {
if pattern.start.label.is_some() {
return true;
}
if node_reuses_bound_var(registry, &pattern.start) {
return true;
}
for step in &pattern.steps {
if step.edge.label.is_some() || step.node.label.is_some() {
return true;
}
if node_reuses_bound_var(registry, &step.node) {
return true;
}
}
false
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum AnchorContext {
MandatoryLeading,
OptionalBlockLeading,
Inner,
}
fn pattern_is_realisable(
registry: &Registry,
pattern: &PathPattern,
context: AnchorContext,
) -> bool {
if pattern.start.label.is_some() {
return true;
}
if let [step] = pattern.steps.as_slice()
&& step.edge.label.is_some()
&& step.edge.quantifier.is_none()
{
return true;
}
let forward_ok = match context {
AnchorContext::MandatoryLeading => false,
AnchorContext::OptionalBlockLeading => matches!(
pattern.steps.as_slice(),
[step] if step.edge.quantifier.is_none()
),
AnchorContext::Inner => true,
};
let reverse_ok = !matches!(context, AnchorContext::MandatoryLeading);
if forward_ok
&& node_reuses_bound_var(registry, &pattern.start)
&& expansion_targets_are_fresh(registry, pattern, ExpandFrom::Start)
{
return true;
}
if reverse_ok
&& let [step] = pattern.steps.as_slice()
&& (!matches!(context, AnchorContext::OptionalBlockLeading)
|| step.edge.quantifier.is_none())
&& node_reuses_bound_var(registry, &step.node)
&& expansion_targets_are_fresh(registry, pattern, ExpandFrom::Far)
{
return true;
}
false
}
#[derive(Clone, Copy)]
enum ExpandFrom {
Start,
Far,
}
fn expansion_targets_are_fresh(
registry: &Registry,
pattern: &PathPattern,
from: ExpandFrom,
) -> bool {
let mut earlier: Vec<&str> = Vec::new();
if let Some(name) = pattern.start.var.as_ref().map(|i| i.name.as_str()) {
earlier.push(name);
}
match from {
ExpandFrom::Start => {
for step in &pattern.steps {
if overwrites_cross_pattern_binding(registry, &step.node, &earlier) {
return false;
}
if let Some(name) = step.node.var.as_ref().map(|i| i.name.as_str()) {
earlier.push(name);
}
}
true
}
ExpandFrom::Far => !overwrites_cross_pattern_binding(registry, &pattern.start, &[]),
}
}
fn overwrites_cross_pattern_binding(
registry: &Registry,
node: &NodePattern,
earlier: &[&str],
) -> bool {
node.var.as_ref().is_some_and(|ident| {
registry.lookup(&ident.name).is_some() && !earlier.contains(&ident.name.as_str())
})
}
fn node_reuses_bound_var(registry: &Registry, node: &NodePattern) -> bool {
node.var.as_ref().is_some_and(|ident| registry.lookup(&ident.name).is_some())
}
fn declare_node(
registry: &mut Registry,
node: &NodePattern,
used: &mut Vec<BindingId>,
node_equalities: &mut Vec<(BindingId, BindingId)>,
) -> Result<BindingId, SyntaxError> {
let id = match &node.var {
None => registry.declare_hidden_node(),
Some(ident) => {
naming::validate_var(ident)?;
match registry.lookup(&ident.name) {
Some(existing) => {
let existing = reuse_as(registry, ident, existing, BindingKind::Node)?;
if registry.optional_depth(existing) > registry.current_depth {
return Err(optional_rebind(ident));
}
if used.contains(&existing) {
let hidden = registry.declare_hidden_node();
node_equalities.push((existing, hidden));
hidden
} else {
existing
}
}
None => registry.declare(ident.name.clone(), BindingKind::Node, true),
}
}
};
used.push(id);
Ok(id)
}
fn declare_edge(registry: &mut Registry, edge: &EdgePattern) -> Result<BindingId, SyntaxError> {
let kind = if edge.quantifier.is_some() {
BindingKind::EdgeGroup
} else {
BindingKind::Edge
};
match &edge.var {
None => {
let name = registry.next_hidden_edge_name();
Ok(registry.declare(name, kind, false))
}
Some(ident) => {
naming::validate_var(ident)?;
if let Some(id) = registry.lookup(&ident.name) {
let prior = registry.kind(id);
return match prior {
BindingKind::Edge | BindingKind::EdgeGroup => {
Err(repeated_edge_variable(ident))
}
BindingKind::Node | BindingKind::Path => Err(kind_mismatch(ident, prior, kind)),
};
}
Ok(registry.declare(ident.name.clone(), kind, true))
}
}
}
fn reuse_as(
registry: &Registry,
ident: &Ident,
id: BindingId,
expected: BindingKind,
) -> Result<BindingId, SyntaxError> {
let prior = registry.kind(id);
if prior == expected {
Ok(id)
} else {
Err(kind_mismatch(ident, prior, expected))
}
}
fn repeated_edge_variable(ident: &Ident) -> SyntaxError {
syntax_error!(
"Edge variable `{}` cannot be repeated",
ident.name,
@ident.span => "under DIFFERENT EDGES an edge cannot bind twice, so the join is always empty"
)
}
fn repeated_path_variable(ident: &Ident) -> SyntaxError {
syntax_error!(
"Path variable `{}` is declared more than once",
ident.name,
@ident.span => "use a fresh name for each path variable"
)
}
fn optional_rebind(ident: &Ident) -> SyntaxError {
syntax_error!(
"Variable `{}` was first bound inside an OPTIONAL and cannot be re-declared outside it",
ident.name,
@ident.span => "a variable an OPTIONAL introduces may be NULL, so a mandatory pattern cannot \
reuse it; name the mandatory element differently"
)
}
fn kind_mismatch(ident: &Ident, prior: BindingKind, found: BindingKind) -> SyntaxError {
syntax_error!(
"Variable `{}` is already bound as {} but reused as {}",
ident.name,
kind_label(prior),
kind_label(found),
@ident.span => "use a fresh name, or reuse the variable as the same kind of element"
)
}
fn kind_label(kind: BindingKind) -> &'static str {
match kind {
BindingKind::Node => "a node",
BindingKind::Edge => "an edge",
BindingKind::EdgeGroup => "an edge group",
BindingKind::Path => "a path",
}
}