Skip to content

Commit a38c812

Browse files
committed
feat(qp): apply new single/multi typestate and replace input
1 parent 65f43e5 commit a38c812

File tree

12 files changed

+436
-369
lines changed

12 files changed

+436
-369
lines changed

lib/query-planner/src/ast/selection_set.rs

Lines changed: 246 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use std::{
66
hash::Hash,
77
};
88

9-
use crate::utils::pretty_display::{get_indent, PrettyDisplay};
9+
use crate::{
10+
ast::merge_path::{Condition, MergePath, Segment},
11+
utils::pretty_display::{get_indent, PrettyDisplay},
12+
};
1013

1114
use super::{arguments::ArgumentsMap, selection_item::SelectionItem};
1215

@@ -55,10 +58,24 @@ impl Display for SelectionSet {
5558
}
5659

5760
impl SelectionSet {
61+
pub fn cost(&self) -> u64 {
62+
let mut cost = 1;
63+
64+
for node in &self.items {
65+
cost += node.cost();
66+
}
67+
68+
cost
69+
}
70+
5871
pub fn is_empty(&self) -> bool {
5972
self.items.is_empty()
6073
}
6174

75+
pub fn contains(&self, other: &Self) -> bool {
76+
selection_items_are_subset_of(&self.items, &other.items)
77+
}
78+
6279
pub fn variable_usages(&self) -> BTreeSet<String> {
6380
self.items
6481
.iter()
@@ -343,6 +360,234 @@ impl PrettyDisplay for InlineFragmentSelection {
343360
}
344361
}
345362

363+
pub fn selection_items_are_subset_of(source: &[SelectionItem], target: &[SelectionItem]) -> bool {
364+
target.iter().all(|target_node| {
365+
source
366+
.iter()
367+
.any(|source_node| selection_item_is_subset_of(source_node, target_node))
368+
})
369+
}
370+
371+
fn selection_item_is_subset_of(source: &SelectionItem, target: &SelectionItem) -> bool {
372+
match (source, target) {
373+
(SelectionItem::Field(source_field), SelectionItem::Field(target_field)) => {
374+
if source_field.name != target_field.name {
375+
return false;
376+
}
377+
378+
if source_field.is_leaf() != target_field.is_leaf() {
379+
return false;
380+
}
381+
382+
selection_items_are_subset_of(
383+
&source_field.selections.items,
384+
&target_field.selections.items,
385+
)
386+
}
387+
// TODO: support fragments
388+
_ => false,
389+
}
390+
}
391+
392+
pub fn merge_selection_set(target: &mut SelectionSet, source: &SelectionSet, as_first: bool) {
393+
if source.items.is_empty() {
394+
return;
395+
}
396+
397+
let mut pending_items = Vec::with_capacity(source.items.len());
398+
for source_item in source.items.iter() {
399+
let mut found = false;
400+
for target_item in target.items.iter_mut() {
401+
match (source_item, target_item) {
402+
(SelectionItem::Field(source_field), SelectionItem::Field(target_field)) => {
403+
if source_field == target_field {
404+
found = true;
405+
merge_selection_set(
406+
&mut target_field.selections,
407+
&source_field.selections,
408+
as_first,
409+
);
410+
break;
411+
}
412+
}
413+
(
414+
SelectionItem::InlineFragment(source_fragment),
415+
SelectionItem::InlineFragment(target_fragment),
416+
) => {
417+
if source_fragment.type_condition == target_fragment.type_condition {
418+
found = true;
419+
merge_selection_set(
420+
&mut target_fragment.selections,
421+
&source_fragment.selections,
422+
as_first,
423+
);
424+
break;
425+
}
426+
}
427+
_ => {}
428+
}
429+
}
430+
431+
if !found {
432+
pending_items.push(source_item.clone())
433+
}
434+
}
435+
436+
if !pending_items.is_empty() {
437+
if as_first {
438+
let mut new_items = pending_items;
439+
new_items.append(&mut target.items);
440+
target.items = new_items;
441+
} else {
442+
target.items.extend(pending_items);
443+
}
444+
}
445+
}
446+
447+
pub fn find_selection_set_by_path_mut<'a>(
448+
root_selection_set: &'a mut SelectionSet,
449+
path: &MergePath,
450+
) -> Option<&'a mut SelectionSet> {
451+
let mut current_selection_set = root_selection_set;
452+
453+
for path_element in path.inner.iter() {
454+
match path_element {
455+
Segment::List => {
456+
continue;
457+
}
458+
Segment::Cast(type_name, condition) => {
459+
let next_selection_set_option =
460+
current_selection_set
461+
.items
462+
.iter_mut()
463+
.find_map(|item| match item {
464+
SelectionItem::Field(_) => None,
465+
SelectionItem::InlineFragment(f) => {
466+
if f.type_condition.eq(type_name)
467+
&& fragment_condition_equal(condition, f)
468+
{
469+
Some(&mut f.selections)
470+
} else {
471+
None
472+
}
473+
}
474+
SelectionItem::FragmentSpread(_) => None,
475+
});
476+
477+
match next_selection_set_option {
478+
Some(next_set) => {
479+
current_selection_set = next_set;
480+
}
481+
None => {
482+
return None;
483+
}
484+
}
485+
}
486+
Segment::Field(field_name, args_hash, condition) => {
487+
let next_selection_set_option =
488+
current_selection_set
489+
.items
490+
.iter_mut()
491+
.find_map(|item| match item {
492+
SelectionItem::Field(field) => {
493+
if field.selection_identifier() == field_name
494+
&& field.arguments_hash() == *args_hash
495+
&& field_condition_equal(condition, field)
496+
{
497+
Some(&mut field.selections)
498+
} else {
499+
None
500+
}
501+
}
502+
SelectionItem::InlineFragment(..) => None,
503+
SelectionItem::FragmentSpread(_) => None,
504+
});
505+
506+
match next_selection_set_option {
507+
Some(next_set) => {
508+
current_selection_set = next_set;
509+
}
510+
None => {
511+
return None;
512+
}
513+
}
514+
}
515+
}
516+
}
517+
Some(current_selection_set)
518+
}
519+
520+
pub fn field_condition_equal(cond: &Option<Condition>, field: &FieldSelection) -> bool {
521+
match cond {
522+
Some(cond) => match cond {
523+
Condition::Include(var_name) => {
524+
field.include_if.as_ref().is_some_and(|v| v == var_name)
525+
}
526+
Condition::Skip(var_name) => field.skip_if.as_ref().is_some_and(|v| v == var_name),
527+
},
528+
None => field.include_if.is_none() && field.skip_if.is_none(),
529+
}
530+
}
531+
532+
fn fragment_condition_equal(cond: &Option<Condition>, fragment: &InlineFragmentSelection) -> bool {
533+
match cond {
534+
Some(cond) => match cond {
535+
Condition::Include(var_name) => {
536+
fragment.include_if.as_ref().is_some_and(|v| v == var_name)
537+
}
538+
Condition::Skip(var_name) => fragment.skip_if.as_ref().is_some_and(|v| v == var_name),
539+
},
540+
None => fragment.include_if.is_none() && fragment.skip_if.is_none(),
541+
}
542+
}
543+
544+
/// Find the arguments conflicts between two selections.
545+
/// Returns a vector of tuples containing the indices of conflicting fields in both "source" and "other"
546+
/// Both indices are returned in order to allow for easy resolution of conflicts later, in either side.
547+
pub fn find_arguments_conflicts(
548+
source: &SelectionSet,
549+
other: &SelectionSet,
550+
) -> Vec<(usize, usize)> {
551+
other
552+
.items
553+
.iter()
554+
.enumerate()
555+
.filter_map(|(index, other_selection)| {
556+
if let SelectionItem::Field(other_field) = other_selection {
557+
let other_identifier = other_field.selection_identifier();
558+
let other_args_hash = other_field.arguments_hash();
559+
560+
let existing_in_self =
561+
source
562+
.items
563+
.iter()
564+
.enumerate()
565+
.find_map(|(self_index, self_selection)| {
566+
if let SelectionItem::Field(self_field) = self_selection {
567+
// If the field selection identifier matches and the arguments hash is different,
568+
// then it means that we can't merge the two input siblings
569+
if self_field.selection_identifier() == other_identifier
570+
&& self_field.arguments_hash() != other_args_hash
571+
{
572+
return Some(self_index);
573+
}
574+
}
575+
576+
None
577+
});
578+
579+
if let Some(existing_index) = existing_in_self {
580+
return Some((existing_index, index));
581+
}
582+
583+
return None;
584+
}
585+
586+
None
587+
})
588+
.collect()
589+
}
590+
346591
#[cfg(test)]
347592
mod tests {
348593
use crate::ast::value::Value;

0 commit comments

Comments
 (0)