diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala index 0eed60bd7b438..912a834c0bba3 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala @@ -170,6 +170,36 @@ object SQLConf { } } + /** + * Holds information about keys that have been removed. + * + * @param key The removed config key. + * @param version Version of Spark where key was removed. + * @param defaultValue The default config value. It can be used to notice + * users that they set non-default value to an already removed config. + * @param comment Additional info regarding to the removed config. + */ + case class RemovedConfig(key: String, version: String, defaultValue: String, comment: String) + + /** + * The map contains info about removed SQL configs. Keys are SQL config names, + * map values contain extra information like the version in which the config was removed, + * config's default value and a comment. + */ + val removedSQLConfigs: Map[String, RemovedConfig] = { + val configs = Seq( + RemovedConfig("spark.sql.fromJsonForceNullableSchema", "3.0.0", "true", + "It was removed to prevent errors like SPARK-23173 for non-default value."), + RemovedConfig( + "spark.sql.legacy.allowCreatingManagedTableUsingNonemptyLocation", "3.0.0", "false", + "It was removed to prevent loosing of users data for non-default value."), + RemovedConfig("spark.sql.legacy.compareDateTimestampInTimestamp", "3.0.0", "true", + "It was removed to prevent errors like SPARK-23549 for non-default value.") + ) + + Map(configs.map { cfg => cfg.key -> cfg } : _*) + } + val ANALYZER_MAX_ITERATIONS = buildConf("spark.sql.analyzer.maxIterations") .internal() .doc("The max number of iterations the analyzer runs.") diff --git a/sql/core/src/main/scala/org/apache/spark/sql/RuntimeConfig.scala b/sql/core/src/main/scala/org/apache/spark/sql/RuntimeConfig.scala index 2f46fa8073bbc..87ac4b6db4ac0 100644 --- a/sql/core/src/main/scala/org/apache/spark/sql/RuntimeConfig.scala +++ b/sql/core/src/main/scala/org/apache/spark/sql/RuntimeConfig.scala @@ -20,6 +20,7 @@ package org.apache.spark.sql import org.apache.spark.annotation.Stable import org.apache.spark.internal.config.{ConfigEntry, OptionalConfigEntry} import org.apache.spark.sql.internal.SQLConf +import org.apache.spark.sql.internal.SQLConf.RemovedConfig /** * Runtime configuration interface for Spark. To access this, use `SparkSession.conf`. @@ -38,6 +39,7 @@ class RuntimeConfig private[sql](sqlConf: SQLConf = new SQLConf) { */ def set(key: String, value: String): Unit = { requireNonStaticConf(key) + requireDefaultValueOfRemovedConf(key, value) sqlConf.setConfString(key, value) } @@ -156,4 +158,14 @@ class RuntimeConfig private[sql](sqlConf: SQLConf = new SQLConf) { throw new AnalysisException(s"Cannot modify the value of a Spark config: $key") } } + + private def requireDefaultValueOfRemovedConf(key: String, value: String): Unit = { + SQLConf.removedSQLConfigs.get(key).foreach { + case RemovedConfig(configName, version, defaultValue, comment) => + if (value != defaultValue) { + throw new AnalysisException( + s"The SQL config '$configName' was removed in the version $version. $comment") + } + } + } } diff --git a/sql/core/src/test/scala/org/apache/spark/sql/internal/SQLConfSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/internal/SQLConfSuite.scala index 1dfbca64f5778..8786696119528 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/internal/SQLConfSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/internal/SQLConfSuite.scala @@ -25,7 +25,6 @@ import org.apache.spark.sql.test.{SharedSparkSession, TestSQLContext} import org.apache.spark.util.Utils class SQLConfSuite extends QueryTest with SharedSparkSession { - import testImplicits._ private val testKey = "test.key.0" private val testVal = "test.val.0" @@ -320,4 +319,15 @@ class SQLConfSuite extends QueryTest with SharedSparkSession { assert(e2.getMessage.contains("spark.sql.shuffle.partitions")) } + test("set removed config to non-default value") { + val config = "spark.sql.fromJsonForceNullableSchema" + val defaultValue = true + + spark.conf.set(config, defaultValue) + + val e = intercept[AnalysisException] { + spark.conf.set(config, !defaultValue) + } + assert(e.getMessage.contains(config)) + } }