1use std::borrow::Cow;
2use std::collections::HashSet;
3use std::{
4 collections::BTreeMap,
5 ops::{Bound, RangeBounds},
6 sync::Arc,
7};
8
9use crate::{
10 interpreter::InterpretedQuery,
11 ir::{
12 Argument, FieldRef, FieldValue, IREdge, IRFold, IRQueryComponent, IRVertex, LocalField,
13 Operation, Vid,
14 },
15};
16
17use super::{dynamic::DynamicallyResolvedValue, CandidateValue, EdgeInfo, Range};
18
19#[non_exhaustive]
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub struct RequiredProperty {
23 pub name: Arc<str>,
24}
25
26impl RequiredProperty {
27 pub fn new(name: Arc<str>) -> Self {
28 Self { name }
29 }
30}
31
32#[cfg_attr(docsrs, doc(notable_trait))]
34pub trait VertexInfo: super::sealed::__Sealed {
35 fn vid(&self) -> Vid;
37
38 fn coerced_to_type(&self) -> Option<&Arc<str>>;
40
41 fn required_properties(&self) -> Box<dyn Iterator<Item = RequiredProperty> + '_>;
51
52 fn statically_required_property(&self, name: &str) -> Option<CandidateValue<FieldValue>>;
66
67 fn dynamically_required_property(&self, name: &str) -> Option<DynamicallyResolvedValue<'_>>;
86
87 fn first_mandatory_edge(&self, name: &str) -> Option<EdgeInfo>;
96
97 fn first_edge(&self, name: &str) -> Option<EdgeInfo>;
101
102 fn edges_with_name<'a>(&'a self, name: &'a str) -> Box<dyn Iterator<Item = EdgeInfo> + 'a>;
107
108 fn mandatory_edges_with_name<'a>(
114 &'a self,
115 name: &'a str,
116 ) -> Box<dyn Iterator<Item = EdgeInfo> + 'a>;
117}
118
119pub(super) trait InternalVertexInfo: super::sealed::__Sealed {
120 fn query(&self) -> &InterpretedQuery;
121
122 fn non_binding_filters(&self) -> bool;
123
124 fn execution_frontier(&self) -> Bound<Vid>;
130
131 fn current_vertex(&self) -> &IRVertex;
133
134 fn current_component(&self) -> &IRQueryComponent;
136
137 fn starting_component(&self) -> &IRQueryComponent;
140
141 fn query_variables(&self) -> &BTreeMap<Arc<str>, FieldValue>;
142
143 fn make_non_folded_edge_info(&self, edge: &IREdge) -> EdgeInfo;
144
145 fn make_folded_edge_info(&self, fold: &IRFold) -> EdgeInfo;
146}
147
148impl<T: InternalVertexInfo + super::sealed::__Sealed> VertexInfo for T {
149 fn vid(&self) -> Vid {
150 self.current_vertex().vid
151 }
152
153 fn coerced_to_type(&self) -> Option<&Arc<str>> {
154 let vertex = self.current_vertex();
155 if vertex.coerced_from_type.is_some() {
156 Some(&vertex.type_name)
157 } else {
158 None
159 }
160 }
161
162 fn required_properties(&self) -> Box<dyn Iterator<Item = RequiredProperty> + '_> {
163 let current_component = self.current_component();
164
165 let current_vertex = self.current_vertex();
166
167 let properties = current_component
168 .outputs
169 .values()
170 .filter(|c| c.vertex_id == current_vertex.vid)
171 .map(|c| RequiredProperty::new(c.field_name.clone()));
172
173 let properties = properties.chain(
174 current_vertex
175 .filters
176 .iter()
177 .map(|f| RequiredProperty::new(f.left().field_name.clone())),
178 );
179
180 let properties = properties.chain(current_component.vertices.values().flat_map(|v| {
181 v.filters
182 .iter()
183 .filter_map(|f| match f.right() {
184 Some(Argument::Tag(FieldRef::ContextField(ctx))) => {
185 if current_vertex.vid == ctx.vertex_id {
186 Some(ctx.field_name.clone())
187 } else {
188 None
189 }
190 }
191 _ => None,
192 })
193 .map(RequiredProperty::new)
194 }));
195
196 let mut seen_property = HashSet::new();
197 Box::new(properties.filter(move |r| seen_property.insert(r.name.clone())))
198 }
199
200 fn statically_required_property(&self, property: &str) -> Option<CandidateValue<FieldValue>> {
201 if self.non_binding_filters() {
202 return None;
211 }
212
213 let query_variables = self.query_variables();
214
215 let mut relevant_filters = filters_on_local_property(self.current_vertex(), property)
219 .filter(|op| {
220 matches!(op.right(), None | Some(Argument::Variable(..)))
223 })
224 .peekable();
225
226 let field = relevant_filters.peek()?.left();
228
229 let candidate =
230 compute_statically_known_candidate(field, relevant_filters, query_variables)
231 .map(|x| x.into_owned());
232 debug_assert!(
233 candidate.clone().unwrap_or(CandidateValue::All) != CandidateValue::Range(Range::full()),
235 "caught returning a range variant with a completely unrestricted range; it should have been CandidateValue::All instead"
236 );
237
238 candidate
239 }
240
241 fn dynamically_required_property(
242 &self,
243 property: &str,
244 ) -> Option<DynamicallyResolvedValue<'_>> {
245 if self.non_binding_filters() {
246 return None;
255 }
256
257 let resolved_range = (Bound::Unbounded, self.execution_frontier());
264 let relevant_filters: Vec<_> = filters_on_local_property(self.current_vertex(), property)
265 .filter(|op| {
266 matches!(
267 op,
268 Operation::Equals(..)
269 | Operation::NotEquals(..)
270 | Operation::LessThan(..)
271 | Operation::LessThanOrEqual(..)
272 | Operation::GreaterThan(..)
273 | Operation::GreaterThanOrEqual(..)
274 | Operation::OneOf(..)
275 ) && match op.right() {
276 Some(Argument::Tag(FieldRef::ContextField(ctx))) => {
277 resolved_range.contains(&ctx.vertex_id)
279 }
280 Some(Argument::Tag(FieldRef::FoldSpecificField(fsf))) => {
281 resolved_range.contains(&fsf.fold_root_vid)
283 }
284 _ => false,
285 }
286 })
287 .collect();
288
289 let first_filter = relevant_filters.first()?;
291
292 let initial_candidate = self.statically_required_property(property).unwrap_or_else(|| {
293 if first_filter.left().field_type.nullable() {
294 CandidateValue::All
295 } else {
296 CandidateValue::Range(Range::full_non_null())
297 }
298 });
299
300 let filter_to_use = {
310 relevant_filters.iter().find(|op| matches!(op, Operation::Equals(..))).unwrap_or_else(
311 || {
312 relevant_filters
313 .iter()
314 .find(|op| matches!(op, Operation::OneOf(..)))
315 .unwrap_or_else(|| {
316 relevant_filters
317 .iter()
318 .find(|op| {
319 matches!(
320 op,
321 Operation::LessThan(..)
322 | Operation::LessThanOrEqual(..)
323 | Operation::GreaterThan(..)
324 | Operation::GreaterThanOrEqual(..)
325 )
326 })
327 .unwrap_or(first_filter)
328 })
329 },
330 )
331 };
332
333 let field = filter_to_use
334 .right()
335 .expect("filter did not have an operand")
336 .as_tag()
337 .expect("operand was not a tag");
338 let bare_operation = filter_to_use
339 .try_map(|_| Ok::<(), ()>(()), |_| Ok(()))
340 .expect("removing operands failed");
341 Some(DynamicallyResolvedValue::new(
342 self.query().clone(),
343 self.starting_component(),
344 field,
345 bare_operation,
346 initial_candidate,
347 ))
348 }
349
350 fn edges_with_name<'a>(&'a self, name: &'a str) -> Box<dyn Iterator<Item = EdgeInfo> + 'a> {
351 let component = self.current_component();
352 let current_vid = self.current_vertex().vid;
353
354 let non_folded_edges = component
355 .edges
356 .values()
357 .filter(move |edge| edge.from_vid == current_vid && edge.edge_name.as_ref() == name)
358 .map(|edge| self.make_non_folded_edge_info(edge.as_ref()));
359 let folded_edges = component
360 .folds
361 .values()
362 .filter(move |fold| fold.from_vid == current_vid && fold.edge_name.as_ref() == name)
363 .map(|fold| self.make_folded_edge_info(fold.as_ref()));
364
365 Box::new(non_folded_edges.chain(folded_edges))
366 }
367
368 fn mandatory_edges_with_name<'a>(
369 &'a self,
370 name: &'a str,
371 ) -> Box<dyn Iterator<Item = EdgeInfo> + 'a> {
372 if self.non_binding_filters() {
373 Box::new(std::iter::empty())
374 } else {
375 Box::new(self.edges_with_name(name).filter(EdgeInfo::is_mandatory))
376 }
377 }
378
379 fn first_mandatory_edge(&self, name: &str) -> Option<EdgeInfo> {
380 self.mandatory_edges_with_name(name).next()
381 }
382
383 fn first_edge(&self, name: &str) -> Option<EdgeInfo> {
384 self.edges_with_name(name).next()
385 }
386}
387
388fn filters_on_local_property<'a: 'b, 'b>(
389 vertex: &'a IRVertex,
390 property_name: &'b str,
391) -> impl Iterator<Item = &'a Operation<LocalField, Argument>> + 'b {
392 vertex.filters.iter().filter(move |op| op.left().field_name.as_ref() == property_name)
393}
394
395fn compute_statically_known_candidate<'a, 'b>(
396 field: &'a LocalField,
397 relevant_filters: impl Iterator<Item = &'a Operation<LocalField, Argument>>,
398 query_variables: &'b BTreeMap<Arc<str>, FieldValue>,
399) -> Option<CandidateValue<Cow<'b, FieldValue>>> {
400 let is_subject_field_nullable = field.field_type.nullable();
401 super::filters::candidate_from_statically_evaluated_filters(
402 relevant_filters,
403 query_variables,
404 is_subject_field_nullable,
405 )
406}
407
408#[cfg(test)]
409mod tests {
410 use std::{ops::Bound, sync::Arc};
411
412 use crate::{
413 interpreter::hints::{
414 vertex_info::compute_statically_known_candidate, CandidateValue, Range,
415 },
416 ir::{Argument, FieldValue, LocalField, Operation, Type, VariableRef},
417 };
418
419 #[test]
420 fn exclude_not_equals_candidates() {
421 let first: Arc<str> = Arc::from("first");
422 let second: Arc<str> = Arc::from("second");
423 let third: Arc<str> = Arc::from("third");
424 let null: Arc<str> = Arc::from("null");
425 let list: Arc<str> = Arc::from("my_list");
426 let longer_list: Arc<str> = Arc::from("longer_list");
427 let nullable_int_type = Type::parse("Int").unwrap();
428 let int_type = Type::parse("Int!").unwrap();
429 let list_int_type = Type::parse("[Int!]!").unwrap();
430
431 let first_var = Argument::Variable(VariableRef {
432 variable_name: first.clone(),
433 variable_type: int_type.clone(),
434 });
435 let second_var = Argument::Variable(VariableRef {
436 variable_name: second.clone(),
437 variable_type: int_type.clone(),
438 });
439 let null_var = Argument::Variable(VariableRef {
440 variable_name: null.clone(),
441 variable_type: nullable_int_type.clone(),
442 });
443 let list_var = Argument::Variable(VariableRef {
444 variable_name: list.clone(),
445 variable_type: list_int_type.clone(),
446 });
447 let longer_list_var = Argument::Variable(VariableRef {
448 variable_name: longer_list.clone(),
449 variable_type: list_int_type.clone(),
450 });
451
452 let local_field =
453 LocalField { field_name: Arc::from("my_field"), field_type: nullable_int_type.clone() };
454
455 let variables = btreemap! {
456 first => FieldValue::Int64(1),
457 second => FieldValue::Int64(2),
458 third => FieldValue::Int64(3),
459 null => FieldValue::Null,
460 list => FieldValue::List(Arc::new([FieldValue::Int64(1), FieldValue::Int64(2)])),
461 longer_list => FieldValue::List(Arc::new([FieldValue::Int64(1), FieldValue::Int64(2), FieldValue::Int64(3)])),
462 };
463
464 let test_data = [
465 (
467 vec![
468 Operation::NotEquals(local_field.clone(), first_var.clone()),
469 Operation::Equals(local_field.clone(), first_var.clone()),
470 ],
471 Some(CandidateValue::Impossible),
472 ),
473 (
475 vec![
476 Operation::NotEquals(local_field.clone(), first_var.clone()),
477 Operation::Equals(local_field.clone(), second_var.clone()),
478 ],
479 Some(CandidateValue::Single(&variables["second"])),
480 ),
481 (
484 vec![
485 Operation::OneOf(local_field.clone(), list_var.clone()),
486 Operation::NotEquals(local_field.clone(), first_var.clone()),
487 ],
488 Some(CandidateValue::Single(&variables["second"])),
489 ),
490 (
493 vec![
494 Operation::OneOf(local_field.clone(), longer_list_var.clone()),
495 Operation::NotOneOf(local_field.clone(), list_var.clone()),
496 ],
497 Some(CandidateValue::Single(&variables["third"])),
498 ),
499 (
502 vec![
503 Operation::GreaterThanOrEqual(local_field.clone(), second_var.clone()),
504 Operation::NotOneOf(local_field.clone(), list_var.clone()),
505 ],
506 Some(CandidateValue::Range(Range::with_start(
507 Bound::Excluded(&variables["second"]),
508 true,
509 ))),
510 ),
511 (
514 vec![
515 Operation::GreaterThanOrEqual(local_field.clone(), second_var.clone()),
516 Operation::NotOneOf(local_field.clone(), list_var.clone()),
517 Operation::IsNotNull(local_field.clone()),
518 ],
519 Some(CandidateValue::Range(Range::with_start(
520 Bound::Excluded(&variables["second"]),
521 false,
522 ))),
523 ),
524 (
527 vec![
528 Operation::GreaterThan(local_field.clone(), second_var.clone()),
529 Operation::IsNotNull(local_field.clone()),
530 ],
531 Some(CandidateValue::Range(Range::with_start(
532 Bound::Excluded(&variables["second"]),
533 false,
534 ))),
535 ),
536 (
539 vec![
540 Operation::LessThanOrEqual(local_field.clone(), second_var.clone()),
541 Operation::NotEquals(local_field.clone(), second_var.clone()),
542 Operation::IsNotNull(local_field.clone()),
543 ],
544 Some(CandidateValue::Range(Range::with_end(
545 Bound::Excluded(&variables["second"]),
546 false,
547 ))),
548 ),
549 (
552 vec![
553 Operation::LessThan(local_field.clone(), second_var.clone()),
554 Operation::IsNotNull(local_field.clone()),
555 ],
556 Some(CandidateValue::Range(Range::with_end(
557 Bound::Excluded(&variables["second"]),
558 false,
559 ))),
560 ),
561 (
564 vec![Operation::IsNotNull(local_field.clone())],
565 Some(CandidateValue::Range(Range::full_non_null())),
566 ),
567 (
570 vec![Operation::NotEquals(local_field.clone(), null_var.clone())],
571 Some(CandidateValue::Range(Range::full_non_null())),
572 ),
573 (vec![Operation::NotEquals(local_field.clone(), first_var.clone())], None),
576 (vec![Operation::NotEquals(local_field.clone(), list_var.clone())], None),
579 ];
580
581 for (filters, expected_output) in test_data {
582 assert_eq!(
583 expected_output,
584 compute_statically_known_candidate(&local_field, filters.iter(), &variables)
585 .as_ref()
586 .map(|x| x.as_deref()),
587 "with {filters:?}",
588 );
589 }
590
591 drop((
593 first_var,
594 second_var,
595 null_var,
596 list_var,
597 longer_list_var,
598 local_field,
599 int_type,
600 nullable_int_type,
601 list_int_type,
602 ));
603 }
604
605 #[test]
606 fn use_schema_to_exclude_null_from_range() {
607 let first: Arc<str> = Arc::from("first");
608 let int_type = Type::parse("Int!").unwrap();
609
610 let first_var = Argument::Variable(VariableRef {
611 variable_name: first.clone(),
612 variable_type: int_type.clone(),
613 });
614
615 let local_field =
616 LocalField { field_name: Arc::from("my_field"), field_type: int_type.clone() };
617
618 let variables = btreemap! {
619 first => FieldValue::Int64(1),
620 };
621
622 let test_data = [
623 (
626 vec![Operation::GreaterThanOrEqual(local_field.clone(), first_var.clone())],
627 Some(CandidateValue::Range(Range::with_start(
628 Bound::Included(&variables["first"]),
629 false,
630 ))),
631 ),
632 (
633 vec![Operation::GreaterThan(local_field.clone(), first_var.clone())],
634 Some(CandidateValue::Range(Range::with_start(
635 Bound::Excluded(&variables["first"]),
636 false,
637 ))),
638 ),
639 (
640 vec![Operation::LessThan(local_field.clone(), first_var.clone())],
641 Some(CandidateValue::Range(Range::with_end(
642 Bound::Excluded(&variables["first"]),
643 false,
644 ))),
645 ),
646 (
647 vec![Operation::LessThanOrEqual(local_field.clone(), first_var.clone())],
648 Some(CandidateValue::Range(Range::with_end(
649 Bound::Included(&variables["first"]),
650 false,
651 ))),
652 ),
653 ];
654
655 for (filters, expected_output) in test_data {
656 assert_eq!(
657 expected_output,
658 compute_statically_known_candidate(&local_field, filters.iter(), &variables)
659 .as_ref()
660 .map(|x| x.as_deref()),
661 "with {filters:?}",
662 );
663 }
664
665 drop((first_var, local_field, int_type));
667 }
668}