Skip to content
This repository has been archived by the owner on Sep 3, 2021. It is now read-only.

Translating fragments on interface types #394

Merged
merged 13 commits into from
Feb 14, 2020
Merged

Translating fragments on interface types #394

merged 13 commits into from
Feb 14, 2020

Conversation

michaeldgraham
Copy link
Collaborator

This PR attempts general support for using inline fragments and fragment spreads when querying interface or object types.

The translation strategy for interface types is extended to handle the complexity that arises from possibly using combinations of inline or spread fragments when querying an interface.

For example, consider this reduced version of the types used in the tests added by this PR:

interface Camera {
  id: ID!
  type: String
}

type OldCamera implements Camera {
  id: ID!
  type: String
  smell: String
}

type NewCamera implements Camera {
  id: ID!
  type: String
  features: [String]
}

A query for an interface type might select only fields on that interface.

query {
  Camera {
    id
    type
  }
}

In contrast, it might select only fields on types implementing the interface by using some combination of fragments.

query {
  Camera {
    ... on OldCamera {
      smell
    }
    ...NewCameraFragment
  }
}

fragment NewCameraFragment on NewCamera {
  features
}

Perhaps more likely, both fields on the interface type and the types implementing it could be selected.

query {
  Camera {
    id
    ... on OldCamera {
      smell
    }
    ...NewCameraFragment
  }
}

fragment NewCameraFragment on NewCamera {
  features
}

Tests

The following 31 translation tests have been added to test/unit/cypherTest.test.js:

query only an interface field
query {
  Camera {
    id
  }
}
query only interface fields
query {
  Camera {
    id
    type
    make
    weight
  }
}
query only interface fields using untyped inline fragment
query {
  Camera {
    id
    type
    ... {
      make
      weight
    }
  }
}
query only fields on an implementing type using an inline fragment
query {
  Camera {
    ... on OldCamera {
      id
      type
    }
  }
}
query same field on implementing type using inline fragment
query {
  Camera {
    id
    ... on OldCamera {
      id
    }
  }
}
query interface and implementing type using inline fragment
query {
  Camera {
    id
    ... on OldCamera {
      id
      type
    }
  }
}
query interface and implementing type using fragment spread
query {
  Camera {
    id
    ...NewCameraFragment
  }
}

fragment NewCameraFragment on NewCamera {
  id
  type
}
query interface and implementing types using inline fragment and fragment spread
query {
  Camera {
    id
    ...NewCameraFragment
    make
    ... on OldCamera {
      smell
    }
  }
}

fragment NewCameraFragment on NewCamera {
  id
  type
  features
}
query interface using multiple fragments on the same implementing type
query {
  Camera {
    weight
    ... on NewCamera {
      id
      operators {
        name
      }
    }
    ...NewCameraFragment
  }
}

fragment NewCameraFragment on NewCamera {
  type
  operators {
    userId
  }
}
Computed query
query only computed interface fields
query {
  CustomCameras {
    id
    type
    make
    weight
  }
}
query only computed fields on an implementing type using an inline fragment
query {
  CustomCameras {
    ... on OldCamera {
      id
      type
    }
  }
}
query computed interface fields using fragments on implementing types
query {
  CustomCameras {
    id
    ... on OldCamera {
      type
    }
    ...NewCameraFragment
    __typename
  }
}

fragment NewCameraFragment on NewCamera {
  type
  weight
}
Field arguments
pagination used on root and nested interface type field
query {
  Camera(first: 2, offset: 1) {
    id
    type
    make
    weight
    operators(first: 1, offset: 1) {
      name
      ... on CameraMan {
        userId
      }
    }
  }
}
ordering used on root and nested interface type field
query {
  Camera(orderBy: type_asc) {
    id
    type
    make
    weight
    operators(orderBy: name_desc) {
      name
      ... on CameraMan {
        userId
      }
    }
  }
}
filtering used on root and nested interface type field with only fragments
query {
  Camera(
    filter: {
      operators_not: null
    }
  ) {
    ... on NewCamera {
      operators(
        filter: {
          name_not: null
        }
      ) {
        ... on CameraMan {
          name
        }
      }
    }
  }
}
filtering used on root and nested interface using fragments and query variables
query getCameras($type: String, $operatorsFilter: _PersonFilter, $computedOperatorName: String) {
  Camera(type: $type) {
    id
    type
    ... on NewCamera {
      operators(filter: $operatorsFilter) {
        userId
      }
    }
    ... on OldCamera {
      operators(filter: $operatorsFilter) {
        userId
      }
    }
    operators(filter: $operatorsFilter) {
      userId
    }
    computedOperators(name: $computedOperatorName) {
      userId
      name
    }
  }
}
Computed mutation
query interface type payload of @cypher mutation field
mutation {
  CustomCamera {
    id
    type
  }
}
query interface type list payload of @cypher mutation field using fragments
mutation {
  CustomCameras {
    id
    ... on NewCamera {
      features
    }
    ...OldCameraFragment
    ...CameraFragment
  }
}
fragment CameraFragment on Camera {
  type
}
fragment OldCameraFragment on OldCamera {
  smell
}
query interface type list payload of @cypher mutation field using only fragments
mutation {
  CustomCameras {
    ... on NewCamera {
      features
    }
    ...OldCameraFragment
  }
}

fragment OldCameraFragment on OldCamera {
  smell
}
query interface type relationship field
query {
  Camera {
    id
    type
    make
    weight
    operators {
      userId
      name
      __typename
    }
  }
}
query interface type relationship field using only inline fragment
query {
  Camera {
    ... on OldCamera {
      id
      type
      operators {
        ... on CameraMan {
          userId
          name
        }
      }
    }
  }
}
query interface type relationship field using inline fragment on implementing type
query {
  Camera {
    ... on OldCamera {
      id
      type
      operators {
        userId
        ... on CameraMan {
          name
        }
      }
    }
  }
}
query interface type relationship fields within inline fragment
query {
  Camera {
    id
    type
    make
    weight
    ... on OldCamera {
      operators {
        userId
        name
        __typename
      }
    }
  }
}
query interface type relationship field on implementing types using inline fragment and fragment spread
query {
  Camera {
    id
    type
    weight
    ... on OldCamera {
      id
      operators {
        userId
        name
      }
    }
    ...NewCameraFragment
  }
}

fragment NewCameraFragment on NewCamera {
  id
  operators {
    userId
  }
}
Computed query
query computed interface type relationship field
query {
  CustomCameras {
    id
    type
    make
    weight
    computedOperators {
      userId
      name
      __typename
    }
  }
}
query computed interface type relationship field using only an inline fragment
query {
  CustomCameras {
    id
    type
    make
    weight
    computedOperators {
      ... on CameraMan {
        userId
        name
      }
    }
  }
}
Mutation
query interfaced relationship mutation payload using fragments
mutation someMutation {
  AddActorKnows(from: { userId: "123" }, to: { userId: "456" }) {
    from {
      name
    }
    to {
      name
      ... on User {
        userId
      }
    }
  }
}
query interfaced relationship mutation payload using only fragments
mutation someMutation {
  AddActorKnows(from: { userId: "123" }, to: { userId: "456" }) {
    from {
      name
    }
    to {
      ... on User {
        userId
      }
    }
  }
}
Introspection: __typename field
query only __typename field on interface type
query {
  Camera {
    __typename
  }
}
query only __typename field on interface type relationship field
query {
  Camera {
    id
    operators {
      __typename
    }
  }
}
query interface __typename as only field not within fragments on implementing types
query {
  Camera {
    __typename
    ... on OldCamera {
      id
      type
      operators {
        __typename
        ... on CameraMan {
          userId
          name
        }
      }
    }
  }
}

Limitations

  • Querying interfaced relationship types using fragments are not yet supported
  • Nested selection sets on the same relationship field in multiple fragments on an implementing type of an interface are not thoroughly merged. Only the first selection set for a relationship field within a fragment on an implementing type is used. See test: query interface using multiple fragments on the same implementing type

Upstream merge
…ted queries

Add new functions for building list comprehensions for fragmented queries

Refactor returns from translation functions, remove derivedTypesParams given general use in selections.js
Also refactors some branching to be more explicit to capture translating fragmented selection sets
See: should be able to query custom cypher field returning interface type

Some existing tests for querying object types using fragments are updated
@codecov-io
Copy link

codecov-io commented Feb 13, 2020

Codecov Report

Merging #394 into master will increase coverage by 0.19%.
The diff coverage is 96.02%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #394      +/-   ##
==========================================
+ Coverage    96.3%   96.49%   +0.19%     
==========================================
  Files          24       24              
  Lines        2844     3027     +183     
==========================================
+ Hits         2739     2921     +182     
- Misses        105      106       +1
Impacted Files Coverage Δ
src/index.js 67.14% <0%> (-4.07%) ⬇️
src/utils.js 94.74% <100%> (+1.19%) ⬆️
src/augment/fields.js 100% <100%> (ø) ⬆️
src/selections.js 96.23% <96.36%> (-1.33%) ⬇️
src/translate.js 98.78% <99.09%> (+0.46%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ff9a341...b370f69. Read the comment docs.

@johnymontana johnymontana merged commit 2dd28d9 into neo4j-graphql:master Feb 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants