1use serde_json::Value;
9use sqlitegraph::backend::GraphBackend;
10use sqlitegraph::graph::GraphEntity;
11use sqlitegraph::snapshot::SnapshotId;
12
13#[derive(Debug)]
14pub enum Query {
15 Match {
16 pattern: Pattern,
17 returns: Vec<String>,
18 },
19}
20
21#[derive(Debug)]
22pub enum Pattern {
23 Node(NodePattern),
24 Edge(NodePattern, String, NodePattern), }
26
27#[derive(Debug)]
28pub struct NodePattern {
29 pub var: String,
30 pub label: Option<String>,
31 pub props: Vec<(String, String)>,
32}
33
34impl NodePattern {
35 fn matches(&self, node: &GraphEntity) -> bool {
36 if let Some(ref label) = self.label {
38 if node.kind != *label {
39 return false;
40 }
41 }
42 for (key, value) in &self.props {
44 match node.data.get(key) {
45 Some(v) if v.as_str() == Some(value) => continue,
46 _ => return false,
47 }
48 }
49 true
50 }
51}
52
53pub fn parse(query: &str) -> anyhow::Result<Query> {
55 let query = query.trim();
56
57 if query.to_uppercase().starts_with("MATCH ") {
58 parse_match(query)
59 } else {
60 anyhow::bail!("Only MATCH queries are supported")
61 }
62}
63
64fn parse_match(query: &str) -> anyhow::Result<Query> {
65 let rest = query[6..].trim();
67
68 let return_pos = rest.to_uppercase().find(" RETURN ");
70 let (pattern_str, returns) = if let Some(pos) = return_pos {
71 let pattern_part = &rest[..pos];
72 let return_part = &rest[pos + 8..];
73 let returns: Vec<String> = return_part
74 .split(',')
75 .map(|s| s.trim().to_string())
76 .collect();
77 (pattern_part.trim(), returns)
78 } else {
79 (rest, vec!["*".to_string()])
80 };
81
82 let pattern = parse_pattern(pattern_str)?;
83
84 Ok(Query::Match { pattern, returns })
85}
86
87fn parse_pattern(s: &str) -> anyhow::Result<Pattern> {
88 let s = s.trim();
89
90 if s.contains("-") && s.contains("->") {
92 parse_edge_pattern(s)
93 } else if s.starts_with('(') && s.ends_with(')') {
94 let inner = &s[1..s.len() - 1];
96 let node = parse_node(inner)?;
97 Ok(Pattern::Node(node))
98 } else {
99 anyhow::bail!("Invalid pattern syntax")
100 }
101}
102
103fn parse_edge_pattern(_s: &str) -> anyhow::Result<Pattern> {
104 anyhow::bail!("Edge patterns not yet implemented in parser")
105}
106
107fn parse_node(s: &str) -> anyhow::Result<NodePattern> {
108 let s = s.trim();
109
110 let var_end = s.find(|c: char| c == ':' || c == '{' || c.is_whitespace());
112 let var = if let Some(end) = var_end {
113 s[..end].trim().to_string()
114 } else {
115 s.to_string()
116 };
117
118 let label = if let Some(colon_pos) = s.find(':') {
120 let after_colon = &s[colon_pos + 1..];
121 let label_end = after_colon.find(|c: char| c == '{' || c.is_whitespace());
122 if let Some(end) = label_end {
123 Some(after_colon[..end].trim().to_string())
124 } else {
125 Some(after_colon.trim().to_string())
126 }
127 } else {
128 None
129 };
130
131 let mut props = Vec::new();
133 if let Some(open_brace) = s.find('{') {
134 if let Some(close_brace) = s.rfind('}') {
135 let props_str = &s[open_brace + 1..close_brace];
136 for part in props_str.split(',') {
138 let part = part.trim();
139 if let Some(colon_pos) = part.find(':') {
140 let key = part[..colon_pos].trim().to_string();
141 let value = part[colon_pos + 1..]
142 .trim()
143 .trim_matches('"')
144 .trim_matches('\'')
145 .to_string();
146 props.push((key, value));
147 }
148 }
149 }
150 }
151
152 Ok(NodePattern { var, label, props })
153}
154
155pub fn execute(backend: &dyn GraphBackend, query: &Query) -> anyhow::Result<Value> {
157 match query {
158 Query::Match { pattern, returns } => execute_match(backend, pattern, returns),
159 }
160}
161
162fn execute_match(
163 backend: &dyn GraphBackend,
164 pattern: &Pattern,
165 returns: &[String],
166) -> anyhow::Result<Value> {
167 match pattern {
168 Pattern::Node(node_pat) => {
169 let snapshot = SnapshotId::current();
170 let node_ids = backend.entity_ids()?;
171
172 let mut results = Vec::new();
173 for id in node_ids.iter().take(1000) {
174 if let Ok(node) = backend.get_node(snapshot, *id) {
176 if node_pat.matches(&node) {
177 let mut obj = serde_json::Map::new();
178
179 for ret in returns {
180 if ret == "*" || *ret == node_pat.var {
181 obj.insert(
182 node_pat.var.clone(),
183 serde_json::json!({
184 "id": node.id,
185 "kind": node.kind,
186 "name": node.name,
187 "data": node.data,
188 }),
189 );
190 } else if ret.starts_with(&format!("{}.", node_pat.var)) {
191 let field = &ret[node_pat.var.len() + 1..];
192 let value = match field {
193 "id" => serde_json::json!(node.id),
194 "kind" => serde_json::json!(node.kind),
195 "name" => serde_json::json!(node.name),
196 _ => node.data.get(field).cloned().unwrap_or(Value::Null),
197 };
198 obj.insert(ret.clone(), value);
199 }
200 }
201
202 if !obj.is_empty() {
203 results.push(Value::Object(obj));
204 }
205 }
206 }
207 }
208
209 Ok(serde_json::json!({
210 "results": results,
211 "count": results.len(),
212 }))
213 }
214 Pattern::Edge(_, _, _) => {
215 anyhow::bail!("Edge pattern queries not yet implemented")
216 }
217 }
218}