grafeo_engine/database/query.rs
1//! Query execution methods for GrafeoDB.
2
3use grafeo_common::utils::error::Result;
4
5#[cfg(feature = "rdf")]
6use grafeo_core::graph::rdf::RdfStore;
7#[cfg(feature = "rdf")]
8use std::sync::Arc;
9
10use super::{FromValue, QueryResult};
11
12impl super::GrafeoDB {
13 /// Executes a closure with a one-shot session, syncing graph context back
14 /// to the database afterward. This ensures `USE GRAPH`, `SESSION SET GRAPH`,
15 /// and `SESSION RESET` persist across one-shot `execute()` calls.
16 fn with_session<F>(&self, func: F) -> Result<QueryResult>
17 where
18 F: FnOnce(&crate::session::Session) -> Result<QueryResult>,
19 {
20 let session = self.session();
21 let result = func(&session);
22 // Sync graph state back, even on error (USE GRAPH may have succeeded
23 // before a subsequent query failed in the same session).
24 *self.current_graph.write() = session.current_graph();
25 result
26 }
27
28 /// Runs a query directly on the database.
29 ///
30 /// A convenience method that creates a temporary session behind the
31 /// scenes. If you're running multiple queries, grab a
32 /// [`session()`](Self::session) instead to avoid the overhead.
33 ///
34 /// Graph context commands (`USE GRAPH`, `SESSION SET GRAPH`, `SESSION RESET`)
35 /// persist across calls: running `execute("USE GRAPH analytics")` followed
36 /// by `execute("MATCH (n) RETURN n")` routes the second query to the
37 /// analytics graph.
38 ///
39 /// # Errors
40 ///
41 /// Returns an error if parsing or execution fails.
42 pub fn execute(&self, query: &str) -> Result<QueryResult> {
43 self.with_session(|s| s.execute(query))
44 }
45
46 /// Executes a GQL query with visibility at the specified epoch.
47 ///
48 /// This enables time-travel queries: the query sees the database
49 /// as it existed at the given epoch.
50 ///
51 /// # Errors
52 ///
53 /// Returns an error if parsing or execution fails.
54 pub fn execute_at_epoch(
55 &self,
56 query: &str,
57 epoch: grafeo_common::types::EpochId,
58 ) -> Result<QueryResult> {
59 self.with_session(|s| s.execute_at_epoch(query, epoch))
60 }
61
62 /// Executes a query with parameters and returns the result.
63 ///
64 /// # Errors
65 ///
66 /// Returns an error if the query fails.
67 pub fn execute_with_params(
68 &self,
69 query: &str,
70 params: std::collections::HashMap<String, grafeo_common::types::Value>,
71 ) -> Result<QueryResult> {
72 self.with_session(|s| s.execute_with_params(query, params))
73 }
74
75 /// Executes a Cypher query and returns the result.
76 ///
77 /// # Errors
78 ///
79 /// Returns an error if the query fails.
80 #[cfg(feature = "cypher")]
81 pub fn execute_cypher(&self, query: &str) -> Result<QueryResult> {
82 self.with_session(|s| s.execute_cypher(query))
83 }
84
85 /// Executes a Cypher query with parameters and returns the result.
86 ///
87 /// # Errors
88 ///
89 /// Returns an error if the query fails.
90 #[cfg(feature = "cypher")]
91 pub fn execute_cypher_with_params(
92 &self,
93 query: &str,
94 params: std::collections::HashMap<String, grafeo_common::types::Value>,
95 ) -> Result<QueryResult> {
96 self.with_session(|s| s.execute_language(query, "cypher", Some(params)))
97 }
98
99 /// Executes a Gremlin query and returns the result.
100 ///
101 /// # Errors
102 ///
103 /// Returns an error if the query fails.
104 #[cfg(feature = "gremlin")]
105 pub fn execute_gremlin(&self, query: &str) -> Result<QueryResult> {
106 self.with_session(|s| s.execute_gremlin(query))
107 }
108
109 /// Executes a Gremlin query with parameters and returns the result.
110 ///
111 /// # Errors
112 ///
113 /// Returns an error if the query fails.
114 #[cfg(feature = "gremlin")]
115 pub fn execute_gremlin_with_params(
116 &self,
117 query: &str,
118 params: std::collections::HashMap<String, grafeo_common::types::Value>,
119 ) -> Result<QueryResult> {
120 self.with_session(|s| s.execute_gremlin_with_params(query, params))
121 }
122
123 /// Executes a GraphQL query and returns the result.
124 ///
125 /// # Errors
126 ///
127 /// Returns an error if the query fails.
128 #[cfg(feature = "graphql")]
129 pub fn execute_graphql(&self, query: &str) -> Result<QueryResult> {
130 self.with_session(|s| s.execute_graphql(query))
131 }
132
133 /// Executes a GraphQL query with parameters and returns the result.
134 ///
135 /// # Errors
136 ///
137 /// Returns an error if the query fails.
138 #[cfg(feature = "graphql")]
139 pub fn execute_graphql_with_params(
140 &self,
141 query: &str,
142 params: std::collections::HashMap<String, grafeo_common::types::Value>,
143 ) -> Result<QueryResult> {
144 self.with_session(|s| s.execute_graphql_with_params(query, params))
145 }
146
147 /// Executes a SQL/PGQ query (SQL:2023 GRAPH_TABLE) and returns the result.
148 ///
149 /// # Errors
150 ///
151 /// Returns an error if the query fails.
152 #[cfg(feature = "sql-pgq")]
153 pub fn execute_sql(&self, query: &str) -> Result<QueryResult> {
154 self.with_session(|s| s.execute_sql(query))
155 }
156
157 /// Executes a SQL/PGQ query with parameters and returns the result.
158 ///
159 /// # Errors
160 ///
161 /// Returns an error if the query fails.
162 #[cfg(feature = "sql-pgq")]
163 pub fn execute_sql_with_params(
164 &self,
165 query: &str,
166 params: std::collections::HashMap<String, grafeo_common::types::Value>,
167 ) -> Result<QueryResult> {
168 self.with_session(|s| s.execute_sql_with_params(query, params))
169 }
170
171 /// Executes a SPARQL query and returns the result.
172 ///
173 /// SPARQL queries operate on the RDF triple store.
174 ///
175 /// # Errors
176 ///
177 /// Returns an error if the query fails.
178 ///
179 /// # Examples
180 ///
181 /// ```no_run
182 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
183 /// use grafeo_engine::GrafeoDB;
184 ///
185 /// let db = GrafeoDB::new_in_memory();
186 /// let result = db.execute_sparql("SELECT ?s ?p ?o WHERE { ?s ?p ?o }")?;
187 /// # Ok(())
188 /// # }
189 /// ```
190 #[cfg(all(feature = "sparql", feature = "rdf"))]
191 pub fn execute_sparql(&self, query: &str) -> Result<QueryResult> {
192 use crate::query::{
193 Executor, optimizer::Optimizer, planner::rdf::RdfPlanner, translators::sparql,
194 };
195
196 // Parse and translate the SPARQL query to a logical plan
197 let logical_plan = sparql::translate(query)?;
198
199 // Optimize the plan
200 let optimizer = Optimizer::from_store(&self.store);
201 let optimized_plan = optimizer.optimize(logical_plan)?;
202
203 // Convert to physical plan using RDF planner
204 let planner = RdfPlanner::new(Arc::clone(&self.rdf_store));
205 #[cfg(feature = "wal")]
206 let planner = planner.with_wal(self.wal.as_ref().map(Arc::clone));
207 let mut physical_plan = planner.plan(&optimized_plan)?;
208
209 // Execute the plan
210 let executor = Executor::with_columns(physical_plan.columns.clone());
211 executor.execute(physical_plan.operator.as_mut())
212 }
213
214 /// Executes a query in the specified language by name.
215 ///
216 /// Supported language names: `"gql"`, `"cypher"`, `"gremlin"`, `"graphql"`,
217 /// `"sparql"`, `"sql"`. Each requires the corresponding feature flag.
218 ///
219 /// # Errors
220 ///
221 /// Returns an error if the language is unknown/disabled, or if the query
222 /// fails.
223 pub fn execute_language(
224 &self,
225 query: &str,
226 language: &str,
227 params: Option<std::collections::HashMap<String, grafeo_common::types::Value>>,
228 ) -> Result<QueryResult> {
229 self.with_session(|s| s.execute_language(query, language, params))
230 }
231
232 /// Returns the RDF store.
233 ///
234 /// This provides direct access to the RDF store for triple operations.
235 #[cfg(feature = "rdf")]
236 #[must_use]
237 pub fn rdf_store(&self) -> &Arc<RdfStore> {
238 &self.rdf_store
239 }
240
241 /// Executes a query and returns a single scalar value.
242 ///
243 /// # Errors
244 ///
245 /// Returns an error if the query fails or doesn't return exactly one row.
246 pub fn query_scalar<T: FromValue>(&self, query: &str) -> Result<T> {
247 let result = self.execute(query)?;
248 result.scalar()
249 }
250}