matrixorigin / matrixorigin/matrixone
[Compatibility]: row constructors cannot compare directly with multi-column scalar subqueries
- Dominant language
- Go
- Stars
- 1.9k
- Forks
- 311
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 768
Description
## Description
MatrixOne cannot compare a row constructor with a multi-column scalar subquery. MySQL 8.0 supports the comparison and applies normal scalar-subquery cardinality and NULL semantics. MatrixOne already supports the closely related row-valued `IN` and `= ANY` forms, so the failure is specific to direct scalar comparison.
## Environment
- Branch: `main`
- Commit: `74668fc075c965c462b30f10ae24a1c19c289cfd`
- Deployment: local single-CN `etc/launch/launch.toml`
- Comparison: MySQL 8.0.45
- Date: 2026-09-07
## Steps to reproduce
```sql
CREATE DATABASE row_scalar_repro;
USE row_scalar_repro;
CREATE TABLE t(id INT PRIMARY KEY, a INT, b INT);
INSERT INTO t VALUES (1,1,5),(2,1,NULL),(3,2,8),(4,2,10);
SELECT (1,5) = (SELECT a,b FROM t WHERE id=1) AS v;
SELECT (1,5) = (SELECT a,b FROM t WHERE id=99) AS empty_v;
```
## Actual behavior
Both statements are rejected during expression binding:
```text
ERROR 20203 (HY000): invalid argument operator =, bad value [TUPLE TUPLE]
```
`<>` and a prepared form such as `SELECT (?,?) = (SELECT a,b ...)` are rejected in the same way.
## Expected behavior
MySQL-compatible results are:
- one matching row: `1`;
- an empty scalar subquery: `NULL`;
- a returned row containing NULL: SQL row-comparison NULL semantics;
- more than one returned row: error 1242, `Subquery returns more than 1 row`.
## Stability and controls
- `=`, `<>`, NULL-row, empty-row, multi-row, and prepared variants reproduced 3/3.
- MySQL returned the expected Boolean/NULL values and error 1242 in all three runs.
- `(a,b) IN (SELECT ...)` and `(a,b) = ANY (SELECT ...)` returned the same rows on MatrixOne and MySQL in all three runs.
- Read-only statements; subsequent queries remained usable.
## Code analysis
`baseBinder.bindComparisonExpr` expands a row comparison only when both AST operands are literal `tree.Tuple` nodes. A multi-column subquery is bound as a scalar-subquery expression whose type is TUPLE, so the generic function binder receives `[TUPLE TUPLE]` before `flatten_subquery.go` can generate per-column row comparisons. The quantified-subquery path already carries a `SubqueryRef.Child` list and calls `generateRowComparison`, which explains why `= ANY` works.
## Regression coverage
After implementation, cover direct `=`, `<>`, ordering comparisons and `<=>` where supported; one row, empty row, NULL fields, multi-row error, correlated input, type coercion, and SQL/binary prepared statements.
## Related
- No matching open or closed issue was found for direct row-constructor versus multi-column scalar-subquery comparison.
Contributor guide
Assessment
This issue has not been assessed yet.