HyukjinKwon commented on code in PR #59386:
URL: https://github.com/apache/spark/pull/59386#discussion_r4260468909
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/command/metricViewCommands.scala:
##########
@@ -78,6 +79,18 @@ case class CreateMetricViewCommand(
// `where`) into the user-supplied properties so v1 DESCRIBE TABLE
EXTENDED surfaces the
// same descriptor rows as the v2 path in `DataSourceV2Strategy`.
val mergedProps = properties ++ metricView.getProperties
+ if (replace && catalog.tableExists(name)) {
+ // Handles `CREATE OR REPLACE VIEW ... WITH METRICS` on an existing
view, the same way
+ // `CreateViewCommand` does: nothing is retained from the old view, so
drop and recreate.
+ val existing = catalog.getTableMetadata(name)
+ if (!existing.isViewLike) {
+ throw
QueryCompilationErrors.unsupportedCreateOrReplaceViewOnTableError(
+ name.nameParts, replace)
+ }
+ ViewHelper.checkCyclicViewReference(analyzed, Seq(existing.fullIdent),
existing.fullIdent)
+ CommandUtils.uncacheTableOrView(sparkSession, existing.identifier)
+ catalog.dropTable(existing.identifier, ignoreIfNotExists = false, purge
= false)
Review Comment:
**Nit (P3):** The existing view is uncached and dropped here, but
`ViewHelper.prepareTable(...)` only runs afterwards as the `createTable`
argument, and it can still reject the new definition. `generateViewProperties`
calls `SchemaUtils.checkColumnNameDuplication`, and nothing earlier on this
path checks that names are unique (`validateUserColumns` only compares counts).
So `CREATE OR REPLACE VIEW mv (a, a) WITH METRICS ...` against an existing
`mv`, or a YAML body with two `select` columns of the same name, fails with
`COLUMN_ALREADY_EXISTS` after `mv` has already been dropped, and the old metric
view is gone. Before this PR that statement failed without touching `mv`, and
the v2 path (`V2CreateViewPreparation.run`) builds the view info before calling
`createOrReplaceView`.
Could you build the `CatalogTable` with `prepareTable` into a local `val`
before the `if (replace && catalog.tableExists(name))` block, and then pass it
to `createTable` after the uncache/drop? `prepareTable` doesn't read the old
view, so moving it up changes nothing else.
**Verification:**
- **Regression:** With an existing session-catalog metric view, CREATE OR
REPLACE VIEW ... WITH METRICS with duplicate output column names fails with
COLUMN_ALREADY_EXISTS, and querying the metric view afterwards still returns
its original results.
- **Behavior:** Existing coverage for successful replace, table rejection,
and direct/indirect metric-view cycles still passes unchanged.
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/command/views.scala:
##########
@@ -616,20 +617,27 @@ object ViewHelper extends SQLConfHelper with Logging with
CapturesConfig {
plan: LogicalPlan,
path: Seq[Seq[String]],
viewIdent: Seq[String]): Unit = {
- plan match {
- case v: View =>
- val ident = v.desc.fullIdent
+ // A metric view read by the plan is a `ResolvedMetricView`, or a
`MetricViewPlaceholder`
+ // when it is the source of another metric view, not a `View`.
+ val viewRef = plan match {
+ case v: View => Some(v.desc.fullIdent)
+ case mv: ResolvedMetricView => Some(mv.identifier.nameParts)
Review Comment:
**Nit (P3):** This arm compares `mv.identifier.nameParts`, but
`ResolvedMetricView.identifier` is the legacy `TableIdentifier` taken from the
placeholder's `CatalogTable`. For a metric view read from a non-session v2
`ViewCatalog`, `V1Table.toCatalogTable` builds it with
`asLegacyTableIdentifier`, which keeps only the last namespace segment; the
full name lives only in `multipartIdentifier`. The `View` and
`MetricViewPlaceholder` arms, and every caller's `viewIdent`, use the full
identifier, as the `@param viewIdent` doc above describes.
With nested namespaces this goes wrong both ways in the v2
`CheckViewReferences` path. `CREATE OR REPLACE VIEW cat.b.v AS SELECT
measure(m) FROM cat.a.b.v` now fails with a spurious `RECURSIVE_VIEW`, because
both sides are `[cat, b, v]`. Replacing `cat.a.b.v` with a view that reads
itself through `measure()` is still not detected. Session-catalog identifiers
are unaffected, which is why the new tests pass.
Could `ResolvedMetricView` carry the metric view's full multi-part
identifier (e.g. `mv.metadata.fullIdent` when `ResolveMetricView` builds it),
so this arm compares the same shape as the other two?
**Verification:**
- **Regression:** In a v2 ViewCatalog with nested namespaces, CREATE OR
REPLACE VIEW of a single-level view whose body reads a same-named metric view
in a deeper namespace through measure() succeeds instead of raising
RECURSIVE_VIEW.
- **Regression:** In the same catalog, replacing a nested-namespace metric
view with a regular view that reads it through measure() fails with
RECURSIVE_VIEW and names the full multi-part identifier.
- **Compatibility:** The session-catalog metric-view cycle tests keep their
RECURSIVE_VIEW condition and rendered paths.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]