From ec147815b0f9ddd7eba870e6e02f00731da8ee42 Mon Sep 17 00:00:00 2001 From: "hope.lb" Date: Fri, 27 Nov 2020 15:05:28 +0800 Subject: [PATCH] Bugfix Aggregation result of MySQL BIT type is wrongly integer Description ----------- The aggregation result data type of BIT data type is an integer instead of BIT type. It's caused by that the executor caches the intermediate result of aggregation to Item_cache_int::value which is integer type. And then when it outputs the result, it forgets the original data type is BIT type. Fix --- Add a new cache Item class Item_cache_bit. When the parameter of aggregation function is MySQL BIT type, it will create an object of this new class to cache the intermediate result. And when it outputs the result, it will convert the value to bit format. --- mysql-test/r/group_by.result | 15 +++++++++++++++ mysql-test/t/group_by.test | 10 ++++++++++ sql/item.cc | 37 ++++++++++++++++++++++++++++++++++++ sql/item.h | 33 +++++++++++++++++++++++++++++++- 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/group_by.result b/mysql-test/r/group_by.result index d1e4823c09d..2cc87064999 100644 --- a/mysql-test/r/group_by.result +++ b/mysql-test/r/group_by.result @@ -4039,3 +4039,18 @@ GROUP BY w.a LIMIT 39; ERROR 22003: latitude value is out of range in 'st_geohash' DROP TABLE w; +CREATE TABLE t1(a BIT, b INT); +INSERT INTO t1 VALUES (b'0', 0), (b'1', 1); +CREATE INDEX i1 ON t1(b); +ANALYZE TABLE t1; +Table Op Msg_type Msg_text +test.t1 analyze status OK +SELECT MIN(a) FROM t1 GROUP BY b; +MIN(a) + + +SELECT MIN(a) FROM t1 IGNORE INDEX(i1) GROUP BY b; +MIN(a) + + +DROP TABLE t1; diff --git a/mysql-test/t/group_by.test b/mysql-test/t/group_by.test index 17b2e9b2412..8e4c1814498 100644 --- a/mysql-test/t/group_by.test +++ b/mysql-test/t/group_by.test @@ -2993,3 +2993,13 @@ FROM w RIGHT JOIN w AS e ON TRUE GROUP BY w.a LIMIT 39; DROP TABLE w; + +CREATE TABLE t1(a BIT, b INT); +INSERT INTO t1 VALUES (b'0', 0), (b'1', 1); +CREATE INDEX i1 ON t1(b); +ANALYZE TABLE t1; + +SELECT MIN(a) FROM t1 GROUP BY b; +SELECT MIN(a) FROM t1 IGNORE INDEX(i1) GROUP BY b; + +DROP TABLE t1; diff --git a/sql/item.cc b/sql/item.cc index 40ce4dfb44e..92370653b87 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -41,6 +41,7 @@ #include "my_dbug.h" #include "my_inttypes.h" #include "my_macros.h" +#include "myisampack.h" // mi_int8store #include "mysql.h" // IS_NUM #include "mysql_time.h" #include "sql/aggregate_check.h" // Distinct_check @@ -9118,6 +9119,13 @@ Item_cache *Item_cache::get_cache(const Item *item) { Item_cache *Item_cache::get_cache(const Item *item, const Item_result type) { switch (type) { case INT_RESULT: + /* + When it's a Item_field of MYSQL_TYPE_BIT, we need to retain its result + as bit format instead of an integer. + */ + if (item->type() == Item::FIELD_ITEM && + item->data_type() == MYSQL_TYPE_BIT) + return new Item_cache_bit(down_cast(item)); return new Item_cache_int(item->data_type()); case REAL_RESULT: return new Item_cache_real(); @@ -9231,6 +9239,35 @@ longlong Item_cache_int::val_int() { return value; } +String *Item_cache_bit::val_str(String *str) { + DBUG_ASSERT(fixed == 1); + if (!has_value()) return nullptr; + + char buff[sizeof(longlong)]; + mi_int8store(buff, value); + + uint length = sizeof(longlong), i = 0; + for (; length && !buff[i]; length--, i++) + ; // skip left '\0's + +#ifndef DBUG_OFF + /* + Now the Item_cache_bit is used in MIN/MAX aggregate calculation or + comparison between field values. The result should not overflow. + */ + int delta = bytes_in_rec - length; + uchar bits = bits_length & 7; + DBUG_ASSERT(delta > 0 || + (delta == 0 && (!bits || (uchar)buff[i] < (1 << bits)))); +#endif + + str->length(0); + if (str->append_with_prefill(buff + i, length, bytes_in_rec, 0)) + return nullptr; + + return str; +} + bool Item_cache_datetime::cache_value_int() { if (!example) return false; diff --git a/sql/item.h b/sql/item.h index 12d305c68b2..dc9ac25ceb9 100644 --- a/sql/item.h +++ b/sql/item.h @@ -6583,7 +6583,7 @@ class Item_cache : public Item_basic_constant { } }; -class Item_cache_int final : public Item_cache { +class Item_cache_int : public Item_cache { protected: longlong value; @@ -6611,6 +6611,37 @@ class Item_cache_int final : public Item_cache { bool cache_value() override; }; +/** + Cache class for Item_field of MYSQL_TYPE_BIT. Since we need to retain the bit + property of result, the val_str() function is overridden to transform the + Item_cache_int::value in bit format. +*/ +class Item_cache_bit : public Item_cache_int { + private: + // Number of bytes for the field of MYSQL_TYPE_BIT. + uint bytes_in_rec; +#ifndef DBUG_OFF + // Number of bits + uint32 bits_length; +#endif + + public: + Item_cache_bit(const Item_field *item_field) : + Item_cache_int(item_field->data_type()), + bytes_in_rec(item_field->field->pack_length_in_rec()) { +#ifndef DBUG_OFF + DBUG_ASSERT(item_field->field->type() == MYSQL_TYPE_BIT); + bits_length = item_field->field->field_length; +#endif + } + + /** + Transform the result Item_cache_int::value in bit format. The process is + similar to Field_bit_as_char::store(). + */ + String *val_str(String *str) override; +}; + class Item_cache_real final : public Item_cache { double value; -- 2.19.1.6.gb485710b