blob: ef8e9454122c875eb9d1c5a5910faee1ac537767 [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 "storage/tablet/tablet_reader.h"
#include <gen_cpp/olap_file.pb.h>
#include <gtest/gtest.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "storage/delete/delete_handler.h"
#include "storage/rowset/rowset_meta.h"
#include "storage/tablet/tablet_schema.h"
namespace doris {
class TabletReaderTest : public testing::Test {
protected:
static TabletSchemaSPtr create_schema(
const std::vector<std::pair<std::string, int32_t>>& name_and_uid) {
TabletSchemaPB schema_pb;
schema_pb.set_keys_type(KeysType::DUP_KEYS);
bool first = true;
for (const auto& [name, uid] : name_and_uid) {
auto* col = schema_pb.add_column();
col->set_unique_id(uid);
col->set_name(name);
col->set_type("INT");
col->set_is_key(first);
col->set_is_nullable(!first);
first = false;
}
auto schema = std::make_shared<TabletSchema>();
schema->init_from_pb(schema_pb);
return schema;
}
// Build a DeleteHandler initialized with a single delete-predicate rowset.
static void init_delete_handler(DeleteHandler& handler, const TabletSchemaSPtr& schema,
const DeletePredicatePB& delete_predicate) {
auto rs_meta = std::make_shared<RowsetMeta>();
rs_meta->set_tablet_schema(schema);
rs_meta->set_version(Version(2, 2));
rs_meta->set_delete_predicate(delete_predicate);
ASSERT_TRUE(handler.init(schema, {rs_meta}, /*version=*/100).ok());
}
};
// The delete columns (resolved by the delete handler to field ids) are mapped back to their
// column unique ids and stripped from all_access_paths; unrelated columns keep their paths.
TEST_F(TabletReaderTest, remove_delete_columns_from_access_paths) {
auto schema = create_schema({{"k1", 10}, {"k2", 11}, {"v1", 12}, {"v2", 13}, {"v4", 15}});
DeletePredicatePB delete_predicate;
auto* p1 = delete_predicate.add_sub_predicates_v2(); // k1 -> erase 10
p1->set_column_name("k1");
p1->set_column_unique_id(10);
p1->set_op("=");
p1->set_cond_value("1");
auto* p2 = delete_predicate.add_sub_predicates_v2(); // v1 -> erase 12
p2->set_column_name("v1");
p2->set_column_unique_id(12);
p2->set_op("=");
p2->set_cond_value("2");
auto* in1 = delete_predicate.add_in_predicates(); // k2 IN (...) -> erase 11
in1->set_column_name("k2");
in1->set_column_unique_id(11);
in1->set_is_not_in(false);
in1->add_values("3");
in1->add_values("4");
DeleteHandler handler;
init_delete_handler(handler, schema, delete_predicate);
std::map<int32_t, TColumnAccessPaths> access_paths;
for (int32_t uid : {10, 11, 12, 13, 15}) {
access_paths[uid] = TColumnAccessPaths {};
}
TabletReader::remove_delete_columns_from_access_paths(handler, *schema, access_paths);
EXPECT_EQ(size_t(2), access_paths.size());
EXPECT_EQ(size_t(1), access_paths.count(13)) << "non-delete column must keep its access path";
EXPECT_EQ(size_t(1), access_paths.count(15)) << "non-delete column must keep its access path";
for (int32_t uid : {10, 11, 12}) {
EXPECT_EQ(size_t(0), access_paths.count(uid)) << "delete column " << uid << " not erased";
}
}
// A delete condition whose column has no access path leaves the map untouched.
TEST_F(TabletReaderTest, remove_delete_columns_keeps_unrelated_paths) {
auto schema = create_schema({{"k1", 10}, {"v1", 12}});
DeletePredicatePB delete_predicate;
auto* p1 = delete_predicate.add_sub_predicates_v2();
p1->set_column_name("k1");
p1->set_column_unique_id(10);
p1->set_op("=");
p1->set_cond_value("1");
DeleteHandler handler;
init_delete_handler(handler, schema, delete_predicate);
std::map<int32_t, TColumnAccessPaths> access_paths;
access_paths[12] = TColumnAccessPaths {};
access_paths[15] = TColumnAccessPaths {};
TabletReader::remove_delete_columns_from_access_paths(handler, *schema, access_paths);
EXPECT_EQ(size_t(2), access_paths.size());
}
// Contract test for the binlog/snapshot incremental-read TSO range forwarding added to
// TabletReader::_capture_rs_readers (tablet_reader.cpp:184-186):
// _reader_context.start_tso = read_params.start_tso;
// _reader_context.end_tso = read_params.end_tso;
// Exercising _capture_rs_readers end to end would require a fully constructed Tablet + rowset
// readers (it unconditionally dereferences _tablet), which is far too heavy and brittle for a
// unit test. Instead we pin down the field contract on both sides: both must be
// std::optional<int64_t> defaulting to nullopt, and the forwarding must preserve the optional
// state (both set / only one / none). This guards against the fields being dropped or their
// type changed, which would silently break the forwarding.
TEST_F(TabletReaderTest, forward_tso_range_field_contract) {
// Both sides default to nullopt.
TabletReader::ReaderParams params;
EXPECT_FALSE(params.start_tso.has_value());
EXPECT_FALSE(params.end_tso.has_value());
RowsetReaderContext default_ctx;
EXPECT_FALSE(default_ctx.start_tso.has_value());
EXPECT_FALSE(default_ctx.end_tso.has_value());
// Equivalent of the forwarding assignments; the optional state must be preserved verbatim.
auto forward = [](const TabletReader::ReaderParams& p) {
RowsetReaderContext ctx;
ctx.start_tso = p.start_tso;
ctx.end_tso = p.end_tso;
return ctx;
};
// both set
params.start_tso = 100;
params.end_tso = 200;
RowsetReaderContext ctx = forward(params);
ASSERT_TRUE(ctx.start_tso.has_value());
ASSERT_TRUE(ctx.end_tso.has_value());
EXPECT_EQ(*ctx.start_tso, 100);
EXPECT_EQ(*ctx.end_tso, 200);
// only start set
params.start_tso = 100;
params.end_tso = std::nullopt;
ctx = forward(params);
ASSERT_TRUE(ctx.start_tso.has_value());
EXPECT_EQ(*ctx.start_tso, 100);
EXPECT_FALSE(ctx.end_tso.has_value());
// none set
params.start_tso = std::nullopt;
params.end_tso = std::nullopt;
ctx = forward(params);
EXPECT_FALSE(ctx.start_tso.has_value());
EXPECT_FALSE(ctx.end_tso.has_value());
}
} // namespace doris