dolthub / dolthub/dolt

Wrong results: LEFT JOIN LATERAL (...) ON TRUE drops outer rows whose lateral body yields zero rows

Open
#11,479 0 comments 0 reactions 0 assignees View on GitHub
bug correctness customer issue sql
Dominant language
Go
Stars
24.4k
Forks
873
Avg merge
1d 8h
Merged PRs (30d)
120

Description

### What happens

`LEFT JOIN LATERAL () ON TRUE` must **preserve every outer row**: when the lateral body
produces zero rows for a given outer row, that outer row must still appear once, with the body's
columns filled with `NULL`. Dolt instead **drops** such outer rows entirely — the `LEFT JOIN LATERAL`
degenerates into `CROSS`/`INNER JOIN LATERAL`. This is silent and diverges from MySQL 8.0.

In the repro, outer row `id=3` has no match in the lateral body, so the body yields zero rows. MySQL
keeps `(3, NULL)`; Dolt omits row `3` altogether — result-set membership is silently wrong.

Two boundaries scope the trigger tightly to "`LEFT JOIN LATERAL` × a body that can yield zero rows":
an **aggregate** lateral body (which always yields exactly one row, so there is nothing to drop) is
correct, and an **ordinary non-LATERAL** `LEFT JOIN` is correct.

### To Reproduce

```sql
CREATE DATABASE IF NOT EXISTS dolt_bug3;
USE dolt_bug3;
DROP TABLE IF EXISTS t0;
DROP TABLE IF EXISTS t1;
CREATE TABLE t0(id INT PRIMARY KEY, c0 INT);
CREATE TABLE t1(id INT PRIMARY KEY, c0 INT);
INSERT INTO t0 VALUES (1,10),(2,20),(3,30); -- id=3 has no match in t1
INSERT INTO t1 VALUES (1,10),(2,20);

-- LEFT JOIN LATERAL ... ON TRUE : outer row id=3 is DROPPED
SELECT t0.id, l.x
FROM t0
LEFT JOIN LATERAL (SELECT 1 AS x FROM t1 WHERE t1.c0 = t0.c0) l ON TRUE
ORDER BY t0.id;
```

### Actual (Dolt 2.2.3) vs Expected (MySQL 8.0)

| outer `id` | lateral body rows | Dolt 2.2.3 (actual) | MySQL 8.0 (expected) |
|---|---|---|---|
| 1 | 1 (matches `c0=10`) | `(1, 1)` | `(1, 1)` |
| 2 | 1 (matches `c0=20`) | `(2, 1)` | `(2, 1)` |
| 3 | 0 (no match) | **row dropped** | `(3, NULL)` |

Dolt returns `{(1,1),(2,1)}`; MySQL 8.0 returns `{(1,1),(2,1),(3,NULL)}`. Row `id=3` is silently
swallowed.

### Isolating boundary experiments

```sql
-- (Boundary 1) aggregate lateral body always yields one row -> nothing to drop -> Dolt CORRECT.
SELECT t0.id, l.n
FROM t0
LEFT JOIN LATERAL (SELECT COUNT(*) AS n FROM t1 WHERE t1.c0 = t0.c0) l ON TRUE
ORDER BY t0.id;

-- (Boundary 2) ordinary (non-LATERAL) LEFT JOIN preserves the outer row -> Dolt CORRECT.
SELECT t0.id, l.x
FROM t0
LEFT JOIN (SELECT c0, 1 AS x FROM t1) l ON l.c0 = t0.c0
ORDER BY t0.id;
```

| Boundary | Dolt 2.2.3 | MySQL 8.0 | verdict |
|---|---|---|---|
| aggregate lateral body (always 1 row) | `(1,1),(2,1),(3,0)` | `(1,1),(2,1),(3,0)` | both correct |
| ordinary non-LATERAL `LEFT JOIN` | `(1,1),(2,1),(3,NULL)` | `(1,1),(2,1),(3,NULL)` | both correct |

The aggregate-body boundary is the sharpest: it is byte-for-byte the same join shape as the failing
query except the body is `SELECT COUNT(*)` (one row) instead of `SELECT 1 … WHERE …` (zero-or-more
rows), and Dolt preserves `id=3` there. So the trigger is exactly "the lateral body can produce zero
rows".

### Related issues (please read before deduping)

This is a **still-open, distinct** manifestation in the LATERAL feature area, **not** a rediscovery of
either closed LATERAL issue:

* **#9820** (`CROSS JOIN LATERAL` + non-equi `JOIN`) — **closed**, and **verified fixed** on Dolt
2.2.3 (its repro now returns correct results here). Different join type (`CROSS`, not `LEFT`) and a
different trigger (non-equi join), so this report is not a reopening of it.
* **#6899** (comma-syntax lateral aggregation) — **closed**. Different syntax (comma lateral) and
concerns aggregation, not `LEFT JOIN LATERAL` row preservation.

The present defect is `LEFT JOIN LATERAL … ON TRUE` failing LEFT-side row preservation when the body
yields zero rows — the same feature area as #9820/#6899 but a separate, currently-reproducing bug.

### Version

Dolt 2.2.3, go-mysql-server. `SELECT dolt_version()` → `2.2.3`; `SELECT version()` → `8.0.31`.
Default configuration, macOS arm64.

---
*Re-verified on Dolt 2.2.3 (`dolt sql-server`); every Actual/Expected value above was cross-checked the same day against a live MySQL 8.0 server (Dolt's stated compatibility target) on identical data, and reproduces deterministically.*

Contributor guide

No contributing guide indexed for this repository

Research direction

Start the supplied SQL reproduction with dolt sql-server on Dolt 2.2.3, then trace the LEFT JOIN LATERAL handling in go-mysql-server. Add regression coverage for a lateral body that returns zero rows, while checking the aggregate and ordinary non-LATERAL boundary cases. Done means the outer row remains with NULL lateral columns and the existing boundary results stay correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, sql
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.