Skip to content
/ server Public
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions mysql-test/suite/parts/r/partition_exchange_generated_columns.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#
# Test EXCHANGE PARTITION with generated columns containing AND/OR conditions
# This verifies that Item_cond::eq() correctly performs set-based comparison
# for commutative AND/OR operations, allowing partition exchange to succeed
# even when condition order differs between original and re-parsed expressions.
# verifies MDEV-38757
#
DROP TABLE IF EXISTS t1, t1_working;
# Test 1: Generated column with OR condition
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
vcol1 TINYINT(1) GENERATED ALWAYS AS (col1 > 80 OR col1 < 20) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB,
PARTITION p2 VALUES IN (2) ENGINE = InnoDB);
CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;
INSERT INTO t1_working (userId, col1) VALUES (1, 10), (1, 50), (1, 90);
# This should succeed: EXCHANGE PARTITION with OR condition
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;
SELECT id, userId, col1, vcol1 FROM t1 ORDER BY id;
id userId col1 vcol1
1 1 10 1
2 1 50 0
3 1 90 1
DROP TABLE t1_working;
DROP TABLE t1;
# Test 2: Generated column with AND condition
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
col2 INT NOT NULL,
vcol2 TINYINT(1) GENERATED ALWAYS AS (col1 > 10 AND col2 < 100) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB);
CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;
INSERT INTO t1_working (userId, col1, col2) VALUES (1, 20, 50), (1, 5, 200);
# This should succeed: EXCHANGE PARTITION with AND condition
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;
SELECT id, userId, col1, col2, vcol2 FROM t1 ORDER BY id;
id userId col1 col2 vcol2
1 1 20 50 1
2 1 5 200 0
DROP TABLE t1_working;
DROP TABLE t1;
# Test 3: Multiple generated columns with different AND/OR combinations
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
col2 INT NOT NULL,
vcol1 TINYINT(1) GENERATED ALWAYS AS (col1 > 80 OR col1 < 20) STORED,
vcol2 TINYINT(1) GENERATED ALWAYS AS (col1 > 10 AND col2 < 100) STORED,
vcol3 TINYINT(1) GENERATED ALWAYS AS (col2 > 50 OR col2 < 10) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB,
PARTITION p2 VALUES IN (2) ENGINE = InnoDB);
CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;
INSERT INTO t1_working (userId, col1, col2) VALUES (1, 15, 75), (1, 90, 5), (1, 50, 200);
# This should succeed: EXCHANGE PARTITION with multiple AND/OR conditions
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;
SELECT id, userId, col1, col2, vcol1, vcol2, vcol3 FROM t1 ORDER BY id;
id userId col1 col2 vcol1 vcol2 vcol3
1 1 15 75 1 1 1
2 1 90 5 1 1 1
3 1 50 200 0 0 1
DROP TABLE t1_working;
DROP TABLE t1;
# Test 4: Nested AND/OR conditions
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
col2 INT NOT NULL,
col3 INT NOT NULL,
vcol4 TINYINT(1) GENERATED ALWAYS AS ((col1 > 10 AND col2 < 100) OR col3 > 50) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB);
CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;
INSERT INTO t1_working (userId, col1, col2, col3) VALUES (1, 20, 50, 30), (1, 5, 200, 60);
# This should succeed: EXCHANGE PARTITION with nested AND/OR conditions
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;
SELECT id, userId, col1, col2, col3, vcol4 FROM t1 ORDER BY id;
id userId col1 col2 col3 vcol4
1 1 20 50 30 1
2 1 5 200 60 1
DROP TABLE t1_working;
DROP TABLE t1;
# End of test
117 changes: 117 additions & 0 deletions mysql-test/suite/parts/t/partition_exchange_generated_columns.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
--source include/have_innodb.inc
--source include/have_partition.inc

--echo #
--echo # Test EXCHANGE PARTITION with generated columns containing AND/OR conditions
--echo # This verifies that Item_cond::eq() correctly performs set-based comparison
--echo # for commutative AND/OR operations, allowing partition exchange to succeed
--echo # even when condition order differs between original and re-parsed expressions.
--echo # verifies MDEV-38757
--echo #

--disable_warnings
DROP TABLE IF EXISTS t1, t1_working;
--enable_warnings

--echo # Test 1: Generated column with OR condition
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
vcol1 TINYINT(1) GENERATED ALWAYS AS (col1 > 80 OR col1 < 20) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB,
PARTITION p2 VALUES IN (2) ENGINE = InnoDB);

CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;

INSERT INTO t1_working (userId, col1) VALUES (1, 10), (1, 50), (1, 90);

--echo # This should succeed: EXCHANGE PARTITION with OR condition
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;

SELECT id, userId, col1, vcol1 FROM t1 ORDER BY id;
DROP TABLE t1_working;
DROP TABLE t1;

--echo # Test 2: Generated column with AND condition
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
col2 INT NOT NULL,
vcol2 TINYINT(1) GENERATED ALWAYS AS (col1 > 10 AND col2 < 100) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB);

CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;

INSERT INTO t1_working (userId, col1, col2) VALUES (1, 20, 50), (1, 5, 200);

--echo # This should succeed: EXCHANGE PARTITION with AND condition
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;

SELECT id, userId, col1, col2, vcol2 FROM t1 ORDER BY id;
DROP TABLE t1_working;
DROP TABLE t1;

--echo # Test 3: Multiple generated columns with different AND/OR combinations
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
col2 INT NOT NULL,
vcol1 TINYINT(1) GENERATED ALWAYS AS (col1 > 80 OR col1 < 20) STORED,
vcol2 TINYINT(1) GENERATED ALWAYS AS (col1 > 10 AND col2 < 100) STORED,
vcol3 TINYINT(1) GENERATED ALWAYS AS (col2 > 50 OR col2 < 10) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB,
PARTITION p2 VALUES IN (2) ENGINE = InnoDB);

CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;

INSERT INTO t1_working (userId, col1, col2) VALUES (1, 15, 75), (1, 90, 5), (1, 50, 200);

--echo # This should succeed: EXCHANGE PARTITION with multiple AND/OR conditions
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;

SELECT id, userId, col1, col2, vcol1, vcol2, vcol3 FROM t1 ORDER BY id;
DROP TABLE t1_working;
DROP TABLE t1;

--echo # Test 4: Nested AND/OR conditions
CREATE TABLE t1 (
id INT NOT NULL AUTO_INCREMENT,
userId INT NOT NULL,
col1 INT NOT NULL,
col2 INT NOT NULL,
col3 INT NOT NULL,
vcol4 TINYINT(1) GENERATED ALWAYS AS ((col1 > 10 AND col2 < 100) OR col3 > 50) STORED,
PRIMARY KEY (id, userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PARTITION BY LIST (userId)
(PARTITION p1 VALUES IN (1) ENGINE = InnoDB);

CREATE TABLE t1_working LIKE t1;
ALTER TABLE t1_working REMOVE PARTITIONING;

INSERT INTO t1_working (userId, col1, col2, col3) VALUES (1, 20, 50, 30), (1, 5, 200, 60);

--echo # This should succeed: EXCHANGE PARTITION with nested AND/OR conditions
ALTER TABLE t1 EXCHANGE PARTITION p1 WITH TABLE t1_working;

SELECT id, userId, col1, col2, col3, vcol4 FROM t1 ORDER BY id;
DROP TABLE t1_working;

DROP TABLE t1;

--echo # End of test
77 changes: 77 additions & 0 deletions sql/item_cmpfunc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5595,6 +5595,83 @@ bool Item_cond::excl_dep_on_grouping_fields(st_select_lex *sel)
}


/**
Compare two Item_cond expressions for equality.

For AND/OR conditions, the order of arguments doesn't matter (commutative),
so we need to do a set-based comparison rather than order-based.

@param item The other Item to compare with
@param config Comparison configuration

@return true if expressions are equivalent, false otherwise
*/
bool Item_cond::eq(const Item *item, const Eq_config &config) const
{
/* Assume we don't have rtti */
if (this == item)
return true;

/* Ensure we are comparing two condition items */
if (item->type() != COND_ITEM)
return false;

const Item_cond *item_cond= (const Item_cond *) item;

/* Must be same type (AND vs OR) */
if (functype() != item_cond->functype())
return false;

/* Must have same number of arguments */
if (list.elements != item_cond->list.elements)
return false;

/* For AND/OR conditions, order doesn't matter - do set-based comparison */
/* Check if every argument in this list has an equivalent in the other list */
List_iterator_fast<Item> li1(const_cast<List<Item>&>(list));
Item *arg1;
while ((arg1= li1++))
{
bool found_match= false;
List_iterator_fast<Item> li2(const_cast<List<Item>&>(item_cond->list));
Item *arg2;
while ((arg2= li2++))
{
if (arg1->eq(arg2, config))
{
found_match= true;
break;
}
}
if (!found_match)
return false;
}

/* Also check the reverse - every argument in other list has equivalent in this list */
/* (This handles cases where there might be duplicates) */
List_iterator_fast<Item> li2(const_cast<List<Item>&>(item_cond->list));
Item *arg2;
while ((arg2= li2++))
{
bool found_match= false;
List_iterator_fast<Item> li1(const_cast<List<Item>&>(list));
Item *arg1;
while ((arg1= li1++))
{
if (arg2->eq(arg1, config))
{
found_match= true;
break;
}
}
if (!found_match)
return false;
}

return true;
}


void Item_cond_and::mark_as_condition_AND_part(TABLE_LIST *embedding)
{
List_iterator<Item> li(list);
Expand Down
1 change: 1 addition & 0 deletions sql/item_cmpfunc.h
Original file line number Diff line number Diff line change
Expand Up @@ -3339,6 +3339,7 @@ class Item_cond :public Item_bool_func
Item *deep_copy(THD *thd) const override;
bool excl_dep_on_table(table_map tab_map) override;
bool excl_dep_on_grouping_fields(st_select_lex *sel) override;
bool eq(const Item *item, const Eq_config &config) const override;

private:
void merge_sub_condition(List_iterator<Item>& li);
Expand Down