blob: f040edf47888111059cb0f4085ed604c268e7fed [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.
#ifndef KUDU_COMMON_ROW_H
#define KUDU_COMMON_ROW_H
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include "kudu/common/schema.h"
#include "kudu/common/types.h"
#include "kudu/gutil/macros.h"
#include "kudu/gutil/map-util.h"
#include "kudu/util/memory/arena.h"
#include "kudu/util/bitmap.h"
namespace kudu {
// A simple cell of data which directly corresponds to a pointer value.
// stack.
struct SimpleConstCell {
public:
// Both parameters must remain valid for the lifetime of the cell object.
SimpleConstCell(const ColumnSchema* col_schema,
const void* value)
: col_schema_(col_schema),
value_(value) {
}
const TypeInfo* typeinfo() const { return col_schema_->type_info(); }
size_t size() const { return col_schema_->type_info()->size(); }
bool is_nullable() const { return col_schema_->is_nullable(); }
const void* ptr() const { return value_; }
bool is_null() const { return value_ == NULL; }
private:
const ColumnSchema* col_schema_;
const void* value_;
};
// Copy the cell data from 'src' to 'dst'. This only copies the data, and not
// the null state. Use CopyCell() if you need to copy the null-ness.
//
// If dst_arena is non-NULL, relocates the data into the given arena.
template <class SrcCellType, class DstCellType, class ArenaType>
Status CopyCellData(const SrcCellType &src, DstCellType* dst, ArenaType *dst_arena) {
DCHECK_EQ(src.typeinfo()->type(), dst->typeinfo()->type());
if (src.typeinfo()->physical_type() == BINARY) {
// If it's a Slice column, need to relocate the referred-to data
// as well as the slice itself.
// TODO: potential optimization here: if the new value is smaller than
// the old value, we could potentially just overwrite in some cases.
const Slice *src_slice = reinterpret_cast<const Slice *>(src.ptr());
Slice *dst_slice = reinterpret_cast<Slice *>(dst->mutable_ptr());
if (dst_arena != NULL) {
if (PREDICT_FALSE(!dst_arena->RelocateSlice(*src_slice, dst_slice))) {
return Status::IOError("out of memory copying slice", src_slice->ToString());
}
} else {
// Just copy the slice without relocating.
// This is used by callers who know that the source row's data is going
// to stick around for the scope of the destination.
*dst_slice = *src_slice;
}
} else {
memcpy(dst->mutable_ptr(), src.ptr(), src.size()); // TODO: inline?
}
return Status::OK();
}
// Copy the cell from 'src' to 'dst'.
//
// This copies the data, and relocates indirect data into the given arena,
// if it is not NULL.
template <class SrcCellType, class DstCellType, class ArenaType>
Status CopyCell(const SrcCellType &src, DstCellType* dst, ArenaType *dst_arena) {
if (src.is_nullable()) {
// Copy the null state.
dst->set_null(src.is_null());
if (src.is_null()) {
// no need to copy any data contents once we marked the destination
// cell as null.
return Status::OK();
}
}
return CopyCellData(src, dst, dst_arena);
}
// Copy all of the cells from one row to another. The two rows must share
// the same Schema. If they do not, use ProjectRow() below.
// This can be used to translate between columnar and row-wise layout, for example.
//
// If 'dst_arena' is set, then will relocate any indirect data to that arena
// during the copy.
template<class RowType1, class RowType2, class ArenaType>
inline Status CopyRow(const RowType1 &src_row, RowType2 *dst_row, ArenaType *dst_arena) {
DCHECK_SCHEMA_EQ(*src_row.schema(), *dst_row->schema());
for (int i = 0; i < src_row.schema()->num_columns(); i++) {
typename RowType1::Cell src = src_row.cell(i);
typename RowType2::Cell dst = dst_row->cell(i);
RETURN_NOT_OK(CopyCell(src, &dst, dst_arena));
}
return Status::OK();
}
// Projection mapping for the specified schemas.
// A projection may contain:
// - columns that are present in the "base schema"
// - columns that are present in the "base schema" but with different types.
// In this case an adapter should be used (e.g. INT8 to INT64, INT8 to STRING, ...)
// - columns that are not present in the "base schema".
// In this case the default value of the projection column will be used.
//
// Example:
// RowProjector projector(&base_schema, &projection);
// projector.Init();
// projector.ProjectRow(row_a, &row_b, &row_b_arena);
class RowProjector {
public:
typedef std::pair<size_t, size_t> ProjectionIdxMapping;
// Construct a projector.
// The two Schema pointers must remain valid for the lifetime of this object.
RowProjector(const Schema* base_schema, const Schema* projection)
: base_schema_(base_schema), projection_(projection),
is_identity_(*base_schema == *projection) {
}
// Initialize the projection mapping with the specified base_schema and projection
Status Init() {
return projection_->GetProjectionMapping(*base_schema_, this);
}
Status Reset(const Schema* base_schema, const Schema* projection) {
base_schema_ = base_schema;
projection_ = projection;
base_cols_mapping_.clear();
projection_defaults_.clear();
is_identity_ = (*base_schema == *projection);
return Init();
}
// Project a row from one schema into another, using the projection mapping.
// Indirected data is copied into the provided dst arena.
//
// Use this method only on the read-path.
// The col_schema.read_default_value() will be used.
template<class RowType1, class RowType2, class ArenaType>
Status ProjectRowForRead(const RowType1& src_row, RowType2 *dst_row, ArenaType *dst_arena) const {
return ProjectRow<RowType1, RowType2, ArenaType, true>(src_row, dst_row, dst_arena);
}
// Project a row from one schema into another, using the projection mapping.
// Indirected data is copied into the provided dst arena.
//
// Use this method only on the write-path.
// The col_schema.write_default_value() will be used.
template<class RowType1, class RowType2, class ArenaType>
Status ProjectRowForWrite(const RowType1& src_row, RowType2 *dst_row,
ArenaType *dst_arena) const {
return ProjectRow<RowType1, RowType2, ArenaType, false>(src_row, dst_row, dst_arena);
}
bool is_identity() const { return is_identity_; }
const Schema* projection() const { return projection_; }
const Schema* base_schema() const { return base_schema_; }
// Returns the mapping between base schema and projection schema columns
// first: is the projection column index, second: is the base_schema index
const std::vector<ProjectionIdxMapping>& base_cols_mapping() const {
return base_cols_mapping_;
}
// Returns the projection indexes of the columns to add with a default value.
//
// These are columns which are present in 'projection_' but not in 'base_schema',
// and for which 'projection' has a default.
const std::vector<size_t>& projection_defaults() const {
return projection_defaults_;
}
private:
friend class Schema;
Status ProjectBaseColumn(size_t proj_col_idx, size_t base_col_idx) {
base_cols_mapping_.emplace_back(proj_col_idx, base_col_idx);
return Status::OK();
}
Status ProjectDefaultColumn(size_t proj_col_idx) {
projection_defaults_.push_back(proj_col_idx);
return Status::OK();
}
Status ProjectExtraColumn(size_t proj_col_idx) {
return Status::InvalidArgument(
"The column '" + projection_->column(proj_col_idx).name() +
"' does not exist in the projection, and it does not have a "
"default value or a nullable type");
}
private:
// Project a row from one schema into another, using the projection mapping.
// Indirected data is copied into the provided dst arena.
template<class RowType1, class RowType2, class ArenaType, bool FOR_READ>
Status ProjectRow(const RowType1& src_row, RowType2 *dst_row, ArenaType *dst_arena) const {
DCHECK_SCHEMA_EQ(*base_schema_, *src_row.schema());
DCHECK_SCHEMA_EQ(*projection_, *dst_row->schema());
// Copy directly from base Data
for (const auto& base_mapping : base_cols_mapping_) {
typename RowType1::Cell src_cell = src_row.cell(base_mapping.second);
typename RowType2::Cell dst_cell = dst_row->cell(base_mapping.first);
RETURN_NOT_OK(CopyCell(src_cell, &dst_cell, dst_arena));
}
// Fill with Defaults
for (auto proj_idx : projection_defaults_) {
const ColumnSchema& col_proj = projection_->column(proj_idx);
const void *vdefault = FOR_READ ? col_proj.read_default_value() :
col_proj.write_default_value();
SimpleConstCell src_cell(&col_proj, vdefault);
typename RowType2::Cell dst_cell = dst_row->cell(proj_idx);
RETURN_NOT_OK(CopyCell(src_cell, &dst_cell, dst_arena));
}
return Status::OK();
}
private:
DISALLOW_COPY_AND_ASSIGN(RowProjector);
// projection_ column index -> base_schema_ index
std::vector<ProjectionIdxMapping> base_cols_mapping_;
std::vector<size_t> projection_defaults_;
const Schema* base_schema_;
const Schema* projection_;
bool is_identity_;
};
// Projection mapping from the schema used to encode a RowChangeList
// to the new specified schema. Used on the read/compaction path to
// project the deltas to the user/latest specified projection.
//
// A projection may contain:
// - columns that are present in the "base schema"
// - columns that are present in the "base schema" but with different types.
// In this case an adapter should be used (e.g. INT8 to INT64, INT8 to STRING, ...)
// - columns that are not present in the "base schema".
// These columns are not considered since they cannot be in the delta.
class DeltaProjector {
public:
// The delta_schema and projection must remain valid for the lifetime
// of the object.
DeltaProjector(const Schema* delta_schema, const Schema* projection)
: delta_schema_(delta_schema), projection_(projection),
is_identity_(*delta_schema == *projection) {
}
Status Init() {
// TODO: doesn't look like this uses the is_identity performance
// shortcut
return projection_->GetProjectionMapping(*delta_schema_, this);
}
bool is_identity() const { return is_identity_; }
const Schema* projection() const { return projection_; }
const Schema* delta_schema() const { return delta_schema_; }
bool get_base_col_from_proj_idx(size_t proj_col_idx, size_t *base_col_idx) const {
return FindCopy(base_cols_mapping_, proj_col_idx, base_col_idx);
}
// TODO: Discourage the use of this. At the moment is only in RowChangeList::Project
bool get_proj_col_from_base_id(size_t col_id, size_t *proj_col_idx) const {
return FindCopy(rbase_cols_mapping_, col_id, proj_col_idx);
}
private:
friend class Schema;
Status ProjectBaseColumn(size_t proj_col_idx, size_t base_col_idx) {
base_cols_mapping_[proj_col_idx] = base_col_idx;
if (delta_schema_->has_column_ids()) {
rbase_cols_mapping_[delta_schema_->column_id(base_col_idx)] = proj_col_idx;
} else {
rbase_cols_mapping_[proj_col_idx] = proj_col_idx;
}
return Status::OK();
}
Status ProjectDefaultColumn(size_t proj_col_idx) {
// Not used, since deltas are update...
// we don't have this column, so we don't have updates
return Status::OK();
}
Status ProjectExtraColumn(size_t proj_col_idx) {
return Status::InvalidArgument(
"The column '" + delta_schema_->column(proj_col_idx).name() +
"' does not exist in the projection, and it does not have a "
"default value or a nullable type");
}
private:
DISALLOW_COPY_AND_ASSIGN(DeltaProjector);
std::unordered_map<size_t, size_t> base_cols_mapping_; // [proj_idx] = base_idx
std::unordered_map<size_t, size_t> rbase_cols_mapping_; // [id] = proj_idx
const Schema* delta_schema_;
const Schema* projection_;
bool is_identity_;
};
// Copy any indirect (eg STRING) data referenced by the given row into the
// provided arena.
//
// The row itself is mutated so that the indirect data points to the relocated
// storage.
template <class RowType, class ArenaType>
inline Status RelocateIndirectDataToArena(RowType *row, ArenaType *dst_arena) {
const Schema* schema = row->schema();
// First calculate the total size we'll need to allocate in the arena.
int size = 0;
for (int i = 0; i < schema->num_columns(); i++) {
typename RowType::Cell cell = row->cell(i);
if (cell.typeinfo()->physical_type() == BINARY) {
if (cell.is_nullable() && cell.is_null()) {
continue;
}
const Slice *slice = reinterpret_cast<const Slice *>(cell.ptr());
size += slice->size();
}
}
if (size == 0) return Status::OK();
// Then allocate it in one shot and copy the actual data.
// Even though Arena allocation is cheap, a row may have hundreds of
// small string columns and each operation is at least one CAS. With
// many concurrent threads copying into a single arena, this avoids
// a lot of contention.
uint8_t* dst = static_cast<uint8_t*>(dst_arena->AllocateBytes(size));
for (int i = 0; i < schema->num_columns(); i++) {
typename RowType::Cell cell = row->cell(i);
if (cell.typeinfo()->physical_type() == BINARY) {
if (cell.is_nullable() && cell.is_null()) {
continue;
}
Slice *slice = reinterpret_cast<Slice *>(cell.mutable_ptr());
slice->relocate(dst);
dst += slice->size();
}
}
return Status::OK();
}
class ContiguousRowHelper {
public:
static size_t non_null_bitmap_size(const Schema& schema) {
return schema.has_nullables() ? BitmapSize(schema.num_columns()) : 0;
}
static uint8_t* non_null_bitmap_ptr(const Schema& schema, uint8_t* row_data) {
return row_data + schema.byte_size();
}
static size_t row_size(const Schema& schema) {
return schema.byte_size() + non_null_bitmap_size(schema);
}
static void InitNullsBitmap(const Schema& schema, Slice& row_data) {
InitNullsBitmap(schema, row_data.mutable_data(), row_data.size() - schema.byte_size());
}
static void InitNullsBitmap(const Schema& schema, uint8_t *row_data, size_t bitmap_size) {
uint8_t *non_null_bitmap = row_data + schema.byte_size();
for (size_t i = 0; i < bitmap_size; ++i) {
non_null_bitmap[i] = 0x00;
}
}
static bool is_null(const Schema& schema, const uint8_t *row_data, size_t col_idx) {
DCHECK(schema.column(col_idx).is_nullable());
return BitmapTest(row_data + schema.byte_size(), col_idx);
}
static void SetCellIsNull(const Schema& schema, uint8_t *row_data, size_t col_idx, bool is_null) {
uint8_t *non_null_bitmap = row_data + schema.byte_size();
BitmapChange(non_null_bitmap, col_idx, is_null);
}
static const uint8_t *cell_ptr(const Schema& schema, const uint8_t *row_data, size_t col_idx) {
return row_data + schema.column_offset(col_idx);
}
static const uint8_t *nullable_cell_ptr(const Schema& schema,
const uint8_t *row_data,
size_t col_idx) {
return is_null(schema, row_data, col_idx) ? NULL : cell_ptr(schema, row_data, col_idx);
}
};
template<class ContiguousRowType>
class ContiguousRowCell {
public:
// Constructs a new ContiguousRowCell.
//
// The 'row' object must outlive this ContiguousRowCell.
ContiguousRowCell(const ContiguousRowType* row, int idx)
: row_(row), col_idx_(idx) {
}
const TypeInfo* typeinfo() const { return type_info(); }
size_t size() const { return type_info()->size(); }
const void* ptr() const { return row_->cell_ptr(col_idx_); }
void* mutable_ptr() const { return row_->mutable_cell_ptr(col_idx_); }
bool is_nullable() const { return row_->schema()->column(col_idx_).is_nullable(); }
bool is_null() const { return row_->is_null(col_idx_); }
void set_null(bool is_null) const { row_->set_null(col_idx_, is_null); }
private:
const TypeInfo* type_info() const {
return row_->schema()->column(col_idx_).type_info();
}
const ContiguousRowType* row_;
int col_idx_;
};
// The row has all columns layed out in memory based on the schema.column_offset()
class ContiguousRow {
public:
typedef ContiguousRowCell<ContiguousRow> Cell;
// Constructs a new ContiguousRow.
//
// The 'schema' and 'row_data' objects must outlive this ContiguousRow.
explicit ContiguousRow(const Schema* schema, uint8_t *row_data = NULL)
: schema_(schema), row_data_(row_data) {
}
const Schema* schema() const {
return schema_;
}
void Reset(uint8_t *row_data) {
row_data_ = row_data;
}
bool is_null(size_t col_idx) const {
return ContiguousRowHelper::is_null(*schema_, row_data_, col_idx);
}
void set_null(size_t col_idx, bool is_null) const {
ContiguousRowHelper::SetCellIsNull(*schema_, row_data_, col_idx, is_null);
}
const uint8_t *cell_ptr(size_t col_idx) const {
return ContiguousRowHelper::cell_ptr(*schema_, row_data_, col_idx);
}
uint8_t *mutable_cell_ptr(size_t col_idx) const {
return const_cast<uint8_t*>(cell_ptr(col_idx));
}
const uint8_t *nullable_cell_ptr(size_t col_idx) const {
return ContiguousRowHelper::nullable_cell_ptr(*schema_, row_data_, col_idx);
}
Cell cell(size_t col_idx) const {
return Cell(this, col_idx);
}
private:
friend class ConstContiguousRow;
const Schema* schema_;
uint8_t *row_data_;
};
// This is the same as ContiguousRow except it refers to a const area of memory that
// should not be mutated.
class ConstContiguousRow {
public:
typedef ContiguousRowCell<ConstContiguousRow> Cell;
// Constructs a new ConstContiguousRow.
//
// The 'row' object's schema and data must outlive this ConstContiguousRow.
explicit ConstContiguousRow(const ContiguousRow &row)
: schema_(row.schema_),
row_data_(row.row_data_) {
}
ConstContiguousRow(const Schema* schema, const void *row_data)
: schema_(schema), row_data_(reinterpret_cast<const uint8_t *>(row_data)) {
}
ConstContiguousRow(const Schema* schema, const Slice& row_slice)
: schema_(schema), row_data_(row_slice.data()) {
}
const Schema* schema() const {
return schema_;
}
const uint8_t *row_data() const {
return row_data_;
}
size_t row_size() const {
return ContiguousRowHelper::row_size(*schema_);
}
bool is_null(size_t col_idx) const {
return ContiguousRowHelper::is_null(*schema_, row_data_, col_idx);
}
const uint8_t *cell_ptr(size_t col_idx) const {
return ContiguousRowHelper::cell_ptr(*schema_, row_data_, col_idx);
}
const uint8_t *nullable_cell_ptr(size_t col_idx) const {
return ContiguousRowHelper::nullable_cell_ptr(*schema_, row_data_, col_idx);
}
Cell cell(size_t col_idx) const {
return Cell(this, col_idx);
}
private:
const Schema* schema_;
const uint8_t *row_data_;
};
// Delete functions from ContiguousRowCell that can mutate the cell by
// specializing for ConstContiguousRow.
template<>
void* ContiguousRowCell<ConstContiguousRow>::mutable_ptr() const;
template<>
void ContiguousRowCell<ConstContiguousRow>::set_null(bool null) const;
// Utility class for building rows corresponding to a given schema.
// This is used only by tests.
// TODO(todd): move it into a test utility.
class RowBuilder {
public:
// Constructs a new RowBuilder.
//
// The 'schema' object must outlive this RowBuilder.
explicit RowBuilder(const Schema* schema)
: schema_(schema),
arena_(1024),
bitmap_size_(ContiguousRowHelper::non_null_bitmap_size(*schema)) {
Reset();
}
// Reset the RowBuilder so that it is ready to build
// the next row.
// NOTE: The previous row's data is invalidated. Even
// if the previous row's data has been copied, indirected
// entries such as strings may end up shared or deallocated
// after Reset. So, the previous row must be fully copied
// (eg using CopyRowToArena()).
void Reset() {
arena_.Reset();
size_t row_size = schema_->byte_size() + bitmap_size_;
buf_ = reinterpret_cast<uint8_t *>(arena_.AllocateBytes(row_size));
CHECK(buf_) << "could not allocate " << row_size << " bytes for row builder";
col_idx_ = 0;
byte_idx_ = 0;
ContiguousRowHelper::InitNullsBitmap(*schema_, buf_, bitmap_size_);
}
void AddString(const Slice &slice) {
CheckNextType(STRING);
AddSlice(slice);
}
void AddString(const std::string &str) {
CheckNextType(STRING);
AddSlice(str);
}
void AddBinary(const Slice &slice) {
CheckNextType(BINARY);
AddSlice(slice);
}
void AddBinary(const std::string &str) {
CheckNextType(BINARY);
AddSlice(str);
}
void AddVarchar(const Slice &slice) {
CheckNextType(VARCHAR);
AddSlice(slice);
}
void AddVarchar(const std::string &str) {
CheckNextType(VARCHAR);
AddSlice(str);
}
void AddInt8(int8_t val) {
CheckNextType(INT8);
*reinterpret_cast<int8_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddUint8(uint8_t val) {
CheckNextType(UINT8);
*reinterpret_cast<uint8_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddInt16(int16_t val) {
CheckNextType(INT16);
*reinterpret_cast<int16_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddUint16(uint16_t val) {
CheckNextType(UINT16);
*reinterpret_cast<uint16_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddInt32(int32_t val) {
CheckNextType(INT32);
*reinterpret_cast<int32_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddUint32(uint32_t val) {
CheckNextType(UINT32);
*reinterpret_cast<uint32_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddInt64(int64_t val) {
CheckNextType(INT64);
*reinterpret_cast<int64_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddTimestamp(int64_t micros_utc_since_epoch) {
CheckNextType(UNIXTIME_MICROS);
*reinterpret_cast<int64_t *>(&buf_[byte_idx_]) = micros_utc_since_epoch;
Advance();
}
void AddDate(int32_t days_since_unix_epoch) {
CheckNextType(DATE);
*reinterpret_cast<int32_t *>(&buf_[byte_idx_]) = days_since_unix_epoch;
Advance();
}
void AddUint64(uint64_t val) {
CheckNextType(UINT64);
*reinterpret_cast<uint64_t *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddFloat(float val) {
CheckNextType(FLOAT);
*reinterpret_cast<float *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddDouble(double val) {
CheckNextType(DOUBLE);
*reinterpret_cast<double *>(&buf_[byte_idx_]) = val;
Advance();
}
void AddNull() {
CHECK(schema_->column(col_idx_).is_nullable());
BitmapSet(buf_ + schema_->byte_size(), col_idx_);
Advance();
}
// Retrieve the data slice from the current row.
// The Add*() functions must have been called an appropriate
// number of times such that all columns are filled in, or else
// a crash will occur.
//
// The data slice returned by this is only valid until the next
// call to Reset().
// Note that the Slice may also contain pointers which refer to
// other parts of the internal Arena, so even if the returned
// data is copied, it is not safe to Reset() before also calling
// CopyRowIndirectDataToArena.
const Slice data() const {
CHECK_EQ(byte_idx_, schema_->byte_size());
return Slice(buf_, byte_idx_ + bitmap_size_);
}
const Schema* schema() const {
return schema_;
}
ConstContiguousRow row() const {
return ConstContiguousRow(schema_, data());
}
private:
DISALLOW_COPY_AND_ASSIGN(RowBuilder);
void AddSlice(const Slice &slice) {
Slice *ptr = reinterpret_cast<Slice *>(buf_ + byte_idx_);
CHECK(arena_.RelocateSlice(slice, ptr)) << "could not allocate space in arena";
Advance();
}
void AddSlice(const std::string &str) {
uint8_t *in_arena = arena_.AddSlice(str);
CHECK(in_arena) << "could not allocate space in arena";
Slice *ptr = reinterpret_cast<Slice *>(buf_ + byte_idx_);
*ptr = Slice(in_arena, str.size());
Advance();
}
void CheckNextType(DataType type) {
CHECK_EQ(schema_->column(col_idx_).type_info()->type(),
type);
}
void Advance() {
int size = schema_->column(col_idx_).type_info()->size();
byte_idx_ += size;
col_idx_++;
}
const Schema* schema_;
Arena arena_;
uint8_t *buf_;
size_t col_idx_;
size_t byte_idx_;
size_t bitmap_size_;
};
} // namespace kudu
#endif