Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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 @@ -156,6 +156,7 @@ object FunctionRegistry {
expression[Substring]("substr"),
expression[Substring]("substring"),
expression[Upper]("ucase"),
expression[UnHex]("unhex"),
expression[Upper]("upper")
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,44 @@ case class Pow(left: Expression, right: Expression)
}
}

/**
* Performs the inverse operation of HEX.
* Resulting characters are returned as a byte array.
*/
case class UnHex(child: Expression)
extends UnaryExpression with AutoCastInputTypes with Serializable {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think unhex only works on StringType, so we should not use AutoCastInputTypes here, we should use checkInputDataTypes instead.


override def expectedChildTypes: Seq[DataType] = Seq(StringType)
Copy link
Contributor

Choose a reason for hiding this comment

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

@davies this could be changed to ByteType, couldn't it? This avoids a cast, if someone calls this with a byte array.

Copy link
Contributor

Choose a reason for hiding this comment

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

hex() return StringType, so unhex() should take StringType, not BinaryType.


override def dataType: DataType = BinaryType

override def eval(input: InternalRow): Any = {
val num = child.eval(input)
if (num == null) {
null
} else {
unhex(num.asInstanceOf[UTF8String].toString)
Copy link
Contributor

Choose a reason for hiding this comment

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

Converting UTF8String to String is slow, please use getBytes

}
}

private def unhex(s: String): Array[Byte] = {
// append a leading 0 if needed
val str = if (s.length % 2 == 1) {"0" + s} else {s}
val result = new Array[Byte](str.length / 2)
var i = 0
while (i < str.length()) {
try {
result(i / 2) = Integer.parseInt(str.substring(i, i + 2), 16).asInstanceOf[Byte]
} catch {
Copy link
Contributor

Choose a reason for hiding this comment

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

// invalid character present, return null
case _: NumberFormatException => return null
Copy link
Contributor

Choose a reason for hiding this comment

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

We could check the value from toDigit here without throwing exceptions (Using exception as control flow is slow).

}
i += 2
}
result
}
}

case class Hypot(left: Expression, right: Expression)
extends BinaryMathExpression(math.hypot, "HYPOT")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,14 @@ class MathFunctionsSuite extends SparkFunSuite with ExpressionEvalHelper {
// scalastyle:on
}

test("unhex") {
checkEvaluation(UnHex(Literal("737472696E67")), "string".getBytes)
// scalastyle:off
// Turn off scala style for non-ascii chars
checkEvaluation(UnHex(Literal("E4B889E9878DE79A84")), "三重的".getBytes)
// scalastyle:on
}

test("hypot") {
testBinary(Hypot, math.hypot)
}
Expand Down
18 changes: 18 additions & 0 deletions sql/core/src/main/scala/org/apache/spark/sql/functions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,24 @@ object functions {
*/
def hex(colName: String): Column = hex(Column(colName))

/**
* Inverse of hex. Interprets each pair of characters as a hexadecimal number
* and converts to the byte representation of number.
*
* @group math_funcs
* @since 1.5.0
*/
def unhex(column: Column): Column = UnHex(column.expr)

/**
* Inverse of hex. Interprets each pair of characters as a hexadecimal number
* and converts to the byte representation of number.
*
* @group math_funcs
* @since 1.5.0
*/
def unhex(colName: String): Column = unhex(Column(colName))

/**
* Computes `sqrt(a^2^ + b^2^)` without intermediate overflow or underflow.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,16 @@ class MathExpressionsSuite extends QueryTest {
checkAnswer(data.selectExpr("hex(cast(d as binary))"), Seq(Row("68656C6C6F")))
}

test("unhex") {
val data = Seq(("1C", "737472696E67")).toDF("a", "b")
checkAnswer(data.select(unhex('a)), Row(Array[Byte](28.toByte)))
checkAnswer(data.select(unhex('b)), Row("string".getBytes))
checkAnswer(data.selectExpr("unhex(a)"), Row(Array[Byte](28.toByte)))
checkAnswer(data.selectExpr("unhex(b)"), Row("string".getBytes))
checkAnswer(data.selectExpr("""unhex("##")"""), Row(null))

}

test("hypot") {
testTwoToOneMathFunction(hypot, hypot, math.hypot)
}
Expand Down