From 15d6cd6f7f64b79290a3e31957c5c1b33d3e9118 Mon Sep 17 00:00:00 2001 From: kjarir Date: Sun, 7 Jun 2026 00:14:26 +0530 Subject: [PATCH 01/12] MDEV-10526: Add binary string support to bitwise operators Bitwise operators (&, |, ^, ~, <<, >>) previously cast all arguments to BIGINT, silently truncating values wider than 64 bits. This broke operations on BINARY, VARBINARY, BLOB, INET6, and UUID columns. Introduces binary_mode detection in fix_length_and_dec(). When any non-literal argument has STRING_RESULT with binary charset, operators switch to byte-by-byte processing via a new Handler_str subclass, returning LONGBLOB of the same length as the input. Bare hex literals (x'FF', 0xFF) and bit literals (b'1010') retain integer mode for backward compatibility. Existing int/decimal handler classes for Item_func_bit_or and Item_func_bit_and are moved from item_cmpfunc.cc to item_func.cc for consistency. New error codes: ER_INVALID_BITWISE_OPERANDS_SIZE ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE Aggregate function support (BIT_AND/BIT_OR/BIT_XOR) to follow in a subsequent commit. Closes: MDEV-10526 --- sql/item_cmpfunc.cc | 70 ----- sql/item_func.cc | 600 ++++++++++++++++++++++++++++++++++++++ sql/item_func.h | 16 +- sql/share/errmsg-utf8.txt | 4 + 4 files changed, 619 insertions(+), 71 deletions(-) diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index eaa705642d843..0b8417f127d54 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -5062,76 +5062,6 @@ bool Item_func_in::ora_join_processor(void *arg) return FALSE; } - -class Func_handler_bit_or_int_to_ulonglong: - public Item_handled_func::Handler_ulonglong -{ -public: - Longlong_null to_longlong_null(Item_handled_func *item) const override - { - DBUG_ASSERT(item->fixed()); - Longlong_null a= item->arguments()[0]->to_longlong_null(); - return a.is_null() ? a : a | item->arguments()[1]->to_longlong_null(); - } -}; - - -class Func_handler_bit_or_dec_to_ulonglong: - public Item_handled_func::Handler_ulonglong -{ -public: - Longlong_null to_longlong_null(Item_handled_func *item) const override - { - DBUG_ASSERT(item->fixed()); - VDec a(item->arguments()[0]); - return a.is_null() ? Longlong_null() : - a.to_xlonglong_null() | VDec(item->arguments()[1]).to_xlonglong_null(); - } -}; - - -bool Item_func_bit_or::fix_length_and_dec(THD *thd) -{ - static Func_handler_bit_or_int_to_ulonglong ha_int_to_ull; - static Func_handler_bit_or_dec_to_ulonglong ha_dec_to_ull; - return fix_length_and_dec_op2_std(&ha_int_to_ull, &ha_dec_to_ull); -} - - -class Func_handler_bit_and_int_to_ulonglong: - public Item_handled_func::Handler_ulonglong -{ -public: - Longlong_null to_longlong_null(Item_handled_func *item) const override - { - DBUG_ASSERT(item->fixed()); - Longlong_null a= item->arguments()[0]->to_longlong_null(); - return a.is_null() ? a : a & item->arguments()[1]->to_longlong_null(); - } -}; - - -class Func_handler_bit_and_dec_to_ulonglong: - public Item_handled_func::Handler_ulonglong -{ -public: - Longlong_null to_longlong_null(Item_handled_func *item) const override - { - DBUG_ASSERT(item->fixed()); - VDec a(item->arguments()[0]); - return a.is_null() ? Longlong_null() : - a.to_xlonglong_null() & VDec(item->arguments()[1]).to_xlonglong_null(); - } -}; - - -bool Item_func_bit_and::fix_length_and_dec(THD *thd) -{ - static Func_handler_bit_and_int_to_ulonglong ha_int_to_ull; - static Func_handler_bit_and_dec_to_ulonglong ha_dec_to_ull; - return fix_length_and_dec_op2_std(&ha_int_to_ull, &ha_dec_to_ull); -} - Item_cond::Item_cond(THD *thd, Item_cond *item) :Item_bool_func(thd, item), and_tables_cache(item->and_tables_cache) diff --git a/sql/item_func.cc b/sql/item_func.cc index a3519b6d5ba20..8179a233ff5f3 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2223,10 +2223,115 @@ class Func_handler_shift_left_decimal_to_ulonglong: }; +class Func_handler_shift_left_bin_to_bin: public Item_handled_func::Handler_str +{ +public: + const Type_handler *return_type_handler(const Item_handled_func *item) const override + { + return &type_handler_long_blob; + } + + bool fix_length_and_dec(Item_handled_func *item) const override + { + item->max_length= item->arguments()[0]->max_length; + item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + return false; + } + + String *val_str(Item_handled_func *item, String *to) const override + { + DBUG_ASSERT(item->fixed()); + StringBuffer<128> a_buf; + String *a= item->arguments()[0]->val_str(&a_buf); + if (item->arguments()[0]->null_value || a == nullptr) + { + item->null_value= true; + return nullptr; + } + + Longlong_null shift_count_null= item->arguments()[1]->to_longlong_null(); + if (item->arguments()[1]->null_value || shift_count_null.is_null()) + { + item->null_value= true; + return nullptr; + } + + longlong shift_count_signed= shift_count_null.value(); + size_t len= a->length(); + + if (to->realloc(len)) + { + item->null_value= true; + return nullptr; + } + + uchar *out_ptr= (uchar *) to->ptr(); + const uchar *a_ptr= (const uchar *) a->ptr(); + + if (shift_count_signed < 0 || (ulonglong)shift_count_signed >= (ulonglong)len * 8) + { + memset(out_ptr, 0, len); + } + else + { + ulonglong shift_count= (ulonglong) shift_count_signed; + size_t byte_shift= shift_count / 8; + uint bit_shift= shift_count % 8; + + for (size_t i= 0; i < len; ++i) + { + size_t src_idx= i + byte_shift; + if (src_idx < len) + { + if (bit_shift > 0) + { + uchar val= (uchar)(a_ptr[src_idx] << bit_shift); + if (src_idx + 1 < len) + { + val|= (uchar)(a_ptr[src_idx + 1] >> (8 - bit_shift)); + } + out_ptr[i]= val; + } + else + { + out_ptr[i]= a_ptr[src_idx]; + } + } + else + { + out_ptr[i]= 0; + } + } + } + + to->length(len); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } +}; + + bool Item_func_shift_left::fix_length_and_dec(THD *thd) { + static Func_handler_shift_left_bin_to_bin ha_bin_to_bin; static Func_handler_shift_left_int_to_ulonglong ha_int_to_ull; static Func_handler_shift_left_decimal_to_ulonglong ha_dec_to_ull; + + bool binary_mode= false; + if (args[0]->result_type() == STRING_RESULT && + args[0]->collation.collation == &my_charset_bin && + dynamic_cast(args[0]->real_item()) == nullptr) + { + binary_mode= true; + } + + if (binary_mode) + { + set_func_handler(&ha_bin_to_bin); + return m_func_handler->fix_length_and_dec(this); + } + return fix_length_and_dec_op1_std(&ha_int_to_ull, &ha_dec_to_ull); } @@ -2257,10 +2362,115 @@ class Func_handler_shift_right_decimal_to_ulonglong: }; +class Func_handler_shift_right_bin_to_bin: public Item_handled_func::Handler_str +{ +public: + const Type_handler *return_type_handler(const Item_handled_func *item) const override + { + return &type_handler_long_blob; + } + + bool fix_length_and_dec(Item_handled_func *item) const override + { + item->max_length= item->arguments()[0]->max_length; + item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + return false; + } + + String *val_str(Item_handled_func *item, String *to) const override + { + DBUG_ASSERT(item->fixed()); + StringBuffer<128> a_buf; + String *a= item->arguments()[0]->val_str(&a_buf); + if (item->arguments()[0]->null_value || a == nullptr) + { + item->null_value= true; + return nullptr; + } + + Longlong_null shift_count_null= item->arguments()[1]->to_longlong_null(); + if (item->arguments()[1]->null_value || shift_count_null.is_null()) + { + item->null_value= true; + return nullptr; + } + + longlong shift_count_signed= shift_count_null.value(); + size_t len= a->length(); + + if (to->realloc(len)) + { + item->null_value= true; + return nullptr; + } + + uchar *out_ptr= (uchar *) to->ptr(); + const uchar *a_ptr= (const uchar *) a->ptr(); + + if (shift_count_signed < 0 || (ulonglong)shift_count_signed >= (ulonglong)len * 8) + { + memset(out_ptr, 0, len); + } + else + { + ulonglong shift_count= (ulonglong) shift_count_signed; + size_t byte_shift= shift_count / 8; + uint bit_shift= shift_count % 8; + + for (size_t i= 0; i < len; ++i) + { + if (i >= byte_shift) + { + size_t src_idx= i - byte_shift; + if (bit_shift > 0) + { + uchar val= (uchar)(a_ptr[src_idx] >> bit_shift); + if (src_idx > 0) + { + val|= (uchar)(a_ptr[src_idx - 1] << (8 - bit_shift)); + } + out_ptr[i]= val; + } + else + { + out_ptr[i]= a_ptr[src_idx]; + } + } + else + { + out_ptr[i]= 0; + } + } + } + + to->length(len); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } +}; + + bool Item_func_shift_right::fix_length_and_dec(THD *thd) { + static Func_handler_shift_right_bin_to_bin ha_bin_to_bin; static Func_handler_shift_right_int_to_ulonglong ha_int_to_ull; static Func_handler_shift_right_decimal_to_ulonglong ha_dec_to_ull; + + bool binary_mode= false; + if (args[0]->result_type() == STRING_RESULT && + args[0]->collation.collation == &my_charset_bin && + dynamic_cast(args[0]->real_item()) == nullptr) + { + binary_mode= true; + } + + if (binary_mode) + { + set_func_handler(&ha_bin_to_bin); + return m_func_handler->fix_length_and_dec(this); + } + return fix_length_and_dec_op1_std(&ha_int_to_ull, &ha_dec_to_ull); } @@ -2289,10 +2499,75 @@ class Func_handler_bit_neg_decimal_to_ulonglong: }; +class Func_handler_bit_neg_bin_to_bin: public Item_handled_func::Handler_str +{ +public: + const Type_handler *return_type_handler(const Item_handled_func *item) const override + { + return &type_handler_long_blob; + } + + bool fix_length_and_dec(Item_handled_func *item) const override + { + item->max_length= item->arguments()[0]->max_length; + item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + return false; + } + + String *val_str(Item_handled_func *item, String *to) const override + { + DBUG_ASSERT(item->fixed()); + StringBuffer<128> a_buf; + String *a= item->arguments()[0]->val_str(&a_buf); + if (item->arguments()[0]->null_value || a == nullptr) + { + item->null_value= true; + return nullptr; + } + + if (to->realloc(a->length())) + { + item->null_value= true; + return nullptr; + } + + const uchar *a_ptr= (const uchar *) a->ptr(); + uchar *out_ptr= (uchar *) to->ptr(); + size_t len= a->length(); + + for (size_t i= 0; i < len; ++i) + { + out_ptr[i]= ~a_ptr[i]; + } + + to->length(len); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } +}; + + bool Item_func_bit_neg::fix_length_and_dec(THD *thd) { + static Func_handler_bit_neg_bin_to_bin ha_bin_to_bin; static Func_handler_bit_neg_int_to_ulonglong ha_int_to_ull; static Func_handler_bit_neg_decimal_to_ulonglong ha_dec_to_ull; + + bool binary_mode= false; + if (args[0]->result_type() == STRING_RESULT && + args[0]->collation.collation == &my_charset_bin && + dynamic_cast(args[0]->real_item()) == nullptr) + { + binary_mode= true; + } + + if (binary_mode) + { + set_func_handler(&ha_bin_to_bin); + return m_func_handler->fix_length_and_dec(this); + } + return fix_length_and_dec_op1_std(&ha_int_to_ull, &ha_dec_to_ull); } @@ -6608,10 +6883,95 @@ class Func_handler_bit_xor_dec_to_ulonglong: }; +class Func_handler_bit_xor_bin_to_bin: public Item_handled_func::Handler_str +{ +public: + const Type_handler *return_type_handler(const Item_handled_func *item) const override + { + return &type_handler_long_blob; + } + + bool fix_length_and_dec(Item_handled_func *item) const override + { + item->max_length= item->arguments()[0]->max_length; + item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + return false; + } + + String *val_str(Item_handled_func *item, String *to) const override + { + DBUG_ASSERT(item->fixed()); + StringBuffer<128> a_buf; + String *a= item->arguments()[0]->val_str(&a_buf); + if (item->arguments()[0]->null_value || a == nullptr) + { + item->null_value= true; + return nullptr; + } + + StringBuffer<128> b_buf; + String *b= item->arguments()[1]->val_str(&b_buf); + if (item->arguments()[1]->null_value || b == nullptr) + { + item->null_value= true; + return nullptr; + } + + if (a->length() != b->length()) + { + my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), (int)a->length(), (int)b->length()); + item->null_value= true; + return nullptr; + } + + if (to->realloc(a->length())) + { + item->null_value= true; + return nullptr; + } + + const uchar *a_ptr= (const uchar *) a->ptr(); + const uchar *b_ptr= (const uchar *) b->ptr(); + uchar *out_ptr= (uchar *) to->ptr(); + size_t len= a->length(); + + for (size_t i= 0; i < len; ++i) + { + out_ptr[i]= a_ptr[i] ^ b_ptr[i]; + } + + to->length(len); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } +}; + + bool Item_func_bit_xor::fix_length_and_dec(THD *thd) { + static Func_handler_bit_xor_bin_to_bin ha_bin_to_bin; static const Func_handler_bit_xor_int_to_ulonglong ha_int_to_ull; static const Func_handler_bit_xor_dec_to_ulonglong ha_dec_to_ull; + + bool binary_mode= false; + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->result_type() == STRING_RESULT && + args[i]->collation.collation == &my_charset_bin && + dynamic_cast(args[i]->real_item()) == nullptr) + { + binary_mode= true; + break; + } + } + + if (binary_mode) + { + set_func_handler(&ha_bin_to_bin); + return m_func_handler->fix_length_and_dec(this); + } + return fix_length_and_dec_op2_std(&ha_int_to_ull, &ha_dec_to_ull); } @@ -7508,3 +7868,243 @@ void pause_execution(THD *thd, double timeout) do_pause(thd, &timed_cond, &cond, timeout); } + + +class Func_handler_bit_and_bin_to_bin: public Item_handled_func::Handler_str +{ +public: + const Type_handler *return_type_handler(const Item_handled_func *item) const override + { + return &type_handler_long_blob; + } + + bool fix_length_and_dec(Item_handled_func *item) const override + { + item->max_length= item->arguments()[0]->max_length; + item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + return false; + } + + String *val_str(Item_handled_func *item, String *to) const override + { + DBUG_ASSERT(item->fixed()); + StringBuffer<128> a_buf; + String *a= item->arguments()[0]->val_str(&a_buf); + if (item->arguments()[0]->null_value || a == nullptr) + { + item->null_value= true; + return nullptr; + } + + StringBuffer<128> b_buf; + String *b= item->arguments()[1]->val_str(&b_buf); + if (item->arguments()[1]->null_value || b == nullptr) + { + item->null_value= true; + return nullptr; + } + + if (a->length() != b->length()) + { + my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), (int)a->length(), (int)b->length()); + item->null_value= true; + return nullptr; + } + + if (to->realloc(a->length())) + { + item->null_value= true; + return nullptr; + } + + const uchar *a_ptr= (const uchar *) a->ptr(); + const uchar *b_ptr= (const uchar *) b->ptr(); + uchar *out_ptr= (uchar *) to->ptr(); + size_t len= a->length(); + + for (size_t i= 0; i < len; ++i) + { + out_ptr[i]= a_ptr[i] & b_ptr[i]; + } + + to->length(len); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } +}; + + +class Func_handler_bit_and_int_to_ulonglong: + public Item_handled_func::Handler_ulonglong +{ +public: + Longlong_null to_longlong_null(Item_handled_func *item) const override + { + DBUG_ASSERT(item->fixed()); + Longlong_null a= item->arguments()[0]->to_longlong_null(); + return a.is_null() ? a : a & item->arguments()[1]->to_longlong_null(); + } +}; + + +class Func_handler_bit_and_dec_to_ulonglong: + public Item_handled_func::Handler_ulonglong +{ +public: + Longlong_null to_longlong_null(Item_handled_func *item) const override + { + DBUG_ASSERT(item->fixed()); + VDec a(item->arguments()[0]); + return a.is_null() ? Longlong_null() : + a.to_xlonglong_null() & VDec(item->arguments()[1]).to_xlonglong_null(); + } +}; + + +bool Item_func_bit_and::fix_length_and_dec(THD *thd) +{ + static Func_handler_bit_and_bin_to_bin ha_bin_to_bin; + static Func_handler_bit_and_int_to_ulonglong ha_int_to_ull; + static Func_handler_bit_and_dec_to_ulonglong ha_dec_to_ull; + + bool binary_mode= false; + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->result_type() == STRING_RESULT && + args[i]->collation.collation == &my_charset_bin && + dynamic_cast(args[i]->real_item()) == nullptr) + { + binary_mode= true; + break; + } + } + + if (binary_mode) + { + set_func_handler(&ha_bin_to_bin); + return m_func_handler->fix_length_and_dec(this); + } + + return fix_length_and_dec_op2_std(&ha_int_to_ull, &ha_dec_to_ull); +} + + +class Func_handler_bit_or_bin_to_bin: public Item_handled_func::Handler_str +{ +public: + const Type_handler *return_type_handler(const Item_handled_func *item) const override + { + return &type_handler_long_blob; + } + + bool fix_length_and_dec(Item_handled_func *item) const override + { + item->max_length= item->arguments()[0]->max_length; + item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + return false; + } + + String *val_str(Item_handled_func *item, String *to) const override + { + DBUG_ASSERT(item->fixed()); + StringBuffer<128> a_buf; + String *a= item->arguments()[0]->val_str(&a_buf); + if (item->arguments()[0]->null_value || a == nullptr) + { + item->null_value= true; + return nullptr; + } + + StringBuffer<128> b_buf; + String *b= item->arguments()[1]->val_str(&b_buf); + if (item->arguments()[1]->null_value || b == nullptr) + { + item->null_value= true; + return nullptr; + } + + if (a->length() != b->length()) + { + my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), (int)a->length(), (int)b->length()); + item->null_value= true; + return nullptr; + } + + if (to->realloc(a->length())) + { + item->null_value= true; + return nullptr; + } + + const uchar *a_ptr= (const uchar *) a->ptr(); + const uchar *b_ptr= (const uchar *) b->ptr(); + uchar *out_ptr= (uchar *) to->ptr(); + size_t len= a->length(); + + for (size_t i= 0; i < len; ++i) + { + out_ptr[i]= a_ptr[i] | b_ptr[i]; + } + + to->length(len); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } +}; + + +class Func_handler_bit_or_int_to_ulonglong: + public Item_handled_func::Handler_ulonglong +{ +public: + Longlong_null to_longlong_null(Item_handled_func *item) const override + { + DBUG_ASSERT(item->fixed()); + Longlong_null a= item->arguments()[0]->to_longlong_null(); + return a.is_null() ? a : a | item->arguments()[1]->to_longlong_null(); + } +}; + + +class Func_handler_bit_or_dec_to_ulonglong: + public Item_handled_func::Handler_ulonglong +{ +public: + Longlong_null to_longlong_null(Item_handled_func *item) const override + { + DBUG_ASSERT(item->fixed()); + VDec a(item->arguments()[0]); + return a.is_null() ? Longlong_null() : + a.to_xlonglong_null() | VDec(item->arguments()[1]).to_xlonglong_null(); + } +}; + + +bool Item_func_bit_or::fix_length_and_dec(THD *thd) +{ + static Func_handler_bit_or_bin_to_bin ha_bin_to_bin; + static Func_handler_bit_or_int_to_ulonglong ha_int_to_ull; + static Func_handler_bit_or_dec_to_ulonglong ha_dec_to_ull; + + bool binary_mode= false; + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->result_type() == STRING_RESULT && + args[i]->collation.collation == &my_charset_bin && + dynamic_cast(args[i]->real_item()) == nullptr) + { + binary_mode= true; + break; + } + } + + if (binary_mode) + { + set_func_handler(&ha_bin_to_bin); + return m_func_handler->fix_length_and_dec(this); + } + + return fix_length_and_dec_op2_std(&ha_int_to_ull, &ha_dec_to_ull); +} diff --git a/sql/item_func.h b/sql/item_func.h index c482cdca44c44..acfda7ce1bd11 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -2915,12 +2915,26 @@ class Item_func_find_in_set :public Item_long_func { return get_item_copy(thd, this); } }; +class Func_handler_bit_and_bin_to_bin; +class Func_handler_bit_or_bin_to_bin; +class Func_handler_bit_xor_bin_to_bin; +class Func_handler_bit_neg_bin_to_bin; +class Func_handler_shift_left_bin_to_bin; +class Func_handler_shift_right_bin_to_bin; + /* Base class for all bit functions: '~', '|', '^', '&', '>>', '<<' */ class Item_func_bit_operator: public Item_handled_func { bool check_arguments() const override - { return check_argument_types_can_return_int(0, arg_count); } + { + for (uint i= 0; i < arg_count; i++) + { + if (args[i]->check_type_traditional_scalar(func_name_cstring())) + return true; + } + return false; + } protected: bool fix_length_and_dec_op1_std(const Handler *ha_int, const Handler *ha_dec) { diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index ef4fd75fc205b..60637e7370983 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12406,3 +12406,7 @@ ER_WARN_QB_NAME_PATH_VIEW_NOT_FOUND eng "Hint %s is ignored. `%s` required at element #%u of the path is not found in the target query block." ER_WARN_QB_NAME_PATH_NOT_SUPPORTED_INSIDE_VIEW eng "Hint %s is ignored. QB_NAME hints with path are not supported inside view definitions." +ER_INVALID_BITWISE_OPERANDS_SIZE + eng "Mismatched length of operands for bitwise operator: %d and %d" +ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE + eng "Operand size %d exceeds maximum limit of 512 bytes for bitwise aggregate function" From f1309a28ff283cb73155f5ffd6d91f44e15ea62a Mon Sep 17 00:00:00 2001 From: kjarir Date: Thu, 11 Jun 2026 00:42:44 +0530 Subject: [PATCH 02/12] MDEV-10526: Address code review feedback on bitwise binary handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change DERIVATION_IMPLICIT to DERIVATION_COERCIBLE in all binary handler fix_length_and_dec() methods - Update return_type_handler() to vary by max_length using blob_type_handler/type_handler_varchar/type_handler_string pattern, matching Item_char_typecast_func_handler_fbt_to_binary - Fix max_length calculation for two-operand operators (&, |, ^) to use MY_MAX of both operand lengths - Remove redundant null_value checks in val_str() — nullptr check on val_str() return value is sufficient - Add early exit for empty string (len == 0) in all handlers - Fix potential size_t overflow in shift left bounds check: use (byte_shift < len - i) instead of (i + byte_shift < len) - Remove duplicate size_t len variable in xor/and/or handlers --- sql/item_func.cc | 145 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 36 deletions(-) diff --git a/sql/item_func.cc b/sql/item_func.cc index 8179a233ff5f3..54a733203a151 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2228,13 +2228,17 @@ class Func_handler_shift_left_bin_to_bin: public Item_handled_func::Handler_str public: const Type_handler *return_type_handler(const Item_handled_func *item) const override { - return &type_handler_long_blob; + if (item->max_length > MAX_FIELD_VARCHARLENGTH) + return Type_handler::blob_type_handler(item->max_length); + if (item->max_length > 255) + return &type_handler_varchar; + return &type_handler_string; } bool fix_length_and_dec(Item_handled_func *item) const override { item->max_length= item->arguments()[0]->max_length; - item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + item->collation.set(&my_charset_bin, DERIVATION_COERCIBLE); return false; } @@ -2243,14 +2247,14 @@ class Func_handler_shift_left_bin_to_bin: public Item_handled_func::Handler_str DBUG_ASSERT(item->fixed()); StringBuffer<128> a_buf; String *a= item->arguments()[0]->val_str(&a_buf); - if (item->arguments()[0]->null_value || a == nullptr) + if (a == nullptr) { item->null_value= true; return nullptr; } Longlong_null shift_count_null= item->arguments()[1]->to_longlong_null(); - if (item->arguments()[1]->null_value || shift_count_null.is_null()) + if (shift_count_null.is_null()) { item->null_value= true; return nullptr; @@ -2258,6 +2262,13 @@ class Func_handler_shift_left_bin_to_bin: public Item_handled_func::Handler_str longlong shift_count_signed= shift_count_null.value(); size_t len= a->length(); + if (len == 0) + { + to->length(0); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } if (to->realloc(len)) { @@ -2280,9 +2291,9 @@ class Func_handler_shift_left_bin_to_bin: public Item_handled_func::Handler_str for (size_t i= 0; i < len; ++i) { - size_t src_idx= i + byte_shift; - if (src_idx < len) + if (byte_shift < len - i) { + size_t src_idx= i + byte_shift; if (bit_shift > 0) { uchar val= (uchar)(a_ptr[src_idx] << bit_shift); @@ -2367,13 +2378,17 @@ class Func_handler_shift_right_bin_to_bin: public Item_handled_func::Handler_str public: const Type_handler *return_type_handler(const Item_handled_func *item) const override { - return &type_handler_long_blob; + if (item->max_length > MAX_FIELD_VARCHARLENGTH) + return Type_handler::blob_type_handler(item->max_length); + if (item->max_length > 255) + return &type_handler_varchar; + return &type_handler_string; } bool fix_length_and_dec(Item_handled_func *item) const override { item->max_length= item->arguments()[0]->max_length; - item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + item->collation.set(&my_charset_bin, DERIVATION_COERCIBLE); return false; } @@ -2382,14 +2397,14 @@ class Func_handler_shift_right_bin_to_bin: public Item_handled_func::Handler_str DBUG_ASSERT(item->fixed()); StringBuffer<128> a_buf; String *a= item->arguments()[0]->val_str(&a_buf); - if (item->arguments()[0]->null_value || a == nullptr) + if (a == nullptr) { item->null_value= true; return nullptr; } Longlong_null shift_count_null= item->arguments()[1]->to_longlong_null(); - if (item->arguments()[1]->null_value || shift_count_null.is_null()) + if (shift_count_null.is_null()) { item->null_value= true; return nullptr; @@ -2397,6 +2412,13 @@ class Func_handler_shift_right_bin_to_bin: public Item_handled_func::Handler_str longlong shift_count_signed= shift_count_null.value(); size_t len= a->length(); + if (len == 0) + { + to->length(0); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } if (to->realloc(len)) { @@ -2504,13 +2526,17 @@ class Func_handler_bit_neg_bin_to_bin: public Item_handled_func::Handler_str public: const Type_handler *return_type_handler(const Item_handled_func *item) const override { - return &type_handler_long_blob; + if (item->max_length > MAX_FIELD_VARCHARLENGTH) + return Type_handler::blob_type_handler(item->max_length); + if (item->max_length > 255) + return &type_handler_varchar; + return &type_handler_string; } bool fix_length_and_dec(Item_handled_func *item) const override { item->max_length= item->arguments()[0]->max_length; - item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + item->collation.set(&my_charset_bin, DERIVATION_COERCIBLE); return false; } @@ -2519,13 +2545,22 @@ class Func_handler_bit_neg_bin_to_bin: public Item_handled_func::Handler_str DBUG_ASSERT(item->fixed()); StringBuffer<128> a_buf; String *a= item->arguments()[0]->val_str(&a_buf); - if (item->arguments()[0]->null_value || a == nullptr) + if (a == nullptr) { item->null_value= true; return nullptr; } - if (to->realloc(a->length())) + size_t len= a->length(); + if (len == 0) + { + to->length(0); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } + + if (to->realloc(len)) { item->null_value= true; return nullptr; @@ -2533,7 +2568,6 @@ class Func_handler_bit_neg_bin_to_bin: public Item_handled_func::Handler_str const uchar *a_ptr= (const uchar *) a->ptr(); uchar *out_ptr= (uchar *) to->ptr(); - size_t len= a->length(); for (size_t i= 0; i < len; ++i) { @@ -6888,13 +6922,18 @@ class Func_handler_bit_xor_bin_to_bin: public Item_handled_func::Handler_str public: const Type_handler *return_type_handler(const Item_handled_func *item) const override { - return &type_handler_long_blob; + if (item->max_length > MAX_FIELD_VARCHARLENGTH) + return Type_handler::blob_type_handler(item->max_length); + if (item->max_length > 255) + return &type_handler_varchar; + return &type_handler_string; } bool fix_length_and_dec(Item_handled_func *item) const override { - item->max_length= item->arguments()[0]->max_length; - item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + item->max_length= MY_MAX(item->arguments()[0]->max_length, + item->arguments()[1]->max_length); + item->collation.set(&my_charset_bin, DERIVATION_COERCIBLE); return false; } @@ -6903,7 +6942,7 @@ class Func_handler_bit_xor_bin_to_bin: public Item_handled_func::Handler_str DBUG_ASSERT(item->fixed()); StringBuffer<128> a_buf; String *a= item->arguments()[0]->val_str(&a_buf); - if (item->arguments()[0]->null_value || a == nullptr) + if (a == nullptr) { item->null_value= true; return nullptr; @@ -6911,7 +6950,7 @@ class Func_handler_bit_xor_bin_to_bin: public Item_handled_func::Handler_str StringBuffer<128> b_buf; String *b= item->arguments()[1]->val_str(&b_buf); - if (item->arguments()[1]->null_value || b == nullptr) + if (b == nullptr) { item->null_value= true; return nullptr; @@ -6924,7 +6963,16 @@ class Func_handler_bit_xor_bin_to_bin: public Item_handled_func::Handler_str return nullptr; } - if (to->realloc(a->length())) + size_t len= a->length(); + if (len == 0) + { + to->length(0); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } + + if (to->realloc(len)) { item->null_value= true; return nullptr; @@ -6933,7 +6981,6 @@ class Func_handler_bit_xor_bin_to_bin: public Item_handled_func::Handler_str const uchar *a_ptr= (const uchar *) a->ptr(); const uchar *b_ptr= (const uchar *) b->ptr(); uchar *out_ptr= (uchar *) to->ptr(); - size_t len= a->length(); for (size_t i= 0; i < len; ++i) { @@ -7875,13 +7922,18 @@ class Func_handler_bit_and_bin_to_bin: public Item_handled_func::Handler_str public: const Type_handler *return_type_handler(const Item_handled_func *item) const override { - return &type_handler_long_blob; + if (item->max_length > MAX_FIELD_VARCHARLENGTH) + return Type_handler::blob_type_handler(item->max_length); + if (item->max_length > 255) + return &type_handler_varchar; + return &type_handler_string; } bool fix_length_and_dec(Item_handled_func *item) const override { - item->max_length= item->arguments()[0]->max_length; - item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + item->max_length= MY_MAX(item->arguments()[0]->max_length, + item->arguments()[1]->max_length); + item->collation.set(&my_charset_bin, DERIVATION_COERCIBLE); return false; } @@ -7890,7 +7942,7 @@ class Func_handler_bit_and_bin_to_bin: public Item_handled_func::Handler_str DBUG_ASSERT(item->fixed()); StringBuffer<128> a_buf; String *a= item->arguments()[0]->val_str(&a_buf); - if (item->arguments()[0]->null_value || a == nullptr) + if (a == nullptr) { item->null_value= true; return nullptr; @@ -7898,7 +7950,7 @@ class Func_handler_bit_and_bin_to_bin: public Item_handled_func::Handler_str StringBuffer<128> b_buf; String *b= item->arguments()[1]->val_str(&b_buf); - if (item->arguments()[1]->null_value || b == nullptr) + if (b == nullptr) { item->null_value= true; return nullptr; @@ -7911,7 +7963,16 @@ class Func_handler_bit_and_bin_to_bin: public Item_handled_func::Handler_str return nullptr; } - if (to->realloc(a->length())) + size_t len= a->length(); + if (len == 0) + { + to->length(0); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } + + if (to->realloc(len)) { item->null_value= true; return nullptr; @@ -7920,7 +7981,6 @@ class Func_handler_bit_and_bin_to_bin: public Item_handled_func::Handler_str const uchar *a_ptr= (const uchar *) a->ptr(); const uchar *b_ptr= (const uchar *) b->ptr(); uchar *out_ptr= (uchar *) to->ptr(); - size_t len= a->length(); for (size_t i= 0; i < len; ++i) { @@ -7995,13 +8055,18 @@ class Func_handler_bit_or_bin_to_bin: public Item_handled_func::Handler_str public: const Type_handler *return_type_handler(const Item_handled_func *item) const override { - return &type_handler_long_blob; + if (item->max_length > MAX_FIELD_VARCHARLENGTH) + return Type_handler::blob_type_handler(item->max_length); + if (item->max_length > 255) + return &type_handler_varchar; + return &type_handler_string; } bool fix_length_and_dec(Item_handled_func *item) const override { - item->max_length= item->arguments()[0]->max_length; - item->collation.set(&my_charset_bin, DERIVATION_IMPLICIT); + item->max_length= MY_MAX(item->arguments()[0]->max_length, + item->arguments()[1]->max_length); + item->collation.set(&my_charset_bin, DERIVATION_COERCIBLE); return false; } @@ -8010,7 +8075,7 @@ class Func_handler_bit_or_bin_to_bin: public Item_handled_func::Handler_str DBUG_ASSERT(item->fixed()); StringBuffer<128> a_buf; String *a= item->arguments()[0]->val_str(&a_buf); - if (item->arguments()[0]->null_value || a == nullptr) + if (a == nullptr) { item->null_value= true; return nullptr; @@ -8018,7 +8083,7 @@ class Func_handler_bit_or_bin_to_bin: public Item_handled_func::Handler_str StringBuffer<128> b_buf; String *b= item->arguments()[1]->val_str(&b_buf); - if (item->arguments()[1]->null_value || b == nullptr) + if (b == nullptr) { item->null_value= true; return nullptr; @@ -8031,7 +8096,16 @@ class Func_handler_bit_or_bin_to_bin: public Item_handled_func::Handler_str return nullptr; } - if (to->realloc(a->length())) + size_t len= a->length(); + if (len == 0) + { + to->length(0); + to->set_charset(&my_charset_bin); + item->null_value= false; + return to; + } + + if (to->realloc(len)) { item->null_value= true; return nullptr; @@ -8040,7 +8114,6 @@ class Func_handler_bit_or_bin_to_bin: public Item_handled_func::Handler_str const uchar *a_ptr= (const uchar *) a->ptr(); const uchar *b_ptr= (const uchar *) b->ptr(); uchar *out_ptr= (uchar *) to->ptr(); - size_t len= a->length(); for (size_t i= 0; i < len; ++i) { From 01d54c01550520f51441fc51729b37c89e3a644d Mon Sep 17 00:00:00 2001 From: kjarir Date: Thu, 11 Jun 2026 11:00:31 +0530 Subject: [PATCH 03/12] MDEV-10526: Implement binary mode for bitwise aggregate functions --- sql/item_sum.cc | 126 ++++++++++++++++++++++++++++++++++++++++++++++++ sql/item_sum.h | 32 +++++++----- 2 files changed, 147 insertions(+), 11 deletions(-) diff --git a/sql/item_sum.cc b/sql/item_sum.cc index efc5b9450c3f6..50ce2b73791b6 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2612,6 +2612,38 @@ bool Item_sum_max::add() /* bit_or and bit_and */ +bool Item_sum_bit::fix_length_and_dec(THD *thd) +{ + // Check for binary string argument + m_binary_mode= false; + if (args[0]->result_type() == STRING_RESULT && + args[0]->collation.collation == &my_charset_bin && + dynamic_cast(args[0]->real_item()) == nullptr) + { + // Size guard + if (args[0]->max_length > 512) + { + my_error(ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE, MYF(0), + args[0]->max_length); + return true; + } + m_binary_mode= true; + m_binary_length= args[0]->max_length; + collation.set(&my_charset_bin, DERIVATION_COERCIBLE); + max_length= m_binary_length; + base_flags&= ~item_base_t::MAYBE_NULL; + null_value= 0; + return false; + } + // Original integer path + if (args[0]->check_type_can_return_int(func_name_cstring())) + return true; + decimals= 0; max_length= 21; unsigned_flag= 1; + base_flags&= ~item_base_t::MAYBE_NULL; + null_value= 0; + return false; +} + longlong Item_sum_bit::val_int() { DBUG_ASSERT(fixed()); @@ -2621,11 +2653,30 @@ longlong Item_sum_bit::val_int() void Item_sum_bit::clear() { + if (m_binary_mode) + { + reset_binary_accumulator(); + return; + } bits= reset_bits; if (as_window_function) clear_as_window(); } +String *Item_sum_bit::val_str(String *str) +{ + if (m_binary_mode) + { + null_value= false; + return &m_str_value; + } + // fall back to integer conversion + longlong nr= val_int(); + if (null_value) return nullptr; + str->set((ulonglong) nr, &my_charset_bin); + return str; +} + Item *Item_sum_or::copy_or_same(THD* thd) { return new (thd->mem_root) Item_sum_or(thd, this); @@ -2685,8 +2736,33 @@ void Item_sum_or::set_bits_from_counters() bits= value | reset_bits; } +void Item_sum_or::reset_binary_accumulator() +{ + m_str_value.alloc(m_binary_length); + m_str_value.length(m_binary_length); + m_str_value.set_charset(&my_charset_bin); + memset((char*) m_str_value.ptr(), 0x00, m_binary_length); +} + bool Item_sum_or::add() { + if (m_binary_mode) + { + StringBuffer<512> arg_buf; + String *arg= args[0]->val_str(&arg_buf); + if (!arg) return 0; // NULL - skip + if (arg->length() != m_binary_length) + { + my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), + (int)arg->length(), (int)m_binary_length); + return true; + } + uchar *acc= (uchar*) m_str_value.ptr(); + const uchar *val= (const uchar*) arg->ptr(); + for (uint i= 0; i < m_binary_length; i++) + acc[i]|= val[i]; + return 0; + } ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) { @@ -2713,8 +2789,33 @@ Item *Item_sum_xor::copy_or_same(THD* thd) } +void Item_sum_xor::reset_binary_accumulator() +{ + m_str_value.alloc(m_binary_length); + m_str_value.length(m_binary_length); + m_str_value.set_charset(&my_charset_bin); + memset((char*) m_str_value.ptr(), 0x00, m_binary_length); +} + bool Item_sum_xor::add() { + if (m_binary_mode) + { + StringBuffer<512> arg_buf; + String *arg= args[0]->val_str(&arg_buf); + if (!arg) return 0; // NULL - skip + if (arg->length() != m_binary_length) + { + my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), + (int)arg->length(), (int)m_binary_length); + return true; + } + uchar *acc= (uchar*) m_str_value.ptr(); + const uchar *val= (const uchar*) arg->ptr(); + for (uint i= 0; i < m_binary_length; i++) + acc[i]^= val[i]; + return 0; + } ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) { @@ -2748,8 +2849,33 @@ Item *Item_sum_and::copy_or_same(THD* thd) } +void Item_sum_and::reset_binary_accumulator() +{ + m_str_value.alloc(m_binary_length); + m_str_value.length(m_binary_length); + m_str_value.set_charset(&my_charset_bin); + memset((char*) m_str_value.ptr(), 0xFF, m_binary_length); +} + bool Item_sum_and::add() { + if (m_binary_mode) + { + StringBuffer<512> arg_buf; + String *arg= args[0]->val_str(&arg_buf); + if (!arg) return 0; // NULL - skip + if (arg->length() != m_binary_length) + { + my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), + (int)arg->length(), (int)m_binary_length); + return true; + } + uchar *acc= (uchar*) m_str_value.ptr(); + const uchar *val= (const uchar*) arg->ptr(); + for (uint i= 0; i < m_binary_length; i++) + acc[i]&= val[i]; + return 0; + } ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) { diff --git a/sql/item_sum.h b/sql/item_sum.h index 39ed79e7c0203..6ae68bc150cd3 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -1260,14 +1260,18 @@ class Item_sum_bit :public Item_sum_int public: Item_sum_bit(THD *thd, Item *item_par, ulonglong reset_arg): Item_sum_int(thd, item_par), reset_bits(reset_arg), bits(reset_arg), - as_window_function(FALSE), num_values_added(0) {} + as_window_function(FALSE), num_values_added(0), m_binary_mode(FALSE), + m_binary_length(0) {} Item_sum_bit(THD *thd, Item_sum_bit *item): Item_sum_int(thd, item), reset_bits(item->reset_bits), bits(item->bits), as_window_function(item->as_window_function), - num_values_added(item->num_values_added) + num_values_added(item->num_values_added), + m_binary_mode(item->m_binary_mode), + m_binary_length(item->m_binary_length) { if (as_window_function) memcpy(bit_counters, item->bit_counters, sizeof(bit_counters)); + m_str_value.copy(item->m_str_value); } enum Sumfunctype sum_func () const override { return SUM_BIT_FUNC;} void clear() override; @@ -1275,19 +1279,16 @@ class Item_sum_bit :public Item_sum_int void reset_field() override; void update_field() override; const Type_handler *type_handler() const override - { return &type_handler_ulonglong; } - bool fix_length_and_dec(THD *thd) override { - if (args[0]->check_type_can_return_int(func_name_cstring())) - return true; - decimals= 0; max_length=21; unsigned_flag= 1; - base_flags&= ~item_base_t::MAYBE_NULL; - null_value= 0; - return FALSE; + if (m_binary_mode) + return Type_handler::blob_type_handler(max_length); + return &type_handler_ulonglong; } // block standard processor for never null bool add_maybe_null_after_ora_join_processor(void *arg) override { return 0; } + bool fix_length_and_dec(THD *thd) override; + String *val_str(String *str) override; void cleanup() override { bits= reset_bits; @@ -1295,8 +1296,10 @@ class Item_sum_bit :public Item_sum_int clear_as_window(); Item_sum_int::cleanup(); } - void setup_window_func(THD *, Window_spec *) override + void setup_window_func(THD *thd, Window_spec *window_spec) override { + if (m_binary_mode) + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "window functions in binary mode"); as_window_function= TRUE; clear_as_window(); } @@ -1331,6 +1334,10 @@ class Item_sum_bit :public Item_sum_int bool remove_as_window(ulonglong value); bool clear_as_window(); virtual void set_bits_from_counters()= 0; + bool m_binary_mode; + String m_str_value; // binary accumulator + uint m_binary_length; // byte length of binary operand + virtual void reset_binary_accumulator() {} }; @@ -1349,6 +1356,7 @@ class Item_sum_or final :public Item_sum_bit private: void set_bits_from_counters() override; + void reset_binary_accumulator() override; protected: Item *shallow_copy(THD *thd) const override @@ -1372,6 +1380,7 @@ class Item_sum_and final :public Item_sum_bit private: void set_bits_from_counters() override; + void reset_binary_accumulator() override; protected: Item *shallow_copy(THD *thd) const override @@ -1393,6 +1402,7 @@ class Item_sum_xor final :public Item_sum_bit private: void set_bits_from_counters() override; + void reset_binary_accumulator() override; protected: Item *shallow_copy(THD *thd) const override From decba38d09484f287b75bd5364db58deb9037f7a Mon Sep 17 00:00:00 2001 From: kjarir Date: Thu, 11 Jun 2026 12:45:44 +0530 Subject: [PATCH 04/12] MDEV-10526: Implement reset_field/update_field for binary mode aggregates Item_sum_bit::reset_field() and update_field() previously assumed integer mode, using int8store/uint8korr to read/write 8 raw bytes to result_field. In binary mode result_field is a string-typed field, so this corrupted temp table memory and crashed the server (SIGSEGV in ha_maria::write_block_record) during GROUP BY queries. Fix uses Field::store()/val_str() for binary mode, matching the pattern used by Item_sum_min_max for string aggregates. Add formal test file mysql-test/main/func_bitops_binary.test covering: - All 6 scalar operators on VARBINARY - INET6_ATON subnet masking (primary use case) - NULL handling - Mismatched length errors - Current hex/binary literal behavior (pending mentor decision on x'FF' semantics) - INET6/UUID CAST restrictions preserved (per Alexander's request) - BIT_AND/BIT_OR/BIT_XOR aggregates including GROUP BY - Empty result set returns neutral elements (not NULL) - 512-byte aggregate size guard - Integer mode backward compatibility - CREATE TABLE AS SELECT type preservation --- mysql-test/main/func_bitops_binary.result | 172 ++++++++++++++++++++ mysql-test/main/func_bitops_binary.test | 188 ++++++++++++++++++++++ sql/item_sum.cc | 39 +++++ 3 files changed, 399 insertions(+) create mode 100644 mysql-test/main/func_bitops_binary.result create mode 100644 mysql-test/main/func_bitops_binary.test diff --git a/mysql-test/main/func_bitops_binary.result b/mysql-test/main/func_bitops_binary.result new file mode 100644 index 0000000000000..0f8a8e910b02f --- /dev/null +++ b/mysql-test/main/func_bitops_binary.result @@ -0,0 +1,172 @@ +# +# MDEV-10526: Binary string support for bitwise operators & aggregate functions +# +# ========================================================================= +# SECTION 1 — Scalar operators on VARBINARY +# ========================================================================= +# Rationale: Tests basic functional correctness of scalar bitwise operators +# (&, |, ^, ~, <<, >>) when applied to VARBINARY(4) operands. +# ========================================================================= +CREATE TABLE t1 (a VARBINARY(4), b VARBINARY(4)); +INSERT INTO t1 VALUES (x'FFFF0000', x'FF00FF00'); +# Expected output: FF000000, FFFFFF00, 00FFFF00, 0000FFFF, FFFE0000, 7FFF8000 +SELECT HEX(a & b), HEX(a | b), HEX(a ^ b), HEX(~a), HEX(a << 1), HEX(a >> 1) FROM t1; +HEX(a & b) HEX(a | b) HEX(a ^ b) HEX(~a) HEX(a << 1) HEX(a >> 1) +FF000000 FFFFFF00 00FFFF00 0000FFFF FFFE0000 7FFF8000 +DROP TABLE t1; +# ========================================================================= +# SECTION 2 — INET6_ATON real-world use case +# ========================================================================= +# Rationale: Verifies the primary target use case (subnet masking and IP math) +# still operates correctly with INET6_ATON (which returns VARBINARY(16)). +# ========================================================================= +# Expected output: 20010DB8000000000000000000000000 +SELECT HEX(INET6_ATON('ffff:ffff::') & INET6_ATON('2001:db8::1')); +HEX(INET6_ATON('ffff:ffff::') & INET6_ATON('2001:db8::1')) +20010DB8000000000000000000000000 +# Expected output: TBD - verify via --record +SELECT HEX(INET6_ATON('ffff:ffff::') | INET6_ATON('2001:db8::1')); +HEX(INET6_ATON('ffff:ffff::') | INET6_ATON('2001:db8::1')) +FFFFFFFF000000000000000000000001 +# Expected output: TBD - verify via --record +SELECT HEX(INET6_ATON('ffff:ffff::') ^ INET6_ATON('2001:db8::1')); +HEX(INET6_ATON('ffff:ffff::') ^ INET6_ATON('2001:db8::1')) +DFFEF247000000000000000000000001 +# ========================================================================= +# SECTION 3 — NULL handling +# ========================================================================= +# Rationale: Ensures that if any of the operands are NULL, the result of the +# bitwise operation is also NULL. +# ========================================================================= +CREATE TABLE t_null (a VARBINARY(4), b VARBINARY(4)); +INSERT INTO t_null VALUES (x'FFFF0000', NULL); +# Expected output: NULL, NULL, NULL, NULL, NULL, NULL +SELECT HEX(a & b), HEX(a | b), HEX(a ^ b), HEX(~b), HEX(a << b), HEX(a >> b) FROM t_null; +HEX(a & b) HEX(a | b) HEX(a ^ b) HEX(~b) HEX(a << b) HEX(a >> b) +NULL NULL NULL NULL NULL NULL +DROP TABLE t_null; +# ========================================================================= +# SECTION 4 — Mismatched length error +# ========================================================================= +# Rationale: Verifies that operating on binary string operands with mismatched +# lengths throws ER_INVALID_BITWISE_OPERANDS_SIZE (error 4265). +# ========================================================================= +CREATE TABLE t_mismatch (a VARBINARY(4), b VARBINARY(2)); +INSERT INTO t_mismatch VALUES (x'FFFF0000', x'FFFF'); +SELECT a & b FROM t_mismatch; +ERROR HY000: Mismatched length of operands for bitwise operator: 4 and 2 +SELECT a | b FROM t_mismatch; +ERROR HY000: Mismatched length of operands for bitwise operator: 4 and 2 +SELECT a ^ b FROM t_mismatch; +ERROR HY000: Mismatched length of operands for bitwise operator: 4 and 2 +DROP TABLE t_mismatch; +# ========================================================================= +# SECTION 5 — Literal behavior (CURRENT state, not final) +# ========================================================================= +# Rationale: Records the current behavior of constant/hex-constant literals and +# string/binary cast expressions. +# NOTE: x'FF' behavior is under discussion with mentors (MDEV-10526), this records CURRENT behavior. +# ========================================================================= +# Expected output: 0 (current behavior — hex literal design decision pending with mentors, see MDEV-10526 discussion) +SELECT x'FF' & 12; +x'FF' & 12 +0 +Warnings: +Warning 1292 Truncated incorrect DECIMAL value: '\xFF' +# Expected output: 12 +SELECT 0xFF & 12; +0xFF & 12 +12 +# Expected output: 60 +SELECT HEX(_binary'a' & _binary'b'); +HEX(_binary'a' & _binary'b') +60 +# Expected output: 0 +SELECT 'a' & 'b'; +'a' & 'b' +0 +Warnings: +Warning 1292 Truncated incorrect DECIMAL value: 'a' +Warning 1292 Truncated incorrect DECIMAL value: 'b' +# ========================================================================= +# SECTION 6 — Existing INET6/UUID error preservation +# ========================================================================= +# Rationale: Confirms that existing parameter data type restrictions for +# INET6 and UUID types are preserved and throw ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION (4079). +# ========================================================================= +SELECT CAST('::' AS INET6) & CAST('::' AS INET6); +ERROR HY000: Illegal parameter data type inet6 for operation '&' +SELECT CAST(UUID() AS UUID) & CAST(UUID() AS UUID); +ERROR HY000: Illegal parameter data type uuid for operation '&' +# ========================================================================= +# SECTION 7 — Aggregate functions +# ========================================================================= +# Rationale: Verifies BIT_AND, BIT_OR, and BIT_XOR aggregate function +# calculations on VARBINARY columns under normal grouping conditions. +# ========================================================================= +CREATE TABLE t_agg (a VARBINARY(4)); +INSERT INTO t_agg VALUES (x'FF000000'), (x'0F0F0F0F'), (x'00FF0000'); +# Expected output: FFFF0F0F, 00000000, F0F00F0F +SELECT HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_agg; +HEX(BIT_OR(a)) HEX(BIT_AND(a)) HEX(BIT_XOR(a)) +FFFF0F0F 00000000 F0F00F0F +DROP TABLE t_agg; +# GROUP BY test with multiple groups +CREATE TABLE t_grp (grp INT, a VARBINARY(4)); +INSERT INTO t_grp VALUES (1, x'FF000000'), (1, x'0F0F0F0F'), (2, x'00FF0000'), (2, x'0F0F0F0F'); +# Expected output: +# 1, FF0F0F0F, 0F000000, F00F0F0F +# 2, 0FFF0F0F, 000F0000, 0FF00F0F +SELECT grp, HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_grp GROUP BY grp; +grp HEX(BIT_OR(a)) HEX(BIT_AND(a)) HEX(BIT_XOR(a)) +1 FF0F0F0F 0F000000 F00F0F0F +2 0FFF0F0F 000F0000 0FF00F0F +DROP TABLE t_grp; +# ========================================================================= +# SECTION 8 — Aggregate edge cases +# ========================================================================= +# Rationale: Tests aggregate functions with NULL rows, empty tables, +# and verify that operand sizes > 512 bytes are correctly blocked by the +# size guard with ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE (4266). +# ========================================================================= +CREATE TABLE t_edge (a VARBINARY(4)); +INSERT INTO t_edge VALUES (x'FF000000'), (NULL), (x'00FF0000'); +# Expected output: FFFF0000, 00000000, FFFF0000 +SELECT HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_edge; +HEX(BIT_OR(a)) HEX(BIT_AND(a)) HEX(BIT_XOR(a)) +FFFF0000 00000000 FFFF0000 +# Empty result set (no rows) +# Expected output: TBD - verify via --record (likely neutral element: 00000000, FFFFFFFF, 00000000 based on binary_length) +SELECT HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_edge WHERE 1=0; +HEX(BIT_OR(a)) HEX(BIT_AND(a)) HEX(BIT_XOR(a)) +00000000 FFFFFFFF 00000000 +DROP TABLE t_edge; +# Size guard validation (operand size > 512 bytes) +SELECT BIT_OR(CAST('a' AS BINARY(513))); +ERROR HY000: Operand size 513 exceeds maximum limit of 512 bytes for bitwise aggregate function +# ========================================================================= +# SECTION 9 — Backward compatibility — integer mode unchanged +# ========================================================================= +# Rationale: Verifies that operations on integer literals behave exactly +# as they did in pre-patch versions of MariaDB. +# ========================================================================= +# Expected output: 1, 7, 6, 18446744073709551615, 4, 4 +SELECT 5 & 3, 5 | 3, 5 ^ 3, ~0, 1 << 2, 8 >> 1; +5 & 3 5 | 3 5 ^ 3 ~0 1 << 2 8 >> 1 +1 7 6 18446744073709551615 4 4 +# ========================================================================= +# SECTION 10 — CREATE TABLE AS SELECT type preservation +# ========================================================================= +# Rationale: Verifies that CTAS (Create Table As Select) correctly preserves +# the resulting datatype as a VARBINARY/binary type. +# ========================================================================= +CREATE TABLE t_src (a VARBINARY(4), b VARBINARY(4)); +CREATE TABLE t_dest AS SELECT a & b AS res FROM t_src; +# Expected output: SHOW CREATE TABLE shows `res` as VARBINARY(4) or blob-type +SHOW CREATE TABLE t_dest; +Table Create Table +t_dest CREATE TABLE `t_dest` ( + `res` binary(4) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_dest; +DROP TABLE t_src; diff --git a/mysql-test/main/func_bitops_binary.test b/mysql-test/main/func_bitops_binary.test new file mode 100644 index 0000000000000..ff1f1e032d537 --- /dev/null +++ b/mysql-test/main/func_bitops_binary.test @@ -0,0 +1,188 @@ +--echo # +--echo # MDEV-10526: Binary string support for bitwise operators & aggregate functions +--echo # + +--echo # ========================================================================= +--echo # SECTION 1 — Scalar operators on VARBINARY +--echo # ========================================================================= +--echo # Rationale: Tests basic functional correctness of scalar bitwise operators +--echo # (&, |, ^, ~, <<, >>) when applied to VARBINARY(4) operands. +--echo # ========================================================================= + +CREATE TABLE t1 (a VARBINARY(4), b VARBINARY(4)); +INSERT INTO t1 VALUES (x'FFFF0000', x'FF00FF00'); + +--echo # Expected output: FF000000, FFFFFF00, 00FFFF00, 0000FFFF, FFFE0000, 7FFF8000 +SELECT HEX(a & b), HEX(a | b), HEX(a ^ b), HEX(~a), HEX(a << 1), HEX(a >> 1) FROM t1; + +DROP TABLE t1; + + +--echo # ========================================================================= +--echo # SECTION 2 — INET6_ATON real-world use case +--echo # ========================================================================= +--echo # Rationale: Verifies the primary target use case (subnet masking and IP math) +--echo # still operates correctly with INET6_ATON (which returns VARBINARY(16)). +--echo # ========================================================================= + +--echo # Expected output: 20010DB8000000000000000000000000 +SELECT HEX(INET6_ATON('ffff:ffff::') & INET6_ATON('2001:db8::1')); + +--echo # Expected output: TBD - verify via --record +SELECT HEX(INET6_ATON('ffff:ffff::') | INET6_ATON('2001:db8::1')); + +--echo # Expected output: TBD - verify via --record +SELECT HEX(INET6_ATON('ffff:ffff::') ^ INET6_ATON('2001:db8::1')); + + +--echo # ========================================================================= +--echo # SECTION 3 — NULL handling +--echo # ========================================================================= +--echo # Rationale: Ensures that if any of the operands are NULL, the result of the +--echo # bitwise operation is also NULL. +--echo # ========================================================================= + +CREATE TABLE t_null (a VARBINARY(4), b VARBINARY(4)); +INSERT INTO t_null VALUES (x'FFFF0000', NULL); + +--echo # Expected output: NULL, NULL, NULL, NULL, NULL, NULL +SELECT HEX(a & b), HEX(a | b), HEX(a ^ b), HEX(~b), HEX(a << b), HEX(a >> b) FROM t_null; + +DROP TABLE t_null; + + +--echo # ========================================================================= +--echo # SECTION 4 — Mismatched length error +--echo # ========================================================================= +--echo # Rationale: Verifies that operating on binary string operands with mismatched +--echo # lengths throws ER_INVALID_BITWISE_OPERANDS_SIZE (error 4265). +--echo # ========================================================================= + +CREATE TABLE t_mismatch (a VARBINARY(4), b VARBINARY(2)); +INSERT INTO t_mismatch VALUES (x'FFFF0000', x'FFFF'); + +--error ER_INVALID_BITWISE_OPERANDS_SIZE +SELECT a & b FROM t_mismatch; + +--error ER_INVALID_BITWISE_OPERANDS_SIZE +SELECT a | b FROM t_mismatch; + +--error ER_INVALID_BITWISE_OPERANDS_SIZE +SELECT a ^ b FROM t_mismatch; + +DROP TABLE t_mismatch; + + +--echo # ========================================================================= +--echo # SECTION 5 — Literal behavior (CURRENT state, not final) +--echo # ========================================================================= +--echo # Rationale: Records the current behavior of constant/hex-constant literals and +--echo # string/binary cast expressions. +--echo # NOTE: x'FF' behavior is under discussion with mentors (MDEV-10526), this records CURRENT behavior. +--echo # ========================================================================= + +--echo # Expected output: 0 (current behavior — hex literal design decision pending with mentors, see MDEV-10526 discussion) +SELECT x'FF' & 12; + +--echo # Expected output: 12 +SELECT 0xFF & 12; + +--echo # Expected output: 60 +SELECT HEX(_binary'a' & _binary'b'); + +--echo # Expected output: 0 +SELECT 'a' & 'b'; + + +--echo # ========================================================================= +--echo # SECTION 6 — Existing INET6/UUID error preservation +--echo # ========================================================================= +--echo # Rationale: Confirms that existing parameter data type restrictions for +--echo # INET6 and UUID types are preserved and throw ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION (4079). +--echo # ========================================================================= + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT CAST('::' AS INET6) & CAST('::' AS INET6); + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT CAST(UUID() AS UUID) & CAST(UUID() AS UUID); + + +--echo # ========================================================================= +--echo # SECTION 7 — Aggregate functions +--echo # ========================================================================= +--echo # Rationale: Verifies BIT_AND, BIT_OR, and BIT_XOR aggregate function +--echo # calculations on VARBINARY columns under normal grouping conditions. +--echo # ========================================================================= + +CREATE TABLE t_agg (a VARBINARY(4)); +INSERT INTO t_agg VALUES (x'FF000000'), (x'0F0F0F0F'), (x'00FF0000'); + +--echo # Expected output: FFFF0F0F, 00000000, F0F00F0F +SELECT HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_agg; + +DROP TABLE t_agg; + +--echo # GROUP BY test with multiple groups +CREATE TABLE t_grp (grp INT, a VARBINARY(4)); +INSERT INTO t_grp VALUES (1, x'FF000000'), (1, x'0F0F0F0F'), (2, x'00FF0000'), (2, x'0F0F0F0F'); + +--echo # Expected output: +--echo # 1, FF0F0F0F, 0F000000, F00F0F0F +--echo # 2, 0FFF0F0F, 000F0000, 0FF00F0F +SELECT grp, HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_grp GROUP BY grp; + +DROP TABLE t_grp; + + +--echo # ========================================================================= +--echo # SECTION 8 — Aggregate edge cases +--echo # ========================================================================= +--echo # Rationale: Tests aggregate functions with NULL rows, empty tables, +--echo # and verify that operand sizes > 512 bytes are correctly blocked by the +--echo # size guard with ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE (4266). +--echo # ========================================================================= + +CREATE TABLE t_edge (a VARBINARY(4)); +INSERT INTO t_edge VALUES (x'FF000000'), (NULL), (x'00FF0000'); + +--echo # Expected output: FFFF0000, 00000000, FFFF0000 +SELECT HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_edge; + +--echo # Empty result set (no rows) +--echo # Expected output: TBD - verify via --record (likely neutral element: 00000000, FFFFFFFF, 00000000 based on binary_length) +SELECT HEX(BIT_OR(a)), HEX(BIT_AND(a)), HEX(BIT_XOR(a)) FROM t_edge WHERE 1=0; + +DROP TABLE t_edge; + +--echo # Size guard validation (operand size > 512 bytes) +--error ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE +SELECT BIT_OR(CAST('a' AS BINARY(513))); + + +--echo # ========================================================================= +--echo # SECTION 9 — Backward compatibility — integer mode unchanged +--echo # ========================================================================= +--echo # Rationale: Verifies that operations on integer literals behave exactly +--echo # as they did in pre-patch versions of MariaDB. +--echo # ========================================================================= + +--echo # Expected output: 1, 7, 6, 18446744073709551615, 4, 4 +SELECT 5 & 3, 5 | 3, 5 ^ 3, ~0, 1 << 2, 8 >> 1; + + +--echo # ========================================================================= +--echo # SECTION 10 — CREATE TABLE AS SELECT type preservation +--echo # ========================================================================= +--echo # Rationale: Verifies that CTAS (Create Table As Select) correctly preserves +--echo # the resulting datatype as a VARBINARY/binary type. +--echo # ========================================================================= + +CREATE TABLE t_src (a VARBINARY(4), b VARBINARY(4)); +CREATE TABLE t_dest AS SELECT a & b AS res FROM t_src; + +--echo # Expected output: SHOW CREATE TABLE shows `res` as VARBINARY(4) or blob-type +SHOW CREATE TABLE t_dest; + +DROP TABLE t_dest; +DROP TABLE t_src; diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 50ce2b73791b6..6bed4fe2e7932 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -3077,12 +3077,51 @@ void Item_sum_avg::reset_field() void Item_sum_bit::reset_field() { + if (m_binary_mode) + { + reset_and_add(); + if (null_value) + { + result_field->set_null(); + result_field->reset(); + } + else + { + result_field->set_notnull(); + result_field->store(m_str_value.ptr(), m_str_value.length(), &my_charset_bin); + } + return; + } reset_and_add(); int8store(result_field->ptr, bits); } void Item_sum_bit::update_field() { + if (m_binary_mode) + { + char buff[512]; // matches ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE + // guard (max 512 bytes) in fix_length_and_dec() + String tmp(buff, sizeof(buff), &my_charset_bin); + String *res= result_field->val_str(&tmp, &tmp); + if (!result_field->is_null()) + { + m_str_value.copy(*res); + add(); + result_field->set_notnull(); + result_field->store(m_str_value.ptr(), m_str_value.length(), &my_charset_bin); + } + else + { + add(); + if (!null_value) + { + result_field->set_notnull(); + result_field->store(m_str_value.ptr(), m_str_value.length(), &my_charset_bin); + } + } + return; + } // We never call update_field when computing the function as a window // function. Setting bits to a random value invalidates the bits counters and // the result of the bit function becomes erroneous. From 6b9dd9e636f67873245b047f22b69138268eadf6 Mon Sep 17 00:00:00 2001 From: kjarir Date: Thu, 11 Jun 2026 12:51:32 +0530 Subject: [PATCH 05/12] MDEV-10526: Add inet4 and geometry to type restriction tests Per Alexander Barkov's request, Section 6 verified that uuid and inet6 cast types continue to error on bitwise operations. This extends coverage to inet4 and geometry types as well, confirming all four types (uuid, inet6, inet4, geometry) correctly raise ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION (4079) and remain out of scope for this patch. --- mysql-test/main/func_bitops_binary.result | 8 ++++++++ mysql-test/main/func_bitops_binary.test | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/mysql-test/main/func_bitops_binary.result b/mysql-test/main/func_bitops_binary.result index 0f8a8e910b02f..6d6d8ea5692a1 100644 --- a/mysql-test/main/func_bitops_binary.result +++ b/mysql-test/main/func_bitops_binary.result @@ -98,6 +98,14 @@ SELECT CAST('::' AS INET6) & CAST('::' AS INET6); ERROR HY000: Illegal parameter data type inet6 for operation '&' SELECT CAST(UUID() AS UUID) & CAST(UUID() AS UUID); ERROR HY000: Illegal parameter data type uuid for operation '&' +SELECT CAST('1.2.3.4' AS INET4) & CAST('1.2.3.4' AS INET4); +ERROR HY000: Illegal parameter data type inet4 for operation '&' +SELECT CAST('1.2.3.4' AS INET4) & 1; +ERROR HY000: Illegal parameter data type inet4 for operation '&' +SELECT ST_GeomFromText('POINT(1 1)') & ST_GeomFromText('POINT(1 1)'); +ERROR HY000: Illegal parameter data type geometry for operation '&' +SELECT ST_GeomFromText('POINT(1 1)') & 1; +ERROR HY000: Illegal parameter data type geometry for operation '&' # ========================================================================= # SECTION 7 — Aggregate functions # ========================================================================= diff --git a/mysql-test/main/func_bitops_binary.test b/mysql-test/main/func_bitops_binary.test index ff1f1e032d537..77d0603abb94d 100644 --- a/mysql-test/main/func_bitops_binary.test +++ b/mysql-test/main/func_bitops_binary.test @@ -107,6 +107,18 @@ SELECT CAST('::' AS INET6) & CAST('::' AS INET6); --error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION SELECT CAST(UUID() AS UUID) & CAST(UUID() AS UUID); +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT CAST('1.2.3.4' AS INET4) & CAST('1.2.3.4' AS INET4); + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT CAST('1.2.3.4' AS INET4) & 1; + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT ST_GeomFromText('POINT(1 1)') & ST_GeomFromText('POINT(1 1)'); + +--error ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION +SELECT ST_GeomFromText('POINT(1 1)') & 1; + --echo # ========================================================================= --echo # SECTION 7 — Aggregate functions From 08a5eb23117cc06098ab62b0809fee8e76230d87 Mon Sep 17 00:00:00 2001 From: kjarir Date: Sun, 14 Jun 2026 19:28:19 +0530 Subject: [PATCH 06/12] MDEV-10526: Fix regressions in main.gis and main.func_json Two regressions were introduced by earlier commits: 1. main.gis: BIT_AND/BIT_OR/BIT_XOR on GEOMETRY columns now incorrectly raised ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE (4266) instead of the correct ER_ILLEGAL_PARAMETER_DATA_TYPE_ FOR_OPERATION (4079). GEOMETRY has STRING_RESULT with my_charset_bin, so it matched our binary_mode detection before the existing type-rejection check could run. Fixed by excluding MYSQL_TYPE_GEOMETRY from binary_mode detection in Item_sum_bit::fix_length_and_dec(), letting it fall through to check_type_can_return_int() as before. 2. main.func_json: '1 ^ json_objectagg(NULL, 'a')' now raised ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION (4079) instead of returning NULL. Our earlier commit changed Item_func_bit_operator::check_arguments() from check_argument_types_can_return_int() to check_type_traditional_scalar(). The latter is stricter: Type_handler_blob_json inherits can_return_int()=true from its BLOB base but has type_collection()=Type_collection_json != type_collection_std, so is_traditional_scalar_type() returns false and the stricter check rejected it. This change was unnecessary for our binary-mode feature - VARBINARY/BLOB/INET6_ATON all pass can_return_int() via the base Type_handler default (only GEOMETRY overrides it to false), so binary mode detection in fix_length_and_dec() (STRING_RESULT + my_charset_bin) works correctly without the stricter check_arguments() override. Reverted check_arguments() to check_argument_types_can_return_int(), restoring pre-patch JSON behavior. Both fixes verified against main.gis, main.func_json, func_bitops_binary, and func_bit - all pass. --- sql/item_func.h | 9 +-------- sql/item_sum.cc | 6 +++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/sql/item_func.h b/sql/item_func.h index acfda7ce1bd11..65b5ccebda37d 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -2927,14 +2927,7 @@ class Func_handler_shift_right_bin_to_bin; class Item_func_bit_operator: public Item_handled_func { bool check_arguments() const override - { - for (uint i= 0; i < arg_count; i++) - { - if (args[i]->check_type_traditional_scalar(func_name_cstring())) - return true; - } - return false; - } + { return check_argument_types_can_return_int(0, arg_count); } protected: bool fix_length_and_dec_op1_std(const Handler *ha_int, const Handler *ha_dec) { diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 6bed4fe2e7932..d49161e80cb89 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2614,10 +2614,14 @@ bool Item_sum_max::add() bool Item_sum_bit::fix_length_and_dec(THD *thd) { - // Check for binary string argument + // Check for binary string argument. + // Exclude GEOMETRY: it has STRING_RESULT + my_charset_bin but must be + // rejected via check_type_can_return_int() below with error 4079, not + // treated as a binary-mode operand. m_binary_mode= false; if (args[0]->result_type() == STRING_RESULT && args[0]->collation.collation == &my_charset_bin && + args[0]->field_type() != MYSQL_TYPE_GEOMETRY && dynamic_cast(args[0]->real_item()) == nullptr) { // Size guard From 389d2975924e2b0e1b695cfd63dc3d0608803d24 Mon Sep 17 00:00:00 2001 From: kjarir Date: Sun, 14 Jun 2026 19:44:39 +0530 Subject: [PATCH 07/12] MDEV-10526: Add boundary, large-literal, and coercibility tests Per Daniel Black's review feedback: - Section 11: CREATE...SELECT length-boundary tests verifying return_type_handler() returns the correct type at each threshold - type_handler_string (<=255 bytes), type_handler_ varchar (256-65532 bytes), and blob_type_handler (>65532 bytes via BLOB columns, since VARBINARY max is MAX_FIELD_VARCHARLENGTH=65532). - Section 12: Larger literal tests using SET @var=REPEAT(...) with CAST(... AS BINARY(n)) to verify byte-loop and max_length logic scale correctly to 100+ byte operands, including at the 512-byte aggregate size guard boundary. - Section 13: COERCIBILITY() validation confirming all 6 scalar binary-mode operators return coercibility 6 (DERIVATION_COERCIBLE), validating the earlier review fix from DERIVATION_IMPLICIT. Verified passing under both normal and --cursor-protocol execution. --- mysql-test/main/func_bitops_binary.result | 182 ++++++++++++++++++++++ mysql-test/main/func_bitops_binary.test | 106 +++++++++++++ 2 files changed, 288 insertions(+) diff --git a/mysql-test/main/func_bitops_binary.result b/mysql-test/main/func_bitops_binary.result index 6d6d8ea5692a1..2b6b5b2a556fd 100644 --- a/mysql-test/main/func_bitops_binary.result +++ b/mysql-test/main/func_bitops_binary.result @@ -178,3 +178,185 @@ t_dest CREATE TABLE `t_dest` ( ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci DROP TABLE t_dest; DROP TABLE t_src; +# ========================================================================= +# SECTION 11 — return_type_handler length-boundary tests +# ========================================================================= +# Rationale: Verifies that CTAS picks the correct storage type at each +# boundary in return_type_handler(): <=255 -> CHAR/BINARY (type_handler_string), +# 256..MAX_FIELD_VARCHARLENGTH -> VARCHAR (type_handler_varchar), +# >MAX_FIELD_VARCHARLENGTH (65532) -> BLOB family (blob_type_handler). +# Covers all binary operators so every Handler_str subclass is exercised. +# ========================================================================= +# -- Small (<=255 bytes): expect BINARY(10) / char-family type +CREATE TABLE t_small (a VARBINARY(10), b VARBINARY(10)); +CREATE TABLE t_small_and AS SELECT a & b AS res FROM t_small; +CREATE TABLE t_small_or AS SELECT a | b AS res FROM t_small; +CREATE TABLE t_small_xor AS SELECT a ^ b AS res FROM t_small; +CREATE TABLE t_small_neg AS SELECT ~a AS res FROM t_small; +CREATE TABLE t_small_shl AS SELECT a << 1 AS res FROM t_small; +CREATE TABLE t_small_shr AS SELECT a >> 1 AS res FROM t_small; +SHOW CREATE TABLE t_small_and; +Table Create Table +t_small_and CREATE TABLE `t_small_and` ( + `res` binary(10) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_small_or; +Table Create Table +t_small_or CREATE TABLE `t_small_or` ( + `res` binary(10) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_small_xor; +Table Create Table +t_small_xor CREATE TABLE `t_small_xor` ( + `res` binary(10) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_small_neg; +Table Create Table +t_small_neg CREATE TABLE `t_small_neg` ( + `res` binary(10) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_small_shl; +Table Create Table +t_small_shl CREATE TABLE `t_small_shl` ( + `res` binary(10) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_small_shr; +Table Create Table +t_small_shr CREATE TABLE `t_small_shr` ( + `res` binary(10) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_small_and, t_small_or, t_small_xor, +t_small_neg, t_small_shl, t_small_shr, t_small; +# -- Medium (256 .. 65532 bytes): expect VARCHAR(300) / varchar-family type +CREATE TABLE t_med (a VARBINARY(300), b VARBINARY(300)); +CREATE TABLE t_med_and AS SELECT a & b AS res FROM t_med; +CREATE TABLE t_med_or AS SELECT a | b AS res FROM t_med; +CREATE TABLE t_med_xor AS SELECT a ^ b AS res FROM t_med; +CREATE TABLE t_med_neg AS SELECT ~a AS res FROM t_med; +CREATE TABLE t_med_shl AS SELECT a << 1 AS res FROM t_med; +CREATE TABLE t_med_shr AS SELECT a >> 1 AS res FROM t_med; +SHOW CREATE TABLE t_med_and; +Table Create Table +t_med_and CREATE TABLE `t_med_and` ( + `res` varbinary(300) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_med_or; +Table Create Table +t_med_or CREATE TABLE `t_med_or` ( + `res` varbinary(300) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_med_xor; +Table Create Table +t_med_xor CREATE TABLE `t_med_xor` ( + `res` varbinary(300) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_med_neg; +Table Create Table +t_med_neg CREATE TABLE `t_med_neg` ( + `res` varbinary(300) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_med_shl; +Table Create Table +t_med_shl CREATE TABLE `t_med_shl` ( + `res` varbinary(300) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_med_shr; +Table Create Table +t_med_shr CREATE TABLE `t_med_shr` ( + `res` varbinary(300) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_med_and, t_med_or, t_med_xor, +t_med_neg, t_med_shl, t_med_shr, t_med; +# -- Large (>65532 bytes): expect BLOB family type +# VARBINARY max is 65532 = MAX_FIELD_VARCHARLENGTH; use BLOB columns (max_length=65535) +# to exercise the blob_type_handler branch in return_type_handler(). +CREATE TABLE t_large (a BLOB, b BLOB); +CREATE TABLE t_large_and AS SELECT a & b AS res FROM t_large; +CREATE TABLE t_large_or AS SELECT a | b AS res FROM t_large; +CREATE TABLE t_large_xor AS SELECT a ^ b AS res FROM t_large; +CREATE TABLE t_large_neg AS SELECT ~a AS res FROM t_large; +SHOW CREATE TABLE t_large_and; +Table Create Table +t_large_and CREATE TABLE `t_large_and` ( + `res` blob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_large_or; +Table Create Table +t_large_or CREATE TABLE `t_large_or` ( + `res` blob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_large_xor; +Table Create Table +t_large_xor CREATE TABLE `t_large_xor` ( + `res` blob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_large_neg; +Table Create Table +t_large_neg CREATE TABLE `t_large_neg` ( + `res` blob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_large_and, t_large_or, t_large_xor, t_large_neg, t_large; +# ========================================================================= +# SECTION 12 — Large literal tests with REPEAT() +# ========================================================================= +# Rationale: Verifies that byte-loop and max_length logic in the binary +# handlers scale correctly to large binary strings well beyond 4-16 bytes. +# Uses _binary cast to force binary mode on user variables. +# ========================================================================= +# -- 100-byte operands: check result length and spot-check first 4 bytes +SET @a100 = REPEAT('a', 100); +SET @b100 = REPEAT('b', 100); +SELECT LENGTH(CAST(@a100 AS BINARY(100)) & CAST(@b100 AS BINARY(100))); +LENGTH(CAST(@a100 AS BINARY(100)) & CAST(@b100 AS BINARY(100))) +100 +SELECT HEX(LEFT(CAST(@a100 AS BINARY(100)) & CAST(@b100 AS BINARY(100)), 4)); +HEX(LEFT(CAST(@a100 AS BINARY(100)) & CAST(@b100 AS BINARY(100)), 4)) +60606060 +SELECT LENGTH(CAST(@a100 AS BINARY(100)) | CAST(@b100 AS BINARY(100))); +LENGTH(CAST(@a100 AS BINARY(100)) | CAST(@b100 AS BINARY(100))) +100 +SELECT LENGTH(CAST(@a100 AS BINARY(100)) ^ CAST(@b100 AS BINARY(100))); +LENGTH(CAST(@a100 AS BINARY(100)) ^ CAST(@b100 AS BINARY(100))) +100 +# -- 512-byte operands (at aggregate size guard boundary) +SET @a512 = REPEAT('x', 512); +SET @b512 = REPEAT('y', 512); +SELECT LENGTH(CAST(@a512 AS BINARY(512)) & CAST(@b512 AS BINARY(512))); +LENGTH(CAST(@a512 AS BINARY(512)) & CAST(@b512 AS BINARY(512))) +512 +SELECT LENGTH(CAST(@a512 AS BINARY(512)) | CAST(@b512 AS BINARY(512))); +LENGTH(CAST(@a512 AS BINARY(512)) | CAST(@b512 AS BINARY(512))) +512 +SELECT LENGTH(CAST(@a512 AS BINARY(512)) ^ CAST(@b512 AS BINARY(512))); +LENGTH(CAST(@a512 AS BINARY(512)) ^ CAST(@b512 AS BINARY(512))) +512 +# ========================================================================= +# SECTION 13 — COERCIBILITY() validation +# ========================================================================= +# Rationale: Confirms that binary-mode results carry DERIVATION_COERCIBLE +# (value 6), which was explicitly set in the fix_length_and_dec() handlers. +# This validates the collation derivation fix from review feedback. +# Per MariaDB docs, DERIVATION_COERCIBLE maps to COERCIBILITY() = 6. +# ========================================================================= +CREATE TABLE t_coerc (a VARBINARY(4), b VARBINARY(4)); +INSERT INTO t_coerc VALUES (x'FFFF0000', x'FF00FF00'); +# All binary-mode scalar operators should return coercibility 6 +SELECT COERCIBILITY(a & b) FROM t_coerc; +COERCIBILITY(a & b) +6 +SELECT COERCIBILITY(a | b) FROM t_coerc; +COERCIBILITY(a | b) +6 +SELECT COERCIBILITY(a ^ b) FROM t_coerc; +COERCIBILITY(a ^ b) +6 +SELECT COERCIBILITY(~a) FROM t_coerc; +COERCIBILITY(~a) +6 +SELECT COERCIBILITY(a << 1) FROM t_coerc; +COERCIBILITY(a << 1) +6 +SELECT COERCIBILITY(a >> 1) FROM t_coerc; +COERCIBILITY(a >> 1) +6 +DROP TABLE t_coerc; diff --git a/mysql-test/main/func_bitops_binary.test b/mysql-test/main/func_bitops_binary.test index 77d0603abb94d..7c9d06f6c4162 100644 --- a/mysql-test/main/func_bitops_binary.test +++ b/mysql-test/main/func_bitops_binary.test @@ -198,3 +198,109 @@ SHOW CREATE TABLE t_dest; DROP TABLE t_dest; DROP TABLE t_src; + + +--echo # ========================================================================= +--echo # SECTION 11 — return_type_handler length-boundary tests +--echo # ========================================================================= +--echo # Rationale: Verifies that CTAS picks the correct storage type at each +--echo # boundary in return_type_handler(): <=255 -> CHAR/BINARY (type_handler_string), +--echo # 256..MAX_FIELD_VARCHARLENGTH -> VARCHAR (type_handler_varchar), +--echo # >MAX_FIELD_VARCHARLENGTH (65532) -> BLOB family (blob_type_handler). +--echo # Covers all binary operators so every Handler_str subclass is exercised. +--echo # ========================================================================= + +--echo # -- Small (<=255 bytes): expect BINARY(10) / char-family type +CREATE TABLE t_small (a VARBINARY(10), b VARBINARY(10)); +CREATE TABLE t_small_and AS SELECT a & b AS res FROM t_small; +CREATE TABLE t_small_or AS SELECT a | b AS res FROM t_small; +CREATE TABLE t_small_xor AS SELECT a ^ b AS res FROM t_small; +CREATE TABLE t_small_neg AS SELECT ~a AS res FROM t_small; +CREATE TABLE t_small_shl AS SELECT a << 1 AS res FROM t_small; +CREATE TABLE t_small_shr AS SELECT a >> 1 AS res FROM t_small; +SHOW CREATE TABLE t_small_and; +SHOW CREATE TABLE t_small_or; +SHOW CREATE TABLE t_small_xor; +SHOW CREATE TABLE t_small_neg; +SHOW CREATE TABLE t_small_shl; +SHOW CREATE TABLE t_small_shr; +DROP TABLE t_small_and, t_small_or, t_small_xor, + t_small_neg, t_small_shl, t_small_shr, t_small; + +--echo # -- Medium (256 .. 65532 bytes): expect VARCHAR(300) / varchar-family type +CREATE TABLE t_med (a VARBINARY(300), b VARBINARY(300)); +CREATE TABLE t_med_and AS SELECT a & b AS res FROM t_med; +CREATE TABLE t_med_or AS SELECT a | b AS res FROM t_med; +CREATE TABLE t_med_xor AS SELECT a ^ b AS res FROM t_med; +CREATE TABLE t_med_neg AS SELECT ~a AS res FROM t_med; +CREATE TABLE t_med_shl AS SELECT a << 1 AS res FROM t_med; +CREATE TABLE t_med_shr AS SELECT a >> 1 AS res FROM t_med; +SHOW CREATE TABLE t_med_and; +SHOW CREATE TABLE t_med_or; +SHOW CREATE TABLE t_med_xor; +SHOW CREATE TABLE t_med_neg; +SHOW CREATE TABLE t_med_shl; +SHOW CREATE TABLE t_med_shr; +DROP TABLE t_med_and, t_med_or, t_med_xor, + t_med_neg, t_med_shl, t_med_shr, t_med; + +--echo # -- Large (>65532 bytes): expect BLOB family type +--echo # VARBINARY max is 65532 = MAX_FIELD_VARCHARLENGTH; use BLOB columns (max_length=65535) +--echo # to exercise the blob_type_handler branch in return_type_handler(). +CREATE TABLE t_large (a BLOB, b BLOB); +CREATE TABLE t_large_and AS SELECT a & b AS res FROM t_large; +CREATE TABLE t_large_or AS SELECT a | b AS res FROM t_large; +CREATE TABLE t_large_xor AS SELECT a ^ b AS res FROM t_large; +CREATE TABLE t_large_neg AS SELECT ~a AS res FROM t_large; +SHOW CREATE TABLE t_large_and; +SHOW CREATE TABLE t_large_or; +SHOW CREATE TABLE t_large_xor; +SHOW CREATE TABLE t_large_neg; +DROP TABLE t_large_and, t_large_or, t_large_xor, t_large_neg, t_large; + + +--echo # ========================================================================= +--echo # SECTION 12 — Large literal tests with REPEAT() +--echo # ========================================================================= +--echo # Rationale: Verifies that byte-loop and max_length logic in the binary +--echo # handlers scale correctly to large binary strings well beyond 4-16 bytes. +--echo # Uses _binary cast to force binary mode on user variables. +--echo # ========================================================================= + +--echo # -- 100-byte operands: check result length and spot-check first 4 bytes +SET @a100 = REPEAT('a', 100); +SET @b100 = REPEAT('b', 100); +SELECT LENGTH(CAST(@a100 AS BINARY(100)) & CAST(@b100 AS BINARY(100))); +SELECT HEX(LEFT(CAST(@a100 AS BINARY(100)) & CAST(@b100 AS BINARY(100)), 4)); +SELECT LENGTH(CAST(@a100 AS BINARY(100)) | CAST(@b100 AS BINARY(100))); +SELECT LENGTH(CAST(@a100 AS BINARY(100)) ^ CAST(@b100 AS BINARY(100))); + +--echo # -- 512-byte operands (at aggregate size guard boundary) +SET @a512 = REPEAT('x', 512); +SET @b512 = REPEAT('y', 512); +SELECT LENGTH(CAST(@a512 AS BINARY(512)) & CAST(@b512 AS BINARY(512))); +SELECT LENGTH(CAST(@a512 AS BINARY(512)) | CAST(@b512 AS BINARY(512))); +SELECT LENGTH(CAST(@a512 AS BINARY(512)) ^ CAST(@b512 AS BINARY(512))); + + +--echo # ========================================================================= +--echo # SECTION 13 — COERCIBILITY() validation +--echo # ========================================================================= +--echo # Rationale: Confirms that binary-mode results carry DERIVATION_COERCIBLE +--echo # (value 6), which was explicitly set in the fix_length_and_dec() handlers. +--echo # This validates the collation derivation fix from review feedback. +--echo # Per MariaDB docs, DERIVATION_COERCIBLE maps to COERCIBILITY() = 6. +--echo # ========================================================================= + +CREATE TABLE t_coerc (a VARBINARY(4), b VARBINARY(4)); +INSERT INTO t_coerc VALUES (x'FFFF0000', x'FF00FF00'); + +--echo # All binary-mode scalar operators should return coercibility 6 +SELECT COERCIBILITY(a & b) FROM t_coerc; +SELECT COERCIBILITY(a | b) FROM t_coerc; +SELECT COERCIBILITY(a ^ b) FROM t_coerc; +SELECT COERCIBILITY(~a) FROM t_coerc; +SELECT COERCIBILITY(a << 1) FROM t_coerc; +SELECT COERCIBILITY(a >> 1) FROM t_coerc; + +DROP TABLE t_coerc; From 567edd600d19d80e9928b021ac0ceb0281b6254f Mon Sep 17 00:00:00 2001 From: kjarir Date: Sun, 14 Jun 2026 19:56:30 +0530 Subject: [PATCH 08/12] MDEV-10526: Add mediumblob/longblob escalation tests to Section 11 Per Daniel Black's reference to commit b5fae7f743d2b2c71ee23b0daf51c2be294733eb, add two standalone CREATE...SELECT LIMIT 0 tests confirming blob_type_handler() correctly escalates the result type based on source column size: MEDIUMBLOB & MEDIUMBLOB -> mediumblob LONGBLOB & LONGBLOB -> longblob This complements the existing string/varchar/blob tier tests, fully covering the return_type_handler boundary behavior shown in the reference commit. --- mysql-test/main/func_bitops_binary.result | 18 ++++++++++++++++++ mysql-test/main/func_bitops_binary.test | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/mysql-test/main/func_bitops_binary.result b/mysql-test/main/func_bitops_binary.result index 2b6b5b2a556fd..73a51b05b8cc5 100644 --- a/mysql-test/main/func_bitops_binary.result +++ b/mysql-test/main/func_bitops_binary.result @@ -296,6 +296,24 @@ t_large_neg CREATE TABLE `t_large_neg` ( `res` blob DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci DROP TABLE t_large_and, t_large_or, t_large_xor, t_large_neg, t_large; +# -- Medium BLOB (>65535 bytes, <=16777215 bytes): expect MEDIUMBLOB +CREATE TABLE t_mblob (a MEDIUMBLOB, b MEDIUMBLOB); +CREATE TABLE t_mblob_dest AS SELECT a & b AS res FROM t_mblob LIMIT 0; +SHOW CREATE TABLE t_mblob_dest; +Table Create Table +t_mblob_dest CREATE TABLE `t_mblob_dest` ( + `res` mediumblob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_mblob_dest, t_mblob; +# -- Long BLOB (>16777215 bytes): expect LONGBLOB +CREATE TABLE t_lblob (a LONGBLOB, b LONGBLOB); +CREATE TABLE t_lblob_dest AS SELECT a & b AS res FROM t_lblob LIMIT 0; +SHOW CREATE TABLE t_lblob_dest; +Table Create Table +t_lblob_dest CREATE TABLE `t_lblob_dest` ( + `res` longblob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t_lblob_dest, t_lblob; # ========================================================================= # SECTION 12 — Large literal tests with REPEAT() # ========================================================================= diff --git a/mysql-test/main/func_bitops_binary.test b/mysql-test/main/func_bitops_binary.test index 7c9d06f6c4162..0a0f3ac8551fa 100644 --- a/mysql-test/main/func_bitops_binary.test +++ b/mysql-test/main/func_bitops_binary.test @@ -258,6 +258,18 @@ SHOW CREATE TABLE t_large_xor; SHOW CREATE TABLE t_large_neg; DROP TABLE t_large_and, t_large_or, t_large_xor, t_large_neg, t_large; +--echo # -- Medium BLOB (>65535 bytes, <=16777215 bytes): expect MEDIUMBLOB +CREATE TABLE t_mblob (a MEDIUMBLOB, b MEDIUMBLOB); +CREATE TABLE t_mblob_dest AS SELECT a & b AS res FROM t_mblob LIMIT 0; +SHOW CREATE TABLE t_mblob_dest; +DROP TABLE t_mblob_dest, t_mblob; + +--echo # -- Long BLOB (>16777215 bytes): expect LONGBLOB +CREATE TABLE t_lblob (a LONGBLOB, b LONGBLOB); +CREATE TABLE t_lblob_dest AS SELECT a & b AS res FROM t_lblob LIMIT 0; +SHOW CREATE TABLE t_lblob_dest; +DROP TABLE t_lblob_dest, t_lblob; + --echo # ========================================================================= --echo # SECTION 12 — Large literal tests with REPEAT() From eeb3b5d82ca5a133e04ae675330cb052d7b8eb3a Mon Sep 17 00:00:00 2001 From: kjarir Date: Fri, 19 Jun 2026 12:19:26 +0530 Subject: [PATCH 09/12] MDEV-10526: Implement Alexander's type-handler-based binary mode detection Per Alexander Barkov's review, replace the dynamic_cast on real_item() approach with a check on the resolved type_handler() of each argument: longstr_count: count of args whose type_handler() is Type_handler_longstr (true string/binary types) hybrid_count: count of args whose type_handler() is Type_handler_hex_hybrid (0x.. style numeric-string hybrids) binary_count: count of args with my_charset_bin collation Binary mode now activates only when ALL operands are true non-hybrid binary strings (longstr_count == N && binary_count == N && hybrid_count == 0), matching exactly what Alexander and Sergei decided: only two true binary strings switch to string mode, preserving backward compatibility for hex/numeric literal usage. This is also more robust than the previous approach - it correctly sees through wrapper expressions like COALESCE(0x30), which previously entered binary mode incorrectly since real_item() doesn't resolve through wrapper functions. Verified: HEX(COALESCE(0x30) & 1) now correctly returns 0 (integer mode) instead of 30 (binary mode). Applied to all 6 scalar operators (&, |, ^, ~, <<, >>) and Item_sum_bit (BIT_AND/BIT_OR/BIT_XOR). The explicit GEOMETRY exclusion in Item_sum_bit is no longer needed since GEOMETRY's type_handler() is not Type_handler_longstr. Verified against Alexander's exact test case: a & 51 = 0, a & 0x33 = 0, a & x'33' = (binary), a & _latin1'3' = 0, a & _binary'3' = (binary), 0x33 & 0x33 = 51 All passing under func_bitops_binary, func_bit, func_json, gis, and both normal/cursor-protocol execution. --- mysql-test/main/func_bitops_binary.result | 31 ++++++-- mysql-test/main/func_bitops_binary.test | 25 ++++-- sql/item_func.cc | 97 +++++++++++------------ sql/item_sum.cc | 17 ++-- 4 files changed, 99 insertions(+), 71 deletions(-) diff --git a/mysql-test/main/func_bitops_binary.result b/mysql-test/main/func_bitops_binary.result index 73a51b05b8cc5..46f48c6748601 100644 --- a/mysql-test/main/func_bitops_binary.result +++ b/mysql-test/main/func_bitops_binary.result @@ -61,13 +61,12 @@ SELECT a ^ b FROM t_mismatch; ERROR HY000: Mismatched length of operands for bitwise operator: 4 and 2 DROP TABLE t_mismatch; # ========================================================================= -# SECTION 5 — Literal behavior (CURRENT state, not final) +# SECTION 5 — Literal behavior (FINAL decided behavior) # ========================================================================= -# Rationale: Records the current behavior of constant/hex-constant literals and -# string/binary cast expressions. -# NOTE: x'FF' behavior is under discussion with mentors (MDEV-10526), this records CURRENT behavior. +# Rationale: Records the decided behavior of constant/hex-constant literals and +# string/binary cast expressions, per Alexander and Sergei's review. # ========================================================================= -# Expected output: 0 (current behavior — hex literal design decision pending with mentors, see MDEV-10526 discussion) +# Expected output: 0 SELECT x'FF' & 12; x'FF' & 12 0 @@ -88,6 +87,28 @@ SELECT 'a' & 'b'; Warnings: Warning 1292 Truncated incorrect DECIMAL value: 'a' Warning 1292 Truncated incorrect DECIMAL value: 'b' +# Verification of binary vs integer mode execution on BLOB operands +CREATE OR REPLACE TABLE t1 (a BLOB); +INSERT INTO t1 VALUES ('a'); +SELECT +a & 51, +a & 0x33, +a & x'33', +a & _latin1 '3', +a & _binary '3', +0x33 & 0x33 +FROM t1; +a & 51 a & 0x33 a & x'33' a & _latin1 '3' a & _binary '3' 0x33 & 0x33 +0 0 ! 0 ! 51 +Warnings: +Warning 1292 Truncated incorrect DECIMAL value: 'a' +Warning 1292 Truncated incorrect DECIMAL value: 'a' +Warning 1292 Truncated incorrect DECIMAL value: 'a' +DROP TABLE t1; +# Verification that COALESCE correctly retains hex-hybrid type handler +SELECT HEX(COALESCE(0x30) & 1); +HEX(COALESCE(0x30) & 1) +0 # ========================================================================= # SECTION 6 — Existing INET6/UUID error preservation # ========================================================================= diff --git a/mysql-test/main/func_bitops_binary.test b/mysql-test/main/func_bitops_binary.test index 0a0f3ac8551fa..08c398a679753 100644 --- a/mysql-test/main/func_bitops_binary.test +++ b/mysql-test/main/func_bitops_binary.test @@ -74,14 +74,13 @@ DROP TABLE t_mismatch; --echo # ========================================================================= ---echo # SECTION 5 — Literal behavior (CURRENT state, not final) +--echo # SECTION 5 — Literal behavior (FINAL decided behavior) --echo # ========================================================================= ---echo # Rationale: Records the current behavior of constant/hex-constant literals and ---echo # string/binary cast expressions. ---echo # NOTE: x'FF' behavior is under discussion with mentors (MDEV-10526), this records CURRENT behavior. +--echo # Rationale: Records the decided behavior of constant/hex-constant literals and +--echo # string/binary cast expressions, per Alexander and Sergei's review. --echo # ========================================================================= ---echo # Expected output: 0 (current behavior — hex literal design decision pending with mentors, see MDEV-10526 discussion) +--echo # Expected output: 0 SELECT x'FF' & 12; --echo # Expected output: 12 @@ -93,6 +92,22 @@ SELECT HEX(_binary'a' & _binary'b'); --echo # Expected output: 0 SELECT 'a' & 'b'; +--echo # Verification of binary vs integer mode execution on BLOB operands +CREATE OR REPLACE TABLE t1 (a BLOB); +INSERT INTO t1 VALUES ('a'); +SELECT + a & 51, + a & 0x33, + a & x'33', + a & _latin1 '3', + a & _binary '3', + 0x33 & 0x33 +FROM t1; +DROP TABLE t1; + +--echo # Verification that COALESCE correctly retains hex-hybrid type handler +SELECT HEX(COALESCE(0x30) & 1); + --echo # ========================================================================= --echo # SECTION 6 — Existing INET6/UUID error preservation diff --git a/sql/item_func.cc b/sql/item_func.cc index 54a733203a151..cde7762a9532e 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2329,15 +2329,12 @@ bool Item_func_shift_left::fix_length_and_dec(THD *thd) static Func_handler_shift_left_int_to_ulonglong ha_int_to_ull; static Func_handler_shift_left_decimal_to_ulonglong ha_dec_to_ull; - bool binary_mode= false; - if (args[0]->result_type() == STRING_RESULT && - args[0]->collation.collation == &my_charset_bin && - dynamic_cast(args[0]->real_item()) == nullptr) - { - binary_mode= true; - } + const Type_handler *th= args[0]->type_handler(); + bool is_longstr= dynamic_cast(th) != nullptr; + bool is_hybrid= dynamic_cast(th) != nullptr; + bool is_binary= args[0]->collation.collation == &my_charset_bin; - if (binary_mode) + if (is_longstr && is_binary && !is_hybrid) { set_func_handler(&ha_bin_to_bin); return m_func_handler->fix_length_and_dec(this); @@ -2479,15 +2476,12 @@ bool Item_func_shift_right::fix_length_and_dec(THD *thd) static Func_handler_shift_right_int_to_ulonglong ha_int_to_ull; static Func_handler_shift_right_decimal_to_ulonglong ha_dec_to_ull; - bool binary_mode= false; - if (args[0]->result_type() == STRING_RESULT && - args[0]->collation.collation == &my_charset_bin && - dynamic_cast(args[0]->real_item()) == nullptr) - { - binary_mode= true; - } + const Type_handler *th= args[0]->type_handler(); + bool is_longstr= dynamic_cast(th) != nullptr; + bool is_hybrid= dynamic_cast(th) != nullptr; + bool is_binary= args[0]->collation.collation == &my_charset_bin; - if (binary_mode) + if (is_longstr && is_binary && !is_hybrid) { set_func_handler(&ha_bin_to_bin); return m_func_handler->fix_length_and_dec(this); @@ -2588,15 +2582,13 @@ bool Item_func_bit_neg::fix_length_and_dec(THD *thd) static Func_handler_bit_neg_int_to_ulonglong ha_int_to_ull; static Func_handler_bit_neg_decimal_to_ulonglong ha_dec_to_ull; - bool binary_mode= false; - if (args[0]->result_type() == STRING_RESULT && - args[0]->collation.collation == &my_charset_bin && - dynamic_cast(args[0]->real_item()) == nullptr) - { - binary_mode= true; - } + DBUG_ASSERT(arg_count == 1); + const Type_handler *th= args[0]->type_handler(); + bool is_longstr= dynamic_cast(th) != nullptr; + bool is_hybrid= dynamic_cast(th) != nullptr; + bool is_binary= args[0]->collation.collation == &my_charset_bin; - if (binary_mode) + if (is_longstr && is_binary && !is_hybrid) { set_func_handler(&ha_bin_to_bin); return m_func_handler->fix_length_and_dec(this); @@ -7001,19 +6993,20 @@ bool Item_func_bit_xor::fix_length_and_dec(THD *thd) static const Func_handler_bit_xor_int_to_ulonglong ha_int_to_ull; static const Func_handler_bit_xor_dec_to_ulonglong ha_dec_to_ull; - bool binary_mode= false; + DBUG_ASSERT(arg_count == 2); + uint longstr_count= 0; + uint binary_count= 0; + uint hybrid_count= 0; + for (uint i= 0; i < arg_count; i++) { - if (args[i]->result_type() == STRING_RESULT && - args[i]->collation.collation == &my_charset_bin && - dynamic_cast(args[i]->real_item()) == nullptr) - { - binary_mode= true; - break; - } + const Type_handler *th= args[i]->type_handler(); + longstr_count+= dynamic_cast(th) != nullptr; + hybrid_count+= dynamic_cast(th) != nullptr; + binary_count+= args[i]->collation.collation == &my_charset_bin; } - if (binary_mode) + if (longstr_count == 2 && binary_count == 2 && hybrid_count == 0) { set_func_handler(&ha_bin_to_bin); return m_func_handler->fix_length_and_dec(this); @@ -8028,19 +8021,20 @@ bool Item_func_bit_and::fix_length_and_dec(THD *thd) static Func_handler_bit_and_int_to_ulonglong ha_int_to_ull; static Func_handler_bit_and_dec_to_ulonglong ha_dec_to_ull; - bool binary_mode= false; + DBUG_ASSERT(arg_count == 2); + uint longstr_count= 0; + uint binary_count= 0; + uint hybrid_count= 0; + for (uint i= 0; i < arg_count; i++) { - if (args[i]->result_type() == STRING_RESULT && - args[i]->collation.collation == &my_charset_bin && - dynamic_cast(args[i]->real_item()) == nullptr) - { - binary_mode= true; - break; - } + const Type_handler *th= args[i]->type_handler(); + longstr_count+= dynamic_cast(th) != nullptr; + hybrid_count+= dynamic_cast(th) != nullptr; + binary_count+= args[i]->collation.collation == &my_charset_bin; } - if (binary_mode) + if (longstr_count == 2 && binary_count == 2 && hybrid_count == 0) { set_func_handler(&ha_bin_to_bin); return m_func_handler->fix_length_and_dec(this); @@ -8161,19 +8155,20 @@ bool Item_func_bit_or::fix_length_and_dec(THD *thd) static Func_handler_bit_or_int_to_ulonglong ha_int_to_ull; static Func_handler_bit_or_dec_to_ulonglong ha_dec_to_ull; - bool binary_mode= false; + DBUG_ASSERT(arg_count == 2); + uint longstr_count= 0; + uint binary_count= 0; + uint hybrid_count= 0; + for (uint i= 0; i < arg_count; i++) { - if (args[i]->result_type() == STRING_RESULT && - args[i]->collation.collation == &my_charset_bin && - dynamic_cast(args[i]->real_item()) == nullptr) - { - binary_mode= true; - break; - } + const Type_handler *th= args[i]->type_handler(); + longstr_count+= dynamic_cast(th) != nullptr; + hybrid_count+= dynamic_cast(th) != nullptr; + binary_count+= args[i]->collation.collation == &my_charset_bin; } - if (binary_mode) + if (longstr_count == 2 && binary_count == 2 && hybrid_count == 0) { set_func_handler(&ha_bin_to_bin); return m_func_handler->fix_length_and_dec(this); diff --git a/sql/item_sum.cc b/sql/item_sum.cc index d49161e80cb89..120d6c023dcab 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2614,15 +2614,13 @@ bool Item_sum_max::add() bool Item_sum_bit::fix_length_and_dec(THD *thd) { - // Check for binary string argument. - // Exclude GEOMETRY: it has STRING_RESULT + my_charset_bin but must be - // rejected via check_type_can_return_int() below with error 4079, not - // treated as a binary-mode operand. - m_binary_mode= false; - if (args[0]->result_type() == STRING_RESULT && - args[0]->collation.collation == &my_charset_bin && - args[0]->field_type() != MYSQL_TYPE_GEOMETRY && - dynamic_cast(args[0]->real_item()) == nullptr) + const Type_handler *th= args[0]->type_handler(); + bool is_longstr= dynamic_cast(th) != nullptr; + bool is_hybrid= dynamic_cast(th) != nullptr; + bool is_binary= args[0]->collation.collation == &my_charset_bin; + + m_binary_mode= is_longstr && is_binary && !is_hybrid; + if (m_binary_mode) { // Size guard if (args[0]->max_length > 512) @@ -2631,7 +2629,6 @@ bool Item_sum_bit::fix_length_and_dec(THD *thd) args[0]->max_length); return true; } - m_binary_mode= true; m_binary_length= args[0]->max_length; collation.set(&my_charset_bin, DERIVATION_COERCIBLE); max_length= m_binary_length; From cbf5aac549da9d45c1d27cf9e97ec49f72c8b82e Mon Sep 17 00:00:00 2001 From: kjarir Date: Fri, 19 Jun 2026 12:36:18 +0530 Subject: [PATCH 10/12] MDEV-10526: Use warning instead of error for mismatched bitwise operand lengths Per Alexander Barkov's review, replaced my_error() with push_warning_printf() (WARN_LEVEL_WARN) for length mismatches in the & | ^ binary string handlers. This matches how MariaDB handles similar truncation/conversion issues elsewhere - THD::raise_condition() automatically escalates WARN_LEVEL_WARN to WARN_LEVEL_ERROR when really_abort_on_warning() is true (i.e. strict SQL mode on INSERT/UPDATE/DELETE). Behavior: SELECT with mismatched lengths (non-strict) -> NULL + warning 4265, statement continues INSERT with mismatched lengths (strict mode) -> warning escalates to ER_INVALID_BITWISE_OPERANDS_SIZE error, statement aborts Updated Section 4 to test the strict-mode INSERT error path, and added Section 14 to test the non-strict SELECT warning path for all three operators (&, |, ^). Note: ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE (the 512-byte aggregate size guard) is unchanged and still uses my_error() - this is a separate hard limit, not a length-mismatch comparison, and was not part of Alexander's review comment. --- mysql-test/main/func_bitops_binary.result | 51 ++++++++++++++++++++--- mysql-test/main/func_bitops_binary.test | 42 ++++++++++++++++--- sql/item_func.cc | 18 ++++++-- 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/mysql-test/main/func_bitops_binary.result b/mysql-test/main/func_bitops_binary.result index 46f48c6748601..50cc8a742b9f3 100644 --- a/mysql-test/main/func_bitops_binary.result +++ b/mysql-test/main/func_bitops_binary.result @@ -46,19 +46,24 @@ HEX(a & b) HEX(a | b) HEX(a ^ b) HEX(~b) HEX(a << b) HEX(a >> b) NULL NULL NULL NULL NULL NULL DROP TABLE t_null; # ========================================================================= -# SECTION 4 — Mismatched length error +# SECTION 4 — Mismatched length error (in strict mode via INSERT) # ========================================================================= # Rationale: Verifies that operating on binary string operands with mismatched -# lengths throws ER_INVALID_BITWISE_OPERANDS_SIZE (error 4265). +# lengths throws ER_INVALID_BITWISE_OPERANDS_SIZE (error 4265) when escalating +# to an error under strict mode on INSERT. # ========================================================================= CREATE TABLE t_mismatch (a VARBINARY(4), b VARBINARY(2)); INSERT INTO t_mismatch VALUES (x'FFFF0000', x'FFFF'); -SELECT a & b FROM t_mismatch; +SET sql_mode='STRICT_TRANS_TABLES'; +CREATE TABLE t_insert_mismatch (res VARBINARY(4)); +INSERT INTO t_insert_mismatch SELECT a & b FROM t_mismatch; ERROR HY000: Mismatched length of operands for bitwise operator: 4 and 2 -SELECT a | b FROM t_mismatch; +INSERT INTO t_insert_mismatch SELECT a | b FROM t_mismatch; ERROR HY000: Mismatched length of operands for bitwise operator: 4 and 2 -SELECT a ^ b FROM t_mismatch; +INSERT INTO t_insert_mismatch SELECT a ^ b FROM t_mismatch; ERROR HY000: Mismatched length of operands for bitwise operator: 4 and 2 +DROP TABLE t_insert_mismatch; +SET sql_mode=DEFAULT; DROP TABLE t_mismatch; # ========================================================================= # SECTION 5 — Literal behavior (FINAL decided behavior) @@ -399,3 +404,39 @@ SELECT COERCIBILITY(a >> 1) FROM t_coerc; COERCIBILITY(a >> 1) 6 DROP TABLE t_coerc; +# ========================================================================= +# SECTION 14 — Mismatched length warning (outside strict mode via SELECT) +# ========================================================================= +# Rationale: Verifies that SELECT with mismatched lengths outside strict mode +# returns NULL with warning 4265 (ER_INVALID_BITWISE_OPERANDS_SIZE) for all +# three binary operators (&, |, ^). +# ========================================================================= +CREATE TABLE t_mismatch (a VARBINARY(4), b VARBINARY(2)); +INSERT INTO t_mismatch VALUES (x'FFFF0000', x'FFFF'); +SET sql_mode=''; +SELECT a & b FROM t_mismatch; +a & b +NULL +Warnings: +Warning 4265 Mismatched length of operands for bitwise operator: 4 and 2 +SHOW WARNINGS; +Level Code Message +Warning 4265 Mismatched length of operands for bitwise operator: 4 and 2 +SELECT a | b FROM t_mismatch; +a | b +NULL +Warnings: +Warning 4265 Mismatched length of operands for bitwise operator: 4 and 2 +SHOW WARNINGS; +Level Code Message +Warning 4265 Mismatched length of operands for bitwise operator: 4 and 2 +SELECT a ^ b FROM t_mismatch; +a ^ b +NULL +Warnings: +Warning 4265 Mismatched length of operands for bitwise operator: 4 and 2 +SHOW WARNINGS; +Level Code Message +Warning 4265 Mismatched length of operands for bitwise operator: 4 and 2 +SET sql_mode=DEFAULT; +DROP TABLE t_mismatch; diff --git a/mysql-test/main/func_bitops_binary.test b/mysql-test/main/func_bitops_binary.test index 08c398a679753..ff36cbaa040df 100644 --- a/mysql-test/main/func_bitops_binary.test +++ b/mysql-test/main/func_bitops_binary.test @@ -52,24 +52,30 @@ DROP TABLE t_null; --echo # ========================================================================= ---echo # SECTION 4 — Mismatched length error +--echo # SECTION 4 — Mismatched length error (in strict mode via INSERT) --echo # ========================================================================= --echo # Rationale: Verifies that operating on binary string operands with mismatched ---echo # lengths throws ER_INVALID_BITWISE_OPERANDS_SIZE (error 4265). +--echo # lengths throws ER_INVALID_BITWISE_OPERANDS_SIZE (error 4265) when escalating +--echo # to an error under strict mode on INSERT. --echo # ========================================================================= CREATE TABLE t_mismatch (a VARBINARY(4), b VARBINARY(2)); INSERT INTO t_mismatch VALUES (x'FFFF0000', x'FFFF'); +SET sql_mode='STRICT_TRANS_TABLES'; +CREATE TABLE t_insert_mismatch (res VARBINARY(4)); + --error ER_INVALID_BITWISE_OPERANDS_SIZE -SELECT a & b FROM t_mismatch; +INSERT INTO t_insert_mismatch SELECT a & b FROM t_mismatch; --error ER_INVALID_BITWISE_OPERANDS_SIZE -SELECT a | b FROM t_mismatch; +INSERT INTO t_insert_mismatch SELECT a | b FROM t_mismatch; --error ER_INVALID_BITWISE_OPERANDS_SIZE -SELECT a ^ b FROM t_mismatch; +INSERT INTO t_insert_mismatch SELECT a ^ b FROM t_mismatch; +DROP TABLE t_insert_mismatch; +SET sql_mode=DEFAULT; DROP TABLE t_mismatch; @@ -331,3 +337,29 @@ SELECT COERCIBILITY(a << 1) FROM t_coerc; SELECT COERCIBILITY(a >> 1) FROM t_coerc; DROP TABLE t_coerc; + + +--echo # ========================================================================= +--echo # SECTION 14 — Mismatched length warning (outside strict mode via SELECT) +--echo # ========================================================================= +--echo # Rationale: Verifies that SELECT with mismatched lengths outside strict mode +--echo # returns NULL with warning 4265 (ER_INVALID_BITWISE_OPERANDS_SIZE) for all +--echo # three binary operators (&, |, ^). +--echo # ========================================================================= + +CREATE TABLE t_mismatch (a VARBINARY(4), b VARBINARY(2)); +INSERT INTO t_mismatch VALUES (x'FFFF0000', x'FFFF'); + +SET sql_mode=''; + +SELECT a & b FROM t_mismatch; +SHOW WARNINGS; + +SELECT a | b FROM t_mismatch; +SHOW WARNINGS; + +SELECT a ^ b FROM t_mismatch; +SHOW WARNINGS; + +SET sql_mode=DEFAULT; +DROP TABLE t_mismatch; diff --git a/sql/item_func.cc b/sql/item_func.cc index cde7762a9532e..97990abc2af55 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -6950,7 +6950,11 @@ class Func_handler_bit_xor_bin_to_bin: public Item_handled_func::Handler_str if (a->length() != b->length()) { - my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), (int)a->length(), (int)b->length()); + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_BITWISE_OPERANDS_SIZE, + ER_THD(thd, ER_INVALID_BITWISE_OPERANDS_SIZE), + (int)a->length(), (int)b->length()); item->null_value= true; return nullptr; } @@ -7951,7 +7955,11 @@ class Func_handler_bit_and_bin_to_bin: public Item_handled_func::Handler_str if (a->length() != b->length()) { - my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), (int)a->length(), (int)b->length()); + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_BITWISE_OPERANDS_SIZE, + ER_THD(thd, ER_INVALID_BITWISE_OPERANDS_SIZE), + (int)a->length(), (int)b->length()); item->null_value= true; return nullptr; } @@ -8085,7 +8093,11 @@ class Func_handler_bit_or_bin_to_bin: public Item_handled_func::Handler_str if (a->length() != b->length()) { - my_error(ER_INVALID_BITWISE_OPERANDS_SIZE, MYF(0), (int)a->length(), (int)b->length()); + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_BITWISE_OPERANDS_SIZE, + ER_THD(thd, ER_INVALID_BITWISE_OPERANDS_SIZE), + (int)a->length(), (int)b->length()); item->null_value= true; return nullptr; } From 821455eabbadb5d9846f4456c1a8c237cc56f048 Mon Sep 17 00:00:00 2001 From: kjarir Date: Fri, 19 Jun 2026 18:09:52 +0530 Subject: [PATCH 11/12] MDEV-10526: Add window function support for binary mode aggregates Per Daniel Black's request, implement window function support for BIT_AND/BIT_OR/BIT_XOR in binary mode, mirroring the existing integer-mode sliding-window technique. Adds a dynamically-sized uint32 counter array (m_binary_bit_counters, sized m_binary_length * 8) allocated via thd->calloc() at setup_window_func() time. Each counter tracks how many active rows in the current window frame have a 1 bit at that position, enabling correct row removal as the frame slides without re-scanning the whole window - the same technique the existing 64-bit integer bit_counters[] array uses, extended to arbitrary byte lengths. add_binary_as_window()/remove_binary_as_window() increment/ decrement per-bit counters. set_bits_from_counters() in each subclass reconstructs the result byte-by-byte: OR: bit set if counter > 0 AND: bit set if counter == num_values_added XOR: bit set if counter is odd Length mismatches in window mode produce the same warning (ER_INVALID_BITWISE_OPERANDS_SIZE) as the non-window path. Verified with both full-partition aggregation and sliding-frame queries - the sliding frame test confirms remove_binary_as_window correctly evicts expired rows from the counters rather than just accumulating monotonically. Closes the window-function gap Daniel Black flagged as Priority 1 in his post-review feedback. --- mysql-test/main/func_bitops_binary.result | 35 ++++ mysql-test/main/func_bitops_binary.test | 31 ++++ sql/item_sum.cc | 195 +++++++++++++++++++++- sql/item_sum.h | 38 ++--- 4 files changed, 274 insertions(+), 25 deletions(-) diff --git a/mysql-test/main/func_bitops_binary.result b/mysql-test/main/func_bitops_binary.result index 50cc8a742b9f3..8375e4ccfd32b 100644 --- a/mysql-test/main/func_bitops_binary.result +++ b/mysql-test/main/func_bitops_binary.result @@ -440,3 +440,38 @@ Level Code Message Warning 4265 Mismatched length of operands for bitwise operator: 4 and 2 SET sql_mode=DEFAULT; DROP TABLE t_mismatch; +# ========================================================================= +# SECTION 15 — Window function support for binary mode aggregates +# ========================================================================= +# Rationale: Per Daniel Black's request, verify BIT_AND/OR/XOR work +# correctly as window functions on binary columns, including proper +# row removal when the window frame slides (not just full partition). +# ========================================================================= +CREATE TABLE t_win_part (grp INT, a VARBINARY(4)); +INSERT INTO t_win_part VALUES +(1, x'FF000000'), (1, x'0F0F0F0F'), +(2, x'00FF0000'), (2, x'000000FF'); +SELECT grp, HEX(a) as hex_a, +HEX(BIT_OR(a) OVER (PARTITION BY grp)) AS or_w, +HEX(BIT_AND(a) OVER (PARTITION BY grp)) AS and_w, +HEX(BIT_XOR(a) OVER (PARTITION BY grp)) AS xor_w +FROM t_win_part ORDER BY grp, hex_a; +grp hex_a or_w and_w xor_w +1 0F0F0F0F FF0F0F0F 0F000000 F00F0F0F +1 FF000000 FF0F0F0F 0F000000 F00F0F0F +2 000000FF 00FF00FF 00000000 00FF00FF +2 00FF0000 00FF00FF 00000000 00FF00FF +DROP TABLE t_win_part; +# Sliding window frame - proves remove_binary_as_window correctly +# evicts rows as the frame moves (not just accumulates everything) +CREATE TABLE t_win_slide (id INT, a VARBINARY(2)); +INSERT INTO t_win_slide VALUES +(1, x'FF00'), (2, x'0F0F'), (3, x'00FF'); +SELECT id, HEX(a) as hex_a, +HEX(BIT_OR(a) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)) AS sliding_or +FROM t_win_slide ORDER BY id; +id hex_a sliding_or +1 FF00 FF00 +2 0F0F FF0F +3 00FF 0FFF +DROP TABLE t_win_slide; diff --git a/mysql-test/main/func_bitops_binary.test b/mysql-test/main/func_bitops_binary.test index ff36cbaa040df..dc53d2f5a2560 100644 --- a/mysql-test/main/func_bitops_binary.test +++ b/mysql-test/main/func_bitops_binary.test @@ -363,3 +363,34 @@ SHOW WARNINGS; SET sql_mode=DEFAULT; DROP TABLE t_mismatch; + + +--echo # ========================================================================= +--echo # SECTION 15 — Window function support for binary mode aggregates +--echo # ========================================================================= +--echo # Rationale: Per Daniel Black's request, verify BIT_AND/OR/XOR work +--echo # correctly as window functions on binary columns, including proper +--echo # row removal when the window frame slides (not just full partition). +--echo # ========================================================================= + +CREATE TABLE t_win_part (grp INT, a VARBINARY(4)); +INSERT INTO t_win_part VALUES + (1, x'FF000000'), (1, x'0F0F0F0F'), + (2, x'00FF0000'), (2, x'000000FF'); +SELECT grp, HEX(a) as hex_a, + HEX(BIT_OR(a) OVER (PARTITION BY grp)) AS or_w, + HEX(BIT_AND(a) OVER (PARTITION BY grp)) AS and_w, + HEX(BIT_XOR(a) OVER (PARTITION BY grp)) AS xor_w +FROM t_win_part ORDER BY grp, hex_a; +DROP TABLE t_win_part; + +--echo # Sliding window frame - proves remove_binary_as_window correctly +--echo # evicts rows as the frame moves (not just accumulates everything) +CREATE TABLE t_win_slide (id INT, a VARBINARY(2)); +INSERT INTO t_win_slide VALUES + (1, x'FF00'), (2, x'0F0F'), (3, x'00FF'); +SELECT id, HEX(a) as hex_a, + HEX(BIT_OR(a) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)) AS sliding_or +FROM t_win_slide ORDER BY id; +DROP TABLE t_win_slide; + diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 120d6c023dcab..4e0b97a9e6d74 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2652,14 +2652,38 @@ longlong Item_sum_bit::val_int() } -void Item_sum_bit::clear() +Item_sum_bit::Item_sum_bit(THD *thd, Item_sum_bit *item): + Item_sum_int(thd, item), reset_bits(item->reset_bits), bits(item->bits), + as_window_function(item->as_window_function), + num_values_added(item->num_values_added), + m_binary_bit_counters(nullptr), + m_binary_mode(item->m_binary_mode), + m_binary_length(item->m_binary_length) { if (m_binary_mode) { - reset_binary_accumulator(); - return; + if (item->m_binary_bit_counters) + { + m_binary_bit_counters= thd->alloc(m_binary_length * 8); + if (m_binary_bit_counters) + memcpy(m_binary_bit_counters, item->m_binary_bit_counters, + m_binary_length * 8 * sizeof(uint32)); + } + } + else if (as_window_function) + { + memcpy(bit_counters, item->bit_counters, sizeof(bit_counters)); } - bits= reset_bits; + m_str_value.copy(item->m_str_value); +} + + +void Item_sum_bit::clear() +{ + if (m_binary_mode) + reset_binary_accumulator(); + else + bits= reset_bits; if (as_window_function) clear_as_window(); } @@ -2683,14 +2707,101 @@ Item *Item_sum_or::copy_or_same(THD* thd) return new (thd->mem_root) Item_sum_or(thd, this); } +void Item_sum_bit::setup_window_func(THD *thd, Window_spec *window_spec) +{ + if (m_binary_mode) + { + m_binary_bit_counters= thd->calloc(m_binary_length * 8); + if (!m_binary_bit_counters) + return; + } + as_window_function= TRUE; + clear_as_window(); +} + bool Item_sum_bit::clear_as_window() { - memset(bit_counters, 0, sizeof(bit_counters)); + if (m_binary_mode) + { + if (m_binary_bit_counters) + memset(m_binary_bit_counters, 0, m_binary_length * 8 * sizeof(uint32)); + } + else + memset(bit_counters, 0, sizeof(bit_counters)); num_values_added= 0; set_bits_from_counters(); return 0; } +bool Item_sum_bit::add_binary_as_window(const String &value) +{ + DBUG_ASSERT(as_window_function); + if (!m_binary_bit_counters) + return true; + + if (value.length() != m_binary_length) + { + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_BITWISE_OPERANDS_SIZE, + ER_THD(thd, ER_INVALID_BITWISE_OPERANDS_SIZE), + (int)value.length(), (int)m_binary_length); + return true; + } + + const uchar *val_ptr= (const uchar*) value.ptr(); + for (uint b_idx= 0; b_idx < m_binary_length; ++b_idx) + { + uchar byte_val= val_ptr[b_idx]; + for (int bit= 0; bit < 8; ++bit) + { + if (byte_val & (1 << bit)) + m_binary_bit_counters[b_idx * 8 + bit]++; + } + } + num_values_added++; + set_bits_from_counters(); + return 0; +} + +bool Item_sum_bit::remove_binary_as_window(const String &value) +{ + DBUG_ASSERT(as_window_function); + if (!m_binary_bit_counters) + return true; + + if (value.length() != m_binary_length) + { + THD *thd= current_thd; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_INVALID_BITWISE_OPERANDS_SIZE, + ER_THD(thd, ER_INVALID_BITWISE_OPERANDS_SIZE), + (int)value.length(), (int)m_binary_length); + return true; + } + + if (num_values_added == 0) + return 0; + + const uchar *val_ptr= (const uchar*) value.ptr(); + for (uint b_idx= 0; b_idx < m_binary_length; ++b_idx) + { + uchar byte_val= val_ptr[b_idx]; + for (int bit= 0; bit < 8; ++bit) + { + if (byte_val & (1 << bit)) + { + uint32 &counter= m_binary_bit_counters[b_idx * 8 + bit]; + if (counter) + counter--; + } + } + } + num_values_added--; + set_bits_from_counters(); + return 0; +} + bool Item_sum_bit::remove_as_window(ulonglong value) { DBUG_ASSERT(as_window_function); @@ -2729,6 +2840,27 @@ bool Item_sum_bit::add_as_window(ulonglong value) void Item_sum_or::set_bits_from_counters() { + if (m_binary_mode) + { + if (!m_binary_bit_counters) + return; + if (m_str_value.alloc(m_binary_length)) + return; + m_str_value.length(m_binary_length); + + uchar *acc= (uchar*) m_str_value.ptr(); + for (uint b_idx= 0; b_idx < m_binary_length; ++b_idx) + { + uchar byte_val= 0; + for (int bit= 0; bit < 8; ++bit) + { + if (m_binary_bit_counters[b_idx * 8 + bit] > 0) + byte_val|= (1 << bit); + } + acc[b_idx]= byte_val; + } + return; + } ulonglong value= 0; for (uint i= 0; i < NUM_BIT_COUNTERS; i++) { @@ -2758,6 +2890,8 @@ bool Item_sum_or::add() (int)arg->length(), (int)m_binary_length); return true; } + if (as_window_function) + return add_binary_as_window(*arg); uchar *acc= (uchar*) m_str_value.ptr(); const uchar *val= (const uchar*) arg->ptr(); for (uint i= 0; i < m_binary_length; i++) @@ -2776,6 +2910,27 @@ bool Item_sum_or::add() void Item_sum_xor::set_bits_from_counters() { + if (m_binary_mode) + { + if (!m_binary_bit_counters) + return; + if (m_str_value.alloc(m_binary_length)) + return; + m_str_value.length(m_binary_length); + + uchar *acc= (uchar*) m_str_value.ptr(); + for (uint b_idx= 0; b_idx < m_binary_length; ++b_idx) + { + uchar byte_val= 0; + for (int bit= 0; bit < 8; ++bit) + { + if (m_binary_bit_counters[b_idx * 8 + bit] % 2 != 0) + byte_val|= (1 << bit); + } + acc[b_idx]= byte_val; + } + return; + } ulonglong value= 0; for (int i= 0; i < NUM_BIT_COUNTERS; i++) { @@ -2811,6 +2966,8 @@ bool Item_sum_xor::add() (int)arg->length(), (int)m_binary_length); return true; } + if (as_window_function) + return add_binary_as_window(*arg); uchar *acc= (uchar*) m_str_value.ptr(); const uchar *val= (const uchar*) arg->ptr(); for (uint i= 0; i < m_binary_length; i++) @@ -2829,6 +2986,32 @@ bool Item_sum_xor::add() void Item_sum_and::set_bits_from_counters() { + if (m_binary_mode) + { + if (!m_binary_bit_counters) + return; + if (m_str_value.alloc(m_binary_length)) + return; + m_str_value.length(m_binary_length); + + uchar *acc= (uchar*) m_str_value.ptr(); + if (num_values_added == 0) + { + memset(acc, 0xFF, m_binary_length); + return; + } + for (uint b_idx= 0; b_idx < m_binary_length; ++b_idx) + { + uchar byte_val= 0; + for (int bit= 0; bit < 8; ++bit) + { + if (m_binary_bit_counters[b_idx * 8 + bit] == num_values_added) + byte_val|= (1 << bit); + } + acc[b_idx]= byte_val; + } + return; + } ulonglong value= 0; if (!num_values_added) { @@ -2871,6 +3054,8 @@ bool Item_sum_and::add() (int)arg->length(), (int)m_binary_length); return true; } + if (as_window_function) + return add_binary_as_window(*arg); uchar *acc= (uchar*) m_str_value.ptr(); const uchar *val= (const uchar*) arg->ptr(); for (uint i= 0; i < m_binary_length; i++) diff --git a/sql/item_sum.h b/sql/item_sum.h index 6ae68bc150cd3..2cc80f4d0f1fe 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -1260,19 +1260,10 @@ class Item_sum_bit :public Item_sum_int public: Item_sum_bit(THD *thd, Item *item_par, ulonglong reset_arg): Item_sum_int(thd, item_par), reset_bits(reset_arg), bits(reset_arg), - as_window_function(FALSE), num_values_added(0), m_binary_mode(FALSE), + as_window_function(FALSE), num_values_added(0), + m_binary_bit_counters(nullptr), m_binary_mode(FALSE), m_binary_length(0) {} - Item_sum_bit(THD *thd, Item_sum_bit *item): - Item_sum_int(thd, item), reset_bits(item->reset_bits), bits(item->bits), - as_window_function(item->as_window_function), - num_values_added(item->num_values_added), - m_binary_mode(item->m_binary_mode), - m_binary_length(item->m_binary_length) - { - if (as_window_function) - memcpy(bit_counters, item->bit_counters, sizeof(bit_counters)); - m_str_value.copy(item->m_str_value); - } + Item_sum_bit(THD *thd, Item_sum_bit *item); enum Sumfunctype sum_func () const override { return SUM_BIT_FUNC;} void clear() override; longlong val_int() override; @@ -1292,22 +1283,25 @@ class Item_sum_bit :public Item_sum_int void cleanup() override { bits= reset_bits; + m_binary_bit_counters= nullptr; if (as_window_function) clear_as_window(); Item_sum_int::cleanup(); } - void setup_window_func(THD *thd, Window_spec *window_spec) override - { - if (m_binary_mode) - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "window functions in binary mode"); - as_window_function= TRUE; - clear_as_window(); - } + void setup_window_func(THD *thd, Window_spec *window_spec) override; void remove() override { if (as_window_function) { - remove_as_window(args[0]->val_int()); + if (m_binary_mode) + { + StringBuffer<512> arg_buf; + String *arg= args[0]->val_str(&arg_buf); + if (arg) + remove_binary_as_window(*arg); + } + else + remove_as_window(args[0]->val_int()); return; } // Unless we're counting bits, we can not remove anything. @@ -1330,9 +1324,13 @@ class Item_sum_bit :public Item_sum_int // this additional information. ulonglong num_values_added; ulonglong bit_counters[NUM_BIT_COUNTERS]; + uint32 *m_binary_bit_counters; bool add_as_window(ulonglong value); bool remove_as_window(ulonglong value); bool clear_as_window(); + bool add_binary_as_window(const String &value); + bool remove_binary_as_window(const String &value); + bool clear_binary_as_window(); virtual void set_bits_from_counters()= 0; bool m_binary_mode; String m_str_value; // binary accumulator From 66410eef186f903983fd802c1ccef8aa75ace690 Mon Sep 17 00:00:00 2001 From: kjarir Date: Sat, 20 Jun 2026 13:37:07 +0530 Subject: [PATCH 12/12] MDEV-10526: Add Spider direct_add() support for bitwise aggregates Per Daniel Black's request, implement Spider pushdown for BIT_AND/BIT_OR/BIT_XOR, allowing each remote shard to compute its own partial aggregate, which is then merged locally via direct_add() rather than fetching and re-aggregating all rows on the coordinator. This mirrors the existing pattern used by SUM/COUNT/MIN/MAX. Correctness rationale: BIT_AND/OR/XOR are associative and commutative, so BIT_OP(all_rows) == BIT_OP(BIT_OP(shard1), BIT_OP(shard2), ...) - identical to how SUM pushdown is valid. sql/item_sum.h, sql/item_sum.cc: Added direct_added/direct_reseted_field/direct_sum_is_null state and direct_bits (integer)/direct_str_value (binary) storage to Item_sum_bit, following the Item_sum_sum pattern. Two direct_add() overloads handle both modes. add() in each subclass (Or/And/Xor) checks direct_added first and merges with the operator-specific logic (|=, &=, ^=) for both integer and binary mode. reset_field()/update_field() updated to consume direct values the same way Item_sum_sum does. DBUG_ASSERT(!as_window_function) guards direct_add(), since Spider has no window function pushdown awareness (confirmed via code search - zero references in storage/spider/). storage/spider/spd_db_mysql.cc: SUM_BIT_FUNC added to the pushdown serialization case in open_item_sum_func(), removed from the skip list. Reuses the existing func_name_cstring() ('bit_or(', 'bit_and(', 'bit_xor(') serialization path shared with COUNT/SUM/MIN/MAX. storage/spider/spd_db_conn.cc: SUM_BIT_FUNC case added to spider_db_fetch_for_item_sum_func(). Binary mode (result_type()==STRING_RESULT) reads the raw row via append_to_str() and calls direct_add(String*, bool). Integer mode required a fix: row->val_int() uses atoi(), a 32-bit signed parser with silent overflow on values beyond INT_MAX - found via testing that pushdown ON vs OFF returned different results for a BIGINT UNSIGNED column with high bits set. Fixed by parsing the raw row string with my_strtoll10() instead (MariaDB's proper 64-bit parser, already used elsewhere in Spider for SHOW TABLE STATUS fields), preserving the full unsigned 64-bit value via bit-pattern-preserving signed/unsigned cast. Verified with a new test (direct_aggregate_bit.test) using a 2-shard Spider table with BIGINT UNSIGNED and VARBINARY columns: - Confirmed via Spider_direct_aggregate status counter that pushdown genuinely occurs (not silently falling back) - Confirmed via filtered mysql.general_log inspection that bit_or(/bit_and(/bit_xor( are the actual SQL sent to each remote shard - Confirmed direct_aggregate=1 and direct_aggregate=0 produce identical results for both binary and integer mode (proves correctness independent of pushdown) - Full spider suite (94 tests) and core regression suite (func_bitops_binary, func_bit, func_json, gis) pass with zero regressions --- sql/item_sum.cc | 151 +++++++++++++++++- sql/item_sum.h | 11 +- .../spider/r/direct_aggregate_bit.result | 129 +++++++++++++++ .../spider/t/direct_aggregate_bit.test | 150 +++++++++++++++++ storage/spider/spd_db_conn.cc | 52 +++++- storage/spider/spd_db_mysql.cc | 2 +- 6 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 storage/spider/mysql-test/spider/r/direct_aggregate_bit.result create mode 100644 storage/spider/mysql-test/spider/t/direct_aggregate_bit.test diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 4e0b97a9e6d74..793ed7464a9fc 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2658,7 +2658,11 @@ Item_sum_bit::Item_sum_bit(THD *thd, Item_sum_bit *item): num_values_added(item->num_values_added), m_binary_bit_counters(nullptr), m_binary_mode(item->m_binary_mode), - m_binary_length(item->m_binary_length) + m_binary_length(item->m_binary_length), + direct_added(item->direct_added), + direct_reseted_field(item->direct_reseted_field), + direct_sum_is_null(item->direct_sum_is_null), + direct_bits(item->direct_bits) { if (m_binary_mode) { @@ -2675,9 +2679,29 @@ Item_sum_bit::Item_sum_bit(THD *thd, Item_sum_bit *item): memcpy(bit_counters, item->bit_counters, sizeof(bit_counters)); } m_str_value.copy(item->m_str_value); + direct_str_value.copy(item->direct_str_value); } +void Item_sum_bit::direct_add(ulonglong add_bits, bool is_null) +{ + DBUG_ASSERT(!as_window_function); + direct_bits= add_bits; + direct_sum_is_null= is_null; + direct_added= true; +} + +void Item_sum_bit::direct_add(const String *add_str, bool is_null) +{ + DBUG_ASSERT(!as_window_function); + if (!is_null && add_str) + direct_str_value.copy(*add_str); + direct_sum_is_null= is_null; + direct_added= true; +} + + + void Item_sum_bit::clear() { if (m_binary_mode) @@ -2881,6 +2905,19 @@ bool Item_sum_or::add() { if (m_binary_mode) { + if (unlikely(direct_added)) + { + direct_added= FALSE; + if (!direct_sum_is_null) + { + null_value= 0; + uchar *acc= (uchar*) m_str_value.ptr(); + const uchar *val= (const uchar*) direct_str_value.ptr(); + for (uint i= 0; i < m_binary_length; i++) + acc[i]|= val[i]; + } + return 0; + } StringBuffer<512> arg_buf; String *arg= args[0]->val_str(&arg_buf); if (!arg) return 0; // NULL - skip @@ -2898,6 +2935,16 @@ bool Item_sum_or::add() acc[i]|= val[i]; return 0; } + if (unlikely(direct_added)) + { + direct_added= FALSE; + if (!direct_sum_is_null) + { + null_value= 0; + bits|= direct_bits; + } + return 0; + } ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) { @@ -2957,6 +3004,19 @@ bool Item_sum_xor::add() { if (m_binary_mode) { + if (unlikely(direct_added)) + { + direct_added= FALSE; + if (!direct_sum_is_null) + { + null_value= 0; + uchar *acc= (uchar*) m_str_value.ptr(); + const uchar *val= (const uchar*) direct_str_value.ptr(); + for (uint i= 0; i < m_binary_length; i++) + acc[i]^= val[i]; + } + return 0; + } StringBuffer<512> arg_buf; String *arg= args[0]->val_str(&arg_buf); if (!arg) return 0; // NULL - skip @@ -2974,6 +3034,16 @@ bool Item_sum_xor::add() acc[i]^= val[i]; return 0; } + if (unlikely(direct_added)) + { + direct_added= FALSE; + if (!direct_sum_is_null) + { + null_value= 0; + bits^= direct_bits; + } + return 0; + } ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) { @@ -3045,6 +3115,19 @@ bool Item_sum_and::add() { if (m_binary_mode) { + if (unlikely(direct_added)) + { + direct_added= FALSE; + if (!direct_sum_is_null) + { + null_value= 0; + uchar *acc= (uchar*) m_str_value.ptr(); + const uchar *val= (const uchar*) direct_str_value.ptr(); + for (uint i= 0; i < m_binary_length; i++) + acc[i]&= val[i]; + } + return 0; + } StringBuffer<512> arg_buf; String *arg= args[0]->val_str(&arg_buf); if (!arg) return 0; // NULL - skip @@ -3062,6 +3145,16 @@ bool Item_sum_and::add() acc[i]&= val[i]; return 0; } + if (unlikely(direct_added)) + { + direct_added= FALSE; + if (!direct_sum_is_null) + { + null_value= 0; + bits&= direct_bits; + } + return 0; + } ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) { @@ -3265,6 +3358,23 @@ void Item_sum_bit::reset_field() { if (m_binary_mode) { + if (unlikely(direct_added)) + { + direct_added= FALSE; + direct_reseted_field= TRUE; + null_value= direct_sum_is_null; + if (null_value) + { + result_field->set_null(); + result_field->reset(); + } + else + { + result_field->set_notnull(); + result_field->store(direct_str_value.ptr(), direct_str_value.length(), &my_charset_bin); + } + return; + } reset_and_add(); if (null_value) { @@ -3278,6 +3388,19 @@ void Item_sum_bit::reset_field() } return; } + if (unlikely(direct_added)) + { + direct_added= FALSE; + direct_reseted_field= TRUE; + null_value= direct_sum_is_null; + bits= null_value ? reset_bits : direct_bits; + if (null_value) + result_field->set_null(); + else + result_field->set_notnull(); + int8store(result_field->ptr, bits); + return; + } reset_and_add(); int8store(result_field->ptr, bits); } @@ -3293,12 +3416,23 @@ void Item_sum_bit::update_field() if (!result_field->is_null()) { m_str_value.copy(*res); + if (unlikely(direct_added || direct_reseted_field)) + { + direct_added= TRUE; + direct_reseted_field= FALSE; + } add(); result_field->set_notnull(); result_field->store(m_str_value.ptr(), m_str_value.length(), &my_charset_bin); } else { + reset_binary_accumulator(); + if (unlikely(direct_added || direct_reseted_field)) + { + direct_added= TRUE; + direct_reseted_field= FALSE; + } add(); if (!null_value) { @@ -3313,8 +3447,21 @@ void Item_sum_bit::update_field() // the result of the bit function becomes erroneous. DBUG_ASSERT(!as_window_function); uchar *res=result_field->ptr; - bits= uint8korr(res); + if (unlikely(direct_added || direct_reseted_field)) + { + direct_added= TRUE; + direct_reseted_field= FALSE; + bits= result_field->is_null() ? reset_bits : uint8korr(res); + } + else + { + bits= uint8korr(res); + } add(); + if (null_value) + result_field->set_null(); + else + result_field->set_notnull(); int8store(res, bits); } diff --git a/sql/item_sum.h b/sql/item_sum.h index 2cc80f4d0f1fe..c29546006ad90 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -1262,7 +1262,7 @@ class Item_sum_bit :public Item_sum_int Item_sum_int(thd, item_par), reset_bits(reset_arg), bits(reset_arg), as_window_function(FALSE), num_values_added(0), m_binary_bit_counters(nullptr), m_binary_mode(FALSE), - m_binary_length(0) {} + m_binary_length(0), direct_added(FALSE), direct_reseted_field(FALSE) {} Item_sum_bit(THD *thd, Item_sum_bit *item); enum Sumfunctype sum_func () const override { return SUM_BIT_FUNC;} void clear() override; @@ -1286,6 +1286,8 @@ class Item_sum_bit :public Item_sum_int m_binary_bit_counters= nullptr; if (as_window_function) clear_as_window(); + direct_added= FALSE; + direct_reseted_field= FALSE; Item_sum_int::cleanup(); } void setup_window_func(THD *thd, Window_spec *window_spec) override; @@ -1312,6 +1314,8 @@ class Item_sum_bit :public Item_sum_int { return true; } + void direct_add(ulonglong add_bits, bool is_null); + void direct_add(const String *add_str, bool is_null); protected: enum bit_counters { NUM_BIT_COUNTERS= 64 }; @@ -1336,6 +1340,11 @@ class Item_sum_bit :public Item_sum_int String m_str_value; // binary accumulator uint m_binary_length; // byte length of binary operand virtual void reset_binary_accumulator() {} + bool direct_added; + bool direct_reseted_field; + bool direct_sum_is_null; + ulonglong direct_bits; + String direct_str_value; }; diff --git a/storage/spider/mysql-test/spider/r/direct_aggregate_bit.result b/storage/spider/mysql-test/spider/r/direct_aggregate_bit.result new file mode 100644 index 0000000000000..69b889033d6b4 --- /dev/null +++ b/storage/spider/mysql-test/spider/r/direct_aggregate_bit.result @@ -0,0 +1,129 @@ +for master_1 +for child2 +child2_1 +child2_2 +child2_3 +for child3 +child3_1 +child3_2 +child3_3 + +drop and create databases +connection master_1; +DROP DATABASE IF EXISTS auto_test_local; +CREATE DATABASE auto_test_local; +USE auto_test_local; +connection child2_1; +DROP DATABASE IF EXISTS auto_test_remote; +CREATE DATABASE auto_test_remote; +USE auto_test_remote; +connection child2_2; +DROP DATABASE IF EXISTS auto_test_remote2; +CREATE DATABASE auto_test_remote2; +USE auto_test_remote2; + +create child tables + +create spider table on master +connection master_1; +CREATE TABLE ta_l2 ( +id INT, +val_int BIGINT UNSIGNED, +val_bin VARBINARY(4), +PRIMARY KEY(id) +) MASTER_1_ENGINE MASTER_1_COMMENT2_P_2_1 +Warnings: +Warning 138 Spider table params in COMMENT or CONNECTION strings have been deprecated and will be removed in a future release. Please use table options instead. +Warning 138 Spider table params in COMMENT or CONNECTION strings have been deprecated and will be removed in a future release. Please use table options instead. +Warning 138 Spider table params in COMMENT or CONNECTION strings have been deprecated and will be removed in a future release. Please use table options instead. +Warning 138 Spider table params in COMMENT or CONNECTION strings have been deprecated and will be removed in a future release. Please use table options instead. +set @old_spider_direct_aggregate=@@session.spider_direct_aggregate; +set spider_direct_aggregate=1; + +Direct aggregate execution verification +SHOW STATUS LIKE 'Spider_direct_aggregate'; +Variable_name Value +Spider_direct_aggregate 0 +SELECT HEX(BIT_OR(val_bin)), HEX(BIT_AND(val_bin)), HEX(BIT_XOR(val_bin)) FROM ta_l2; +HEX(BIT_OR(val_bin)) HEX(BIT_AND(val_bin)) HEX(BIT_XOR(val_bin)) +FFFFFFFF 00000000 FFF0F0F0 +SHOW STATUS LIKE 'Spider_direct_aggregate'; +Variable_name Value +Spider_direct_aggregate 6 +SELECT BIT_OR(val_int), BIT_AND(val_int), BIT_XOR(val_int) FROM ta_l2; +BIT_OR(val_int) BIT_AND(val_int) BIT_XOR(val_int) +18446744069414584575 0 18446744069414584575 +SHOW STATUS LIKE 'Spider_direct_aggregate'; +Variable_name Value +Spider_direct_aggregate 12 + +Disable direct aggregate and verify correctness +SET spider_direct_aggregate=0; +SELECT HEX(BIT_OR(val_bin)), HEX(BIT_AND(val_bin)), HEX(BIT_XOR(val_bin)) FROM ta_l2; +HEX(BIT_OR(val_bin)) HEX(BIT_AND(val_bin)) HEX(BIT_XOR(val_bin)) +FFFFFFFF 00000000 FFF0F0F0 +SELECT BIT_OR(val_int), BIT_AND(val_int), BIT_XOR(val_int) FROM ta_l2; +BIT_OR(val_int) BIT_AND(val_int) BIT_XOR(val_int) +18446744069414584575 0 18446744069414584575 +SHOW STATUS LIKE 'Spider_direct_aggregate'; +Variable_name Value +Spider_direct_aggregate 12 +set spider_direct_aggregate=@old_spider_direct_aggregate; + +Verify pushed-down aggregate SQL sent to remote child nodes +connection child2_2; +SELECT argument FROM mysql.general_log +WHERE command_type != 'Execute' + AND (argument LIKE '%bit_or%' OR argument LIKE '%bit_and%' OR argument LIKE '%bit_xor%') +ORDER BY event_time; +argument +select bit_xor(`val_bin`),bit_and(`val_bin`),bit_or(`val_bin`),min(`val_bin`) from `auto_test_remote2`.`ta_r3` +select bit_or(`val_int`),bit_and(`val_int`),bit_xor(`val_int`),min(`val_int`) from `auto_test_remote2`.`ta_r3` +SELECT argument FROM mysql.general_log +WHERE command_type != 'Execute' + AND (argument LIKE '%bit_or%' OR argument LIKE '%bit_and%' OR argument LIKE '%bit_xor%') +ORDER BY event_time +SELECT id, val_int, HEX(val_bin) FROM ta_r3 ORDER BY id; +id val_int HEX(val_bin) +4 280375465082880 0000FF00 +5 1095216660735 000000FF +connection child2_1; +SELECT argument FROM mysql.general_log +WHERE command_type != 'Execute' + AND (argument LIKE '%bit_or%' OR argument LIKE '%bit_and%' OR argument LIKE '%bit_xor%') +ORDER BY event_time; +argument +select bit_xor(`val_bin`),bit_and(`val_bin`),bit_or(`val_bin`),min(`val_bin`) from `auto_test_remote`.`ta_r2` +select bit_or(`val_int`),bit_and(`val_int`),bit_xor(`val_int`),min(`val_int`) from `auto_test_remote`.`ta_r2` +SELECT argument FROM mysql.general_log +WHERE command_type != 'Execute' + AND (argument LIKE '%bit_or%' OR argument LIKE '%bit_and%' OR argument LIKE '%bit_xor%') +ORDER BY event_time +SELECT id, val_int, HEX(val_bin) FROM ta_r2 ORDER BY id; +id val_int HEX(val_bin) +1 17293822569102704640 F0000000 +2 1080863910568919040 0F0F0F0F +3 71776119061217280 00FF0000 + +deinit +connection master_1; +DROP DATABASE IF EXISTS auto_test_local; +connection child2_1; +SET @@global.general_log = @general_log_backup; +SET GLOBAL log_output = @old_log_output; +DROP DATABASE IF EXISTS auto_test_remote; +connection child2_2; +SET @@global.general_log = @general_log_backup; +SET GLOBAL log_output = @old_log_output; +DROP DATABASE IF EXISTS auto_test_remote2; +for master_1 +for child2 +child2_1 +child2_2 +child2_3 +for child3 +child3_1 +child3_2 +child3_3 + +end of test diff --git a/storage/spider/mysql-test/spider/t/direct_aggregate_bit.test b/storage/spider/mysql-test/spider/t/direct_aggregate_bit.test new file mode 100644 index 0000000000000..58032311368dd --- /dev/null +++ b/storage/spider/mysql-test/spider/t/direct_aggregate_bit.test @@ -0,0 +1,150 @@ +--disable_warnings +--disable_query_log +--disable_result_log +--source test_init.inc +--enable_result_log +--enable_query_log + + +--echo +--echo drop and create databases +--connection master_1 +DROP DATABASE IF EXISTS auto_test_local; +CREATE DATABASE auto_test_local; +USE auto_test_local; + --connection child2_1 + DROP DATABASE IF EXISTS auto_test_remote; + CREATE DATABASE auto_test_remote; + USE auto_test_remote; + --connection child2_2 + DROP DATABASE IF EXISTS auto_test_remote2; + CREATE DATABASE auto_test_remote2; + USE auto_test_remote2; +--enable_warnings + +--echo +--echo create child tables + --disable_query_log + --disable_result_log + --connection child2_2 + --disable_warnings + DROP TABLE IF EXISTS ta_r3; + --enable_warnings + CREATE TABLE ta_r3 ( + id INT, + val_int BIGINT UNSIGNED, + val_bin VARBINARY(4), + PRIMARY KEY(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + SET @old_log_output = @@global.log_output; + SET GLOBAL log_output = 'TABLE,FILE'; + SET @general_log_backup = @@global.general_log; + SET @@global.general_log = 1; + TRUNCATE TABLE mysql.general_log; + + --connection child2_1 + --disable_warnings + DROP TABLE IF EXISTS ta_r2; + --enable_warnings + CREATE TABLE ta_r2 ( + id INT, + val_int BIGINT UNSIGNED, + val_bin VARBINARY(4), + PRIMARY KEY(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + SET @old_log_output = @@global.log_output; + SET GLOBAL log_output = 'TABLE,FILE'; + SET @general_log_backup = @@global.general_log; + SET @@global.general_log = 1; + TRUNCATE TABLE mysql.general_log; + --enable_query_log + --enable_result_log + +--echo +--echo create spider table on master + --connection master_1 + --disable_query_log + echo CREATE TABLE ta_l2 ( + id INT, + val_int BIGINT UNSIGNED, + val_bin VARBINARY(4), + PRIMARY KEY(id) + ) MASTER_1_ENGINE MASTER_1_COMMENT2_P_2_1; + CREATE TABLE ta_l2 ( + id INT, + val_int BIGINT UNSIGNED, + val_bin VARBINARY(4), + PRIMARY KEY(id) + ) ENGINE=Spider COMMENT='table "ta_r3"' + PARTITION BY RANGE(id) ( + PARTITION pt1 VALUES LESS THAN (4) COMMENT='srv "s_2_1", table "ta_r2", priority "1000"', + PARTITION pt2 VALUES LESS THAN MAXVALUE COMMENT='srv "s_2_2", priority "1000001"' + ); + --disable_ps_protocol + INSERT INTO ta_l2 (id, val_int, val_bin) VALUES + (1, 0xF000000000000000, x'F0000000'), + (2, 0x0F00000000000000, x'0F0F0F0F'), + (3, 0x00FF000000000000, x'00FF0000'), + (4, 0x0000FF0000000000, x'0000FF00'), + (5, 0x000000FF000000FF, x'000000FF'); + --enable_ps_protocol + --enable_query_log + + --disable_ps2_protocol + set @old_spider_direct_aggregate=@@session.spider_direct_aggregate; + set spider_direct_aggregate=1; + +--echo +--echo Direct aggregate execution verification +SHOW STATUS LIKE 'Spider_direct_aggregate'; +SELECT HEX(BIT_OR(val_bin)), HEX(BIT_AND(val_bin)), HEX(BIT_XOR(val_bin)) FROM ta_l2; +SHOW STATUS LIKE 'Spider_direct_aggregate'; +SELECT BIT_OR(val_int), BIT_AND(val_int), BIT_XOR(val_int) FROM ta_l2; +SHOW STATUS LIKE 'Spider_direct_aggregate'; + +--echo +--echo Disable direct aggregate and verify correctness +SET spider_direct_aggregate=0; +SELECT HEX(BIT_OR(val_bin)), HEX(BIT_AND(val_bin)), HEX(BIT_XOR(val_bin)) FROM ta_l2; +SELECT BIT_OR(val_int), BIT_AND(val_int), BIT_XOR(val_int) FROM ta_l2; +SHOW STATUS LIKE 'Spider_direct_aggregate'; + +set spider_direct_aggregate=@old_spider_direct_aggregate; +--enable_ps2_protocol + +--echo +--echo Verify pushed-down aggregate SQL sent to remote child nodes + --connection child2_2 + SELECT argument FROM mysql.general_log + WHERE command_type != 'Execute' + AND (argument LIKE '%bit_or%' OR argument LIKE '%bit_and%' OR argument LIKE '%bit_xor%') + ORDER BY event_time; + SELECT id, val_int, HEX(val_bin) FROM ta_r3 ORDER BY id; + --connection child2_1 + SELECT argument FROM mysql.general_log + WHERE command_type != 'Execute' + AND (argument LIKE '%bit_or%' OR argument LIKE '%bit_and%' OR argument LIKE '%bit_xor%') + ORDER BY event_time; + SELECT id, val_int, HEX(val_bin) FROM ta_r2 ORDER BY id; + +--echo +--echo deinit +--disable_warnings +--connection master_1 +DROP DATABASE IF EXISTS auto_test_local; + --connection child2_1 + SET @@global.general_log = @general_log_backup; + SET GLOBAL log_output = @old_log_output; + DROP DATABASE IF EXISTS auto_test_remote; + --connection child2_2 + SET @@global.general_log = @general_log_backup; + SET GLOBAL log_output = @old_log_output; + DROP DATABASE IF EXISTS auto_test_remote2; +--disable_query_log +--disable_result_log +--source test_deinit.inc +--enable_result_log +--enable_query_log +--enable_warnings +--echo +--echo end of test diff --git a/storage/spider/spd_db_conn.cc b/storage/spider/spd_db_conn.cc index ea3d2978cf0ca..5c5ccb26c19ed 100644 --- a/storage/spider/spd_db_conn.cc +++ b/storage/spider/spd_db_conn.cc @@ -2191,13 +2191,63 @@ int spider_db_fetch_for_item_sum_func( row->next(); } break; + case Item_sum::SUM_BIT_FUNC: + { + Item_sum_bit *item_sum_bit = (Item_sum_bit *) item_sum; + if (item_sum_bit->result_type() == STRING_RESULT) + { + if (row->is_null()) + { + item_sum_bit->direct_add((const String *) NULL, TRUE); + } + else + { + char buf[MAX_FIELD_WIDTH]; + spider_string tmp_str(buf, MAX_FIELD_WIDTH, share->access_charset); + tmp_str.init_calc_mem(SPD_MID_DB_FETCH_FOR_ITEM_SUM_FUNC_3); + tmp_str.length(0); + if ((error_num = row->append_to_str(&tmp_str))) + DBUG_RETURN(error_num); + item_sum_bit->direct_add(tmp_str.get_str(), FALSE); + } + } + else + { + /* + * row->val_int() uses atoi() which is a 32-bit signed parser and + * overflows silently for BIGINT UNSIGNED values > INT_MAX. + * BIT_AND/OR/XOR operate on ulonglong (up to ULONGLONG_MAX), so + * we must parse the raw string with my_strtoll10() which correctly + * handles the full 64-bit unsigned range. + */ + if (row->is_null()) + { + item_sum_bit->direct_add((ulonglong) 0, TRUE); + } + else + { + char buf[MAX_FIELD_WIDTH]; + spider_string tmp_str(buf, MAX_FIELD_WIDTH, share->access_charset); + tmp_str.init_calc_mem(SPD_MID_DB_FETCH_FOR_ITEM_SUM_FUNC_3); + tmp_str.length(0); + if ((error_num = row->append_to_str(&tmp_str))) + DBUG_RETURN(error_num); + int conv_error= 0; + ulonglong bits= (ulonglong) my_strtoll10(tmp_str.ptr(), + (char **) NULL, + &conv_error); + item_sum_bit->direct_add(bits, FALSE); + } + } + row->next(); + } + break; case Item_sum::COUNT_DISTINCT_FUNC: case Item_sum::SUM_DISTINCT_FUNC: case Item_sum::AVG_FUNC: case Item_sum::AVG_DISTINCT_FUNC: case Item_sum::STD_FUNC: case Item_sum::VARIANCE_FUNC: - case Item_sum::SUM_BIT_FUNC: case Item_sum::UDF_SUM_FUNC: case Item_sum::GROUP_CONCAT_FUNC: default: diff --git a/storage/spider/spd_db_mysql.cc b/storage/spider/spd_db_mysql.cc index c485b3f6b81fe..fbb90881e410e 100644 --- a/storage/spider/spd_db_mysql.cc +++ b/storage/spider/spd_db_mysql.cc @@ -6108,6 +6108,7 @@ int spider_db_mbase_util::open_item_sum_func( case Item_sum::SUM_FUNC: case Item_sum::MIN_FUNC: case Item_sum::MAX_FUNC: + case Item_sum::SUM_BIT_FUNC: { LEX_CSTRING org_func_name= item_sum->func_name_cstring(); const char *func_name = org_func_name.str; @@ -6198,7 +6199,6 @@ int spider_db_mbase_util::open_item_sum_func( break; case Item_sum::STD_FUNC: case Item_sum::VARIANCE_FUNC: - case Item_sum::SUM_BIT_FUNC: case Item_sum::UDF_SUM_FUNC: case Item_sum::GROUP_CONCAT_FUNC: default: