apollographql / federation

🌐  Build and scale a single data graph across multiple services with Apollo's federation gateway.
https://apollographql.com/docs/federation/
Other
668 stars 254 forks source link

Undefined variable reference in generated subgraph query #3112

Closed carldunham closed 2 months ago

carldunham commented 3 months ago

Describe the bug

This repo includes schema and a sample query that fails validation using the new Rust validation code.

To Reproduce

Steps to reproduce the behavior:

  1. Setup services: clone the repo, run make run
  2. Submit request in query.graphql, provide a value for "time" (can be any enum value).
  3. See error

Expected behavior

The query should succeed.

Output

{
  "errors": [
    {
      "message": "Variable \"$time\" is not defined.",
      "locations": [
        {
          "line": 1,
          "column": 572
        }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    },
    {
      "message": "Variable \"$time\" is not defined.",
      "locations": [
        {
          "line": 1,
          "column": 572
        }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ]
}

yet $time is clearly defined and used.

Desktop (please complete the following information):

Additional context

Router version v1.52.0 Rover version 0.12.2

carldunham commented 3 months ago

Note that the error is repeated. When running older versions of Router, there was not a validation error, but invalid subgraph queries where generated. So it may be that the validation errors are coming from validating the query plan queries, not the main query itself. In those versions, you can also see that the time variable is not included in some nodes that include it.

carldunham commented 3 months ago

Running this with router v1.45.1 gives a different error. I've included the query plan here too:

{
  "data": {
    "feed": {
      "elements": null,
      "__typename": "User",
      "id": ""
    },
    "identity": null
  },
  "errors": [
    {
      "message": "HTTP fetch failed from 'a': 422: Unprocessable Entity",
      "extensions": {
        "code": "SUBREQUEST_HTTP_ERROR",
        "service": "a",
        "reason": "422: Unprocessable Entity",
        "http": {
          "status": 422
        }
      }
    },
    {
      "message": "Variable \"$time\" is not defined by operation \"UserProfile__a__3\".",
      "locations": [
        {
          "line": 1,
          "column": 572
        }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ],
  "extensions": {
    "apolloQueryPlan": {
      "object": {
        "kind": "QueryPlan",
        "node": {
          "kind": "Parallel",
          "nodes": [
            {
              "kind": "Sequence",
              "nodes": [
                {
                  "kind": "Fetch",
                  "serviceName": "b",
                  "variableUsages": [],
                  "operation": "query UserProfile__b__0{feed:userInfoByName(name:\"\"){...userProfileFeedStructuredDataFragment ...on User{...userProfileFeedStructuredDataFragment}}}fragment userProfileFeedStructuredDataFragment on UserInfo{__typename id}",
                  "operationName": "UserProfile__b__0",
                  "operationKind": "query",
                  "id": null,
                  "inputRewrites": null,
                  "outputRewrites": null,
                  "schemaAwareHash": "5fc5cc7a9dccd37fa60d4c039b72978b4cf7fbb48fd034b9f80e70a6c0b95586",
                  "authorization": {
                    "is_authenticated": false,
                    "scopes": [],
                    "policies": []
                  }
                },
                {
                  "kind": "Flatten",
                  "path": [
                    "feed"
                  ],
                  "node": {
                    "kind": "Fetch",
                    "serviceName": "a",
                    "requires": [
                      {
                        "kind": "InlineFragment",
                        "typeCondition": "User",
                        "selections": [
                          {
                            "kind": "Field",
                            "name": "__typename"
                          },
                          {
                            "kind": "Field",
                            "name": "id"
                          }
                        ]
                      }
                    ],
                    "variableUsages": [
                      "time"
                    ],
                    "operation": "query UserProfile__a__1($representations:[_Any!]!$time:TimeRange){_entities(representations:$representations){...on User{elements(time:$time){edges{node{__typename ...on PostInfo{__typename commentInfo{__typename lastAuthorNote{__typename user{__typename ...on UnavailableUser{__typename id}}}}}}}}}}}",
                    "operationName": "UserProfile__a__1",
                    "operationKind": "query",
                    "id": null,
                    "inputRewrites": null,
                    "outputRewrites": null,
                    "schemaAwareHash": "ab243653c1159d74c4717b924beb9d5b61e1f8b7e915f0eb9769fb294df461a8",
                    "authorization": {
                      "is_authenticated": false,
                      "scopes": [],
                      "policies": []
                    }
                  }
                },
                {
                  "kind": "Flatten",
                  "path": [
                    "feed",
                    "elements",
                    "edges",
                    "@",
                    "node",
                    "commentInfo",
                    "lastAuthorNote",
                    "user"
                  ],
                  "node": {
                    "kind": "Fetch",
                    "serviceName": "b",
                    "requires": [
                      {
                        "kind": "InlineFragment",
                        "typeCondition": "UnavailableUser",
                        "selections": [
                          {
                            "kind": "Field",
                            "name": "__typename"
                          },
                          {
                            "kind": "Field",
                            "name": "id"
                          }
                        ]
                      }
                    ],
                    "variableUsages": [],
                    "operation": "query UserProfile__b__2($representations:[_Any!]!){_entities(representations:$representations){...on UnavailableUser{__typename isPermanentlySuspended}}}",
                    "operationName": "UserProfile__b__2",
                    "operationKind": "query",
                    "id": null,
                    "inputRewrites": null,
                    "outputRewrites": null,
                    "schemaAwareHash": "5002545f4885a405e1aa40355d6e15e0b17aea1a6d27d41b4c8f2e6fc1e680c7",
                    "authorization": {
                      "is_authenticated": false,
                      "scopes": [],
                      "policies": []
                    }
                  }
                }
              ]
            },
            {
              "kind": "Sequence",
              "nodes": [
                {
                  "kind": "Fetch",
                  "serviceName": "a",
                  "variableUsages": [],
                  "operation": "query UserProfile__a__3{identity{upvotedPosts{edges{node{__typename ...on Item{__typename}...on PostInfo{__typename commentInfo{__typename lastAuthorNote{__typename user{__typename ...on UnavailableUser{...userProfileFeedStructuredDataFragment}}}}}}}}downvotedPosts{edges{node{__typename ...on Item{__typename}...on PostInfo{__typename commentInfo{__typename lastAuthorNote{__typename user{__typename ...on UnavailableUser{...userProfileFeedStructuredDataFragment}}}}}}}}}}fragment userProfileFeedStructuredDataFragment on UserInfo{__typename id ...on User{elements(time:$time){edges{node{__typename}}}}}",
                  "operationName": "UserProfile__a__3",
                  "operationKind": "query",
                  "id": null,
                  "inputRewrites": null,
                  "outputRewrites": null,
                  "schemaAwareHash": "d12821696b8f892f32d14d379098133d7766565eec359c63bfad804f551c0c02",
                  "authorization": {
                    "is_authenticated": false,
                    "scopes": [],
                    "policies": []
                  }
                },
                {
                  "kind": "Parallel",
                  "nodes": [
                    {
                      "kind": "Flatten",
                      "path": [
                        "identity",
                        "upvotedPosts",
                        "edges",
                        "@",
                        "node",
                        "commentInfo",
                        "lastAuthorNote",
                        "user"
                      ],
                      "node": {
                        "kind": "Fetch",
                        "serviceName": "b",
                        "requires": [
                          {
                            "kind": "InlineFragment",
                            "typeCondition": "UnavailableUser",
                            "selections": [
                              {
                                "kind": "Field",
                                "name": "__typename"
                              },
                              {
                                "kind": "Field",
                                "name": "id"
                              }
                            ]
                          }
                        ],
                        "variableUsages": [],
                        "operation": "query UserProfile__b__4($representations:[_Any!]!){_entities(representations:$representations){...on UnavailableUser{__typename isPermanentlySuspended}}}",
                        "operationName": "UserProfile__b__4",
                        "operationKind": "query",
                        "id": null,
                        "inputRewrites": null,
                        "outputRewrites": null,
                        "schemaAwareHash": "579e20fda4bc09213dc0481b685e5dc09427aea0b516faf42daf7fb187502ec3",
                        "authorization": {
                          "is_authenticated": false,
                          "scopes": [],
                          "policies": []
                        }
                      }
                    },
                    {
                      "kind": "Flatten",
                      "path": [
                        "identity",
                        "downvotedPosts",
                        "edges",
                        "@",
                        "node",
                        "commentInfo",
                        "lastAuthorNote",
                        "user"
                      ],
                      "node": {
                        "kind": "Fetch",
                        "serviceName": "b",
                        "requires": [
                          {
                            "kind": "InlineFragment",
                            "typeCondition": "UnavailableUser",
                            "selections": [
                              {
                                "kind": "Field",
                                "name": "__typename"
                              },
                              {
                                "kind": "Field",
                                "name": "id"
                              }
                            ]
                          }
                        ],
                        "variableUsages": [],
                        "operation": "query UserProfile__b__5($representations:[_Any!]!){_entities(representations:$representations){...on UnavailableUser{__typename isPermanentlySuspended}}}",
                        "operationName": "UserProfile__b__5",
                        "operationKind": "query",
                        "id": null,
                        "inputRewrites": null,
                        "outputRewrites": null,
                        "schemaAwareHash": "f5f8264c18ca96ba2a5ab7e1a067d027b1ec444e2791a9e13c99198986d1cad9",
                        "authorization": {
                          "is_authenticated": false,
                          "scopes": [],
                          "policies": []
                        }
                      }
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  }
}
goto-bus-stop commented 3 months ago

Interesting, thanks for the report and the detailed reproduction! This looks like a bug in the JS query planner. The generated subgraph query is the one that doesn't have the right variables defined. Did it work in a previous Router version?

carldunham commented 3 months ago

Did it work in a previous Router version?

We have seen this in v1.33.2, but not sure about earlier versions. What would be one to test?

goto-bus-stop commented 3 months ago

Sorry, does that mean it worked in 1.33.2, or that it already didn't work in 1.33.2? 😄

I'm just looking to find out if this is a recent regression in query planning or if it's something that has existed for a long time.

carldunham commented 3 months ago

We have seen this in v1.33.2

That means we are seeing this bug in v1.33.2. it is easily reproducible in my test repo with whatever version you care to try.

goto-bus-stop commented 3 months ago

OK perfect, thank you. It looks similar to an issue we had in the work-in-progress Rust query planner that has since been fixed.

Moved the issue to the federation repo so the JS query planner team can have a look at prioritising this.

duckki commented 3 months ago

I agree this looks similar to the Rust QP bug that @goto-bus-stop fixed recently.

clenfest commented 3 months ago

Hey @carldunham, can you confirm that this is not an issue if you have query fragment generation turned on? i.e. the following in your router yaml:

supergraph:
  generate_query_fragments: true

Not sure if that's an option for you while waiting for the fix.

carldunham commented 3 months ago

Hi, Chris. I see the same behavior with that setting. Tried setting it to true and to false, still having the same result.