Repository navigation
Conversation
… up scalar aggregate
There was a problem hiding this comment.
Thanks @comphead
nit: In decorrelate.rs around line 511, putting a filter between the two aggregates gives a confusing error instead of the not-implemented one:
SELECT o.k, (SELECT count(*) FROM (SELECT count(*) AS n FROM nested_agg_inner AS i WHERE i.id = o.k) WHERE n >= 0)
FROM nested_agg_outer AS o ORDER BY o.k;
-- Schema error: Schema contains duplicate unqualified field name "count(Int64(1))"The first optimizer pass now leaves the subquery correlated, and the second pass rewrites a reshaped plan that fails with a schema error. With datafusion.optimizer.max_passes = 1 it gives the expected Physical plan does not support logical expression ScalarSubquery. It's still an error (main returned (2, 0) here, which was wrong), so it would be nice to add it as a test case even if the message stays as is.
| // A pulled up scalar aggregate below gives no row for unmatched | ||
| // outer rows, so this aggregate would see an empty input. | ||
| // See https://github.com/apache/datafusion/issues/26508 | ||
| if self.pulled_up_scalar_agg { |
There was a problem hiding this comment.
This also blocks some shapes that were correct before and now fail to plan. For example, max over max, sum over sum and count(m) over max all returned the right rows on main, and now hit Physical plan does not support logical expression ScalarSubquery:
SELECT o.k, (SELECT max(m) FROM (SELECT max(i.v) AS m FROM nested_agg_inner AS i WHERE i.id = o.k))
FROM nested_agg_outer AS o ORDER BY o.k;
-- main: (1, 10), (2, NULL) this PR: errorBlocking makes sense to me, since a precise rule would have to evaluate the outer aggregate on the inner aggregate's empty-input row. Could you mention this in the PR description and add a test that pins the new error, so it's clear this is an intended trade-off?
| statement error DataFusion error: This feature is not implemented: Physical plan does not support logical expression ScalarSubquery | ||
| SELECT o.k, (SELECT max(n) FROM (SELECT count(*) AS n FROM nested_agg_inner AS i WHERE i.id = o.k)) FROM nested_agg_outer AS o ORDER BY o.k; | ||
|
|
||
| # IN and LATERAL. Expected `2` and `(1, 1), (2, 0)`. |
There was a problem hiding this comment.
nit: Two more shapes that returned wrong rows on main are now blocked too: EXISTS, and a projection between the two aggregates. It would be good to cover them here:
+# EXISTS. Expected `1, 2`.
+statement error DataFusion error: This feature is not implemented: Physical plan does not support logical expression Exists
+SELECT o.k FROM nested_agg_outer AS o WHERE EXISTS (SELECT max(n) FROM (SELECT count(*) AS n FROM nested_agg_inner AS i WHERE i.id = o.k)) ORDER BY o.k;
+
+# A projection between the aggregates. Expected `(1, 2), (2, 1)`.
+statement error DataFusion error: This feature is not implemented: Physical plan does not support logical expression ScalarSubquery
+SELECT o.k, (SELECT max(n2) FROM (SELECT n + 1 AS n2 FROM (SELECT count(*) AS n FROM nested_agg_inner AS i WHERE i.id = o.k))) FROM nested_agg_outer AS o ORDER BY o.k;
Which issue does this PR close?
Rationale for this change
An aggregate without
GROUP BYreturns one row even when its input is empty. When such an aggregate is in a correlated subquery and another aggregate sits above it, DataFusion returns wrong results for outer rows that match no inner rows:For
a = 2this returnsv1 = 0, v2 = NULLinstead ofv1 = 1, v2 = 0, which DuckDB, SQLite and PostgreSQL return. The same shape gives wrong results underINandLEFT JOIN LATERALtoo.When the correlated filter is pulled up, the inner scalar aggregate becomes a grouped aggregate, which returns no row for the outer rows it does not match. The aggregate above then runs over an empty input. The count bug handling only fixes the aggregate the join reads, which here is the outer one, so the inner row is never restored. The existing
TODOnext topulled_up_scalar_aggdescribed this case.What changes are included in this PR?
PullUpCorrelatedExprstops the pull up when it reaches anAggregateafter a scalar aggregate has already been pulled up. The subquery stays correlated, the same way a window over an aggregate with count bug handling does today. Such a query now fails withPhysical plan does not support logical expression ScalarSubqueryinstead of returning wrong rows.A projection over the scalar aggregate (
SELECT n + 1 FROM (SELECT count(*) AS n ...)) and an aggregate over a grouped aggregate are still pulled up.What is the testing strategy for this PR?
New
sqllogictestcases at the end ofsubquery.sltcover the two queries from the issue, theINandLATERALforms, and the two shapes that must still be pulled up. Allsqllogictestfiles, thedatafusion-optimizerunit tests and thecore_integrationsql::tests (which include the TPC-DS planning tests) pass locally.Are there any user-facing changes?
Queries with this shape now return a "not implemented" error instead of wrong results. No API changes.