use std::sync::atomic::{AtomicU64, Ordering};
use super::super::Executor;
use crate::{
db::traits::DatabaseAdapter,
error::Result,
graphql::{FieldSelection, GraphQLArgument, ParsedQuery},
};
static MULTI_ROOT_QUERIES_TOTAL: AtomicU64 = AtomicU64::new(0);
pub fn multi_root_queries_total() -> u64 {
MULTI_ROOT_QUERIES_TOTAL.load(Ordering::Relaxed)
}
#[derive(Debug)]
pub struct RootFieldResult {
pub field_name: String,
pub data: serde_json::Value,
}
#[derive(Debug)]
pub struct PipelineResult {
pub fields: Vec<RootFieldResult>,
pub parallel: bool,
}
impl PipelineResult {
#[must_use]
pub fn merge_into_data_map(&self) -> serde_json::Map<String, serde_json::Value> {
self.fields.iter().map(|f| (f.field_name.clone(), f.data.clone())).collect()
}
}
#[must_use]
pub const fn is_multi_root(parsed: &ParsedQuery) -> bool {
parsed.selections.len() > 1
}
pub fn extract_root_field_names(parsed: &ParsedQuery) -> impl Iterator<Item = &str> + '_ {
parsed.selections.iter().map(|s| s.response_key())
}
pub(super) fn field_selection_to_query(field: &FieldSelection) -> String {
format!("{{ {} }}", serialize_field(field))
}
fn serialize_field(field: &FieldSelection) -> String {
let mut s = String::new();
if let Some(alias) = &field.alias {
s.push_str(alias);
s.push_str(": ");
}
s.push_str(&field.name);
if !field.arguments.is_empty() {
s.push('(');
let args: Vec<String> = field.arguments.iter().map(serialize_arg).collect();
s.push_str(&args.join(", "));
s.push(')');
}
if !field.nested_fields.is_empty() {
s.push_str(" { ");
let sub: Vec<String> = field.nested_fields.iter().map(serialize_field).collect();
s.push_str(&sub.join(" "));
s.push_str(" }");
}
s
}
fn serialize_arg(arg: &GraphQLArgument) -> String {
format!("{}: {}", arg.name, arg_value_to_graphql(arg))
}
fn arg_value_to_graphql(arg: &GraphQLArgument) -> String {
match arg.value_type.as_str() {
"variable" => {
serde_json::from_str::<String>(&arg.value_json)
.unwrap_or_else(|_| arg.value_json.clone())
},
"object" => {
serde_json::from_str::<serde_json::Value>(&arg.value_json)
.map_or_else(|_| arg.value_json.clone(), |v| json_value_to_graphql(&v))
},
"enum" => {
serde_json::from_str::<String>(&arg.value_json)
.unwrap_or_else(|_| arg.value_json.clone())
},
_ => arg.value_json.clone(),
}
}
fn json_value_to_graphql(val: &serde_json::Value) -> String {
match val {
serde_json::Value::Object(map) => {
let pairs: Vec<String> =
map.iter().map(|(k, v)| format!("{k}: {}", json_value_to_graphql(v))).collect();
format!("{{{}}}", pairs.join(", "))
},
serde_json::Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(json_value_to_graphql).collect();
format!("[{}]", items.join(", "))
},
serde_json::Value::String(s) => format!("\"{s}\""),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "null".to_string(),
}
}
impl<A: DatabaseAdapter> Executor<A> {
pub async fn execute_parallel(
&self,
parsed: &ParsedQuery,
variables: Option<&serde_json::Value>,
) -> Result<PipelineResult> {
MULTI_ROOT_QUERIES_TOTAL.fetch_add(1, Ordering::Relaxed);
let field_queries: Vec<(String, String)> = parsed
.selections
.iter()
.map(|f| (f.response_key().to_string(), field_selection_to_query(f)))
.collect();
let runners: Vec<_> = field_queries.iter().map(|_| self.query_runner()).collect();
let futs: Vec<_> = runners
.iter()
.zip(field_queries.iter())
.map(|(runner, (_, query))| runner.execute_regular_query(query.as_str(), variables))
.collect();
let results = futures::future::try_join_all(futs).await?;
let fields = results
.into_iter()
.zip(field_queries.iter())
.map(|(response, (field_name, _))| {
let data = response["data"][field_name.as_str()].clone();
Ok(RootFieldResult {
field_name: field_name.clone(),
data,
})
})
.collect::<Result<Vec<_>>>()?;
Ok(PipelineResult {
fields,
parallel: true,
})
}
}