Skip to content

Commit 99b7d2a

Browse files
ilicmarkodbMaxGekk
authored andcommitted
[SPARK-51673][SQL] Apply default collation to alter view query
### What changes were proposed in this pull request? Fixed the application of default collation in `ALTER VIEW` queries. For example, if a view has a default collation and we execute `ALTER VIEW v AS SELECT 'a' AS c1`, the default collation was not being applied to the literal `a`. ### Why are the changes needed? Bug fix. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? Tests added to `DefaultCollationTestSuite.scala` ### Was this patch authored or co-authored using generative AI tooling? No. Closes #50468 from ilicmarkodb/fix_alter_view. Authored-by: ilicmarkodb <[email protected]> Signed-off-by: Max Gekk <[email protected]>
1 parent 6692642 commit 99b7d2a

File tree

3 files changed

+78
-9
lines changed

3 files changed

+78
-9
lines changed

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveDDLCommandStringTypes.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ object ResolveDDLCommandStringTypes extends Rule[LogicalPlan] {
6666
StringType(defaultCollation)
6767
}
6868

69+
case alterViewAs: AlterViewAs =>
70+
alterViewAs.child match {
71+
case resolvedPersistentView: ResolvedPersistentView =>
72+
val collation = resolvedPersistentView.metadata.collation.getOrElse(defaultCollation)
73+
StringType(collation)
74+
case resolvedTempView: ResolvedTempView =>
75+
val collation = resolvedTempView.metadata.collation.getOrElse(defaultCollation)
76+
StringType(collation)
77+
case _ =>
78+
// As a safeguard, use the default collation for unknown cases.
79+
StringType(defaultCollation)
80+
}
81+
6982
// Check if view has default collation
7083
case _ if AnalysisContext.get.collation.isDefined =>
7184
StringType(AnalysisContext.get.collation.get)

sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ case class CreateViewCommand(
137137
originalText,
138138
analyzedPlan,
139139
aliasedPlan,
140-
referredTempFunctions)
140+
referredTempFunctions,
141+
collation)
141142
catalog.createTempView(name.table, tableDefinition, overrideIfExists = replace)
142143
} else if (viewType == GlobalTempView) {
143144
val db = sparkSession.sessionState.conf.getConf(StaticSQLConf.GLOBAL_TEMP_DATABASE)
@@ -739,8 +740,10 @@ object ViewHelper extends SQLConfHelper with Logging {
739740
originalText: Option[String],
740741
analyzedPlan: LogicalPlan,
741742
aliasedPlan: LogicalPlan,
742-
referredTempFunctions: Seq[String]): TemporaryViewRelation = {
743-
val uncache = getRawTempView(name.table).map { r =>
743+
referredTempFunctions: Seq[String],
744+
collation: Option[String] = None): TemporaryViewRelation = {
745+
val rawTempView = getRawTempView(name.table)
746+
val uncache = rawTempView.map { r =>
744747
needsToUncache(r, aliasedPlan)
745748
}.getOrElse(false)
746749
val storeAnalyzedPlanForView = session.sessionState.conf.storeAnalyzedPlanForView ||
@@ -754,6 +757,16 @@ object ViewHelper extends SQLConfHelper with Logging {
754757
}
755758
CommandUtils.uncacheTableOrView(session, name)
756759
}
760+
// When called from CreateViewCommand, this function determines the collation from the
761+
// DEFAULT COLLATION clause in the query or assigns None if unspecified.
762+
// When called from AlterViewAsCommand, it retrieves the collation from the view's metadata.
763+
val defaultCollation = if (collation.isDefined) {
764+
collation
765+
} else if (rawTempView.isDefined) {
766+
rawTempView.get.tableMeta.collation
767+
} else {
768+
None
769+
}
757770
if (!storeAnalyzedPlanForView) {
758771
TemporaryViewRelation(
759772
prepareTemporaryView(
@@ -762,10 +775,11 @@ object ViewHelper extends SQLConfHelper with Logging {
762775
analyzedPlan,
763776
aliasedPlan.schema,
764777
originalText.get,
765-
referredTempFunctions))
778+
referredTempFunctions,
779+
defaultCollation))
766780
} else {
767781
TemporaryViewRelation(
768-
prepareTemporaryViewStoringAnalyzedPlan(name, aliasedPlan),
782+
prepareTemporaryViewStoringAnalyzedPlan(name, aliasedPlan, defaultCollation),
769783
Some(aliasedPlan))
770784
}
771785
}
@@ -795,7 +809,8 @@ object ViewHelper extends SQLConfHelper with Logging {
795809
analyzedPlan: LogicalPlan,
796810
viewSchema: StructType,
797811
originalText: String,
798-
tempFunctions: Seq[String]): CatalogTable = {
812+
tempFunctions: Seq[String],
813+
collation: Option[String]): CatalogTable = {
799814

800815
val tempViews = collectTemporaryViews(analyzedPlan)
801816
val tempVariables = collectTemporaryVariables(analyzedPlan)
@@ -812,7 +827,8 @@ object ViewHelper extends SQLConfHelper with Logging {
812827
schema = viewSchema,
813828
viewText = Some(originalText),
814829
createVersion = org.apache.spark.SPARK_VERSION,
815-
properties = newProperties)
830+
properties = newProperties,
831+
collation = collation)
816832
}
817833

818834
/**
@@ -821,12 +837,14 @@ object ViewHelper extends SQLConfHelper with Logging {
821837
*/
822838
private def prepareTemporaryViewStoringAnalyzedPlan(
823839
viewName: TableIdentifier,
824-
analyzedPlan: LogicalPlan): CatalogTable = {
840+
analyzedPlan: LogicalPlan,
841+
collation: Option[String]): CatalogTable = {
825842
CatalogTable(
826843
identifier = viewName,
827844
tableType = CatalogTableType.VIEW,
828845
storage = CatalogStorageFormat.empty,
829846
schema = analyzedPlan.schema,
830-
properties = Map((VIEW_STORING_ANALYZED_PLAN, "true")))
847+
properties = Map((VIEW_STORING_ANALYZED_PLAN, "true")),
848+
collation = collation)
831849
}
832850
}

sql/core/src/test/scala/org/apache/spark/sql/collation/DefaultCollationTestSuite.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,44 @@ class DefaultCollationTestSuiteV1 extends DefaultCollationTestSuite {
475475
checkAnswer(sql(s"SELECT COUNT(*) FROM $testView WHERE c2 = 'b'"), Seq(Row(0)))
476476
}
477477
}
478+
479+
test("ALTER VIEW check default collation") {
480+
Seq("", "TEMPORARY").foreach { temporary =>
481+
withView(testView) {
482+
sql(s"CREATE $temporary VIEW $testView DEFAULT COLLATION UTF8_LCASE AS SELECT 1")
483+
sql(s"ALTER VIEW $testView AS SELECT 'a' AS c1, 'b' AS c2")
484+
val prefix = "SYSTEM.BUILTIN"
485+
checkAnswer(sql(s"SELECT COLLATION(c1) FROM $testView"),
486+
Row(s"$prefix.UTF8_LCASE"))
487+
checkAnswer(sql(s"SELECT COLLATION(c2) FROM $testView"),
488+
Row(s"$prefix.UTF8_LCASE"))
489+
sql(s"ALTER VIEW $testView AS SELECT 'c' AS c3 WHERE 'a' = 'A'")
490+
checkAnswer(sql(s"SELECT COLLATION(c3) FROM $testView"),
491+
Row(s"$prefix.UTF8_LCASE"))
492+
}
493+
withTable(testTable) {
494+
sql(s"CREATE TABLE $testTable (c1 STRING COLLATE UTF8_LCASE, c2 STRING, c3 INT)")
495+
sql(s"INSERT INTO $testTable VALUES ('a', 'b', 1)")
496+
withView(testView) {
497+
sql(s"CREATE $temporary VIEW $testView DEFAULT COLLATION sr_AI_CI AS SELECT 'a' AS c1")
498+
// scalastyle:off
499+
sql(
500+
s"""ALTER VIEW $testView AS
501+
| SELECT *, 'c' AS c4,
502+
| (SELECT (SELECT CASE 'š' = 'S' WHEN TRUE THEN 'd' ELSE 'b' END)) AS c5
503+
| FROM $testTable
504+
| WHERE c1 = 'A' AND 'ć' = 'Č'""".stripMargin)
505+
// scalastyle:on
506+
val prefix = "SYSTEM.BUILTIN"
507+
checkAnswer(sql(s"SELECT COLLATION(c4) FROM $testView"),
508+
Row(s"$prefix.sr_CI_AI"))
509+
checkAnswer(sql(s"SELECT COLLATION(c5) FROM $testView"),
510+
Row(s"$prefix.sr_CI_AI"))
511+
checkAnswer(sql(s"SELECT c5 FROM $testView"), Row("d"))
512+
}
513+
}
514+
}
515+
}
478516
}
479517

480518
class DefaultCollationTestSuiteV2 extends DefaultCollationTestSuite with DatasourceV2SQLBase {

0 commit comments

Comments
 (0)