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

diqiu50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 5d40ec4403 [#11926] Encode Trino dialect Hive views using Trino's 
native Presto View format (#12294)
5d40ec4403 is described below

commit 5d40ec44034995a3652fc7ad773b78a9acf1b1ce
Author: Yuhui <[email protected]>
AuthorDate: Mon Aug 17 10:47:01 2026 +0800

    [#11926] Encode Trino dialect Hive views using Trino's native Presto View 
format (#12294)
    
    ### What changes were proposed in this pull request?
    Store and read Trino dialect views in the Hive catalog using Trino's own
    native "Presto View" Hive Metastore encoding (via a new
    `TrinoNativeViewCodec`), instead of the previous custom
    marker/prefix-based encoding.
    
    ### Why are the changes needed?
    So Hive-backed views created through Gravitino are interoperable with a
    native Trino/Presto Hive connector pointed at the same Hive Metastore,
    and vice versa. This is the catalog-side (non-Trino-connector) portion
    of #11926, split out for independent review.
    
    ### Does this PR introduce _any_ user-facing change?
    No behavior change for existing non-Trino views; Trino dialect views
    stored via the Hive catalog now use Trino's native encoding format.
    
    ### How was this patch tested?
    Existing and new unit tests in `catalog-hive`
    (`TestHiveCatalogOperations`, `TestTrinoNativeViewCodec`).
---
 catalogs/catalog-hive/build.gradle.kts             |   1 +
 .../catalog/hive/HiveTablePropertiesMetadata.java  |   5 +
 .../apache/gravitino/catalog/hive/HiveView.java    |  33 +-
 .../catalog/hive/HiveViewCatalogOperations.java    | 273 +++++++-
 .../catalog/hive/TrinoNativeViewCodec.java         | 460 +++++++++++++
 .../catalog/hive/TestHiveCatalogOperations.java    | 741 ++++++++++++++++++++-
 .../catalog/hive/TestTrinoNativeViewCodec.java     | 318 +++++++++
 docs/apache-hive-catalog.md                        |   6 +-
 8 files changed, 1767 insertions(+), 70 deletions(-)

diff --git a/catalogs/catalog-hive/build.gradle.kts 
b/catalogs/catalog-hive/build.gradle.kts
index 35bf217757..6f913cb9aa 100644
--- a/catalogs/catalog-hive/build.gradle.kts
+++ b/catalogs/catalog-hive/build.gradle.kts
@@ -49,6 +49,7 @@ dependencies {
   implementation(libs.commons.io)
   implementation(libs.commons.lang3)
   implementation(libs.guava)
+  implementation(libs.jackson.databind)
   implementation(libs.hadoop3.auth) {
     exclude("*")
   }
diff --git 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTablePropertiesMetadata.java
 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTablePropertiesMetadata.java
index fb4bd8ea15..48fa40c9e9 100644
--- 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTablePropertiesMetadata.java
+++ 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTablePropertiesMetadata.java
@@ -50,6 +50,11 @@ public class HiveTablePropertiesMetadata extends 
BasePropertiesMetadata {
     List<PropertyEntry<?>> propertyEntries =
         ImmutableList.of(
             stringReservedPropertyEntry(COMMENT, "table comment", true),
+            stringReservedPropertyEntry(
+                TrinoNativeViewCodec.PRESTO_VIEW_FLAG,
+                "Marks a view stored using Trino's native \"Presto View\" HMS 
format; managed "
+                    + "internally by the Trino dialect view encoding, not 
settable by users",
+                true),
             stringReservedPropertyEntry(NUM_FILES, "number of files", false),
             stringReservedPropertyEntry(TOTAL_SIZE, "total size of the table", 
false),
             booleanReservedPropertyEntry(
diff --git 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveView.java
 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveView.java
index a2aeee7a0b..a3d8da8350 100644
--- 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveView.java
+++ 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveView.java
@@ -40,9 +40,12 @@ import org.apache.gravitino.rel.View;
 
 /**
  * Represents a view stored in Hive Metastore (VIRTUAL_VIEW table type). The 
SQL dialect is detected
- * from table properties: Trino views start with "/* Presto View:", Spark 
views carry {@code
- * spark.sql.create.version}, Flink views carry properties prefixed with 
{@code flink.}, and all
- * other views are treated as native Hive SQL views.
+ * from table properties: Trino views carry the {@code presto_view} HMS 
property with a {@code
+ * comment} of {@code "Presto View"} and are encoded using Trino's own native 
"Presto View" format
+ * (see {@link TrinoNativeViewCodec}), so this catalog is interoperable with 
views created directly
+ * by a native Trino/Presto Hive connector, not only ones created through 
Gravitino. Spark views
+ * carry {@code spark.sql.create.version}, Flink views carry properties 
prefixed with {@code
+ * flink.}, and all other views are treated as native Hive SQL views.
  */
 @Unstable
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -54,8 +57,6 @@ public class HiveView implements View {
 
   static final String SPARK_VERSION_KEY = "spark.sql.create.version";
   static final String FLINK_PROPERTY_PREFIX = "flink.";
-  private static final String TRINO_VIEW_MARKER_KEY = "presto_view";
-  private static final String TRINO_VIEW_PREFIX = "/* Presto View:";
 
   private String name;
   private String comment;
@@ -109,18 +110,24 @@ public class HiveView implements View {
   }
 
   /**
-   * Detects the SQL dialect from HMS table properties and view text.
+   * Detects the SQL dialect from HMS table properties.
    *
-   * @param viewOriginalText The original view text from HMS.
    * @param parameters The HMS table parameters map.
    * @return The detected dialect string: "trino", "spark", "flink", or "hive".
+   * @throws UnsupportedOperationException if the view carries the {@code 
presto_view} marker but is
+   *     not a plain Trino view (e.g. a Trino/Presto materialized view); its 
encoded body cannot be
+   *     read as SQL by any engine.
    */
-  static String detectDialect(String viewOriginalText, Map<String, String> 
parameters) {
-    if (parameters != null && 
"true".equalsIgnoreCase(parameters.get(TRINO_VIEW_MARKER_KEY))) {
-      return Dialects.TRINO;
-    }
-    if (StringUtils.startsWith(viewOriginalText, TRINO_VIEW_PREFIX)) {
-      return Dialects.TRINO;
+  static String detectDialect(Map<String, String> parameters) {
+    if (parameters != null
+        && 
"true".equalsIgnoreCase(parameters.get(TrinoNativeViewCodec.PRESTO_VIEW_FLAG))) 
{
+      if (TrinoNativeViewCodec.PRESTO_VIEW_COMMENT.equalsIgnoreCase(
+          parameters.get(HiveConstants.COMMENT))) {
+        return Dialects.TRINO;
+      }
+      throw new UnsupportedOperationException(
+          "View carries the presto_view marker but is not a plain Trino view 
(e.g. a Trino/Presto "
+              + "materialized view); its encoded body cannot be read by 
Gravitino");
     }
     if (parameters != null && parameters.containsKey(SPARK_VERSION_KEY)) {
       return Dialects.SPARK;
diff --git 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
index f3d078ff2e..7fcd812e5e 100644
--- 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
+++ 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java
@@ -26,11 +26,14 @@ 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.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.NameIdentifier;
@@ -50,12 +53,33 @@ 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.rel.types.Types;
 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 String SUPPORTED_VIEW_DIALECTS =
+      String.join(", ", Dialects.HIVE, Dialects.TRINO, Dialects.FLINK, 
Dialects.SPARK);
+
+  /**
+   * The HMS-level representation derived from a logical view definition: the 
columns, comment, and
+   * {@code viewOriginalText} actually stored on the underlying HMS table. For 
a Trino dialect view
+   * these differ from the caller-supplied values (see {@link 
#encodeHmsView}); for every other
+   * dialect they are passed through unchanged.
+   */
+  private static final class HmsViewEncoding {
+    final Column[] columns;
+    final String comment;
+    final String viewOriginalText;
+
+    HmsViewEncoding(Column[] columns, String comment, String viewOriginalText) 
{
+      this.columns = columns;
+      this.comment = comment;
+      this.viewOriginalText = viewOriginalText;
+    }
+  }
 
   private final Supplier<CachedClientPool> clientPoolSupplier;
   private final Supplier<String> catalogNameSupplier;
@@ -116,18 +140,20 @@ class HiveViewCatalogOperations implements ViewCatalog {
     Map<String, String> safeProperties = properties == null ? 
ImmutableMap.of() : properties;
     SQLRepresentation sqlRepresentation =
         validateSQLRepresentation(
-            representations, defaultCatalog, defaultSchema, safeProperties, 
ident);
+            representations, defaultCatalog, defaultSchema, safeProperties, 
columns, ident);
 
     try {
       Map<String, String> params = Maps.newHashMap(safeProperties);
       params.put(TABLE_TYPE, TableType.VIRTUAL_VIEW.name());
-      String viewOriginalText = toHmsViewOriginalText(sqlRepresentation, 
ident);
+      HmsViewEncoding encoding =
+          encodeHmsView(
+              sqlRepresentation, columns, comment, defaultCatalog, 
defaultSchema, params, ident);
 
       HiveTable hiveTable =
           HiveTable.builder()
               .withName(ident.name())
-              .withComment(comment)
-              .withColumns(copyColumns(columns))
+              .withComment(encoding.comment)
+              .withColumns(encoding.columns)
               .withProperties(params)
               .withAuditInfo(
                   AuditInfo.builder()
@@ -136,7 +162,7 @@ class HiveViewCatalogOperations implements ViewCatalog {
                       .build())
               .withCatalogName(catalogName())
               .withDatabaseName(schemaIdent.name())
-              .withViewOriginalText(viewOriginalText)
+              .withViewOriginalText(encoding.viewOriginalText)
               .build();
 
       clientPool()
@@ -177,6 +203,34 @@ class HiveViewCatalogOperations implements ViewCatalog {
         throw new NoSuchViewException("No view named %s (it is a table, not a 
view)", ident.name());
       }
 
+      // Reuse the same dialect detection as loadHiveView()/toHiveView() so 
that a presto_view
+      // entry that is not a plain Trino view (e.g. a Trino/Presto 
materialized view) is rejected
+      // here too, instead of being silently treated as a non-Trino view.
+      boolean isTrinoView =
+          
Dialects.TRINO.equalsIgnoreCase(HiveView.detectDialect(currentHiveTable.properties()));
+      // Gravitino's view model has no owner/runAsInvoker/path concept, so 
replacing a native Trino
+      // view that carries a non-default value for any of them would silently 
discard it (e.g. a
+      // SECURITY DEFINER view with an owner would silently become an 
ownerless SECURITY INVOKER
+      // view). Reject the replace instead of doing that.
+      boolean currentTrinoViewHasUnrepresentableFields = false;
+      if (isTrinoView) {
+        TrinoNativeViewCodec.ViewDefinition currentDefinition;
+        try {
+          currentDefinition = 
TrinoNativeViewCodec.decode(currentHiveTable.viewOriginalText());
+        } catch (IllegalArgumentException e) {
+          throw new UnsupportedOperationException(
+              "View "
+                  + ident
+                  + " carries the presto_view marker but its payload cannot be 
"
+                  + "decoded",
+              e);
+        }
+        currentTrinoViewHasUnrepresentableFields =
+            currentDefinition.owner != null
+                || !currentDefinition.runAsInvoker
+                || !currentDefinition.path.isEmpty();
+      }
+
       String newViewName = currentHiveTable.name();
       String updatedViewOriginalText = currentHiveTable.viewOriginalText();
       Map<String, String> updatedProperties = 
Maps.newHashMap(currentHiveTable.properties());
@@ -196,18 +250,48 @@ class HiveViewCatalogOperations implements ViewCatalog {
         } else if (change instanceof ViewChange.SetProperty) {
           ViewChange.SetProperty sp = (ViewChange.SetProperty) change;
           if (COMMENT.equals(sp.getProperty())) {
+            if (isTrinoView) {
+              throw new UnsupportedOperationException(
+                  "Trino dialect views store their comment inside the encoded 
view payload; use "
+                      + "ReplaceView to change it, not SetProperty(comment)");
+            }
             updatedComment = sp.getValue();
+          } else if 
(TrinoNativeViewCodec.PRESTO_VIEW_FLAG.equals(sp.getProperty())) {
+            throw new UnsupportedOperationException(
+                "Property '"
+                    + TrinoNativeViewCodec.PRESTO_VIEW_FLAG
+                    + "' is reserved for native Trino view storage and cannot 
be set directly; "
+                    + "use ReplaceView to change the view's dialect");
           } else {
             updatedProperties.put(sp.getProperty(), sp.getValue());
           }
         } else if (change instanceof ViewChange.RemoveProperty) {
           String property = ((ViewChange.RemoveProperty) change).getProperty();
           if (COMMENT.equals(property)) {
+            if (isTrinoView) {
+              throw new UnsupportedOperationException(
+                  "Trino dialect views store their comment inside the encoded 
view payload; use "
+                      + "ReplaceView to change it, not 
RemoveProperty(comment)");
+            }
             updatedComment = null;
+          } else if (TrinoNativeViewCodec.PRESTO_VIEW_FLAG.equals(property)) {
+            throw new UnsupportedOperationException(
+                "Property '"
+                    + TrinoNativeViewCodec.PRESTO_VIEW_FLAG
+                    + "' is reserved for native Trino view storage and cannot 
be removed "
+                    + "directly; use ReplaceView to change the view's 
dialect");
           } else {
             updatedProperties.remove(property);
           }
         } else if (change instanceof ViewChange.ReplaceView) {
+          if (currentTrinoViewHasUnrepresentableFields) {
+            throw new UnsupportedOperationException(
+                "View "
+                    + ident
+                    + " is a native Trino view with a non-default owner, 
runAsInvoker, or SQL "
+                    + "path; Gravitino cannot represent these fields, so 
replacing it would "
+                    + "silently discard them");
+          }
           ViewChange.ReplaceView replace = (ViewChange.ReplaceView) change;
           SQLRepresentation sqlRepresentation =
               validateSQLRepresentation(
@@ -215,10 +299,21 @@ class HiveViewCatalogOperations implements ViewCatalog {
                   replace.getDefaultCatalog(),
                   replace.getDefaultSchema(),
                   updatedProperties,
+                  replace.getColumns(),
                   ident);
-          updatedColumns = copyColumns(replace.getColumns());
-          updatedComment = replace.getComment();
-          updatedViewOriginalText = toHmsViewOriginalText(sqlRepresentation, 
ident);
+          HmsViewEncoding encoding =
+              encodeHmsView(
+                  sqlRepresentation,
+                  replace.getColumns(),
+                  replace.getComment(),
+                  replace.getDefaultCatalog(),
+                  replace.getDefaultSchema(),
+                  updatedProperties,
+                  ident);
+          updatedColumns = encoding.columns;
+          updatedViewOriginalText = encoding.viewOriginalText;
+          updatedComment = encoding.comment;
+          isTrinoView = 
Dialects.TRINO.equalsIgnoreCase(sqlRepresentation.dialect());
         } else {
           throw new IllegalArgumentException(
               "Unsupported view change type: " + 
change.getClass().getSimpleName());
@@ -324,6 +419,11 @@ class HiveViewCatalogOperations implements ViewCatalog {
       return true;
     } catch (NoSuchViewException e) {
       return false;
+    } catch (UnsupportedOperationException e) {
+      // The HMS entry exists but Gravitino cannot fully interpret it (e.g. a 
materialized view or
+      // an undecodable native Trino view payload); treat it as existing so 
callers (e.g. a rename
+      // target check) don't collide with it.
+      return true;
     }
   }
 
@@ -364,19 +464,52 @@ class HiveViewCatalogOperations implements ViewCatalog {
       AuditInfo auditInfo) {
     Map<String, String> params =
         Maps.newHashMap(properties != null ? properties : ImmutableMap.of());
-    String representationSql = viewOriginalText;
-    String detectedDialect = HiveView.detectDialect(representationSql, params);
+    String detectedDialect = HiveView.detectDialect(params);
     switch (detectedDialect.toLowerCase(Locale.ROOT)) {
       case Dialects.HIVE:
+      case Dialects.TRINO:
       case Dialects.FLINK:
       case Dialects.SPARK:
         break;
       default:
-        // TODO(design-docs/gravitino-logical-view-management.md): support 
loading trino HMS views.
         throw new UnsupportedOperationException(
             String.format(
-                "Hive catalog currently supports only '%s', '%s' and '%s' view 
dialects, but found '%s' for view %s",
-                Dialects.HIVE, Dialects.FLINK, Dialects.SPARK, 
detectedDialect, ident));
+                "Hive catalog currently supports only [%s] view dialects, but 
found '%s' for view %s",
+                SUPPORTED_VIEW_DIALECTS, detectedDialect, ident));
+    }
+
+    String representationSql;
+    String resolvedComment;
+    String restoredDefaultCatalog = null;
+    String restoredDefaultSchema = null;
+    Column[] resolvedColumns;
+    if (Dialects.TRINO.equalsIgnoreCase(detectedDialect)) {
+      // Trino dialect views are encoded using Trino's native "Presto View" 
format, so the SQL,
+      // comment, default catalog/schema, and real columns all live in the 
encoded payload; the
+      // underlying HMS table only carries a single dummy column (see 
hmsColumns()).
+      TrinoNativeViewCodec.ViewDefinition decoded;
+      try {
+        decoded = TrinoNativeViewCodec.decode(viewOriginalText);
+      } catch (IllegalArgumentException e) {
+        throw new UnsupportedOperationException(
+            "View " + ident + " carries the presto_view marker but its payload 
cannot be decoded",
+            e);
+      }
+      representationSql = decoded.originalSql;
+      resolvedComment = decoded.comment;
+      restoredDefaultCatalog = decoded.catalog;
+      restoredDefaultSchema = decoded.schema;
+      resolvedColumns =
+          decoded.columns.stream()
+              .map(
+                  c ->
+                      Column.of(
+                          c.name, 
TrinoNativeViewCodec.fromTrinoTypeString(c.type), c.comment))
+              .toArray(Column[]::new);
+    } else {
+      representationSql = viewOriginalText;
+      resolvedComment = comment;
+      resolvedColumns = copyColumns(columns);
     }
 
     SQLRepresentation rep =
@@ -387,11 +520,13 @@ class HiveViewCatalogOperations implements ViewCatalog {
 
     return HiveView.builder()
         .withName(ident.name())
-        .withComment(comment)
-        .withColumns(copyColumns(columns))
+        .withComment(resolvedComment)
+        .withColumns(resolvedColumns)
         .withRepresentations(new SQLRepresentation[] {rep})
         .withProperties(params)
         .withAuditInfo(auditInfo)
+        .withDefaultCatalog(restoredDefaultCatalog)
+        .withDefaultSchema(restoredDefaultSchema)
         .build();
   }
 
@@ -407,6 +542,7 @@ class HiveViewCatalogOperations implements ViewCatalog {
       String defaultCatalog,
       String defaultSchema,
       Map<String, String> properties,
+      Column[] columns,
       NameIdentifier ident) {
     int representationCount = representations == null ? 0 : 
representations.length;
     Representation firstRepresentation =
@@ -431,6 +567,23 @@ class HiveViewCatalogOperations implements ViewCatalog {
             defaultSchema,
             ident);
         return selected;
+      case Dialects.TRINO:
+        // The default catalog/schema are encoded into the Trino native view 
payload by
+        // toHmsViewOriginalText() and restored in toHiveView(), so no value 
is required to be null
+        // here.
+        Preconditions.checkArgument(
+            columns != null && columns.length > 0,
+            "Dialect '%s' requires at least one column for view %s; without it 
the encoded "
+                + "payload cannot be decoded on the next load",
+            selected.dialect(),
+            ident);
+        Preconditions.checkArgument(
+            defaultSchema == null || defaultCatalog != null,
+            "Dialect '%s' does not support a defaultSchema without a 
defaultCatalog for view "
+                + "%s, since Trino's native view format rejects such a 
payload",
+            selected.dialect(),
+            ident);
+        return selected;
       case Dialects.FLINK:
         Preconditions.checkArgument(
             defaultCatalog == null && defaultSchema == null,
@@ -457,27 +610,88 @@ class HiveViewCatalogOperations implements ViewCatalog {
             HiveView.SPARK_VERSION_KEY);
         return selected;
       default:
-        // TODO(design-docs/gravitino-logical-view-management.md): support 
creating trino HMS views.
         throw new UnsupportedOperationException(
             String.format(
-                "Hive catalog currently supports only '%s', '%s' and '%s' view 
dialects, but got '%s' for view %s",
-                Dialects.HIVE, Dialects.FLINK, Dialects.SPARK, 
selected.dialect(), ident));
+                "Hive catalog currently supports only [%s] view dialects, but 
got '%s' for view %s",
+                SUPPORTED_VIEW_DIALECTS, selected.dialect(), ident));
+    }
+  }
+
+  /**
+   * Sets or clears the {@code presto_view} marker in the given HMS property 
map, so that a Trino
+   * dialect view is recognized as a native Trino view (see {@link 
TrinoNativeViewCodec}).
+   */
+  private void applyTrinoViewMarker(Map<String, String> params, String 
dialect) {
+    if (!Dialects.TRINO.equalsIgnoreCase(dialect)) {
+      params.remove(TrinoNativeViewCodec.PRESTO_VIEW_FLAG);
+      return;
     }
+    params.put(TrinoNativeViewCodec.PRESTO_VIEW_FLAG, "true");
   }
 
-  private String toHmsViewOriginalText(SQLRepresentation representation, 
NameIdentifier ident) {
+  /**
+   * Derives the HMS-level representation of a view from its logical 
definition, shared by {@link
+   * #createView} and the {@code ReplaceView} branch of {@link #alterView}. 
The caller supplies the
+   * logical SQL representation, output columns, user comment, and default 
catalog/schema; {@code
+   * properties} is mutated in place to set or clear the {@code presto_view} 
marker.
+   */
+  private HmsViewEncoding encodeHmsView(
+      SQLRepresentation sqlRepresentation,
+      Column[] columns,
+      String comment,
+      String defaultCatalog,
+      String defaultSchema,
+      Map<String, String> properties,
+      NameIdentifier ident) {
+    applyTrinoViewMarker(properties, sqlRepresentation.dialect());
+    String viewOriginalText =
+        toHmsViewOriginalText(
+            sqlRepresentation, columns, comment, defaultCatalog, 
defaultSchema, ident);
+    String hmsComment =
+        Dialects.TRINO.equalsIgnoreCase(sqlRepresentation.dialect())
+            ? TrinoNativeViewCodec.PRESTO_VIEW_COMMENT
+            : comment;
+    return new HmsViewEncoding(
+        hmsColumns(columns, sqlRepresentation.dialect()), hmsComment, 
viewOriginalText);
+  }
+
+  private String toHmsViewOriginalText(
+      SQLRepresentation representation,
+      Column[] columns,
+      String comment,
+      String defaultCatalog,
+      String defaultSchema,
+      NameIdentifier ident) {
     switch (representation.dialect().toLowerCase(Locale.ROOT)) {
       case Dialects.HIVE:
       case Dialects.FLINK:
       case Dialects.SPARK:
         return representation.sql();
+      case Dialects.TRINO:
+        List<TrinoNativeViewCodec.ViewColumn> viewColumns =
+            Arrays.stream(columns == null ? new Column[0] : columns)
+                .map(
+                    c ->
+                        new TrinoNativeViewCodec.ViewColumn(
+                            c.name(),
+                            
TrinoNativeViewCodec.toTrinoTypeString(c.dataType()),
+                            c.comment()))
+                .collect(Collectors.toList());
+        return TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                representation.sql(),
+                defaultCatalog,
+                defaultSchema,
+                viewColumns,
+                comment,
+                /* owner= */ null,
+                /* runAsInvoker= */ true,
+                /* path= */ Collections.emptyList()));
       default:
-        // TODO(design-docs/gravitino-logical-view-management.md): support 
serializing trino HMS
-        // view definitions.
         throw new UnsupportedOperationException(
             String.format(
-                "Hive catalog currently supports only '%s', '%s' and '%s' view 
dialects, but got '%s' for view %s",
-                Dialects.HIVE, Dialects.FLINK, Dialects.SPARK, 
representation.dialect(), ident));
+                "Hive catalog currently supports only [%s] view dialects, but 
got '%s' for view %s",
+                SUPPORTED_VIEW_DIALECTS, representation.dialect(), ident));
     }
   }
 
@@ -494,6 +708,19 @@ class HiveViewCatalogOperations implements ViewCatalog {
     return columns == null ? new Column[0] : columns.clone();
   }
 
+  /**
+   * Builds the columns to store on the underlying HMS table. Trino dialect 
views store their real
+   * columns inside the encoded Trino native view payload (see {@link 
#toHmsViewOriginalText}), so
+   * the HMS table itself only carries a single dummy column, matching real 
Trino's own behavior
+   * (see {@code io.trino.plugin.hive.HiveMetadata#createView}).
+   */
+  private Column[] hmsColumns(Column[] columns, String dialect) {
+    if (Dialects.TRINO.equalsIgnoreCase(dialect)) {
+      return new Column[] {Column.of("dummy", Types.StringType.get())};
+    }
+    return copyColumns(columns);
+  }
+
   private CachedClientPool clientPool() {
     return clientPoolSupplier.get();
   }
diff --git 
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/TrinoNativeViewCodec.java
 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/TrinoNativeViewCodec.java
new file mode 100644
index 0000000000..8962aa67d6
--- /dev/null
+++ 
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/TrinoNativeViewCodec.java
@@ -0,0 +1,460 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.Locale;
+import javax.annotation.Nullable;
+import org.apache.gravitino.rel.types.Type;
+import org.apache.gravitino.rel.types.Types;
+
+/**
+ * Encodes and decodes Trino/Presto's native "Presto View" HMS view format, so 
that views created by
+ * Gravitino's Trino dialect are readable by a native Trino Hive connector 
(and vice versa).
+ *
+ * <p>Trino recognizes a Hive Metastore VIRTUAL_VIEW table as a Trino view 
when it carries the
+ * {@code presto_view=true} table parameter and its {@code comment} table 
parameter equals {@code
+ * "Presto View"}; the view body is stored in {@code viewOriginalText} as 
{@code "/* Presto View: "
+ * + base64(json) + " * /"}. The JSON payload mirrors Trino's {@code 
ConnectorViewDefinition} wire
+ * format (see {@code io.trino.plugin.hive.ViewReaderUtil} /{@code
+ * io.trino.spi.connector.ConnectorViewDefinition} in the Trino source tree): 
{@code Optional}
+ * fields are serialized as the raw value or JSON {@code null}, never omitted, 
since Trino's decoder
+ * requires every field to be present.
+ */
+final class TrinoNativeViewCodec {
+
+  static final String PRESTO_VIEW_FLAG = "presto_view";
+  static final String PRESTO_VIEW_COMMENT = "Presto View";
+
+  private static final String VIEW_PREFIX = "/* Presto View: ";
+  private static final String VIEW_SUFFIX = " */";
+  private static final ObjectMapper MAPPER = new ObjectMapper();
+  // Trino's default time/timestamp precision (milliseconds) when none is 
specified.
+  private static final int DEFAULT_PRECISION = 3;
+
+  private TrinoNativeViewCodec() {}
+
+  /** A single view output column, mirroring Trino's {@code 
ConnectorViewDefinition.ViewColumn}. */
+  static final class ViewColumn {
+    final String name;
+    final String type;
+    @Nullable final String comment;
+
+    ViewColumn(String name, String type, @Nullable String comment) {
+      this.name = name;
+      this.type = type;
+      this.comment = comment;
+    }
+  }
+
+  /** Mirrors the fields of Trino's {@code ConnectorViewDefinition}. */
+  static final class ViewDefinition {
+    final String originalSql;
+    @Nullable final String catalog;
+    @Nullable final String schema;
+    final List<ViewColumn> columns;
+    @Nullable final String comment;
+    @Nullable final String owner;
+    final boolean runAsInvoker;
+    // Trino's SQL path setting for the view; Gravitino's view model has no 
equivalent, so this is
+    // only populated on decode() (to let callers detect and reject a 
non-empty path) and is always
+    // written back empty by encode().
+    final List<String> path;
+
+    ViewDefinition(
+        String originalSql,
+        @Nullable String catalog,
+        @Nullable String schema,
+        List<ViewColumn> columns,
+        @Nullable String comment,
+        @Nullable String owner,
+        boolean runAsInvoker,
+        List<String> path) {
+      this.originalSql = originalSql;
+      this.catalog = catalog;
+      this.schema = schema;
+      this.columns = columns;
+      this.comment = comment;
+      this.owner = owner;
+      this.runAsInvoker = runAsInvoker;
+      this.path = path;
+    }
+  }
+
+  /**
+   * Encodes a view definition into Trino's native {@code viewOriginalText} 
format.
+   *
+   * @param definition the view definition to encode
+   * @return the encoded {@code "/* Presto View: ... * /"} string
+   */
+  static String encode(ViewDefinition definition) {
+    ObjectNode root = MAPPER.createObjectNode();
+    root.put("originalSql", definition.originalSql);
+    root.put("catalog", definition.catalog);
+    root.put("schema", definition.schema);
+
+    ArrayNode columns = root.putArray("columns");
+    for (ViewColumn column : definition.columns) {
+      ObjectNode columnNode = columns.addObject();
+      columnNode.put("name", column.name);
+      columnNode.put("type", column.type);
+      columnNode.put("comment", column.comment);
+    }
+
+    root.put("comment", definition.comment);
+    root.put("owner", definition.owner);
+    root.put("runAsInvoker", definition.runAsInvoker);
+    root.putArray("path");
+
+    byte[] bytes;
+    try {
+      bytes = MAPPER.writeValueAsBytes(root);
+    } catch (JsonProcessingException e) {
+      throw new RuntimeException("Failed to encode Trino native view 
definition", e);
+    }
+    return VIEW_PREFIX + Base64.getEncoder().encodeToString(bytes) + 
VIEW_SUFFIX;
+  }
+
+  /**
+   * Decodes a Trino native {@code viewOriginalText} value.
+   *
+   * @param viewOriginalText the raw {@code viewOriginalText} HMS field value
+   * @return the decoded view definition
+   */
+  static ViewDefinition decode(String viewOriginalText) {
+    if (viewOriginalText == null
+        || !viewOriginalText.startsWith(VIEW_PREFIX)
+        || !viewOriginalText.endsWith(VIEW_SUFFIX)) {
+      throw new IllegalArgumentException(
+          "Not a valid Trino native view: viewOriginalText is missing the 
Presto View prefix/suffix");
+    }
+    String encoded =
+        viewOriginalText.substring(
+            VIEW_PREFIX.length(), viewOriginalText.length() - 
VIEW_SUFFIX.length());
+    byte[] bytes = Base64.getDecoder().decode(encoded);
+
+    JsonNode root;
+    try {
+      root = MAPPER.readTree(bytes);
+    } catch (IOException e) {
+      throw new IllegalArgumentException("Failed to decode Trino native view 
definition", e);
+    }
+
+    JsonNode columnsNode = root.path("columns");
+    if (!columnsNode.isArray() || columnsNode.isEmpty()) {
+      throw new IllegalArgumentException(
+          "Not a valid Trino native view: 'columns' field is missing, not an 
array, or empty");
+    }
+    List<ViewColumn> columns = new ArrayList<>();
+    for (JsonNode columnNode : columnsNode) {
+      String name = textOrNull(columnNode, "name");
+      String type = textOrNull(columnNode, "type");
+      if (name == null || type == null) {
+        throw new IllegalArgumentException(
+            "Not a valid Trino native view: a column is missing 'name' or 
'type'");
+      }
+      columns.add(new ViewColumn(name, type, textOrNull(columnNode, 
"comment")));
+    }
+
+    String originalSql = textOrNull(root, "originalSql");
+    if (originalSql == null) {
+      throw new IllegalArgumentException(
+          "Not a valid Trino native view: 'originalSql' field is missing or 
null");
+    }
+
+    List<String> path = new ArrayList<>();
+    for (JsonNode pathNode : root.path("path")) {
+      path.add(pathNode.asText());
+    }
+
+    return new ViewDefinition(
+        originalSql,
+        textOrNull(root, "catalog"),
+        textOrNull(root, "schema"),
+        columns,
+        textOrNull(root, "comment"),
+        textOrNull(root, "owner"),
+        root.path("runAsInvoker").asBoolean(true),
+        path);
+  }
+
+  @Nullable
+  private static String textOrNull(JsonNode node, String field) {
+    JsonNode value = node.get(field);
+    return value == null || value.isNull() ? null : value.asText();
+  }
+
+  /**
+   * Converts a Gravitino type to its Trino type signature string (e.g. {@code 
varchar(10)}, {@code
+   * row(a integer,b varchar)}), for use in the encoded view's column list.
+   *
+   * @param type the Gravitino type
+   * @return the Trino type signature string
+   */
+  static String toTrinoTypeString(Type type) {
+    switch (type.name()) {
+      case BOOLEAN:
+        return "boolean";
+      case BYTE:
+        // Trino has no unsigned integer types; widen to the next size up to 
preserve range,
+        // matching GeneralDataTypeTransformer's Gravitino-to-Trino type 
mapping.
+        return ((Types.ByteType) type).signed() ? "tinyint" : "smallint";
+      case SHORT:
+        return ((Types.ShortType) type).signed() ? "smallint" : "integer";
+      case INTEGER:
+        return ((Types.IntegerType) type).signed() ? "integer" : "bigint";
+      case LONG:
+        return ((Types.LongType) type).signed() ? "bigint" : "decimal(20,0)";
+      case FLOAT:
+        return "real";
+      case DOUBLE:
+        return "double";
+      case STRING:
+        return "varchar";
+      case VARCHAR:
+        return "varchar(" + ((Types.VarCharType) type).length() + ")";
+      case FIXEDCHAR:
+        return "char(" + ((Types.FixedCharType) type).length() + ")";
+      case DATE:
+        return "date";
+      case TIME:
+        Types.TimeType timeType = (Types.TimeType) type;
+        int timePrecision = timeType.hasPrecisionSet() ? timeType.precision() 
: DEFAULT_PRECISION;
+        return "time(" + timePrecision + ")";
+      case TIMESTAMP:
+        Types.TimestampType timestampType = (Types.TimestampType) type;
+        int timestampPrecision =
+            timestampType.hasPrecisionSet() ? timestampType.precision() : 
DEFAULT_PRECISION;
+        return timestampType.hasTimeZone()
+            ? "timestamp(" + timestampPrecision + ") with time zone"
+            : "timestamp(" + timestampPrecision + ")";
+      case UUID:
+        return "uuid";
+      case DECIMAL:
+        Types.DecimalType decimalType = (Types.DecimalType) type;
+        return "decimal(" + decimalType.precision() + "," + 
decimalType.scale() + ")";
+      case BINARY:
+        return "varbinary";
+      case LIST:
+        return "array(" + toTrinoTypeString(((Types.ListType) 
type).elementType()) + ")";
+      case MAP:
+        Types.MapType mapType = (Types.MapType) type;
+        return "map("
+            + toTrinoTypeString(mapType.keyType())
+            + ","
+            + toTrinoTypeString(mapType.valueType())
+            + ")";
+      case STRUCT:
+        Types.StructType structType = (Types.StructType) type;
+        StringBuilder row = new StringBuilder("row(");
+        Types.StructType.Field[] fields = structType.fields();
+        for (int i = 0; i < fields.length; i++) {
+          if (i > 0) {
+            row.append(',');
+          }
+          row.append(rowFieldName(fields[i].name()))
+              .append(' ')
+              .append(toTrinoTypeString(fields[i].type()));
+        }
+        return row.append(')').toString();
+      default:
+        throw new UnsupportedOperationException("Unsupported conversion to 
Trino type: " + type);
+    }
+  }
+
+  /**
+   * Quotes a row field name, matching Trino's own {@code NamedTypeSignature}, 
which always quotes
+   * named row fields unconditionally; a plain-looking name can still be a 
reserved keyword (e.g.
+   * {@code select}), which Trino cannot parse unquoted.
+   */
+  private static String rowFieldName(String name) {
+    return "\"" + name.replace("\"", "\"\"") + "\"";
+  }
+
+  /**
+   * Converts a Trino type signature string (as produced by {@link 
#toTrinoTypeString}) back into a
+   * Gravitino type. Used to restore a Trino dialect view's real column list 
from its encoded
+   * payload, since the underlying HMS table only carries a single dummy 
column (matching Trino's
+   * own native behavior; see {@code 
io.trino.plugin.hive.HiveMetadata#createView}).
+   *
+   * @param typeString the Trino type signature string
+   * @return the Gravitino type
+   */
+  static Type fromTrinoTypeString(String typeString) {
+    String s = typeString.trim();
+    String lower = s.toLowerCase(Locale.ROOT);
+    switch (lower) {
+      case "boolean":
+        return Types.BooleanType.get();
+      case "tinyint":
+        return Types.ByteType.get();
+      case "smallint":
+        return Types.ShortType.get();
+      case "integer":
+        return Types.IntegerType.get();
+      case "bigint":
+        return Types.LongType.get();
+      case "real":
+        return Types.FloatType.get();
+      case "double":
+        return Types.DoubleType.get();
+      case "varchar":
+        return Types.StringType.get();
+      case "date":
+        return Types.DateType.get();
+      case "varbinary":
+        return Types.BinaryType.get();
+      case "uuid":
+        return Types.UUIDType.get();
+      default:
+        break;
+    }
+    if (lower.startsWith("varchar(")) {
+      return Types.VarCharType.of(Integer.parseInt(innerContent(s)));
+    }
+    if (lower.startsWith("char(")) {
+      return Types.FixedCharType.of(Integer.parseInt(innerContent(s)));
+    }
+    if (lower.startsWith("time(")) {
+      if (lower.endsWith("with time zone")) {
+        throw new UnsupportedOperationException(
+            "Unsupported Trino type: TIME WITH TIME ZONE has no Gravitino 
equivalent: "
+                + typeString);
+      }
+      return Types.TimeType.of(parsePrecision(s));
+    }
+    if (lower.startsWith("timestamp(")) {
+      return lower.endsWith("with time zone")
+          ? Types.TimestampType.withTimeZone(parsePrecision(s))
+          : Types.TimestampType.withoutTimeZone(parsePrecision(s));
+    }
+    if (lower.startsWith("decimal(")) {
+      String[] parts = splitTopLevel(innerContent(s));
+      return Types.DecimalType.of(
+          Integer.parseInt(parts[0].trim()), 
Integer.parseInt(parts[1].trim()));
+    }
+    if (lower.startsWith("array(")) {
+      return Types.ListType.nullable(fromTrinoTypeString(innerContent(s)));
+    }
+    if (lower.startsWith("map(")) {
+      String[] parts = splitTopLevel(innerContent(s));
+      return Types.MapType.valueNullable(
+          fromTrinoTypeString(parts[0]), fromTrinoTypeString(parts[1]));
+    }
+    if (lower.startsWith("row(")) {
+      String[] parts = splitTopLevel(innerContent(s));
+      Types.StructType.Field[] fields = new 
Types.StructType.Field[parts.length];
+      for (int i = 0; i < parts.length; i++) {
+        fields[i] = parseRowField(parts[i].trim(), typeString);
+      }
+      return Types.StructType.of(fields);
+    }
+    throw new UnsupportedOperationException("Unsupported Trino type: " + 
typeString);
+  }
+
+  private static String innerContent(String s) {
+    return s.substring(s.indexOf('(') + 1, s.length() - 1);
+  }
+
+  /**
+   * Extracts the integer precision from a type string of the form {@code 
name(N)[ trailing text]},
+   * e.g. {@code timestamp(3) with time zone}; unlike {@link #innerContent}, 
this does not assume
+   * the string ends with the closing parenthesis.
+   */
+  private static int parsePrecision(String s) {
+    int openIdx = s.indexOf('(');
+    int closeIdx = s.indexOf(')', openIdx);
+    return Integer.parseInt(s.substring(openIdx + 1, closeIdx).trim());
+  }
+
+  /**
+   * Parses a single {@code row(...)} field of the form {@code name type} or 
{@code "quoted name"
+   * type} into a Gravitino struct field. Gravitino's struct type has no 
concept of an anonymous
+   * field, so a Trino row field without a name (e.g. from {@code ROW(1, 
'a')}) is rejected rather
+   * than guessed at.
+   */
+  private static Types.StructType.Field parseRowField(String field, String 
typeString) {
+    String fieldName;
+    String fieldType;
+    if (field.startsWith("\"")) {
+      int closeQuote = field.indexOf('"', 1);
+      while (closeQuote != -1
+          && closeQuote + 1 < field.length()
+          && field.charAt(closeQuote + 1) == '"') {
+        closeQuote = field.indexOf('"', closeQuote + 2);
+      }
+      if (closeQuote == -1) {
+        throw new UnsupportedOperationException(
+            "Unsupported Trino type: malformed quoted row field name: " + 
typeString);
+      }
+      fieldName = field.substring(1, closeQuote).replace("\"\"", "\"");
+      fieldType = field.substring(closeQuote + 1).trim();
+    } else {
+      int spaceIdx = field.indexOf(' ');
+      if (spaceIdx <= 0) {
+        throw new UnsupportedOperationException(
+            "Unsupported Trino type: anonymous row fields are not supported: " 
+ typeString);
+      }
+      fieldName = field.substring(0, spaceIdx);
+      fieldType = field.substring(spaceIdx + 1);
+    }
+    return Types.StructType.Field.nullableField(fieldName, 
fromTrinoTypeString(fieldType));
+  }
+
+  /**
+   * Splits a comma-separated argument list, ignoring commas nested inside 
parentheses or inside a
+   * double-quoted row field name (which may itself contain commas, e.g. 
{@code row("a,b" integer)}
+   * ; a doubled {@code ""} inside the quotes is an escaped literal quote, not 
a terminator).
+   */
+  private static String[] splitTopLevel(String s) {
+    List<String> parts = new ArrayList<>();
+    int depth = 0;
+    boolean inQuotes = false;
+    int start = 0;
+    for (int i = 0; i < s.length(); i++) {
+      char c = s.charAt(i);
+      if (c == '"') {
+        if (inQuotes && i + 1 < s.length() && s.charAt(i + 1) == '"') {
+          i++;
+        } else {
+          inQuotes = !inQuotes;
+        }
+      } else if (inQuotes) {
+        // Ignore parentheses/commas inside a quoted field name.
+      } else if (c == '(') {
+        depth++;
+      } else if (c == ')') {
+        depth--;
+      } else if (c == ',' && depth == 0) {
+        parts.add(s.substring(start, i));
+        start = i + 1;
+      }
+    }
+    parts.add(s.substring(start));
+    return parts.toArray(new String[0]);
+  }
+}
diff --git 
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
index e0b600fb69..bd651aacbf 100644
--- 
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
+++ 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
@@ -49,7 +49,9 @@ import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Base64;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -215,7 +217,7 @@ class TestHiveCatalogOperations {
   }
 
   @Test
-  void testCreateViewRejectsTrinoDialect() throws Exception {
+  void testCreateViewAcceptsTrinoDialect() throws Exception {
     HiveCatalogOperations op = new HiveCatalogOperations();
     op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
 
@@ -223,6 +225,101 @@ class TestHiveCatalogOperations {
     HiveClient hiveClient = mock(HiveClient.class);
     HiveSchema schema = 
HiveSchema.builder().withCatalogName("hive").withName("db").build();
     when(hiveClient.getDatabase(anyString(), anyString())).thenReturn(schema);
+
+    ArgumentCaptor<HiveTable> hiveTableCaptor = 
ArgumentCaptor.forClass(HiveTable.class);
+    doNothing().when(hiveClient).createTable(hiveTableCaptor.capture());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    View view =
+        op.createView(
+            NameIdentifier.of("db", "v_trino"),
+            null,
+            new Column[] {Column.of("c1", Types.IntegerType.get())},
+            new SQLRepresentation[] {
+              SQLRepresentation.builder().withDialect("trino").withSql("SELECT 
1").build()
+            },
+            null,
+            null,
+            Maps.newHashMap());
+
+    Assertions.assertNotNull(view);
+    Assertions.assertEquals(1, view.representations().length);
+    SQLRepresentation rep = (SQLRepresentation) view.representations()[0];
+    Assertions.assertEquals("trino", rep.dialect());
+    Assertions.assertEquals("SELECT 1", rep.sql());
+
+    Map<String, String> storedProperties = 
hiveTableCaptor.getValue().properties();
+    Assertions.assertEquals("true", storedProperties.get("presto_view"));
+    Assertions.assertEquals("Presto View", storedProperties.get("comment"));
+    TrinoNativeViewCodec.ViewDefinition decoded =
+        
TrinoNativeViewCodec.decode(hiveTableCaptor.getValue().viewOriginalText());
+    Assertions.assertEquals("SELECT 1", decoded.originalSql);
+  }
+
+  @Test
+  void testCreateViewLoadRoundTripPreservesTrinoComment() throws Exception {
+    // A Trino dialect view's user-facing comment lives inside the encoded 
payload, not the HMS
+    // "comment" property (which is fixed to the "Presto View" marker), so it 
must round-trip
+    // through create + load.
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    HiveSchema schema = 
HiveSchema.builder().withCatalogName("hive").withName("db").build();
+    when(hiveClient.getDatabase(anyString(), anyString())).thenReturn(schema);
+
+    ArgumentCaptor<HiveTable> hiveTableCaptor = 
ArgumentCaptor.forClass(HiveTable.class);
+    doNothing().when(hiveClient).createTable(hiveTableCaptor.capture());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    View created =
+        op.createView(
+            NameIdentifier.of("db", "v_trino"),
+            "a view comment",
+            new Column[] {Column.of("c1", Types.IntegerType.get())},
+            new SQLRepresentation[] {
+              SQLRepresentation.builder().withDialect("trino").withSql("SELECT 
1").build()
+            },
+            null,
+            null,
+            Maps.newHashMap());
+    Assertions.assertEquals("a view comment", created.comment());
+
+    when(hiveClient.getTable(anyString(), anyString(), anyString()))
+        .thenReturn(hiveTableCaptor.getValue());
+    View loaded = op.loadView(NameIdentifier.of("db", "v_trino"));
+    Assertions.assertEquals("a view comment", loaded.comment());
+  }
+
+  @Test
+  void testCreateViewTrinoDialectStoresDummyHmsColumnAndRealColumnsInPayload() 
throws Exception {
+    // Real Trino stores only a single dummy HMS column for a Presto View (see
+    // io.trino.plugin.hive.HiveMetadata#createView); the real columns live in 
the encoded
+    // payload. Gravitino must match this so a native Trino reading the HMS 
table directly (or
+    // Gravitino reloading its own view) resolves the correct column 
count/types.
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    HiveSchema schema = 
HiveSchema.builder().withCatalogName("hive").withName("db").build();
+    when(hiveClient.getDatabase(anyString(), anyString())).thenReturn(schema);
+
+    ArgumentCaptor<HiveTable> hiveTableCaptor = 
ArgumentCaptor.forClass(HiveTable.class);
+    doNothing().when(hiveClient).createTable(hiveTableCaptor.capture());
     when(clientPool.run(any()))
         .thenAnswer(
             invocation -> {
@@ -231,21 +328,160 @@ class TestHiveCatalogOperations {
             });
     op.clientPool = clientPool;
 
-    UnsupportedOperationException exception =
+    Column[] columns = {
+      Column.of("id", Types.LongType.get(), "id column"),
+      Column.of("name", Types.StringType.get(), null)
+    };
+
+    View created =
+        op.createView(
+            NameIdentifier.of("db", "v_trino"),
+            null,
+            columns,
+            new SQLRepresentation[] {
+              SQLRepresentation.builder().withDialect("trino").withSql("SELECT 
id, name").build()
+            },
+            null,
+            null,
+            Maps.newHashMap());
+
+    HiveTable storedTable = hiveTableCaptor.getValue();
+    Assertions.assertEquals(1, storedTable.columns().length);
+    Assertions.assertEquals("dummy", storedTable.columns()[0].name());
+
+    Assertions.assertEquals(2, created.columns().length);
+    Assertions.assertEquals("id", created.columns()[0].name());
+    Assertions.assertEquals("name", created.columns()[1].name());
+
+    when(hiveClient.getTable(anyString(), anyString(), 
anyString())).thenReturn(storedTable);
+    View reloaded = op.loadView(NameIdentifier.of("db", "v_trino"));
+    Assertions.assertEquals(2, reloaded.columns().length);
+    Assertions.assertEquals("id", reloaded.columns()[0].name());
+    Assertions.assertEquals("id column", reloaded.columns()[0].comment());
+    Assertions.assertEquals("name", reloaded.columns()[1].name());
+  }
+
+  @Test
+  void testCreateViewPersistsTrinoDefaultCatalogAndSchema() throws Exception {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    HiveSchema schema = 
HiveSchema.builder().withCatalogName("hive").withName("db").build();
+    when(hiveClient.getDatabase(anyString(), anyString())).thenReturn(schema);
+
+    ArgumentCaptor<HiveTable> hiveTableCaptor = 
ArgumentCaptor.forClass(HiveTable.class);
+    doNothing().when(hiveClient).createTable(hiveTableCaptor.capture());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    // Simulates a Trino "USE gt_hive.db; CREATE VIEW v AS SELECT * FROM t" 
statement, where
+    // the unqualified "t" must be resolved against 
defaultCatalog/defaultSchema on reload.
+    op.createView(
+        NameIdentifier.of("db", "v_trino"),
+        null,
+        new Column[] {Column.of("c1", Types.IntegerType.get())},
+        new SQLRepresentation[] {
+          SQLRepresentation.builder().withDialect("trino").withSql("SELECT * 
FROM t").build()
+        },
+        "gt_hive",
+        "db",
+        Maps.newHashMap());
+
+    TrinoNativeViewCodec.ViewDefinition decoded =
+        
TrinoNativeViewCodec.decode(hiveTableCaptor.getValue().viewOriginalText());
+    Assertions.assertEquals("gt_hive", decoded.catalog);
+    Assertions.assertEquals("db", decoded.schema);
+
+    when(hiveClient.getTable(anyString(), anyString(), anyString()))
+        .thenReturn(hiveTableCaptor.getValue());
+    View loaded = op.loadView(NameIdentifier.of("db", "v_trino"));
+
+    Assertions.assertEquals("gt_hive", loaded.defaultCatalog());
+    Assertions.assertEquals("db", loaded.defaultSchema());
+  }
+
+  @Test
+  void testCreateViewRejectsTrinoSchemaWithoutCatalog() throws Exception {
+    // Trino's own ConnectorViewDefinition rejects a schema without a catalog; 
accepting it here
+    // would persist a payload that a native Trino connector cannot decode.
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    HiveSchema schema = 
HiveSchema.builder().withCatalogName("hive").withName("db").build();
+    when(hiveClient.getDatabase(anyString(), anyString())).thenReturn(schema);
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    IllegalArgumentException exception =
         Assertions.assertThrows(
-            UnsupportedOperationException.class,
+            IllegalArgumentException.class,
             () ->
                 op.createView(
                     NameIdentifier.of("db", "v_trino"),
                     null,
-                    new Column[0],
+                    new Column[] {Column.of("c1", Types.IntegerType.get())},
                     new SQLRepresentation[] {
                       
SQLRepresentation.builder().withDialect("trino").withSql("SELECT 1").build()
                     },
                     null,
-                    null,
+                    "db",
                     Maps.newHashMap()));
-    Assertions.assertTrue(exception.getMessage().contains("supports only"));
+
+    Assertions.assertTrue(
+        exception
+            .getMessage()
+            .contains("does not support a defaultSchema without a 
defaultCatalog"));
+  }
+
+  @Test
+  void testCreateViewClearsStaleTrinoMarkerForNonTrinoDialect() throws 
Exception {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    HiveSchema schema = 
HiveSchema.builder().withCatalogName("hive").withName("db").build();
+    when(hiveClient.getDatabase(anyString(), anyString())).thenReturn(schema);
+
+    ArgumentCaptor<HiveTable> hiveTableCaptor = 
ArgumentCaptor.forClass(HiveTable.class);
+    doNothing().when(hiveClient).createTable(hiveTableCaptor.capture());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    Map<String, String> propertiesWithStaleMarker = Maps.newHashMap();
+    propertiesWithStaleMarker.put("presto_view", "true");
+
+    op.createView(
+        NameIdentifier.of("db", "v_hive"),
+        null,
+        new Column[0],
+        new SQLRepresentation[] {
+          SQLRepresentation.builder().withDialect("hive").withSql("SELECT 
1").build()
+        },
+        null,
+        null,
+        propertiesWithStaleMarker);
+
+    
Assertions.assertNull(hiveTableCaptor.getValue().properties().get("presto_view"));
   }
 
   @Test
@@ -481,12 +717,23 @@ class TestHiveCatalogOperations {
   }
 
   @Test
-  void testLoadViewRejectsTrinoDialect() throws Exception {
+  void testLoadViewAcceptsTrinoDialect() throws Exception {
     HiveCatalogOperations op = new HiveCatalogOperations();
     op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
 
     CachedClientPool clientPool = mock(CachedClientPool.class);
     HiveClient hiveClient = mock(HiveClient.class);
+    String encoded =
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                null,
+                null,
+                List.of(new TrinoNativeViewCodec.ViewColumn("_col0", 
"integer", null)),
+                null,
+                null,
+                true,
+                List.of()));
     when(hiveClient.getTable(anyString(), anyString(), anyString()))
         .thenReturn(
             HiveTable.builder()
@@ -494,6 +741,7 @@ class TestHiveCatalogOperations {
                 .withCatalogName("hive")
                 .withDatabaseName("db")
                 .withColumns(new Column[0])
+                .withComment("Presto View")
                 .withProperties(
                     Maps.newHashMap(
                         ImmutableMap.of(
@@ -501,7 +749,7 @@ class TestHiveCatalogOperations {
                             TableType.VIRTUAL_VIEW.name(),
                             "presto_view",
                             "true")))
-                .withViewOriginalText("SELECT 1")
+                .withViewOriginalText(encoded)
                 .build());
     when(clientPool.run(any()))
         .thenAnswer(
@@ -511,11 +759,107 @@ class TestHiveCatalogOperations {
             });
     op.clientPool = clientPool;
 
-    UnsupportedOperationException exception =
-        Assertions.assertThrows(
-            UnsupportedOperationException.class,
-            () -> op.loadView(NameIdentifier.of("db", "v_trino")));
-    Assertions.assertTrue(exception.getMessage().contains("supports only"));
+    View loaded = op.loadView(NameIdentifier.of("db", "v_trino"));
+
+    SQLRepresentation representation = (SQLRepresentation) 
loaded.representations()[0];
+    Assertions.assertEquals("trino", representation.dialect());
+    Assertions.assertEquals("SELECT 1", representation.sql());
+  }
+
+  @Test
+  void testLoadViewAcceptsNativeTrinoView() throws Exception {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    // A native Presto/Trino view (created outside Gravitino, e.g. by a native 
Trino Hive
+    // connector pointed at the same Hive Metastore) is encoded using Trino's 
own native format;
+    // Gravitino must be able to read it directly, not just views it created 
itself.
+    String encoded =
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                "native_catalog",
+                "native_db",
+                List.of(new TrinoNativeViewCodec.ViewColumn("id", "integer", 
null)),
+                "a comment",
+                null,
+                true,
+                List.of()));
+    when(hiveClient.getTable(anyString(), anyString(), anyString()))
+        .thenReturn(
+            HiveTable.builder()
+                .withName("v_native_trino")
+                .withCatalogName("hive")
+                .withDatabaseName("db")
+                .withColumns(new Column[0])
+                .withComment("Presto View")
+                .withProperties(
+                    Maps.newHashMap(
+                        ImmutableMap.of(
+                            HiveConstants.TABLE_TYPE,
+                            TableType.VIRTUAL_VIEW.name(),
+                            "presto_view",
+                            "true")))
+                .withViewOriginalText(encoded)
+                .build());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    View loaded = op.loadView(NameIdentifier.of("db", "v_native_trino"));
+
+    SQLRepresentation representation = (SQLRepresentation) 
loaded.representations()[0];
+    Assertions.assertEquals("trino", representation.dialect());
+    Assertions.assertEquals("SELECT 1", representation.sql());
+    Assertions.assertEquals("a comment", loaded.comment());
+    Assertions.assertEquals("native_catalog", loaded.defaultCatalog());
+    Assertions.assertEquals("native_db", loaded.defaultSchema());
+  }
+
+  @Test
+  void testLoadViewRejectsPrestoViewThatIsNotAPlainView() throws Exception {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    // A Trino materialized view also carries presto_view=true, but with a 
different comment
+    // marker ("Presto Materialized View"); it must be rejected rather than 
misread as a plain
+    // Trino view.
+    when(hiveClient.getTable(anyString(), anyString(), anyString()))
+        .thenReturn(
+            HiveTable.builder()
+                .withName("v_materialized")
+                .withCatalogName("hive")
+                .withDatabaseName("db")
+                .withColumns(new Column[0])
+                .withComment("Presto Materialized View")
+                .withProperties(
+                    Maps.newHashMap(
+                        ImmutableMap.of(
+                            HiveConstants.TABLE_TYPE,
+                            TableType.VIRTUAL_VIEW.name(),
+                            "presto_view",
+                            "true")))
+                .withViewOriginalText("/* Presto View: base64encodedpayload 
*/")
+                .build());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () -> op.loadView(NameIdentifier.of("db", "v_materialized")));
   }
 
   @Test
@@ -590,7 +934,291 @@ class TestHiveCatalogOperations {
   }
 
   @Test
-  void testAlterViewReplaceRejectsTrinoDialect() throws Exception {
+  void testAlterViewReplaceAcceptsTrinoDialect() throws Exception {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    String encoded =
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                null,
+                null,
+                List.of(new TrinoNativeViewCodec.ViewColumn("c1", "integer", 
null)),
+                null,
+                null,
+                true,
+                List.of()));
+    HiveTable currentTable =
+        HiveTable.builder()
+            .withName("v_hive")
+            .withCatalogName("hive")
+            .withDatabaseName("db")
+            .withColumns(new Column[0])
+            .withComment("Presto View")
+            .withProperties(
+                Maps.newHashMap(
+                    ImmutableMap.of(
+                        HiveConstants.TABLE_TYPE,
+                        TableType.VIRTUAL_VIEW.name(),
+                        "presto_view",
+                        "true")))
+            .withViewOriginalText(encoded)
+            .build();
+    when(hiveClient.getTable(anyString(), anyString(), 
anyString())).thenReturn(currentTable);
+
+    ArgumentCaptor<HiveTable> hiveTableCaptor = 
ArgumentCaptor.forClass(HiveTable.class);
+    doNothing()
+        .when(hiveClient)
+        .alterTable(anyString(), anyString(), anyString(), 
hiveTableCaptor.capture());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    View updated =
+        op.alterView(
+            NameIdentifier.of("db", "v_hive"),
+            ViewChange.replaceView(
+                new Column[] {Column.of("c1", Types.IntegerType.get())},
+                new SQLRepresentation[] {
+                  
SQLRepresentation.builder().withDialect("trino").withSql("SELECT 2").build()
+                },
+                null,
+                null,
+                null));
+
+    Assertions.assertEquals("true", 
hiveTableCaptor.getValue().properties().get("presto_view"));
+    Assertions.assertEquals("Presto View", 
hiveTableCaptor.getValue().properties().get("comment"));
+    TrinoNativeViewCodec.ViewDefinition decoded =
+        
TrinoNativeViewCodec.decode(hiveTableCaptor.getValue().viewOriginalText());
+    Assertions.assertEquals("SELECT 2", decoded.originalSql);
+    SQLRepresentation representation = (SQLRepresentation) 
updated.representations()[0];
+    Assertions.assertEquals("trino", representation.dialect());
+    Assertions.assertEquals("SELECT 2", representation.sql());
+  }
+
+  @Test
+  void testAlterViewReplaceRejectsExistingNonDefaultOwner() throws Exception {
+    // Gravitino's view model has no owner concept; replacing a native Trino 
view that has a
+    // non-null owner (a SECURITY DEFINER view) would silently turn it into an 
ownerless view.
+    assertReplaceRejectsUnrepresentableNativeView(
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                null,
+                null,
+                List.of(new TrinoNativeViewCodec.ViewColumn("c1", "integer", 
null)),
+                null,
+                "alice",
+                true,
+                List.of())));
+  }
+
+  @Test
+  void testAlterViewReplaceRejectsExistingRunAsInvokerFalse() throws Exception 
{
+    // runAsInvoker=false means SECURITY DEFINER; Gravitino always writes 
runAsInvoker=true, so
+    // replacing such a view would silently downgrade it to SECURITY INVOKER.
+    assertReplaceRejectsUnrepresentableNativeView(
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                null,
+                null,
+                List.of(new TrinoNativeViewCodec.ViewColumn("c1", "integer", 
null)),
+                null,
+                null,
+                false,
+                List.of())));
+  }
+
+  @Test
+  void testAlterViewReplaceRejectsExistingNonEmptyPath() throws Exception {
+    // Gravitino's view model has no SQL path concept; replacing a native 
Trino view that has a
+    // non-empty path would silently discard it. TrinoNativeViewCodec.encode() 
always writes an
+    // empty path (Gravitino itself never produces one), so this payload is 
built by hand to
+    // simulate a view created directly by a native Trino connector with a 
non-empty path.
+    String encoded =
+        "/* Presto View: "
+            + Base64.getEncoder()
+                .encodeToString(
+                    ("{\"originalSql\":\"SELECT 
1\",\"catalog\":null,\"schema\":null,"
+                            + 
"\"columns\":[{\"name\":\"c1\",\"type\":\"integer\",\"comment\":null}],"
+                            + 
"\"comment\":null,\"owner\":null,\"runAsInvoker\":true,"
+                            + 
"\"path\":[{\"catalog\":\"c\",\"schema\":\"s\"}]}")
+                        .getBytes(StandardCharsets.UTF_8))
+            + " */";
+    assertReplaceRejectsUnrepresentableNativeView(encoded);
+  }
+
+  private void assertReplaceRejectsUnrepresentableNativeView(String encoded) 
throws Exception {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    HiveTable currentTable =
+        HiveTable.builder()
+            .withName("v_trino")
+            .withCatalogName("hive")
+            .withDatabaseName("db")
+            .withColumns(new Column[0])
+            .withComment("Presto View")
+            .withProperties(
+                Maps.newHashMap(
+                    ImmutableMap.of(
+                        HiveConstants.TABLE_TYPE,
+                        TableType.VIRTUAL_VIEW.name(),
+                        "presto_view",
+                        "true")))
+            .withViewOriginalText(encoded)
+            .build();
+    when(hiveClient.getTable(anyString(), anyString(), 
anyString())).thenReturn(currentTable);
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () ->
+            op.alterView(
+                NameIdentifier.of("db", "v_trino"),
+                ViewChange.replaceView(
+                    new Column[] {Column.of("c1", Types.IntegerType.get())},
+                    new SQLRepresentation[] {
+                      
SQLRepresentation.builder().withDialect("trino").withSql("SELECT 2").build()
+                    },
+                    null,
+                    null,
+                    null)));
+  }
+
+  @Test
+  void testAlterViewRejectsSetPropertyCommentOnTrinoView() throws Exception {
+    // A Trino dialect view's HMS "comment" property is fixed to "Presto View" 
(the marker Trino
+    // itself relies on to recognize the view); the real comment lives inside 
the encoded payload.
+    // Setting it directly (bypassing ReplaceView) would desynchronize the two 
and make the view
+    // unloadable on the next read, so it must be rejected instead.
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    String encoded =
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                null,
+                null,
+                List.of(new TrinoNativeViewCodec.ViewColumn("c1", "integer", 
null)),
+                null,
+                null,
+                true,
+                List.of()));
+    HiveTable currentTable =
+        HiveTable.builder()
+            .withName("v_trino")
+            .withCatalogName("hive")
+            .withDatabaseName("db")
+            .withColumns(new Column[0])
+            .withComment("Presto View")
+            .withProperties(
+                Maps.newHashMap(
+                    ImmutableMap.of(
+                        HiveConstants.TABLE_TYPE,
+                        TableType.VIRTUAL_VIEW.name(),
+                        "presto_view",
+                        "true")))
+            .withViewOriginalText(encoded)
+            .build();
+    when(hiveClient.getTable(anyString(), anyString(), 
anyString())).thenReturn(currentTable);
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () ->
+            op.alterView(
+                NameIdentifier.of("db", "v_trino"), 
ViewChange.setProperty("comment", "my note")));
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () ->
+            op.alterView(NameIdentifier.of("db", "v_trino"), 
ViewChange.removeProperty("comment")));
+  }
+
+  @Test
+  void testAlterViewRejectsRemovingPrestoViewMarkerFromTrinoView() throws 
Exception {
+    // The presto_view HMS property is part of the native Trino view storage 
contract; removing it
+    // directly (bypassing ReplaceView) would leave the encoded payload in 
viewOriginalText while
+    // making the view misclassify as Hive dialect on the next load, exposing 
the raw payload as
+    // SQL.
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    String encoded =
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                null,
+                null,
+                List.of(new TrinoNativeViewCodec.ViewColumn("c1", "integer", 
null)),
+                null,
+                null,
+                true,
+                List.of()));
+    HiveTable currentTable =
+        HiveTable.builder()
+            .withName("v_trino")
+            .withCatalogName("hive")
+            .withDatabaseName("db")
+            .withColumns(new Column[0])
+            .withComment("Presto View")
+            .withProperties(
+                Maps.newHashMap(
+                    ImmutableMap.of(
+                        HiveConstants.TABLE_TYPE,
+                        TableType.VIRTUAL_VIEW.name(),
+                        "presto_view",
+                        "true")))
+            .withViewOriginalText(encoded)
+            .build();
+    when(hiveClient.getTable(anyString(), anyString(), 
anyString())).thenReturn(currentTable);
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () ->
+            op.alterView(
+                NameIdentifier.of("db", "v_trino"), 
ViewChange.removeProperty("presto_view")));
+  }
+
+  @Test
+  void testAlterViewRejectsSettingPrestoViewMarkerOnNonTrinoView() throws 
Exception {
+    // Setting presto_view=true on a plain Hive view directly (bypassing 
ReplaceView) would make
+    // the view misclassify as Trino dialect on the next load, and decoding 
its plain SQL
+    // viewOriginalText as a Trino native payload would fail.
     HiveCatalogOperations op = new HiveCatalogOperations();
     op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
 
@@ -616,24 +1244,73 @@ class TestHiveCatalogOperations {
             });
     op.clientPool = clientPool;
 
-    UnsupportedOperationException exception =
-        Assertions.assertThrows(
-            UnsupportedOperationException.class,
-            () ->
-                op.alterView(
-                    NameIdentifier.of("db", "v_hive"),
-                    ViewChange.replaceView(
-                        new Column[0],
-                        new SQLRepresentation[] {
-                          SQLRepresentation.builder()
-                              .withDialect("trino")
-                              .withSql("SELECT 2")
-                              .build()
-                        },
-                        null,
-                        null,
-                        null)));
-    Assertions.assertTrue(exception.getMessage().contains("supports only"));
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () ->
+            op.alterView(
+                NameIdentifier.of("db", "v_hive"), 
ViewChange.setProperty("presto_view", "true")));
+  }
+
+  @Test
+  void testAlterViewReplaceClearsTrinoMarkerForNonTrinoDialect() throws 
Exception {
+    HiveCatalogOperations op = new HiveCatalogOperations();
+    op.initialize(Maps.newHashMap(), null, HIVE_PROPERTIES_METADATA);
+
+    CachedClientPool clientPool = mock(CachedClientPool.class);
+    HiveClient hiveClient = mock(HiveClient.class);
+    String encoded =
+        TrinoNativeViewCodec.encode(
+            new TrinoNativeViewCodec.ViewDefinition(
+                "SELECT 1",
+                null,
+                null,
+                List.of(new TrinoNativeViewCodec.ViewColumn("c1", "integer", 
null)),
+                null,
+                null,
+                true,
+                List.of()));
+    HiveTable currentTable =
+        HiveTable.builder()
+            .withName("v_trino")
+            .withCatalogName("hive")
+            .withDatabaseName("db")
+            .withColumns(new Column[0])
+            .withComment("Presto View")
+            .withProperties(
+                Maps.newHashMap(
+                    ImmutableMap.of(
+                        HiveConstants.TABLE_TYPE,
+                        TableType.VIRTUAL_VIEW.name(),
+                        "presto_view",
+                        "true")))
+            .withViewOriginalText(encoded)
+            .build();
+    when(hiveClient.getTable(anyString(), anyString(), 
anyString())).thenReturn(currentTable);
+
+    ArgumentCaptor<HiveTable> hiveTableCaptor = 
ArgumentCaptor.forClass(HiveTable.class);
+    doNothing()
+        .when(hiveClient)
+        .alterTable(anyString(), anyString(), anyString(), 
hiveTableCaptor.capture());
+    when(clientPool.run(any()))
+        .thenAnswer(
+            invocation -> {
+              ClientPool.Action<?, HiveClient, ?> action = 
invocation.getArgument(0);
+              return action.run(hiveClient);
+            });
+    op.clientPool = clientPool;
+
+    op.alterView(
+        NameIdentifier.of("db", "v_trino"),
+        ViewChange.replaceView(
+            new Column[0],
+            new SQLRepresentation[] {
+              SQLRepresentation.builder().withDialect("hive").withSql("SELECT 
2").build()
+            },
+            null,
+            null,
+            null));
+
+    
Assertions.assertNull(hiveTableCaptor.getValue().properties().get("presto_view"));
   }
 
   @Test
diff --git 
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestTrinoNativeViewCodec.java
 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestTrinoNativeViewCodec.java
new file mode 100644
index 0000000000..b936896650
--- /dev/null
+++ 
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestTrinoNativeViewCodec.java
@@ -0,0 +1,318 @@
+/*
+ * 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 java.util.List;
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class TestTrinoNativeViewCodec {
+
+  @Test
+  void testEncodeDecodeRoundTrip() {
+    TrinoNativeViewCodec.ViewDefinition definition =
+        new TrinoNativeViewCodec.ViewDefinition(
+            "SELECT id, name FROM t",
+            "hive",
+            "db",
+            List.of(
+                new TrinoNativeViewCodec.ViewColumn("id", "bigint", null),
+                new TrinoNativeViewCodec.ViewColumn("name", "varchar(50)", 
"name column")),
+            "a view comment",
+            null,
+            true,
+            List.of());
+
+    String encoded = TrinoNativeViewCodec.encode(definition);
+    Assertions.assertTrue(encoded.startsWith("/* Presto View: "));
+    Assertions.assertTrue(encoded.endsWith(" */"));
+
+    TrinoNativeViewCodec.ViewDefinition decoded = 
TrinoNativeViewCodec.decode(encoded);
+    Assertions.assertEquals("SELECT id, name FROM t", decoded.originalSql);
+    Assertions.assertEquals("hive", decoded.catalog);
+    Assertions.assertEquals("db", decoded.schema);
+    Assertions.assertEquals("a view comment", decoded.comment);
+    Assertions.assertNull(decoded.owner);
+    Assertions.assertTrue(decoded.runAsInvoker);
+    Assertions.assertEquals(2, decoded.columns.size());
+    Assertions.assertEquals("id", decoded.columns.get(0).name);
+    Assertions.assertEquals("bigint", decoded.columns.get(0).type);
+    Assertions.assertNull(decoded.columns.get(0).comment);
+    Assertions.assertEquals("name", decoded.columns.get(1).name);
+    Assertions.assertEquals("varchar(50)", decoded.columns.get(1).type);
+    Assertions.assertEquals("name column", decoded.columns.get(1).comment);
+  }
+
+  @Test
+  void testEncodeDecodeRoundTripWithNullCatalogAndSchema() {
+    TrinoNativeViewCodec.ViewDefinition definition =
+        new TrinoNativeViewCodec.ViewDefinition(
+            "SELECT 1",
+            null,
+            null,
+            List.of(new TrinoNativeViewCodec.ViewColumn("_col0", "integer", 
null)),
+            null,
+            null,
+            true,
+            List.of());
+
+    TrinoNativeViewCodec.ViewDefinition decoded =
+        TrinoNativeViewCodec.decode(TrinoNativeViewCodec.encode(definition));
+    Assertions.assertNull(decoded.catalog);
+    Assertions.assertNull(decoded.schema);
+    Assertions.assertNull(decoded.comment);
+  }
+
+  @Test
+  void testDecodeRejectsMissingPrefixOrSuffix() {
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> TrinoNativeViewCodec.decode("not 
a presto view"));
+  }
+
+  @Test
+  void testToTrinoTypeStringForPrimitives() {
+    Assertions.assertEquals(
+        "boolean", 
TrinoNativeViewCodec.toTrinoTypeString(Types.BooleanType.get()));
+    Assertions.assertEquals(
+        "tinyint", 
TrinoNativeViewCodec.toTrinoTypeString(Types.ByteType.get()));
+    Assertions.assertEquals(
+        "smallint", 
TrinoNativeViewCodec.toTrinoTypeString(Types.ShortType.get()));
+    Assertions.assertEquals(
+        "integer", 
TrinoNativeViewCodec.toTrinoTypeString(Types.IntegerType.get()));
+    Assertions.assertEquals("bigint", 
TrinoNativeViewCodec.toTrinoTypeString(Types.LongType.get()));
+    Assertions.assertEquals("real", 
TrinoNativeViewCodec.toTrinoTypeString(Types.FloatType.get()));
+    Assertions.assertEquals(
+        "double", 
TrinoNativeViewCodec.toTrinoTypeString(Types.DoubleType.get()));
+    Assertions.assertEquals(
+        "varchar", 
TrinoNativeViewCodec.toTrinoTypeString(Types.StringType.get()));
+    Assertions.assertEquals(
+        "varchar(10)", 
TrinoNativeViewCodec.toTrinoTypeString(Types.VarCharType.of(10)));
+    Assertions.assertEquals(
+        "char(5)", 
TrinoNativeViewCodec.toTrinoTypeString(Types.FixedCharType.of(5)));
+    Assertions.assertEquals("date", 
TrinoNativeViewCodec.toTrinoTypeString(Types.DateType.get()));
+    Assertions.assertEquals(
+        "timestamp(3)",
+        
TrinoNativeViewCodec.toTrinoTypeString(Types.TimestampType.withoutTimeZone()));
+    Assertions.assertEquals(
+        "timestamp(3) with time zone",
+        
TrinoNativeViewCodec.toTrinoTypeString(Types.TimestampType.withTimeZone()));
+    Assertions.assertEquals(
+        "time(3)", 
TrinoNativeViewCodec.toTrinoTypeString(Types.TimeType.get()));
+    Assertions.assertEquals("uuid", 
TrinoNativeViewCodec.toTrinoTypeString(Types.UUIDType.get()));
+    Assertions.assertEquals(
+        "decimal(10,2)", 
TrinoNativeViewCodec.toTrinoTypeString(Types.DecimalType.of(10, 2)));
+    Assertions.assertEquals(
+        "varbinary", 
TrinoNativeViewCodec.toTrinoTypeString(Types.BinaryType.get()));
+  }
+
+  @Test
+  void testToTrinoTypeStringForComplexTypes() {
+    Assertions.assertEquals(
+        "array(integer)",
+        
TrinoNativeViewCodec.toTrinoTypeString(Types.ListType.nullable(Types.IntegerType.get())));
+    Assertions.assertEquals(
+        "map(varchar,integer)",
+        TrinoNativeViewCodec.toTrinoTypeString(
+            Types.MapType.valueNullable(Types.StringType.get(), 
Types.IntegerType.get())));
+    Assertions.assertEquals(
+        "row(\"a\" integer,\"b\" varchar)",
+        TrinoNativeViewCodec.toTrinoTypeString(
+            Types.StructType.of(
+                Types.StructType.Field.nullableField("a", 
Types.IntegerType.get()),
+                Types.StructType.Field.nullableField("b", 
Types.StringType.get()))));
+  }
+
+  @Test
+  void testToTrinoTypeStringPreservesExplicitPrecision() {
+    Assertions.assertEquals(
+        "timestamp(6)",
+        
TrinoNativeViewCodec.toTrinoTypeString(Types.TimestampType.withoutTimeZone(6)));
+    Assertions.assertEquals(
+        "timestamp(6) with time zone",
+        
TrinoNativeViewCodec.toTrinoTypeString(Types.TimestampType.withTimeZone(6)));
+    Assertions.assertEquals(
+        "time(6)", 
TrinoNativeViewCodec.toTrinoTypeString(Types.TimeType.of(6)));
+  }
+
+  @Test
+  void testToTrinoTypeStringWidensUnsignedIntegralTypes() {
+    Assertions.assertEquals(
+        "smallint", 
TrinoNativeViewCodec.toTrinoTypeString(Types.ByteType.unsigned()));
+    Assertions.assertEquals(
+        "integer", 
TrinoNativeViewCodec.toTrinoTypeString(Types.ShortType.unsigned()));
+    Assertions.assertEquals(
+        "bigint", 
TrinoNativeViewCodec.toTrinoTypeString(Types.IntegerType.unsigned()));
+    Assertions.assertEquals(
+        "decimal(20,0)", 
TrinoNativeViewCodec.toTrinoTypeString(Types.LongType.unsigned()));
+  }
+
+  @Test
+  void testFromTrinoTypeStringForPrimitives() {
+    Assertions.assertEquals(
+        Types.BooleanType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("boolean"));
+    Assertions.assertEquals(
+        Types.ByteType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("tinyint"));
+    Assertions.assertEquals(
+        Types.ShortType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("smallint"));
+    Assertions.assertEquals(
+        Types.IntegerType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("integer"));
+    Assertions.assertEquals(
+        Types.LongType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("bigint"));
+    Assertions.assertEquals(
+        Types.FloatType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("real"));
+    Assertions.assertEquals(
+        Types.DoubleType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("double"));
+    Assertions.assertEquals(
+        Types.StringType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("varchar"));
+    Assertions.assertEquals(
+        Types.VarCharType.of(10), 
TrinoNativeViewCodec.fromTrinoTypeString("varchar(10)"));
+    Assertions.assertEquals(
+        Types.FixedCharType.of(5), 
TrinoNativeViewCodec.fromTrinoTypeString("char(5)"));
+    Assertions.assertEquals(Types.DateType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("date"));
+    Assertions.assertEquals(
+        Types.TimestampType.withoutTimeZone(6),
+        TrinoNativeViewCodec.fromTrinoTypeString("timestamp(6)"));
+    Assertions.assertEquals(
+        Types.TimestampType.withTimeZone(6),
+        TrinoNativeViewCodec.fromTrinoTypeString("timestamp(6) with time 
zone"));
+    Assertions.assertEquals(
+        Types.TimeType.of(6), 
TrinoNativeViewCodec.fromTrinoTypeString("time(6)"));
+    Assertions.assertEquals(Types.UUIDType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("uuid"));
+    Assertions.assertEquals(
+        Types.DecimalType.of(10, 2), 
TrinoNativeViewCodec.fromTrinoTypeString("decimal(10,2)"));
+    Assertions.assertEquals(
+        Types.BinaryType.get(), 
TrinoNativeViewCodec.fromTrinoTypeString("varbinary"));
+  }
+
+  @Test
+  void testFromTrinoTypeStringForComplexTypesRoundTripsWithToTrinoTypeString() 
{
+    Types.ListType listType = Types.ListType.nullable(Types.IntegerType.get());
+    Assertions.assertEquals(
+        listType,
+        
TrinoNativeViewCodec.fromTrinoTypeString(TrinoNativeViewCodec.toTrinoTypeString(listType)));
+
+    Types.MapType mapType =
+        Types.MapType.valueNullable(Types.StringType.get(), 
Types.IntegerType.get());
+    Assertions.assertEquals(
+        mapType,
+        
TrinoNativeViewCodec.fromTrinoTypeString(TrinoNativeViewCodec.toTrinoTypeString(mapType)));
+
+    Types.StructType structType =
+        Types.StructType.of(
+            Types.StructType.Field.nullableField("a", Types.IntegerType.get()),
+            Types.StructType.Field.nullableField("b", Types.StringType.get()));
+    Assertions.assertEquals(
+        structType,
+        TrinoNativeViewCodec.fromTrinoTypeString(
+            TrinoNativeViewCodec.toTrinoTypeString(structType)));
+
+    // Nested: array(row(a integer))
+    Types.ListType nested =
+        Types.ListType.nullable(
+            Types.StructType.of(
+                Types.StructType.Field.nullableField("a", 
Types.IntegerType.get())));
+    Assertions.assertEquals(
+        nested,
+        
TrinoNativeViewCodec.fromTrinoTypeString(TrinoNativeViewCodec.toTrinoTypeString(nested)));
+  }
+
+  @Test
+  void testFromTrinoTypeStringHandlesQuotedRowFieldNames() {
+    Types.StructType structType =
+        Types.StructType.of(
+            Types.StructType.Field.nullableField("my field", 
Types.IntegerType.get()));
+    String encoded = TrinoNativeViewCodec.toTrinoTypeString(structType);
+    Assertions.assertEquals("row(\"my field\" integer)", encoded);
+    Assertions.assertEquals(structType, 
TrinoNativeViewCodec.fromTrinoTypeString(encoded));
+  }
+
+  @Test
+  void testToTrinoTypeStringQuotesReservedKeywordRowFieldName() {
+    Types.StructType structType =
+        Types.StructType.of(
+            Types.StructType.Field.nullableField("select", 
Types.IntegerType.get()));
+    String encoded = TrinoNativeViewCodec.toTrinoTypeString(structType);
+    Assertions.assertEquals("row(\"select\" integer)", encoded);
+    Assertions.assertEquals(structType, 
TrinoNativeViewCodec.fromTrinoTypeString(encoded));
+  }
+
+  @Test
+  void testFromTrinoTypeStringRejectsAnonymousRowField() {
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () -> 
TrinoNativeViewCodec.fromTrinoTypeString("row(integer,varchar)"));
+  }
+
+  @Test
+  void testTimestampTimeAndUuidRoundTripWithToTrinoTypeString() {
+    Types.TimestampType timestamp = Types.TimestampType.withoutTimeZone(3);
+    Assertions.assertEquals(
+        timestamp,
+        TrinoNativeViewCodec.fromTrinoTypeString(
+            TrinoNativeViewCodec.toTrinoTypeString(timestamp)));
+
+    Types.TimestampType timestampWithTimeZone = 
Types.TimestampType.withTimeZone(3);
+    Assertions.assertEquals(
+        timestampWithTimeZone,
+        TrinoNativeViewCodec.fromTrinoTypeString(
+            TrinoNativeViewCodec.toTrinoTypeString(timestampWithTimeZone)));
+
+    Types.TimeType time = Types.TimeType.of(3);
+    Assertions.assertEquals(
+        time,
+        
TrinoNativeViewCodec.fromTrinoTypeString(TrinoNativeViewCodec.toTrinoTypeString(time)));
+
+    Types.UUIDType uuid = Types.UUIDType.get();
+    Assertions.assertEquals(
+        uuid,
+        
TrinoNativeViewCodec.fromTrinoTypeString(TrinoNativeViewCodec.toTrinoTypeString(uuid)));
+  }
+
+  @Test
+  void testFromTrinoTypeStringRejectsUnknownType() {
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () -> TrinoNativeViewCodec.fromTrinoTypeString("json"));
+  }
+
+  @Test
+  void testFromTrinoTypeStringRejectsTimeWithTimeZone() {
+    Assertions.assertThrows(
+        UnsupportedOperationException.class,
+        () -> TrinoNativeViewCodec.fromTrinoTypeString("time(3) with time 
zone"));
+  }
+
+  @Test
+  void testDecodeRejectsEmptyColumns() {
+    TrinoNativeViewCodec.ViewDefinition definition =
+        new TrinoNativeViewCodec.ViewDefinition(
+            "SELECT 1", null, null, List.of(), null, null, true, List.of());
+    String encoded = TrinoNativeViewCodec.encode(definition);
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> 
TrinoNativeViewCodec.decode(encoded));
+  }
+
+  @Test
+  void testDecodeRejectsMalformedBase64Payload() {
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> TrinoNativeViewCodec.decode("/* Presto View: not-valid-base64!! 
*/"));
+  }
+}
diff --git a/docs/apache-hive-catalog.md b/docs/apache-hive-catalog.md
index 59763b05a1..67f6fcc584 100644
--- a/docs/apache-hive-catalog.md
+++ b/docs/apache-hive-catalog.md
@@ -241,9 +241,11 @@ Support for altering partitions is under development.
 
 - Supports list, create, load, alter, and drop for views stored in the Hive 
Metastore Service as `VIRTUAL_VIEW`.
 - Each view must contain exactly one SQL representation.
-- Supports creating views with the `hive`, `flink`, or `spark` dialect.
-- When loading an existing HMS view, Gravitino automatically detects whether 
the view uses the `hive`, `flink`, `spark`, or `trino` dialect.
+- Supports creating views with the `hive`, `trino`, `flink`, or `spark` 
dialect.
+- When loading an existing HMS view, Gravitino automatically detects whether 
the view uses the `hive`, `trino`, `flink`, or `spark` dialect.
 - For the `hive` and `flink` dialects, `defaultCatalog` and `defaultSchema` 
must be `null`.
+- For the `trino` dialect, `defaultSchema` requires `defaultCatalog` to also 
be set (a schema without a catalog cannot be represented).
+- The `trino` dialect requires at least one output column, and is stored using 
Trino's own native "Presto View" Hive Metastore encoding, so a view created 
through Gravitino is interoperable with a native Trino/Presto Hive connector 
pointed at the same Hive Metastore, and vice versa. The HMS `presto_view` 
property this relies on is reserved and managed internally based on the view's 
dialect; it cannot be set or removed directly. Gravitino's view model cannot 
represent a native Trino view' [...]
 - The `flink` dialect requires at least one view property with the prefix 
`flink.` to be set. The Flink connector automatically sets 
`flink.schema.num-columns`; when using the REST API directly, set at least one 
`flink.*` property explicitly.
 - The `spark` dialect requires the view property `spark.sql.create.version` to 
be set; without it the view round-trips as the `hive` dialect on reload.
 

Reply via email to