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.split(',').map(|s| s.trim().to_string()).collect();
74 (pattern_part.trim(), returns)
75 } else {
76 (rest, vec!["*".to_string()])
77 };
78
79 let pattern = parse_pattern(pattern_str)?;
80
81 Ok(Query::Match { pattern, returns })
82}
83
84fn parse_pattern(s: &str) -> anyhow::Result<Pattern> {
85 let s = s.trim();
86
87 if s.contains("-") && s.contains("->") {
89 parse_edge_pattern(s)
90 } else if s.starts_with('(') && s.ends_with(')') {
91 let inner = &s[1..s.len() - 1];
93 let node = parse_node(inner)?;
94 Ok(Pattern::Node(node))
95 } else {
96 anyhow::bail!("Invalid pattern syntax")
97 }
98}
99
100fn parse_edge_pattern(_s: &str) -> anyhow::Result<Pattern> {
101 anyhow::bail!("Edge patterns not yet implemented in parser")
102}
103
104fn parse_node(s: &str) -> anyhow::Result<NodePattern> {
105 let s = s.trim();
106
107 let var_end = s.find(|c: char| c == ':' || c == '{' || c.is_whitespace());
109 let var = if let Some(end) = var_end {
110 s[..end].trim().to_string()
111 } else {
112 s.to_string()
113 };
114
115 let label = if let Some(colon_pos) = s.find(':') {
117 let after_colon = &s[colon_pos + 1..];
118 let label_end = after_colon.find(|c: char| c == '{' || c.is_whitespace());
119 if let Some(end) = label_end {
120 Some(after_colon[..end].trim().to_string())
121 } else {
122 Some(after_colon.trim().to_string())
123 }
124 } else {
125 None
126 };
127
128 let mut props = Vec::new();
130 if let Some(open_brace) = s.find('{') {
131 if let Some(close_brace) = s.rfind('}') {
132 let props_str = &s[open_brace + 1..close_brace];
133 for part in props_str.split(',') {
135 let part = part.trim();
136 if let Some(colon_pos) = part.find(':') {
137 let key = part[..colon_pos].trim().to_string();
138 let value = part[colon_pos + 1..]
139 .trim()
140 .trim_matches('"')
141 .trim_matches('\'')
142 .to_string();
143 props.push((key, value));
144 }
145 }
146 }
147 }
148
149 Ok(NodePattern { var, label, props })
150}
151
152pub fn execute(backend: &dyn GraphBackend, query: &Query) -> anyhow::Result<Value> {
154 match query {
155 Query::Match { pattern, returns } => execute_match(backend, pattern, returns),
156 }
157}
158
159fn execute_match(
160 backend: &dyn GraphBackend,
161 pattern: &Pattern,
162 returns: &[String],
163) -> anyhow::Result<Value> {
164 match pattern {
165 Pattern::Node(node_pat) => {
166 let snapshot = SnapshotId::current();
167 let node_ids = backend.entity_ids()?;
168
169 let mut results = Vec::new();
170 for id in node_ids.iter().take(1000) {
171 if let Ok(node) = backend.get_node(snapshot, *id) {
173 if node_pat.matches(&node) {
174 let mut obj = serde_json::Map::new();
175
176 for ret in returns {
177 if ret == "*" || *ret == node_pat.var {
178 obj.insert(
179 node_pat.var.clone(),
180 serde_json::json!({
181 "id": node.id,
182 "kind": node.kind,
183 "name": node.name,
184 "data": node.data,
185 }),
186 );
187 } else if ret.starts_with(&format!("{}.", node_pat.var)) {
188 let field = &ret[node_pat.var.len() + 1..];
189 let value = match field {
190 "id" => serde_json::json!(node.id),
191 "kind" => serde_json::json!(node.kind),
192 "name" => serde_json::json!(node.name),
193 _ => node.data.get(field).cloned().unwrap_or(Value::Null),
194 };
195 obj.insert(ret.clone(), value);
196 }
197 }
198
199 if !obj.is_empty() {
200 results.push(Value::Object(obj));
201 }
202 }
203 }
204 }
205
206 Ok(serde_json::json!({
207 "results": results,
208 "count": results.len(),
209 }))
210 }
211 Pattern::Edge(_, _, _) => {
212 anyhow::bail!("Edge pattern queries not yet implemented")
213 }
214 }
215}