#pragma once #include "spacetimedb/bsatn/traits.h" #include "spacetimedb/query_builder/expr.h" #include #include #include #include #include #ifndef SPACETIMEDB_QUERY_BUILDER_ENABLE_BSATN #define SPACETIMEDB_QUERY_BUILDER_ENABLE_BSATN 1 #endif #ifndef SPACETIMEDB_QUERY_BUILDER_ENABLE_INDEXED_WHERE #define SPACETIMEDB_QUERY_BUILDER_ENABLE_INDEXED_WHERE 0 #endif namespace SpacetimeDB::query_builder { template struct query_row_type; template class Table; template class FromWhere; template struct HasCols; template struct HasIxCols; template struct CanBeLookupTable : std::false_type {}; template using query_row_type_t = typename query_row_type>::type; template class RawQuery { public: using row_type = TRow; explicit RawQuery(std::string sql) : sql_(std::move(sql)) {} template requires(!std::same_as, RawQuery> && requires { typename query_row_type_t; } && std::same_as, TRow> && requires(TQuery&& query) { { std::forward(query).into_sql() } -> std::convertible_to; }) RawQuery(TQuery&& query) : sql_(std::forward(query).into_sql()) {} [[nodiscard]] const std::string& sql() const { return sql_; } [[nodiscard]] std::string into_sql() const { return sql_; } private: std::string sql_; }; template concept QueryLike = requires(const T& query) { { query.into_sql() } -> std::convertible_to; }; template concept QueryBuilderReturn = requires { typename query_row_type_t; } && QueryLike>; namespace detail { template struct row_tag {}; template constexpr void assert_where_predicate_is_column_only() { static_assert( std::is_invocable_v, "where() predicates must accept only table columns. Indexed columns are only available in semijoin predicates."); } inline std::false_type lookup_table_allowed(...); template auto adl_lookup_table_allowed(int) -> decltype(lookup_table_allowed(row_tag{})); template std::false_type adl_lookup_table_allowed(...); } // namespace detail template inline constexpr bool can_be_lookup_row_v = CanBeLookupTable::value || decltype(detail::adl_lookup_table_allowed(0))::value; template inline constexpr bool can_be_lookup_table_v = CanBeLookupTable>::value; template struct CanBeLookupTable> : std::bool_constant> {}; template class Table { public: using row_type = TRow; using cols_type = TCols; using ix_cols_type = TIxCols; constexpr Table(const char* table_name, TCols cols, TIxCols ix_cols) : table_name_(table_name), cols_(std::move(cols)), ix_cols_(std::move(ix_cols)) {} [[nodiscard]] constexpr const char* name() const { return table_name_; } [[nodiscard]] constexpr const TCols& cols() const { return cols_; } [[nodiscard]] constexpr const TIxCols& ix_cols() const { return ix_cols_; } [[nodiscard]] RawQuery build() const { std::string sql; sql.reserve(16 + std::char_traits::length(table_name_)); sql += "SELECT * FROM \""; sql += table_name_; sql += "\""; return RawQuery(std::move(sql)); } [[nodiscard]] std::string into_sql() const { return build().into_sql(); } // `where` is the ergonomic entry point. Normal C++ predicates receive only // columns; indexed columns are reserved for joins unless explicitly enabled. template [[nodiscard]] auto where(TFn&& predicate) const { if constexpr (SPACETIMEDB_QUERY_BUILDER_ENABLE_INDEXED_WHERE && std::is_invocable_v) { return where_ix(std::forward(predicate)); } else { detail::assert_where_predicate_is_column_only(); return where_col(std::forward(predicate)); } } template [[nodiscard]] auto Where(TFn&& predicate) const { return where(std::forward(predicate)); } template [[nodiscard]] auto filter(TFn&& predicate) const { return where(std::forward(predicate)); } template [[nodiscard]] auto Filter(TFn&& predicate) const { return where(std::forward(predicate)); } template [[nodiscard]] auto left_semijoin(const Table& right, TFn&& predicate) const; template [[nodiscard]] auto LeftSemijoin(const Table& right, TFn&& predicate) const { return left_semijoin(right, std::forward(predicate)); } template [[nodiscard]] auto right_semijoin(const Table& right, TFn&& predicate) const; template [[nodiscard]] auto RightSemijoin(const Table& right, TFn&& predicate) const { return right_semijoin(right, std::forward(predicate)); } private: template [[nodiscard]] auto where_col(TFn&& predicate) const { auto expr = detail::make_bool_expr(std::forward(predicate)(cols_)); return FromWhere(*this, std::move(expr)); } template [[nodiscard]] auto where_ix(TFn&& predicate) const { auto expr = detail::make_bool_expr(std::forward(predicate)(cols_, ix_cols_)); return FromWhere(*this, std::move(expr)); } const char* table_name_; TCols cols_; TIxCols ix_cols_; }; template class FromWhere { public: using row_type = TRow; using cols_type = TCols; using ix_cols_type = TIxCols; constexpr FromWhere(Table table, BoolExpr expr) : table_(std::move(table)), expr_(std::move(expr)) {} [[nodiscard]] constexpr const char* table_name() const { return table_.name(); } [[nodiscard]] constexpr const Table& table() const { return table_; } [[nodiscard]] const BoolExpr& expr() const { return expr_; } [[nodiscard]] RawQuery build() const { std::string predicate = expr_.format(); std::string sql; sql.reserve(24 + std::char_traits::length(table_.name()) + predicate.size()); sql += "SELECT * FROM \""; sql += table_.name(); sql += "\" WHERE "; sql += predicate; return RawQuery(std::move(sql)); } [[nodiscard]] std::string into_sql() const { return build().into_sql(); } // `where` is the ergonomic entry point. Normal C++ predicates receive only // columns; indexed columns are reserved for joins unless explicitly enabled. template [[nodiscard]] FromWhere where(TFn&& predicate) const { if constexpr (SPACETIMEDB_QUERY_BUILDER_ENABLE_INDEXED_WHERE && std::is_invocable_v) { return where_ix(std::forward(predicate)); } else { detail::assert_where_predicate_is_column_only(); return where_col(std::forward(predicate)); } } template [[nodiscard]] FromWhere Where(TFn&& predicate) const { return where(std::forward(predicate)); } template [[nodiscard]] FromWhere filter(TFn&& predicate) const { return where(std::forward(predicate)); } template [[nodiscard]] FromWhere Filter(TFn&& predicate) const { return where(std::forward(predicate)); } template [[nodiscard]] auto left_semijoin(const Table& right, TFn&& predicate) const; template [[nodiscard]] auto LeftSemijoin(const Table& right, TFn&& predicate) const { return left_semijoin(right, std::forward(predicate)); } template [[nodiscard]] auto right_semijoin(const Table& right, TFn&& predicate) const; template [[nodiscard]] auto RightSemijoin(const Table& right, TFn&& predicate) const { return right_semijoin(right, std::forward(predicate)); } private: template [[nodiscard]] FromWhere where_col(TFn&& predicate) const { auto extra = detail::make_bool_expr(std::forward(predicate)(table_.cols())); return FromWhere(table_, expr_.and_(extra)); } template [[nodiscard]] FromWhere where_ix(TFn&& predicate) const { auto extra = detail::make_bool_expr(std::forward(predicate)(table_.cols(), table_.ix_cols())); return FromWhere(table_, expr_.and_(extra)); } Table table_; BoolExpr expr_; }; template struct query_row_type> { using type = TRow; }; template struct query_row_type> { using type = TRow; }; template struct query_row_type> { using type = TRow; }; } // namespace SpacetimeDB::query_builder #if SPACETIMEDB_QUERY_BUILDER_ENABLE_BSATN namespace SpacetimeDB::bsatn { template struct algebraic_type_of<::SpacetimeDB::query_builder::RawQuery> { static AlgebraicType get() { std::vector elements; elements.emplace_back(std::string("__query__"), algebraic_type_of::get()); return AlgebraicType::make_product(std::make_unique(std::move(elements))); } }; template struct bsatn_traits<::SpacetimeDB::query_builder::RawQuery> { static void serialize(Writer&, const ::SpacetimeDB::query_builder::RawQuery&) { std::fputs("SpacetimeDB bindings-cpp internal error: attempted to BSATN-serialize query_builder::RawQuery. " "RawQuery is only valid as a view return type and should not be serialized directly.\n", stderr); std::abort(); } static ::SpacetimeDB::query_builder::RawQuery deserialize(Reader&) { std::fputs("SpacetimeDB bindings-cpp internal error: attempted to BSATN-deserialize query_builder::RawQuery. " "RawQuery should only appear in query-view metadata handling.\n", stderr); std::abort(); } static AlgebraicType algebraic_type() { return algebraic_type_of<::SpacetimeDB::query_builder::RawQuery>::get(); } }; } // namespace SpacetimeDB::bsatn #endif