1use futures::Stream;
11use std::collections::HashMap;
12use std::pin::Pin;
13use std::sync::Arc;
14use std::time::Duration;
15use uni_common::{Result, UniError};
16
17#[doc(inline)]
19pub use uni_common::value::{Edge, FromValue, Node, Path, Value};
20
21#[derive(Debug, Clone, Default)]
25pub struct QueryMetrics {
26 pub parse_time: Duration,
28 pub plan_time: Duration,
30 pub exec_time: Duration,
32 pub total_time: Duration,
34 pub rows_returned: usize,
36 pub rows_scanned: usize,
38 pub bytes_read: usize,
40 pub plan_cache_hit: bool,
42 pub l0_reads: usize,
44 pub storage_reads: usize,
46 pub cache_hits: usize,
48}
49
50#[derive(Debug, Clone)]
52pub struct Row {
53 pub(crate) columns: Arc<Vec<String>>,
55 pub(crate) values: Vec<Value>,
57}
58
59impl Row {
60 pub fn new(columns: Arc<Vec<String>>, values: Vec<Value>) -> Self {
62 Self { columns, values }
63 }
64
65 pub fn columns(&self) -> &[String] {
67 &self.columns
68 }
69
70 pub fn values(&self) -> &[Value] {
72 &self.values
73 }
74
75 pub fn into_values(self) -> Vec<Value> {
77 self.values
78 }
79
80 pub fn get<T: FromValue>(&self, column: &str) -> Result<T> {
87 let idx = self
88 .columns
89 .iter()
90 .position(|c| c == column)
91 .ok_or_else(|| UniError::Query {
92 message: format!("Column '{}' not found", column),
93 query: None,
94 })?;
95 self.get_idx(idx)
96 }
97
98 pub fn get_idx<T: FromValue>(&self, index: usize) -> Result<T> {
105 if index >= self.values.len() {
106 return Err(UniError::Query {
107 message: format!("Column index {} out of bounds", index),
108 query: None,
109 });
110 }
111 T::from_value(&self.values[index])
112 }
113
114 pub fn try_get<T: FromValue>(&self, column: &str) -> Option<T> {
116 self.get(column).ok()
117 }
118
119 pub fn value(&self, column: &str) -> Option<&Value> {
121 let idx = self.columns.iter().position(|c| c == column)?;
122 self.values.get(idx)
123 }
124
125 pub fn as_map(&self) -> HashMap<&str, &Value> {
127 self.columns
128 .iter()
129 .zip(&self.values)
130 .map(|(col, val)| (col.as_str(), val))
131 .collect()
132 }
133
134 pub fn to_json(&self) -> serde_json::Value {
136 serde_json::to_value(self.as_map()).unwrap_or(serde_json::Value::Null)
137 }
138}
139
140impl std::ops::Index<usize> for Row {
141 type Output = Value;
142 fn index(&self, index: usize) -> &Self::Output {
143 &self.values[index]
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
152pub enum QueryWarning {
153 IndexUnavailable {
155 label: String,
157 index_name: String,
159 reason: String,
161 },
162 NoIndexForFilter {
164 label: String,
166 property: String,
168 },
169 RrfPointContext,
172 Other(String),
174}
175
176impl std::fmt::Display for QueryWarning {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 match self {
179 QueryWarning::IndexUnavailable {
180 label,
181 index_name,
182 reason,
183 } => {
184 write!(
185 f,
186 "Index '{}' on label '{}' is unavailable: {}",
187 index_name, label, reason
188 )
189 }
190 QueryWarning::NoIndexForFilter { label, property } => {
191 write!(
192 f,
193 "No index available for filter on {}.{}, using full scan",
194 label, property
195 )
196 }
197 QueryWarning::RrfPointContext => {
198 write!(
199 f,
200 "RRF fusion degenerated to equal-weight fusion in point-computation context \
201 (no global ranking available). Consider using method: 'weighted' with explicit weights."
202 )
203 }
204 QueryWarning::Other(msg) => write!(f, "{}", msg),
205 }
206 }
207}
208
209#[derive(Debug)]
211pub struct QueryResult {
212 pub(crate) columns: Arc<Vec<String>>,
214 pub(crate) rows: Vec<Row>,
216 pub(crate) warnings: Vec<QueryWarning>,
218 pub(crate) metrics: QueryMetrics,
220}
221
222impl QueryResult {
223 #[doc(hidden)]
225 pub fn new(
226 columns: Arc<Vec<String>>,
227 rows: Vec<Row>,
228 warnings: Vec<QueryWarning>,
229 metrics: QueryMetrics,
230 ) -> Self {
231 Self {
232 columns,
233 rows,
234 warnings,
235 metrics,
236 }
237 }
238
239 pub fn columns(&self) -> &[String] {
241 &self.columns
242 }
243
244 pub fn len(&self) -> usize {
246 self.rows.len()
247 }
248
249 pub fn is_empty(&self) -> bool {
251 self.rows.is_empty()
252 }
253
254 pub fn rows(&self) -> &[Row] {
256 &self.rows
257 }
258
259 pub fn into_rows(self) -> Vec<Row> {
261 self.rows
262 }
263
264 pub fn iter(&self) -> impl Iterator<Item = &Row> {
266 self.rows.iter()
267 }
268
269 pub fn warnings(&self) -> &[QueryWarning] {
271 &self.warnings
272 }
273
274 pub fn has_warnings(&self) -> bool {
276 !self.warnings.is_empty()
277 }
278
279 pub fn metrics(&self) -> &QueryMetrics {
281 &self.metrics
282 }
283
284 #[doc(hidden)]
289 pub fn update_parse_timing(
290 &mut self,
291 parse_time: std::time::Duration,
292 total_time: std::time::Duration,
293 ) {
294 self.metrics.parse_time = parse_time;
295 self.metrics.total_time = total_time;
296 }
297}
298
299impl IntoIterator for QueryResult {
300 type Item = Row;
301 type IntoIter = std::vec::IntoIter<Row>;
302
303 fn into_iter(self) -> Self::IntoIter {
304 self.rows.into_iter()
305 }
306}
307
308#[derive(Debug)]
310pub struct ExecuteResult {
311 pub(crate) affected_rows: usize,
313 pub(crate) nodes_created: usize,
315 pub(crate) nodes_deleted: usize,
317 pub(crate) relationships_created: usize,
319 pub(crate) relationships_deleted: usize,
321 pub(crate) properties_set: usize,
323 pub(crate) labels_added: usize,
325 pub(crate) labels_removed: usize,
327 pub(crate) metrics: QueryMetrics,
329}
330
331impl ExecuteResult {
332 #[doc(hidden)]
337 pub fn new(affected_rows: usize) -> Self {
338 Self {
339 affected_rows,
340 nodes_created: 0,
341 nodes_deleted: 0,
342 relationships_created: 0,
343 relationships_deleted: 0,
344 properties_set: 0,
345 labels_added: 0,
346 labels_removed: 0,
347 metrics: QueryMetrics::default(),
348 }
349 }
350
351 #[doc(hidden)]
353 pub fn with_details(
354 affected_rows: usize,
355 stats: &uni_store::runtime::l0::MutationStats,
356 metrics: QueryMetrics,
357 ) -> Self {
358 Self {
359 affected_rows,
360 nodes_created: stats.nodes_created,
361 nodes_deleted: stats.nodes_deleted,
362 relationships_created: stats.relationships_created,
363 relationships_deleted: stats.relationships_deleted,
364 properties_set: stats.properties_set,
365 labels_added: stats.labels_added,
366 labels_removed: stats.labels_removed,
367 metrics,
368 }
369 }
370
371 pub fn affected_rows(&self) -> usize {
373 self.affected_rows
374 }
375
376 pub fn nodes_created(&self) -> usize {
378 self.nodes_created
379 }
380
381 pub fn nodes_deleted(&self) -> usize {
383 self.nodes_deleted
384 }
385
386 pub fn relationships_created(&self) -> usize {
388 self.relationships_created
389 }
390
391 pub fn relationships_deleted(&self) -> usize {
393 self.relationships_deleted
394 }
395
396 pub fn properties_set(&self) -> usize {
398 self.properties_set
399 }
400
401 pub fn labels_added(&self) -> usize {
403 self.labels_added
404 }
405
406 pub fn labels_removed(&self) -> usize {
408 self.labels_removed
409 }
410
411 pub fn metrics(&self) -> &QueryMetrics {
413 &self.metrics
414 }
415}
416
417pub struct QueryCursor {
419 pub(crate) columns: Arc<Vec<String>>,
421 pub(crate) stream: Pin<Box<dyn Stream<Item = Result<Vec<Row>>> + Send>>,
423}
424
425impl QueryCursor {
426 #[doc(hidden)]
428 pub fn new(
429 columns: Arc<Vec<String>>,
430 stream: Pin<Box<dyn Stream<Item = Result<Vec<Row>>> + Send>>,
431 ) -> Self {
432 Self { columns, stream }
433 }
434
435 pub fn columns(&self) -> &[String] {
437 &self.columns
438 }
439
440 pub async fn next_batch(&mut self) -> Option<Result<Vec<Row>>> {
442 use futures::StreamExt;
443 self.stream.next().await
444 }
445
446 pub async fn collect_remaining(mut self) -> Result<Vec<Row>> {
452 use futures::StreamExt;
453 let mut rows = Vec::new();
454 while let Some(batch_res) = self.stream.next().await {
455 rows.extend(batch_res?);
456 }
457 Ok(rows)
458 }
459}