1mod exec;
8mod lex;
9mod parse;
10mod tree;
11
12use std::cell::{OnceCell, RefCell};
13use std::rc::Rc;
14
15use rupg_common::{Error, Result, SqlState};
16use rupg_types::{Array, Value};
17
18use crate::{Call, Kernel, bad_value};
19
20pub(super) mod flags {
22 pub(crate) const EXTENDED: u32 = 0o1;
23 pub(crate) const ADVF: u32 = 0o2;
24 pub(crate) const ADVANCED: u32 = 0o3;
25 pub(crate) const QUOTE: u32 = 0o4;
26 pub(crate) const ICASE: u32 = 0o10;
27 pub(crate) const EXPANDED: u32 = 0o40;
28 pub(crate) const NLSTOP: u32 = 0o100;
29 pub(crate) const NLANCH: u32 = 0o200;
30 pub(crate) const NEWLINE: u32 = 0o300;
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub(super) enum Code {
36 BadPat,
37 Collate,
38 Ctype,
39 Escape,
40 Subreg,
41 Brack,
42 Paren,
43 Brace,
44 BadBr,
45 Range,
46 BadRpt,
47 Assert,
48 BadOpt,
49 TooBig,
50}
51
52impl Code {
53 fn message(self) -> &'static str {
55 match self {
56 Code::BadPat => "invalid regexp (reg version 0.8)",
57 Code::Collate => "invalid collating element",
58 Code::Ctype => "invalid character class",
59 Code::Escape => "invalid escape \\ sequence",
60 Code::Subreg => "invalid backreference number",
61 Code::Brack => "brackets [] not balanced",
62 Code::Paren => "parentheses () not balanced",
63 Code::Brace => "braces {} not balanced",
64 Code::BadBr => "invalid repetition count(s)",
65 Code::Range => "invalid character range",
66 Code::BadRpt => "quantifier operand invalid",
67 Code::Assert => "\"cannot happen\" -- you found a bug",
68 Code::BadOpt => "invalid embedded option",
69 Code::TooBig => "regular expression is too complex",
70 }
71 }
72
73 fn error(self) -> Error {
75 Error::new(
76 SqlState::INVALID_REGULAR_EXPRESSION,
77 format!("invalid regular expression: {}", self.message()),
78 )
79 }
80}
81
82pub(crate) struct Regex {
84 parsed: parse::Parsed,
85 prog: exec::Program,
86 tree: OnceCell<std::result::Result<tree::Tree, Code>>,
88}
89
90impl Regex {
91 fn compile(pattern: &str, cflags: u32) -> std::result::Result<Self, Code> {
93 let chars: Vec<u32> = pattern.chars().map(u32::from).collect();
94 let parsed = parse::Parser::run(&chars, cflags)?;
95 let prog = exec::Program::new(&parsed.node, parsed.groups, parsed.cflags)?;
96 Ok(Regex { parsed, prog, tree: OnceCell::new() })
97 }
98
99 pub(crate) fn is_match(&self, s: &str) -> bool {
101 let chars: Vec<u32> = s.chars().map(u32::from).collect();
102 self.prog.first(&chars, 0).is_some()
103 }
104
105 pub(crate) fn groups(&self) -> usize {
107 self.parsed.groups
108 }
109
110 pub(crate) fn find(&self, s: &str) -> Result<Option<tree::Groups>> {
116 let chars: Vec<u32> = s.chars().map(u32::from).collect();
117 self.find_chars(&chars, 0)
118 }
119
120 pub(crate) fn find_chars(&self, chars: &[u32], from: usize) -> Result<Option<tree::Groups>> {
126 self.search(chars, from).map_err(Code::error)
127 }
128
129 fn search(
131 &self,
132 chars: &[u32],
133 from: usize,
134 ) -> std::result::Result<Option<tree::Groups>, Code> {
135 let tree = self.tree.get_or_init(|| tree::Tree::new(&self.parsed));
136 let tree = tree.as_ref().map_err(|code| *code)?;
137 let mut from = from;
138 while let Some(begin) = self.prog.first(chars, from) {
139 if let Some(groups) = tree.find(chars, begin) {
140 return Ok(Some(groups));
141 }
142 from = begin + 1;
143 }
144 Ok(None)
145 }
146}
147
148const MAX_CACHED: usize = 32;
150
151type Cached = (String, u32, Rc<Regex>);
153
154thread_local! {
155 static CACHE: RefCell<Vec<Cached>> = const { RefCell::new(Vec::new()) };
157}
158
159pub(crate) fn compile(pattern: &str, cflags: u32) -> Result<Rc<Regex>> {
165 if let Some(found) = CACHE.with_borrow_mut(|cache| {
166 let at = cache.iter().position(|(p, f, _)| *f == cflags && p == pattern)?;
167 let entry = cache.remove(at);
168 let regex = Rc::clone(&entry.2);
169 cache.insert(0, entry);
170 Some(regex)
171 }) {
172 return Ok(found);
173 }
174 let regex = Rc::new(Regex::compile(pattern, cflags).map_err(Code::error)?);
175 CACHE.with_borrow_mut(|cache| {
176 cache.truncate(MAX_CACHED - 1);
177 cache.insert(0, (pattern.to_owned(), cflags, Rc::clone(®ex)));
178 });
179 Ok(regex)
180}
181
182#[derive(Clone, Debug, PartialEq, Eq)]
184pub struct AuthMatch {
185 pub group: Option<String>,
187}
188
189pub fn auth_compile(pattern: &str) -> std::result::Result<(), &'static str> {
195 Regex::compile(pattern, flags::ADVANCED).map(drop).map_err(Code::message)
196}
197
198pub(crate) fn validate(pattern: &str, cflags: u32) -> std::result::Result<(), String> {
200 Regex::compile(pattern, cflags).map(drop).map_err(|code| code.message().to_string())
201}
202
203pub fn auth_search(pattern: &str, s: &str) -> std::result::Result<Option<AuthMatch>, &'static str> {
209 let regex = Regex::compile(pattern, flags::ADVANCED).map_err(Code::message)?;
210 let points: Vec<u32> = s.chars().map(u32::from).collect();
211 let Some(groups) = regex.search(&points, 0).map_err(Code::message)? else { return Ok(None) };
212 let group = groups.get(1).copied().flatten().map(|span| chars(s, span));
213 Ok(Some(AuthMatch { group }))
214}
215
216fn arg(args: &[Value], at: usize) -> Result<&str> {
218 args.get(at).and_then(Value::as_str).ok_or_else(bad_value)
219}
220
221fn matches(args: &[Value], cflags: u32) -> Result<bool> {
223 Ok(compile(arg(args, 1)?, cflags)?.is_match(arg(args, 0)?))
224}
225
226fn regexeq(_: &Call<'_>, args: &[Value]) -> Result<Value> {
227 Ok(Value::Bool(matches(args, flags::ADVANCED)?))
228}
229
230fn regexne(_: &Call<'_>, args: &[Value]) -> Result<Value> {
231 Ok(Value::Bool(!matches(args, flags::ADVANCED)?))
232}
233
234fn icregexeq(_: &Call<'_>, args: &[Value]) -> Result<Value> {
235 Ok(Value::Bool(matches(args, flags::ADVANCED | flags::ICASE)?))
236}
237
238fn icregexne(_: &Call<'_>, args: &[Value]) -> Result<Value> {
239 Ok(Value::Bool(!matches(args, flags::ADVANCED | flags::ICASE)?))
240}
241
242fn chars(s: &str, (so, eo): (usize, usize)) -> String {
244 s.chars().skip(so).take(eo - so).collect()
245}
246
247fn substring(s: &str, re: &Regex) -> Result<Value> {
249 let Some(groups) = re.find(s)? else { return Ok(Value::Null) };
250 let at = usize::from(re.groups() > 0);
251 Ok(groups[at].map_or(Value::Null, |span| Value::Text(chars(s, span))))
252}
253
254fn textregexsubstr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
256 substring(arg(args, 0)?, &*compile(arg(args, 1)?, flags::ADVANCED)?)
257}
258
259pub(crate) fn similar_substring(_: &Call<'_>, args: &[Value]) -> Result<Value> {
261 let run = || {
262 let pattern = similar_escape_internal(arg(args, 1)?, Some(arg(args, 2)?))?;
263 substring(arg(args, 0)?, &*compile(&pattern, flags::ADVANCED)?)
264 };
265 run().map_err(|e| e.with_context("SQL function \"substring\" statement 1"))
267}
268
269fn similar_escape_internal(pattern: &str, escape: Option<&str>) -> Result<String> {
271 let escape = match escape {
272 None => Some('\\'),
273 Some(e) => {
274 let mut chars = e.chars();
275 let first = chars.next();
276 if chars.next().is_some() {
277 return Err(Error::new(SqlState::INVALID_ESCAPE_SEQUENCE, "invalid escape string")
278 .with_hint("Escape string must be empty or one character."));
279 }
280 first
281 }
282 };
283 let wide_escape = escape.is_some_and(|e| e.len_utf8() > 1);
285 let mut out = String::from("^(?:");
286 let mut after_escape = false;
287 let mut quotes = 0;
288 let mut bracket_depth = 0;
289 let mut class_pos = 0;
290 for c in pattern.chars() {
291 if wide_escape && c.len_utf8() > 1 {
292 if after_escape {
293 out.push('\\');
294 out.push(c);
295 after_escape = false;
296 } else if Some(c) == escape {
297 after_escape = true;
298 } else {
299 out.push(c);
300 }
301 continue;
302 }
303 if after_escape {
304 if c == '"' && bracket_depth < 1 {
305 match quotes {
306 0 => out.push_str("){1,1}?("),
307 1 => out.push_str("){1,1}(?:"),
308 _ => {
309 return Err(Error::new(
310 SqlState::INVALID_USE_OF_ESCAPE_CHARACTER,
311 "SQL regular expression may not contain more than two escape-double-quote separators",
312 ));
313 }
314 }
315 quotes += 1;
316 } else {
317 out.push('\\');
318 out.push(c);
319 class_pos = 3;
320 }
321 after_escape = false;
322 } else if Some(c) == escape {
323 after_escape = true;
324 } else if bracket_depth > 0 {
325 if c == '\\' {
326 out.push('\\');
327 }
328 out.push(c);
329 if c == ']' && class_pos > 2 {
330 bracket_depth -= 1;
331 } else if c == '[' {
332 bracket_depth += 1;
333 class_pos = 3;
334 } else if c == '^' {
335 class_pos += 1;
336 } else {
337 class_pos = 3;
338 }
339 } else {
340 match c {
341 '[' => {
342 out.push(c);
343 bracket_depth = 1;
344 class_pos = 1;
345 }
346 '%' => out.push_str(".*"),
347 '_' => out.push('.'),
348 '(' => out.push_str("(?:"),
349 '\\' | '.' | '^' | '$' => {
350 out.push('\\');
351 out.push(c);
352 }
353 _ => out.push(c),
354 }
355 }
356 }
357 out.push_str(")$");
358 Ok(out)
359}
360
361fn similar_to_escape_1(_: &Call<'_>, args: &[Value]) -> Result<Value> {
363 Ok(Value::Text(similar_escape_internal(arg(args, 0)?, None)?))
364}
365
366fn similar_to_escape_2(_: &Call<'_>, args: &[Value]) -> Result<Value> {
368 Ok(Value::Text(similar_escape_internal(arg(args, 0)?, Some(arg(args, 1)?))?))
369}
370
371fn similar_escape(_: &Call<'_>, args: &[Value]) -> Result<Value> {
373 if matches!(args.first(), Some(Value::Null)) {
374 return Ok(Value::Null);
375 }
376 let escape = match args.get(1) {
377 Some(Value::Null) | None => None,
378 Some(_) => Some(arg(args, 1)?),
379 };
380 Ok(Value::Text(similar_escape_internal(arg(args, 0)?, escape)?))
381}
382
383fn re_flags(options: Option<&str>) -> Result<(u32, bool)> {
385 let mut cflags = flags::ADVANCED;
386 let mut glob = false;
387 for c in options.unwrap_or_default().chars() {
388 match c {
389 'g' => glob = true,
390 'b' => cflags &= !(flags::ADVANCED | flags::EXTENDED | flags::QUOTE),
391 'c' => cflags &= !flags::ICASE,
392 'e' => {
393 cflags |= flags::EXTENDED;
394 cflags &= !(flags::ADVANCED | flags::QUOTE);
395 }
396 'i' => cflags |= flags::ICASE,
397 'm' | 'n' => cflags |= flags::NEWLINE,
398 'p' => {
399 cflags |= flags::NLSTOP;
400 cflags &= !flags::NLANCH;
401 }
402 'q' => {
403 cflags |= flags::QUOTE;
404 cflags &= !(flags::ADVANCED | flags::EXTENDED);
405 }
406 's' => cflags &= !flags::NEWLINE,
407 't' => cflags &= !flags::EXPANDED,
408 'w' => {
409 cflags &= !flags::NLSTOP;
410 cflags |= flags::NLANCH;
411 }
412 'x' => cflags |= flags::EXPANDED,
413 _ => {
414 return Err(Error::new(
415 SqlState::INVALID_PARAMETER_VALUE,
416 format!("invalid regular expression option: \"{c}\""),
417 ));
418 }
419 }
420 }
421 Ok((cflags, glob))
422}
423
424fn flags_at(args: &[Value], at: usize) -> Result<(u32, bool)> {
426 re_flags(if args.len() > at { Some(arg(args, at)?) } else { None })
427}
428
429fn options(args: &[Value]) -> Result<(u32, bool)> {
431 flags_at(args, 2)
432}
433
434fn no_global(function: &str) -> Error {
436 Error::new(
437 SqlState::INVALID_PARAMETER_VALUE,
438 format!("{function} does not support the \"global\" option"),
439 )
440}
441
442fn regexp_match(_: &Call<'_>, args: &[Value]) -> Result<Value> {
444 let (cflags, glob) = options(args)?;
445 if glob {
446 return Err(
447 no_global("regexp_match()").with_hint("Use the regexp_matches function instead.")
448 );
449 }
450 let s = arg(args, 0)?;
451 let re = compile(arg(args, 1)?, cflags)?;
452 let Some(groups) = re.find(s)? else { return Ok(Value::Null) };
453 let spans = if re.groups() > 0 { &groups[1..] } else { &groups[..1] };
454 let values = spans.iter().map(|span| span.map(|span| Value::Text(chars(s, span))));
455 Ok(Value::Array(Box::new(Array::one(values.collect()))))
456}
457
458fn regexp_like(_: &Call<'_>, args: &[Value]) -> Result<Value> {
460 let (cflags, glob) = options(args)?;
461 if glob {
462 return Err(no_global("regexp_like()"));
463 }
464 Ok(Value::Bool(compile(arg(args, 1)?, cflags)?.is_match(arg(args, 0)?)))
465}
466
467fn text(chars: &[u32], (so, eo): (usize, usize)) -> String {
469 chars[so..eo].iter().filter_map(|&c| char::from_u32(c)).collect()
470}
471
472fn next_search((so, eo): (usize, usize)) -> usize {
474 if so == eo { eo + 1 } else { eo }
475}
476
477fn whole(groups: &tree::Groups) -> Result<(usize, usize)> {
479 groups.first().copied().flatten().ok_or_else(|| Error::internal("a match without a span"))
480}
481
482fn setup_matches(
484 chars: &[u32],
485 re: &Regex,
486 start: usize,
487 glob: bool,
488 subpatterns: bool,
489 ignore_degenerate: bool,
490) -> Result<Vec<tree::Groups>> {
491 let subpatterns = subpatterns && re.groups() > 0;
492 let mut out = Vec::new();
493 let mut search = start;
494 let mut prev_end = 0;
495 while search <= chars.len() {
496 let Some(groups) = re.find_chars(chars, search)? else { break };
497 let (so, eo) = whole(&groups)?;
498 if !ignore_degenerate || (so < chars.len() && eo > prev_end) {
499 out.push(if subpatterns { groups[1..].to_vec() } else { groups[..1].to_vec() });
500 }
501 prev_end = eo;
502 if !glob {
503 break;
504 }
505 search = next_search((so, eo));
506 }
507 Ok(out)
508}
509
510fn bad_parameter(name: &str, value: i64) -> Error {
512 Error::new(
513 SqlState::INVALID_PARAMETER_VALUE,
514 format!("invalid value for parameter \"{name}\": {value}"),
515 )
516}
517
518fn param(
520 args: &[Value],
521 (at, name): (usize, &str),
522 default: i64,
523 valid: impl Fn(i64) -> bool,
524) -> Result<i64> {
525 if args.len() <= at {
526 return Ok(default);
527 }
528 let value = args.get(at).and_then(Value::as_i64).ok_or_else(bad_value)?;
529 if valid(value) { Ok(value) } else { Err(bad_parameter(name, value)) }
530}
531
532fn start_at(start: i64) -> usize {
534 usize::try_from(start - 1).unwrap_or(0)
535}
536
537fn substitute(out: &mut String, replacement: &str, groups: &tree::Groups, chars: &[u32]) {
539 let mut it = replacement.chars().peekable();
540 while let Some(c) = it.next() {
541 if c != '\\' {
542 out.push(c);
543 continue;
544 }
545 let span = match it.peek() {
546 Some(&d @ '1'..='9') => groups.get(d as usize - '0' as usize).copied().flatten(),
547 Some('&') => groups.first().copied().flatten(),
548 Some('\\') => {
549 it.next();
550 out.push('\\');
551 continue;
552 }
553 _ => {
554 out.push('\\');
555 continue;
556 }
557 };
558 it.next();
559 if let Some(span) = span {
560 out.push_str(&text(chars, span));
561 }
562 }
563}
564
565fn replace(s: &str, re: &Regex, replacement: &str, start: usize, n: i64) -> Result<Value> {
567 let chars: Vec<u32> = s.chars().map(u32::from).collect();
568 let mut out = String::new();
569 let mut done = 0;
570 let mut search = start;
571 let mut count = 0;
572 while search <= chars.len() {
573 let Some(groups) = re.find_chars(&chars, search)? else { break };
574 let span = whole(&groups)?;
575 count += 1;
576 if n > 0 && count != n {
577 search = next_search(span);
578 continue;
579 }
580 out.push_str(&text(&chars, (done, span.0)));
581 substitute(&mut out, replacement, &groups, &chars);
582 done = span.1;
583 if n > 0 {
584 break;
585 }
586 search = next_search(span);
587 }
588 out.push_str(&text(&chars, (done, chars.len())));
589 Ok(Value::Text(out))
590}
591
592fn textregexreplace_noopt(_: &Call<'_>, args: &[Value]) -> Result<Value> {
594 let re = compile(arg(args, 1)?, flags::ADVANCED)?;
595 replace(arg(args, 0)?, &re, arg(args, 2)?, 0, 1)
596}
597
598fn textregexreplace(_: &Call<'_>, args: &[Value]) -> Result<Value> {
600 let options = arg(args, 3)?;
601 if let Some(first) = options.chars().next().filter(char::is_ascii_digit) {
602 return Err(Error::new(
603 SqlState::INVALID_PARAMETER_VALUE,
604 format!("invalid regular expression option: \"{first}\""),
605 )
606 .with_hint("If you meant to use regexp_replace() with a start parameter, cast the fourth argument to integer explicitly."));
607 }
608 let (cflags, glob) = re_flags(Some(options))?;
609 let re = compile(arg(args, 1)?, cflags)?;
610 replace(arg(args, 0)?, &re, arg(args, 2)?, 0, i64::from(!glob))
611}
612
613fn textregexreplace_extended(_: &Call<'_>, args: &[Value]) -> Result<Value> {
615 let start = param(args, (3, "start"), 1, |v| v > 0)?;
616 let n = param(args, (4, "n"), 1, |v| v >= 0)?;
617 let (cflags, glob) = flags_at(args, 5)?;
618 let n = if args.len() <= 4 { i64::from(!glob) } else { n };
619 let re = compile(arg(args, 1)?, cflags)?;
620 replace(arg(args, 0)?, &re, arg(args, 2)?, start_at(start), n)
621}
622
623fn all_flags(args: &[Value], at: usize, function: &str) -> Result<u32> {
625 let (cflags, glob) = flags_at(args, at)?;
626 if glob {
627 return Err(no_global(function));
628 }
629 Ok(cflags)
630}
631
632fn regexp_count(_: &Call<'_>, args: &[Value]) -> Result<Value> {
634 let start = param(args, (2, "start"), 1, |v| v > 0)?;
635 let cflags = all_flags(args, 3, "regexp_count()")?;
636 let re = compile(arg(args, 1)?, cflags)?;
637 let chars: Vec<u32> = arg(args, 0)?.chars().map(u32::from).collect();
638 let found = setup_matches(&chars, &re, start_at(start), true, false, false)?;
639 Ok(Value::Int4(i32::try_from(found.len()).unwrap_or(i32::MAX)))
640}
641
642fn nth_span(
644 chars: &[u32],
645 re: &Regex,
646 start: i64,
647 n: i64,
648 subexpr: i64,
649) -> Result<Option<(usize, usize)>> {
650 let found = setup_matches(chars, re, start_at(start), true, subexpr > 0, false)?;
651 let npatterns = if subexpr > 0 && re.groups() > 0 { re.groups() } else { 1 };
652 let (Ok(n), Ok(subexpr)) = (usize::try_from(n), usize::try_from(subexpr)) else {
653 return Ok(None);
654 };
655 if n > found.len() || subexpr > npatterns {
656 return Ok(None);
657 }
658 Ok(found[n - 1][subexpr.saturating_sub(1)])
659}
660
661fn regexp_instr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
663 let start = param(args, (2, "start"), 1, |v| v > 0)?;
664 let n = param(args, (3, "n"), 1, |v| v > 0)?;
665 let endoption = param(args, (4, "endoption"), 0, |v| v == 0 || v == 1)?;
666 let subexpr = param(args, (6, "subexpr"), 0, |v| v >= 0)?;
667 let cflags = all_flags(args, 5, "regexp_instr()")?;
668 let re = compile(arg(args, 1)?, cflags)?;
669 let chars: Vec<u32> = arg(args, 0)?.chars().map(u32::from).collect();
670 let pos = nth_span(&chars, &re, start, n, subexpr)?
671 .map_or(0, |(so, eo)| if endoption == 1 { eo + 1 } else { so + 1 });
672 Ok(Value::Int4(i32::try_from(pos).unwrap_or(i32::MAX)))
673}
674
675fn regexp_substr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
677 let start = param(args, (2, "start"), 1, |v| v > 0)?;
678 let n = param(args, (3, "n"), 1, |v| v > 0)?;
679 let subexpr = param(args, (5, "subexpr"), 0, |v| v >= 0)?;
680 let cflags = all_flags(args, 4, "regexp_substr()")?;
681 let re = compile(arg(args, 1)?, cflags)?;
682 let chars: Vec<u32> = arg(args, 0)?.chars().map(u32::from).collect();
683 Ok(nth_span(&chars, &re, start, n, subexpr)?
684 .map_or(Value::Null, |span| Value::Text(text(&chars, span))))
685}
686
687pub(crate) fn regexp_matches(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
689 let (cflags, glob) = options(args)?;
690 let re = compile(arg(args, 1)?, cflags)?;
691 let chars: Vec<u32> = arg(args, 0)?.chars().map(u32::from).collect();
692 let found = setup_matches(&chars, &re, 0, glob, true, false)?;
693 Ok(found
694 .iter()
695 .map(|spans| {
696 let values = spans.iter().map(|span| span.map(|span| Value::Text(text(&chars, span))));
697 vec![Value::Array(Box::new(Array::one(values.collect())))]
698 })
699 .collect())
700}
701
702fn split(args: &[Value], function: &str) -> Result<Vec<String>> {
704 let cflags = all_flags(args, 2, function)?;
705 let re = compile(arg(args, 1)?, cflags)?;
706 let chars: Vec<u32> = arg(args, 0)?.chars().map(u32::from).collect();
707 let found = setup_matches(&chars, &re, 0, true, false, true)?;
708 let mut parts = Vec::with_capacity(found.len() + 1);
709 let mut from = 0;
710 for groups in &found {
711 let (so, eo) = whole(groups)?;
712 parts.push(text(&chars, (from, so)));
713 from = eo;
714 }
715 parts.push(text(&chars, (from, chars.len())));
716 Ok(parts)
717}
718
719pub(crate) fn regexp_split_to_table(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
721 Ok(split(args, "regexp_split_to_table()")?
722 .into_iter()
723 .map(|part| vec![Value::Text(part)])
724 .collect())
725}
726
727fn regexp_split_to_array(_: &Call<'_>, args: &[Value]) -> Result<Value> {
729 let parts = split(args, "regexp_split_to_array()")?;
730 let values = parts.into_iter().map(|part| Some(Value::Text(part))).collect();
731 Ok(Value::Array(Box::new(Array::one(values))))
732}
733
734pub(crate) fn by_src(src: &str) -> Option<Kernel> {
736 Some(match src {
737 "textregexeq" | "nameregexeq" => regexeq,
738 "textregexne" | "nameregexne" => regexne,
739 "texticregexeq" | "nameicregexeq" => icregexeq,
740 "texticregexne" | "nameicregexne" => icregexne,
741 "textregexsubstr" => textregexsubstr,
742 "similar_to_escape_1" => similar_to_escape_1,
743 "similar_to_escape_2" => similar_to_escape_2,
744 "similar_escape" => similar_escape,
745 "regexp_match" | "regexp_match_no_flags" => regexp_match,
746 "regexp_like" | "regexp_like_no_flags" => regexp_like,
747 "textregexreplace_noopt" => textregexreplace_noopt,
748 "textregexreplace" => textregexreplace,
749 "textregexreplace_extended"
750 | "textregexreplace_extended_no_n"
751 | "textregexreplace_extended_no_flags" => textregexreplace_extended,
752 "regexp_count" | "regexp_count_no_start" | "regexp_count_no_flags" => regexp_count,
753 "regexp_instr"
754 | "regexp_instr_no_start"
755 | "regexp_instr_no_n"
756 | "regexp_instr_no_endoption"
757 | "regexp_instr_no_flags"
758 | "regexp_instr_no_subexpr" => regexp_instr,
759 "regexp_substr"
760 | "regexp_substr_no_start"
761 | "regexp_substr_no_n"
762 | "regexp_substr_no_flags"
763 | "regexp_substr_no_subexpr" => regexp_substr,
764 "regexp_split_to_array" | "regexp_split_to_array_no_flags" => regexp_split_to_array,
765 _ => return None,
766 })
767}
768
769#[cfg(test)]
770mod tests {
771 use super::*;
772
773 fn is(pattern: &str, s: &str) -> bool {
774 Regex::compile(pattern, flags::ADVANCED)
775 .map(|r| r.is_match(s))
776 .unwrap_or_else(|code| panic!("{pattern}: {code:?}"))
777 }
778
779 fn err(pattern: &str) -> Code {
780 Regex::compile(pattern, flags::ADVANCED)
781 .err()
782 .unwrap_or_else(|| panic!("{pattern} compiled"))
783 }
784
785 #[test]
786 fn matches_like_postgres() {
787 assert!(is("^(pg_class)$", "pg_class"));
788 assert!(!is("^(pg_class)$", "pg_classes"));
789 assert!(is("a.c", "xabcx"));
790 assert!(is("^a{2,3}$", "aaa"));
791 assert!(!is("^a{2,3}$", "aaaa"));
792 assert!(is("(a*)*b", "aaab"));
793 assert!(is("^(a+)\\1$", "aaaa"));
794 assert!(!is("^(a+)\\1$", "aaa"));
795 assert!(is("foo(?=bar)", "foobar"));
796 assert!(!is("foo(?!bar)", "foobar"));
797 assert!(is("(?<=a)b", "ab"));
798 assert!(!is("(?<!a)b", "ab"));
799 assert!(is("\\mfoo\\M", "a foo b"));
800 assert!(!is("\\mfoo\\M", "afoob"));
801 assert!(is("[[:digit:]]+", "x12"));
802 assert!(is("[^a-c]", "abcd"));
803 assert!(!is("[^a-c]", "abc"));
804 }
805
806 #[test]
807 fn errors_like_postgres() {
808 assert_eq!(err("*a"), Code::BadRpt);
809 assert_eq!(err("a)"), Code::Paren);
810 assert_eq!(err("(a"), Code::Paren);
811 assert_eq!(err("[a"), Code::Brack);
812 assert_eq!(err("a{3,2}"), Code::BadBr);
813 assert_eq!(err("[z-a]"), Code::Range);
814 assert_eq!(err("[[:foo:]]"), Code::Ctype);
815 assert_eq!(err("\\1(a)"), Code::Subreg);
816 }
817}