Re: [Maria-developers] 57c19902326: MDEV-20017 Implement TO_CHAR() Oracle compatible function
Hi, Monty! On Apr 13, Michael Widenius wrote:
commit 57c19902326 Author: Michael Widenius <michael.widenius@gmail.com> 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, "ation_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, "ation_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
Hi! On Fri, Apr 16, 2021 at 5:44 PM Sergei Golubchik <serg@mariadb.org> wrote:
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".
Fixed by reusing ER_STD_INVALID_ARGUMENT
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.
Agree, I did miss this when fixing a fill loop in the original commit. I have now removed strfill(), as you requested. <cut>
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
<cut>
+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.
That may not work well for prepared statements or stored procdures where we may not call fix_length_and_dec() twice. I also think it is a bit wrong that one gets a different number of warning messages depending on when the arguments are processed. For now, I rather keep things as they are. Someone that has time to do checking of the above cases and ensures that we do not miss any warnings messages in some context can then consider what to do.
--- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc
<cut>
+ 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?
128. I have fixed the error message
+#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 <cut>
Not enum? Not even safe (with parentheses) #define? enum would be ideal here but at the very least make these defines safe.
This is 100% safe in the context it is used. However, I agree that enum is better and I have now converted the above to an enum.
+ +/** + Modify the quotation flag and check whether the subsequent process is skipped
Could you reword it please?
Changed to: Flip 'quotation_flag' if we found a quote (") character. The old function comment explains this in more detail.
+ + @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.
The above is safe in the context it is used. For simple things like this, which is only used in a few places in the current file, I personally prefer #defines, so I am ok with the current code. Regards, Monty
participants (2)
-
Michael Widenius
-
Sergei Golubchik