diff --git a/crates/bindings-cpp/include/spacetimedb/index_iterator.h b/crates/bindings-cpp/include/spacetimedb/index_iterator.h index 4e1b1367908..910cac857fe 100644 --- a/crates/bindings-cpp/include/spacetimedb/index_iterator.h +++ b/crates/bindings-cpp/include/spacetimedb/index_iterator.h @@ -195,6 +195,59 @@ class IndexIterator { return writer.take_buffer(); } + template + void init_prefix_range_scan(IndexId index_id, const std::tuple& values) { + constexpr std::size_t N = sizeof...(FieldTypes); + constexpr std::size_t prefix_count = N - 1; + + SpacetimeDB::bsatn::Writer prefix_writer; + serialize_tuple_prefix(prefix_writer, values, std::make_index_sequence{}); + auto prefix_buffer = prefix_writer.take_buffer(); + + const auto& range = std::get(values); + auto rstart_buffer = serialize_range_start(range); + auto rend_buffer = serialize_range_end(range); + + Status status = FFI::datastore_index_scan_range_bsatn( + index_id, + prefix_buffer.data(), prefix_buffer.size(), ColId{static_cast(prefix_count)}, + rstart_buffer.data(), rstart_buffer.size(), + rend_buffer.data(), rend_buffer.size(), + &iter_handle_ + ); + + if (status != StatusCode::OK) { + std::abort(); // IndexIterator: multi-column prefix+range scan failed + } + } + + template + void init_exact_prefix_scan(IndexId index_id, const std::tuple& values) { + constexpr std::size_t N = sizeof...(FieldTypes); + constexpr std::size_t prefix_count = N - 1; + + SpacetimeDB::bsatn::Writer prefix_writer; + serialize_tuple_prefix(prefix_writer, values, std::make_index_sequence{}); + auto prefix_buffer = prefix_writer.take_buffer(); + + SpacetimeDB::bsatn::Writer bound_writer; + bound_writer.write_u8(0); // Bound::Inclusive + serialize(bound_writer, std::get(values)); + auto bound_buffer = bound_writer.take_buffer(); + + Status status = FFI::datastore_index_scan_range_bsatn( + index_id, + prefix_buffer.data(), prefix_buffer.size(), ColId{static_cast(prefix_count)}, + bound_buffer.data(), bound_buffer.size(), + bound_buffer.data(), bound_buffer.size(), + &iter_handle_ + ); + + if (status != StatusCode::OK) { + std::abort(); // IndexIterator: multi-column exact prefix scan failed + } + } + /** * @brief Create iterator for prefix-only match (N-1 columns specified) * @@ -245,97 +298,45 @@ class IndexIterator { } /** - * @brief Create iterator for prefix match with range on last column - * - * Finds all rows where the first N-1 columns match exactly and the last column - * falls within the specified range. - * - * @example Range on last column - find scores for a player at specific levels: - * @code - * FIELD_NamedMultiColumnIndex(score, by_player_and_level, player_id, level) - * - * // Find scores for player 123 at levels 1-10 - * auto scores = ctx.db[score_by_player_and_level].filter( - * std::make_tuple(uint32_t(123), range_inclusive(1u, 10u)) - * ); - * @endcode - */ - template - IndexIterator(IndexId index_id, const std::tuple& values) - requires (is_range_v) - { - // Serialize prefix value - SpacetimeDB::bsatn::Writer prefix_writer; - serialize(prefix_writer, std::get<0>(values)); - auto prefix_buffer = prefix_writer.take_buffer(); - - // Serialize range as start/end bounds - const auto& range = std::get<1>(values); - auto rstart_buffer = serialize_range_start(range); - auto rend_buffer = serialize_range_end(range); - - // Call FFI with prefix_elems = 1 (only the prefix column) - Status status = FFI::datastore_index_scan_range_bsatn( - index_id, - prefix_buffer.data(), prefix_buffer.size(), ColId{1}, - rstart_buffer.data(), rstart_buffer.size(), - rend_buffer.data(), rend_buffer.size(), - &iter_handle_ - ); - - if (status != StatusCode::OK) { - std::abort(); // IndexIterator: prefix+range match failed - } - advance(); - } - - /** - * @brief Create iterator for multi-column exact match + * @brief Create iterator for multi-column exact prefix or prefix+range scan * - * Efficiently finds all rows where all indexed columns exactly match the tuple values. + * Scans rows where all supplied prefix columns match exactly. If the final + * supplied tuple element is a Range, that range is applied to the final + * supplied column. * * @tparam FieldTypes The types of the indexed fields * @param index_id The multi-column index to scan - * @param values Tuple of values to match (one per column) + * @param values Tuple of exact values optionally terminated by Range * - * @example Multi-column exact match: + * @example Multi-column exact and range matches: * @code * FIELD_NamedMultiColumnIndex(score, by_player_and_level, player_id, level) * * // Find exact score for player 123 at level 5 * auto iter = IndexIterator(index_id, std::make_tuple(uint32_t(123), uint32_t(5))); + * + * // Find scores for player 123 at levels 1-10 + * auto scores = IndexIterator( + * index_id, + * std::make_tuple(uint32_t(123), range_inclusive(1u, 10u)) + * ); * @endcode */ template IndexIterator(IndexId index_id, const std::tuple& values) - requires (sizeof...(FieldTypes) > 1 && sizeof...(FieldTypes) <= 6) + requires (sizeof...(FieldTypes) > 0 && sizeof...(FieldTypes) <= 6) { - constexpr std::size_t N = sizeof...(FieldTypes); - constexpr std::size_t prefix_count = N - 1; - - // Serialize first N-1 elements into prefix buffer - SpacetimeDB::bsatn::Writer prefix_writer; - serialize_tuple_prefix(prefix_writer, values, std::make_index_sequence{}); - auto prefix_buffer = prefix_writer.take_buffer(); - - // Serialize the last element as both start and end bounds (exact match) - SpacetimeDB::bsatn::Writer bound_writer; - bound_writer.write_u8(0); // Bound::Included - serialize(bound_writer, std::get(values)); // Last element only - auto bound_buffer = bound_writer.take_buffer(); - - // Call FFI with prefix_elems = N-1 (as per C# pattern) - Status status = FFI::datastore_index_scan_range_bsatn( - index_id, - prefix_buffer.data(), prefix_buffer.size(), ColId{static_cast(prefix_count)}, - bound_buffer.data(), bound_buffer.size(), // Last value as start - bound_buffer.data(), bound_buffer.size(), // Last value as end - &iter_handle_ + static_assert( + detail::valid_index_scan_tuple_v, + "Range in a multi-column index filter must be the final supplied element" ); - - if (status != StatusCode::OK) { - std::abort(); // IndexIterator: multi-column exact match failed + + if constexpr (detail::last_is_range_arg_v) { + init_prefix_range_scan(index_id, values); + } else { + init_exact_prefix_scan(index_id, values); } + advance(); } @@ -521,12 +522,12 @@ class IndexIterator { // For now we rely on btree scan to handle bounds correctly // Constants for performance tuning - static constexpr size_t INITIAL_ROW_BUFFER_SIZE = 4096; + static constexpr size_t INITIAL_ROW_BUFFER_SIZE = 128 * 1024; static constexpr size_t MAX_ROW_BUFFER_SIZE = 1024 * 1024; static constexpr size_t TYPICAL_BATCH_SIZE = 32; static constexpr int16_t ITER_EXHAUSTED = -1; static constexpr int16_t ITER_OK = 0; - static constexpr uint16_t ERROR_BUFFER_TOO_SMALL = 3; + static constexpr int16_t ERROR_BUFFER_TOO_SMALL = static_cast(StatusCode::BUFFER_TOO_SMALL.inner); // Helper to serialize tuple elements without treating tuple as a type template @@ -586,13 +587,19 @@ class IndexIterator { return; } - if (ret == ERROR_BUFFER_TOO_SMALL) { - if (buffer_len > MAX_ROW_BUFFER_SIZE) { + while (ret == ERROR_BUFFER_TOO_SMALL) { + const size_t requested_size = buffer_len; + size_t new_size = requested_size; + if (new_size <= row_buffer_.size()) { + new_size = row_buffer_.size() * 2; + } + if (new_size > MAX_ROW_BUFFER_SIZE) { std::abort(); // Buffer size exceeds maximum } - row_buffer_.resize(buffer_len); - ret = FFI::row_iter_bsatn_advance(iter_handle_, - row_buffer_.data(), + row_buffer_.resize(new_size); + buffer_len = row_buffer_.size(); + ret = FFI::row_iter_bsatn_advance(iter_handle_, + row_buffer_.data(), &buffer_len); } @@ -710,4 +717,4 @@ class IndexIteratorRange { } // namespace SpacetimeDB -#endif // SPACETIMEDB_INDEX_ITERATOR_H \ No newline at end of file +#endif // SPACETIMEDB_INDEX_ITERATOR_H diff --git a/crates/bindings-cpp/include/spacetimedb/range_queries.h b/crates/bindings-cpp/include/spacetimedb/range_queries.h index c56bd91aeb4..481ea4a586f 100644 --- a/crates/bindings-cpp/include/spacetimedb/range_queries.h +++ b/crates/bindings-cpp/include/spacetimedb/range_queries.h @@ -130,6 +130,32 @@ constexpr bool is_range_v = is_range::value; template concept RangeType = is_range_v; +namespace detail { + +template +inline constexpr bool is_range_arg_v = is_range_v>; + +template +inline constexpr bool contains_range_arg_v = (is_range_arg_v || ...); + +template +struct last_is_range_arg : std::false_type {}; + +template +struct last_is_range_arg : last_is_range_arg {}; + +template +struct last_is_range_arg : std::bool_constant> {}; + +template +inline constexpr bool last_is_range_arg_v = last_is_range_arg::value; + +template +inline constexpr bool valid_index_scan_tuple_v = + !contains_range_arg_v || last_is_range_arg_v; + +} // namespace detail + // ============================================================================= // Integration with field accessors // ============================================================================= @@ -209,4 +235,4 @@ SPACETIMEDB_REDUCER(test_range_queries, ReducerContext ctx) { // Count people in range size_t count = ctx.db[person_age].count_range(range_from(50)); } -*/ \ No newline at end of file +*/ diff --git a/crates/bindings-cpp/include/spacetimedb/table_with_constraints.h b/crates/bindings-cpp/include/spacetimedb/table_with_constraints.h index b25e5cb44bd..5e62aff3eec 100644 --- a/crates/bindings-cpp/include/spacetimedb/table_with_constraints.h +++ b/crates/bindings-cpp/include/spacetimedb/table_with_constraints.h @@ -639,7 +639,7 @@ class TypedMultiColumnIndexAccessor : public TableAccessor { index_name_(index_name), column_list_(column_list) {} - // Exact match on all columns (template method - types deduced from call) + // Exact supplied prefix, optionally terminated by a range. template IndexIteratorRange filter(const std::tuple& values) const requires (sizeof...(FieldTypes) > 0 && sizeof...(FieldTypes) <= 6) @@ -652,26 +652,23 @@ class TypedMultiColumnIndexAccessor : public TableAccessor { return IndexIteratorRange(IndexIterator(id, values)); } - - // Prefix-only match: find all rows where first N-1 columns match - template - IndexIteratorRange filter(const FirstColType& prefix_value) const - requires (!is_tuple_v) - { + + // Range on the first indexed column. + template + IndexIteratorRange filter(const Range& range) const { IndexId id = resolve_index_id(); if (id.inner == 0) { return IndexIteratorRange(IndexIterator()); } - // Use prefix_match_tag to disambiguate constructor - return IndexIteratorRange(IndexIterator(prefix_match_tag{}, id, prefix_value)); + return IndexIteratorRange(IndexIterator(id, range)); } - // Prefix + range match: find rows where first N-1 columns match and last is in range - template - IndexIteratorRange filter(const std::tuple& values) const - requires (is_range_v) + // Prefix-only match: find all rows where the first indexed column matches. + template + IndexIteratorRange filter(const FirstColType& prefix_value) const + requires (!is_tuple_v && !is_range_v) { IndexId id = resolve_index_id(); @@ -679,7 +676,8 @@ class TypedMultiColumnIndexAccessor : public TableAccessor { return IndexIteratorRange(IndexIterator()); } - return IndexIteratorRange(IndexIterator(id, values)); + // Use prefix_match_tag to disambiguate constructor + return IndexIteratorRange(IndexIterator(prefix_match_tag{}, id, prefix_value)); } }; diff --git a/crates/bindings-cpp/tests/compile/cases/indexes/error_multi_column_range_not_terminal.cpp b/crates/bindings-cpp/tests/compile/cases/indexes/error_multi_column_range_not_terminal.cpp new file mode 100644 index 00000000000..fefed0e1ce7 --- /dev/null +++ b/crates/bindings-cpp/tests/compile/cases/indexes/error_multi_column_range_not_terminal.cpp @@ -0,0 +1,23 @@ +#include "spacetimedb.h" + +#include +#include + +using namespace SpacetimeDB; + +struct InvalidRangeRow { + uint32_t a; + uint32_t b; + uint32_t c; +}; + +SPACETIMEDB_STRUCT(InvalidRangeRow, a, b, c) +SPACETIMEDB_TABLE(InvalidRangeRow, invalid_range_row, Public) +FIELD_NamedMultiColumnIndex(invalid_range_row, by_all, a, b, c) + +SPACETIMEDB_REDUCER(check_invalid_middle_range, ReducerContext ctx) { + auto rows = ctx.db[invalid_range_row_by_all].filter( + std::make_tuple(uint32_t(1), range_from(uint32_t(2)), uint32_t(3))); + (void)rows; + return Ok(); +} diff --git a/crates/bindings-cpp/tests/compile/cases/indexes/ok_multi_column_range_prefixes.cpp b/crates/bindings-cpp/tests/compile/cases/indexes/ok_multi_column_range_prefixes.cpp new file mode 100644 index 00000000000..fa5285d5c0b --- /dev/null +++ b/crates/bindings-cpp/tests/compile/cases/indexes/ok_multi_column_range_prefixes.cpp @@ -0,0 +1,64 @@ +#include "spacetimedb.h" + +#include +#include + +using namespace SpacetimeDB; + +struct MultiRangeRow { + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f; +}; + +SPACETIMEDB_STRUCT(MultiRangeRow, a, b, c, d, e, f) +SPACETIMEDB_TABLE(MultiRangeRow, multi_range_row, Public) +FIELD_NamedMultiColumnIndex(multi_range_row, by_all, a, b, c, d, e, f) + +SPACETIMEDB_REDUCER(check_multi_column_range_prefixes, ReducerContext ctx) { + auto q1 = ctx.db[multi_range_row_by_all].filter(range_from(uint32_t(1))); + auto q2 = ctx.db[multi_range_row_by_all].filter(uint32_t(1)); + + auto q3 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), range(uint32_t(2), uint32_t(3)))); + auto q4 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2))); + + auto q5 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), range_inclusive(uint32_t(3), uint32_t(4)))); + auto q6 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), uint32_t(3))); + + auto q7 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), uint32_t(3), range_to(uint32_t(4)))); + auto q8 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), uint32_t(3), uint32_t(4))); + + auto q9 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), uint32_t(3), uint32_t(4), range_to_inclusive(uint32_t(5)))); + auto q10 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), uint32_t(3), uint32_t(4), uint32_t(5))); + + auto q11 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), uint32_t(3), uint32_t(4), uint32_t(5), range_full())); + auto q12 = ctx.db[multi_range_row_by_all].filter( + std::make_tuple(uint32_t(1), uint32_t(2), uint32_t(3), uint32_t(4), uint32_t(5), uint32_t(6))); + + (void)q1; + (void)q2; + (void)q3; + (void)q4; + (void)q5; + (void)q6; + (void)q7; + (void)q8; + (void)q9; + (void)q10; + (void)q11; + (void)q12; + + return Ok(); +} diff --git a/crates/bindings-cpp/tests/compile/run-compile-tests.ps1 b/crates/bindings-cpp/tests/compile/run-compile-tests.ps1 index 2dd8a40eaa4..71975a2ccb1 100644 --- a/crates/bindings-cpp/tests/compile/run-compile-tests.ps1 +++ b/crates/bindings-cpp/tests/compile/run-compile-tests.ps1 @@ -1,6 +1,6 @@ [CmdletBinding()] param( - [ValidateSet("http-handlers")] + [ValidateSet("http-handlers", "indexes")] [string]$Suite = "http-handlers" ) @@ -100,6 +100,12 @@ $cases = switch ($Suite) { (New-CompileCase "error_http_router_wrong_return_type" "cases/http-handlers/error_http_router_wrong_return_type.cpp" "failure" "no viable conversion from returned value of type 'unsigned int' to function return type 'SpacetimeDB::Router'") ) } + "indexes" { + @( + (New-CompileCase "ok_multi_column_range_prefixes" "cases/indexes/ok_multi_column_range_prefixes.cpp" "success") + (New-CompileCase "error_multi_column_range_not_terminal" "cases/indexes/error_multi_column_range_not_terminal.cpp" "failure" "Range in a multi-column index filter must be the final supplied element") + ) + } } New-Item -ItemType Directory -Force -Path $buildRoot | Out-Null diff --git a/crates/bindings-cpp/tests/compile/run-compile-tests.sh b/crates/bindings-cpp/tests/compile/run-compile-tests.sh index d7c634bc9d7..78138b47ab4 100644 --- a/crates/bindings-cpp/tests/compile/run-compile-tests.sh +++ b/crates/bindings-cpp/tests/compile/run-compile-tests.sh @@ -24,7 +24,7 @@ while [[ $# -gt 0 ]]; do esac done -if [[ "$SUITE" != "http-handlers" ]]; then +if [[ "$SUITE" != "http-handlers" && "$SUITE" != "indexes" ]]; then echo "Unsupported suite: $SUITE" >&2 exit 1 fi @@ -54,81 +54,95 @@ if ! cmake --build "$LIBRARY_BUILD_DIR" >"$LIBRARY_BUILD_LOG" 2>&1; then exit 1 fi -declare -a CASE_NAMES=( - "ok_http_handlers_basic" - "error_http_handler_no_args" - "error_http_handler_immutable_ctx" - "error_http_handler_wrong_ctx" - "error_http_handler_no_request_arg" - "error_http_handler_wrong_request_arg_type" - "error_http_handler_no_return_type" - "error_http_handler_wrong_return_type" - "error_http_handler_no_sender" - "error_http_handler_no_connection_id" - "error_http_handler_no_db" - "error_http_router_not_a_function" - "error_http_router_with_args" - "error_http_router_wrong_return_type" -) - declare -A CASE_EXPECTATION declare -A CASE_MARKER declare -A CASE_SOURCE -CASE_EXPECTATION["ok_http_handlers_basic"]="success" -CASE_SOURCE["ok_http_handlers_basic"]="$SCRIPT_DIR/cases/http-handlers/ok_http_handlers_basic.cpp" - -CASE_EXPECTATION["error_http_handler_no_args"]="failure" -CASE_MARKER["error_http_handler_no_args"]="too few arguments provided to function-like macro invocation" -CASE_SOURCE["error_http_handler_no_args"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_args.cpp" - -CASE_EXPECTATION["error_http_handler_immutable_ctx"]="failure" -CASE_MARKER["error_http_handler_immutable_ctx"]="First parameter of HTTP handler must be HandlerContext" -CASE_SOURCE["error_http_handler_immutable_ctx"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_immutable_ctx.cpp" - -CASE_EXPECTATION["error_http_handler_wrong_ctx"]="failure" -CASE_MARKER["error_http_handler_wrong_ctx"]="First parameter of HTTP handler must be HandlerContext" -CASE_SOURCE["error_http_handler_wrong_ctx"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_wrong_ctx.cpp" - -CASE_EXPECTATION["error_http_handler_no_request_arg"]="failure" -CASE_MARKER["error_http_handler_no_request_arg"]="too few arguments provided to function-like macro invocation" -CASE_SOURCE["error_http_handler_no_request_arg"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_request_arg.cpp" - -CASE_EXPECTATION["error_http_handler_wrong_request_arg_type"]="failure" -CASE_MARKER["error_http_handler_wrong_request_arg_type"]="Second parameter of HTTP handler must be HttpRequest" -CASE_SOURCE["error_http_handler_wrong_request_arg_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_wrong_request_arg_type.cpp" - -CASE_EXPECTATION["error_http_handler_no_return_type"]="failure" -CASE_MARKER["error_http_handler_no_return_type"]="non-void function does not return a value" -CASE_SOURCE["error_http_handler_no_return_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_return_type.cpp" - -CASE_EXPECTATION["error_http_handler_wrong_return_type"]="failure" -CASE_MARKER["error_http_handler_wrong_return_type"]="no viable conversion from returned value of type 'unsigned int' to function return type 'SpacetimeDB::HttpResponse'" -CASE_SOURCE["error_http_handler_wrong_return_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_wrong_return_type.cpp" - -CASE_EXPECTATION["error_http_handler_no_sender"]="failure" -CASE_MARKER["error_http_handler_no_sender"]="no member named 'sender' in 'SpacetimeDB::HandlerContext'" -CASE_SOURCE["error_http_handler_no_sender"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_sender.cpp" - -CASE_EXPECTATION["error_http_handler_no_connection_id"]="failure" -CASE_MARKER["error_http_handler_no_connection_id"]="no member named 'connection_id' in 'SpacetimeDB::HandlerContext'" -CASE_SOURCE["error_http_handler_no_connection_id"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_connection_id.cpp" - -CASE_EXPECTATION["error_http_handler_no_db"]="failure" -CASE_MARKER["error_http_handler_no_db"]="no member named 'db' in 'SpacetimeDB::HandlerContext'" -CASE_SOURCE["error_http_handler_no_db"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_db.cpp" - -CASE_EXPECTATION["error_http_router_not_a_function"]="failure" -CASE_MARKER["error_http_router_not_a_function"]="illegal initializer" -CASE_SOURCE["error_http_router_not_a_function"]="$SCRIPT_DIR/cases/http-handlers/error_http_router_not_a_function.cpp" +if [[ "$SUITE" == "http-handlers" ]]; then + declare -a CASE_NAMES=( + "ok_http_handlers_basic" + "error_http_handler_no_args" + "error_http_handler_immutable_ctx" + "error_http_handler_wrong_ctx" + "error_http_handler_no_request_arg" + "error_http_handler_wrong_request_arg_type" + "error_http_handler_no_return_type" + "error_http_handler_wrong_return_type" + "error_http_handler_no_sender" + "error_http_handler_no_connection_id" + "error_http_handler_no_db" + "error_http_router_not_a_function" + "error_http_router_with_args" + "error_http_router_wrong_return_type" + ) + + CASE_EXPECTATION["ok_http_handlers_basic"]="success" + CASE_SOURCE["ok_http_handlers_basic"]="$SCRIPT_DIR/cases/http-handlers/ok_http_handlers_basic.cpp" + + CASE_EXPECTATION["error_http_handler_no_args"]="failure" + CASE_MARKER["error_http_handler_no_args"]="too few arguments provided to function-like macro invocation" + CASE_SOURCE["error_http_handler_no_args"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_args.cpp" + + CASE_EXPECTATION["error_http_handler_immutable_ctx"]="failure" + CASE_MARKER["error_http_handler_immutable_ctx"]="First parameter of HTTP handler must be HandlerContext" + CASE_SOURCE["error_http_handler_immutable_ctx"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_immutable_ctx.cpp" + + CASE_EXPECTATION["error_http_handler_wrong_ctx"]="failure" + CASE_MARKER["error_http_handler_wrong_ctx"]="First parameter of HTTP handler must be HandlerContext" + CASE_SOURCE["error_http_handler_wrong_ctx"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_wrong_ctx.cpp" + + CASE_EXPECTATION["error_http_handler_no_request_arg"]="failure" + CASE_MARKER["error_http_handler_no_request_arg"]="too few arguments provided to function-like macro invocation" + CASE_SOURCE["error_http_handler_no_request_arg"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_request_arg.cpp" + + CASE_EXPECTATION["error_http_handler_wrong_request_arg_type"]="failure" + CASE_MARKER["error_http_handler_wrong_request_arg_type"]="Second parameter of HTTP handler must be HttpRequest" + CASE_SOURCE["error_http_handler_wrong_request_arg_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_wrong_request_arg_type.cpp" + + CASE_EXPECTATION["error_http_handler_no_return_type"]="failure" + CASE_MARKER["error_http_handler_no_return_type"]="non-void function does not return a value" + CASE_SOURCE["error_http_handler_no_return_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_return_type.cpp" + + CASE_EXPECTATION["error_http_handler_wrong_return_type"]="failure" + CASE_MARKER["error_http_handler_wrong_return_type"]="no viable conversion from returned value of type 'unsigned int' to function return type 'SpacetimeDB::HttpResponse'" + CASE_SOURCE["error_http_handler_wrong_return_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_wrong_return_type.cpp" + + CASE_EXPECTATION["error_http_handler_no_sender"]="failure" + CASE_MARKER["error_http_handler_no_sender"]="no member named 'sender' in 'SpacetimeDB::HandlerContext'" + CASE_SOURCE["error_http_handler_no_sender"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_sender.cpp" + + CASE_EXPECTATION["error_http_handler_no_connection_id"]="failure" + CASE_MARKER["error_http_handler_no_connection_id"]="no member named 'connection_id' in 'SpacetimeDB::HandlerContext'" + CASE_SOURCE["error_http_handler_no_connection_id"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_connection_id.cpp" + + CASE_EXPECTATION["error_http_handler_no_db"]="failure" + CASE_MARKER["error_http_handler_no_db"]="no member named 'db' in 'SpacetimeDB::HandlerContext'" + CASE_SOURCE["error_http_handler_no_db"]="$SCRIPT_DIR/cases/http-handlers/error_http_handler_no_db.cpp" + + CASE_EXPECTATION["error_http_router_not_a_function"]="failure" + CASE_MARKER["error_http_router_not_a_function"]="illegal initializer" + CASE_SOURCE["error_http_router_not_a_function"]="$SCRIPT_DIR/cases/http-handlers/error_http_router_not_a_function.cpp" + + CASE_EXPECTATION["error_http_router_with_args"]="failure" + CASE_MARKER["error_http_router_with_args"]="too many arguments provided to function-like macro invocation" + CASE_SOURCE["error_http_router_with_args"]="$SCRIPT_DIR/cases/http-handlers/error_http_router_with_args.cpp" + + CASE_EXPECTATION["error_http_router_wrong_return_type"]="failure" + CASE_MARKER["error_http_router_wrong_return_type"]="no viable conversion from returned value of type 'unsigned int' to function return type 'SpacetimeDB::Router'" + CASE_SOURCE["error_http_router_wrong_return_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_router_wrong_return_type.cpp" +else + declare -a CASE_NAMES=( + "ok_multi_column_range_prefixes" + "error_multi_column_range_not_terminal" + ) -CASE_EXPECTATION["error_http_router_with_args"]="failure" -CASE_MARKER["error_http_router_with_args"]="too many arguments provided to function-like macro invocation" -CASE_SOURCE["error_http_router_with_args"]="$SCRIPT_DIR/cases/http-handlers/error_http_router_with_args.cpp" + CASE_EXPECTATION["ok_multi_column_range_prefixes"]="success" + CASE_SOURCE["ok_multi_column_range_prefixes"]="$SCRIPT_DIR/cases/indexes/ok_multi_column_range_prefixes.cpp" -CASE_EXPECTATION["error_http_router_wrong_return_type"]="failure" -CASE_MARKER["error_http_router_wrong_return_type"]="no viable conversion from returned value of type 'unsigned int' to function return type 'SpacetimeDB::Router'" -CASE_SOURCE["error_http_router_wrong_return_type"]="$SCRIPT_DIR/cases/http-handlers/error_http_router_wrong_return_type.cpp" + CASE_EXPECTATION["error_multi_column_range_not_terminal"]="failure" + CASE_MARKER["error_multi_column_range_not_terminal"]="Range in a multi-column index filter must be the final supplied element" + CASE_SOURCE["error_multi_column_range_not_terminal"]="$SCRIPT_DIR/cases/indexes/error_multi_column_range_not_terminal.cpp" +fi FAILURES=0 diff --git a/docs/docs/00200-core-concepts/00300-tables/00300-indexes.md b/docs/docs/00200-core-concepts/00300-tables/00300-indexes.md index a52bc7bf406..430fde8c3ca 100644 --- a/docs/docs/00200-core-concepts/00300-tables/00300-indexes.md +++ b/docs/docs/00200-core-concepts/00300-tables/00300-indexes.md @@ -558,6 +558,28 @@ for score in ctx.db.score().by_player_and_level().filter((123u32, 5u32)) { } ``` + + + +```cpp +// Find all scores for player 123 (prefix match) +for (auto score : ctx.db[score_by_player_and_level].filter(uint32_t(123))) { + LOG_INFO("Level " + std::to_string(score.level) + ": " + std::to_string(score.points) + " points"); +} + +// Find scores for player 123 at levels 1-10 +for (auto score : ctx.db[score_by_player_and_level].filter( + std::make_tuple(uint32_t(123), range_inclusive(uint32_t(1), uint32_t(10))))) { + LOG_INFO("Level " + std::to_string(score.level) + ": " + std::to_string(score.points) + " points"); +} + +// Find the exact score for player 123 at level 5 +for (auto score : ctx.db[score_by_player_and_level].filter( + std::make_tuple(uint32_t(123), uint32_t(5)))) { + LOG_INFO("Points: " + std::to_string(score.points)); +} +``` +