This is an automated email from the ASF dual-hosted git repository.
mdeepak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/orc.git
The following commit(s) were added to refs/heads/master by this push:
new e4fdb94 ORC-420: [C++] Implement string dictionary encoding for C++
writer
e4fdb94 is described below
commit e4fdb9473f31b4f1a9ee571258cbcbdff012c7b7
Author: Gang Wu <[email protected]>
AuthorDate: Fri Oct 19 15:59:38 2018 -0700
ORC-420: [C++] Implement string dictionary encoding for C++ writer
Fixes #326
Signed-off-by: Deepak Majeti <[email protected]>
---
c++/include/orc/Writer.hh | 6 +
c++/src/ColumnWriter.cc | 536 ++++++++++++++++++++++++++++++++++---
c++/src/ColumnWriter.hh | 5 +
c++/src/RLE.hh | 4 +-
c++/src/RLEv1.hh | 3 +-
c++/src/RLEv2.hh | 3 +-
c++/src/Writer.cc | 7 +
c++/test/CMakeLists.txt | 1 +
c++/test/TestDictionaryEncoding.cc | 456 +++++++++++++++++++++++++++++++
9 files changed, 980 insertions(+), 41 deletions(-)
diff --git a/c++/include/orc/Writer.hh b/c++/include/orc/Writer.hh
index 7efb12b..a333a23 100644
--- a/c++/include/orc/Writer.hh
+++ b/c++/include/orc/Writer.hh
@@ -185,6 +185,12 @@ namespace orc {
* @return if not set, the default is false
*/
bool getEnableIndex() const;
+
+ /**
+ * Get whether or not to enable dictionary encoding
+ * @return if not set, the default is false
+ */
+ bool getEnableDictionary() const;
};
class Writer {
diff --git a/c++/src/ColumnWriter.cc b/c++/src/ColumnWriter.cc
index 9b128e3..c6c30f3 100644
--- a/c++/src/ColumnWriter.cc
+++ b/c++/src/ColumnWriter.cc
@@ -204,6 +204,10 @@ namespace orc {
}
}
+ void ColumnWriter::writeDictionary() {
+ // PASS
+ }
+
class StructColumnWriter : public ColumnWriter {
public:
StructColumnWriter(
@@ -237,6 +241,8 @@ namespace orc {
virtual void writeIndex(
std::vector<proto::Stream> &streams) const override;
+ virtual void writeDictionary() override;
+
virtual void reset() override;
private:
@@ -382,6 +388,12 @@ namespace orc {
}
}
+ void StructColumnWriter::writeDictionary() {
+ for (uint32_t i = 0; i < children.size(); ++i) {
+ children[i]->writeDictionary();
+ }
+ }
+
class IntegerColumnWriter : public ColumnWriter {
public:
IntegerColumnWriter(
@@ -831,6 +843,137 @@ namespace orc {
dataStream->recordPosition(rowIndexPosition.get());
}
+ /**
+ * Implementation of increasing sorted string dictionary
+ */
+ class StringDictionary {
+ public:
+ struct DictEntry {
+ DictEntry(const char * str, size_t len):data(str),length(len) {}
+ const char * data;
+ size_t length;
+ };
+
+ StringDictionary():totalLength(0) {}
+
+ // insert a new string into dictionary, return its insertion order
+ size_t insert(const char * data, size_t len);
+
+ // write dictionary data & length to output buffer
+ void flush(AppendOnlyBufferedStream * dataStream,
+ RleEncoder * lengthEncoder) const;
+
+ // reorder input index buffer from insertion order to dictionary order
+ void reorder(std::vector<int64_t>& idxBuffer) const;
+
+ // get dict entries in insertion order
+ void getEntriesInInsertionOrder(std::vector<const DictEntry *>&) const;
+
+ // return count of entries
+ size_t size() const;
+
+ // return total length of strings in the dictioanry
+ uint64_t length() const;
+
+ void clear();
+
+ private:
+ struct LessThan {
+ bool operator()(const DictEntry& left, const DictEntry& right) const {
+ int ret = memcmp(left.data, right.data, std::min(left.length,
right.length));
+ if (ret != 0) {
+ return ret < 0;
+ }
+ return left.length < right.length;
+ }
+ };
+
+ std::map<DictEntry, size_t, LessThan> dict;
+ std::vector<std::vector<char>> data;
+ uint64_t totalLength;
+
+ // use friend class here to avoid being bothered by const function calls
+ friend class StringColumnWriter;
+ friend class CharColumnWriter;
+ friend class VarCharColumnWriter;
+ // store indexes of insertion order in the dictionary for not-null rows
+ std::vector<int64_t> idxInDictBuffer;
+ };
+
+ // insert a new string into dictionary, return its insertion order
+ size_t StringDictionary::insert(const char * str, size_t len) {
+ auto ret = dict.insert({DictEntry(str, len), dict.size()});
+ if (ret.second) {
+ // make a copy to internal storage
+ data.push_back(std::vector<char>(len));
+ memcpy(data.back().data(), str, len);
+ // update dictionary entry to link pointer to internal storage
+ DictEntry * entry = const_cast<DictEntry *>(&(ret.first->first));
+ entry->data = data.back().data();
+ totalLength += len;
+ }
+ return ret.first->second;
+ }
+
+ // write dictionary data & length to output buffer
+ void StringDictionary::flush(AppendOnlyBufferedStream * dataStream,
+ RleEncoder * lengthEncoder) const {
+ for (auto it = dict.cbegin(); it != dict.cend(); ++it) {
+ dataStream->write(it->first.data, it->first.length);
+ lengthEncoder->write(static_cast<int64_t>(it->first.length));
+ }
+ }
+
+ /**
+ * Reorder input index buffer from insertion order to dictionary order
+ *
+ * We require this function because string values are buffered by indexes
+ * in their insertion order. Until the entire dictionary is complete can
+ * we get their sorted indexes in the dictionary in that ORC specification
+ * demands dictionary should be ordered. Therefore this function transforms
+ * the indexes from insertion order to dictionary value order for final
+ * output.
+ */
+ void StringDictionary::reorder(std::vector<int64_t>& idxBuffer) const {
+ // iterate the dictionary to get mapping from insertion order to value
order
+ std::vector<size_t> mapping(dict.size());
+ size_t dictIdx = 0;
+ for (auto it = dict.cbegin(); it != dict.cend(); ++it) {
+ mapping[it->second] = dictIdx++;
+ }
+
+ // do the transformation
+ for (size_t i = 0; i != idxBuffer.size(); ++i) {
+ idxBuffer[i] = static_cast<int64_t>(
+ mapping[static_cast<size_t>(idxBuffer[i])]);
+ }
+ }
+
+ // get dict entries in insertion order
+ void StringDictionary::getEntriesInInsertionOrder(
+ std::vector<const DictEntry *>& entries) const {
+ entries.resize(dict.size());
+ for (auto it = dict.cbegin(); it != dict.cend(); ++it) {
+ entries[it->second] = &(it->first);
+ }
+ }
+
+ // return count of entries
+ size_t StringDictionary::size() const {
+ return dict.size();
+ }
+
+ // return total length of strings in the dictioanry
+ uint64_t StringDictionary::length() const {
+ return totalLength;
+ }
+
+ void StringDictionary::clear() {
+ totalLength = 0;
+ data.clear();
+ dict.clear();
+ }
+
class StringColumnWriter : public ColumnWriter {
public:
StringColumnWriter(const Type& type,
@@ -850,10 +993,50 @@ namespace orc {
virtual void recordPosition() const override;
+ virtual void createRowIndexEntry() override;
+
+ virtual void writeDictionary() override;
+
+ virtual void reset() override;
+
+ private:
+ /**
+ * dictionary related functions
+ */
+ bool checkDictionaryKeyRatio();
+ void createDirectStreams();
+ void createDictStreams();
+ void deleteDictStreams();
+ void fallbackToDirectEncoding();
+
protected:
- std::unique_ptr<RleEncoder> lengthEncoder;
- std::unique_ptr<AppendOnlyBufferedStream> dataStream;
RleVersion rleVersion;
+ bool useCompression;
+ const StreamsFactory& streamsFactory;
+ bool alignedBitPacking;
+
+ // direct encoding streams
+ std::unique_ptr<RleEncoder> directLengthEncoder;
+ std::unique_ptr<AppendOnlyBufferedStream> directDataStream;
+
+ // dictionary encoding streams
+ std::unique_ptr<RleEncoder> dictDataEncoder;
+ std::unique_ptr<RleEncoder> dictLengthEncoder;
+ std::unique_ptr<AppendOnlyBufferedStream> dictStream;
+
+ /**
+ * dictionary related variables
+ */
+ StringDictionary dictionary;
+ // whether or not dictionary checking is done
+ bool doneDictionaryCheck;
+ // whether or not it should be used
+ bool useDictionary;
+ // keys in the dictionary should not exceed this ratio
+ double dictSizeThreshold;
+
+ // record start row of each row group; null rows are skipped
+ mutable std::vector<size_t> startOfRowGroups;
};
StringColumnWriter::StringColumnWriter(
@@ -861,16 +1044,24 @@ namespace orc {
const StreamsFactory& factory,
const WriterOptions& options) :
ColumnWriter(type, factory, options),
- rleVersion(options.getRleVersion()) {
- std::unique_ptr<BufferedOutputStream> lengthStream =
- factory.createStream(proto::Stream_Kind_LENGTH);
- lengthEncoder = createRleEncoder(std::move(lengthStream),
- false,
- rleVersion,
- memPool,
- options.getAlignedBitpacking());
- dataStream.reset(new AppendOnlyBufferedStream(
- factory.createStream(proto::Stream_Kind_DATA)));
+ rleVersion(options.getRleVersion()),
+ useCompression(options.getCompression() !=
CompressionKind_NONE),
+ streamsFactory(factory),
+
alignedBitPacking(options.getAlignedBitpacking()),
+ doneDictionaryCheck(false),
+ useDictionary(options.getEnableDictionary()),
+
dictSizeThreshold(options.getDictionaryKeySizeThreshold()){
+ if (type.getKind() == TypeKind::BINARY) {
+ useDictionary = false;
+ doneDictionaryCheck = true;
+ }
+
+ if (useDictionary) {
+ createDictStreams();
+ } else {
+ doneDictionaryCheck = true;
+ createDirectStreams();
+ }
if (enableIndex) {
recordPosition();
@@ -892,7 +1083,9 @@ namespace orc {
const char* notNull = stringBatch->hasNulls ?
stringBatch->notNull.data() + offset : nullptr;
- lengthEncoder->add(length, numValues, notNull);
+ if (!useDictionary){
+ directLengthEncoder->add(length, numValues, notNull);
+ }
StringColumnStatisticsImpl* strStats =
dynamic_cast<StringColumnStatisticsImpl*>(colIndexStatistics.get());
@@ -902,7 +1095,12 @@ namespace orc {
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
if (!notNull || notNull[i]) {
- dataStream->write(data[i], static_cast<size_t>(length[i]));
+ if (useDictionary) {
+ size_t index = dictionary.insert(data[i],
static_cast<size_t>(length[i]));
+ dictionary.idxInDictBuffer.push_back(static_cast<int64_t>(index));
+ } else {
+ directDataStream->write(data[i], static_cast<size_t>(length[i]));
+ }
strStats->update(data[i], static_cast<size_t>(length[i]));
strStats->increase(1);
} else if (!hasNull) {
@@ -917,38 +1115,225 @@ namespace orc {
void StringColumnWriter::flush(std::vector<proto::Stream>& streams) {
ColumnWriter::flush(streams);
- proto::Stream length;
- length.set_kind(proto::Stream_Kind_LENGTH);
- length.set_column(static_cast<uint32_t>(columnId));
- length.set_length(lengthEncoder->flush());
- streams.push_back(length);
+ if (useDictionary) {
+ proto::Stream data;
+ data.set_kind(proto::Stream_Kind_DATA);
+ data.set_column(static_cast<uint32_t>(columnId));
+ data.set_length(dictDataEncoder->flush());
+ streams.push_back(data);
+
+ proto::Stream dict;
+ dict.set_kind(proto::Stream_Kind_DICTIONARY_DATA);
+ dict.set_column(static_cast<uint32_t>(columnId));
+ dict.set_length(dictStream->flush());
+ streams.push_back(dict);
+
+ proto::Stream length;
+ length.set_kind(proto::Stream_Kind_LENGTH);
+ length.set_column(static_cast<uint32_t>(columnId));
+ length.set_length(dictLengthEncoder->flush());
+ streams.push_back(length);
+ } else {
+ proto::Stream length;
+ length.set_kind(proto::Stream_Kind_LENGTH);
+ length.set_column(static_cast<uint32_t>(columnId));
+ length.set_length(directLengthEncoder->flush());
+ streams.push_back(length);
- proto::Stream data;
- data.set_kind(proto::Stream_Kind_DATA);
- data.set_column(static_cast<uint32_t>(columnId));
- data.set_length(dataStream->flush());
- streams.push_back(data);
+ proto::Stream data;
+ data.set_kind(proto::Stream_Kind_DATA);
+ data.set_column(static_cast<uint32_t>(columnId));
+ data.set_length(directDataStream->flush());
+ streams.push_back(data);
+ }
}
uint64_t StringColumnWriter::getEstimatedSize() const {
uint64_t size = ColumnWriter::getEstimatedSize();
- size += lengthEncoder->getBufferSize();
- size += dataStream->getSize();
+ if (!useDictionary) {
+ size += directLengthEncoder->getBufferSize();
+ size += directDataStream->getSize();
+ } else {
+ size += dictionary.length();
+ size += dictionary.size() * sizeof(int32_t);
+ size += dictionary.idxInDictBuffer.size() * sizeof(int32_t);
+ if (useCompression) {
+ size /= 3; // estimated ratio is 3:1
+ }
+ }
return size;
}
void StringColumnWriter::getColumnEncoding(
std::vector<proto::ColumnEncoding>& encodings) const {
proto::ColumnEncoding encoding;
- encoding.set_kind(RleVersionMapper(rleVersion));
- encoding.set_dictionarysize(0);
+ if (!useDictionary) {
+ encoding.set_kind(rleVersion == RleVersion_1 ?
+ proto::ColumnEncoding_Kind_DIRECT :
+ proto::ColumnEncoding_Kind_DIRECT_V2);
+ } else {
+ encoding.set_kind(rleVersion == RleVersion_1 ?
+ proto::ColumnEncoding_Kind_DICTIONARY :
+ proto::ColumnEncoding_Kind_DICTIONARY_V2);
+ }
+ encoding.set_dictionarysize(static_cast<uint32_t>(dictionary.size()));
encodings.push_back(encoding);
}
void StringColumnWriter::recordPosition() const {
ColumnWriter::recordPosition();
- dataStream->recordPosition(rowIndexPosition.get());
- lengthEncoder->recordPosition(rowIndexPosition.get());
+ if (!useDictionary) {
+ directDataStream->recordPosition(rowIndexPosition.get());
+ directLengthEncoder->recordPosition(rowIndexPosition.get());
+ } else {
+ if (enableIndex) {
+ startOfRowGroups.push_back(dictionary.idxInDictBuffer.size());
+ }
+ }
+ }
+
+ bool StringColumnWriter::checkDictionaryKeyRatio() {
+ if (!doneDictionaryCheck) {
+ useDictionary = dictionary.size() <= static_cast<size_t>(
+ static_cast<double>(dictionary.idxInDictBuffer.size()) *
dictSizeThreshold);
+ doneDictionaryCheck = true;
+ }
+
+ return useDictionary;
+ }
+
+ void StringColumnWriter::createRowIndexEntry() {
+ if (useDictionary && !doneDictionaryCheck) {
+ if (!checkDictionaryKeyRatio()) {
+ fallbackToDirectEncoding();
+ }
+ }
+ ColumnWriter::createRowIndexEntry();
+ }
+
+ void StringColumnWriter::reset() {
+ ColumnWriter::reset();
+
+ dictionary.clear();
+ dictionary.idxInDictBuffer.resize(0);
+ startOfRowGroups.clear();
+ startOfRowGroups.push_back(0);
+ }
+
+ void StringColumnWriter::createDirectStreams() {
+ std::unique_ptr<BufferedOutputStream> directLengthStream =
+ streamsFactory.createStream(proto::Stream_Kind_LENGTH);
+ directLengthEncoder = createRleEncoder(std::move(directLengthStream),
+ false,
+ rleVersion,
+ memPool,
+ alignedBitPacking);
+ directDataStream.reset(new AppendOnlyBufferedStream(
+ streamsFactory.createStream(proto::Stream_Kind_DATA)));
+ }
+
+ void StringColumnWriter::createDictStreams() {
+ std::unique_ptr<BufferedOutputStream> dictDataStream =
+ streamsFactory.createStream(proto::Stream_Kind_DATA);
+ dictDataEncoder = createRleEncoder(std::move(dictDataStream),
+ false,
+ rleVersion,
+ memPool,
+ alignedBitPacking);
+ std::unique_ptr<BufferedOutputStream> dictLengthStream =
+ streamsFactory.createStream(proto::Stream_Kind_LENGTH);
+ dictLengthEncoder = createRleEncoder(std::move(dictLengthStream),
+ false,
+ rleVersion,
+ memPool,
+ alignedBitPacking);
+ dictStream.reset(new AppendOnlyBufferedStream(
+ streamsFactory.createStream(proto::Stream_Kind_DICTIONARY_DATA)));
+ }
+
+ void StringColumnWriter::deleteDictStreams() {
+ dictDataEncoder.reset(nullptr);
+ dictLengthEncoder.reset(nullptr);
+ dictStream.reset(nullptr);
+
+ dictionary.clear();
+ dictionary.idxInDictBuffer.clear();
+ startOfRowGroups.clear();
+ }
+
+ void StringColumnWriter::writeDictionary() {
+ if (useDictionary && !doneDictionaryCheck) {
+ // when index is disabled, dictionary check happens while writing 1st
stripe
+ if (!checkDictionaryKeyRatio()) {
+ fallbackToDirectEncoding();
+ return;
+ }
+ }
+
+ if (useDictionary) {
+ // flush dictionary data & length streams
+ dictionary.flush(dictStream.get(), dictLengthEncoder.get());
+
+ // convert index from insertion order to dictionary order
+ dictionary.reorder(dictionary.idxInDictBuffer);
+
+ // write data sequences
+ int64_t * data = dictionary.idxInDictBuffer.data();
+ if (enableIndex) {
+ size_t prevOffset = 0;
+ for (size_t i = 0; i < startOfRowGroups.size(); ++i) {
+ // write sequences in batch for a row group stride
+ size_t offset = startOfRowGroups[i];
+ dictDataEncoder->add(data + prevOffset, offset - prevOffset,
nullptr);
+
+ // update index positions
+ int rowGroupId = static_cast<int>(i);
+ proto::RowIndexEntry* indexEntry =
+ (rowGroupId < rowIndex->entry_size()) ?
+ rowIndex->mutable_entry(rowGroupId) : rowIndexEntry.get();
+
+ // add positions for direct streams
+ RowIndexPositionRecorder recorder(*indexEntry);
+ dictDataEncoder->recordPosition(&recorder);
+
+ prevOffset = offset;
+ }
+
+ dictDataEncoder->add(data + prevOffset,
+ dictionary.idxInDictBuffer.size() - prevOffset,
+ nullptr);
+ } else {
+ dictDataEncoder->add(data, dictionary.idxInDictBuffer.size(), nullptr);
+ }
+ }
+ }
+
+ void StringColumnWriter::fallbackToDirectEncoding() {
+ createDirectStreams();
+
+ if (enableIndex) {
+ // fallback happens at the 1st row group;
+ // simply complete positions for direct streams
+ proto::RowIndexEntry * indexEntry = rowIndexEntry.get();
+ RowIndexPositionRecorder recorder(*indexEntry);
+ directDataStream->recordPosition(&recorder);
+ directLengthEncoder->recordPosition(&recorder);
+ }
+
+ // get dictionary entries in insertion order
+ std::vector<const StringDictionary::DictEntry *> entries;
+ dictionary.getEntriesInInsertionOrder(entries);
+
+ // store each length of the data into a vector
+ const StringDictionary::DictEntry * dictEntry = nullptr;
+ for (uint64_t i = 0; i != dictionary.idxInDictBuffer.size(); ++i) {
+ // write one row data in direct encoding
+ dictEntry = entries[static_cast<size_t>(dictionary.idxInDictBuffer[i])];
+ directDataStream->write(dictEntry->data, dictEntry->length);
+ directLengthEncoder->write(static_cast<int64_t>(dictEntry->length));
+ }
+
+ deleteDictStreams();
}
struct Utf8Utils {
@@ -1083,14 +1468,25 @@ namespace orc {
' ',
static_cast<size_t>(length[i]) - originLength);
}
- dataStream->write(charData, static_cast<size_t>(length[i]));
+
+ if (useDictionary) {
+ size_t index = dictionary.insert(charData,
static_cast<size_t>(length[i]));
+ dictionary.idxInDictBuffer.push_back(static_cast<int64_t>(index));
+ } else {
+ directDataStream->write(charData, static_cast<size_t>(length[i]));
+ }
+
strStats->update(charData, static_cast<size_t>(length[i]));
strStats->increase(1);
} else if (!hasNull) {
hasNull = true;
}
}
- lengthEncoder->add(length, numValues, notNull);
+
+ if (!useDictionary) {
+ directLengthEncoder->add(length, numValues, notNull);
+ }
+
if (hasNull) {
strStats->setHasNull(true);
}
@@ -1139,9 +1535,14 @@ namespace orc {
if (!notNull || notNull[i]) {
uint64_t itemLength = Utf8Utils::truncateBytesTo(
maxLength, data[i], static_cast<uint64_t>(length[i]));
-
length[i] = static_cast<int64_t>(itemLength);
- dataStream->write(data[i], static_cast<size_t>(length[i]));
+
+ if (useDictionary) {
+ size_t index = dictionary.insert(data[i],
static_cast<size_t>(length[i]));
+ dictionary.idxInDictBuffer.push_back(static_cast<int64_t>(index));
+ } else {
+ directDataStream->write(data[i], static_cast<size_t>(length[i]));
+ }
strStats->update(data[i], static_cast<size_t>(length[i]));
strStats->increase(1);
@@ -1149,7 +1550,11 @@ namespace orc {
hasNull = true;
}
}
- lengthEncoder->add(length, numValues, notNull);
+
+ if (!useDictionary) {
+ directLengthEncoder->add(length, numValues, notNull);
+ }
+
if (hasNull) {
strStats->setHasNull(true);
}
@@ -1193,7 +1598,7 @@ namespace orc {
for (uint64_t i = 0; i < numValues; ++i) {
uint64_t unsignedLength = static_cast<uint64_t>(length[i]);
if (!notNull || notNull[i]) {
- dataStream->write(data[i], unsignedLength);
+ directDataStream->write(data[i], unsignedLength);
binStats->update(unsignedLength);
binStats->increase(1);
@@ -1201,7 +1606,7 @@ namespace orc {
hasNull = true;
}
}
- lengthEncoder->add(length, numValues, notNull);
+ directLengthEncoder->add(length, numValues, notNull);
if (hasNull) {
binStats->setHasNull(true);
}
@@ -1680,6 +2085,10 @@ namespace orc {
virtual void recordPosition() const override;
+ virtual void writeDictionary() override;
+
+ virtual void reset() override;
+
private:
std::unique_ptr<RleEncoder> lengthEncoder;
RleVersion rleVersion;
@@ -1843,6 +2252,19 @@ namespace orc {
lengthEncoder->recordPosition(rowIndexPosition.get());
}
+ void ListColumnWriter::reset() {
+ ColumnWriter::reset();
+ if (child) {
+ child->reset();
+ }
+ }
+
+ void ListColumnWriter::writeDictionary() {
+ if (child) {
+ child->writeDictionary();
+ }
+ }
+
class MapColumnWriter : public ColumnWriter {
public:
MapColumnWriter(const Type& type,
@@ -1878,6 +2300,10 @@ namespace orc {
virtual void recordPosition() const override;
+ virtual void writeDictionary() override;
+
+ virtual void reset() override;
+
private:
std::unique_ptr<ColumnWriter> keyWriter;
std::unique_ptr<ColumnWriter> elemWriter;
@@ -2077,6 +2503,25 @@ namespace orc {
lengthEncoder->recordPosition(rowIndexPosition.get());
}
+ void MapColumnWriter::reset() {
+ ColumnWriter::reset();
+ if (keyWriter) {
+ keyWriter->reset();
+ }
+ if (elemWriter) {
+ elemWriter->reset();
+ }
+ }
+
+ void MapColumnWriter::writeDictionary() {
+ if (keyWriter) {
+ keyWriter->writeDictionary();
+ }
+ if (elemWriter) {
+ elemWriter->writeDictionary();
+ }
+ }
+
class UnionColumnWriter : public ColumnWriter {
public:
UnionColumnWriter(const Type& type,
@@ -2112,6 +2557,10 @@ namespace orc {
virtual void recordPosition() const override;
+ virtual void writeDictionary() override;
+
+ virtual void reset() override;
+
private:
std::unique_ptr<ByteRleEncoder> rleEncoder;
std::vector<ColumnWriter*> children;
@@ -2281,6 +2730,19 @@ namespace orc {
rleEncoder->recordPosition(rowIndexPosition.get());
}
+ void UnionColumnWriter::reset() {
+ ColumnWriter::reset();
+ for (uint32_t i = 0; i < children.size(); ++i) {
+ children[i]->reset();
+ }
+ }
+
+ void UnionColumnWriter::writeDictionary() {
+ for (uint32_t i = 0; i < children.size(); ++i) {
+ children[i]->writeDictionary();
+ }
+ }
+
std::unique_ptr<ColumnWriter> buildWriter(
const Type& type,
const StreamsFactory& factory,
diff --git a/c++/src/ColumnWriter.hh b/c++/src/ColumnWriter.hh
index 14fc54a..a1419d7 100644
--- a/c++/src/ColumnWriter.hh
+++ b/c++/src/ColumnWriter.hh
@@ -165,6 +165,11 @@ namespace orc {
*/
virtual void reset();
+ /**
+ * Write dictionary to streams for string columns
+ */
+ virtual void writeDictionary();
+
protected:
/**
* Utility function to translate ColumnStatistics into protobuf form and
diff --git a/c++/src/RLE.hh b/c++/src/RLE.hh
index 3eb3489..6822bd8 100644
--- a/c++/src/RLE.hh
+++ b/c++/src/RLE.hh
@@ -79,6 +79,8 @@ namespace orc {
*/
virtual void recordPosition(PositionRecorder* recorder) const;
+ virtual void write(int64_t val) = 0;
+
protected:
std::unique_ptr<BufferedOutputStream> outputStream;
size_t bufferPosition;
@@ -88,8 +90,6 @@ namespace orc {
bool isSigned;
char* buffer;
- virtual void write(int64_t val) = 0;
-
virtual void writeByte(char c);
virtual void writeVulong(int64_t val);
diff --git a/c++/src/RLEv1.hh b/c++/src/RLEv1.hh
index 26a180e..8e31d70 100644
--- a/c++/src/RLEv1.hh
+++ b/c++/src/RLEv1.hh
@@ -37,12 +37,13 @@ public:
*/
uint64_t flush() override;
+ void write(int64_t val) override;
+
private:
int64_t delta;
bool repeat;
uint64_t tailRunLength;
- void write(int64_t val) override;
void writeValues();
};
diff --git a/c++/src/RLEv2.hh b/c++/src/RLEv2.hh
index a51b276..f85dabd 100644
--- a/c++/src/RLEv2.hh
+++ b/c++/src/RLEv2.hh
@@ -75,6 +75,8 @@ public:
*/
uint64_t flush() override;
+ void write(int64_t val) override;
+
private:
const bool alignedBitPacking;
@@ -95,7 +97,6 @@ private:
void computeZigZagLiterals(EncodingOption& option);
void preparePatchedBlob(EncodingOption& option);
- void write(int64_t val) override ;
void writeInts(int64_t* input, uint32_t offset, size_t len, uint32_t
bitSize);
void initializeLiterals(int64_t val);
void writeValues(EncodingOption& option);
diff --git a/c++/src/Writer.cc b/c++/src/Writer.cc
index 9a7a777..6af1043 100644
--- a/c++/src/Writer.cc
+++ b/c++/src/Writer.cc
@@ -196,6 +196,10 @@ namespace orc {
return privateBits->enableIndex;
}
+ bool WriterOptions::getEnableDictionary() const {
+ return privateBits->dictionaryKeySizeThreshold > 0.0;
+ }
+
Writer::~Writer() {
// PASS
}
@@ -381,6 +385,9 @@ namespace orc {
columnWriter->mergeRowGroupStatsIntoStripeStats();
}
+ // dictionary should be written before any stream is flushed
+ columnWriter->writeDictionary();
+
std::vector<proto::Stream> streams;
// write ROW_INDEX streams
if (options.getEnableIndex()) {
diff --git a/c++/test/CMakeLists.txt b/c++/test/CMakeLists.txt
index 015814a..6e3f3b3 100644
--- a/c++/test/CMakeLists.txt
+++ b/c++/test/CMakeLists.txt
@@ -34,6 +34,7 @@ add_executable (orc-test
TestCompression.cc
TestDecompression.cc
TestDecimal.cc
+ TestDictionaryEncoding.cc
TestDriver.cc
TestInt128.cc
TestReader.cc
diff --git a/c++/test/TestDictionaryEncoding.cc
b/c++/test/TestDictionaryEncoding.cc
new file mode 100644
index 0000000..d4952a9
--- /dev/null
+++ b/c++/test/TestDictionaryEncoding.cc
@@ -0,0 +1,456 @@
+/**
+ * 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.
+ */
+
+#include "orc/OrcFile.hh"
+
+#include "MemoryInputStream.hh"
+#include "MemoryOutputStream.hh"
+
+#include "wrap/gmock.h"
+#include "wrap/gtest-wrapper.h"
+
+#include <cmath>
+#include <sstream>
+
+namespace orc {
+
+ const int DEFAULT_MEM_STREAM_SIZE = 10 * 1024 * 1024; // 10M
+ const int DICTIONARY_SIZE_1K = 1000;
+ const double DICT_THRESHOLD = 0.2; // make sure dictionary is used
+ const double FALLBACK_THRESHOLD = 0.0; // make sure fallback happens
+
+ static std::unique_ptr<Reader> createReader(MemoryPool * memoryPool,
+ std::unique_ptr<InputStream>
stream) {
+ ReaderOptions options;
+ options.setMemoryPool(*memoryPool);
+ return createReader(std::move(stream), options);
+ }
+
+ static std::unique_ptr<RowReader> createRowReader(Reader* reader) {
+ RowReaderOptions rowReaderOpts;
+ return reader->createRowReader(rowReaderOpts);
+ }
+
+ void testStringDictionary(bool enableIndex, double threshold) {
+ MemoryOutputStream memStream(DEFAULT_MEM_STREAM_SIZE);
+ MemoryPool * pool = getDefaultPool();
+ std::unique_ptr<Type>
type(Type::buildTypeFromString("struct<col1:string>"));
+
+ WriterOptions options;
+ options.setStripeSize(1024);
+ options.setCompressionBlockSize(1024);
+ options.setCompression(CompressionKind_ZLIB);
+ options.setMemoryPool(pool);
+ options.setDictionaryKeySizeThreshold(threshold);
+ options.setRowIndexStride(enableIndex ? 10000 : 0);
+ std::unique_ptr<Writer> writer = createWriter(*type, &memStream, options);
+
+ char dataBuffer[327675];
+ uint64_t offset = 0, rowCount = 65535;
+ std::unique_ptr<ColumnVectorBatch> batch =
+ writer->createRowBatch(rowCount);
+ StructVectorBatch * structBatch =
+ dynamic_cast<StructVectorBatch *>(batch.get());
+ StringVectorBatch * strBatch =
+ dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ uint64_t dictionarySize = DICTIONARY_SIZE_1K;
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ std::ostringstream os;
+ os << (i % dictionarySize);
+ memcpy(dataBuffer + offset, os.str().c_str(), os.str().size());
+
+ strBatch->data[i] = dataBuffer + offset;
+ strBatch->length[i] = static_cast<int64_t>(os.str().size());
+
+ offset += os.str().size();
+ }
+ structBatch->numElements = rowCount;
+ strBatch->numElements = rowCount;
+ writer->add(*batch);
+ writer->close();
+
+ std::unique_ptr<InputStream> inStream(
+ new MemoryInputStream (memStream.getData(), memStream.getLength()));
+ std::unique_ptr<Reader> reader = createReader(pool, std::move(inStream));
+ std::unique_ptr<RowReader> rowReader = createRowReader(reader.get());
+ EXPECT_EQ(rowCount, reader->getNumberOfRows());
+
+ batch = rowReader->createRowBatch(rowCount);
+ EXPECT_EQ(true, rowReader->next(*batch));
+ EXPECT_EQ(rowCount, batch->numElements);
+
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ structBatch = dynamic_cast<StructVectorBatch *>(batch.get());
+ strBatch = dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+ std::string str(
+ strBatch->data[i],
+ static_cast<size_t>(strBatch->length[i]));
+ EXPECT_EQ(i % dictionarySize, static_cast<uint64_t>(atoi(str.c_str())));
+ }
+
+ EXPECT_EQ(false, rowReader->next(*batch));
+ }
+
+ void testVarcharDictionary(bool enableIndex, double threshold) {
+ MemoryOutputStream memStream(DEFAULT_MEM_STREAM_SIZE);
+ MemoryPool * pool = getDefaultPool();
+ std::unique_ptr<Type>
type(Type::buildTypeFromString("struct<col1:varchar(2)>"));
+
+ WriterOptions options;
+ options.setStripeSize(1024);
+ options.setCompressionBlockSize(1024);
+ options.setCompression(CompressionKind_ZLIB);
+ options.setMemoryPool(pool);
+ options.setDictionaryKeySizeThreshold(threshold);
+ options.setRowIndexStride(enableIndex ? 10000 : 0);
+ std::unique_ptr<Writer> writer = createWriter(*type, &memStream, options);
+
+ char dataBuffer[327675];
+ uint64_t offset = 0, rowCount = 65535;
+ std::unique_ptr<ColumnVectorBatch> batch =
+ writer->createRowBatch(rowCount);
+ StructVectorBatch * structBatch =
+ dynamic_cast<StructVectorBatch *>(batch.get());
+ StringVectorBatch * varcharBatch =
+ dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ uint64_t dictionarySize = DICTIONARY_SIZE_1K;
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ std::ostringstream os;
+ os << (i % dictionarySize);
+ memcpy(dataBuffer + offset, os.str().c_str(), os.str().size());
+
+ varcharBatch->data[i] = dataBuffer + offset;
+ varcharBatch->length[i] = static_cast<int64_t>(os.str().size());
+
+ offset += os.str().size();
+ }
+ structBatch->numElements = rowCount;
+ varcharBatch->numElements = rowCount;
+ writer->add(*batch);
+ writer->close();
+
+ std::unique_ptr<InputStream> inStream(
+ new MemoryInputStream (memStream.getData(), memStream.getLength()));
+ std::unique_ptr<Reader> reader = createReader(pool, std::move(inStream));
+ std::unique_ptr<RowReader> rowReader = createRowReader(reader.get());
+ EXPECT_EQ(rowCount, reader->getNumberOfRows());
+
+ batch = rowReader->createRowBatch(rowCount);
+ EXPECT_EQ(true, rowReader->next(*batch));
+ EXPECT_EQ(rowCount, batch->numElements);
+
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ structBatch = dynamic_cast<StructVectorBatch *>(batch.get());
+ varcharBatch = dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ std::ostringstream os;
+ os << (i % dictionarySize);
+ EXPECT_FALSE(varcharBatch->length[i] > 2);
+ std::string varcharRead(
+ varcharBatch->data[i],
+ static_cast<size_t>(varcharBatch->length[i]));
+ std::string varcharExpected = os.str().substr(0, 2);
+ EXPECT_EQ(varcharRead, varcharExpected);
+ }
+
+ EXPECT_EQ(false, rowReader->next(*batch));
+ }
+
+ void testCharDictionary(bool enableIndex, double threshold) {
+ MemoryOutputStream memStream(DEFAULT_MEM_STREAM_SIZE);
+ MemoryPool * pool = getDefaultPool();
+ std::unique_ptr<Type>
type(Type::buildTypeFromString("struct<col1:char(3)>"));
+
+ WriterOptions options;
+ options.setStripeSize(1024);
+ options.setCompressionBlockSize(1024);
+ options.setCompression(CompressionKind_ZLIB);
+ options.setMemoryPool(pool);
+ options.setDictionaryKeySizeThreshold(threshold);
+ options.setRowIndexStride(enableIndex ? 10000 : 0);
+ std::unique_ptr<Writer> writer = createWriter(*type, &memStream, options);
+
+ char dataBuffer[327675];
+ uint64_t offset = 0, rowCount = 10000;
+ std::unique_ptr<ColumnVectorBatch> batch =
+ writer->createRowBatch(rowCount);
+ StructVectorBatch * structBatch =
+ dynamic_cast<StructVectorBatch *>(batch.get());
+ StringVectorBatch * charBatch =
+ dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ uint64_t dictionarySize = DICTIONARY_SIZE_1K;
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ std::ostringstream os;
+ os << (i % dictionarySize);
+ memcpy(dataBuffer + offset, os.str().c_str(), os.str().size());
+ charBatch->data[i] = dataBuffer + offset;
+ charBatch->length[i] = static_cast<int64_t>(os.str().size());
+ offset += os.str().size();
+ }
+ structBatch->numElements = rowCount;
+ charBatch->numElements = rowCount;
+ writer->add(*batch);
+ writer->close();
+
+ std::unique_ptr<InputStream> inStream(
+ new MemoryInputStream (memStream.getData(), memStream.getLength()));
+ std::unique_ptr<Reader> reader = createReader(pool, std::move(inStream));
+ std::unique_ptr<RowReader> rowReader = createRowReader(reader.get());
+ EXPECT_EQ(rowCount, reader->getNumberOfRows());
+
+ batch = rowReader->createRowBatch(rowCount);
+ EXPECT_EQ(true, rowReader->next(*batch));
+ EXPECT_EQ(rowCount, batch->numElements);
+
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ structBatch = dynamic_cast<StructVectorBatch *>(batch.get());
+ charBatch = dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ EXPECT_EQ(3, charBatch->length[i]);
+ std::string charsRead(
+ charBatch->data[i],
+ static_cast<size_t>(charBatch->length[i]));
+
+ std::ostringstream os;
+ os << (i % dictionarySize);
+ std::string charsExpected = os.str().substr(0, 3);
+ while (charsExpected.length() < 3) {
+ charsExpected += ' ';
+ }
+ EXPECT_EQ(charsExpected, charsRead);
+ }
+
+ EXPECT_EQ(false, rowReader->next(*batch));
+ }
+
+ void testStringDictionaryWithNull(double threshold, bool enableIndex) {
+ MemoryOutputStream memStream(DEFAULT_MEM_STREAM_SIZE);
+ MemoryPool * pool = getDefaultPool();
+ std::unique_ptr<Type>
type(Type::buildTypeFromString("struct<col1:string>"));
+
+ WriterOptions options;
+ options.setStripeSize(1024);
+ options.setCompressionBlockSize(1024);
+ options.setCompression(CompressionKind_ZLIB);
+ options.setMemoryPool(pool);
+ options.setDictionaryKeySizeThreshold(threshold);
+ options.setRowIndexStride(enableIndex ? 10000 : 0);
+ std::unique_ptr<Writer> writer = createWriter(*type, &memStream, options);
+
+ char dataBuffer[327675];
+ uint64_t offset = 0, rowCount = 65535;
+ std::unique_ptr<ColumnVectorBatch> batch =
+ writer->createRowBatch(rowCount);
+ StructVectorBatch * structBatch =
+ dynamic_cast<StructVectorBatch *>(batch.get());
+ StringVectorBatch * strBatch =
+ dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ uint64_t dictionarySize = DICTIONARY_SIZE_1K;
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ if (i % 2 == 0) {
+ strBatch->notNull[i] = false;
+ } else {
+ std::ostringstream os;
+ os << (i % dictionarySize);
+ memcpy(dataBuffer + offset, os.str().c_str(), os.str().size());
+
+ strBatch->notNull[i] = true;
+ strBatch->data[i] = dataBuffer + offset;
+ strBatch->length[i] = static_cast<int64_t>(os.str().size());
+
+ offset += os.str().size();
+ }
+ }
+
+ structBatch->numElements = rowCount;
+ strBatch->numElements = rowCount;
+ strBatch->hasNulls = true;
+ writer->add(*batch);
+ writer->close();
+
+ std::unique_ptr<InputStream> inStream(
+ new MemoryInputStream (memStream.getData(), memStream.getLength()));
+ std::unique_ptr<Reader> reader = createReader(pool, std::move(inStream));
+ std::unique_ptr<RowReader> rowReader = createRowReader(reader.get());
+ EXPECT_EQ(rowCount, reader->getNumberOfRows());
+
+ batch = rowReader->createRowBatch(rowCount);
+ EXPECT_EQ(true, rowReader->next(*batch));
+ EXPECT_EQ(rowCount, batch->numElements);
+
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ structBatch = dynamic_cast<StructVectorBatch *>(batch.get());
+ strBatch = dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ if (i % 2 == 0) {
+ EXPECT_FALSE(strBatch->notNull[i]);
+ } else {
+ EXPECT_TRUE(strBatch->notNull[i]);
+ std::string str(strBatch->data[i],
+ static_cast<size_t>(strBatch->length[i]));
+ EXPECT_EQ(i % dictionarySize,
static_cast<uint64_t>(atoi(str.c_str())));
+ }
+ }
+
+ EXPECT_EQ(false, rowReader->next(*batch));
+ }
+
+ void testDictionaryMultipleStripes(double threshold, bool enableIndex) {
+ MemoryOutputStream memStream(DEFAULT_MEM_STREAM_SIZE);
+ MemoryPool * pool = getDefaultPool();
+ std::unique_ptr<Type>
type(Type::buildTypeFromString("struct<col1:string>"));
+
+ WriterOptions options;
+ options.setStripeSize(1);
+ options.setCompressionBlockSize(1024);
+ options.setCompression(CompressionKind_ZLIB);
+ options.setMemoryPool(pool);
+ options.setDictionaryKeySizeThreshold(threshold);
+ options.setRowIndexStride(enableIndex ? 10000 : 0);
+ std::unique_ptr<Writer> writer = createWriter(*type, &memStream, options);
+
+ char dataBuffer[800000];
+ uint64_t offset = 0, rowCount = 65535, stripeCount = 3;
+ std::unique_ptr<ColumnVectorBatch> batch =
+ writer->createRowBatch(rowCount);
+ StructVectorBatch * structBatch =
+ dynamic_cast<StructVectorBatch *>(batch.get());
+ StringVectorBatch * strBatch =
+ dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+
+ uint64_t dictionarySize = DICTIONARY_SIZE_1K;
+
+ for (uint64_t stripe = 0; stripe != stripeCount; ++stripe) {
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ std::ostringstream os;
+ os << (i % dictionarySize);
+ memcpy(dataBuffer + offset, os.str().c_str(), os.str().size());
+
+ strBatch->data[i] = dataBuffer + offset;
+ strBatch->length[i] = static_cast<int64_t>(os.str().size());
+
+ offset += os.str().size();
+ }
+
+ structBatch->numElements = rowCount;
+ strBatch->numElements = rowCount;
+
+ writer->add(*batch);
+ }
+
+ writer->close();
+
+ std::unique_ptr<InputStream> inStream(new MemoryInputStream(
+ memStream.getData(), memStream.getLength()));
+ std::unique_ptr<Reader> reader = createReader(pool, std::move(inStream));
+ std::unique_ptr<RowReader> rowReader = createRowReader(reader.get());
+
+ // make sure there are multiple stripes
+ EXPECT_EQ(rowCount * stripeCount, reader->getNumberOfRows());
+ EXPECT_EQ(stripeCount, reader->getNumberOfStripes());
+
+ // test reading sequentially for data correctness
+ batch = rowReader->createRowBatch(rowCount);
+ for (uint64_t stripe = 0; stripe != stripeCount; ++stripe) {
+ EXPECT_EQ(true, rowReader->next(*batch));
+
+ for (uint64_t i = 0; i < rowCount; ++i) {
+ structBatch = dynamic_cast<StructVectorBatch *>(batch.get());
+ strBatch = dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+ std::string str(strBatch->data[i],
+ static_cast<size_t>(strBatch->length[i]));
+ EXPECT_EQ(i % dictionarySize,
static_cast<uint64_t>(atoi(str.c_str())));
+ }
+ }
+ EXPECT_EQ(false, rowReader->next(*batch));
+
+ // test seeking to check positions
+ batch = rowReader->createRowBatch(1);
+
+ for (uint64_t stripe = 0; stripe != stripeCount; ++stripe) {
+ for (uint64_t i = 0; i < rowCount; i += 10000 / 2) {
+ rowReader->seekToRow(stripe * rowCount + i);
+ EXPECT_EQ(true, rowReader->next(*batch));
+
+ structBatch = dynamic_cast<StructVectorBatch *>(batch.get());
+ strBatch = dynamic_cast<StringVectorBatch *>(structBatch->fields[0]);
+ std::string str(strBatch->data[0],
+ static_cast<size_t>(strBatch->length[0]));
+ EXPECT_EQ(i % dictionarySize,
static_cast<uint64_t>(atoi(str.c_str())));
+ }
+ }
+ }
+
+ // test dictionary encoding with index disabled
+ // the decision of using dictionary if made at the end of 1st stripe
+ TEST(DictionaryEncoding, writeStringDictionaryEncodingWithoutIndex) {
+ testStringDictionary(false, DICT_THRESHOLD);
+ testStringDictionary(false, FALLBACK_THRESHOLD);
+ }
+
+ // test dictionary encoding with index enabled
+ // the decision of using dictionary if made at the end of 1st row group
+ TEST(DictionaryEncoding, writeStringDictionaryEncodingWithIndex) {
+ testStringDictionary(true, DICT_THRESHOLD);
+ testStringDictionary(true, FALLBACK_THRESHOLD);
+ }
+
+ TEST(DictionaryEncoding, writeVarcharDictionaryEncodingWithoutIndex) {
+ testVarcharDictionary(false, DICT_THRESHOLD);
+ testVarcharDictionary(false, FALLBACK_THRESHOLD);
+ }
+
+ TEST(DictionaryEncoding, writeVarcharDictionaryEncodingWithIndex) {
+ testVarcharDictionary(true, DICT_THRESHOLD);
+ testVarcharDictionary(true, FALLBACK_THRESHOLD);
+ }
+
+ TEST(DictionaryEncoding, writeCharDictionaryEncodingWithoutIndex) {
+ testCharDictionary(false, DICT_THRESHOLD);
+ testCharDictionary(false, FALLBACK_THRESHOLD);
+ }
+
+ TEST(DictionaryEncoding, writeCharDictionaryEncodingWithIndex) {
+ testCharDictionary(true, DICT_THRESHOLD);
+ testCharDictionary(true, FALLBACK_THRESHOLD);
+ }
+
+ TEST(DictionaryEncoding, stringDictionaryWithNullWithIndex) {
+ testStringDictionaryWithNull(DICT_THRESHOLD, true);
+ testStringDictionaryWithNull(FALLBACK_THRESHOLD, true);
+ }
+
+ TEST(DictionaryEncoding, stringDictionaryWithNullWithoutIndex) {
+ testStringDictionaryWithNull(DICT_THRESHOLD, false);
+ testStringDictionaryWithNull(FALLBACK_THRESHOLD, false);
+ }
+
+ TEST(DictionaryEncoding, multipleStripesWithIndex) {
+ testDictionaryMultipleStripes(DICT_THRESHOLD, true);
+ testDictionaryMultipleStripes(FALLBACK_THRESHOLD, true);
+ }
+
+ TEST(DictionaryEncoding, multipleStripesWithoutIndex) {
+ testDictionaryMultipleStripes(DICT_THRESHOLD, false);
+ testDictionaryMultipleStripes(FALLBACK_THRESHOLD, false);
+ }
+}