use std::collections::BTreeMap;
use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::connection::DatabaseClient;
use crate::error::{Result, SurqlError};
use crate::query::builder::Query;
use crate::query::results::{extract_result, records, ListResult};
pub async fn execute_query(client: &DatabaseClient, query: &Query) -> Result<Value> {
let surql = query.to_surql()?;
client.query(&surql).await
}
pub async fn execute_raw(
client: &DatabaseClient,
surql: &str,
vars: Option<BTreeMap<String, Value>>,
) -> Result<Value> {
match vars {
Some(v) => client.query_with_vars(surql, v).await,
None => client.query(surql).await,
}
}
pub async fn fetch_one<T: DeserializeOwned>(
client: &DatabaseClient,
query: &Query,
) -> Result<Option<T>> {
let raw = execute_query(client, query).await?;
let mut rows = flatten_rows(&raw);
let Some(first) = rows.drain(..).next() else {
return Ok(None);
};
match first {
Value::Object(obj) => deserialize_row(obj).map(Some),
other => {
serde_json::from_value::<T>(other)
.map(Some)
.map_err(|e| SurqlError::Serialization {
reason: e.to_string(),
})
}
}
}
pub async fn fetch_all<T: DeserializeOwned>(
client: &DatabaseClient,
query: &Query,
) -> Result<Vec<T>> {
let raw = execute_query(client, query).await?;
extract_rows::<T>(&raw)
}
pub async fn fetch_many<T: DeserializeOwned>(
client: &DatabaseClient,
query: &Query,
) -> Result<ListResult<T>> {
let items = fetch_all::<T>(client, query).await?;
let limit = query.limit_value.and_then(|v| u64::try_from(v).ok());
let offset = query.offset_value.and_then(|v| u64::try_from(v).ok());
Ok(records(items, None, limit, offset))
}
pub async fn execute_raw_typed<T: DeserializeOwned>(
client: &DatabaseClient,
surql: &str,
) -> Result<Vec<T>> {
let raw = client.query(surql).await?;
extract_rows::<T>(&raw)
}
pub(crate) fn extract_rows<T: DeserializeOwned>(raw: &Value) -> Result<Vec<T>> {
let rows = flatten_rows(raw);
rows.into_iter()
.map(|row| {
serde_json::from_value::<T>(row).map_err(|e| SurqlError::Serialization {
reason: e.to_string(),
})
})
.collect()
}
fn deserialize_row<T: DeserializeOwned>(row: serde_json::Map<String, Value>) -> Result<T> {
serde_json::from_value::<T>(Value::Object(row)).map_err(|e| SurqlError::Serialization {
reason: e.to_string(),
})
}
pub(crate) fn flatten_rows(raw: &Value) -> Vec<Value> {
let mut out: Vec<Value> = Vec::new();
match raw {
Value::Array(items) => {
for item in items {
append_flattened(&mut out, item);
}
}
other => append_flattened(&mut out, other),
}
if out.is_empty() {
return extract_result(raw).into_iter().map(Value::Object).collect();
}
out
}
fn append_flattened(out: &mut Vec<Value>, value: &Value) {
match value {
Value::Null => {}
Value::Array(inner) => {
for v in inner {
append_flattened(out, v);
}
}
Value::Object(obj) => {
if let Some(inner) = obj.get("result") {
append_flattened(out, inner);
} else {
out.push(Value::Object(obj.clone()));
}
}
other => out.push(other.clone()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
use serde_json::json;
#[derive(Debug, Deserialize, PartialEq)]
struct Row {
name: String,
age: u32,
}
#[test]
fn extract_rows_nested_format() {
let raw = json!([
{"result": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 40}
]}
]);
let rows: Vec<Row> = extract_rows(&raw).unwrap();
assert_eq!(
rows,
vec![
Row {
name: "Alice".into(),
age: 30
},
Row {
name: "Bob".into(),
age: 40
}
]
);
}
#[test]
fn extract_rows_flat_format() {
let raw = json!([{"name": "Alice", "age": 30}]);
let rows: Vec<Row> = extract_rows(&raw).unwrap();
assert_eq!(
rows,
vec![Row {
name: "Alice".into(),
age: 30
}]
);
}
#[test]
fn extract_rows_empty() {
let raw = json!([]);
let rows: Vec<Row> = extract_rows(&raw).unwrap();
assert!(rows.is_empty());
}
#[test]
fn extract_rows_returns_serialization_error_on_shape_mismatch() {
let raw = json!([{"result": [{"name": "Alice", "age": "not-a-number"}]}]);
let err = extract_rows::<Row>(&raw).unwrap_err();
assert!(matches!(err, SurqlError::Serialization { .. }));
}
}