blob: 77c14afb12f1ab4f7897c931986bcb6da0cfb4d4 [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 "format_v2/wal/wal_reader.h"
#include <gtest/gtest.h>
#include <chrono>
#include <filesystem>
#include <memory>
#include "agent/be_exec_version_manager.h"
#include "core/block/block.h"
#include "core/column/column_string.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_map.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_string.h"
#include "core/data_type/data_type_struct.h"
#include "io/fs/local_file_system.h"
#include "load/group_commit/wal/wal_file_reader.h"
#include "load/group_commit/wal/wal_writer.h"
namespace doris::format::wal {
namespace {
std::string temporary_wal_path() {
const auto root = std::filesystem::temp_directory_path() /
("doris-wal-v2-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
const auto path = root / "1" / "2" / "1_1_1_test";
std::filesystem::create_directories(path.parent_path());
return path.string();
}
PBlock serialize_block(const Block& block) {
PBlock pblock;
size_t uncompressed_bytes = 0;
size_t compressed_bytes = 0;
int64_t compress_time = 0;
EXPECT_TRUE(block.serialize(BeExecVersionManager::get_newest_version(), &pblock,
&uncompressed_bytes, &compressed_bytes, &compress_time,
segment_v2::CompressionTypePB::SNAPPY)
.ok());
return pblock;
}
} // namespace
TEST(WalReaderV2Test, ParseColumnIdsPreservesHeaderOrder) {
std::vector<int32_t> column_ids;
ASSERT_TRUE(parse_wal_column_ids("17,4,99", &column_ids).ok());
EXPECT_EQ(column_ids, (std::vector<int32_t> {17, 4, 99}));
}
TEST(WalReaderV2Test, ParseColumnIdsRejectsMalformedOrAmbiguousHeaders) {
std::vector<int32_t> column_ids;
EXPECT_FALSE(parse_wal_column_ids("", &column_ids).ok());
EXPECT_FALSE(parse_wal_column_ids("17,,99", &column_ids).ok());
EXPECT_FALSE(parse_wal_column_ids("17,nope,99", &column_ids).ok());
EXPECT_FALSE(parse_wal_column_ids("17,4,17", &column_ids).ok());
}
TEST(WalReaderV2Test, WriterBackedReaderPreservesNestedSchemaAndMovesFirstPayload) {
const auto wal_path = temporary_wal_path();
const auto int_type = make_nullable(std::make_shared<DataTypeInt32>());
const auto array_type = make_nullable(std::make_shared<DataTypeArray>(int_type));
const auto string_type = make_nullable(std::make_shared<DataTypeString>());
const auto map_type = make_nullable(std::make_shared<DataTypeMap>(string_type, int_type));
const auto struct_type = make_nullable(std::make_shared<DataTypeStruct>(
DataTypes {int_type, array_type}, Strings {"id", "nested_items"}));
Block source;
auto array_column = array_type->create_column();
array_column->insert_default();
source.insert({std::move(array_column), array_type, "items"});
auto string_column = string_type->create_column();
string_column->insert_data("value", 5);
source.insert({std::move(string_column), string_type, "renamed_later"});
auto map_column = map_type->create_column();
map_column->insert_default();
source.insert({std::move(map_column), map_type, "properties"});
auto struct_column = struct_type->create_column();
struct_column->insert_default();
source.insert({std::move(struct_column), struct_type, "record"});
auto pblock = serialize_block(source);
WalWriter writer(wal_path);
ASSERT_TRUE(writer.init(io::global_local_filesystem()).ok());
ASSERT_TRUE(writer.append_header("17,4,88,99").ok());
ASSERT_TRUE(writer.append_blocks({&pblock}).ok());
ASSERT_TRUE(writer.finalize().ok());
std::shared_ptr<io::FileSystemProperties> properties;
std::unique_ptr<io::FileDescription> description;
WalReader reader(properties, description, nullptr, nullptr, {});
reader._wal_reader = std::make_shared<WalFileReader>(wal_path);
ASSERT_TRUE(reader._wal_reader->init().ok());
std::string encoded_ids;
ASSERT_TRUE(reader._wal_reader->read_header(reader._version, encoded_ids).ok());
ASSERT_TRUE(parse_wal_column_ids(encoded_ids, &reader._column_ids).ok());
std::vector<ColumnDefinition> schema;
ASSERT_TRUE(reader.get_schema(&schema).ok());
ASSERT_EQ(schema.size(), 4);
ASSERT_EQ(schema[0].children.size(), 1);
EXPECT_TRUE(schema[0].children[0].type->is_nullable());
ASSERT_EQ(schema[2].children.size(), 2);
EXPECT_TRUE(schema[2].children[0].type->is_nullable());
EXPECT_TRUE(schema[2].children[1].type->is_nullable());
ASSERT_EQ(schema[3].children.size(), 2);
EXPECT_EQ(schema[3].children[0].name, "id");
EXPECT_EQ(schema[3].children[1].name, "nested_items");
ASSERT_EQ(schema[3].children[1].children.size(), 1);
auto request = std::make_shared<FileScanRequest>();
request->local_positions.emplace(LocalColumnId(1), LocalIndex(0));
request->local_positions.emplace(LocalColumnId(0), LocalIndex(1));
ASSERT_TRUE(reader.open(std::move(request)).ok());
Block output({
{string_type->create_column(), string_type, "renamed"},
{array_type->create_column(), array_type, "items"},
});
size_t rows = 0;
bool eof = false;
ASSERT_TRUE(reader.get_block(&output, &rows, &eof).ok());
EXPECT_EQ(rows, 1);
EXPECT_FALSE(eof);
EXPECT_EQ(reader._first_block.ByteSizeLong(), 0);
EXPECT_EQ(output.get_by_position(0).column->get_data_at(0).to_string(), "value");
ASSERT_TRUE(reader.close().ok());
std::filesystem::remove_all(
std::filesystem::path(wal_path).parent_path().parent_path().parent_path());
}
TEST(WalReaderV2Test, MaterializationTransfersColumnOwnership) {
std::shared_ptr<io::FileSystemProperties> properties;
std::unique_ptr<io::FileDescription> description;
WalReader reader(properties, description, nullptr, nullptr, {});
reader._request = std::make_shared<FileScanRequest>();
reader._request->local_positions.emplace(LocalColumnId(0), LocalIndex(0));
const auto type = std::make_shared<DataTypeString>();
auto source_column = ColumnString::create();
source_column->insert_data("payload", 7);
const auto* original = source_column.get();
Block source({{std::move(source_column), type, "value"}});
Block output({{type->create_column(), type, "value"}});
ASSERT_TRUE(reader._materialize_requested_columns(&source, &output).ok());
EXPECT_FALSE(static_cast<bool>(source.get_by_position(0).column));
EXPECT_EQ(output.get_by_position(0).column.get(), original);
}
} // namespace doris::format::wal