Initial commit: Crafter.DB
Header-only C++26 module library for typed, file-backed tables with schema reflection, migrations, queries, and crash-safe commits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
bfd0cc261b
34 changed files with 7506 additions and 0 deletions
205
interfaces/Crafter.DB-Query.cppm
Normal file
205
interfaces/Crafter.DB-Query.cppm
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
// 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;
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue