Skip to content

Commit 5a6f382

Browse files
authored
[Spark] Support dropping the CHECK constraints table feature (#2987)
#### Which Delta project/connector is this regarding? <!-- Please add the component selected below to the beginning of the pull request title For example: [Spark] Title of my pull request --> - [x] Spark - [ ] Standalone - [ ] Flink - [ ] Kernel - [ ] Other (fill in here) ## Description This PR adds support for dropping the `checkConstraints` table feature using the `ALTER TABLE ... DROP FEATURE` command. It throws an error if the table still contains CHECK constraints (as dropping a feature should never change the logical state of a table). ## How was this patch tested? Added a test to `CheckConstraintsSuite` ## Does this PR introduce _any_ user-facing changes? Yes, `ALTER TABLE ... DROP FEATURE checkConstraints` is now supported.
1 parent 4fac1f1 commit 5a6f382

File tree

6 files changed

+92
-1
lines changed

6 files changed

+92
-1
lines changed

spark/src/main/resources/error/delta-error-classes.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@
170170
],
171171
"sqlState" : "42703"
172172
},
173+
"DELTA_CANNOT_DROP_CHECK_CONSTRAINT_FEATURE" : {
174+
"message" : [
175+
"Cannot drop the CHECK constraints table feature.",
176+
"The following constraints must be dropped first: <constraints>."
177+
],
178+
"sqlState" : "0AKDE"
179+
},
173180
"DELTA_CANNOT_EVALUATE_EXPRESSION" : {
174181
"message" : [
175182
"Cannot evaluate expression: <expression>"

spark/src/main/scala/org/apache/spark/sql/delta/DeltaErrors.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,13 @@ trait DeltaErrorsBase
336336
messageParameters = Array.empty)
337337
}
338338

339+
def cannotDropCheckConstraintFeature(constraintNames: Seq[String]): AnalysisException = {
340+
new DeltaAnalysisException(
341+
errorClass = "DELTA_CANNOT_DROP_CHECK_CONSTRAINT_FEATURE",
342+
messageParameters = Array(constraintNames.map(formatColumn).mkString(", "))
343+
)
344+
}
345+
339346
def incorrectLogStoreImplementationException(
340347
sparkConf: SparkConf,
341348
cause: Throwable): Throwable = {

spark/src/main/scala/org/apache/spark/sql/delta/PreDowngradeTableFeatureCommand.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.apache.spark.sql.delta.catalog.DeltaTableV2
2424
import org.apache.spark.sql.delta.commands.{AlterTableSetPropertiesDeltaCommand, AlterTableUnsetPropertiesDeltaCommand, DeltaReorgTableCommand, DeltaReorgTableMode, DeltaReorgTableSpec}
2525
import org.apache.spark.sql.delta.commands.columnmapping.RemoveColumnMappingCommand
2626
import org.apache.spark.sql.delta.commands.optimize.OptimizeMetrics
27+
import org.apache.spark.sql.delta.constraints.Constraints
2728
import org.apache.spark.sql.delta.managedcommit.ManagedCommitUtils
2829
import org.apache.spark.sql.delta.metering.DeltaLogging
2930
import org.apache.spark.sql.delta.util.{Utils => DeltaUtils}
@@ -378,3 +379,22 @@ case class ColumnMappingPreDowngradeCommand(table: DeltaTableV2)
378379
true
379380
}
380381
}
382+
383+
case class CheckConstraintsPreDowngradeTableFeatureCommand(table: DeltaTableV2)
384+
extends PreDowngradeTableFeatureCommand {
385+
386+
/**
387+
* Throws an exception if the table has CHECK constraints, and returns false otherwise (as no
388+
* action was required).
389+
*
390+
* We intentionally error out instead of removing the CHECK constraints here, as dropping a
391+
* table feature should not never alter the logical representation of a table (only its physical
392+
* representation). Instead, we ask the user to explicitly drop the constraints before the table
393+
* feature can be dropped.
394+
*/
395+
override def removeFeatureTracesIfNeeded(): Boolean = {
396+
val checkConstraintNames = Constraints.getCheckConstraintNames(table.initialSnapshot.metadata)
397+
if (checkConstraintNames.isEmpty) return false
398+
throw DeltaErrors.cannotDropCheckConstraintFeature(checkConstraintNames)
399+
}
400+
}

spark/src/main/scala/org/apache/spark/sql/delta/TableFeature.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,12 +457,24 @@ object InvariantsTableFeature
457457

458458
object CheckConstraintsTableFeature
459459
extends LegacyWriterFeature(name = "checkConstraints", minWriterVersion = 3)
460-
with FeatureAutomaticallyEnabledByMetadata {
460+
with FeatureAutomaticallyEnabledByMetadata
461+
with RemovableFeature {
461462
override def metadataRequiresFeatureToBeEnabled(
462463
metadata: Metadata,
463464
spark: SparkSession): Boolean = {
464465
Constraints.getCheckConstraints(metadata, spark).nonEmpty
465466
}
467+
468+
override def preDowngradeCommand(table: DeltaTableV2): PreDowngradeTableFeatureCommand =
469+
CheckConstraintsPreDowngradeTableFeatureCommand(table)
470+
471+
override def validateRemoval(snapshot: Snapshot): Boolean =
472+
Constraints.getCheckConstraintNames(snapshot.metadata).isEmpty
473+
474+
override def actionUsesFeature(action: Action): Boolean = {
475+
// This method is never called, as it is only used for ReaderWriterFeatures.
476+
throw new UnsupportedOperationException()
477+
}
466478
}
467479

468480
object ChangeDataFeedTableFeature

spark/src/main/scala/org/apache/spark/sql/delta/constraints/Constraints.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ object Constraints {
4949
/** A SQL expression to check for when writing out data. */
5050
case class Check(name: String, expression: Expression) extends Constraint
5151

52+
def getCheckConstraintNames(metadata: Metadata): Seq[String] = {
53+
metadata.configuration.keys.collect {
54+
case key if key.toLowerCase(Locale.ROOT).startsWith("delta.constraints.") =>
55+
key.stripPrefix("delta.constraints.")
56+
}.toSeq
57+
}
58+
5259
/**
5360
* Extract CHECK constraints from the table properties. Note that some CHECK constraints may also
5461
* come from schema metadata; these constraints were never released in a public API but are

spark/src/test/scala/org/apache/spark/sql/delta/schema/CheckConstraintsSuite.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ package org.apache.spark.sql.delta.schema
1818

1919
import scala.collection.JavaConverters._
2020

21+
import org.apache.spark.sql.catalyst.TableIdentifier
22+
import org.apache.spark.sql.delta.DeltaLog
23+
2124
// scalastyle:off import.ordering.noEmptyLine
2225
import org.apache.spark.sql.delta.constraints.CharVarcharConstraint
2326
import org.apache.spark.sql.delta.sources.DeltaSQLConf
@@ -459,4 +462,39 @@ class CheckConstraintsSuite extends QueryTest
459462
)
460463
}
461464
}
465+
466+
test("drop table feature") {
467+
withTable("table") {
468+
sql("CREATE TABLE table (a INT, b INT) USING DELTA " +
469+
"TBLPROPERTIES ('delta.feature.checkConstraints' = 'supported')")
470+
sql("ALTER TABLE table ADD CONSTRAINT c1 CHECK (a > 0)")
471+
sql("ALTER TABLE table ADD CONSTRAINT c2 CHECK (b > 0)")
472+
473+
val error1 = intercept[AnalysisException] {
474+
sql("ALTER TABLE table DROP FEATURE checkConstraints")
475+
}
476+
checkError(
477+
error1,
478+
errorClass = "DELTA_CANNOT_DROP_CHECK_CONSTRAINT_FEATURE",
479+
parameters = Map("constraints" -> "`c1`, `c2`")
480+
)
481+
val deltaLog = DeltaLog.forTable(spark, TableIdentifier("table"))
482+
assert(deltaLog.update().protocol.readerAndWriterFeatureNames.contains("checkConstraints"))
483+
484+
sql("ALTER TABLE table DROP CONSTRAINT c1")
485+
val error2 = intercept[AnalysisException] {
486+
sql("ALTER TABLE table DROP FEATURE checkConstraints")
487+
}
488+
checkError(
489+
error2,
490+
errorClass = "DELTA_CANNOT_DROP_CHECK_CONSTRAINT_FEATURE",
491+
parameters = Map("constraints" -> "`c2`")
492+
)
493+
assert(deltaLog.update().protocol.readerAndWriterFeatureNames.contains("checkConstraints"))
494+
495+
sql("ALTER TABLE table DROP CONSTRAINT c2")
496+
sql("ALTER TABLE table DROP FEATURE checkConstraints")
497+
assert(!deltaLog.update().protocol.readerAndWriterFeatureNames.contains("checkConstraints"))
498+
}
499+
}
462500
}

0 commit comments

Comments
 (0)