Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to filter queries by child entity #3184

Merged
merged 1 commit into from
Jun 27, 2022
Merged

Conversation

kamilkisiela
Copy link
Contributor

@kamilkisiela kamilkisiela commented Jan 25, 2022

#2960

Given the schema:

type Purpose @entity {
  id: ID!
  sender: Sender!
  purpose: String!
  createdAt: BigInt!
  transactionHash: String!
}

type Sender @entity {
  id: ID!
  address: Bytes!
  purposes: [Purpose!] @derivedFrom(field: "sender")
  createdAt: BigInt!
  purposeCount: BigInt!
}

The sender_ is added to Purpose_filter input object.

input Purpose_filter {
    ...
+  sender_: Sender_filter
}

The Sender_filter input object is untouched.

Example

query {
  purposes(
    where: {
      purpose_contains: "...",
      sender_: {
        id: "..."
      }
    }
  ) {
    id
    sender {
      id
      purposeCount
    }
  }
}
select
  'Purpose' as entity,
  to_jsonb(c.*) as data
from
  (
    select
      c.*
    from
      "sgd1"."purpose" c
    where
      c.block_range @ > $ 1
      and c."purpose" like $ 2
      and (
        exists (
          select
            1
          from
            "sgd1"."sender" as i
          where
            c."sender" = i."id"
            and i."block_range" @ > $ 3
            and i."id" = $ 4
        )
      )
    group by
      c.id
    order by
      c."id"
    limit
      100
  ) c

Child filters are limited to be only 1 level deep, so no Child(Child(...)) and this pull request DOES NOT implement a support for interface types.

Continues #3022

@dotansimha dotansimha changed the title Filter by child entity graph, graphql: filter by child entity Jan 26, 2022
@kamilkisiela kamilkisiela force-pushed the kamil-filter-by-child branch from 9afb13b to 46d2872 Compare March 3, 2022 17:02
@tilacog tilacog self-requested a review March 4, 2022 01:11
@lutter
Copy link
Collaborator

lutter commented Mar 7, 2022

Could you rebase this to latest master when you get a chance?

@dotansimha dotansimha force-pushed the kamil-filter-by-child branch from 46d2872 to 771a995 Compare March 8, 2022 15:30
@dotansimha
Copy link
Contributor

@lutter done! :)

Note that this PR is only for filtering, for sorting we have this one: #3096 (targeted into this PR, not master, and @kamilkisiela had some questions there before proceeding).

@dotansimha dotansimha requested a review from lutter March 8, 2022 16:19
store/postgres/src/relational_queries.rs Outdated Show resolved Hide resolved
store/postgres/src/relational_queries.rs Outdated Show resolved Hide resolved
store/postgres/src/relational_queries.rs Outdated Show resolved Hide resolved
@dotansimha dotansimha force-pushed the kamil-filter-by-child branch 2 times, most recently from 809b6fd to f34918a Compare March 13, 2022 14:48
@kamilkisiela kamilkisiela force-pushed the kamil-filter-by-child branch from f34918a to 2a77b5d Compare April 5, 2022 10:51
@kamilkisiela kamilkisiela force-pushed the kamil-filter-by-child branch 2 times, most recently from 1e9f27b to 5523cf7 Compare April 27, 2022 12:35
@dotansimha dotansimha linked an issue May 2, 2022 that may be closed by this pull request
@dotansimha dotansimha force-pushed the kamil-filter-by-child branch from 5523cf7 to 6e04d05 Compare May 3, 2022 06:27
@dotansimha dotansimha changed the title graph, graphql: filter by child entity feat(graph,graphql): filter by child entity May 3, 2022
@dotansimha dotansimha force-pushed the kamil-filter-by-child branch from 6e04d05 to 6f9312f Compare May 3, 2022 06:28
@kamilkisiela kamilkisiela force-pushed the kamil-filter-by-child branch 3 times, most recently from b863adb to 461a827 Compare May 4, 2022 09:38
@dotansimha dotansimha requested review from saihaj, lutter and azf20 May 4, 2022 13:39
@dizzyd dizzyd assigned dizzyd and lutter and unassigned dizzyd May 9, 2022
Copy link
Collaborator

@lutter lutter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting closer! I actually hadn't thought about the issue that forced you to use group by but I think using the exists(select 1 ..) strategy I outlined in the comment will make this easier.

We'll also need tests for all this; the best place for them is probably graphql/tests/query.rs - I recently revamped them to (hopefully) make it easier to add tests there. The tests for this should use child filters that traverse Musician.bands, Musician.written_songs, Band.members, Song.written_by and SongStat.song. For example, what I have in mind for Musician.bands is something like musicians(where: { bands_: { name: "something" } }) { id }

graph/src/components/store/mod.rs Outdated Show resolved Hide resolved
store/postgres/src/relational_queries.rs Outdated Show resolved Hide resolved
store/postgres/src/relational_queries.rs Outdated Show resolved Hide resolved
graphql/tests/query.rs Outdated Show resolved Hide resolved
graphql/src/schema/api.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@lutter lutter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting really, really close. There's a few smallish things to touch up, but other than that, this is almost ready.

@@ -82,6 +82,19 @@ impl From<&str> for EntityType {

impl CheapClone for EntityType {}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EntityFilterDerivative(bool);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be better as an enum AttributeStorage { Direct, Derived }; even clearer would be to make EntityFilter::Child a struct, i.e.

    Child {
        attr: Attribute,
        entity_type: EntityType,
        filter: Box<EntityFilter>,
        derived: bool,
    },

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphql/src/schema/api.rs Show resolved Hide resolved
return Some(input_values);
}

let input_field_type = input_field_type.unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, from line 401, would be clearer and avoid an unwrap() like this:

        let input_field_type = match input_field_type {
            None => {
                let mut input_values: Vec<InputValue> = vec![];

                if let Some(parent) = parent_type_name {
                    extend_with_child_filter_input_value(field, &parent, &mut input_values);
                }

                return Some(input_values);
            }
            Some(input_field_type) => input_field_type,
        };

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, even better to do what follows in the match, too and avoid the early return:

        let mut input_values: Vec<InputValue> = match input_field_type {
            None => {
                vec![]
            }
            Some(input_field_type) => vec![
                "",
                "not",
                "contains",
                "contains_nocase",
                "not_contains",
                "not_contains_nocase",
            ]
            .into_iter()
            .map(|filter_type| {
                input_value(
                    &field.name,
                    filter_type,
                    Type::ListType(Box::new(Type::NonNullType(Box::new(
                        input_field_type.clone(),
                    )))),
                )
            })
            .collect(),
        };

        if let Some(parent) = parent_type_name {
            extend_with_child_filter_input_value(field, &parent, &mut input_values);
        }

        Some(input_values)

This could also be a let mut input_values = input_field_type.map(|input_field_type| ...).unwrap_or(vec![]) but I am fine with either

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type::NonNullType(inner) => resolve_type_name(inner),
Type::NamedType(name) => name,
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is get_base_type from graph/src/data/graphql (just do something like use graph::data::graphql::TypeExt as _ to be able to use it)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphql/tests/query.rs Show resolved Hide resolved
Child(attr, entity, child_filter, _) => {
if child_filter_ancestor {
return Err(StoreError::QueryExecutionError(
"Only a single level sub filter is allowed".to_string(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about something like Child filters can not be nested?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


out.push_sql("exists (select 1");

out.push_sql(" from ");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor but these two push_sql could be collapsed into one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let parent_column = self.table.primary_key();

if child_column.is_list() {
// Type A: p.id = any(c.{parent_field})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't that be c.id = any(i.{parent_field}) ? And similar in the comments for the other cases

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out.push_sql(child_prefix);
out.push_identifier(BLOCK_RANGE_COLUMN)?;
out.push_sql(" @> ");
out.push_bind_param::<Integer, _>(&self.block)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to use BlockRangeColumn::contains - the way it is written now only works for mutable entities, but would fail for immutable ones since they don't have a block_range column, only a block$ column

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lutter
Copy link
Collaborator

lutter commented Jun 20, 2022

This is good to go - there's one small thing around how the commits are structured/named: see this for how we usually do that. With this PR, it might be best to just squash everything into a mega commit all: Add child filters

@LukvonStrom
Copy link

@ALL (@lutter, @kamilkisiela) when is the ETA for merge of this awesome addition to the graphprotocol? :)

@kamilkisiela kamilkisiela changed the title feat(graph,graphql): filter by child entity Allow to filter queries by child entity Jun 27, 2022
@kamilkisiela kamilkisiela force-pushed the kamil-filter-by-child branch from 474af82 to b68d755 Compare June 27, 2022 11:28
@kamilkisiela kamilkisiela force-pushed the kamil-filter-by-child branch from b68d755 to 976dc62 Compare June 27, 2022 11:29
@kamilkisiela
Copy link
Contributor Author

@lutter I squashed all commits and renamed it

@mhpaler
Copy link

mhpaler commented Aug 6, 2022

@kamilkisiela @lutter and anyone else involved in this PR. Kinda late to the party on this one, but I'd like to offer a HUGE thank you for getting this into the graph-node. It's just such an amazing boost to querying. Thank you!!

@kamilkisiela kamilkisiela deleted the kamil-filter-by-child branch August 8, 2022 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Filtering on the basis of child entities
7 participants