use std::sync::Arc;
use ahash::HashSet;
use hive_router_plan_executor::projection::plan::{FieldProjectionPlan, ProjectionValueSource};
use hive_router_query_planner::ast::{
operation::OperationDefinition,
selection_item::SelectionItem,
selection_set::{FieldSelection, InlineFragmentSelection, SelectionSet},
value::Value,
};
use crate::pipeline::trie::{PathIndex, Trie};
pub(crate) fn rebuild_nulled_operation(
original_operation: &OperationDefinition,
nulled_field_trie: &Trie,
) -> OperationDefinition {
let mut used_variables = HashSet::default();
let selection_set = rebuild_nulled_selection_set(
&original_operation.selection_set,
nulled_field_trie,
PathIndex::root(),
&mut used_variables,
);
let variable_definitions = original_operation
.variable_definitions
.as_ref()
.map(|var_defs| {
var_defs
.iter()
.filter(|var_def| used_variables.contains(&var_def.name))
.cloned()
.collect()
});
OperationDefinition {
name: original_operation.name.clone(),
operation_kind: original_operation.operation_kind.clone(),
selection_set,
variable_definitions,
}
}
fn rebuild_nulled_selection_set(
original_selection_set: &SelectionSet,
nulled_field_trie: &Trie,
path_position: PathIndex,
used_variables: &mut HashSet<String>,
) -> SelectionSet {
if !nulled_field_trie.has_children(path_position) {
collect_variables_recursive(original_selection_set, used_variables);
return original_selection_set.clone();
}
let mut kept_items = Vec::with_capacity(original_selection_set.items.len());
for selection in &original_selection_set.items {
match selection {
SelectionItem::Field(field) => {
let path_segment = field.alias.as_ref().unwrap_or(&field.name);
let Some((child_path_position, is_nulled)) =
nulled_field_trie.find_segment_at_position(path_position, path_segment)
else {
collect_field_own_variables(field, used_variables);
collect_variables_recursive(&field.selections, used_variables);
kept_items.push(selection.clone());
continue;
};
if is_nulled {
continue;
}
let filtered_selections = rebuild_nulled_selection_set(
&field.selections,
nulled_field_trie,
child_path_position,
used_variables,
);
if filtered_selections.is_empty() && !field.selections.is_empty() {
continue;
}
collect_field_own_variables(field, used_variables);
kept_items.push(SelectionItem::Field(
field.with_new_selections(filtered_selections),
));
}
SelectionItem::InlineFragment(fragment) => {
let filtered_selections = rebuild_nulled_selection_set(
&fragment.selections,
nulled_field_trie,
path_position,
used_variables,
);
if filtered_selections.is_empty() && !fragment.selections.is_empty() {
continue;
}
collect_fragment_own_variables(fragment, used_variables);
kept_items.push(SelectionItem::InlineFragment(
fragment.with_new_selections(filtered_selections),
));
}
SelectionItem::FragmentSpread(_) => {
}
}
}
SelectionSet { items: kept_items }
}
pub(crate) fn rebuild_nulled_projection_plan(
original_plans: &Vec<FieldProjectionPlan>,
nulled_field_trie: &Trie,
) -> Vec<FieldProjectionPlan> {
rebuild_nulled_projection_plan_recursive(original_plans, nulled_field_trie, PathIndex::root())
.unwrap_or_default()
}
fn rebuild_nulled_projection_plan_recursive(
original_plans: &Vec<FieldProjectionPlan>,
nulled_field_trie: &Trie,
path_position: PathIndex,
) -> Option<Vec<FieldProjectionPlan>> {
let mut kept_plans = Vec::with_capacity(original_plans.len());
for plan in original_plans {
let path_segment = &plan.response_key;
let Some((child_path_position, is_nulled)) =
nulled_field_trie.find_segment_at_position(path_position, path_segment)
else {
kept_plans.push(plan.clone());
continue;
};
if is_nulled {
kept_plans.push(plan.with_new_value(ProjectionValueSource::Null));
continue;
}
let new_value = match &plan.value {
ProjectionValueSource::ResponseData {
selections: Some(selections),
} => ProjectionValueSource::ResponseData {
selections: rebuild_nulled_projection_plan_recursive(
selections,
nulled_field_trie,
child_path_position,
)
.map(Arc::new),
},
other => other.clone(),
};
kept_plans.push(plan.with_new_value(new_value));
}
if kept_plans.is_empty() {
None
} else {
Some(kept_plans)
}
}
fn collect_variables_recursive(selection_set: &SelectionSet, used_variables: &mut HashSet<String>) {
for item in &selection_set.items {
match item {
SelectionItem::Field(field) => {
collect_field_own_variables(field, used_variables);
collect_variables_recursive(&field.selections, used_variables);
}
SelectionItem::InlineFragment(fragment) => {
collect_fragment_own_variables(fragment, used_variables);
collect_variables_recursive(&fragment.selections, used_variables);
}
SelectionItem::FragmentSpread(_) => {
}
}
}
}
fn collect_field_own_variables(field: &FieldSelection, used_variables: &mut HashSet<String>) {
if let Some(args) = &field.arguments {
for arg in args.values() {
collect_variables_from_value(arg, used_variables);
}
}
if let Some(var_name) = &field.skip_if {
used_variables.insert(var_name.clone());
}
if let Some(var_name) = &field.include_if {
used_variables.insert(var_name.clone());
}
}
fn collect_fragment_own_variables(
fragment: &InlineFragmentSelection,
used_variables: &mut HashSet<String>,
) {
if let Some(var_name) = &fragment.skip_if {
used_variables.insert(var_name.clone());
}
if let Some(var_name) = &fragment.include_if {
used_variables.insert(var_name.clone());
}
}
fn collect_variables_from_value(value: &Value, used_variables: &mut HashSet<String>) {
match value {
Value::Variable(var_name) => {
used_variables.insert(var_name.clone());
}
Value::List(items) => {
for item in items {
collect_variables_from_value(item, used_variables);
}
}
Value::Object(fields) => {
for val in fields.values() {
collect_variables_from_value(val, used_variables);
}
}
Value::Null
| Value::Int(_)
| Value::Float(_)
| Value::String(_)
| Value::Boolean(_)
| Value::Enum(_) => {
}
}
}