Skip to content

Commit 414abad

Browse files
Add subscription smoketests that join on a view's primary key
1 parent 2149325 commit 414abad

3 files changed

Lines changed: 138 additions & 59 deletions

File tree

crates/smoketests/modules/views-query/src/lib.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use spacetimedb::{Query, ReducerContext, Table, ViewContext};
1+
use spacetimedb::{AnonymousViewContext, Query, ReducerContext, Table, ViewContext};
22

33
#[spacetimedb::table(accessor = user, public)]
44
pub struct User {
@@ -88,3 +88,21 @@ fn users_who_are_above_20_and_below_30(ctx: &ViewContext) -> impl Query<Person>
8888
fn users_who_are_above_eq_20_and_below_eq_30(ctx: &ViewContext) -> impl Query<Person> {
8989
ctx.from.person().r#where(|p| p.age.gte(20).and(p.age.lte(30)))
9090
}
91+
92+
#[spacetimedb::view(accessor = anonymous_adult_people, public)]
93+
fn anonymous_adult_people(ctx: &AnonymousViewContext) -> impl Query<Person> {
94+
ctx.from.person().r#where(|p| p.age.gte(20))
95+
}
96+
97+
#[spacetimedb::view(accessor = online_users_identity_1, public)]
98+
fn online_users_identity_1(ctx: &ViewContext) -> impl Query<User> {
99+
ctx.from.user().r#where(|u| u.online).filter(|u| u.identity.eq(1))
100+
}
101+
102+
#[spacetimedb::view(accessor = users_whos_age_is_known_identity_1, public)]
103+
fn users_whos_age_is_known_identity_1(ctx: &ViewContext) -> impl Query<User> {
104+
ctx.from
105+
.user()
106+
.left_semijoin(ctx.from.person(), |p, u| p.identity.eq(u.identity))
107+
.filter(|u| u.identity.eq(1))
108+
}

crates/smoketests/modules/views-subscribe/src/lib.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
1-
use spacetimedb::{Identity, ProcedureContext, ReducerContext, Table, ViewContext};
1+
use spacetimedb::{Identity, ProcedureContext, Query, ReducerContext, Table, ViewContext};
22

33
#[spacetimedb::table(accessor = player_state)]
44
pub struct PlayerState {
55
#[primary_key]
66
identity: Identity,
77
#[unique]
88
name: String,
9+
online: bool,
910
}
1011

1112
#[spacetimedb::view(accessor = my_player, public)]
1213
pub fn my_player(ctx: &ViewContext) -> Option<PlayerState> {
1314
ctx.db.player_state().identity().find(ctx.sender())
1415
}
1516

17+
#[spacetimedb::view(accessor = all_players, public)]
18+
pub fn all_players(ctx: &ViewContext) -> impl Query<PlayerState> {
19+
ctx.from.player_state()
20+
}
21+
22+
#[spacetimedb::view(accessor = online_players, public)]
23+
pub fn online_players(ctx: &ViewContext) -> impl Query<PlayerState> {
24+
ctx.from.player_state().r#where(|row| row.online)
25+
}
26+
1627
#[spacetimedb::reducer]
1728
pub fn insert_player(ctx: &ReducerContext, name: String) {
1829
ctx.db.player_state().insert(PlayerState {
1930
name,
2031
identity: ctx.sender(),
32+
online: true,
2133
});
2234
}
2335

@@ -28,6 +40,7 @@ pub fn insert_player_proc(ctx: &mut ProcedureContext, name: String) {
2840
tx.db.player_state().insert(PlayerState {
2941
name: name.clone(),
3042
identity: sender,
43+
online: true,
3144
});
3245
});
3346
}

crates/smoketests/tests/smoketests/views.rs

Lines changed: 105 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use serde_json::json;
1+
use serde_json::{json, Value};
22
use spacetimedb_smoketests::{require_dotnet, require_pnpm, Smoketest};
33

44
const TS_VIEWS_SUBSCRIBE_MODULE: &str = r#"import { schema, t, table } from "spacetimedb/server";
@@ -77,6 +77,30 @@ public static partial class Module
7777
}
7878
"#;
7979

80+
fn project_inserts_and_deletes_for_view(events: Vec<Value>, view_name: &str) -> Vec<Value> {
81+
events
82+
.into_iter()
83+
.map(|event| {
84+
json!({
85+
view_name: {
86+
"deletes": event[view_name]["deletes"]
87+
.as_array()
88+
.unwrap()
89+
.iter()
90+
.map(|row| json!({"name": row["name"]}))
91+
.collect::<Vec<_>>(),
92+
"inserts": event[view_name]["inserts"]
93+
.as_array()
94+
.unwrap()
95+
.iter()
96+
.map(|row| json!({"name": row["name"]}))
97+
.collect::<Vec<_>>()
98+
}
99+
})
100+
})
101+
.collect()
102+
}
103+
80104
/// Tests that views populate the st_view_* system tables
81105
#[test]
82106
fn test_st_view_tables() {
@@ -404,24 +428,7 @@ fn test_subscribing_with_different_identities() {
404428
test.call("insert_player", &["Bob"]).unwrap();
405429
let events = sub.collect().unwrap();
406430

407-
let projection: Vec<serde_json::Value> = events
408-
.into_iter()
409-
.map(|event| {
410-
let deletes = event["my_player"]["deletes"]
411-
.as_array()
412-
.unwrap()
413-
.iter()
414-
.map(|row| json!({"name": row["name"]}))
415-
.collect::<Vec<_>>();
416-
let inserts = event["my_player"]["inserts"]
417-
.as_array()
418-
.unwrap()
419-
.iter()
420-
.map(|row| json!({"name": row["name"]}))
421-
.collect::<Vec<_>>();
422-
json!({"my_player": {"deletes": deletes, "inserts": inserts}})
423-
})
424-
.collect();
431+
let projection = project_inserts_and_deletes_for_view(events, "my_player");
425432

426433
assert_eq!(
427434
serde_json::json!(projection),
@@ -500,26 +507,7 @@ fn test_procedure_triggers_subscription_updates() {
500507
let sub = test.subscribe_background(&["select * from my_player"], 1).unwrap();
501508
test.call("insert_player_proc", &["Alice"]).unwrap();
502509
let events = sub.collect().unwrap();
503-
504-
let projection: Vec<serde_json::Value> = events
505-
.into_iter()
506-
.map(|event| {
507-
let deletes = event["my_player"]["deletes"]
508-
.as_array()
509-
.unwrap()
510-
.iter()
511-
.map(|row| json!({"name": row["name"]}))
512-
.collect::<Vec<_>>();
513-
let inserts = event["my_player"]["inserts"]
514-
.as_array()
515-
.unwrap()
516-
.iter()
517-
.map(|row| json!({"name": row["name"]}))
518-
.collect::<Vec<_>>();
519-
json!({"my_player": {"deletes": deletes, "inserts": inserts}})
520-
})
521-
.collect();
522-
510+
let projection = project_inserts_and_deletes_for_view(events, "my_player");
523511
assert_eq!(
524512
serde_json::json!(projection),
525513
serde_json::json!([
@@ -543,24 +531,7 @@ fn test_typescript_procedure_triggers_subscription_updates() {
543531
test.call("insert_player_proc", &["Alice"]).unwrap();
544532
let events = sub.collect().unwrap();
545533

546-
let projection: Vec<serde_json::Value> = events
547-
.into_iter()
548-
.map(|event| {
549-
let deletes = event["my_player"]["deletes"]
550-
.as_array()
551-
.unwrap()
552-
.iter()
553-
.map(|row| json!({"name": row["name"]}))
554-
.collect::<Vec<_>>();
555-
let inserts = event["my_player"]["inserts"]
556-
.as_array()
557-
.unwrap()
558-
.iter()
559-
.map(|row| json!({"name": row["name"]}))
560-
.collect::<Vec<_>>();
561-
json!({"my_player": {"deletes": deletes, "inserts": inserts}})
562-
})
563-
.collect();
534+
let projection = project_inserts_and_deletes_for_view(events, "my_player");
564535

565536
assert_eq!(
566537
serde_json::json!(projection),
@@ -609,3 +580,80 @@ fn test_csharp_query_builder_view_query() {
609580
1 | true"#,
610581
);
611582
}
583+
584+
#[test]
585+
fn test_subscribe_join_with_view_on_primary_key_col() {
586+
let test = Smoketest::builder().precompiled_module("views-subscribe").build();
587+
588+
test.call("insert_player_proc", &["Alice"]).unwrap();
589+
590+
let query =
591+
"SELECT all_players.* FROM player_state JOIN all_players ON player_state.identity = all_players.identity";
592+
let events = test.subscribe(&[query], 0).unwrap();
593+
let projection = project_inserts_and_deletes_for_view(events, "all_players");
594+
595+
assert_eq!(
596+
serde_json::json!(projection),
597+
serde_json::json!([
598+
{"all_players": {"deletes": [], "inserts": [{"name": "Alice"}]}}
599+
])
600+
);
601+
}
602+
603+
#[test]
604+
fn test_subscribe_join_two_views_on_primary_key_col() {
605+
let test = Smoketest::builder().precompiled_module("views-query").build();
606+
607+
let query = "SELECT online_users.* \
608+
FROM online_users \
609+
JOIN users_whos_age_is_known \
610+
ON online_users.identity = users_whos_age_is_known.identity";
611+
let events = test.subscribe(&[query], 0).unwrap();
612+
let projection = project_inserts_and_deletes_for_view(events, "online_users");
613+
614+
assert_eq!(
615+
serde_json::json!(projection),
616+
serde_json::json!([
617+
{"online_users": {"deletes": [], "inserts": [{"name": "Alice"}]}}
618+
])
619+
);
620+
}
621+
622+
#[test]
623+
fn test_subscribe_join_with_anonymous_view_on_primary_key_col() {
624+
let test = Smoketest::builder().precompiled_module("views-query").build();
625+
626+
let query = "SELECT anonymous_adult_people.* \
627+
FROM person \
628+
JOIN anonymous_adult_people \
629+
ON person.identity = anonymous_adult_people.identity \
630+
WHERE anonymous_adult_people.identity = 1";
631+
let events = test.subscribe(&[query], 0).unwrap();
632+
let projection = project_inserts_and_deletes_for_view(events, "anonymous_adult_people");
633+
634+
assert_eq!(
635+
serde_json::json!(projection),
636+
serde_json::json!([
637+
{"anonymous_adult_people": {"deletes": [], "inserts": [{"name": "Alice"}]}}
638+
])
639+
);
640+
}
641+
642+
#[test]
643+
fn test_subscribe_join_two_sender_views_with_filters_on_both_sides() {
644+
let test = Smoketest::builder().precompiled_module("views-query").build();
645+
646+
let query = "SELECT online_users_identity_1.* \
647+
FROM online_users_identity_1 \
648+
JOIN users_whos_age_is_known_identity_1 \
649+
ON online_users_identity_1.identity = users_whos_age_is_known_identity_1.identity";
650+
let events = test.subscribe(&[query], 0).unwrap();
651+
let projection = project_inserts_and_deletes_for_view(events, "online_users_identity_1");
652+
653+
assert_eq!(
654+
serde_json::json!(projection),
655+
serde_json::json!([
656+
{"online_users_identity_1": {"deletes": [], "inserts": [{"name": "Alice"}]}}
657+
])
658+
);
659+
}

0 commit comments

Comments
 (0)