Skip to content
/ server Public

MDEV-24931: Fix Bitmap<64>::is_prefix assertion with >64-column NATURAL JOIN on derived table#4756

Open
bodyhedia44 wants to merge 1 commit intoMariaDB:10.6from
bodyhedia44:10.6
Open

MDEV-24931: Fix Bitmap<64>::is_prefix assertion with >64-column NATURAL JOIN on derived table#4756
bodyhedia44 wants to merge 1 commit intoMariaDB:10.6from
bodyhedia44:10.6

Conversation

@bodyhedia44
Copy link

Problem

When a NATURAL JOIN operates on a derived table (or view with derived_merge=off) having more than 64 columns, the server crashes with:
Assertion `prefix_size <= width' failed in Bitmap<64u>::is_prefix

UBSAN also reports: shift exponent 32 is too large for 32-bit type 'int'

Root Cause

generate_derived_keys_for_table() in sql_select.cc creates derived keys with no cap on the number of key parts. When a 65-column table is NATURAL JOINed:

  1. Shift overflow: (key_part_map)(1 << parts) — when parts >= 32, this is undefined behavior (shifting a 32-bit 1 by 32+ bits).
  2. Bitmap overflow: A derived key with 65 parts is created, but key_map (Bitmap<64>) and key_part_map (uint64) can only track 64 bits.
  3. Crash: make_join_statistics() calls eq_part.is_prefix(65) on a Bitmap<64>, hitting the assertion.

How to Reproduce

CREATE DATABASE IF NOT EXISTS test;
USE test;

DROP TABLE IF EXISTS t;

CREATE TABLE t (c1 INT,c2 INT,c3 INT,c4 INT,c5 INT,c6 INT,c7 INT,c8 INT,c9 INT,c10 INT,c11 INT,c12 INT,c13 INT,c14 INT,c15 INT,c16 INT,c17 INT,c18 INT,c19 INT,c20 INT,c21 INT,c22 INT,c23 INT,c24 INT,c25 INT,c26 INT,c27 INT,c28 INT,c29 INT,c30 INT,c31 INT,c32 INT,c33 INT,c34 INT,c35 INT,c36 INT,c37 INT,c38 INT,c39 INT,c40 INT,c41 INT,c42 INT,c43 INT,c44 INT,c45 INT,c46 INT,c47 INT,c48 INT,c49 INT,c50 INT,c51 INT,c52 INT,c53 INT,c54 INT,c55 INT,c56 INT,c57 INT,c58 INT,c59 INT,c60 INT,c61 INT,c62 INT,c63 INT,c64 INT,c65 INT) ENGINE=InnoDB;

SET SESSION optimizer_switch='derived_merge=off';

SELECT * FROM t AS a NATURAL JOIN (SELECT * FROM t) AS b;

DROP TABLE t;

Fix

  • Cap derived keys at sizeof(key_part_map) * 8 (64) parts in generate_derived_keys_for_table().
  • Excess key parts beyond 64 are excluded (keyuse->key = MAX_KEY).
  • Fix the shift to cast before shifting: (key_part_map) 1 << parts instead of (key_part_map)(1 << parts).

Test

Added main.mdev24931 with three cases:

  • 65-column derived table via subquery (the crash case).
  • 65-column view with derived_merge=off (original bug report).
  • 64-column boundary test (should always work).

…AL JOIN on derived table

When a NATURAL JOIN operates on a derived table (or view with
derived_merge=off) having more than 64 columns, the optimizer's
generate_derived_keys_for_table() would:

1) Overflow a 32-bit shift: (key_part_map)(1 << parts) when parts >= 32
2) Create a derived key with more parts than Bitmap<64>/key_part_map can hold
3) Crash on DBUG_ASSERT(prefix_size <= width) in Bitmap<64>::is_prefix(65)

Fix: cap derived keys at sizeof(key_part_map)*8 (64) parts. Excess columns
are excluded from the key (keyuse->key = MAX_KEY). Also fix the shift to
cast before shifting: (key_part_map) 1 << parts.
@gkodinov gkodinov added the External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. label Mar 9, 2026
Copy link
Member

@gkodinov gkodinov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution. This is a preliminary review.

KEYUSE *first_keyuse= keyuse;
uint prev_part= keyuse->keypart;
uint parts= 0;
uint max_parts= sizeof(key_part_map) * 8;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be sizeof(key_part_map) IMHO. I'd use table->file->max_key_parts() to get the info from the SE.

while (i < count && keyuse->used_tables == first_keyuse->used_tables &&
keyuse->keypart == prev_part);
parts++;
if (parts < max_parts)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the extra check here? You are already checking above.

}
else
{
keyuse->key= MAX_KEY;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment here mentioning what does setting MAX_KEY to a key really mean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements.

Development

Successfully merging this pull request may close these issues.

2 participants