1use ahash::HashSet;
2use indexmap::IndexMap;
3use std::fmt::{Display, Formatter as FmtFormatter, Result as FmtResult};
4use std::sync::Arc;
5use tracing::warn;
6
7use hive_router_query_planner::{
8 ast::{
9 operation::OperationDefinition,
10 selection_item::SelectionItem,
11 selection_set::{FieldSelection, InlineFragmentSelection, SelectionSet},
12 },
13 state::supergraph_state::OperationKind,
14 utils::pretty_display::{get_indent, PrettyDisplay},
15};
16
17use crate::projection::error::ProjectionError;
18use crate::{introspection::schema::SchemaMetadata, utils::consts::TYPENAME_FIELD_NAME};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum TypeCondition {
22 Exact(String),
23 OneOf(HashSet<String>),
24}
25
26impl TypeCondition {
27 pub fn matches(&self, type_name: &str) -> bool {
28 match self {
29 TypeCondition::Exact(expected) => type_name == expected,
30 TypeCondition::OneOf(possible) => possible.contains(type_name),
31 }
32 }
33
34 pub fn union(self, other: TypeCondition) -> TypeCondition {
35 use TypeCondition::*;
36 match (self, other) {
37 (Exact(left), Exact(right)) => {
38 if left == right {
39 Exact(left)
41 } else {
42 OneOf(HashSet::from_iter(vec![left, right]))
44 }
45 }
46 (OneOf(mut types), Exact(exact)) | (Exact(exact), OneOf(mut types)) => {
47 types.insert(exact);
50 OneOf(types)
51 }
52 (OneOf(mut left), OneOf(right)) => {
53 left.extend(right);
55 OneOf(left)
56 }
57 }
58 }
59
60 pub fn intersect(self, other: TypeCondition) -> TypeCondition {
61 use TypeCondition::*;
62 match (self, other) {
63 (Exact(left), Exact(right)) => {
64 if left == right {
65 Exact(left)
67 } else {
68 OneOf(HashSet::default())
72 }
73 }
74 (OneOf(types), Exact(exact)) | (Exact(exact), OneOf(types)) => {
75 if types.contains(&exact) {
76 Exact(exact)
78 } else {
79 OneOf(HashSet::default())
83 }
84 }
85 (OneOf(mut left), OneOf(right)) => {
86 left.retain(|t| right.contains(t));
87 if left.len() == 1 {
88 Exact(left.into_iter().next().expect("Set has one element"))
90 } else {
91 OneOf(left)
94 }
95 }
96 }
97 }
98}
99
100#[derive(Debug, Clone)]
101pub enum ProjectionValueSource {
102 ResponseData {
104 selections: Option<Arc<Vec<FieldProjectionPlan>>>,
105 },
106 Null,
108}
109
110#[derive(Debug, Clone)]
111pub struct FieldProjectionPlan {
112 pub field_name: String,
113 pub response_key: String,
114 pub is_typename: bool,
115 pub parent_type_guard: Option<TypeCondition>,
120 pub conditions: Option<FieldProjectionCondition>,
121 pub value: ProjectionValueSource,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum FieldProjectionCondition {
126 IncludeIfVariable(String),
127 SkipIfVariable(String),
128 ParentTypeCondition(TypeCondition),
129 FieldTypeCondition(TypeCondition),
130 EnumValuesCondition(HashSet<String>),
131 Or(Box<FieldProjectionCondition>, Box<FieldProjectionCondition>),
132 And(Box<FieldProjectionCondition>, Box<FieldProjectionCondition>),
133}
134
135pub enum FieldProjectionConditionError {
136 InvalidParentType,
137 InvalidFieldType,
138 Skip,
139 InvalidEnumValue,
140 Fatal(ProjectionError),
141}
142
143impl From<ProjectionError> for FieldProjectionConditionError {
144 fn from(err: ProjectionError) -> Self {
145 FieldProjectionConditionError::Fatal(err)
146 }
147}
148
149impl FieldProjectionCondition {
150 pub fn and(&self, right: FieldProjectionCondition) -> FieldProjectionCondition {
152 use FieldProjectionCondition::*;
153
154 match (self, right) {
155 (ParentTypeCondition(left), ParentTypeCondition(right)) => {
156 ParentTypeCondition(left.clone().intersect(right))
157 }
158 (FieldTypeCondition(left), FieldTypeCondition(right)) => {
159 FieldTypeCondition(left.clone().intersect(right))
160 }
161 (EnumValuesCondition(left), EnumValuesCondition(right)) => {
162 let mut left = left.clone();
163 left.retain(|v| right.contains(v));
164 EnumValuesCondition(left)
165 }
166 (left, right) => And(Box::new(left.clone()), Box::new(right)),
167 }
168 }
169
170 pub fn or(&self, right: FieldProjectionCondition) -> FieldProjectionCondition {
175 use FieldProjectionCondition::*;
176
177 if self == &right {
179 return self.clone();
180 }
181
182 match (self, right) {
183 (ParentTypeCondition(left), ParentTypeCondition(right)) => {
184 ParentTypeCondition(left.clone().union(right))
185 }
186 (FieldTypeCondition(left), FieldTypeCondition(right)) => {
187 FieldTypeCondition(left.clone().union(right))
188 }
189 (EnumValuesCondition(left), EnumValuesCondition(right)) => {
190 let mut result = left.clone();
191 result.extend(right);
192 EnumValuesCondition(result)
193 }
194 (left, right) => Or(Box::new(left.clone()), Box::new(right)),
195 }
196 }
197}
198
199impl FieldProjectionPlan {
200 pub fn from_operation(
201 operation: &OperationDefinition,
202 schema_metadata: &SchemaMetadata,
203 ) -> (&'static str, Vec<FieldProjectionPlan>) {
204 let root_type_name = match operation.operation_kind {
205 Some(OperationKind::Query) => "Query",
206 Some(OperationKind::Mutation) => "Mutation",
207 Some(OperationKind::Subscription) => "Subscription",
208 None => "Query",
209 };
210
211 let mut plans = Self::from_selection_set(
212 &operation.selection_set,
213 schema_metadata,
214 root_type_name,
215 &None,
216 )
217 .unwrap_or_default();
218
219 for plan in &mut plans {
220 Self::remove_redundant_child_guards(plan, schema_metadata);
221 }
222
223 (root_type_name, plans)
224 }
225
226 fn from_selection_set(
227 selection_set: &SelectionSet,
228 schema_metadata: &SchemaMetadata,
229 parent_type_name: &str,
230 parent_condition: &Option<FieldProjectionCondition>,
231 ) -> Option<Vec<FieldProjectionPlan>> {
232 let mut field_selections: IndexMap<String, FieldProjectionPlan> = IndexMap::new();
233
234 for selection_item in &selection_set.items {
235 match selection_item {
236 SelectionItem::Field(field) => {
237 Self::process_field(
238 field,
239 &mut field_selections,
240 schema_metadata,
241 parent_type_name,
242 parent_condition,
243 );
244 }
245 SelectionItem::InlineFragment(inline_fragment) => {
246 Self::process_inline_fragment(
247 inline_fragment,
248 &mut field_selections,
249 schema_metadata,
250 parent_condition,
251 );
252 }
253 SelectionItem::FragmentSpread(_) => {
254 unreachable!(
256 "Fragment spreads should not exist in the final response projection."
257 );
258 }
259 }
260 }
261
262 if field_selections.is_empty() {
263 None
264 } else {
265 Some(field_selections.into_values().collect())
266 }
267 }
268
269 fn type_names_from(condition: &TypeCondition) -> Vec<&str> {
271 match condition {
272 TypeCondition::Exact(ty) => vec![ty.as_str()],
273 TypeCondition::OneOf(types) => types.iter().map(|s| s.as_str()).collect(),
274 }
275 }
276
277 fn remove_redundant_child_guards(
289 parent_field: &mut FieldProjectionPlan,
290 schema_metadata: &SchemaMetadata,
291 ) {
292 let ProjectionValueSource::ResponseData { selections } = &mut parent_field.value else {
293 return;
294 };
295
296 let Some(selections_arc) = selections else {
297 return;
298 };
299
300 let selections_mut = Arc::make_mut(selections_arc);
301
302 let possible_child_types = parent_field.parent_type_guard.as_ref().map(|parent_guard| {
304 let parent_types = Self::type_names_from(parent_guard);
305
306 HashSet::from_iter(parent_types.iter().filter_map(|parent_type| {
308 schema_metadata
309 .type_fields
310 .get(*parent_type)
311 .and_then(|fields| fields.get(&parent_field.field_name))
312 .map(|field_info| field_info.output_type_name.as_str())
313 }))
314 });
315
316 for child in selections_mut {
317 if let (Some(child_guard), Some(possible_types)) =
319 (&child.parent_type_guard, &possible_child_types)
320 {
321 let is_redundant = match child_guard {
323 TypeCondition::Exact(ty) => {
324 possible_types.len() == 1 && possible_types.contains(ty.as_str())
325 }
326 TypeCondition::OneOf(guard_types) => {
327 guard_types.len() == possible_types.len()
328 && guard_types
329 .iter()
330 .all(|t| possible_types.contains(t.as_str()))
331 }
332 };
333
334 if is_redundant {
335 child.parent_type_guard = None;
336 }
337 }
338
339 Self::remove_redundant_child_guards(child, schema_metadata);
340 }
341 }
342
343 fn apply_directive_conditions(
344 condition: Option<FieldProjectionCondition>,
345 include_if: &Option<String>,
346 skip_if: &Option<String>,
347 ) -> Option<FieldProjectionCondition> {
348 let mut condition = condition;
349 if let Some(include_if_var) = include_if {
350 condition = Self::and_optional(
351 condition,
352 Some(FieldProjectionCondition::IncludeIfVariable(
353 include_if_var.clone(),
354 )),
355 );
356 }
357 if let Some(skip_if_var) = skip_if {
358 condition = Self::and_optional(
359 condition,
360 Some(FieldProjectionCondition::SkipIfVariable(
361 skip_if_var.clone(),
362 )),
363 );
364 }
365 condition
366 }
367
368 fn combine_optional<T, F>(left: Option<T>, right: Option<T>, combiner: F) -> Option<T>
369 where
370 F: FnOnce(T, T) -> T,
371 {
372 match (left, right) {
373 (None, None) => None,
374 (Some(c), None) | (None, Some(c)) => Some(c),
375 (Some(l), Some(r)) => Some(combiner(l, r)),
376 }
377 }
378
379 fn or_optional(
381 left: Option<FieldProjectionCondition>,
382 right: Option<FieldProjectionCondition>,
383 ) -> Option<FieldProjectionCondition> {
384 Self::combine_optional(left, right, |l, r| l.or(r))
385 }
386
387 fn and_optional(
389 left: Option<FieldProjectionCondition>,
390 right: Option<FieldProjectionCondition>,
391 ) -> Option<FieldProjectionCondition> {
392 Self::combine_optional(left, right, |l, r| l.and(r))
393 }
394
395 fn merge_conditions(
399 left_guard: &Option<TypeCondition>,
400 left_condition: Option<FieldProjectionCondition>,
401 right_guard: &Option<TypeCondition>,
402 right_condition: Option<FieldProjectionCondition>,
403 ) -> Option<FieldProjectionCondition> {
404 match (left_guard, right_guard, left_condition, right_condition) {
405 (_, _, None, None) => None,
407
408 (Some(lg), Some(rg), lc, rc) if lg != rg => {
411 Some(Self::condition_with_guard(lg, lc).or(Self::condition_with_guard(rg, rc)))
412 }
413
414 (_, _, lc, rc) => Self::or_optional(lc, rc),
421 }
422 }
423
424 fn condition_with_guard(
431 guard: &TypeCondition,
432 condition: Option<FieldProjectionCondition>,
433 ) -> FieldProjectionCondition {
434 let parent_check = FieldProjectionCondition::ParentTypeCondition(guard.clone());
435 match condition {
436 Some(cond) => parent_check.and(cond),
437 None => parent_check,
438 }
439 }
440
441 fn merge_plan(
447 field_selections: &mut IndexMap<String, FieldProjectionPlan>,
448 plan_to_merge: FieldProjectionPlan,
449 ) {
450 let Some(existing_plan) = field_selections.get_mut(&plan_to_merge.response_key) else {
451 field_selections.insert(plan_to_merge.response_key.clone(), plan_to_merge);
453 return;
454 };
455
456 let existing_guard = existing_plan.parent_type_guard.clone();
458 let new_guard = plan_to_merge.parent_type_guard.clone();
459
460 existing_plan.parent_type_guard = match (
464 existing_plan.parent_type_guard.take(),
465 plan_to_merge.parent_type_guard,
466 ) {
467 (None, _) | (_, None) => None, (Some(left), Some(right)) => Some(left.union(right)),
469 };
470
471 existing_plan.conditions = Self::merge_conditions(
472 &existing_guard,
473 existing_plan.conditions.take(),
474 &new_guard,
475 plan_to_merge.conditions,
476 );
477
478 match (&mut existing_plan.value, plan_to_merge.value) {
479 (
480 ProjectionValueSource::ResponseData {
481 selections: existing_selections,
482 },
483 ProjectionValueSource::ResponseData {
484 selections: new_selections,
485 },
486 ) => {
487 if let Some(new_selections) = new_selections {
488 match existing_selections {
489 Some(selections) => {
490 let selections_mut = Arc::make_mut(selections);
491 let new_selections_vec = Arc::try_unwrap(new_selections)
492 .unwrap_or_else(|arc| (*arc).clone());
493
494 let mut selections_map: IndexMap<String, FieldProjectionPlan> =
496 selections_mut
497 .drain(..)
498 .map(|plan| (plan.response_key.clone(), plan))
499 .collect();
500
501 for new_plan in new_selections_vec {
503 Self::merge_plan(&mut selections_map, new_plan);
504 }
505
506 selections_mut.extend(selections_map.into_values());
508 }
509 None => *existing_selections = Some(new_selections),
510 }
511 }
512 }
513 (ProjectionValueSource::Null, ProjectionValueSource::Null) => {
514 }
516 _ => {
517 warn!("Merging plans with `Null` value source is not supported during initial plan construction.");
521 existing_plan.value = ProjectionValueSource::Null;
522 }
523 }
524 }
525
526 fn simplify_condition(
527 condition: FieldProjectionCondition,
528 parent_type_guard: &Option<TypeCondition>,
529 ) -> Option<FieldProjectionCondition> {
530 let Some(TypeCondition::Exact(guard_type)) = parent_type_guard else {
531 return Some(condition);
532 };
533
534 match condition {
535 FieldProjectionCondition::ParentTypeCondition(TypeCondition::Exact(cond_type))
537 if &cond_type == guard_type =>
538 {
539 None
540 }
541
542 FieldProjectionCondition::ParentTypeCondition(TypeCondition::OneOf(types))
544 if types.len() == 1
545 && types.iter().next().map(|t| t.as_str()) == Some(guard_type) =>
546 {
547 None
548 }
549
550 FieldProjectionCondition::And(left, right) => {
552 let left_simplified = Self::simplify_condition(*left, parent_type_guard);
553 let right_simplified = Self::simplify_condition(*right, parent_type_guard);
554
555 match (left_simplified, right_simplified) {
556 (None, None) => None,
557 (Some(cond), None) | (None, Some(cond)) => Some(cond),
558 (Some(l), Some(r)) => Some(l.and(r)),
559 }
560 }
561
562 FieldProjectionCondition::Or(left, right) => {
564 let left_simplified = Self::simplify_condition(*left, parent_type_guard);
565 let right_simplified = Self::simplify_condition(*right, parent_type_guard);
566
567 match (left_simplified, right_simplified) {
568 (None, None) => None,
569 (Some(cond), None) | (None, Some(cond)) => Some(cond),
570 (Some(l), Some(r)) => Some(l.or(r)),
571 }
572 }
573
574 other => Some(other),
576 }
577 }
578
579 fn process_field(
580 field: &FieldSelection,
581 field_selections: &mut IndexMap<String, FieldProjectionPlan>,
582 schema_metadata: &SchemaMetadata,
583 parent_type_name: &str,
584 parent_condition: &Option<FieldProjectionCondition>,
585 ) {
586 let field_name = &field.name;
587 let response_key = field.alias.as_ref().unwrap_or(field_name).clone();
588
589 let field_type = if field_name == TYPENAME_FIELD_NAME {
590 "String".to_string()
591 } else {
592 let field_map = match schema_metadata.type_fields.get(parent_type_name) {
593 Some(fields) => fields,
594 None => {
595 warn!(
596 "No fields found for type `{}` in schema metadata.",
597 parent_type_name
598 );
599 return;
600 }
601 };
602 match field_map.get(field_name) {
603 Some(f) => f.output_type_name.clone(),
604 None => {
605 warn!(
606 "Field `{}` not found in type `{}` in schema metadata.",
607 field_name, parent_type_name
608 );
609 return;
610 }
611 }
612 };
613
614 let type_condition = if schema_metadata.is_object_type(&field_type)
615 || schema_metadata.is_scalar_type(&field_type)
616 {
617 TypeCondition::Exact(field_type.to_string())
618 } else {
619 TypeCondition::OneOf(
620 schema_metadata
621 .possible_types
622 .get_possible_types(&field_type),
623 )
624 };
625
626 let parent_type_guard = parent_condition.as_ref().and_then(Self::get_type_guard);
627 let conditions_for_selections = Self::apply_directive_conditions(
628 parent_type_guard
629 .as_ref()
630 .map(|_| FieldProjectionCondition::ParentTypeCondition(type_condition.clone())),
631 &field.include_if,
632 &field.skip_if,
633 );
634
635 let mut condition_for_field = if schema_metadata.is_union_type(&field_type)
636 || schema_metadata.is_interface_type(&field_type)
637 {
638 Self::and_optional(
639 parent_condition.clone(),
640 Some(FieldProjectionCondition::FieldTypeCondition(type_condition)),
641 )
642 } else {
643 parent_condition.clone()
646 };
647 condition_for_field = Self::apply_directive_conditions(
648 condition_for_field,
649 &field.include_if,
650 &field.skip_if,
651 );
652
653 if let Some(enum_values) = schema_metadata.enum_values.get(&field_type) {
654 condition_for_field = Self::and_optional(
655 condition_for_field,
656 Some(FieldProjectionCondition::EnumValuesCondition(
657 enum_values.clone(),
658 )),
659 );
660 }
661
662 let final_conditions =
663 condition_for_field.and_then(|cond| Self::simplify_condition(cond, &parent_type_guard));
664
665 let new_plan = FieldProjectionPlan {
666 field_name: field_name.to_string(),
667 response_key,
668 parent_type_guard,
669 is_typename: field_name == TYPENAME_FIELD_NAME,
670 conditions: final_conditions,
671 value: ProjectionValueSource::ResponseData {
672 selections: Self::from_selection_set(
673 &field.selections,
674 schema_metadata,
675 &field_type,
676 &conditions_for_selections,
677 )
678 .map(Arc::new),
679 },
680 };
681
682 Self::merge_plan(field_selections, new_plan);
683 }
684
685 fn process_inline_fragment(
686 inline_fragment: &InlineFragmentSelection,
687 field_selections: &mut IndexMap<String, FieldProjectionPlan>,
688 schema_metadata: &SchemaMetadata,
689 parent_condition: &Option<FieldProjectionCondition>,
690 ) {
691 let inline_fragment_type = &inline_fragment.type_condition;
692 let type_condition = if schema_metadata.is_object_type(inline_fragment_type) {
693 TypeCondition::Exact(inline_fragment_type.to_string())
694 } else {
695 TypeCondition::OneOf(
696 schema_metadata
697 .possible_types
698 .get_possible_types(inline_fragment_type),
699 )
700 };
701
702 let mut condition_for_fragment = Self::and_optional(
703 parent_condition.clone(),
704 Some(FieldProjectionCondition::ParentTypeCondition(
705 type_condition.clone(),
706 )),
707 );
708
709 condition_for_fragment = Self::apply_directive_conditions(
710 condition_for_fragment,
711 &inline_fragment.include_if,
712 &inline_fragment.skip_if,
713 );
714
715 if let Some(mut inline_fragment_selections) = Self::from_selection_set(
716 &inline_fragment.selections,
717 schema_metadata,
718 inline_fragment_type,
719 &condition_for_fragment,
720 ) {
721 for selection in &mut inline_fragment_selections {
723 selection.parent_type_guard = Some(type_condition.clone());
724 }
725
726 for selection in inline_fragment_selections {
727 Self::merge_plan(field_selections, selection);
728 }
729 }
730 }
731
732 pub fn with_new_value(&self, new_value: ProjectionValueSource) -> FieldProjectionPlan {
733 FieldProjectionPlan {
734 field_name: self.field_name.clone(),
735 response_key: self.response_key.clone(),
736 parent_type_guard: self.parent_type_guard.clone(),
737 conditions: self.conditions.clone(),
738 is_typename: self.is_typename,
739 value: new_value,
740 }
741 }
742
743 fn get_type_guard(condition: &FieldProjectionCondition) -> Option<TypeCondition> {
744 match condition {
745 FieldProjectionCondition::ParentTypeCondition(tc) => Some(tc.clone()),
746 FieldProjectionCondition::And(a, b) => Self::combine_optional(
747 Self::get_type_guard(a),
748 Self::get_type_guard(b),
749 |ga, gb| ga.intersect(gb),
750 ),
751 FieldProjectionCondition::Or(a, b) => Self::combine_optional(
752 Self::get_type_guard(a),
753 Self::get_type_guard(b),
754 |ga, gb| ga.union(gb),
755 ),
756 _ => None,
757 }
758 }
759}
760
761impl Display for TypeCondition {
762 fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
763 match self {
764 TypeCondition::Exact(type_name) => write!(f, "Exact({})", type_name),
765 TypeCondition::OneOf(types) => {
766 write!(f, "OneOf(")?;
767 let types_vec: Vec<_> = types.iter().collect();
768 for (i, type_name) in types_vec.iter().enumerate() {
769 if i > 0 {
770 write!(f, ", ")?;
771 }
772 write!(f, "{}", type_name)?;
773 }
774 write!(f, ")")
775 }
776 }
777 }
778}
779
780impl Display for FieldProjectionCondition {
781 fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
782 match self {
783 FieldProjectionCondition::IncludeIfVariable(var) => {
784 write!(f, "Include(if: ${})", var)
785 }
786 FieldProjectionCondition::SkipIfVariable(var) => {
787 write!(f, "Skip(if: ${})", var)
788 }
789 FieldProjectionCondition::ParentTypeCondition(tc) => {
790 write!(f, "ParentType({})", tc)
791 }
792 FieldProjectionCondition::FieldTypeCondition(tc) => {
793 write!(f, "FieldType({})", tc)
794 }
795 FieldProjectionCondition::EnumValuesCondition(values) => {
796 write!(f, "EnumValues(")?;
797 let values_vec: Vec<_> = values.iter().collect();
798 for (i, value) in values_vec.iter().enumerate() {
799 if i > 0 {
800 write!(f, ", ")?;
801 }
802 write!(f, "{}", value)?;
803 }
804 write!(f, ")")
805 }
806 FieldProjectionCondition::Or(left, right) => {
807 write!(f, "({} OR {})", left, right)
808 }
809 FieldProjectionCondition::And(left, right) => {
810 write!(f, "({} AND {})", left, right)
811 }
812 }
813 }
814}
815
816impl Display for FieldProjectionPlan {
817 fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
818 self.pretty_fmt(f, 0)
819 }
820}
821
822impl PrettyDisplay for FieldProjectionPlan {
823 fn pretty_fmt(&self, f: &mut FmtFormatter<'_>, depth: usize) -> FmtResult {
824 let indent = get_indent(depth);
825
826 if self.response_key == self.field_name {
827 writeln!(f, "{}{}: {{", indent, self.response_key)?;
828 } else {
829 writeln!(
830 f,
831 "{}{} (alias for {}) {{",
832 indent, self.response_key, self.field_name
833 )?;
834 }
835
836 if let Some(parent_type_guard) = self.parent_type_guard.as_ref() {
837 writeln!(f, "{} type guard: {}", indent, parent_type_guard)?;
838 }
839
840 if let Some(conditions) = self.conditions.as_ref() {
841 writeln!(f, "{} conditions: {}", indent, conditions)?;
842 }
843
844 match &self.value {
845 ProjectionValueSource::ResponseData { selections } => {
846 if let Some(selections) = selections {
847 writeln!(f, "{} selections:", indent)?;
848 for selection in selections.iter() {
849 selection.pretty_fmt(f, depth + 2)?;
850 }
851 }
852 }
853 ProjectionValueSource::Null => {
854 writeln!(f, "{} value: Null", indent)?;
855 }
856 }
857
858 writeln!(f, "{}}}", indent)?;
859 Ok(())
860 }
861}