From 34f36a335b734b04142820117761771c55da4b2f Mon Sep 17 00:00:00 2001 From: halfspawn Date: Fri, 6 Oct 2017 09:23:06 +0200 Subject: [PATCH] MDEV-14012 - sql_mode=Oracle: substr(): treat position 0 as position 1 --- .../suite/compat/oracle/r/func_substr.result | 50 +++++++++++++++++++ .../suite/compat/oracle/t/func_substr.test | 28 +++++++++++ sql/item_create.cc | 50 +++++++++++++++++++ sql/item_strfunc.cc | 4 +- sql/item_strfunc.h | 19 ++++++- sql/sql_lex.cc | 16 ++++++ sql/sql_lex.h | 3 +- sql/sql_yacc.yy | 12 ++--- sql/sql_yacc_ora.yy | 12 ++--- 9 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 mysql-test/suite/compat/oracle/r/func_substr.result create mode 100644 mysql-test/suite/compat/oracle/t/func_substr.test diff --git a/mysql-test/suite/compat/oracle/r/func_substr.result b/mysql-test/suite/compat/oracle/r/func_substr.result new file mode 100644 index 00000000000..eca5f480191 --- /dev/null +++ b/mysql-test/suite/compat/oracle/r/func_substr.result @@ -0,0 +1,50 @@ +SET sql_mode=ORACLE; +SELECT SUBSTR('abc',2,1),SUBSTR('abc',1,1), SUBSTR('abc',0,1) FROM dual; +SUBSTR('abc',2,1) SUBSTR('abc',1,1) SUBSTR('abc',0,1) +b a a +SELECT SUBSTR('abc',2),SUBSTR('abc',1), SUBSTR('abc',0) FROM dual; +SUBSTR('abc',2) SUBSTR('abc',1) SUBSTR('abc',0) +bc abc abc +SELECT SUBSTR(null,2,1),SUBSTR(null,1), SUBSTR(null,0) FROM dual; +SUBSTR(null,2,1) SUBSTR(null,1) SUBSTR(null,0) +NULL NULL NULL +SELECT SUBSTR('abc',-2),SUBSTR('abc',-1), SUBSTR('abc',-0) FROM dual; +SUBSTR('abc',-2) SUBSTR('abc',-1) SUBSTR('abc',-0) +bc c abc +SELECT SUBSTR('abc',-2,1),SUBSTR('abc',-1,1), SUBSTR('abc',-0,1) FROM dual; +SUBSTR('abc',-2,1) SUBSTR('abc',-1,1) SUBSTR('abc',-0,1) +b c a +SELECT SUBSTR('abc',null) FROM dual; +SUBSTR('abc',null) +NULL +SELECT SUBSTR('abc',2,null),SUBSTR('abc',1,null), SUBSTR('abc',0,null) FROM dual; +SUBSTR('abc',2,null) SUBSTR('abc',1,null) SUBSTR('abc',0,null) +NULL NULL NULL +SELECT SUBSTR('abc',2,0),SUBSTR('abc',1,0), SUBSTR('abc',0,0) FROM dual; +SUBSTR('abc',2,0) SUBSTR('abc',1,0) SUBSTR('abc',0,0) + +create table t1 (c1 varchar(10),start integer, length integer); +INSERT INTO t1 VALUES ('abc', 1, 1); +INSERT INTO t1 VALUES ('abc', 0, 1); +INSERT INTO t1 VALUES (null, 1, 1); +INSERT INTO t1 VALUES (null, 0, 1); +select substr(c1,start,length) from t1; +substr(c1,start,length) +a +a +NULL +NULL +drop table t1; +EXPLAIN EXTENDED SELECT SUBSTR('abc',2,1) ; +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 substr_oracle('abc',2,1) AS "SUBSTR('abc',2,1)" +CREATE VIEW v1 AS SELECT SUBSTR('abc',2,1) ; +SHOW CREATE VIEW v1; +View Create View character_set_client collation_connection +v1 CREATE VIEW "v1" AS select substr_oracle('abc',2,1) AS "SUBSTR('abc',2,1)" latin1 latin1_swedish_ci +SELECT * FROM v1; +SUBSTR('abc',2,1) +b +DROP VIEW v1; diff --git a/mysql-test/suite/compat/oracle/t/func_substr.test b/mysql-test/suite/compat/oracle/t/func_substr.test new file mode 100644 index 00000000000..5d5ec78abdd --- /dev/null +++ b/mysql-test/suite/compat/oracle/t/func_substr.test @@ -0,0 +1,28 @@ +# +# MDEV-14012 - sql_mode=Oracle: substr(): treat position 0 as position 1 +# + +SET sql_mode=ORACLE; +SELECT SUBSTR('abc',2,1),SUBSTR('abc',1,1), SUBSTR('abc',0,1) FROM dual; +SELECT SUBSTR('abc',2),SUBSTR('abc',1), SUBSTR('abc',0) FROM dual; +SELECT SUBSTR(null,2,1),SUBSTR(null,1), SUBSTR(null,0) FROM dual; +SELECT SUBSTR('abc',-2),SUBSTR('abc',-1), SUBSTR('abc',-0) FROM dual; +SELECT SUBSTR('abc',-2,1),SUBSTR('abc',-1,1), SUBSTR('abc',-0,1) FROM dual; +SELECT SUBSTR('abc',null) FROM dual; +SELECT SUBSTR('abc',2,null),SUBSTR('abc',1,null), SUBSTR('abc',0,null) FROM dual; +SELECT SUBSTR('abc',2,0),SUBSTR('abc',1,0), SUBSTR('abc',0,0) FROM dual; + +create table t1 (c1 varchar(10),start integer, length integer); +INSERT INTO t1 VALUES ('abc', 1, 1); +INSERT INTO t1 VALUES ('abc', 0, 1); +INSERT INTO t1 VALUES (null, 1, 1); +INSERT INTO t1 VALUES (null, 0, 1); +select substr(c1,start,length) from t1; +drop table t1; + +EXPLAIN EXTENDED SELECT SUBSTR('abc',2,1) ; + +CREATE VIEW v1 AS SELECT SUBSTR('abc',2,1) ; +SHOW CREATE VIEW v1; +SELECT * FROM v1; +DROP VIEW v1; diff --git a/sql/item_create.cc b/sql/item_create.cc index fdfa95912ac..bc3ec0266ee 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -2876,6 +2876,20 @@ protected: }; +class Create_func_substr_oracle : public Create_native_func +{ +public: + virtual Item *create_native(THD *thd, LEX_CSTRING *name, + List *item_list); + + static Create_func_substr_oracle s_singleton; + +protected: + Create_func_substr_oracle() {} + virtual ~Create_func_substr_oracle() {} +}; + + class Create_func_subtime : public Create_func_arg2 { public: @@ -6466,6 +6480,40 @@ Create_func_substr_index::create_3_arg(THD *thd, Item *arg1, Item *arg2, Item *a } +Create_func_substr_oracle Create_func_substr_oracle::s_singleton; + +Item* +Create_func_substr_oracle::create_native(THD *thd, LEX_CSTRING *name, + List *item_list) +{ + Item *func= NULL; + int arg_count= item_list ? item_list->elements : 0; + + switch (arg_count) { + case 2: + { + Item *param_1= item_list->pop(); + Item *param_2= item_list->pop(); + func= new (thd->mem_root) Item_func_substr_oracle(thd, param_1, param_2); + break; + } + case 3: + { + Item *param_1= item_list->pop(); + Item *param_2= item_list->pop(); + Item *param_3= item_list->pop(); + func= new (thd->mem_root) Item_func_substr_oracle(thd, param_1, param_2, param_3); + break; + } + default: + my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name->str); + break; + } + + return func; +} + + Create_func_subtime Create_func_subtime::s_singleton; Item* @@ -7119,6 +7167,8 @@ static Native_func_registry func_array[] = { { C_STRING_WITH_LEN("ST_WITHIN") }, GEOM_BUILDER(Create_func_within)}, { { C_STRING_WITH_LEN("ST_X") }, GEOM_BUILDER(Create_func_x)}, { { C_STRING_WITH_LEN("ST_Y") }, GEOM_BUILDER(Create_func_y)}, + { { C_STRING_WITH_LEN("SUBSTR_ORACLE") }, + BUILDER(Create_func_substr_oracle)}, { { C_STRING_WITH_LEN("SUBSTRING_INDEX") }, BUILDER(Create_func_substr_index)}, { { C_STRING_WITH_LEN("SUBTIME") }, BUILDER(Create_func_subtime)}, { { C_STRING_WITH_LEN("TAN") }, BUILDER(Create_func_tan)}, diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 69f8ff185b5..d7e526906c6 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -1777,7 +1777,7 @@ String *Item_func_substr::val_str(String *str) DBUG_ASSERT(fixed == 1); String *res = args[0]->val_str(str); /* must be longlong to avoid truncation */ - longlong start= args[1]->val_int(); + longlong start= get_position(); /* Assumes that the maximum length of a String is < INT_MAX32. */ /* Limit so that code sees out-of-bound value properly. */ longlong length= arg_count == 3 ? args[2]->val_int() : INT_MAX32; @@ -1827,7 +1827,7 @@ void Item_func_substr::fix_length_and_dec() DBUG_ASSERT(collation.collation != NULL); if (args[1]->const_item()) { - int32 start= (int32) args[1]->val_int(); + int32 start= (int32) get_position(); if (args[1]->null_value) max_length= 0; else if (start < 0) diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 1c35588f884..c322136bef2 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -505,9 +505,12 @@ public: class Item_func_substr :public Item_str_func { String tmp_value; +protected: + virtual longlong get_position() { return args[1]->val_int(); } public: Item_func_substr(THD *thd, Item *a, Item *b): Item_str_func(thd, a, b) {} - Item_func_substr(THD *thd, Item *a, Item *b, Item *c): Item_str_func(thd, a, b, c) {} + Item_func_substr(THD *thd, Item *a, Item *b, Item *c): + Item_str_func(thd, a, b, c) {} String *val_str(String *); void fix_length_and_dec(); const char *func_name() const { return "substr"; } @@ -515,6 +518,20 @@ public: { return get_item_copy(thd, mem_root, this); } }; +class Item_func_substr_oracle :public Item_func_substr +{ +protected: + longlong get_position() + { longlong pos= args[1]->val_int(); return pos == 0 ? 1 : pos; } +public: + Item_func_substr_oracle(THD *thd, Item *a, Item *b): + Item_func_substr(thd, a, b) {} + Item_func_substr_oracle(THD *thd, Item *a, Item *b, Item *c): + Item_func_substr(thd, a, b, c) {} + const char *func_name() const { return "substr_oracle"; } + Item *get_copy(THD *thd, MEM_ROOT *mem_root) + { return get_item_copy(thd, mem_root, this); } +}; class Item_func_substr_index :public Item_str_func { diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 169f33f81e2..a479fc9fe30 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -7182,6 +7182,22 @@ bool LEX::add_grant_command(THD *thd, enum_sql_command sql_command_arg, } +Item *LEX::make_item_func_substr(THD *thd, Item *a, Item *b, Item *c) +{ + return (thd->variables.sql_mode & MODE_ORACLE) ? + new (thd->mem_root) Item_func_substr_oracle(thd, a, b, c) : + new (thd->mem_root) Item_func_substr(thd, a, b, c); +} + + +Item *LEX::make_item_func_substr(THD *thd, Item *a, Item *b) +{ + return (thd->variables.sql_mode & MODE_ORACLE) ? + new (thd->mem_root) Item_func_substr_oracle(thd, a, b) : + new (thd->mem_root) Item_func_substr(thd, a, b); +} + + Item *LEX::make_item_func_replace(THD *thd, Item *org, Item *find, diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 7be918f2c0b..9eeb9652022 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3355,7 +3355,8 @@ public: const char *end); Item *make_item_func_replace(THD *thd, Item *org, Item *find, Item *replace); - + Item *make_item_func_substr(THD *thd, Item *a, Item *b, Item *c); + Item *make_item_func_substr(THD *thd, Item *a, Item *b); /* Create a my_var instance for a ROW field variable that was used as an OUT SP parameter: CALL p1(var.field); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 17fcd83f681..e6cebaa1ccc 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -9865,26 +9865,22 @@ function_call_nonkeyword: } | SUBSTRING '(' expr ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5, $7); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5, $7))) MYSQL_YYABORT; } | SUBSTRING '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5))) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr FOR_SYM expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5, $7); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5, $7))) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5))) MYSQL_YYABORT; } | SYSDATE opt_time_precision diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index 3f531f07e54..7f8da103ea1 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -9895,26 +9895,22 @@ function_call_nonkeyword: } | SUBSTRING '(' expr ',' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5, $7); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5, $7))) MYSQL_YYABORT; } | SUBSTRING '(' expr ',' expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5))) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr FOR_SYM expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5, $7); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5, $7))) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr ')' { - $$= new (thd->mem_root) Item_func_substr(thd, $3, $5); - if ($$ == NULL) + if (!($$= Lex->make_item_func_substr(thd, $3, $5))) MYSQL_YYABORT; } | SYSDATE opt_time_precision