Copilot commented on code in PR #10998:
URL: https://github.com/apache/gravitino/pull/10998#discussion_r3212508955


##########
catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java:
##########
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.hive;
+
+import static org.apache.gravitino.catalog.hive.HiveConstants.COMMENT;
+import static 
org.apache.gravitino.catalog.hive.HiveConstants.HIVE_FILTER_FIELD_PARAMS;
+import static org.apache.gravitino.catalog.hive.HiveConstants.TABLE_TYPE;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchViewException;
+import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
+import org.apache.gravitino.hive.CachedClientPool;
+import org.apache.gravitino.hive.HiveTable;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.SQLRepresentation;
+import org.apache.gravitino.rel.View;
+import org.apache.gravitino.rel.ViewCatalog;
+import org.apache.gravitino.rel.ViewChange;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class HiveViewCatalogOperations implements ViewCatalog {
+  private static final Logger LOG = 
LoggerFactory.getLogger(HiveViewCatalogOperations.class);
+  private static final short MAX_TABLES = -1;
+
+  private final Supplier<CachedClientPool> clientPoolSupplier;
+  private final Supplier<String> catalogNameSupplier;
+  private final Predicate<NameIdentifier> schemaExistsChecker;
+
+  HiveViewCatalogOperations(
+      Supplier<CachedClientPool> clientPoolSupplier,
+      Supplier<String> catalogNameSupplier,
+      Predicate<NameIdentifier> schemaExistsChecker) {
+    this.clientPoolSupplier = clientPoolSupplier;
+    this.catalogNameSupplier = catalogNameSupplier;
+    this.schemaExistsChecker = schemaExistsChecker;
+  }
+
+  @Override
+  public NameIdentifier[] listViews(Namespace namespace) throws 
NoSuchSchemaException {
+    NameIdentifier schemaIdent = NameIdentifier.of(namespace.levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", namespace);
+    }
+    try {
+      String viewFilter =
+          String.format("%stableType like \"VIRTUAL_VIEW\"", 
HIVE_FILTER_FIELD_PARAMS);
+      List<String> views =
+          clientPool()
+              .run(
+                  c ->
+                      c.listTableNamesByFilter(
+                          catalogName(), schemaIdent.name(), viewFilter, 
MAX_TABLES));
+      return views.stream()
+          .map(name -> NameIdentifier.of(namespace, name))
+          .toArray(NameIdentifier[]::new);
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Failed to list Hive views in " + namespace, 
e);
+    }
+  }
+
+  @Override
+  public View loadView(NameIdentifier ident) throws NoSuchViewException {
+    return loadHiveView(ident);
+  }
+
+  @Override
+  public View createView(
+      NameIdentifier ident,
+      String comment,
+      Column[] columns,
+      Representation[] representations,
+      String defaultCatalog,
+      String defaultSchema,
+      Map<String, String> properties)
+      throws NoSuchSchemaException, ViewAlreadyExistsException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", schemaIdent);
+    }
+    SQLRepresentation sqlRepresentation = 
extractSupportedSqlRepresentation(representations, ident);
+
+    try {
+      Map<String, String> params =
+          Maps.newHashMap(properties == null ? ImmutableMap.of() : properties);
+      if (comment != null) {
+        params.put(COMMENT, comment);
+      }
+      params.put(TABLE_TYPE, TableType.VIRTUAL_VIEW.name());
+      String viewOriginalText = toHmsViewOriginalText(sqlRepresentation, 
ident);
+
+      HiveTable hiveTable =
+          HiveTable.builder()
+              .withName(ident.name())
+              .withComment(null)
+              .withColumns(new Column[0])
+              .withProperties(params)
+              .withAuditInfo(
+                  AuditInfo.builder()
+                      .withCreator(PrincipalUtils.getCurrentUserName())
+                      .withCreateTime(Instant.now())
+                      .build())
+              .withCatalogName(catalogName())
+              .withDatabaseName(schemaIdent.name())
+              .withViewOriginalText(viewOriginalText)
+              .build();
+
+      clientPool()
+          .run(
+              c -> {
+                c.createTable(hiveTable);
+                return null;
+              });
+
+      LOG.info("Created Hive view {} in Hive Metastore", ident.name());
+      return loadHiveView(ident);
+    } catch (Exception e) {
+      if (isAlreadyExistsError(e)) {
+        throw new ViewAlreadyExistsException("View %s already exists in Hive 
Metastore", ident);
+      }
+      throw new RuntimeException("Failed to create Hive view " + ident, e);
+    }
+  }
+
+  @Override
+  public View alterView(NameIdentifier ident, ViewChange... changes)
+      throws NoSuchViewException, ViewAlreadyExistsException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+
+    for (ViewChange change : changes) {
+      if (change instanceof ViewChange.RenameView) {
+        NameIdentifier targetIdent =
+            NameIdentifier.of(ident.namespace(), ((ViewChange.RenameView) 
change).getNewName());
+        if (viewExists(targetIdent)) {
+          throw new ViewAlreadyExistsException(
+              "View %s already exists in Hive Metastore", targetIdent);
+        }
+      }
+    }
+
+    try {
+      HiveTable currentHiveTable =
+          clientPool().run(c -> c.getTable(catalogName(), schemaIdent.name(), 
ident.name()));
+      if (!TableType.VIRTUAL_VIEW
+          .name()
+          .equalsIgnoreCase(currentHiveTable.properties().get(TABLE_TYPE))) {
+        throw new NoSuchViewException("No view named %s (it is a table, not a 
view)", ident.name());
+      }
+
+      Map<String, String> params = 
Maps.newHashMap(currentHiveTable.properties());
+      String newSql = currentHiveTable.viewOriginalText();
+      String newName = ident.name();
+
+      for (ViewChange change : changes) {
+        if (change instanceof ViewChange.RenameView) {
+          newName = ((ViewChange.RenameView) change).getNewName();
+        } else if (change instanceof ViewChange.SetProperty) {
+          ViewChange.SetProperty sp = (ViewChange.SetProperty) change;
+          params.put(sp.getProperty(), sp.getValue());
+        } else if (change instanceof ViewChange.RemoveProperty) {
+          params.remove(((ViewChange.RemoveProperty) change).getProperty());
+        } else if (change instanceof ViewChange.ReplaceView) {
+          ViewChange.ReplaceView replace = (ViewChange.ReplaceView) change;
+          SQLRepresentation sqlRepresentation =
+              extractSupportedSqlRepresentation(replace.getRepresentations(), 
ident);
+          if (replace.getComment() == null) {
+            params.remove(COMMENT);
+          } else {
+            params.put(COMMENT, replace.getComment());
+          }
+          newSql = toHmsViewOriginalText(sqlRepresentation, ident);
+        } else {
+          throw new IllegalArgumentException(
+              "Unsupported view change type: " + 
change.getClass().getSimpleName());
+        }
+      }
+
+      final String originalName = ident.name();
+      final String finalNewName = newName;
+      final String finalSql = newSql;
+      HiveTable updatedHiveTable =
+          HiveTable.builder()
+              .withName(finalNewName)
+              .withComment(null)
+              .withColumns(new Column[0])

Review Comment:
   When constructing the updated HMS table during `alterView`, the code again 
hard-codes an empty column list (`withColumns(new Column[0])`). Even if the 
view already has columns in HMS, this risks wiping them out on alter. Prefer 
keeping the existing columns (or applying `ReplaceView` columns) instead of 
overwriting with empty.
   



##########
catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java:
##########
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.hive;
+
+import static org.apache.gravitino.catalog.hive.HiveConstants.COMMENT;
+import static 
org.apache.gravitino.catalog.hive.HiveConstants.HIVE_FILTER_FIELD_PARAMS;
+import static org.apache.gravitino.catalog.hive.HiveConstants.TABLE_TYPE;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchViewException;
+import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
+import org.apache.gravitino.hive.CachedClientPool;
+import org.apache.gravitino.hive.HiveTable;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.SQLRepresentation;
+import org.apache.gravitino.rel.View;
+import org.apache.gravitino.rel.ViewCatalog;
+import org.apache.gravitino.rel.ViewChange;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class HiveViewCatalogOperations implements ViewCatalog {
+  private static final Logger LOG = 
LoggerFactory.getLogger(HiveViewCatalogOperations.class);
+  private static final short MAX_TABLES = -1;
+
+  private final Supplier<CachedClientPool> clientPoolSupplier;
+  private final Supplier<String> catalogNameSupplier;
+  private final Predicate<NameIdentifier> schemaExistsChecker;
+
+  HiveViewCatalogOperations(
+      Supplier<CachedClientPool> clientPoolSupplier,
+      Supplier<String> catalogNameSupplier,
+      Predicate<NameIdentifier> schemaExistsChecker) {
+    this.clientPoolSupplier = clientPoolSupplier;
+    this.catalogNameSupplier = catalogNameSupplier;
+    this.schemaExistsChecker = schemaExistsChecker;
+  }
+
+  @Override
+  public NameIdentifier[] listViews(Namespace namespace) throws 
NoSuchSchemaException {
+    NameIdentifier schemaIdent = NameIdentifier.of(namespace.levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", namespace);
+    }
+    try {
+      String viewFilter =
+          String.format("%stableType like \"VIRTUAL_VIEW\"", 
HIVE_FILTER_FIELD_PARAMS);
+      List<String> views =
+          clientPool()
+              .run(
+                  c ->
+                      c.listTableNamesByFilter(
+                          catalogName(), schemaIdent.name(), viewFilter, 
MAX_TABLES));
+      return views.stream()
+          .map(name -> NameIdentifier.of(namespace, name))
+          .toArray(NameIdentifier[]::new);
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Failed to list Hive views in " + namespace, 
e);
+    }
+  }
+
+  @Override
+  public View loadView(NameIdentifier ident) throws NoSuchViewException {
+    return loadHiveView(ident);
+  }
+
+  @Override
+  public View createView(
+      NameIdentifier ident,
+      String comment,
+      Column[] columns,
+      Representation[] representations,
+      String defaultCatalog,
+      String defaultSchema,
+      Map<String, String> properties)
+      throws NoSuchSchemaException, ViewAlreadyExistsException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", schemaIdent);
+    }
+    SQLRepresentation sqlRepresentation = 
extractSupportedSqlRepresentation(representations, ident);
+
+    try {
+      Map<String, String> params =
+          Maps.newHashMap(properties == null ? ImmutableMap.of() : properties);
+      if (comment != null) {
+        params.put(COMMENT, comment);
+      }
+      params.put(TABLE_TYPE, TableType.VIRTUAL_VIEW.name());
+      String viewOriginalText = toHmsViewOriginalText(sqlRepresentation, 
ident);
+
+      HiveTable hiveTable =
+          HiveTable.builder()
+              .withName(ident.name())
+              .withComment(null)
+              .withColumns(new Column[0])

Review Comment:
   `createView` ignores the provided `columns` and always persists an empty 
column list (`withColumns(new Column[0])`). This loses the view output schema 
even when callers supply it, and prevents the schema from being round-tripped 
on load/alter. Consider persisting the provided columns into the HMS 
StorageDescriptor (via `HiveTable` columns) and returning them from `loadView`.
   



##########
catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java:
##########
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.hive;
+
+import static org.apache.gravitino.catalog.hive.HiveConstants.COMMENT;
+import static 
org.apache.gravitino.catalog.hive.HiveConstants.HIVE_FILTER_FIELD_PARAMS;
+import static org.apache.gravitino.catalog.hive.HiveConstants.TABLE_TYPE;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchViewException;
+import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
+import org.apache.gravitino.hive.CachedClientPool;
+import org.apache.gravitino.hive.HiveTable;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.SQLRepresentation;
+import org.apache.gravitino.rel.View;
+import org.apache.gravitino.rel.ViewCatalog;
+import org.apache.gravitino.rel.ViewChange;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class HiveViewCatalogOperations implements ViewCatalog {
+  private static final Logger LOG = 
LoggerFactory.getLogger(HiveViewCatalogOperations.class);
+  private static final short MAX_TABLES = -1;
+
+  private final Supplier<CachedClientPool> clientPoolSupplier;
+  private final Supplier<String> catalogNameSupplier;
+  private final Predicate<NameIdentifier> schemaExistsChecker;
+
+  HiveViewCatalogOperations(
+      Supplier<CachedClientPool> clientPoolSupplier,
+      Supplier<String> catalogNameSupplier,
+      Predicate<NameIdentifier> schemaExistsChecker) {
+    this.clientPoolSupplier = clientPoolSupplier;
+    this.catalogNameSupplier = catalogNameSupplier;
+    this.schemaExistsChecker = schemaExistsChecker;
+  }
+
+  @Override
+  public NameIdentifier[] listViews(Namespace namespace) throws 
NoSuchSchemaException {
+    NameIdentifier schemaIdent = NameIdentifier.of(namespace.levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", namespace);
+    }
+    try {
+      String viewFilter =
+          String.format("%stableType like \"VIRTUAL_VIEW\"", 
HIVE_FILTER_FIELD_PARAMS);
+      List<String> views =
+          clientPool()
+              .run(
+                  c ->
+                      c.listTableNamesByFilter(
+                          catalogName(), schemaIdent.name(), viewFilter, 
MAX_TABLES));
+      return views.stream()
+          .map(name -> NameIdentifier.of(namespace, name))
+          .toArray(NameIdentifier[]::new);
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Failed to list Hive views in " + namespace, 
e);
+    }
+  }
+
+  @Override
+  public View loadView(NameIdentifier ident) throws NoSuchViewException {
+    return loadHiveView(ident);
+  }
+
+  @Override
+  public View createView(
+      NameIdentifier ident,
+      String comment,
+      Column[] columns,
+      Representation[] representations,
+      String defaultCatalog,
+      String defaultSchema,
+      Map<String, String> properties)
+      throws NoSuchSchemaException, ViewAlreadyExistsException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", schemaIdent);
+    }
+    SQLRepresentation sqlRepresentation = 
extractSupportedSqlRepresentation(representations, ident);
+
+    try {
+      Map<String, String> params =
+          Maps.newHashMap(properties == null ? ImmutableMap.of() : properties);
+      if (comment != null) {
+        params.put(COMMENT, comment);
+      }
+      params.put(TABLE_TYPE, TableType.VIRTUAL_VIEW.name());
+      String viewOriginalText = toHmsViewOriginalText(sqlRepresentation, 
ident);
+
+      HiveTable hiveTable =
+          HiveTable.builder()
+              .withName(ident.name())
+              .withComment(null)
+              .withColumns(new Column[0])
+              .withProperties(params)
+              .withAuditInfo(
+                  AuditInfo.builder()
+                      .withCreator(PrincipalUtils.getCurrentUserName())
+                      .withCreateTime(Instant.now())
+                      .build())
+              .withCatalogName(catalogName())
+              .withDatabaseName(schemaIdent.name())
+              .withViewOriginalText(viewOriginalText)
+              .build();
+
+      clientPool()
+          .run(
+              c -> {
+                c.createTable(hiveTable);
+                return null;
+              });
+
+      LOG.info("Created Hive view {} in Hive Metastore", ident.name());
+      return loadHiveView(ident);
+    } catch (Exception e) {
+      if (isAlreadyExistsError(e)) {
+        throw new ViewAlreadyExistsException("View %s already exists in Hive 
Metastore", ident);
+      }
+      throw new RuntimeException("Failed to create Hive view " + ident, e);
+    }
+  }
+
+  @Override
+  public View alterView(NameIdentifier ident, ViewChange... changes)
+      throws NoSuchViewException, ViewAlreadyExistsException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+
+    for (ViewChange change : changes) {
+      if (change instanceof ViewChange.RenameView) {
+        NameIdentifier targetIdent =
+            NameIdentifier.of(ident.namespace(), ((ViewChange.RenameView) 
change).getNewName());
+        if (viewExists(targetIdent)) {
+          throw new ViewAlreadyExistsException(
+              "View %s already exists in Hive Metastore", targetIdent);
+        }
+      }
+    }
+
+    try {
+      HiveTable currentHiveTable =
+          clientPool().run(c -> c.getTable(catalogName(), schemaIdent.name(), 
ident.name()));
+      if (!TableType.VIRTUAL_VIEW
+          .name()
+          .equalsIgnoreCase(currentHiveTable.properties().get(TABLE_TYPE))) {
+        throw new NoSuchViewException("No view named %s (it is a table, not a 
view)", ident.name());
+      }
+
+      Map<String, String> params = 
Maps.newHashMap(currentHiveTable.properties());
+      String newSql = currentHiveTable.viewOriginalText();
+      String newName = ident.name();
+
+      for (ViewChange change : changes) {
+        if (change instanceof ViewChange.RenameView) {
+          newName = ((ViewChange.RenameView) change).getNewName();
+        } else if (change instanceof ViewChange.SetProperty) {
+          ViewChange.SetProperty sp = (ViewChange.SetProperty) change;
+          params.put(sp.getProperty(), sp.getValue());
+        } else if (change instanceof ViewChange.RemoveProperty) {
+          params.remove(((ViewChange.RemoveProperty) change).getProperty());
+        } else if (change instanceof ViewChange.ReplaceView) {
+          ViewChange.ReplaceView replace = (ViewChange.ReplaceView) change;
+          SQLRepresentation sqlRepresentation =
+              extractSupportedSqlRepresentation(replace.getRepresentations(), 
ident);
+          if (replace.getComment() == null) {
+            params.remove(COMMENT);
+          } else {
+            params.put(COMMENT, replace.getComment());
+          }
+          newSql = toHmsViewOriginalText(sqlRepresentation, ident);

Review Comment:
   `alterView` handles `ViewChange.ReplaceView` by updating only the SQL text 
and comment, but ignores `replace.getColumns()`, `replace.getDefaultCatalog()` 
and `replace.getDefaultSchema()`. `ReplaceView` is defined as an atomic 
replacement of the view body (columns/representations/defaults/comment), so 
this will silently drop caller intent. Either apply these fields (e.g., update 
HMS columns/defaults) or reject non-null/non-empty values with a clear 
`IllegalArgumentException`/`UnsupportedOperationException`.



##########
catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java:
##########
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.hive;
+
+import static org.apache.gravitino.catalog.hive.HiveConstants.COMMENT;
+import static 
org.apache.gravitino.catalog.hive.HiveConstants.HIVE_FILTER_FIELD_PARAMS;
+import static org.apache.gravitino.catalog.hive.HiveConstants.TABLE_TYPE;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchViewException;
+import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
+import org.apache.gravitino.hive.CachedClientPool;
+import org.apache.gravitino.hive.HiveTable;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.SQLRepresentation;
+import org.apache.gravitino.rel.View;
+import org.apache.gravitino.rel.ViewCatalog;
+import org.apache.gravitino.rel.ViewChange;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class HiveViewCatalogOperations implements ViewCatalog {
+  private static final Logger LOG = 
LoggerFactory.getLogger(HiveViewCatalogOperations.class);
+  private static final short MAX_TABLES = -1;
+
+  private final Supplier<CachedClientPool> clientPoolSupplier;
+  private final Supplier<String> catalogNameSupplier;
+  private final Predicate<NameIdentifier> schemaExistsChecker;
+
+  HiveViewCatalogOperations(
+      Supplier<CachedClientPool> clientPoolSupplier,
+      Supplier<String> catalogNameSupplier,
+      Predicate<NameIdentifier> schemaExistsChecker) {
+    this.clientPoolSupplier = clientPoolSupplier;
+    this.catalogNameSupplier = catalogNameSupplier;
+    this.schemaExistsChecker = schemaExistsChecker;
+  }
+
+  @Override
+  public NameIdentifier[] listViews(Namespace namespace) throws 
NoSuchSchemaException {
+    NameIdentifier schemaIdent = NameIdentifier.of(namespace.levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", namespace);
+    }
+    try {
+      String viewFilter =
+          String.format("%stableType like \"VIRTUAL_VIEW\"", 
HIVE_FILTER_FIELD_PARAMS);
+      List<String> views =
+          clientPool()
+              .run(
+                  c ->
+                      c.listTableNamesByFilter(
+                          catalogName(), schemaIdent.name(), viewFilter, 
MAX_TABLES));
+      return views.stream()
+          .map(name -> NameIdentifier.of(namespace, name))
+          .toArray(NameIdentifier[]::new);
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Failed to list Hive views in " + namespace, 
e);
+    }
+  }
+
+  @Override
+  public View loadView(NameIdentifier ident) throws NoSuchViewException {
+    return loadHiveView(ident);
+  }
+
+  @Override
+  public View createView(
+      NameIdentifier ident,
+      String comment,
+      Column[] columns,
+      Representation[] representations,
+      String defaultCatalog,
+      String defaultSchema,
+      Map<String, String> properties)
+      throws NoSuchSchemaException, ViewAlreadyExistsException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+    if (!schemaExistsChecker.test(schemaIdent)) {
+      throw new NoSuchSchemaException("Schema %s does not exist", schemaIdent);
+    }
+    SQLRepresentation sqlRepresentation = 
extractSupportedSqlRepresentation(representations, ident);
+
+    try {
+      Map<String, String> params =
+          Maps.newHashMap(properties == null ? ImmutableMap.of() : properties);
+      if (comment != null) {
+        params.put(COMMENT, comment);
+      }
+      params.put(TABLE_TYPE, TableType.VIRTUAL_VIEW.name());
+      String viewOriginalText = toHmsViewOriginalText(sqlRepresentation, 
ident);
+
+      HiveTable hiveTable =
+          HiveTable.builder()
+              .withName(ident.name())
+              .withComment(null)
+              .withColumns(new Column[0])
+              .withProperties(params)
+              .withAuditInfo(
+                  AuditInfo.builder()
+                      .withCreator(PrincipalUtils.getCurrentUserName())
+                      .withCreateTime(Instant.now())
+                      .build())
+              .withCatalogName(catalogName())
+              .withDatabaseName(schemaIdent.name())
+              .withViewOriginalText(viewOriginalText)
+              .build();
+
+      clientPool()
+          .run(
+              c -> {
+                c.createTable(hiveTable);
+                return null;
+              });
+
+      LOG.info("Created Hive view {} in Hive Metastore", ident.name());
+      return loadHiveView(ident);
+    } catch (Exception e) {
+      if (isAlreadyExistsError(e)) {
+        throw new ViewAlreadyExistsException("View %s already exists in Hive 
Metastore", ident);
+      }
+      throw new RuntimeException("Failed to create Hive view " + ident, e);
+    }
+  }
+
+  @Override
+  public View alterView(NameIdentifier ident, ViewChange... changes)
+      throws NoSuchViewException, ViewAlreadyExistsException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+
+    for (ViewChange change : changes) {
+      if (change instanceof ViewChange.RenameView) {
+        NameIdentifier targetIdent =
+            NameIdentifier.of(ident.namespace(), ((ViewChange.RenameView) 
change).getNewName());
+        if (viewExists(targetIdent)) {
+          throw new ViewAlreadyExistsException(
+              "View %s already exists in Hive Metastore", targetIdent);
+        }
+      }
+    }
+
+    try {
+      HiveTable currentHiveTable =
+          clientPool().run(c -> c.getTable(catalogName(), schemaIdent.name(), 
ident.name()));
+      if (!TableType.VIRTUAL_VIEW
+          .name()
+          .equalsIgnoreCase(currentHiveTable.properties().get(TABLE_TYPE))) {
+        throw new NoSuchViewException("No view named %s (it is a table, not a 
view)", ident.name());
+      }
+
+      Map<String, String> params = 
Maps.newHashMap(currentHiveTable.properties());
+      String newSql = currentHiveTable.viewOriginalText();
+      String newName = ident.name();
+
+      for (ViewChange change : changes) {
+        if (change instanceof ViewChange.RenameView) {
+          newName = ((ViewChange.RenameView) change).getNewName();
+        } else if (change instanceof ViewChange.SetProperty) {
+          ViewChange.SetProperty sp = (ViewChange.SetProperty) change;
+          params.put(sp.getProperty(), sp.getValue());
+        } else if (change instanceof ViewChange.RemoveProperty) {
+          params.remove(((ViewChange.RemoveProperty) change).getProperty());
+        } else if (change instanceof ViewChange.ReplaceView) {
+          ViewChange.ReplaceView replace = (ViewChange.ReplaceView) change;
+          SQLRepresentation sqlRepresentation =
+              extractSupportedSqlRepresentation(replace.getRepresentations(), 
ident);
+          if (replace.getComment() == null) {
+            params.remove(COMMENT);
+          } else {
+            params.put(COMMENT, replace.getComment());
+          }
+          newSql = toHmsViewOriginalText(sqlRepresentation, ident);
+        } else {
+          throw new IllegalArgumentException(
+              "Unsupported view change type: " + 
change.getClass().getSimpleName());
+        }
+      }
+
+      final String originalName = ident.name();
+      final String finalNewName = newName;
+      final String finalSql = newSql;
+      HiveTable updatedHiveTable =
+          HiveTable.builder()
+              .withName(finalNewName)
+              .withComment(null)
+              .withColumns(new Column[0])
+              .withProperties(params)
+              .withAuditInfo(currentHiveTable.auditInfo())
+              .withCatalogName(catalogName())
+              .withDatabaseName(schemaIdent.name())
+              .withViewOriginalText(finalSql)
+              .build();
+
+      clientPool()
+          .run(
+              c -> {
+                c.alterTable(catalogName(), schemaIdent.name(), originalName, 
updatedHiveTable);
+                return null;
+              });
+
+      LOG.info("Altered Hive view {} (now {})", ident.name(), finalNewName);
+      return loadHiveView(NameIdentifier.of(ident.namespace(), finalNewName));
+    } catch (NoSuchViewException | ViewAlreadyExistsException e) {
+      throw e;
+    } catch (UnsupportedOperationException e) {
+      throw e;
+    } catch (Exception e) {
+      if (isAlreadyExistsError(e)) {
+        throw new ViewAlreadyExistsException(
+            "View %s already exists in Hive Metastore",
+            NameIdentifier.of(ident.namespace(), 
extractRenameTargetName(ident.name(), changes)));
+      }
+      throw new RuntimeException("Failed to alter Hive view " + ident, e);
+    }
+  }
+
+  @Override
+  public boolean dropView(NameIdentifier ident) {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+    try {
+      clientPool()
+          .run(
+              c -> {
+                c.dropTable(catalogName(), schemaIdent.name(), ident.name(), 
false, false);
+                return null;
+              });
+      LOG.info("Dropped Hive view {}", ident.name());
+      return true;
+    } catch (Exception e) {
+      if (isNotFoundError(e)) {
+        return false;
+      }
+      throw new RuntimeException("Failed to drop Hive view " + ident, e);
+    }
+  }
+
+  @Override
+  public boolean viewExists(NameIdentifier ident) {
+    try {
+      loadHiveView(ident);
+      return true;
+    } catch (NoSuchViewException e) {
+      return false;
+    }
+  }
+
+  private HiveView loadHiveView(NameIdentifier ident) throws 
NoSuchViewException {
+    NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
+    try {
+      HiveTable hiveTable =
+          clientPool().run(c -> c.getTable(catalogName(), schemaIdent.name(), 
ident.name()));
+      if 
(!TableType.VIRTUAL_VIEW.name().equalsIgnoreCase(hiveTable.properties().get(TABLE_TYPE)))
 {
+        throw new NoSuchViewException("No view named %s (it is a table, not a 
view)", ident);
+      }
+
+      Map<String, String> params =
+          Maps.newHashMap(
+              hiveTable.properties() != null ? hiveTable.properties() : 
ImmutableMap.of());
+      String viewOriginalText = hiveTable.viewOriginalText();
+      String detectedDialect = HiveView.detectDialect(viewOriginalText, 
params);
+      if (!HiveView.HIVE_DIALECT.equalsIgnoreCase(detectedDialect)) {
+        // TODO(design-docs/gravitino-logical-view-management.md): support 
loading trino/spark HMS
+        // views.
+        throw new UnsupportedOperationException(
+            String.format(
+                "Hive catalog currently supports only '%s' view dialect, but 
found '%s' for view %s",
+                HiveView.HIVE_DIALECT, detectedDialect, ident));
+      }
+
+      SQLRepresentation rep =
+          SQLRepresentation.builder()
+              .withDialect(HiveView.HIVE_DIALECT)
+              .withSql(viewOriginalText != null ? viewOriginalText : "")
+              .build();
+
+      return HiveView.builder()
+          .withName(ident.name())
+          .withComment(params.get(COMMENT))
+          .withColumns(new Column[0])
+          .withRepresentations(new SQLRepresentation[] {rep})
+          .withProperties(params)
+          .withAuditInfo(hiveTable.auditInfo())
+          .build();

Review Comment:
   `loadHiveView` always returns `withColumns(new Column[0])` even though 
`HiveTable` already contains parsed columns from HMS (via 
`HiveTableConverter.getColumns`). This prevents callers from seeing the view 
schema and makes `View.columns()` effectively unusable for Hive views. Use the 
columns from `hiveTable.columns()` (or document/reject if unavailable).



##########
catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/converter/HiveTableConverter.java:
##########
@@ -106,6 +108,14 @@ private static Map<String, String> buildTableProperties(
 
     Optional.ofNullable(table.getTableType()).ifPresent(t -> 
properties.put(TABLE_TYPE, t));
 
+    // VIRTUAL_VIEW tables may have a minimal or absent StorageDescriptor — 
skip SD fields.
+    if (TableType.VIRTUAL_VIEW.name().equalsIgnoreCase(table.getTableType())) {
+      // Remove the HMS-internal "tableType" key added by Gravitino; the 
canonical TABLE_TYPE
+      // property (key "table-type") is set above via table.getTableType().
+      properties.remove("tableType");
+      return properties;
+    }

Review Comment:
   This method notes that VIRTUAL_VIEW tables may have a minimal/absent 
`StorageDescriptor` and skips SD fields, but `fromHiveTable` still calls 
`getDistribution/getSortOrders/getColumns`, each of which dereferences 
`table.getSd()` without null checks. If a view comes back without an SD, this 
will NPE before reaching this early return. Either enforce a non-null SD for 
views on read, or add null-handling in those helper methods.



##########
catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/converter/HiveTableConverter.java:
##########
@@ -135,13 +145,27 @@ public static org.apache.hadoop.hive.metastore.api.Table 
toHiveTable(HiveTable h
         hiveTable.properties().getOrDefault(TABLE_TYPE, 
String.valueOf(TableType.MANAGED_TABLE));
     table.setTableType(tableType.toUpperCase());
 
-    List<FieldSchema> partitionFields =
-        hiveTable.partitionFieldNames().stream()
-            .map(fieldName -> buildPartitionKeyField(fieldName, hiveTable))
-            .collect(Collectors.toList());
-    table.setSd(buildStorageDescriptor(hiveTable, partitionFields));
+    if (TableType.VIRTUAL_VIEW.name().equalsIgnoreCase(tableType)) {
+      // Views require a minimal StorageDescriptor (HMS validates its 
presence).
+      StorageDescriptor sd = new StorageDescriptor();
+      sd.setCols(new ArrayList<>());

Review Comment:
   `toHiveTable` builds a VIRTUAL_VIEW with an empty `StorageDescriptor.cols`. 
Hive Metastore typically stores a view's output schema in `sd.cols`, and 
leaving it empty can break DESCRIBE/column resolution and may cause alters to 
wipe schema. Populate `sd.cols` from `hiveTable.columns()` (and keep it in sync 
on alter/replace).
   



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


Reply via email to