blob: 575c732185b57abb879bab961ca8c46cdefdc865 [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.
#pragma once
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest_prod.h>
#include "kudu/common/schema.h"
#include "kudu/gutil/port.h"
#include "kudu/util/slice.h"
#include "kudu/util/status.h"
namespace google {
namespace protobuf {
template <typename T> class RepeatedPtrField;
} // namespace protobuf
} // namespace google
namespace kudu {
namespace master {
class MasterTest_AlterTableAddAndDropRangeWithSpecificHashSchema_Test;
}
class Arena;
class ConstContiguousRow;
class KuduPartialRow;
class PartitionPB;
class PartitionSchemaPB;
class PartitionSchemaPB_HashBucketSchemaPB;
template <typename Buffer> class KeyEncoder;
// This class is a representation of a table's row (or a row limit) according
// to the table's partition schema. A table's partition can be defined by a pair
// of such PartitionKey objects: the beginning and the end of a partition. Rows
// that belong to a given partition all fall within the same partition key
// range, i.e. they are between the beginning and the end of the partition
// defined by two PartitionKey objects. Essentially, there is a way to serialize
// a subset of primary keys columns in a table's row given the table's partition
// schema, and PartitionKey consists of the hash and the range parts of that
// serialized representation.
class PartitionKey {
public:
// Build a PartitionKey that represents infinity. For the beginning of a
// partition represents '-inf'; for the end of a partition represents '+inf'.
PartitionKey() = default;
// Build PartitionKey object given hash and range parts.
PartitionKey(std::string hash_key, std::string range_key)
: hash_key_(std::move(hash_key)),
range_key_(std::move(range_key)) {
}
PartitionKey& operator=(const PartitionKey& other) {
hash_key_ = other.hash_key_;
range_key_ = other.range_key_;
return *this;
}
bool operator==(const PartitionKey& other) const {
return hash_key_ == other.hash_key_ && range_key_ == other.range_key_;
}
bool operator!=(const PartitionKey& other) const {
return !(*this == other);
}
bool operator<(const PartitionKey& other) const {
if (hash_key_ < other.hash_key_) {
return true;
}
if (hash_key_ == other.hash_key_) {
return range_key_ < other.range_key_;
}
return false;
}
bool operator<=(const PartitionKey& other) const {
return !(*this > other);
}
bool operator>(const PartitionKey& other) const {
return other < *this;
}
bool operator>=(const PartitionKey& other) const {
return !(*this < other);
}
bool empty() const {
return hash_key_.empty() && range_key_.empty();
}
const std::string& hash_key() const { return hash_key_; }
const std::string& range_key() const { return range_key_; }
std::string* mutable_hash_key() { return &hash_key_; }
std::string* mutable_range_key() { return &range_key_; }
// The serialized representation of this partition key. In legacy Kudu
// versions, this string representation was used as a partition key.
std::string ToString() const {
return hash_key_ + range_key_;
}
// Return a string with hexadecimal representation of the data comprising
// the key: useful for representing the key in human-readable format.
std::string DebugString() const;
private:
std::string hash_key_; // the hash part of the key, encoded
std::string range_key_; // the range part of the key, encoded
};
// A Partition describes the set of rows that a Tablet is responsible for
// serving. Each tablet is assigned a single Partition.
//
// Partitions consist primarily of a start and end partition key. Every row with
// a partition key that falls in a Tablet's Partition will be served by that
// tablet.
//
// In addition to the start and end partition keys, a Partition holds metadata
// to determine if a scan can prune, or skip, a partition based on the scan's
// start and end primary keys, and predicates.
class Partition {
public:
const std::vector<int32_t>& hash_buckets() const {
return hash_buckets_;
}
const PartitionKey& begin() const {
return begin_;
}
const PartitionKey& end() const {
return end_;
}
// Returns true iff the given partition 'rhs' is equivalent to this one.
bool operator==(const Partition& rhs) const;
bool operator!=(const Partition& rhs) const {
return !(*this == rhs);
}
// Serializes a partition into a protobuf message.
void ToPB(PartitionPB* pb) const;
// Deserializes a protobuf message into a partition.
//
// The protobuf message is not validated, since partitions are only expected
// to be created by the master process.
static void FromPB(const PartitionPB& pb, Partition* partition);
// Knowing the number of hash bucketing dimensions, it's possible to separate
// the encoded hash-related part from the range-related part, so it's possible
// to build corresponding PartitionKey object from a string where the hash-
// and the range-related parts are concatenated.
static PartitionKey StringToPartitionKey(const std::string& key_str,
size_t hash_dimensions_num);
private:
friend class PartitionSchema;
std::vector<int32_t> hash_buckets_;
PartitionKey begin_;
PartitionKey end_;
};
std::ostream& operator<<(std::ostream& out, const PartitionKey& key);
// A partition schema describes how the rows of a table are distributed among
// tablets.
//
// Primarily, a table's partition schema is responsible for translating the
// primary key column values of a row into a partition key that can be used to
// determine the tablet containing the key.
//
// The partition schema for a table is made up of a single range component, and
// per-range hash components, where a hash component consists of zero or more
// hash bucket dimensions. When all the ranges have the same hash component,
// the partition schema for a table reduces into a single range component and
// a table-wide hash component. The range component is called 'range schema',
// and the collection of per-range hash components is called 'hash schema'. With
// that, the partition schema for a table consists of the range schema and
// the hash schema.
//
// Each hash bucket dimension includes one or more columns from the set of the
// table's primary key columns, with the restriction that an individual primary
// key column may only be included in a single hash dimension.
//
// In encoded partition keys (they are represented as sequence of bytes),
// first comes the hash-related part, and then comes the range-related part.
//
// To determine the hash bucket of an individual row, the values of the columns
// of the hash component are encoded into bytes (in PK or lexicographic
// preserving encoding), then hashed into a u64, then modded into an i32. When
// constructing a partition key from a row, the buckets of the row are simply
// encoded into the partition key in order (again in PK or lexicographic
// preserving encoding).
//
// The range component contains a (possibly full or empty) subset of the primary
// key columns. When encoding the partition key, the columns of the partition
// component are encoded in order.
//
// The above is true of the relationship between rows and partition keys. It
// gets trickier with partitions (tablet partition key boundaries), because the
// boundaries of tablets do not necessarily align to rows. For instance,
// currently the absolute-start and absolute-end primary keys of a table
// represented as an empty key, but do not have a corresponding row. Partitions
// are similar, but instead of having just one absolute-start and absolute-end,
// each component of a partition schema has an absolute-start and absolute-end.
// When creating the initial set of partitions during table creation, we deal
// with this by "carrying through" absolute-start or absolute-ends into lower
// significance components.
//
// Notes on redaction:
//
// For the purposes of redaction, Kudu considers partitions and partition
// schemas to be metadata - not sensitive data which needs to be redacted from
// log files. However, the partition keys of individual rows _are_ considered
// sensitive, so we redact them from log messages and error messages. Thus,
// methods which format partitions and partition schemas will never redact, but
// the methods which format individual partition keys do redact.
class PartitionSchema {
public:
// This structure represents the range component of the table's partitioning
// schema. It consists of at least one column, and every column must be one
// of the primary key's columns.
struct RangeSchema {
std::vector<ColumnId> column_ids;
};
// This structure represents one dimension of the hash bucketing. To find the
// hash value (which directly corresponds to the hash bucket index) for a
// particular row in the given hash dimension, it's necessary to compute the
// hash value for a row by calling
// HashFunction(value_of_column_ids[0], ..., value_of_column_ids[N-1]), where
// N = column_ids.size().
//
// NOTE: this structure corresponds to PartitionSchemaPB::HashBucketSchemaPB
struct HashDimension {
std::vector<ColumnId> column_ids;
int32_t num_buckets;
uint32_t seed;
bool operator==(const HashDimension& other) const {
if (this == &other) {
return true;
}
if (seed != other.seed) {
return false;
}
if (num_buckets != other.num_buckets) {
return false;
}
if (column_ids != other.column_ids) {
return false;
}
return true;
}
bool operator!=(const HashDimension& other) const {
return !(*this == other);
}
};
// A hash schema consists of zero or more hash dimensions. With that,
// N-dimensional hash bucketing for a row is defined by N hash values computed
// in each dimension of the hash schema.
typedef std::vector<HashDimension> HashSchema;
// A structure representing a range with custom hash schema.
struct RangeWithHashSchema {
std::string lower; // encoded range key: lower boundary
std::string upper; // encoded range key: upper boundary
HashSchema hash_schema; // hash schema for the range
};
typedef std::vector<RangeWithHashSchema> RangesWithHashSchemas;
// Extracts HashSchema from a protobuf repeated field of hash buckets.
static Status ExtractHashSchemaFromPB(
const Schema& schema,
const google::protobuf::RepeatedPtrField<PartitionSchemaPB_HashBucketSchemaPB>&
hash_schema_pb,
HashSchema* hash_schema);
// Deserializes a protobuf message into a partition schema. If not nullptr,
// the optional output parameter 'ranges_with_hash_schemas' is populated
// with the information on table's ranges with their hash schemas.
static Status FromPB(
const PartitionSchemaPB& pb,
const Schema& schema,
PartitionSchema* partition_schema,
RangesWithHashSchemas* ranges_with_hash_schemas = nullptr) WARN_UNUSED_RESULT;
// Helper function that validates the hash schemas.
static Status ValidateHashSchema(const Schema& schema,
const HashSchema& hash_schema);
// Serializes a partition schema into a protobuf message.
// Requires a schema to encode the range bounds.
Status ToPB(const Schema& schema, PartitionSchemaPB* pb) const;
// Returns partition key for the row.
PartitionKey EncodeKey(const KuduPartialRow& row) const;
PartitionKey EncodeKey(const ConstContiguousRow& row) const;
// Creates the set of table partitions for a partition schema and collection
// of split rows and split bounds.
//
// Split bounds define disjoint ranges for which tablets will be created. If
// empty, then Kudu assumes a single unbounded range. Each split key must fall
// into one of the ranges, and results in the range being split. The number
// of resulting partitions is the product of the number of hash buckets for
// each hash bucket component, multiplied by
// (split_rows.size() + max(1, range_bounds.size())).
Status CreatePartitions(
const std::vector<KuduPartialRow>& split_rows,
const std::vector<std::pair<KuduPartialRow, KuduPartialRow>>& range_bounds,
const Schema& schema,
std::vector<Partition>* partitions) const WARN_UNUSED_RESULT;
// Create the set of partitions given the specified ranges with per-range
// hash schemas. The 'partitions' output parameter must be non-null.
Status CreatePartitions(
const RangesWithHashSchemas& ranges_with_hash_schemas,
const Schema& schema,
std::vector<Partition>* partitions) const WARN_UNUSED_RESULT;
// Create the set of partitions for a single range with specified hash schema.
Status CreatePartitionsForRange(
const std::pair<KuduPartialRow, KuduPartialRow>& range_bound,
const HashSchema& range_hash_schema,
const Schema& schema,
std::vector<Partition>* partitions) const WARN_UNUSED_RESULT;
// Check if the given partition contains the specified row. The row must have
// all the columns participating in the table's partition schema
// set to particular values.
bool PartitionContainsRow(const Partition& partition,
const KuduPartialRow& row) const;
bool PartitionContainsRow(const Partition& partition,
const ConstContiguousRow& row) const;
// Check if the specified row is probably in the given partition.
// The collection of columns set to particular values in the row can be a
// subset of all the columns participating in the table's partition schema.
// This method can be used to optimize the set of values for IN list
// predicates. As of now, this method is effectively implemented only for
// single-column hash and single-column range partition schemas, meaning
// that it can return false positives in case of other than single-row range
// and hash schemas.
//
// NOTE: this method returns false positives in some cases (see above)
//
// TODO(aserbin): implement this for multi-row range schemas as well,
// substituting non-specified columns in the row with values
// from the partition's start key and return logically inverted
// result of calling PartitionContainsRow() with the
// artificially constructed row
bool PartitionMayContainRow(const Partition& partition,
const KuduPartialRow& row) const;
// Used to determine whether the partition keys information shows up.
enum HashPartitionInfo {
HIDE = 0,
SHOW = 1,
};
// Returns a text description of the partition suitable for debug printing.
//
// Partitions are considered metadata, so no redaction will happen on the hash
// and range bound values.
std::string PartitionDebugString(const Partition& partition, const Schema& schema,
HashPartitionInfo hp = HashPartitionInfo::SHOW) const;
// Returns a text description of a partition key suitable for debug printing.
std::string PartitionKeyDebugString(const PartitionKey& key,
const Schema& schema) const;
std::string PartitionKeyDebugString(const KuduPartialRow& row) const;
std::string PartitionKeyDebugString(const ConstContiguousRow& row) const;
// Returns a text description of the range partition with the provided
// inclusive lower bound and exclusive upper bound.
//
// Range partitions are considered metadata, so no redaction will happen on
// the row values.
std::string RangePartitionDebugString(const KuduPartialRow& lower_bound,
const KuduPartialRow& upper_bound) const;
std::string RangePartitionDebugString(Slice lower_bound,
Slice upper_bound,
const Schema& schema) const;
// Returns a text description of the partition with the provided inclusive
// lower bound and exclusive upper bound along with custom hash schema for
// the partition if present. The custom hash schema is a space separated
// descriptions of hash dimensions.
std::string RangeWithCustomHashPartitionDebugString(Slice lower_bound,
Slice upper_bound,
const Schema& schema) const;
// Returns a text description of this partition schema suitable for debug printing.
//
// The partition schema is considered metadata, so partition bound information
// is not redacted from the returned string.
std::string DebugString(const Schema& schema) const;
// Returns a text description of this partition schema suitable for display in the web UI.
// The format of this string is not guaranteed to be identical cross-version.
//
// 'range_partitions' should include the set of range partitions in the table,
// as formatted by 'RangePartitionDebugString'.
std::string DisplayString(const Schema& schema,
const std::vector<std::string>& range_partitions) const;
// Returns header and entry HTML cells for the partition schema for the master
// table web UI. This is an abstraction leak, but it's better than leaking the
// internals of partitions to the master path handlers.
//
// Partitions are considered metadata, so no redaction will be done.
std::string PartitionTableHeader(const Schema& schema) const;
std::string PartitionTableEntry(const Schema& schema, const Partition& partition) const;
// Returns 'true' iff the partition schema 'rhs' is equivalent to this one.
bool operator==(const PartitionSchema& rhs) const;
// Returns 'true' iff the partition schema 'rhs' is not equivalent to this one.
bool operator!=(const PartitionSchema& rhs) const {
return !(*this == rhs);
}
// Transforms an exclusive lower bound range partition key into an inclusive
// lower bound range partition key.
//
// The provided partial row is considered metadata, so error messages may
// contain unredacted row data.
Status MakeLowerBoundRangePartitionKeyInclusive(KuduPartialRow* row) const;
// Transforms an inclusive upper bound range partition key into an exclusive
// upper bound range partition key.
//
// The provided partial row is considered metadata, so error messages may
// contain unredacted row data.
Status MakeUpperBoundRangePartitionKeyExclusive(KuduPartialRow* row) const;
// Decodes a range partition key into a partial row, with variable-length
// fields stored in the arena.
Status DecodeRangeKey(Slice* encoded_key,
KuduPartialRow* partial_row,
Arena* arena) const;
const RangeSchema& range_schema() const {
return range_schema_;
}
// TODO(aserbin): this method is becoming obsolete with the introduction of
// custom per-range hash schemas -- update this or remove
// completely
const HashSchema& hash_schema() const {
return hash_schema_;
}
// Return all the known ranges that have custom hash schemas. The ranges are
// sorted by the lower bound in ascending order; the ranges do not intersect.
const RangesWithHashSchemas& ranges_with_custom_hash_schemas() const {
return ranges_with_custom_hash_schemas_;
}
bool HasCustomHashSchemas() const {
return !ranges_with_custom_hash_schemas_.empty();
}
// Given the specified table schema, populate the 'range_column_indexes'
// container with column indexes of the range partition keys.
// If any of the columns is not in the key range columns then an
// InvalidArgument status is returned.
Status GetRangeSchemaColumnIndexes(
const Schema& schema,
std::vector<int>* range_column_indexes) const;
Status GetHashSchemaForRange(const KuduPartialRow& lower,
const Schema& schema,
HashSchema* hash_schema) const;
// Drop range partition with the specified lower and upper bounds. The
// method updates member fields of this class, so that PartitionSchema::ToPB()
// generates PartitionSchemaPB::custom_hash_schema_ranges field accordingly.
Status DropRange(const KuduPartialRow& lower,
const KuduPartialRow& upper,
const Schema& schema);
private:
friend class PartitionPruner;
friend class PartitionPrunerTest;
FRIEND_TEST(master::MasterTest, AlterTableAddAndDropRangeWithSpecificHashSchema);
FRIEND_TEST(PartitionTest, CustomHashSchemaRangesToPB);
FRIEND_TEST(PartitionTest, DropRange);
FRIEND_TEST(PartitionTest, HasCustomHashSchemasWhenAddingAndDroppingRanges);
FRIEND_TEST(PartitionTest, TestPartitionSchemaPB);
FRIEND_TEST(PartitionTest, TestIncrementRangePartitionBounds);
FRIEND_TEST(PartitionTest, TestIncrementRangePartitionStringBounds);
FRIEND_TEST(PartitionTest, TestVarcharRangePartitions);
// Tests if the hash partition contains the row with given hash_idx.
bool HashPartitionContainsRow(const Partition& partition,
const KuduPartialRow& row,
int hash_idx) const;
// Tests if the range partition contains the row.
bool RangePartitionContainsRow(const Partition& partition,
const KuduPartialRow& row) const;
// Returns a text description of the encoded range key suitable for debug printing.
std::string RangeKeyDebugString(Slice range_key, const Schema& schema) const;
std::string RangeKeyDebugString(const KuduPartialRow& key) const;
std::string RangeKeyDebugString(const ConstContiguousRow& key) const;
// Encodes the specified columns of a row into lexicographic sort-order
// preserving format.
static void EncodeColumns(const KuduPartialRow& row,
const std::vector<ColumnId>& column_ids,
std::string* buf);
// Encodes the specified columns of a row into lexicographic sort-order
// preserving format.
static void EncodeColumns(const ConstContiguousRow& row,
const std::vector<ColumnId>& column_ids,
std::string* buf);
// Returns the hash value of the encoded hash columns. The encoded columns
// must match the columns of the hash dimension.
static uint32_t HashValueForEncodedColumns(
const std::string& encoded_hash_columns,
const HashDimension& hash_dimension);
// Assigns the row to a bucket according to the hash rules.
template<typename Row>
static uint32_t HashValueForRow(const Row& row,
const HashDimension& hash_dimension);
// Generates hash partitions for each combination of hash buckets in hash_schemas.
static std::vector<Partition> GenerateHashPartitions(
const HashSchema& hash_schema,
const KeyEncoder<std::string>& hash_encoder);
// PartitionKeyDebugString implementation for row types.
template<typename Row>
std::string PartitionKeyDebugStringImpl(const Row& row) const;
// Private templated helper for PartitionContainsRow.
template<typename Row>
bool PartitionContainsRowImpl(const Partition& partition,
const Row& row) const;
// Private templated helper for HashPartitionContainsRow.
template<typename Row>
bool HashPartitionContainsRowImpl(const Partition& partition,
const Row& row,
const HashSchema& hash_schema,
int hash_value) const;
// Private templated helper for RangePartitionContainsRow.
template<typename Row>
bool RangePartitionContainsRowImpl(const Partition& partition,
const Row& row) const;
// Private templated helper for EncodeKey.
template<typename Row>
void EncodeKeyImpl(const Row& row,
std::string* range_buf,
std::string* hash_buf) const;
// Returns true if all of the columns in the range partition key are unset in
// the row.
bool IsRangePartitionKeyEmpty(const KuduPartialRow& row) const;
// Appends the stringified range partition components of a partial row to a
// vector.
//
// If any columns of the range partition do not exist in the partial row, the
// logical minimum value for that column will be used instead.
void AppendRangeDebugStringComponentsOrMin(const KuduPartialRow& row,
std::vector<std::string>* components) const;
// Returns the stringified hash and range schema components of the partition
// schema.
//
// Partition schemas are considered metadata, so no redaction will happen on
// the hash and range bound values.
std::vector<std::string> DebugStringComponents(const Schema& schema) const;
// Encode the provided row into a range key. The row must not include values
// for any columns not in the range key. Missing range values will be filled
// with the logical minimum value for the column. A row without any values
// will encode to an empty string.
//
// This method is useful used for encoding splits and bounds.
Status EncodeRangeKey(const KuduPartialRow& row, const Schema& schema, std::string* key) const;
// Decodes the hash bucket component of a partition key into its buckets.
//
// This should only be called with partition keys created from a row, not with
// partition keys from a partition.
Status DecodeHashBuckets(Slice* encoded_key, std::vector<int32_t>* buckets) const;
// Clears the state of this partition schema.
void Clear();
// Validates that this partition schema is valid. Returns OK, or an
// appropriate error code for an invalid partition schema.
Status Validate(const Schema& schema) const;
// Check the range partition schema for consistency: make sure the columns
// used in the range partitioning are present in the table's schema and there
// aren't any duplicate columns.
Status CheckRangeSchema(const Schema& schema) const;
// Update partitions' boundaries provided with 'partitions' in-out parameter,
// assuming the 'partitions' are populated by calling the CreatePartitions()
// method. When a table has two or more hash components, there will be gaps
// in between partitions at the boundaries of the component ranges. This
// method fills in the address space to have the proper ordering of the
// serialized partition keys -- that's important for partition pruning and
// overall ordering of the serialized partition keys.
void UpdatePartitionBoundaries(const KeyEncoder<std::string>& hash_encoder,
std::vector<Partition>* partitions) const;
// Similar to the above, but update the boundaries for just a single partition
// specified along with its hash schema.
static void UpdatePartitionBoundaries(
const KeyEncoder<std::string>& hash_encoder,
const HashSchema& partition_hash_schema,
Partition* partition);
// Validates the split rows, converts them to partition key form, and inserts
// them into splits in sorted order.
Status EncodeRangeSplits(const std::vector<KuduPartialRow>& split_rows,
const Schema& schema,
std::vector<std::string>* splits) const;
// Validates the range bounds, converts them to partition key form, and
// inserts them into 'bounds_with_hash_schemas' in sorted order. The hash schemas
// per range are stored within 'range_hash_schemas'. If 'range_hash_schemas' is empty,
// it indicates that the table wide hash schema will be used per range.
Status EncodeRangeBounds(
const std::vector<std::pair<KuduPartialRow, KuduPartialRow>>& range_bounds,
const std::vector<HashSchema>& range_hash_schemas,
const Schema& schema,
RangesWithHashSchemas* bounds_with_hash_schemas) const;
// Splits the encoded range bounds by the split points. The splits and bounds within
// 'bounds_with_hash_schemas' must be sorted. If `bounds_with_hash_schemas` is empty,
// then a single unbounded range is assumed. If any of the splits falls outside
// of the bounds, then an InvalidArgument status is returned.
Status SplitRangeBounds(const Schema& schema,
const std::vector<std::string>& splits,
RangesWithHashSchemas* bounds_with_hash_schemas) const;
// Increments a range partition key, setting 'increment' to true if the
// increment succeeds, or false if all range partition columns are already the
// maximum value. Unset columns will be incremented to increment(min_value).
Status IncrementRangePartitionKey(KuduPartialRow* row, bool* increment) const;
// Find hash schema for the given encoded range key. Depending on the
// partition schema and the key, it might be either table-wide or a custom
// hash schema for a particular range.
const HashSchema& GetHashSchemaForRange(const std::string& range_key) const;
RangeSchema range_schema_;
HashSchema hash_schema_;
// This contains only ranges with range-specific (i.e. different from
// the table-wide) hash schemas. This array is sorted by a range's lower bound
// in ascending order; the ranges do not intersect.
RangesWithHashSchemas ranges_with_custom_hash_schemas_;
// Encoded start of the range --> index of the hash bucket schemas for the
// range in the 'ranges_with_custom_hash_schemas_' array container.
// NOTE: the contents of this map and 'ranges_with_custom_hash_schemas_'
// are tightly coupled -- it's necessary to clear/set this map
// along with 'ranges_with_custom_hash_schemas_'.
typedef std::map<std::string, size_t> HashSchemasByEncodedLowerRange;
HashSchemasByEncodedLowerRange hash_schema_idx_by_encoded_range_start_;
};
} // namespace kudu