Program Listing for File condition.hpp
↰ Return to documentation for file (cif++/condition.hpp)
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "cif++/item.hpp"
#include "cif++/row.hpp"
#include "cif++/text.hpp"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <format>
#include <functional>
#include <iostream>
#include <optional>
#include <regex>
#include <string>
#include <string_view>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>
namespace cif
{
// --------------------------------------------------------------------
[[deprecated("use get_category_items instead")]]
iset get_category_fields(const category &cat);
iset get_category_items(const category &cat);
std::optional<uint16_t> get_item_ix(const category &cat, std::string_view col);
bool is_item_type_uchar(const category &cat, std::string_view col);
// --------------------------------------------------------------------
// some more templates to be able to do querying
namespace detail
{
struct condition_impl
{
virtual ~condition_impl() = default;
virtual condition_impl *prepare(const category &) { return this; }
[[nodiscard]] virtual bool test(const_row_handle) const = 0;
virtual void str(std::ostream &) const = 0;
[[nodiscard]] virtual std::optional<const_row_handle> single() const { return std::nullopt; };
virtual bool equals([[maybe_unused]] const condition_impl *rhs) const { return false; }
};
struct all_condition_impl : public condition_impl
{
[[nodiscard]] bool test(const_row_handle) const override { return true; }
void str(std::ostream &os) const override { os << "*"; }
};
struct or_condition_impl;
struct and_condition_impl;
struct not_condition_impl;
} // namespace detail
class condition
{
public:
using condition_impl = detail::condition_impl;
condition()
: m_impl(nullptr)
{
}
explicit condition(condition_impl *impl)
: m_impl(impl)
{
}
condition(const condition &) = delete;
condition(condition &&rhs) noexcept
: m_impl(nullptr)
{
swap(*this, rhs);
}
condition &operator=(const condition &) = delete;
condition &operator=(condition &&rhs) noexcept
{
swap(*this, rhs);
return *this;
}
~condition()
{
delete m_impl;
m_impl = nullptr;
}
bool prepare(const category &c);
bool operator()(const_row_handle r) const
{
assert(this->m_impl != nullptr);
return m_impl ? m_impl->test(r) : false;
}
explicit operator bool() const { return not empty(); }
[[nodiscard]] bool empty() const { return m_impl == nullptr; }
[[nodiscard]] std::optional<const_row_handle> single() const
{
return m_impl ? m_impl->single() : std::optional<const_row_handle>();
}
friend condition operator||(condition &&a, condition &&b);
friend condition operator&&(condition &&a, condition &&b);
friend struct detail::or_condition_impl;
friend struct detail::and_condition_impl;
friend struct detail::not_condition_impl;
friend void swap(condition &lhs, condition &rhs) noexcept
{
std::swap(lhs.m_impl, rhs.m_impl);
}
friend std::ostream &operator<<(std::ostream &os, const condition &cond)
{
if (cond.m_impl)
cond.m_impl->str(os);
return os;
}
private:
void optimise(condition_impl *&impl);
condition_impl *m_impl;
};
namespace detail
{
struct key_is_empty_condition_impl : public condition_impl
{
key_is_empty_condition_impl(std::string item_name)
: m_item_name(std::move(item_name))
{
}
condition_impl *prepare(const category &c) override
{
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
m_item_ix = *ix;
else
m_missing_key = true;
return this;
}
[[nodiscard]] bool test(const_row_handle r) const override
{
return m_missing_key or r[m_item_ix].empty();
}
void str(std::ostream &os) const override
{
os << m_item_name << " IS NULL";
}
std::string m_item_name;
uint16_t m_item_ix = 0;
bool m_missing_key = false;
};
struct key_is_not_empty_condition_impl : public condition_impl
{
key_is_not_empty_condition_impl(std::string item_name)
: m_item_name(std::move(item_name))
{
}
condition_impl *prepare(const category &c) override
{
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
return this;
}
return nullptr;
}
[[nodiscard]] bool test(const_row_handle r) const override
{
return not r[m_item_ix].empty();
}
void str(std::ostream &os) const override
{
os << m_item_name << " IS NOT NULL";
}
std::string m_item_name;
uint16_t m_item_ix = 0;
};
struct key_equals_condition_impl : public condition_impl
{
key_equals_condition_impl(item &&i)
: m_item_name(i.name())
, m_value(std::forward<item_value>(i.value()))
{
}
condition_impl *prepare(const category &c) override;
[[nodiscard]] bool test(const_row_handle r) const override
{
return m_single_hit.has_value() ? *m_single_hit == r : r[m_item_ix].compare(m_value, m_icase) == 0;
}
void str(std::ostream &os) const override
{
os << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value;
}
[[nodiscard]] std::optional<const_row_handle> single() const override
{
return m_single_hit;
}
bool equals(const condition_impl *rhs) const override
{
if (typeid(*rhs) == typeid(key_equals_condition_impl))
{
auto ri = static_cast<const key_equals_condition_impl *>(rhs);
if (m_single_hit.has_value() or ri->m_single_hit.has_value())
return m_single_hit == ri->m_single_hit;
else
// watch out, both m_item_ix might be the same while item_names might be diffent (in case they both do not exist in the category)
return m_item_ix == ri->m_item_ix and m_value == ri->m_value and m_item_name == ri->m_item_name;
}
return this == rhs;
}
std::string m_item_name;
uint16_t m_item_ix = 0;
bool m_icase = false;
item_value m_value;
std::optional<const_row_handle> m_single_hit;
};
struct key_equals_or_empty_condition_impl : public condition_impl
{
key_equals_or_empty_condition_impl(key_equals_condition_impl *equals)
: m_item_name(equals->m_item_name)
, m_value(equals->m_value)
, m_icase(equals->m_icase)
, m_single_hit(equals->m_single_hit)
{
}
condition_impl *prepare(const category &c) override
{
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
m_icase = is_item_type_uchar(c, m_item_name);
}
else
m_key_is_missing = true;
return this;
}
[[nodiscard]] bool test(const_row_handle r) const override
{
bool result = false;
if (m_key_is_missing)
result = true;
else if (m_single_hit.has_value())
result = *m_single_hit == r;
else
result = r[m_item_ix].empty() or r[m_item_ix].compare(m_value, m_icase) == 0;
return result;
}
void str(std::ostream &os) const override
{
os << '(' << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value << " OR " << m_item_name << " IS NULL)";
}
[[nodiscard]] std::optional<const_row_handle> single() const override
{
return m_single_hit;
}
[[nodiscard]] bool equals(const condition_impl *rhs) const override
{
if (typeid(*rhs) == typeid(key_equals_or_empty_condition_impl))
{
auto ri = static_cast<const key_equals_or_empty_condition_impl *>(rhs);
if (m_single_hit.has_value() or ri->m_single_hit.has_value())
return m_single_hit == ri->m_single_hit;
else
// watch out, both m_item_ix might be the same while item_names might be diffent (in case they both do not exist in the category)
return m_item_ix == ri->m_item_ix and m_value == ri->m_value and m_item_name == ri->m_item_name;
}
return this == rhs;
}
std::string m_item_name;
uint16_t m_item_ix = 0;
item_value m_value;
bool m_icase = false;
bool m_key_is_missing = false;
std::optional<const_row_handle> m_single_hit;
};
struct key_compare_condition_impl : public condition_impl
{
template <typename COMP>
key_compare_condition_impl(std::string item_name, COMP &&comp, std::string s)
: m_item_name(std::move(item_name))
, m_compare(std::forward<COMP>(comp))
, m_str(std::move(s))
{
}
condition_impl *prepare(const category &c) override
{
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
m_icase = is_item_type_uchar(c, m_item_name);
return this;
}
return nullptr;
}
[[nodiscard]] bool test(const_row_handle r) const override
{
return m_compare(r, m_icase);
}
void str(std::ostream &os) const override
{
os << m_item_name << (m_icase ? "^ " : " ") << m_str;
}
std::string m_item_name;
uint16_t m_item_ix = 0;
bool m_icase = false;
std::function<bool(const_row_handle, bool)> m_compare;
std::string m_str;
};
struct key_matches_condition_impl : public condition_impl
{
key_matches_condition_impl(std::string item_name, std::regex rx)
: m_item_name(std::move(item_name))
, mRx(std::move(rx))
{
}
condition_impl *prepare(const category &c) override
{
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
return this;
}
return nullptr;
}
[[nodiscard]] bool test(const_row_handle r) const override
{
auto txt = r[m_item_ix].get<std::string>();
return std::regex_match(txt.begin(), txt.end(), mRx);
}
void str(std::ostream &os) const override
{
os << m_item_name << " =~ expression";
}
std::string m_item_name;
uint16_t m_item_ix{};
std::regex mRx;
};
template <typename T>
struct any_is_condition_impl : public condition_impl
{
using valueType = T;
any_is_condition_impl(const valueType &value)
: mValue(value)
{
}
[[nodiscard]] bool test(const_row_handle r) const override
{
auto &c = r.get_category();
bool result = false;
for (auto &f : get_category_items(c))
{
if (r[f].compare(mValue) == 0)
{
result = true;
break;
}
}
return result;
}
void str(std::ostream &os) const override
{
os << "<any> == " << mValue;
}
valueType mValue;
};
struct any_matches_condition_impl : public condition_impl
{
any_matches_condition_impl(std::regex rx)
: mRx(std::move(rx))
{
}
[[nodiscard]] bool test(const_row_handle r) const override
{
auto &c = r.get_category();
bool result = false;
for (auto &f : get_category_items(c))
{
try
{
auto txt = r[f].get<std::string>();
if (std::regex_match(txt.begin(), txt.end(), mRx))
{
result = true;
break;
}
}
catch (const std::exception &ex) // NOLINT(bugprone-empty-catch)
{
}
}
return result;
}
void str(std::ostream &os) const override
{
os << "<any> =~ expression";
}
std::regex mRx;
};
// TODO: Optimize and_condition by having a list of sub items.
// That way you can also collapse multiple _is_ conditions in
// case they make up an indexed tuple.
struct and_condition_impl : public condition_impl
{
and_condition_impl() = default;
and_condition_impl(condition &&a, condition &&b)
{
if (typeid(*a.m_impl) == typeid(*this))
{
auto *ai = static_cast<and_condition_impl *>(a.m_impl);
std::swap(m_sub, ai->m_sub);
m_sub.emplace_back(std::exchange(b.m_impl, nullptr));
}
else if (typeid(*b.m_impl) == typeid(*this))
{
auto *bi = static_cast<and_condition_impl *>(b.m_impl);
std::swap(m_sub, bi->m_sub);
m_sub.emplace_back(std::exchange(a.m_impl, nullptr));
}
else
{
m_sub.emplace_back(std::exchange(a.m_impl, nullptr));
m_sub.emplace_back(std::exchange(b.m_impl, nullptr));
}
}
~and_condition_impl() override // NOLINT(modernize-use-equals-default)
{
for (auto sub : m_sub)
delete sub;
}
condition_impl *prepare(const category &c) override;
[[nodiscard]] bool test(const_row_handle r) const override;
void str(std::ostream &os) const override
{
os << '(';
bool first = true;
for (auto sub : m_sub)
{
if (first)
first = false;
else
os << " AND ";
sub->str(os);
}
os << ')';
}
[[nodiscard]] std::optional<const_row_handle> single() const override
{
std::optional<const_row_handle> result;
for (auto sub : m_sub)
{
auto s = sub->single();
if (not result.has_value())
{
result = s;
continue;
}
if (s == result)
continue;
result.reset();
break;
}
return result;
}
static condition_impl *combine_equal(std::vector<and_condition_impl *> &subs, or_condition_impl *oc);
std::vector<condition_impl *> m_sub;
std::optional<const_row_handle> m_single; // Potential result of index lookup
};
struct or_condition_impl : public condition_impl
{
or_condition_impl(condition &&a, condition &&b)
{
if (typeid(*a.m_impl) == typeid(*this))
{
auto *ai = static_cast<or_condition_impl *>(a.m_impl);
std::swap(m_sub, ai->m_sub);
m_sub.emplace_back(std::exchange(b.m_impl, nullptr));
}
else if (typeid(*b.m_impl) == typeid(*this))
{
auto *bi = static_cast<or_condition_impl *>(b.m_impl);
std::swap(m_sub, bi->m_sub);
m_sub.emplace_back(std::exchange(a.m_impl, nullptr));
}
else
{
m_sub.emplace_back(std::exchange(a.m_impl, nullptr));
m_sub.emplace_back(std::exchange(b.m_impl, nullptr));
}
}
~or_condition_impl() override // NOLINT(modernize-use-equals-default)
{
for (auto sub : m_sub)
delete sub;
}
condition_impl *prepare(const category &c) override;
[[nodiscard]] bool test(const_row_handle r) const override
{
bool result = false;
for (auto sub : m_sub)
{
if (not sub->test(r))
continue;
result = true;
break;
}
return result;
}
void str(std::ostream &os) const override
{
bool first = true;
os << '(';
for (auto sub : m_sub)
{
if (first)
first = false;
else
os << " OR ";
sub->str(os);
}
os << ')';
}
[[nodiscard]] std::optional<const_row_handle> single() const override
{
std::optional<const_row_handle> result;
for (auto sub : m_sub)
{
auto s = sub->single();
if (not result.has_value())
{
result = s;
continue;
}
if (s == result)
continue;
result.reset();
break;
}
return result;
}
std::vector<condition_impl *> m_sub;
};
struct not_condition_impl : public condition_impl
{
not_condition_impl(condition &&a)
{
std::swap(mA, a.m_impl);
}
~not_condition_impl() override
{
delete mA;
}
condition_impl *prepare(const category &c) override
{
return mA->prepare(c) ? this : nullptr;
}
[[nodiscard]] bool test(const_row_handle r) const override
{
return not mA->test(r);
}
void str(std::ostream &os) const override
{
os << "NOT (";
mA->str(os);
os << ')';
}
condition_impl *mA = nullptr;
};
} // namespace detail
inline condition operator and(condition &&a, condition &&b)
{
if (a.m_impl and b.m_impl)
return condition(new detail::and_condition_impl(std::move(a), std::move(b)));
if (a.m_impl)
return a;
return b;
}
inline condition operator or(condition &&a, condition &&b)
{
if (a.m_impl and b.m_impl)
{
if (typeid(*a.m_impl) == typeid(detail::key_equals_condition_impl) and
typeid(*b.m_impl) == typeid(detail::key_is_empty_condition_impl))
{
auto ci = static_cast<detail::key_equals_condition_impl *>(a.m_impl);
auto ce = static_cast<detail::key_is_empty_condition_impl *>(b.m_impl);
if (ci->m_item_name == ce->m_item_name)
return condition(new detail::key_equals_or_empty_condition_impl(ci));
}
if (typeid(*b.m_impl) == typeid(detail::key_equals_condition_impl) and
typeid(*a.m_impl) == typeid(detail::key_is_empty_condition_impl))
{
auto ci = static_cast<detail::key_equals_condition_impl *>(b.m_impl);
auto ce = static_cast<detail::key_is_empty_condition_impl *>(a.m_impl);
if (ci->m_item_name == ce->m_item_name)
return condition(new detail::key_equals_or_empty_condition_impl(ci));
}
return condition(new detail::or_condition_impl(std::move(a), std::move(b)));
}
if (a.m_impl)
return a;
return b;
}
struct empty_type
{
};
inline constexpr empty_type null = empty_type();
struct key
{
explicit key(std::string item_name)
: m_item_name(std::move(item_name))
{
}
explicit key(const char *item_name)
: m_item_name(item_name)
{
}
explicit key(std::string_view item_name)
: m_item_name(item_name)
{
}
key(const key &) = delete;
key &operator=(const key &) = delete;
std::string m_item_name;
};
template <typename T>
concept Numeric = ((std::is_floating_point_v<T> or std::is_integral_v<T>) and not std::is_same_v<T, bool>);
inline condition operator==(const key &key, const item_value &value)
{
if (not value.empty())
return condition(new detail::key_equals_condition_impl({ key.m_item_name, value }));
else
return condition(new detail::key_is_empty_condition_impl(key.m_item_name));
}
inline condition operator!=(const key &key, const item_value &value)
{
return condition(new detail::not_condition_impl(operator==(key, value)));
}
template <Numeric T>
condition operator>(const key &key, const T &v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) > 0; },
std::format(" > {}", v)));
}
template <Numeric T>
condition operator>=(const key &key, const T &v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) >= 0; },
std::format(" >= {}", v)));
}
template <Numeric T>
condition operator<(const key &key, const T &v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) < 0; },
std::format(" < {}", v)));
}
template <Numeric T>
condition operator<=(const key &key, const T &v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) <= 0; },
std::format(" <= {}", v)));
}
inline condition operator>(const key &key, std::string_view v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) > 0; },
std::format(" > {}", v)));
}
inline condition operator>=(const key &key, std::string_view v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) >= 0; },
std::format(" >= {}", v)));
}
inline condition operator<(const key &key, std::string_view v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) < 0; },
std::format(" < {}", v)));
}
inline condition operator<=(const key &key, std::string_view v)
{
return condition(new detail::key_compare_condition_impl(
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) <= 0; },
std::format(" <= {}", v)));
}
inline condition operator==(const key &key, const std::regex &rx)
{
return condition(new detail::key_matches_condition_impl(key.m_item_name, rx));
}
inline condition operator==(const key &key, const empty_type &)
{
return condition(new detail::key_is_empty_condition_impl(key.m_item_name));
}
inline condition operator!=(const key &key, const empty_type &)
{
return condition(new detail::key_is_not_empty_condition_impl(key.m_item_name));
}
template <typename T>
condition operator==(const key &key, const std::optional<T> &v)
{
if (v.has_value())
return condition(new detail::key_equals_condition_impl({ key.m_item_name, *v }));
else
return condition(new detail::key_is_empty_condition_impl(key.m_item_name));
}
template <typename T>
condition operator!=(const key &key, const std::optional<T> &v)
{
if (v.has_value())
return condition(new detail::not_condition_impl(condition(new detail::key_equals_condition_impl({ key.m_item_name, *v }))));
else
return condition(new detail::not_condition_impl(condition(new detail::key_is_empty_condition_impl(key.m_item_name))));
}
inline condition operator not(condition &&rhs)
{
return condition(new detail::not_condition_impl(std::move(rhs)));
}
struct any_type
{
};
inline constexpr any_type any = any_type{};
template <typename T>
condition operator==(const any_type &, const T &v)
{
return condition(new detail::any_is_condition_impl<T>(v));
}
inline condition operator==(const any_type &, const std::regex &rx)
{
return condition(new detail::any_matches_condition_impl(rx));
}
inline condition all()
{
return condition(new detail::all_condition_impl());
}
namespace literals
{
inline key operator""_key(const char *text, std::size_t length)
{
return key(std::string(text, length));
}
} // namespace literals
} // namespace cif