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]
