You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by ur...@apache.org on 2026/09/29 19:03:46 UTC

(spark) branch master updated: [SPARK-59323][SQL] Record temp variables read by an IDENTIFIER clause as referred by the view

This is an automated email from the ASF dual-hosted git repository.

uros-b pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new ef61d20c10f5 [SPARK-59323][SQL] Record temp variables read by an IDENTIFIER clause as referred by the view
ef61d20c10f5 is described below

commit ef61d20c10f52a882a60fc646a71ae211017bffa
Author: Stevo Mitric <st...@gmail.com>
AuthorDate: Tue Sep 29 19:03:22 2026 +0000

    [SPARK-59323][SQL] Record temp variables read by an IDENTIFIER clause as referred by the view
    
    ### What changes were proposed in this pull request?
    
    A temporary variable read only inside a view's `IDENTIFIER` clause is now recorded as a variable the view refers to, so it stays resolvable when the stored view text is analyzed again.
    
    `ResolveIdentifierClause` replaces the placeholder with the plan built from the *evaluated* name, which drops the identifier expression. The `VariableReference` is therefore absent from the analyzed plan, and `ViewHelper.collectTemporaryVariables` — the only source for the view's `referredTempVariablesNames` property — cannot see it. On re-analysis `ColumnResolutionHelper.resolveVariables` restricts variable lookup to that property whenever `AnalysisContext.catalogAndNamespace` is non [...]
    
    The names are now recorded while they are still visible, in a new `AnalysisContext.referredTempVariableNamesUnderIdentifier` accumulator, and consumed by every command that builds a view from stored text: `CREATE VIEW` (v1 and the v2 `ViewCatalog` path), `ALTER VIEW`, and `CACHE TABLE AS SELECT`. For a temporary view the names are stored in its metadata so they resolve on re-analysis; for a persisted view they are instead rejected by `verifyTemporaryObjectsNotExists` / `CheckViewRefer [...]
    
    Nested-view analysis resets the accumulator, because a nested view records its own variables in its own metadata. SQL-function expansion, by contrast, shares the enclosing accumulator: a SQL function keeps no IDENTIFIER-variable metadata of its own, so a variable its body reads through an `IDENTIFIER` clause is part of the enclosing view's definition and must be recorded there. A variable used only to compute an object's *name* (e.g. `CREATE TEMPORARY VIEW IDENTIFIER(v) AS ...`) is no [...]
    
    This also fixes `collectTemporaryVariablesInExpressionTree`, which had an extra `children` hop and so skipped the expression's own root node.
    
    ### Why are the changes needed?
    
    The view is unusable after creation:
    
    ```sql
    CREATE OR REPLACE TEMPORARY VIEW t AS SELECT 1 AS c1;
    DECLARE OR REPLACE VARIABLE relation_name STRING DEFAULT 't';
    CREATE OR REPLACE TEMPORARY VIEW v AS SELECT * FROM IDENTIFIER(relation_name);
    SELECT * FROM v;
    ```
    
    fails with
    
    ```
    [NOT_A_CONSTANT_STRING.NOT_CONSTANT] The expression relation_name used for the
    routine or clause IDENTIFIER must be a constant STRING which is NOT NULL.
    ```
    
    `CREATE VIEW` succeeds, so the failure only shows up on every later reference. The same gap affected `ALTER VIEW`, `CACHE TABLE AS SELECT`, and a temporary view that selects a temporary SQL function reading a variable through `IDENTIFIER`; a persisted view could also silently store such a dependency instead of rejecting it.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. The query above now returns the rows of `t` instead of failing. A view's stored properties may now list a referred temporary variable that was previously omitted. A persisted view (or a v2 `ViewCatalog` view) that reads a session variable through an `IDENTIFIER` clause is now rejected with `INVALID_TEMP_OBJ_REFERENCE`, unless `spark.sql.legacy.allowSessionVariableInPersistedView` is enabled.
    
    ### How was this patch tested?
    
    New cases in `identifier-clause.sql` (regenerated through `SQLQueryTestSuite`) covering table- and expression-position `IDENTIFIER`, `GLOBAL TEMPORARY VIEW`, `ALTER VIEW`, nested views, a temporary view over a temporary SQL function, `CACHE TABLE AS SELECT` (including target-name exclusion), and persisted-view rejection (including an `IDENTIFIER` inside a scalar subquery) with the legacy flag. New `DataSourceV2MetadataViewSuite` cases cover the v2 `ViewCatalog` CREATE/ALTER paths, bot [...]
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Co-Authored-By: Claude Opus 4.8
    
    Closes #58606 from stevomitric/SPARK-identifier-clause-temp-vars.
    
    Authored-by: Stevo Mitric <st...@gmail.com>
    Signed-off-by: Uros Bojanic <22...@users.noreply.github.com>
---
 .../spark/sql/catalyst/analysis/Analyzer.scala     |  72 ++
 .../catalyst/analysis/ApplyDefaultCollation.scala  |   2 +-
 .../analysis/ResolveIdentifierClause.scala         |  94 ++-
 .../sql/catalyst/normalizer/NormalizeCTEIds.scala  |   2 +-
 .../sql/catalyst/plans/logical/v2Commands.scala    |  22 +-
 .../catalyst/analysis/ResolveSessionCatalog.scala  |  18 +-
 .../sql/execution/command/metricViewCommands.scala |  16 +-
 .../apache/spark/sql/execution/command/views.scala |  88 +-
 .../execution/datasources/v2/CacheTableExec.scala  |   6 +-
 .../datasources/v2/DataSourceV2Strategy.scala      |   6 +-
 .../identifier-clause-legacy.sql.out               | 890 ++++++++++++++++++++
 .../analyzer-results/identifier-clause.sql.out     | 890 ++++++++++++++++++++
 .../sql-tests/inputs/identifier-clause.sql         | 182 +++++
 .../results/identifier-clause-legacy.sql.out       | 895 +++++++++++++++++++++
 .../sql-tests/results/identifier-clause.sql.out    | 895 +++++++++++++++++++++
 .../connector/DataSourceV2MetadataViewSuite.scala  |  53 ++
 .../spark/sql/execution/MetricViewSuite.scala      |  49 ++
 .../spark/sql/execution/SQLViewTestSuite.scala     |   4 +-
 18 files changed, 4126 insertions(+), 58 deletions(-)

diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala
index 34eb76d26999..002c6199de5d 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala
@@ -166,6 +166,20 @@ case class AnalysisContext(
     //    lookup a temporary function. And export to the view metadata.
     referredTempFunctionNames: mutable.Set[String] = mutable.Set.empty,
     referredTempVariableNames: Seq[Seq[String]] = Seq.empty,
+    // Like `referredTempFunctionNames`, this is populated only by fixed-point analysis (by
+    // `ResolveIdentifierClause`, the sole writer). A temporary view, temporary ALTER VIEW, or
+    // CACHE TABLE AS SELECT stores the names in its metadata so they resolve when the stored view
+    // text is analyzed again. A persisted CREATE/ALTER VIEW instead rejects them -- usually in
+    // `ResolveIdentifierClause` while resolving the body, and otherwise (e.g. an IDENTIFIER nested
+    // in a scalar subquery) by persisted-view validation: `verifyTemporaryObjectsNotExists`, which
+    // the v1 runnable commands call while executing and the v2 commands reach during analysis via
+    // `CheckViewReferences`. When `spark.sql.legacy.allowSessionVariableInPersistedView` is set,
+    // the persisted paths simply discard the set. The single-pass resolver has no IDENTIFIER-clause
+    // resolution of its own, so there is no second writer to keep in sync. LinkedHashSet keeps
+    // insertion order so the recorded names (and any error naming them) are deterministic when more
+    // than one variable is read via an IDENTIFIER clause.
+    referredTempVariableNamesUnderIdentifier: mutable.Set[Seq[String]] =
+      mutable.LinkedHashSet.empty,
     outerPlan: Option[LogicalPlan] = None,
     collation: Option[String] = None,
 
@@ -257,6 +271,10 @@ object AnalysisContext {
       referredTempViewNames = viewDesc.viewReferredTempViewNames,
       referredTempFunctionNames = mutable.Set(viewDesc.viewReferredTempFunctionNames: _*),
       referredTempVariableNames = viewDesc.viewReferredTempVariableNames,
+      // Reset rather than share: a temporary nested view records its own IDENTIFIER-clause
+      // variables in its own metadata, and a persisted one does not carry them at all, so in
+      // neither case may they be attributed to the object whose creation is driving this analysis.
+      referredTempVariableNamesUnderIdentifier = mutable.LinkedHashSet.empty,
       collation = viewDesc.collation)
     context.setSinglePassResolverBridgeState(originContext.getSinglePassResolverBridgeState)
     set(context)
@@ -270,6 +288,13 @@ object AnalysisContext {
       resolutionPathEntries = function.functionStoredResolutionPath
         .map(CatalogManager.deserializePathEntriesOrFail(
           _, "SQL function", function.name.unquotedString)),
+      // Unlike `withAnalysisContext(viewDesc)`, do NOT reset this accumulator. A SQL function has
+      // no IDENTIFIER-variable metadata of its own, so a variable its body reads through IDENTIFIER
+      // clause is part of the enclosing object's definition (e.g. the temporary view or CACHE TABLE
+      // AS SELECT that selects the function), and must be recorded there. Sharing the caller's set
+      // records it; a nested view, by contrast, stores its own and so is reset.
+      referredTempVariableNamesUnderIdentifier =
+        originContext.referredTempVariableNamesUnderIdentifier,
       collation = function.collation)
     set(context)
     try f finally { set(originContext) }
@@ -499,6 +524,53 @@ class Analyzer(
   private def executeSameContext(plan: LogicalPlan): LogicalPlan =
     runWithSessionConf(super.execute(plan))
 
+  /**
+   * Like [[executeAndCheck]], but also returns the temporary variables recorded via IDENTIFIER
+   * clauses during this analysis (`AnalysisContext.referredTempVariableNamesUnderIdentifier`).
+   *
+   * Those variables are absent from the analyzed plan (the placeholder is replaced by the plan
+   * built from the evaluated name), and the accumulator that holds them is discarded when the
+   * analysis scope exits. A caller that separately validates a freshly analyzed body against
+   * persisted-view rules (metric-view creation) therefore cannot recover them afterwards, so this
+   * entry point reads them inside the owning scope and freezes them into the returned result.
+   *
+   * When single-pass resolution is forced on, this defers to [[executeAndCheck]] so the configured
+   * routing is preserved: the only caller analyzes a metric-view placeholder, which is explicitly
+   * unsupported by the single-pass resolver, and forced mode must surface that incompatibility
+   * rather than silently succeeding through fixed-point analysis (no IDENTIFIER variables are
+   * captured on that path -- the call fails before persisted-view validation is reached).
+   * Otherwise it runs the fixed-point analyzer directly, in a context it owns so the accumulator
+   * stays readable.
+   */
+  def executeAndCheckReferredTempVariablesUnderIdentifier(
+      plan: LogicalPlan,
+      tracker: QueryPlanningTracker): (LogicalPlan, Seq[Seq[String]]) = {
+    if (plan.analyzed) {
+      (plan, Seq.empty)
+    } else if (conf.getConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED)) {
+      (executeAndCheck(plan, tracker), Seq.empty)
+    } else {
+      def analyze(): (LogicalPlan, Seq[Seq[String]]) = AnalysisHelper.markInAnalyzer {
+        val analyzed = QueryPlanningTracker.withTracker(tracker) {
+          executeSameContext(plan)
+        }
+        // Read the accumulator before the surrounding context is reset / restored below.
+        val referredTempVariablesUnderIdentifier =
+          AnalysisContext.get.referredTempVariableNamesUnderIdentifier.toSeq
+        checkAnalysis(analyzed)
+        (analyzed, referredTempVariablesUnderIdentifier)
+      }
+      if (AnalysisContext.get.isDefault) {
+        AnalysisContext.reset()
+        try analyze() finally AnalysisContext.reset()
+      } else {
+        AnalysisContext.withNewAnalysisContext {
+          analyze()
+        }
+      }
+    }
+  }
+
   def resolver: Resolver = conf.resolver
 
   /**
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ApplyDefaultCollation.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ApplyDefaultCollation.scala
index 42b27ba9408e..b041bc0c0cdb 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ApplyDefaultCollation.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ApplyDefaultCollation.scala
@@ -197,7 +197,7 @@ object ApplyDefaultCollation extends Rule[LogicalPlan] {
             collation = getCollationFromSchemaMetadata(catalog, identifier.namespace())))
 
         case createView@CreateView(ResolvedIdentifier(
-        catalog: SupportsNamespaces, identifier), _, _, _, _, _, _, _, _, _, _, _)
+        catalog: SupportsNamespaces, identifier), _, _, _, _, _, _, _, _, _, _, _, _)
           if createView.collation.isEmpty =>
           val newCreateView = CurrentOrigin.withOrigin(createView.origin) {
             createView.copy(
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveIdentifierClause.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveIdentifierClause.scala
index 320cfc0a77c3..de3214d59253 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveIdentifierClause.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveIdentifierClause.scala
@@ -21,7 +21,7 @@ import scala.collection.mutable
 
 import org.apache.spark.SparkException
 import org.apache.spark.sql.catalyst.expressions.{Expression, SubqueryExpression, VariableReference}
-import org.apache.spark.sql.catalyst.plans.logical.{CreateView, LogicalPlan, V2WriteCommand}
+import org.apache.spark.sql.catalyst.plans.logical.{AlterViewAs, CacheTableAsSelect, Command, CreateView, LogicalPlan, V2WriteCommand}
 import org.apache.spark.sql.catalyst.rules.{Rule, RuleExecutor}
 import org.apache.spark.sql.catalyst.trees.TreePattern._
 import org.apache.spark.sql.errors.QueryCompilationErrors
@@ -44,23 +44,59 @@ class ResolveIdentifierClause(earlyBatches: Seq[RuleExecutor[LogicalPlan]#Batch]
           apply0(createView)
         } else {
           val referredTempVars = new mutable.ArrayBuffer[Seq[String]]
-          val analyzedChild = apply0(createView.child)
+          // The target-name identifier (child) is not part of the view definition, so a variable
+          // used only to compute it must not be recorded as a variable the view refers to.
+          val analyzedChild = apply0(createView.child, recordUnderIdentifier = false)
           val analyzedQuery = apply0(createView.query, Some(referredTempVars))
           if (referredTempVars.nonEmpty) {
+            // Report the resolved target so this early rejection names the same view as the
+            // run-time `verifyTemporaryObjectsNotExists` path (which handles e.g. an IDENTIFIER
+            // nested in a scalar subquery), rather than a placeholder.
+            val viewName = analyzedChild match {
+              case r: ResolvedIdentifier =>
+                r.catalog.name +: (r.identifier.namespace.toSeq :+ r.identifier.name)
+              case u: UnresolvedIdentifier => u.nameParts
+              case _ => Seq("unknown")
+            }
             throw QueryCompilationErrors.notAllowedToCreatePermanentViewByReferencingTempVarError(
-              Seq("unknown"),
+              viewName,
               referredTempVars.head
             )
           }
           createView.copy(child = analyzedChild, query = analyzedQuery)
         }
+      // Same as [[CreateView]]: only the query body's IDENTIFIER-clause variables are dependencies
+      // of the view definition, so resolve the ALTER target without recording. Recording a variable
+      // used only to compute the target is rejected by `verifyTemporaryObjectsNotExists` for a
+      // persisted view; for a temporary view (which skips that validator) it just stores an
+      // inaccurate dependency. That is harmless to reads -- stored names are an allow-list that is
+      // not proactively resolved -- but still wrong.
+      case alterView: AlterViewAs =>
+        val analyzedChild = apply0(alterView.child, recordUnderIdentifier = false)
+        val analyzedQuery = apply0(alterView.query)
+        alterView.copy(child = analyzedChild, query = analyzedQuery)
+      // CACHE TABLE AS SELECT builds a temporary view from the SELECT body, so like [[CreateView]]
+      // only the body's IDENTIFIER-clause variables are dependencies. The target name is an
+      // expression on the node (not a plan wrapped in `PlanWithUnresolvedIdentifier`), so the
+      // command guard in `apply0` does not exclude it; resolve the name without recording and the
+      // body with it.
+      case cacheTableAsSelect: CacheTableAsSelect =>
+        val analyzedName = cacheTableAsSelect.tempViewName.transformUpWithPruning(
+          _.containsPattern(UNRESOLVED_IDENTIFIER)) {
+          case e: ExpressionWithUnresolvedIdentifier if e.identifierExpr.resolved =>
+            e.exprBuilder.apply(
+              IdentifierResolution.evalIdentifierExpr(e.identifierExpr), e.otherExprs)
+        }
+        val analyzedPlan = apply0(cacheTableAsSelect.plan)
+        cacheTableAsSelect.copy(tempViewName = analyzedName, plan = analyzedPlan)
       case _ => apply0(plan)
     }
   }
 
   private def apply0(
       plan: LogicalPlan,
-      referredTempVars: Option[mutable.ArrayBuffer[Seq[String]]] = None): LogicalPlan =
+      referredTempVars: Option[mutable.ArrayBuffer[Seq[String]]] = None,
+      recordUnderIdentifier: Boolean = true): LogicalPlan =
     plan.resolveOperatorsUpWithPruning(_.containsAnyPattern(
       UNRESOLVED_IDENTIFIER, PLAN_WITH_UNRESOLVED_IDENTIFIER)) {
       case p: PlanWithUnresolvedIdentifier if p.identifierExpr.resolved && p.childrenResolved =>
@@ -69,14 +105,30 @@ class ResolveIdentifierClause(earlyBatches: Seq[RuleExecutor[LogicalPlan]#Batch]
           referredTempVars.get ++= collectTemporaryVariablesInLogicalPlan(p)
         }
 
-        executor.execute(p.planBuilder.apply(
+        val resolvedPlan = executor.execute(p.planBuilder.apply(
           IdentifierResolution.evalIdentifierExpr(p.identifierExpr), p.children))
+        // Record the variables whenever the identifier resolves to something other than a command:
+        // the generic body case (a view/ALTER body, a CACHE TABLE AS SELECT body, or even a
+        // standalone SELECT whose recorded set is simply never consumed). When the identifier
+        // instead supplies the *name* of a command (e.g. the target of
+        // `CREATE TEMPORARY VIEW IDENTIFIER(v) AS ...`), the evaluated plan is that command and `v`
+        // names the object rather than a variable it refers to, so recording it would wrongly
+        // persist it as a dependency. The recorded set is consumed by temporary-view create/ALTER
+        // and CACHE TABLE AS SELECT (stored as metadata) and by persisted CREATE/ALTER VIEW
+        // (rejected -- unless the legacy flag makes the persisted paths discard it instead).
+        if (recordUnderIdentifier && !resolvedPlan.isInstanceOf[Command]) {
+          recordTemporaryVariablesUnderIdentifier(p.identifierExpr)
+        }
+        resolvedPlan
       case w: V2WriteCommand if w.table.isInstanceOf[PlanWithUnresolvedIdentifier] =>
         val p = w.table.asInstanceOf[PlanWithUnresolvedIdentifier]
         if (p.identifierExpr.resolved && p.childrenResolved) {
           if (referredTempVars.isDefined) {
             referredTempVars.get ++= collectTemporaryVariablesInLogicalPlan(p)
           }
+          if (recordUnderIdentifier) {
+            recordTemporaryVariablesUnderIdentifier(p.identifierExpr)
+          }
           executor.execute(p.planBuilder.apply(
             IdentifierResolution.evalIdentifierExpr(p.identifierExpr), p.children)) match {
             case nr: NamedRelation => w.withNewTable(nr)
@@ -95,12 +147,27 @@ class ResolveIdentifierClause(earlyBatches: Seq[RuleExecutor[LogicalPlan]#Batch]
             if (referredTempVars.isDefined) {
               referredTempVars.get ++= collectTemporaryVariablesInExpressionTree(e)
             }
+            if (recordUnderIdentifier) {
+              recordTemporaryVariablesUnderIdentifier(e.identifierExpr)
+            }
 
             e.exprBuilder.apply(
               IdentifierResolution.evalIdentifierExpr(e.identifierExpr), e.otherExprs)
         }
     }
 
+  /**
+   * Records the temporary variables read by an identifier expression in the [[AnalysisContext]].
+   * Evaluating the identifier expression is the last time these references are visible: the
+   * placeholder is replaced by the plan or expression built from the evaluated name, which no
+   * longer mentions them. Temporary view creation persists the recorded names so that the
+   * variables are still resolvable when the stored view text is analyzed again.
+   */
+  private def recordTemporaryVariablesUnderIdentifier(identifierExpr: Expression): Unit = {
+    AnalysisContext.get.referredTempVariableNamesUnderIdentifier ++=
+      collectTemporaryVariablesInExpressionTree(identifierExpr)
+  }
+
   private def collectTemporaryVariablesInLogicalPlan(child: LogicalPlan): Seq[Seq[String]] = {
     def collectTempVars(child: LogicalPlan): Seq[Seq[String]] = {
       child.flatMap { plan =>
@@ -110,16 +177,13 @@ class ResolveIdentifierClause(earlyBatches: Seq[RuleExecutor[LogicalPlan]#Batch]
     collectTempVars(child)
   }
 
+  // Visits `child` itself as well as its descendants, so that an identifier expression which is
+  // just a variable reference is collected too.
   private def collectTemporaryVariablesInExpressionTree(child: Expression): Seq[Seq[String]] = {
-    def collectTempVars(child: Expression): Seq[Seq[String]] = {
-      child.flatMap { expr =>
-        expr.children.flatMap(_.flatMap {
-          case e: SubqueryExpression => collectTemporaryVariablesInLogicalPlan(e.plan)
-          case r: VariableReference => Seq(r.originalNameParts)
-          case _ => Seq.empty
-        })
-      }.distinct
-    }
-    collectTempVars(child)
+    child.flatMap {
+      case e: SubqueryExpression => collectTemporaryVariablesInLogicalPlan(e.plan)
+      case r: VariableReference => Seq(r.originalNameParts)
+      case _ => Seq.empty
+    }.distinct
   }
 }
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/normalizer/NormalizeCTEIds.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/normalizer/NormalizeCTEIds.scala
index 660f11d368ab..0af414377ff2 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/normalizer/NormalizeCTEIds.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/normalizer/NormalizeCTEIds.scala
@@ -37,7 +37,7 @@ object NormalizeCTEIds extends Rule[LogicalPlan] {
       curId: AtomicLong,
       cteIdToNewId: mutable.Map[Long, Long]): LogicalPlan = {
     plan transformDownWithSubqueries {
-      case ctas @ CacheTableAsSelect(_, plan, _, _, _, _, _) =>
+      case ctas @ CacheTableAsSelect(_, plan, _, _, _, _, _, _) =>
         ctas.copy(plan = applyInternal(plan, curId, cteIdToNewId))
 
       case withCTE @ WithCTE(plan, cteDefs) =>
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
index 37283a324475..fed130d0b316 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
@@ -1908,14 +1908,17 @@ case class AlterViewAs(
     originalText: String,
     query: LogicalPlan,
     isAnalyzed: Boolean = false,
-    referredTempFunctions: Seq[String] = Seq.empty)
+    referredTempFunctions: Seq[String] = Seq.empty,
+    referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty)
   extends Command with AnalysisOnlyCommand with CTEInChildren {
 
   override def childrenToAnalyze: Seq[LogicalPlan] = Seq(child, query)
 
   override def markAsAnalyzed(analysisContext: AnalysisContext): LogicalPlan = copy(
     isAnalyzed = true,
-    referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq)
+    referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq,
+    referredTempVariablesUnderIdentifier =
+      analysisContext.referredTempVariableNamesUnderIdentifier.toSeq)
 
   override protected def withNewChildrenInternal(
       newChildren: IndexedSeq[LogicalPlan]): LogicalPlan = {
@@ -1966,14 +1969,17 @@ case class CreateView(
     replace: Boolean,
     viewSchemaMode: ViewSchemaMode,
     isAnalyzed: Boolean = false,
-    referredTempFunctions: Seq[String] = Seq.empty)
+    referredTempFunctions: Seq[String] = Seq.empty,
+    referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty)
   extends Command with AnalysisOnlyCommand with CTEInChildren {
 
   override def childrenToAnalyze: Seq[LogicalPlan] = Seq(child, query)
 
   override def markAsAnalyzed(analysisContext: AnalysisContext): LogicalPlan = copy(
     isAnalyzed = true,
-    referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq)
+    referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq,
+    referredTempVariablesUnderIdentifier =
+      analysisContext.referredTempVariableNamesUnderIdentifier.toSeq)
 
   override protected def withNewChildrenInternal(
       newChildren: IndexedSeq[LogicalPlan]): LogicalPlan = {
@@ -2063,7 +2069,8 @@ case class CacheTableAsSelect(
     isLazy: Boolean,
     options: Map[String, String],
     isAnalyzed: Boolean = false,
-    referredTempFunctions: Seq[String] = Seq.empty)
+    referredTempFunctions: Seq[String] = Seq.empty,
+    referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty)
   extends AnalysisOnlyCommand with CTEInChildren {
 
   /**
@@ -2090,7 +2097,10 @@ case class CacheTableAsSelect(
     copy(
       isAnalyzed = true,
       // Collect the referred temporary functions from AnalysisContext
-      referredTempFunctions = ac.referredTempFunctionNames.toSeq)
+      referredTempFunctions = ac.referredTempFunctionNames.toSeq,
+      // Collect the temporary variables read via an IDENTIFIER clause in the SELECT body, so the
+      // text-backed temporary view stays resolvable when re-analyzed on read.
+      referredTempVariablesUnderIdentifier = ac.referredTempVariableNamesUnderIdentifier.toSeq)
   }
 
   override def withCTEDefs(cteDefs: Seq[CTERelationDef]): LogicalPlan = {
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala b/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
index e6fc6d8d862c..dd1a26fa1a6b 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
@@ -567,21 +567,23 @@ class ResolveSessionCatalog(val catalogManager: CatalogManager)
         location) =>
       AlterTableSetLocationCommand(ident, Some(partitionSpec), location)
 
-    // The final `_, _` are AlterViewAs.isAnalyzed and referredTempFunctions. We drop both:
-    // AlterViewAsCommand is a separate AnalysisOnlyCommand and gets its own markAsAnalyzed pass
-    // from HandleSpecialCommand after this rewrite.
-    case AlterViewAs(ResolvedViewIdentifier(ident), originalText, query, _, _) =>
+    // The final `_, _, _` are AlterViewAs.isAnalyzed, referredTempFunctions and
+    // referredTempVariablesUnderIdentifier. We drop them all: AlterViewAsCommand is a separate
+    // AnalysisOnlyCommand and gets its own markAsAnalyzed pass from HandleSpecialCommand after
+    // this rewrite.
+    case AlterViewAs(ResolvedViewIdentifier(ident), originalText, query, _, _, _) =>
       AlterViewAsCommand(ident, originalText, query)
 
     case AlterViewSchemaBinding(ResolvedViewIdentifier(ident), viewSchemaMode) =>
       AlterViewSchemaBindingCommand(ident, viewSchemaMode)
 
-    // The final `_, _` are CreateView.isAnalyzed and referredTempFunctions. We drop both:
-    // CreateViewCommand is a separate AnalysisOnlyCommand and gets its own markAsAnalyzed pass
-    // from HandleSpecialCommand after this rewrite.
+    // The final `_, _, _` are CreateView.isAnalyzed, referredTempFunctions and
+    // referredTempVariablesUnderIdentifier. We drop them all: CreateViewCommand is a separate
+    // AnalysisOnlyCommand and gets its own markAsAnalyzed pass from HandleSpecialCommand after
+    // this rewrite.
     case CreateView(CreateViewInSessionCatalog(ident), userSpecifiedColumns, comment,
         collation, properties, originalText, query, allowExisting, replace, viewSchemaMode,
-        _, _) =>
+        _, _, _) =>
       CreateViewCommand(
         name = ident,
         userSpecifiedColumns = userSpecifiedColumns,
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/metricViewCommands.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/metricViewCommands.scala
index 3ae6dcad174f..89de49e03502 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/metricViewCommands.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/metricViewCommands.scala
@@ -29,6 +29,7 @@ import org.apache.spark.sql.connector.catalog.CatalogV2Util
 import org.apache.spark.sql.errors.QueryCompilationErrors
 import org.apache.spark.sql.execution.datasources.LogicalRelation
 import org.apache.spark.sql.execution.datasources.v2.DataSourceV2Relation
+import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.metricview.serde.MetricView
 import org.apache.spark.sql.metricview.util.MetricViewPlanner
 import org.apache.spark.sql.types.StructType
@@ -193,9 +194,20 @@ object MetricViewHelper {
     // property emission (e.g. `metric_view.*` keys) without keeping it on the placeholder.
     val (placeholder, metricView) = MetricViewPlanner.planWrite(
       tableMeta, viewText, session.sessionState.sqlParser)
-    val analyzed = analyzer.executeAndCheck(placeholder, new QueryPlanningTracker)
+    val (analyzed, referredTempVariablesUnderIdentifier) =
+      analyzer.executeAndCheckReferredTempVariablesUnderIdentifier(
+        placeholder, new QueryPlanningTracker)
+    // A variable read via an IDENTIFIER clause in the metric-view source is absent from the
+    // analyzed plan, so it is captured during analysis and passed to the validator explicitly,
+    // honoring `spark.sql.legacy.allowSessionVariableInPersistedView` like other persisted views.
+    val referredVarsUnderIdentifier =
+      if (session.sessionState.conf.getConf(SQLConf.VARIABLES_UNDER_IDENTIFIER_IN_VIEW)) {
+        Nil
+      } else {
+        referredTempVariablesUnderIdentifier
+      }
     ViewHelper.verifyTemporaryObjectsNotExists(
-      isTemporary = false, nameParts, analyzed, Seq.empty)
+      isTemporary = false, nameParts, analyzed, Seq.empty, referredVarsUnderIdentifier)
     (analyzed, metricView)
   }
 }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala
index 4338bd367fe0..a8285f6e2dd9 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala
@@ -37,7 +37,7 @@ import org.apache.spark.sql.connector.catalog.{CatalogManager, CatalogPlugin, Id
 import org.apache.spark.sql.connector.catalog.CatalogV2Implicits.{IdentifierHelper, NamespaceHelper}
 import org.apache.spark.sql.errors.QueryCompilationErrors
 import org.apache.spark.sql.execution.datasources.v2.DataSourceV2Relation
-import org.apache.spark.sql.internal.StaticSQLConf
+import org.apache.spark.sql.internal.{SQLConf, StaticSQLConf}
 import org.apache.spark.sql.types.{MetadataBuilder, StructType}
 import org.apache.spark.sql.util.SchemaUtils
 import org.apache.spark.util.ArrayImplicits._
@@ -84,7 +84,8 @@ case class CreateViewCommand(
     viewType: ViewType,
     viewSchemaMode: ViewSchemaMode = SchemaUnsupported,
     isAnalyzed: Boolean = false,
-    referredTempFunctions: Seq[String] = Seq.empty)
+    referredTempFunctions: Seq[String] = Seq.empty,
+    referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty)
   extends RunnableCommand
   with AnalysisOnlyCommand
   with CTEInChildren
@@ -105,7 +106,10 @@ case class CreateViewCommand(
     copy(
       isAnalyzed = true,
       // Collect the referred temporary functions from AnalysisContext
-      referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq)
+      referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq,
+      // The analyzed plan no longer mentions the variables read by IDENTIFIER clauses.
+      referredTempVariablesUnderIdentifier =
+        analysisContext.referredTempVariableNamesUnderIdentifier.toSeq)
   }
 
   private def isTemporary = viewType == LocalTempView || viewType == GlobalTempView
@@ -134,8 +138,20 @@ case class CreateViewCommand(
 
     // When creating a permanent view, not allowed to reference temporary objects.
     // This should be called after `qe.assertAnalyzed()` (i.e., `child` can be resolved)
+    // A variable read via an IDENTIFIER clause is absent from the analyzed plan (the placeholder is
+    // replaced by the evaluated name), so it cannot be rediscovered by scanning `analyzedPlan` --
+    // notably when the IDENTIFIER sits inside a scalar subquery. Pass the recorded names explicitly
+    // so a persisted view still rejects the dependency, honoring
+    // `spark.sql.legacy.allowSessionVariableInPersistedView` like ALTER VIEW.
+    val referredVarsUnderIdentifier =
+      if (sparkSession.sessionState.conf.getConf(SQLConf.VARIABLES_UNDER_IDENTIFIER_IN_VIEW)) {
+        Nil
+      } else {
+        referredTempVariablesUnderIdentifier
+      }
     verifyTemporaryObjectsNotExists(
-      isTemporary, name.nameParts, analyzedPlan, referredTempFunctions)
+      isTemporary, name.nameParts, analyzedPlan, referredTempFunctions,
+      referredVarsUnderIdentifier)
     verifyAutoGeneratedAliasesNotExists(analyzedPlan, isTemporary, name.nameParts)
 
     SchemaUtils.checkIndeterminateCollationInSchema(plan.schema)
@@ -151,7 +167,8 @@ case class CreateViewCommand(
         analyzedPlan,
         aliasedPlan,
         referredTempFunctions,
-        collation)
+        collation,
+        referredTempVariablesUnderIdentifier = referredTempVariablesUnderIdentifier)
       catalog.createTempView(name.table, tableDefinition, overrideIfExists = replace)
     } else if (viewType == GlobalTempView) {
       val db = sparkSession.sessionState.conf.getConf(StaticSQLConf.GLOBAL_TEMP_DATABASE)
@@ -165,7 +182,8 @@ case class CreateViewCommand(
         originalText,
         analyzedPlan,
         aliasedPlan,
-        referredTempFunctions)
+        referredTempFunctions,
+        referredTempVariablesUnderIdentifier = referredTempVariablesUnderIdentifier)
       catalog.createGlobalTempView(name.table, tableDefinition, overrideIfExists = replace)
     } else if (catalog.tableExists(name)) {
       val tableMetadata = catalog.getTableMetadata(name)
@@ -235,7 +253,8 @@ case class AlterViewAsCommand(
     originalText: String,
     query: LogicalPlan,
     isAnalyzed: Boolean = false,
-    referredTempFunctions: Seq[String] = Seq.empty)
+    referredTempFunctions: Seq[String] = Seq.empty,
+    referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty)
   extends RunnableCommand with AnalysisOnlyCommand with CTEInChildren {
 
   import ViewHelper._
@@ -252,12 +271,26 @@ case class AlterViewAsCommand(
     copy(
       isAnalyzed = true,
       // Collect the referred temporary functions from AnalysisContext
-      referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq)
+      referredTempFunctions = analysisContext.referredTempFunctionNames.toSeq,
+      // The analyzed plan no longer mentions the variables read by IDENTIFIER clauses.
+      referredTempVariablesUnderIdentifier =
+        analysisContext.referredTempVariableNamesUnderIdentifier.toSeq)
   }
 
   override def run(session: SparkSession): Seq[Row] = {
     val isTemporary = session.sessionState.catalog.isTempView(name)
-    verifyTemporaryObjectsNotExists(isTemporary, name.nameParts, query, referredTempFunctions)
+    // As in CREATE VIEW, a persisted view may not depend on a session variable read via an
+    // IDENTIFIER clause. `spark.sql.legacy.allowSessionVariableInPersistedView` relaxes this, and
+    // it must be honored here too so ALTER VIEW is consistent with CREATE VIEW.
+    val referredVarsUnderIdentifier =
+      if (session.sessionState.conf.getConf(SQLConf.VARIABLES_UNDER_IDENTIFIER_IN_VIEW)) {
+        Nil
+      } else {
+        referredTempVariablesUnderIdentifier
+      }
+    verifyTemporaryObjectsNotExists(
+      isTemporary, name.nameParts, query, referredTempFunctions,
+      referredVarsUnderIdentifier)
     verifyAutoGeneratedAliasesNotExists(query, isTemporary, name.nameParts)
     SchemaUtils.checkIndeterminateCollationInSchema(query.schema)
     if (isTemporary) {
@@ -283,7 +316,8 @@ case class AlterViewAsCommand(
       Some(originalText),
       analyzedPlan,
       aliasedPlan = analyzedPlan,
-      referredTempFunctions)
+      referredTempFunctions,
+      referredTempVariablesUnderIdentifier = referredTempVariablesUnderIdentifier)
     session.sessionState.catalog.alterTempViewDefinition(name, tableDefinition)
   }
 
@@ -629,7 +663,8 @@ object ViewHelper extends SQLConfHelper with Logging with CapturesConfig {
       isTemporary: Boolean,
       viewNameParts: Seq[String],
       child: LogicalPlan,
-      referredTempFunctions: Seq[String]): Unit = {
+      referredTempFunctions: Seq[String],
+      referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty): Unit = {
     import org.apache.spark.sql.connector.catalog.CatalogV2Implicits._
     if (!isTemporary) {
       val tempViews = collectTemporaryViews(child)
@@ -641,7 +676,11 @@ object ViewHelper extends SQLConfHelper with Logging with CapturesConfig {
         throw QueryCompilationErrors.notAllowedToCreatePermanentViewByReferencingTempFuncError(
           viewNameParts, funcName)
       }
-      val tempVars = collectTemporaryVariables(child)
+      // Variables read only by an IDENTIFIER clause are not present in `child` (the placeholder is
+      // replaced by the plan built from the evaluated name), so they are collected during the
+      // analysis and passed in separately.
+      val tempVars =
+        (collectTemporaryVariables(child) ++ referredTempVariablesUnderIdentifier).distinct
       tempVars.foreach { nameParts =>
         throw QueryCompilationErrors.notAllowedToCreatePermanentViewByReferencingTempVarError(
           viewNameParts, nameParts)
@@ -709,7 +748,9 @@ object ViewHelper extends SQLConfHelper with Logging with CapturesConfig {
       analyzedPlan: LogicalPlan,
       aliasedPlan: LogicalPlan,
       referredTempFunctions: Seq[String],
-      collation: Option[String] = None): TemporaryViewRelation = {
+      collation: Option[String] = None,
+      referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty)
+    : TemporaryViewRelation = {
     val rawTempView = getRawTempView(name.table)
     val uncache = rawTempView.map { r =>
       needsToUncache(r, aliasedPlan)
@@ -744,7 +785,8 @@ object ViewHelper extends SQLConfHelper with Logging with CapturesConfig {
           aliasedPlan.schema,
           originalText.get,
           referredTempFunctions,
-          defaultCollation))
+          defaultCollation,
+          referredTempVariablesUnderIdentifier))
     } else {
       TemporaryViewRelation(
         prepareTemporaryViewStoringAnalyzedPlan(name, aliasedPlan, defaultCollation),
@@ -788,10 +830,14 @@ object ViewHelper extends SQLConfHelper with Logging with CapturesConfig {
       viewSchema: StructType,
       originalText: String,
       tempFunctions: Seq[String],
-      collation: Option[String]): CatalogTable = {
+      collation: Option[String],
+      referredTempVariablesUnderIdentifier: Seq[Seq[String]] = Seq.empty): CatalogTable = {
 
     val tempViews = collectTemporaryViews(analyzedPlan)
-    val tempVariables = collectTemporaryVariables(analyzedPlan)
+    // `analyzedPlan` no longer mentions the variables an IDENTIFIER clause read, so those are
+    // collected during the analysis instead (see `AnalysisContext`).
+    val tempVariables =
+      (collectTemporaryVariables(analyzedPlan) ++ referredTempVariablesUnderIdentifier).distinct
     // TBLPROPERTIES is not allowed for temporary view, so we don't use it for
     // generating temporary view properties
     val newProperties = generateViewProperties(
@@ -955,8 +1001,12 @@ object CheckViewReferences extends (LogicalPlan => Unit) {
     case cv: CreateView if cv.isAnalyzed =>
       requireViewCatalog(cv.child)
       val fullIdent = fullIdentFor(cv.child)
+      // A persisted view may not depend on a session variable read via an IDENTIFIER clause;
+      // `spark.sql.legacy.allowSessionVariableInPersistedView` relaxes this, matching the v1 path.
       verifyTemporaryObjectsNotExists(
-        isTemporary = false, fullIdent, cv.query, cv.referredTempFunctions)
+        isTemporary = false, fullIdent, cv.query, cv.referredTempFunctions,
+        if (SQLConf.get.getConf(SQLConf.VARIABLES_UNDER_IDENTIFIER_IN_VIEW)) Nil
+        else cv.referredTempVariablesUnderIdentifier)
       verifyAutoGeneratedAliasesNotExists(cv.query, isTemporary = false, fullIdent)
       // Cycles can only form when REPLACE'ing an existing view; a plain CREATE against an
       // existing view fails earlier with `viewAlreadyExistsError` and against a non-existent
@@ -971,7 +1021,9 @@ object CheckViewReferences extends (LogicalPlan => Unit) {
       // an AlterViewAs reaches this rule the catalog is guaranteed to be a ViewCatalog.
       val fullIdent = fullIdentFor(av.child)
       verifyTemporaryObjectsNotExists(
-        isTemporary = false, fullIdent, av.query, av.referredTempFunctions)
+        isTemporary = false, fullIdent, av.query, av.referredTempFunctions,
+        if (SQLConf.get.getConf(SQLConf.VARIABLES_UNDER_IDENTIFIER_IN_VIEW)) Nil
+        else av.referredTempVariablesUnderIdentifier)
       verifyAutoGeneratedAliasesNotExists(av.query, isTemporary = false, fullIdent)
       checkCyclicViewReference(av.query, Seq(fullIdent), fullIdent)
 
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala
index 730f26ea7f7a..130ba6d1e0b8 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/CacheTableExec.scala
@@ -91,7 +91,8 @@ case class CacheTableAsSelectExec(
     originalText: String,
     override val isLazy: Boolean,
     override val options: Map[String, String],
-    referredTempFunctions: Seq[String]) extends BaseCacheTableExec {
+    referredTempFunctions: Seq[String],
+    referredTempVariablesUnderIdentifier: Seq[Seq[String]]) extends BaseCacheTableExec {
   override lazy val relationName: String = tempViewName
 
   override def planToCache: LogicalPlan = UnresolvedRelation(Seq(tempViewName))
@@ -110,7 +111,8 @@ case class CacheTableAsSelectExec(
       replace = false,
       viewType = LocalTempView,
       isAnalyzed = true,
-      referredTempFunctions = referredTempFunctions
+      referredTempFunctions = referredTempFunctions,
+      referredTempVariablesUnderIdentifier = referredTempVariablesUnderIdentifier
     ).run(session)
     try {
       super.run()
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
index 0d6416255ba3..32232532bbd6 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
@@ -333,7 +333,7 @@ class DataSourceV2Strategy(session: SparkSession) extends Strategy with Predicat
     // cases fire (it throws MISSING_CATALOG_ABILITY.VIEWS otherwise).
     case CreateView(ResolvedIdentifier(catalog, ident), userSpecifiedColumns, comment,
         collation, properties, originalText, child, allowExisting, replace, viewSchemaMode,
-        _, _) =>
+        _, _, _) =>
       val sqlText = originalText.getOrElse {
         throw QueryCompilationErrors.createPersistedViewFromDatasetAPINotAllowedError()
       }
@@ -376,7 +376,7 @@ class DataSourceV2Strategy(session: SparkSession) extends Strategy with Predicat
         originalText, analyzed, allowExisting, replace, deps) :: Nil
 
     case AlterViewAs(rpv @ ResolvedPersistentView(catalog, ident, _),
-        originalText, query, _, _) =>
+        originalText, query, _, _, _) =>
       AlterV2ViewExec(catalog.asInstanceOf[ViewCatalog], ident, rpv.info,
         originalText, query) :: Nil
 
@@ -798,7 +798,7 @@ class DataSourceV2Strategy(session: SparkSession) extends Strategy with Predicat
     case r: CacheTableAsSelect =>
       CacheTableAsSelectExec(
         r.tempViewNameString, r.plan, r.originalText, r.isLazy, r.options,
-        r.referredTempFunctions) :: Nil
+        r.referredTempFunctions, r.referredTempVariablesUnderIdentifier) :: Nil
 
     case r: UncacheTable =>
       def isTempView(table: LogicalPlan): Boolean = table match {
diff --git a/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause-legacy.sql.out b/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause-legacy.sql.out
index 24f34afc6f09..404f8ed04018 100644
--- a/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause-legacy.sql.out
+++ b/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause-legacy.sql.out
@@ -3177,3 +3177,893 @@ DROP SCHEMA identifier_clause_test_schema
 -- !query analysis
 DropNamespace false, false
 +- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_clause_test_schema]
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_var_target`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_var_name STRING DEFAULT 'identifier_var_target'
+-- !query analysis
+CreateVariable default(cast(identifier_var_target as string), sql=''identifier_var_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_var_name
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_view AS
+SELECT * FROM IDENTIFIER(identifier_var_name)
+-- !query analysis
+CreateViewCommand `identifier_var_view`, SELECT * FROM IDENTIFIER(identifier_var_name), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_var_name]]
+   +- Project [c1#x]
+      +- SubqueryAlias identifier_var_target
+         +- View (`identifier_var_target`, [c1#x])
+            +- Project [cast(c1#x as int) AS c1#x]
+               +- Project [1 AS c1#x]
+                  +- OneRowRelation
+
+
+-- !query
+SELECT count(*) AS row_count FROM identifier_var_view
+-- !query analysis
+Aggregate [count(1) AS row_count#xL]
++- SubqueryAlias identifier_var_view
+   +- View (`identifier_var_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [c1#x]
+            +- SubqueryAlias identifier_var_target
+               +- View (`identifier_var_target`, [c1#x])
+                  +- Project [cast(c1#x as int) AS c1#x]
+                     +- Project [1 AS c1#x]
+                        +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_var_view
+-- !query analysis
+DropTempViewCommand identifier_var_view, false
+
+
+-- !query
+DROP VIEW identifier_var_target
+-- !query analysis
+DropTempViewCommand identifier_var_target, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_var_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_var_name
+
+
+-- !query
+CREATE TEMPORARY VIEW identifier_perm_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_perm_target`, SELECT 1 AS c1, false, false, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_name STRING DEFAULT 'identifier_perm_target'
+-- !query analysis
+CreateVariable default(cast(identifier_perm_target as string), sql=''identifier_perm_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_name
+
+
+-- !query
+CREATE VIEW identifier_perm_view AS SELECT * FROM IDENTIFIER(identifier_perm_name)
+-- !query analysis
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_clause_test_schema`.`identifier_perm_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_perm_name`"
+  }
+}
+
+
+-- !query
+DROP VIEW identifier_perm_target
+-- !query analysis
+DropTempViewCommand identifier_perm_target, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_name
+
+
+-- !query
+CREATE SCHEMA identifier_alter_schema
+-- !query analysis
+CreateNamespace false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_alter_schema]
+
+
+-- !query
+USE identifier_alter_schema
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_alter_schema]
+
+
+-- !query
+CREATE TABLE identifier_alter_target (c1 INT) USING parquet
+-- !query analysis
+CreateDataSourceTableCommand `spark_catalog`.`identifier_alter_schema`.`identifier_alter_target`, false
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_alter_name STRING DEFAULT 'identifier_alter_target'
+-- !query analysis
+CreateVariable default(cast(identifier_alter_target as string), sql=''identifier_alter_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_alter_name
+
+
+-- !query
+CREATE VIEW identifier_alter_view AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+ALTER VIEW identifier_alter_view AS SELECT * FROM IDENTIFIER(identifier_alter_name)
+-- !query analysis
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_alter_name`"
+  }
+}
+
+
+-- !query
+SELECT * FROM identifier_alter_view
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias spark_catalog.identifier_alter_schema.identifier_alter_view
+   +- View (`spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_alter_view
+-- !query analysis
+DropTableCommand `spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`, false, true, false
+
+
+-- !query
+DROP TABLE identifier_alter_target
+-- !query analysis
+DropTable false, false
++- ResolvedIdentifier V2SessionCatalog(spark_catalog), identifier_alter_schema.identifier_alter_target
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_alter_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_alter_name
+
+
+-- !query
+USE default
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [default]
+
+
+-- !query
+DROP SCHEMA identifier_alter_schema
+-- !query analysis
+DropNamespace false, false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_alter_schema]
+
+
+-- !query
+CREATE SCHEMA ivt_schema
+-- !query analysis
+CreateNamespace false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [ivt_schema]
+
+
+-- !query
+USE ivt_schema
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [ivt_schema]
+
+
+-- !query
+CREATE VIEW ivt_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`ivt_schema`.`ivt_target`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE ivt_name STRING DEFAULT 'ivt_target'
+-- !query analysis
+CreateVariable default(cast(ivt_target as string), sql=''ivt_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.ivt_name
+
+
+-- !query
+ALTER VIEW IDENTIFIER(ivt_name) AS SELECT 2 AS c1
+-- !query analysis
+AlterViewAsCommand `spark_catalog`.`ivt_schema`.`ivt_target`, SELECT 2 AS c1, true
+   +- Project [2 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM ivt_target
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias spark_catalog.ivt_schema.ivt_target
+   +- View (`spark_catalog`.`ivt_schema`.`ivt_target`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [2 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW ivt_target
+-- !query analysis
+DropTableCommand `spark_catalog`.`ivt_schema`.`ivt_target`, false, true, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE ivt_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.ivt_name
+
+
+-- !query
+USE default
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [default]
+
+
+-- !query
+DROP SCHEMA ivt_schema
+-- !query analysis
+DropNamespace false, false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [ivt_schema]
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_expr_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_expr_col
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_expr_view AS
+SELECT IDENTIFIER(identifier_expr_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_expr_view`, SELECT IDENTIFIER(identifier_expr_col) AS x FROM VALUES(1) AS t(c1), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_expr_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_expr_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_expr_view
+   +- View (`identifier_expr_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_expr_view
+-- !query analysis
+DropTempViewCommand identifier_expr_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_expr_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_expr_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_gtv_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_gtv_col
+
+
+-- !query
+CREATE OR REPLACE GLOBAL TEMPORARY VIEW identifier_gtv_view AS
+SELECT IDENTIFIER(identifier_gtv_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_gtv_view`, SELECT IDENTIFIER(identifier_gtv_col) AS x FROM VALUES(1) AS t(c1), false, true, GlobalTempView, UNSUPPORTED, true, [[identifier_gtv_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM global_temp.identifier_gtv_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias global_temp.identifier_gtv_view
+   +- View (`global_temp`.`identifier_gtv_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW global_temp.identifier_gtv_view
+-- !query analysis
+DropTempViewCommand global_temp.identifier_gtv_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_gtv_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_gtv_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_view_name STRING DEFAULT 'identifier_named_view'
+-- !query analysis
+CreateVariable default(cast(identifier_named_view as string), sql=''identifier_named_view''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_view_name
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_view_name) AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_named_view`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_named_view
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias identifier_named_view
+   +- View (`identifier_named_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_named_view
+-- !query analysis
+DropTempViewCommand identifier_named_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_view_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_view_name
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_name_part STRING DEFAULT 'identifier_named_view2'
+-- !query analysis
+CreateVariable default(cast(identifier_named_view2 as string), sql=''identifier_named_view2''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_name_part
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_body_part STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_body_part
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_name_part) AS
+SELECT IDENTIFIER(identifier_body_part) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_named_view2`, SELECT IDENTIFIER(identifier_body_part) AS x FROM VALUES(1) AS t(c1), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_body_part]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_named_view2
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_named_view2
+   +- View (`identifier_named_view2`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_named_view2
+-- !query analysis
+DropTempViewCommand identifier_named_view2, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_name_part
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_name_part
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_body_part
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_body_part
+
+
+-- !query
+CREATE SCHEMA identifier_named_perm_schema
+-- !query analysis
+CreateNamespace false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_named_perm_schema]
+
+
+-- !query
+USE identifier_named_perm_schema
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_named_perm_schema]
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_view_name STRING DEFAULT 'identifier_named_perm_view'
+-- !query analysis
+CreateVariable default(cast(identifier_named_perm_view as string), sql=''identifier_named_perm_view''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_view_name
+
+
+-- !query
+CREATE VIEW IDENTIFIER(identifier_perm_view_name) AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`identifier_named_perm_schema`.`identifier_named_perm_view`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_named_perm_view
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias spark_catalog.identifier_named_perm_schema.identifier_named_perm_view
+   +- View (`spark_catalog`.`identifier_named_perm_schema`.`identifier_named_perm_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_named_perm_view
+-- !query analysis
+DropTableCommand `spark_catalog`.`identifier_named_perm_schema`.`identifier_named_perm_view`, false, true, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_view_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_view_name
+
+
+-- !query
+USE default
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [default]
+
+
+-- !query
+DROP SCHEMA identifier_named_perm_schema
+-- !query analysis
+DropNamespace false, false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_named_perm_schema]
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=true
+-- !query analysis
+SetCommand (spark.sql.legacy.allowSessionVariableInPersistedView,Some(true))
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_p2a_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_p2a_col
+
+
+-- !query
+CREATE VIEW identifier_p2a_view AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`default`.`identifier_p2a_view`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+ALTER VIEW identifier_p2a_view AS SELECT IDENTIFIER(identifier_p2a_col) FROM VALUES(1) AS t(c1)
+-- !query analysis
+AlterViewAsCommand `spark_catalog`.`default`.`identifier_p2a_view`, SELECT IDENTIFIER(identifier_p2a_col) FROM VALUES(1) AS t(c1), true, [[identifier_p2a_col]]
+   +- Project [c1#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_p2a_view
+-- !query analysis
+DropTableCommand `spark_catalog`.`default`.`identifier_p2a_view`, false, true, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_p2a_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_p2a_col
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=false
+-- !query analysis
+SetCommand (spark.sql.legacy.allowSessionVariableInPersistedView,Some(false))
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_nested_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_nested_col
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_inner AS
+SELECT IDENTIFIER(identifier_nested_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_nested_inner`, SELECT IDENTIFIER(identifier_nested_col) AS x FROM VALUES(1) AS t(c1), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_nested_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_outer AS
+SELECT x FROM identifier_nested_inner
+-- !query analysis
+CreateViewCommand `identifier_nested_outer`, SELECT x FROM identifier_nested_inner, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [x#x]
+      +- SubqueryAlias identifier_nested_inner
+         +- View (`identifier_nested_inner`, [x#x])
+            +- Project [cast(x#x as int) AS x#x]
+               +- Project [c1#x AS x#x]
+                  +- SubqueryAlias t
+                     +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_nested_outer
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_nested_outer
+   +- View (`identifier_nested_outer`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [x#x]
+            +- SubqueryAlias identifier_nested_inner
+               +- View (`identifier_nested_inner`, [x#x])
+                  +- Project [cast(x#x as int) AS x#x]
+                     +- Project [c1#x AS x#x]
+                        +- SubqueryAlias t
+                           +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_nested_outer
+-- !query analysis
+DropTempViewCommand identifier_nested_outer, false
+
+
+-- !query
+DROP VIEW identifier_nested_inner
+-- !query analysis
+DropTempViewCommand identifier_nested_inner, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_nested_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_nested_col
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_fn_target`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_fn_var STRING DEFAULT 'identifier_fn_target'
+-- !query analysis
+CreateVariable default(cast(identifier_fn_target as string), sql=''identifier_fn_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_fn_var
+
+
+-- !query
+CREATE TEMPORARY FUNCTION identifier_fn() RETURN SELECT count(*) FROM IDENTIFIER(identifier_fn_var)
+-- !query analysis
+CreateSQLFunctionCommand identifier_fn, , SELECT count(*) FROM IDENTIFIER(identifier_fn_var), false, true, false, false
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_view AS SELECT identifier_fn() AS c
+-- !query analysis
+CreateViewCommand `identifier_fn_view`, SELECT identifier_fn() AS c, false, true, LocalTempView, UNSUPPORTED, true, [identifier_fn], [[identifier_fn_var]]
+   +- Project [identifier_fn() AS c#xL]
+      :  +- Aggregate [count(1) AS count(1)#xL]
+      :     +- SubqueryAlias identifier_fn_target
+      :        +- View (`identifier_fn_target`, [c1#x])
+      :           +- Project [cast(c1#x as int) AS c1#x]
+      :              +- Project [1 AS c1#x]
+      :                 +- OneRowRelation
+      +- Project
+         +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_fn_view
+-- !query analysis
+Project [c#xL]
++- SubqueryAlias identifier_fn_view
+   +- View (`identifier_fn_view`, [c#xL])
+      +- Project [cast(c#xL as bigint) AS c#xL]
+         +- Project [identifier_fn() AS c#xL]
+            :  +- Aggregate [count(1) AS count(1)#xL]
+            :     +- SubqueryAlias identifier_fn_target
+            :        +- View (`identifier_fn_target`, [c1#x])
+            :           +- Project [cast(c1#x as int) AS c1#x]
+            :              +- Project [1 AS c1#x]
+            :                 +- OneRowRelation
+            +- Project
+               +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_fn_view
+-- !query analysis
+DropTempViewCommand identifier_fn_view, false
+
+
+-- !query
+DROP TEMPORARY FUNCTION identifier_fn
+-- !query analysis
+DropFunctionCommand identifier_fn, false, true
+
+
+-- !query
+DROP VIEW identifier_fn_target
+-- !query analysis
+DropTempViewCommand identifier_fn_target, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_fn_var
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_fn_var
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_talter_view AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_talter_view`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_talter_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_talter_col
+
+
+-- !query
+ALTER VIEW identifier_talter_view AS
+SELECT IDENTIFIER(identifier_talter_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+AlterViewAsCommand `identifier_talter_view`, SELECT IDENTIFIER(identifier_talter_col) AS x FROM VALUES(1) AS t(c1), true, [[identifier_talter_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_talter_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_talter_view
+   +- View (`identifier_talter_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_talter_view
+-- !query analysis
+DropTempViewCommand identifier_talter_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_talter_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_talter_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_col
+
+
+-- !query
+CACHE TABLE identifier_cache_view AS
+SELECT IDENTIFIER(identifier_cache_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CacheTableAsSelect identifier_cache_view, SELECT IDENTIFIER(identifier_cache_col) AS x FROM VALUES(1) AS t(c1), false, true, [[identifier_cache_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_cache_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_cache_view
+   +- View (`identifier_cache_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+UNCACHE TABLE identifier_cache_view
+-- !query analysis
+UncacheTable false, true
+   +- SubqueryAlias identifier_cache_view
+      +- View (`identifier_cache_view`, [x#x])
+         +- Project [cast(x#x as int) AS x#x]
+            +- Project [c1#x AS x#x]
+               +- SubqueryAlias t
+                  +- LocalRelation [c1#x]
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_name STRING DEFAULT 'identifier_cache_named'
+-- !query analysis
+CreateVariable default(cast(identifier_cache_named as string), sql=''identifier_cache_named''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_name
+
+
+-- !query
+CACHE TABLE IDENTIFIER(identifier_cache_name) AS SELECT 1 AS c1
+-- !query analysis
+CacheTableAsSelect identifier_cache_named, SELECT 1 AS c1, false, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_cache_named
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias identifier_cache_named
+   +- View (`identifier_cache_named`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+UNCACHE TABLE identifier_cache_named
+-- !query analysis
+UncacheTable false, true
+   +- SubqueryAlias identifier_cache_named
+      +- View (`identifier_cache_named`, [c1#x])
+         +- Project [cast(c1#x as int) AS c1#x]
+            +- Project [1 AS c1#x]
+               +- OneRowRelation
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_name
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_subq_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_subq_col
+
+
+-- !query
+CREATE VIEW identifier_subq_view AS
+SELECT (SELECT IDENTIFIER(identifier_subq_col) FROM VALUES(1) AS t(c1)) AS x
+-- !query analysis
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`default`.`identifier_subq_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_subq_col`"
+  }
+}
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_subq_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_subq_col
diff --git a/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause.sql.out b/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause.sql.out
index 2b1db879cb58..46a2df4745db 100644
--- a/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause.sql.out
+++ b/sql/core/src/test/resources/sql-tests/analyzer-results/identifier-clause.sql.out
@@ -2892,3 +2892,893 @@ DROP SCHEMA identifier_clause_test_schema
 -- !query analysis
 DropNamespace false, false
 +- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_clause_test_schema]
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_var_target`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_var_name STRING DEFAULT 'identifier_var_target'
+-- !query analysis
+CreateVariable default(cast(identifier_var_target as string), sql=''identifier_var_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_var_name
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_view AS
+SELECT * FROM IDENTIFIER(identifier_var_name)
+-- !query analysis
+CreateViewCommand `identifier_var_view`, SELECT * FROM IDENTIFIER(identifier_var_name), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_var_name]]
+   +- Project [c1#x]
+      +- SubqueryAlias identifier_var_target
+         +- View (`identifier_var_target`, [c1#x])
+            +- Project [cast(c1#x as int) AS c1#x]
+               +- Project [1 AS c1#x]
+                  +- OneRowRelation
+
+
+-- !query
+SELECT count(*) AS row_count FROM identifier_var_view
+-- !query analysis
+Aggregate [count(1) AS row_count#xL]
++- SubqueryAlias identifier_var_view
+   +- View (`identifier_var_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [c1#x]
+            +- SubqueryAlias identifier_var_target
+               +- View (`identifier_var_target`, [c1#x])
+                  +- Project [cast(c1#x as int) AS c1#x]
+                     +- Project [1 AS c1#x]
+                        +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_var_view
+-- !query analysis
+DropTempViewCommand identifier_var_view, false
+
+
+-- !query
+DROP VIEW identifier_var_target
+-- !query analysis
+DropTempViewCommand identifier_var_target, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_var_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_var_name
+
+
+-- !query
+CREATE TEMPORARY VIEW identifier_perm_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_perm_target`, SELECT 1 AS c1, false, false, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_name STRING DEFAULT 'identifier_perm_target'
+-- !query analysis
+CreateVariable default(cast(identifier_perm_target as string), sql=''identifier_perm_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_name
+
+
+-- !query
+CREATE VIEW identifier_perm_view AS SELECT * FROM IDENTIFIER(identifier_perm_name)
+-- !query analysis
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_clause_test_schema`.`identifier_perm_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_perm_name`"
+  }
+}
+
+
+-- !query
+DROP VIEW identifier_perm_target
+-- !query analysis
+DropTempViewCommand identifier_perm_target, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_name
+
+
+-- !query
+CREATE SCHEMA identifier_alter_schema
+-- !query analysis
+CreateNamespace false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_alter_schema]
+
+
+-- !query
+USE identifier_alter_schema
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_alter_schema]
+
+
+-- !query
+CREATE TABLE identifier_alter_target (c1 INT) USING parquet
+-- !query analysis
+CreateDataSourceTableCommand `spark_catalog`.`identifier_alter_schema`.`identifier_alter_target`, false
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_alter_name STRING DEFAULT 'identifier_alter_target'
+-- !query analysis
+CreateVariable default(cast(identifier_alter_target as string), sql=''identifier_alter_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_alter_name
+
+
+-- !query
+CREATE VIEW identifier_alter_view AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+ALTER VIEW identifier_alter_view AS SELECT * FROM IDENTIFIER(identifier_alter_name)
+-- !query analysis
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_alter_name`"
+  }
+}
+
+
+-- !query
+SELECT * FROM identifier_alter_view
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias spark_catalog.identifier_alter_schema.identifier_alter_view
+   +- View (`spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_alter_view
+-- !query analysis
+DropTableCommand `spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`, false, true, false
+
+
+-- !query
+DROP TABLE identifier_alter_target
+-- !query analysis
+DropTable false, false
++- ResolvedIdentifier V2SessionCatalog(spark_catalog), identifier_alter_schema.identifier_alter_target
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_alter_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_alter_name
+
+
+-- !query
+USE default
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [default]
+
+
+-- !query
+DROP SCHEMA identifier_alter_schema
+-- !query analysis
+DropNamespace false, false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_alter_schema]
+
+
+-- !query
+CREATE SCHEMA ivt_schema
+-- !query analysis
+CreateNamespace false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [ivt_schema]
+
+
+-- !query
+USE ivt_schema
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [ivt_schema]
+
+
+-- !query
+CREATE VIEW ivt_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`ivt_schema`.`ivt_target`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE ivt_name STRING DEFAULT 'ivt_target'
+-- !query analysis
+CreateVariable default(cast(ivt_target as string), sql=''ivt_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.ivt_name
+
+
+-- !query
+ALTER VIEW IDENTIFIER(ivt_name) AS SELECT 2 AS c1
+-- !query analysis
+AlterViewAsCommand `spark_catalog`.`ivt_schema`.`ivt_target`, SELECT 2 AS c1, true
+   +- Project [2 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM ivt_target
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias spark_catalog.ivt_schema.ivt_target
+   +- View (`spark_catalog`.`ivt_schema`.`ivt_target`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [2 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW ivt_target
+-- !query analysis
+DropTableCommand `spark_catalog`.`ivt_schema`.`ivt_target`, false, true, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE ivt_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.ivt_name
+
+
+-- !query
+USE default
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [default]
+
+
+-- !query
+DROP SCHEMA ivt_schema
+-- !query analysis
+DropNamespace false, false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [ivt_schema]
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_expr_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_expr_col
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_expr_view AS
+SELECT IDENTIFIER(identifier_expr_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_expr_view`, SELECT IDENTIFIER(identifier_expr_col) AS x FROM VALUES(1) AS t(c1), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_expr_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_expr_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_expr_view
+   +- View (`identifier_expr_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_expr_view
+-- !query analysis
+DropTempViewCommand identifier_expr_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_expr_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_expr_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_gtv_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_gtv_col
+
+
+-- !query
+CREATE OR REPLACE GLOBAL TEMPORARY VIEW identifier_gtv_view AS
+SELECT IDENTIFIER(identifier_gtv_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_gtv_view`, SELECT IDENTIFIER(identifier_gtv_col) AS x FROM VALUES(1) AS t(c1), false, true, GlobalTempView, UNSUPPORTED, true, [[identifier_gtv_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM global_temp.identifier_gtv_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias global_temp.identifier_gtv_view
+   +- View (`global_temp`.`identifier_gtv_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW global_temp.identifier_gtv_view
+-- !query analysis
+DropTempViewCommand global_temp.identifier_gtv_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_gtv_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_gtv_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_view_name STRING DEFAULT 'identifier_named_view'
+-- !query analysis
+CreateVariable default(cast(identifier_named_view as string), sql=''identifier_named_view''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_view_name
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_view_name) AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_named_view`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_named_view
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias identifier_named_view
+   +- View (`identifier_named_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_named_view
+-- !query analysis
+DropTempViewCommand identifier_named_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_view_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_view_name
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_name_part STRING DEFAULT 'identifier_named_view2'
+-- !query analysis
+CreateVariable default(cast(identifier_named_view2 as string), sql=''identifier_named_view2''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_name_part
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_body_part STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_body_part
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_name_part) AS
+SELECT IDENTIFIER(identifier_body_part) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_named_view2`, SELECT IDENTIFIER(identifier_body_part) AS x FROM VALUES(1) AS t(c1), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_body_part]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_named_view2
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_named_view2
+   +- View (`identifier_named_view2`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_named_view2
+-- !query analysis
+DropTempViewCommand identifier_named_view2, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_name_part
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_name_part
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_body_part
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_body_part
+
+
+-- !query
+CREATE SCHEMA identifier_named_perm_schema
+-- !query analysis
+CreateNamespace false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_named_perm_schema]
+
+
+-- !query
+USE identifier_named_perm_schema
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_named_perm_schema]
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_view_name STRING DEFAULT 'identifier_named_perm_view'
+-- !query analysis
+CreateVariable default(cast(identifier_named_perm_view as string), sql=''identifier_named_perm_view''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_view_name
+
+
+-- !query
+CREATE VIEW IDENTIFIER(identifier_perm_view_name) AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`identifier_named_perm_schema`.`identifier_named_perm_view`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_named_perm_view
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias spark_catalog.identifier_named_perm_schema.identifier_named_perm_view
+   +- View (`spark_catalog`.`identifier_named_perm_schema`.`identifier_named_perm_view`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_named_perm_view
+-- !query analysis
+DropTableCommand `spark_catalog`.`identifier_named_perm_schema`.`identifier_named_perm_view`, false, true, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_view_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_perm_view_name
+
+
+-- !query
+USE default
+-- !query analysis
+SetCatalogAndNamespace
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [default]
+
+
+-- !query
+DROP SCHEMA identifier_named_perm_schema
+-- !query analysis
+DropNamespace false, false
++- ResolvedNamespace V2SessionCatalog(spark_catalog), [identifier_named_perm_schema]
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=true
+-- !query analysis
+SetCommand (spark.sql.legacy.allowSessionVariableInPersistedView,Some(true))
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_p2a_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_p2a_col
+
+
+-- !query
+CREATE VIEW identifier_p2a_view AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `spark_catalog`.`default`.`identifier_p2a_view`, SELECT 1 AS c1, false, false, PersistedView, COMPENSATION, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+ALTER VIEW identifier_p2a_view AS SELECT IDENTIFIER(identifier_p2a_col) FROM VALUES(1) AS t(c1)
+-- !query analysis
+AlterViewAsCommand `spark_catalog`.`default`.`identifier_p2a_view`, SELECT IDENTIFIER(identifier_p2a_col) FROM VALUES(1) AS t(c1), true, [[identifier_p2a_col]]
+   +- Project [c1#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_p2a_view
+-- !query analysis
+DropTableCommand `spark_catalog`.`default`.`identifier_p2a_view`, false, true, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_p2a_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_p2a_col
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=false
+-- !query analysis
+SetCommand (spark.sql.legacy.allowSessionVariableInPersistedView,Some(false))
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_nested_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_nested_col
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_inner AS
+SELECT IDENTIFIER(identifier_nested_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CreateViewCommand `identifier_nested_inner`, SELECT IDENTIFIER(identifier_nested_col) AS x FROM VALUES(1) AS t(c1), false, true, LocalTempView, UNSUPPORTED, true, [[identifier_nested_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_outer AS
+SELECT x FROM identifier_nested_inner
+-- !query analysis
+CreateViewCommand `identifier_nested_outer`, SELECT x FROM identifier_nested_inner, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [x#x]
+      +- SubqueryAlias identifier_nested_inner
+         +- View (`identifier_nested_inner`, [x#x])
+            +- Project [cast(x#x as int) AS x#x]
+               +- Project [c1#x AS x#x]
+                  +- SubqueryAlias t
+                     +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_nested_outer
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_nested_outer
+   +- View (`identifier_nested_outer`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [x#x]
+            +- SubqueryAlias identifier_nested_inner
+               +- View (`identifier_nested_inner`, [x#x])
+                  +- Project [cast(x#x as int) AS x#x]
+                     +- Project [c1#x AS x#x]
+                        +- SubqueryAlias t
+                           +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_nested_outer
+-- !query analysis
+DropTempViewCommand identifier_nested_outer, false
+
+
+-- !query
+DROP VIEW identifier_nested_inner
+-- !query analysis
+DropTempViewCommand identifier_nested_inner, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_nested_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_nested_col
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_target AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_fn_target`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_fn_var STRING DEFAULT 'identifier_fn_target'
+-- !query analysis
+CreateVariable default(cast(identifier_fn_target as string), sql=''identifier_fn_target''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_fn_var
+
+
+-- !query
+CREATE TEMPORARY FUNCTION identifier_fn() RETURN SELECT count(*) FROM IDENTIFIER(identifier_fn_var)
+-- !query analysis
+CreateSQLFunctionCommand identifier_fn, , SELECT count(*) FROM IDENTIFIER(identifier_fn_var), false, true, false, false
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_view AS SELECT identifier_fn() AS c
+-- !query analysis
+CreateViewCommand `identifier_fn_view`, SELECT identifier_fn() AS c, false, true, LocalTempView, UNSUPPORTED, true, [identifier_fn], [[identifier_fn_var]]
+   +- Project [identifier_fn() AS c#xL]
+      :  +- Aggregate [count(1) AS count(1)#xL]
+      :     +- SubqueryAlias identifier_fn_target
+      :        +- View (`identifier_fn_target`, [c1#x])
+      :           +- Project [cast(c1#x as int) AS c1#x]
+      :              +- Project [1 AS c1#x]
+      :                 +- OneRowRelation
+      +- Project
+         +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_fn_view
+-- !query analysis
+Project [c#xL]
++- SubqueryAlias identifier_fn_view
+   +- View (`identifier_fn_view`, [c#xL])
+      +- Project [cast(c#xL as bigint) AS c#xL]
+         +- Project [identifier_fn() AS c#xL]
+            :  +- Aggregate [count(1) AS count(1)#xL]
+            :     +- SubqueryAlias identifier_fn_target
+            :        +- View (`identifier_fn_target`, [c1#x])
+            :           +- Project [cast(c1#x as int) AS c1#x]
+            :              +- Project [1 AS c1#x]
+            :                 +- OneRowRelation
+            +- Project
+               +- OneRowRelation
+
+
+-- !query
+DROP VIEW identifier_fn_view
+-- !query analysis
+DropTempViewCommand identifier_fn_view, false
+
+
+-- !query
+DROP TEMPORARY FUNCTION identifier_fn
+-- !query analysis
+DropFunctionCommand identifier_fn, false, true
+
+
+-- !query
+DROP VIEW identifier_fn_target
+-- !query analysis
+DropTempViewCommand identifier_fn_target, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_fn_var
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_fn_var
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_talter_view AS SELECT 1 AS c1
+-- !query analysis
+CreateViewCommand `identifier_talter_view`, SELECT 1 AS c1, false, true, LocalTempView, UNSUPPORTED, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_talter_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_talter_col
+
+
+-- !query
+ALTER VIEW identifier_talter_view AS
+SELECT IDENTIFIER(identifier_talter_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+AlterViewAsCommand `identifier_talter_view`, SELECT IDENTIFIER(identifier_talter_col) AS x FROM VALUES(1) AS t(c1), true, [[identifier_talter_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_talter_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_talter_view
+   +- View (`identifier_talter_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+DROP VIEW identifier_talter_view
+-- !query analysis
+DropTempViewCommand identifier_talter_view, false
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_talter_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_talter_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_col
+
+
+-- !query
+CACHE TABLE identifier_cache_view AS
+SELECT IDENTIFIER(identifier_cache_col) AS x FROM VALUES(1) AS t(c1)
+-- !query analysis
+CacheTableAsSelect identifier_cache_view, SELECT IDENTIFIER(identifier_cache_col) AS x FROM VALUES(1) AS t(c1), false, true, [[identifier_cache_col]]
+   +- Project [c1#x AS x#x]
+      +- SubqueryAlias t
+         +- LocalRelation [c1#x]
+
+
+-- !query
+SELECT * FROM identifier_cache_view
+-- !query analysis
+Project [x#x]
++- SubqueryAlias identifier_cache_view
+   +- View (`identifier_cache_view`, [x#x])
+      +- Project [cast(x#x as int) AS x#x]
+         +- Project [c1#x AS x#x]
+            +- SubqueryAlias t
+               +- LocalRelation [c1#x]
+
+
+-- !query
+UNCACHE TABLE identifier_cache_view
+-- !query analysis
+UncacheTable false, true
+   +- SubqueryAlias identifier_cache_view
+      +- View (`identifier_cache_view`, [x#x])
+         +- Project [cast(x#x as int) AS x#x]
+            +- Project [c1#x AS x#x]
+               +- SubqueryAlias t
+                  +- LocalRelation [c1#x]
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_col
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_name STRING DEFAULT 'identifier_cache_named'
+-- !query analysis
+CreateVariable default(cast(identifier_cache_named as string), sql=''identifier_cache_named''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_name
+
+
+-- !query
+CACHE TABLE IDENTIFIER(identifier_cache_name) AS SELECT 1 AS c1
+-- !query analysis
+CacheTableAsSelect identifier_cache_named, SELECT 1 AS c1, false, true
+   +- Project [1 AS c1#x]
+      +- OneRowRelation
+
+
+-- !query
+SELECT * FROM identifier_cache_named
+-- !query analysis
+Project [c1#x]
++- SubqueryAlias identifier_cache_named
+   +- View (`identifier_cache_named`, [c1#x])
+      +- Project [cast(c1#x as int) AS c1#x]
+         +- Project [1 AS c1#x]
+            +- OneRowRelation
+
+
+-- !query
+UNCACHE TABLE identifier_cache_named
+-- !query analysis
+UncacheTable false, true
+   +- SubqueryAlias identifier_cache_named
+      +- View (`identifier_cache_named`, [c1#x])
+         +- Project [cast(c1#x as int) AS c1#x]
+            +- Project [1 AS c1#x]
+               +- OneRowRelation
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_name
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_cache_name
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_subq_col STRING DEFAULT 'c1'
+-- !query analysis
+CreateVariable default(cast(c1 as string), sql=''c1''), true
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_subq_col
+
+
+-- !query
+CREATE VIEW identifier_subq_view AS
+SELECT (SELECT IDENTIFIER(identifier_subq_col) FROM VALUES(1) AS t(c1)) AS x
+-- !query analysis
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`default`.`identifier_subq_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_subq_col`"
+  }
+}
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_subq_col
+-- !query analysis
+DropVariable false
++- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.identifier_subq_col
diff --git a/sql/core/src/test/resources/sql-tests/inputs/identifier-clause.sql b/sql/core/src/test/resources/sql-tests/inputs/identifier-clause.sql
index 3c4c4f73e3bc..591c1e772559 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/identifier-clause.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/identifier-clause.sql
@@ -468,3 +468,185 @@ SELECT EXTRACT(IDENTIFIER('YEAR') FROM DATE'2024-01-15');
 SELECT TIMESTAMPADD(IDENTIFIER('YEAR'), 1, DATE'2024-01-15');
 
 DROP SCHEMA identifier_clause_test_schema;
+
+-- A variable read only by a temporary view's IDENTIFIER clause is still a variable the view refers
+-- to, so it stays resolvable when the stored view text is analyzed again.
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_target AS SELECT 1 AS c1;
+DECLARE OR REPLACE VARIABLE identifier_var_name STRING DEFAULT 'identifier_var_target';
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_view AS
+SELECT * FROM IDENTIFIER(identifier_var_name);
+SELECT count(*) AS row_count FROM identifier_var_view;
+DROP VIEW identifier_var_view;
+DROP VIEW identifier_var_target;
+DROP TEMPORARY VARIABLE identifier_var_name;
+
+-- A permanent view must still reject a temporary variable read via an IDENTIFIER clause.
+CREATE TEMPORARY VIEW identifier_perm_target AS SELECT 1 AS c1;
+DECLARE OR REPLACE VARIABLE identifier_perm_name STRING DEFAULT 'identifier_perm_target';
+CREATE VIEW identifier_perm_view AS SELECT * FROM IDENTIFIER(identifier_perm_name);
+DROP VIEW identifier_perm_target;
+DROP TEMPORARY VARIABLE identifier_perm_name;
+
+-- A permanent ALTER VIEW must also reject a temporary variable read via an IDENTIFIER clause.
+-- The IDENTIFIER target is a permanent table so the variable is the only temporary object.
+CREATE SCHEMA identifier_alter_schema;
+USE identifier_alter_schema;
+CREATE TABLE identifier_alter_target (c1 INT) USING parquet;
+DECLARE OR REPLACE VARIABLE identifier_alter_name STRING DEFAULT 'identifier_alter_target';
+CREATE VIEW identifier_alter_view AS SELECT 1 AS c1;
+ALTER VIEW identifier_alter_view AS SELECT * FROM IDENTIFIER(identifier_alter_name);
+SELECT * FROM identifier_alter_view;
+DROP VIEW identifier_alter_view;
+DROP TABLE identifier_alter_target;
+DROP TEMPORARY VARIABLE identifier_alter_name;
+USE default;
+DROP SCHEMA identifier_alter_schema;
+
+-- ALTER VIEW whose TARGET is picked by a variable, with a body that has no temp dependency, must
+-- still succeed: the variable is not a dependency of the view definition.
+CREATE SCHEMA ivt_schema;
+USE ivt_schema;
+CREATE VIEW ivt_target AS SELECT 1 AS c1;
+DECLARE OR REPLACE VARIABLE ivt_name STRING DEFAULT 'ivt_target';
+ALTER VIEW IDENTIFIER(ivt_name) AS SELECT 2 AS c1;
+SELECT * FROM ivt_target;
+DROP VIEW ivt_target;
+DROP TEMPORARY VARIABLE ivt_name;
+USE default;
+DROP SCHEMA ivt_schema;
+
+-- A temporary view whose body reads a variable via an IDENTIFIER clause in expression position
+-- (here as a column name). Unlike the table-position case above, this identifier expression
+-- resolves to a bare variable reference, so recording it requires visiting the root of the
+-- expression tree. If it is not recorded, the variable is missing from the view's referred-variable
+-- metadata (the stored view text still names it via IDENTIFIER), so view-context re-analysis cannot
+-- resolve the reference and reading the view back fails.
+DECLARE OR REPLACE VARIABLE identifier_expr_col STRING DEFAULT 'c1';
+CREATE OR REPLACE TEMPORARY VIEW identifier_expr_view AS
+SELECT IDENTIFIER(identifier_expr_col) AS x FROM VALUES(1) AS t(c1);
+SELECT * FROM identifier_expr_view;
+DROP VIEW identifier_expr_view;
+DROP TEMPORARY VARIABLE identifier_expr_col;
+
+-- A GLOBAL TEMPORARY VIEW reads a variable via an IDENTIFIER clause the same way a session-local
+-- temporary view does, so the variable stays resolvable when the stored view text is analyzed again.
+DECLARE OR REPLACE VARIABLE identifier_gtv_col STRING DEFAULT 'c1';
+CREATE OR REPLACE GLOBAL TEMPORARY VIEW identifier_gtv_view AS
+SELECT IDENTIFIER(identifier_gtv_col) AS x FROM VALUES(1) AS t(c1);
+SELECT * FROM global_temp.identifier_gtv_view;
+DROP VIEW global_temp.identifier_gtv_view;
+DROP TEMPORARY VARIABLE identifier_gtv_col;
+
+-- A variable used only to supply a view's NAME via an IDENTIFIER clause is not part of the view
+-- definition, so it must not be recorded as a referred variable of the view.
+DECLARE OR REPLACE VARIABLE identifier_view_name STRING DEFAULT 'identifier_named_view';
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_view_name) AS SELECT 1 AS c1;
+SELECT * FROM identifier_named_view;
+DROP VIEW identifier_named_view;
+DROP TEMPORARY VARIABLE identifier_view_name;
+
+-- When both the NAME and the BODY use an IDENTIFIER clause, only the body variable is a dependency
+-- of the view; the name variable must not be recorded.
+DECLARE OR REPLACE VARIABLE identifier_name_part STRING DEFAULT 'identifier_named_view2';
+DECLARE OR REPLACE VARIABLE identifier_body_part STRING DEFAULT 'c1';
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_name_part) AS
+SELECT IDENTIFIER(identifier_body_part) AS x FROM VALUES(1) AS t(c1);
+SELECT * FROM identifier_named_view2;
+DROP VIEW identifier_named_view2;
+DROP TEMPORARY VARIABLE identifier_name_part;
+DROP TEMPORARY VARIABLE identifier_body_part;
+
+-- A PERSISTED view whose NAME is supplied by an IDENTIFIER variable, with a body that has no
+-- temporary dependency, is created successfully: the target-name variable is not part of the view
+-- definition. Were it wrongly recorded, this persisted CREATE would be rejected with
+-- INVALID_TEMP_OBJ_REFERENCE, so this case hard-locks the target-name exclusion (the temporary
+-- cases above only surface a leak through the analyzer-results referred-variable metadata).
+CREATE SCHEMA identifier_named_perm_schema;
+USE identifier_named_perm_schema;
+DECLARE OR REPLACE VARIABLE identifier_perm_view_name STRING DEFAULT 'identifier_named_perm_view';
+CREATE VIEW IDENTIFIER(identifier_perm_view_name) AS SELECT 1 AS c1;
+SELECT * FROM identifier_named_perm_view;
+DROP VIEW identifier_named_perm_view;
+DROP TEMPORARY VARIABLE identifier_perm_view_name;
+USE default;
+DROP SCHEMA identifier_named_perm_schema;
+
+-- ALTER VIEW honors `spark.sql.legacy.allowSessionVariableInPersistedView` just like CREATE VIEW:
+-- with it set, the DDL is allowed instead of rejected (the legacy permissive behavior). The flag
+-- does not persist the variable into the stored view, so the view is not guaranteed to resolve in a
+-- fresh session; the default (tested by `identifier_alter_view` above) rejects it outright.
+SET spark.sql.legacy.allowSessionVariableInPersistedView=true;
+DECLARE OR REPLACE VARIABLE identifier_p2a_col STRING DEFAULT 'c1';
+CREATE VIEW identifier_p2a_view AS SELECT 1 AS c1;
+ALTER VIEW identifier_p2a_view AS SELECT IDENTIFIER(identifier_p2a_col) FROM VALUES(1) AS t(c1);
+DROP VIEW identifier_p2a_view;
+DROP TEMPORARY VARIABLE identifier_p2a_col;
+SET spark.sql.legacy.allowSessionVariableInPersistedView=false;
+
+-- Nested temporary views lock in the `withAnalysisContext` accumulator reset: creating the outer
+-- view (which selects the inner view) resolves the inner view along the way, and the inner view's
+-- IDENTIFIER-clause variable must be attributed to the inner view only -- it must never be recorded
+-- as a referred variable of the outer view. The inner `CreateViewCommand` records
+-- `identifier_nested_col`; the outer `CreateViewCommand` must record nothing.
+DECLARE OR REPLACE VARIABLE identifier_nested_col STRING DEFAULT 'c1';
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_inner AS
+SELECT IDENTIFIER(identifier_nested_col) AS x FROM VALUES(1) AS t(c1);
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_outer AS
+SELECT x FROM identifier_nested_inner;
+SELECT * FROM identifier_nested_outer;
+DROP VIEW identifier_nested_outer;
+DROP VIEW identifier_nested_inner;
+DROP TEMPORARY VARIABLE identifier_nested_col;
+
+-- A temporary SQL function whose body reads a relation via an IDENTIFIER clause, referenced by a
+-- temporary view. A SQL function keeps no IDENTIFIER-variable metadata of its own, so the variable
+-- is part of the enclosing view's transitive definition: expanding the function while creating the
+-- view must record the variable as a referred variable of the view (the function-body analysis
+-- context must not discard it). Otherwise re-resolving the stored view text on read fails.
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_target AS SELECT 1 AS c1;
+DECLARE OR REPLACE VARIABLE identifier_fn_var STRING DEFAULT 'identifier_fn_target';
+CREATE TEMPORARY FUNCTION identifier_fn() RETURN SELECT count(*) FROM IDENTIFIER(identifier_fn_var);
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_view AS SELECT identifier_fn() AS c;
+SELECT * FROM identifier_fn_view;
+DROP VIEW identifier_fn_view;
+DROP TEMPORARY FUNCTION identifier_fn;
+DROP VIEW identifier_fn_target;
+DROP TEMPORARY VARIABLE identifier_fn_var;
+
+-- ALTER of a LOCAL TEMPORARY view whose new body reads a column via an IDENTIFIER clause. The
+-- forwarded referred-variable dependency must be stored so re-analyzing the altered view text on
+-- read still resolves the variable.
+CREATE OR REPLACE TEMPORARY VIEW identifier_talter_view AS SELECT 1 AS c1;
+DECLARE OR REPLACE VARIABLE identifier_talter_col STRING DEFAULT 'c1';
+ALTER VIEW identifier_talter_view AS
+SELECT IDENTIFIER(identifier_talter_col) AS x FROM VALUES(1) AS t(c1);
+SELECT * FROM identifier_talter_view;
+DROP VIEW identifier_talter_view;
+DROP TEMPORARY VARIABLE identifier_talter_col;
+
+-- CACHE TABLE AS SELECT reads a column via an IDENTIFIER clause. The dependency must be threaded
+-- through the cache command so the text-backed temporary view it creates stays resolvable for the
+-- eager caching read (and any later lazy read).
+DECLARE OR REPLACE VARIABLE identifier_cache_col STRING DEFAULT 'c1';
+CACHE TABLE identifier_cache_view AS
+SELECT IDENTIFIER(identifier_cache_col) AS x FROM VALUES(1) AS t(c1);
+SELECT * FROM identifier_cache_view;
+UNCACHE TABLE identifier_cache_view;
+DROP TEMPORARY VARIABLE identifier_cache_col;
+
+-- A variable used only to compute the CACHE TABLE target name is not part of the cached view
+-- definition, so it must not be recorded as a referred variable of the view.
+DECLARE OR REPLACE VARIABLE identifier_cache_name STRING DEFAULT 'identifier_cache_named';
+CACHE TABLE IDENTIFIER(identifier_cache_name) AS SELECT 1 AS c1;
+SELECT * FROM identifier_cache_named;
+UNCACHE TABLE identifier_cache_named;
+DROP TEMPORARY VARIABLE identifier_cache_name;
+
+-- A persisted CREATE VIEW must reject a session variable read via an IDENTIFIER clause even when it
+-- appears only inside a scalar subquery. The variable is absent from the analyzed plan, so the
+-- recorded set (not a scan of the plan) is what the validator must consult; otherwise the view is
+-- wrongly created and stores text that cannot be resolved on read.
+DECLARE OR REPLACE VARIABLE identifier_subq_col STRING DEFAULT 'c1';
+CREATE VIEW identifier_subq_view AS
+SELECT (SELECT IDENTIFIER(identifier_subq_col) FROM VALUES(1) AS t(c1)) AS x;
+DROP TEMPORARY VARIABLE identifier_subq_col;
diff --git a/sql/core/src/test/resources/sql-tests/results/identifier-clause-legacy.sql.out b/sql/core/src/test/resources/sql-tests/results/identifier-clause-legacy.sql.out
index f881ebd53c72..c71579950aa4 100644
--- a/sql/core/src/test/resources/sql-tests/results/identifier-clause-legacy.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/identifier-clause-legacy.sql.out
@@ -3570,3 +3570,898 @@ DROP SCHEMA identifier_clause_test_schema
 struct<>
 -- !query output
 
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_var_name STRING DEFAULT 'identifier_var_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_view AS
+SELECT * FROM IDENTIFIER(identifier_var_name)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT count(*) AS row_count FROM identifier_var_view
+-- !query schema
+struct<row_count:bigint>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_var_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_var_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_var_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE TEMPORARY VIEW identifier_perm_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_name STRING DEFAULT 'identifier_perm_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_perm_view AS SELECT * FROM IDENTIFIER(identifier_perm_name)
+-- !query schema
+struct<>
+-- !query output
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_clause_test_schema`.`identifier_perm_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_perm_name`"
+  }
+}
+
+
+-- !query
+DROP VIEW identifier_perm_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE SCHEMA identifier_alter_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE identifier_alter_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE TABLE identifier_alter_target (c1 INT) USING parquet
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_alter_name STRING DEFAULT 'identifier_alter_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_alter_view AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW identifier_alter_view AS SELECT * FROM IDENTIFIER(identifier_alter_name)
+-- !query schema
+struct<>
+-- !query output
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_alter_name`"
+  }
+}
+
+
+-- !query
+SELECT * FROM identifier_alter_view
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_alter_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TABLE identifier_alter_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_alter_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE default
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP SCHEMA identifier_alter_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE SCHEMA ivt_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE ivt_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW ivt_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE ivt_name STRING DEFAULT 'ivt_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW IDENTIFIER(ivt_name) AS SELECT 2 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM ivt_target
+-- !query schema
+struct<c1:int>
+-- !query output
+2
+
+
+-- !query
+DROP VIEW ivt_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE ivt_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE default
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP SCHEMA ivt_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_expr_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_expr_view AS
+SELECT IDENTIFIER(identifier_expr_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_expr_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_expr_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_expr_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_gtv_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE GLOBAL TEMPORARY VIEW identifier_gtv_view AS
+SELECT IDENTIFIER(identifier_gtv_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM global_temp.identifier_gtv_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW global_temp.identifier_gtv_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_gtv_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_view_name STRING DEFAULT 'identifier_named_view'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_view_name) AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_named_view
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_named_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_view_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_name_part STRING DEFAULT 'identifier_named_view2'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_body_part STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_name_part) AS
+SELECT IDENTIFIER(identifier_body_part) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_named_view2
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_named_view2
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_name_part
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_body_part
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE SCHEMA identifier_named_perm_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE identifier_named_perm_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_view_name STRING DEFAULT 'identifier_named_perm_view'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW IDENTIFIER(identifier_perm_view_name) AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_named_perm_view
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_named_perm_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_view_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE default
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP SCHEMA identifier_named_perm_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=true
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.legacy.allowSessionVariableInPersistedView	true
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_p2a_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_p2a_view AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW identifier_p2a_view AS SELECT IDENTIFIER(identifier_p2a_col) FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_p2a_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_p2a_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=false
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.legacy.allowSessionVariableInPersistedView	false
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_nested_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_inner AS
+SELECT IDENTIFIER(identifier_nested_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_outer AS
+SELECT x FROM identifier_nested_inner
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_nested_outer
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_nested_outer
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_nested_inner
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_nested_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_fn_var STRING DEFAULT 'identifier_fn_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE TEMPORARY FUNCTION identifier_fn() RETURN SELECT count(*) FROM IDENTIFIER(identifier_fn_var)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_view AS SELECT identifier_fn() AS c
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_fn_view
+-- !query schema
+struct<c:bigint>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_fn_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY FUNCTION identifier_fn
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_fn_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_fn_var
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_talter_view AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_talter_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW identifier_talter_view AS
+SELECT IDENTIFIER(identifier_talter_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_talter_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_talter_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_talter_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CACHE TABLE identifier_cache_view AS
+SELECT IDENTIFIER(identifier_cache_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_cache_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+UNCACHE TABLE identifier_cache_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_name STRING DEFAULT 'identifier_cache_named'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CACHE TABLE IDENTIFIER(identifier_cache_name) AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_cache_named
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+UNCACHE TABLE identifier_cache_named
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_subq_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_subq_view AS
+SELECT (SELECT IDENTIFIER(identifier_subq_col) FROM VALUES(1) AS t(c1)) AS x
+-- !query schema
+struct<>
+-- !query output
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`default`.`identifier_subq_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_subq_col`"
+  }
+}
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_subq_col
+-- !query schema
+struct<>
+-- !query output
+
diff --git a/sql/core/src/test/resources/sql-tests/results/identifier-clause.sql.out b/sql/core/src/test/resources/sql-tests/results/identifier-clause.sql.out
index 045348cf7ea0..38f5f4178801 100644
--- a/sql/core/src/test/resources/sql-tests/results/identifier-clause.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/identifier-clause.sql.out
@@ -3197,3 +3197,898 @@ DROP SCHEMA identifier_clause_test_schema
 struct<>
 -- !query output
 
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_var_name STRING DEFAULT 'identifier_var_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_var_view AS
+SELECT * FROM IDENTIFIER(identifier_var_name)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT count(*) AS row_count FROM identifier_var_view
+-- !query schema
+struct<row_count:bigint>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_var_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_var_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_var_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE TEMPORARY VIEW identifier_perm_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_name STRING DEFAULT 'identifier_perm_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_perm_view AS SELECT * FROM IDENTIFIER(identifier_perm_name)
+-- !query schema
+struct<>
+-- !query output
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_clause_test_schema`.`identifier_perm_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_perm_name`"
+  }
+}
+
+
+-- !query
+DROP VIEW identifier_perm_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE SCHEMA identifier_alter_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE identifier_alter_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE TABLE identifier_alter_target (c1 INT) USING parquet
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_alter_name STRING DEFAULT 'identifier_alter_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_alter_view AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW identifier_alter_view AS SELECT * FROM IDENTIFIER(identifier_alter_name)
+-- !query schema
+struct<>
+-- !query output
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`identifier_alter_schema`.`identifier_alter_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_alter_name`"
+  }
+}
+
+
+-- !query
+SELECT * FROM identifier_alter_view
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_alter_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TABLE identifier_alter_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_alter_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE default
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP SCHEMA identifier_alter_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE SCHEMA ivt_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE ivt_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW ivt_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE ivt_name STRING DEFAULT 'ivt_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW IDENTIFIER(ivt_name) AS SELECT 2 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM ivt_target
+-- !query schema
+struct<c1:int>
+-- !query output
+2
+
+
+-- !query
+DROP VIEW ivt_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE ivt_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE default
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP SCHEMA ivt_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_expr_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_expr_view AS
+SELECT IDENTIFIER(identifier_expr_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_expr_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_expr_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_expr_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_gtv_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE GLOBAL TEMPORARY VIEW identifier_gtv_view AS
+SELECT IDENTIFIER(identifier_gtv_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM global_temp.identifier_gtv_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW global_temp.identifier_gtv_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_gtv_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_view_name STRING DEFAULT 'identifier_named_view'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_view_name) AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_named_view
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_named_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_view_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_name_part STRING DEFAULT 'identifier_named_view2'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_body_part STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW IDENTIFIER(identifier_name_part) AS
+SELECT IDENTIFIER(identifier_body_part) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_named_view2
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_named_view2
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_name_part
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_body_part
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE SCHEMA identifier_named_perm_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE identifier_named_perm_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_perm_view_name STRING DEFAULT 'identifier_named_perm_view'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW IDENTIFIER(identifier_perm_view_name) AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_named_perm_view
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_named_perm_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_perm_view_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+USE default
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP SCHEMA identifier_named_perm_schema
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=true
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.legacy.allowSessionVariableInPersistedView	true
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_p2a_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_p2a_view AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW identifier_p2a_view AS SELECT IDENTIFIER(identifier_p2a_col) FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_p2a_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_p2a_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SET spark.sql.legacy.allowSessionVariableInPersistedView=false
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.legacy.allowSessionVariableInPersistedView	false
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_nested_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_inner AS
+SELECT IDENTIFIER(identifier_nested_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_nested_outer AS
+SELECT x FROM identifier_nested_inner
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_nested_outer
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_nested_outer
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_nested_inner
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_nested_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_target AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_fn_var STRING DEFAULT 'identifier_fn_target'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE TEMPORARY FUNCTION identifier_fn() RETURN SELECT count(*) FROM IDENTIFIER(identifier_fn_var)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_fn_view AS SELECT identifier_fn() AS c
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_fn_view
+-- !query schema
+struct<c:bigint>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_fn_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY FUNCTION identifier_fn
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP VIEW identifier_fn_target
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_fn_var
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW identifier_talter_view AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_talter_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+ALTER VIEW identifier_talter_view AS
+SELECT IDENTIFIER(identifier_talter_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_talter_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+DROP VIEW identifier_talter_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_talter_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CACHE TABLE identifier_cache_view AS
+SELECT IDENTIFIER(identifier_cache_col) AS x FROM VALUES(1) AS t(c1)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_cache_view
+-- !query schema
+struct<x:int>
+-- !query output
+1
+
+
+-- !query
+UNCACHE TABLE identifier_cache_view
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_col
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_cache_name STRING DEFAULT 'identifier_cache_named'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CACHE TABLE IDENTIFIER(identifier_cache_name) AS SELECT 1 AS c1
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT * FROM identifier_cache_named
+-- !query schema
+struct<c1:int>
+-- !query output
+1
+
+
+-- !query
+UNCACHE TABLE identifier_cache_named
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_cache_name
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+DECLARE OR REPLACE VARIABLE identifier_subq_col STRING DEFAULT 'c1'
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+CREATE VIEW identifier_subq_view AS
+SELECT (SELECT IDENTIFIER(identifier_subq_col) FROM VALUES(1) AS t(c1)) AS x
+-- !query schema
+struct<>
+-- !query output
+org.apache.spark.sql.AnalysisException
+{
+  "errorClass" : "INVALID_TEMP_OBJ_REFERENCE",
+  "sqlState" : "42K0F",
+  "messageParameters" : {
+    "obj" : "VIEW",
+    "objName" : "`spark_catalog`.`default`.`identifier_subq_view`",
+    "tempObj" : "VARIABLE",
+    "tempObjName" : "`identifier_subq_col`"
+  }
+}
+
+
+-- !query
+DROP TEMPORARY VARIABLE identifier_subq_col
+-- !query schema
+struct<>
+-- !query output
+
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2MetadataViewSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2MetadataViewSuite.scala
index a80b2e0c21d7..c5c189e22cee 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2MetadataViewSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2MetadataViewSuite.scala
@@ -287,6 +287,59 @@ class DataSourceV2MetadataViewSuite extends SharedSparkSession {
     }
   }
 
+  test("v2 CREATE / ALTER VIEW rejects a temporary variable read via an IDENTIFIER clause") {
+    // A variable read via an IDENTIFIER clause is not present in the analyzed view body (the
+    // placeholder is replaced by the plan built from the evaluated name), so it is captured during
+    // analysis (referredTempVariablesUnderIdentifier) and must be rejected for a permanent v2 view,
+    // just like a temporary function. The ALTER path is only covered once the v2 command threads
+    // the captured variables into CheckViewReferences.
+    withTable("spark_catalog.default.t") {
+      Seq(1, 2, 3).toDF("x").write.saveAsTable("spark_catalog.default.t")
+      sql("DECLARE OR REPLACE VARIABLE v2_ident_col STRING DEFAULT 'x'")
+      try {
+        val createEx = intercept[AnalysisException] {
+          sql("CREATE VIEW view_catalog.default.v_ident_var AS " +
+            "SELECT IDENTIFIER(v2_ident_col) FROM spark_catalog.default.t")
+        }
+        assert(createEx.getCondition == "INVALID_TEMP_OBJ_REFERENCE")
+
+        sql("CREATE VIEW view_catalog.default.v_ident_var AS SELECT x FROM spark_catalog.default.t")
+        val alterEx = intercept[AnalysisException] {
+          sql("ALTER VIEW view_catalog.default.v_ident_var AS " +
+            "SELECT IDENTIFIER(v2_ident_col) FROM spark_catalog.default.t")
+        }
+        assert(alterEx.getCondition == "INVALID_TEMP_OBJ_REFERENCE")
+      } finally {
+        sql("DROP TEMPORARY VARIABLE v2_ident_col")
+      }
+    }
+  }
+
+  test("v2 CREATE / ALTER VIEW allows a temporary variable under an IDENTIFIER clause when the " +
+    "legacy flag is enabled") {
+    // With `spark.sql.legacy.allowSessionVariableInPersistedView` enabled, `CheckViewReferences`
+    // takes its legacy branch for both the v2 CREATE VIEW and ALTER VIEW commands and permits the
+    // dependency instead of rejecting it (the default rejection is covered above). This checks that
+    // both commands are allowed under the flag; the flag does not persist the variable.
+    withTable("spark_catalog.default.t") {
+      Seq(1, 2, 3).toDF("x").write.saveAsTable("spark_catalog.default.t")
+      sql("DECLARE OR REPLACE VARIABLE v2_ident_col STRING DEFAULT 'x'")
+      try {
+        withSQLConf(SQLConf.VARIABLES_UNDER_IDENTIFIER_IN_VIEW.key -> "true") {
+          // CREATE is allowed under the flag.
+          sql("CREATE VIEW view_catalog.default.v_ident_flag AS " +
+            "SELECT IDENTIFIER(v2_ident_col) FROM spark_catalog.default.t")
+          // ALTER is allowed under the flag too.
+          sql("ALTER VIEW view_catalog.default.v_ident_flag AS " +
+            "SELECT IDENTIFIER(v2_ident_col) AS y FROM spark_catalog.default.t")
+        }
+      } finally {
+        sql("DROP VIEW IF EXISTS view_catalog.default.v_ident_flag")
+        sql("DROP TEMPORARY VARIABLE v2_ident_col")
+      }
+    }
+  }
+
   // --- v2 view DDL / inspection on a non-session v2 catalog ----------------------------
   // ResolveSessionCatalog's `ResolvedViewIdentifier` matcher is gated on isSessionCatalog, so
   // these plans flow through to DataSourceV2Strategy with a `ResolvedPersistentView` child.
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/execution/MetricViewSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/execution/MetricViewSuite.scala
index 6cfe051e3014..2c156549142d 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/execution/MetricViewSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/execution/MetricViewSuite.scala
@@ -18,6 +18,7 @@
 package org.apache.spark.sql.execution
 
 import org.apache.spark.sql.{AnalysisException, QueryTest}
+import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.metricview.serde.{AssetSource, Column, DimensionExpression, MeasureExpression, MetricView, MetricViewFactory, SQLSource}
 import org.apache.spark.sql.test.SharedSparkSession
 
@@ -81,6 +82,54 @@ abstract class MetricViewSuite extends QueryTest {
     }
   }
 
+  test("metric view SQL source rejects a temporary variable read via an IDENTIFIER clause") {
+    // A variable read via an IDENTIFIER clause in the metric-view SQL source is absent from the
+    // analyzed plan, so it is captured during analysis and must be rejected for a persisted metric
+    // view, unless `spark.sql.legacy.allowSessionVariableInPersistedView` is enabled.
+    val metricView = MetricView(
+      "0.1",
+      SQLSource("SELECT region, count FROM IDENTIFIER(mv_ident_source)"),
+      None,
+      Seq(
+        Column("region", DimensionExpression("region"), 0),
+        Column("count_sum", MeasureExpression("sum(count)"), 1)))
+    sql(s"DECLARE OR REPLACE VARIABLE mv_ident_source STRING DEFAULT '$testTableName'")
+    try {
+      val ex = intercept[AnalysisException] {
+        createMetricView("mv_ident_metric_view", metricView)
+      }
+      assert(ex.getCondition == "INVALID_TEMP_OBJ_REFERENCE")
+
+      // With the legacy flag enabled, creation is allowed (the flag does not persist the variable).
+      withSQLConf(SQLConf.VARIABLES_UNDER_IDENTIFIER_IN_VIEW.key -> "true") {
+        withView("mv_ident_metric_view") {
+          createMetricView("mv_ident_metric_view", metricView)
+        }
+      }
+    } finally {
+      sql("DROP TEMPORARY VARIABLE mv_ident_source")
+    }
+  }
+
+  test("metric view creation preserves configured analyzer routing under forced single-pass") {
+    // `MetricViewPlaceholder` is explicitly unsupported by the single-pass resolver. Metric-view
+    // creation analyzes the source through `executeAndCheckReferredTempVariablesUnderIdentifier`,
+    // which must route through HybridAnalyzer when single-pass is forced on so that incompatibility
+    // surfaces, rather than silently analyzing through fixed-point.
+    val metricView = MetricView(
+      "0.1",
+      SQLSource("SELECT region, count FROM test_table"),
+      None,
+      Seq(
+        Column("region", DimensionExpression("region"), 0),
+        Column("count_sum", MeasureExpression("sum(count)"), 1)))
+    withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "true") {
+      intercept[Exception] {
+        createMetricView("mv_single_pass_view", metricView)
+      }
+    }
+  }
+
   test("test source type") {
     val sources = Seq(
       AssetSource(testTableName),
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala
index 1e5b1cdfaeee..9eda53fa0987 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/execution/SQLViewTestSuite.scala
@@ -833,7 +833,7 @@ class PersistedViewTestSuite extends SQLViewTestSuite with SharedSparkSession {
       condition = "INVALID_TEMP_OBJ_REFERENCE",
       parameters = Map(
         "obj" -> "VIEW",
-        "objName" -> "`unknown`",
+        "objName" -> "`spark_catalog`.`default`.`v_table_3`",
         "tempObj" -> "VARIABLE",
         "tempObjName" -> "`table_name`"
       )
@@ -848,7 +848,7 @@ class PersistedViewTestSuite extends SQLViewTestSuite with SharedSparkSession {
       condition = "INVALID_TEMP_OBJ_REFERENCE",
       parameters = Map(
         "obj" -> "VIEW",
-        "objName" -> "`unknown`",
+        "objName" -> "`v_table_4`",
         "tempObj" -> "VARIABLE",
         "tempObjName" -> "`table_name`"
       )


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org