Crafter.DB/interfaces/Crafter.DB-Query.cppm

205 lines
9.3 KiB
Text
Raw Normal View History

// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Queries are C++. A lambda over Row<T> is the general form; column tags
// give a readable expression form (iban == x && amount > 100) whose types
// the engine can look into: comparisons on the orderedBy column found in a
// top-level && chain become binary-search bounds instead of a scan, and
// string comparisons pre-encode their Key once instead of once per row.
export module Crafter.DB:Query;
import std;
import :Reflect;
import :Formatter;
import :Schema;
import :Storage;
import :Codec;
import :Row;
export namespace Crafter::DB {
enum class CompareOp : std::uint8_t { Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual };
template<auto Ptr, CompareOp Op, class Operand>
struct Compare {
using Table = MemberClass<Ptr>;
static constexpr auto pointer = Ptr;
static constexpr CompareOp op = Op;
Operand operand;
bool operator()(Row<Table> row) const {
const auto value = row.template Get<Ptr>();
if constexpr (Op == CompareOp::Equal) return value == operand;
else if constexpr (Op == CompareOp::NotEqual) return !(value == operand);
else if constexpr (Op == CompareOp::Less) return value < operand;
else if constexpr (Op == CompareOp::LessEqual) return value <= operand;
else if constexpr (Op == CompareOp::Greater) return value > operand;
else return value >= operand;
}
};
template<class A, class B>
struct And {
A a;
B b;
template<class R> bool operator()(R row) const { return a(row) && b(row); }
};
template<class A, class B>
struct Or {
A a;
B b;
template<class R> bool operator()(R row) const { return a(row) || b(row); }
};
template<class A>
struct Not {
A a;
template<class R> bool operator()(R row) const { return !a(row); }
};
template<class P> struct IsExpression : std::false_type {};
template<auto Ptr, CompareOp Op, class V> struct IsExpression<Compare<Ptr, Op, V>> : std::true_type {};
template<class A, class B> struct IsExpression<And<A, B>> : std::true_type {};
template<class A, class B> struct IsExpression<Or<A, B>> : std::true_type {};
template<class A> struct IsExpression<Not<A>> : std::true_type {};
template<class P>
concept Expression = IsExpression<std::remove_cvref_t<P>>::value;
template<class M>
concept StringColumn = std::is_same_v<M, std::string>;
template<auto Ptr, CompareOp Op, class V>
auto MakeCompare(V&& value) {
using M = MemberTypeOf<Ptr>;
if constexpr (StringColumn<M>) {
if constexpr (Op == CompareOp::Equal || Op == CompareOp::NotEqual) return Compare<Ptr, Op, Key>{ Key(std::string_view(value)) };
else return Compare<Ptr, Op, std::string>{ std::string(std::string_view(value)) };
} else {
return Compare<Ptr, Op, M>{ static_cast<M>(std::forward<V>(value)) };
}
}
template<auto Ptr, class V> auto operator==(Column<Ptr>, V&& value) { return MakeCompare<Ptr, CompareOp::Equal>(std::forward<V>(value)); }
template<auto Ptr, class V> auto operator!=(Column<Ptr>, V&& value) { return MakeCompare<Ptr, CompareOp::NotEqual>(std::forward<V>(value)); }
template<auto Ptr, class V> auto operator<(Column<Ptr>, V&& value) { return MakeCompare<Ptr, CompareOp::Less>(std::forward<V>(value)); }
template<auto Ptr, class V> auto operator<=(Column<Ptr>, V&& value) { return MakeCompare<Ptr, CompareOp::LessEqual>(std::forward<V>(value)); }
template<auto Ptr, class V> auto operator>(Column<Ptr>, V&& value) { return MakeCompare<Ptr, CompareOp::Greater>(std::forward<V>(value)); }
template<auto Ptr, class V> auto operator>=(Column<Ptr>, V&& value) { return MakeCompare<Ptr, CompareOp::GreaterEqual>(std::forward<V>(value)); }
template<Expression A, Expression B> auto operator&&(A a, B b) { return And<A, B>{ std::move(a), std::move(b) }; }
template<Expression A, Expression B> auto operator||(A a, B b) { return Or<A, B>{ std::move(a), std::move(b) }; }
template<Expression A> auto operator!(A a) { return Not<A>{ std::move(a) }; }
// Bounds on the ordered column harvested from a predicate's top-level
// conjunction. Anything under || or ! contributes nothing (the whole
// predicate still runs as the filter, so this is only ever a narrowing).
template<class K>
struct Bounds {
std::optional<K> lower;
bool lowerExclusive = false;
std::optional<K> upper;
bool upperExclusive = false;
void Lower(const K& key, bool exclusive) {
if (!lower || *lower < key || (*lower == key && exclusive)) {
lower = key;
lowerExclusive = exclusive;
}
}
void Upper(const K& key, bool exclusive) {
if (!upper || key < *upper || (*upper == key && exclusive)) {
upper = key;
upperExclusive = exclusive;
}
}
};
template<class P> struct IsAndType : std::false_type {};
template<class A, class B> struct IsAndType<And<A, B>> : std::true_type {};
template<class P> struct IsCompareType : std::false_type {};
template<auto Ptr, CompareOp Op, class V> struct IsCompareType<Compare<Ptr, Op, V>> : std::true_type {};
template<class T, class P> requires HasOrderedBy<T>
void CollectBounds(const P& predicate, Bounds<OrderedKey<T>>& bounds) {
if constexpr (IsAndType<P>::value) {
CollectBounds<T>(predicate.a, bounds);
CollectBounds<T>(predicate.b, bounds);
} else if constexpr (IsCompareType<P>::value) {
if constexpr (SameMemberPointer<P::pointer, T::orderedBy>()) {
const OrderedKey<T> key = static_cast<OrderedKey<T>>(predicate.operand);
switch (P::op) {
case CompareOp::Equal: bounds.Lower(key, false); bounds.Upper(key, false); break;
case CompareOp::Less: bounds.Upper(key, true); break;
case CompareOp::LessEqual: bounds.Upper(key, false); break;
case CompareOp::Greater: bounds.Lower(key, true); break;
case CompareOp::GreaterEqual: bounds.Lower(key, false); break;
case CompareOp::NotEqual: break;
}
}
}
}
// A lazy range of Row<T> with the conveniences a result needs. Chainable:
// table.Range<...>(a, b).Where(pred).Select<&T::amount>().
template<TableType T, std::ranges::view R> requires std::is_same_v<std::ranges::range_value_t<R>, Row<T>>
class Query : public std::ranges::view_interface<Query<T, R>> {
public:
Query() = default;
explicit Query(R range) : range(std::move(range)) {}
// A Query is iterable as a const object whenever its underlying view
// is (LiveRange is; std::ranges::filter_view, by the standard's
// design, is not).
template<class Self> requires std::ranges::range<std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, const R, R>>
auto begin(this Self&& self) { return std::ranges::begin(self.range); }
template<class Self> requires std::ranges::range<std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, const R, R>>
auto end(this Self&& self) { return std::ranges::end(self.range); }
template<class Self> requires std::ranges::range<std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, const R, R>>
std::vector<T> Materialize(this Self&& self) {
std::vector<T> out;
for (Row<T> row : self.range) out.push_back(row.Materialize());
return out;
}
template<class Self> requires std::ranges::range<std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, const R, R>>
std::uint64_t Count(this Self&& self) {
std::uint64_t n = 0;
for ([[maybe_unused]] Row<T> row : self.range) n++;
return n;
}
template<class Self> requires std::ranges::range<std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, const R, R>>
std::optional<Row<T>> First(this Self&& self) {
for (Row<T> row : self.range) return row;
return std::nullopt;
}
template<class Self> requires std::ranges::range<std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, const R, R>>
bool Any(this Self&& self) { return self.First().has_value(); }
template<class Pred>
auto Where(Pred pred) && {
return Query<T, std::ranges::filter_view<R, Pred>>(std::ranges::filter_view<R, Pred>(std::move(range), std::move(pred)));
}
template<class Pred>
auto Where(Pred pred) const& {
return Query<T, std::ranges::filter_view<R, Pred>>(std::ranges::filter_view<R, Pred>(range, std::move(pred)));
}
// Lazy view of one column's values (views for strings, values otherwise).
template<auto Ptr> requires std::is_same_v<MemberClass<Ptr>, T>
auto Select() && {
return std::views::transform(std::move(range), [](Row<T> row) { return row.template Get<Ptr>(); });
}
const R& Range() const noexcept { return range; }
private:
R range;
};
}