Files
SpacetimeDB/crates/bindings/tests/ui/views.rs
Shubham Mishra 5100aec3d4 Some more UI tests for Views (#3829)
# Description of Changes
As title

# API and ABI breaking changes
NA

# Expected complexity level and risk
0

---------

Signed-off-by: Shubham Mishra <shivam828787@gmail.com>
Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
2025-12-08 19:33:02 +00:00

207 lines
5.8 KiB
Rust

use spacetimedb::{reducer, table, view, AnonymousViewContext, Identity, Query, ReducerContext, ViewContext};
#[table(name = test)]
struct Test {
#[unique]
id: u32,
#[index(btree)]
x: u32,
}
#[reducer]
fn view_handle_no_iter(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: ViewHandle does not expose `iter()`
for _ in read_only.db.test().iter() {}
}
#[reducer]
fn view_handle_no_count(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: ViewHandle does not expose `count()`
let _ = read_only.db.test().count();
}
#[reducer]
fn view_handle_no_insert(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: ViewHandle does not expose `insert()`
read_only.db.test().insert(Test { id: 0, x: 0 });
}
#[reducer]
fn view_handle_no_try_insert(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: ViewHandle does not expose `try_insert()`
read_only.db.test().try_insert(Test { id: 0, x: 0 });
}
#[reducer]
fn view_handle_no_delete(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: ViewHandle does not expose `delete()`
read_only.db.test().delete(Test { id: 0, x: 0 });
}
#[reducer]
fn read_only_unqiue_index_no_delete(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: unique read-only index does not expose `delete()`
read_only.db.test().id().delete(&0);
}
#[reducer]
fn read_only_unqiue_index_no_update(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: unique read-only index does not expose `update()`
read_only.db.test().id().update(Test { id: 0, x: 0 });
}
#[reducer]
fn read_only_btree_index_no_delete(ctx: &ReducerContext) {
let read_only = ctx.as_read_only();
// Should not compile: read-only btree index does not expose `delete()`
read_only.db.test().x().delete(0u32..);
}
#[table(name = player)]
struct Player {
#[unique]
identity: Identity,
}
struct NotSpacetimeType {}
/// Private views not allowed; must be `#[view(public, ...)]`
#[view(name = view_def_no_public)]
fn view_def_no_public(_: &ViewContext) -> Vec<Player> {
vec![]
}
/// Duplicate `public`
#[view(name = view_def_dup_public, public, public)]
fn view_def_dup_public() -> Vec<Player> {
vec![]
}
/// Duplicate `name`
#[view(name = view_def_dup_name, name = view_def_dup_name, public)]
fn view_def_dup_name() -> Vec<Player> {
vec![]
}
/// Unsupported attribute arg
#[view(name = view_def_unsupported_arg, public, anonymous)]
fn view_def_unsupported_arg() -> Vec<Player> {
vec![]
}
/// A `ViewContext` is required
#[view(name = view_def_no_context, public)]
fn view_def_no_context() -> Vec<Player> {
vec![]
}
/// A `ViewContext` is required
#[view(name = view_def_wrong_context, public)]
fn view_def_wrong_context(_: &ReducerContext) -> Vec<Player> {
vec![]
}
/// Must pass the `ViewContext` by ref
#[view(name = view_def_pass_context_by_value, public)]
fn view_def_pass_context_by_value(_: ViewContext) -> Vec<Player> {
vec![]
}
/// The view context must be the first parameter
#[view(name = view_def_wrong_context_position, public)]
fn view_def_wrong_context_position(_: &u32, _: &ViewContext) -> Vec<Player> {
vec![]
}
/// Must return `Vec<T>` or `Option<T>` where `T` is a SpacetimeType
#[view(name = view_def_no_return, public)]
fn view_def_no_return(_: &ViewContext) {}
/// Must return `Vec<T>` or `Option<T>` where `T` is a SpacetimeType
#[view(name = view_def_wrong_return, public)]
fn view_def_wrong_return(_: &ViewContext) -> Player {
Player {
identity: Identity::ZERO,
}
}
/// Must return `Vec<T>` or `Option<T>` where `T` is a SpacetimeType
#[view(name = view_def_returns_not_a_spacetime_type, public)]
fn view_def_returns_not_a_spacetime_type(_: &AnonymousViewContext) -> Option<NotSpacetimeType> {
None
}
/// Cannot use a view as a scheduled function
#[view(name = sched_table_view, public)]
fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec<PlayerInfo> {
vec![]
}
#[table(name = player_info)]
struct PlayerInfo {
#[unique]
identity: Identity,
#[index(btree)]
weight: u32,
age: u8,
}
/// Comparing incompatible types in `where` condition: Identity != int
#[view(name = view_bad_where, public)]
fn view_bad_where(ctx: &ViewContext) -> Query<Player> {
ctx.from.player().r#where(|a| a.identity.eq(42)).build()
}
/// Comparing incompatible types in `where` condition: u8 != u32
#[view(name = view_bad_where_int_types, public)]
fn view_bad_where_int_types(ctx: &ViewContext) -> Query<PlayerInfo> {
ctx.from.player_info().r#where(|a| a.age.eq(4200u32)).build()
}
/// Joining incompatible types
/// -- weight is u32, identity is Identity
#[view(name = view_bad_join, public)]
fn view_bad_join(ctx: &ViewContext) -> Query<PlayerInfo> {
ctx.from
.player_info()
.left_semijoin(ctx.from.player(), |a, b| a.weight.eq(b.identity))
.build()
}
/// Joining non-index columns
/// -- age is not indexed
#[view(name = view_join_non_indexed_column, public)]
fn view_join_non_indexed_column(ctx: &ViewContext) -> Query<PlayerInfo> {
ctx.from
.player()
.right_semijoin(ctx.from.player_info(), |a, b| a.identity.eq(b.age))
.build()
}
/// Right join returns right table's type
/// -- should be PlayerInfo, not Player
#[view(name = view_right_join_wrong_return_type, public)]
fn view_right_join_wrong_return_type(ctx: &ViewContext) -> Query<Player> {
ctx.from
.player()
.right_semijoin(ctx.from.player_info(), |a, b| a.identity.eq(b.identity))
.build()
}
/// Using non-existent table
/// -- xyz table does not exist
#[view(name = view_nonexistent_table, public)]
fn view_nonexistent_table(ctx: &ViewContext) -> Query<T> {
ctx.from.xyz().build()
}
fn main() {}