-
Notifications
You must be signed in to change notification settings - Fork 409
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FLASH-480] Fix bug: rename column (#227)
* fix bug: rename column * log info instead of error when column rename is detected
- Loading branch information
1 parent
96b4f0e
commit 1b40aba
Showing
5 changed files
with
244 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
#pragma once | ||
|
||
#include <map> | ||
#include <set> | ||
#include <utility> | ||
|
||
#include <Core/Types.h> | ||
#include <Storages/Transaction/Types.h> | ||
|
||
/// === Some Private struct / method for SchemaBuilder | ||
/// Notice that this file should only included by SchemaBuilder.cpp and unittest for this file. | ||
|
||
namespace DB | ||
{ | ||
|
||
constexpr char tmpNamePrefix[] = "_tiflash_tmp_"; | ||
|
||
struct TmpTableNameGenerator | ||
{ | ||
using TableName = std::pair<String, String>; | ||
TableName operator()(const TableName & name) { return std::make_pair(name.first, String(tmpNamePrefix) + name.second); } | ||
}; | ||
|
||
struct TmpColNameGenerator | ||
{ | ||
String operator()(const String & name) { return String(tmpNamePrefix) + name; } | ||
}; | ||
|
||
|
||
struct ColumnNameWithID | ||
{ | ||
String name; | ||
ColumnID id; | ||
|
||
explicit ColumnNameWithID(String name_ = "", ColumnID id_ = 0) : name(std::move(name_)), id(id_) {} | ||
|
||
bool operator==(const ColumnNameWithID & rhs) const { return name == rhs.name; } | ||
|
||
bool operator<(const ColumnNameWithID & rhs) const { return name < rhs.name; } | ||
}; | ||
|
||
struct TmpColNameWithIDGenerator | ||
{ | ||
ColumnNameWithID operator()(const ColumnNameWithID & name_with_id) | ||
{ | ||
return ColumnNameWithID{String(tmpNamePrefix) + name_with_id.name, name_with_id.id}; | ||
} | ||
}; | ||
|
||
|
||
// CyclicRenameResolver resolves cyclic table rename and column rename. | ||
// TmpNameGenerator rename current name to a temp name that will not conflict with other names. | ||
template <typename Name_, typename TmpNameGenerator> | ||
struct CyclicRenameResolver | ||
{ | ||
using Name = Name_; | ||
using NamePair = std::pair<Name, Name>; | ||
using NamePairs = std::vector<NamePair>; | ||
using NameSet = std::set<Name>; | ||
using NameMap = std::map<Name, Name>; | ||
|
||
// visited records which name has been processed. | ||
NameSet visited; | ||
TmpNameGenerator name_gen; | ||
|
||
// We will not ensure correctness if we call it multiple times, so we make it a rvalue call. | ||
NamePairs resolve(NameMap && rename_map) && | ||
{ | ||
NamePairs result; | ||
for (auto it = rename_map.begin(); it != rename_map.end(); /* */) | ||
{ | ||
if (!visited.count(it->first)) | ||
{ | ||
resolveImpl(rename_map, it, result); | ||
} | ||
// remove dependency of `it` since we have already done rename | ||
it = rename_map.erase(it); | ||
} | ||
return result; | ||
} | ||
|
||
private: | ||
NamePair resolveImpl(NameMap & rename_map, typename NameMap::iterator & it, NamePairs & result) | ||
{ | ||
Name origin_name = it->first; | ||
Name target_name = it->second; | ||
visited.insert(it->first); | ||
auto next_it = rename_map.find(target_name); | ||
if (next_it == rename_map.end()) | ||
{ | ||
// The target name does not exist, so we can rename it directly. | ||
result.push_back(NamePair(origin_name, target_name)); | ||
return NamePair(); | ||
} | ||
else if (visited.find(target_name) != visited.end()) | ||
{ | ||
// The target name is visited, so this is a cyclic rename. | ||
auto tmp_name = name_gen(target_name); | ||
result.push_back(NamePair(target_name, tmp_name)); | ||
result.push_back(NamePair(origin_name, target_name)); | ||
return NamePair(target_name, tmp_name); | ||
} | ||
else | ||
{ | ||
// The target name is in rename map, so we continue to resolve it. | ||
auto pair = resolveImpl(rename_map, next_it, result); | ||
if (pair.first == origin_name) | ||
{ | ||
origin_name = pair.second; | ||
} | ||
result.push_back(NamePair(origin_name, target_name)); | ||
return pair; | ||
} | ||
} | ||
}; | ||
|
||
|
||
} // namespace DB |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
dbms/src/Storages/Transaction/tests/gtest_rename_resolver.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#include <test_utils/TiflashTestBasic.h> | ||
|
||
#include <Storages/Transaction/SchemaBuilder-internal.h> | ||
#include <Storages/Transaction/SchemaBuilder.h> | ||
|
||
namespace DB::tests | ||
{ | ||
|
||
TEST(CyclicRenameResolver_test, resolve_normal) | ||
{ | ||
using Resolver = CyclicRenameResolver<String, TmpColNameGenerator>; | ||
std::map<String, String> rename_map; | ||
rename_map["a"] = "aa"; | ||
rename_map["b"] = "bb"; | ||
|
||
typename Resolver::NamePairs rename_result = Resolver().resolve(std::move(rename_map)); | ||
|
||
ASSERT_EQ(rename_result.size(), 2UL); | ||
// a -> aa | ||
ASSERT_EQ(rename_result[0].first, "a"); | ||
ASSERT_EQ(rename_result[0].second, "aa"); | ||
// b -> bb | ||
ASSERT_EQ(rename_result[1].first, "b"); | ||
ASSERT_EQ(rename_result[1].second, "bb"); | ||
} | ||
|
||
TEST(CyclicRenameResolver_test, resolve_linked) | ||
{ | ||
using Resolver = CyclicRenameResolver<String, TmpColNameGenerator>; | ||
std::map<String, String> rename_map; | ||
rename_map["a"] = "c"; | ||
rename_map["b"] = "a"; | ||
|
||
typename Resolver::NamePairs rename_result = Resolver().resolve(std::move(rename_map)); | ||
|
||
ASSERT_EQ(rename_result.size(), 2UL); | ||
// a -> c | ||
ASSERT_EQ(rename_result[0].first, "a"); | ||
ASSERT_EQ(rename_result[0].second, "c"); | ||
// b -> a | ||
ASSERT_EQ(rename_result[1].first, "b"); | ||
ASSERT_EQ(rename_result[1].second, "a"); | ||
} | ||
|
||
TEST(CyclicRenameResolver_test, resolve_simple_cycle) | ||
{ | ||
using Resolver = CyclicRenameResolver<String, TmpColNameGenerator>; | ||
std::map<String, String> rename_map; | ||
rename_map["a"] = "b"; | ||
rename_map["b"] = "a"; | ||
|
||
typename Resolver::NamePairs rename_result = Resolver().resolve(std::move(rename_map)); | ||
|
||
TmpColNameGenerator generator; | ||
|
||
ASSERT_EQ(rename_result.size(), 3UL); | ||
// a -> tmp_a | ||
ASSERT_EQ(rename_result[0].first, "a"); | ||
ASSERT_EQ(rename_result[0].second, generator("a")); | ||
// b -> a | ||
ASSERT_EQ(rename_result[1].first, "b"); | ||
ASSERT_EQ(rename_result[1].second, "a"); | ||
// tmp_a -> b | ||
ASSERT_EQ(rename_result[2].first, generator("a")); | ||
ASSERT_EQ(rename_result[2].second, "b"); | ||
} | ||
|
||
TEST(CyclicRenameResolver_test, resolve_id_simple_cycle) | ||
{ | ||
using Resolver = CyclicRenameResolver<ColumnNameWithID, TmpColNameWithIDGenerator>; | ||
std::map<ColumnNameWithID, ColumnNameWithID> rename_map; | ||
rename_map[ColumnNameWithID{"a", 1}] = ColumnNameWithID{"b", 1}; | ||
rename_map[ColumnNameWithID{"b", 2}] = ColumnNameWithID{"a", 2}; | ||
|
||
typename Resolver::NamePairs rename_result = Resolver().resolve(std::move(rename_map)); | ||
|
||
TmpColNameWithIDGenerator generator; | ||
|
||
ASSERT_EQ(rename_result.size(), 3UL); | ||
// a -> tmp_a | ||
ASSERT_EQ(rename_result[0].first, ColumnNameWithID("a", 1L)); | ||
ASSERT_EQ(rename_result[0].second, generator(ColumnNameWithID{"a", 1})); | ||
// b -> a | ||
ASSERT_EQ(rename_result[1].first, ColumnNameWithID("b", 2L)); | ||
ASSERT_EQ(rename_result[1].second, ColumnNameWithID("a", 2L)); | ||
// tmp_a -> b | ||
ASSERT_EQ(rename_result[2].first, generator(ColumnNameWithID{"a", 1})); | ||
ASSERT_EQ(rename_result[2].second, ColumnNameWithID("b", 1)); | ||
} | ||
|
||
} // namespace DB |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters