Bug: The withWatermark method in Dataset.scala is throwing a ParseException when a number is passed to the eventTime parameter
Status: Open | Priority: Major | Resolution: 
Created: 04/Sep/24 12:30
Affects Version/s: 3.3.0

In the below withWatermark function of Dataset.scala:-

def withWatermark(eventTime: String, delayThreshold: String): Dataset[T] = withTypedPlan

{

val parsedDelay = IntervalUtils.fromIntervalString(delayThreshold) require(!IntervalUtils.isNegative(parsedDelay),
s"delay threshold ($delayThreshold) should not be negative.")

EliminateEventTimeWatermark( EventTimeWatermark({color:#172b4d}UnresolvedAttribute(eventTime),{color} parsedDelay, logicalPlan))

}

if we pass number as eventTime parameter. It fails with below ParseException

{color:#de350b}Caused by: org.apache.spark.sql.catalyst.parser.ParseException: {color}
{color:#de350b}Syntax error at or near '1'(line 1, pos 0){color}

{color:#de350b}== SQL =={color}
{color:#de350b}1{color}
{color:#de350b}^^^{color}

{color:#de350b}    at org.apache.spark.sql.catalyst.parser.ParseException.withCommand(ParseDriver.scala:315){color}
{color:#de350b}    at org.apache.spark.sql.catalyst.parser.AbstractSqlParser.parse(ParseDriver.scala:152){color}
{color:#de350b}    at org.apache.spark.sql.catalyst.parser.AbstractSqlParser.parseMultipartIdentifier(ParseDriver.scala:68){color}
{color:#de350b}    at org.apache.spark.sql.catalyst.analysis.UnresolvedAttribute$.apply(unresolved.scala:161){color}
{color:#de350b}    at org.apache.spark.sql.Dataset.withWatermark(Dataset.scala:748){color}

 

In spark 3.1.1 it was able to parse number properly. It was not throwing any error.

The current issue is seen because of the checkin done for the issue https://issues.apache.org/jira/browse/SPARK-34636.

The changes made to the {{apply}} method in the {{Unresolved.scala}} file are causing the issue. I have attached the relevant code sections below for Spark 3.1 and Spark 3.2, highlighting the differences.:-

*Spark 3.1*
def apply(name: String): UnresolvedAttribute = {color:#00875a}new UnresolvedAttribute(name.split("
."){color})

*Spark 3.2*
def apply(name: String): UnresolvedAttribute =
    new UnresolvedAttribute(CatalystSqlParser.parseMultipartIdentifier(name))

Please visit this link to see the code changes:- [https://github.com/apache/spark/commit/94fd6cb0ce56684de47eb5cca925055859c968ef]

In above code for spark 3.1, we can see that in object instantiation of the apply Method only name.split is being done.
But with the above mentioned changed in spark 3.2 code and in later versions of spark  CatalystSqlParser.parseMultipartIdentifier(name) is being called.
In turn this parseMultipartIdentifier method will call singleMultipartIdentifier method of SQLParser. we can see in the below code snippet.

override def parseMultipartIdentifier(sqlText: String): Seq[String] = {
parse(sqlText) { parser =>
astBuilder.visitSingleMultipartIdentifier({color:#4c9aff}parser.singleMultipartIdentifier(){color})
}
}

Now, once {color:#4c9aff}parser.singleMultipartIdentifier(){color} method is called. It will try to validate sqlText using the singleMultipartIdentifier rule. This sqlText parameter will contain the same value which we passed to eventTime  parameter of withWaterMark function. 
Now when this sqlText is a number(For Example "1"), than it will throw {color:#de350b}ParseException{color} which i mentioned in the beginning, as singleMultipartIdentifier rule can't parse Integer Value (numbers).

 

 

Possible Solution that i can suggest: 
Introduce a new method in object of UnresolvedAttribute as shown below

object UnresolvedAttribute {

{color:#172b4d}def numberString(name:String){color}

{color:#172b4d}: UnresolvedAttribute = new UnresolvedAttribute(name.split("{color}
."))

}

and in withWatermark function of Dataset.scala do a function call to UnresolvedAttribute.numberString(eventTime) inplace of calling a constructor UnresolvedAttribute(eventTime) as shown below

def withWatermark(eventTime: String, delayThreshold: String): Dataset[T] = withTypedPlan

{

val parsedDelay = IntervalUtils.fromIntervalString(delayThreshold) require(!IntervalUtils.isNegative(parsedDelay), 
s"delay threshold ($delayThreshold) should not be negative.")

EliminateEventTimeWatermark( EventTimeWatermark({color:#4c9aff}UnresolvedAttribute.numberString(eventTime),{color} parsedDelay, logicalPlan))

}

Code changes might be needed in the following locations::-
 # //branch/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/unresolved.scala
 # //branch/sql/core/src/main/scala/org/apache/spark/sql/Dataset.scala