huaxingao commented on code in PR #13979:
URL: https://github.com/apache/iceberg/pull/13979#discussion_r4276008421


##########
spark/v4.2/spark/src/main/scala/org/apache/spark/sql/catalyst/analysis/ViewUtil.scala:
##########
@@ -73,6 +99,75 @@ object ViewUtil {
         None
     }
 
+  /**
+   * Build the referenced-by view chain from fully qualified view identifier 
parts.
+   * Entries must belong to the same catalog as the loaded target.
+   */
+  def buildReferencedByChain(
+      viewChain: Seq[Seq[String]],
+      targetCatalogName: String): java.util.List[TableIdentifier] = {
+    viewChain.foreach { parts =>
+      require(
+        parts.size >= 3,
+        s"View chain entry must be fully qualified [catalog, namespace..., 
name], got: " +
+          parts.mkString("."))
+    }
+
+    val crossCatalogViews = viewChain.filter(parts => 
!parts.headOption.contains(targetCatalogName))
+    if (crossCatalogViews.nonEmpty) {
+      throw new IllegalStateException(

Review Comment:
   This throws again in the latest commit, and 
`crossCatalogViewAccessFailsWhenReferencedByIsEnabled` is back too (the Oct 5 
version kept only the same-catalog views instead). Is failing the intended 
behavior now? 



##########
spark/v4.2/spark-extensions/src/main/scala/org/apache/iceberg/spark/extensions/IcebergSparkSessionExtensions.scala:
##########
@@ -33,6 +33,8 @@ class IcebergSparkSessionExtensions extends 
(SparkSessionExtensions => Unit) {
     extensions.injectParser { case (_, parser) => new 
IcebergSparkSqlExtensionsParser(parser) }
 
     // analyzer extensions
+    // Run before Spark's ResolveRelations so Iceberg can rewrite view 
children with load context.
+    extensions.injectHintResolutionRule { spark => ResolveViews(spark) }

Review Comment:
   This also runs when the flag is off, so it changes current behavior. With an 
Iceberg view and a temp view both named `shadow_v`:
   
   ```sql
   DESCRIBE TABLE shadow_v
   -- flag off, with this line:    returns the Iceberg view's columns
   -- flag off, without this line: returns the temp view's columns
   ```
   
   The hint batch runs before Spark resolves temp views, so the Iceberg view 
now wins. Could the hint-batch rule do nothing when the flag is off?



##########
spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/LoadContextTableCatalog.scala:
##########
@@ -0,0 +1,61 @@
+/*
+ * 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.catalyst.analysis
+
+import org.apache.iceberg.catalog.LoadContext
+import org.apache.iceberg.spark.SparkSupportsLoadContext
+import org.apache.spark.sql.connector.catalog.Identifier
+import org.apache.spark.sql.connector.catalog.Table
+import org.apache.spark.sql.connector.catalog.TableCatalog
+import org.apache.spark.sql.connector.catalog.TableChange
+import org.apache.spark.sql.util.CaseInsensitiveStringMap
+
+/**
+ * Catalog for a table read through a view, so that reloads keep the view 
chain. Spark's table
+ * version refresh before execution reloads tables by identifier only, using 
the relation's
+ * catalog. Only read operations are supported.
+ */
+private[analysis] case class LoadContextTableCatalog(

Review Comment:
   `LoadContext` doesn't define `equals`/`hashCode`, and every query creates a 
new one. So two plans for the same view never compare equal here. For example, 
with the flag on:
   
   ```java
   Dataset<Row> df1 = spark.sql("SELECT * FROM simple_view");
   df1.cache();
   df1.count();
   spark.sql("SELECT * FROM simple_view"); // doesn't use the cache (it does 
with the flag off)
   ```
   
   Could we add `equals`/`hashCode` to `LoadContext`?



##########
spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveViews.scala:
##########
@@ -18,29 +18,260 @@
  */
 package org.apache.spark.sql.catalyst.analysis
 
+import org.apache.iceberg.catalog.LoadContext
+import org.apache.iceberg.spark.SparkSQLProperties
+import org.apache.iceberg.spark.SparkSupportsLoadContext
 import org.apache.spark.sql.SparkSession
 import org.apache.spark.sql.catalyst.analysis.ViewUtil.IcebergViewHelper
+import org.apache.spark.sql.catalyst.expressions.Alias
+import org.apache.spark.sql.catalyst.expressions.Cast
+import org.apache.spark.sql.catalyst.expressions.SubqueryExpression
+import org.apache.spark.sql.catalyst.expressions.UpCast
+import org.apache.spark.sql.catalyst.parser.ParseException
 import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
+import org.apache.spark.sql.catalyst.plans.logical.Project
+import org.apache.spark.sql.catalyst.plans.logical.SubqueryAlias
 import org.apache.spark.sql.catalyst.plans.logical.views.ResolvedV2View
+import 
org.apache.spark.sql.catalyst.plans.logical.views.UnResolvedRelationFromView
 import org.apache.spark.sql.catalyst.rules.Rule
+import org.apache.spark.sql.catalyst.trees.CurrentOrigin
+import org.apache.spark.sql.catalyst.trees.Origin
 import org.apache.spark.sql.connector.catalog.CatalogManager
+import org.apache.spark.sql.connector.catalog.CatalogPlugin
+import org.apache.spark.sql.connector.catalog.DelegatingTable
+import org.apache.spark.sql.connector.catalog.Identifier
 import org.apache.spark.sql.connector.catalog.LookupCatalog
+import org.apache.spark.sql.connector.catalog.TableCatalog
+import org.apache.spark.sql.connector.catalog.V1Table
+import org.apache.spark.sql.connector.catalog.View
+import org.apache.spark.sql.errors.QueryCompilationErrors
+import org.apache.spark.sql.execution.datasources.v2.DataSourceV2Relation
 
 /**
  * Resolves Iceberg views referenced by commands that use 
`UnresolvedTableOrView`.
  *
  * In Spark 4.2, view relations are expanded through 
`RelationCatalog.loadRelation`, so Spark owns
  * query parsing, identifier resolution, and schema-mode application. This 
rule only converts the
  * remaining command targets to `ResolvedV2View` for Iceberg command planning.
+ *
+ * When referenced-by is enabled, this rule also expands Iceberg views itself 
so that tables they
+ * read are loaded with the referencing view chain.
  */
 case class ResolveViews(spark: SparkSession) extends Rule[LogicalPlan] with 
LookupCatalog {
+
+  import org.apache.spark.sql.connector.catalog.CatalogV2Implicits._
+
   protected lazy val catalogManager: CatalogManager = 
spark.sessionState.catalogManager
 
-  override def apply(plan: LogicalPlan): LogicalPlan = plan resolveOperators {
+  override def apply(plan: LogicalPlan): LogicalPlan = {
+    if (referencedByEnabled) {
+      resolveWithReferencedBy(plan)
+    } else {
+      resolveViewCommands(plan)
+    }
+  }
+
+  private def resolveWithReferencedBy(plan: LogicalPlan): LogicalPlan = plan 
resolveOperators {
+    case u @ UnresolvedRelation(nameParts, _, _)
+        if catalogManager.v1SessionCatalog.isTempView(nameParts) =>
+      u
+
+    case u @ UnresolvedRelation(parts @ CatalogAndIdentifier(catalog, ident), 
_, _) =>
+      ViewUtil
+        .loadView(catalog, ident)

Review Comment:
   I think the `loadView` problem is back. With the flag on, every table read 
now calls `loadView` first, then Spark loads the table, so a REST catalog gets 
an extra 404 for each table read.
   
   Maybe we can keep the single `loadRelation` call from the earlier version, 
and just return `u` for `V1Table` / `DelegatingTable`? Something like:
   
   ```scala
   catalog.loadRelation(ident) match {
     case view: View if ViewUtil.isIcebergView(view) => 
createViewRelation(parts, catalog, ident, view)
     case _: V1Table | _: DelegatingTable => u
     case table: Table => SubqueryAlias(..., DataSourceV2Relation.create(table, 
...))
     case _ => u
   }
   ```



-- 
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]

Reply via email to