Bug #121030 Upgrade fails with MY-013135 on tables with CHECK constraints/generated columns/partitions
Submitted: 29 Jul 21:55 Modified: 31 Jul 7:54
Reporter: Sabalesh Mahajan Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Data Dictionary Severity:S2 (Serious)
Version:8.0.42+ and 8.4.5+ OS:Any
Assigned to: CPU Architecture:Any

[29 Jul 21:55] Sabalesh Mahajan
Description:
In-place upgrade to MySQL 8.0.42+ or 8.4.5+ fails with MY-013135 ("Incorrect information in file") when both of these conditions exist on the instance:

1. A stored procedure whose body contains `CREATE TEMPORARY TABLE ... ENGINE=Memory`
2. A table with CHECK constraints, generated columns, or partition expressions that use SQL functions (e.g., `regexp_like()`)

Either condition alone does not cause the failure. Both must be present.

The root cause is in `sql/dd/impl/upgrade/server.cc`. During `do_server_upgrade_checks()`, the `check_routines` phase compiles stored procedures. When it encounters `ENGINE=Memory`, `resolve_engine()` fails because the MEMORY storage engine is not loaded during bootstrap. This sets error 1286 (`ER_UNKNOWN_STORAGE_ENGINE`) in the THD diagnostics area. The function `invalid_routine()` returns early without clearing the error:

```c
// sql/dd/impl/upgrade/server.cc — invalid_routine()
if (error) return (thd->get_stmt_da()->mysql_errno() == ER_PARSE_ERROR);
thd->clear_error();  // ONLY reached when error == false
```

The stale THD error then propagates to `check_table_funs()` (added in MySQL 8.4.5, backported to 8.0.42+), which calls `open_table()` → `open_table_from_share()` → `unpack_value_generator()` → `fix_fields()`. Expression evaluation sees the stale `is_error()` and fails, producing MY-013135.

The `invalid_routine()` function is byte-for-byte identical in 8.0.40, 8.0.44, 8.4.4, and 8.4.5. The bug was latent until `check_table_funs` was added to the `process_schema` pipeline.

Related: Bug #120326 (identical mechanism with partitioned tables instead of CHECK constraints)

How to repeat:
### 1. Deploy MySQL 8.0.40 (or any version below 8.0.42)

```bash
mysqld --initialize-insecure --datadir=/tmp/repro_data --basedir=/path/to/mysql-8.0.40
mysqld --defaults-file=/dev/null --datadir=/tmp/repro_data --port=3307 \
  --socket=/tmp/repro.sock --log-error=/tmp/repro.err --log_error_verbosity=3 &
```

### 2. Create stored procedure with ENGINE=Memory (condition 1)

```sql
CREATE DATABASE testrepro;
USE testrepro;

DELIMITER //
CREATE PROCEDURE my_proc()
BEGIN
  CREATE TEMPORARY TABLE IF NOT EXISTS temp_memory (
    id BIGINT PRIMARY KEY
  ) ENGINE=Memory;
  DROP TEMPORARY TABLE IF EXISTS temp_memory;
END //
DELIMITER ;
```

### 3. Create table with CHECK constraints using regexp_like() (condition 2)

```sql
CREATE TABLE job_config (
  id smallint unsigned NOT NULL AUTO_INCREMENT,
  source_name varchar(128) NOT NULL,
  target_name varchar(128) NOT NULL,
  fallback_name varchar(128) DEFAULT NULL,
  job_type enum('standard','tiered','custom') NOT NULL DEFAULT 'standard',
  filter_col varchar(128) NOT NULL DEFAULT 'created_at',
  retain_months smallint unsigned NOT NULL DEFAULT '36',
  expire_months smallint unsigned NOT NULL DEFAULT '84',
  stage_months smallint unsigned DEFAULT NULL,
  active tinyint(1) NOT NULL DEFAULT '1',
  priority smallint unsigned NOT NULL DEFAULT '100',
  created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_source (source_name),
  KEY idx_type_active (job_type, active, priority),
  CONSTRAINT chk_retain_min CHECK ((retain_months >= 6)),
  CONSTRAINT chk_target_name CHECK (regexp_like(target_name,_utf8mb4'^[a-zA-Z_][a-zA-Z0-9_]*$')),
  CONSTRAINT chk_fallback_name CHECK (((fallback_name is null) or regexp_like(fallback_name,_utf8mb4'^[a-zA-Z_][a-zA-Z0-9_]*$'))),
  CONSTRAINT chk_source_name CHECK (regexp_like(source_name,_utf8mb4'^[a-zA-Z_][a-zA-Z0-9_]*$')),
  CONSTRAINT chk_expire_gt_retain CHECK ((expire_months > retain_months)),
  CONSTRAINT chk_expire_min CHECK ((expire_months >= 6)),
  CONSTRAINT chk_filter_col CHECK (regexp_like(filter_col,_utf8mb4'^[a-zA-Z_][a-zA-Z0-9_]*$')),
  CONSTRAINT chk_tiered CHECK (((job_type <> _utf8mb4'tiered') or ((fallback_name is not null) and (stage_months is not null))))
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO job_config (source_name, target_name) VALUES ('events', 'events_history');
```

### 4. Stop 8.0.40 and start 8.4.5 (or 8.0.44)

```bash
mysqladmin -S /tmp/repro.sock shutdown

/path/to/mysql-8.4.5/bin/mysqld --defaults-file=/dev/null \
  --datadir=/tmp/repro_data --port=3307 --socket=/tmp/repro.sock \
  --log-error=/tmp/repro_upgrade.err --log_error_verbosity=3 --upgrade=AUTO
```

### 5. Observe failure

```
[ERROR] [MY-013135] [Server] Incorrect information in file: './testrepro/job_config.frm'
[Warning] [MY-014078] [Server] Can not open table `testrepro`.`job_config`; functions in constraints, partitions, or virtual columns may have failed.
[ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
[ERROR] [MY-010119] [Server] Aborting
```

### 6. Verify: drop the stored procedure, retry — SUCCEEDS

On a fresh datadir with the same table but no stored procedure referencing ENGINE=Memory, upgrade to 8.4.5 completes without error.

### 7. Verify: drop the CHECK constraints, retry — SUCCEEDS

On a fresh datadir with the stored procedure but only simple CHECK constraints (no `regexp_like()` or other function calls), upgrade to 8.4.5 completes without error.

---

## Test Matrix (confirmed)

| Source | Target | check_table_funs? | Result |
|--------|--------|-------------------|--------|
| 8.0.40 | 8.0.44 | YES | FAILED |
| 8.0.40 | 8.4.4 | NO | SUCCEEDED |
| 8.0.40 | 8.4.5 | YES | FAILED |
| 8.0.40 | 8.4.6 | YES | FAILED |
| 8.0.40 | 8.4.7 | YES | FAILED |
| 8.0.44 (fresh) | 8.4.4 | NO | SUCCEEDED |
| 8.0.44 (fresh) | 8.4.5 | YES | FAILED |

---

Suggested fix:
Clear THD before calling `check_table_funs`:

```c
// sql/dd/impl/upgrade/server.cc — do_server_upgrade_checks()
auto process_schema = [&](std::unique_ptr<Schema> &schema) {
    return check_tables(...) || check_events(...) || check_routines(...) || check_views(...) ||
           (thd->clear_error(), false) ||
           check_table_funs(...);
};
```