Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -791,9 +791,9 @@ class Analyzer(
// AggregateFunction's with the exception of First and Last in their default mode
// (which we handle) and possibly some Hive UDAF's.
case First(expr, _) =>
First(ifExpr(expr), Literal(true))
First(ifExpr(expr), true)
case Last(expr, _) =>
Last(ifExpr(expr), Literal(true))
Last(ifExpr(expr), true)
case a: AggregateFunction =>
a.withNewChildren(a.children.map(ifExpr))
}.transform {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@

package org.apache.spark.sql.catalyst.expressions.aggregate

import org.apache.spark.sql.catalyst.analysis.{FunctionRegistry, TypeCheckResult}
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{TypeCheckFailure, TypeCheckSuccess}
import org.apache.spark.sql.AnalysisException
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.TypeCheckSuccess
import org.apache.spark.sql.catalyst.dsl.expressions._
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.types._
Expand Down Expand Up @@ -49,12 +50,16 @@ import org.apache.spark.sql.types._
""",
group = "agg_funcs",
since = "2.0.0")
case class First(child: Expression, ignoreNullsExpr: Expression)
case class First(child: Expression, ignoreNulls: Boolean)
extends DeclarativeAggregate with ExpectsInputTypes {

def this(child: Expression) = this(child, Literal.create(false, BooleanType))
def this(child: Expression) = this(child, false)

override def children: Seq[Expression] = child :: ignoreNullsExpr :: Nil
def this(child: Expression, ignoreNullsExpr: Expression) = {
this(child, FirstLast.validateIgnoreNullExpr(ignoreNullsExpr))
}

override def children: Seq[Expression] = child :: Nil

override def nullable: Boolean = true

Expand All @@ -71,16 +76,11 @@ case class First(child: Expression, ignoreNullsExpr: Expression)
val defaultCheck = super.checkInputDataTypes()
if (defaultCheck.isFailure) {
defaultCheck
} else if (!ignoreNullsExpr.foldable) {
TypeCheckFailure(
s"The second argument of First must be a boolean literal, but got: ${ignoreNullsExpr.sql}")
} else {
TypeCheckSuccess
}
}

private def ignoreNulls: Boolean = ignoreNullsExpr.eval().asInstanceOf[Boolean]

private lazy val first = AttributeReference("first", child.dataType)()

private lazy val valueSet = AttributeReference("valueSet", BooleanType)()
Expand Down Expand Up @@ -120,3 +120,10 @@ case class First(child: Expression, ignoreNullsExpr: Expression)

override def toString: String = s"$prettyName($child)${if (ignoreNulls) " ignore nulls"}"
}

object FirstLast {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this deduplication is a little bit too much but I guess it's fine.

def validateIgnoreNullExpr(exp: Expression): Boolean = exp match {
case Literal(b: Boolean, BooleanType) => b
case _ => throw new AnalysisException("The second argument should be a boolean literal.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can pass the function name so that we can give a better error message.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

package org.apache.spark.sql.catalyst.expressions.aggregate

import org.apache.spark.sql.catalyst.analysis.{FunctionRegistry, TypeCheckResult}
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.{TypeCheckFailure, TypeCheckSuccess}
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult
import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.TypeCheckSuccess
import org.apache.spark.sql.catalyst.dsl.expressions._
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.types._
Expand Down Expand Up @@ -49,12 +49,16 @@ import org.apache.spark.sql.types._
""",
group = "agg_funcs",
since = "2.0.0")
case class Last(child: Expression, ignoreNullsExpr: Expression)
case class Last(child: Expression, ignoreNulls: Boolean)
extends DeclarativeAggregate with ExpectsInputTypes {

def this(child: Expression) = this(child, Literal.create(false, BooleanType))
def this(child: Expression) = this(child, false)

override def children: Seq[Expression] = child :: ignoreNullsExpr :: Nil
def this(child: Expression, ignoreNullsExpr: Expression) = {
this(child, FirstLast.validateIgnoreNullExpr(ignoreNullsExpr))
}

override def children: Seq[Expression] = child :: Nil

override def nullable: Boolean = true

Expand All @@ -71,16 +75,11 @@ case class Last(child: Expression, ignoreNullsExpr: Expression)
val defaultCheck = super.checkInputDataTypes()
if (defaultCheck.isFailure) {
defaultCheck
} else if (!ignoreNullsExpr.foldable) {
TypeCheckFailure(
s"The second argument of Last must be a boolean literal, but got: ${ignoreNullsExpr.sql}")
} else {
TypeCheckSuccess
}
}

private def ignoreNulls: Boolean = ignoreNullsExpr.eval().asInstanceOf[Boolean]

private lazy val last = AttributeReference("last", child.dataType)()

private lazy val valueSet = AttributeReference("valueSet", BooleanType)()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ object RewriteDistinctAggregates extends Rule[LogicalPlan] {

// Select the result of the first aggregate in the last aggregate.
val result = AggregateExpression(
aggregate.First(evalWithinGroup(regularGroupId, operator.toAttribute), Literal(true)),
aggregate.First(evalWithinGroup(regularGroupId, operator.toAttribute), true),
mode = Complete,
isDistinct = false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1535,15 +1535,15 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
*/
override def visitFirst(ctx: FirstContext): Expression = withOrigin(ctx) {
val ignoreNullsExpr = ctx.IGNORE != null
First(expression(ctx.expression), Literal(ignoreNullsExpr)).toAggregateExpression()
First(expression(ctx.expression), ignoreNullsExpr).toAggregateExpression()
}

/**
* Create a [[Last]] expression.
*/
override def visitLast(ctx: LastContext): Expression = withOrigin(ctx) {
val ignoreNullsExpr = ctx.IGNORE != null
Last(expression(ctx.expression), Literal(ignoreNullsExpr)).toAggregateExpression()
Last(expression(ctx.expression), ignoreNullsExpr).toAggregateExpression()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import org.apache.spark.sql.types.IntegerType

class LastTestSuite extends SparkFunSuite {
val input = AttributeReference("input", IntegerType, nullable = true)()
val evaluator = DeclarativeAggregateEvaluator(Last(input, Literal(false)), Seq(input))
val evaluatorIgnoreNulls = DeclarativeAggregateEvaluator(Last(input, Literal(true)), Seq(input))
val evaluator = DeclarativeAggregateEvaluator(Last(input, false), Seq(input))
val evaluatorIgnoreNulls = DeclarativeAggregateEvaluator(Last(input, true), Seq(input))

test("empty buffer") {
assert(evaluator.initialize() === InternalRow(null, false))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -785,10 +785,10 @@ class ExpressionParserSuite extends AnalysisTest {
}

test("SPARK-19526 Support ignore nulls keywords for first and last") {
assertEqual("first(a ignore nulls)", First('a, Literal(true)).toAggregateExpression())
assertEqual("first(a)", First('a, Literal(false)).toAggregateExpression())
assertEqual("last(a ignore nulls)", Last('a, Literal(true)).toAggregateExpression())
assertEqual("last(a)", Last('a, Literal(false)).toAggregateExpression())
assertEqual("first(a ignore nulls)", First('a, true).toAggregateExpression())
assertEqual("first(a)", First('a, false).toAggregateExpression())
assertEqual("last(a ignore nulls)", Last('a, true).toAggregateExpression())
assertEqual("last(a)", Last('a, false).toAggregateExpression())
}

test("timestamp literals") {
Expand Down
4 changes: 2 additions & 2 deletions sql/core/src/main/scala/org/apache/spark/sql/functions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ object functions {
* @since 2.0.0
*/
def first(e: Column, ignoreNulls: Boolean): Column = withAggregateFunction {
new First(e.expr, Literal(ignoreNulls))
First(e.expr, ignoreNulls)
}

/**
Expand Down Expand Up @@ -586,7 +586,7 @@ object functions {
* @since 2.0.0
*/
def last(e: Column, ignoreNulls: Boolean): Column = withAggregateFunction {
new Last(e.expr, Literal(ignoreNulls))
new Last(e.expr, ignoreNulls)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,13 @@ class DataFrameAggregateSuite extends QueryTest
val groupBy = df.groupBy("b").agg(count("*"))
checkAnswer(groupBy, Row(null, 1) :: Row(Row(null), 1) :: Row(Row(1.0), 1) :: Nil)
}

test("SPARK-32344: Unevaluable's set to FIRST/LAST ignoreNullsExpr in distinct aggregates") {
val queryTemplate = (agg: String) =>
s"SELECT $agg(DISTINCT v) FROM (SELECT v FROM VALUES 1, 2, 3 t(v) ORDER BY v)"
checkAnswer(sql(queryTemplate("FIRST")), Row(1))
checkAnswer(sql(queryTemplate("LAST")), Row(3))
}
}

case class B(c: Option[Double])
Expand Down