diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php index 61cfb37d75e..89c14552325 100644 --- a/lib/dml/moodle_database.php +++ b/lib/dml/moodle_database.php @@ -2515,6 +2515,30 @@ abstract class moodle_database { return ''; } + /** + * Returns the word-beginning boundary marker if this database driver supports regex syntax when searching. + * @return string The word-beginning boundary marker. Otherwise, an empty string. + */ + public function sql_regex_get_word_beginning_boundary_marker() { + if ($this->sql_regex_supported()) { + return '[[:<:]]'; + } + + return ''; + } + + /** + * Returns the word-end boundary marker if this database driver supports regex syntax when searching. + * @return string The word-end boundary marker. Otherwise, an empty string. + */ + public function sql_regex_get_word_end_boundary_marker() { + if ($this->sql_regex_supported()) { + return '[[:>:]]'; + } + + return ''; + } + /** * Returns the SQL that allows to find intersection of two or more queries * diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php index a91de206bd9..3f718772f8d 100644 --- a/lib/dml/mysqli_native_moodle_database.php +++ b/lib/dml/mysqli_native_moodle_database.php @@ -244,9 +244,9 @@ class mysqli_native_moodle_database extends moodle_database { /** * Set 'dbcollation' option * - * @return string $dbcollation + * @return string|null $dbcollation */ - private function detect_collation(): string { + private function detect_collation(): ?string { if ($this->external) { return null; } @@ -1889,6 +1889,38 @@ class mysqli_native_moodle_database extends moodle_database { return $collation . ($positivematch ? 'REGEXP' : 'NOT REGEXP'); } + /** + * Returns the word-beginning boundary marker based on MySQL version. + * @return string The word-beginning boundary marker. + */ + public function sql_regex_get_word_beginning_boundary_marker() { + $ismysql = ($this->get_dbtype() == 'mysqli' || $this->get_dbtype() == 'auroramysql'); + $ismysqlge8d0d4 = ($ismysql && version_compare($this->get_server_info()['version'], '8.0.4', '>=')); + if ($ismysqlge8d0d4) { + return '\\b'; + } + // Prior to MySQL 8.0.4, MySQL used the Henry Spencer regular expression library to support regular expression operations, + // rather than International Components for Unicode (ICU). + // MariaDB still supports the "old marker" (MDEV-5357). + return '[[:<:]]'; + } + + /** + * Returns the word-end boundary marker based on MySQL version. + * @return string The word-end boundary marker. + */ + public function sql_regex_get_word_end_boundary_marker() { + $ismysql = ($this->get_dbtype() == 'mysqli' || $this->get_dbtype() == 'auroramysql'); + $ismysqlge8d0d4 = ($ismysql && version_compare($this->get_server_info()['version'], '8.0.4', '>=')); + if ($ismysqlge8d0d4) { + return '\\b'; + } + // Prior to MySQL 8.0.4, MySQL used the Henry Spencer regular expression library to support regular expression operations, + // rather than International Components for Unicode (ICU). + // MariaDB still supports the "old marker" (MDEV-5357). + return '[[:>:]]'; + } + /** * Returns the SQL to be used in order to an UNSIGNED INTEGER column to SIGNED. * diff --git a/lib/searchlib.php b/lib/searchlib.php index 440eb4b9f29..de3840bc917 100644 --- a/lib/searchlib.php +++ b/lib/searchlib.php @@ -418,6 +418,8 @@ function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $ if ($DB->sql_regex_supported()) { $REGEXP = $DB->sql_regex(true); $NOTREGEXP = $DB->sql_regex(false); + $regexwordbegin = $DB->sql_regex_get_word_beginning_boundary_marker(); + $regexwordend = $DB->sql_regex_get_word_end_boundary_marker(); } $params = array(); @@ -451,40 +453,40 @@ function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $ switch($type){ case TOKEN_STRING: $SQLString .= "((".$DB->sql_like($datafield, ":$name1", false).") OR (".$DB->sql_like($metafield, ":$name2", false)."))"; - $params[$name1] = "%$value%"; - $params[$name2] = "%$value%"; + $params[$name1] = "%$value%"; + $params[$name2] = "%$value%"; break; case TOKEN_EXACT: $SQLString .= "(($datafield $REGEXP :$name1) OR ($metafield $REGEXP :$name2))"; - $params[$name1] = "[[:<:]]".$value."[[:>:]]"; - $params[$name2] = "[[:<:]]".$value."[[:>:]]"; + $params[$name1] = $regexwordbegin.$value.$regexwordend; + $params[$name2] = $regexwordbegin.$value.$regexwordend; break; case TOKEN_META: if ($metafield != '') { $SQLString .= "(".$DB->sql_like($metafield, ":$name1", false).")"; - $params[$name1] = "%$value%"; + $params[$name1] = "%$value%"; } break; case TOKEN_USER: $SQLString .= "(($mainidfield = $useridfield) AND ((".$DB->sql_like($userfirstnamefield, ":$name1", false).") OR (".$DB->sql_like($userlastnamefield, ":$name2", false).")))"; - $params[$name1] = "%$value%"; - $params[$name2] = "%$value%"; + $params[$name1] = "%$value%"; + $params[$name2] = "%$value%"; break; case TOKEN_USERID: $SQLString .= "($useridfield = :$name1)"; - $params[$name1] = $value; + $params[$name1] = $value; break; case TOKEN_INSTANCE: $SQLString .= "($instancefield = :$name1)"; - $params[$name1] = $value; + $params[$name1] = $value; break; case TOKEN_DATETO: $SQLString .= "($timefield <= :$name1)"; - $params[$name1] = $value; + $params[$name1] = $value; break; case TOKEN_DATEFROM: $SQLString .= "($timefield >= :$name1)"; - $params[$name1] = $value; + $params[$name1] = $value; break; case TOKEN_TAGS: $sqlstrings = []; @@ -504,8 +506,8 @@ function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $ break; case TOKEN_NEGATE: $SQLString .= "(NOT ((".$DB->sql_like($datafield, ":$name1", false).") OR (".$DB->sql_like($metafield, ":$name2", false).")))"; - $params[$name1] = "%$value%"; - $params[$name2] = "%$value%"; + $params[$name1] = "%$value%"; + $params[$name2] = "%$value%"; break; default: return ''; diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 65aad45a492..0fe07438bd0 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -60,6 +60,8 @@ Declaration is as follow: * New function set_additional_classes() has been implemented to add additional classes to action_menu. * Most Behat functionality for the Moodle App has been removed from core, refer to the documentation in order to upgrade your testing setup: https://moodledev.io/general/app/development/testing/acceptance-testing#upgrading-tests-from-an-older-version +* New DML driver methods `$DB->sql_regex_get_word_beginning_boundary_marker` and `$DB->sql_regex_get_word_end_boundary_marker` + for managing word boundary markers in a database driver supporting regex syntax when searching. === 4.0 ===