blob: 324ea2026a9e047a68a7048f67c17f0642bf109a [file]
// 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include <limits>
#include <optional>
#include <string>
#include "arrow/buffer.h"
#include "arrow/io/memory.h"
#include "parquet/file_reader.h"
#include "parquet/properties.h"
#include "parquet/test_util.h"
namespace parquet {
using schema::ColumnPath;
namespace test {
TEST(TestReaderProperties, Basics) {
ReaderProperties props;
ASSERT_EQ(props.buffer_size(), kDefaultBufferSize);
ASSERT_EQ(props.footer_read_size(), kDefaultFooterReadSize);
ASSERT_FALSE(props.is_buffered_stream_enabled());
ASSERT_FALSE(props.page_checksum_verification());
}
TEST(TestWriterProperties, Basics) {
std::shared_ptr<WriterProperties> props = WriterProperties::Builder().build();
ASSERT_EQ(kDefaultDataPageSize, props->data_pagesize());
ASSERT_EQ(DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT, props->dictionary_pagesize_limit());
ASSERT_EQ(ParquetVersion::PARQUET_2_6, props->version());
ASSERT_EQ(ParquetDataPageVersion::V1, props->data_page_version());
ASSERT_FALSE(props->page_checksum_enabled());
}
TEST(TestWriterProperties, DefaultCompression) {
std::shared_ptr<WriterProperties> props = WriterProperties::Builder().build();
ASSERT_EQ(props->compression(ColumnPath::FromDotString("any")),
Compression::UNCOMPRESSED);
ASSERT_EQ(props->compression_level(ColumnPath::FromDotString("any")),
::arrow::util::kUseDefaultCompressionLevel);
}
TEST(TestWriterProperties, AdvancedHandling) {
WriterProperties::Builder builder;
builder.compression("gzip", Compression::GZIP);
builder.compression("zstd", Compression::ZSTD);
builder.compression(Compression::SNAPPY);
builder.encoding(Encoding::DELTA_BINARY_PACKED);
builder.encoding("delta-length", Encoding::DELTA_LENGTH_BYTE_ARRAY);
builder.data_page_version(ParquetDataPageVersion::V2);
std::shared_ptr<WriterProperties> props = builder.build();
ASSERT_EQ(Compression::GZIP, props->compression(ColumnPath::FromDotString("gzip")));
ASSERT_EQ(Compression::ZSTD, props->compression(ColumnPath::FromDotString("zstd")));
ASSERT_EQ(Compression::SNAPPY,
props->compression(ColumnPath::FromDotString("delta-length")));
ASSERT_EQ(Encoding::DELTA_BINARY_PACKED,
props->encoding(ColumnPath::FromDotString("gzip")));
ASSERT_EQ(Encoding::DELTA_LENGTH_BYTE_ARRAY,
props->encoding(ColumnPath::FromDotString("delta-length")));
ASSERT_EQ(ParquetDataPageVersion::V2, props->data_page_version());
}
TEST(TestWriterProperties, SetCodecOptions) {
constexpr int ZSTD_c_windowLog = 101;
WriterProperties::Builder builder;
builder.compression("gzip", Compression::GZIP);
auto zstd_codec_options = std::make_shared<::arrow::util::ZstdCodecOptions>();
zstd_codec_options->compression_context_params = {{ZSTD_c_windowLog, 23}};
builder.codec_options("zstd", zstd_codec_options);
builder.compression("brotli", Compression::BROTLI);
auto gzip_codec_options = std::make_shared<::arrow::util::GZipCodecOptions>();
gzip_codec_options->compression_level = 5;
gzip_codec_options->window_bits = 12;
builder.codec_options("gzip", gzip_codec_options);
auto codec_options = std::make_shared<CodecOptions>();
builder.codec_options(codec_options);
auto brotli_codec_options = std::make_shared<::arrow::util::BrotliCodecOptions>();
brotli_codec_options->compression_level = 11;
brotli_codec_options->window_bits = 20;
builder.codec_options("brotli", brotli_codec_options);
std::shared_ptr<WriterProperties> props = builder.build();
ASSERT_EQ(5,
props->codec_options(ColumnPath::FromDotString("gzip"))->compression_level);
ASSERT_EQ(12, std::dynamic_pointer_cast<::arrow::util::GZipCodecOptions>(
props->codec_options(ColumnPath::FromDotString("gzip")))
->window_bits);
ASSERT_EQ(Codec::UseDefaultCompressionLevel(),
props->codec_options(ColumnPath::FromDotString("zstd"))->compression_level);
ASSERT_EQ(11,
props->codec_options(ColumnPath::FromDotString("brotli"))->compression_level);
ASSERT_EQ(20, std::dynamic_pointer_cast<::arrow::util::BrotliCodecOptions>(
props->codec_options(ColumnPath::FromDotString("brotli")))
->window_bits);
}
TEST(TestWriterProperties, BloomFilterNdvDefaults) {
BloomFilterOptions options;
ASSERT_FALSE(options.ndv.has_value());
ASSERT_TRUE(options.fold);
options.fpp = 0.05;
auto props = WriterProperties::Builder()
.max_row_group_length(12345)
->enable_bloom_filter("a", options)
->build();
const auto resolved = props->bloom_filter_options(ColumnPath::FromDotString("a"));
ASSERT_TRUE(resolved.has_value());
ASSERT_TRUE(resolved->ndv.has_value());
ASSERT_EQ(12345, resolved->ndv.value());
ASSERT_EQ(options.fpp, resolved->fpp);
ASSERT_TRUE(resolved->fold);
}
TEST(TestWriterProperties, BloomFilterExplicitNdv) {
BloomFilterOptions options{.ndv = 777, .fpp = 0.05};
auto props = WriterProperties::Builder()
.max_row_group_length(12345)
->enable_bloom_filter("a", options)
->build();
const auto resolved = props->bloom_filter_options(ColumnPath::FromDotString("a"));
ASSERT_TRUE(resolved.has_value());
ASSERT_TRUE(resolved->ndv.has_value());
ASSERT_EQ(777, resolved->ndv.value());
}
TEST(TestWriterProperties, BloomFilterNdvDefaultWithoutFolding) {
BloomFilterOptions options{.ndv = std::nullopt, .fpp = 0.05, .fold = false};
auto props = WriterProperties::Builder()
.max_row_group_length(12345)
->enable_bloom_filter("a", options)
->build();
const auto resolved = props->bloom_filter_options(ColumnPath::FromDotString("a"));
ASSERT_TRUE(resolved.has_value());
ASSERT_TRUE(resolved->ndv.has_value());
ASSERT_EQ(12345, resolved->ndv.value());
ASSERT_EQ(options.fpp, resolved->fpp);
ASSERT_FALSE(resolved->fold);
}
TEST(TestWriterProperties, BloomFilterExplicitNdvWithoutFolding) {
BloomFilterOptions options{.ndv = 777, .fpp = 0.05, .fold = false};
auto props = WriterProperties::Builder()
.max_row_group_length(12345)
->enable_bloom_filter("a", options)
->build();
const auto resolved = props->bloom_filter_options(ColumnPath::FromDotString("a"));
ASSERT_TRUE(resolved.has_value());
ASSERT_TRUE(resolved->ndv.has_value());
ASSERT_EQ(777, resolved->ndv.value());
ASSERT_FALSE(resolved->fold);
}
TEST(TestWriterProperties, BloomFilterRejectsNegativeNdv) {
BloomFilterOptions options{.ndv = -1, .fpp = 0.05};
EXPECT_THROW_THAT(
[&]() { WriterProperties::Builder().enable_bloom_filter("a", options)->build(); },
ParquetException,
::testing::Property(
&ParquetException::what,
::testing::HasSubstr("Bloom filter number of distinct values must be >= 0")));
}
TEST(TestWriterProperties, BloomFilterRejectsNanFpp) {
BloomFilterOptions options{.ndv = 777, .fpp = std::numeric_limits<double>::quiet_NaN()};
EXPECT_THROW_THAT(
[&]() { WriterProperties::Builder().enable_bloom_filter("a", options)->build(); },
ParquetException,
::testing::Property(
&ParquetException::what,
::testing::HasSubstr(
"Bloom filter false positive probability must be in (0.0, 1.0)")));
}
TEST(TestWriterProperties, BloomFilterAllowsZeroNdv) {
BloomFilterOptions options{.ndv = 0, .fpp = 0.05};
auto props = WriterProperties::Builder().enable_bloom_filter("a", options)->build();
const auto resolved = props->bloom_filter_options(ColumnPath::FromDotString("a"));
ASSERT_TRUE(resolved.has_value());
ASSERT_TRUE(resolved->ndv.has_value());
ASSERT_EQ(0, resolved->ndv.value());
}
TEST(TestWriterProperties, ContentDefinedChunkingSettings) {
WriterProperties::Builder builder;
std::shared_ptr<WriterProperties> props = builder.build();
ASSERT_FALSE(props->content_defined_chunking_enabled());
auto cdc_options = props->content_defined_chunking_options();
ASSERT_EQ(cdc_options.min_chunk_size, 256 * 1024);
ASSERT_EQ(cdc_options.max_chunk_size, 1024 * 1024);
ASSERT_EQ(cdc_options.norm_level, 0);
builder.enable_content_defined_chunking();
builder.content_defined_chunking_options(CdcOptions{512 * 1024, 2048 * 1024, 1});
props = builder.build();
ASSERT_TRUE(props->content_defined_chunking_enabled());
cdc_options = props->content_defined_chunking_options();
ASSERT_EQ(cdc_options.min_chunk_size, 512 * 1024);
ASSERT_EQ(cdc_options.max_chunk_size, 2048 * 1024);
ASSERT_EQ(cdc_options.norm_level, 1);
}
TEST(TestReaderProperties, GetStreamInsufficientData) {
// ARROW-6058
std::string data = "shorter than expected";
auto buf = std::make_shared<Buffer>(data);
auto reader = std::make_shared<::arrow::io::BufferReader>(buf);
ReaderProperties props;
try {
ARROW_UNUSED(props.GetStream(reader, 12, 15));
FAIL() << "No exception raised";
} catch (const ParquetException& e) {
std::string ex_what =
"IOError: File too short: expected to be able to read 15 bytes, got 9";
ASSERT_EQ(ex_what, e.what());
}
}
struct WriterPropertiesTestCase {
WriterPropertiesTestCase(std::shared_ptr<WriterProperties> props, std::string label)
: properties(std::move(props)), label(std::move(label)) {}
std::shared_ptr<WriterProperties> properties;
std::string label;
};
void PrintTo(const WriterPropertiesTestCase& p, std::ostream* os) { *os << p.label; }
class WriterPropertiesTest : public testing::TestWithParam<WriterPropertiesTestCase> {};
TEST_P(WriterPropertiesTest, RoundTripThroughBuilder) {
const std::shared_ptr<WriterProperties>& properties = GetParam().properties;
const std::vector<std::shared_ptr<ColumnPath>> columns{
ColumnPath::FromDotString("a"),
ColumnPath::FromDotString("b"),
};
const auto round_tripped = WriterProperties::Builder(*properties).build();
ASSERT_EQ(round_tripped->content_defined_chunking_enabled(),
properties->content_defined_chunking_enabled());
ASSERT_EQ(round_tripped->created_by(), properties->created_by());
ASSERT_EQ(round_tripped->data_pagesize(), properties->data_pagesize());
ASSERT_EQ(round_tripped->data_page_version(), properties->data_page_version());
ASSERT_EQ(round_tripped->dictionary_index_encoding(),
properties->dictionary_index_encoding());
ASSERT_EQ(round_tripped->dictionary_pagesize_limit(),
properties->dictionary_pagesize_limit());
ASSERT_EQ(round_tripped->file_encryption_properties(),
properties->file_encryption_properties());
ASSERT_EQ(round_tripped->max_rows_per_page(), properties->max_rows_per_page());
ASSERT_EQ(round_tripped->max_row_group_length(), properties->max_row_group_length());
ASSERT_EQ(round_tripped->memory_pool(), properties->memory_pool());
ASSERT_EQ(round_tripped->page_checksum_enabled(), properties->page_checksum_enabled());
ASSERT_EQ(round_tripped->size_statistics_level(), properties->size_statistics_level());
ASSERT_EQ(round_tripped->sorting_columns(), properties->sorting_columns());
ASSERT_EQ(round_tripped->store_decimal_as_integer(),
properties->store_decimal_as_integer());
ASSERT_EQ(round_tripped->write_batch_size(), properties->write_batch_size());
ASSERT_EQ(round_tripped->version(), properties->version());
const auto cdc_options = properties->content_defined_chunking_options();
const auto round_tripped_cdc_options =
round_tripped->content_defined_chunking_options();
ASSERT_EQ(round_tripped_cdc_options.min_chunk_size, cdc_options.min_chunk_size);
ASSERT_EQ(round_tripped_cdc_options.max_chunk_size, cdc_options.max_chunk_size);
ASSERT_EQ(round_tripped_cdc_options.norm_level, cdc_options.norm_level);
for (const auto& column : columns) {
const auto& column_properties = properties->column_properties(column);
const auto& round_tripped_col = round_tripped->column_properties(column);
ASSERT_EQ(round_tripped_col.compression(), column_properties.compression());
ASSERT_EQ(round_tripped_col.compression_level(),
column_properties.compression_level());
ASSERT_EQ(round_tripped_col.dictionary_enabled(),
column_properties.dictionary_enabled());
ASSERT_EQ(round_tripped_col.encoding(), column_properties.encoding());
ASSERT_EQ(round_tripped_col.max_statistics_size(),
column_properties.max_statistics_size());
ASSERT_EQ(round_tripped_col.page_index_enabled(),
column_properties.page_index_enabled());
ASSERT_EQ(round_tripped_col.statistics_enabled(),
column_properties.statistics_enabled());
const auto round_tripped_bloom_filter_options =
round_tripped_col.bloom_filter_options();
const auto bloom_filter_options = column_properties.bloom_filter_options();
ASSERT_EQ(round_tripped_bloom_filter_options.has_value(),
bloom_filter_options.has_value());
if (bloom_filter_options.has_value()) {
ASSERT_EQ(round_tripped_bloom_filter_options->ndv, bloom_filter_options->ndv);
ASSERT_EQ(round_tripped_bloom_filter_options->fpp, bloom_filter_options->fpp);
ASSERT_EQ(round_tripped_bloom_filter_options->fold, bloom_filter_options->fold);
}
}
}
std::vector<WriterPropertiesTestCase> writer_properties_test_cases() {
std::vector<WriterPropertiesTestCase> test_cases;
test_cases.emplace_back(default_writer_properties(), "default_properties");
{
WriterProperties::Builder builder;
const auto column_a = ColumnPath::FromDotString("a");
builder.created_by("parquet-cpp-properties-test");
builder.encoding(Encoding::BYTE_STREAM_SPLIT);
builder.compression(Compression::ZSTD);
builder.compression_level(2);
builder.disable_dictionary();
builder.disable_statistics();
builder.enable_content_defined_chunking();
builder.dictionary_pagesize_limit(DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT - 1);
builder.write_batch_size(DEFAULT_WRITE_BATCH_SIZE - 1);
builder.max_row_group_length(DEFAULT_MAX_ROW_GROUP_LENGTH - 1);
builder.data_pagesize(kDefaultDataPageSize - 1);
builder.max_rows_per_page(kDefaultMaxRowsPerPage - 1);
builder.data_page_version(ParquetDataPageVersion::V2);
builder.version(ParquetVersion::type::PARQUET_2_4);
builder.enable_store_decimal_as_integer();
builder.disable_write_page_index();
builder.set_size_statistics_level(SizeStatisticsLevel::ColumnChunk);
builder.set_sorting_columns(
std::vector<SortingColumn>{SortingColumn{1, true, false}});
test_cases.emplace_back(builder.build(), "override_defaults");
}
const auto column_a = ColumnPath::FromDotString("a");
{
WriterProperties::Builder builder;
builder.disable_dictionary();
builder.enable_dictionary(column_a);
test_cases.emplace_back(builder.build(), "dictionary_column_override");
}
{
WriterProperties::Builder builder;
builder.disable_statistics();
builder.enable_statistics(column_a);
test_cases.emplace_back(builder.build(), "statistics_column_override");
}
{
WriterProperties::Builder builder;
builder.compression(Compression::SNAPPY);
builder.compression(column_a, Compression::UNCOMPRESSED);
builder.compression_level(column_a, 2);
test_cases.emplace_back(builder.build(), "compression_column_override");
}
{
WriterProperties::Builder builder;
builder.encoding(Encoding::UNDEFINED);
builder.encoding(column_a, Encoding::BYTE_STREAM_SPLIT);
test_cases.emplace_back(builder.build(), "encoding_column_override");
}
{
WriterProperties::Builder builder;
builder.disable_write_page_index();
builder.enable_write_page_index(column_a);
test_cases.emplace_back(builder.build(), "page_index_column_override");
}
{
WriterProperties::Builder builder;
builder.max_row_group_length(12345);
builder.enable_bloom_filter(
column_a, BloomFilterOptions{.ndv = std::nullopt, .fpp = 0.05, .fold = false});
test_cases.emplace_back(builder.build(), "bloom_filter_column_override");
}
return test_cases;
}
INSTANTIATE_TEST_SUITE_P(WriterPropertiesTest, WriterPropertiesTest,
::testing::ValuesIn(writer_properties_test_cases()));
} // namespace test
} // namespace parquet