mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-07-25 03:22:43 -04:00
cf73acff92
# Description of Changes Adds primary keys to procedural views in C++. This mirrors the work from #5111, #5246, and #5327 adding the feature and the docs changes. # API and ABI breaking changes None # Expected complexity level and risk 3 # Testing - [x] Equivalent tests as were added in #5111 and #5246 for rust, typescript, and C#
172 lines
8.1 KiB
C++
172 lines
8.1 KiB
C++
#pragma once
|
|
|
|
#include "spacetimedb/view_context.h"
|
|
#include "spacetimedb/query_builder.h"
|
|
#include "spacetimedb/internal/Module.h"
|
|
#include "spacetimedb/internal/v10_builder.h"
|
|
#include "spacetimedb/table_with_constraints.h"
|
|
#include "spacetimedb/macros.h" // For parseParameterNames
|
|
#include "spacetimedb/error_handling.h"
|
|
#include <string>
|
|
#include <vector>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace SpacetimeDB {
|
|
namespace Internal {
|
|
|
|
// Note: parseParameterNames is in macros.h and works for any context type
|
|
// (ReducerContext, ViewContext, or AnonymousViewContext)
|
|
|
|
/**
|
|
* @brief Type trait to validate view return types
|
|
*
|
|
* Views must return std::vector<T> or std::optional<T> where T is a SpacetimeType.
|
|
* This is enforced at compile-time via static_assert in the macro.
|
|
*/
|
|
template<typename T>
|
|
struct is_valid_view_return_type_impl : std::false_type {};
|
|
|
|
// Specialization for std::vector<T> where T is Serializable
|
|
template<typename T>
|
|
struct is_valid_view_return_type_impl<std::vector<T>>
|
|
: std::integral_constant<bool, bsatn::Serializable<T>> {};
|
|
|
|
// Specialization for std::optional<T> where T is Serializable
|
|
template<typename T>
|
|
struct is_valid_view_return_type_impl<std::optional<T>>
|
|
: std::integral_constant<bool, bsatn::Serializable<T>> {};
|
|
|
|
template<typename T, typename = void>
|
|
struct is_valid_query_builder_view_return_type : std::false_type {};
|
|
|
|
template<typename T>
|
|
struct is_valid_query_builder_view_return_type<T, std::void_t<query_builder::query_row_type_t<T>>>
|
|
: std::integral_constant<
|
|
bool,
|
|
query_builder::QueryBuilderReturn<T> && bsatn::Serializable<query_builder::query_row_type_t<T>>> {};
|
|
|
|
template<typename T>
|
|
struct is_valid_view_return_type
|
|
: std::conditional_t<
|
|
is_valid_query_builder_view_return_type<T>::value,
|
|
is_valid_query_builder_view_return_type<T>,
|
|
is_valid_view_return_type_impl<T>> {};
|
|
|
|
} // namespace Internal
|
|
} // namespace SpacetimeDB
|
|
|
|
/**
|
|
* @brief Macro for defining SpacetimeDB views
|
|
*
|
|
* CRITICAL: Return type must be explicit (no arrow notation in C++ macros).
|
|
* Views return the specified return_type directly.
|
|
*
|
|
* NOTE: Additional parameters are temporarily disabled as the SpacetimeDB host
|
|
* doesn't fully support parameterized views yet. Only the context parameter is allowed.
|
|
*
|
|
* Allowed return types:
|
|
* - std::vector<T> where T is a SpacetimeType
|
|
* - std::optional<T> where T is a SpacetimeType
|
|
* - Query<T> (or another query-builder return) where T is a SpacetimeType
|
|
*
|
|
* @param return_type The return type (e.g., std::vector<Person>, std::optional<Person>)
|
|
* @param view_name The name of the view function
|
|
* @param access_enum Must be Public (Private views not yet supported)
|
|
* @param ctx_param Must be either ViewContext ctx or AnonymousViewContext ctx
|
|
*
|
|
* Example:
|
|
* @code
|
|
* SPACETIMEDB_VIEW(std::vector<Person>, get_adults, Public, ViewContext ctx) {
|
|
* std::vector<Person> adults;
|
|
* for (const auto& person : ctx.db[person_age].filter(range_from(18u))) {
|
|
* adults.push_back(person);
|
|
* }
|
|
* return adults;
|
|
* }
|
|
*
|
|
* SPACETIMEDB_VIEW(std::optional<uint64_t>, count_people, Public, AnonymousViewContext ctx) {
|
|
* return std::optional<uint64_t>(ctx.db[person].count());
|
|
* }
|
|
*
|
|
* // TODO: Future with parameters:
|
|
* // SPACETIMEDB_VIEW(std::vector<Person>, search_by_age, Public, ViewContext ctx, uint32_t min_age, uint32_t max_age) {
|
|
* // std::vector<Person> results;
|
|
* // for (const auto& person : ctx.db[person_age].filter(range_between(min_age, max_age))) {
|
|
* // results.push_back(person);
|
|
* // }
|
|
* // return Ok(results);
|
|
* // }
|
|
* @endcode
|
|
*/
|
|
/* TODO: When parameters are supported, change signature to:
|
|
* #define SPACETIMEDB_VIEW(return_type, view_name, access_enum, ctx_param, ...)
|
|
*/
|
|
#define SPACETIMEDB_VIEW(return_type, view_name, access_enum, ctx_param) \
|
|
/* Compile-time assertion that views must be Public for now */ \
|
|
static_assert(access_enum == SpacetimeDB::Internal::TableAccess::Public, \
|
|
"Views must be Public - Private views are not yet supported"); \
|
|
\
|
|
/* Validate return type at compile-time */ \
|
|
static_assert(::SpacetimeDB::Internal::is_valid_view_return_type<return_type>::value, \
|
|
"View return type must be std::vector<T>, std::optional<T>, Query<T>, or another query-builder return where T is a SpacetimeType"); \
|
|
\
|
|
/* TODO: When parameters are supported, forward declaration becomes: */ \
|
|
/* return_type view_name(ctx_param, __VA_ARGS__); */ \
|
|
return_type view_name(ctx_param); \
|
|
inline constexpr auto CONCAT(view_name, _view) = ::SpacetimeDB::detail::MakeQuerySourceTag<return_type>(#view_name); \
|
|
\
|
|
/* Preinit registration function */ \
|
|
/* Views run at priority 40 to ensure tables/reducers are registered first */ \
|
|
__attribute__((export_name("__preinit__40_view_" #view_name))) \
|
|
extern "C" void CONCAT(_spacetimedb_preinit_register_view_, view_name)() { \
|
|
/* Convert access_enum to bool (matching SPACETIMEDB_TABLE pattern) */ \
|
|
bool is_public = (access_enum == SpacetimeDB::Internal::TableAccess::Public); \
|
|
\
|
|
/* TODO: When parameters are supported, uncomment: */ \
|
|
/* std::vector<std::string> param_names = parseParameterNames(#__VA_ARGS__); */ \
|
|
std::vector<std::string> param_names; \
|
|
\
|
|
/* Register the view with the V10Builder system */ \
|
|
/* RegisterView validates ctx_param is ViewContext or AnonymousViewContext */ \
|
|
::SpacetimeDB::Internal::getV10Builder().RegisterView<decltype(&view_name)>( \
|
|
#view_name, view_name, is_public, param_names); \
|
|
} \
|
|
\
|
|
/* TODO: When parameters are supported, function definition becomes: */ \
|
|
/* return_type view_name(ctx_param, __VA_ARGS__) */ \
|
|
return_type view_name(ctx_param)
|
|
|
|
#define SPACETIMEDB_VIEW_NAMED(return_type, view_name, canonical_name, access_enum, ctx_param) \
|
|
static_assert(access_enum == SpacetimeDB::Internal::TableAccess::Public, \
|
|
"Views must be Public - Private views are not yet supported"); \
|
|
static_assert(::SpacetimeDB::Internal::is_valid_view_return_type<return_type>::value, \
|
|
"View return type must be std::vector<T>, std::optional<T>, Query<T>, or another query-builder return where T is a SpacetimeType"); \
|
|
return_type view_name(ctx_param); \
|
|
inline constexpr auto CONCAT(view_name, _view) = ::SpacetimeDB::detail::MakeQuerySourceTag<return_type>(canonical_name); \
|
|
__attribute__((export_name("__preinit__40_view_" #view_name))) \
|
|
extern "C" void CONCAT(_spacetimedb_preinit_register_view_, view_name)() { \
|
|
bool is_public = (access_enum == SpacetimeDB::Internal::TableAccess::Public); \
|
|
std::vector<std::string> param_names; \
|
|
::SpacetimeDB::Internal::getV10Builder().RegisterView<decltype(&view_name)>( \
|
|
#view_name, view_name, is_public, param_names); \
|
|
SpacetimeDB::Module::RegisterExplicitFunctionName(#view_name, canonical_name); \
|
|
} \
|
|
return_type view_name(ctx_param)
|
|
|
|
#define VIEW_PrimaryKey(view_name, field_name) \
|
|
static_assert([]() constexpr { \
|
|
using RowType = typename std::remove_cv_t<decltype(view_name##_view)>::type; \
|
|
using FieldType = decltype(std::declval<RowType>().field_name); \
|
|
static_assert(::SpacetimeDB::FilterableValue<FieldType>, \
|
|
"View primary key field '" #field_name "' must have a filterable type."); \
|
|
return true; \
|
|
}(), "View primary key validation for " #view_name "." #field_name); \
|
|
inline std::true_type indexed_member_lookup(::SpacetimeDB::query_builder::member_tag< \
|
|
typename std::remove_cv_t<decltype(view_name##_view)>::type, \
|
|
&std::remove_cv_t<decltype(view_name##_view)>::type::field_name>); \
|
|
extern "C" __attribute__((export_name("__preinit__41_view_primary_key_" #view_name "_" #field_name "_line_" SPACETIMEDB_STRINGIFY(__LINE__)))) \
|
|
void SPACETIMEDB_PASTE(__preinit__41_view_primary_key_, SPACETIMEDB_PASTE(view_name, SPACETIMEDB_PASTE(_, SPACETIMEDB_PASTE(field_name, SPACETIMEDB_PASTE(_line_, __LINE__)))))() { \
|
|
::SpacetimeDB::Module::RegisterViewPrimaryKey(#view_name, std::vector<std::string>{#field_name}); \
|
|
}
|