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);
+ }
+ }
}