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