Skip to content

fix: MySQL comprehensions generated SQL that silently matched nothing - #44

Merged
richardwooding merged 1 commit into
mainfrom
fix/mysql-comprehensions
Aug 22, 2026
Merged

fix: MySQL comprehensions generated SQL that silently matched nothing#44
richardwooding merged 1 commit into
mainfrom
fix/mysql-comprehensions

Conversation

@richardwooding

Copy link
Copy Markdown
Contributor

Ports SPANDigital/cel2sql#177 and the MySQL half of SPANDigital/cel2sql#169 to the Java port.

The bugs

MySQL used the default comprehension source — JSON_TABLE(...) AS x — where a bare reference to x is an unknown column (JSON_TABLE is table-valued). The integration harness knew: it carried Assumptions.assumeTrue(dialect.name() != MYSQL, "MySQL: comprehensions not supported") instead of a fix, so every MySQL comprehension case was skipped.

Fixing the column reference alone isn't enough: the MySQL 8.x optimizer transforms a correlated EXISTS into a semijoin and loses the correlation to the JSON_TABLE source, so exists()/all() execute without error and silently match nothing. Works from MySQL 9.x; verified against 8.4.11 and 9.7.1 locally (semijoin=off fixes it, LIMIT 1 does not, COUNT comparisons are never transformed).

The fix

  • MySqlDialect.writeComprehensionSource(SELECT value AS x FROM JSON_TABLE(...) AS jt) AS _t, the same derived-table idiom SqliteDialect already uses for json_each
  • New Dialect.writeComprehensionExists/writeComprehensionNotExists defaults (EXISTS/NOT EXISTS) with MySQL overriding to (SELECT COUNT(*) FROM ...) > 0 / = 0
  • The MySQL comprehension skip is removed from the integration testscomp_all/comp_exists/comp_exists_one now execute against the MySQL container in CI, which is the real verification for this PR
  • Unit expectations added for all five macros (all/exists/exists_one/filter/map) on MySQL

Other dialects' output is unchanged.

Same fix landed in Go (cel2sql v3.8.10) and Python (pycel2sql v0.4.2); a C# port PR follows.

Ports SPANDigital/cel2sql#177 (and the MySQL half of #169). MySQL used
the default comprehension source, JSON_TABLE(...) AS x, where a bare
reference to x is an unknown column — and the integration harness
carried an assumption skipping every MySQL comprehension case
('comprehensions not supported') instead of a fix. On top of that, the
MySQL 8.x optimizer transforms a correlated EXISTS into a semijoin and
loses the correlation to JSON_TABLE, so exists()/all() would execute
without error and match nothing (works from 9.x; verified against
MySQL 8.4.11 and 9.7.1).

- MySqlDialect.writeComprehensionSource renames the value column to
  the iteration variable through a derived table, the same idiom
  SqliteDialect already uses for json_each
- New Dialect.writeComprehensionExists/writeComprehensionNotExists
  default to EXISTS/NOT EXISTS; MySQL emits COUNT comparisons, which
  the 8.x optimizer never transforms
- The MySQL comprehension skip is removed from the integration tests,
  so comp_all/comp_exists/comp_exists_one now execute against the
  MySQL container in CI
- Unit expectations added for all five macros on MySQL
@richardwooding
richardwooding merged commit 543e384 into main Aug 22, 2026
4 checks passed
@richardwooding
richardwooding deleted the fix/mysql-comprehensions branch August 22, 2026 22:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant