diff --git a/src/core/algorithms/dc/base_provider.h b/src/core/algorithms/dc/base_provider.h new file mode 100644 index 000000000..c5ce39e89 --- /dev/null +++ b/src/core/algorithms/dc/base_provider.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +namespace model { + +class PredicateBuilder; + +template +class BaseProvider { +protected: + static std::shared_ptr instance_; + + BaseProvider() = default; + + // FIXME: this is wrong, there should be one class representing the algorithm, + // which will create/clear singletons and call PredicateBuilder, PliShardBuiler, + // SomeOtherBuilder in order.. + friend PredicateBuilder; + + static void CreateInstance() { + if (instance_) { + throw std::runtime_error(Derived::ClassName() + + " instance is already created. " + "Perhaps PredicateBuilder has not been deleted"); + } + instance_ = std::shared_ptr(new Derived()); + } + + static void ClearInstance() { + if (instance_) { + Derived::Clear(); + instance_.reset(); + } + } + +public: + BaseProvider(BaseProvider const &) = delete; + BaseProvider &operator=(BaseProvider const &) = delete; + + static std::shared_ptr GetInstance() { + if (!instance_) { + throw std::runtime_error(Derived::ClassName() + + " instance is not created. " + "Perhaps PredicateBuilder has not been created"); + } + return instance_; + } +}; + +template +std::shared_ptr BaseProvider::instance_ = nullptr; + +} // namespace model diff --git a/src/core/algorithms/dc/column_operand.cpp b/src/core/algorithms/dc/column_operand.cpp new file mode 100644 index 000000000..723b28cf8 --- /dev/null +++ b/src/core/algorithms/dc/column_operand.cpp @@ -0,0 +1,9 @@ +#include "column_operand.h" + +namespace model { + +size_t hash_value(ColumnOperand const& col_op) noexcept { + return std::hash()(col_op); +} + +} // namespace model diff --git a/src/core/algorithms/dc/column_operand.h b/src/core/algorithms/dc/column_operand.h new file mode 100644 index 000000000..8d34e1493 --- /dev/null +++ b/src/core/algorithms/dc/column_operand.h @@ -0,0 +1,75 @@ +#pragma once + +#include + +#include "table/column.h" + +namespace model { + +/** + * @brief Represents a column operand within a predicate for FastADC. + * + * FastADC processes Denial Constraints (DCs) that involve comparisons between + * pairs of rows within a dataset. A typical DC example, derived from a Functional + * Dependency such as A -> B, is expressed as: `forall t, s in r, not (t.A = s.A and t.B != s.B)` + * This denotes that for any pair of rows in the relation, it should not be the case + * that while the values in column "A" are equal, the values in column "B" are unequal. + * + * A predicate in this context (e.g., t.A == s.A) comprises three elements to be fully + * represented: the column operand from the first tuple ("t.A"), the comparison operator + * ("="), and the column operand from the second tuple ("s.A"). The `ColumnOperand` class + * encapsulates the column operand part of a predicate, such as "t.A" or "s.A". + * + * The class distinguishes between operands derived from the first tuple (t) and those + * from the second tuple (s) using a boolean flag `tuple_`, where `true` indicates an + * operand from the first tuple (t), and `false` indicates an operand from the second + * tuple (s). + */ +class ColumnOperand { +private: + Column const* column_; + bool tuple_; + +public: + ColumnOperand(Column const* column, bool tuple) : column_(column), tuple_(tuple) {} + + bool operator==(ColumnOperand const& rhs) const { + return column_ == rhs.column_ && tuple_ == rhs.tuple_; + } + + Column const* GetColumn() const { + return column_; + } + + bool GetTuple() const { + return tuple_; + } + + // here TS means (t, s) + ColumnOperand GetInvTS() const { + return ColumnOperand(column_, !tuple_); + } + + std::string ToString() const { + return (tuple_ ? "t." : "s.") + column_->GetName(); + } +}; + +// NOLINTBEGIN(readability-identifier-naming) +size_t hash_value(model::ColumnOperand const& k) noexcept; +// NOLINTEND(readability-identifier-naming) + +} // namespace model + +namespace std { +template <> +struct hash { + size_t operator()(model::ColumnOperand const& k) const noexcept { + size_t seed = 0; + boost::hash_combine(seed, k.GetColumn()->GetIndex()); + boost::hash_combine(seed, k.GetTuple()); + return seed; + } +}; + +} // namespace std diff --git a/src/core/algorithms/dc/operator.cpp b/src/core/algorithms/dc/operator.cpp new file mode 100644 index 000000000..cf85ba211 --- /dev/null +++ b/src/core/algorithms/dc/operator.cpp @@ -0,0 +1,92 @@ +#include "algorithms/dc/operator.h" + +namespace model { + +bool Operator::Eval(std::byte const* v1, std::byte const* v2, Type const& type) const { + CompareResult cr = type.Compare(v1, v2); + + switch (op_) { + case OperatorType::kEqual: + return cr == CompareResult::kEqual; + case OperatorType::kUnequal: + return cr != CompareResult::kEqual; + case OperatorType::kGreater: + return cr == CompareResult::kGreater; + case OperatorType::kLess: + return cr == CompareResult::kLess; + case OperatorType::kGreaterEqual: + return cr == CompareResult::kGreater || cr == CompareResult::kEqual; + case OperatorType::kLessEqual: + return cr == CompareResult::kLess || cr == CompareResult::kEqual; + default: + return false; + } +} + +std::unordered_map const Operator::InitializeInverseMap() { + return {{OperatorType::kEqual, OperatorType::kUnequal}, + {OperatorType::kUnequal, OperatorType::kEqual}, + {OperatorType::kGreater, OperatorType::kLessEqual}, + {OperatorType::kLess, OperatorType::kGreaterEqual}, + {OperatorType::kGreaterEqual, OperatorType::kLess}, + {OperatorType::kLessEqual, OperatorType::kGreater}}; +} + +std::unordered_map const Operator::InitializeSymmetricMap() { + return {{OperatorType::kEqual, OperatorType::kEqual}, + {OperatorType::kUnequal, OperatorType::kUnequal}, + {OperatorType::kGreater, OperatorType::kLess}, + {OperatorType::kLess, OperatorType::kGreater}, + {OperatorType::kGreaterEqual, OperatorType::kLessEqual}, + {OperatorType::kLessEqual, OperatorType::kGreaterEqual}}; +} + +std::unordered_map> const +Operator::InitializeImplicationsMap() { + return {{OperatorType::kEqual, + {OperatorType::kEqual, OperatorType::kGreaterEqual, OperatorType::kLessEqual}}, + {OperatorType::kUnequal, {OperatorType::kUnequal}}, + {OperatorType::kGreater, + {OperatorType::kGreater, OperatorType::kGreaterEqual, OperatorType::kUnequal}}, + {OperatorType::kLess, + {OperatorType::kLess, OperatorType::kLessEqual, OperatorType::kUnequal}}, + {OperatorType::kGreaterEqual, {OperatorType::kGreaterEqual}}, + {OperatorType::kLessEqual, {OperatorType::kLessEqual}}}; +} + +std::unordered_map> const +Operator::InitializeTransitivesMap() { + return {{OperatorType::kEqual, {OperatorType::kEqual}}, + {OperatorType::kUnequal, {OperatorType::kEqual}}, + {OperatorType::kGreater, + {OperatorType::kGreater, OperatorType::kGreaterEqual, OperatorType::kEqual}}, + {OperatorType::kLess, + {OperatorType::kLess, OperatorType::kLessEqual, OperatorType::kEqual}}, + {OperatorType::kGreaterEqual, + {OperatorType::kGreater, OperatorType::kGreaterEqual, OperatorType::kEqual}}, + {OperatorType::kLessEqual, + {OperatorType::kLess, OperatorType::kLessEqual, OperatorType::kEqual}}}; +} + +std::unordered_map const Operator::InitializeShortStringMap() { + return {{OperatorType::kEqual, "=="}, {OperatorType::kUnequal, "!="}, + {OperatorType::kGreater, ">"}, {OperatorType::kLess, "<"}, + {OperatorType::kGreaterEqual, ">="}, {OperatorType::kLessEqual, "<="}}; +} + +std::unordered_map const Operator::kInverseMap = + Operator::InitializeInverseMap(); +std::unordered_map const Operator::kSymmetricMap = + Operator::InitializeSymmetricMap(); +std::unordered_map> const Operator::kImplicationsMap = + Operator::InitializeImplicationsMap(); +std::unordered_map> const Operator::kTransitivesMap = + Operator::InitializeTransitivesMap(); +std::unordered_map const Operator::kShortStringMap = + Operator::InitializeShortStringMap(); + +size_t hash_value(model::Operator const& k) noexcept { + return std::hash()(k); +} + +} // namespace model diff --git a/src/core/algorithms/dc/operator.h b/src/core/algorithms/dc/operator.h new file mode 100644 index 000000000..bbe6ad9e7 --- /dev/null +++ b/src/core/algorithms/dc/operator.h @@ -0,0 +1,95 @@ +#pragma once + +#include + +#include "type.h" + +namespace model { + +enum class OperatorType { kEqual, kUnequal, kGreater, kLess, kGreaterEqual, kLessEqual }; + +constexpr std::array kAllOperatorTypes = { + OperatorType::kEqual, OperatorType::kUnequal, OperatorType::kGreater, + OperatorType::kLess, OperatorType::kGreaterEqual, OperatorType::kLessEqual}; + +class Operator { +private: + OperatorType op_; + static std::unordered_map const kInverseMap; + static std::unordered_map const kSymmetricMap; + static std::unordered_map> const kImplicationsMap; + static std::unordered_map> const kTransitivesMap; + static std::unordered_map const kShortStringMap; + +public: + Operator(OperatorType type) : op_(type) {} + + bool operator==(Operator const& rhs) const { + return op_ == rhs.op_; + } + + bool Eval(std::byte const* v1, std::byte const* v2, Type const& type) const; + + // 'a op b' <=> !'a op.inverse b' + Operator GetInverse() const { + return Operator(kInverseMap.at(op_)); + } + + // 'a op b' <=> 'b op.symmetric a' + Operator GetSymmetric() const { + return Operator(kSymmetricMap.at(op_)); + } + + // If 'a op b', then 'a op.implications[i] b' + std::vector GetImplications() const { + std::vector implications; + for (auto type : kImplicationsMap.at(op_)) { + implications.push_back(Operator(type)); + } + return implications; + } + + // If 'a op b' and 'b op.transitives[i] c', then 'a op c' + std::vector GetTransitives() const { + std::vector transitives; + for (auto type : kTransitivesMap.at(op_)) { + transitives.push_back(Operator(type)); + } + return transitives; + } + + std::string ToString() const { + return kShortStringMap.at(op_); + } + + OperatorType GetType() const { + return op_; + } + + static std::unordered_map const InitializeInverseMap(); + + static std::unordered_map const InitializeSymmetricMap(); + + static std::unordered_map> const + InitializeImplicationsMap(); + + static std::unordered_map> const + InitializeTransitivesMap(); + + static std::unordered_map const InitializeShortStringMap(); +}; + +// NOLINTBEGIN(readability-identifier-naming) +size_t hash_value(model::Operator const& k) noexcept; +// NOLINTEND(readability-identifier-naming) + +} // namespace model + +namespace std { +template <> +struct hash { + size_t operator()(model::Operator const& k) const noexcept { + return static_cast(k.GetType()); + } +}; +}; // namespace std diff --git a/src/core/algorithms/dc/predicate.cpp b/src/core/algorithms/dc/predicate.cpp new file mode 100644 index 000000000..db61b7ba6 --- /dev/null +++ b/src/core/algorithms/dc/predicate.cpp @@ -0,0 +1,62 @@ +#include "dc/predicate.h" + +#include "dc/predicate_provider.h" + +namespace model { + +PredicatePtr GetPredicate(Operator const& op, ColumnOperand const& l, ColumnOperand const& r) { + return PredicateProvider::GetInstance()->GetPredicate(op, l, r); +} + +PredicatePtr GetPredicateByType(PredicatesSpan predicates, OperatorType type) { + auto it = std::find_if(predicates.begin(), predicates.end(), + [type](PredicatePtr p) { return p->GetOperator() == type; }); + + return it == predicates.end() ? nullptr : *it; +} + +bool Predicate::Satisfies(std::vector& col_data, int t, int s) const { + TypedColumnData const& lhs = col_data[l_.GetColumn()->GetIndex()]; + TypedColumnData const& rhs = col_data[r_.GetColumn()->GetIndex()]; + + // Assumes that types in both columns are the same (and they should) + Type const& type = lhs.GetType(); + + std::byte const* l_val = lhs.GetValue(l_.GetTuple() ? t : s); + std::byte const* r_val = rhs.GetValue(r_.GetTuple() ? t : s); + + return op_.Eval(l_val, r_val, type); +} + +PredicatePtr Predicate::GetSymmetric() const { + if (!symmetric_) { + symmetric_ = GetPredicate(op_.GetSymmetric(), r_, l_); + } + return symmetric_; +} + +PredicatePtr Predicate::GetInvTS() const { + if (!inv_TS_) { + inv_TS_ = GetPredicate(op_, r_.GetInvTS(), l_.GetInvTS()); + } + return inv_TS_; +} + +PredicatePtr Predicate::GetInverse() const { + if (!inverse_) { + inverse_ = GetPredicate(op_.GetInverse(), l_, r_); + } + return inverse_; +} + +std::vector Predicate::GetImplications() const { + if (implications_.empty()) { + auto op_implications = op_.GetImplications(); + for (auto const& op_implication : op_implications) { + implications_.push_back(GetPredicate(op_implication, l_, r_)); + } + } + return implications_; +} + +} // namespace model diff --git a/src/core/algorithms/dc/predicate.h b/src/core/algorithms/dc/predicate.h new file mode 100644 index 000000000..653ee3d93 --- /dev/null +++ b/src/core/algorithms/dc/predicate.h @@ -0,0 +1,116 @@ +#pragma once + +#include + +#include + +#include "column_operand.h" +#include "operator.h" +#include "table/typed_column_data.h" + +namespace model { + +class Predicate; +using PredicatePtr = Predicate const*; +using PredicatesVector = std::vector; +using PredicatesSpan = std::span; + +/** + * @brief Represents a predicate for FastADC. + * + * FastADC processes Denial Constraints (DCs) that involve comparisons between + * pairs of rows within a dataset. A typical DC example, derived from a Functional + * Dependency such as A -> B, is expressed as: `forall t, s in r, not (t.A = s.A and t.B != s.B)` + * This denotes that for any pair of rows in the relation, it should not be the case + * that while the values in column "A" are equal, the values in column "B" are unequal. + * + * A predicate in this context (e.g., t.A == s.A) comprises three elements to be fully + * represented: the column operand from the first tuple ("t.A"), the comparison operator + * ("="), and the column operand from the second tuple ("s.A"). + */ +class Predicate { +private: + Operator op_; + ColumnOperand l_; + ColumnOperand r_; + + mutable PredicatePtr symmetric_{}; + mutable PredicatePtr operator_symmetric_{}; + mutable PredicatePtr inv_TS_{}; + mutable PredicatePtr inverse_{}; + mutable std::vector implications_; + +public: + Predicate(Operator const& op, ColumnOperand const& l, ColumnOperand const& r) + : op_(op), l_(l), r_(r) {} + + // FIXME: mb pass some table representation other than vector of columns data? + bool Satisfies(std::vector& col_data, int t, int s) const; + + PredicatePtr GetSymmetric() const; + + PredicatePtr GetInvTS() const; + + PredicatePtr GetInverse() const; + + std::vector GetImplications() const; + + Operator GetOperator() const { + return op_; + } + + ColumnOperand GetLeftOperand() const { + return l_; + } + + ColumnOperand GetRightOperand() const { + return r_; + } + + bool IsCrossColumn() const { + return l_.GetColumn() != r_.GetColumn(); + } + + bool HasSameOperandsAs(Predicate const& rhs) const { + return l_ == rhs.GetLeftOperand() && r_ == rhs.GetRightOperand(); + } + + bool HasSameOperandsAs(PredicatePtr rhs) const { + return l_ == rhs->GetLeftOperand() && r_ == rhs->GetRightOperand(); + } + + bool operator==(Predicate const& rhs) const { + return op_ == rhs.op_ && l_ == rhs.l_ && r_ == rhs.r_; + } + + std::string ToString() const { + return l_.ToString() + " " + op_.ToString() + " " + r_.ToString(); + } +}; + +/** Create predicate object and return pointer to it or obtain it from cache */ +PredicatePtr GetPredicate(Operator const& op, ColumnOperand const& l, ColumnOperand const& r); + +PredicatePtr GetPredicateByType(PredicatesSpan predicates, OperatorType type); + +} // namespace model + +namespace std { +template <> +struct hash { + size_t operator()(model::Predicate const& k) const noexcept { + std::size_t seed = 0; + boost::hash_combine(seed, k.GetOperator()); + boost::hash_combine(seed, k.GetLeftOperand()); + boost::hash_combine(seed, k.GetRightOperand()); + return seed; + } +}; + +template <> +struct hash> { + size_t operator()(std::shared_ptr const& k) const { + return std::hash()(*k); + } +}; +}; // namespace std diff --git a/src/core/algorithms/dc/predicate_builder.h b/src/core/algorithms/dc/predicate_builder.h new file mode 100644 index 000000000..82beedb93 --- /dev/null +++ b/src/core/algorithms/dc/predicate_builder.h @@ -0,0 +1,13 @@ +#pragma once + +#include "dc/predicate_provider.h" + +namespace model { + +class PredicateBuilder { +public: + PredicateBuilder() { + PredicateProvider::CreateInstance(); + } +}; +} // namespace model diff --git a/src/core/algorithms/dc/predicate_provider.cpp b/src/core/algorithms/dc/predicate_provider.cpp new file mode 100644 index 000000000..46120c65e --- /dev/null +++ b/src/core/algorithms/dc/predicate_provider.cpp @@ -0,0 +1,14 @@ +#include "dc/predicate_provider.h" + +#include "dc/column_operand.h" +#include "dc/operator.h" + +namespace model { + +PredicatePtr PredicateProvider::GetPredicate(Operator const& op, ColumnOperand const& left, + ColumnOperand const& right) { + auto [iter, _] = predicates_[op][left].try_emplace(right, op, left, right); + return &iter->second; +} + +} // namespace model diff --git a/src/core/algorithms/dc/predicate_provider.h b/src/core/algorithms/dc/predicate_provider.h new file mode 100644 index 000000000..ccc8c5607 --- /dev/null +++ b/src/core/algorithms/dc/predicate_provider.h @@ -0,0 +1,45 @@ +#pragma once + +#include "dc/base_provider.h" +#include "dc/column_operand.h" +#include "operator.h" +#include "predicate.h" + +namespace model { + +/** + * @brief Singleton storage for Predicate objects. + * + * A Predicate represents a relational condition between two rows from potentially different + * columns, denoted as "t.A_i op s.A_j", where t and s represent different row identifiers, A_i + * and A_j represent columns (which may be the same or different), and 'op' is a relational operator + * (one of <, <=, >, >=, ==, !=). This class manages a centralized repository of Predicate objects. + * + * Usage: + * To access or create a Predicate, use PredicateProvider::GetInstance()->GetPredicate(op, operand1, + * operand2), where 'op' is the relational operator, and 'operand1' and 'operand2' are the column + * operands involved. + */ +class PredicateProvider : public BaseProvider { +private: + using ColumnMap = std::unordered_map; + using OperatorMap = std::unordered_map; + // predicates_[op][col1][col2] corresponds to the related Predicate object + std::unordered_map predicates_; + + friend BaseProvider; + + static std::string ClassName() { + return "PredicateProvider"; + } + + static void Clear() { + instance_->predicates_.clear(); + } + +public: + PredicatePtr GetPredicate(Operator const& op, ColumnOperand const& left, + ColumnOperand const& right); +}; + +} // namespace model diff --git a/src/tests/all_csv_configs.cpp b/src/tests/all_csv_configs.cpp index 3b789a7e7..53efaa0a9 100644 --- a/src/tests/all_csv_configs.cpp +++ b/src/tests/all_csv_configs.cpp @@ -100,4 +100,5 @@ CSVConfig const kTestDif1 = CreateCsvConfig("dif_tables/TestDif1.csv", ',', true CSVConfig const kTestDif2 = CreateCsvConfig("dif_tables/TestDif2.csv", ',', true); CSVConfig const kTestDif3 = CreateCsvConfig("dif_tables/TestDif3.csv", ',', true); CSVConfig const kSimpleTypes1 = CreateCsvConfig("SimpleTypes1.csv", ',', true); +CSVConfig const kTmpDC = CreateCsvConfig("tmp_dc.csv", ',', true); } // namespace tests diff --git a/src/tests/all_csv_configs.h b/src/tests/all_csv_configs.h index 442ff8309..e6b7a4a31 100644 --- a/src/tests/all_csv_configs.h +++ b/src/tests/all_csv_configs.h @@ -88,4 +88,5 @@ extern CSVConfig const kTestDif1; extern CSVConfig const kTestDif2; extern CSVConfig const kTestDif3; extern CSVConfig const kSimpleTypes1; +extern CSVConfig const kTmpDC; } // namespace tests diff --git a/src/tests/test_dc_structures.cpp b/src/tests/test_dc_structures.cpp new file mode 100644 index 000000000..6e1fce55c --- /dev/null +++ b/src/tests/test_dc_structures.cpp @@ -0,0 +1,240 @@ +#include + +#include +#include + +#include "all_csv_configs.h" +#include "csv_parser/csv_parser.h" +#include "dc/column_operand.h" +#include "dc/operator.h" +#include "dc/predicate.h" +#include "dc/predicate_builder.h" +#include "table/column_layout_typed_relation_data.h" + +namespace tests { + +namespace fs = std::filesystem; +namespace mo = model; + +class TestOperatorInt : public ::testing::Test { +private: + std::unique_ptr left_owner_; + std::unique_ptr right_owner_; + +protected: + std::unique_ptr type_; + std::byte* left_ptr_; + std::byte* right_ptr_; + std::array all_operators_ = { + {mo::Operator(mo::OperatorType::kEqual), mo::Operator(mo::OperatorType::kUnequal), + mo::Operator(mo::OperatorType::kGreater), mo::Operator(mo::OperatorType::kLess), + mo::Operator(mo::OperatorType::kGreaterEqual), + mo::Operator(mo::OperatorType::kLessEqual)}}; + + void SetUp() override { + type_ = std::make_unique(); + left_owner_.reset(type_->MakeValue(0)); + right_owner_.reset(type_->MakeValue(0)); + left_ptr_ = left_owner_.get(); + right_ptr_ = right_owner_.get(); + } + + void SetVals(int left, int right) { + mo::Type::GetValue(left_ptr_) = left; + mo::Type::GetValue(right_ptr_) = right; + } +}; + +TEST_F(TestOperatorInt, Equal) { + mo::Operator op(mo::OperatorType::kEqual); + + auto test = [&](int l, int r, bool res) { + this->SetVals(l, r); + + EXPECT_EQ(res, op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_))); + }; + + test(1, 1, true); + test(1, 2, false); +} + +TEST_F(TestOperatorInt, Greater) { + mo::Operator op(mo::OperatorType::kGreater); + + auto test = [&](int l, int r, bool res) { + this->SetVals(l, r); + + EXPECT_EQ(res, op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_))); + }; + + test(1, 1, false); + test(1, 2, false); + test(-10, -20, true); +} + +TEST_F(TestOperatorInt, Implication) { + auto test = [&](int l, int r) { + this->SetVals(l, r); + + for (auto& op : all_operators_) { + auto implications = op.GetImplications(); + bool op_result = op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_)); + + for (auto& imp_op : implications) { + if (op_result) { + EXPECT_TRUE(imp_op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_))); + } + } + } + }; + + test(0, 0); + test(5, 4); + test(5, -5); + test(5, 5); + test(10, 4); + test(12313, -10); +} + +TEST_F(TestOperatorInt, Inverse) { + auto test = [&](int l, int r) { + this->SetVals(l, r); + + for (auto& op : all_operators_) { + auto inverse_op = op.GetInverse(); + bool op_result = op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_)); + bool inverse_result = + inverse_op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_)); + + // The result of an operation and its inverse should be opposite + EXPECT_NE(op_result, inverse_result); + } + }; + + test(0, 0); + test(5, 4); + test(5, -5); + test(5, 5); + test(10, 4); + test(12313, -10); +} + +TEST_F(TestOperatorInt, Symmetric) { + auto test = [&](int l, int r) { + this->SetVals(l, r); + + for (auto& op : all_operators_) { + auto symmetric_op = op.GetSymmetric(); + bool op_result = op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_)); + + bool symmetric_result = + symmetric_op.Eval(this->right_ptr_, this->left_ptr_, *(this->type_)); + + EXPECT_EQ(op_result, symmetric_result); + } + }; + + test(0, 0); + test(5, 4); + test(5, -5); + test(5, 5); + test(10, 4); + test(12313, -10); +} + +TEST_F(TestOperatorInt, Transitives) { + auto test = [&](int a, int b, int c) { + for (auto& op : all_operators_) { + this->SetVals(a, b); + bool ab_result = op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_)); + + auto transitives = op.GetTransitives(); + for (auto& trans_op : transitives) { + this->SetVals(b, c); + bool bc_trans_result = + trans_op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_)); + + if (ab_result && bc_trans_result) { + this->SetVals(a, c); + // If 'a op b' and 'b trans_op c', then 'a op c' should also be true + EXPECT_TRUE(op.Eval(this->left_ptr_, this->right_ptr_, *(this->type_))); + } + } + } + }; + + test(1, 2, 3); + test(3, 2, 1); + test(2, 2, 2); + test(-2, 0, 2); + test(-3, -2, 1); + test(-1, -5, -4); + test(2, 2, 3); + test(-1, -1, 0); + test(1, 3, 3); + test(-4, -2, -2); + test(5, 3, 4); + test(0, -1, 1); + test(4, 2, 2); + test(1, 0, 0); + test(1000, 2000, 3000); + test(-1000, -500, 0); + test(10000, -5000, 5000); + test(3, 3, 2); + test(-1, -1, -2); + test(2, 3, 1); + test(-3, -1, -4); + test(3, 1, 2); + test(0, -2, -1); +} + +TEST(TestOperatorString, Compare) { + std::unique_ptr type(mo::CreateType(mo::TypeId::kString, true)); + mo::StringType const* s = static_cast(type.get()); + + mo::Operator eq(mo::OperatorType::kEqual); + mo::Operator neq(mo::OperatorType::kUnequal); + + auto test = [&](std::string l, std::string r, bool res, mo::Operator& op) { + using Owner = std::unique_ptr; + + Owner l_owner(s->MakeValue(l), s->GetDeleter()); + Owner r_owner(s->MakeValue(r), s->GetDeleter()); + + std::byte* l_val = l_owner.get(); + std::byte* r_val = r_owner.get(); + + EXPECT_EQ(res, op.Eval(l_val, r_val, *s)); + }; + + test("sfsdf", "sfsdf", true, eq); + test("sfsdf", "sfsdf", false, neq); + test("abc", "cba", false, eq); + test("abc", "cba", true, neq); +} + +TEST(Predicate, PredicateCreatesCorrectly) { + CSVParser parser{kTmpDC}; + std::unique_ptr table = + mo::ColumnLayoutTypedRelationData::CreateFrom(parser, true); + std::vector col_data = std::move(table->GetColumnData()); + Column const *first = col_data[0].GetColumn(), *second = col_data[1].GetColumn(); + // FIXME: temprorary to make test work + model::PredicateBuilder pbuilder; + + mo::PredicatePtr s_a_less_t_b = + mo::GetPredicate(mo::Operator(mo::OperatorType::kLess), mo::ColumnOperand(first, true), + mo::ColumnOperand(second, false)); + + EXPECT_TRUE(s_a_less_t_b->Satisfies(col_data, 0, 1)); + EXPECT_TRUE(s_a_less_t_b->Satisfies(col_data, 1, 0)); + + mo::PredicatePtr s_a_neq_t_a = + GetPredicate(mo::Operator(mo::OperatorType::kUnequal), mo::ColumnOperand(first, true), + mo::ColumnOperand(first, false)); + + EXPECT_FALSE(s_a_neq_t_a->Satisfies(col_data, 0, 1)); + EXPECT_FALSE(s_a_neq_t_a->Satisfies(col_data, 1, 0)); +} + +} // namespace tests diff --git a/test_input_data/tmp_dc.csv b/test_input_data/tmp_dc.csv new file mode 100644 index 000000000..a3005ce83 --- /dev/null +++ b/test_input_data/tmp_dc.csv @@ -0,0 +1,3 @@ +A,B +1,2 +1,10