Description:
ALTER TABLE ... EXCHANGE PARTITION accepts a standalone InnoDB table whose identically named functional index uses a different expression from the partitioned table. The source table indexes (a + 2), while the destination metadata declares (a + 1). After the exchange, a predicate using (a + 1) finds the row with a table scan but misses it when the destination functional index is forced. Actual result:
The exchange succeeds. For WHERE a + 1 = 2, the table-scan count is 1 and the forced-index count is 0. Expected result:
The exchange must reject the tables as having different metadata because their functional-index expressions differ. If an exchange is accepted, indexed and non-indexed evaluation of the same predicate must return the same rows. Impact:
The operation can leave physical functional-index keys inconsistent with the destination table definition, causing silent wrong query results when the index is selected. Rebuilding or otherwise repairing the affected index is required to restore consistent access. Tested versions:
MySQL Community Server 9.7.2 and 26.7.0. Environment and configuration:
Linux on x86_64 using InnoDB and the default utf8mb4 character set and utf8mb4_0900_ai_ci collation. The exchange uses WITHOUT VALIDATION.
Impact: A permitted metadata operation can cause indexed queries to silently omit qualifying rows while a table scan returns them, making query results depend on access path.
The defect causes silent wrong query results after an accepted DDL operation: an indexed access path omits a qualifying row that a table scan finds.
How to repeat:
```sql
CREATE DATABASE exchange_fi_test;
USE exchange_fi_test;
CREATE TABLE part_t (
a INT NOT NULL,
KEY idx_a_plus_1 ((a + 1))
) ENGINE=InnoDB
PARTITION BY RANGE (a) (
PARTITION p0 VALUES LESS THAN MAXVALUE
);
CREATE TABLE swap_t (
a INT NOT NULL,
KEY idx_a_plus_1 ((a + 2))
) ENGINE=InnoDB;
INSERT INTO swap_t VALUES (1);
ALTER TABLE part_t
EXCHANGE PARTITION p0
WITH TABLE swap_t WITHOUT VALIDATION;
SHOW CREATE TABLE part_t;
SHOW CREATE TABLE swap_t;
SELECT COUNT(*) AS table_scan_count
FROM part_t IGNORE INDEX (idx_a_plus_1)
WHERE a + 1 = 2;
SELECT COUNT(*) AS forced_index_count
FROM part_t FORCE INDEX (idx_a_plus_1)
WHERE a + 1 = 2;
EXPLAIN SELECT a
FROM part_t FORCE INDEX (idx_a_plus_1)
WHERE a + 1 = 2;
```
Actual result
-------------
```text
table_scan_count
1
forced_index_count
0
EXPLAIN
-> Index lookup on part_t using idx_a_plus_1 ((a + 1) = 2)
```
Expected result
---------------
ALTER TABLE ... EXCHANGE PARTITION should fail because the two functional indexes have different expressions. If the exchange is accepted, both queries must return a count of 1 because index selection cannot change the qualifying row set.