Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
20 changes: 19 additions & 1 deletion sql/hive/src/main/scala/org/apache/spark/sql/hive/hiveUDFs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ private[hive] case class HiveSimpleUDF(
with HiveInspectors
with CodegenFallback
with Logging
with UserDefinedExpression {
with UserDefinedExpression
with ImplicitCastInputTypes {

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.

ScalaUDF doesn't extend ImplicitCastInputTypes either. Does it have the same problem?

@ulysses-you ulysses-you Sep 17, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, ImplicitTypeCasts has checked ScalaUDF as case udf: ScalaUDF if udf.inputTypes.nonEmpty =>


override lazy val deterministic: Boolean = isUDFDeterministic && children.forall(_.deterministic)

Expand All @@ -69,6 +70,23 @@ private[hive] case class HiveSimpleUDF(
udfType != null && udfType.deterministic() && !udfType.stateful()
}

override def inputTypes: Seq[AbstractDataType] = {
val inTypes = children.map(_.dataType)

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.

I'm confused. The expected type should be defined by the function signature, but not the actual function inputs. What are we doing here?

As an example, ScalaUDF.inputTypes is derived from the function signature (captured by encoders).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The ScalaUDF also implicit convert input type to expected type at ImplicitTypeCasts.

Let's say we have a udf Array[Double] => Double = { data => data.sum } and we run spark.sql("select udf(array(1.0, 1.1, 1.2))"). Then ImplicitTypeCasts will cast array<decimal> to array<double>.

But Hive udf can't enjoy it, now we only use Hive ObjectInspector to convert data type at running time. It's the difference.

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.

Ah I misread the code. We get children data type only to skip decimal.

if (!inTypes.exists(_.existsRecursively(_.isInstanceOf[DecimalType]))) {

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.

Why do we need special handling for decimal types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's a compatible issue. In normal case, data type is converted by Hive ObjectInspector at running time. But Hive not support input decimal type when method required double type. Unfortunately the default type of 1.1 is different between Spark and Hive, which are decimal and double. Then caused this issue.

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.

Ah, I see. Could you describe it in the PR description?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah.

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.

Sorry I didn't get it. ImplicitCastInputTypes can implicit cast decimal to double, doesn't it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In first commit I did it for all types but test not passed. The reason is a UDF required an Object type.

We convert Java Object Type to NullType in HiveInspectors.javaTypeToDataType (seems this can be changed ?)

 // Hive seems to return this for struct types?
    case c: Class[_] if c == classOf[java.lang.Object] => NullType

So we can't reflect the UDF method using a NullType, the error msg is:

in query: cannot resolve 'example_format('%o', 93)' due to data type mismatch: argument 2 requires array<null> type, however, '93' is of int type.; line 1 pos 7;

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.

if a hive udf requires Object, I think it means AnyDataType. We should only special case it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How about this ?

    val expectTypes = method.getGenericParameterTypes.map(javaTypeToDataType)
    if (expectTypes.exists(_.existsRecursively(_.isInstanceOf[NullType]))) {
      children.map(_.dataType)
    } else {
      expectTypes
    }

@cloud-fan cloud-fan Sep 18, 2020

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.

method.getGenericParameterTypes.map(javaTypeToDataType).map { dt =>
  if (dt.existsRecursively(_.isInstanceOf[NullType])) AnyDataType else dt
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Seems we should check data type and replace NullType to AnyDataType on by one to avoid such case Map<Double, Object> input.

I will do an another check if we can change HiveInspectors.javaTypeToDataType that using AnyDataType directly.

inTypes
} else {
val expectTypes = method.getGenericParameterTypes.map(javaTypeToDataType)
// check decimal
inTypes.zip(expectTypes).map { case (in, expect) =>
if (in.existsRecursively(_.isInstanceOf[DecimalType])) {
expect
} else {
in
}
}
}
}

override def foldable: Boolean = isUDFDeterministic && children.forall(_.foldable)

// Create parameter converters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,24 @@ class HiveUDFSuite extends QueryTest with TestHiveSingleton with SQLTestUtils {
}
}

test("SPARK-32877: Fix Hive UDF not support decimal type in complex type") {

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.

If this is not covered in any other test case, it looks worth of having this.

withUserDefinedFunction("testArraySum" -> false) {
sql(s"CREATE FUNCTION testArraySum AS '${classOf[ArraySumUDF].getName}'")
checkAnswer(
sql("SELECT testArraySum(array(1, 1.1, 1.2))"),
Seq(Row(3.3)))

val msg = intercept[AnalysisException] {
sql("SELECT testArraySum(1)")
}.getMessage
assert(msg.contains(s"No handler for UDF/UDAF/UDTF '${classOf[ArraySumUDF].getName}'"))

val msg2 = intercept[AnalysisException] {
sql("SELECT testArraySum(1, 2)")
}.getMessage
assert(msg2.contains(s"No handler for UDF/UDAF/UDTF '${classOf[ArraySumUDF].getName}'"))
}
}
}

class TestPair(x: Int, y: Int) extends Writable with Serializable {
Expand Down Expand Up @@ -741,3 +759,14 @@ class StatelessUDF extends UDF {
result
}
}

class ArraySumUDF extends UDF {
import scala.collection.JavaConverters._
def evaluate(values: java.util.List[java.lang.Double]): java.lang.Double = {
var r = 0d
for (v <- values.asScala) {
r += v
}
r
}
}