Skip to main content

graphrecords_query/operations/string_operations/
matches.rs

1use super::{string_argument_map_bare, string_argument_map_indexed};
2use crate::{
3    Bare, BareValueDomain, Explain, Failure, IndexDomain, Indexed, Labeled, Mask, Operand,
4    QueryResult,
5    capabilities::StringValue,
6    error::string::InvalidRegexPattern,
7    execution::EvaluationCache,
8    operations::{
9        Apply, ArgumentSource, ElementKernel, ElementPipeline, Keyed, Operation, OperationContext,
10        Prepare, Unaligned,
11    },
12    optimizer::{Estimate, OperationInputs, OptimizerHints, PlanIdentity, PlanInputs, Stats},
13    registry::{describe::ArgumentRetention, operation_manifest},
14    traits::Matches,
15};
16use graphrecords_core::GraphRecord;
17use regex::Regex;
18
19pub(super) fn regex_matches(label: &'static str, value: &str, pattern: &str) -> QueryResult<bool> {
20    let expression = Regex::new(pattern).map_err(|error| {
21        Failure::new(label, InvalidRegexPattern::new(pattern.to_string(), error))
22    })?;
23
24    Ok(expression.is_match(value))
25}
26
27#[derive(Clone, Explain, Operation, OperationInputs, OptimizerHints, PlanIdentity, PlanInputs)]
28#[operation(scope = Element)]
29#[explain(label = "Matches")]
30#[plan(optimizer_hints(empty = if_all))]
31pub struct MatchesOperation<A> {
32    #[argument]
33    pattern: A,
34}
35
36impl<A: Prepare> Prepare for MatchesOperation<A> {
37    type Prepared<'a>
38        = A::Prepared<'a>
39    where
40        Self: 'a;
41
42    fn prepare<'a>(
43        &'a self,
44        graphrecord: &'a GraphRecord,
45        cache: &'a EvaluationCache<'a>,
46    ) -> QueryResult<Self::Prepared<'a>> {
47        self.pattern.prepare(graphrecord, cache)
48    }
49}
50
51impl<I, V, A> ElementKernel<Indexed<I, V>> for MatchesOperation<A>
52where
53    I: IndexDomain,
54    V: StringValue,
55    A: ArgumentSource<Keyed<I>>,
56    A::ValueDomain: StringValue,
57{
58    type Emission = A::Retention;
59    type OutShape = Indexed<I, Mask>;
60
61    fn pipeline<'a>(
62        _graphrecord: &'a GraphRecord,
63        prepared: Self::Prepared<'a>,
64    ) -> QueryResult<ElementPipeline<'a, Indexed<I, V>, Self>> {
65        Ok(string_argument_map_indexed::<_, V, Mask, A>(
66            prepared,
67            Self::LABEL,
68            |label, value, pattern| regex_matches(label, &value, &pattern),
69        ))
70    }
71
72    fn estimate(&self, input: Estimate, _stats: &Stats) -> Estimate {
73        Estimate {
74            selectivity: None,
75            ..input.with_unknown_distinct()
76        }
77    }
78}
79
80impl<V, A> ElementKernel<Bare<V>> for MatchesOperation<A>
81where
82    V: StringValue + BareValueDomain,
83    A: ArgumentSource<Unaligned>,
84    A::ValueDomain: StringValue,
85{
86    type Emission = A::Retention;
87    type OutShape = Bare<Mask>;
88
89    fn pipeline<'a>(
90        _graphrecord: &'a GraphRecord,
91        prepared: Self::Prepared<'a>,
92    ) -> QueryResult<ElementPipeline<'a, Bare<V>, Self>> {
93        Ok(string_argument_map_bare::<V, Mask, A>(
94            prepared,
95            Self::LABEL,
96            |label, value, pattern| regex_matches(label, &value, &pattern),
97        ))
98    }
99
100    fn estimate(&self, input: Estimate, _stats: &Stats) -> Estimate {
101        Estimate {
102            selectivity: None,
103            ..input.with_unknown_distinct()
104        }
105    }
106}
107
108impl<O, A> Matches<A> for O
109where
110    MatchesOperation<A>: Operation,
111    O: Apply<MatchesOperation<A>>,
112{
113    type ReturnOperand = O::Output;
114
115    fn matches(&self, pattern: A) -> Self::ReturnOperand {
116        Self::ReturnOperand::new(OperationContext::new(
117            self.clone(),
118            MatchesOperation { pattern },
119        ))
120    }
121}
122
123operation_manifest! {
124    MatchesOperation<A> {
125        method: Matches<A>::matches;
126        scope: element;
127
128        kernel {
129            parameters: <I: IndexDomain, V: StringValue>;
130            argument: A: ArgumentSource<Keyed<I>> where A::ValueDomain: StringValue;
131            input: Indexed<I, V>;
132            output: Indexed<I, Mask>;
133            emission: ArgumentRetention;
134        }
135        kernel {
136            parameters: <V: StringValue + BareValueDomain>;
137            argument: A: ArgumentSource<Unaligned> where A::ValueDomain: StringValue;
138            input: Bare<V>;
139            output: Bare<Mask>;
140            emission: ArgumentRetention;
141        }
142    }
143}