graphrecords_query/operations/string_operations/
matches.rs1use 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}