Skip to content

Fix EXISTS over ungrouped aggregate subqueries - #3780

Open
fulghum wants to merge 1 commit into
mainfrom
fulghum/dolt-11489
Open

Fix EXISTS over ungrouped aggregate subqueries#3780
fulghum wants to merge 1 commit into
mainfrom
fulghum/dolt-11489

Conversation

@fulghum

@fulghum fulghum commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Ungrouped aggregates produce one row even when their input is empty. Preserve that cardinality when simplifying EXISTS subqueries, and fold provably nonempty aggregate EXISTS predicates directly so correlated forms do not regress to per-row subquery execution.

Teach TableCopier to expose its independently analyzed create and source trees, accept narrow integrator-owned create destinations, and close the create iterator before copying rows. Adds coverage for SELECT and write statements, including CTAS, plus HAVING / LIMIT / OFFSET boundaries.

Fixes dolthub/dolt#11489
Fixes dolthub/dolt#11477
Fixes dolthub/dolt#11508

@itoqa

itoqa Bot commented Sep 4, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: eb050ac: 19 test cases ran, 19 passed ✅.

Summary

Coverage spans core data operations for creating and copying tables, preserving columns, values, filters, limits, and source-to-destination alignment. It also exercises edge cases around empty aggregates, correlated checks, row-removing conditions, invalid plan layouts, error handling, and resource cleanup, with results indicating healthy behavior across these paths.

Safe to merge — the exercised application behaviors all passed, with no regressions, new failures, or previously flagged failures attributable to this PR. No merge-blocking risk was identified.

Tests run by Ito

View full run

Result Severity Type Description
General The aggregate checks returned both outer rows without grouping, then returned only the matching grouped row and the nonmatching row for the inverse check.
General Correlated EXISTS queries returned no outer rows when false HAVING, LIMIT 0, or LIMIT 1 with OFFSET 1 removed the aggregate row. The results matched an explicitly empty subquery, and the focused regression test passed.
General Creating a table from matching, renamed, extra-column, and limited queries kept the expected columns and values in every case.
General Creating tables from reordered, renamed, and limited queries kept each value under the right column and copied the expected rows.
General A backend copy failure returned the real error instead of claiming that the table was copied. The fallback table-creation path also kept the right columns and copied only the selected row.
General The local database returned the expected rows for the combined query, the filtered query, the unmatched correlation, and the equivalent query. The database port is not a web page, so browser navigation was not used for the SQL check.
General The browser check was unavailable because this local target speaks MySQL, not HTTP. Source inspection and focused Go tests confirm that the create plan and source query are both kept after analysis.
Cardinality A false HAVING condition, a zero limit, and an offset past the only aggregate row all return no outer rows. This is the expected result when each parent removes the aggregate row.
Cardinality A positive limit keeps the aggregate result, so both outer rows are returned. A zero limit or an offset past that row returns no rows, as expected.
Copier The browser check was unavailable because this local target speaks MySQL, not HTTP. Source inspection and focused Go tests confirm that the create plan and source query are both kept after analysis.
Copier Malformed table-copy layouts are rejected with an invalid child-count error, and no replacement copier is created.
Create Creating a table from a query kept the selected columns and copied both source rows correctly.
Create Creating a table from a limited query kept the selected columns and copied exactly the first row.
Exists The correlated COUNT query kept both outer rows, even when the inner filter found no match. The matching NOT EXISTS query returned no rows, as expected.
Exists The empty filtered COUNT query kept both outer rows for EXISTS. The matching NOT EXISTS query returned no rows, as expected.
Iterator The create-table step closes its iterator and stops when closing returns an error. The focused lifecycle test passed.
Rev A row with a NULL key was returned even though no inner row matched it. This is the expected SQL behavior because COUNT(*) still produces one aggregate row for empty input.
Rev The empty COUNT aggregate kept the EXISTS condition true, while the separate enabled filter removed the disabled row. The query returned only id 1, as expected.
Rev Creating a table from filtered source data copied the row with value 10 and left out the row with value 20. The new table also has the expected integer column.

Tip

Reply with @itoqa to send us feedback on this test run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment