Commit f738cc98 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-29095 REGEXP_REPLACE treats empty strings different than REPLACE in ORACLE mode

Turning REGEXP_REPLACE into two schema-qualified functions:
- mariadb_schema.regexp_replace()
- oracle_schema.regexp_replace()

Fixing oracle_schema.regexp_replace(subj,pattern,replacement) to treat
NULL in "replacement" as an empty string.

Adding new classes implementing oracle_schema.regexp_replace():
- Item_func_regexp_replace_oracle
- Create_func_regexp_replace_oracle

Adding helper methods:
- String *Item::val_str_null_to_empty(String *to)
- String *Item::val_str_null_to_empty(String *to, bool null_to_empty)

and reusing these methods in both Item_func_replace and
Item_func_regexp_replace.
parent 3699a7e1
......@@ -1772,6 +1772,85 @@ Level Code Message
Note 1003 select trim(both ' ' from 'a') AS "oracle_schema.TRIM(BOTH ' ' FROM 'a')"
Warnings:
Note 1003 select trim(both ' ' from 'a') AS "oracle_schema.TRIM(BOTH ' ' FROM 'a')"
CALL p3('REGEXP_REPLACE(''test'',''t'','''')');
----------
sql_mode='' qualifier=''
query
EXPLAIN EXTENDED SELECT REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select regexp_replace('test','t','') AS `REGEXP_REPLACE('test','t','')`
----------
sql_mode='' qualifier='unknown_schema.'
query
EXPLAIN EXTENDED SELECT unknown_schema.REGEXP_REPLACE('test','t','')
errmsg
ERROR: FUNCTION unknown_schema.REGEXP_REPLACE does not exist
----------
sql_mode='' qualifier='mariadb_schema.'
query
EXPLAIN EXTENDED SELECT mariadb_schema.REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select regexp_replace('test','t','') AS `mariadb_schema.REGEXP_REPLACE('test','t','')`
----------
sql_mode='' qualifier='maxdb_schema.'
query
EXPLAIN EXTENDED SELECT maxdb_schema.REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select regexp_replace('test','t','') AS `maxdb_schema.REGEXP_REPLACE('test','t','')`
----------
sql_mode='' qualifier='oracle_schema.'
query
EXPLAIN EXTENDED SELECT oracle_schema.REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select oracle_schema.regexp_replace('test','t','') AS `oracle_schema.REGEXP_REPLACE('test','t','')`
----------
sql_mode='ORACLE' qualifier=''
query
EXPLAIN EXTENDED SELECT REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select regexp_replace('test','t','') AS "REGEXP_REPLACE('test','t','')"
----------
sql_mode='ORACLE' qualifier='unknown_schema.'
query
EXPLAIN EXTENDED SELECT unknown_schema.REGEXP_REPLACE('test','t','')
errmsg
ERROR: FUNCTION unknown_schema.REGEXP_REPLACE does not exist
----------
sql_mode='ORACLE' qualifier='mariadb_schema.'
query
EXPLAIN EXTENDED SELECT mariadb_schema.REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select mariadb_schema.regexp_replace('test','t','') AS "mariadb_schema.REGEXP_REPLACE('test','t','')"
----------
sql_mode='ORACLE' qualifier='maxdb_schema.'
query
EXPLAIN EXTENDED SELECT maxdb_schema.REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select mariadb_schema.regexp_replace('test','t','') AS "maxdb_schema.REGEXP_REPLACE('test','t','')"
----------
sql_mode='ORACLE' qualifier='oracle_schema.'
query
EXPLAIN EXTENDED SELECT oracle_schema.REGEXP_REPLACE('test','t','')
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Level Code Message
Note 1003 select regexp_replace('test','t','') AS "oracle_schema.REGEXP_REPLACE('test','t','')"
Warnings:
Note 1003 select regexp_replace('test','t','') AS "oracle_schema.REGEXP_REPLACE('test','t','')"
CALL p3('CONCAT_OPERATOR_ORACLE(''a'')');
----------
sql_mode='' qualifier=''
......
SET sql_mode=ORACLE;
#
# MDEV-29095 REGEXP_REPLACE treats empty strings different than REPLACE in ORACLE mode
#
CREATE TABLE t1 (replacement VARCHAR(10));
INSERT INTO t1 VALUES (NULL), ('');
SELECT replacement, REGEXP_REPLACE('abba','a',replacement) FROM t1 ORDER BY replacement;
replacement REGEXP_REPLACE('abba','a',replacement)
NULL bb
bb
DROP TABLE t1;
SELECT REGEXP_REPLACE('abba','a',null);
REGEXP_REPLACE('abba','a',null)
bb
EXPLAIN EXTENDED SELECT REPLACE('abba','a',null) ;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Warnings:
Note 1003 select replace('abba','a',NULL) AS "REPLACE('abba','a',null)"
CREATE VIEW v1 AS SELECT REPLACE('abba','a',null) ;
SHOW CREATE VIEW v1;
View Create View character_set_client collation_connection
v1 CREATE VIEW "v1" AS select replace('abba','a',NULL) AS "REPLACE('abba','a',null)" latin1 latin1_swedish_ci
SELECT * FROM v1;
REPLACE('abba','a',null)
bb
SET sql_mode=DEFAULT;
SHOW CREATE VIEW v1;
View Create View character_set_client collation_connection
v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select oracle_schema.replace('abba','a',NULL) AS `REPLACE('abba','a',null)` latin1 latin1_swedish_ci
SELECT * FROM v1;
REPLACE('abba','a',null)
bb
DROP VIEW v1;
......@@ -165,6 +165,7 @@ CALL p3('TRIM(1,2)');
CALL p3('TRIM(''a'')');
CALL p3('TRIM(BOTH '' '' FROM ''a'')');
CALL p3('REGEXP_REPLACE(''test'',''t'','''')');
# Deprecated compatibility XXX_ORACLE functions.
# These functions are implemented as simple native functions
......
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-29095 REGEXP_REPLACE treats empty strings different than REPLACE in ORACLE mode
--echo #
#SELECT REGEXP_REPLACE(null,'a','b') ;
#SELECT REGEXP_REPLACE('ab',null,'b') ;
#SELECT REGEXP_REPLACE('ab','a',null) ;
#SELECT REGEXP_REPLACE('ab',null,null) ;
CREATE TABLE t1 (replacement VARCHAR(10));
INSERT INTO t1 VALUES (NULL), ('');
SELECT replacement, REGEXP_REPLACE('abba','a',replacement) FROM t1 ORDER BY replacement;
DROP TABLE t1;
SELECT REGEXP_REPLACE('abba','a',null);
EXPLAIN EXTENDED SELECT REPLACE('abba','a',null) ;
CREATE VIEW v1 AS SELECT REPLACE('abba','a',null) ;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
SET sql_mode=DEFAULT;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
DROP VIEW v1;
......@@ -904,6 +904,19 @@ class Item: public Value_source,
expressions with subqueries in the ORDER/GROUP clauses.
*/
String *val_str() { return val_str(&str_value); }
String *val_str_null_to_empty(String *to)
{
String *res= val_str(to);
if (res)
return res;
to->set_charset(collation.collation);
to->length(0);
return to;
}
String *val_str_null_to_empty(String *to, bool null_to_empty)
{
return null_to_empty ? val_str_null_to_empty(to) : val_str(to);
}
virtual Item_func *get_item_func() { return NULL; }
const MY_LOCALE *locale_from_val_str();
......
......@@ -2666,7 +2666,10 @@ class Create_func_regexp_instr : public Create_func_arg2
class Create_func_regexp_replace : public Create_func_arg3
{
public:
virtual Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3);
Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) override
{
return new (thd->mem_root) Item_func_regexp_replace(thd, arg1, arg2, arg3);
}
static Create_func_regexp_replace s_singleton;
......@@ -2675,6 +2678,28 @@ class Create_func_regexp_replace : public Create_func_arg3
virtual ~Create_func_regexp_replace() = default;
};
Create_func_regexp_replace Create_func_regexp_replace::s_singleton;
class Create_func_regexp_replace_oracle : public Create_func_arg3
{
public:
Item *create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3) override
{
return new (thd->mem_root) Item_func_regexp_replace_oracle(thd, arg1,
arg2, arg3);
}
static Create_func_regexp_replace_oracle s_singleton;
protected:
Create_func_regexp_replace_oracle() = default;
virtual ~Create_func_regexp_replace_oracle() = default;
};
Create_func_regexp_replace_oracle
Create_func_regexp_replace_oracle::s_singleton;
class Create_func_regexp_substr : public Create_func_arg2
{
......@@ -6464,15 +6489,6 @@ Create_func_regexp_instr::create_2_arg(THD *thd, Item *arg1, Item *arg2)
}
Create_func_regexp_replace Create_func_regexp_replace::s_singleton;
Item*
Create_func_regexp_replace::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *arg3)
{
return new (thd->mem_root) Item_func_regexp_replace(thd, arg1, arg2, arg3);
}
Create_func_regexp_substr Create_func_regexp_substr::s_singleton;
Item*
......@@ -7616,6 +7632,8 @@ const Native_func_registry func_array_oracle_overrides[] =
{ { STRING_WITH_LEN("LENGTH") }, BUILDER(Create_func_char_length)},
{ { STRING_WITH_LEN("LPAD") }, BUILDER(Create_func_lpad_oracle)},
{ { STRING_WITH_LEN("LTRIM") }, BUILDER(Create_func_ltrim_oracle)},
{ { STRING_WITH_LEN("REGEXP_REPLACE") },
BUILDER(Create_func_regexp_replace_oracle)},
{ { STRING_WITH_LEN("RPAD") }, BUILDER(Create_func_rpad_oracle)},
{ { STRING_WITH_LEN("RTRIM") }, BUILDER(Create_func_rtrim_oracle)},
......
......@@ -1149,8 +1149,7 @@ bool Item_func_reverse::fix_length_and_dec()
Fix that this works with binary strings when using USE_MB
*/
String *Item_func_replace::val_str_internal(String *str,
String *empty_string_for_null)
String *Item_func_replace::val_str_internal(String *str, bool null_to_empty)
{
DBUG_ASSERT(fixed == 1);
String *res,*res2,*res3;
......@@ -1168,13 +1167,8 @@ String *Item_func_replace::val_str_internal(String *str,
res=args[0]->val_str(str);
if (args[0]->null_value)
goto null;
res2=args[1]->val_str(&tmp_value);
if (args[1]->null_value)
{
if (!empty_string_for_null)
goto null;
res2= empty_string_for_null;
}
if (!(res2= args[1]->val_str_null_to_empty(&tmp_value, null_to_empty)))
goto null;
res->set_charset(collation.collation);
#ifdef USE_MB
......@@ -1191,12 +1185,8 @@ String *Item_func_replace::val_str_internal(String *str,
if (binary_cmp && (offset=res->strstr(*res2)) < 0)
return res;
#endif
if (!(res3=args[2]->val_str(&tmp_value2)))
{
if (!empty_string_for_null)
goto null;
res3= empty_string_for_null;
}
if (!(res3= args[2]->val_str_null_to_empty(&tmp_value2, null_to_empty)))
goto null;
from_length= res2->length();
to_length= res3->length();
......@@ -1279,7 +1269,7 @@ String *Item_func_replace::val_str_internal(String *str,
}
while ((offset=res->strstr(*res2,(uint) offset)) >= 0);
}
if (empty_string_for_null && !res->length())
if (null_to_empty && !res->length())
goto null;
return res;
......@@ -1385,20 +1375,22 @@ bool Item_func_regexp_replace::append_replacement(String *str,
}
String *Item_func_regexp_replace::val_str(String *str)
String *Item_func_regexp_replace::val_str_internal(String *str,
bool null_to_empty)
{
DBUG_ASSERT(fixed == 1);
char buff0[MAX_FIELD_WIDTH];
char buff2[MAX_FIELD_WIDTH];
String tmp0(buff0,sizeof(buff0),&my_charset_bin);
String tmp2(buff2,sizeof(buff2),&my_charset_bin);
String *source= args[0]->val_str(&tmp0);
String *replace= args[2]->val_str(&tmp2);
String *source, *replace;
LEX_CSTRING src, rpl;
int startoffset= 0;
if ((null_value= (args[0]->null_value || args[2]->null_value ||
re.recompile(args[1]))))
if ((null_value=
(!(source= args[0]->val_str(&tmp0)) ||
!(replace= args[2]->val_str_null_to_empty(&tmp2, null_to_empty)) ||
re.recompile(args[1]))))
return (String *) 0;
if (!(source= re.convert_if_needed(source, &re.subject_converter)) ||
......
......@@ -353,12 +353,13 @@ class Item_func_reverse :public Item_str_func
class Item_func_replace :public Item_str_func
{
String tmp_value,tmp_value2;
protected:
String *val_str_internal(String *str, bool null_to_empty);
public:
Item_func_replace(THD *thd, Item *org, Item *find, Item *replace):
Item_str_func(thd, org, find, replace) {}
String *val_str(String *to) { return val_str_internal(to, NULL); };
String *val_str(String *to) { return val_str_internal(to, false); };
bool fix_length_and_dec();
String *val_str_internal(String *str, String *empty_string_for_null);
const Schema *schema() const { return &mariadb_schema; }
void print(String *str, enum_query_type query_type)
{
......@@ -377,7 +378,7 @@ class Item_func_replace_oracle :public Item_func_replace
public:
Item_func_replace_oracle(THD *thd, Item *org, Item *find, Item *replace):
Item_func_replace(thd, org, find, replace) {}
String *val_str(String *to) { return val_str_internal(to, &tmp_emtpystr); };
String *val_str(String *to) { return val_str_internal(to, true); };
const Schema *schema() const { return &oracle_schema_ref; }
void print(String *str, enum_query_type query_type)
{
......@@ -401,10 +402,18 @@ class Item_func_regexp_replace :public Item_str_func
bool append_replacement(String *str,
const LEX_CSTRING *source,
const LEX_CSTRING *replace);
protected:
String *val_str_internal(String *str, bool null_to_empty);
public:
Item_func_regexp_replace(THD *thd, Item *a, Item *b, Item *c):
Item_str_func(thd, a, b, c)
{}
const Schema *schema() const { return &mariadb_schema; }
void print(String *str, enum_query_type query_type)
{
print_sql_mode_qualified_name(str, query_type);
print_args_parenthesized(str, query_type);
}
void cleanup()
{
DBUG_ENTER("Item_func_regex::cleanup");
......@@ -412,7 +421,10 @@ class Item_func_regexp_replace :public Item_str_func
re.cleanup();
DBUG_VOID_RETURN;
}
String *val_str(String *str);
String *val_str(String *str)
{
return val_str_internal(str, false);
}
bool fix_fields(THD *thd, Item **ref);
bool fix_length_and_dec();
const char *func_name() const { return "regexp_replace"; }
......@@ -420,6 +432,26 @@ class Item_func_regexp_replace :public Item_str_func
};
class Item_func_regexp_replace_oracle: public Item_func_regexp_replace
{
public:
Item_func_regexp_replace_oracle(THD *thd, Item *a, Item *b, Item *c)
:Item_func_regexp_replace(thd, a, b, c)
{}
const Schema *schema() const { return &oracle_schema_ref; }
bool fix_length_and_dec()
{
bool rc= Item_func_regexp_replace::fix_length_and_dec();
maybe_null= true; // Empty result is converted to NULL
return rc;
}
String *val_str(String *str)
{
return val_str_internal(str, true);
}
};
class Item_func_regexp_substr :public Item_str_func
{
Regexp_processor_pcre re;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment