Skip to content

MDEV-39520: REGEXP_INSTR(): add the optional MySQL 8.0 arguments - #5035

Open
MohamedM216 wants to merge 1 commit into
MariaDB:mainfrom
MohamedM216:mysql8-extended-regexp
Open

MohamedM216 wants to merge 1 commit into
MariaDB:mainfrom
MohamedM216:mysql8-extended-regexp

Conversation

@MohamedM216

@MohamedM216 MohamedM216 commented May 3, 2026 •

Copy link
Copy Markdown
Contributor

Reference Issue

MDEV-39520

Description

This PR implements the MySQL 8.0 extension of REGEXP_INSTR():

REGEXP_INSTR(subject, pattern[, pos[, occurrence[, return_option[, match_type]]]])
  • pos: the 1-based character position to start the search from (default 1)
  • occurrence: which occurrence of a match to report (default 1)
  • return_option: 0 = position of the match start, 1 = position after the match end (default 0)
  • match_type: 'c', 'i', 'm', 'n', 'u', same letters as REGEXP_LIKE()

Examples

SELECT REGEXP_INSTR('abbabba', 'b{2}', 1, 2);             # 5
SELECT REGEXP_INSTR('abcabc', 'b', 1, 1, 1);              # 3 (position after the match)
SELECT REGEXP_INSTR('Abba', 'ABBA', 1, 1, 0, 'i');        # 1 (case-insensitive)
SELECT REGEXP_INSTR(_utf8mb4 X'CEB26161', '(?=a)', 1, 2); # 3, matches MySQL 8.0

Implementation notes

  • Item_func_regexp_instr::val_int() iterates the matches with Regexp_processor_pcre::exec() and converts the byte offset of the requested occurrence into a 1-based character position with CHARSET_INFO::numchars(), the same way MySQL 8.0 (ICU) counts positions.
  • Zero-length match progression: after an empty match the search continues one character past the match end (ICU does the same, one code point past an empty match). The character length is read at the match end, not at the position the search started from: a zero-width match like (?=x) is normally found away from the search start, and restarting the search inside a multibyte character makes PCRE2 report a UTF-8 error.
  • A constant match_type is parsed at prepare time. A non-constant one is re-applied per row, and Regexp_processor_pcre::compile() caches the compiled pattern keyed on (pattern, library flags), so the regex is only recompiled when the effective flags actually change.
  • Charsets that are not ASCII-compatible (ucs2, utf16, utf32) are converted before the match_type letters are parsed, so both the parsing and the error messages see the real value, not raw bytes.
  • NULL in any argument propagates; a negative/zero pos or occurrence, a return_option outside {0,1} raise ER_WRONG_PARAMETERS_TO_NATIVE_FCT; an invalid match_type letter raises ER_WRONG_VALUE_FOR_TYPE; an argument above the 32-bit range can never match and yields 0.

Differences from MySQL 8.0

As agreed with the @sanja-byelkin , all remaining incompatibilities are documented in
the commit message. In short:

  1. An empty pattern is accepted (MySQL 8.0 rejects it with ER_REGEXP_ILLEGAL_ARGUMENT); pre-existing MariaDB behaviour of the 2-argument form.
  2. Error codes differ (ER_WRONG_PARAMETERS_TO_NATIVE_FCT, ER_WRONG_VALUE_FOR_TYPE instead of the ICU-specific ER_REGEXP_*).
  3. The 'u' match_type letter is accepted as a no-op.
  4. The MariaDB-only @@default_regex_flags remains in effect; a match_type argument only overrides the case/multiline/dotall parts.
  5. The dialect is PCRE2 rather than ICU; Unicode property classes, case folding and some constructs can differ in edge cases.
  6. Subjects with bytes that are not valid UTF-8 produce a warning and "no match" (PCRE2 UTF error); MySQL 8.0/ICU may behave differently. Not covered by the test file because the warning text is PCRE2-version specific.

Test

New test mysql-test/main/regexp_instr_mysql8.test covers all 2..6-argument forms, multibyte subjects, zero-length matches and zero-width lookaheads (found away from the search start), NULL and unsigned argument propagation, non-constant match_type (recompilation through the (pattern, flags) cache), prepared statements, and the error paths.

@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch 2 times, most recently from d2ac796 to ef7d499 Compare May 3, 2026 16:29
@gkodinov gkodinov added the External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. label May 4, 2026

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution! This is a preliminary review.

First of all: this is implementing just one sub-task of what https://jira.mariadb.org/browse/MDEV-39106 calls for. Either do all of it, as scoped in the jira, in a single go. Or create Jira sub-task(s) into the above jira and target these instead.

There's also some suggestions below.

Comment thread sql/item_cmpfunc.cc Resolved
Comment thread sql/item_cmpfunc.h Resolved
@MohamedM216 MohamedM216 changed the title MDEV-39106: Add MySQL 8.0 extended syntax for REGEXP_INSTR MDEV-39520: Add MySQL 8.0 extended syntax for REGEXP_INSTR May 4, 2026
@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch 2 times, most recently from d0e479a to 2634104 Compare May 4, 2026 17:46
@MohamedM216
MohamedM216 requested a review from gkodinov May 4, 2026 19:07

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the failing buildbot tests. Some improvement suggestions below too.

Comment thread sql/item_cmpfunc.cc Resolved
Comment thread sql/item_cmpfunc.h Resolved
@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch from 2634104 to 1518554 Compare May 5, 2026 12:35
@MohamedM216

Copy link
Copy Markdown
Contributor Author

The current failing tests aren't related to my patch and they pass locally.

@MohamedM216
MohamedM216 requested a review from gkodinov May 5, 2026 17:52

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Please stand by for the final review.

@sanja-byelkin sanja-byelkin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found implementation problem should be fixed. Also performance and functionality test should be made after code review done

Comment thread sql/item_cmpfunc.cc Resolved
Comment thread sql/item_cmpfunc.cc
{
char mt_buf[64];
String mt_tmp(mt_buf, sizeof(mt_buf), &my_charset_latin1);
String *match_type_str= args[5]->val_str(&mt_tmp);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If It was constant and flags was calculated on fix field what sens to repeat here again and again the same exercise?

Comment thread sql/item_cmpfunc.cc Resolved
Comment thread sql/item_cmpfunc.cc Resolved
@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch from 1518554 to 96f67b0 Compare May 14, 2026 20:53
@MohamedM216
MohamedM216 requested a review from sanja-byelkin May 14, 2026 21:06
@MohamedM216

Copy link
Copy Markdown
Contributor Author

Thanks @sanja-byelkin for the detailed feedback!

@MohamedM216

Copy link
Copy Markdown
Contributor Author

Hi @sanja-byelkin, just following up on this PR. I'd appreciate your feedback. Thanks!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends MariaDB’s REGEXP_INSTR() implementation to support MySQL 8.0-style extended syntax (2–6 arguments: pos, occurrence, return_option, match_type) and adds an MTR test to validate compatibility and edge cases.

Changes:

  • Switch REGEXP_INSTR function creation to native-function parsing to accept 2–6 arguments.
  • Implement extended-argument semantics in Item_func_regexp_instr (positioning, occurrences, return option, match_type flag parsing).
  • Add mysql-test coverage for the extended syntax (regexp_instr_mysql8.test/.result).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
sql/item_create.cc Updates function factory to accept 2–6 args for REGEXP_INSTR.
sql/item_cmpfunc.h Extends regexp processor API and updates Item_func_regexp_instr ctor to take an argument list.
sql/item_cmpfunc.cc Implements MySQL 8.0 extended REGEXP_INSTR behavior, including match_type parsing and occurrence scanning.
mysql-test/main/regexp_instr_mysql8.test Adds a new MTR test covering the extended syntax and edge cases.
mysql-test/main/regexp_instr_mysql8.result Expected results for the new MTR test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sql/item_cmpfunc.h Resolved
Comment thread sql/item_cmpfunc.cc Resolved
Comment thread sql/item_cmpfunc.cc Outdated
Comment on lines 6598 to 6601
// args[1]: pattern (may be recompiled per row)
if ((null_value= re.recompile(args[1])))
return 0;

Comment thread sql/item_cmpfunc.cc Resolved
@sanja-byelkin

sanja-byelkin commented Jun 9, 2026 •

Copy link
Copy Markdown
Member

I buildbot above tests failing with memory leak around regexp

@MohamedM216

Copy link
Copy Markdown
Contributor Author

@sanja-byelkin I believe these failures are unrelated to my PR; the mariabackup.incremental_compressed crash is an InnoDB redo log recovery assertion in log0recv.cc and perfschema.socket_connect is a networking test.

@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch from 96f67b0 to 3152786 Compare June 9, 2026 18:24
@MohamedM216
MohamedM216 requested a review from gkodinov June 20, 2026 20:58

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Please consider squashing the two commits before the end of the review.

A stylistic change proposed below.

Comment thread sql/item_cmpfunc.cc Resolved
Comment thread sql/item_cmpfunc.cc Resolved
Comment thread sql/item_cmpfunc.cc Resolved
@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch from 3152786 to 14cbc27 Compare June 22, 2026 17:02
@MohamedM216
MohamedM216 requested a review from gkodinov June 22, 2026 17:09
@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch 2 times, most recently from ff294d6 to d638ca5 Compare September 12, 2026 08:33
@MohamedM216

Copy link
Copy Markdown
Contributor Author

First of all is it MDEV-39520 or MDEV-39106 (PR title, commit comment and test headers contradicts)

It's MDEV-39520. I've updated the commit message. Sorry for confusion.

@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch from d638ca5 to 18a2b36 Compare September 12, 2026 12:47
@MohamedM216

MohamedM216 commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor Author

@sanja-byelkin Thanks for the detailed feedback. I've pushed a new patch. Looking forward to your review. Thanks!

@MohamedM216

Copy link
Copy Markdown
Contributor Author

https://buildbot.mariadb.org/#builders/719/builds/18033 has environment-specific encoding issue. It passes locally and on other buidbots. Also the only part that fails is the Multibyte characters.

@sanja-byelkin

Copy link
Copy Markdown
Member

It looks like the fail connected to the fix https://buildbot.mariadb.org/#/builders/719/builds/18033/steps/9/logs/stdio

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on Sanja's behalf

@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch 2 times, most recently from 9bb64dd to 11a699c Compare October 1, 2026 14:52
@MohamedM216

Copy link
Copy Markdown
Contributor Author

It looks like the fail connected to the fix https://buildbot.mariadb.org/#/builders/719/builds/18033/steps/9/logs/stdio

@sanja-byelkin @gkodinov Fixed. Since we can't reliably force the mysqltest client's file-I/O charset from within the .test file via SQL, I've replaced the raw multibyte strings with _utf8mb4 X'...' HEX literals. This keeps the test file 100% ASCII and bypasses the buildbot's locale while still thoroughly testing the multibyte regex logic.

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not mind a good workaround. Approved. With one suggestion.

@MohamedM216

Copy link
Copy Markdown
Contributor Author

@gkodinov I've already added the original strings as comments above each query in the .test file. Do you mean to echo them as well in the .result file?

@gkodinov

gkodinov commented Oct 2, 2026

Copy link
Copy Markdown
Member

@gkodinov I've already added the original strings as comments above each query in the .test file. Do you mean to echo them as well in the .result file?

not really. there's one you've missed though.

@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch from 11a699c to 98558d5 Compare October 2, 2026 11:04
@MohamedM216

Copy link
Copy Markdown
Contributor Author

@gkodinov Yeah! You mean the last query. Actually, it uses the same string as the query before it. I've added the comment again, BTW.

@MohamedM216
MohamedM216 requested a review from gkodinov October 8, 2026 20:16

@gkodinov gkodinov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Please keep working with the final reviewer.

@MohamedM216

Copy link
Copy Markdown
Contributor Author

Hi @sanja-byelkin, I'm just following up on this PR. Thanks!

@sanja-byelkin sanja-byelkin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of 98558d5. I built both this PR and MySQL 8.4.13 (bundled ICU 77.1) and ran the same queries on both; "MySQL:" values below are from that run, not from the manual (the manual says nothing about errors for bad arguments).

Blocker

  • Zero-length match advance is measured at the wrong place (sql/item_cmpfunc.cc, val_int(), the match_end == match_start branch). After an empty match the code adds the length of the character at byte_offset (where the search started) instead of the character at match_end. With mixed character widths the next search skips a character or starts inside one:
    SELECT REGEXP_INSTR(_utf8mb4 X'CEB26161', '(?=a)', 1, 2);          -- 'βaa': this PR 0, MySQL 3
    SELECT REGEXP_INSTR(_utf8mb4 X'61CEB2CEB2', _utf8mb4 X'283F3DCEB229', 1, 2);
                                                                        -- 'aββ','(?=β)': this PR 0 + warning 1139
                                                                        -- "Regex error 'bad offset into UTF string'", MySQL 3
    Measure the character at subject_ptr + match_end and add both cases to the test.

Behaviour that differs from MySQL 8.0

  • pos past the end. MySQL raises error 3686 "Index out of bounds in regular expression search." already for pos = CHAR_LENGTH + 1 on a non-empty subject (REGEXP_INSTR('abc','c',4)); this PR returns 0, and the test records 0 for exactly that query. For an empty subject MySQL returns 0 for any pos, including 0.
  • pos below 1 (0, -1, 18446744073709551615): MySQL error 3686, this PR error 1583. With an empty subject MySQL returns 0 for pos 0, this PR raises 1583.
  • occurrence below 1: MySQL returns the first match (REGEXP_INSTR('abcabc','b',1,0) and ...,1,-5) give 2); this PR raises error 1583.
  • return_option not 0/1: MySQL raises 1210 "Incorrect arguments to regexp_instr: return_option must be 1 or 0." (at prepare for a constant); this PR raises 1583, whose text names no argument. The same 1583 for pos/occurrence/return_option does not tell the user which argument is wrong.
  • Unknown match_type letter: MySQL 1210 "Incorrect arguments to regexp_instr"; this PR 1411 ER_WRONG_VALUE_FOR_TYPE.
  • 'u' is accepted and ignored. MySQL maps it to UREGEX_UNIX_LINES. With PCRE2 configured for LF it happens to change nothing, but the code comment "no direct PCRE2 equivalent" is wrong (PCRE2_NEWLINE_LF / pcre2_set_newline()). Please fix the comment, or implement it.
  • @@default_regex_flags vs match_type (my earlier question): any match_type, including '', clears MULTILINE and DOTALL coming from @@default_regex_flags:
    SET default_regex_flags='DOTALL';
    SELECT REGEXP_INSTR('a\nb','a.b');            -- 1
    SELECT REGEXP_INSTR('a\nb','a.b',1,1,0,'');   -- 0
    In MySQL a missing match_type and '' are the same thing (there is no such variable). Please either override only the flags a letter names, or document and test this. The comment "The MySQL 8.0 behavior is that match_type fully overrides the default flags" should state what MariaDB does instead of referring to MySQL.
  • Empty patterns in the test. REGEXP_INSTR('hello',''), ('','') and ('abc','',1,N) all give error 3685 "Illegal argument to a regular expression." in MySQL. MariaDB accepted the 2-argument form before this patch, so this is not a regression, but the new occurrence tests with '' record behaviour MySQL does not have; please say so in the test or drop them.

Correctness / robustness

  • Constant match_type evaluated at prepare without !is_expensive() (fix_length_and_dec()). fix_owner() skips expensive constant patterns; the match_type path does not, so a subquery or stored function as match_type runs during prepare, and thd->is_error() is not checked after val_str(). (A two-row subquery still ends with error 1242 in my run, so I did not see a user-visible failure, but the pattern differs from the sibling.)
  • match_type is parsed as raw bytes. A ucs2/utf16 'i' is rejected with Incorrect match_type value: '' for function regexp_instr: the message shows the value as empty. MySQL rejects it too (1210), so this is about the message: please convert to ASCII before parsing, or at least format the value with ErrConvString.
  • const_cast<String*>(match_type_str)->c_ptr_safe() in the error path can write to / reallocate the argument item's own buffer; use ErrConvString(match_type_str).ptr().
  • Hardcoded "regexp_instr" in Regexp_processor_pcre::init(match_type, ...). The processor is shared by all REGEXP functions, and the PR says the others follow; pass the owner's func_name().
  • Per-row recompilation with a non-constant match_type: init() empties m_prev_pattern, so compile() runs on every row even if neither pattern nor flags changed, and the code bypasses recompile() by hand. Remember the flags the pattern was compiled with and recompile only when they change. This also bears on the performance test I asked for earlier, which is still needed.
  • match_type evaluated twice per row (args[5]->is_null() then val_str()); drop is_null(), the null_value check after val_str() covers it.
  • pos/occurrence ignore unsigned_flag: 18446744073709551615 wraps to -1.
  • Nullability (my earlier comment): Item_func::fix_fields() already ORs MAYBE_NULL from all arguments, so the new loop after fix_owner() is a no-op and can go. One case remains: with a non-constant match_type, a failed per-row compile() returns NULL even when no argument is nullable.
  • m_match_type_is_const is not initialized in the constructor.

Code structure and style

  • The fetch / NULL / range-check / my_error block is repeated for args 2, 3, 4; check_arguments() repeats the int check three times (check_argument_types_can_return_int(2, MY_MIN(arg_count, 5)) exists); the match_type read (mt_buf[64], val_str, re.init) is duplicated in fix_length_and_dec() and val_int(). Please factor.
  • The init() overload has a different contract from the existing init() (can fail, raises an error, drops the pattern cache), so consider a separate set_match_type() called after init(), which also avoids recomputing the case rule; and document it (// false = OK, true = error is all there is now).
  • Coding style: a = b / column-aligned assignments instead of a= b, trailing whitespace on six new lines, C-style casts (size_t), (longlong), (int).
  • Comments: the m_prev_pattern reset comment limits it to "non-constant match_type with a constant pattern" but it runs on every call; say why recompile() is skipped for a non-constant match_type; say pos counts characters; "1=end pos" should be "position after the end of the match"; drop comments that restate the code (args[5]: match_type string).

Tests

  • No error path is tested: pos 0 / negative / past the end, occurrence 0, return_option 2, an unknown letter (constant and from a column), 1 and 7 arguments.
  • No zero-length match on a multibyte subject (would have caught the blocker); only utf8mb4 subjects; NULL only as literals.
  • "Non-constant match_type with constant pattern" uses rows 'c' and 'ic', which both resolve to case-sensitive and both print 0, so the result cannot show a per-row flag change — use 'c' and 'i'.
  • The multibyte section still uses plain # comments that do not reach the result file (my earlier --echo comment).
  • Header says "Improve REGEXP_INSTR for MySQL 8.0 compatibility" (neither the ticket title nor the commit subject); footer should be --echo # End of 13.2 tests.

Commit message

  • The body says the multibyte section keeps the test file "100% pure ASCII", but the # comments there contain raw UTF-8.
  • Please describe the user-visible behaviour: the new errors for bad arguments, the @@default_regex_flags interaction, and that the result can now be NULL from the new arguments. The first paragraph could name the use case (queries ported from MySQL 8.0 failing with error 1582). The PR description ("one of a series") cannot serve as the squashed commit message; use the commit's own message.

@sanja-byelkin

Copy link
Copy Markdown
Member

Just to be clear I do not request one-to-one MySQL compatibilities just all incompatibilities (which we do not can/want fix) should be listed (probably so far commit message is the best place)

@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch 2 times, most recently from add8548 to e3b6f45 Compare November 13, 2026 20:43
@MohamedM216

MohamedM216 commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @sanja-byelkin , thanks for the feedback!

Zero-length match advance is measured at the wrong place (sql/item_cmpfunc.cc, val_int(), the match_end == match_start branch). After an empty match the code adds the length of the character at byte_offset (where the search started) instead of the character at match_end. With mixed character widths the next search skips a character or starts inside one

FIXED.

Correctness / robustness

applied

and tests are updated.

The patch https://github.com/MariaDB/server/compare/add85486f1f9f2f914baa3e4b19196c8471a7ffd..e3b6f45d5925c7694aaa130c2576287b535ad917

The differences between MariaDB and MySQL are listed in the commit message and in the PR description.

Thanks!

@MohamedM216 MohamedM216 changed the title MDEV-39520: Add MySQL 8.0 extended syntax for REGEXP_INSTR MDEV-39520: REGEXP_INSTR(): add the optional MySQL 8.0 arguments Nov 13, 2026
REGEXP_INSTR(subject, pattern[, pos[, occurrence[, return_option[,
             match_type]]]])

Implement the optional arguments that MySQL 8.0 added to REGEXP_INSTR():

- pos            1-based character position to start the search from
                 (default 1),
- occurrence     the 1-based number of the match to report (default 1),
- return_option  0 = position of the first character of the match,
                 1 = position right after the match end (default 0),
- match_type     'c', 'i', 'm', 'n', 'u' -- the same letters as in
                 REGEXP_LIKE()/REGEXP_SUBSTR()/REGEXP_REPLACE().

All positions are character positions: the byte offsets returned by
PCRE2 (in the library charset, utf8mb4) are converted to 1-based code
point positions with CHARSET_INFO::numchars(), the same way MySQL 8.0
(ICU) counts them.

After a zero-length match the next occurrence cannot be found at the
same position again: the search continues one character past the end
of the match, exactly as MySQL 8.0 (ICU) continues one code point past
an empty match. The character length is read at the end of the match,
not at the position the search started from: a zero-width match like
"(?=x)" is normally found away from the position the search started
at, and restarting the search inside a multibyte character would make
PCRE2 report a UTF-8 error.

A constant match_type is parsed at prepare time (a constant NULL
match_type makes every row return NULL, a constant invalid letter
raises the error at prepare time). A non-constant match_type is
re-parsed and re-applied per row; Regexp_processor_pcre::compile()
caches the compiled pattern keyed on both the pattern and the library
flags, so the regular expression is only recompiled when the effective
flags actually change.

match_type is parsed byte by byte as ASCII. Charsets that are not
ASCII-compatible (ucs2, utf16, utf32) are converted to latin1 first,
so both the parsing and the error message operate on the real value,
not on raw bytes.

Error handling:
- a NULL subject/pattern/pos/occurrence/return_option/match_type
  argument makes the result NULL;
- a negative pos/occurrence/return_option, a zero pos or occurrence,
  and a return_option outside {0,1} raise
  ER_WRONG_PARAMETERS_TO_NATIVE_FCT;
- an invalid match_type letter raises ER_WRONG_VALUE_FOR_TYPE;
- a pos or occurrence above the 32-bit range can never match and makes
  the function return 0;
- if the requested occurrence does not exist, the function returns 0.

The new test mysql-test/main/regexp_instr_mysql8.test covers all
2..6-argument forms, multibyte subjects, zero-length matches (including
zero-width lookaheads found away from the search start), NULL and
unsigned argument propagation, non-constant match_type (recompilation
through the (pattern, flags) cache), prepared statements and the error
paths.

Differences from MySQL 8.0
--------------------------
The MariaDB regexp functions run on PCRE2 rather than ICU. All of the
differences below are inherent to the existing MariaDB regexp
implementation (shared with REGEXP_LIKE/REGEXP_SUBSTR/REGEXP_REPLACE),
not newly introduced by this patch:

1. An empty pattern is accepted and matches at every character
   position. MySQL 8.0 rejects it with ER_REGEXP_ILLEGAL_ARGUMENT
   (3685, "Illegal argument to a regular expression").

2. The error codes differ. Out-of-range pos/occurrence/return_option
   raise ER_WRONG_PARAMETERS_TO_NATIVE_FCT (1210) and an invalid
   match_type letter raises ER_WRONG_VALUE_FOR_TYPE (1411); MySQL 8.0
   reports regexp-specific ICU errors instead.

3. The 'u' match_type letter (Unix-only line endings) is accepted as a
   no-op: PCRE2 has no direct equivalent of ICU's Unix-only line end
   handling.

4. The MariaDB-only @@default_regex_flags variable remains in effect;
   a match_type argument only overrides the case sensitivity,
   multiline and dotall parts of the flags.

5. The regexp dialect is PCRE2, so Unicode property classes, case
   folding and some pattern constructs can behave differently from ICU
   in edge cases.

6. A subject whose bytes are not valid UTF-8 (possible with a binary
   literal introduced as _utf8mb4) makes PCRE2 report a UTF-8 error,
   which is pushed as a warning and treated as "no match" by
   REGEXP_INSTR(). MySQL 8.0 (ICU) may behave differently for such
   input. This is deliberately not covered by the test file, because
   the warning text is PCRE2-version specific.
@MohamedM216
MohamedM216 force-pushed the mysql8-extended-regexp branch from e3b6f45 to 80a090e Compare November 13, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements.

Development

Successfully merging this pull request may close these issues.

4 participants