This is an automated email from the ASF dual-hosted git repository. jackie pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push: new 777796a3615 Encode column and segment names when constructing metadata read URL (#16940) 777796a3615 is described below commit 777796a36151d73bdc4f7b08e9ece55f213d1ee4 Author: Xiaotian (Jackie) Jiang <17555551+jackie-ji...@users.noreply.github.com> AuthorDate: Fri Oct 3 11:22:37 2025 -0700 Encode column and segment names when constructing metadata read URL (#16940) --- .../broker/api/resources/PinotBrokerRouting.java | 7 +- .../controller/api/resources/DebugResource.java | 8 +- .../api/resources/PinotSegmentRestletResource.java | 27 +++--- .../api/resources/PinotTableRestletResource.java | 4 +- .../util/ServerSegmentMetadataReader.java | 69 +++++++-------- .../tests/HybridClusterIntegrationTest.java | 16 +--- .../tests/OfflineClusterIntegrationTest.java | 15 ---- .../server/api/resources/ReingestionResource.java | 5 +- .../api/resources/SegmentMetadataFetcher.java | 8 +- .../pinot/server/api/resources/TablesResource.java | 99 ++++++++-------------- .../utils/builder/ControllerRequestURLBuilder.java | 57 ++----------- .../pinot/spi/utils/builder/UrlBuilderUtils.java | 75 ++++++++++++++++ 12 files changed, 180 insertions(+), 210 deletions(-) diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java index 546e155cf0c..dd55158e5ad 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java @@ -29,6 +29,7 @@ import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; import javax.inject.Inject; import javax.ws.rs.DELETE; +import javax.ws.rs.Encoded; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -40,6 +41,7 @@ import org.apache.helix.HelixManager; import org.apache.pinot.broker.routing.BrokerRoutingManager; import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.utils.DatabaseUtils; +import org.apache.pinot.common.utils.URIUtils; import org.apache.pinot.core.auth.Actions; import org.apache.pinot.core.auth.Authorize; import org.apache.pinot.core.auth.TargetType; @@ -97,9 +99,10 @@ public class PinotBrokerRouting { }) public String refreshRouting( @ApiParam(value = "Table name (with type)") @PathParam("tableName") String tableNameWithType, - @ApiParam(value = "Segment name") @PathParam("segmentName") String segmentName, + @ApiParam(value = "Segment name") @PathParam("segmentName") @Encoded String segmentName, @Context HttpHeaders headers) { - _routingManager.refreshSegment(DatabaseUtils.translateTableName(tableNameWithType, headers), segmentName); + _routingManager.refreshSegment(DatabaseUtils.translateTableName(tableNameWithType, headers), + URIUtils.decode(segmentName)); return "Success"; } diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java index 6a2235389c1..106bb3a4aef 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java @@ -42,6 +42,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import javax.ws.rs.DefaultValue; +import javax.ws.rs.Encoded; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -63,6 +64,7 @@ import org.apache.pinot.common.restlet.resources.SegmentConsumerInfo; import org.apache.pinot.common.restlet.resources.SegmentErrorInfo; import org.apache.pinot.common.restlet.resources.SegmentServerDebugInfo; import org.apache.pinot.common.utils.DatabaseUtils; +import org.apache.pinot.common.utils.URIUtils; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.api.debug.TableDebugInfo; import org.apache.pinot.controller.api.exception.ControllerApplicationException; @@ -168,12 +170,12 @@ public class DebugResource { }) public TableDebugInfo.SegmentDebugInfo getSegmentDebugInfo( @ApiParam(value = "Name of the table (with type)", required = true) @PathParam("tableName") - String tableNameWithType, - @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") String segmentName, + String tableNameWithType, + @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") @Encoded String segmentName, @Context HttpHeaders headers) throws Exception { tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType, headers); - return debugSegment(tableNameWithType, segmentName); + return debugSegment(tableNameWithType, URIUtils.decode(segmentName)); } /** diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java index 33441ed9067..def5f3fa843 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java @@ -350,7 +350,7 @@ public class PinotSegmentRestletResource { if (segmentMetadata != null) { Map<String, Object> result = new HashMap<>(segmentMetadata); - if (columns.size() > 0) { + if (!columns.isEmpty()) { JsonNode segmentsMetadataJson = getExtraMetaData(tableName, segmentName, columns); if (segmentsMetadataJson.has("indexes")) { result.put("indexes", segmentsMetadataJson.get("indexes")); @@ -815,7 +815,7 @@ public class PinotSegmentRestletResource { } String tableNameWithType = ResourceUtils.getExistingTableNamesWithType(_pinotHelixResourceManager, tableName, tableType, LOGGER).get(0); - if (segments == null || segments.isEmpty()) { + if (segments.isEmpty()) { deleteSegmentsInternal(tableNameWithType, _pinotHelixResourceManager.getSegmentsFromPropertyStore(tableNameWithType), retentionPeriod); return new SuccessResponse("All segments of table " + tableNameWithType + " deleted"); @@ -876,9 +876,9 @@ public class PinotSegmentRestletResource { + " specified in the segment lineage entries and cannot be queried from the table, false by default") @QueryParam("excludeReplacedSegments") @DefaultValue("false") boolean excludeReplacedSegments, @ApiParam(value = "Start timestamp (inclusive) in milliseconds", required = true) @QueryParam("startTimestamp") - String startTimestampStr, + String startTimestampStr, @ApiParam(value = "End timestamp (exclusive) in milliseconds", required = true) @QueryParam("endTimestamp") - String endTimestampStr, + String endTimestampStr, @ApiParam(value = "Whether to ignore segments that are partially overlapping with the [start, end)" + "for deletion, true by default") @QueryParam("excludeOverlapping") @DefaultValue("true") boolean excludeOverlapping, @@ -926,12 +926,13 @@ public class PinotSegmentRestletResource { public String getServerMetadata( @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String tableTypeStr, - @Encoded @ApiParam(value = "Segments to include (all if not specified)", allowMultiple = true) - @QueryParam("segments") @Nullable List<String> segments, - @Encoded @ApiParam(value = "Columns name", allowMultiple = true) @QueryParam("columns") - @Nullable List<String> columns, @Context HttpHeaders headers) { + @ApiParam(value = "Segments to include (all if not specified)", allowMultiple = true) @QueryParam("segments") + List<String> segments, + @ApiParam(value = "Columns to include (use '*' to include all)", allowMultiple = true) @QueryParam("columns") + List<String> columns, + @Context HttpHeaders headers) { tableName = DatabaseUtils.translateTableName(tableName, headers); - String segmentCount = (segments == null) ? "all" : String.valueOf(segments.size()); + String segmentCount = segments.isEmpty() ? "all" : String.valueOf(segments.size()); LOGGER.info("Received a request to fetch metadata for {} segments for table {}", segmentCount, tableName); TableType tableType = Constants.validateTableType(tableTypeStr); @@ -1162,14 +1163,12 @@ public class PinotSegmentRestletResource { * @param segments name of the segments to include in metadata * @return Map<String, String> metadata of the table segments -> map of segment name to its metadata */ - private JsonNode getSegmentsMetadataFromServer(String tableNameWithType, @Nullable List<String> columns, - @Nullable List<String> segments) + private JsonNode getSegmentsMetadataFromServer(String tableNameWithType, List<String> columns, List<String> segments) throws InvalidConfigException, IOException { TableMetadataReader tableMetadataReader = new TableMetadataReader(_executor, _connectionManager, _pinotHelixResourceManager); - return tableMetadataReader - .getSegmentsMetadata(tableNameWithType, columns, segments, - _controllerConf.getServerAdminRequestTimeoutSeconds() * 1000); + return tableMetadataReader.getSegmentsMetadata(tableNameWithType, columns, segments, + _controllerConf.getServerAdminRequestTimeoutSeconds() * 1000); } @POST diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java index 7f52166d24c..505216602fb 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java @@ -1097,8 +1097,8 @@ public class PinotTableRestletResource { public String getTableAggregateMetadata( @ApiParam(value = "Name of the table", required = true) @PathParam("tableName") String tableName, @ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String tableTypeStr, - @ApiParam(value = "Columns name", allowMultiple = true) @QueryParam("columns") @DefaultValue("") - List<String> columns, @Context HttpHeaders headers) { + @ApiParam(value = "Columns name", allowMultiple = true) @QueryParam("columns") List<String> columns, + @Context HttpHeaders headers) { tableName = DatabaseUtils.translateTableName(tableName, headers); LOGGER.info("Received a request to fetch aggregate metadata for a table {}", tableName); TableType tableType = Constants.validateTableType(tableTypeStr); diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java b/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java index c0ebfd5313c..53cf628c7fb 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java @@ -39,7 +39,6 @@ import javax.annotation.Nullable; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager; @@ -51,6 +50,7 @@ import org.apache.pinot.common.utils.RoaringBitmapUtils; import org.apache.pinot.controller.api.resources.TableStaleSegmentResponse; import org.apache.pinot.segment.local.data.manager.StaleSegment; import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.builder.UrlBuilderUtils; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.roaringbitmap.RoaringBitmap; @@ -430,38 +430,41 @@ public class ServerSegmentMetadataReader { return serverResponses; } - private String generateAggregateSegmentMetadataServerURL(String tableNameWithType, List<String> columns, + private String generateAggregateSegmentMetadataServerURL(String tableNameWithType, @Nullable List<String> columns, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); - String paramsStr = generateParam(COLUMNS_KEY, columns); - return String.format("%s/tables/%s/metadata?%s", endpoint, tableNameWithType, paramsStr); + tableNameWithType = encode(tableNameWithType); + String columnsParam = UrlBuilderUtils.generateColumnsParam(columns); + String url = String.format("%s/tables/%s/metadata", endpoint, tableNameWithType); + return columnsParam != null ? url + "?" + columnsParam : url; } public String generateSegmentMetadataServerURL(String tableNameWithType, String segmentName, @Nullable List<String> columns, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); - segmentName = URLEncoder.encode(segmentName, StandardCharsets.UTF_8); - String paramsStr = generateParam(COLUMNS_KEY, columns); - return String.format("%s/tables/%s/segments/%s/metadata?%s", endpoint, tableNameWithType, segmentName, paramsStr); + tableNameWithType = encode(tableNameWithType); + segmentName = UrlBuilderUtils.encode(segmentName); + String columnsParam = UrlBuilderUtils.generateColumnsParam(columns); + String url = String.format("%s/tables/%s/segments/%s/metadata", endpoint, tableNameWithType, segmentName); + return columnsParam != null ? url + "?" + columnsParam : url; } public String generateTableMetadataServerURL(String tableNameWithType, @Nullable List<String> columns, - @Nullable List<String> segmentsToInclude, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); - String paramsStr = generateParam(COLUMNS_KEY, columns) + "&" + generateParam(SEGMENTS_KEY, segmentsToInclude); - return String.format("%s/tables/%s/segments/metadata?%s", endpoint, tableNameWithType, paramsStr); + @Nullable List<String> segments, String endpoint) { + tableNameWithType = encode(tableNameWithType); + String columnsAndSegmentsParam = UrlBuilderUtils.generateColumnsAndSegmentsParam(columns, segments); + String url = String.format("%s/tables/%s/segments/metadata", endpoint, tableNameWithType); + return columnsAndSegmentsParam != null ? url + "?" + columnsAndSegmentsParam : url; } private String generateCheckReloadSegmentsServerURL(String tableNameWithType, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); + tableNameWithType = encode(tableNameWithType); return String.format("%s/tables/%s/segments/needReload", endpoint, tableNameWithType); } @Deprecated private String generateValidDocIdsURL(String tableNameWithType, String segmentName, String validDocIdsType, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); - segmentName = URLEncoder.encode(segmentName, StandardCharsets.UTF_8); + tableNameWithType = encode(tableNameWithType); + segmentName = encode(segmentName); String url = String.format("%s/segments/%s/%s/validDocIds", endpoint, tableNameWithType, segmentName); if (validDocIdsType != null) { url = url + "?validDocIdsType=" + validDocIdsType; @@ -471,18 +474,15 @@ public class ServerSegmentMetadataReader { private String generateValidDocIdsBitmapURL(String tableNameWithType, String segmentName, String validDocIdsType, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); - segmentName = URLEncoder.encode(segmentName, StandardCharsets.UTF_8); + tableNameWithType = encode(tableNameWithType); + segmentName = encode(segmentName); String url = String.format("%s/segments/%s/%s/validDocIdsBitmap", endpoint, tableNameWithType, segmentName); - if (validDocIdsType != null) { - url = url + "?validDocIdsType=" + validDocIdsType; - } - return url; + return validDocIdsType != null ? url + "?validDocIdsType=" + validDocIdsType : url; } private Pair<String, String> generateValidDocIdsMetadataURL(String tableNameWithType, List<String> segmentNames, String validDocIdsType, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); + tableNameWithType = encode(tableNameWithType); TableSegments tableSegments = new TableSegments(segmentNames); String jsonTableSegments; try { @@ -499,28 +499,19 @@ public class ServerSegmentMetadataReader { } private String generateStaleSegmentsServerURL(String tableNameWithType, String endpoint) { - tableNameWithType = URLEncoder.encode(tableNameWithType, StandardCharsets.UTF_8); + tableNameWithType = encode(tableNameWithType); return String.format("%s/tables/%s/segments/isStale", endpoint, tableNameWithType); } - private String generateParam(String key, List<String> values) { - String paramsStr = ""; - if (CollectionUtils.isEmpty(values)) { - return paramsStr; - } - List<String> params = new ArrayList<>(values.size()); - for (String value : values) { - params.add(key + "=" + value); - } - paramsStr = String.join("&", params); - return paramsStr; + private static String encode(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8); } - public class TableReloadResponse { - private int _numFailedResponses; - private List<String> _serverReloadResponses; + public static class TableReloadResponse { + private final int _numFailedResponses; + private final List<String> _serverReloadResponses; - TableReloadResponse(int numFailedResponses, List<String> serverReloadResponses) { + private TableReloadResponse(int numFailedResponses, List<String> serverReloadResponses) { _numFailedResponses = numFailedResponses; _serverReloadResponses = serverReloadResponses; } diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java index 57a7ad236d9..b8a6f5c2f9a 100644 --- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java +++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java @@ -19,9 +19,6 @@ package org.apache.pinot.integration.tests; import com.fasterxml.jackson.databind.JsonNode; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -125,12 +122,7 @@ public class HybridClusterIntegrationTest extends BaseHybridClusterIntegrationTe segmentMetadataFromDirectEndpoint.get("segment.total.docs")); } // get list of segment names to pass in query params for following tests - List<String> segmentNames = getSegmentNames(getTableName(), TableType.OFFLINE.toString()); - List<String> segments = new ArrayList<>(); - for (String segment : segmentNames) { - String encodedSegmentName = URLEncoder.encode(segment, StandardCharsets.UTF_8.toString()); - segments.add(encodedSegmentName); - } + List<String> segments = getSegmentNames(getTableName(), TableType.OFFLINE.toString()); // with null column params { String jsonOutputStr = sendGetRequest(_controllerRequestURLBuilder.forSegmentsMetadataFromServer(getTableName(), @@ -143,7 +135,7 @@ public class HybridClusterIntegrationTest extends BaseHybridClusterIntegrationTe JsonNode segmentMetadataFromDirectEndpoint = JsonUtils.stringToJsonNode(jsonOutputStr); Assert.assertEquals(segmentMetadataFromAllEndpoint.get("totalDocs"), segmentMetadataFromDirectEndpoint.get("segment.total.docs")); - Assert.assertEquals(tableSegmentsMetadata.get(segmentNames.get(0)).get("columns").size(), 0); + Assert.assertEquals(tableSegmentsMetadata.get(segments.get(0)).get("columns").size(), 0); } // with * column param { @@ -157,7 +149,7 @@ public class HybridClusterIntegrationTest extends BaseHybridClusterIntegrationTe JsonNode segmentMetadataFromDirectEndpoint = JsonUtils.stringToJsonNode(jsonOutputStr); Assert.assertEquals(segmentMetadataFromAllEndpoint.get("totalDocs"), segmentMetadataFromDirectEndpoint.get("segment.total.docs")); - Assert.assertEquals(tableSegmentsMetadata.get(segmentNames.get(0)).get("columns").size(), 79); + Assert.assertEquals(tableSegmentsMetadata.get(segments.get(0)).get("columns").size(), 79); } // with specified column params { @@ -172,7 +164,7 @@ public class HybridClusterIntegrationTest extends BaseHybridClusterIntegrationTe JsonNode segmentMetadataFromDirectEndpoint = JsonUtils.stringToJsonNode(jsonOutputStr); Assert.assertEquals(segmentMetadataFromAllEndpoint.get("totalDocs"), segmentMetadataFromDirectEndpoint.get("segment.total.docs")); - Assert.assertEquals(tableSegmentsMetadata.get(segmentNames.get(0)).get("columns").size(), columns.size()); + Assert.assertEquals(tableSegmentsMetadata.get(segments.get(0)).get("columns").size(), columns.size()); } } diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java index 02c16814488..25c09423795 100644 --- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java +++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java @@ -3763,20 +3763,10 @@ public class OfflineClusterIntegrationTest extends BaseClusterIntegrationTestSet + "/tables/mytable/metadata?columns=DivActualElapsedTime&columns=CRSElapsedTime&columns=OriginStateName")); validateMetadataResponse(threeSVColumnsResponse, 3, 0); - JsonNode threeSVColumnsWholeEncodedResponse = JsonUtils.stringToJsonNode(sendGetRequest( - getControllerBaseApiUrl() + "/tables/mytable/metadata?columns=" - + "DivActualElapsedTime%26columns%3DCRSElapsedTime%26columns%3DOriginStateName")); - validateMetadataResponse(threeSVColumnsWholeEncodedResponse, 3, 0); - JsonNode threeMVColumnsResponse = JsonUtils.stringToJsonNode(sendGetRequest(getControllerBaseApiUrl() + "/tables/mytable/metadata?columns=DivLongestGTimes&columns=DivWheelsOns&columns=DivAirports")); validateMetadataResponse(threeMVColumnsResponse, 3, 3); - JsonNode threeMVColumnsWholeEncodedResponse = JsonUtils.stringToJsonNode(sendGetRequest( - getControllerBaseApiUrl() + "/tables/mytable/metadata?columns=" - + "DivLongestGTimes%26columns%3DDivWheelsOns%26columns%3DDivAirports")); - validateMetadataResponse(threeMVColumnsWholeEncodedResponse, 3, 3); - JsonNode zeroColumnResponse = JsonUtils.stringToJsonNode(sendGetRequest(getControllerBaseApiUrl() + "/tables/mytable/metadata")); validateMetadataResponse(zeroColumnResponse, 0, 0); @@ -3798,11 +3788,6 @@ public class OfflineClusterIntegrationTest extends BaseClusterIntegrationTestSet getControllerBaseApiUrl() + "/tables/mytable/metadata?columns=" + "CRSElapsedTime&columns=%2A&columns=OriginStateName")); validateMetadataResponse(starWithExtraEncodedColumnResponse, 83, 10); - - JsonNode starWithExtraColumnWholeEncodedResponse = JsonUtils.stringToJsonNode(sendGetRequest( - getControllerBaseApiUrl() + "/tables/mytable/metadata?columns=" - + "CRSElapsedTime%26columns%3D%2A%26columns%3DOriginStateName")); - validateMetadataResponse(starWithExtraColumnWholeEncodedResponse, 83, 10); } private void validateMetadataResponse(JsonNode response, int numTotalColumn, int numMVColumn) { diff --git a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java index 9b1c93e4dea..6386a85b52c 100644 --- a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java +++ b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java @@ -42,6 +42,7 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import javax.inject.Inject; +import javax.ws.rs.Encoded; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -54,6 +55,7 @@ import javax.ws.rs.core.Response; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.common.utils.URIUtils; import org.apache.pinot.core.data.manager.realtime.RealtimeTableDataManager; import org.apache.pinot.segment.local.realtime.writer.StatelessRealtimeSegmentWriter; import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; @@ -148,7 +150,8 @@ public class ReingestionResource { @ApiResponse(code = 200, message = "Success", response = ReingestionJob.class), @ApiResponse(code = 500, message = "Internal server error", response = ErrorInfo.class) }) - public Response reingestSegment(@PathParam("segmentName") String segmentName) { + public Response reingestSegment(@PathParam("segmentName") @Encoded String encodedSegmentName) { + String segmentName = URIUtils.decode(encodedSegmentName); LOGGER.info("Re-ingesting segment: {}", segmentName); // if segment is not in LLC format, return error diff --git a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java index c557d0681b8..b30abe3db2f 100644 --- a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java +++ b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java @@ -75,13 +75,7 @@ public class SegmentMetadataFetcher { throws JsonProcessingException { IndexSegment segment = segmentDataManager.getSegment(); SegmentMetadata segmentMetadata = segment.getSegmentMetadata(); - Set<String> columnSet; - if (columns.size() == 1 && columns.get(0).equals("*")) { - // Making code consistent and returning metadata and indexes only for non-virtual columns. - columnSet = segment.getPhysicalColumnNames(); - } else { - columnSet = new HashSet<>(columns); - } + Set<String> columnSet = columns.contains("*") ? segment.getPhysicalColumnNames() : new HashSet<>(columns); ObjectNode segmentMetadataJson = (ObjectNode) segmentMetadata.toJson(columnSet); segmentMetadataJson.set(COLUMN_INDEX_KEY, JsonUtils.objectToJsonNode(getIndexesForSegmentColumns(segmentDataManager, columnSet))); diff --git a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java index 0b4e3e97793..b482d662a5e 100644 --- a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java +++ b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java @@ -61,7 +61,6 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.model.IdealState; @@ -188,7 +187,6 @@ public class TablesResource { } @GET - @Encoded @Produces(MediaType.APPLICATION_JSON) @Path("/tables/{tableName}/metadata") @ApiOperation(value = "List metadata for all segments of a given table", notes = "List segments metadata of table " @@ -200,8 +198,8 @@ public class TablesResource { }) public String getSegmentMetadata( @ApiParam(value = "Table Name with type", required = true) @PathParam("tableName") String tableName, - @ApiParam(value = "Column name", allowMultiple = true) @QueryParam("columns") @DefaultValue("") - List<String> columns, @Context HttpHeaders headers) + @ApiParam(value = "Column name", allowMultiple = true) @QueryParam("columns") List<String> columns, + @Context HttpHeaders headers) throws WebApplicationException { tableName = DatabaseUtils.translateTableName(tableName, headers); InstanceDataManager instanceDataManager = _serverInstance.getInstanceDataManager(); @@ -215,21 +213,7 @@ public class TablesResource { throw new WebApplicationException("Table: " + tableName + " is not found", Response.Status.NOT_FOUND); } - List<String> decodedColumns = new ArrayList<>(columns.size()); - for (String column : columns) { - decodedColumns.add(URIUtils.decode(column)); - } - - boolean allColumns = false; - // For robustness, loop over all columns, if any of the columns is "*", return metadata for all columns. - for (String column : decodedColumns) { - if (column.equals("*")) { - allColumns = true; - break; - } - } - Set<String> columnSet = allColumns ? null : new HashSet<>(decodedColumns); - + Set<String> columnSet = columns.contains("*") ? null : new HashSet<>(columns); List<SegmentDataManager> segmentDataManagers = tableDataManager.acquireAllSegments(); long totalSegmentSizeBytes = 0; long totalNumRows = 0; @@ -327,14 +311,13 @@ public class TablesResource { } @GET - @Encoded @Path("/tables/{tableName}/indexes") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Provide index metadata", notes = "Provide index details for the table") @ApiResponses(value = { - @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Internal server error", - response = ErrorInfo.class), @ApiResponse(code = 404, message = "Table or segment not found", response = - ErrorInfo.class) + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Internal server error", response = ErrorInfo.class), + @ApiResponse(code = 404, message = "Table or segment not found", response = ErrorInfo.class) }) public String getTableIndexes( @ApiParam(value = "Table name including type", required = true, example = "myTable_OFFLINE") @@ -373,7 +356,6 @@ public class TablesResource { } @GET - @Encoded @Path("/tables/{tableName}/segments/{segmentName}/metadata") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Provide segment metadata", notes = "Provide segments metadata for the segment on server") @@ -385,16 +367,13 @@ public class TablesResource { public String getSegmentMetadata( @ApiParam(value = "Table name including type", required = true, example = "myTable_OFFLINE") @PathParam("tableName") String tableName, - @ApiParam(value = "Segment name", required = true) @PathParam("segmentName") String segmentName, - @ApiParam(value = "Column name", allowMultiple = true) @QueryParam("columns") @DefaultValue("") - List<String> columns, @Context HttpHeaders headers) { + @ApiParam(value = "Segment name", required = true) @PathParam("segmentName") @Encoded String segmentName, + @ApiParam(value = "Column name", allowMultiple = true) @QueryParam("columns") List<String> columns, + @Context HttpHeaders headers) { tableName = DatabaseUtils.translateTableName(tableName, headers); - for (int i = 0; i < columns.size(); i++) { - columns.set(i, URIUtils.decode(columns.get(i))); - } + segmentName = URIUtils.decode(segmentName); TableDataManager tableDataManager = ServerResourceUtils.checkGetTableDataManager(_serverInstance, tableName); - segmentName = URIUtils.decode(segmentName); SegmentDataManager segmentDataManager = tableDataManager.acquireSegment(segmentName); if (segmentDataManager == null) { throw new WebApplicationException(String.format("Table %s segments %s does not exist", tableName, segmentName), @@ -413,7 +392,6 @@ public class TablesResource { } @GET - @Encoded @Path("/tables/{tableName}/segments/metadata") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Provide segments metadata", notes = "Provide segments metadata for the segments on server") @@ -425,36 +403,25 @@ public class TablesResource { public String getSegmentsMetadata( @ApiParam(value = "Table name including type", required = true, example = "myTable_OFFLINE") @PathParam("tableName") String tableName, - @Nullable @ApiParam(value = "Segments name", allowMultiple = true) @QueryParam("segments") List<String> segments, - @Nullable @ApiParam(value = "Column name", allowMultiple = true) @QueryParam("columns") List<String> columns, + @ApiParam(value = "Segments to include (all if not specified)", allowMultiple = true) @QueryParam("segments") + List<String> segments, + @ApiParam(value = "Columns to include (use '*' to include all)", allowMultiple = true) @QueryParam("columns") + List<String> columns, @Context HttpHeaders headers) { tableName = DatabaseUtils.translateTableName(tableName, headers); TableDataManager tableDataManager = ServerResourceUtils.checkGetTableDataManager(_serverInstance, tableName); - // decode columns and segments - List<String> decodedSegments = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(segments)) { - for (String segment : segments) { - decodedSegments.add(URIUtils.decode(segment)); - } - } List<SegmentDataManager> segmentDataManagers; - if (!decodedSegments.isEmpty()) { - segmentDataManagers = tableDataManager.acquireSegments(decodedSegments, new ArrayList<>()); + if (!segments.isEmpty()) { + segmentDataManagers = tableDataManager.acquireSegments(segments, new ArrayList<>()); } else { segmentDataManagers = tableDataManager.acquireAllSegments(); } - List<String> decodedColumns = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(columns)) { - for (String column: columns) { - decodedColumns.add(URIUtils.decode(column)); - } - } // get metadata for every segment in the list Map<String, JsonNode> response = new HashMap<>(); try { for (SegmentDataManager segmentDataManager: segmentDataManagers) { String segmentName = segmentDataManager.getSegmentName(); - String segmentMetadata = SegmentMetadataFetcher.getSegmentMetadata(segmentDataManager, decodedColumns); + String segmentMetadata = SegmentMetadataFetcher.getSegmentMetadata(segmentDataManager, columns); JsonNode segmentMetadataJson = JsonUtils.stringToJsonNode(segmentMetadata); response.put(segmentName, segmentMetadataJson); } @@ -514,6 +481,7 @@ public class TablesResource { @Context HttpHeaders httpHeaders) throws Exception { tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType, httpHeaders); + segmentName = URIUtils.decode(segmentName); LOGGER.info("Received a request to download segment {} for table {}", segmentName, tableNameWithType); // Validate data access ServerResourceUtils.validateDataAccess(_accessControlFactory, tableNameWithType, httpHeaders); @@ -628,8 +596,8 @@ public class TablesResource { @ApiParam(value = "Name of the table with type REALTIME", required = true, example = "myTable_REALTIME") @PathParam("tableNameWithType") String tableNameWithType, @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") @Encoded String segmentName, - @ApiParam(value = "Valid doc ids type") - @QueryParam("validDocIdsType") String validDocIdsType, @Context HttpHeaders httpHeaders) { + @ApiParam(value = "Valid doc ids type") @QueryParam("validDocIdsType") @Nullable String validDocIdsType, + @Context HttpHeaders httpHeaders) { tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType, httpHeaders); segmentName = URIUtils.decode(segmentName); LOGGER.info("Received a request to download validDocIds for segment {} table {}", segmentName, tableNameWithType); @@ -680,18 +648,16 @@ public class TablesResource { @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Provides segment validDocId metadata", notes = "Provides segment validDocId metadata") @ApiResponses(value = { - @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Internal server error", - response = ErrorInfo.class), @ApiResponse(code = 404, message = "Table or segment not found", response = - ErrorInfo.class) + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Internal server error", response = ErrorInfo.class), + @ApiResponse(code = 404, message = "Table or segment not found", response = ErrorInfo.class) }) public String getValidDocIdsMetadata( @ApiParam(value = "Table name including type", required = true, example = "myTable_REALTIME") @PathParam("tableNameWithType") String tableNameWithType, - @ApiParam(value = "Valid doc ids type") - @QueryParam("validDocIdsType") String validDocIdsType, + @ApiParam(value = "Valid doc ids type") @QueryParam("validDocIdsType") String validDocIdsType, @ApiParam(value = "Segment name", allowMultiple = true) @QueryParam("segmentNames") List<String> segmentNames, - @Context HttpHeaders headers) - throws Exception { + @Context HttpHeaders headers) { tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType, headers); return ResourceUtils.convertToJsonString( processValidDocIdsMetadata(tableNameWithType, segmentNames, validDocIdsType)); @@ -702,9 +668,9 @@ public class TablesResource { @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Provides segment validDocIds metadata", notes = "Provides segment validDocIds metadata") @ApiResponses(value = { - @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Internal server error", - response = ErrorInfo.class), @ApiResponse(code = 404, message = "Table or segment not found", response = - ErrorInfo.class) + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Internal server error", response = ErrorInfo.class), + @ApiResponse(code = 404, message = "Table or segment not found", response = ErrorInfo.class) }) public String getValidDocIdsMetadata( @ApiParam(value = "Table name including type", required = true, example = "myTable_REALTIME") @@ -869,11 +835,12 @@ public class TablesResource { public String uploadLLCSegment( @ApiParam(value = "Name of the REALTIME table", required = true) @PathParam("realtimeTableName") String realtimeTableName, - @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") String segmentName, + @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") @Encoded String segmentName, @QueryParam("uploadTimeoutMs") @DefaultValue("-1") int timeoutMs, @Context HttpHeaders headers) throws Exception { realtimeTableName = DatabaseUtils.translateTableName(realtimeTableName, headers); + segmentName = URIUtils.decode(segmentName); LOGGER.info("Received a request to upload low level consumer segment {} for table {}", segmentName, realtimeTableName); @@ -943,11 +910,12 @@ public class TablesResource { public TableLLCSegmentUploadResponse uploadLLCSegmentV2( @ApiParam(value = "Name of the REALTIME table", required = true) @PathParam("realtimeTableNameWithType") String realtimeTableNameWithType, - @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") String segmentName, + @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") @Encoded String segmentName, @QueryParam("uploadTimeoutMs") @DefaultValue("-1") int timeoutMs, @Context HttpHeaders headers) throws Exception { realtimeTableNameWithType = DatabaseUtils.translateTableName(realtimeTableNameWithType, headers); + segmentName = URIUtils.decode(segmentName); LOGGER.info("Received a request to upload low level consumer segment {} for table {}", segmentName, realtimeTableNameWithType); @@ -1014,10 +982,11 @@ public class TablesResource { public String uploadCommittedSegment( @ApiParam(value = "Name of the real-time table", required = true) @PathParam("realtimeTableName") String realtimeTableName, - @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") String segmentName, + @ApiParam(value = "Name of the segment", required = true) @PathParam("segmentName") @Encoded String segmentName, @QueryParam("uploadTimeoutMs") @DefaultValue("-1") int timeoutMs, @Context HttpHeaders headers) throws Exception { realtimeTableName = DatabaseUtils.translateTableName(realtimeTableName, headers); + segmentName = URIUtils.decode(segmentName); LOGGER.info("Received a request to upload committed segment: {} for table: {}", segmentName, realtimeTableName); // Check it's real-time table diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java index c17bc7c0b61..2da3d879d0a 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java @@ -20,12 +20,10 @@ package org.apache.pinot.spi.utils.builder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; @@ -342,46 +340,11 @@ public class ControllerRequestURLBuilder { } public String forTableAggregateMetadata(String tableName, @Nullable List<String> columns) { - return StringUtil.join("/", _baseUrl, "tables", tableName, "metadata") + constructColumnsParameter(columns); - } - - private String constructColumnsParameter(@Nullable List<String> columns) { - if (!CollectionUtils.isEmpty(columns)) { - StringBuilder parameter = new StringBuilder(); - parameter.append("?columns="); - parameter.append(columns.get(0)); - int numColumns = columns.size(); - if (numColumns > 1) { - for (int i = 1; i < numColumns; i++) { - parameter.append("&columns=").append(columns.get(i)); - } - } - return parameter.toString(); - } else { - return ""; - } - } - - private String constructQueryParametersString(Map<String, List<String>> queryParams) { - if (queryParams.isEmpty()) { - return ""; - } - StringBuilder query = new StringBuilder("?"); - boolean firstParam = true; - for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { - String key = entry.getKey(); - for (String value : entry.getValue()) { - if (!firstParam) { - query.append("&"); - } - query.append(key).append("=").append(value); - firstParam = false; - } - } - return query.toString(); + String columnsParam = UrlBuilderUtils.generateColumnsParam(columns); + String url = StringUtil.join("/", _baseUrl, "tables", tableName, "metadata"); + return columnsParam != null ? url + "?" + columnsParam : url; } - public String forSchemaValidate() { return StringUtil.join("/", _baseUrl, "schemas", "validate"); } @@ -471,16 +434,10 @@ public class ControllerRequestURLBuilder { public String forSegmentsMetadataFromServer(String tableName, @Nullable List<String> columns, @Nullable List<String> segments) { - String basePath = StringUtil.join("/", _baseUrl, "segments", tableName, "metadata"); - Map<String, List<String>> queryParams = new LinkedHashMap<>(); - if (!CollectionUtils.isEmpty(columns)) { - queryParams.put("columns", columns); - } - if (!CollectionUtils.isEmpty(segments)) { - queryParams.put("segments", segments); - } - String queryString = constructQueryParametersString(queryParams); - return basePath + queryString; + tableName = encode(tableName); + String columnsAndSegmentsParam = UrlBuilderUtils.generateColumnsAndSegmentsParam(columns, segments); + String url = StringUtil.join("/", _baseUrl, "segments", tableName, "metadata"); + return columnsAndSegmentsParam != null ? url + "?" + columnsAndSegmentsParam : url; } public String forSegmentMetadata(String tableName, String segmentName) { diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UrlBuilderUtils.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UrlBuilderUtils.java new file mode 100644 index 00000000000..d67e7887082 --- /dev/null +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UrlBuilderUtils.java @@ -0,0 +1,75 @@ +/** + * 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.pinot.spi.utils.builder; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import javax.annotation.Nullable; +import org.apache.commons.collections4.CollectionUtils; + + +public class UrlBuilderUtils { + private UrlBuilderUtils() { + } + + @Nullable + public static String generateColumnsParam(@Nullable List<String> columns) { + if (CollectionUtils.isEmpty(columns)) { + return null; + } + StringBuilder builder = new StringBuilder("columns=").append(encode(columns.get(0))); + int numColumns = columns.size(); + for (int i = 1; i < numColumns; i++) { + builder.append("&columns=").append(encode(columns.get(i))); + } + return builder.toString(); + } + + @Nullable + public static String generateSegmentsParam(@Nullable List<String> segments) { + if (CollectionUtils.isEmpty(segments)) { + return null; + } + StringBuilder builder = new StringBuilder("segments=").append(encode(segments.get(0))); + int numSegments = segments.size(); + for (int i = 1; i < numSegments; i++) { + builder.append("&segments=").append(encode(segments.get(i))); + } + return builder.toString(); + } + + public static String encode(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8); + } + + @Nullable + public static String generateColumnsAndSegmentsParam(@Nullable List<String> columns, + @Nullable List<String> segments) { + String columnsParam = generateColumnsParam(columns); + String segmentsParam = generateSegmentsParam(segments); + if (columnsParam == null) { + return segmentsParam; + } + if (segmentsParam == null) { + return columnsParam; + } + return columnsParam + "&" + segmentsParam; + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org