From 6df82778f7e7ed11c6665023a814dfe543537ab7 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 8 Sep 2023 11:33:06 -0700 Subject: [PATCH] graphql: Add a query test Check the situation where an entity that has empty relations to other entities is followed by one that does have them and ensure that we link up the children correctly in that situation. --- store/test-store/tests/graphql/query.rs | 148 ++++++++++++++++++++---- 1 file changed, 125 insertions(+), 23 deletions(-) diff --git a/store/test-store/tests/graphql/query.rs b/store/test-store/tests/graphql/query.rs index d5e82834a1e..0c219558e14 100644 --- a/store/test-store/tests/graphql/query.rs +++ b/store/test-store/tests/graphql/query.rs @@ -581,6 +581,7 @@ async fn insert_test_entities( vec![ entity! { is => id: "m3", name: "Tom", mainBand: "b2", bands: vec!["b1", "b2"], favoriteCount: 5, birthDate: timestamp.clone(), vid: 2i64 }, entity! { is => id: "m4", name: "Valerie", bands: Vec::::new(), favoriteCount: 20, birthDate: timestamp.clone(), vid: 3i64 }, + entity! { is => id: "m5", name: "Paul", mainBand: "b2", bands: vec!["b2"], favoriteCount: 2 , birthDate: timestamp.clone(), vid: 4i64 }, ], )]; let entities1 = insert_ops(&manifest.schema, entities1); @@ -771,7 +772,8 @@ fn can_query_one_to_one_relationship() { object! { name: "John", mainBand: object! { name: "The Musicians" }, favoriteCount: "10", birthDate: "1710837304040956" }, object! { name: "Lisa", mainBand: object! { name: "The Musicians" }, favoriteCount: "100", birthDate: "1710837304040956" }, object! { name: "Tom", mainBand: object! { name: "The Amateurs" }, favoriteCount: "5", birthDate: "1710837304040956" }, - object! { name: "Valerie", mainBand: r::Value::Null, favoriteCount: "20", birthDate: "1710837304040956" } + object! { name: "Valerie", mainBand: r::Value::Null, favoriteCount: "20", birthDate: "1710837304040956" }, + object! { name: "Paul", mainBand: object! { name: "The Amateurs" }, favoriteCount: "2", birthDate: "1710837304040956" } ], songStats: vec![ object! { @@ -815,7 +817,8 @@ fn can_filter_by_timestamp() { object! { name: "John" }, object! { name: "Lisa" }, object! { name: "Tom" }, - object! { name: "Valerie" } + object! { name: "Valerie" }, + object! { name: "Paul" }, ], }; let data = extract_data!(result).unwrap(); @@ -868,6 +871,9 @@ fn can_query_one_to_many_relationships_in_both_directions() { object! { name: "Valerie", writtenSongs: Vec::::new() }, + object! { + name: "Paul", writtenSongs: Vec::::new() + }, ] }; @@ -906,15 +912,16 @@ fn can_query_many_to_many_relationship() { let the_amateurs = object! { name: "The Amateurs", - members: members(vec![ "John", "Tom" ]) + members: members(vec![ "John", "Tom", "Paul" ]) }; let exp = object! { musicians: vec![ object! { name: "John", bands: vec![ the_musicians.clone(), the_amateurs.clone() ]}, object! { name: "Lisa", bands: vec![ the_musicians.clone() ] }, - object! { name: "Tom", bands: vec![ the_musicians, the_amateurs ] }, - object! { name: "Valerie", bands: Vec::::new() } + object! { name: "Tom", bands: vec![ the_musicians, the_amateurs.clone() ] }, + object! { name: "Valerie", bands: Vec::::new() }, + object! { name: "Paul", bands: vec![ the_amateurs ] } ] }; @@ -996,10 +1003,12 @@ fn can_query_with_sorting_by_child_entity() { object! { name: "Valerie", mainBand: r::Value::Null }, object! { name: "Lisa", mainBand: object! { name: "The Musicians" } }, object! { name: "John", mainBand: object! { name: "The Musicians" } }, + object! { name: "Paul", mainBand: object! { name: "The Amateurs"} }, object! { name: "Tom", mainBand: object! { name: "The Amateurs"} }, ], asc: vec![ object! { name: "Tom", mainBand: object! { name: "The Amateurs"} }, + object! { name: "Paul", mainBand: object! { name: "The Amateurs"} }, object! { name: "John", mainBand: object! { name: "The Musicians" } }, object! { name: "Lisa", mainBand: object! { name: "The Musicians" } }, object! { name: "Valerie", mainBand: r::Value::Null }, @@ -1307,7 +1316,8 @@ fn can_query_with_child_filter_on_list_type_field() { let exp = object! { musicians: vec![ object! { name: "John", bands: vec![ the_musicians.clone(), the_amateurs.clone() ]}, - object! { name: "Tom", bands: vec![ the_musicians, the_amateurs ] }, + object! { name: "Tom", bands: vec![ the_musicians, the_amateurs.clone() ] }, + object! { name: "Paul", bands: vec![ the_amateurs ] }, ] }; @@ -1352,7 +1362,8 @@ fn can_query_with_child_filter_on_named_type_field() { run_query(QUERY, |result, _| { let exp = object! { musicians: vec![ - object! { name: "Tom", mainBand: object! { id: "b2"} } + object! { name: "Tom", mainBand: object! { id: "b2"} }, + object! { name: "Paul", mainBand: object! { id: "b2"} } ] }; @@ -1703,7 +1714,7 @@ fn skip_directive_works_with_query_variables() { run_query((QUERY, object! { skip: true }), |result, _| { // Assert that only names are returned - let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie"] + let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie", "Paul"] .into_iter() .map(|name| object! { name: name }) .collect(); @@ -1719,7 +1730,8 @@ fn skip_directive_works_with_query_variables() { object! { id: "m1", name: "John" }, object! { id: "m2", name: "Lisa"}, object! { id: "m3", name: "Tom" }, - object! { id: "m4", name: "Valerie" } + object! { id: "m4", name: "Valerie" }, + object! { id: "m5", name: "Paul" } ] }; let data = extract_data!(result).unwrap(); @@ -1745,7 +1757,8 @@ fn include_directive_works_with_query_variables() { object! { id: "m1", name: "John" }, object! { id: "m2", name: "Lisa"}, object! { id: "m3", name: "Tom" }, - object! { id: "m4", name: "Valerie" } + object! { id: "m4", name: "Valerie" }, + object! { id: "m5", name: "Paul" } ] }; let data = extract_data!(result).unwrap(); @@ -1754,7 +1767,7 @@ fn include_directive_works_with_query_variables() { run_query((QUERY, object! { include: false }), |result, _| { // Assert that only names are returned - let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie"] + let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie", "Paul"] .into_iter() .map(|name| object! { name: name }) .collect(); @@ -1894,7 +1907,7 @@ fn skip_is_nullable() { "; run_query(QUERY, |result, _| { - let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie"] + let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie", "Paul"] .into_iter() .map(|name| object! { name: name }) .collect(); @@ -1915,7 +1928,7 @@ fn first_is_nullable() { "; run_query(QUERY, |result, _| { - let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie"] + let musicians: Vec<_> = ["John", "Lisa", "Tom", "Valerie", "Paul"] .into_iter() .map(|name| object! { name: name }) .collect(); @@ -1992,7 +2005,8 @@ fn can_filter_by_relationship_fields() { let exp = object! { musicians: vec![ - object! { id: "m3", name: "Tom", mainBand: object! { id: "b2"} } + object! { id: "m3", name: "Tom", mainBand: object! { id: "b2"} }, + object! { id: "m5", name: "Paul", mainBand: object! { id: "b2"} } ], bands: vec![ object! { @@ -2074,6 +2088,10 @@ fn can_use_nested_filter() { object! { name: "Valerie", bands: Vec::::new(), + }, + object! { + name: "Paul", + bands: vec![ object! { id: "b2" }] } ] }; @@ -2096,7 +2114,7 @@ fn ignores_invalid_field_arguments() { // Without validations Ok(Some(r::Value::Object(obj))) => match obj.get("musicians").unwrap() { r::Value::List(lst) => { - assert_eq!(4, lst.len()); + assert_eq!(5, lst.len()); } _ => panic!("expected a list of values"), }, @@ -2202,6 +2220,7 @@ fn missing_variable() { object! { id: "m2" }, object! { id: "m3" }, object! { id: "m4" }, + object! { id: "m5" }, ] }; @@ -2232,6 +2251,7 @@ fn missing_variable() { object! { id: "m2" }, object! { id: "m3" }, object! { id: "m4" }, + object! { id: "m5" }, ] }; @@ -2326,13 +2346,15 @@ fn query_at_block() { up to block number 2 and data for block number 3 is therefore not yet available"; const BLOCK_HASH_NOT_FOUND: &str = "no block with that hash found"; + let all_musicians = vec!["m1", "m2", "m3", "m4", "m5"]; + musicians_at("number: 7000", Err(BLOCK_NOT_INDEXED), "n7000"); musicians_at("number: 0", Ok(vec!["m1", "m2"]), "n0"); - musicians_at("number: 1", Ok(vec!["m1", "m2", "m3", "m4"]), "n1"); + musicians_at("number: 1", Ok(all_musicians.clone()), "n1"); musicians_at(&hash(&BLOCKS[0]), Ok(vec!["m1", "m2"]), "h0"); - musicians_at(&hash(&BLOCKS[1]), Ok(vec!["m1", "m2", "m3", "m4"]), "h1"); - musicians_at(&hash(&BLOCKS[2]), Ok(vec!["m1", "m2", "m3", "m4"]), "h2"); + musicians_at(&hash(&BLOCKS[1]), Ok(all_musicians.clone()), "h1"); + musicians_at(&hash(&BLOCKS[2]), Ok(all_musicians.clone()), "h2"); musicians_at(&hash(&BLOCKS[3]), Err(BLOCK_NOT_INDEXED2), "h3"); musicians_at(&hash(&BLOCKS[4]), Err(BLOCK_HASH_NOT_FOUND), "h4"); } @@ -2371,17 +2393,19 @@ fn query_at_block_with_vars() { up to block number 2 and data for block number 3 is therefore not yet available"; const BLOCK_HASH_NOT_FOUND: &str = "no block with that hash found"; + let all_musicians = vec!["m1", "m2", "m3", "m4", "m5"]; + musicians_at_nr(7000, Err(BLOCK_NOT_INDEXED), "n7000"); musicians_at_nr(0, Ok(vec!["m1", "m2"]), "n0"); - musicians_at_nr(1, Ok(vec!["m1", "m2", "m3", "m4"]), "n1"); + musicians_at_nr(1, Ok(all_musicians.clone()), "n1"); musicians_at_nr_gte(7000, Err(BLOCK_NOT_INDEXED), "ngte7000"); - musicians_at_nr_gte(0, Ok(vec!["m1", "m2", "m3", "m4"]), "ngte0"); - musicians_at_nr_gte(1, Ok(vec!["m1", "m2", "m3", "m4"]), "ngte1"); + musicians_at_nr_gte(0, Ok(all_musicians.clone()), "ngte0"); + musicians_at_nr_gte(1, Ok(all_musicians.clone()), "ngte1"); musicians_at_hash(&BLOCKS[0], Ok(vec!["m1", "m2"]), "h0"); - musicians_at_hash(&BLOCKS[1], Ok(vec!["m1", "m2", "m3", "m4"]), "h1"); - musicians_at_hash(&BLOCKS[2], Ok(vec!["m1", "m2", "m3", "m4"]), "h2"); + musicians_at_hash(&BLOCKS[1], Ok(all_musicians.clone()), "h1"); + musicians_at_hash(&BLOCKS[2], Ok(all_musicians.clone()), "h2"); musicians_at_hash(&BLOCKS[3], Err(BLOCK_NOT_INDEXED2), "h3"); musicians_at_hash(&BLOCKS[4], Err(BLOCK_HASH_NOT_FOUND), "h4"); } @@ -2822,6 +2846,7 @@ fn can_query_with_or_and_filter() { musicians: vec![ object! { name: "John", id: "m1" }, object! { name: "Tom", id: "m3" }, + object! { name: "Paul", id: "m5" }, ], }; let data = extract_data!(result).unwrap(); @@ -2847,6 +2872,7 @@ fn can_query_with_or_explicit_and_filter() { musicians: vec![ object! { name: "John", id: "m1" }, object! { name: "Tom", id: "m3" }, + object! { name: "Paul", id: "m5" }, ], }; let data = extract_data!(result).unwrap(); @@ -3049,3 +3075,79 @@ fn simple_aggregation() { assert_eq!(data, exp); }) } + +/// Check that if we have entities where a related entity is null, followed +/// by one where it is not null that the children are joined correctly to +/// their respective parent +#[test] +fn children_are_joined_correctly() { + // Get just the `id` for the `mainBand` and `bands` + const QUERY1: &str = " + query { + musicians { + id + mainBand { id } + bands { id } + } + } + "; + + // Get the `id` and one more attribute for the `mainBand` and `bands` + const QUERY2: &str = " + query { + musicians { + id + mainBand { id name } + bands { id name } + } + } + "; + + run_query(QUERY1, |result, _| { + fn b1() -> r::Value { + object! { id: "b1" } + } + fn b2() -> r::Value { + object! { id: "b2" } + } + let null = r::Value::Null; + let none = Vec::::new(); + + let exp = object! { + musicians: vec![ + object! { id: "m1", mainBand: b1(), bands: vec![ b1(), b2() ] }, + object! { id: "m2", mainBand: b1(), bands: vec![ b1() ] }, + object! { id: "m3", mainBand: b2(), bands: vec![ b1(), b2() ] }, + object! { id: "m4", mainBand: null, bands: none }, + object! { id: "m5", mainBand: b2(), bands: vec![ b2() ] }, + ], + }; + + let data = extract_data!(result).unwrap(); + assert_eq!(data, exp); + }); + + run_query(QUERY2, |result, _| { + fn b1() -> r::Value { + object! { id: "b1", name: "The Musicians" } + } + fn b2() -> r::Value { + object! { id: "b2", name: "The Amateurs" } + } + let null = r::Value::Null; + let none = Vec::::new(); + + let exp = object! { + musicians: vec![ + object! { id: "m1", mainBand: b1(), bands: vec![ b1(), b2() ] }, + object! { id: "m2", mainBand: b1(), bands: vec![ b1() ] }, + object! { id: "m3", mainBand: b2(), bands: vec![ b1(), b2() ] }, + object! { id: "m4", mainBand: null, bands: none }, + object! { id: "m5", mainBand: b2(), bands: vec![ b2() ] }, + ], + }; + + let data = extract_data!(result).unwrap(); + assert_eq!(data, exp); + }); +}