jerryshao commented on code in PR #12626:
URL: https://github.com/apache/gravitino/pull/12626#discussion_r4130815625


##########
common/src/main/java/org/apache/gravitino/dto/requests/SemanticModelUpdateRequest.java:
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.semantic.SemanticModelDefinitionDTO;
+import org.apache.gravitino.rest.RESTRequest;
+import org.apache.gravitino.semantic.SemanticModelChange;
+
+/** Represents one change in a request to alter a Semantic Model. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
+@JsonSubTypes({
+  @JsonSubTypes.Type(
+      value = SemanticModelUpdateRequest.RenameSemanticModelRequest.class,
+      name = "rename"),
+  @JsonSubTypes.Type(
+      value = 
SemanticModelUpdateRequest.UpdateSemanticModelCommentRequest.class,
+      name = "updateComment"),
+  @JsonSubTypes.Type(
+      value = SemanticModelUpdateRequest.SetSemanticModelPropertyRequest.class,
+      name = "setProperty"),
+  @JsonSubTypes.Type(
+      value = 
SemanticModelUpdateRequest.RemoveSemanticModelPropertyRequest.class,
+      name = "removeProperty"),
+  @JsonSubTypes.Type(
+      value = 
SemanticModelUpdateRequest.ReplaceSemanticModelDefinitionRequest.class,
+      name = "replaceDefinition")
+})
+public interface SemanticModelUpdateRequest extends RESTRequest {
+
+  /**
+   * Returns the Semantic Model change represented by this request.
+   *
+   * @return The Semantic Model change.
+   */
+  SemanticModelChange semanticModelChange();
+
+  /** Represents a request to rename a Semantic Model. */
+  @Getter
+  @EqualsAndHashCode
+  @ToString
+  class RenameSemanticModelRequest implements SemanticModelUpdateRequest {
+
+    @JsonProperty("newName")
+    private final String newName;
+
+    /** Default constructor for Jackson deserialization. */
+    public RenameSemanticModelRequest() {
+      this(null);
+    }
+
+    /**
+     * Creates a request to rename a Semantic Model.
+     *
+     * @param newName The new Semantic Model name.
+     */
+    public RenameSemanticModelRequest(String newName) {
+      this.newName = newName;
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(newName), "\"newName\" field is required and 
cannot be empty");
+    }
+
+    @Override
+    public SemanticModelChange semanticModelChange() {
+      return SemanticModelChange.rename(newName);
+    }
+  }
+
+  /** Represents a request to update a Semantic Model comment. */
+  @Getter
+  @EqualsAndHashCode
+  @ToString
+  class UpdateSemanticModelCommentRequest implements 
SemanticModelUpdateRequest {
+
+    @Nullable
+    @JsonProperty("newComment")
+    private final String newComment;
+
+    /** Default constructor for Jackson deserialization. */
+    public UpdateSemanticModelCommentRequest() {
+      this(null);
+    }
+
+    /**
+     * Creates a request to update or clear a Semantic Model comment.
+     *
+     * @param newComment The new comment, or {@code null} to clear it.
+     */
+    public UpdateSemanticModelCommentRequest(@Nullable String newComment) {
+      this.newComment = newComment;
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      // A null comment clears the current comment; an empty comment is stored 
as supplied.
+    }
+
+    @Override
+    public SemanticModelChange semanticModelChange() {
+      return SemanticModelChange.updateComment(newComment);
+    }
+  }
+
+  /** Represents a request to set a Semantic Model property. */
+  @Getter
+  @EqualsAndHashCode
+  @ToString
+  class SetSemanticModelPropertyRequest implements SemanticModelUpdateRequest {
+
+    @JsonProperty("property")
+    private final String property;
+
+    @JsonProperty("value")
+    private final String value;
+
+    /** Default constructor for Jackson deserialization. */
+    public SetSemanticModelPropertyRequest() {
+      this(null, null);
+    }
+
+    /**
+     * Creates a request to set a Semantic Model property.
+     *
+     * @param property The property name.
+     * @param value The property value.
+     */
+    public SetSemanticModelPropertyRequest(String property, String value) {
+      this.property = property;
+      this.value = value;
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(property), "\"property\" field is required 
and cannot be empty");
+      Preconditions.checkArgument(value != null, "\"value\" field is required 
and cannot be null");
+    }
+
+    @Override
+    public SemanticModelChange semanticModelChange() {
+      return SemanticModelChange.setProperty(property, value);
+    }
+  }
+
+  /** Represents a request to remove a Semantic Model property. */
+  @Getter
+  @EqualsAndHashCode
+  @ToString
+  class RemoveSemanticModelPropertyRequest implements 
SemanticModelUpdateRequest {
+
+    @JsonProperty("property")
+    private final String property;
+
+    /** Default constructor for Jackson deserialization. */
+    public RemoveSemanticModelPropertyRequest() {
+      this(null);
+    }
+
+    /**
+     * Creates a request to remove a Semantic Model property.
+     *
+     * @param property The property name.
+     */
+    public RemoveSemanticModelPropertyRequest(String property) {
+      this.property = property;
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(property), "\"property\" field is required 
and cannot be empty");
+    }
+
+    @Override
+    public SemanticModelChange semanticModelChange() {
+      return SemanticModelChange.removeProperty(property);
+    }
+  }
+
+  /** Represents a request to replace the complete Semantic Model definition. 
*/
+  @Getter
+  @EqualsAndHashCode
+  @ToString
+  class ReplaceSemanticModelDefinitionRequest implements 
SemanticModelUpdateRequest {
+
+    @JsonProperty("definition")
+    private final SemanticModelDefinitionDTO definition;
+
+    /** Default constructor for Jackson deserialization. */
+    public ReplaceSemanticModelDefinitionRequest() {
+      this(null);
+    }
+
+    /**
+     * Creates a request to replace the complete Semantic Model definition.
+     *
+     * @param definition The replacement definition DTO.
+     */
+    public ReplaceSemanticModelDefinitionRequest(SemanticModelDefinitionDTO 
definition) {
+      this.definition = definition;
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          definition != null, "\"definition\" field is required and cannot be 
null");
+      definition.toDefinition();

Review Comment:
   [Nit] `toDefinition()` runs twice per `replaceDefinition` update: once here 
with the result discarded, then again in `semanticModelChange()` (line 242). 
For a definition with many datasets, fields and metrics that doubles the 
DTO-to-API conversion on every alter request.
   
   Converting once and keeping the result would avoid it, e.g. validate into a 
field and return `SemanticModelChange.replaceDefinition(converted)` — with the 
caveat that `semanticModelChange()` then still needs to behave sensibly if it 
is ever called without `validate()` first. Purely a cleanup; the current 
behaviour is correct.
   
   Verified by: read both methods in full and confirmed 
`SemanticModelOperations.alterSemanticModel:242-249` calls `validate()` and 
then `semanticModelChange()` on the same instances.



##########
server/src/main/java/org/apache/gravitino/server/web/rest/SemanticModelOperations.java:
##########
@@ -157,4 +204,103 @@ public Response loadSemanticModel(
           OperationType.LOAD, semanticModel, schema, e);
     }
   }
+
+  /**
+   * Alters a Semantic Model atomically.
+   *
+   * @param metalake The metalake name.
+   * @param catalog The catalog name.
+   * @param schema The schema name.
+   * @param semanticModel The current Semantic Model name.
+   * @param request The updates to apply.
+   * @return A response containing the altered Semantic Model.
+   */
+  @PUT
+  @Path("{semanticModel}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "alter-semantic-model." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "alter-semantic-model", absolute = true)
+  public Response alterSemanticModel(
+      @PathParam("metalake") String metalake,
+      @PathParam("catalog") String catalog,
+      @PathParam("schema") String schema,
+      @PathParam("semanticModel") String semanticModel,
+      SemanticModelUpdatesRequest request) {
+    LOG.info(
+        "Received alter Semantic Model request: {}.{}.{}.{}",
+        metalake,
+        catalog,
+        schema,
+        semanticModel);
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            if (request == null) {
+              throw new IllegalArgumentException("Request body must not be 
null");
+            }

Review Comment:
   [Nit] This branch is not covered, unlike its counterpart on create. 
`testCreateSemanticModelRejectsNullBody` posts the literal body `null` through 
the `postJson` helper and asserts 400 plus `"Request body must not be null"` 
(`TestSemanticModelOperations.java:389-398`), but the new `put` helper only 
takes a typed `SemanticModelUpdatesRequest`, so a `null` alter body is never 
exercised.
   
   A `putJson(path, "null")` helper mirroring `postJson`, plus one assertion, 
would close the gap and lock the 400 in place.
   
   Verified by: read the full test file and both request helpers; grepped the 
test for null-body cases — only the create one exists.



##########
server/src/main/java/org/apache/gravitino/server/web/rest/SemanticModelOperations.java:
##########
@@ -63,6 +72,44 @@ public SemanticModelOperations(SemanticModelDispatcher 
dispatcher) {
     this.dispatcher = dispatcher;
   }
 
+  /**
+   * Lists Semantic Models in a schema.
+   *
+   * @param metalake The metalake name.
+   * @param catalog The catalog name.
+   * @param schema The schema name.
+   * @return A response containing Semantic Model identifiers.
+   */
+  @GET
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "list-semantic-model." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "list-semantic-model", absolute = true)
+  public Response listSemanticModels(
+      @PathParam("metalake") String metalake,
+      @PathParam("catalog") String catalog,
+      @PathParam("schema") String schema) {
+    LOG.info(
+        "Received list Semantic Models request for schema: {}.{}.{}", 
metalake, catalog, schema);
+    try {
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            Namespace namespace = NamespaceUtil.ofSemanticModel(metalake, 
catalog, schema);
+            NameIdentifier[] identifiers = 
dispatcher.listSemanticModels(namespace);
+            identifiers = identifiers == null ? new NameIdentifier[0] : 
identifiers;

Review Comment:
   [Nit] This null fallback is unreachable and the test branch that pairs with 
it asserts behaviour no implementation can produce.
   
   `SemanticModelCatalog.listSemanticModels` declares a plain 
`NameIdentifier[]` with no nullability allowance 
(`api/src/main/java/org/apache/gravitino/semantic/SemanticModelCatalog.java:35-42`),
 the merged core implementation always builds an array via 
`toArray(NameIdentifier[]::new)` 
(`core/.../ManagedSemanticModelOperations.java:75-87` on `origin/main`), and 
`SemanticModelNormalizeDispatcher` just forwards it. No other list endpoint in 
the server guards this — compare `ViewOperations.listViews`, which passes the 
dispatcher result straight into `EntityListResponse`.
   
   Suggest dropping this line and the matching `thenReturn(null)` assertion in 
`TestSemanticModelOperations#testListSemanticModels`, so the dispatcher 
contract stays the single source of truth.
   
   Verified by: read the API interface, both core implementations (PR head and 
`origin/main`), the normalize dispatcher, and `ViewOperations.listViews` in 
this run.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to