use std::sync::atomic::{AtomicU64, Ordering};
use super::super::{Executor, root_type_name};
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>,
security_context: Option<&crate::security::SecurityContext>,
) -> Result<PipelineResult> {
MULTI_ROOT_QUERIES_TOTAL.fetch_add(1, Ordering::Relaxed);
let root_type = root_type_name(&parsed.operation_type);
let dispatched: Vec<(usize, String, String)> = parsed
.selections
.iter()
.enumerate()
.filter(|(_, f)| f.name != "__typename")
.map(|(i, f)| (i, f.response_key().to_string(), field_selection_to_query(f)))
.collect();
let runners: Vec<_> = dispatched.iter().map(|_| self.query_runner()).collect();
let futs: Vec<_> = runners
.iter()
.zip(dispatched.iter())
.map(|(runner, (_, _, query))| {
runner.execute_regular_query_maybe_security(
query.as_str(),
variables,
security_context,
)
})
.collect();
let results = futures::future::try_join_all(futs).await?;
let mut dispatched_data: std::collections::HashMap<usize, serde_json::Value> = results
.into_iter()
.zip(dispatched.iter())
.map(|(response, (index, key, _))| (*index, response["data"][key.as_str()].clone()))
.collect();
let fields = parsed
.selections
.iter()
.enumerate()
.map(|(index, f)| {
let field_name = f.response_key().to_string();
let data = if f.name == "__typename" {
serde_json::Value::String(root_type.to_string())
} else {
dispatched_data.remove(&index).unwrap_or(serde_json::Value::Null)
};
RootFieldResult { field_name, data }
})
.collect();
Ok(PipelineResult {
fields,
parallel: true,
})
}
}