-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement set based aod verifier, support aod mining in fastod #468
base: main
Are you sure you want to change the base?
Conversation
d7ece66
to
7794d3a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First three commits:
namespace config { | ||
|
||
ColumnIndexOption::ColumnIndexOption( | ||
std::string_view name, std::string_view description, | ||
typename Option<config::IndexType>::DefaultFunc get_default_value) | ||
: common_option_(name, description, std::move(get_default_value)) {} | ||
|
||
std::string_view ColumnIndexOption::GetName() const { | ||
return common_option_.GetName(); | ||
} | ||
|
||
Option<config::IndexType> ColumnIndexOption::operator()( | ||
config::IndexType* value_ptr, std::function<config::IndexType()> get_col_count, | ||
typename Option<config::IndexType>::ValueCheckFunc value_check_func) const { | ||
Option<config::IndexType> option = common_option_(value_ptr); | ||
option.SetValueCheck( | ||
[get_col_count = std::move(get_col_count), | ||
value_check_func = std::move(value_check_func)](config::IndexType const index) { | ||
config::ValidateIndex(index, get_col_count()); | ||
if (value_check_func) { | ||
value_check_func(index); | ||
} | ||
}); | ||
return option; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're already in config
namespace so things like config::IndexType
or config::ValidateIndex
are redundant, just IndexType
/ValidateIndex
is enough
|
||
ColumnIndexOption::ColumnIndexOption( | ||
std::string_view name, std::string_view description, | ||
typename Option<config::IndexType>::DefaultFunc get_default_value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the point of typename
here? It's already a fully resolved type due to <config::IndexType>
|
||
Option<config::IndexType> ColumnIndexOption::operator()( | ||
config::IndexType* value_ptr, std::function<config::IndexType()> get_col_count, | ||
typename Option<config::IndexType>::ValueCheckFunc value_check_func) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same, typename
not needed
namespace config { | ||
|
||
// Simplifies creating of options that represent a single index of the attribute in the table | ||
class ColumnIndexOption { | ||
public: | ||
ColumnIndexOption(std::string_view name, std::string_view description, | ||
typename Option<config::IndexType>::DefaultFunc get_default_value = nullptr); | ||
|
||
[[nodiscard]] std::string_view GetName() const; | ||
// Creates an option which performs a check that index is not greater than column count | ||
[[nodiscard]] Option<config::IndexType> operator()( | ||
config::IndexType* value_ptr, std::function<config::IndexType()> get_col_count, | ||
typename Option<config::IndexType>::ValueCheckFunc value_check_func = nullptr) const; | ||
|
||
private: | ||
CommonOption<config::IndexType> const common_option_; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same with redundant config::
and typename Option<..>
typename Option<config::IndicesType>::DefaultFunc calculate_default, | ||
bool allow_empty) | ||
: common_option_(name, description, std::move(calculate_default), NormalizeIndices, nullptr), | ||
allow_empty_(allow_empty) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
redundant typename
and config
if (!indices.empty()) { | ||
config::ValidateIndex(indices.back(), get_col_count()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
redundant config
if (is_stripped_partition_) { | ||
values.reserve(group_end - group_begin); | ||
|
||
for (size_t i = group_begin; i < group_end; i) { | ||
size_t const index = (*sp_indexes_)[i]; | ||
|
||
values.emplace_back(data_->GetValue(index, left), data_->GetValue(index, right)); | ||
} | ||
} else { | ||
for (size_t i = group_begin; i < group_end; i) { | ||
DataFrame::Range const range = (*rb_indexes_)[i]; | ||
|
||
for (size_t j = range.first; j <= range.second; j) { | ||
values.emplace_back(data_->GetValue(j, left), data_->GetValue(j, right)); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDK if I should comment on this code since you've just moved it to another place, but the else
branch does not have the values.reserve
size_t prev_group_max_index = 0; | ||
size_t current_group_max_index = 0; | ||
bool is_first_group = true; | ||
|
||
for (size_t i = 0; i < values.size(); i ) { | ||
auto const& [first, second] = values[i]; | ||
|
||
if (i != 0 && values[i - 1].first != first) { | ||
is_first_group = false; | ||
prev_group_max_index = current_group_max_index; | ||
current_group_max_index = i; | ||
} else if (values[current_group_max_index].second <= second) { | ||
current_group_max_index = i; | ||
} | ||
|
||
if (!is_first_group && values[prev_group_max_index].second > second) { | ||
return true; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop constantly checking for i != 0
.
Moreover, the first iteration of this loop will always look like
if (values[0].second <= values[0].second) //current_group_max_index is zero
current_group_max_index = 0;
since i == 0
and !is_first_group
is false.
So we can just start from i = 1
and remove that check
if constexpr (Ascending) { | ||
std::sort(values.begin(), values.end(), | ||
[](auto const& p1, auto const& p2) { return p1.first < p2.first; }); | ||
} else { | ||
std::sort(values.begin(), values.end(), | ||
[](auto const& p1, auto const& p2) { return p2.first < p1.first; }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lambdas looks identical, I would do
std::sort(values.begin(), values.end(),
[](auto const& p1, auto const& p2) {
return Ascending ? p1.first < p2.first : p1.first > p2.first;
});
Though I'm not sure whether we can use constexpr values with ternary operator, but I think that with lambdas we can
if constexpr (Ascending) { | ||
if constexpr (Ordering == od::Ordering::ascending) { | ||
cs_asc_[key].emplace(value); | ||
} else { | ||
cs_desc_[key].emplace(value); | ||
} | ||
} | ||
|
||
template <bool Ascending> | ||
template <od::Ordering Ordering> | ||
void CSPut(AttributeSet const& key, AttributePair&& value) { | ||
if constexpr (Ascending) { | ||
if constexpr (Ordering == od::Ordering::ascending) { | ||
cs_asc_[key].emplace(std::move(value)); | ||
} else { | ||
cs_desc_[key].emplace(std::move(value)); | ||
} | ||
} | ||
|
||
template <bool Ascending> | ||
template <od::Ordering Ordering> | ||
std::unordered_set<AttributePair>& CSGet(AttributeSet const& key) { | ||
if constexpr (Ascending) { | ||
if constexpr (Ordering == od::Ordering::ascending) { | ||
return cs_asc_[key]; | ||
} else { | ||
return cs_desc_[key]; | ||
} | ||
} | ||
|
||
template <bool Ascending> | ||
void AddToResult(fastod::CanonicalOD<Ascending>&& od) { | ||
if constexpr (Ascending) { | ||
template <od::Ordering Ordering> | ||
void AddToResult(fastod::CanonicalOD<Ordering>&& od) { | ||
if constexpr (Ordering == od::Ordering::ascending) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO it is better to define
constexpr bool is_ascending(od::Ordering order) {
return order == od::Ordering::ascending;
}
and then use if constexpr (is_ascending(Ordering))
Or maybe even
template <od::Ordering Ordering>
concept Ascending = (Ordering == od::Ordering::ascending);
template <od::Ordering Ordering>
concept Descending = (Ordering == od::Ordering::descending);
and do
if constexpr (Ascending<Ordering>) {
cs_asc_[key].emplace(value);
} else {
cs_desc_[key].emplace(value);
}
bool operator==(CanonicalOD<od::Ordering::ascending> const& x, | ||
CanonicalOD<od::Ordering::ascending> const& y) { | ||
return x.context_ == y.context_ && x.ap_ == y.ap_; | ||
} | ||
|
||
bool operator!=(CanonicalOD<true> const& x, CanonicalOD<true> const& y) { | ||
bool operator!=(CanonicalOD<od::Ordering::ascending> const& x, | ||
CanonicalOD<od::Ordering::ascending> const& y) { | ||
return !(x == y); | ||
} | ||
|
||
bool operator<(CanonicalOD<true> const& x, CanonicalOD<true> const& y) { | ||
bool operator<(CanonicalOD<od::Ordering::ascending> const& x, | ||
CanonicalOD<od::Ordering::ascending> const& y) { | ||
if (x.ap_ != y.ap_) { | ||
return x.ap_ < y.ap_; | ||
} | ||
|
||
return x.context_ < y.context_; | ||
} | ||
|
||
bool operator==(CanonicalOD<false> const& x, CanonicalOD<false> const& y) { | ||
bool operator==(CanonicalOD<od::Ordering::descending> const& x, | ||
CanonicalOD<od::Ordering::descending> const& y) { | ||
return x.context_ == y.context_ && x.ap_ == y.ap_; | ||
} | ||
|
||
bool operator!=(CanonicalOD<false> const& x, CanonicalOD<false> const& y) { | ||
bool operator!=(CanonicalOD<od::Ordering::descending> const& x, | ||
CanonicalOD<od::Ordering::descending> const& y) { | ||
return !(x == y); | ||
} | ||
|
||
bool operator<(CanonicalOD<false> const& x, CanonicalOD<false> const& y) { | ||
bool operator<(CanonicalOD<od::Ordering::descending> const& x, | ||
CanonicalOD<od::Ordering::descending> const& y) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use AscCanonicalOD
and DescCanonicalOD
here
using Comp = std::conditional_t<Ordering == od::Ordering::ascending, std::less<int>, | ||
std::greater<int>>; | ||
std::sort(values.begin(), values.end(), [](auto const& p1, auto const& p2) { | ||
return Comp()(p1.left_value, p2.left_value); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest
template <Ordering Ordering, std::totally_ordered T>
bool Comp(T const& lhs, T const& rhs) {
if constexpr (Ordering == Ordering::ascending) {
return std::less<>{}(lhs, rhs);
} else {
return std::greater<>{}(lhs, rhs);
}
}
And then we won't need to define Comp each time (I saw that the same Comp gets defined second time in Implement aod verifier and cover it with tests
)
std::sort(values.begin(), values.end(), [](auto const& p1, auto const& p2) {
return od::Comp<Ordering>(p1.left_value, p2.left_value);
});
inline AttributeSet CreateAttributeSet(std::ranges::input_range auto const& attributes, | ||
model::ColumnIndex size) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe std::span<const model::ColumnIndex>
?
We're only need a view of a sequence and this way we're explicitly saying that we want model::ColumnIndex
objects there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Store DataFrame in ComplexStrippedPartition as raw pointer
:
A commit description explaining why will come in handy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, some commit description will be nice
namespace util { | ||
|
||
template <typename T> | ||
concept Printable = requires(std::stringstream& sstream, T& t) { sstream << t; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
concept Printable = requires(std::stringstream& sstream, T& t) { sstream << t; }; | |
concept Printable = requires(std::stringstream& sstream, const T& t) { sstream << t; }; |
will allow more types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also maybe just std::ostream
instead of stringstream
? It seems like we can safely accept any output stream here
template <std::ranges::input_range Range> | ||
requires Printable<std::ranges::range_value_t<Range>> | ||
std::string RangeToString(Range const& range) { | ||
std::stringstream sstream; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::stringstream sstream; | |
std::ostringstream oss; |
since we're only performing output operations
for (auto pt{range.begin()}; pt != range.end(); pt) { | ||
if (pt != range.begin()) { | ||
sstream << ", "; | ||
} | ||
sstream << *pt; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use range-based for for example like so
for (auto pt{range.begin()}; pt != range.end(); pt) { | |
if (pt != range.begin()) { | |
sstream << ", "; | |
} | |
sstream << *pt; | |
} | |
bool first = true; | |
for (const auto& elem : range) { | |
if (!first) { | |
oss << ", "; | |
} else { | |
first = false; | |
} | |
oss << elem; | |
} |
using Comp = std::conditional_t<Ordering == od::Ordering::ascending, std::less<int>, | ||
std::greater<int>>; | ||
std::sort(values.begin(), values.end(), [](auto const& t1, auto const& t2) { | ||
return Comp()(t1.left_value, t2.left_value) || | ||
(t1.left_value == t2.left_value && t1.right_value < t2.right_value); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Comp
defined as a result of fixing previous issue
RemovalSet UnionRemovalSets(RemovalSetAsVec const& vec1, RemovalSetAsVec const& vec2) { | ||
RemovalSet removal_set{vec1.begin(), vec1.end()}; | ||
removal_set.insert(vec2.begin(), vec2.end()); | ||
return removal_set; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this function gets used? It seems like we don't need it
Tuple const& value = values[i]; | ||
auto it = std::upper_bound(subseq_len_to_last.begin(), subseq_len_to_last.end(), | ||
value.right_value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we use value
only as value.right_value
, so let's just define it as int value_right = values[i].right_value;
std::vector<int> subseq_len_to_last(values.size() 1, std::numeric_limits<int>::max()); | ||
subseq_len_to_last.front() = std::numeric_limits<int>::min(); | ||
std::vector<size_t> predecessors(values.size(), std::numeric_limits<size_t>::max()); | ||
std::vector<size_t> subseq_len_to_index(values.size() 1, | ||
std::numeric_limits<size_t>::max()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would extract values.size()
to like and n
variable since it gets used a lot
std::unordered_set<model::TupleIndex> longest_subseq_elements; | ||
for (size_t i = subseq_len_to_index[longest_subseq]; | ||
i != std::numeric_limits<model::TupleIndex>::max(); i = predecessors[i]) { | ||
longest_subseq_elements.insert(values[i].tuple_index); | ||
} | ||
|
||
for (auto const& [index, left_value, right_value] : values) { | ||
if (!longest_subseq_elements.contains(index)) { | ||
removal_set.push_back(index); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is longest_subseq_elements
is an unordered_set? Can't we just define like vector<bool>
of boost::dynamic_bitset
with values.size() = n
size and do a faster lookup this way?
Well, maybe not if n
is much more that the size of longest subseq elements..
Like
std::unordered_set<model::TupleIndex> longest_subseq_elements; | |
for (size_t i = subseq_len_to_index[longest_subseq]; | |
i != std::numeric_limits<model::TupleIndex>::max(); i = predecessors[i]) { | |
longest_subseq_elements.insert(values[i].tuple_index); | |
} | |
for (auto const& [index, left_value, right_value] : values) { | |
if (!longest_subseq_elements.contains(index)) { | |
removal_set.push_back(index); | |
} | |
} | |
for (size_t i = subseq_len_to_index[longest_subseq]; | |
i != std::numeric_limits<size_t>::max(); i = predecessors[i]) { | |
is_in_lis[i] = true; | |
} | |
for (size_t i = 0; i < n; i) { | |
if (!is_in_lis[i]) { | |
removal_set.push_back(values[i].tuple_index); | |
} | |
} |
od::RemovalSetAsVec ComplexStrippedPartition::CommonSplitRemovalSet( | ||
model::ColumnIndex right) const { | ||
od::RemovalSetAsVec removal_set; | ||
|
||
for (size_t begin_pointer = 0; begin_pointer < sp_begins_->size() - 1; begin_pointer ) { | ||
size_t const group_begin = (*sp_begins_)[begin_pointer]; | ||
size_t const group_end = (*sp_begins_)[begin_pointer 1]; | ||
|
||
std::vector<ValueAndIndex> values; | ||
for (size_t i = group_begin; i < group_end; i ) { | ||
model::TupleIndex const tuple_index = (*sp_indexes_)[i]; | ||
values.emplace_back(data_->GetValue(tuple_index, right), tuple_index); | ||
} | ||
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values); | ||
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end()); | ||
} | ||
|
||
return removal_set; | ||
} | ||
|
||
od::RemovalSetAsVec ComplexStrippedPartition::RangeBasedSplitRemovalSet( | ||
model::ColumnIndex right) const { | ||
od::RemovalSetAsVec removal_set; | ||
|
||
for (size_t begin_pointer = 0; begin_pointer < rb_begins_->size() - 1; begin_pointer) { | ||
size_t const group_begin = (*rb_begins_)[begin_pointer]; | ||
size_t const group_end = (*rb_begins_)[begin_pointer 1]; | ||
|
||
std::vector<ValueAndIndex> values; | ||
for (size_t i = group_begin; i < group_end; i) { | ||
DataFrame::Range const range = (*rb_indexes_)[i]; | ||
|
||
for (size_t j = range.first; j <= range.second; j) { | ||
values.emplace_back(data_->GetValue(j, right), j); | ||
} | ||
} | ||
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values); | ||
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, a lot of code duplication in these two methods. The lines:
od::RemovalSetAsVec removal_set;
for (size_t begin_pointer = 0; begin_pointer < sp_begins_->size() - 1; begin_pointer ) {
size_t const group_begin = (*sp_begins_)[begin_pointer];
size_t const group_end = (*sp_begins_)[begin_pointer 1];
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end());
are virtually the same in both cases.
I think we can create a helper function like this:
void ProcessGroups(const std::vector<size_t>& begins, std::function</*type*/> collect_values, od::RemovalSetAsVec& removal_set)
{
for (size_t begin_pointer = 0; begin_pointer < begins.size() - 1; begin_pointer) {
size_t const group_begin = begins[begin_pointer];
size_t const group_end = begins[begin_pointer 1];
std::vector<ValueAndIndex> values;
collect_values(group_begin, group_end, values);
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
removal_set.insert(
removal_set.end(),
std::make_move_iterator(cur_removal_set.begin()),
std::make_move_iterator(cur_removal_set.end())
);
}
}
And use it like so:
od::RemovalSetAsVec ComplexStrippedPartition::CommonSplitRemovalSet(model::ColumnIndex right) const {
od::RemovalSetAsVec removal_set;
ProcessGroups( *sp_begins_,
[this, right](size_t group_begin, size_t group_end, std::vector<ValueAndIndex>& values) {
values.reserve(group_end - group_begin);
for (size_t i = group_begin; i < group_end; i) {
model::TupleIndex const tuple_index = (*sp_indexes_)[i];
values.emplace_back(data_->GetValue(tuple_index, right), tuple_index);
}
},
removal_set
);
return removal_set;
}
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values); | ||
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need cur_removal_set
object anymore - might as well move elements to a removal_set:
removal_set.insert(
removal_set.end(),
std::make_move_iterator(cur_removal_set.begin()),
std::make_move_iterator(cur_removal_set.end())
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, nevermind, there are integers there
std::vector<ValueAndIndex> values; | ||
for (size_t i = group_begin; i < group_end; i ) { | ||
model::TupleIndex const tuple_index = (*sp_indexes_)[i]; | ||
values.emplace_back(data_->GetValue(tuple_index, right), tuple_index); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we know how much time we will call emplace_back
, so we should reserve memory to avoid reallocations:
values.reserve(group_end - group_begin);
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values); | ||
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same thing here with make_move_iterator
std::vector<ValueAndIndex> values; | ||
for (size_t i = group_begin; i < group_end; i) { | ||
DataFrame::Range const range = (*rb_indexes_)[i]; | ||
|
||
for (size_t j = range.first; j <= range.second; j) { | ||
values.emplace_back(data_->GetValue(j, right), j); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, we can avoid reallocations here, but this might increase the constant factor in time complexity. I'm not sure if it's worth it, as it depends on how frequently this function is called and the number of reallocations. However, we can do something like this:
size_t total_values = 0;
for (size_t i = group_begin; i < group_end; i) {
DataFrame::Range const& range = (*rb_indexes_)[i];
total_values = range.second - range.first 1;
}
values.reserve(total_values);
using namespace algos::od; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add using namespace config::names;
and do :%s/config::names:://g
::testing::Values(/* {}: Col5 -> Col1 */ | ||
SetBasedAodVerifierParams(kTestFD, /*oc_context=*/{}, | ||
/*oc_left=*/5, /*oc_right=*/1, | ||
/*ordering=*/Ordering::ascending, | ||
/*ofd_context=*/{5}, /*ofd_right=*/1, | ||
/*removal_set=*/{}), | ||
/* {}: Col5 -> Col2 */ | ||
SetBasedAodVerifierParams(kTestFD, /*oc_context=*/{}, | ||
/*oc_left=*/5, /*oc_right=*/2, | ||
/*ordering=*/Ordering::ascending, | ||
/*ofd_context=*/{5}, /*ofd_right=*/2, | ||
/*removal_set=*/{2, 5, 8}), | ||
/* {Col3}: Col4 -> Col1 */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk if this is possible, but can we add some descriptive names to individual tests in this test suite? Like make those comments {}: Col5 -> Col1
into names? If it's hard, then feel free to ignore
SetBasedAodVerifier::Error expected_error = | ||
static_cast<SetBasedAodVerifier::Error>(expected_removal_set_vec.size()) / | ||
verifier->GetTupleCount(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we're specifying type in static_cast
, we can just use auto expected_error
{config::names::kOcLeftOrdering, Ordering}}; | ||
algos::ConfigureFromMap(verifier, execute_params); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
separate with empty line
{config::names::kOFDRightIndex, ofd_right}}; | ||
algos::ConfigureFromMap(verifier, execute_params); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
separate with empty line
No description provided.