blob: 5c08678b88841b73e017b267542678e09d1f0be9 [file] [log] [blame]
/*
* 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.druid.query;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.druid.java.util.common.HumanReadableBytes;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.granularity.Granularity;
import org.apache.druid.query.QueryContexts.Vectorize;
import org.apache.druid.segment.QueryableIndexStorageAdapter;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
/**
* Immutable holder for query context parameters with typed access methods.
* Code builds up a map of context values from serialization or during
* planning. Once that map is handed to the {@code QueryContext}, that map
* is effectively immutable.
* <p>
* The implementation uses a {@link TreeMap} so that the serialized form of a query
* lists context values in a deterministic order. Jackson will call
* {@code getContext()} on the query, which will call {@link #asMap()} here,
* which returns the sorted {@code TreeMap}.
* <p>
* The {@code TreeMap} is a mutable class. We'd prefer an immutable class, but
* we can choose either ordering or immutability. Since the semantics of the context
* is that it is immutable once it is placed in a query. Code should NEVER get the
* context map from a query and modify it, even if the actual implementation
* allows it.
*/
public class QueryContext
{
private static final QueryContext EMPTY = new QueryContext(null);
private final Map<String, Object> context;
public QueryContext(Map<String, Object> context)
{
// There is no semantic difference between an empty and a null context.
// Ensure that a context always exists to avoid the need to check for
// a null context. Jackson serialization will omit empty contexts.
this.context = context == null
? Collections.emptyMap()
: Collections.unmodifiableMap(new TreeMap<>(context));
}
public static QueryContext empty()
{
return EMPTY;
}
public static QueryContext of(Map<String, Object> context)
{
return new QueryContext(context);
}
public boolean isEmpty()
{
return context.isEmpty();
}
public Map<String, Object> asMap()
{
return context;
}
/**
* Check if the given key is set. If the client will then fetch the value,
* consider using one of the {@code get<Type>(String key)} methods instead:
* they each return {@code null} if the value is not set.
*/
public boolean containsKey(String key)
{
return context.containsKey(key);
}
/**
* Return a value as a generic {@code Object}, returning {@code null} if the
* context value is not set.
*/
@Nullable
public Object get(String key)
{
return context.get(key);
}
/**
* Return a value as an {@code String}, returning {@link null} if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
@Nullable
public String getString(String key)
{
return getString(key, null);
}
public String getString(String key, String defaultValue)
{
return QueryContexts.parseString(context, key, defaultValue);
}
/**
* Return a value as an {@code Boolean}, returning {@link null} if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
public Boolean getBoolean(final String key)
{
return QueryContexts.getAsBoolean(key, get(key));
}
/**
* Return a value as an {@code boolean}, returning the default value if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
public boolean getBoolean(final String key, final boolean defaultValue)
{
return QueryContexts.parseBoolean(context, key, defaultValue);
}
/**
* Return a value as an {@code Integer}, returning {@link null} if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
public Integer getInt(final String key)
{
return QueryContexts.getAsInt(key, get(key));
}
/**
* Return a value as an {@code int}, returning the default value if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
public int getInt(final String key, final int defaultValue)
{
return QueryContexts.parseInt(context, key, defaultValue);
}
/**
* Return a value as an {@code Long}, returning {@link null} if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
public Long getLong(final String key)
{
return QueryContexts.getAsLong(key, get(key));
}
/**
* Return a value as an {@code long}, returning the default value if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
public long getLong(final String key, final long defaultValue)
{
return QueryContexts.parseLong(context, key, defaultValue);
}
/**
* Return a value as an {@code Float}, returning {@link null} if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
@SuppressWarnings("unused")
public Float getFloat(final String key)
{
return QueryContexts.getAsFloat(key, get(key));
}
/**
* Return a value as an {@code float}, returning the default value if the
* context value is not set.
*
* @throws BadQueryContextException for an invalid value
*/
public float getFloat(final String key, final float defaultValue)
{
return QueryContexts.getAsFloat(key, get(key), defaultValue);
}
public HumanReadableBytes getHumanReadableBytes(final String key, final HumanReadableBytes defaultValue)
{
return QueryContexts.getAsHumanReadableBytes(key, get(key), defaultValue);
}
public HumanReadableBytes getHumanReadableBytes(final String key, final long defaultBytes)
{
return QueryContexts.getAsHumanReadableBytes(key, get(key), HumanReadableBytes.valueOf(defaultBytes));
}
public <E extends Enum<E>> E getEnum(String key, Class<E> clazz, E defaultValue)
{
return QueryContexts.getAsEnum(key, get(key), clazz, defaultValue);
}
public Granularity getGranularity(String key, ObjectMapper jsonMapper)
{
final String granularityString = getString(key);
if (granularityString == null) {
return null;
}
try {
return jsonMapper.readValue(granularityString, Granularity.class);
}
catch (IOException e) {
throw QueryContexts.badTypeException(key, "a Granularity", granularityString);
}
}
public boolean isDebug()
{
return getBoolean(QueryContexts.ENABLE_DEBUG, QueryContexts.DEFAULT_ENABLE_DEBUG);
}
public boolean isBySegment()
{
return isBySegment(QueryContexts.DEFAULT_BY_SEGMENT);
}
public boolean isBySegment(boolean defaultValue)
{
return getBoolean(QueryContexts.BY_SEGMENT_KEY, defaultValue);
}
public boolean isPopulateCache()
{
return isPopulateCache(QueryContexts.DEFAULT_POPULATE_CACHE);
}
public boolean isPopulateCache(boolean defaultValue)
{
return getBoolean(QueryContexts.POPULATE_CACHE_KEY, defaultValue);
}
public boolean isUseCache()
{
return isUseCache(QueryContexts.DEFAULT_USE_CACHE);
}
public boolean isUseCache(boolean defaultValue)
{
return getBoolean(QueryContexts.USE_CACHE_KEY, defaultValue);
}
public boolean isPopulateResultLevelCache()
{
return isPopulateResultLevelCache(QueryContexts.DEFAULT_POPULATE_RESULTLEVEL_CACHE);
}
public boolean isPopulateResultLevelCache(boolean defaultValue)
{
return getBoolean(QueryContexts.POPULATE_RESULT_LEVEL_CACHE_KEY, defaultValue);
}
public boolean isUseResultLevelCache()
{
return isUseResultLevelCache(QueryContexts.DEFAULT_USE_RESULTLEVEL_CACHE);
}
public boolean isUseResultLevelCache(boolean defaultValue)
{
return getBoolean(QueryContexts.USE_RESULT_LEVEL_CACHE_KEY, defaultValue);
}
public boolean isFinalize(boolean defaultValue)
{
return getBoolean(QueryContexts.FINALIZE_KEY, defaultValue);
}
public boolean isSerializeDateTimeAsLong(boolean defaultValue)
{
return getBoolean(QueryContexts.SERIALIZE_DATE_TIME_AS_LONG_KEY, defaultValue);
}
public boolean isSerializeDateTimeAsLongInner(boolean defaultValue)
{
return getBoolean(QueryContexts.SERIALIZE_DATE_TIME_AS_LONG_INNER_KEY, defaultValue);
}
public Vectorize getVectorize()
{
return getVectorize(QueryContexts.DEFAULT_VECTORIZE);
}
public Vectorize getVectorize(Vectorize defaultValue)
{
return getEnum(QueryContexts.VECTORIZE_KEY, Vectorize.class, defaultValue);
}
public Vectorize getVectorizeVirtualColumns()
{
return getVectorizeVirtualColumns(QueryContexts.DEFAULT_VECTORIZE_VIRTUAL_COLUMN);
}
public Vectorize getVectorizeVirtualColumns(Vectorize defaultValue)
{
return getEnum(
QueryContexts.VECTORIZE_VIRTUAL_COLUMNS_KEY,
Vectorize.class,
defaultValue
);
}
public int getVectorSize()
{
return getVectorSize(QueryableIndexStorageAdapter.DEFAULT_VECTOR_SIZE);
}
public int getVectorSize(int defaultSize)
{
return getInt(QueryContexts.VECTOR_SIZE_KEY, defaultSize);
}
public int getMaxSubqueryRows(int defaultSize)
{
return getInt(QueryContexts.MAX_SUBQUERY_ROWS_KEY, defaultSize);
}
public String getMaxSubqueryMemoryBytes(String defaultMemoryBytes)
{
// Generic to allow for both strings and numbers to be passed as values in the query context
Object maxSubqueryBytesObject = get(QueryContexts.MAX_SUBQUERY_BYTES_KEY);
if (maxSubqueryBytesObject == null) {
maxSubqueryBytesObject = defaultMemoryBytes;
}
return String.valueOf(maxSubqueryBytesObject);
}
public boolean isUseNestedForUnknownTypeInSubquery(boolean defaultUseNestedForUnkownTypeInSubquery)
{
return getBoolean(QueryContexts.USE_NESTED_FOR_UNKNOWN_TYPE_IN_SUBQUERY, defaultUseNestedForUnkownTypeInSubquery);
}
public int getUncoveredIntervalsLimit()
{
return getUncoveredIntervalsLimit(QueryContexts.DEFAULT_UNCOVERED_INTERVALS_LIMIT);
}
public int getUncoveredIntervalsLimit(int defaultValue)
{
return getInt(QueryContexts.UNCOVERED_INTERVALS_LIMIT_KEY, defaultValue);
}
public int getPriority()
{
return getPriority(QueryContexts.DEFAULT_PRIORITY);
}
public int getPriority(int defaultValue)
{
return getInt(QueryContexts.PRIORITY_KEY, defaultValue);
}
public String getLane()
{
return getString(QueryContexts.LANE_KEY);
}
public boolean getEnableParallelMerges()
{
return getBoolean(
QueryContexts.BROKER_PARALLEL_MERGE_KEY,
QueryContexts.DEFAULT_ENABLE_PARALLEL_MERGE
);
}
public int getParallelMergeInitialYieldRows(int defaultValue)
{
return getInt(QueryContexts.BROKER_PARALLEL_MERGE_INITIAL_YIELD_ROWS_KEY, defaultValue);
}
public int getParallelMergeSmallBatchRows(int defaultValue)
{
return getInt(QueryContexts.BROKER_PARALLEL_MERGE_SMALL_BATCH_ROWS_KEY, defaultValue);
}
public int getParallelMergeParallelism(int defaultValue)
{
return getInt(QueryContexts.BROKER_PARALLELISM, defaultValue);
}
public long getJoinFilterRewriteMaxSize()
{
return getLong(
QueryContexts.JOIN_FILTER_REWRITE_MAX_SIZE_KEY,
QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_MAX_SIZE
);
}
public boolean getEnableJoinFilterPushDown()
{
return getBoolean(
QueryContexts.JOIN_FILTER_PUSH_DOWN_KEY,
QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_PUSH_DOWN
);
}
public boolean getEnableJoinFilterRewrite()
{
return getBoolean(
QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY,
QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE
);
}
public boolean isSecondaryPartitionPruningEnabled()
{
return getBoolean(
QueryContexts.SECONDARY_PARTITION_PRUNING_KEY,
QueryContexts.DEFAULT_SECONDARY_PARTITION_PRUNING
);
}
public long getMaxQueuedBytes(long defaultValue)
{
return getLong(QueryContexts.MAX_QUEUED_BYTES_KEY, defaultValue);
}
public long getMaxScatterGatherBytes()
{
return getLong(QueryContexts.MAX_SCATTER_GATHER_BYTES_KEY, Long.MAX_VALUE);
}
public boolean hasTimeout()
{
return getTimeout() != QueryContexts.NO_TIMEOUT;
}
public long getTimeout()
{
return getTimeout(getDefaultTimeout());
}
public long getTimeout(long defaultTimeout)
{
final long timeout = getLong(QueryContexts.TIMEOUT_KEY, defaultTimeout);
if (timeout >= 0) {
return timeout;
}
throw new BadQueryContextException(
StringUtils.format(
"Timeout [%s] must be a non negative value, but was %d",
QueryContexts.TIMEOUT_KEY,
timeout
)
);
}
public long getDefaultTimeout()
{
final long defaultTimeout = getLong(QueryContexts.DEFAULT_TIMEOUT_KEY, QueryContexts.DEFAULT_TIMEOUT_MILLIS);
if (defaultTimeout >= 0) {
return defaultTimeout;
}
throw new BadQueryContextException(
StringUtils.format(
"Timeout [%s] must be a non negative value, but was %d",
QueryContexts.DEFAULT_TIMEOUT_KEY,
defaultTimeout
)
);
}
public void verifyMaxQueryTimeout(long maxQueryTimeout)
{
long timeout = getTimeout();
if (timeout > maxQueryTimeout) {
throw new BadQueryContextException(
StringUtils.format(
"Configured %s = %d is more than enforced limit of %d.",
QueryContexts.TIMEOUT_KEY,
timeout,
maxQueryTimeout
)
);
}
}
public void verifyMaxScatterGatherBytes(long maxScatterGatherBytesLimit)
{
long curr = getLong(QueryContexts.MAX_SCATTER_GATHER_BYTES_KEY, 0);
if (curr > maxScatterGatherBytesLimit) {
throw new BadQueryContextException(
StringUtils.format(
"Configured %s = %d is more than enforced limit of %d.",
QueryContexts.MAX_SCATTER_GATHER_BYTES_KEY,
curr,
maxScatterGatherBytesLimit
)
);
}
}
public int getNumRetriesOnMissingSegments(int defaultValue)
{
return getInt(QueryContexts.NUM_RETRIES_ON_MISSING_SEGMENTS_KEY, defaultValue);
}
public boolean allowReturnPartialResults(boolean defaultValue)
{
return getBoolean(QueryContexts.RETURN_PARTIAL_RESULTS_KEY, defaultValue);
}
public boolean getEnableJoinFilterRewriteValueColumnFilters()
{
return getBoolean(
QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY,
QueryContexts.DEFAULT_ENABLE_JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS
);
}
public boolean getEnableRewriteJoinToFilter()
{
return getBoolean(
QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY,
QueryContexts.DEFAULT_ENABLE_REWRITE_JOIN_TO_FILTER
);
}
public boolean getEnableJoinLeftScanDirect()
{
return getBoolean(
QueryContexts.SQL_JOIN_LEFT_SCAN_DIRECT,
QueryContexts.DEFAULT_ENABLE_SQL_JOIN_LEFT_SCAN_DIRECT
);
}
public int getInSubQueryThreshold()
{
return getInSubQueryThreshold(QueryContexts.DEFAULT_IN_SUB_QUERY_THRESHOLD);
}
public int getInSubQueryThreshold(int defaultValue)
{
return getInt(
QueryContexts.IN_SUB_QUERY_THRESHOLD_KEY,
defaultValue
);
}
public boolean isTimeBoundaryPlanningEnabled()
{
return getBoolean(
QueryContexts.TIME_BOUNDARY_PLANNING_KEY,
QueryContexts.DEFAULT_ENABLE_TIME_BOUNDARY_PLANNING
);
}
public boolean isWindowingStrictValidation()
{
return getBoolean(
QueryContexts.WINDOWING_STRICT_VALIDATION,
QueryContexts.DEFAULT_WINDOWING_STRICT_VALIDATION
);
}
public QueryResourceId getQueryResourceId()
{
return new QueryResourceId(getString(QueryContexts.QUERY_RESOURCE_ID));
}
public String getBrokerServiceName()
{
return getString(QueryContexts.BROKER_SERVICE_NAME);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
QueryContext other = (QueryContext) o;
return context.equals(other.context);
}
@Override
public int hashCode()
{
return Objects.hash(context);
}
@Override
public String toString()
{
return "QueryContext{" +
"context=" + context +
'}';
}
}