Skip to main content

uqa_graph/operators/
vertex_match.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Label and predicate based single-vertex matching.
8
9use super::{
10    BTreeMap, DocId, GraphPayload, GraphPostingList, GraphStore, GraphStoreError, GraphStoreResult,
11    Payload, PostingEntry, PostingList, VertexId, VertexPredicate, DEFAULT_GRAPH_SCORE,
12};
13
14/// Single-vertex Match: every vertex in `graph` whose label matches and
15/// whose predicates all hold. Useful as a Cypher-style anchor.
16pub struct VertexMatch<'a> {
17    pub graph: &'a str,
18    pub label: Option<&'a str>,
19    pub predicate: Option<VertexPredicate>,
20    pub score: f64,
21}
22
23impl<'a> VertexMatch<'a> {
24    pub fn new(graph: &'a str) -> Self {
25        Self {
26            graph,
27            label: None,
28            predicate: None,
29            score: DEFAULT_GRAPH_SCORE,
30        }
31    }
32
33    pub fn label(mut self, label: &'a str) -> Self {
34        self.label = Some(label);
35        self
36    }
37
38    pub fn predicate(mut self, p: VertexPredicate) -> Self {
39        self.predicate = Some(p);
40        self
41    }
42
43    pub fn execute<G: GraphStore>(&self, store: &G) -> GraphStoreResult<GraphPostingList> {
44        let candidates: Vec<VertexId> = match self.label {
45            Some(l) => store.vertex_ids_by_label(l, self.graph)?,
46            None => store.vertex_ids_in_graph(self.graph)?.into_iter().collect(),
47        };
48        let mut entries: Vec<PostingEntry> = Vec::new();
49        let mut graph_payloads: BTreeMap<DocId, GraphPayload> = BTreeMap::new();
50        for vid in candidates {
51            let vtx = store.get_vertex(vid).ok_or_else(|| {
52                GraphStoreError::CorruptGraph(format!("missing matched vertex {vid}"))
53            })?;
54            if let Some(pred) = &self.predicate {
55                if !pred.matches(vtx) {
56                    continue;
57                }
58            }
59            entries.push(PostingEntry::new(vid, Payload::with_score(self.score)));
60            graph_payloads.insert(
61                vid,
62                GraphPayload {
63                    subgraph_vertices: vec![vid],
64                    subgraph_edges: Vec::new(),
65                    graph_name: self.graph.to_string(),
66                    score_override: Some(self.score),
67                },
68            );
69        }
70        entries.sort_by_key(|e| e.doc_id);
71        GraphPostingList::try_from_parts(
72            PostingList::from_sorted_unchecked(entries),
73            graph_payloads,
74        )
75        .map_err(Into::into)
76    }
77}
78
79// -------------------------------------------------------------------------
80// GMatch (subgraph isomorphism)
81// -------------------------------------------------------------------------