Skip to content

Commit eeaa636

Browse files
committed
IGNORE NULLS in first / last expressions
1 parent 1a52a62 commit eeaa636

File tree

3 files changed

+29
-1
lines changed

3 files changed

+29
-1
lines changed

sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,8 @@ primaryExpression
552552
| CASE whenClause+ (ELSE elseExpression=expression)? END #searchedCase
553553
| CASE value=expression whenClause+ (ELSE elseExpression=expression)? END #simpleCase
554554
| CAST '(' expression AS dataType ')' #cast
555+
| FIRST '(' expression (IGNORE NULLS)? ')' #first
556+
| LAST '(' expression (IGNORE NULLS)? ')' #last
555557
| constant #constantDefault
556558
| ASTERISK #star
557559
| qualifiedName '.' ASTERISK #star
@@ -710,7 +712,7 @@ nonReserved
710712
| VIEW | REPLACE
711713
| IF
712714
| NO | DATA
713-
| START | TRANSACTION | COMMIT | ROLLBACK
715+
| START | TRANSACTION | COMMIT | ROLLBACK | IGNORE
714716
| SORT | CLUSTER | DISTRIBUTE | UNSET | TBLPROPERTIES | SKEWED | STORED | DIRECTORIES | LOCATION
715717
| EXCHANGE | ARCHIVE | UNARCHIVE | FILEFORMAT | TOUCH | COMPACT | CONCATENATE | CHANGE
716718
| CASCADE | RESTRICT | BUCKETS | CLUSTERED | SORTED | PURGE | INPUTFORMAT | OUTPUTFORMAT
@@ -836,6 +838,7 @@ TRANSACTION: 'TRANSACTION';
836838
COMMIT: 'COMMIT';
837839
ROLLBACK: 'ROLLBACK';
838840
MACRO: 'MACRO';
841+
IGNORE: 'IGNORE';
839842

840843
IF: 'IF';
841844

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.apache.spark.sql.AnalysisException
3131
import org.apache.spark.sql.catalyst.{FunctionIdentifier, TableIdentifier}
3232
import org.apache.spark.sql.catalyst.analysis._
3333
import org.apache.spark.sql.catalyst.expressions._
34+
import org.apache.spark.sql.catalyst.expressions.aggregate.{First, Last}
3435
import org.apache.spark.sql.catalyst.parser.SqlBaseParser._
3536
import org.apache.spark.sql.catalyst.plans._
3637
import org.apache.spark.sql.catalyst.plans.logical._
@@ -1022,6 +1023,22 @@ class AstBuilder extends SqlBaseBaseVisitor[AnyRef] with Logging {
10221023
Cast(expression(ctx.expression), visitSparkDataType(ctx.dataType))
10231024
}
10241025

1026+
/**
1027+
* Create a [[First]] expression.
1028+
*/
1029+
override def visitFirst(ctx: FirstContext): Expression = withOrigin(ctx) {
1030+
val ignoreNullsExpr = ctx.IGNORE != null && ctx.NULLS != null
1031+
First(expression(ctx.expression), Literal(ignoreNullsExpr)).toAggregateExpression()
1032+
}
1033+
1034+
/**
1035+
* Create a [[Last]] expression.
1036+
*/
1037+
override def visitLast(ctx: LastContext): Expression = withOrigin(ctx) {
1038+
val ignoreNullsExpr = ctx.IGNORE != null && ctx.NULLS != null
1039+
Last(expression(ctx.expression), Literal(ignoreNullsExpr)).toAggregateExpression()
1040+
}
1041+
10251042
/**
10261043
* Create a (windowed) Function expression.
10271044
*/

sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import java.sql.{Date, Timestamp}
2121
import org.apache.spark.sql.catalyst.FunctionIdentifier
2222
import org.apache.spark.sql.catalyst.analysis.{UnresolvedAttribute, _}
2323
import org.apache.spark.sql.catalyst.expressions._
24+
import org.apache.spark.sql.catalyst.expressions.aggregate.{First, Last}
2425
import org.apache.spark.sql.catalyst.plans.PlanTest
2526
import org.apache.spark.sql.types._
2627
import org.apache.spark.unsafe.types.CalendarInterval
@@ -549,4 +550,11 @@ class ExpressionParserSuite extends PlanTest {
549550
val complexName2 = FunctionIdentifier("ba``r", Some("fo``o"))
550551
assertEqual(complexName2.quotedString, UnresolvedAttribute("fo``o.ba``r"))
551552
}
553+
554+
test("SPARK-19526 Support ignore nulls keywords for first and last") {
555+
assertEqual("first(a ignore nulls)", First('a, Literal(true)))
556+
assertEqual("first(a)", First('a, Literal(false)))
557+
assertEqual("last(a ignore nulls)", Last('a, Literal(true)))
558+
assertEqual("last(a)", Last('a, Literal(false)))
559+
}
552560
}

0 commit comments

Comments
 (0)