1use futures::Stream;
11use std::collections::HashMap;
12use std::pin::Pin;
13use std::sync::Arc;
14use uni_common::{Result, UniError};
15
16#[doc(inline)]
18pub use uni_common::value::{Edge, FromValue, Node, Path, Value};
19
20#[derive(Debug, Clone)]
22pub struct Row {
23 pub columns: Arc<Vec<String>>,
25 pub values: Vec<Value>,
27}
28
29impl Row {
30 pub fn get<T: FromValue>(&self, column: &str) -> Result<T> {
37 let idx = self
38 .columns
39 .iter()
40 .position(|c| c == column)
41 .ok_or_else(|| UniError::Query {
42 message: format!("Column '{}' not found", column),
43 query: None,
44 })?;
45 self.get_idx(idx)
46 }
47
48 pub fn get_idx<T: FromValue>(&self, index: usize) -> Result<T> {
55 if index >= self.values.len() {
56 return Err(UniError::Query {
57 message: format!("Column index {} out of bounds", index),
58 query: None,
59 });
60 }
61 T::from_value(&self.values[index])
62 }
63
64 pub fn try_get<T: FromValue>(&self, column: &str) -> Option<T> {
66 self.get(column).ok()
67 }
68
69 pub fn value(&self, column: &str) -> Option<&Value> {
71 let idx = self.columns.iter().position(|c| c == column)?;
72 self.values.get(idx)
73 }
74
75 pub fn as_map(&self) -> HashMap<&str, &Value> {
77 self.columns
78 .iter()
79 .zip(&self.values)
80 .map(|(col, val)| (col.as_str(), val))
81 .collect()
82 }
83
84 pub fn to_json(&self) -> serde_json::Value {
86 serde_json::to_value(self.as_map()).unwrap_or(serde_json::Value::Null)
87 }
88}
89
90impl std::ops::Index<usize> for Row {
91 type Output = Value;
92 fn index(&self, index: usize) -> &Self::Output {
93 &self.values[index]
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
102pub enum QueryWarning {
103 IndexUnavailable {
105 label: String,
107 index_name: String,
109 reason: String,
111 },
112 NoIndexForFilter {
114 label: String,
116 property: String,
118 },
119 RrfPointContext,
122 Other(String),
124}
125
126impl std::fmt::Display for QueryWarning {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 match self {
129 QueryWarning::IndexUnavailable {
130 label,
131 index_name,
132 reason,
133 } => {
134 write!(
135 f,
136 "Index '{}' on label '{}' is unavailable: {}",
137 index_name, label, reason
138 )
139 }
140 QueryWarning::NoIndexForFilter { label, property } => {
141 write!(
142 f,
143 "No index available for filter on {}.{}, using full scan",
144 label, property
145 )
146 }
147 QueryWarning::RrfPointContext => {
148 write!(
149 f,
150 "RRF fusion degenerated to equal-weight fusion in point-computation context \
151 (no global ranking available). Consider using method: 'weighted' with explicit weights."
152 )
153 }
154 QueryWarning::Other(msg) => write!(f, "{}", msg),
155 }
156 }
157}
158
159#[derive(Debug)]
161pub struct QueryResult {
162 pub columns: Arc<Vec<String>>,
164 pub rows: Vec<Row>,
166 pub warnings: Vec<QueryWarning>,
168}
169
170impl QueryResult {
171 pub fn columns(&self) -> &[String] {
173 &self.columns
174 }
175
176 pub fn len(&self) -> usize {
178 self.rows.len()
179 }
180
181 pub fn is_empty(&self) -> bool {
183 self.rows.is_empty()
184 }
185
186 pub fn rows(&self) -> &[Row] {
188 &self.rows
189 }
190
191 pub fn into_rows(self) -> Vec<Row> {
193 self.rows
194 }
195
196 pub fn iter(&self) -> impl Iterator<Item = &Row> {
198 self.rows.iter()
199 }
200
201 pub fn warnings(&self) -> &[QueryWarning] {
203 &self.warnings
204 }
205
206 pub fn has_warnings(&self) -> bool {
208 !self.warnings.is_empty()
209 }
210}
211
212impl IntoIterator for QueryResult {
213 type Item = Row;
214 type IntoIter = std::vec::IntoIter<Row>;
215
216 fn into_iter(self) -> Self::IntoIter {
217 self.rows.into_iter()
218 }
219}
220
221#[derive(Debug)]
223pub struct ExecuteResult {
224 pub affected_rows: usize,
226}
227
228pub struct QueryCursor {
230 pub columns: Arc<Vec<String>>,
232 pub stream: Pin<Box<dyn Stream<Item = Result<Vec<Row>>> + Send>>,
234}
235
236impl QueryCursor {
237 pub fn columns(&self) -> &[String] {
239 &self.columns
240 }
241
242 pub async fn next_batch(&mut self) -> Option<Result<Vec<Row>>> {
244 use futures::StreamExt;
245 self.stream.next().await
246 }
247
248 pub async fn collect_remaining(mut self) -> Result<Vec<Row>> {
254 use futures::StreamExt;
255 let mut rows = Vec::new();
256 while let Some(batch_res) = self.stream.next().await {
257 rows.extend(batch_res?);
258 }
259 Ok(rows)
260 }
261}