mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-06-28 08:49:38 -04:00
346e2b2514
# Description of Changes - Added a query builder for C++ module bindings - Added query-builder table/filter/join types - Added semijoin support with compile-time checks for lookup-table and indexed-field usage - Added support for returning query-builder queries from C++ views - Hooked query-builder metadata into the C++ table/view macros and V10 module-def path - Added test coverage for the new C++ query-builder behavior - Compile tests for pass/fail cases - SQL tests for generated query output - Added a C++ test module for view primary key coverage - **Update:** Switched the core to pass the columns and index-columns metadata with the table source for better client-side codegen to have some shared code between server + client. # API and ABI breaking changes - No intended API or ABI breaking changes - Adds a new public query-builder API to the C++ bindings - C++ views can now return query-builder query types in addition to materialized row results # Expected complexity level and risk 3 - Mostly contained to C++ bindings, but it touches macros, view registration/serialization, and module-def generation, so there are a few places where the pieces need to stay in sync. # Testing I've done end to end testing of I think every type as well as built some tests to confirm the SQL output. - [x] Run the C++ query-builder SQL tests [crates/bindings-cpp/tests/query-builder-compile/run_query_builder_compile_tests.sh] - [x] Smoke test a generated C++ module using query-builder views --------- Signed-off-by: Jason Larabie <jason@clockworklabs.io> Co-authored-by: Ryan <r.ekhoff@clockworklabs.io> Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
537 lines
22 KiB
C++
537 lines
22 KiB
C++
#pragma once
|
|
|
|
#include "spacetimedb/bsatn/timestamp.h"
|
|
#include "spacetimedb/bsatn/types.h"
|
|
#include <charconv>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <iomanip>
|
|
#include <locale>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
namespace SpacetimeDB::query_builder {
|
|
|
|
template<typename TRow, typename TValue>
|
|
class Col;
|
|
|
|
template<typename T>
|
|
struct is_col : std::false_type {};
|
|
|
|
template<typename TValue, typename TRhs>
|
|
inline constexpr bool is_rhs_for_value_v =
|
|
std::is_same_v<TValue, std::remove_cvref_t<TRhs>> ||
|
|
(std::is_same_v<TValue, std::string> && std::is_convertible_v<TRhs, std::string_view>);
|
|
|
|
template<typename TRow>
|
|
class ColumnRef {
|
|
public:
|
|
constexpr ColumnRef()
|
|
: table_name_(""), column_name_("") {}
|
|
|
|
constexpr ColumnRef(const char* table_name, const char* column_name)
|
|
: table_name_(table_name), column_name_(column_name) {}
|
|
|
|
[[nodiscard]] std::string format() const {
|
|
return "\"" + std::string(table_name_) + "\".\"" + std::string(column_name_) + "\"";
|
|
}
|
|
|
|
[[nodiscard]] constexpr const char* table_name() const { return table_name_; }
|
|
[[nodiscard]] constexpr const char* column_name() const { return column_name_; }
|
|
|
|
private:
|
|
const char* table_name_;
|
|
const char* column_name_;
|
|
};
|
|
|
|
namespace detail {
|
|
|
|
inline std::string quote_string(std::string_view value) {
|
|
std::string escaped;
|
|
escaped.reserve(value.size() + 2);
|
|
escaped.push_back('\'');
|
|
for (char ch : value) {
|
|
escaped.push_back(ch);
|
|
if (ch == '\'') {
|
|
escaped.push_back('\'');
|
|
}
|
|
}
|
|
escaped.push_back('\'');
|
|
return escaped;
|
|
}
|
|
|
|
inline std::string trim_timestamp_fraction(std::string value) {
|
|
// Keep this in sync with the current Timestamp::to_string() UTC form.
|
|
// If that representation changes away from a +00:00 / Z suffix, revisit this trimming logic.
|
|
const std::size_t plus = value.rfind("+00:00");
|
|
const std::size_t z = value.rfind('Z');
|
|
const std::size_t dot = value.find('.');
|
|
const std::size_t suffix = plus != std::string::npos ? plus : z;
|
|
if (suffix == std::string::npos || dot == std::string::npos || dot > suffix) {
|
|
return value;
|
|
}
|
|
|
|
std::size_t trim = suffix;
|
|
while (trim > dot + 1 && value[trim - 1] == '0') {
|
|
--trim;
|
|
}
|
|
if (trim == dot + 1) {
|
|
value.erase(dot, suffix - dot);
|
|
} else {
|
|
value.erase(trim, suffix - trim);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
inline std::string literal_sql(const std::string& value) { return quote_string(value); }
|
|
inline std::string literal_sql(std::string_view value) { return quote_string(value); }
|
|
inline std::string literal_sql(const char* value) { return quote_string(value == nullptr ? "" : value); }
|
|
inline std::string literal_sql(bool value) { return value ? "TRUE" : "FALSE"; }
|
|
inline std::string literal_sql(const Identity& value) { return "0x" + value.to_hex_string(); }
|
|
inline std::string literal_sql(const ConnectionId& value) { return "0x" + value.to_string(); }
|
|
inline std::string literal_sql(const Timestamp& value) { return quote_string(trim_timestamp_fraction(value.to_string())); }
|
|
inline std::string literal_sql(const TimeDuration&) = delete;
|
|
inline std::string literal_sql(const std::vector<uint8_t>& value) {
|
|
std::ostringstream out;
|
|
out << "0x" << std::hex << std::setfill('0');
|
|
for (uint8_t byte : value) {
|
|
out << std::setw(2) << static_cast<unsigned>(byte);
|
|
}
|
|
return out.str();
|
|
}
|
|
inline std::string literal_sql(const u128& value) { return value.to_string(); }
|
|
inline std::string literal_sql(const i128& value) { return value.to_string(); }
|
|
inline std::string literal_sql(const u256& value) { return value.to_string(); }
|
|
inline std::string literal_sql(const i256& value) { return value.to_string(); }
|
|
|
|
template<typename TFloat>
|
|
inline std::string format_floating_point(TFloat value) {
|
|
char buffer[64];
|
|
const auto result = std::to_chars(buffer, buffer + sizeof(buffer), value, std::chars_format::general);
|
|
if (result.ec == std::errc{}) {
|
|
return std::string(buffer, result.ptr);
|
|
}
|
|
|
|
std::ostringstream out;
|
|
out.imbue(std::locale::classic());
|
|
out << std::setprecision(std::numeric_limits<TFloat>::max_digits10);
|
|
out << value;
|
|
return out.str();
|
|
}
|
|
|
|
inline std::string literal_sql(float value) {
|
|
return format_floating_point(value);
|
|
}
|
|
|
|
inline std::string literal_sql(double value) {
|
|
return format_floating_point(value);
|
|
}
|
|
|
|
template<typename TValue>
|
|
std::string literal_sql(const TValue& value)
|
|
requires(std::is_integral_v<TValue> && !std::is_same_v<std::remove_cv_t<TValue>, bool>)
|
|
{
|
|
return std::to_string(value);
|
|
}
|
|
|
|
template<typename TRow>
|
|
class Operand {
|
|
public:
|
|
static Operand column(ColumnRef<TRow> column) { return Operand(std::move(column)); }
|
|
static Operand literal(std::string sql) { return Operand(std::move(sql)); }
|
|
|
|
[[nodiscard]] std::string format() const {
|
|
return std::holds_alternative<ColumnRef<TRow>>(value_)
|
|
? std::get<ColumnRef<TRow>>(value_).format()
|
|
: std::get<std::string>(value_);
|
|
}
|
|
|
|
private:
|
|
explicit Operand(ColumnRef<TRow> column) : value_(std::move(column)) {}
|
|
explicit Operand(std::string sql) : value_(std::move(sql)) {}
|
|
|
|
std::variant<ColumnRef<TRow>, std::string> value_;
|
|
};
|
|
|
|
template<typename TRow, typename TValue>
|
|
Operand<TRow> to_operand(const Col<TRow, TValue>& column);
|
|
|
|
template<typename TRow, typename TValue>
|
|
Operand<TRow> to_operand(const TValue& value) {
|
|
return Operand<TRow>::literal(literal_sql(value));
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
template<typename TRow>
|
|
class BoolExpr {
|
|
public:
|
|
enum class Kind {
|
|
Eq,
|
|
Ne,
|
|
Gt,
|
|
Lt,
|
|
Gte,
|
|
Lte,
|
|
And,
|
|
Or,
|
|
Not,
|
|
};
|
|
|
|
static BoolExpr compare(Kind kind, detail::Operand<TRow> lhs, detail::Operand<TRow> rhs) {
|
|
return BoolExpr(std::make_shared<Node>(kind, std::move(lhs), std::move(rhs)));
|
|
}
|
|
|
|
static BoolExpr always(bool value) {
|
|
return compare(
|
|
Kind::Eq,
|
|
detail::Operand<TRow>::literal(value ? "TRUE" : "FALSE"),
|
|
detail::Operand<TRow>::literal("TRUE"));
|
|
}
|
|
|
|
[[nodiscard]] std::string format() const {
|
|
return format_node(root_);
|
|
}
|
|
|
|
[[nodiscard]] BoolExpr and_(const BoolExpr& other) const {
|
|
return BoolExpr(std::make_shared<Node>(Kind::And, root_, other.root_));
|
|
}
|
|
[[nodiscard]] BoolExpr And(const BoolExpr& other) const { return and_(other); }
|
|
|
|
[[nodiscard]] BoolExpr or_(const BoolExpr& other) const {
|
|
return BoolExpr(std::make_shared<Node>(Kind::Or, root_, other.root_));
|
|
}
|
|
[[nodiscard]] BoolExpr Or(const BoolExpr& other) const { return or_(other); }
|
|
|
|
[[nodiscard]] BoolExpr not_() const {
|
|
return BoolExpr(std::make_shared<Node>(Kind::Not, root_, nullptr));
|
|
}
|
|
[[nodiscard]] BoolExpr Not() const { return not_(); }
|
|
|
|
private:
|
|
struct Node;
|
|
|
|
struct CompareData {
|
|
detail::Operand<TRow> lhs;
|
|
detail::Operand<TRow> rhs;
|
|
};
|
|
|
|
struct LogicData {
|
|
std::shared_ptr<const Node> left;
|
|
std::shared_ptr<const Node> right;
|
|
};
|
|
|
|
struct NotData {
|
|
std::shared_ptr<const Node> child;
|
|
};
|
|
|
|
struct Node {
|
|
Node(Kind kind_in, detail::Operand<TRow> lhs_in, detail::Operand<TRow> rhs_in)
|
|
: kind(kind_in), data(CompareData{std::move(lhs_in), std::move(rhs_in)}) {}
|
|
|
|
Node(Kind kind_in, std::shared_ptr<const Node> left_in, std::shared_ptr<const Node> right_in)
|
|
: kind(kind_in),
|
|
data(kind_in == Kind::Not
|
|
? NodeData(NotData{std::move(left_in)})
|
|
: NodeData(LogicData{std::move(left_in), std::move(right_in)})) {}
|
|
|
|
Kind kind;
|
|
using NodeData = std::variant<CompareData, LogicData, NotData>;
|
|
NodeData data;
|
|
};
|
|
|
|
explicit BoolExpr(std::shared_ptr<const Node> root)
|
|
: root_(std::move(root)) {}
|
|
|
|
static std::string format_node(const std::shared_ptr<const Node>& node) {
|
|
switch (node->kind) {
|
|
case Kind::Eq: {
|
|
const auto& compare = std::get<CompareData>(node->data);
|
|
return "(" + compare.lhs.format() + " = " + compare.rhs.format() + ")";
|
|
}
|
|
case Kind::Ne: {
|
|
const auto& compare = std::get<CompareData>(node->data);
|
|
return "(" + compare.lhs.format() + " <> " + compare.rhs.format() + ")";
|
|
}
|
|
case Kind::Gt: {
|
|
const auto& compare = std::get<CompareData>(node->data);
|
|
return "(" + compare.lhs.format() + " > " + compare.rhs.format() + ")";
|
|
}
|
|
case Kind::Lt: {
|
|
const auto& compare = std::get<CompareData>(node->data);
|
|
return "(" + compare.lhs.format() + " < " + compare.rhs.format() + ")";
|
|
}
|
|
case Kind::Gte: {
|
|
const auto& compare = std::get<CompareData>(node->data);
|
|
return "(" + compare.lhs.format() + " >= " + compare.rhs.format() + ")";
|
|
}
|
|
case Kind::Lte: {
|
|
const auto& compare = std::get<CompareData>(node->data);
|
|
return "(" + compare.lhs.format() + " <= " + compare.rhs.format() + ")";
|
|
}
|
|
case Kind::And: {
|
|
const auto& logic = std::get<LogicData>(node->data);
|
|
return "(" + format_node(logic.left) + " AND " + format_node(logic.right) + ")";
|
|
}
|
|
case Kind::Or: {
|
|
const auto& logic = std::get<LogicData>(node->data);
|
|
return "(" + format_node(logic.left) + " OR " + format_node(logic.right) + ")";
|
|
}
|
|
case Kind::Not: {
|
|
const auto& not_data = std::get<NotData>(node->data);
|
|
return "(NOT " + format_node(not_data.child) + ")";
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::shared_ptr<const Node> root_;
|
|
};
|
|
|
|
namespace detail {
|
|
|
|
template<typename TRow>
|
|
BoolExpr<TRow> make_bool_expr(BoolExpr<TRow> expr) {
|
|
return expr;
|
|
}
|
|
|
|
template<typename TRow>
|
|
BoolExpr<TRow> make_bool_expr(bool value) {
|
|
return BoolExpr<TRow>::always(value);
|
|
}
|
|
|
|
template<typename TRow>
|
|
BoolExpr<TRow> make_bool_expr(const Col<TRow, bool>& column) {
|
|
return column.eq(true);
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
template<typename TRow, typename TValue>
|
|
class Col {
|
|
public:
|
|
constexpr Col() = default;
|
|
|
|
constexpr Col(const char* table_name, const char* column_name)
|
|
: column_(table_name, column_name) {}
|
|
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> eq(const TRhs& rhs) const { return compare(BoolExpr<TRow>::Kind::Eq, rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> Eq(const TRhs& rhs) const { return eq(rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> ne(const TRhs& rhs) const { return compare(BoolExpr<TRow>::Kind::Ne, rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> Ne(const TRhs& rhs) const { return ne(rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> gt(const TRhs& rhs) const { return compare(BoolExpr<TRow>::Kind::Gt, rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> Gt(const TRhs& rhs) const { return gt(rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> lt(const TRhs& rhs) const { return compare(BoolExpr<TRow>::Kind::Lt, rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> Lt(const TRhs& rhs) const { return lt(rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> gte(const TRhs& rhs) const { return compare(BoolExpr<TRow>::Kind::Gte, rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> Gte(const TRhs& rhs) const { return gte(rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> lte(const TRhs& rhs) const { return compare(BoolExpr<TRow>::Kind::Lte, rhs); }
|
|
template<typename TRhs>
|
|
requires(is_rhs_for_value_v<TValue, TRhs>)
|
|
[[nodiscard]] BoolExpr<TRow> Lte(const TRhs& rhs) const { return lte(rhs); }
|
|
|
|
// Keep incompatible non-column RHS values on a dedicated overload so they
|
|
// fail with the same diagnostic shape as mismatched column comparisons.
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto eq(const TRhs&) const {
|
|
static_assert(is_rhs_for_value_v<TValue, TRhs>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto Eq(const TRhs& rhs) const { return eq(rhs); }
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto ne(const TRhs&) const {
|
|
static_assert(is_rhs_for_value_v<TValue, TRhs>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto Ne(const TRhs& rhs) const { return ne(rhs); }
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto gt(const TRhs&) const {
|
|
static_assert(is_rhs_for_value_v<TValue, TRhs>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto Gt(const TRhs& rhs) const { return gt(rhs); }
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto lt(const TRhs&) const {
|
|
static_assert(is_rhs_for_value_v<TValue, TRhs>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto Lt(const TRhs& rhs) const { return lt(rhs); }
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto gte(const TRhs&) const {
|
|
static_assert(is_rhs_for_value_v<TValue, TRhs>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto Gte(const TRhs& rhs) const { return gte(rhs); }
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto lte(const TRhs&) const {
|
|
static_assert(is_rhs_for_value_v<TValue, TRhs>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TRhs>
|
|
requires(!is_rhs_for_value_v<TValue, TRhs> && !is_col<std::remove_cvref_t<TRhs>>::value)
|
|
[[nodiscard]] auto Lte(const TRhs& rhs) const { return lte(rhs); }
|
|
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> eq(const Col<TRow, TOtherValue>& rhs) const { return compare(BoolExpr<TRow>::Kind::Eq, rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> Eq(const Col<TRow, TOtherValue>& rhs) const { return eq(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> ne(const Col<TRow, TOtherValue>& rhs) const { return compare(BoolExpr<TRow>::Kind::Ne, rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> Ne(const Col<TRow, TOtherValue>& rhs) const { return ne(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> gt(const Col<TRow, TOtherValue>& rhs) const { return compare(BoolExpr<TRow>::Kind::Gt, rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> Gt(const Col<TRow, TOtherValue>& rhs) const { return gt(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> lt(const Col<TRow, TOtherValue>& rhs) const { return compare(BoolExpr<TRow>::Kind::Lt, rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> Lt(const Col<TRow, TOtherValue>& rhs) const { return lt(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> gte(const Col<TRow, TOtherValue>& rhs) const { return compare(BoolExpr<TRow>::Kind::Gte, rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> Gte(const Col<TRow, TOtherValue>& rhs) const { return gte(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> lte(const Col<TRow, TOtherValue>& rhs) const { return compare(BoolExpr<TRow>::Kind::Lte, rhs); }
|
|
template<typename TOtherValue>
|
|
requires(std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] BoolExpr<TRow> Lte(const Col<TRow, TOtherValue>& rhs) const { return lte(rhs); }
|
|
|
|
// Keep mismatched column-to-column comparisons on a dedicated overload so
|
|
// they fail here with a clear diagnostic instead of disappearing into the
|
|
// generic operand-conversion path.
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto eq(const Col<TRow, TOtherValue>&) const {
|
|
static_assert(std::is_same_v<TValue, TOtherValue>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto Eq(const Col<TRow, TOtherValue>& rhs) const { return eq(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto ne(const Col<TRow, TOtherValue>&) const {
|
|
static_assert(std::is_same_v<TValue, TOtherValue>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto Ne(const Col<TRow, TOtherValue>& rhs) const { return ne(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto gt(const Col<TRow, TOtherValue>&) const {
|
|
static_assert(std::is_same_v<TValue, TOtherValue>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto Gt(const Col<TRow, TOtherValue>& rhs) const { return gt(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto lt(const Col<TRow, TOtherValue>&) const {
|
|
static_assert(std::is_same_v<TValue, TOtherValue>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto Lt(const Col<TRow, TOtherValue>& rhs) const { return lt(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto gte(const Col<TRow, TOtherValue>&) const {
|
|
static_assert(std::is_same_v<TValue, TOtherValue>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto Gte(const Col<TRow, TOtherValue>& rhs) const { return gte(rhs); }
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto lte(const Col<TRow, TOtherValue>&) const {
|
|
static_assert(std::is_same_v<TValue, TOtherValue>, "Column comparison requires both sides to have the same value type.");
|
|
return BoolExpr<TRow>::always(false);
|
|
}
|
|
template<typename TOtherValue>
|
|
requires(!std::is_same_v<TValue, TOtherValue>)
|
|
[[nodiscard]] auto Lte(const Col<TRow, TOtherValue>& rhs) const { return lte(rhs); }
|
|
|
|
[[nodiscard]] constexpr const ColumnRef<TRow>& column_ref() const { return column_; }
|
|
|
|
private:
|
|
template<typename TRhs>
|
|
[[nodiscard]] BoolExpr<TRow> compare(typename BoolExpr<TRow>::Kind kind, const TRhs& rhs) const {
|
|
return BoolExpr<TRow>::compare(kind, detail::to_operand<TRow>(*this), detail::to_operand<TRow>(rhs));
|
|
}
|
|
|
|
ColumnRef<TRow> column_;
|
|
};
|
|
|
|
template<typename TRow, typename TValue>
|
|
struct is_col<Col<TRow, TValue>> : std::true_type {};
|
|
|
|
namespace detail {
|
|
|
|
template<typename TRow, typename TValue>
|
|
Operand<TRow> to_operand(const Col<TRow, TValue>& column) {
|
|
return Operand<TRow>::column(column.column_ref());
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
} // namespace SpacetimeDB::query_builder
|