// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Queries are C++. A lambda over Row 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 struct Compare { using Table = MemberClass; static constexpr auto pointer = Ptr; static constexpr CompareOp op = Op; Operand operand; bool operator()(Row row) const { const auto value = row.template Get(); 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 struct And { A a; B b; template bool operator()(R row) const { return a(row) && b(row); } }; template struct Or { A a; B b; template bool operator()(R row) const { return a(row) || b(row); } }; template struct Not { A a; template bool operator()(R row) const { return !a(row); } }; template struct IsExpression : std::false_type {}; template struct IsExpression> : std::true_type {}; template struct IsExpression> : std::true_type {}; template struct IsExpression> : std::true_type {}; template struct IsExpression> : std::true_type {}; template concept Expression = IsExpression>::value; template concept StringColumn = std::is_same_v; template auto MakeCompare(V&& value) { using M = MemberTypeOf; if constexpr (StringColumn) { if constexpr (Op == CompareOp::Equal || Op == CompareOp::NotEqual) return Compare{ Key(std::string_view(value)) }; else return Compare{ std::string(std::string_view(value)) }; } else { return Compare{ static_cast(std::forward(value)) }; } } template auto operator==(Column, V&& value) { return MakeCompare(std::forward(value)); } template auto operator!=(Column, V&& value) { return MakeCompare(std::forward(value)); } template auto operator<(Column, V&& value) { return MakeCompare(std::forward(value)); } template auto operator<=(Column, V&& value) { return MakeCompare(std::forward(value)); } template auto operator>(Column, V&& value) { return MakeCompare(std::forward(value)); } template auto operator>=(Column, V&& value) { return MakeCompare(std::forward(value)); } template auto operator&&(A a, B b) { return And{ std::move(a), std::move(b) }; } template auto operator||(A a, B b) { return Or{ std::move(a), std::move(b) }; } template auto operator!(A a) { return Not{ 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 struct Bounds { std::optional lower; bool lowerExclusive = false; std::optional 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 struct IsAndType : std::false_type {}; template struct IsAndType> : std::true_type {}; template struct IsCompareType : std::false_type {}; template struct IsCompareType> : std::true_type {}; template requires HasOrderedBy void CollectBounds(const P& predicate, Bounds>& bounds) { if constexpr (IsAndType

::value) { CollectBounds(predicate.a, bounds); CollectBounds(predicate.b, bounds); } else if constexpr (IsCompareType

::value) { if constexpr (SameMemberPointer()) { const OrderedKey key = static_cast>(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 with the conveniences a result needs. Chainable: // table.Range<...>(a, b).Where(pred).Select<&T::amount>(). template requires std::is_same_v, Row> class Query : public std::ranges::view_interface> { 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 requires std::ranges::range>, const R, R>> auto begin(this Self&& self) { return std::ranges::begin(self.range); } template requires std::ranges::range>, const R, R>> auto end(this Self&& self) { return std::ranges::end(self.range); } template requires std::ranges::range>, const R, R>> std::vector Materialize(this Self&& self) { std::vector out; for (Row row : self.range) out.push_back(row.Materialize()); return out; } template requires std::ranges::range>, const R, R>> std::uint64_t Count(this Self&& self) { std::uint64_t n = 0; for ([[maybe_unused]] Row row : self.range) n++; return n; } template requires std::ranges::range>, const R, R>> std::optional> First(this Self&& self) { for (Row row : self.range) return row; return std::nullopt; } template requires std::ranges::range>, const R, R>> bool Any(this Self&& self) { return self.First().has_value(); } template auto Where(Pred pred) && { return Query>(std::ranges::filter_view(std::move(range), std::move(pred))); } template auto Where(Pred pred) const& { return Query>(std::ranges::filter_view(range, std::move(pred))); } // Lazy view of one column's values (views for strings, values otherwise). template requires std::is_same_v, T> auto Select() && { return std::views::transform(std::move(range), [](Row row) { return row.template Get(); }); } const R& Range() const noexcept { return range; } private: R range; }; }