diff --git a/common/utils/src/main/resources/error/error-conditions.json b/common/utils/src/main/resources/error/error-conditions.json index 3512fe34e92a..939f3059b230 100644 --- a/common/utils/src/main/resources/error/error-conditions.json +++ b/common/utils/src/main/resources/error/error-conditions.json @@ -3746,6 +3746,12 @@ ], "sqlState" : "21000" }, + "ROW_VALUE_IS_NULL" : { + "message" : [ + "Found NULL in a row at the index , expected a non-NULL value." + ], + "sqlState" : "22023" + }, "RULE_ID_NOT_FOUND" : { "message" : [ "Not found an id for the rule name \"\". Please modify RuleIdCollection.scala if you are adding a new rule." @@ -7253,11 +7259,6 @@ "Primitive types are not supported." ] }, - "_LEGACY_ERROR_TEMP_2232" : { - "message" : [ - "Value at index is null." - ] - }, "_LEGACY_ERROR_TEMP_2233" : { "message" : [ "Only Data Sources providing FileFormat are supported: ." diff --git a/sql/api/src/main/scala/org/apache/spark/sql/Row.scala b/sql/api/src/main/scala/org/apache/spark/sql/Row.scala index 0c065dd4d4ba..3f02e742aac0 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/Row.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/Row.scala @@ -219,7 +219,7 @@ trait Row extends Serializable { * Returns the value at position i as a primitive boolean. * * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ def getBoolean(i: Int): Boolean = getAnyValAs[Boolean](i) @@ -227,7 +227,7 @@ trait Row extends Serializable { * Returns the value at position i as a primitive byte. * * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ def getByte(i: Int): Byte = getAnyValAs[Byte](i) @@ -235,7 +235,7 @@ trait Row extends Serializable { * Returns the value at position i as a primitive short. * * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ def getShort(i: Int): Short = getAnyValAs[Short](i) @@ -243,7 +243,7 @@ trait Row extends Serializable { * Returns the value at position i as a primitive int. * * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ def getInt(i: Int): Int = getAnyValAs[Int](i) @@ -251,7 +251,7 @@ trait Row extends Serializable { * Returns the value at position i as a primitive long. * * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ def getLong(i: Int): Long = getAnyValAs[Long](i) @@ -260,7 +260,7 @@ trait Row extends Serializable { * Throws an exception if the type mismatches or if the value is null. * * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ def getFloat(i: Int): Float = getAnyValAs[Float](i) @@ -268,7 +268,7 @@ trait Row extends Serializable { * Returns the value at position i as a primitive double. * * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ def getDouble(i: Int): Double = getAnyValAs[Double](i) @@ -523,7 +523,7 @@ trait Row extends Serializable { * * @throws UnsupportedOperationException when schema is not defined. * @throws ClassCastException when data type does not match. - * @throws NullPointerException when value is null. + * @throws org.apache.spark.SparkRuntimeException when value is null. */ private def getAnyValAs[T <: AnyVal](i: Int): T = if (isNullAt(i)) throw DataTypeErrors.valueIsNullError(i) diff --git a/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala b/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala index 63413b69f210..32b4198dc1a6 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/errors/DataTypeErrors.scala @@ -272,8 +272,8 @@ private[sql] object DataTypeErrors extends DataTypeErrorsBase { } def valueIsNullError(index: Int): Throwable = { - new SparkException( - errorClass = "_LEGACY_ERROR_TEMP_2232", + new SparkRuntimeException( + errorClass = "ROW_VALUE_IS_NULL", messageParameters = Map( "index" -> index.toString), cause = null) diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/RowTest.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/RowTest.scala index dd4d1e5b9d46..732d03c146bf 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/RowTest.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/RowTest.scala @@ -24,7 +24,7 @@ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.must.Matchers import org.scalatest.matchers.should.Matchers._ -import org.apache.spark.{SparkException, SparkIllegalArgumentException, SparkUnsupportedOperationException} +import org.apache.spark.{SparkIllegalArgumentException, SparkRuntimeException, SparkUnsupportedOperationException} import org.apache.spark.sql.catalyst.InternalRow import org.apache.spark.sql.catalyst.expressions.{GenericRow, GenericRowWithSchema} import org.apache.spark.sql.types._ @@ -87,8 +87,9 @@ class RowTest extends AnyFunSpec with Matchers { sampleRowWithoutCol3.getValuesMap[String](List("col1", "col2")) shouldBe expected } - it("getAs() on type extending AnyVal throws an exception when accessing field that is null") { - intercept[SparkException] { + it("getAnyValAs() on type extending AnyVal throws an exception when accessing " + + "field that is null") { + intercept[SparkRuntimeException] { sampleRowWithoutCol3.getInt(sampleRowWithoutCol3.fieldIndex("col3")) } } diff --git a/sql/core/src/test/scala/org/apache/spark/sql/RowSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/RowSuite.scala index cb8dafb9a8f3..4c4560e3fc48 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/RowSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/RowSuite.scala @@ -17,7 +17,7 @@ package org.apache.spark.sql -import org.apache.spark.{SparkFunSuite, SparkUnsupportedOperationException} +import org.apache.spark.{SparkFunSuite, SparkRuntimeException, SparkUnsupportedOperationException} import org.apache.spark.sql.catalyst.expressions.{GenericInternalRow, SpecificInternalRow} import org.apache.spark.sql.test.SharedSparkSession import org.apache.spark.sql.types._ @@ -123,4 +123,17 @@ class RowSuite extends SparkFunSuite with SharedSparkSession { parameters = Map("methodName" -> "fieldIndex", "className" -> "Row", "fieldName" -> "`foo`") ) } + + test("SPARK-42307: get a value from a null column should result in error") { + val position = 0 + val rowWithNullValue = Row.fromSeq(Seq(null)) + + checkError( + exception = intercept[SparkRuntimeException] { + rowWithNullValue.getLong(position) + }, + errorClass = "ROW_VALUE_IS_NULL", + parameters = Map("index" -> position.toString) + ) + } }