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

jerryshao 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 3d353c5ae4 [#10830] feat(api): add View, ViewCatalog, ViewChange and 
SQLRepresentation APIs (#10831)
3d353c5ae4 is described below

commit 3d353c5ae4f05137aa7996c99c686265725b7b88
Author: mchades <[email protected]>
AuthorDate: Fri Apr 24 14:12:08 2026 +0800

    [#10830] feat(api): add View, ViewCatalog, ViewChange and SQLRepresentation 
APIs (#10831)
    
    ### What changes were proposed in this pull request?
    
    This PR introduces the first batch of logical view support, focused on
    API surface plus the `loadView` read path.
    
    Changes included:
    
    1. API additions in `api/`:
       - `View`
       - `ViewCatalog`
       - `ViewChange`
       - `Representation` / `SQLRepresentation`
       - `Dialects`
       - `ViewAlreadyExistsException`
    2. Core load path in `core/`:
    - `ViewOperationDispatcher` now supports `loadView(...)` with
    EntityStore auto-import.
    - `EntityCombinedView` to combine catalog-side view metadata and
    Gravitino entity metadata.
    3. Catalog adapter/test support:
    - `IcebergView` implementation for mapping Iceberg REST
    `LoadViewResponse` to Gravitino `View`.
    - `TestCatalogOperations` and `TestViewOperationDispatcher` coverage for
    view loading and auto-import behavior.
    
    Out of scope in this PR:
    - Full view CRUD implementation in dispatcher/connectors
    (`listViews/createView/alterView/dropView`) is **not** implemented yet.
    
    ### Why are the changes needed?
    
    This is the foundational step for logical view management in Gravitino,
    establishing the public API model and enabling the initial `loadView`
    integration path.
    
    Fix: #10830
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes.
    
    - New public API types for view management are added in the `api`
    module.
    - Runtime behavior change is limited to the `loadView` path introduced
    in core dispatcher flow.
    - `createView/alterView/dropView/listViews` runtime support is not part
    of this PR.
    
    ### How was this change tested?
    
    - Added/updated API unit tests:
    -
    `api/src/test/java/org/apache/gravitino/rel/TestSQLRepresentation.java`
      - `api/src/test/java/org/apache/gravitino/rel/TestView.java`
      - `api/src/test/java/org/apache/gravitino/rel/TestViewChange.java`
    - Added core dispatcher tests:
    -
    
`core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java`
    - Updated connector test utilities:
    -
    
`core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java`
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
---
 .../exceptions/ViewAlreadyExistsException.java     |  48 +++
 .../gravitino/rel/{View.java => Dialects.java}     |  45 ++-
 .../rel/{View.java => Representation.java}         |  34 +--
 .../apache/gravitino/rel/SQLRepresentation.java    | 137 +++++++++
 .../main/java/org/apache/gravitino/rel/View.java   |  82 ++++-
 .../java/org/apache/gravitino/rel/ViewCatalog.java |  87 +++++-
 .../java/org/apache/gravitino/rel/ViewChange.java  | 338 +++++++++++++++++++++
 .../gravitino/rel/TestSQLRepresentation.java       |  72 +++++
 .../java/org/apache/gravitino/rel/TestView.java    | 155 ++++++++++
 .../org/apache/gravitino/rel/TestViewChange.java   | 126 ++++++++
 .../catalog/lakehouse/iceberg/IcebergView.java     |  12 +
 .../gravitino/catalog/EntityCombinedView.java      |  22 ++
 .../gravitino/catalog/ViewOperationDispatcher.java |   4 +-
 .../catalog/TestViewOperationDispatcher.java       |  12 +
 .../gravitino/connector/TestCatalogOperations.java |  19 +-
 15 files changed, 1121 insertions(+), 72 deletions(-)

diff --git 
a/api/src/main/java/org/apache/gravitino/exceptions/ViewAlreadyExistsException.java
 
b/api/src/main/java/org/apache/gravitino/exceptions/ViewAlreadyExistsException.java
new file mode 100644
index 0000000000..81e674c292
--- /dev/null
+++ 
b/api/src/main/java/org/apache/gravitino/exceptions/ViewAlreadyExistsException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.exceptions;
+
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.errorprone.annotations.FormatString;
+
+/** Exception thrown when a view with specified name already exists. */
+public class ViewAlreadyExistsException extends AlreadyExistsException {
+  /**
+   * Constructs a new exception with the specified detail message.
+   *
+   * @param message the detail message.
+   * @param args the arguments to the message.
+   */
+  @FormatMethod
+  public ViewAlreadyExistsException(@FormatString String message, Object... 
args) {
+    super(message, args);
+  }
+
+  /**
+   * Constructs a new exception with the specified detail message and cause.
+   *
+   * @param cause the cause.
+   * @param message the detail message.
+   * @param args the arguments to the message.
+   */
+  @FormatMethod
+  public ViewAlreadyExistsException(Throwable cause, @FormatString String 
message, Object... args) {
+    super(cause, message, args);
+  }
+}
diff --git a/api/src/main/java/org/apache/gravitino/rel/View.java 
b/api/src/main/java/org/apache/gravitino/rel/Dialects.java
similarity index 50%
copy from api/src/main/java/org/apache/gravitino/rel/View.java
copy to api/src/main/java/org/apache/gravitino/rel/Dialects.java
index 1d425e7494..93e95ef555 100644
--- a/api/src/main/java/org/apache/gravitino/rel/View.java
+++ b/api/src/main/java/org/apache/gravitino/rel/Dialects.java
@@ -18,37 +18,26 @@
  */
 package org.apache.gravitino.rel;
 
-import java.util.Collections;
-import java.util.Map;
-import javax.annotation.Nullable;
-import org.apache.gravitino.Auditable;
-import org.apache.gravitino.Namespace;
-import org.apache.gravitino.annotation.Unstable;
-
 /**
- * An interface representing a view in a {@link Namespace}. It defines the 
basic properties of a
- * view. A catalog implementation with {@link ViewCatalog} should implement 
this interface.
+ * Predefined SQL dialect identifiers used by {@link 
SQLRepresentation#dialect()}.
+ *
+ * <p>Dialect values are lowercase identifiers so they can be compared 
case-insensitively.
+ * Applications are free to use dialect strings that are not listed here; this 
class only provides
+ * constants for the dialects Gravitino recognizes out of the box.
  */
-@Unstable
-public interface View extends Auditable {
+public final class Dialects {
+
+  /** The Trino SQL dialect. */
+  public static final String TRINO = "trino";
+
+  /** The Apache Spark SQL dialect. */
+  public static final String SPARK = "spark";
 
-  /**
-   * @return The name of the view.
-   */
-  String name();
+  /** The Apache Hive SQL dialect. */
+  public static final String HIVE = "hive";
 
-  /**
-   * @return The comment of the view, null if no comment is set.
-   */
-  @Nullable
-  default String comment() {
-    return null;
-  }
+  /** The Apache Flink SQL dialect. */
+  public static final String FLINK = "flink";
 
-  /**
-   * @return The properties of the view, empty map if no properties are set.
-   */
-  default Map<String, String> properties() {
-    return Collections.emptyMap();
-  }
+  private Dialects() {}
 }
diff --git a/api/src/main/java/org/apache/gravitino/rel/View.java 
b/api/src/main/java/org/apache/gravitino/rel/Representation.java
similarity index 54%
copy from api/src/main/java/org/apache/gravitino/rel/View.java
copy to api/src/main/java/org/apache/gravitino/rel/Representation.java
index 1d425e7494..42f32aff4f 100644
--- a/api/src/main/java/org/apache/gravitino/rel/View.java
+++ b/api/src/main/java/org/apache/gravitino/rel/Representation.java
@@ -18,37 +18,23 @@
  */
 package org.apache.gravitino.rel;
 
-import java.util.Collections;
-import java.util.Map;
-import javax.annotation.Nullable;
-import org.apache.gravitino.Auditable;
-import org.apache.gravitino.Namespace;
 import org.apache.gravitino.annotation.Unstable;
 
 /**
- * An interface representing a view in a {@link Namespace}. It defines the 
basic properties of a
- * view. A catalog implementation with {@link ViewCatalog} should implement 
this interface.
+ * A representation of a view's underlying definition. A view can carry 
multiple representations
+ * targeting different engines or dialects. Currently only the {@link 
#TYPE_SQL SQL} representation
+ * type is supported.
  */
 @Unstable
-public interface View extends Auditable {
+public interface Representation {
 
-  /**
-   * @return The name of the view.
-   */
-  String name();
-
-  /**
-   * @return The comment of the view, null if no comment is set.
-   */
-  @Nullable
-  default String comment() {
-    return null;
-  }
+  /** The representation type for SQL-based view definitions. */
+  String TYPE_SQL = "sql";
 
   /**
-   * @return The properties of the view, empty map if no properties are set.
+   * Returns the representation type. The only supported value today is {@link 
#TYPE_SQL}.
+   *
+   * @return The representation type identifier.
    */
-  default Map<String, String> properties() {
-    return Collections.emptyMap();
-  }
+  String type();
 }
diff --git a/api/src/main/java/org/apache/gravitino/rel/SQLRepresentation.java 
b/api/src/main/java/org/apache/gravitino/rel/SQLRepresentation.java
new file mode 100644
index 0000000000..a483ce0487
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/rel/SQLRepresentation.java
@@ -0,0 +1,137 @@
+/*
+ * 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.rel;
+
+import com.google.common.base.Preconditions;
+import java.util.Objects;
+import org.apache.gravitino.annotation.Unstable;
+
+/**
+ * A SQL-based {@link Representation} of a view. Each {@code 
SQLRepresentation} carries the SQL text
+ * together with the {@link #dialect() dialect} it is expressed in.
+ *
+ * <p>Default catalog and schema used to resolve unqualified identifiers 
referenced by the SQL are
+ * defined at the {@link View} level and shared across all representations of 
the view.
+ */
+@Unstable
+public final class SQLRepresentation implements Representation {
+
+  private final String dialect;
+  private final String sql;
+
+  private SQLRepresentation(String dialect, String sql) {
+    this.dialect = dialect;
+    this.sql = sql;
+  }
+
+  /**
+   * Creates a new builder for {@link SQLRepresentation}.
+   *
+   * @return A new {@link Builder} instance.
+   */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public String type() {
+    return Representation.TYPE_SQL;
+  }
+
+  /**
+   * Returns the SQL dialect of this representation, e.g. {@code "trino"} or 
{@code "spark"}. See
+   * {@link Dialects} for well-known values.
+   *
+   * @return The dialect identifier.
+   */
+  public String dialect() {
+    return dialect;
+  }
+
+  /**
+   * Returns the SQL text of this representation.
+   *
+   * @return The SQL text.
+   */
+  public String sql() {
+    return sql;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    SQLRepresentation that = (SQLRepresentation) o;
+    return dialect.equals(that.dialect) && sql.equals(that.sql);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(dialect, sql);
+  }
+
+  @Override
+  public String toString() {
+    return "SQLRepresentation{" + "dialect='" + dialect + '\'' + ", sql='" + 
sql + '\'' + '}';
+  }
+
+  /** A builder for {@link SQLRepresentation}. */
+  public static final class Builder {
+
+    private String dialect;
+    private String sql;
+
+    private Builder() {}
+
+    /**
+     * Sets the SQL dialect.
+     *
+     * @param dialect The dialect identifier; must be non-null and non-empty.
+     * @return This builder.
+     */
+    public Builder withDialect(String dialect) {
+      this.dialect = dialect;
+      return this;
+    }
+
+    /**
+     * Sets the SQL text.
+     *
+     * @param sql The SQL text; must be non-null and non-empty.
+     * @return This builder.
+     */
+    public Builder withSql(String sql) {
+      this.sql = sql;
+      return this;
+    }
+
+    /**
+     * Builds a new {@link SQLRepresentation}.
+     *
+     * @return The constructed {@link SQLRepresentation}.
+     * @throws IllegalArgumentException If {@code dialect} or {@code sql} is 
null or empty.
+     */
+    public SQLRepresentation build() {
+      Preconditions.checkArgument(
+          dialect != null && !dialect.isEmpty(), "dialect must not be null or 
empty");
+      Preconditions.checkArgument(sql != null && !sql.isEmpty(), "sql must not 
be null or empty");
+      return new SQLRepresentation(dialect, sql);
+    }
+  }
+}
diff --git a/api/src/main/java/org/apache/gravitino/rel/View.java 
b/api/src/main/java/org/apache/gravitino/rel/View.java
index 1d425e7494..c4bf41440d 100644
--- a/api/src/main/java/org/apache/gravitino/rel/View.java
+++ b/api/src/main/java/org/apache/gravitino/rel/View.java
@@ -20,25 +20,31 @@ package org.apache.gravitino.rel;
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Optional;
 import javax.annotation.Nullable;
 import org.apache.gravitino.Auditable;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.annotation.Unstable;
 
 /**
- * An interface representing a view in a {@link Namespace}. It defines the 
basic properties of a
- * view. A catalog implementation with {@link ViewCatalog} should implement 
this interface.
+ * An interface representing a logical view in a {@link Namespace}. A view is 
a named query whose
+ * definition may be expressed in one or more SQL dialects. A catalog 
implementation with {@link
+ * ViewCatalog} should implement this interface.
  */
 @Unstable
 public interface View extends Auditable {
 
   /**
-   * @return The name of the view.
+   * Returns the name of the view.
+   *
+   * @return The view name.
    */
   String name();
 
   /**
-   * @return The comment of the view, null if no comment is set.
+   * Returns the comment of the view, or {@code null} if no comment is set.
+   *
+   * @return The view comment, or {@code null}.
    */
   @Nullable
   default String comment() {
@@ -46,7 +52,73 @@ public interface View extends Auditable {
   }
 
   /**
-   * @return The properties of the view, empty map if no properties are set.
+   * Returns the output schema of the view. Implementations should return an 
empty array when the
+   * output schema is unknown; callers should not rely on a {@code null} 
return value.
+   *
+   * @return The view output columns.
+   */
+  Column[] columns();
+
+  /**
+   * Returns the representations of the view. A view carries at least one 
{@link Representation},
+   * typically a {@link SQLRepresentation} for one or more dialects.
+   *
+   * @return The view representations.
+   */
+  Representation[] representations();
+
+  /**
+   * Returns the default catalog used to resolve unqualified identifiers 
referenced by the view
+   * definition, or {@code null} if not set. The default catalog is shared 
across all {@link
+   * #representations() representations}.
+   *
+   * @return The default catalog, or {@code null}.
+   */
+  @Nullable
+  default String defaultCatalog() {
+    return null;
+  }
+
+  /**
+   * Returns the default schema used to resolve unqualified identifiers 
referenced by the view
+   * definition, or {@code null} if not set. The default schema is shared 
across all {@link
+   * #representations() representations}.
+   *
+   * @return The default schema, or {@code null}.
+   */
+  @Nullable
+  default String defaultSchema() {
+    return null;
+  }
+
+  /**
+   * Looks up the {@link SQLRepresentation} for the given dialect. Matching is 
case-insensitive.
+   *
+   * @param dialect The dialect identifier to look up, e.g. {@code "trino"}.
+   * @return An {@link Optional} containing the matching {@link 
SQLRepresentation}, or {@link
+   *     Optional#empty()} if no representation for the dialect exists or if 
the matching
+   *     representation is not a {@link SQLRepresentation}.
+   */
+  default Optional<SQLRepresentation> sqlFor(String dialect) {
+    if (dialect == null) {
+      return Optional.empty();
+    }
+    Representation[] reps = representations();
+    for (Representation rep : reps) {
+      if (rep instanceof SQLRepresentation) {
+        SQLRepresentation sqlRep = (SQLRepresentation) rep;
+        if (dialect.equalsIgnoreCase(sqlRep.dialect())) {
+          return Optional.of(sqlRep);
+        }
+      }
+    }
+    return Optional.empty();
+  }
+
+  /**
+   * Returns the properties of the view, or an empty map if no properties are 
set.
+   *
+   * @return The view properties.
    */
   default Map<String, String> properties() {
     return Collections.emptyMap();
diff --git a/api/src/main/java/org/apache/gravitino/rel/ViewCatalog.java 
b/api/src/main/java/org/apache/gravitino/rel/ViewCatalog.java
index 2bf423f97b..a56d8ffad5 100644
--- a/api/src/main/java/org/apache/gravitino/rel/ViewCatalog.java
+++ b/api/src/main/java/org/apache/gravitino/rel/ViewCatalog.java
@@ -18,20 +18,36 @@
  */
 package org.apache.gravitino.rel;
 
+import java.util.Map;
+import javax.annotation.Nullable;
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
 import org.apache.gravitino.annotation.Unstable;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchViewException;
+import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
 
 /**
  * The ViewCatalog interface defines the public API for managing views in a 
schema. If the catalog
  * implementation supports views, it must implement this interface.
- *
- * <p>Note: This is a minimal interface. Full operations (create, list, alter, 
drop) will be added
- * when Gravitino APIs support views.
  */
 @Unstable
 public interface ViewCatalog {
 
+  /**
+   * List the views in a namespace from the catalog.
+   *
+   * <p>This is a default method that throws {@link 
UnsupportedOperationException}. Catalog
+   * implementations that support view management should override this method.
+   *
+   * @param namespace A namespace.
+   * @return An array of view identifiers in the namespace.
+   * @throws NoSuchSchemaException If the schema does not exist.
+   */
+  default NameIdentifier[] listViews(Namespace namespace) throws 
NoSuchSchemaException {
+    throw new UnsupportedOperationException("listViews is not supported");
+  }
+
   /**
    * Load view metadata by {@link NameIdentifier} from the catalog.
    *
@@ -54,4 +70,69 @@ public interface ViewCatalog {
       return false;
     }
   }
+
+  /**
+   * Create a view in the catalog.
+   *
+   * <p>This is a default method that throws {@link 
UnsupportedOperationException}. Catalog
+   * implementations that support view management should override this method.
+   *
+   * @param ident A view identifier.
+   * @param comment The view comment, may be {@code null}.
+   * @param columns The output columns of the view.
+   * @param representations The representations of the view. At least one 
representation is
+   *     expected.
+   * @param defaultCatalog The default catalog used to resolve unqualified 
identifiers referenced by
+   *     the view definition, or {@code null} if not set.
+   * @param defaultSchema The default schema used to resolve unqualified 
identifiers referenced by
+   *     the view definition, or {@code null} if not set.
+   * @param properties The view properties.
+   * @return The created view metadata.
+   * @throws NoSuchSchemaException If the schema does not exist.
+   * @throws ViewAlreadyExistsException If the view already exists.
+   */
+  default View createView(
+      NameIdentifier ident,
+      @Nullable String comment,
+      Column[] columns,
+      Representation[] representations,
+      @Nullable String defaultCatalog,
+      @Nullable String defaultSchema,
+      Map<String, String> properties)
+      throws NoSuchSchemaException, ViewAlreadyExistsException {
+    throw new UnsupportedOperationException("createView is not supported");
+  }
+
+  /**
+   * Apply the {@link ViewChange changes} to a view in the catalog.
+   *
+   * <p>Implementations may reject the change. If any change is rejected, no 
changes should be
+   * applied to the view.
+   *
+   * <p>This is a default method that throws {@link 
UnsupportedOperationException}. Catalog
+   * implementations that support view management should override this method.
+   *
+   * @param ident A view identifier.
+   * @param changes View changes to apply to the view.
+   * @return The updated view metadata.
+   * @throws NoSuchViewException If the view does not exist.
+   * @throws IllegalArgumentException If the change is rejected by the 
implementation.
+   */
+  default View alterView(NameIdentifier ident, ViewChange... changes)
+      throws NoSuchViewException, IllegalArgumentException {
+    throw new UnsupportedOperationException("alterView is not supported");
+  }
+
+  /**
+   * Drop a view from the catalog.
+   *
+   * <p>This is a default method that throws {@link 
UnsupportedOperationException}. Catalog
+   * implementations that support view management should override this method.
+   *
+   * @param ident A view identifier.
+   * @return True if the view is dropped, false if the view does not exist.
+   */
+  default boolean dropView(NameIdentifier ident) {
+    throw new UnsupportedOperationException("dropView is not supported");
+  }
 }
diff --git a/api/src/main/java/org/apache/gravitino/rel/ViewChange.java 
b/api/src/main/java/org/apache/gravitino/rel/ViewChange.java
new file mode 100644
index 0000000000..996ddf42a2
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/rel/ViewChange.java
@@ -0,0 +1,338 @@
+/*
+ * 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.rel;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.apache.gravitino.annotation.Unstable;
+
+/**
+ * The ViewChange interface defines a change that can be applied to a view via 
{@link
+ * ViewCatalog#alterView(org.apache.gravitino.NameIdentifier, ViewChange...)}.
+ */
+@Unstable
+public interface ViewChange {
+
+  /**
+   * Create a ViewChange for renaming a view.
+   *
+   * @param newName The new view name.
+   * @return A ViewChange for the rename.
+   */
+  static ViewChange rename(String newName) {
+    return new RenameView(newName);
+  }
+
+  /**
+   * Create a ViewChange for setting a view property.
+   *
+   * <p>If the property already exists, it will be replaced with the new value.
+   *
+   * @param property The property name.
+   * @param value The new property value.
+   * @return A ViewChange for the addition.
+   */
+  static ViewChange setProperty(String property, String value) {
+    return new SetProperty(property, value);
+  }
+
+  /**
+   * Create a ViewChange for removing a view property.
+   *
+   * <p>If the property does not exist, the change will succeed.
+   *
+   * @param property The property name.
+   * @return A ViewChange for the removal.
+   */
+  static ViewChange removeProperty(String property) {
+    return new RemoveProperty(property);
+  }
+
+  /**
+   * Create a ViewChange that atomically replaces the view body, i.e. its 
columns, representations,
+   * default catalog, default schema and comment. Properties and the view name 
are not affected by
+   * this change.
+   *
+   * <p>The provided values are treated as a full replacement: any existing 
columns, representations
+   * and defaults are discarded. Callers that only intend to change a subset 
of the body should read
+   * the current values first and pass them back unchanged.
+   *
+   * @param columns The new output columns of the view.
+   * @param representations The new representations of the view. At least one 
representation is
+   *     expected.
+   * @param defaultCatalog The new default catalog, or {@code null} to unset 
it.
+   * @param defaultSchema The new default schema, or {@code null} to unset it.
+   * @param comment The new comment, or {@code null} to unset it.
+   * @return A ViewChange for the replacement.
+   */
+  static ViewChange replaceView(
+      Column[] columns,
+      Representation[] representations,
+      @Nullable String defaultCatalog,
+      @Nullable String defaultSchema,
+      @Nullable String comment) {
+    return new ReplaceView(columns, representations, defaultCatalog, 
defaultSchema, comment);
+  }
+
+  /** A ViewChange to rename a view. */
+  final class RenameView implements ViewChange {
+    private final String newName;
+
+    private RenameView(String newName) {
+      Preconditions.checkArgument(
+          newName != null && !newName.isEmpty(), "newName must not be null or 
empty");
+      this.newName = newName;
+    }
+
+    /**
+     * Retrieves the new name for the view.
+     *
+     * @return The new view name.
+     */
+    public String getNewName() {
+      return newName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      RenameView that = (RenameView) o;
+      return newName.equals(that.newName);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(newName);
+    }
+
+    @Override
+    public String toString() {
+      return "RENAMEVIEW " + newName;
+    }
+  }
+
+  /**
+   * A ViewChange to set a view property.
+   *
+   * <p>If the property already exists, it must be replaced with the new value.
+   */
+  final class SetProperty implements ViewChange {
+    private final String property;
+    private final String value;
+
+    private SetProperty(String property, String value) {
+      Preconditions.checkArgument(
+          property != null && !property.isEmpty(), "property must not be null 
or empty");
+      this.property = property;
+      this.value = value;
+    }
+
+    /**
+     * Retrieves the name of the property.
+     *
+     * @return The property name.
+     */
+    public String getProperty() {
+      return property;
+    }
+
+    /**
+     * Retrieves the value of the property.
+     *
+     * @return The property value.
+     */
+    public String getValue() {
+      return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      SetProperty that = (SetProperty) o;
+      return property.equals(that.property) && Objects.equals(value, 
that.value);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(property, value);
+    }
+
+    @Override
+    public String toString() {
+      return "SETPROPERTY " + property + " " + value;
+    }
+  }
+
+  /**
+   * A ViewChange to remove a view property.
+   *
+   * <p>If the property does not exist, the change should succeed.
+   */
+  final class RemoveProperty implements ViewChange {
+    private final String property;
+
+    private RemoveProperty(String property) {
+      Preconditions.checkArgument(
+          property != null && !property.isEmpty(), "property must not be null 
or empty");
+      this.property = property;
+    }
+
+    /**
+     * Retrieves the name of the property to be removed from the view.
+     *
+     * @return The property name scheduled for removal.
+     */
+    public String getProperty() {
+      return property;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      RemoveProperty that = (RemoveProperty) o;
+      return property.equals(that.property);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(property);
+    }
+
+    @Override
+    public String toString() {
+      return "REMOVEPROPERTY " + property;
+    }
+  }
+
+  /**
+   * A ViewChange that atomically replaces the view body (columns, 
representations, default catalog,
+   * default schema and comment).
+   */
+  final class ReplaceView implements ViewChange {
+    private final Column[] columns;
+    private final Representation[] representations;
+    @Nullable private final String defaultCatalog;
+    @Nullable private final String defaultSchema;
+    @Nullable private final String comment;
+
+    private ReplaceView(
+        Column[] columns,
+        Representation[] representations,
+        @Nullable String defaultCatalog,
+        @Nullable String defaultSchema,
+        @Nullable String comment) {
+      Preconditions.checkArgument(columns != null, "columns must not be null");
+      Preconditions.checkArgument(
+          representations != null && representations.length > 0,
+          "representations must not be null or empty");
+      this.columns = Arrays.copyOf(columns, columns.length);
+      this.representations = Arrays.copyOf(representations, 
representations.length);
+      this.defaultCatalog = defaultCatalog;
+      this.defaultSchema = defaultSchema;
+      this.comment = comment;
+    }
+
+    /**
+     * Retrieves the new output columns of the view.
+     *
+     * @return The new columns.
+     */
+    public Column[] getColumns() {
+      return Arrays.copyOf(columns, columns.length);
+    }
+
+    /**
+     * Retrieves the new representations of the view.
+     *
+     * @return The new representations.
+     */
+    public Representation[] getRepresentations() {
+      return Arrays.copyOf(representations, representations.length);
+    }
+
+    /**
+     * Retrieves the new default catalog, or {@code null} if it should be 
unset.
+     *
+     * @return The new default catalog, or {@code null}.
+     */
+    @Nullable
+    public String getDefaultCatalog() {
+      return defaultCatalog;
+    }
+
+    /**
+     * Retrieves the new default schema, or {@code null} if it should be unset.
+     *
+     * @return The new default schema, or {@code null}.
+     */
+    @Nullable
+    public String getDefaultSchema() {
+      return defaultSchema;
+    }
+
+    /**
+     * Retrieves the new view comment, or {@code null} if it should be unset.
+     *
+     * @return The new comment, or {@code null}.
+     */
+    @Nullable
+    public String getComment() {
+      return comment;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      ReplaceView that = (ReplaceView) o;
+      return Arrays.equals(columns, that.columns)
+          && Arrays.equals(representations, that.representations)
+          && Objects.equals(defaultCatalog, that.defaultCatalog)
+          && Objects.equals(defaultSchema, that.defaultSchema)
+          && Objects.equals(comment, that.comment);
+    }
+
+    @Override
+    public int hashCode() {
+      int result = Objects.hash(defaultCatalog, defaultSchema, comment);
+      result = 31 * result + Arrays.hashCode(columns);
+      result = 31 * result + Arrays.hashCode(representations);
+      return result;
+    }
+
+    @Override
+    public String toString() {
+      return "REPLACEVIEW columns="
+          + Arrays.toString(columns)
+          + ", representations="
+          + Arrays.toString(representations)
+          + ", defaultCatalog="
+          + defaultCatalog
+          + ", defaultSchema="
+          + defaultSchema
+          + ", comment="
+          + comment;
+    }
+  }
+}
diff --git 
a/api/src/test/java/org/apache/gravitino/rel/TestSQLRepresentation.java 
b/api/src/test/java/org/apache/gravitino/rel/TestSQLRepresentation.java
new file mode 100644
index 0000000000..818fc241b7
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/rel/TestSQLRepresentation.java
@@ -0,0 +1,72 @@
+/*
+ * 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.rel;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class TestSQLRepresentation {
+
+  @Test
+  public void testBuildSqlRepresentation() {
+    SQLRepresentation representation =
+        SQLRepresentation.builder().withDialect("trino").withSql("select 
1").build();
+
+    assertEquals(Representation.TYPE_SQL, representation.type());
+    assertEquals("trino", representation.dialect());
+    assertEquals("select 1", representation.sql());
+  }
+
+  @Test
+  public void testSqlRepresentationEqualsAndHashCode() {
+    SQLRepresentation representation1 =
+        SQLRepresentation.builder().withDialect("trino").withSql("select 
1").build();
+    SQLRepresentation representation2 =
+        SQLRepresentation.builder().withDialect("trino").withSql("select 
1").build();
+    SQLRepresentation representation3 =
+        SQLRepresentation.builder().withDialect("spark").withSql("select 
1").build();
+
+    assertEquals(representation1, representation2);
+    assertEquals(representation1.hashCode(), representation2.hashCode());
+    assertNotEquals(representation1, representation3);
+  }
+
+  @Test
+  public void testBuildSqlRepresentationRejectsEmptyDialect() {
+    IllegalArgumentException exception =
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> SQLRepresentation.builder().withDialect("").withSql("select 
1").build());
+
+    assertEquals("dialect must not be null or empty", exception.getMessage());
+  }
+
+  @Test
+  public void testBuildSqlRepresentationRejectsEmptySql() {
+    IllegalArgumentException exception =
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> 
SQLRepresentation.builder().withDialect("trino").withSql("").build());
+
+    assertEquals("sql must not be null or empty", exception.getMessage());
+  }
+}
diff --git a/api/src/test/java/org/apache/gravitino/rel/TestView.java 
b/api/src/test/java/org/apache/gravitino/rel/TestView.java
new file mode 100644
index 0000000000..4205424b6e
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/rel/TestView.java
@@ -0,0 +1,155 @@
+/*
+ * 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.rel;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Instant;
+import java.util.Optional;
+import org.apache.gravitino.Audit;
+import org.junit.jupiter.api.Test;
+
+public class TestView {
+
+  @Test
+  public void testSqlForReturnsMatchingSqlRepresentation() {
+    SQLRepresentation trinoRepresentation =
+        SQLRepresentation.builder().withDialect("trino").withSql("select 
1").build();
+    SQLRepresentation sparkRepresentation =
+        SQLRepresentation.builder().withDialect("spark").withSql("select 
2").build();
+
+    View view =
+        new View() {
+          @Override
+          public String name() {
+            return "test_view";
+          }
+
+          @Override
+          public Column[] columns() {
+            return new Column[0];
+          }
+
+          @Override
+          public Representation[] representations() {
+            return new Representation[] {trinoRepresentation, 
sparkRepresentation};
+          }
+
+          @Override
+          public Audit auditInfo() {
+            return testAudit();
+          }
+        };
+
+    Optional<SQLRepresentation> sqlRepresentation = view.sqlFor("TRINO");
+
+    assertTrue(sqlRepresentation.isPresent());
+    assertEquals(trinoRepresentation, sqlRepresentation.get());
+  }
+
+  @Test
+  public void testSqlForReturnsEmptyWhenDialectIsMissing() {
+    View view =
+        
testView(SQLRepresentation.builder().withDialect("spark").withSql("select 
1").build());
+
+    assertFalse(view.sqlFor("trino").isPresent());
+  }
+
+  @Test
+  public void testSqlForReturnsEmptyWhenDialectIsNull() {
+    View view =
+        
testView(SQLRepresentation.builder().withDialect("trino").withSql("select 
1").build());
+
+    assertFalse(view.sqlFor(null).isPresent());
+  }
+
+  @Test
+  public void testSqlForReturnsEmptyWhenRepresentationIsNotSql() {
+    View view =
+        testView(
+            new Representation() {
+              @Override
+              public String type() {
+                return "custom";
+              }
+            });
+
+    assertFalse(view.sqlFor("trino").isPresent());
+  }
+
+  @Test
+  public void testDefaultCatalogAndSchemaDefaultToNull() {
+    View view =
+        
testView(SQLRepresentation.builder().withDialect("trino").withSql("select 
1").build());
+
+    assertNull(view.defaultCatalog());
+    assertNull(view.defaultSchema());
+  }
+
+  private static View testView(Representation... representations) {
+    return new View() {
+      @Override
+      public String name() {
+        return "test_view";
+      }
+
+      @Override
+      public Column[] columns() {
+        return new Column[0];
+      }
+
+      @Override
+      public Representation[] representations() {
+        return representations;
+      }
+
+      @Override
+      public Audit auditInfo() {
+        return testAudit();
+      }
+    };
+  }
+
+  private static Audit testAudit() {
+    return new Audit() {
+      @Override
+      public String creator() {
+        return "test";
+      }
+
+      @Override
+      public Instant createTime() {
+        return Instant.EPOCH;
+      }
+
+      @Override
+      public String lastModifier() {
+        return "test";
+      }
+
+      @Override
+      public Instant lastModifiedTime() {
+        return Instant.EPOCH;
+      }
+    };
+  }
+}
diff --git a/api/src/test/java/org/apache/gravitino/rel/TestViewChange.java 
b/api/src/test/java/org/apache/gravitino/rel/TestViewChange.java
new file mode 100644
index 0000000000..aba17bf5d7
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/rel/TestViewChange.java
@@ -0,0 +1,126 @@
+/*
+ * 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.rel;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Test;
+
+public class TestViewChange {
+
+  @Test
+  public void testReplaceView() {
+    Column[] columns = {Column.of("id", Types.IntegerType.get(), "id")};
+    Representation[] representations = {
+      SQLRepresentation.builder().withDialect("trino").withSql("select 
id").build()
+    };
+
+    ViewChange.ReplaceView replaceView =
+        (ViewChange.ReplaceView)
+            ViewChange.replaceView(
+                columns, representations, "test_catalog", "test_schema", 
"test_comment");
+
+    assertArrayEquals(columns, replaceView.getColumns());
+    assertArrayEquals(representations, replaceView.getRepresentations());
+    assertEquals("test_catalog", replaceView.getDefaultCatalog());
+    assertEquals("test_schema", replaceView.getDefaultSchema());
+    assertEquals("test_comment", replaceView.getComment());
+  }
+
+  @Test
+  public void testReplaceViewDefensivelyCopiesArrays() {
+    Column[] columns = {Column.of("id", Types.IntegerType.get(), "id")};
+    Representation[] representations = {
+      SQLRepresentation.builder().withDialect("trino").withSql("select 
id").build()
+    };
+
+    ViewChange.ReplaceView replaceView =
+        (ViewChange.ReplaceView) ViewChange.replaceView(columns, 
representations, null, null, null);
+
+    columns[0] = Column.of("name", Types.StringType.get(), "name");
+    representations[0] =
+        SQLRepresentation.builder().withDialect("spark").withSql("select 
name").build();
+
+    assertEquals("id", replaceView.getColumns()[0].name());
+    assertEquals("trino", ((SQLRepresentation) 
replaceView.getRepresentations()[0]).dialect());
+
+    replaceView.getColumns()[0] = Column.of("age", Types.IntegerType.get(), 
"age");
+    replaceView.getRepresentations()[0] =
+        SQLRepresentation.builder().withDialect("hive").withSql("select 
age").build();
+
+    assertEquals("id", replaceView.getColumns()[0].name());
+    assertEquals("trino", ((SQLRepresentation) 
replaceView.getRepresentations()[0]).dialect());
+  }
+
+  @Test
+  public void testReplaceViewAllowsNullDefaultsAndComment() {
+    ViewChange.ReplaceView replaceView =
+        (ViewChange.ReplaceView)
+            ViewChange.replaceView(
+                new Column[] {Column.of("id", Types.IntegerType.get(), "id")},
+                new Representation[] {
+                  
SQLRepresentation.builder().withDialect("trino").withSql("select id").build()
+                },
+                null,
+                null,
+                null);
+
+    assertNull(replaceView.getDefaultCatalog());
+    assertNull(replaceView.getDefaultSchema());
+    assertNull(replaceView.getComment());
+  }
+
+  @Test
+  public void testReplaceViewRejectsNullColumns() {
+    IllegalArgumentException exception =
+        assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                ViewChange.replaceView(
+                    null,
+                    new Representation[] {
+                      
SQLRepresentation.builder().withDialect("trino").withSql("select id").build()
+                    },
+                    null,
+                    null,
+                    null));
+
+    assertEquals("columns must not be null", exception.getMessage());
+  }
+
+  @Test
+  public void testReplaceViewRejectsEmptyRepresentations() {
+    IllegalArgumentException exception =
+        assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                ViewChange.replaceView(
+                    new Column[] {Column.of("id", Types.IntegerType.get(), 
"id")},
+                    new Representation[0],
+                    null,
+                    null,
+                    null));
+
+    assertEquals("representations must not be null or empty", 
exception.getMessage());
+  }
+}
diff --git 
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergView.java
 
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergView.java
index 73894d4745..955b0ed98c 100644
--- 
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergView.java
+++ 
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergView.java
@@ -24,6 +24,8 @@ import java.util.Map;
 import lombok.Getter;
 import lombok.ToString;
 import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
 import org.apache.gravitino.rel.View;
 import org.apache.iceberg.rest.responses.LoadViewResponse;
 
@@ -68,6 +70,16 @@ public class IcebergView implements View {
     return properties != null ? properties : Collections.emptyMap();
   }
 
+  @Override
+  public Column[] columns() {
+    return new Column[0];
+  }
+
+  @Override
+  public Representation[] representations() {
+    return new Representation[0];
+  }
+
   @Override
   public AuditInfo auditInfo() {
     return auditInfo;
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedView.java 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedView.java
index da8946b569..e11f89b2d6 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedView.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedView.java
@@ -21,6 +21,8 @@ package org.apache.gravitino.catalog;
 import java.util.Map;
 import org.apache.gravitino.Audit;
 import org.apache.gravitino.meta.GenericEntity;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
 import org.apache.gravitino.rel.View;
 
 /**
@@ -57,6 +59,26 @@ public final class EntityCombinedView implements View {
     return this;
   }
 
+  @Override
+  public Column[] columns() {
+    return view.columns();
+  }
+
+  @Override
+  public Representation[] representations() {
+    return view.representations();
+  }
+
+  @Override
+  public String defaultCatalog() {
+    return view.defaultCatalog();
+  }
+
+  @Override
+  public String defaultSchema() {
+    return view.defaultSchema();
+  }
+
   @Override
   public String name() {
     return view.name();
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java 
b/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
index 70f080f7d7..4c334effe6 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
@@ -38,8 +38,8 @@ import org.slf4j.LoggerFactory;
 /**
  * {@code ViewOperationDispatcher} is the operation dispatcher for view 
operations.
  *
- * <p>Currently only supports loadView(). Full CRUD operations (create, alter, 
drop) needs to be
- * added when Gravitino APIs support view management.
+ * <p>Currently only supports loadView() with EntityStore auto-import. Full 
CRUD operations
+ * (listViews, createView, alterView, dropView) will be implemented in a 
follow-up PR.
  */
 public class ViewOperationDispatcher extends OperationDispatcher implements 
ViewDispatcher {
 
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
 
b/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
index 9f91ea6afe..f73cccb583 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
@@ -49,6 +49,8 @@ import org.apache.gravitino.exceptions.NoSuchViewException;
 import org.apache.gravitino.lock.LockManager;
 import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.meta.GenericEntity;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
 import org.apache.gravitino.rel.View;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
@@ -100,6 +102,16 @@ public class TestViewOperationDispatcher extends 
TestOperationDispatcher {
         return name;
       }
 
+      @Override
+      public Column[] columns() {
+        return new Column[0];
+      }
+
+      @Override
+      public Representation[] representations() {
+        return new Representation[0];
+      }
+
       @Override
       public Map<String, String> properties() {
         return props;
diff --git 
a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java 
b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
index 78658035e9..15c4be23bf 100644
--- 
a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
@@ -302,16 +302,6 @@ public class TestCatalogOperations
     }
   }
 
-  // ViewCatalog methods
-  @Override
-  public View loadView(NameIdentifier ident) throws NoSuchViewException {
-    if (views.containsKey(ident)) {
-      return views.get(ident);
-    } else {
-      throw new NoSuchViewException("View %s does not exist", ident);
-    }
-  }
-
   @Override
   public NameIdentifier[] listSchemas(Namespace namespace) throws 
NoSuchCatalogException {
     return schemas.keySet().stream()
@@ -1433,4 +1423,13 @@ public class TestCatalogOperations
 
     return aliasList.toArray(new String[0]);
   }
+
+  @Override
+  public View loadView(NameIdentifier ident) throws NoSuchViewException {
+    if (views.containsKey(ident)) {
+      return views.get(ident);
+    } else {
+      throw new NoSuchViewException("View %s does not exist", ident);
+    }
+  }
 }

Reply via email to