← Back to team overview

maria-developers team mailing list archive

Re: 57c19902326: MDEV-20017 Implement TO_CHAR() Oracle compatible function

 

Hi, Monty!

On Apr 13, Michael Widenius wrote:
> commit 57c19902326
> Author: Michael Widenius <michael.widenius@xxxxxxxxx>
> Date:   Sun Jan 24 23:56:43 2021 +0200
> 
> diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt
> index aecb00563f7..b23522ac830 100644
> --- a/sql/share/errmsg-utf8.txt
> +++ b/sql/share/errmsg-utf8.txt
> @@ -7975,3 +7975,5 @@ ER_PK_INDEX_CANT_BE_IGNORED
>          eng "A primary key cannot be marked as IGNORE"
>  ER_FUNCTION_CANNOT_BE_USED_IN_CLAUSE
>          eng "Function '%s' cannot be used in the %s clause"
> +ER_ORACLE_COMPAT_FUNCTION_ERROR
> +        eng "Oracle compatibility function error: %s"

Why? We normally just say "invalid argument" or something, in no other case
we say "oracle compatibility function" or "sybase compatibility function" or
"odbc compatibility function".

> diff --git a/sql/sql_string.cc b/sql/sql_string.cc
> index 95a57017c53..9c57bb22085 100644
> --- a/sql/sql_string.cc
> +++ b/sql/sql_string.cc
> @@ -1275,3 +1275,15 @@ void Binary_string::shrink(size_t arg_length)
>      }
>    }
>  }
> +
> +bool Binary_string::strfill(char fill, size_t len)
> +{
> +  if (len)
> +  {
> +    if (alloc(length() + len))
> +      return 1;
> +    memset(Ptr + str_length, fill, len);
> +    str_length+= (uint32) len;
> +  }
> +  return 0;
> +}

There's Binary_string::fill() already.

better use it or, at the very least, declare

 bool strfill(char fill, size_t len) { return fill(str_length + len, fill); }

in sql_string.h. And this swapped order of arguments is confusing,
please fix it too.

In fact, I think it's confusing to have both:

  fill(max_len, fill_char)
  strfill(fill_char, fill_length)

if you want to keep both, it'd be better to rename them to something
that somehow reflects the difference, for example:

  strfill -> append_many or append_repeated

but really, I personally would just delete strfill.

> diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h
> index af266956b05..9b78d6c159e 100644
> --- a/sql/item_timefunc.h
> +++ b/sql/item_timefunc.h
> @@ -978,6 +978,57 @@ class Item_func_time_format: public Item_func_date_format
>  };
>  
>  
> +/* the max length of datetime format models string in Oracle is 144 */
> +#define MAX_DATETIME_FORMAT_MODEL_LEN 144
> +
> +class Item_func_tochar :public Item_str_func
> +{
> +  const MY_LOCALE *locale;
> +  THD *thd;
> +  String warning_message;
> +  bool fixed_length;
> +
> +  /*
> +    When datetime format models is parsed, use uint16 integers to
> +    represent the format models and store in fmt_array.
> +  */
> +  uint16 fmt_array[MAX_DATETIME_FORMAT_MODEL_LEN+1];
> +
> +  bool check_arguments() const override
> +  {
> +    return check_argument_types_can_return_text(1, arg_count);
> +  }
> +
> +public:
> +  Item_func_tochar(THD *thd, Item *a, Item *b):
> +    Item_str_func(thd, a, b), locale(0)
> +  {
> +    /* NOTE: max length of warning message is 64 */
> +    warning_message.alloc(64);
> +    warning_message.length(0);
> +  }

As far as I understand, this warning_message was introduced to issue
the same error for every row, even if the format string is const_item and
is parsed only once, in fix_fields.

I don't think it's worth the trouble. Two simpler approaches are:
* if the format string is invalid - parse it every time even if const, or
* if the const format string is invalid - issue the error only once.

so, please, remove warning_message and just use push_warning or my_error
where the error is discovered.

> +  ~Item_func_tochar() { warning_message.free(); }
> +  String *val_str(String *str) override;
> +  LEX_CSTRING func_name_cstring() const override
> +  {
> +    static LEX_CSTRING name= {STRING_WITH_LEN("to_char") };
> +    return name;
> +  }
> +  bool fix_length_and_dec() override;
> +  bool parse_format_string(const String *format, uint *fmt_len);
> +
> +  bool check_vcol_func_processor(void *arg) override
> +  {
> +    if (arg_count > 2)
> +      return false;
> +    return mark_unsupported_function(func_name(), "()", arg, VCOL_SESSION_FUNC);
> +  }
> +
> +  Item *get_copy(THD *thd) override
> +  { return get_item_copy<Item_func_tochar>(thd, this); }
> +};
> +
> +
>  class Item_func_from_unixtime :public Item_datetimefunc
>  {
>    bool check_arguments() const override
> diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc
> index 04d913b0fca..44d2ec7912d 100644
> --- a/sql/item_timefunc.cc
> +++ b/sql/item_timefunc.cc
> @@ -1914,6 +1913,805 @@ String *Item_func_date_format::val_str(String *str)
>    return 0;
>  }
>  
> +/*
> +  Oracle has many formatting models, we list all but only part of them
> +  are implemented, because some models depend on oracle functions
> +  which mariadb is not supported.
> +
> +  Models for datetime, used by TO_CHAR/TO_DATE. Normal format characters are
> +  stored as short integer < 256, while format characters are stored as a
> +  integer > 256
> +*/
> +
> +#define FMT_BASE       128

128? or 256?

> +#define FMT_AD         FMT_BASE+1
> +#define FMT_AD_DOT     FMT_BASE+2
> +#define FMT_AM         FMT_BASE+3
> +#define FMT_AM_DOT     FMT_BASE+4
> +#define FMT_BC         FMT_BASE+5
> +#define FMT_BC_DOT     FMT_BASE+6
> +#define FMT_CC         FMT_BASE+7
> +#define FMT_SCC        FMT_BASE+8
> +#define FMT_D          FMT_BASE+9
> +#define FMT_DAY        FMT_BASE+10
> +#define FMT_DD         FMT_BASE+11
> +#define FMT_DDD        FMT_BASE+12
> +#define FMT_DL         FMT_BASE+13
> +#define FMT_DS         FMT_BASE+14
> +#define FMT_DY         FMT_BASE+15
> +#define FMT_E          FMT_BASE+16
> +#define FMT_EE         FMT_BASE+17
> +#define FMT_FF         FMT_BASE+18
> +#define FMT_FM         FMT_BASE+19
> +#define FMT_FX         FMT_BASE+20
> +#define FMT_HH         FMT_BASE+21
> +#define FMT_HH12       FMT_BASE+22
> +#define FMT_HH24       FMT_BASE+23
> +#define FMT_IW         FMT_BASE+24
> +#define FMT_I          FMT_BASE+25
> +#define FMT_IY         FMT_BASE+26
> +#define FMT_IYY        FMT_BASE+27
> +#define FMT_IYYY       FMT_BASE+28
> +#define FMT_J          FMT_BASE+29
> +#define FMT_MI         FMT_BASE+30
> +#define FMT_MM         FMT_BASE+31
> +#define FMT_MON        FMT_BASE+32
> +#define FMT_MONTH      FMT_BASE+33
> +#define FMT_PM         FMT_BASE+34
> +#define FMT_PM_DOT     FMT_BASE+35
> +#define FMT_RM         FMT_BASE+37
> +#define FMT_RR         FMT_BASE+38
> +#define FMT_RRRR       FMT_BASE+39
> +#define FMT_SS         FMT_BASE+40
> +#define FMT_SSSSSS     FMT_BASE+41
> +#define FMT_TS         FMT_BASE+42
> +#define FMT_TZD        FMT_BASE+43
> +#define FMT_TZH        FMT_BASE+44
> +#define FMT_TZM        FMT_BASE+45
> +#define FMT_TZR        FMT_BASE+46
> +#define FMT_W          FMT_BASE+47
> +#define FMT_WW         FMT_BASE+48
> +#define FMT_X          FMT_BASE+49
> +#define FMT_Y          FMT_BASE+50
> +#define FMT_YY         FMT_BASE+51
> +#define FMT_YYY        FMT_BASE+52
> +#define FMT_YYYY       FMT_BASE+53
> +#define FMT_YYYY_COMMA FMT_BASE+54
> +#define FMT_YEAR       FMT_BASE+55
> +#define FMT_SYYYY      FMT_BASE+56
> +#define FMT_SYEAR      FMT_BASE+57

Not enum? Not even safe (with parentheses) #define?
enum would be ideal here but at the very least make these defines safe.

> +
> +
> +/**
> +  Modify the quotation flag and check whether the subsequent process is skipped

Could you reword it please?

> +
> +  @param cftm             Character or FMT... format descriptor
> +  @param quotation_flag   Points to 'true' if we are inside a quoted string
> +
> +  @return true  If we are inside a quoted string or if we found a '"' character
> +  @return false Otherwise
> +*/
> +
> +static inline bool check_quotation(uint16 cfmt, bool *quotation_flag)
> +{
> +  if (cfmt == '"')
> +  {
> +    *quotation_flag= !*quotation_flag;
> +    return true;
> +  }
> +  return *quotation_flag;
> +}
> +
> +#define INVALID_CHARACTER(x) (((x) >= 'A' && (x) <= 'Z') ||((x) >= '0' && (x) <= '9') || (x) >= 127 || ((x) < 32))

why not to make this static inline too?
side-effect safe, no risk of double evaluation of x.

> +
> +
> +/**
> +  Special characters are directly output in the result
> +
> +  @return 0  If found not acceptable character
> +  @return #  Number of copied characters
> +*/
> +
> +static uint parse_special(char cfmt, const char *ptr, const char *end,
> +                         uint16 *array)
> +{
> +  int offset= 0;
> +  char tmp1;
> +
> +  /* Non-printable character and Multibyte encoded characters */
> +  if (INVALID_CHARACTER(cfmt))
> +    return 0;
> +
> +  /*
> +   * '&' with text is used for variable input, but '&' with other
> +   * special charaters like '|'. '*' is used as separator
> +   */
> +  if (cfmt == '&' && ptr + 1 < end)
> +  {
> +    tmp1= my_toupper(system_charset_info, *(ptr+1));
> +    if (tmp1 >= 'A' && tmp1 <= 'Z')
> +      return 0;
> +  }
> +
> +  do {
> +    /*
> +      Continuously store the special characters in fmt_array until non-special
> +      characters appear
> +     */
> +    *array++= (uint16) (uchar) *ptr++;
> +    offset++;
> +    if (ptr == end)
> +      break;
> +    tmp1= my_toupper(system_charset_info, *ptr);
> +  } while (!INVALID_CHARACTER(tmp1) && tmp1 != '"');
> +  return offset;
> +}
> +
> +
> +/**
> +  Parse the format string, convert it to an compact array and calculate the
> +  length of output string
> +
> +  @param format   Format string
> +  @param fmt_len  Function will store max length of formated date string here
> +
> +  @return 0 ok. fmt_len is updated
> +  @return 1 error.  In this case 'warning_string' is set to error message
> +*/
> +
> +bool Item_func_tochar::parse_format_string(const String *format, uint *fmt_len)
> +{
> +  const char *ptr, *end;
> +  uint16 *tmp_fmt= fmt_array;
> +  uint tmp_len= 0;
> +  int offset= 0;
> +  bool quotation_flag= false;
> +
> +  ptr= format->ptr();
> +  end= ptr + format->length();
> +
> +  if (format->length() > MAX_DATETIME_FORMAT_MODEL_LEN)
> +  {
> +    warning_message.append(STRING_WITH_LEN("datetime format string is too "
> +                                           "long"));
> +    return 1;
> +  }
> +
> +  for (; ptr < end; ptr++, tmp_fmt++)
> +  {
> +    uint ulen;
> +    char cfmt, next_char;
> +
> +    cfmt= my_toupper(system_charset_info, *ptr);
> +
> +    /*
> +      Oracle datetime format support text in double quotation marks like
> +      'YYYY"abc"MM"xyz"DD', When this happens, store the text and quotation
> +      marks, and use the text as a separator in make_date_time_oracle.
> +
> +      NOTE: the quotation mark is not print in return value. for example:
> +      select TO_CHAR(sysdate, 'YYYY"abc"MM"xyzDD"') will return 2021abc01xyz11
> +     */
> +    if (check_quotation(cfmt, &quotation_flag))
> +    {
> +      *tmp_fmt= *ptr;
> +      tmp_len+= 1;
> +      continue;
> +    }
> +
> +    switch (cfmt) {
> +    case 'A':                                   // AD/A.D./AM/A.M.
> +      if (ptr+1 >= end)
> +        goto error;
> +      next_char= my_toupper(system_charset_info, *(ptr+1));
> +      if (next_char == 'D')
> +      {
> +        *tmp_fmt= FMT_AD;
> +        ptr+= 1;
> +        tmp_len+= 2;
> +      }
> +      else if (next_char == 'M')
> +      {
> +        *tmp_fmt= FMT_AM;
> +        ptr+= 1;
> +        tmp_len+= 2;
> +      }
> +      else if (next_char == '.' && ptr+3 < end && *(ptr+3) == '.')
> +      {
> +        if (my_toupper(system_charset_info, *(ptr+2)) == 'D')
> +        {
> +          *tmp_fmt= FMT_AD_DOT;
> +          ptr+= 3;
> +          tmp_len+= 4;
> +        }
> +        else if (my_toupper(system_charset_info, *(ptr+2)) == 'M')
> +        {
> +          *tmp_fmt= FMT_AM_DOT;
> +          ptr+= 3;
> +          tmp_len+= 4;
> +        }
> +        else
> +          goto error;
> +      }
> +      else
> +        goto error;
> +      break;
> +    case 'B':                                     // BC and B.C
> +      if (ptr+1 >= end)
> +        goto error;
> +      next_char= my_toupper(system_charset_info, *(ptr+1));
> +      if (next_char == 'C')
> +      {
> +        *tmp_fmt= FMT_BC;
> +        ptr+= 1;
> +        tmp_len+= 2;
> +      }
> +      else if (next_char == '.' && ptr+3 < end &&
> +               my_toupper(system_charset_info, *(ptr+2)) == 'C' &&
> +               *(ptr+3) == '.')
> +      {
> +        *tmp_fmt= FMT_BC_DOT;
> +        ptr+= 3;
> +        tmp_len+= 4;
> +      }
> +      else
> +        goto error;
> +      break;
> +    case 'P':                                   // PM or P.M.
> +      next_char= my_toupper(system_charset_info, *(ptr+1));
> +      if (next_char == 'M')
> +      {
> +        *tmp_fmt= FMT_PM;
> +        ptr+= 1;
> +        tmp_len+= 2;
> +      }
> +      else if (next_char == '.' &&
> +               my_toupper(system_charset_info, *(ptr+2)) == 'M' &&
> +               my_toupper(system_charset_info, *(ptr+3)) == '.')
> +      {
> +        *tmp_fmt= FMT_PM_DOT;
> +        ptr+= 3;
> +        tmp_len+= 4;
> +      }
> +      else
> +        goto error;
> +      break;
> +    case 'Y':                                   // Y, YY, YYY o YYYYY
> +      if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'Y')
> +      {
> +        *tmp_fmt= FMT_Y;
> +        tmp_len+= 1;
> +        break;
> +      }
> +      if (ptr + 2 == end ||
> +          my_toupper(system_charset_info, *(ptr+2)) != 'Y') /* YY */
> +      {
> +        *tmp_fmt= FMT_YY;
> +        ulen= 2;
> +      }
> +      else
> +      {
> +        if (ptr + 3 < end && my_toupper(system_charset_info, *(ptr+3)) == 'Y')
> +        {
> +          *tmp_fmt= FMT_YYYY;
> +          ulen= 4;
> +        }
> +        else
> +        {
> +          *tmp_fmt= FMT_YYY;
> +          ulen= 3;
> +        }
> +      }
> +      ptr+= ulen-1;
> +      tmp_len+= ulen;
> +      break;
> +
> +    case 'R':                                   // RR or RRRR
> +      if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'R')
> +        goto error;
> +
> +      if (ptr + 2 == end || my_toupper(system_charset_info, *(ptr+2)) != 'R')
> +      {
> +        *tmp_fmt= FMT_RR;
> +        ulen= 2;
> +      }
> +      else
> +      {
> +        if (ptr + 3 >= end || my_toupper(system_charset_info, *(ptr+3)) != 'R')
> +          goto error;
> +        *tmp_fmt= FMT_RRRR;
> +        ulen= 4;
> +      }
> +      ptr+= ulen-1;
> +      tmp_len+= ulen;
> +      break;
> +    case 'M':
> +    {
> +      char tmp1;
> +      if (ptr + 1 >= end)
> +        goto error;
> +
> +      tmp1= my_toupper(system_charset_info, *(ptr+1));
> +      if (tmp1 == 'M')
> +      {
> +        *tmp_fmt= FMT_MM;
> +        tmp_len+= 2;
> +        ptr+= 1;
> +      }
> +      else if (tmp1 == 'I')
> +      {
> +        *tmp_fmt= FMT_MI;
> +        tmp_len+= 2;
> +        ptr+= 1;
> +      }
> +      else if (tmp1 == 'O')
> +      {
> +        if (ptr + 2 >= end)
> +          goto error;
> +        char tmp2= my_toupper(system_charset_info, *(ptr+2));
> +        if (tmp2 != 'N')
> +          goto error;
> +
> +        if (ptr + 4 >= end ||
> +            my_toupper(system_charset_info, *(ptr+3)) != 'T' ||
> +            my_toupper(system_charset_info, *(ptr+4)) != 'H')
> +        {
> +          *tmp_fmt= FMT_MON;
> +          tmp_len+= 3;
> +          ptr+= 2;
> +        }
> +        else
> +        {
> +          *tmp_fmt= FMT_MONTH;
> +          tmp_len+= (locale->max_month_name_length *
> +                     my_charset_utf8mb3_bin.mbmaxlen);
> +          ptr+= 4;
> +        }
> +      }
> +      else
> +        goto error;
> +    }
> +    break;
> +    case 'D':                                   // DD, DY, or DAY
> +    {
> +      if (ptr + 1 >= end)
> +        goto error;
> +      char tmp1= my_toupper(system_charset_info, *(ptr+1));
> +
> +      if (tmp1 == 'D')
> +      {
> +        *tmp_fmt= FMT_DD;
> +        tmp_len+= 2;
> +      }
> +      else if (tmp1 == 'Y')
> +      {
> +        *tmp_fmt= FMT_DY;
> +        tmp_len+= 3;
> +      }
> +      else if (tmp1 == 'A')                     // DAY
> +      {
> +        if (ptr + 2 == end || my_toupper(system_charset_info, *(ptr+2)) != 'Y')
> +          goto error;
> +        *tmp_fmt= FMT_DAY;
> +        tmp_len+= locale->max_day_name_length * my_charset_utf8mb3_bin.mbmaxlen;
> +        ptr+= 1;
> +      }
> +      else
> +        goto error;
> +      ptr+= 1;
> +    }
> +    break;
> +    case 'H':                                   // HH, HH12 or HH23
> +    {
> +      char tmp1, tmp2, tmp3;
> +      if (ptr + 1 >= end)
> +        goto error;
> +      tmp1= my_toupper(system_charset_info, *(ptr+1));
> +
> +      if (tmp1 != 'H')
> +        goto error;
> +
> +      if (ptr+3 >= end)
> +      {
> +        *tmp_fmt= FMT_HH;
> +        ptr+= 1;
> +      }
> +      else
> +      {
> +        tmp2= *(ptr+2);
> +        tmp3= *(ptr+3);
> +
> +        if (tmp2 == '1' && tmp3 == '2')
> +        {
> +          *tmp_fmt= FMT_HH12;
> +          ptr+= 3;
> +        }
> +        else if (tmp2 == '2' && tmp3 == '4')
> +        {
> +          *tmp_fmt= FMT_HH24;
> +          ptr+= 3;
> +        }
> +        else
> +        {
> +          *tmp_fmt= FMT_HH;
> +          ptr+= 1;
> +        }
> +      }
> +      tmp_len+= 2;
> +      break;
> +    }
> +    case 'S':                                   // SS
> +      if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'S')
> +        goto error;
> +
> +      *tmp_fmt= FMT_SS;
> +      tmp_len+= 2;
> +      ptr+= 1;
> +      break;
> +    case '|':
> +      /*
> +        If only one '|' just ignore it, else append others, for example:
> +        TO_CHAR('2000-11-05', 'YYYY|MM||||DD') --> 200011|||05
> +      */
> +      if (ptr + 1 == end || *(ptr+1) != '|')
> +      {
> +        tmp_fmt--;
> +        break;
> +      }
> +      ptr++;                                    // Skip first '|'
> +      do
> +      {
> +        *tmp_fmt++= *ptr++;
> +        tmp_len++;
> +      } while ((ptr < end) && *ptr == '|');
> +      ptr--;                                    // Fix ptr for above for loop
> +      tmp_fmt--;
> +      break;
> +
> +    default:
> +      offset= parse_special(cfmt, ptr, end, tmp_fmt);
> +      if (!offset)
> +        goto error;
> +      /* ptr++ is in the for loop, so we must move ptr to offset-1 */
> +      ptr+= (offset-1);
> +      tmp_fmt+= (offset-1);
> +      tmp_len+= offset;
> +      break;
> +    }
> +  }
> +  *fmt_len= tmp_len;
> +  *tmp_fmt= 0;
> +  return 0;
> +
> +error:
> +  warning_message.append(STRING_WITH_LEN("date format not recognized at "));
> +  warning_message.append(ptr, MY_MIN(8, end- ptr));
> +  return 1;
> +}
> +
> +
> +static inline bool append_val(int val, int size, String *str)
> +{
> +  ulong len= 0;
> +  char intbuff[15];
> +
> +  len= (ulong) (int10_to_str(val, intbuff, 10) - intbuff);
> +  return str->append_with_prefill(intbuff, len, size, '0');
> +}
> +
> +
> +static bool make_date_time_oracle(const uint16 *fmt_array,
> +                                  const MYSQL_TIME *l_time,
> +                                  const MY_LOCALE *locale,
> +                                  String *str)
> +{
> +  bool quotation_flag= false;
> +  const uint16 *ptr= fmt_array;
> +  uint hours_i;
> +  uint weekday;
> +
> +  str->length(0);
> +
> +  while (*ptr)
> +  {
> +    if (check_quotation(*ptr, &quotation_flag))
> +    {
> +      /* don't display '"' in the result, so if it is '"', skip it */
> +      if (*ptr != '"')
> +      {
> +        DBUG_ASSERT(*ptr <= 255);
> +        str->append((char) *ptr);
> +      }
> +      ptr++;
> +      continue;
> +    }
> +
> +    switch (*ptr) {
> +
> +    case FMT_AM:
> +    case FMT_PM:
> +      if (l_time->hour > 11)
> +        str->append("PM", 2);
> +      else
> +        str->append("AM", 2);
> +      break;
> +
> +    case FMT_AM_DOT:
> +    case FMT_PM_DOT:
> +      if (l_time->hour > 11)
> +        str->append(STRING_WITH_LEN("P.M."));
> +      else
> +        str->append(STRING_WITH_LEN("A.M."));
> +      break;
> +
> +    case FMT_AD:
> +    case FMT_BC:
> +      if (l_time->year > 0)
> +        str->append(STRING_WITH_LEN("AD"));
> +      else
> +        str->append(STRING_WITH_LEN("BC"));
> +      break;
> +
> +    case FMT_AD_DOT:
> +    case FMT_BC_DOT:
> +      if (l_time->year > 0)
> +        str->append(STRING_WITH_LEN("A.D."));
> +      else
> +        str->append(STRING_WITH_LEN("B.C."));
> +      break;
> +
> +    case FMT_Y:
> +      if (append_val(l_time->year%10, 1, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_YY:
> +    case FMT_RR:
> +      if (append_val(l_time->year%100, 2, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_YYY:
> +      if (append_val(l_time->year%1000, 3, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_YYYY:
> +    case FMT_RRRR:
> +      if (append_val(l_time->year, 4, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_MM:
> +      if (append_val(l_time->month, 2, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_MON:
> +      {
> +        if (l_time->month == 0)
> +        {
> +          str->append("00", 2);
> +        }
> +        else
> +        {
> +          const char *month_name= (locale->ab_month_names->
> +                                   type_names[l_time->month-1]);
> +          size_t m_len= strlen(month_name);
> +          str->append(month_name, m_len, system_charset_info);
> +        }
> +      }
> +      break;
> +
> +    case FMT_MONTH:
> +      {
> +        if (l_time->month == 0)
> +        {
> +          str->append("00", 2);
> +        }
> +        else
> +        {
> +          const char *month_name= (locale->month_names->
> +                                   type_names[l_time->month-1]);
> +          size_t month_byte_len= strlen(month_name);
> +          size_t month_char_len;
> +          str->append(month_name, month_byte_len, system_charset_info);
> +          month_char_len= my_numchars_mb(&my_charset_utf8mb3_general_ci,
> +                                         month_name, month_name +
> +                                         month_byte_len);
> +          if (str->strfill(' ', locale->max_month_name_length - month_char_len))
> +            goto err_exit;
> +        }
> +      }
> +      break;
> +
> +    case FMT_DD:
> +      if (append_val(l_time->day, 2, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_DY:
> +      {
> +        if (l_time->day == 0)
> +          str->append("00", 2);
> +        else
> +        {
> +          weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
> +                                          l_time->day), 0);
> +          const char *day_name= locale->ab_day_names->type_names[weekday];
> +          str->append(day_name, strlen(day_name), system_charset_info);
> +        }
> +      }
> +      break;
> +
> +    case FMT_DAY:
> +      {
> +        if (l_time->day == 0)
> +          str->append("00", 2, system_charset_info);
> +        else
> +        {
> +          const char *day_name;
> +          size_t day_byte_len, day_char_len;
> +          weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
> +                                          l_time->day), 0);
> +          day_name= locale->day_names->type_names[weekday];
> +          day_byte_len= strlen(day_name);
> +          str->append(day_name, day_byte_len, system_charset_info);
> +          day_char_len= my_numchars_mb(&my_charset_utf8mb3_general_ci,
> +                                       day_name, day_name + day_byte_len);
> +          if (str->strfill(' ', locale->max_day_name_length - day_char_len))
> +            goto err_exit;
> +        }
> +      }
> +      break;
> +
> +    case FMT_HH12:
> +    case FMT_HH:
> +      hours_i= (l_time->hour%24 + 11)%12+1;
> +      if (append_val(hours_i, 2, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_HH24:
> +      if (append_val(l_time->hour, 2, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_MI:
> +      if (append_val(l_time->minute, 2, str))
> +        goto err_exit;
> +      break;
> +
> +    case FMT_SS:
> +      if (append_val(l_time->second, 2, str))
> +        goto err_exit;
> +      break;
> +
> +    default:
> +      str->append((char) *ptr);
> +    }
> +
> +    ptr++;
> +  };
> +  return false;
> +
> +err_exit:
> +  return true;
> +}
> +
> +
> +bool Item_func_tochar::fix_length_and_dec()
> +{
> +  thd= current_thd;
> +  CHARSET_INFO *cs= thd->variables.collation_connection;
> +  Item *arg1= args[1]->this_item();
> +  my_repertoire_t repertoire= arg1->collation.repertoire;
> +  StringBuffer<STRING_BUFFER_USUAL_SIZE> buffer;
> +  String *str;
> +
> +  locale= thd->variables.lc_time_names;
> +  if (!thd->variables.lc_time_names->is_ascii)
> +    repertoire|= MY_REPERTOIRE_EXTENDED;
> +  collation.set(cs, arg1->collation.derivation, repertoire);
> +
> +  /* first argument must be datetime or string */
> +  enum_field_types arg0_mysql_type= args[0]->field_type();
> +
> +  max_length= 0;
> +  switch (arg0_mysql_type) {
> +  case MYSQL_TYPE_TIME:
> +  case MYSQL_TYPE_DATE:
> +  case MYSQL_TYPE_DATETIME:
> +  case MYSQL_TYPE_TIMESTAMP:
> +  case MYSQL_TYPE_VARCHAR:
> +  case MYSQL_TYPE_STRING:
> +    break;
> +  default:
> +  {
> +    my_printf_error(ER_ORACLE_COMPAT_FUNCTION_ERROR,
> +                    ER(ER_ORACLE_COMPAT_FUNCTION_ERROR),
> +                    MYF(0),
> +                    "data type of first argument must be type "
> +                    "date/datetime/time or string");

that's not how MariaDB works, it converts types.
In particular, using an integer 20200101 in the date context is
perfectly ok.

> +    return TRUE;
> +  }
> +  }
> +  if (args[1]->basic_const_item() && (str= args[1]->val_str(&buffer)))
> +  {
> +    uint ulen;
> +    fixed_length= 1;
> +    if (parse_format_string(str, &ulen))
> +    {
> +      my_printf_error(ER_ORACLE_COMPAT_FUNCTION_ERROR,
> +                      ER(ER_ORACLE_COMPAT_FUNCTION_ERROR),
> +                      MYF(0),
> +                      warning_message.c_ptr());
> +      return TRUE;
> +    }
> +    max_length= (uint32) (ulen * collation.collation->mbmaxlen);
> +  }
> +  else
> +  {
> +    fixed_length= 0;
> +    max_length= (uint32) MY_MIN(arg1->max_length * 10 *
> +                                collation.collation->mbmaxlen,
> +                                MAX_BLOB_WIDTH);
> +  }
> +  set_maybe_null();
> +  return FALSE;
> +}
> +
> +
> +String *Item_func_tochar::val_str(String* str)
> + {
> +  StringBuffer<64> format_buffer;
> +  String *format;
> +  MYSQL_TIME l_time;
> +  const MY_LOCALE *lc= locale;
> +  date_conv_mode_t mode= TIME_CONV_NONE;
> +  size_t max_result_length= max_length;
> +
> +  if (warning_message.length())
> +    goto null_date;
> +
> +  if ((null_value= args[0]->get_date(thd, &l_time,
> +                                     Temporal::Options(mode, thd))))
> +    return 0;
> +
> +  if (!fixed_length)
> +  {
> +    uint ulen;
> +    if (!(format= args[1]->val_str(&format_buffer)) || !format->length() ||
> +        parse_format_string(format, &ulen))
> +      goto null_date;
> +    max_result_length= ((size_t) ulen) * collation.collation->mbmaxlen;
> +  }
> +
> +  if (str->alloc(max_result_length))
> +    goto null_date;
> +
> +  /* Create the result string */
> +  str->set_charset(collation.collation);
> +  if (!make_date_time_oracle(fmt_array, &l_time, lc, str))
> +    return str;
> +
> +null_date:
> +
> +  if (warning_message.length())
> +  {
> +    push_warning_printf(thd,
> +                        Sql_condition::WARN_LEVEL_WARN,
> +                        ER_ORACLE_COMPAT_FUNCTION_ERROR,
> +                        ER_THD(thd, ER_ORACLE_COMPAT_FUNCTION_ERROR),
> +                        warning_message.c_ptr());
> +    if (!fixed_length)
> +      warning_message.length(0);
> +  }
> +
> +  null_value= 1;
> +  return 0;
> +}
> +
>  
>  bool Item_func_from_unixtime::fix_length_and_dec()
>  {

Regards,
Sergei


Follow ups