planner: information_schema REGEXP predicate is lower-cased by the extractor, breaking case-sensitive and negated classes
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
**Affected versions**: master (since #64301) and release-8.5 v8.5.4+ (cherry-pick #64365). Found during a PR-by-PR review of release-8.5.
### 1. Minimal reproduce step (Required)
```sql
CREATE TABLE UPPERONLY (x INT);
CREATE TABLE MixedCaseTbl (x INT);
CREATE TABLE lc_tbl (x INT);
CREATE TABLE tbl_99 (x INT);
-- (a) should match only UPPERONLY (pure uppercase)
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'test' AND table_name REGEXP '^[A-Z]+$';
-- (b) should match all 4 tables (all consist of non-digits)
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'test' AND table_name REGEXP '^\D+$';
```
### 2. What did you expect to see? (Required)
- (a) returns only `UPPERONLY` — same as evaluating `'MixedCaseTbl' REGEXP '^[A-Z]+$'` directly, which is false.
- (b) returns all four tables.
### 3. What did you see instead (Required)
- (a) returns `MixedCaseTbl, UPPERONLY, lc_tbl` — lowercase / mixed-case names are wrongly matched.
- (b) returns **empty** — `\D` was turned into `\d`, inverting the semantics.
- `EXPLAIN` shows the pattern was lower-cased: `table_name_pattern:[^[a-z]+$]`.
Any pattern containing case-sensitive classes (`[A-Z]`, `[^A-Z]`) or negated shorthand classes (`\D`, `\S`, `\W`, `\B`) is corrupted this way.
### 4. What is your TiDB version? (Required)
Reproduced on v8.5.7 (release-8.5, git 1fdc13626a). Root cause confirmed by code inspection in master @ 6f5bfe198f (2026-08-01), which carries identical code.
### Root cause analysis
#64301 (fixing #64249) reworked the information_schema predicate extractor to compile REGEXP predicates into Go regexps. In doing so it routes the pattern through `extractLikePatternCol(..., toLower=true, ...)` (`pkg/planner/core/memtable_infoschema_extractor.go`), which applies `strings.ToLower(pattern)` (`pkg/planner/core/memtable_predicate_extractor.go`) before compiling `(?i)`, and then drops the original predicate from `remained` so filtering relies solely on the corrupted pattern.
Lower-casing a regex pattern is not semantics-preserving, even under `(?i)`:
- `^[A-Z]+$` → `(?i)^[a-z]+$` — matches letters of any case, so lowercase names wrongly match;
- `^\D+$` → `(?i)^\d+$` — negated class inverted, matching digits only, so the query returns empty;
- same for `\S`→`\s`, `\W`→`\w`, `[^A-Z]`→`[^a-z]`, etc.
The case-insensitive matching intent of #64301 (`lower_case_table_names=2`) is already achieved by the `(?i)` flag alone — `(?i)` applied to the **original** pattern makes literal letters and `[A-Z]`-style classes match case-insensitively without touching `\D`-style classes. Suggested fix: stop lower-casing the pattern text (compile `(?i)` + original pattern), or otherwise restrict the lower-casing so it cannot rewrite character-class contents.
Related: #64249 (original issue), #64301 (master PR), #64365 (release-8.5 cherry-pick).
Contributor guide
Research direction
Start with pkg/planner/core/memtable_infoschema_extractor.go and pkg/planner/core/memtable_predicate_extractor.go, tracing extractLikePatternCol(..., toLower=true, ...) and the strings.ToLower call. Run the SQL reproduction from the issue and inspect EXPLAIN output. Done means case-sensitive and negated regex classes retain their semantics while the intended case-insensitive matching still works.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100