cloud-fan commented on code in PR #60076:
URL: https://github.com/apache/spark/pull/60076#discussion_r4483410046
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ColumnResolutionHelper.scala:
##########
@@ -473,6 +485,104 @@ trait ColumnResolutionHelper extends Logging with
DataTypeErrorsBase {
includeLastResort = includeLastResort)
}
+ /** Hides helper grouping outputs when missing-column resolution searches an
Aggregate. */
+ private def resolveOutputWithoutHavingHelpers(
+ plan: LogicalPlan,
+ nameParts: Seq[String]): Option[NamedExpression] = {
+ if
(conf.getConf(SQLConf.HAVING_WITH_WINDOW_RESOLVES_GROUPING_COLUMN_FIRST)) {
+ resolveWithoutHavingHelpers(nameParts, plan.output,
plan.resolve(nameParts, conf.resolver))
+ } else {
+ plan.resolve(nameParts, conf.resolver)
+ }
+ }
+
+ /** Keeps helper grouping outputs out of ordinary SELECT-alias resolution. */
+ private def resolveChildrenWithHavingScope(
+ plan: LogicalPlan,
+ nameParts: Seq[String]): Option[NamedExpression] = {
+ val hasHavingScope =
+
plan.getTagValue(ColumnResolutionHelper.HAVING_GROUPING_SCOPE_TAG).contains(true)
||
+ (plan match {
+ // QUALIFY searches below the projection that trims HAVING helper
outputs.
+ case project: Project => project.child.getTagValue(
+ ColumnResolutionHelper.HAVING_GROUPING_SCOPE_TAG).contains(true)
+ case _ => false
+ })
+ if (hasHavingScope) {
+ resolveWithoutHavingHelpers(
+ nameParts, plan.children.flatMap(_.output),
plan.resolveChildren(nameParts, conf.resolver))
+ } else {
+ plan.resolveChildren(nameParts, conf.resolver)
+ }
+ }
+
+ /** Keeps ordinary lookup indexed and hides HAVING helpers in resolved
results and errors. */
+ private def resolveWithoutHavingHelpers(
+ nameParts: Seq[String],
+ output: => Seq[Attribute],
+ resolve: => Option[NamedExpression]): Option[NamedExpression] = {
+ lazy val allOutput = output
+ lazy val visibleOutput = allOutput.filterNot(
+
_.metadata.contains(ColumnResolutionHelper.HAVING_GROUPING_COLUMN_METADATA_KEY))
+ val resolved = try {
+ resolve
+ } catch {
+ // Helpers can cause ambiguity or a nested-field error before lookup
returns a candidate.
+ case _: AnalysisException if visibleOutput.length != allOutput.length =>
+ return LocalRelation(visibleOutput).resolve(nameParts, conf.resolver)
+ }
+ val mayReferenceHelpers = resolved.exists { expression =>
+ val references = expression.references
+ references.exists(
+
_.metadata.contains(ColumnResolutionHelper.HAVING_GROUPING_COLUMN_METADATA_KEY))
||
+ // NULL field extraction can discard the helper reference during
resolution.
+ (nameParts.length > 1 && references.isEmpty &&
+ visibleOutput.length != allOutput.length)
+ }
+ if (mayReferenceHelpers) {
+ LocalRelation(visibleOutput).resolve(nameParts, conf.resolver)
+ } else {
+ resolved
+ }
+ }
+
+ /**
+ * Resolves complete grouping expressions before SELECT aliases in extracted
HAVING filters.
+ * Resolve against the full input to retain ambiguity, and only accept
actual grouping
+ * expressions so ungrouped inputs cannot mask aliases or influence their
type coercion.
+ */
+ private def resolveHavingGroupingExpressions(
+ expression: Expression,
+ plan: LogicalPlan,
+ wrap: Expression => Expression = identity): Expression = {
+ if
(!plan.getTagValue(ColumnResolutionHelper.HAVING_GROUPING_SCOPE_TAG).contains(true))
{
+ return expression
+ }
+ val aggregate = plan.collectFirst { case aggregate: Aggregate => aggregate
}
+ aggregate.map { aggregate =>
+ // Leave lambda parameters and captures alone until the lambda has been
bound.
+ def resolveGrouping(expression: Expression): Expression = expression
match {
+ case _ if expression.resolved => expression
+ case lambda: LambdaFunction if !lambda.bound => lambda
+ case _ =>
+ val candidate = resolveExpressionByPlanOutput(expression,
aggregate.child) match {
+ case Alias(child, _) => child
+ case other => other
+ }
+ val grouping = if (candidate.resolved &&
+
aggregate.groupingExpressions.exists(_.semanticEquals(candidate))) {
+ // Keep inputs until aggregate extraction. UpdateOuterReferences
then maps
+ // complete correlated grouping expressions to their real
aggregate outputs.
+ Some(candidate)
Review Comment:
**Blocking (P1):** With
`spark.sql.analyzer.havingWithWindowResolvesGroupingColumnFirst.enabled=true`,
this line binds a HAVING reference to a grouping key directly to the
Aggregate's input attribute (`k#1`). When the key isn't selected as-is, the
Aggregate outputs only the helper alias. So the Filter counts as resolved but
references an attribute its child doesn't produce until
ResolveAggregateFunctions remaps it. `preserveHavingScope` also stops
ResolveReferences from adding `k#1` to the Aggregate.
ResolveSQLFunctions runs between those two rules (Analyzer.scala:708 vs.
724). Its Filter-over-Aggregate deferral (3023-3026) only recognizes unresolved
filters, aggregates, grouping functions and `TempResolvedColumn`, so it
rewrites this Filter or its Aggregate and puts a Project between them. After
that, the `Filter(cond, agg: Aggregate)` remap never matches, and CheckAnalysis
fails with missing attributes.
Here are two examples, using `CREATE TEMPORARY FUNCTION having_id(v INT)
RETURNS INT RETURN v`. By my reading of the rules, both fail analysis with the
conf on. On the base branch and with the conf off, both return `(20, 1)`:
```sql
SELECT sum(v) AS s, count(*) OVER () AS n FROM VALUES (1, 10), (2, 20) AS
t(k, v)
GROUP BY k HAVING having_id(k) > 1;
SELECT having_id(sum(v)) AS s, count(*) OVER () AS n FROM VALUES (1, 10),
(2, 20) AS t(k, v)
GROUP BY k HAVING k > 1;
```
On the base branch, `k` resolves to a `TempResolvedColumn`, so the
SQL-function rewrite waits until the grouping column is in the Aggregate
output. The new goldens use a SQL UDF only inside a FROM subquery, so they
don't cover this.
See **Shared repair plan 1** in the review body.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala:
##########
@@ -4838,7 +4889,14 @@ object UpdateOuterReferences extends Rule[LogicalPlan] {
// Collect the aliases from output of aggregate.
val outerAliases = a.aggregateExpressions collect { case a: Alias
=> a }
// Update the subquery plan to record the OuterReference to point
to outer query plan.
- s.withNewPlan(updateOuterReferenceInSubquery(s.plan, outerAliases))
+ val newPlan = updateOuterReferenceInSubquery(s.plan, outerAliases)
+ val newSubquery = s.withNewPlan(newPlan)
+ if
(f.getTagValue(ColumnResolutionHelper.HAVING_GROUPING_SCOPE_TAG).contains(true))
{
Review Comment:
**Non-blocking (P2):** This refresh only fires while the HAVING Filter still
carries `HAVING_GROUPING_SCOPE_TAG` and still sits directly over its Aggregate.
Two Resolution-batch rules break that after the subquery is resolved:
- `ResolveQualify.resolveQualifyCondition` always rebuilds the nested Filter
with `f.copy(child = newChild)` (Analyzer.scala:4608-4610). Only the rule's
result root gets `copyTagsFrom`, so the HAVING Filter loses the tag.
- When HAVING contains a SQL UDF, the `ResolveSQLFunctions` Filter case
(3092-3097) rebuilds an untagged Filter over a Project, so `Filter(_, a:
Aggregate)` no longer matches here at all.
ResolveAggregateFunctions doesn't fix the outer attributes before that,
because for a computed key the recorded input (`g#1`) is not itself a grouping
expression. The Filter therefore keeps referencing `g#1`, which the Aggregate
doesn't output, and CheckAnalysis fails. By my reading of the rules, this fails
analysis with the conf on:
```sql
SELECT (g + 1) + 10 AS g, count(*) OVER () AS n
FROM VALUES (1), (2) AS t(g)
GROUP BY g + 1 HAVING (SELECT g + 1) >= 3 QUALIFY n > 0;
```
Without `QUALIFY n > 0` it returns `(13, 1)`, as the new golden shows.
Replacing the QUALIFY with a `having_id(1) > 0 AND` term in HAVING fails the
same way.
See **Shared repair plan 1** in the review body.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala:
##########
@@ -3883,12 +3932,14 @@ class Analyzer(
throw
QueryCompilationErrors.lateralColumnAliasInAggWithWindowAndHavingUnsupportedError(
lcaRef.nameParts)
})
- val (windowExpressions, aggregateExpressions) =
extract(aggregateExprs, groupingExprs)
- // Create an Aggregate operator to evaluate aggregation functions.
- val withAggregate = Aggregate(groupingExprs, aggregateExpressions,
child)
- // Add a Filter operator for conditions in the Having clause.
- val withFilter = Filter(condition, withAggregate)
- val withWindow = addWindow(windowExpressions, withFilter)
+ val withWindow = if (
+
conf.getConf(SQLConf.HAVING_WITH_WINDOW_RESOLVES_GROUPING_COLUMN_FIRST)) {
+ addWindowsWithHaving(groupingExprs, aggregateExprs, condition, child)
Review Comment:
**Non-blocking (P2):** This is the only place where the conf moves HAVING
below every window. Two unchanged producers that build the HAVING Filter
themselves don't get the fix:
- GROUPING SETS / ROLLUP / CUBE: when the condition already resolves in
`ResolveGroupingAnalytics.tryResolveHavingCondition` (Analyzer.scala:936-987),
for example a grouping column compared with a literal of the same type, that
rule returns `Filter(newCond, newChild)` over the window-bearing expanded
Aggregate. ExtractWindowExpressions' Aggregate case then extracts the windows
below that Filter.
- Generator queries: `ResolveUnresolvedHaving.insertFilterBeforeWindowChain`
(4977-4991) only recurses through directly stacked Windows, so the Project
between nested windows leaves the Filter above the inner window.
By my reading of these rules, with the conf on:
- `SELECT k, count(*) OVER () AS n FROM VALUES (1, 10), (2, 20), (3, 30) AS
t(k, v) GROUP BY GROUPING SETS ((k)) HAVING k = 2` still computes n=3 instead
of 1.
- The new nested-window golden query with `GROUP BY ROLLUP(k)` computes n=10
instead of 1.
- The same query with `explode(array(k)) AS col` added to the SELECT list
computes n=2 instead of 1.
This extends an [existing
discussion](https://github.com/apache/spark/pull/60076#issuecomment-6633654035).
**Recommended change:** With the conf on, place HAVING below every window on
the remaining producers. When the grouping-sets Aggregate carries window
functions, ResolveGroupingAnalytics should leave the HAVING clause unresolved
over the expanded Aggregate, as it already does when the condition cannot
resolve yet, so window extraction places it with the conf-gated logic. The
generator path's HAVING placement should continue through the Project that sits
between nested windows while the condition's columns are still available below
it. Add HAVING golden coverage for grouping-sets and generator queries with
windows under both conf values.
**Why this works:** Both producers then hand placement to the logic that
already filters groups before every window. Grouping-sets queries reach the
same UnresolvedHaving-over-Aggregate shape that their other sub-path already
produces. Generator queries walk through the extraction Projects to the
innermost window, guarded by the existing reference check.
**Scope:** Route grouping-sets HAVING with windows, and generator HAVING
with nested windows, to below-every-window placement when the conf is on.
**Compatibility:** With the conf off, every producer keeps its base-branch
placement, including the unconditional SPARK-58503 behavior for directly
stacked windows in generator queries.
**Risks:** With the conf on, grouping-sets HAVING conditions that used to
resolve inside ResolveGroupingAnalytics now resolve through the HAVING-scope
lookup and helpers. Results for grouping keys must agree, since both paths are
grouping-first, and grouping()/grouping_id() rewriting must still apply to the
moved Filter. Turning the conf off rolls this back. Pushing the generator
path's Filter through extraction Projects changes those plans with the conf on.
The existing reference check must keep it from crossing a Project that does not
pass the condition's columns through.
**Constraints:** Gate both changes on
spark.sql.analyzer.havingWithWindowResolvesGroupingColumnFirst.enabled so that
the conf-off plans match the base branch. Keep the unconditional SPARK-58503
placement for directly stacked windows unchanged.
**Success:** With the conf on, `SELECT k, count(*) OVER () AS n FROM VALUES
(1, 10), (2, 20), (3, 30) AS t(k, v) GROUP BY GROUPING SETS ((k)) HAVING k = 2`
returns n=1, and the ROLLUP query with `sum(rank() OVER (ORDER BY sum(v))) OVER
()` and `HAVING k = 2` returns 1. With the conf on, `SELECT explode(array(k))
AS col, k, sum(rank() OVER (ORDER BY sum(v))) OVER () AS n FROM VALUES (1, 10),
(2, 20), (3, 30) AS t(k, v) GROUP BY k HAVING k = 2` returns n=1. With the conf
off, these queries keep their base-branch results (n=3, 10 and 2).
**Verification:**
- **Regression:** With the conf on, GROUPING SETS, ROLLUP and CUBE queries
with single and nested window functions whose HAVING condition compares a
grouping column with a same-typed literal compute their windows only over the
groups that HAVING keeps.
- **Regression:** With the conf on, a generator query with nested window
functions and HAVING computes the inner window only over the groups that HAVING
keeps.
- **Compatibility:** With the conf off, the same grouping-sets and generator
queries keep the base branch's plans and results, and directly stacked
generator windows keep the SPARK-58503 placement.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ColumnResolutionHelper.scala:
##########
@@ -473,6 +485,104 @@ trait ColumnResolutionHelper extends Logging with
DataTypeErrorsBase {
includeLastResort = includeLastResort)
}
+ /** Hides helper grouping outputs when missing-column resolution searches an
Aggregate. */
+ private def resolveOutputWithoutHavingHelpers(
+ plan: LogicalPlan,
+ nameParts: Seq[String]): Option[NamedExpression] = {
+ if
(conf.getConf(SQLConf.HAVING_WITH_WINDOW_RESOLVES_GROUPING_COLUMN_FIRST)) {
+ resolveWithoutHavingHelpers(nameParts, plan.output,
plan.resolve(nameParts, conf.resolver))
+ } else {
+ plan.resolve(nameParts, conf.resolver)
+ }
+ }
+
+ /** Keeps helper grouping outputs out of ordinary SELECT-alias resolution. */
+ private def resolveChildrenWithHavingScope(
+ plan: LogicalPlan,
+ nameParts: Seq[String]): Option[NamedExpression] = {
+ val hasHavingScope =
+
plan.getTagValue(ColumnResolutionHelper.HAVING_GROUPING_SCOPE_TAG).contains(true)
||
+ (plan match {
+ // QUALIFY searches below the projection that trims HAVING helper
outputs.
+ case project: Project => project.child.getTagValue(
+ ColumnResolutionHelper.HAVING_GROUPING_SCOPE_TAG).contains(true)
+ case _ => false
+ })
+ if (hasHavingScope) {
+ resolveWithoutHavingHelpers(
+ nameParts, plan.children.flatMap(_.output),
plan.resolveChildren(nameParts, conf.resolver))
+ } else {
+ plan.resolveChildren(nameParts, conf.resolver)
+ }
+ }
+
+ /** Keeps ordinary lookup indexed and hides HAVING helpers in resolved
results and errors. */
+ private def resolveWithoutHavingHelpers(
+ nameParts: Seq[String],
+ output: => Seq[Attribute],
+ resolve: => Option[NamedExpression]): Option[NamedExpression] = {
+ lazy val allOutput = output
+ lazy val visibleOutput = allOutput.filterNot(
+
_.metadata.contains(ColumnResolutionHelper.HAVING_GROUPING_COLUMN_METADATA_KEY))
+ val resolved = try {
+ resolve
+ } catch {
+ // Helpers can cause ambiguity or a nested-field error before lookup
returns a candidate.
+ case _: AnalysisException if visibleOutput.length != allOutput.length =>
+ return LocalRelation(visibleOutput).resolve(nameParts, conf.resolver)
+ }
+ val mayReferenceHelpers = resolved.exists { expression =>
+ val references = expression.references
+ references.exists(
+
_.metadata.contains(ColumnResolutionHelper.HAVING_GROUPING_COLUMN_METADATA_KEY))
||
+ // NULL field extraction can discard the helper reference during
resolution.
+ (nameParts.length > 1 && references.isEmpty &&
+ visibleOutput.length != allOutput.length)
+ }
+ if (mayReferenceHelpers) {
+ LocalRelation(visibleOutput).resolve(nameParts, conf.resolver)
+ } else {
+ resolved
+ }
+ }
+
+ /**
+ * Resolves complete grouping expressions before SELECT aliases in extracted
HAVING filters.
+ * Resolve against the full input to retain ambiguity, and only accept
actual grouping
+ * expressions so ungrouped inputs cannot mask aliases or influence their
type coercion.
+ */
+ private def resolveHavingGroupingExpressions(
+ expression: Expression,
+ plan: LogicalPlan,
+ wrap: Expression => Expression = identity): Expression = {
+ if
(!plan.getTagValue(ColumnResolutionHelper.HAVING_GROUPING_SCOPE_TAG).contains(true))
{
+ return expression
+ }
+ val aggregate = plan.collectFirst { case aggregate: Aggregate => aggregate
}
+ aggregate.map { aggregate =>
+ // Leave lambda parameters and captures alone until the lambda has been
bound.
+ def resolveGrouping(expression: Expression): Expression = expression
match {
+ case _ if expression.resolved => expression
+ case lambda: LambdaFunction if !lambda.bound => lambda
+ case _ =>
+ val candidate = resolveExpressionByPlanOutput(expression,
aggregate.child) match {
+ case Alias(child, _) => child
+ case other => other
+ }
+ val grouping = if (candidate.resolved &&
Review Comment:
**Non-blocking (P2):** `candidate.resolved` only holds here for grouping
expressions that resolve by attribute lookup alone. For a key like
`lower(name)`, `resolveExpressionByPlanOutput` resolves `name` but leaves
`lower` as an unresolved function. The subtree therefore never matches the
grouping key. The walk descends to the bare `name`, and
`resolveChildrenWithHavingScope` binds it to a same-named SELECT alias.
ResolveFunctions waits for resolved arguments, so the function is never
compared with the grouping key first. Keys that need an implicit cast, such as
`GROUP BY k + 1.5`, take the same fallback.
By my reading of the code, this query returns `bob!`:
```sql
SELECT concat(lower(name), '!') AS name
FROM VALUES ('Bob'), ('bob'), ('Al') AS t(name)
GROUP BY lower(name) HAVING lower(name) = 'bob';
```
Adding `, count(*) OVER () AS n` makes it return no row, with the conf on as
well as off. The operator key in `GROUP BY g + 1 HAVING g + 1 >= 3` is fixed.
So with the conf on, function and cast keys still get the wrong groups that the
PR fixes for other keys. The description scopes function and type normalization
out, but this is the same failure on the same path.
**Recommended change:** In the HAVING-scope grouping lookup, handle a HAVING
subtree whose columns all resolve against the Aggregate input but which stays
unresolved because of function calls or implicit casts. Resolve a detached copy
of it over that input the way the analyzer would, including functions and
implicit casts. Bind the subtree to the grouping expression only when that copy
semantically equals one. Otherwise discard the copy and keep today's alias
fallback. Add HAVING golden coverage for function-based and cast-requiring
grouping keys under both conf values.
**Why this works:** The tentative copy gives the lookup the same view of
lower(name) or k + 1.5 that the Aggregate's own grouping expressions have. A
complete match then binds through the existing grouping path, and
ResolveAggregateFunctions later remaps it to the Aggregate output. A non-match
leaves the original unresolved subtree unchanged, so alias binding and its type
behavior stay as they are now.
**Scope:** Let the HAVING-scope lookup recognize grouping keys that need
function resolution or implicit casts, without changing alias fallback for
anything else.
**Compatibility:** Alias fallback for non-grouped names keeps its current
binding and types, and analysis with the conf off is unchanged.
**Risks:** Each candidate subtree that is unresolved only because of
functions or casts now pays a tentative resolution over the Aggregate input.
This analyzer-only cost applies only with the conf on and is bounded by the
HAVING condition's size. The tentative resolution must have no side effects,
for example on SQL function context or nested-resolution state, and its
failures must leave the subtree untouched so that errors stay as they are today.
**Constraints:** Accept only complete grouping-expression matches, and keep
alias fallback and its types for every other name. Apply only to the HAVING
Filter created under the conf, so analysis with the conf off is unchanged.
**Success:** With the conf on, `SELECT concat(lower(name), '!') AS name,
count(*) OVER () AS n FROM VALUES ('Bob'), ('bob'), ('Al') AS t(name) GROUP BY
lower(name) HAVING lower(name) = 'bob'` returns the `bob!` row, as the same
query without the window does. With the conf on, a grouping key whose HAVING
spelling needs an implicit cast, such as `GROUP BY k + 1.5`, binds to the
grouping key rather than a same-named alias. Names that do not form a complete
grouping expression keep resolving to SELECT aliases with the alias types.
**Verification:**
- **Regression:** With the conf on, HAVING references to function-based
grouping keys (such as lower and substr) and cast-requiring grouping keys,
whose input column shares a name with a SELECT alias, keep the same groups as
the equivalent query without a window function.
- **Compatibility:** With the conf on, the existing alias-fallback,
ambiguity and alias-type cases keep their results, and with the conf off the
new cases keep the base branch's results.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala:
##########
@@ -465,6 +465,17 @@ object SQLConf {
.intConf
.createWithDefault(100)
+ val HAVING_WITH_WINDOW_RESOLVES_GROUPING_COLUMN_FIRST =
+
buildConf("spark.sql.analyzer.havingWithWindowResolvesGroupingColumnFirst.enabled")
+ .internal()
+ .withBindingPolicy(ConfigBindingPolicy.SESSION)
+ .doc("When true, preserve HAVING name-resolution scope during window
extraction so that " +
Review Comment:
**Nit (P3):** The doc only describes name precedence, but this flag also
switches ExtractWindowExpressions to `addWindowsWithHaving`
(Analyzer.scala:3935-3942). That path puts HAVING below every window, including
the inner window of a nested window function, and it changes results even
without a name collision. For `SELECT k, sum(rank() OVER (ORDER BY sum(v)))
OVER () AS n FROM VALUES (1, 10), (2, 20), (3, 30) AS t(k, v) GROUP BY k HAVING
k = 2`, the new golden returns n=1 with the flag on. With the flag off, the
inner rank runs over all three groups. Could the doc say that true also filters
groups before every window, and that false keeps the legacy placement as well?
**Verification:**
- **Inspection:** The conf's doc text names both the HAVING name-resolution
precedence and the HAVING-below-every-window placement, for both values.
##########
sql/core/src/test/resources/sql-tests/inputs/having.sql:
##########
@@ -127,3 +127,165 @@ SELECT col2 FROM hav_outer GROUP BY col2 HAVING (SELECT
max(col1)) > 1;
SELECT col2 FROM hav_outer GROUP BY col2 HAVING (SELECT col1);
DROP VIEW hav_outer;
+
+-- HAVING keeps grouping scope below a window; aggregate aliases keep their
types.
+SET
spark.sql.analyzer.havingWithWindowResolvesGroupingColumnFirst.enabled=true;
Review Comment:
**Non-blocking (P2):** Every conf-on query in this block uses plain GROUP
BY. GROUPING SETS / ROLLUP / CUBE queries with a window function also reach
`addWindowsWithHaving` whenever their HAVING condition is still unresolved in
`tryResolveHavingCondition`, for example because it calls an aggregate. On that
path the Expand's virtual `spark_grouping_id` becomes a hidden helper unless
`grouping_id()` is selected. ResolveGroupingAnalytics then rewrites
`grouping()` / `grouping_id()` in HAVING and ORDER BY into references to that
same name. I traced `... GROUP BY GROUPING SETS ((k), ()) HAVING sum(v) > 0 AND
grouping_id() = 0`, with `grouping_id()` unselected, and found no failure. But
nothing pins it: the existing grouping-sets + HAVING + window goldens in
window-with-aggregates.sql run with the conf off.
**Verification:**
- **Behavior:** With the conf on, grouping-sets queries with windows and a
HAVING condition on an aggregate, a grouping() predicate, or an ORDER BY
grouping_id() resolve without exposing the hidden grouping-id helper, and
return the rows the equivalent queries without windows produce.
- **Compatibility:** With the conf off, the same queries return the base
branch's results.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]