1use std::fmt;
11
12use qubit_redact::Redact;
13use qubit_redact::RedactionWriter;
14use qubit_redact::Redactor;
15
16use crate::Condition;
17use crate::FilterExpressionBuilder;
18use crate::FilterExpressionView;
19use crate::FilterLimitKind;
20use crate::FilterLimits;
21use crate::FilterMatchOptions;
22use crate::Metadata;
23use crate::MetadataError;
24use crate::MetadataResult;
25use crate::filter::internal::ExpressionChildren;
26use crate::filter::internal::FilterExpressionNode;
27use crate::filter::internal::MatchOutcome;
28
29#[derive(Clone, PartialEq)]
55#[must_use]
56pub struct FilterExpression {
57 node: FilterExpressionNode,
59 node_count: usize,
61 max_depth: usize,
63}
64
65impl fmt::Debug for FilterExpression {
66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72 let output = Redactor::strict().redact_text(self);
73 let text = output.text_or_marker("<redaction incomplete>");
74 formatter.write_str(text.as_ref())
75 }
76}
77
78impl Redact for FilterExpression {
79 fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
84 writer.record("FilterExpression", |fields| match &self.node {
85 FilterExpressionNode::Condition(condition) => {
86 fields.unmarked("operator", || "Condition");
87 fields.nested("condition", condition);
88 }
89 FilterExpressionNode::And(children) => {
90 fields.unmarked("operator", || "And");
91 fields.nested("children", &ExpressionChildren(children));
92 }
93 FilterExpressionNode::Or(children) => {
94 fields.unmarked("operator", || "Or");
95 fields.nested("children", &ExpressionChildren(children));
96 }
97 FilterExpressionNode::Not(inner) => {
98 fields.unmarked("operator", || "Not");
99 fields.nested("inner", inner.as_ref());
100 }
101 FilterExpressionNode::True => {
102 fields.unmarked("operator", || "True");
103 }
104 FilterExpressionNode::False => {
105 fields.unmarked("operator", || "False");
106 }
107 });
108 }
109}
110
111impl FilterExpression {
112 #[inline(always)]
114 #[must_use]
115 pub const fn builder() -> FilterExpressionBuilder {
116 FilterExpressionBuilder::new()
117 }
118
119 #[inline(always)]
121 pub const fn match_all() -> Self {
122 Self::true_expression()
123 }
124
125 #[inline(always)]
127 pub const fn match_none() -> Self {
128 Self::false_expression()
129 }
130
131 #[inline]
146 pub(crate) fn condition(condition: Condition) -> MetadataResult<Self> {
147 condition.validate_operands()?;
148 Ok(Self {
149 node: FilterExpressionNode::Condition(condition),
150 node_count: 1,
151 max_depth: 1,
152 })
153 }
154
155 #[inline]
161 pub(crate) const fn true_expression() -> Self {
162 Self {
163 node: FilterExpressionNode::True,
164 node_count: 1,
165 max_depth: 1,
166 }
167 }
168
169 #[inline]
175 pub(crate) const fn false_expression() -> Self {
176 Self {
177 node: FilterExpressionNode::False,
178 node_count: 1,
179 max_depth: 1,
180 }
181 }
182
183 #[inline]
193 pub(crate) fn not_expression(expression: Self) -> Self {
194 let node_count = expression.node_count + 1;
195 let max_depth = expression.max_depth + 1;
196 Self {
197 node: FilterExpressionNode::Not(Box::new(expression)),
198 node_count,
199 max_depth,
200 }
201 }
202
203 #[inline]
214 pub fn try_and(self, other: Self) -> MetadataResult<Self> {
215 let expression = Self::and_unchecked(self, other);
216 if expression.validate_structure_limits(FilterLimits::MAX).is_err() {
217 expression.validate_limits(FilterLimits::MAX)?;
218 }
219 Ok(expression)
220 }
221
222 #[inline]
233 pub fn try_or(self, other: Self) -> MetadataResult<Self> {
234 let expression = Self::or_unchecked(self, other);
235 if expression.validate_structure_limits(FilterLimits::MAX).is_err() {
236 expression.validate_limits(FilterLimits::MAX)?;
237 }
238 Ok(expression)
239 }
240
241 #[inline]
248 pub fn try_not(self) -> MetadataResult<Self> {
249 let expression = self.negated_unchecked();
250 if expression.validate_structure_limits(FilterLimits::MAX).is_err() {
251 expression.validate_limits(FilterLimits::MAX)?;
252 }
253 Ok(expression)
254 }
255
256 #[inline(always)]
262 #[must_use = "the expression view should be inspected"]
263 pub fn view(&self) -> FilterExpressionView<'_> {
264 match &self.node {
265 FilterExpressionNode::Condition(condition) => FilterExpressionView::Condition(condition),
266 FilterExpressionNode::And(children) => FilterExpressionView::And(children),
267 FilterExpressionNode::Or(children) => FilterExpressionView::Or(children),
268 FilterExpressionNode::Not(inner) => FilterExpressionView::Not(inner),
269 FilterExpressionNode::True => FilterExpressionView::True,
270 FilterExpressionNode::False => FilterExpressionView::False,
271 }
272 }
273
274 pub(crate) fn and_unchecked(left: Self, right: Self) -> Self {
285 if left.is_false() || right.is_false() {
286 return Self::false_expression();
287 }
288 if left.is_true() {
289 return right;
290 }
291 if right.is_true() {
292 return left;
293 }
294 Self::combine_and(left, right)
295 }
296
297 pub(crate) fn or_unchecked(left: Self, right: Self) -> Self {
308 if left.is_true() || right.is_true() {
309 return Self::true_expression();
310 }
311 if left.is_false() {
312 return right;
313 }
314 if right.is_false() {
315 return left;
316 }
317 Self::combine_or(left, right)
318 }
319
320 pub(crate) fn negated_unchecked(self) -> Self {
326 match self {
327 Self {
328 node: FilterExpressionNode::True,
329 ..
330 } => Self::false_expression(),
331 Self {
332 node: FilterExpressionNode::False,
333 ..
334 } => Self::true_expression(),
335 Self {
336 node: FilterExpressionNode::Not(inner),
337 ..
338 } => *inner,
339 expression => Self::not_expression(expression),
340 }
341 }
342
343 #[inline]
349 pub(crate) const fn is_true(&self) -> bool {
350 matches!(&self.node, FilterExpressionNode::True)
351 }
352
353 #[inline]
359 pub(crate) const fn is_false(&self) -> bool {
360 matches!(&self.node, FilterExpressionNode::False)
361 }
362
363 pub(crate) fn evaluate(&self, metadata: &Metadata, options: FilterMatchOptions) -> MatchOutcome {
374 match &self.node {
375 FilterExpressionNode::Condition(condition) => {
376 condition.evaluate(metadata, options.numeric_comparison_policy())
377 }
378 FilterExpressionNode::And(children) => {
379 MatchOutcome::and(children.iter().map(|child| child.evaluate(metadata, options)))
380 }
381 FilterExpressionNode::Or(children) => {
382 MatchOutcome::or(children.iter().map(|child| child.evaluate(metadata, options)))
383 }
384 FilterExpressionNode::Not(inner) => inner.evaluate(metadata, options).not(),
385 FilterExpressionNode::True => MatchOutcome::True,
386 FilterExpressionNode::False => MatchOutcome::False,
387 }
388 }
389
390 #[cfg(feature = "schema")]
408 pub(crate) fn visit_conditions<F>(&self, visitor: &mut F) -> MetadataResult<()>
409 where
410 F: FnMut(&Condition) -> MetadataResult<()>,
411 {
412 match &self.node {
413 FilterExpressionNode::Condition(condition) => visitor(condition),
414 FilterExpressionNode::And(children) | FilterExpressionNode::Or(children) => {
415 for child in children {
416 child.visit_conditions(visitor)?;
417 }
418 Ok(())
419 }
420 FilterExpressionNode::Not(inner) => inner.visit_conditions(visitor),
421 FilterExpressionNode::True | FilterExpressionNode::False => Ok(()),
422 }
423 }
424
425 pub(crate) fn validate_limits(&self, limits: FilterLimits) -> MetadataResult<()> {
440 let mut node_count = 0;
441 self.validate_limits_at(limits, 1, &mut node_count)
442 }
443
444 pub(crate) fn validate_structure_limits(&self, limits: FilterLimits) -> MetadataResult<()> {
457 if self.max_depth > limits.max_depth() {
458 return Err(MetadataError::FilterLimitExceeded {
459 kind: FilterLimitKind::Depth,
460 value: limits.max_depth() + 1,
461 maximum: limits.max_depth(),
462 });
463 }
464 if self.node_count > limits.max_nodes() {
465 return Err(MetadataError::FilterLimitExceeded {
466 kind: FilterLimitKind::Nodes,
467 value: limits.max_nodes() + 1,
468 maximum: limits.max_nodes(),
469 });
470 }
471 Ok(())
472 }
473
474 fn validate_limits_at(&self, limits: FilterLimits, depth: usize, node_count: &mut usize) -> MetadataResult<()> {
487 if depth > limits.max_depth() {
488 return Err(MetadataError::FilterLimitExceeded {
489 kind: FilterLimitKind::Depth,
490 value: depth,
491 maximum: limits.max_depth(),
492 });
493 }
494 *node_count += 1;
495 if *node_count > limits.max_nodes() {
496 return Err(MetadataError::FilterLimitExceeded {
497 kind: FilterLimitKind::Nodes,
498 value: *node_count,
499 maximum: limits.max_nodes(),
500 });
501 }
502 match &self.node {
503 FilterExpressionNode::Condition(condition) => condition.validate_limits(limits),
504 FilterExpressionNode::And(children) | FilterExpressionNode::Or(children) => {
505 for child in children {
506 child.validate_limits_at(limits, depth + 1, node_count)?;
507 }
508 Ok(())
509 }
510 FilterExpressionNode::Not(inner) => inner.validate_limits_at(limits, depth + 1, node_count),
511 FilterExpressionNode::True | FilterExpressionNode::False => Ok(()),
512 }
513 }
514
515 #[cfg(test)]
518 fn assert_cached_metrics_consistent(&self) {
519 match &self.node {
520 FilterExpressionNode::And(children) | FilterExpressionNode::Or(children) => {
521 for child in children {
522 child.assert_cached_metrics_consistent();
523 }
524 }
525 FilterExpressionNode::Not(inner) => inner.assert_cached_metrics_consistent(),
526 FilterExpressionNode::Condition(_) | FilterExpressionNode::True | FilterExpressionNode::False => {}
527 }
528 let (node_count, max_depth) = self.recursive_metrics();
529 assert_eq!(
530 self.node_count, node_count,
531 "cached node count differs from the expression tree"
532 );
533 assert_eq!(
534 self.max_depth, max_depth,
535 "cached maximum depth differs from the expression tree"
536 );
537 }
538
539 #[cfg(test)]
541 fn recursive_metrics(&self) -> (usize, usize) {
542 match &self.node {
543 FilterExpressionNode::Condition(_) | FilterExpressionNode::True | FilterExpressionNode::False => (1, 1),
544 FilterExpressionNode::And(children) | FilterExpressionNode::Or(children) => {
545 let mut node_count = 1;
546 let mut max_child_depth = 0;
547 for child in children {
548 let (child_node_count, child_max_depth) = child.recursive_metrics();
549 node_count += child_node_count;
550 max_child_depth = max_child_depth.max(child_max_depth);
551 }
552 (node_count, max_child_depth + 1)
553 }
554 FilterExpressionNode::Not(inner) => {
555 let (node_count, max_depth) = inner.recursive_metrics();
556 (node_count + 1, max_depth + 1)
557 }
558 }
559 }
560
561 fn combine_and(left: Self, right: Self) -> Self {
568 let left_same_kind = matches!(&left.node, FilterExpressionNode::And(_));
569 let right_same_kind = matches!(&right.node, FilterExpressionNode::And(_));
570 let (node_count, max_depth) = Self::combined_metrics(&left, &right, left_same_kind, right_same_kind);
571 let mut children = match left {
572 Self {
573 node: FilterExpressionNode::And(children),
574 ..
575 } => children,
576 expression => vec![expression],
577 };
578 match right {
579 Self {
580 node: FilterExpressionNode::And(mut nested),
581 ..
582 } => children.append(&mut nested),
583 expression => children.push(expression),
584 }
585 Self {
586 node: FilterExpressionNode::And(children),
587 node_count,
588 max_depth,
589 }
590 }
591
592 fn combine_or(left: Self, right: Self) -> Self {
599 let left_same_kind = matches!(&left.node, FilterExpressionNode::Or(_));
600 let right_same_kind = matches!(&right.node, FilterExpressionNode::Or(_));
601 let (node_count, max_depth) = Self::combined_metrics(&left, &right, left_same_kind, right_same_kind);
602 let mut children = match left {
603 Self {
604 node: FilterExpressionNode::Or(children),
605 ..
606 } => children,
607 expression => vec![expression],
608 };
609 match right {
610 Self {
611 node: FilterExpressionNode::Or(mut nested),
612 ..
613 } => children.append(&mut nested),
614 expression => children.push(expression),
615 }
616 Self {
617 node: FilterExpressionNode::Or(children),
618 node_count,
619 max_depth,
620 }
621 }
622
623 fn combined_metrics(left: &Self, right: &Self, left_same_kind: bool, right_same_kind: bool) -> (usize, usize) {
638 let node_count = match (left_same_kind, right_same_kind) {
639 (true, true) => left.node_count + right.node_count - 1,
640 (true, false) | (false, true) => left.node_count + right.node_count,
641 (false, false) => left.node_count + right.node_count + 1,
642 };
643 let max_depth = match (left_same_kind, right_same_kind) {
644 (true, true) => left.max_depth.max(right.max_depth),
645 (true, false) => left.max_depth.max(right.max_depth + 1),
646 (false, true) => (left.max_depth + 1).max(right.max_depth),
647 (false, false) => left.max_depth.max(right.max_depth) + 1,
648 };
649 (node_count, max_depth)
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::FilterExpression;
656
657 #[test]
660 fn test_cached_metrics_match_recursive_metrics() {
661 let leaf = FilterExpression::builder()
662 .exists("leaf")
663 .build()
664 .expect("leaf expression should build");
665 leaf.assert_cached_metrics_consistent();
666
667 let left = FilterExpression::builder()
668 .exists("left_1")
669 .exists("left_2")
670 .build()
671 .expect("left expression should build");
672 let right = FilterExpression::builder()
673 .exists("right_1")
674 .exists("right_2")
675 .build()
676 .expect("right expression should build");
677 let flattened = left.try_and(right).expect("flattened AND should build");
678 flattened.assert_cached_metrics_consistent();
679
680 let nested = flattened
681 .try_or(
682 FilterExpression::builder()
683 .exists("alternative")
684 .build()
685 .expect("alternative expression should build"),
686 )
687 .expect("nested OR should build")
688 .try_not()
689 .expect("negated expression should build");
690 nested.assert_cached_metrics_consistent();
691
692 FilterExpression::match_all()
693 .try_and(nested.clone())
694 .expect("true AND expression should simplify")
695 .assert_cached_metrics_consistent();
696 FilterExpression::match_none()
697 .try_or(nested)
698 .expect("false OR expression should simplify")
699 .assert_cached_metrics_consistent();
700 FilterExpression::match_all()
701 .try_not()
702 .expect("constant negation should simplify")
703 .assert_cached_metrics_consistent();
704 }
705
706 #[test]
707 fn test_cached_metrics_combination_matrix() {
708 use crate::FilterLimitKind;
709 use crate::FilterLimits;
710 use crate::MetadataError;
711
712 let leaf = FilterExpression::builder()
713 .exists("chunk_index")
714 .build()
715 .expect("leaf expression should build");
716 let and = leaf.clone().try_and(leaf.clone()).expect("AND expression should build");
717 let or = leaf.clone().try_or(leaf.clone()).expect("OR expression should build");
718 let deep = and
719 .clone()
720 .try_or(leaf.clone())
721 .expect("nested OR expression should build")
722 .try_not()
723 .expect("NOT expression should build");
724 let tall_and = and
725 .clone()
726 .try_and(or.clone().try_not().expect("NOT expression should build"))
727 .expect("taller AND expression should build");
728 let tall_or = or
729 .clone()
730 .try_or(and.clone().try_not().expect("NOT expression should build"))
731 .expect("taller OR expression should build");
732 let roots = [leaf, and, or, deep, tall_and, tall_or];
733
734 for left in &roots {
735 for right in &roots {
736 for expression in [
737 left.clone()
738 .try_and(right.clone())
739 .expect("AND combination should build"),
740 left.clone().try_or(right.clone()).expect("OR combination should build"),
741 ] {
742 expression.assert_cached_metrics_consistent();
743 let (nodes, depth) = expression.recursive_metrics();
744 let exact = FilterLimits::builder()
745 .max_nodes(nodes)
746 .max_depth(depth)
747 .build()
748 .expect("exact limits should build");
749 assert!(expression.validate_limits(exact).is_ok());
750
751 if nodes > 1 {
752 let low_nodes = FilterLimits::builder()
753 .max_nodes(nodes - 1)
754 .build()
755 .expect("lower node limit should build");
756 assert!(matches!(
757 expression.validate_limits(low_nodes),
758 Err(MetadataError::FilterLimitExceeded {
759 kind: FilterLimitKind::Nodes,
760 ..
761 })
762 ));
763 }
764
765 if depth > 1 {
766 let low_depth = FilterLimits::builder()
767 .max_depth(depth - 1)
768 .build()
769 .expect("lower depth limit should build");
770 assert!(matches!(
771 expression.validate_limits(low_depth),
772 Err(MetadataError::FilterLimitExceeded {
773 kind: FilterLimitKind::Depth,
774 ..
775 })
776 ));
777 }
778 }
779 }
780 }
781 }
782
783 #[test]
784 fn test_cached_metrics_after_combination_and_simplification_sequence() {
785 let leaf = FilterExpression::builder()
786 .exists("chunk_index")
787 .build()
788 .expect("leaf expression should build");
789 leaf.assert_cached_metrics_consistent();
790
791 let and = leaf.clone().try_and(leaf.clone()).expect("AND should build");
792 and.assert_cached_metrics_consistent();
793 let or = and.try_or(leaf.clone()).expect("OR should build");
794 or.assert_cached_metrics_consistent();
795 let negated = or.try_not().expect("NOT should build");
796 negated.assert_cached_metrics_consistent();
797 let and_true = negated
798 .clone()
799 .try_and(FilterExpression::match_all())
800 .expect("true AND should simplify");
801 and_true.assert_cached_metrics_consistent();
802 let or_false = and_true
803 .try_or(FilterExpression::match_none())
804 .expect("false OR should simplify");
805 or_false.assert_cached_metrics_consistent();
806 let once_negated = or_false.try_not().expect("first NOT should build");
807 once_negated.assert_cached_metrics_consistent();
808 let twice_negated = once_negated.try_not().expect("second NOT should build");
809 twice_negated.assert_cached_metrics_consistent();
810 }
811}