Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.apache.spark.sql.catalyst.expressions.aggregate.AggregateExpression
import org.apache.spark.sql.catalyst.optimizer.BooleanSimplification
import org.apache.spark.sql.catalyst.plans._
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.catalyst.util.TypeUtils
import org.apache.spark.sql.connector.catalog.TableChange.{AddColumn, DeleteColumn, RenameColumn, UpdateColumnComment, UpdateColumnNullability, UpdateColumnType}
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.types._
Expand Down Expand Up @@ -379,6 +380,8 @@ trait CheckAnalysis extends PredicateHelper {
failAnalysis(s"Invalid partitioning: ${badReferences.mkString(", ")}")
}

create.tableSchema.foreach(f => TypeUtils.failWithIntervalType(f.dataType))

// If the view output doesn't have the same number of columns neither with the child
// output, nor with the query column names, throw an AnalysisException.
// If the view's child output can't up cast to the view output,
Expand Down Expand Up @@ -437,23 +440,27 @@ trait CheckAnalysis extends PredicateHelper {
if (parent.nonEmpty) {
findField("add to", parent)
}
TypeUtils.failWithIntervalType(add.dataType())
case update: UpdateColumnType =>
val field = findField("update", update.fieldNames)
val fieldName = update.fieldNames.quoted
update.newDataType match {
case _: StructType =>
throw new AnalysisException(
s"Cannot update ${table.name} field $fieldName type: " +
s"update a struct by adding, deleting, or updating its fields")
alter.failAnalysis(s"Cannot update ${table.name} field $fieldName type: " +
s"update a struct by updating its fields")
case _: MapType =>
throw new AnalysisException(
s"Cannot update ${table.name} field $fieldName type: " +
s"update a map by updating $fieldName.key or $fieldName.value")
alter.failAnalysis(s"Cannot update ${table.name} field $fieldName type: " +
s"update a map by updating $fieldName.key or $fieldName.value")
case _: ArrayType =>
throw new AnalysisException(
s"Cannot update ${table.name} field $fieldName type: " +
s"update the element by updating $fieldName.element")
case _: AtomicType =>
alter.failAnalysis(s"Cannot update ${table.name} field $fieldName type: " +
s"update the element by updating $fieldName.element")
case u: UserDefinedType[_] =>
alter.failAnalysis(s"Cannot update ${table.name} field $fieldName type: " +
s"update a UserDefinedType[${u.sql}] by updating its fields")
case _: CalendarIntervalType =>
alter.failAnalysis(s"Cannot update ${table.name} field $fieldName to " +
s"interval type")
case _ =>
// update is okay
}
if (!Cast.canUpCast(field.dataType, update.newDataType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.spark.sql.catalyst.util

import org.apache.spark.sql.AnalysisException
import org.apache.spark.sql.catalyst.analysis.{TypeCheckResult, TypeCoercion}
import org.apache.spark.sql.catalyst.expressions.RowOrdering
import org.apache.spark.sql.types._
Expand Down Expand Up @@ -98,4 +99,18 @@ object TypeUtils {
case _: AtomicType => true
case _ => false
}

def failWithIntervalType(dataType: DataType): Unit = {
dataType match {
case CalendarIntervalType =>
throw new AnalysisException("Cannot use interval type in the table schema.")
case ArrayType(et, _) => failWithIntervalType(et)
case MapType(kt, vt, _) =>
failWithIntervalType(kt)
failWithIntervalType(vt)
case s: StructType => s.foreach(f => failWithIntervalType(f.dataType))
case u: UserDefinedType[_] => failWithIntervalType(u.sqlType)
case _ =>
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import org.apache.spark.sql.catalyst.catalog.CatalogTypes.TablePartitionSpec
import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeReference}
import org.apache.spark.sql.catalyst.plans.DescribeTableSchema
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.catalyst.util.{escapeSingleQuotedString, quoteIdentifier, CaseInsensitiveMap}
import org.apache.spark.sql.catalyst.util.{escapeSingleQuotedString, quoteIdentifier, CaseInsensitiveMap, TypeUtils}
import org.apache.spark.sql.execution.datasources.{DataSource, PartitioningUtils}
import org.apache.spark.sql.execution.datasources.csv.CSVFileFormat
import org.apache.spark.sql.execution.datasources.json.JsonFileFormat
Expand Down Expand Up @@ -241,6 +241,7 @@ case class AlterTableAddColumnsCommand(
"in the table definition of " + table.identifier,
conf.caseSensitiveAnalysis)
DDLUtils.checkDataColNames(catalogTable, colsToAdd.map(_.name))
colsToAdd.foreach(f => TypeUtils.failWithIntervalType(f.dataType))

catalog.alterTableDataSchema(table, StructType(catalogTable.dataSchema ++ colsToAdd))
Seq.empty[Row]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.apache.spark.sql.catalyst.TableIdentifier
import org.apache.spark.sql.catalyst.catalog.CatalogTable
import org.apache.spark.sql.catalyst.expressions.Attribute
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
import org.apache.spark.sql.catalyst.util.TypeUtils
import org.apache.spark.sql.execution.command.{DDLUtils, RunnableCommand}
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.types._
Expand All @@ -48,6 +49,9 @@ case class CreateTable(
"create table without data insertion can only use ErrorIfExists or Ignore as SaveMode.")
}

tableDesc.schema.foreach(f => TypeUtils.failWithIntervalType(f.dataType))
query.foreach(_.schema.foreach(f => TypeUtils.failWithIntervalType(f.dataType)))

override def children: Seq[LogicalPlan] = query.toSeq
override def output: Seq[Attribute] = Seq.empty
override lazy val resolved: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ trait AlterTableTests extends SharedSparkSession {
}
}

test("AlterTable: add column with interval type") {
val t = s"${catalogAndNamespace}table_name"
withTable(t) {
sql(s"CREATE TABLE $t (id int, point struct<x: double, y: double>) USING $v2Format")
val e1 =
intercept[AnalysisException](sql(s"ALTER TABLE $t ADD COLUMN data interval"))
assert(e1.getMessage.contains("Cannot use interval type in the table schema."))
val e2 =
intercept[AnalysisException](sql(s"ALTER TABLE $t ADD COLUMN point.z interval"))
assert(e2.getMessage.contains("Cannot use interval type in the table schema."))
}
}

test("AlterTable: add column with position") {
val t = s"${catalogAndNamespace}table_name"
withTable(t) {
Expand Down Expand Up @@ -310,6 +323,15 @@ trait AlterTableTests extends SharedSparkSession {
}
}

test("AlterTable: update column type to interval") {
val t = s"${catalogAndNamespace}table_name"
withTable(t) {
sql(s"CREATE TABLE $t (id int) USING $v2Format")
val e = intercept[AnalysisException](sql(s"ALTER TABLE $t ALTER COLUMN id TYPE interval"))
assert(e.getMessage.contains("id to interval type"))
}
}

test("AlterTable: SET/DROP NOT NULL") {
val t = s"${catalogAndNamespace}table_name"
withTable(t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@ class DataSourceV2SQLSuite
checkAnswer(spark.internalCreateDataFrame(rdd, table.schema), Seq.empty)
}

test("CreateTable: invalid schema if has interval type") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we also test CTAS and replace table?

val e1 = intercept[AnalysisException](
sql(s"CREATE TABLE table_name (id int, value interval) USING $v2Format"))
assert(e1.getMessage.contains(s"Cannot use interval type in the table schema."))
val e2 = intercept[AnalysisException](
sql(s"CREATE TABLE table_name (id array<interval>) USING $v2Format"))
assert(e2.getMessage.contains(s"Cannot use interval type in the table schema."))
}

test("CreateTableAsSelect: use v2 plan because catalog is set") {
val basicCatalog = catalog("testcat").asTableCatalog
val atomicCatalog = catalog("testcat_atomic").asTableCatalog
Expand Down