1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-40029 Add support for bit fields to HEAP

This adds support for BIT_FIELD in record and keys for HEAP tables.

The HEAP engine now has HA_CAN_BIT_FIELD set in table_flags()

Multiple bugs in BIT field handling was found fixed. Some in HEAP table
code, other bugs was affecting usage of BIT fields as keys.
This commit is contained in:
Monty
2026-06-13 20:51:37 +03:00
parent 1286c59ddf
commit ce0bc07763
7 changed files with 253 additions and 80 deletions

View File

@@ -108,9 +108,16 @@ id select_type table type possible_keys key key_len ref rows Extra
select a+0 from t1;
a+0
0
4
5
9
104
106
108
111
116
118
119
122
123
127
23
24
28
@@ -118,8 +125,10 @@ a+0
30
31
34
4
44
49
5
56
57
59
@@ -133,70 +142,68 @@ a+0
79
87
88
9
94
94
104
106
108
111
116
118
119
122
123
127
explain select b+0 from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL a 5 NULL 38 Using index
select b+0 from t1;
b+0
177
245
178
363
36
398
499
399
83
438
202
307
345
379
135
188
343
152
206
454
42
133
118
123
133
135
152
177
178
188
202
206
245
280
307
343
345
349
351
36
363
368
368
379
380
390
398
399
403
411
411
42
438
446
454
46
468
280
446
499
67
368
390
380
368
118
411
403
83
explain select a+0, b+0 from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL a 5 NULL 38 Using index
select a+0, b+0 from t1;
a+0 b+0
0 177
4 245
5 178
9 363
104 280
106 446
108 67
111 368
116 390
118 380
119 368
122 118
123 411
127 403
23 36
24 398
28 499
@@ -204,8 +211,10 @@ a+0 b+0
30 83
31 438
34 202
4 245
44 307
49 345
5 178
56 379
57 135
59 188
@@ -219,18 +228,9 @@ a+0 b+0
79 349
87 351
88 411
9 363
94 46
94 468
104 280
106 446
108 67
111 368
116 390
118 380
119 368
122 118
123 411
127 403
explain select a+0, b+0 from t1 where a > 40 and b > 200 order by 1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 2 NULL 27 Using where; Using index; Using filesort
@@ -296,17 +296,20 @@ create table t1 (a int not null, b bit, c bit(9), key(a, b, c));
insert into t1 values
(4, NULL, 1), (4, 0, 3), (2, 1, 4), (1, 1, 100), (4, 0, 23), (4, 0, 54),
(56, 0, 22), (4, 1, 100), (23, 0, 1), (4, 0, 34);
explain select a+0, b+0, c+0 from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL a 9 NULL 10 Using index
select a+0, b+0, c+0 from t1;
a+0 b+0 c+0
1 1 100
2 1 4
4 NULL 1
4 0 3
23 0 1
4 0 23
4 0 3
4 0 34
4 0 54
4 1 100
23 0 1
4 NULL 1
56 0 22
select hex(min(b)) from t1 where a = 4;
hex(min(b))
@@ -327,19 +330,19 @@ a+0 b+0 c+0
select a+0, b+0, c+0 from t1 where a = 4 and b = 1 and c=100;
a+0 b+0 c+0
4 1 100
select a+0, b+0, c+0 from t1 order by b desc;
select a+0, b+0, c+0 from t1 order by b desc, a, c;
a+0 b+0 c+0
2 1 4
1 1 100
2 1 4
4 1 100
4 0 3
4 0 23
4 0 54
56 0 22
23 0 1
4 0 34
4 0 54
23 0 1
56 0 22
4 NULL 1
select a+0, b+0, c+0 from t1 order by c;
select a+0, b+0, c+0 from t1 order by c,a,b;
a+0 b+0 c+0
4 NULL 1
23 0 1

View File

@@ -48,6 +48,7 @@ insert into t1 values (b'00'), (b'01'), (b'10'), (b'100');
select a+0 from t1;
alter table t1 add key (a);
explain select a+0 from t1;
--sorted_result
select a+0 from t1;
drop table t1;
@@ -60,10 +61,13 @@ insert into t1 values
(30, 83), (5, 178), (60, 343), (4, 245), (104, 280), (106, 446), (127, 403),
(44, 307), (68, 454), (57, 135);
explain select a+0 from t1;
--sorted_result
select a+0 from t1;
explain select b+0 from t1;
--sorted_result
select b+0 from t1;
explain select a+0, b+0 from t1;
--sorted_result
select a+0, b+0 from t1;
explain select a+0, b+0 from t1 where a > 40 and b > 200 order by 1;
select a+0, b+0 from t1 where a > 40 and b > 200 order by 1;
@@ -80,6 +84,8 @@ create table t1 (a int not null, b bit, c bit(9), key(a, b, c));
insert into t1 values
(4, NULL, 1), (4, 0, 3), (2, 1, 4), (1, 1, 100), (4, 0, 23), (4, 0, 54),
(56, 0, 22), (4, 1, 100), (23, 0, 1), (4, 0, 34);
explain select a+0, b+0, c+0 from t1;
--sorted_result
select a+0, b+0, c+0 from t1;
select hex(min(b)) from t1 where a = 4;
select hex(min(c)) from t1 where a = 4 and b = 0;
@@ -87,8 +93,8 @@ select hex(max(b)) from t1;
select a+0, b+0, c+0 from t1 where a = 4 and b = 0 limit 2;
select a+0, b+0, c+0 from t1 where a = 4 and b = 1;
select a+0, b+0, c+0 from t1 where a = 4 and b = 1 and c=100;
select a+0, b+0, c+0 from t1 order by b desc;
select a+0, b+0, c+0 from t1 order by c;
select a+0, b+0, c+0 from t1 order by b desc, a, c;
select a+0, b+0, c+0 from t1 order by c,a,b;
drop table t1;
create table t1(a bit(2), b bit(2));

View File

@@ -0,0 +1,86 @@
#
# HEAP hash index on a BIT column whose uneven (X & 7)
# high bits are stored in the record null-map header.
#
# hp_rec_hashnr() (used to place a row in a hash bucket on INSERT)
# must hash the same bits that hp_make_key()/hp_hashnr() use when
# searching. The bug hashed a record-body byte instead of the
# null-map bits, so placement and lookup disagreed and rows became
# unreachable through the index.
#
# Needs ENGINE=MEMORY advertising HA_CAN_BIT_FIELD so the uneven
# bits really live in the null map (seg->bit_length > 0), a key on
# the BIT column, and enough rows to force more than one hash bucket.
#
# BIT(10): 2 high bits in the null map, 1 byte in the record body.
# Column a is NULLable and stores NULLs.
CREATE TABLE t1 (a INT, b BIT(10), KEY(b)) ENGINE=MEMORY;
INSERT INTO t1 SELECT seq, seq FROM seq_1_to_400;
INSERT INTO t1 VALUES (NULL, b'1100000000'), (NULL, NULL);
# Every row must be reachable through the hash index. Under the bug
# only a handful are found (placement vs. lookup hash disagree).
SELECT COUNT(*) AS total FROM t1;
total
402
SELECT COUNT(*) AS found_via_index FROM t1 t
WHERE EXISTS (SELECT 1 FROM t1 f FORCE INDEX(b) WHERE f.b <=> t.b);
found_via_index
402
# Point lookups via the index must agree with a full scan.
SELECT COUNT(*) AS via_index FROM t1 FORCE INDEX(b)
WHERE b = CAST(123 AS UNSIGNED);
via_index
1
SELECT COUNT(*) AS via_scan FROM t1 IGNORE INDEX(b)
WHERE b = CAST(123 AS UNSIGNED);
via_scan
1
SELECT COUNT(*) AS dup_value_via_index FROM t1 FORCE INDEX(b)
WHERE b = b'1100000000';
dup_value_via_index
1
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
DROP TABLE t1;
# BIT(3): all 3 bits live in the null map (0 body bytes), with a
# NULLable column that also stores NULLs.
CREATE TABLE t2 (a INT, b BIT(3), KEY(b)) ENGINE=MEMORY;
INSERT INTO t2 VALUES (1,b'000'),(2,b'001'),(3,b'010'),(4,b'011'),
(5,b'100'),(6,b'101'),(7,b'110'),(8,b'111'),
(NULL,b'101'),(NULL,NULL),(9,NULL);
SELECT COUNT(*) AS total FROM t2;
total
11
SELECT COUNT(*) AS found_via_index FROM t2 t
WHERE EXISTS (SELECT 1 FROM t2 f FORCE INDEX(b) WHERE f.b <=> t.b);
found_via_index
11
SELECT a FROM t2 FORCE INDEX(b) WHERE b=b'101' ORDER BY a;
a
NULL
6
CHECK TABLE t2;
Table Op Msg_type Msg_text
test.t2 check status OK
DROP TABLE t2;
# Multi-part key with two BIT segments (b BIT(1), c BIT(9)) and a
# NULLable BIT column that stores NULLs. A stray key-pointer bump
# in hp_key_cmp() used to shift the second BIT segment's offset, so
# the lookup on the full key found nothing.
CREATE TABLE t3 (a INT NOT NULL, b BIT, c BIT(9), KEY(a,b,c)) ENGINE=MEMORY;
INSERT INTO t3 VALUES
(4, NULL, 1), (4, 0, 3), (2, 1, 4), (1, 1, 100), (4, 0, 23), (4, 0, 54),
(56, 0, 22), (4, 1, 100), (23, 0, 1), (4, 0, 34);
SELECT a+0, b+0, c+0 FROM t3 WHERE a=4 AND b=1 AND c=100;
a+0 b+0 c+0
4 1 100
SELECT COUNT(*) AS found_via_index FROM t3 t WHERE EXISTS
(SELECT 1 FROM t3 f FORCE INDEX(a)
WHERE f.a<=>t.a AND f.b<=>t.b AND f.c<=>t.c);
found_via_index
10
CHECK TABLE t3;
Table Op Msg_type Msg_text
test.t3 check status OK
DROP TABLE t3;

View File

@@ -0,0 +1,67 @@
--source include/have_sequence.inc
--echo #
--echo # HEAP hash index on a BIT column whose uneven (X & 7)
--echo # high bits are stored in the record null-map header.
--echo #
--echo # hp_rec_hashnr() (used to place a row in a hash bucket on INSERT)
--echo # must hash the same bits that hp_make_key()/hp_hashnr() use when
--echo # searching. The bug hashed a record-body byte instead of the
--echo # null-map bits, so placement and lookup disagreed and rows became
--echo # unreachable through the index.
--echo #
--echo # Needs ENGINE=MEMORY advertising HA_CAN_BIT_FIELD so the uneven
--echo # bits really live in the null map (seg->bit_length > 0), a key on
--echo # the BIT column, and enough rows to force more than one hash bucket.
--echo #
--echo # BIT(10): 2 high bits in the null map, 1 byte in the record body.
--echo # Column a is NULLable and stores NULLs.
CREATE TABLE t1 (a INT, b BIT(10), KEY(b)) ENGINE=MEMORY;
INSERT INTO t1 SELECT seq, seq FROM seq_1_to_400;
INSERT INTO t1 VALUES (NULL, b'1100000000'), (NULL, NULL);
--echo # Every row must be reachable through the hash index. Under the bug
--echo # only a handful are found (placement vs. lookup hash disagree).
SELECT COUNT(*) AS total FROM t1;
SELECT COUNT(*) AS found_via_index FROM t1 t
WHERE EXISTS (SELECT 1 FROM t1 f FORCE INDEX(b) WHERE f.b <=> t.b);
--echo # Point lookups via the index must agree with a full scan.
SELECT COUNT(*) AS via_index FROM t1 FORCE INDEX(b)
WHERE b = CAST(123 AS UNSIGNED);
SELECT COUNT(*) AS via_scan FROM t1 IGNORE INDEX(b)
WHERE b = CAST(123 AS UNSIGNED);
SELECT COUNT(*) AS dup_value_via_index FROM t1 FORCE INDEX(b)
WHERE b = b'1100000000';
CHECK TABLE t1;
DROP TABLE t1;
--echo # BIT(3): all 3 bits live in the null map (0 body bytes), with a
--echo # NULLable column that also stores NULLs.
CREATE TABLE t2 (a INT, b BIT(3), KEY(b)) ENGINE=MEMORY;
INSERT INTO t2 VALUES (1,b'000'),(2,b'001'),(3,b'010'),(4,b'011'),
(5,b'100'),(6,b'101'),(7,b'110'),(8,b'111'),
(NULL,b'101'),(NULL,NULL),(9,NULL);
SELECT COUNT(*) AS total FROM t2;
SELECT COUNT(*) AS found_via_index FROM t2 t
WHERE EXISTS (SELECT 1 FROM t2 f FORCE INDEX(b) WHERE f.b <=> t.b);
SELECT a FROM t2 FORCE INDEX(b) WHERE b=b'101' ORDER BY a;
CHECK TABLE t2;
DROP TABLE t2;
--echo # Multi-part key with two BIT segments (b BIT(1), c BIT(9)) and a
--echo # NULLable BIT column that stores NULLs. A stray key-pointer bump
--echo # in hp_key_cmp() used to shift the second BIT segment's offset, so
--echo # the lookup on the full key found nothing.
CREATE TABLE t3 (a INT NOT NULL, b BIT, c BIT(9), KEY(a,b,c)) ENGINE=MEMORY;
INSERT INTO t3 VALUES
(4, NULL, 1), (4, 0, 3), (2, 1, 4), (1, 1, 100), (4, 0, 23), (4, 0, 54),
(56, 0, 22), (4, 1, 100), (23, 0, 1), (4, 0, 34);
SELECT a+0, b+0, c+0 FROM t3 WHERE a=4 AND b=1 AND c=100;
SELECT COUNT(*) AS found_via_index FROM t3 t WHERE EXISTS
(SELECT 1 FROM t3 f FORCE INDEX(a)
WHERE f.a<=>t.a AND f.b<=>t.b AND f.c<=>t.c);
CHECK TABLE t3;
DROP TABLE t3;

View File

@@ -10551,18 +10551,22 @@ int Field_bit::cmp_offset(my_ptrdiff_t row_offset)
}
uint Field_bit::get_key_image(uchar *buff, uint length, const uchar *ptr_arg, imagetype type_arg) const
uint Field_bit::get_key_image(uchar *buff, uint length,
const uchar *ptr_arg, imagetype type_arg) const
{
uint bit_byte= 0;
if (bit_len)
{
const uchar *bit_ptr_for_arg= ptr_arg + (bit_ptr - ptr);
uchar bits= get_rec_bits(bit_ptr_for_arg, bit_ofs, bit_len);
*buff++= bits;
length--;
bit_byte= 1;
}
uint tmp_data_length = MY_MIN(length, bytes_in_rec);
memcpy(buff, ptr, tmp_data_length);
return tmp_data_length + 1;
/* We don't support prefix read on bit fields */
DBUG_ASSERT(length == bytes_in_rec);
memcpy(buff, ptr_arg, bytes_in_rec);
return bytes_in_rec + bit_byte;
}

View File

@@ -43,7 +43,7 @@ public:
HA_CAN_SQL_HANDLER | HA_CAN_ONLINE_BACKUPS |
HA_REC_NOT_IN_SEQ | HA_CAN_INSERT_DELAYED | HA_NO_TRANSACTIONS |
HA_HAS_RECORDS | HA_STATS_RECORDS_IS_EXACT | HA_CAN_HASH_KEYS |
HA_CAN_GEOMETRY);
HA_CAN_GEOMETRY | HA_CAN_BIT_FIELD);
}
ulong index_flags(uint inx, uint part, bool all_parts) const override
{
@@ -101,6 +101,7 @@ public:
int delete_table(const char *from) override;
void drop_table(const char *name) override;
int rename_table(const char * from, const char * to) override;
int check(THD* thd, HA_CHECK_OPT* check_opt) override;
int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info) override;
void update_create_info(HA_CREATE_INFO *create_info) override;

View File

@@ -316,7 +316,7 @@ ulong hp_hashnr(HP_KEYDEF *keydef, const uchar *key)
}
}
#ifdef ONLY_FOR_HASH_DEBUGGING
DBUG_PRINT("exit", ("hash: 0x%lx", nr));
DBUG_PRINT("exit", ("hash: 0x%lx", hasher.m_nr1));
#endif
return((ulong) hasher.m_nr1);
}
@@ -755,10 +755,9 @@ int hp_key_cmp(HP_KEYDEF *keydef, const uchar *rec, const uchar *key,
if (bits != (*key))
return 1;
dec= 1;
key++;
}
if (bcmp(rec + seg->start, key, seg->length - dec))
if (bcmp(rec + seg->start, key + dec, seg->length - dec))
return 1;
}
}
@@ -848,6 +847,7 @@ uint hp_rb_make_key(HP_KEYDEF *keydef, uchar *key,
for (seg= keydef->seg, endseg= seg + keydef->keysegs; seg < endseg; seg++)
{
size_t char_length;
size_t offset;
if (seg->null_bit)
{
if (!(*key++= 1 - MY_TEST(rec[seg->null_pos] & seg->null_bit)))
@@ -907,10 +907,12 @@ uint hp_rb_make_key(HP_KEYDEF *keydef, uchar *key,
continue;
}
offset= 0;
char_length= seg->length;
if (seg->charset->mbmaxlen > 1)
{
char_length= hp_charpos(seg->charset,
char_length= hp_charpos(seg->charset,
rec + seg->start, rec + seg->start + char_length,
char_length / seg->charset->mbmaxlen);
set_if_smaller(char_length, seg->length); /* QQ: ok to remove? */
@@ -920,11 +922,15 @@ uint hp_rb_make_key(HP_KEYDEF *keydef, uchar *key,
}
if (seg->type == HA_KEYTYPE_BIT && seg->bit_length)
{
*key++= get_rec_bits(rec + seg->bit_pos,
/*
The uneven high bits are stored in the record null-map header.
Put them in the first key byte.
*/
*key= get_rec_bits(rec + seg->bit_pos,
seg->bit_start, seg->bit_length);
char_length--;
offset= 1;
}
memcpy(key, rec + seg->start, (size_t) char_length);
memcpy(key + offset, rec + seg->start, char_length - offset);
key+= seg->length;
}
memcpy(key, &recpos, sizeof(uchar*));