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

Query planner fails when using @skip or @include directives with input params #6196

Open
nathanmarcos opened this issue Oct 25, 2024 · 0 comments

Comments

@nathanmarcos
Copy link

Describe the bug

Hello, I think we just hit an edge case where the query planner fails to create a plan for a query that uses @skip or @include directives for a field that has @requires and also accepts inputs.

To Reproduce

Steps to reproduce the behavior:

  1. We will need 2 subgraphs, in this example, product and shipping.

Product Schema:

  @link(
    url: "https://specs.apollo.dev/federation/v2.3"
    import: ["@key"]
  )

  type Query {
    allProducts: [Product!]
  }

  type Product @key(fields: "id") {
    id: ID!
    dimensions(unitType: UnitType): ProductDimensions
  }

  type ProductDimensions {
    size: Int
    weight: Int
  }

  enum UnitType {
    METRIC
    IMPERIAL
  }

Shipping Schema:

  @link(
    url: "https://specs.apollo.dev/federation/v2.3"
    import: ["@key", "@external", "@requires"]
  )

  extend type Product @key(fields: "id") {
    id: ID!
    dimensions(unitType: UnitType): ProductDimensions @external
    shippingEstimate: Int @requires(fields: "dimensions { size weight }")
  }

  extend type ProductDimensions {
    size: Int @external
    weight: Int @external
  }

  enum UnitType {
    METRIC
    IMPERIAL
  }

The supergraph:

schema
  @link(url: "https://specs.apollo.dev/link/v1.0")
  @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
{
  query: Query
}

directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE

directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION

directive @join__graph(name: String!, url: String!) on ENUM_VALUE

directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE

directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR

directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION

directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA

scalar join__FieldSet

enum join__Graph {
  PRODUCT @join__graph(name: "product", url: "http://localhost:4010")
  SHIPPING @join__graph(name: "shipping", url: "http://localhost:4011")
}

scalar link__Import

enum link__Purpose {
  """
  `SECURITY` features provide metadata necessary to securely resolve fields.
  """
  SECURITY

  """
  `EXECUTION` features provide metadata necessary for operation execution.
  """
  EXECUTION
}

type Product
  @join__type(graph: PRODUCT, key: "id")
  @join__type(graph: SHIPPING, key: "id", extension: true)
{
  id: ID!
  dimensions(unitType: UnitType): ProductDimensions @join__field(graph: PRODUCT) @join__field(graph: SHIPPING, external: true)
  shippingEstimate: Int @join__field(graph: SHIPPING, requires: "dimensions { size weight }")
}

type ProductDimensions
  @join__type(graph: PRODUCT)
  @join__type(graph: SHIPPING)
{
  size: Int @join__field(graph: PRODUCT) @join__field(graph: SHIPPING, external: true)
  weight: Int @join__field(graph: PRODUCT) @join__field(graph: SHIPPING, external: true)
}

type Query
  @join__type(graph: PRODUCT)
  @join__type(graph: SHIPPING)
{
  allProducts: [Product!] @join__field(graph: PRODUCT)
}

enum UnitType
  @join__type(graph: PRODUCT)
  @join__type(graph: SHIPPING)
{
  METRIC @join__enumValue(graph: PRODUCT) @join__enumValue(graph: SHIPPING)
  IMPERIAL @join__enumValue(graph: PRODUCT) @join__enumValue(graph: SHIPPING)
}
  1. Shoot the following query to router:
query AllProducts {
  allProducts {
    id
    dimensions(unitType: IMPERIAL) {
      size
      weight
    }
    shippingEstimate @include(if: true)
  }
}
  1. You will get the following error:
  • When using the JS query planner:
"Fields \"dimensions\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional." 
  • When using the Rust query planner:
"value retrieval failed: Federation error: An internal error has occurred, please report this bug to Apollo.\n\nDetails: Query planning produced an invalid subgraph operation.\nError: operation must not provide conflicting field arguments for the same name `dimensions`\n"

Expected behavior

Ideally this should not fail. Also, when removing either the unitType input or the @include directive from the query it works just fine.

Desktop (please complete the following information):

  • OS: macOS Sonoma 14.6.1
  • Router Version: v1.57.0

Off-topic but also related (Maybe this deserves another dedicated issue)

We would love to be able to tell router to use the unitType input (when available in the query) when fetching the field to fulfill the the @requires directive, example:

  extend type Product @key(fields: "id") {
    id: ID!
    dimensions(unitType: UnitType): ProductDimensions @external
    shippingEstimate: Int @requires(fields: "dimensions(unitType: $unitType) { size weight }")
    Inject it here ---------------------------------------------------^
  }

So both requested dimensions field and the shippingEstimate would use the same input defined by the client. Currently we can't guarantee that the dimensions used to calculate the shippingEstimate will use the correct unitType.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant