MDEV-10526: Add binary string support to bitwise operators#5190
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for binary-to-binary bitwise operations (AND, OR, XOR, NOT, and bitwise shifts) on binary strings by implementing new handler classes and adding corresponding error messages for mismatched operand sizes. The review feedback highlights several critical issues: a potential integer overflow/wrap-around in the left-shift index calculation on 32-bit systems, incorrect ASCII string conversion of numeric arguments in mixed-type bitwise operations, and potential undefined behavior when handling empty binary strings due to passing a null pointer to memset.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| size_t src_idx= i + byte_shift; | ||
| if (src_idx < len) | ||
| { |
There was a problem hiding this comment.
The calculation size_t src_idx = i + byte_shift is susceptible to integer overflow/wrap-around on 32-bit systems if len is very large (e.g., close to size_t max). If i + byte_shift overflows, src_idx wraps around and can evaluate to a value less than len, leading to incorrect data copying from wrapped-around indices instead of zero-filling. To prevent this, perform the bounds check using subtraction (byte_shift < len - i) before computing src_idx.
if (byte_shift < len - i)
{
size_t src_idx= i + byte_shift;| StringBuffer<128> b_buf; | ||
| String *b= item->arguments()[1]->val_str(&b_buf); |
There was a problem hiding this comment.
In mixed-type bitwise operations (e.g., binary_string & numeric), calling val_str on the numeric argument converts it to its decimal string representation (e.g., 255 becomes the 3-byte ASCII string "255" / 0x323535), rather than its binary representation (e.g., 0xFF). This causes mismatched length errors (e.g., VARBINARY(1) & 255 compares length 1 with length 3) or incorrect bitwise operations if the lengths happen to match. To align with standard bitwise behavior (and MySQL 8.0 compatibility), numeric arguments should be converted to a binary string representation of their integer value, padded or truncated to match the length of the other binary string operand.
| size_t len= a->length(); | ||
|
|
||
| if (to->realloc(len)) |
There was a problem hiding this comment.
When the input binary string a is empty (len == 0), calling to->realloc(0) can return a null pointer or do nothing. Subsequently, passing a null pointer to memset (e.g., memset(out_ptr, 0, len)) is technically undefined behavior in C/C++, even if the length is 0. Adding an early exit for len == 0 avoids this potential undefined behavior and improves efficiency by bypassing unnecessary allocation and loop overhead.
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))fa5cd7c to
862a606
Compare
grooverdan
left a comment
There was a problem hiding this comment.
Should be off the main branch as a new feature.
from @abarkov
"Would be nice to add tests that uuid, inet6, inet4, geometry are not allowed for bit operations. Later we can probably implement bit operations for uuid, inet6, inet4. But to avoid compatibility problems we need to make sure they return error now."
221664d to
6b9dd9e
Compare
gkodinov
left a comment
There was a problem hiding this comment.
Thank you for your contribution! This is a preliminary review.
Please squash your commits to 1 per feature. Maybe keep the spider part in a separate commit and the rest of it in another? Just a suggestion. But there definitely should not be 13 commits.
| # ========================================================================= | ||
| 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 |
There was a problem hiding this comment.
do you really need a table for that?
934bb95 to
75cfdf6
Compare
gkodinov
left a comment
There was a problem hiding this comment.
LGTM. Please stand by for the final review.
|
I'm reviewing the spider bit |
| String *a= item->arguments()[0]->val_str(&a_buf); | ||
| if (a == nullptr) | ||
| { | ||
| item->null_value= true; |
There was a problem hiding this comment.
normally this would just be null_value= true for the current object. Is null and invalid argument? Do we need to test if (item->arguments[0]->null_value) before val_str?
| const uchar *a_ptr= (const uchar *) a->ptr(); | ||
|
|
||
| if (shift_count_signed < 0 || (ulonglong)shift_count_signed >= (ulonglong)len * 8) | ||
| { |
There was a problem hiding this comment.
on style is ok to have a single line (only) in a if like condition without braces if its clear.
| item->max_length= item->arguments()[0]->max_length; | ||
| item->collation.set(&my_charset_bin, DERIVATION_COERCIBLE); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
^ common shared with shift type handers. Can the commonality be in the same class.
| @@ -0,0 +1,154 @@ | |||
| --disable_warnings | |||
There was a problem hiding this comment.
Why disable warnings?
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements binary string support for bitwise operators and aggregate functions (MDEV-10526), including window function and Spider pushdown support. The review identified several critical issues: heap buffer overflows in Item_sum_bit due to incorrect byte allocation sizes with thd->alloc and thd->calloc, an out-of-bounds read and incorrect parsing of 64-bit unsigned integers in Spider's spd_db_conn.cc using my_strtoll10, and potential null pointer dereferences in the binary accumulator reset functions due to unchecked allocation results.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| { | ||
| if (item->m_binary_bit_counters) | ||
| { | ||
| m_binary_bit_counters= thd->alloc<uint32>(m_binary_length * 8); |
There was a problem hiding this comment.
The call to thd->alloc<uint32>(m_binary_length * 8) allocates m_binary_length * 8 bytes rather than elements because alloc takes the size in bytes. This leads to a severe buffer overflow when memcpy copies m_binary_length * 8 * sizeof(uint32) bytes into it. It should be changed to allocate the correct size in bytes.
m_binary_bit_counters= (uint32*) thd->alloc(m_binary_length * 8 * sizeof(uint32));| { | ||
| if (m_binary_mode) | ||
| { | ||
| m_binary_bit_counters= thd->calloc<uint32>(m_binary_length * 8); |
There was a problem hiding this comment.
The call to thd->calloc<uint32>(m_binary_length * 8) allocates m_binary_length * 8 bytes instead of elements. This leads to a severe buffer overflow when writing up to m_binary_length * 8 elements of uint32. It should be changed to allocate the correct size in bytes.
m_binary_bit_counters= (uint32*) thd->calloc(m_binary_length * 8 * sizeof(uint32));| int conv_error= 0; | ||
| ulonglong bits= (ulonglong) my_strtoll10(tmp_str.ptr(), | ||
| (char **) NULL, | ||
| &conv_error); |
There was a problem hiding this comment.
Using my_strtoll10 to parse unsigned 64-bit integers is incorrect because it clamps values greater than LONGLONG_MAX to LONGLONG_MAX and sets an error. Additionally, my_strtoll10 does not take a length parameter and expects a null-terminated string, which can lead to an out-of-bounds read if tmp_str is not null-terminated. We should use my_strntoull10 with tmp_str.length() instead.
int conv_error= 0;
const char *endptr;
ulonglong bits= my_strntoull10(tmp_str.ptr(),
tmp_str.length(),
&conv_error,
&endptr);| 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; | ||
| } |
There was a problem hiding this comment.
This part is not covered in the spider test, despite this bit added in the spider commit. Can you add test coverage?
| 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; | ||
| } |
There was a problem hiding this comment.
This part is not covered in the spider test, despite this bit added in the spider commit. Can you add test coverage?
| if (unlikely(direct_added || direct_reseted_field)) | ||
| { | ||
| direct_added= TRUE; | ||
| direct_reseted_field= FALSE; | ||
| } |
There was a problem hiding this comment.
This part is not covered in the spider test, despite this bit added in the spider commit. Can you add test coverage? Same for other bits added in Item_sum_bit::update_field in the spider commit - the method itself is not reached
| if (row->is_null()) | ||
| { | ||
| item_sum_bit->direct_add((const String *) NULL, TRUE); | ||
| } |
There was a problem hiding this comment.
Please add test coverage for this branch, same below
…ates
Extends all six scalar bitwise operators (&, |, ^, ~, <<, >>)
and aggregate functions (BIT_AND, BIT_OR, BIT_XOR) to work
byte-by-byte on BINARY/VARBINARY columns, returning VARBINARY
of the same length.
Previously all operators silently cast binary arguments to
BIGINT (64-bit), truncating values wider than 64 bits. This
broke operations on INET6 addresses, UUIDs, and any binary
column wider than 8 bytes.
Binary mode activates when both operands have a true non-hybrid
binary string type_handler (Type_handler_longstr with my_charset_bin
collation, excluding hex hybrids like 0xFF). Uses the existing
Item_handled_func pluggable Handler pattern, adding new
Handler_str subclasses per operator alongside existing
Handler_ulonglong handlers.
Aggregate functions gain a parallel String accumulator with
correct neutral elements (0xFF for AND, 0x00 for OR/XOR).
Window function support added via a dynamically-sized uint32
bit-counter array (same sliding-window technique as the
existing 64-bit bit_counters[], extended to arbitrary lengths).
Key fixes included:
- reset_field()/update_field() binary mode (crash fix:
raw int8store on string-typed temp table field caused SIGSEGV)
- return_type_handler() varies by max_length following the
blob_type_handler/type_handler_varchar/type_handler_string
pattern from Item_char_typecast_func_handler_fbt_to_binary
- DERIVATION_COERCIBLE (not IMPLICIT) for binary results
- MY_MAX() overflow pattern replaced with plain increment
in window function counters
- Mismatched lengths push warning (not hard error), escalating
to error under strict SQL mode via THD::raise_condition()
- Regressions in main.gis (geometry entering binary mode via
STRING_RESULT+my_charset_bin) and main.func_json (JSON
over-rejected by stricter check_arguments() override) fixed
- GEOMETRY and hex hybrids excluded from binary mode detection
New error codes:
ER_INVALID_BITWISE_OPERANDS_SIZE
ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE
Closes: MDEV-10526
Review feedback addressed (gkodinov, mariadb-YuchenPei):
- Remove unnecessary expected-output comments from test file
- Add my_error(ER_OUTOFMEMORY) to all realloc/alloc failure paths
- Add DBUG_ASSERT(argument_count() == 2) in 2-operand val_str()
- Fix NULL check style: check null_value before val_str return
- Add algorithm comments to shift handlers
- Refactor shift left/right into base class Func_handler_shift_bin_to_bin
with virtual do_shift() - eliminates duplication
- Group same-type struct members together in Item_sum_bit
- Rewrite Spider test as single-server test (spider_same_server_link=1)
- Add GROUP BY test cases for reset_field/update_field coverage
- Refactor SUM_BIT_FUNC in spd_db_conn.cc to reduce duplication
75cfdf6 to
7b07819
Compare
Implements Spider direct_add() support for BIT_AND/BIT_OR/BIT_XOR, allowing each remote shard to compute its own partial aggregate merged locally, mirroring the existing SUM/COUNT/MIN/MAX pattern. BIT_AND/OR/XOR are associative and commutative so partial results from shards can be merged correctly via direct_add(). Also fixes a pre-existing Spider bug: row->val_int() uses atoi() which silently overflows for BIGINT UNSIGNED values above INT_MAX. Fixed for this code path using my_strtoll10() instead. Closes: MDEV-10526 Review feedback addressed (mariadb-YuchenPei): - Rewrite Spider test as single-server test (spider_same_server_link=1) - Add GROUP BY test cases for reset_field/update_field coverage - Add NULL row test coverage for direct_add() NULL branch - Refactor SUM_BIT_FUNC in spd_db_conn.cc to reduce duplication
7b07819 to
719f5a4
Compare
Draft for review. Implements byte-by-byte binary string mode for all scalar bitwise operators. Aggregate function
support (BIT_AND/BIT_OR/BIT_XOR) to follow.
Tested: