dtenedor commented on code in PR #59640:
URL: https://github.com/apache/spark/pull/59640#discussion_r4428430109
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala:
##########
@@ -1014,6 +1037,25 @@ trait CheckAnalysis extends LookupCatalog with
QueryErrorsBase with PlanToString
create.tableSchema.foreach(f =>
TypeUtils.failWithIntervalType(f.dataType))
TypeUtils.failUnsupportedDataType(create.tableSchema, SQLConf.get)
SchemaUtils.checkIndeterminateCollationInSchema(create.tableSchema)
+ create match {
+ // The columns of CREATE TABLE AS SELECT come from its query,
not from DDL.
+ case ctas: V2CreateTableAsSelectPlan =>
+ TypeUtils.checkNoUnknownFileTypeWrite(ctas.query.output)
+ case _ =>
+ TypeUtils.checkFileTypeHasQualifier(create.tableSchema)
+ }
+ // FILE has no defined ordering, so it cannot be a partition or
clustering column.
+ create.partitioning.foreach { transform =>
Review Comment:
**Low: after the rebase, FILE can be a write-ordering key.** SPARK-34586,
now on master, adds `writeOrdering` to all four `V2CreateTablePlan` nodes and
checks it only for unknown columns, but this loop checks only
`create.partitioning`, so `ORDERED BY (f)`, `LOCALLY ORDERED BY (bucket(4, f))`
or a struct key that contains a FILE will pass analysis in CREATE, CTAS,
REPLACE and RTAS. On `testcat` and the other catalogs in this repository the
statement then fails at planning with `UNSUPPORTED_FEATURE.TABLE_OPERATION`
instead of a FILE error, and a catalog that advertises
`SUPPORTS_CREATE_TABLE_WITH_WRITE_DISTRIBUTION_AND_ORDERING` would store a FILE
sort key (V1 tables reject these clauses in `ResolveSessionCatalog`, and
`DISTRIBUTED BY PARTITION` only distributes by partition columns, which this
loop covers). Master leaves orderability to the catalog on purpose
(`TableInfo.writeOrdering()`), but a catalog can't know that FILE has no
ordering. Suggest checking the columns of `create.
writeOrdering.flatMap(_.expression().references())` with
`checkNoFileTypeInColumnUsage` after this loop, under a new
`FILE_TYPE_UNSUPPORTED_COLUMN_USAGE.WRITE_ORDERING_COLUMN` subclass and with
tests on `testcat` (analysis runs before the planner's capability check), or
listing it with the follow-ups in the description.
##########
sql/core/src/test/scala/org/apache/spark/sql/FileTypeRestrictionsSuite.scala:
##########
@@ -0,0 +1,550 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql
+
+import org.apache.spark.SparkConf
+import org.apache.spark.api.python.{PythonBroadcast, PythonEvalType,
SimplePythonFunction}
+import org.apache.spark.broadcast.Broadcast
+import org.apache.spark.sql.api.java.UDF1
+import org.apache.spark.sql.catalyst.TableIdentifier
+import org.apache.spark.sql.catalyst.catalog.{CatalogStorageFormat,
CatalogTable, CatalogTableType}
+import org.apache.spark.sql.connector.catalog.InMemoryCatalog
+import org.apache.spark.sql.execution.python.{UserDefinedPythonFunction,
UserDefinedPythonTableFunction}
+import org.apache.spark.sql.expressions.{MutableAggregationBuffer,
UserDefinedAggregateFunction}
+import org.apache.spark.sql.functions.{col, udf}
+import org.apache.spark.sql.test.SharedSparkSession
+import org.apache.spark.sql.types.{DataType, FileType, IntegerType, MapType,
StructType}
+
+/**
+ * Tests the rules on where the FILE type can be used: table columns must be
FILE EXTERNAL,
+ * routines declare a plain FILE, and FILE has no defined ordering.
+ */
+class FileTypeRestrictionsSuite extends QueryTest with SharedSparkSession {
+
+ override protected def sparkConf: SparkConf =
+ super.sparkConf.set("spark.sql.catalog.testcat",
classOf[InMemoryCatalog].getName)
+
+ // A FILE without a qualifier, at the top level and nested.
+ private val unqualifiedFileTypes = Seq(
+ "FILE",
+ "ARRAY<FILE>",
+ "MAP<STRING, FILE>",
+ "STRUCT<f1: FILE>",
+ "STRUCT<f1: INT, f2: STRUCT<f3: ARRAY<FILE>>>")
+
+ private val externalFile = "SELECT CAST(NULL AS FILE EXTERNAL) AS f"
+
+ test("a table column definition requires FILE EXTERNAL") {
+ Seq("testcat.t", "t").foreach { table =>
+ val provider = if (table.startsWith("testcat")) "" else "USING parquet"
+ unqualifiedFileTypes.foreach { dataType =>
+ withTable(table) {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"CREATE TABLE $table (id INT, c $dataType) $provider")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ }
+ }
+ }
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT, c STRUCT<f1: ARRAY<FILE
EXTERNAL>>)")
+ }
+ }
+
+ test("REPLACE TABLE requires FILE EXTERNAL") {
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT)")
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql("REPLACE TABLE testcat.t (id INT, c FILE)")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ }
+ }
+
+ test("ALTER TABLE ADD COLUMNS and REPLACE COLUMNS require FILE EXTERNAL") {
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT)")
+ unqualifiedFileTypes.foreach { dataType =>
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"ALTER TABLE testcat.t ADD COLUMN c $dataType")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"ALTER TABLE testcat.t REPLACE COLUMNS (id INT, c $dataType)")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ }
+ sql("ALTER TABLE testcat.t ADD COLUMN c FILE EXTERNAL")
+ }
+ }
+
+ test("CREATE TABLE AS SELECT cannot store a FILE without a qualifier") {
+ Seq("testcat.t", "t").foreach { table =>
+ val provider = if (table.startsWith("testcat")) "" else "USING parquet"
+ withTable(table) {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"CREATE TABLE $table $provider AS SELECT 1 AS id, CAST(NULL
AS FILE) AS f")
+ },
+ condition = "FILE_TYPE_UNKNOWN_WRITE",
+ parameters = Map("colNames" -> "[`f`]"))
+ }
+ }
+ }
+
+ test("schema evolution cannot add a FILE without a qualifier") {
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT, s STRUCT<a: INT>)")
+ val schema = spark.table("testcat.t").schema
+ Seq(
+ "SELECT 1 AS id, named_struct('a', 1) AS s, CAST(NULL AS FILE) AS f"
-> "[`f`]",
+ "SELECT 1 AS id, named_struct('a', 1, 'f', CAST(NULL AS FILE)) AS s"
-> "[`s`.`f`]"
+ ).foreach { case (query, colNames) =>
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"INSERT WITH SCHEMA EVOLUTION INTO testcat.t $query")
+ },
+ condition = "FILE_TYPE_UNKNOWN_WRITE",
+ parameters = Map("colNames" -> colNames))
+ // The table is not changed.
+ assert(spark.table("testcat.t").schema === schema)
+ }
+ // A FILE EXTERNAL column can be added.
+ sql("INSERT WITH SCHEMA EVOLUTION INTO testcat.t " +
+ "SELECT 1 AS id, named_struct('a', 1) AS s, CAST(NULL AS FILE
EXTERNAL) AS f")
+ assert(spark.table("testcat.t").schema === schema.add("f",
FileType.external))
+ }
+ }
+
+ test("CREATE TABLE LIKE cannot copy a FILE without a qualifier from a view")
{
+ withTempView("v") {
+ withView("pv") {
+ sql("CREATE TEMPORARY VIEW v AS SELECT 1 AS id, CAST(NULL AS FILE) AS
f")
+ sql("CREATE VIEW pv AS SELECT 1 AS id, CAST(NULL AS FILE) AS f")
+ for (source <- Seq("v", "pv"); target <- Seq("t", "testcat.t")) {
+ withTable(target) {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"CREATE TABLE $target LIKE $source")
Review Comment:
**Low: these tests don't pin the move to analysis or the table-source
branch.** `sql(...)` analyzes and then runs the command, so this test passes
whether the check is in `CheckAnalysis` or back in `CreateTableLikeExec.run`,
and every source is a view, so the new arm's `ResolvedTable` branch (the only
one with its own schema conversion) and a V2 source copied to a session-catalog
target never run. Suggest an analysis-only assertion such as
`spark.sessionState.executePlan(spark.sessionState.sqlParser.parsePlan("CREATE
TABLE testcat.t LIKE v")).assertAnalyzed()` expecting
`FILE_TYPE_UNKNOWN_WRITE`, a `testcat.src` table holding a bare FILE created
through the catalog API (as the `ALTER TABLE ... CLUSTER BY` test does for a
session table) and copied to both `testcat.t` and `t`, and a positive control
that copies a view holding `FILE EXTERNAL`.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala:
##########
@@ -1044,6 +1086,20 @@ trait CheckAnalysis extends LookupCatalog with
QueryErrorsBase with PlanToString
}
}
+ // A view can hold a FILE without a qualifier, which a table cannot
store. A V1 target
+ // is checked by `CreateTableLikeCommand`, as the V1 command carries
no resolved source.
Review Comment:
**Low: this comment gets the V1 condition wrong.** `ResolveSessionCatalog`
rewrites `CreateTableLike` into `CreateTableLikeCommand` only when both the
target and the source are in the session catalog, so `CREATE TABLE t LIKE
testcat.src` has a V1 target but is checked by this arm; the PR description
uses the same "V1 target" wording. A correction to what I wrote in the previous
round: the V1 check isn't limited to execution, because the rewrite in
`ResolveSessionCatalog` still sees the resolved source, and that is where this
PR already checks V1 `ALTER TABLE ... CLUSTER BY`. Suggest rewording to
something like "When the target and source are both in the session catalog,
`ResolveSessionCatalog` has already rewritten this into
`CreateTableLikeCommand`, which checks the source when it runs"; moving the V1
check into that rewrite, so both paths fail at analysis, is optional.
##########
sql/api/src/main/scala/org/apache/spark/sql/types/FileType.scala:
##########
@@ -25,30 +25,30 @@ import org.apache.spark.annotation.Unstable
/**
* The data type representing a reference to a file, with metadata describing
the file.
*
- * In SQL the type is named `FILE`, and the `EXTERNAL` keyword is planned to
be required when
- * declaring a table column, as in `CREATE TABLE t (f FILE EXTERNAL)`; until
then a bare `FILE` is
- * accepted wherever a type is. Its JSON representation is `"file external"`
for
- * `FileType.external` and `"file unknown"` for `FileType()`, except that an
external file in a
- * struct field is the plain `"file"`. A bare `"file"` is read as external
inside a struct field
- * and as unknown elsewhere.
+ * In SQL the type is named `FILE`. A table column must be declared with the
`EXTERNAL` keyword,
+ * as in `CREATE TABLE t (f FILE EXTERNAL)`, while the signature of a function
declares a bare
+ * `FILE`. Its JSON representation is `"file external"` for
`FileType.external` and
+ * `"file unknown"` for `FileType()`, except that an external file in a struct
field is the plain
+ * `"file"`. A bare `"file"` is read as external inside a struct field and as
unknown elsewhere.
*
* FileType is planned to be physically stored as a struct with the following
fields (see
* `FileType.FIELDS`), but it deliberately does not extend [[StructType]]:
- * - uri (STRING): the opaque URI to the file
+ * - uri (STRING): the URI of the referenced file, which may be absolute or
relative
* - offset (BIGINT): the start of the referenced byte range
- * - size (BIGINT): the size of the file in bytes
- * - content_type (STRING): the MIME/content type of the file
- * - checksum (STRING): an algorithm-tagged integrity token for the file,
e.g. "ETAG:{etag}"
+ * - size (BIGINT): the length of the referenced byte range
+ * - content_type (STRING): the MIME/content type of the referenced bytes
+ * - checksum (STRING): an algorithm-tagged integrity token for the
referenced bytes, e.g.
+ * "MD5:{digest}", or "ETAG:{etag}" for the whole file
* - inline (BINARY): the referenced bytes stored inline in the value
*
* FileType may be EXTERNAL (simple references, lifecycle independent of the
table) or UNKNOWN
* (mode not yet determined, such as the result of a cast or of a UDF). Mode
is part of type
* identity - files with different modes are distinct under `equals`.
*
- * FileType is planned to be not comparable -- it cannot be used in join
expressions, grouping
- * expressions, or as a partition/clustering column. Users can extract the uri
field for such
- * operations. This type currently only guarantees the schema-level pieces
(DDL and JSON); the
- * physical representation and most of the restrictions are added by later
parts.
+ * FileType is not comparable -- it cannot be the key of a map built in a
query or of a map in the
+ * return type of a function, and it cannot be used in join expressions,
grouping expressions, set
+ * operations, or as a partition/clustering column. Users can extract the uri
field for such
Review Comment:
**Low: this list of where FILE can't be used is off.** "Set operations" is
too broad, since `UNION ALL` is allowed, and the list leaves out sorting,
hashing, repartition and `REBALANCE` keys and bucket columns, which the PR also
rejects; after the rebase, write-ordering keys belong here too. Suggest stating
the rule instead (an operation that compares, sorts, groups or hashes a FILE
fails at analysis), followed by the DDL cases: partition, bucket, clustering
and write-ordering columns.
##########
sql/core/src/test/scala/org/apache/spark/sql/FileTypeRestrictionsSuite.scala:
##########
@@ -0,0 +1,550 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql
+
+import org.apache.spark.SparkConf
+import org.apache.spark.api.python.{PythonBroadcast, PythonEvalType,
SimplePythonFunction}
+import org.apache.spark.broadcast.Broadcast
+import org.apache.spark.sql.api.java.UDF1
+import org.apache.spark.sql.catalyst.TableIdentifier
+import org.apache.spark.sql.catalyst.catalog.{CatalogStorageFormat,
CatalogTable, CatalogTableType}
+import org.apache.spark.sql.connector.catalog.InMemoryCatalog
+import org.apache.spark.sql.execution.python.{UserDefinedPythonFunction,
UserDefinedPythonTableFunction}
+import org.apache.spark.sql.expressions.{MutableAggregationBuffer,
UserDefinedAggregateFunction}
+import org.apache.spark.sql.functions.{col, udf}
+import org.apache.spark.sql.test.SharedSparkSession
+import org.apache.spark.sql.types.{DataType, FileType, IntegerType, MapType,
StructType}
+
+/**
+ * Tests the rules on where the FILE type can be used: table columns must be
FILE EXTERNAL,
+ * routines declare a plain FILE, and FILE has no defined ordering.
+ */
+class FileTypeRestrictionsSuite extends QueryTest with SharedSparkSession {
+
+ override protected def sparkConf: SparkConf =
+ super.sparkConf.set("spark.sql.catalog.testcat",
classOf[InMemoryCatalog].getName)
+
+ // A FILE without a qualifier, at the top level and nested.
+ private val unqualifiedFileTypes = Seq(
+ "FILE",
+ "ARRAY<FILE>",
+ "MAP<STRING, FILE>",
+ "STRUCT<f1: FILE>",
+ "STRUCT<f1: INT, f2: STRUCT<f3: ARRAY<FILE>>>")
+
+ private val externalFile = "SELECT CAST(NULL AS FILE EXTERNAL) AS f"
+
+ test("a table column definition requires FILE EXTERNAL") {
+ Seq("testcat.t", "t").foreach { table =>
+ val provider = if (table.startsWith("testcat")) "" else "USING parquet"
+ unqualifiedFileTypes.foreach { dataType =>
+ withTable(table) {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"CREATE TABLE $table (id INT, c $dataType) $provider")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ }
+ }
+ }
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT, c STRUCT<f1: ARRAY<FILE
EXTERNAL>>)")
+ }
+ }
+
+ test("REPLACE TABLE requires FILE EXTERNAL") {
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT)")
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql("REPLACE TABLE testcat.t (id INT, c FILE)")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ }
+ }
+
+ test("ALTER TABLE ADD COLUMNS and REPLACE COLUMNS require FILE EXTERNAL") {
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT)")
+ unqualifiedFileTypes.foreach { dataType =>
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"ALTER TABLE testcat.t ADD COLUMN c $dataType")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"ALTER TABLE testcat.t REPLACE COLUMNS (id INT, c $dataType)")
+ },
+ condition = "FILE_TYPE_MISSING_QUALIFIER")
+ }
+ sql("ALTER TABLE testcat.t ADD COLUMN c FILE EXTERNAL")
+ }
+ }
+
+ test("CREATE TABLE AS SELECT cannot store a FILE without a qualifier") {
+ Seq("testcat.t", "t").foreach { table =>
+ val provider = if (table.startsWith("testcat")) "" else "USING parquet"
+ withTable(table) {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"CREATE TABLE $table $provider AS SELECT 1 AS id, CAST(NULL
AS FILE) AS f")
+ },
+ condition = "FILE_TYPE_UNKNOWN_WRITE",
+ parameters = Map("colNames" -> "[`f`]"))
+ }
+ }
+ }
+
+ test("schema evolution cannot add a FILE without a qualifier") {
+ withTable("testcat.t") {
+ sql("CREATE TABLE testcat.t (id INT, s STRUCT<a: INT>)")
+ val schema = spark.table("testcat.t").schema
+ Seq(
+ "SELECT 1 AS id, named_struct('a', 1) AS s, CAST(NULL AS FILE) AS f"
-> "[`f`]",
+ "SELECT 1 AS id, named_struct('a', 1, 'f', CAST(NULL AS FILE)) AS s"
-> "[`s`.`f`]"
+ ).foreach { case (query, colNames) =>
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"INSERT WITH SCHEMA EVOLUTION INTO testcat.t $query")
+ },
+ condition = "FILE_TYPE_UNKNOWN_WRITE",
+ parameters = Map("colNames" -> colNames))
+ // The table is not changed.
+ assert(spark.table("testcat.t").schema === schema)
+ }
+ // A FILE EXTERNAL column can be added.
+ sql("INSERT WITH SCHEMA EVOLUTION INTO testcat.t " +
+ "SELECT 1 AS id, named_struct('a', 1) AS s, CAST(NULL AS FILE
EXTERNAL) AS f")
+ assert(spark.table("testcat.t").schema === schema.add("f",
FileType.external))
+ }
+ }
+
+ test("CREATE TABLE LIKE cannot copy a FILE without a qualifier from a view")
{
+ withTempView("v") {
+ withView("pv") {
+ sql("CREATE TEMPORARY VIEW v AS SELECT 1 AS id, CAST(NULL AS FILE) AS
f")
+ sql("CREATE VIEW pv AS SELECT 1 AS id, CAST(NULL AS FILE) AS f")
+ for (source <- Seq("v", "pv"); target <- Seq("t", "testcat.t")) {
+ withTable(target) {
+ checkError(
+ exception = intercept[AnalysisException] {
+ sql(s"CREATE TABLE $target LIKE $source")
+ },
+ condition = "FILE_TYPE_UNKNOWN_WRITE",
+ parameters = Map("colNames" -> "[`f`]"))
+ assert(!spark.catalog.tableExists(target))
+ // As for CREATE TABLE AS SELECT, IF NOT EXISTS does not skip the
check when the
+ // table exists.
Review Comment:
**Nit: test wording.** "As for CREATE TABLE AS SELECT" reads as a statement
about CTAS; "As with" says what is meant, and no CTAS `IF NOT EXISTS` test
backs the comparison yet. Two more in this file: the test name "a column cannot
be altered to or from a FILE" (L215) over-claims, since a `VOID` column can
become `FILE EXTERNAL`, and "Neither registration nor analysis runs Python"
(L488) holds for that test but not in general, since analyzing a call to a UDTF
with an `analyze` method does run Python. Suggest rewording all three and
adding the CTAS `IF NOT EXISTS` case.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala:
##########
@@ -29,7 +29,8 @@ import org.apache.spark.sql.catalyst.optimizer.InlineCTE
import org.apache.spark.sql.catalyst.plans.logical._
import
org.apache.spark.sql.catalyst.trees.TreePattern.{LATERAL_COLUMN_ALIAS_REFERENCE,
PLAN_EXPRESSION, UNRESOLVED_WINDOW_EXPRESSION}
import org.apache.spark.sql.catalyst.util.{CharVarcharUtils, StringUtils,
TypeUtils}
-import org.apache.spark.sql.connector.catalog.{CatalogManager, LookupCatalog,
SupportsPartitionManagement}
+import org.apache.spark.sql.connector.catalog.{CatalogManager, CatalogV2Util,
LookupCatalog, SupportsPartitionManagement}
Review Comment:
**Nit: this import can go, which also removes a rebase conflict.**
`CatalogV2Implicits._` is already imported, so the new `CreateTableLike` arm
can use `table.columns.asSchema` instead of
`CatalogV2Util.v2ColumnsToStructType(table.columns())`. This line sits next to
master's SPARK-34586 import change, which is what makes this file conflict;
with it reverted, a trial three-way merge of `CheckAnalysis.scala` with master
is clean, leaving only the one-line import conflict in
`ResolveSessionCatalog.scala`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]