/* Copyright (c) 2009, 2025, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ #include "mariadb.h" #include "sql_priv.h" #include "sql_class.h" #include "sql_base.h" #include "m_ctype.h" #include "strfunc.h" #include "item_numconvfunc.h" #include "lex_ident_sys.h" #include "simple_tokenizer.h" #include "sql_list.h" #include "sql_string.h" #define SIMPLE_PARSER_V2 #include "simple_parser.h" #undef SIMPLE_PARSER_V2 // An alias for a shorter code notation using enum_warning_level= Sql_state_errno_level::enum_warning_level; class Tokenizer: public Extended_string_tokenizer { public: Tokenizer(CHARSET_INFO *cs, const LEX_CSTRING &str) :Extended_string_tokenizer(cs, str) { } /* A class to detect quickly if a certain character is a one-character token. It also handles case-insensitivity for these tokens. */ class Single_char_token { public: static constexpr uchar _C= 'C'; // Positional currency (C, L, U) static constexpr uchar _B= 'B'; // Prefix/inline flag B static constexpr uchar _S= 'S'; // Sign S static constexpr uchar _G= 'G'; // Group decimiter G // Single char tokens: $ B . D , G 09 C L U static uchar elem(uchar ch) { static const uchar elements[256]= { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* !"#$%&'()*+,-./ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0123456789:;<=>? */ 0, 0,_B,_C, 1, 0, 0,_G, 0, 0, 0, 0,_C, 0, 0, 0, /* @ABCDEFGHIJKLMNO */ 0, 0, 0,_S, 0,_C, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* PQRSTUVWXYZ[\]^_ */ 0, 0,_B,_C, 1, 0, 0,_G, 0, 0, 0, 0,_C, 0, 0, 0, /* `abcdefghijklmno */ 0, 0, 0,_S, 0,_C, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* pqrstuvwxyz{|}~. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* ................ */ }; return elements[ch]; } }; // Wrappers for the above class static bool is_positional_currency(char ch) { return Single_char_token::elem((uchar) ch) == Single_char_token::_C; } static bool is_currency_flag_B(char ch) { return Single_char_token::elem((uchar) ch) == Single_char_token::_B; } static bool is_group_delimiter_G(char ch) { return Single_char_token::elem((uchar) ch) == Single_char_token::_G; } static bool is_sign_S(char ch) { return Single_char_token::elem((uchar) ch) == Single_char_token::_S; } // Combining wrappers static bool is_currency_flag(char ch) { return ch == '$' || is_currency_flag_B((uchar) ch); } enum class TokenID { // Special purpose tokens: tNULL= 0, // returned if the tokenizer failed to detect a token // also used if the parser failed to parse a token tEMPTY= 1, // returned on empty optional constructs in a grammar like: // rule ::= [ rule1 ] // when rule1 does not present in the input. tEOF= 2, // returned when the end of input is reached // One character tokens tCOMMA= ',', tDOLLAR= '$', tPERIOD= '.', tB= 'B', tC= 'C', tD= 'D', tG= 'G', tL= 'L', tU= 'U', tV= 'V', tS= 'S', // Other tokens, values must be greater than any of the above tMI= 256, tFM, tPR, tTM, tTM9, tTME, tZEROS, tNINES, tXCHAIN, tEEEE }; // A helper wrapper for Lex_cstring with convenience methods class LS: public Lex_cstring { public: using Lex_cstring::Lex_cstring; const char *ptr() const { return Lex_cstring::str; } size_t length() const { return Lex_cstring::length; } const char *end() const { return ptr() ? ptr() + length() : nullptr; } static LS empty() { return empty_clex_str; } #ifndef DBUG_OFF void print(String *str) const { str->append(ptr(), length()); } void print_var_value(String *str, const LS & name) const { str->append(name); str->append("='", 2); str->append(ptr(), length()); str->append('\''); } #endif const LS & to_LS() const { return *this; } LS ltrim(CHARSET_INFO *cs) const { const char *start= ptr() + cs->scan(ptr(), end(), MY_SEQ_SPACES); return {start, end()}; } LS ltrim_currency_flags() const { const char *p; for (p= ptr() ; p < end() && is_currency_flag((uchar) *p) ; p++) { } return {p, end()}; } const char *find_dollar(size_t skip) const { for (const char *p= ptr(); p < end(); p++) { if (*p == '$') { if (skip == 0) return p; skip--; } } return nullptr; } const char *find_currency_flag_B(size_t skip) const { for (const char *p= ptr(); p < end(); p++) { if (is_currency_flag_B(*p)) { if (skip == 0) return p; skip--; } } return nullptr; } /* Return a string with the left character removed. The string must not be empty. */ LS chop_left() const { DBUG_ASSERT(ptr()); DBUG_ASSERT(length() > 0); return {ptr() + 1, length() - 1}; } /* Return a string with the right character removed. The string must not be empty. */ LS chop_right() const { DBUG_ASSERT(ptr()); DBUG_ASSERT(length() > 0); return {ptr(), length() - 1}; } // A byte at the given position char at(size_t pos) const { DBUG_ASSERT(pos < length()); return ptr()[pos]; } // A byte at the very first postion char front() const { return at(0); } // The very last byte char back() const { DBUG_ASSERT(ptr()); DBUG_ASSERT(length() > 0); return ptr()[length() - 1]; } }; class Token: public LS { TokenID m_id; public: Token() :LS(), m_id(TokenID::tNULL) { } Token(const LEX_CSTRING &str, TokenID id) :LS(str), m_id(id) { } TokenID id() const { return m_id; } static Token empty(const char *pos) { return Token(LS(pos, pos), TokenID::tEMPTY); } static Token empty() { return Token(LS::empty(), TokenID::tEMPTY); } explicit operator bool() const { return m_id != TokenID::tNULL; } }; protected: Token get_token(CHARSET_INFO *cs) { if (eof()) return Token(LS(m_ptr, m_ptr), TokenID::tEOF); const char *head= m_ptr; if (Single_char_token::elem((uchar) *head)) { TokenID id= (TokenID) my_toupper(system_charset_info, head[0]); return m_ptr++, Token(LS(m_ptr - 1, 1), id); } // Digit chains - return as a single token if (head[0] == '0' || head[0] == '9' || head[0] == 'X' || head[0] == 'x') { for ( ; !get_char(head[0]) ;) { } if (head[0] == '0') return Token(LS(head, m_ptr), TokenID::tZEROS); if (head[0] == '9') return Token(LS(head, m_ptr), TokenID::tNINES); if (head[0] == 'X' || head[0] == 'x') return Token(LS(head, m_ptr), TokenID::tXCHAIN); } // Two-char tokens if (m_ptr + 2 <= m_end) { const LEX_CSTRING str= LS(m_ptr, 2); if ("MI"_Lex_ident_column.streq(str)) return m_ptr+=2, Token(str, TokenID::tMI); if ("FM"_Lex_ident_column.streq(str)) return m_ptr+=2, Token(str, TokenID::tFM); if ("PR"_Lex_ident_column.streq(str)) return m_ptr+=2, Token(str, TokenID::tPR); if ("TM"_Lex_ident_column.streq(str)) { if (m_ptr + 3 <= m_end) { // Three-char tokens: TM9 TME if (m_ptr[2] == '9') return m_ptr+= 3, Token(LS(head, m_ptr), TokenID::tTM9); if (m_ptr[2]=='E' || m_ptr[2]=='e') return m_ptr+= 3, Token(LS(head, m_ptr), TokenID::tTME); } return m_ptr+=2, Token(LS(head, m_ptr), TokenID::tTM); } } // Four-char tokens: EEEE if (m_ptr + 4 <= m_end) { const LEX_CSTRING str= LS(m_ptr, 4); if ("EEEE"_Lex_ident_column.streq(str)) return m_ptr+=4, Token(str, TokenID::tEEEE); } return Token(LS(m_ptr, m_ptr), TokenID::tNULL); } #ifndef DBUG_OFF // A helper method for debugging void trace_tokens(CHARSET_INFO *cs, const LEX_CSTRING &fmt) { Tokenizer::Token tok; for (tok= get_token(cs) ; tok && tok.id() != Tokenizer::TokenID::tEOF; tok= get_token(cs)) { } } #endif }; // End of class Tokenizer /* A convenience operator, to use: "123"_LS instead of: CSTRING_WITH_LEN("123"). Must be outside of a class. */ static inline constexpr Tokenizer::LS operator""_LS(const char *str, size_t length) { return Tokenizer::LS(str, length); } class Parser: public Tokenizer, public Parser_templates { private: THD *m_thd; Token m_look_ahead_token; LEX_CSTRING m_func_name; const char *m_start; bool m_error; // Syntax or raised by the caller, e.g. in methods add() public: Parser() :Tokenizer(&my_charset_bin, null_clex_str), m_thd(nullptr), m_look_ahead_token(Token()), m_func_name(null_clex_str), m_start(nullptr), m_error(true) { } Parser(THD *thd, const LEX_CSTRING func_name, CHARSET_INFO *cs, const LEX_CSTRING &str) :Tokenizer(cs, str), m_thd(thd), m_look_ahead_token(get_token(cs)), m_func_name(func_name), m_start(str.str), m_error(false) { } void set_syntax_error() { m_error= true; } void set_fatal_error() { m_error= true; } bool is_error() const { return m_error; } LS buffer() const { return {m_start, m_end}; } TokenID look_ahead_token_id() const { return is_error() ? TokenID::tNULL : m_look_ahead_token.id(); } /* Return an empty token at the position of the current look ahead token with a zero length. Used for optional grammar constructs. For example, if the grammar is "rule ::= ruleA [ruleB] ruleC" and the input is "A C", then: - the optional rule "ruleB" will point to the input position "C" with a zero length - while the rule "ruleC" will point to the same input position "C" with a non-zero length */ Token empty_token() const // For templates in simple_parser.h { return Token::empty(m_look_ahead_token.ptr()); } static Token null_token() // For templates in simple_parser.h { return Token(); } /* Return the current look ahead token and scan the next one */ Token shift() { DBUG_ASSERT(!is_error()); const Token res= m_look_ahead_token; m_look_ahead_token= get_token(m_cs); return res; } THD *thd() const { return m_thd; } public: /* Return the current look ahead token if it matches the given ID and scan the next one. */ Token token(TokenID id) { if (m_look_ahead_token.id() != id || is_error()) return null_token(); return shift(); } void raise_not_supported_yet(THD *thd, enum_warning_level level, const LS & str) const { char buff[128]; size_t errlen= my_snprintf(buff, sizeof(buff), "='%.*s'", (int) str.length(), str.ptr()); ErrConvString txt(buff, errlen, m_cs); if (level == Sql_condition::WARN_LEVEL_ERROR) my_error(ER_NOT_SUPPORTED_YET, MYF(0), txt.ptr()); else push_warning_printf(thd, level, ER_NOT_SUPPORTED_YET, ER_THD(thd, ER_NOT_SUPPORTED_YET), txt.ptr()); } void raise_bad_format_at(THD *thd, enum_warning_level level, ErrConvString *txt) const { if (level == Sql_condition::WARN_LEVEL_ERROR) my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "", txt->ptr(), m_func_name.str); else push_warning_printf(thd, level, ER_WRONG_VALUE_FOR_TYPE, ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE), "", txt->ptr(), m_func_name.str); } void raise_bad_format_at(THD *thd, enum_warning_level level, const char *pos= nullptr) const { const LS buf= buffer(); if (!pos) pos= m_look_ahead_token.ptr(); DBUG_ASSERT(pos >= buf.ptr() && pos <= buf.end()); ErrConvString txt(pos, buf.end() - pos, m_cs); raise_bad_format_at(thd, level, &txt); } enum feature_t { F_NONE= 0, F_INT_DIGIT= 1 << 0, F_INT_B= 1 << 1, F_INT_DOLLAR= 1 << 2, F_INT_GROUP_COMMA= 1 << 3, F_INT_GROUP_G= 1 << 4, F_INT_HEX= 1 << 5, F_FRAC_DIGIT= 1 << 10, F_FRAC_B= 1 << 11, F_FRAC_DOLLAR= 1 << 12, F_FRAC_DEC_PERIOD= 1 << 13, F_FRAC_DEC_D= 1 << 14, F_FRAC_DEC_V= 1 << 15, F_FRAC_DEC_CLU= 1 << 16, F_EEEE= 1 << 17, F_POSTFIX_CLU= 1 << 20, F_PREFIX_CLU= 1 << 21, F_PREFIX_B= 1 << 22, F_PREFIX_DOLLAR= 1 << 23, F_PREFIX_SIGN= 1 << 25, F_POSTFIX_SIGN= 1 << 26, F_FMT_TM= 1 << 27, F_FMT_FLAG_FM= 1 << 28 }; // A common parent class for various containers class LS_container: public LS { public: using Container= LS_container; using LS::LS; static LS_container empty(const Parser &parser) { return parser.empty_token(); } static LS_container empty() { return LS::empty(); } explicit operator bool() const { return ptr() != nullptr; } /* Concatenate list elements into a single container. There must be no any delimiters in between. As far as spaces are not allowed in the format, it's always true. */ bool concat(LS && rhs) { if (!ptr()) { LS::operator=(rhs); return false; } if (rhs.length() == 0) return false; if (end() != rhs.ptr()) { DBUG_ASSERT(0); return true; } Lex_cstring::length+= rhs.length(); return false; } // Methods that allow to pass it as a container to LIST. size_t count() const { return length(); } bool add(Parser *p, LS && rhs) { return concat(std::move(rhs)); } }; /* Counters: - for flags '$' and 'B' - group separators '.' and 'G' - digits '0' Currency prefix flags can have flags '$' and 'B': 'BC99.9' Integer and fraction parts of the format can have digits '0', '9', 'X', and additional elements: - Integer digits can have both flags and group separators: '9,B,9$' - Fractional digits can have flags '$' and 'B' only: '.9B$9' (but cannot have group separators) */ class Decimal_flag_counters { public: using Container= Decimal_flag_counters; uint32 m_dollar_count; uint32 m_B_count; uint32 m_comma_count; uint32 m_G_count; uint32 m_0_count; Decimal_flag_counters() :m_dollar_count(0), m_B_count(0), m_comma_count(0), m_G_count(0), m_0_count(0) { } Decimal_flag_counters(Decimal_flag_counters && rhs) = default; Decimal_flag_counters & operator=(Decimal_flag_counters && rhs) = default; void join(const Decimal_flag_counters & rhs) { m_dollar_count+= rhs.m_dollar_count; m_B_count+= rhs.m_B_count; m_comma_count+= rhs.m_comma_count; m_G_count+= rhs.m_G_count; m_0_count+= rhs.m_0_count; } void add(char ch) { if (ch == '$') { m_dollar_count++; return; } if (ch == ',') { m_comma_count++; return; } if (ch == '0') { m_0_count++; return ; } if (is_group_delimiter_G(ch)) { m_G_count++; return ; } if (is_currency_flag_B(ch)) { m_B_count++; return; } DBUG_ASSERT(ch == '9'); } // Methods needed to pass this class to templates in simple_parser.h static Decimal_flag_counters empty(const Parser & parser) { return Decimal_flag_counters(); } static Decimal_flag_counters empty() { return Decimal_flag_counters(); } explicit operator bool() const { return true; } // Other methods size_t non_digit_length() const { return m_dollar_count + m_B_count + m_comma_count + m_G_count; } }; /********** Rules consisting of a single token *****/ using TokenEOF= TokenParser; // Prefix/inline flag 'B' using TokenB= TokenParser; /* The following chains are returned by the tokenizer as a single token: GRAMMAR: zeros: '0' [ '0'... ] GRAMMAR: nines: '9' [ '9'... ] GRAMMAR: xchain: 'X' [ 'X'...] Examples: '00' '99' 'XX' */ using Zeros= TokenParser; using Nines= TokenParser; using XChain= TokenParser; /********** Rules consisting of a single token with their own container ***/ /** The containers appear in the final structure Format::Goal after parsing */ // Format prefix flag 'FM'. There are no any other format prefix flags. class Format_flags: public LS_container { public: using LS_container::LS_container; using Container= CONTAINER1; using LParser= TokenParser; feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT("FM"_Lex_ident_column.streq(to_LS())); return F_FMT_FLAG_FM; } }; // EEEE - scientific modifier class EEEE: public LS_container { public: using LS_container::LS_container; using Container= CONTAINER1; using LParser= TokenParser; feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT("EEEE"_Lex_ident_column.streq(to_LS())); return F_EEEE; } }; // prefix_sign: 'S' class Prefix_sign: public LS_container { public: using LS_container::LS_container; using Container= CONTAINER1; using LParser= TokenParser; feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT(length() == 1); DBUG_ASSERT(is_sign_S(front())); return F_PREFIX_SIGN; } /* Get a prefix sign from the subject string "ls" according to the format in "this". Strip the sign (if found) from the string "ls". @param OUT neg - the sign is returned here: true if minus was scanned from "ls" false if plus was scanned from "ls" undefinite if "this" does not contain a prefix sign @param IN/OUT ls - the subject string, we search for "+" or "-" in the front of it. @retval false Success: - the format in "this" does not have a sign - the format in "this" contains a sign, and the sign was scanned fron the subject string @retval true Error: - the format in "this" has a non-empty sign, but the subject string does not have a prefix sign */ bool get(bool *neg, LS *ls) const { *neg= false; if (length() == 0) return false; // There is no a prefix sign in the format if (!is_sign_S(front())) { DBUG_ASSERT(0); // Unknown prefix sign return true; } if (ls->length() == 0) return true; // The subject string is empty, could not scan the sign const char sign= ls->front(); if (sign != '+' && sign != '-') return true; // The subject string does not start with a sign *neg= sign == '-'; *ls= ls->chop_left(); // Strip the sign from the subject string return false; // Sign found } }; /********** Rules consisting of 2 token choices ***/ // GRAMMAR: decimal_flag: 'B' | '$' using Decimal_flag_cond= TokenChoiceCond2; // GRAMMAR: group_separator: ',' | 'G' using Group_separator_cond= TokenChoiceCond2; class Group_separator: public TokenChoice { using TokenChoice::TokenChoice; }; // GRAMMAR: zeros_or_nines: zeros | nines using Zeros_or_nines_cond= TokenChoiceCond2; class Zeros_or_nines: public TokenChoice { public: using TokenChoice::TokenChoice; // Initializing from rules Zeros_or_nines(Zeros && rhs) :TokenChoice(std::move(rhs)) { } Zeros_or_nines(Nines && rhs) :TokenChoice(std::move(rhs)) { } }; /********** Postfix sign ****/ // GRAMMAR: postfix_sign_signature: 'S' | 'MI' | 'PR' using Postfix_sign_cond= TokenChoiceCond3; class Postfix_sign_signature: public TokenChoice { using TokenChoice::TokenChoice; }; // GRAMMAR: postfix_specific_sign_signature: 'MI' | 'PR' using Postfix_specific_sign_cond= TokenChoiceCond2; class Postfix_specific_sign_signature: public TokenChoice { using TokenChoice::TokenChoice; }; /* A container for the postfix sign. Depending on the grammar rule, the postfix sign can be: - postfix_specific_sign: MI, PR - postfix_sign: MI, PR, S (all sign variants) */ class Postfix_sign: public LS_container { public: using LS_container::LS_container; using Container= CONTAINER1; // Initializing from rules Postfix_sign(Postfix_sign_signature && rhs) :LS_container(std::move(static_cast(rhs))) { } Postfix_sign(Postfix_specific_sign_signature && rhs) :LS_container(std::move(static_cast(rhs))) { } // Conversion methods feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT(length() == 1 || length() == 2); return F_POSTFIX_SIGN; } /* Get a trailing sign (S, MI) from a subject string in "ls" Strip the sign from 'ls' if found. @param OUT neg - the sign is returned here: true if a negative sign was scanned from "ls" false if a positive sign was scanned from "ls" undefinite if "this" does not have a postfix sign @param IN/OUT ls - the subject string, we search postfix signs in it @param IN positive - the character that represents a positive sign @param IN negative - the character that represents a negative sign @retval false Success: - the format in "this" does not have a sign - the format in "this" contains a sign, and the sign was scanned fron the subject string @retval true Error: - the format in "this" has a non-empty sign, but the subject string does not have a postfix sign */ static bool get_S_MI(bool *neg, LS *ls, char positive, char negative) { if (!ls->length()) return true; // The subject string is empty, failed to get the sign char sign= ls->back(); if (sign != positive && sign != negative) return true; // Not a sign character *neg= sign == negative; *ls= ls->chop_right(); return false; } /* Get a PR-style angle-surrounding sign from a subject string in "ls". Strip the sign from 'ls' if found. @param OUT neg - the sign is returned here: true if a negative sign was scanned from "ls" false if a positive sign was scanned from "ls" undefinite if "this" does not have a PR sign @param IN/OUT ls - the subject string, we search a surrounding sign in @retval false Success: - the format in "this" does not have a sign - the format in "this" contains a sign, and the sign was scanned fron the subject string @retval true Error: - the format in "this" has a non-empty sign, but the subject string does not have a surrounding sign */ static bool get_PR(bool *neg, LS *ls) { if (ls->length() < 2) return true; // The subject string needs at least two characers char leading= ls->front(); char trailing= ls->back(); if (!(leading == ' ' && trailing == ' ') && !(leading == '<' && trailing == '>')) return true; // Unknown *neg= leading == '<'; *ls= ls->chop_right().chop_left(); return false; } /* Get any know postfix sign from the subject string "ls" according to the format in "this". Strip the sign (if found) from the string "ls". @param OUT neg - the sign is returned here: true if minus was scanned from "ls" false if plus was scanned from "ls" undefinite if "this" does not contain a postfix sign @param IN/OUT ls - the subject string, we search postfix signs @retval false Success: - the format in "this" does not have a sign - the format in "this" contains a sign, and the sign was scanned fron the subject string @retval true Error: - the format in "this" has a non-empty sign, but the subject string does not have a matching sign */ bool get(bool *neg, LS *ls) const { if (length() == 0) { *neg= false; return false; // The format in "this" does not have a postfix sign } DBUG_ASSERT(length() <= 2); // S MI PR if (is_sign_S(front())) return get_S_MI(neg, ls, '+', '-'); // Search for '+' or '-' in the end if ("MI"_Lex_ident_column.streq(to_LS())) return get_S_MI(neg, ls, ' ', '-'); // Search for ' ' or '-' in the end if ("PR"_Lex_ident_column.streq(to_LS())) return get_PR(neg, ls); // Search for blanks or angle brakets around DBUG_ASSERT(0); // Unknown postfix sign format return true; } }; /* GRAMMAR: positional_currency_signature: 'C' | 'L' | 'U' The position of CLU inside the format is important, hence the name. Note, the position of the dollar sign is not important, it can be specified once on any position inside the number. */ using Positional_currency_signature_cond= TokenChoiceCond3; // GRAMMAR: prefix_currency_signature: positional_currency_signature class Prefix_currency: public LS_container { public: using LS_container::LS_container; class Cond: public Positional_currency_signature_cond { }; using Container= CONTAINER1; using LParser= TokenChoice; // Conversion methods feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT(length() == 1); DBUG_ASSERT(is_positional_currency((uchar) front())); return (feature_t) F_PREFIX_CLU; } }; // GRAMMAR: postfix_currency_signature: positional_currency_signature class Postfix_currency: public LS_container { public: using LS_container::LS_container; class Cond: public Positional_currency_signature_cond { }; using Container= CONTAINER1; using LParser= TokenChoice; // Conversion methods feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT(length() == 1); DBUG_ASSERT(is_positional_currency((uchar) front())); return (feature_t) F_POSTFIX_CLU; } }; // GRAMMAR: dec_delimiter_currency_signature: positional_currency_signature class Dec_delimiter_pDVCLU: public LS_container { public: using LS_container::LS_container; class Cond: public Positional_currency_signature_cond { }; using Container= CONTAINER1; using LParser= TokenChoice; // Conversion methods feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT(length() == 1); if (is_positional_currency(front())) return F_FRAC_DEC_CLU; switch (front()) { case '.': return F_FRAC_DEC_PERIOD; case 'D': case 'd': return F_FRAC_DEC_D; case 'V': case 'v': return F_FRAC_DEC_V; default: break; } DBUG_ASSERT(0); return F_NONE; } }; // GRAMMAR: format_TM_signature: 'TM' | 'TM9' | 'TME' class Format_TM: public LS_container { public: using LS_container::LS_container; using Cond= TokenChoiceCond3; using Container= CONTAINER1; using LParser= TokenChoice; // Delete copying Format_TM(const Format_TM & rhs) = delete; Format_TM & operator=(const Format_TM & rhs) = delete; // Initializing from itself Format_TM(Format_TM && rhs) = default; Format_TM & operator=(Format_TM && rhs) = default; // Initializing from its components Format_TM(LS_container && rhs) :LS_container(std::move(rhs)) { } // Initializing from rules Format_TM(LParser && rhs) :LS_container(std::move(rhs)) { } // Conversion methods feature_t features_found() const { if (!length()) return F_NONE; DBUG_ASSERT(length() == 2 || length() == 3); return F_FMT_TM; } }; // GRAMMAR: fraction_pDV_signature: '.' | 'D' | 'V' using Fraction_pDV_signature_cond= TokenChoiceCond3; class Fraction_pDV_signature: public TokenChoice { using TokenChoice::TokenChoice; }; // Rules consisting of more complex token choices // GRAMMAR: fractional_element: zeros_or_nines | decimal_flag class Fractional_element_cond { public: static bool allowed_token_id(TokenID id) { return Zeros_or_nines_cond::allowed_token_id(id) || Decimal_flag_cond::allowed_token_id(id); } }; class Fractional_element: public TokenChoice { using TokenChoice::TokenChoice; }; // GRAMMAR: integer_element: fractional_element | group_separator class Integer_element_cond { public: static bool allowed_token_id(TokenID id) { return Fractional_element_cond::allowed_token_id(id) || Group_separator_cond::allowed_token_id(id); } }; class Integer_element: public TokenChoice { using TokenChoice::TokenChoice; }; /* Rules consisting of a LIST of token choices */ // GRAMMAR: currency_prefix_flag: decimal_flag // GRAMMAR: currency_prefix_flags: currency_prefix_flag [ currency_prefix_flag...] class Currency_prefix_flags: public LS_container { public: using LS_container::LS_container; using Container= CONTAINER1; using Flag= TokenChoice; using LParser= LIST; // Conversion methods Decimal_flag_counters prefix_flag_counters() const { Decimal_flag_counters tmp; for (size_t i= 0; i < length(); i++) tmp.add(at(i)); return tmp; } feature_t features_found() const { DBUG_ASSERT(length() <= 2); // $ + B uint res= F_NONE; for (size_t i= 0; i < length(); i++) { if (at(i) == '$') res|= F_PREFIX_DOLLAR; else if (is_currency_flag_B(at(i))) res|= F_PREFIX_B; else { DBUG_ASSERT(0); } } return (feature_t) res; } }; /* Digits: - decimal digit placeholders: 0 9 - hex digit placeholders: X (only in the integer part of a number) - inline flags: $ B - group separators: , G (only in the integer part of a number) */ class Digits: public LS_container, public Decimal_flag_counters { using A= LS_container; using B= Decimal_flag_counters; public: using Container= OR_CONTAINER2; // Default ctor Digits() = default; // Delete copying Digits(const Digits & rhs) = delete; Digits & operator=(const Digits & rhs) = delete; // Initializing from itself Digits(Digits && rhs) = default; Digits & operator=(Digits && rhs) = default; // Initializing from its components Digits(A && a, B && b) :A(std::move(a)), B(std::move(b)) { } // Initializing from rules Digits(Zeros_or_nines && rhs) :A(std::move(rhs)) { } Digits(XChain && rhs) :A(std::move(rhs)) { } // Other methods /* Check if the counters are valid: @retval false - OK - the counters are valid @retval true - Error - the counters are not valid (e.g. double B) */ bool check_counters() const { /* - $ can appear only once - B can appear only once - Comma and G cannot co-exist */ return m_dollar_count > 1 || m_B_count > 1 || (m_comma_count > 0 && m_G_count > 0); } /* Check if digits with inline flags are parsed and flags counters are valid. @retval true - parsed and valid @retval false - not parser or not valid */ explicit operator bool() const { return LS_container::operator bool() && !check_counters(); } /* Hide the inherited concat() to prevent descendants from using it in a mistake instead of join(). */ bool concat()= delete; // Join two consequence sequences of digits '00'+'99' -> '0099' bool join(Digits && rhs) { B::join(std::move(rhs)); return A::concat(std::move(rhs)); } bool add(Parser *p, LS && rhs) // for LIST { DBUG_ASSERT(rhs.length() > 0); B::add(rhs.front()); if (check_counters()) return true; return A::add(p, std::move(rhs)); } }; // End of class Digits /********** Rules related to the integer part of a number ***/ // GRAMMAR: integer: integer_element [ integer_element...] class Integer: public Digits { public: using Container= CONTAINER1; using Digits::Digits; // Deleting copying Integer(const Integer & rhs) = delete; Integer & operator=(const Integer & rhs) = delete; // Initializing from itself Integer(Integer && rhs) = default; Integer & operator=(Integer && rhs) = default; // Initializing from its components Integer(Digits && rhs) :Digits(std::move(rhs)) { } // Helper constructors used in descendants // Zeros_or_nines + extra digits Integer(Zeros_or_nines && head, Integer && tail) :Digits(std::move(head)) { /* According to the grammar, in the integer context "head" can be either zeros or nines. A mixture of zeros and nines is only possible in the fractional part. So if front() is '0', it means the entire head consists of zeros. */ DBUG_ASSERT(!head.length() || head.front() == head.back()); if (head.length() && head.front() == '0') m_0_count= (uint32) head.length(); // see the comment above Integer::join(std::move(tail)); } /* A tail of an integer number. It can start with a group character. */ using Tail= LIST; // Conversion methods static feature_t features_supported_by_to_dbln_fixed() { return (feature_t) (F_INT_DIGIT | F_INT_B | F_INT_GROUP_COMMA); } feature_t features_found() const { uint res= F_NONE; if (length() > non_digit_length()) { res|= F_INT_DIGIT; if (back() == 'X' || back() == 'x') res|= F_INT_HEX; } if (m_B_count) res|= F_INT_B; if (m_dollar_count) res|= F_INT_DOLLAR; if (m_comma_count) res|= F_INT_GROUP_COMMA; if (m_G_count) res|= F_INT_GROUP_G; return (feature_t) res; } Double_null to_dbln_fixed(LS sbj, CHARSET_INFO *cs) const { DBUG_ASSERT(m_G_count == 0); size_t non_digit_count= m_dollar_count + m_B_count + m_G_count; sbj= sbj.ltrim(cs); DBUG_ASSERT(non_digit_count <= length()); // $ and B are flags, they don't need to match anything in sbj size_t chars_to_match= length() - non_digit_count; if (chars_to_match < sbj.length()) return Double_null(); // Can never match /* Skip the leading format characters which require a match in the subject string (i.e. digits and commas) and which are outside of the subject length. sbj='12, fmt= '$99B9' -> fmt= '9B9' */ size_t skip= chars_to_match - sbj.length(); LS fmt(ptr(), end()); for ( ; fmt.length() > 0 && skip > 0; fmt= fmt.chop_left()) { /* Can not skip zeros, they must have a match in the subject string and thus can be used to set the mininum number of digits, e.g. in to_number(.., '099') the subject string must have at least 3 digits. */ if (fmt.front() == '0') return Double_null(); if (!is_currency_flag(fmt.front())) skip--; } DBUG_ASSERT(fmt.length() >= sbj.length()); double nr= 0; size_t digit_matched= 0; for (size_t pos= 0; pos < sbj.length(); pos++, fmt= fmt.chop_left()) { fmt= fmt.ltrim_currency_flags(); if (fmt.front() == ',') { if (sbj.at(pos) != ',') return Double_null(); continue; } DBUG_ASSERT(fmt.front() == '0' || fmt.front() == '9'); if (sbj.at(pos) < '0' || sbj.at(pos) > '9') return Double_null(); digit_matched++; nr*= 10; nr+= (uint) ((uint) (uchar) sbj.at(pos)) - (uchar) '0'; } DBUG_ASSERT(fmt.ltrim_currency_flags().length() == 0); return digit_matched ? Double_null(nr) : Double_null(); } }; /********** Rules related to the fractional part of a number ***/ // GRAMMAR: fraction_body: fractional_element [ fractional_element ... ] class Fraction_body: public Digits { public: using Digits::Digits; using Container= CONTAINER1; using LParser= LIST; // Initializing from its components Fraction_body(Digits && rhs) :Digits(std::move(rhs)) { } // Conversion methods static feature_t features_supported_by_to_dbln_fixed() { return (feature_t) (F_FRAC_DIGIT | F_FRAC_B | F_FRAC_DEC_PERIOD); } feature_t features_found() const { uint res= F_NONE; if (length() > non_digit_length()) res|= F_FRAC_DIGIT; if (m_B_count) res|= F_FRAC_B; if (m_dollar_count) res|= F_FRAC_DOLLAR; return (feature_t) res; } Double_null to_dbln_fixed(LS sbj, CHARSET_INFO *cs) const { size_t flag_count= m_dollar_count + m_B_count; sbj= sbj.ltrim(cs); DBUG_ASSERT(flag_count <= length()); // $ and B are flags, they don't need to match anything in sbj size_t chars_to_match= length() - flag_count; if (chars_to_match < sbj.length()) return Double_null(); // Can never match /* Skip the trailing format characters which require a match in the subject string (i.e. digits) and which are outside of the subject length. sbj='.99', fmt= '.99B9' -> fmt= '.99B' sbj='.99', fmt= '.9B99' -> fmt= '.9B9' */ size_t skip= chars_to_match - sbj.length(); LS fmt= to_LS(); for ( ; fmt.length() && skip > 0; fmt= fmt.chop_right()) { if (!is_currency_flag(fmt.back())) skip--; } DBUG_ASSERT(fmt.length() >= sbj.length()); double nr= 0; size_t digits_matched= 0; for (size_t pos= 0; pos < sbj.length(); pos++, fmt= fmt.chop_left()) { fmt= fmt.ltrim_currency_flags(); DBUG_ASSERT(fmt.front() == '0' || fmt.front() == '9'); if (sbj.at(pos) < '0' && sbj.at(pos) > '9') return Double_null(); digits_matched++; nr*= 10; nr+= ((uint) (uchar) sbj.at(pos)) - (uchar) '0'; } DBUG_ASSERT(fmt.ltrim_currency_flags().ptr() == fmt.end()); double tmp= digits_matched < array_elements(log_10) ? log_10[digits_matched] : pow(10.0, (double) digits_matched); /* Unlike Integer, Fraction does not need any digits to match to return a not-NULL result. It's enough that the decimal delimiter matched: to_number('.', '.') -> 0 */ return digits_matched ? Double_null(nr / tmp) : Double_null(0); } }; /* GRAMMAR: fraction_pDV: fraction_pDV_signature [ fraction_body ] GRAMMAR: fraction_pDVCLU: positional_currency [ fraction_body ] GRAMMAR: | fraction_pDV [ postfix_currency_signature ] Examples: positional_currency [ fraction_body ] 'C' 'C99' Examples: fraction_pDV [ postfix_currency_signature ] '.99' 'V99' '.99C' 'V99C' */ class Fraction: public Dec_delimiter_pDVCLU, public Fraction_body, public Postfix_currency { using A= Dec_delimiter_pDVCLU; using B= Fraction_body; using C= Postfix_currency; public: explicit operator bool() const { return A::operator bool() && B::operator bool() && C::operator bool(); } using Container= OR_CONTAINER3; size_t length() const { return A::length() + B::length() + C::length(); } // Default ctor and initialization from its components Fraction() = default; Fraction(A && a, B && b, C && c) :A(std::move(a)), B(std::move(b)), C(std::move(c)) { } // Sub-component rules class pDV: public AND2 { using AND2::AND2; }; private: class pDVCLU_signature_Opt_Fraction_body: public AND2 { using AND2::AND2; }; class pDV_Opt_Postfix_currency: public AND2 { using AND2::AND2; }; public: // Initialization from rules Fraction(pDV && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(C::empty()) { } Fraction(pDV::Opt && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(C::empty()) { } Fraction(pDVCLU_signature_Opt_Fraction_body && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(C::empty()) { } Fraction(pDV_Opt_Postfix_currency && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(std::move(static_cast(rhs))) { } using LParser= OR2C; // Conversion methods feature_t features_found() const { return (feature_t) (A::features_found() | B::features_found() | C::features_found()); } Double_null to_dbln_fixed(LS ls, CHARSET_INFO *cs) const { DBUG_ASSERT(!(features_found() & (F_FRAC_DEC_D | F_FRAC_DEC_CLU))); if (!ls.length() || ls.front() != '.') return Double_null(); return Fraction_body::to_dbln_fixed(ls.chop_left(), cs); } }; /********** A tail of a decimal number Its integer part can start with a group character. GRAMMAR: decimal_tail_pDVCLU: integer_tail [ fraction_pDVCLU ] GRAMMAR: | fraction_pDVCLU GRAMMAR: decimal_tail_pDV: integer_tail [ fraction_pDV ] GRAMMAR: | fraction_pDV Examples: integer_tail [ fraction_pDVCLU ] '99' '99.99' '99V99' '99C99' ',99' 'G99C99' ',99.' Examples: fraction_pDVCLU See above. Examples: integer_tail [ fraction_pDV ] '99' '99.' '99D99' '99V99' ',99' ',99.' Examples: fraction_pDV See above. */ class Decimal_tail: public Integer, public Fraction { using A= Integer; using B= Fraction; public: using Container= OR_CONTAINER2; explicit operator bool() const { return A::operator bool() && B::operator bool(); } // Default ctor and initializing from its componenets Decimal_tail() = default; Decimal_tail(A && a, B && b) :A(std::move(a)), B(std::move(b)) { } Decimal_tail(B && b) :A(A::Container::empty()), B(std::move(b)) { } // Dependency rules private: using Integer_tail_Opt_Fraction_pDVCLU= AND2; using Integer_tail_Opt_Fraction_pDV= AND2; public: // Initializing from rules Decimal_tail(Integer_tail_Opt_Fraction_pDVCLU && rhs) :A(static_cast(std::move(rhs))), B(static_cast(std::move(rhs))) { } Decimal_tail(Integer_tail_Opt_Fraction_pDV && rhs) :A(static_cast(std::move(rhs))), B(static_cast(std::move(rhs))) { } Decimal_tail(Fraction::pDV && rhs) :A(A::Container::empty()), B(std::move(rhs)) { } // Derived rules using Tail_pDVCLU= OR2C; using Tail_pDV= OR2C; }; /********** A decimal number ****/ class Decimal: public Decimal_tail { public: using Decimal_tail::Decimal_tail; using Container= CONTAINER1; // Initializing from rules Decimal(XChain && rhs) :Decimal_tail(std::move(rhs), Fraction::Container::empty()) { } // Helper constructors used in descendants Decimal(Zeros_or_nines && head, Decimal_tail && tail) :Decimal_tail( Integer::Container(std::move(head), std::move(static_cast(tail))), std::move(static_cast(tail))) { } }; /********** A tail of an approximate number (scientific notation) ***/ /* An approximate tail: Its integer part can start with a group character. GRAMMAR: approximate_tail_pDVCLU: decimal_tail_pDVCLU [ EEEE ] GRAMMAR: | EEEE GRAMMAR: approximate_tail_pDV: decimal_tail_pDV [ EEEE ] GRAMMAR: | EEEE Examples: decimal_tail_pDVCLU [ EEEE ] '99EEEE' '99.99EEEE' '99V99EEEE' '99C99EEEE' ',99EEEE' 'G99C99EEEE' ',99.EEEE' Examples: decimal_tail_pDV [ EEEE ] '99EEEE' '99.EEEE' '99D99EEEE' '99V99EEEE' ',99EEEE' ',99.EEEE' Also, as EEEE is optional, examples without EEEE at the end are valid. */ class Approximate_tail: public Decimal_tail, public EEEE { using A= Decimal_tail; using B= EEEE; public: using Container= OR_CONTAINER2; explicit operator bool() const { return A::operator bool() && B::operator bool(); } // Default ctor Approximate_tail() = default; // Deleting copying Approximate_tail(const Approximate_tail & rhs) = delete; Approximate_tail & operator=(const Approximate_tail & rhs) = delete; // Initialization from itself Approximate_tail(Approximate_tail && rhs) = default; Approximate_tail & operator=(Approximate_tail && rhs) = default; // Initialization from its components Approximate_tail(A && a, B && b) :A(std::move(a)), B(std::move(b)) { } // Dependency rules private: using Decimal_tail_pDVCLU_Opt_EEEE= AND2; using Decimal_tail_pDV_Opt_EEEE= AND2; public: // Initialization from rules Approximate_tail(EEEE::LParser::Opt && rhs) :A(A::Container::empty()), B(std::move(rhs)) { } Approximate_tail(Decimal_tail_pDVCLU_Opt_EEEE && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))) { } Approximate_tail(Decimal_tail_pDV_Opt_EEEE && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))) { } Approximate_tail(XChain && rhs) :A(Decimal(std::move(rhs))), B(B::Container::empty()) { } Approximate_tail(Fraction::pDV && rhs) :A(std::move(rhs)), B(B::Container::empty()) { } Approximate_tail(Fraction && rhs) :A(Decimal_tail::Container(std::move(rhs))), B(B::Container::empty()) { } // Derived rules using Tail_pDVCLU= OR2C; using Tail_pDV= OR2C; }; /********** A well formed approximate number ***/ /* Its integer part starts with a valid element (a digit or a flag). It cannot start with a group character. GRAMMAR: approximate_pDVCLU: zero_or_nines [ approximate_tail_pDVCLU ] GRAMMAR: | fraction_pDVCLU GRAMMAR: approximate_pDV: zero_or_nines [ approximate_tail_pDV ] GRAMMAR: | fraction_pDV Examples: zero_or_nines [ approximate_tail_pDVCLU ] '99' '99.99' '99V99' '99C99' '9,99' 'G99C99' '9,99.' '99EEEE' '99.99EEEE' '99V99EEEE' '99C99EEEE' '9,99EEEE' 'G99C99EEEE' '9,99.EEEE' Examples: zero_or_nines [ approximate_tail_pDV ] '99' '99.' '99D99' '99V99' '9,99' '9,99.' '99EEEE' '99.EEEE' '99D99EEEE' '99V99EEEE' '9,99EEEE' '9,99.EEEE' Note, in examples above any '9' can be replaced to '0' to get also a valid example. See examples for fraction_pDVCLU and fraction_pDV in their rules above. */ class Approximate: public Approximate_tail { public: using Approximate_tail::Approximate_tail; using Container= CONTAINER1; // Delete copying Approximate(const Approximate & rhs) = delete; Approximate & operator=(const Approximate & rhs) = delete; // Initializing from itself Approximate(Approximate && rhs) = default; Approximate & operator=(Approximate && rhs) = default; // Dependency rules private: using Zeros_or_nines_Opt_approximate_tail_pDVCLU= AND2; using Zeros_or_nines_Opt_approximate_tail_pDV= AND2; protected: // Dependency rules, also used by Unsigned_currency using Nines_Opt_approximate_tail_pDVCLU= AND2; using Zeros_Opt_approximate_tail_pDVCLU= AND2; public: // Initializing from rules Approximate(Zeros_Opt_approximate_tail_pDVCLU && rhs) :Approximate_tail( Decimal( std::move(static_cast(rhs)), std::move(static_cast(rhs))), std::move(static_cast(rhs))) { } Approximate(Nines_Opt_approximate_tail_pDVCLU && rhs) :Approximate_tail( Decimal( std::move(static_cast(rhs)), std::move(static_cast(rhs))), std::move(static_cast(rhs))) { } Approximate(Zeros_or_nines_Opt_approximate_tail_pDVCLU && rhs) :Approximate_tail( Decimal( std::move(static_cast(rhs)), std::move(static_cast(rhs))), std::move(static_cast(rhs))) { } Approximate(Zeros_or_nines_Opt_approximate_tail_pDV && rhs) :Approximate_tail( Decimal( std::move(static_cast(rhs)), std::move(static_cast(rhs))), std::move(static_cast(rhs))) { } // Derived rules using pDVCLU= OR2C; using pDV= OR2C; // Other methods bool check(const Parser & parser, const char **pos) const { if (Integer::length() == 0 && EEEE::length() != 0) { *pos= EEEE::ptr(); return true; } return false; } #ifndef DBUG_OFF void print(String *str) const { DBUG_ASSERT(Dec_delimiter_pDVCLU::length() || !Fraction_body::length()); str->append("A='"_LS); Integer::print(str); if (Dec_delimiter_pDVCLU::length()) { str->append("["_LS); Dec_delimiter_pDVCLU::print(str); str->append("]"_LS); } Fraction_body::print(str); str->append("'"_LS); if (Postfix_currency::length()) Postfix_currency::print_var_value(str, "RC"_LS); EEEE::print(str); } #endif // Other methods /* Return the fragment consisting only of digits, without trailing currency and without EEEE. */ LS to_LS_digits() const { /* Integer and Fraction_body can be empty and point to empty_lex_cstring. In this case it's not correct to concatenate them. */ if (!Integer::length()) return Fraction_body::to_LS(); if (!Fraction_body::length()) return Integer::to_LS(); // Ok to contatenate DBUG_ASSERT(Integer::end() < Fraction_body::ptr()); return {Integer::ptr(), Fraction_body::end()}; } // Conversion methods feature_t features_found() const { return (feature_t) (Integer::features_found() | Fraction::features_found() | EEEE::features_found()); } static feature_t features_supported_by_to_dbln_fixed() { return (feature_t) (Integer::features_supported_by_to_dbln_fixed() | Fraction::features_supported_by_to_dbln_fixed()); } static feature_t features_supported_by_to_dbln_EEEE() { // Group separators are not supported in for EEEE return (feature_t) (F_INT_DIGIT | F_INT_B | F_INT_DOLLAR | F_FRAC_DIGIT | F_FRAC_B | F_FRAC_DOLLAR | F_FRAC_DEC_PERIOD | F_EEEE); } Double_null to_dbln_fixed(const LS sbj, CHARSET_INFO *cs) const { for (const char *period= sbj.ptr(); period < sbj.end(); period++) { if (*period == '.') { const LS ls_int(sbj.ptr(), period); const LS ls_frac(period, sbj.end()); // Integer format length it can be 0 in a format like this: '$.9' Double_null d_int= ls_int.length() == 0 ? Double_null(0) : Integer::to_dbln_fixed(ls_int, cs); if (d_int.is_null()) return d_int; Double_null d_frac= Fraction::to_dbln_fixed(ls_frac, cs); if (d_frac.is_null()) return d_frac; return Double_null(d_int.value() + d_frac.value()); } } return Integer::to_dbln_fixed(sbj, cs); } Double_null to_dbln_EEEE(const LS sbj, CHARSET_INFO *cs) const { if (sbj.length() > 0) { char *end; int error; double nr= cs->strntod((char *)sbj.ptr(), sbj.length(), &end, &error); if (!error && end >= sbj.end()) return Double_null(nr); } /* - Empty, - Out or range - Trailing garbage: to_number('1e+3x', '99EEEE') */ return Double_null(); } }; /********** Rules related to a number with a prefix currency signature ***/ /* GRAMMAR: lflagged_approximate: 'B' approximate_pDV */ class LFlagged_approximate: public Currency_prefix_flags, public Approximate { using A= Currency_prefix_flags; using B= Approximate; public: using Container= OR_CONTAINER2; explicit operator bool() const { return A::operator bool() && B::operator bool(); } // Default ctor LFlagged_approximate() = default; // Delete copying LFlagged_approximate(const LFlagged_approximate & rhs) = delete; LFlagged_approximate & operator=(const LFlagged_approximate & rhs) = delete; // Initialization from itself LFlagged_approximate(LFlagged_approximate && rhs) = default; LFlagged_approximate & operator=(LFlagged_approximate && rhs) = default; // Initialization from its components LFlagged_approximate(A && a, B && b) :A(std::move(a)), B(std::move(b)) { } // Dependency rules using B_Opt_Approximate_pDV= AND2; // Initialization from rules LFlagged_approximate(B_Opt_Approximate_pDV && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))) { } LFlagged_approximate(Approximate::pDV && rhs) :A(A::empty()), B(std::move(rhs)) { } }; /********** Unsigned currency ***/ /* GRAMMAR: unsigned_currency: unsigned_currency0 GRAMMAR: | unsigned_currency1 GRAMMAR: usigned_currency0: zeros [ approximate_tail_pDVCLU ]; Unsigned_currency1 - a currency not starting with zeros: GRAMMAR: unsigned_currency1: GRAMMAR: nines [ approximate_tail_pDVCLU ] GRAMMAR: | decimal_flags [ approximate_pDVCLU ] GRAMMAR: | left_currency GRAMMAR: | fraction_pDV [EEEE] [ positional_currency ] GRAMMAR: left_currency: prefix_currency_signature [ approximate_pDV ] GRAMMAR: | prefix_currency_signature 'B' [ approximate_pDV ] */ class Unsigned_currency: public Prefix_currency, public Currency_prefix_flags, public Approximate { using A= Prefix_currency; using B= Currency_prefix_flags; using C= Approximate; public: using Container= OR_CONTAINER3; explicit operator bool() const { return A::operator bool() && B::operator bool() && C::operator bool(); } // Default ctor Unsigned_currency() = default; // Delete copying Unsigned_currency(const Unsigned_currency & rhs) = delete; Unsigned_currency & operator=(const Unsigned_currency & rhs) = delete; // Initializing from itself Unsigned_currency(Unsigned_currency &&rhs) = default; Unsigned_currency & operator=(Unsigned_currency && rhs) = default; // Initializing from its componenents Unsigned_currency(A && a, B && b, C && c) :A(std::move(a)), B(std::move(b)), C(std::move(c)) { } // Dependency rules private: using Left_currency_tail= OR2C; using Left_currency= AND2; using Decimal_flags_Opt_approximate_pDVCLU= AND2; using Fraction_pDV_Opt_EEEE_Opt_postfix_currency= AND3; public: // Initializing from rules Unsigned_currency(Nines_Opt_approximate_tail_pDVCLU &&rhs) :A(A::empty()), B(B::empty()), C(std::move(rhs)) { } Unsigned_currency(Decimal_flags_Opt_approximate_pDVCLU &&rhs) :A(A::empty()), B(std::move(static_cast(rhs))), C(std::move(static_cast(rhs))) { } Unsigned_currency(Left_currency &&rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(std::move(static_cast(rhs))) { } Unsigned_currency(Fraction_pDV_Opt_EEEE_Opt_postfix_currency &&rhs) :A(A::empty()), B(B::empty()), C(Decimal(Fraction( std::move(static_cast(rhs)), std::move(static_cast(rhs)), std::move(static_cast(rhs)))), EEEE(std::move(static_cast(rhs)))) { } Unsigned_currency(Zeros_Opt_approximate_tail_pDVCLU && rhs) :A(A::empty()), B(B::empty()), C(std::move(rhs)) { } using Unsigned_currency0= Zeros_Opt_approximate_tail_pDVCLU; using Unsigned_currency1= OR4C; using LParser= OR2C; // Other methods bool check(const Parser & parser, const Decimal_flag_counters & prefix_flag_counters, const char **pos) const { if (Approximate::check(parser, pos)) return true; size_t approx_dollar_count= Integer::m_dollar_count + Fraction::m_dollar_count; // e.g. '$.$' if (prefix_flag_counters.m_dollar_count + approx_dollar_count > 1) { if (prefix_flag_counters.m_dollar_count > 1) *pos= Currency_prefix_flags::to_LS().find_dollar(1); else *pos= Approximate::to_LS_digits(). find_dollar(!prefix_flag_counters.m_dollar_count); return true; } // e.g. 'B.B' if (prefix_flag_counters.m_B_count + Integer::m_B_count + Fraction::m_B_count > 1) { if (prefix_flag_counters.m_B_count > 1) *pos= Currency_prefix_flags::to_LS().find_currency_flag_B(1); else *pos= Approximate::to_LS_digits(). find_currency_flag_B(!prefix_flag_counters.m_B_count); return true; } // '$9C' '9$C' if (prefix_flag_counters.m_dollar_count || approx_dollar_count) { if (Dec_delimiter_pDVCLU::length()) { const char *dec= Dec_delimiter_pDVCLU::ptr(); if (is_positional_currency(dec[0])) { *pos= dec; return true; } } // '$.C' if (Postfix_currency::length()) { *pos= Postfix_currency::ptr(); return true; } } // 'C0$', 'C$U' 'V$U' if (Prefix_currency::length() && approx_dollar_count) { *pos= Approximate::to_LS_digits().find_dollar(0); return true; } return false; } #ifndef DBUG_OFF void print(String *str) const { if (Currency_prefix_flags::length()) Currency_prefix_flags::print_var_value(str, "CFl"_LS); if (Prefix_currency::length()) Prefix_currency::print_var_value(str, "LC"_LS); Approximate::print(str); } #endif // Conversion methods feature_t features_found() const { return (feature_t) (A::features_found() | B::features_found() | C::features_found()); } static feature_t features_supported_by_to_dbln_fixed() { return (feature_t) (Approximate::features_supported_by_to_dbln_fixed() | F_PREFIX_B | F_PREFIX_DOLLAR | F_INT_DOLLAR | F_FRAC_DOLLAR); } static feature_t features_supported_by_to_dbln_EEEE() { return (feature_t) (Approximate::features_supported_by_to_dbln_EEEE() | //F_PREFIX_CLU F_PREFIX_B | F_PREFIX_DOLLAR ); } bool get_prefix_dollar_sign(LS *ls) const { if (Currency_prefix_flags::prefix_flag_counters().m_dollar_count || Integer::m_dollar_count || Fraction::m_dollar_count) { if (!ls->length() || ls->front() != '$') return true; *ls= ls->chop_left(); } return false; } Double_null to_dbln_fixed(LS src, CHARSET_INFO *cs) const { return get_prefix_dollar_sign(&src) ? Double_null() : Approximate::to_dbln_fixed(src, cs); } Double_null to_dbln_EEEE(LS src, CHARSET_INFO *cs) const { return get_prefix_dollar_sign(&src) ? Double_null() : Approximate::to_dbln_EEEE(src, cs); } }; /********** A currency with a postfix sign ***/ /* For the grammar simplicity, currency0_with_postfix_sign includes xchain (optionally preceding by zeros), allthough it cannot really be followed by a postfix sign. "xxx_with_postix_sign" here means "xxx which can optionally be followed by a postfix sign", or "xxx which does not have a preceeding prefix sign". currency0_with_postfix_sign: zeros xchain | zeros [ approximate_tail_pDVCLU ] [ postfix_sign ] Rewriting the grammar as: GRAMMAR: currency0_with_postfix_sign: zeros [ zeros_tail ] GRAMMAR: zeros_tail: xchain GRAMMAR: | approximate_tail_pDVCLU [ postfix_sign ] GRAMMAR: | postfix_sign */ class Currency0_tail_with_postfix_sign: public Approximate_tail, public Postfix_sign { using A= Approximate_tail; using B= Postfix_sign; public: using Container= OR_CONTAINER2; explicit operator bool() const { return A::operator bool() && B::operator bool(); } // Delete copying Currency0_tail_with_postfix_sign(const Currency0_tail_with_postfix_sign &) = delete; Currency0_tail_with_postfix_sign & operator= (const Currency0_tail_with_postfix_sign & rhs) = delete; // Initialization from itself Currency0_tail_with_postfix_sign(Currency0_tail_with_postfix_sign && rhs) = default; Currency0_tail_with_postfix_sign & operator= (Currency0_tail_with_postfix_sign && rhs) = default; // Initialization from its components Currency0_tail_with_postfix_sign(A && a, B && b) :A(std::move(a)), B(std::move(b)) { } // Dependency rules private: using Approximate_tail_pDVCLU_Opt_postfix_sign= AND2; public: // Initialization from rules Currency0_tail_with_postfix_sign(XChain && rhs) :A(std::move(rhs)), B(B::empty()) { } Currency0_tail_with_postfix_sign( Approximate_tail_pDVCLU_Opt_postfix_sign &&rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))) { } Currency0_tail_with_postfix_sign(Postfix_sign_signature::Opt && rhs) :A(A::Container::empty()), B(std::move(rhs)) { } using LParser=OR3C; }; /* GRAMMAR: currency_with_postfix_sign: xchain GRAMMAR: | currency0_with_postfix_sign GRAMMAR: | unsigned_currency1 [ postfix_sign ] GRAMMAR: | postfix_specific_sign_signature */ class Currency_with_postfix_sign: public Unsigned_currency, public Postfix_sign { using A= Unsigned_currency; using B= Postfix_sign; public: using Container= OR_CONTAINER2; explicit operator bool() const { return A::operator bool() && B::operator bool(); } // Default ctor Currency_with_postfix_sign() = default; // Delete copying Currency_with_postfix_sign(const Currency_with_postfix_sign &) = delete; Currency_with_postfix_sign & operator=(const Currency_with_postfix_sign &) = delete; // Initialization from itself Currency_with_postfix_sign(Currency_with_postfix_sign && rhs) = default; Currency_with_postfix_sign & operator=(Currency_with_postfix_sign && rhs) = default; // Initialization from its components Currency_with_postfix_sign(A && a, B && b) :A(std::move(a)), B(std::move(b)) { } // Dependency rules private: using Currency0_with_postfix_sign= AND2; using Unsigned_currency1_Opt_Postfix_sign= AND2; public: // Initialization from rules Currency_with_postfix_sign(XChain && rhs) :A(Unsigned_currency::Container(Approximate::Container(std::move(rhs)))), B(B::Container::empty()) { } Currency_with_postfix_sign(Currency0_with_postfix_sign && rhs) :A(Unsigned_currency::Container( Approximate::Container( Decimal( Zeros_or_nines(static_cast(rhs)), std::move(static_cast(rhs))), std::move(static_cast(rhs))))), B(std::move(static_cast(rhs))) { } Currency_with_postfix_sign(Unsigned_currency1_Opt_Postfix_sign && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))) { } Currency_with_postfix_sign(Postfix_specific_sign_signature && rhs) :A(A::Container::empty()), B(std::move(rhs)) { } using LParser= OR4C; // Other methods bool check(const Parser & parser, const Decimal_flag_counters & prefix_flag_counters, const char **pos) const { return Unsigned_currency::check(parser, prefix_flag_counters, pos); } #ifndef DBUG_OFF void print(String *str) const { Unsigned_currency::print(str); if (Postfix_sign::length()) Postfix_sign::print_var_value(str, "RS"_LS); } #endif // Conversion methods static feature_t features_supported_by_to_dbln_fixed() { return (feature_t) (Unsigned_currency::features_supported_by_to_dbln_fixed() | F_POSTFIX_SIGN); } static feature_t features_supported_by_to_dbln_EEEE() { return (feature_t) (Unsigned_currency::features_supported_by_to_dbln_EEEE() | F_POSTFIX_SIGN); } feature_t features_found() const { return (feature_t) (Postfix_sign::features_found() | Unsigned_currency::features_found()); } Double_null to_dbln_fixed(LS src, CHARSET_INFO *cs) const { bool neg; if (Postfix_sign::get(&neg, &src)) return Double_null(); const Double_null rc= Unsigned_currency::to_dbln_fixed(src, cs); return rc.is_null() || !neg ? rc : -rc; } Double_null to_dbln_EEEE(LS src, CHARSET_INFO *cs) const { bool neg; if (Postfix_sign::get(&neg, &src)) return Double_null(); const Double_null rc= Unsigned_currency::to_dbln_EEEE(src, cs); return rc.is_null() || !neg ? rc : -rc; } }; /********** An unsigned format ***/ /* GRAMMAR: unsigned_format: unsigned_currency GRAMMAR: | format_TM_signature */ class Unsigned_format: public Unsigned_currency, public Format_TM { using A= Unsigned_currency; using B= Format_TM; public: using Container= OR_CONTAINER2; explicit operator bool() const { return A::operator bool() && B::operator bool(); } // Default ctor Unsigned_format() = default; // Delete copying Unsigned_format(const Unsigned_format & rhs) = delete; Unsigned_format & operator=(const Unsigned_format & rhs) = delete; // Initializing from itself Unsigned_format(Unsigned_format && rhs) = default; Unsigned_format & operator=(Unsigned_format && rhs) = default; // Initializing from its componenet Unsigned_format(A && a, B && b) :A(std::move(a)), B(std::move(b)) { } // Initilizing from rules Unsigned_format(Unsigned_currency::LParser && rhs) :A(std::move(rhs)), B(B::empty()) { } Unsigned_format(Format_TM::LParser && rhs) :A(A::Container::empty()), B(std::move(rhs)) { } using LParser= OR2C; }; /********** The format - the top level rules ****/ /* GRAMMAR: format_FM_tail: currency_with_postfix_sign GRAMMAR: | 'S' [ unsigned_format ] GRAMMAR: | format_TM */ class Format_FM_tail: public Prefix_sign, public Unsigned_format, public Postfix_sign { using A= Prefix_sign; using B= Unsigned_format; using C= Postfix_sign; public: explicit operator bool() const { return A::operator bool() && B::operator bool() && C::operator bool(); } using Container= OR_CONTAINER3; // Delete copying Format_FM_tail(const Format_FM_tail & rhs) = delete; Format_FM_tail & operator=(const Format_FM_tail & rhs) = delete; // Initializing from itself Format_FM_tail(Format_FM_tail && rhs) = default; Format_FM_tail & operator=(Format_FM_tail && rhs) = default; // Initialization from its components Format_FM_tail(A && a, B && b, C && c) :A(std::move(a)), B(std::move(b)), C(std::move(c)) { } // Dependency rules private: using Prefix_sign_Opt_unsigned_format= AND2; public: // Initializing from rules Format_FM_tail(Format_TM::LParser &&rhs) :A(A::Container::empty()), B(Unsigned_format::Container(std::move(rhs))), C(C::empty()) { } Format_FM_tail(Currency_with_postfix_sign::LParser && rhs) :A(A::empty()), B(Unsigned_format::Container(static_cast(rhs))), C(std::move(static_cast(rhs))) { } Format_FM_tail(Prefix_sign_Opt_unsigned_format && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(C::empty()) { } using LParser= OR3C; }; /* GRAMMAR: format: currency_with_postfix_sign GRAMMAR: | 'FM' [ format_FM_tail ] GRAMMAR: | 'S' ['FM'] [ unsigned_format ] GRAMMAR: | format_TM_signature */ class Format: public Format_flags, public Prefix_sign, public Currency_with_postfix_sign, public Format_TM { using A= Format_flags; using B= Prefix_sign; using C= Currency_with_postfix_sign; using D= Format_TM; public: using Container= OR_CONTAINER4; explicit operator bool() const { return A::operator bool() && B::operator bool() && C::operator bool() && D::operator bool(); } // Delete copying Format(const Format & rhs) = delete; Format & operator=(const Format & rhs) = delete; // Initialization from itself Format(Format && rhs) = default; Format & operator=(Format && rhs) = default; // Initialization from its components Format(A && a, B && b, C && c, D && d) :A(std::move(a)), B(std::move(b)), C(std::move(c)), D(std::move(d)) { } // Dependency rules private: using Format_FM= AND2; using Format_prefix_sign_Opt_FM_Opt_unsigned_format= AND3; public: // Initialization from rules Format(Format_FM && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(std::move(static_cast(rhs)), std::move(static_cast(rhs))), D(std::move(static_cast(rhs))) { } Format(Format_prefix_sign_Opt_FM_Opt_unsigned_format && rhs) :A(std::move(static_cast(rhs))), B(std::move(static_cast(rhs))), C(Currency_with_postfix_sign::Container( std::move(static_cast(rhs)))), D(std::move(static_cast(rhs))) { } Format(Format_TM::LParser && rhs) :A(A::Container::empty()), B(B::Container::empty()), C(C::Container::empty()), D(Format_TM::Container(std::move(rhs))) { } using LParser= OR4C; // Other methods bool check(const Parser & parser, const char **pos) const { if (Currency_with_postfix_sign::operator bool() && Currency_with_postfix_sign::check(parser, prefix_flag_counters(), pos)) return true; return false; } #ifndef DBUG_OFF void print(String *str) const { if (Format_flags::length() > 0) Format_flags::print_var_value(str, "FFl"_LS); if (Prefix_sign::length() > 0) Prefix_sign::print_var_value(str, "LS"_LS); if (Currency_with_postfix_sign::operator bool()) Currency_with_postfix_sign::print(str); if (Format_TM::length()) Format_TM::print_var_value(str, "TM"_LS); } void print_as_note(THD *thd) const { StringBuffer<64> tmp; print(&tmp); push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR, "%.*s", (int) tmp.length(), tmp.ptr()); } #endif // Conversion methods feature_t features_found() const { return (feature_t) (A::features_found() | B::features_found() | C::features_found() | D::features_found()); }; static feature_t features_supported_by_to_dbln_fixed() { return (feature_t) (Currency_with_postfix_sign::features_supported_by_to_dbln_fixed() | F_PREFIX_SIGN | F_FMT_FLAG_FM); } static feature_t features_supported_by_to_dbln_EEEE() { return (feature_t) (Currency_with_postfix_sign::features_supported_by_to_dbln_EEEE() | F_PREFIX_SIGN | F_FMT_FLAG_FM); } static feature_t features_supported_by_to_dbln_XXXX() { return (feature_t) (F_INT_DIGIT | F_INT_HEX | F_FMT_FLAG_FM); } Double_null to_dbln_XXXX(LS sbj, CHARSET_INFO *cs) const { /* Return NULL if: - The subject string is empty, or - The subject string is longer than the format, or - The format has leading 0s and the subject is shorter than the format */ if (!sbj.length() || sbj.length() > Integer::length() || (Integer::m_0_count > 0 && sbj.length() < Integer::length())) return Double_null(); double res= 0; for (size_t i= 0; i < sbj.length(); i++) { int hex_char= hexchar_to_int(sbj.at(i)); if (hex_char < 0) return Double_null(); res*= 16; res+= hex_char; } return Double_null(res); } Double_null to_dbln_fixed(LS src, CHARSET_INFO *cs) const { bool neg; if (Prefix_sign::get(&neg, &src)) return Double_null(); const Double_null nr= Currency_with_postfix_sign::to_dbln_fixed(src, cs); return nr.is_null() || !neg ? nr : -nr; } Double_null to_dbln_EEEE(LS src, CHARSET_INFO *cs) const { bool neg; if (Prefix_sign::get(&neg, &src)) Double_null(); const Double_null nr= Currency_with_postfix_sign::to_dbln_EEEE(src, cs); return nr.is_null() || !neg ? nr : -nr; } }; public: // goal: [ format ] EOF class Goal: public AND2 { public: using AND2::AND2; // Delete copying Goal(const Goal & rhs) = delete; Goal & operator=(const Goal & rhs) = delete; // Initializing from itself Goal & operator=(Goal && rhs) = default; bool check(const Parser & parser, enum_warning_level level) const { if (!operator bool()) { parser.raise_bad_format_at(parser.thd(), level); return true; } const char *pos= nullptr; if (!Format::check(parser, &pos)) return false; DBUG_ASSERT(pos); const LS buffer(parser.buffer()); if (pos > buffer.ptr() && pos < buffer.end()) { /* If an error happened in the middle of the format, let's print it into two parts: '$999$' -> '$999 + $' to see the whole format and the position where the error happened. */ StringBuffer tmp(parser.charset()); const LS part1(buffer.ptr(), pos); const LS part2(pos, buffer.end()); tmp.append(part1.ptr(), part1.length()); tmp.append(" + "_LS); tmp.append(part2.ptr(), part2.length()); ErrConvString err(&tmp); parser.raise_bad_format_at(parser.thd(), level, &err); return true; } parser.raise_bad_format_at(parser.thd(), level, pos); return true; } }; }; /* End of class Parser */ class Item_func_to_number: public Item_handled_func { /* Structures used to cache the format if args[1] is an evaluable constant during fix_length_and_dec_time. */ Parser::Goal m_format; Parser m_parser; StringBuffer<32> m_format_buffer; public: Item_func_to_number(THD *thd, List &list) :Item_handled_func(thd, list) { } LEX_CSTRING func_name_cstring() const override { static LEX_CSTRING name= {STRING_WITH_LEN("to_number") }; return name; } Item *shallow_copy(THD *thd) const override { return nullptr; } // Get an Item_func_to_number pointer from a Item_handled_func pointer. static Item_func_to_number *to_func_to_number(Item_handled_func *func) { DBUG_ASSERT(dynamic_cast(func)); return static_cast(func); } double val_real_from_dbln(const String & sbj, const Double_null &nr) { null_value= nr.is_null(); if (null_value) raise_conversion_warning(current_thd, m_format_buffer, sbj); return nr.is_null() ? 0 : nr.value(); } bool fix_length_and_dec_double() { set_maybe_null(); decimals= NOT_FIXED_DEC; max_length= float_length(decimals); return false; } /********** Helper methods to handle String ***/ /* Note, the code in Parser does not support character sets with mbminlen>1, so in case of ucs2, utf16, utf32 arguments we need to convert them to some character set with mbminlen==1. Let's use utf8mb4 as such character set. */ /* Convert a string to utf8mb4, if needed. If mbminlen is already 1, then do nothing. */ bool convert_to_mb1_if_needed(String *str) { if (str->charset()->mbminlen == 1) return false; uint errors= 0; String tmp; if (tmp.copy(str, &my_charset_utf8mb4_bin, &errors)) return true; str->swap(tmp); return false; } /* Copy with a character set conversion if needed. If "from" has mbminlen > 1 then convert to utf8mb4, otherwise just copy. "to" and "from" must be two different objects. */ bool copy_or_convert_to_mb1(String *to, const String & from) { DBUG_ASSERT(to != &from); uint errors= 0; return from.charset()->mbminlen > 1 ? to->copy(&from, &my_charset_utf8mb4_bin, &errors) : to->copy(from); } /* Similar to copy_or_convert_to_mb1(), but "to" can point to "from". */ bool copy_or_convert_to_mb1_maybe_self(String *to, const String & from) { if (to != &from) return copy_or_convert_to_mb1(to, from); if (from.charset()->mbminlen == 1) return false; String tmp; if (copy_or_convert_to_mb1(&tmp, from)) return true; to->swap(tmp); return false; } /********** Item_handled_func::Handler implementations ****/ // A helper abstract class, to define fix_length_and_dec(). class Ha_double: public Item_handled_func::Handler_double { public: bool fix_length_and_dec(Item_handled_func *func) const override { return to_func_to_number(func)->fix_length_and_dec_double(); } }; /** Helper templates to get args[0] and convert it according to the format **/ template double val_real_fixed(const Parser::Format & format) { DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;); StringBuffer subject_buffer; String *sbj= args[0]->val_str(&subject_buffer); if ((null_value= !sbj || convert_to_mb1_if_needed(sbj))) return 0; const T & exec= static_cast(format); return val_real_from_dbln(*sbj, exec.to_dbln_fixed(sbj->to_lex_cstring(), sbj->charset())); } template double val_real_signed_EEEE(const Parser::Format & format) { DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;); StringBuffer subject_buffer; String *sbj= args[0]->val_str(&subject_buffer); if ((null_value= !sbj || convert_to_mb1_if_needed(sbj))) return 0; const T & exec= static_cast(format); return val_real_from_dbln(*sbj, exec.to_dbln_EEEE(sbj->to_lex_cstring(), sbj->charset())); } template double val_real_XXXX(const Parser::Format & format) { DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;); StringBuffer subject_buffer; String *sbj= args[0]->val_str(&subject_buffer); if ((null_value= !sbj || convert_to_mb1_if_needed(sbj))) return 0; const T & exec= static_cast(format); return val_real_from_dbln(*sbj, exec.to_dbln_XXXX(sbj->to_lex_cstring(), sbj->charset())); } /***** Classes to handle the case with arg_count==1: to_number('123') */ // With numeric input class Ha_double_without_format_using_val_real: public Ha_double { public: double val_real(Item_handled_func *func) const override { DBUG_EXECUTE_IF("numconv_format", func->null_value= false; return 0;); return to_func_to_number(func)->val_real_from_item(func->arguments()[0]); } static const Ha_double_without_format_using_val_real s_singleton; }; // With string input class Ha_double_without_format_using_val_str: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_signed_EEEE(tfunc->m_format); } static const Ha_double_without_format_using_val_str s_singleton; }; /********** Classes to handle a number with a fixed format ***/ // Integer-only formats (with or without group separators) class Ha_double_integer: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_fixed(tfunc->m_format); } static const Ha_double_integer s_singleton; }; // Fraction-only formats starting with a decimal delimiter class Ha_double_fraction: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_fixed(tfunc->m_format); } static const Ha_double_fraction s_singleton; }; // Unsigned currency class Ha_double_unsigned_currency: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_fixed(tfunc->m_format); } static const Ha_double_unsigned_currency s_singleton; }; // A currency with a postfix sign class Ha_double_currency_with_postfix_sign: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_fixed (tfunc->m_format); } static const Ha_double_currency_with_postfix_sign s_singleton; }; // Signed currency (with a prefix or postfix sign) class Ha_double_signed_currency: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_fixed(tfunc->m_format); } static const Ha_double_signed_currency s_singleton; }; /***** The class to handler a number with a EEEE (scientific) format ***/ class Ha_double_signed_EEEE: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_signed_EEEE(tfunc->m_format); } static const Ha_double_signed_EEEE s_singleton; }; /***** The class to handler a number with a XXXX (hexadecimal) format ***/ class Ha_double_XXXX: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_XXXX(tfunc->m_format); } static const Ha_double_XXXX s_singleton; }; /********** Format detected per row ***/ double val_real_with_format_per_row() { auto const level= Sql_condition::WARN_LEVEL_WARN; StringBuffer subject_buffer; String *sbj, *fmt; if ((null_value= !(sbj= args[0]->val_str(&subject_buffer)) || !(fmt= args[1]->val_str(&m_format_buffer)) || copy_or_convert_to_mb1_maybe_self(&m_format_buffer, *fmt))) return 0; // Parse the format and validate it THD *thd= current_thd; m_parser= Parser(thd, func_name_cstring(), m_format_buffer.charset(), m_format_buffer.to_lex_cstring()); m_format= Parser::Goal(&m_parser); if ((null_value= m_format.check(m_parser, level))) return 0; /* If the caller wants to check the format syntax only, let's leave here, to avoid func_handler_by_format() raising "unsupported format" errors. */ DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;); const Handler *ha= func_handler_by_format(thd, m_format, m_parser, level); if ((null_value= !ha)) return 0; return ha->val_real(this); } class Ha_double_with_format_per_row: public Ha_double { public: double val_real(Item_handled_func *func) const override { Item_func_to_number *tfunc= to_func_to_number(func); return tfunc->val_real_with_format_per_row(); } static const Ha_double_with_format_per_row s_singleton; }; /********** A warning on conversion failures ***/ void raise_conversion_warning(THD *thd, const String &fmt, const String &sbj) { StringBuffer tmp_fmt(fmt.charset()); tmp_fmt.append("='"_LS); tmp_fmt.append(fmt.ptr(), fmt.length()); tmp_fmt.append("'"_LS); ErrConvString err_fmt(&tmp_fmt); ErrConvString err_sbj(&sbj); push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_VALUE_FOR_TYPE, ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE), err_fmt.ptr(), err_sbj.ptr(), func_name()); } /********** fix_length_and_dec() related methods ***/ const Item_handled_func::Handler * func_handler_by_format(THD *thd, const Parser::Goal & format, const Parser & parser, enum_warning_level level) const { /* Format TM is not tested here. It returns an error or a warning with NULL. It's not supported by to_number(). It's only supported by to_char(). */ const Parser::feature_t features_found= format.features_found(); Parser::feature_t f_supported= Parser::F_NONE; // Hexadecimal formats if (features_found & Parser::F_INT_HEX) { f_supported= Parser::Format::features_supported_by_to_dbln_XXXX(); if ((features_found & ~f_supported) == 0) return &Ha_double_XXXX::s_singleton; } // Scientific numeric formats if (format.EEEE::length()) { f_supported= Parser::Format::features_supported_by_to_dbln_EEEE(); if ((features_found & ~f_supported) == 0) return &Ha_double_signed_EEEE::s_singleton; } // Fixed numeric formats f_supported= Parser::Integer::features_supported_by_to_dbln_fixed(); if ((features_found & ~f_supported) == 0) return &Ha_double_integer::s_singleton; f_supported= Parser::Fraction::features_supported_by_to_dbln_fixed(); if ((features_found & ~f_supported) == 0) return &Ha_double_fraction::s_singleton; f_supported= Parser::Unsigned_currency:: features_supported_by_to_dbln_fixed(); if ((features_found & ~f_supported) == 0) return &Ha_double_unsigned_currency::s_singleton; f_supported= Parser::Currency_with_postfix_sign:: features_supported_by_to_dbln_fixed(); if ((features_found & ~f_supported) == 0) return &Ha_double_currency_with_postfix_sign::s_singleton; f_supported= Parser::Format::features_supported_by_to_dbln_fixed(); if ((features_found & ~f_supported) == 0) return &Ha_double_signed_currency::s_singleton; // A syntactically correct but not supported yet format parser.raise_not_supported_yet(thd, level, parser.buffer()); return nullptr; } bool set_func_handler_for_const_format(THD *thd) { const auto level= Sql_condition::WARN_LEVEL_ERROR; // Evaluate the format and cache its value in m_format_buffer. String *fmt= args[1]->val_str(&m_format_buffer); if (!fmt || copy_or_convert_to_mb1_maybe_self(&m_format_buffer, *fmt)) return null_value= true;// SQL NULL or EOM // Parse the format and validate it m_parser= Parser(thd, func_name_cstring(), m_format_buffer.charset(), m_format_buffer.to_lex_cstring()); m_format= Parser::Goal(&m_parser); if ((null_value= m_format.check(m_parser, level))) return true; DBUG_EXECUTE_IF("numconv_format", (m_format.print_as_note(thd));); // Determine the handler const Handler *ha= func_handler_by_format(thd, m_format, m_parser, level); if (!ha) return true; set_func_handler(ha); return false; } bool fix_length_and_dec(THD *thd) override { DBUG_ASSERT(arg_count >= 1 && arg_count <= 2); const Type_handler *th0= args[0]->type_handler(); if (arg_count == 1) // to_number('123') { bool using_string= th0->cmp_type() == STRING_RESULT; if (!(using_string ? th0->can_return_text() : th0->can_return_real())) { my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0), th0->name().ptr(), func_name()); return true; } if (using_string) set_func_handler(&Ha_double_without_format_using_val_str::s_singleton); else set_func_handler(&Ha_double_without_format_using_val_real::s_singleton); return m_func_handler->fix_length_and_dec(this); } const Type_handler *th1= args[1]->type_handler(); if (th0->cmp_type() != STRING_RESULT || !th0->can_return_text() || th1->cmp_type() != STRING_RESULT || !th1->can_return_text()) { my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0), th0->name().ptr(), th1->name().ptr(), func_name()); return true; } if (args[1]->can_eval_in_optimize()) // to_number('123',const_expr) { /* If args[1] is a contant, then evaluate the format, parse and cache the parsed format, to avoid its evaluation and parsing per row. */ if (set_func_handler_for_const_format(thd)) return true; } else // The format is not constant set_func_handler(&Ha_double_with_format_per_row::s_singleton); return m_func_handler->fix_length_and_dec(this); } }; /********** Handler's singletons ****/ const Item_func_to_number::Ha_double_without_format_using_val_real Item_func_to_number::Ha_double_without_format_using_val_real::s_singleton; const Item_func_to_number::Ha_double_without_format_using_val_str Item_func_to_number::Ha_double_without_format_using_val_str::s_singleton; const Item_func_to_number::Ha_double_XXXX Item_func_to_number::Ha_double_XXXX::s_singleton; const Item_func_to_number::Ha_double_with_format_per_row Item_func_to_number::Ha_double_with_format_per_row::s_singleton; const Item_func_to_number::Ha_double_integer Item_func_to_number::Ha_double_integer::s_singleton; const Item_func_to_number::Ha_double_fraction Item_func_to_number::Ha_double_fraction::s_singleton; const Item_func_to_number::Ha_double_unsigned_currency Item_func_to_number:: Ha_double_unsigned_currency::s_singleton; const Item_func_to_number::Ha_double_currency_with_postfix_sign Item_func_to_number:: Ha_double_currency_with_postfix_sign::s_singleton; const Item_func_to_number::Ha_double_signed_currency Item_func_to_number:: Ha_double_signed_currency::s_singleton; const Item_func_to_number::Ha_double_signed_EEEE Item_func_to_number::Ha_double_signed_EEEE::s_singleton; /********** Create_func related things ***/ class Create_func_to_number : public Create_native_func { public: Item *create_native(THD *thd, const LEX_CSTRING *name, List * args) override { if (unlikely(!args || args->elements < 1 || args->elements > 2)) { my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name->str); return NULL; } return new (thd->mem_root) Item_func_to_number(thd, *args); } static Create_func_to_number s_singleton; protected: Create_func_to_number() = default; ~Create_func_to_number() override = default; }; Create_func_to_number Create_func_to_number::s_singleton; Create_func & create_func_to_number= Create_func_to_number::s_singleton;