mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-06 15:49:35 -04:00
Expand negative testing and fixed issues around brittle checks
This commit is contained in:
@@ -21,6 +21,14 @@ 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:
|
||||
@@ -314,30 +322,194 @@ public:
|
||||
: 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:
|
||||
@@ -349,6 +521,9 @@ private:
|
||||
ColumnRef<TRow> column_;
|
||||
};
|
||||
|
||||
template<typename TRow, typename TValue>
|
||||
struct is_col<Col<TRow, TValue>> : std::true_type {};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename TRow, typename TValue>
|
||||
|
||||
@@ -13,6 +13,12 @@ struct IxJoinEq;
|
||||
template<typename TRow, auto MemberPtr>
|
||||
struct member_tag {};
|
||||
|
||||
template<typename T>
|
||||
struct is_ix_col : std::false_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_ix_join_eq : std::false_type {};
|
||||
|
||||
inline std::false_type indexed_member_lookup(...);
|
||||
|
||||
template<typename TRow, auto MemberPtr>
|
||||
@@ -36,12 +42,27 @@ public:
|
||||
return eq(rhs);
|
||||
}
|
||||
|
||||
// Keep mismatched indexed-column comparisons on a dedicated overload so they
|
||||
// fail here with a clear diagnostic instead of falling through to BoolExpr.
|
||||
template<typename TOtherRow, typename TOtherValue>
|
||||
[[nodiscard]] auto eq(const IxCol<TOtherRow, TOtherValue>&) const {
|
||||
static_assert(std::is_same_v<TValue, TOtherValue>, "Semijoin indexed equality requires both sides to have the same value type.");
|
||||
return IxJoinEq<TRow, TOtherRow, TValue>{};
|
||||
}
|
||||
|
||||
template<typename TOtherRow, typename TOtherValue>
|
||||
[[nodiscard]] auto Eq(const IxCol<TOtherRow, TOtherValue>& rhs) const {
|
||||
return eq(rhs);
|
||||
}
|
||||
|
||||
template<typename TRhs>
|
||||
requires(!is_ix_col<std::remove_cvref_t<TRhs>>::value)
|
||||
[[nodiscard]] BoolExpr<TRow> eq(const TRhs& rhs) const {
|
||||
return compare(BoolExpr<TRow>::Kind::Eq, rhs);
|
||||
}
|
||||
|
||||
template<typename TRhs>
|
||||
requires(!is_ix_col<std::remove_cvref_t<TRhs>>::value)
|
||||
[[nodiscard]] BoolExpr<TRow> Eq(const TRhs& rhs) const { return eq(rhs); }
|
||||
|
||||
[[nodiscard]] constexpr const ColumnRef<TRow>& column_ref() const { return column_; }
|
||||
@@ -58,6 +79,9 @@ private:
|
||||
friend class IxCol;
|
||||
};
|
||||
|
||||
template<typename TRow, typename TValue>
|
||||
struct is_ix_col<IxCol<TRow, TValue>> : std::true_type {};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename, typename TRow, auto MemberPtr>
|
||||
@@ -71,6 +95,9 @@ struct IxJoinEq {
|
||||
ColumnRef<TRightRow> rhs;
|
||||
};
|
||||
|
||||
template<typename TLeftRow, typename TRightRow, typename TValue>
|
||||
struct is_ix_join_eq<IxJoinEq<TLeftRow, TRightRow, TValue>> : std::true_type {};
|
||||
|
||||
template<typename TLeftRow, typename TLeftCols, typename TLeftIxCols, typename TRightRow, typename TRightCols, typename TRightIxCols>
|
||||
class LeftSemiJoin {
|
||||
public:
|
||||
@@ -269,28 +296,52 @@ template<typename TLeftRow, typename TLeftCols, typename TLeftIxCols, typename T
|
||||
[[nodiscard]] auto left_semijoin_impl(const Table<TLeftRow, TLeftCols, TLeftIxCols>& left, const Table<TRightRow, TRightCols, TRightIxCols>& right, TFn&& predicate) {
|
||||
static_assert(can_be_lookup_table_v<Table<TRightRow, TRightCols, TRightIxCols>>, "Lookup side of a semijoin must opt in via CanBeLookupTable.");
|
||||
const auto join = std::forward<TFn>(predicate)(left.ix_cols(), right.ix_cols());
|
||||
return LeftSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left, right, join.lhs, join.rhs);
|
||||
using TJoin = std::remove_cvref_t<decltype(join)>;
|
||||
if constexpr (is_ix_join_eq<TJoin>::value) {
|
||||
return LeftSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left, right, join.lhs, join.rhs);
|
||||
} else {
|
||||
static_assert(is_ix_join_eq<TJoin>::value, "Semijoin predicate must compare two indexed columns with eq().");
|
||||
return LeftSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left, right, {}, {});
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TLeftRow, typename TLeftCols, typename TLeftIxCols, typename TRightRow, typename TRightCols, typename TRightIxCols, typename TFn>
|
||||
[[nodiscard]] auto left_semijoin_impl(const FromWhere<TLeftRow, TLeftCols, TLeftIxCols>& left, const Table<TRightRow, TRightCols, TRightIxCols>& right, TFn&& predicate) {
|
||||
static_assert(can_be_lookup_table_v<Table<TRightRow, TRightCols, TRightIxCols>>, "Lookup side of a semijoin must opt in via CanBeLookupTable.");
|
||||
const auto join = std::forward<TFn>(predicate)(left.table().ix_cols(), right.ix_cols());
|
||||
return LeftSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left.table(), right, join.lhs, join.rhs, left.expr());
|
||||
using TJoin = std::remove_cvref_t<decltype(join)>;
|
||||
if constexpr (is_ix_join_eq<TJoin>::value) {
|
||||
return LeftSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left.table(), right, join.lhs, join.rhs, left.expr());
|
||||
} else {
|
||||
static_assert(is_ix_join_eq<TJoin>::value, "Semijoin predicate must compare two indexed columns with eq().");
|
||||
return LeftSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left.table(), right, {}, {}, left.expr());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TLeftRow, typename TLeftCols, typename TLeftIxCols, typename TRightRow, typename TRightCols, typename TRightIxCols, typename TFn>
|
||||
[[nodiscard]] auto right_semijoin_impl(const Table<TLeftRow, TLeftCols, TLeftIxCols>& left, const Table<TRightRow, TRightCols, TRightIxCols>& right, TFn&& predicate) {
|
||||
static_assert(can_be_lookup_table_v<Table<TRightRow, TRightCols, TRightIxCols>>, "Lookup side of a semijoin must opt in via CanBeLookupTable.");
|
||||
const auto join = std::forward<TFn>(predicate)(left.ix_cols(), right.ix_cols());
|
||||
return RightSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left, right, join.lhs, join.rhs);
|
||||
using TJoin = std::remove_cvref_t<decltype(join)>;
|
||||
if constexpr (is_ix_join_eq<TJoin>::value) {
|
||||
return RightSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left, right, join.lhs, join.rhs);
|
||||
} else {
|
||||
static_assert(is_ix_join_eq<TJoin>::value, "Semijoin predicate must compare two indexed columns with eq().");
|
||||
return RightSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left, right, {}, {});
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TLeftRow, typename TLeftCols, typename TLeftIxCols, typename TRightRow, typename TRightCols, typename TRightIxCols, typename TFn>
|
||||
[[nodiscard]] auto right_semijoin_impl(const FromWhere<TLeftRow, TLeftCols, TLeftIxCols>& left, const Table<TRightRow, TRightCols, TRightIxCols>& right, TFn&& predicate) {
|
||||
static_assert(can_be_lookup_table_v<Table<TRightRow, TRightCols, TRightIxCols>>, "Lookup side of a semijoin must opt in via CanBeLookupTable.");
|
||||
const auto join = std::forward<TFn>(predicate)(left.table().ix_cols(), right.ix_cols());
|
||||
return RightSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left.table(), right, join.lhs, join.rhs, left.expr());
|
||||
using TJoin = std::remove_cvref_t<decltype(join)>;
|
||||
if constexpr (is_ix_join_eq<TJoin>::value) {
|
||||
return RightSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left.table(), right, join.lhs, join.rhs, left.expr());
|
||||
} else {
|
||||
static_assert(is_ix_join_eq<TJoin>::value, "Semijoin predicate must compare two indexed columns with eq().");
|
||||
return RightSemiJoin<TLeftRow, TLeftCols, TLeftIxCols, TRightRow, TRightCols, TRightIxCols>(left.table(), right, {}, {}, left.expr());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <spacetimedb.h>
|
||||
|
||||
using namespace SpacetimeDB;
|
||||
|
||||
template<typename TRow>
|
||||
auto TableFor(const char* table_name) {
|
||||
return QueryBuilder{}.table<TRow>(
|
||||
table_name,
|
||||
query_builder::HasCols<TRow>::get(table_name),
|
||||
query_builder::HasIxCols<TRow>::get(table_name));
|
||||
}
|
||||
|
||||
struct PlayerInfo {
|
||||
uint8_t age;
|
||||
};
|
||||
SPACETIMEDB_STRUCT(PlayerInfo, age)
|
||||
SPACETIMEDB_TABLE(PlayerInfo, player_info, Public)
|
||||
|
||||
auto invalid_filter = TableFor<PlayerInfo>("player_info").where([](const auto& players) {
|
||||
return players.age.eq(4200);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <spacetimedb.h>
|
||||
|
||||
using namespace SpacetimeDB;
|
||||
|
||||
template<typename TRow>
|
||||
auto TableFor(const char* table_name) {
|
||||
return QueryBuilder{}.table<TRow>(
|
||||
table_name,
|
||||
query_builder::HasCols<TRow>::get(table_name),
|
||||
query_builder::HasIxCols<TRow>::get(table_name));
|
||||
}
|
||||
|
||||
struct User {
|
||||
Identity identity;
|
||||
};
|
||||
SPACETIMEDB_STRUCT(User, identity)
|
||||
SPACETIMEDB_TABLE(User, user, Public)
|
||||
FIELD_PrimaryKey(user, identity)
|
||||
|
||||
struct Membership {
|
||||
Identity membership_identity;
|
||||
uint64_t tenant_id;
|
||||
};
|
||||
SPACETIMEDB_STRUCT(Membership, membership_identity, tenant_id)
|
||||
SPACETIMEDB_TABLE(Membership, membership, Public)
|
||||
FIELD_PrimaryKey(membership, membership_identity)
|
||||
FIELD_Index(membership, tenant_id)
|
||||
|
||||
auto invalid_join = TableFor<User>("user").right_semijoin(
|
||||
TableFor<Membership>("membership"),
|
||||
[](const auto& users, const auto& memberships) {
|
||||
return users.identity.eq(memberships.tenant_id);
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <spacetimedb.h>
|
||||
|
||||
using namespace SpacetimeDB;
|
||||
|
||||
template<typename TRow>
|
||||
auto TableFor(const char* table_name) {
|
||||
return QueryBuilder{}.table<TRow>(
|
||||
table_name,
|
||||
query_builder::HasCols<TRow>::get(table_name),
|
||||
query_builder::HasIxCols<TRow>::get(table_name));
|
||||
}
|
||||
|
||||
struct User {
|
||||
Identity identity;
|
||||
uint64_t tenant_id;
|
||||
};
|
||||
SPACETIMEDB_STRUCT(User, identity, tenant_id)
|
||||
SPACETIMEDB_TABLE(User, user, Public)
|
||||
FIELD_PrimaryKey(user, identity)
|
||||
|
||||
auto invalid_filter = TableFor<User>("user").where([](const auto& users) {
|
||||
return users.identity.eq(users.tenant_id);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <spacetimedb.h>
|
||||
|
||||
using namespace SpacetimeDB;
|
||||
|
||||
template<typename TRow>
|
||||
auto TableFor(const char* table_name) {
|
||||
return QueryBuilder{}.table<TRow>(
|
||||
table_name,
|
||||
query_builder::HasCols<TRow>::get(table_name),
|
||||
query_builder::HasIxCols<TRow>::get(table_name));
|
||||
}
|
||||
|
||||
struct User {
|
||||
uint64_t id;
|
||||
};
|
||||
SPACETIMEDB_STRUCT(User, id)
|
||||
SPACETIMEDB_TABLE(User, user, Public)
|
||||
FIELD_PrimaryKey(user, id)
|
||||
|
||||
struct Membership {
|
||||
uint64_t id;
|
||||
uint64_t user_id;
|
||||
};
|
||||
SPACETIMEDB_STRUCT(Membership, id, user_id)
|
||||
SPACETIMEDB_TABLE(Membership, membership, Public)
|
||||
FIELD_PrimaryKey(membership, id)
|
||||
FIELD_Index(membership, user_id)
|
||||
|
||||
auto invalid_join = TableFor<User>("user").right_semijoin(
|
||||
TableFor<Membership>("membership"),
|
||||
[](const auto& users, const auto& memberships) {
|
||||
return users.id.eq(1ULL);
|
||||
});
|
||||
@@ -95,7 +95,11 @@ compile_should_fail() {
|
||||
}
|
||||
|
||||
compile_should_pass "$SCRIPT_DIR/pass_query_integration.cpp"
|
||||
compile_should_fail "$SCRIPT_DIR/fail_invalid_join_predicate.cpp" "Semijoin predicate must compare two indexed columns with eq()."
|
||||
compile_should_fail "$SCRIPT_DIR/fail_incompatible_where_types.cpp" "Column comparison requires both sides to have the same value type."
|
||||
compile_should_fail "$SCRIPT_DIR/fail_implicit_numeric_where_types.cpp" "Column comparison requires both sides to have the same value type."
|
||||
compile_should_fail "$SCRIPT_DIR/fail_non_index_join.cpp" "no member named 'tenant_id'"
|
||||
compile_should_fail "$SCRIPT_DIR/fail_incompatible_join_types.cpp" "Semijoin indexed equality requires both sides to have the same value type."
|
||||
compile_should_fail "$SCRIPT_DIR/fail_event_lookup.cpp" "Lookup side of a semijoin must opt in via CanBeLookupTable."
|
||||
|
||||
echo "All query-builder compile tests passed"
|
||||
|
||||
Reference in New Issue
Block a user