Skip to main content

rupg_func/regex/
mod.rs

1//! The regular expressions of PostgreSQL, the engine of Henry Spencer in `src/backend/regex`, for the C locale.
2//!
3//! The lexer and the parser are ports of `regc_lex.c` and `regcomp.c`, so that a pattern gives the same error as in PostgreSQL. The matcher is not a port: it is a backtracking machine that tells if a match is in the string and where the leftmost match starts. The positions of the match and of its groups follow the rules of preference of Spencer: the tree of subexpressions of `regcomp.c` and the dissection of `regexec.c` find them.
4//!
5//! The functions here are the operators `~`, `~*`, `!~` and `!~*`, `substring` with a pattern, `similar_to_escape`, `regexp_replace`, `regexp_match`, `regexp_matches`, `regexp_like`, `regexp_count`, `regexp_instr`, `regexp_substr` and the functions that split a string at the matches. [`auth_compile`] and [`auth_search`] are the regular expressions of `pg_hba.conf` and `pg_ident.conf`.
6
7mod 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
20/// The bits of `cflags`, as in `regex.h`.
21pub(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/// The error codes of `regex.h` that the compiler can give.
34#[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    /// The message of `regerrs.h`.
54    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    /// The error of `RE_compile_and_cache`.
74    fn error(self) -> Error {
75        Error::new(
76            SqlState::INVALID_REGULAR_EXPRESSION,
77            format!("invalid regular expression: {}", self.message()),
78        )
79    }
80}
81
82/// A compiled pattern.
83pub(crate) struct Regex {
84    parsed: parse::Parsed,
85    prog: exec::Program,
86    /// The tree of subexpressions, which the first search for the positions of a match builds.
87    tree: OnceCell<std::result::Result<tree::Tree, Code>>,
88}
89
90impl Regex {
91    /// `pg_regcomp` for a pattern and its flags.
92    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    /// True when the pattern matches a part of `s`.
100    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    /// `re_nsub`: the number of the groups that capture.
106    pub(crate) fn groups(&self) -> usize {
107        self.parsed.groups
108    }
109
110    /// `pg_regexec`: the positions in characters of the leftmost match in `s`, first the whole match and then each group, or `None` when the pattern does not match.
111    ///
112    /// # Errors
113    ///
114    /// `2201B` for a pattern that is too complex.
115    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    /// [`Regex::find`] in the characters `chars` from the character `from`. The constraints see all the characters, also the characters before `from`.
121    ///
122    /// # Errors
123    ///
124    /// `2201B` for a pattern that is too complex.
125    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    /// [`Regex::find_chars`] with the code of `regerror` for the error.
130    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
148/// The number of patterns that `RE_compile_and_cache` keeps, `MAX_CACHED_RES`.
149const MAX_CACHED: usize = 32;
150
151/// A compiled pattern with its source and its flags.
152type Cached = (String, u32, Rc<Regex>);
153
154thread_local! {
155    /// The patterns that the session compiled last, the newest first.
156    static CACHE: RefCell<Vec<Cached>> = const { RefCell::new(Vec::new()) };
157}
158
159/// `RE_compile_and_cache`: the compiled pattern, from the cache when it is there.
160///
161/// # Errors
162///
163/// `2201B` for a pattern that is not valid.
164pub(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(&regex)));
178    });
179    Ok(regex)
180}
181
182/// A match of [`auth_search`].
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub struct AuthMatch {
185    /// The text of the first group, or `None` when the pattern has no group or the first group did not match.
186    pub group: Option<String>,
187}
188
189/// `regcomp_auth_token`: compile a regular expression of `pg_hba.conf` or `pg_ident.conf` with the flags `REG_ADVANCED`.
190///
191/// # Errors
192///
193/// The message of `pg_regerror` for a pattern that is not valid.
194pub fn auth_compile(pattern: &str) -> std::result::Result<(), &'static str> {
195    Regex::compile(pattern, flags::ADVANCED).map(drop).map_err(Code::message)
196}
197
198/// The check of the pattern of `like_regex` in a `jsonpath`: the message of `pg_regerror` when `pattern` is not valid with the `cflags`.
199pub(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
203/// `regexec_auth_token`: find the regular expression `pattern` of [`auth_compile`] in `s`. The result is `None` when the pattern does not match.
204///
205/// # Errors
206///
207/// The message of `pg_regerror` for a pattern that is not valid or that is too complex.
208pub 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
216/// The string of an argument.
217fn arg(args: &[Value], at: usize) -> Result<&str> {
218    args.get(at).and_then(Value::as_str).ok_or_else(bad_value)
219}
220
221/// `RE_compile_and_execute` for an operator: the string and the pattern of the call.
222fn 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
242/// The text of `s` from the character `so` up to the character `eo`.
243fn chars(s: &str, (so, eo): (usize, usize)) -> String {
244    s.chars().skip(so).take(eo - so).collect()
245}
246
247/// `textregexsubstr` for a compiled pattern: the text of the first group, or of the whole match when the pattern has no group.
248fn 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
254/// `textregexsubstr`: `substring(string FROM pattern)`.
255fn textregexsubstr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
256    substring(arg(args, 0)?, &*compile(arg(args, 1)?, flags::ADVANCED)?)
257}
258
259/// `substring(string SIMILAR pattern ESCAPE escape)`, the function in SQL of `system_functions.sql` that calls `substring` with `similar_to_escape`.
260pub(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    // `fmgr_sql` runs the body, and its error context names the statement.
266    run().map_err(|e| e.with_context("SQL function \"substring\" statement 1"))
267}
268
269/// `similar_escape_internal`: the pattern of `SIMILAR TO` as a regular expression. The escape is `\` when it is `None`, and an empty escape means no escape.
270fn 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    // PostgreSQL reads the pattern by bytes when the escape has one byte, and by characters when it has more. A character of more than one byte then does not change the place in a bracket expression.
284    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
361/// `similar_to_escape(pattern)`, with `\` as the escape.
362fn similar_to_escape_1(_: &Call<'_>, args: &[Value]) -> Result<Value> {
363    Ok(Value::Text(similar_escape_internal(arg(args, 0)?, None)?))
364}
365
366/// `similar_to_escape(pattern, escape)`.
367fn 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
371/// `similar_escape(pattern, escape)`, the old form that is not strict: a null escape is `\`.
372fn 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
383/// `parse_re_flags`: the compile flags of the options of a function, and true for the option `g`.
384fn 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
424/// The options of a function as its argument at `at`, when it has one.
425fn flags_at(args: &[Value], at: usize) -> Result<(u32, bool)> {
426    re_flags(if args.len() > at { Some(arg(args, at)?) } else { None })
427}
428
429/// The options of a function as its third argument, when it has one.
430fn options(args: &[Value]) -> Result<(u32, bool)> {
431    flags_at(args, 2)
432}
433
434/// The error for the option `g` of a function that finds one match.
435fn 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
442/// `regexp_match(string, pattern [, flags])`: the text of each group of the first match, or of the whole match when the pattern has no group.
443fn 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
458/// `regexp_like(string, pattern [, flags])`.
459fn 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
467/// The text of the characters `chars` from `so` up to `eo`.
468fn text(chars: &[u32], (so, eo): (usize, usize)) -> String {
469    chars[so..eo].iter().filter_map(|&c| char::from_u32(c)).collect()
470}
471
472/// The position after a match that starts at `so` and ends at `eo`, where the next search starts. The search after an empty match starts one character later, or it finds the same match again.
473fn next_search((so, eo): (usize, usize)) -> usize {
474    if so == eo { eo + 1 } else { eo }
475}
476
477/// The span of the whole match.
478fn whole(groups: &tree::Groups) -> Result<(usize, usize)> {
479    groups.first().copied().flatten().ok_or_else(|| Error::internal("a match without a span"))
480}
481
482/// `setup_regexp_matches`: the spans of each match from the character `start`, or of the first match only when `glob` is false. With `subpatterns`, a match has the span of each group when the pattern has groups. Else it has the span of the whole match. With `ignore_degenerate`, an empty match at the end of the string or just after a match does not count.
483fn 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
510/// The error for a bad value of an integer parameter.
511fn 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
518/// The integer parameter `name` at `at`, or `default` when the call does not have it. `valid` tells the values that the parameter takes.
519fn 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
532/// The character where the search starts, from the parameter `start` that counts from 1.
533fn start_at(start: i64) -> usize {
534    usize::try_from(start - 1).unwrap_or(0)
535}
536
537/// `appendStringInfoRegexpSubstr`: the replacement with `\1` to `\9` as the text of each group, `\&` as the text of the match and `\\` as one backslash. A backslash before another character stays.
538fn 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
565/// `replace_text_regexp`: `s` with the match number `n` from the character `start` replaced, or each match when `n` is 0.
566fn 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
592/// `textregexreplace_noopt`: `regexp_replace(string, pattern, replacement)`, which replaces the first match.
593fn 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
598/// `textregexreplace`: `regexp_replace(string, pattern, replacement, flags)`. Options that start with a digit give an error with a hint, as the call may need the form with a start.
599fn 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
613/// `textregexreplace_extended`: `regexp_replace(string, pattern, replacement, start [, n [, flags]])`. Without `n`, the option `g` tells if each match is replaced.
614fn 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
623/// The options at `at` of a function that finds each match, which do not take `g`.
624fn 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
632/// `regexp_count(string, pattern [, start [, flags]])`: the number of the matches.
633fn 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
642/// The span of the group `subexpr` of the match number `n` from the character `start` for `regexp_instr` and `regexp_substr`, or of the whole match when `subexpr` is 0. `None` when the string has fewer matches or the pattern fewer groups.
643fn 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
661/// `regexp_instr(string, pattern [, start [, n [, endoption [, flags [, subexpr]]]]])`: the position from 1 of the start of the match, or of the character after it when `endoption` is 1, or 0.
662fn 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
675/// `regexp_substr(string, pattern [, start [, n [, flags [, subexpr]]]])`: the text of the match, or null.
676fn 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
687/// `regexp_matches(string, pattern [, flags])`: one row for each match, or for the first match without the option `g`. Each row is an array with the text of each group, or of the match when the pattern has no group.
688pub(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
702/// The parts of the string between the matches, for `regexp_split_to_table` and `regexp_split_to_array`. An empty match at the end of the string or just after a match does not split.
703fn 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
719/// `regexp_split_to_table(string, pattern [, flags])`: one row for each part.
720pub(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
727/// `regexp_split_to_array(string, pattern [, flags])`: the parts as an array.
728fn 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
734/// The kernels of the regular expression functions by `prosrc`.
735pub(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}