From 17818c89a0344c3fb0943f53c0c074251d065fdb Mon Sep 17 00:00:00 2001
From: halfspawn <j.brauge@qualiac.com>
Date: Thu, 27 Apr 2017 15:37:29 +0200
Subject: [PATCH] substring

---
 .../suite/compat/oracle/r/func_substr.result       | 16 ++++++++
 mysql-test/suite/compat/oracle/t/func_substr.test  | 10 +++++
 sql/item_strfunc.cc                                | 45 +++++++++++++++++++++-
 sql/item_strfunc.h                                 | 17 +++++++-
 sql/sql_lex.cc                                     | 17 ++++++++
 sql/sql_lex.h                                      |  3 +-
 sql/sql_yacc.yy                                    | 12 ++----
 sql/sql_yacc_ora.yy                                | 12 ++----
 8 files changed, 112 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 0000000..898080c
--- /dev/null
+++ b/mysql-test/suite/compat/oracle/r/func_substr.result
@@ -0,0 +1,16 @@
+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
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 0000000..a00f84c
--- /dev/null
+++ b/mysql-test/suite/compat/oracle/t/func_substr.test
@@ -0,0 +1,10 @@
+#
+# Testing substr : if start index is equal to 0, act as if it's equals to 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;
diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc
index 447648c..32fd0bd 100644
--- a/sql/item_strfunc.cc
+++ b/sql/item_strfunc.cc
@@ -1772,10 +1772,14 @@ void Item_func_right::fix_length_and_dec()
 
 String *Item_func_substr::val_str(String *str)
 {
+  return substr_value(str, args[1]->val_int());
+}
+
+
+String *Item_func_substr::substr_value(String *str, longlong start)
+{
   DBUG_ASSERT(fixed == 1);
   String *res  = args[0]->val_str(str);
-  /* must be longlong to avoid truncation */
-  longlong start= args[1]->val_int();
   /* 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;
@@ -1844,6 +1848,43 @@ void Item_func_substr::fix_length_and_dec()
   max_length*= collation.collation->mbmaxlen;
 }
 
+String *Item_func_substr_oracle::val_str(String *str)
+{
+  longlong start= args[1]->val_int();
+  return Item_func_substr::substr_value(str, start == 0 ? 1 : start);
+}
+
+
+void Item_func_substr_oracle::fix_length_and_dec()
+{
+  max_length= args[0]->max_length;
+
+  agg_arg_charsets_for_string_result(collation, args, 1);
+  DBUG_ASSERT(collation.collation != NULL);
+  if (args[1]->const_item())
+  {
+    int32 start= (int32) args[1]->val_int();
+    if (start == 0)
+      start= 1;
+
+    if (args[1]->null_value)
+      max_length= 0;
+    else if (start < 0)
+      max_length= ((uint)(-start) > max_length) ? 0 : (uint)(-start);
+    else
+      max_length-= MY_MIN((uint)(start - 1), max_length);
+  }
+  if (arg_count == 3 && args[2]->const_item())
+  {
+    int32 length= (int32) args[2]->val_int();
+    if (args[2]->null_value || length <= 0)
+      max_length= 0; /* purecov: inspected */
+    else
+      set_if_smaller(max_length, (uint) length);
+  }
+  max_length*= collation.collation->mbmaxlen;
+}
+
 
 void Item_func_substr_index::fix_length_and_dec()
 { 
diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h
index e4b11fd..19cd83c 100644
--- a/sql/item_strfunc.h
+++ b/sql/item_strfunc.h
@@ -510,14 +510,29 @@ class Item_func_substr :public Item_str_func
   String tmp_value;
 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();
+  String *substr_value(String *str, longlong start);
   const char *func_name() const { return "substr"; }
   Item *get_copy(THD *thd, MEM_ROOT *mem_root)
   { return get_item_copy<Item_func_substr>(thd, mem_root, this); }
 };
 
+class Item_func_substr_oracle :public Item_func_substr
+{
+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) {}
+  String *val_str(String *);
+  void fix_length_and_dec();
+  const char *func_name() const { return "substr_oracle"; }
+  Item *get_copy(THD *thd, MEM_ROOT *mem_root)
+  { return get_item_copy<Item_func_substr_oracle>(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 6d16b21..485c4c3 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -7041,6 +7041,7 @@ bool LEX::sp_add_cfetch(THD *thd, const LEX_CSTRING *name)
   return false;
 }
 
+
 Item *LEX::make_item_func_replace(THD *thd,
                                   Item *org,
                                   Item *find,
@@ -7050,3 +7051,19 @@ Item *LEX::make_item_func_replace(THD *thd,
     new (thd->mem_root) Item_func_replace_oracle(thd, org, find, replace) :
     new (thd->mem_root) Item_func_replace(thd, org, find, replace);
 }
+
+
+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);
+}
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index f65f8a1..02a6015 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -3598,7 +3598,8 @@ struct LEX: public Query_tables_list
   void check_automatic_up(enum sub_select_type type);
 
   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);
 };
 
 
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 0eef2b5..c636ac7 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -9733,26 +9733,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 e18c62d..c748069 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -9824,26 +9824,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
-- 
2.6.3.windows.1

