Skip to content
This repository has been archived by the owner on Nov 15, 2024. It is now read-only.

Commit

Permalink
[SPARK-21300][SQL] ExternalMapToCatalyst should null-check map key pr…
Browse files Browse the repository at this point in the history
…ior to converting to internal value.

## What changes were proposed in this pull request?

`ExternalMapToCatalyst` should null-check map key prior to converting to internal value to throw an appropriate Exception instead of something like NPE.

## How was this patch tested?

Added a test and existing tests.

Author: Takuya UESHIN <ueshin@databricks.com>

Closes apache#18524 from ueshin/issues/SPARK-21300.

(cherry picked from commit ce10545)
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
  • Loading branch information
ueshin authored and MatthewRBruce committed Jul 31, 2018
1 parent ff93071 commit af7d974
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ object JavaTypeInference {
inputObject,
ObjectType(keyType.getRawType),
serializerFor(_, keyType),
keyNullable = true,
ObjectType(valueType.getRawType),
serializerFor(_, valueType),
valueNullable = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ object ScalaReflection extends ScalaReflection {
inputObject,
dataTypeFor(keyType),
serializerFor(_, keyType, keyPath, seenTypeSet),
keyNullable = !keyType.typeSymbol.asClass.isPrimitive,
dataTypeFor(valueType),
serializerFor(_, valueType, valuePath, seenTypeSet),
valueNullable = !valueType.typeSymbol.asClass.isPrimitive)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,18 +659,21 @@ object ExternalMapToCatalyst {
inputMap: Expression,
keyType: DataType,
keyConverter: Expression => Expression,
keyNullable: Boolean,
valueType: DataType,
valueConverter: Expression => Expression,
valueNullable: Boolean): ExternalMapToCatalyst = {
val id = curId.getAndIncrement()
val keyName = "ExternalMapToCatalyst_key" + id
val keyIsNull = "ExternalMapToCatalyst_key_isNull" + id
val valueName = "ExternalMapToCatalyst_value" + id
val valueIsNull = "ExternalMapToCatalyst_value_isNull" + id

ExternalMapToCatalyst(
keyName,
keyIsNull,
keyType,
keyConverter(LambdaVariable(keyName, "false", keyType, false)),
keyConverter(LambdaVariable(keyName, keyIsNull, keyType, keyNullable)),
valueName,
valueIsNull,
valueType,
Expand All @@ -686,6 +689,8 @@ object ExternalMapToCatalyst {
*
* @param key the name of the map key variable that used when iterate the map, and used as input for
* the `keyConverter`
* @param keyIsNull the nullability of the map key variable that used when iterate the map, and
* used as input for the `keyConverter`
* @param keyType the data type of the map key variable that used when iterate the map, and used as
* input for the `keyConverter`
* @param keyConverter A function that take the `key` as input, and converts it to catalyst format.
Expand All @@ -701,6 +706,7 @@ object ExternalMapToCatalyst {
*/
case class ExternalMapToCatalyst private(
key: String,
keyIsNull: String,
keyType: DataType,
keyConverter: Expression,
value: String,
Expand Down Expand Up @@ -731,6 +737,7 @@ case class ExternalMapToCatalyst private(

val keyElementJavaType = ctx.javaType(keyType)
val valueElementJavaType = ctx.javaType(valueType)
ctx.addMutableState("boolean", keyIsNull, "")
ctx.addMutableState(keyElementJavaType, key, "")
ctx.addMutableState("boolean", valueIsNull, "")
ctx.addMutableState(valueElementJavaType, value, "")
Expand Down Expand Up @@ -768,6 +775,12 @@ case class ExternalMapToCatalyst private(
defineEntries -> defineKeyValue
}

val keyNullCheck = if (ctx.isPrimitiveType(keyType)) {
s"$keyIsNull = false;"
} else {
s"$keyIsNull = $key == null;"
}

val valueNullCheck = if (ctx.isPrimitiveType(valueType)) {
s"$valueIsNull = false;"
} else {
Expand All @@ -790,6 +803,7 @@ case class ExternalMapToCatalyst private(
$defineEntries
while($entries.hasNext()) {
$defineKeyValue
$keyNullCheck
$valueNullCheck

${genKeyConverter.code}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,18 @@ class ExpressionEncoderSuite extends PlanTest with AnalysisTest {
checkNullable[String](true)
}

test("null check for map key") {
test("null check for map key: String") {
val encoder = ExpressionEncoder[Map[String, Int]]()
val e = intercept[RuntimeException](encoder.toRow(Map(("a", 1), (null, 2))))
assert(e.getMessage.contains("Cannot use null as map key"))
}

test("null check for map key: Integer") {
val encoder = ExpressionEncoder[Map[Integer, String]]()
val e = intercept[RuntimeException](encoder.toRow(Map((1, "a"), (null, "b"))))
assert(e.getMessage.contains("Cannot use null as map key"))
}

private def encodeDecodeTest[T : ExpressionEncoder](
input: T,
testName: String): Unit = {
Expand Down

0 comments on commit af7d974

Please sign in to comment.