uqa_sql/expr/scalar_helpers/
like_pattern.rs1use super::casing;
10use crate::{
11 error::{Result, SQLError},
12 expr::conversion::value_to_string_with_control,
13};
14use uqa_core::{
15 memory::{Produced, ProductionControl, ProductionVec},
16 Value,
17};
18
19pub struct CompiledLikePattern {
21 case_insensitive: bool,
22 binary: bool,
23 pattern_chars: Produced<Vec<LikePatternToken<char>>>,
24 pattern_ascii: Option<Produced<Vec<LikePatternToken<u8>>>>,
25}
26
27#[derive(Clone, Copy, PartialEq, Eq)]
28enum LikePatternToken<T> {
29 Literal(T),
30 AnyOne,
31 AnyMany,
32 DanglingEscape,
33}
34
35impl CompiledLikePattern {
36 #[must_use]
37 pub fn new(pattern: &str, case_insensitive: bool) -> Self {
38 Self::with_escape(pattern, case_insensitive, None)
39 .expect("the default LIKE escape is exactly one character")
40 }
41
42 pub fn from_value(pattern: &Value, case_insensitive: bool) -> Result<Self> {
43 Self::from_value_with_escape_with_control(
44 pattern,
45 case_insensitive,
46 None,
47 &ProductionControl::uncontrolled(),
48 )
49 }
50
51 pub fn from_value_with_escape_with_control(
53 pattern: &Value,
54 case_insensitive: bool,
55 escape: Option<&Value>,
56 control: &ProductionControl<'_>,
57 ) -> Result<Self> {
58 if let Some(pattern) = crate::expr::datums::binary_payload(pattern, control)? {
59 if case_insensitive {
60 return Err(SQLError::Internal("bytea has no ILIKE operator".into()));
61 }
62 let escape = escape
63 .map(|value| crate::expr::datums::binary_payload(value, control))
64 .transpose()?;
65 let escape = match escape.as_ref() {
66 None => Some(b'\\'),
67 Some(Some(bytes)) => match bytes.bytes() {
68 [] => None,
69 [byte] => Some(*byte),
70 _ => return Err(invalid_escape()),
71 },
72 Some(None) => {
73 return Err(SQLError::Internal(
74 "bytea escape has an invalid carrier".into(),
75 ))
76 }
77 };
78 let mut tokens = ProductionVec::new(*control);
79 let mut bytes = pattern.bytes().iter().copied();
80 while let Some(byte) = bytes.next() {
81 control.check()?;
82 let token = if escape == Some(byte) {
83 bytes
84 .next()
85 .map_or(LikePatternToken::DanglingEscape, LikePatternToken::Literal)
86 } else {
87 match byte {
88 b'%' => LikePatternToken::AnyMany,
89 b'_' => LikePatternToken::AnyOne,
90 other => LikePatternToken::Literal(other),
91 }
92 };
93 tokens.push_copy(token)?;
94 }
95 return Ok(Self {
96 case_insensitive,
97 binary: true,
98 pattern_chars: ProductionVec::new(*control).finish()?,
99 pattern_ascii: Some(tokens.finish()?),
100 });
101 }
102 let pattern = value_to_string_with_control(pattern, control)?;
103 let escape = escape
104 .map(|value| value_to_string_with_control(value, control))
105 .transpose()?;
106 Self::with_escape_with_control(
107 &pattern,
108 case_insensitive,
109 escape.as_deref().map(String::as_str),
110 control,
111 )
112 }
113
114 pub fn with_escape(
115 pattern: &str,
116 case_insensitive: bool,
117 escape: Option<&str>,
118 ) -> Result<Self> {
119 Self::with_escape_with_control(
120 pattern,
121 case_insensitive,
122 escape,
123 &ProductionControl::uncontrolled(),
124 )
125 }
126
127 pub fn with_escape_with_control(
129 pattern: &str,
130 case_insensitive: bool,
131 escape: Option<&str>,
132 control: &ProductionControl<'_>,
133 ) -> Result<Self> {
134 control.check()?;
135 let escape = escape_character(escape)?;
136 let pattern_chars = compile(pattern, case_insensitive, escape, control)?;
137 let mut ascii = ProductionVec::new(*control);
138 let mut all_ascii = true;
139 for token in pattern_chars.iter() {
140 control.check()?;
141 let token = match token {
142 LikePatternToken::Literal(character) if character.is_ascii() => {
143 LikePatternToken::Literal(*character as u8)
144 }
145 LikePatternToken::Literal(_) => {
146 all_ascii = false;
147 break;
148 }
149 LikePatternToken::AnyOne => LikePatternToken::AnyOne,
150 LikePatternToken::AnyMany => LikePatternToken::AnyMany,
151 LikePatternToken::DanglingEscape => LikePatternToken::DanglingEscape,
152 };
153 ascii.push_copy(token)?;
154 }
155 let pattern_ascii = if all_ascii {
156 Some(ascii.finish()?)
157 } else {
158 None
159 };
160 Ok(Self {
161 case_insensitive,
162 binary: false,
163 pattern_chars,
164 pattern_ascii,
165 })
166 }
167
168 #[must_use]
169 pub fn is_match(&self, haystack: &str) -> bool {
170 self.try_is_match(haystack).unwrap_or(false)
171 }
172
173 pub fn try_is_match(&self, haystack: &str) -> Result<bool> {
174 self.try_is_match_with_control(haystack, &ProductionControl::uncontrolled())
175 }
176
177 pub fn try_is_match_with_control(
178 &self,
179 haystack: &str,
180 control: &ProductionControl<'_>,
181 ) -> Result<bool> {
182 control.check()?;
183 let normalized = if self.case_insensitive {
184 Some(casing::lowercase(haystack, control)?)
185 } else {
186 None
187 };
188 let haystack = normalized.as_deref().map_or(haystack, String::as_str);
189 if let Some(pattern) = self
190 .pattern_ascii
191 .as_deref()
192 .filter(|_| self.binary || haystack.is_ascii())
193 {
194 return wildcard_match(haystack.as_bytes(), pattern, control);
195 }
196 let mut characters = ProductionVec::new(*control);
197 for character in haystack.chars() {
198 characters.push_copy(character)?;
199 }
200 wildcard_match(&characters, &self.pattern_chars, control)
201 }
202
203 #[must_use]
204 pub fn matches_value(&self, haystack: &Value) -> bool {
205 self.try_matches_value(haystack).unwrap_or(false)
206 }
207
208 pub fn try_matches_value(&self, haystack: &Value) -> Result<bool> {
209 self.try_matches_value_with_control(haystack, &ProductionControl::uncontrolled())
210 }
211
212 pub fn try_matches_value_with_control(
213 &self,
214 haystack: &Value,
215 control: &ProductionControl<'_>,
216 ) -> Result<bool> {
217 if self.binary {
218 let bytes =
219 crate::expr::datums::binary_payload(haystack, control)?.ok_or_else(|| {
220 SQLError::Internal("bytea LIKE source has an invalid carrier".into())
221 })?;
222 return wildcard_match(
223 bytes.bytes(),
224 self.pattern_ascii
225 .as_deref()
226 .expect("binary pattern tokens"),
227 control,
228 );
229 }
230 match haystack {
231 Value::Datum(datum) => self.try_matches_value_with_control(
232 &*crate::expr::datums::read_with_control(datum, control)?,
233 control,
234 ),
235 Value::Str(text) | Value::FixedChar(text) => {
236 self.try_is_match_with_control(text, control)
237 }
238 Value::Null => self.try_is_match_with_control("", control),
239 other => self
240 .try_is_match_with_control(&value_to_string_with_control(other, control)?, control),
241 }
242 }
243}
244
245pub(in crate::expr) fn escape_character(escape: Option<&str>) -> Result<Option<char>> {
246 let Some(escape) = escape else {
247 return Ok(Some('\\'));
248 };
249 let mut characters = escape.chars();
250 let first = characters.next();
251 if characters.next().is_some() {
252 return Err(invalid_escape());
253 }
254 Ok(first)
255}
256
257pub(in crate::expr) fn invalid_escape() -> SQLError {
258 SQLError::Diagnostic {
259 sqlstate: "22025".into(),
260 message: "invalid escape string".into(),
261 detail: None,
262 hint: Some("Escape string must be empty or one character.".into()),
263 }
264}
265
266fn compile(
267 pattern: &str,
268 insensitive: bool,
269 escape: Option<char>,
270 control: &ProductionControl<'_>,
271) -> Result<Produced<Vec<LikePatternToken<char>>>> {
272 let mut output = ProductionVec::new(*control);
273 let mut characters = pattern.chars();
274 while let Some(character) = characters.next() {
275 control.check()?;
276 if escape == Some(character) {
277 let Some(literal) = characters.next() else {
278 output.push_copy(LikePatternToken::DanglingEscape)?;
279 break;
280 };
281 push_literal(&mut output, literal, insensitive)?;
282 continue;
283 }
284 match character {
285 '%' => output.push_copy(LikePatternToken::AnyMany)?,
286 '_' => output.push_copy(LikePatternToken::AnyOne)?,
287 literal => push_literal(&mut output, literal, insensitive)?,
288 }
289 }
290 Ok(output.finish()?)
291}
292
293fn push_literal(
294 output: &mut ProductionVec<'_, LikePatternToken<char>>,
295 literal: char,
296 insensitive: bool,
297) -> Result<()> {
298 if insensitive {
299 for character in literal.to_lowercase() {
300 output.push_copy(LikePatternToken::Literal(character))?;
301 }
302 } else {
303 output.push_copy(LikePatternToken::Literal(literal))?;
304 }
305 Ok(())
306}
307
308fn wildcard_match<T: Copy + Eq>(
309 haystack: &[T],
310 pattern: &[LikePatternToken<T>],
311 control: &ProductionControl<'_>,
312) -> Result<bool> {
313 let mut haystack_index = 0;
314 let mut pattern_index = 0;
315 let mut star: Option<(usize, usize)> = None;
316 while haystack_index < haystack.len() {
317 control.check()?;
318 match pattern.get(pattern_index) {
319 Some(LikePatternToken::Literal(literal)) if *literal == haystack[haystack_index] => {
320 haystack_index += 1;
321 pattern_index += 1;
322 }
323 Some(LikePatternToken::AnyOne) => {
324 haystack_index += 1;
325 pattern_index += 1;
326 }
327 Some(LikePatternToken::AnyMany) => {
328 star = Some((pattern_index, haystack_index));
329 pattern_index += 1;
330 }
331 Some(LikePatternToken::DanglingEscape) => {
332 return Err(SQLError::Routine {
333 sqlstate: "22025".into(),
334 message: "LIKE pattern must not end with escape character".into(),
335 });
336 }
337 _ => {
338 if let Some((star_pattern, star_haystack)) = star {
339 pattern_index = star_pattern + 1;
340 haystack_index = star_haystack + 1;
341 star = Some((star_pattern, haystack_index));
342 } else {
343 return Ok(false);
344 }
345 }
346 }
347 }
348 while matches!(pattern.get(pattern_index), Some(LikePatternToken::AnyMany)) {
349 control.check()?;
350 pattern_index += 1;
351 }
352 Ok(pattern_index == pattern.len())
353}
354
355#[cfg(test)]
356mod tests;