blob: 493997a0319bac1ab28d5b8f78669c708b94b0c7 [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 IMPALA_PARQUET_COLUMN_READERS_H
#define IMPALA_PARQUET_COLUMN_READERS_H
#include <boost/scoped_ptr.hpp>
#include "exec/hdfs-parquet-scanner.h"
#include "util/codec.h"
#include "util/dict-encoding.h"
#include "util/rle-encoding.h"
namespace impala {
class Tuple;
class MemPool;
/// Decoder for all supported Parquet level encodings. Optionally reads, decodes, and
/// caches level values in batches.
/// Level values are unsigned 8-bit integers because we support a maximum nesting
/// depth of 100, as enforced by the FE. Using a small type saves memory and speeds up
/// populating the level cache (e.g., with RLE we can memset() repeated values).
///
/// Inherits from RleDecoder instead of containing one for performance reasons.
/// The containment design would require two BitReaders per column reader. The extra
/// BitReader causes enough bloat for a column reader to require another cache line.
/// TODO: It is not clear whether the inheritance vs. containment choice still makes
/// sense with column-wise materialization. The containment design seems cleaner and
/// we should revisit.
class ParquetLevelDecoder : public RleDecoder {
public:
ParquetLevelDecoder(bool is_def_level_decoder)
: cached_levels_(NULL),
num_cached_levels_(0),
cached_level_idx_(0),
encoding_(parquet::Encoding::PLAIN),
max_level_(0),
cache_size_(0),
num_buffered_values_(0),
decoding_error_code_(is_def_level_decoder ?
TErrorCode::PARQUET_DEF_LEVEL_ERROR : TErrorCode::PARQUET_REP_LEVEL_ERROR) {
}
/// Initialize the LevelDecoder. Reads and advances the provided data buffer if the
/// encoding requires reading metadata from the page header.
Status Init(const string& filename, parquet::Encoding::type encoding,
MemPool* cache_pool, int cache_size, int max_level, int num_buffered_values,
uint8_t** data, int* data_size);
/// Returns the next level or INVALID_LEVEL if there was an error.
inline int16_t ReadLevel();
/// Decodes and caches the next batch of levels. Resets members associated with the
/// cache. Returns a non-ok status if there was a problem decoding a level, or if a
/// level was encountered with a value greater than max_level_.
Status CacheNextBatch(int batch_size);
/// Functions for working with the level cache.
inline bool CacheHasNext() const { return cached_level_idx_ < num_cached_levels_; }
inline uint8_t CacheGetNext() {
DCHECK_LT(cached_level_idx_, num_cached_levels_);
return cached_levels_[cached_level_idx_++];
}
inline void CacheSkipLevels(int num_levels) {
DCHECK_LE(cached_level_idx_ + num_levels, num_cached_levels_);
cached_level_idx_ += num_levels;
}
inline int CacheSize() const { return num_cached_levels_; }
inline int CacheRemaining() const { return num_cached_levels_ - cached_level_idx_; }
inline int CacheCurrIdx() const { return cached_level_idx_; }
private:
/// Initializes members associated with the level cache. Allocates memory for
/// the cache from pool, if necessary.
Status InitCache(MemPool* pool, int cache_size);
/// Decodes and writes a batch of levels into the cache. Sets the number of
/// values written to the cache in *num_cached_levels. Returns false if there was
/// an error decoding a level or if there was a level value greater than max_level_.
bool FillCache(int batch_size, int* num_cached_levels);
/// Buffer for a batch of levels. The memory is allocated and owned by a pool in
/// passed in Init().
uint8_t* cached_levels_;
/// Number of valid level values in the cache.
int num_cached_levels_;
/// Current index into cached_levels_.
int cached_level_idx_;
parquet::Encoding::type encoding_;
/// For error checking and reporting.
int max_level_;
/// Number of level values cached_levels_ has memory allocated for.
int cache_size_;
/// Number of remaining data values in the current data page.
int num_buffered_values_;
string filename_;
TErrorCode::type decoding_error_code_;
};
/// Base class for reading a Parquet column. Reads a logical column, not necessarily a
/// column materialized in the file (e.g. collections). The two subclasses are
/// BaseScalarColumnReader and CollectionColumnReader. Column readers read one def and rep
/// level pair at a time. The current def and rep level are exposed to the user, and the
/// corresponding value (if defined) can optionally be copied into a slot via
/// ReadValue(). Can also write position slots.
class ParquetColumnReader {
public:
/// Creates a column reader for 'node' and associates it with the given parent scanner.
/// Adds the new column reader to the parent's object pool.
/// 'slot_desc' may be NULL, in which case the returned column reader can only be used
/// to read def/rep levels.
/// 'is_collection_field' should be set to true if the returned reader is reading a
/// collection. This cannot be determined purely by 'node' because a repeated scalar
/// node represents both an array and the array's items (in this case
/// 'is_collection_field' should be true if the reader reads one value per array, and
/// false if it reads one value per item). The reader is added to the runtime state's
/// object pool. Does not create child readers for collection readers; these must be
/// added by the caller.
static ParquetColumnReader* Create(const SchemaNode& node, bool is_collection_field,
const SlotDescriptor* slot_desc, HdfsParquetScanner* parent);
virtual ~ParquetColumnReader() { }
int def_level() const { return def_level_; }
int rep_level() const { return rep_level_; }
const SlotDescriptor* slot_desc() const { return slot_desc_; }
const parquet::SchemaElement& schema_element() const { return *node_.element; }
int16_t max_def_level() const { return max_def_level_; }
int16_t max_rep_level() const { return max_rep_level_; }
int def_level_of_immediate_repeated_ancestor() const {
return node_.def_level_of_immediate_repeated_ancestor;
}
const SlotDescriptor* pos_slot_desc() const { return pos_slot_desc_; }
void set_pos_slot_desc(const SlotDescriptor* pos_slot_desc) {
DCHECK(pos_slot_desc_ == NULL);
pos_slot_desc_ = pos_slot_desc;
}
/// Returns true if this reader materializes collections (i.e. CollectionValues).
virtual bool IsCollectionReader() const { return false; }
const char* filename() const { return parent_->filename(); }
/// Read the current value (or null) into 'tuple' for this column. This should only be
/// called when a value is defined, i.e., def_level() >=
/// def_level_of_immediate_repeated_ancestor() (since empty or NULL collections produce
/// no output values), otherwise NextLevels() should be called instead.
///
/// Advances this column reader to the next value (i.e. NextLevels() doesn't need to be
/// called after calling ReadValue()).
///
/// Returns false if execution should be aborted for some reason, e.g. parse_error_ is
/// set, the query is cancelled, or the scan node limit was reached. Otherwise returns
/// true.
///
/// NextLevels() must be called on this reader before calling ReadValue() for the first
/// time. This is to initialize the current value that ReadValue() will read.
///
/// TODO: this is the function that needs to be codegen'd (e.g. CodegenReadValue())
/// The codegened functions from all the materialized cols will then be combined
/// into one function.
/// TODO: another option is to materialize col by col for the entire row batch in
/// one call. e.g. MaterializeCol would write out 1024 values. Our row batches
/// are currently dense so we'll need to figure out something there.
virtual bool ReadValue(MemPool* pool, Tuple* tuple) = 0;
/// Same as ReadValue() but does not advance repetition level. Only valid for columns
/// not in collections.
virtual bool ReadNonRepeatedValue(MemPool* pool, Tuple* tuple) = 0;
/// Returns true if this reader needs to be seeded with NextLevels() before
/// calling ReadValueBatch() or ReadNonRepeatedValueBatch().
/// Note that all readers need to be seeded before calling the non-batched ReadValue().
virtual bool NeedsSeedingForBatchedReading() const { return true; }
/// Batched version of ReadValue() that reads up to max_values at once and materializes
/// them into tuples in tuple_mem. Returns the number of values actually materialized
/// in *num_values. The return value, error behavior and state changes are generally
/// the same as in ReadValue(). For example, if an error occurs in the middle of
/// materializing a batch then false is returned, and num_values, tuple_mem, as well as
/// this column reader are left in an undefined state, assuming that the caller will
/// immediately abort execution.
virtual bool ReadValueBatch(MemPool* pool, int max_values, int tuple_size,
uint8_t* tuple_mem, int* num_values);
/// Batched version of ReadNonRepeatedValue() that reads up to max_values at once and
/// materializes them into tuples in tuple_mem.
/// The return value and error behavior are the same as in ReadValueBatch().
virtual bool ReadNonRepeatedValueBatch(MemPool* pool, int max_values, int tuple_size,
uint8_t* tuple_mem, int* num_values);
/// Advances this column reader's def and rep levels to the next logical value, i.e. to
/// the next scalar value or the beginning of the next collection, without attempting to
/// read the value. This is used to skip past def/rep levels that don't materialize a
/// value, such as the def/rep levels corresponding to an empty containing collection.
///
/// NextLevels() must be called on this reader before calling ReadValue() for the first
/// time. This is to initialize the current value that ReadValue() will read.
///
/// Returns false if execution should be aborted for some reason, e.g. parse_error_ is
/// set, the query is cancelled, or the scan node limit was reached. Otherwise returns
/// true.
virtual bool NextLevels() = 0;
/// Should only be called if pos_slot_desc_ is non-NULL. Writes pos_current_value_ to
/// 'tuple' (i.e. "reads" the synthetic position field of the parent collection into
/// 'tuple') and increments pos_current_value_.
void ReadPosition(Tuple* tuple);
/// Returns true if this column reader has reached the end of the row group.
inline bool RowGroupAtEnd() { return rep_level_ == HdfsParquetScanner::ROW_GROUP_END; }
/// If 'row_batch' is non-NULL, transfers the remaining resources backing tuples to it,
/// and frees up other resources. If 'row_batch' is NULL frees all resources instead.
virtual void Close(RowBatch* row_batch) = 0;
protected:
HdfsParquetScanner* parent_;
const SchemaNode& node_;
const SlotDescriptor* slot_desc_;
/// The slot descriptor for the position field of the tuple, if there is one. NULL if
/// there's not. Only one column reader for a given tuple desc will have this set.
const SlotDescriptor* pos_slot_desc_;
/// The next value to write into the position slot, if there is one. 64-bit int because
/// the pos slot is always a BIGINT Set to -1 when this column reader does not have a
/// current rep and def level (i.e. before the first NextLevels() call or after the last
/// value in the column has been read).
int64_t pos_current_value_;
/// The current repetition and definition levels of this reader. Advanced via
/// ReadValue() and NextLevels(). Set to -1 when this column reader does not have a
/// current rep and def level (i.e. before the first NextLevels() call or after the last
/// value in the column has been read). If this is not inside a collection, rep_level_ is
/// always 0.
/// int16_t is large enough to hold the valid levels 0-255 and sentinel value -1.
/// The maximum values are cached here because they are accessed in inner loops.
int16_t rep_level_;
int16_t max_rep_level_;
int16_t def_level_;
int16_t max_def_level_;
// Cache frequently accessed members of slot_desc_ for perf.
/// slot_desc_->tuple_offset(). -1 if slot_desc_ is NULL.
int tuple_offset_;
/// slot_desc_->null_indicator_offset(). Invalid if slot_desc_ is NULL.
NullIndicatorOffset null_indicator_offset_;
ParquetColumnReader(HdfsParquetScanner* parent, const SchemaNode& node,
const SlotDescriptor* slot_desc)
: parent_(parent),
node_(node),
slot_desc_(slot_desc),
pos_slot_desc_(NULL),
pos_current_value_(HdfsParquetScanner::INVALID_POS),
rep_level_(HdfsParquetScanner::INVALID_LEVEL),
max_rep_level_(node_.max_rep_level),
def_level_(HdfsParquetScanner::INVALID_LEVEL),
max_def_level_(node_.max_def_level),
tuple_offset_(slot_desc == NULL ? -1 : slot_desc->tuple_offset()),
null_indicator_offset_(slot_desc == NULL ? NullIndicatorOffset() :
slot_desc->null_indicator_offset()) {
DCHECK_GE(node_.max_rep_level, 0);
DCHECK_LE(node_.max_rep_level, std::numeric_limits<int16_t>::max());
DCHECK_GE(node_.max_def_level, 0);
DCHECK_LE(node_.max_def_level, std::numeric_limits<int16_t>::max());
// rep_level_ is always valid and equal to 0 if col not in collection.
if (max_rep_level() == 0) rep_level_ = 0;
}
/// Called in the middle of creating a scratch tuple batch to simulate failures
/// such as exceeding memory limit or cancellation. Returns false if the debug
/// action deems that the parquet column reader should halt execution. 'val_count'
/// is the counter which the column reader uses to track the number of tuples
/// produced so far. If the column reader should halt execution, 'parse_status_'
/// is updated with the error status and 'val_count' is set to 0.
bool ColReaderDebugAction(int* val_count);
};
/// Reader for a single column from the parquet file. It's associated with a
/// ScannerContext::Stream and is responsible for decoding the data. Super class for
/// per-type column readers. This contains most of the logic, the type specific functions
/// must be implemented in the subclass.
class BaseScalarColumnReader : public ParquetColumnReader {
public:
BaseScalarColumnReader(HdfsParquetScanner* parent, const SchemaNode& node,
const SlotDescriptor* slot_desc)
: ParquetColumnReader(parent, node, slot_desc),
data_(NULL),
data_end_(NULL),
def_levels_(true),
rep_levels_(false),
page_encoding_(parquet::Encoding::PLAIN_DICTIONARY),
num_buffered_values_(0),
num_values_read_(0),
metadata_(NULL),
stream_(NULL),
decompressed_data_pool_(new MemPool(parent->scan_node_->mem_tracker())) {
DCHECK_GE(node_.col_idx, 0) << node_.DebugString();
}
virtual ~BaseScalarColumnReader() { }
/// This is called once for each row group in the file.
Status Reset(const parquet::ColumnMetaData* metadata, ScannerContext::Stream* stream) {
DCHECK(stream != NULL);
DCHECK(metadata != NULL);
num_buffered_values_ = 0;
data_ = NULL;
data_end_ = NULL;
stream_ = stream;
metadata_ = metadata;
num_values_read_ = 0;
def_level_ = -1;
// See ColumnReader constructor.
rep_level_ = max_rep_level() == 0 ? 0 : -1;
pos_current_value_ = -1;
if (metadata_->codec != parquet::CompressionCodec::UNCOMPRESSED) {
RETURN_IF_ERROR(Codec::CreateDecompressor(
NULL, false, PARQUET_TO_IMPALA_CODEC[metadata_->codec], &decompressor_));
}
ClearDictionaryDecoder();
return Status::OK();
}
virtual void Close(RowBatch* row_batch) {
if (row_batch != nullptr && CurrentPageContainsTupleData()) {
row_batch->tuple_data_pool()->AcquireData(decompressed_data_pool_.get(), false);
} else {
decompressed_data_pool_->FreeAll();
}
if (decompressor_ != nullptr) decompressor_->Close();
}
int64_t total_len() const { return metadata_->total_compressed_size; }
int col_idx() const { return node_.col_idx; }
THdfsCompression::type codec() const {
if (metadata_ == NULL) return THdfsCompression::NONE;
return PARQUET_TO_IMPALA_CODEC[metadata_->codec];
}
/// Reads the next definition and repetition levels for this column. Initializes the
/// next data page if necessary.
virtual bool NextLevels() { return NextLevels<true>(); }
// Check the data stream to see if there is a dictionary page. If there is,
// use that page to initialize dict_decoder_ and advance the data stream
// past the dictionary page.
Status InitDictionary();
// Returns the dictionary or NULL if the dictionary doesn't exist
virtual DictDecoderBase* GetDictionaryDecoder() { return nullptr; }
// Returns whether the datatype for this column requires conversion from the on-disk
// format for correctness. For example, timestamps can require an offset to be
// applied.
virtual bool NeedsConversion() { return false; }
// Returns whether the datatype for this column requires validation. For example,
// the timestamp format has certain bit combinations that are invalid, and these
// need to be validated when read from disk.
virtual bool NeedsValidation() { return false; }
// TODO: Some encodings might benefit a lot from a SkipValues(int num_rows) if
// we know this row can be skipped. This could be very useful with stats and big
// sections can be skipped. Implement that when we can benefit from it.
protected:
// Friend parent scanner so it can perform validation (e.g. ValidateEndOfRowGroup())
friend class HdfsParquetScanner;
// Class members that are accessed for every column should be included up here so they
// fit in as few cache lines as possible.
/// Pointer to start of next value in data page
uint8_t* data_;
/// End of the data page.
const uint8_t* data_end_;
/// Decoder for definition levels.
ParquetLevelDecoder def_levels_;
/// Decoder for repetition levels.
ParquetLevelDecoder rep_levels_;
/// Page encoding for values. Cached here for perf.
parquet::Encoding::type page_encoding_;
/// Num values remaining in the current data page
int num_buffered_values_;
// Less frequently used members that are not accessed in inner loop should go below
// here so they do not occupy precious cache line space.
/// The number of values seen so far. Updated per data page.
int64_t num_values_read_;
const parquet::ColumnMetaData* metadata_;
boost::scoped_ptr<Codec> decompressor_;
ScannerContext::Stream* stream_;
/// Pool to allocate decompression buffers from.
boost::scoped_ptr<MemPool> decompressed_data_pool_;
/// Header for current data page.
parquet::PageHeader current_page_header_;
/// Reads the next page header into next_page_header/next_header_size.
/// If the stream reaches the end before reading a complete page header,
/// eos is set to true. If peek is false, the stream position is advanced
/// past the page header. If peek is true, the stream position is not moved.
/// Returns an error status if the next page header could not be read.
Status ReadPageHeader(bool peek, parquet::PageHeader* next_page_header,
uint32_t* next_header_size, bool* eos);
/// Read the next data page. If a dictionary page is encountered, that will be read and
/// this function will continue reading the next data page.
Status ReadDataPage();
/// Try to move the the next page and buffer more values. Return false and sets rep_level_,
/// def_level_ and pos_current_value_ to -1 if no more pages or an error encountered.
bool NextPage();
/// Implementation for NextLevels().
template <bool ADVANCE_REP_LEVEL>
bool NextLevels();
/// Creates a dictionary decoder from values/size. 'decoder' is set to point to a
/// dictionary decoder stored in this object. Subclass must implement this. Returns
/// an error status if the dictionary values could not be decoded successfully.
virtual Status CreateDictionaryDecoder(uint8_t* values, int size,
DictDecoderBase** decoder) = 0;
/// Return true if the column has a dictionary decoder. Subclass must implement this.
virtual bool HasDictionaryDecoder() = 0;
/// Clear the dictionary decoder so HasDictionaryDecoder() will return false. Subclass
/// must implement this.
virtual void ClearDictionaryDecoder() = 0;
/// Initializes the reader with the data contents. This is the content for the entire
/// decompressed data page. Decoders can initialize state from here. The caller must
/// validate the input such that 'size' is non-negative and that 'data' has at least
/// 'size' bytes remaining.
virtual Status InitDataPage(uint8_t* data, int size) = 0;
/// Returns true if the current data page may contain strings referenced by returned
/// batches. Cases where this is not true are:
/// * Dictionary-compressed pages, where any string data lives in 'dictionary_pool_'.
/// * Fixed-length slots, where there is no string data.
bool CurrentPageContainsTupleData() {
return page_encoding_ != parquet::Encoding::PLAIN_DICTIONARY
&& slot_desc_ != nullptr && slot_desc_->type().IsStringType();
}
};
/// Collections are not materialized directly in parquet files; only scalar values appear
/// in the file. CollectionColumnReader uses the definition and repetition levels of child
/// column readers to figure out the boundaries of each collection in this column.
class CollectionColumnReader : public ParquetColumnReader {
public:
CollectionColumnReader(HdfsParquetScanner* parent, const SchemaNode& node,
const SlotDescriptor* slot_desc)
: ParquetColumnReader(parent, node, slot_desc) {
DCHECK(node_.is_repeated());
if (slot_desc != NULL) DCHECK(slot_desc->type().IsCollectionType());
}
virtual ~CollectionColumnReader() { }
vector<ParquetColumnReader*>* children() { return &children_; }
virtual bool IsCollectionReader() const { return true; }
/// The repetition level indicating that the current value is the first in a new
/// collection (meaning the last value read was the final item in the previous
/// collection).
int new_collection_rep_level() const { return max_rep_level() - 1; }
/// Materializes CollectionValue into tuple slot (if materializing) and advances to next
/// value.
virtual bool ReadValue(MemPool* pool, Tuple* tuple);
/// Same as ReadValue but does not advance repetition level. Only valid for columns not
/// in collections.
virtual bool ReadNonRepeatedValue(MemPool* pool, Tuple* tuple);
/// Advances all child readers to the beginning of the next collection and updates this
/// reader's state.
virtual bool NextLevels();
/// This is called once for each row group in the file.
void Reset() {
def_level_ = -1;
rep_level_ = -1;
pos_current_value_ = -1;
}
virtual void Close(RowBatch* row_batch) {
for (ParquetColumnReader* child_reader: children_) {
child_reader->Close(row_batch);
}
}
private:
/// Column readers of fields contained within this collection. There is at least one
/// child reader per collection reader. Child readers either materialize slots in the
/// collection item tuples, or there is a single child reader that does not materialize
/// any slot and is only used by this reader to read def and rep levels.
vector<ParquetColumnReader*> children_;
/// Updates this reader's def_level_, rep_level_, and pos_current_value_ based on child
/// reader's state.
void UpdateDerivedState();
/// Recursively reads from children_ to assemble a single CollectionValue into
/// the appropriate destination slot in 'tuple'. Also advances rep_level_ and
/// def_level_ via NextLevels().
///
/// Returns false if execution should be aborted for some reason, e.g. parse_error_ is
/// set, the query is cancelled, or the scan node limit was reached. Otherwise returns
/// true.
inline bool ReadSlot(Tuple* tuple, MemPool* pool);
};
}
#endif