Azure / data-api-builder

Data API builder provides modern REST and GraphQL endpoints to your Azure Databases and on-prem stores.
https://aka.ms/dab/docs
MIT License
785 stars 142 forks source link

[Bug]: GraphQL query execution fails after dab update #2266

Closed evacotelin closed 1 week ago

evacotelin commented 1 week ago

What happened?

We have the following GraphQL schema file:

type AppReport @model {
  id: ID
  description: String
  reportType: String
  reportSubType: String
  status: String
  latitude: Float
  longitude: Float
  created: DateTime
  createdBy: String
  createdById: String
  approved: Boolean
  approvedBy: String
  approvedDate: DateTime
  attachments: [ReportAttachment]
  deleted: Boolean
  emailSent: Boolean
  caseResolutionText: String
}

type ReportAttachment {
  id: ID
  type: String
  url: String
  latitude: Float
  longitude: Float
}

type UserAction @model {
  id: ID
  reportId: String
  type: String
  created: DateTime
  createdBy: String
  createdById: String
  confirmed: Boolean
  confirmedBy: String
  confirmedDate: DateTime
  content: String
  deleted: Boolean
  caseResolutionText: String
}

type UserSubscription @model {
  id: ID
  _ts: String
  userId: String
  subscriptionType: String
  registrationType: String
  registrationId: String
  areaId: String
}

And the following config file:

{
  "$schema": "https://github.com/Azure/data-api-builder/releases/download/v1.1.7/dab.draft.schema.json",
  "data-source": {
    "database-type": "cosmosdb_nosql",
    "connection-string": "@env('DB_CONNECTION_STRING')",
    "options": {
      "database": "ecovocegql",
      "schema": "staticwebapp.database.schema.gql"
    }
  },
  "runtime": {
    "graphql": {
      "enabled": true,
      "path": "/graphql"
    },
    "host": {
      "cors": {
        "origins": ["*"],
        "allow-credentials": false
      },
      "authentication": {
        "provider": "StaticWebApps"
      },
      "mode": "production"
    }
  },
  "entities": {
    "AppReport": {
      "source": "reports",
      "graphql": true,
      "permissions": [
        {
          "role": "anonymous",
          "actions": ["*"]
        }
      ]
    },
    "UserAction": {
      "graphql": true,
      "source": "actions",
      "permissions": [
        {
          "role": "anonymous",
          "actions": ["*"]
        }
      ]
    },
    "UserSubscription": {
      "graphql": true,
      "source": "subscriptions",
      "permissions": [
        {
          "role": "anonymous",
          "actions": ["*"]
        }
      ]
    }
  }
}

After the dab update, when executing a getReports query, we get this error: "The given key 'ReportAttachment' was not present in the dictionary." This used to work before the update, now there is some sort of validation that requires the nested 'ReportAttachment' field to be not empty, which is not always the case.

Version

1.1.7+74ea6c5f37f8629fd7f8b13fc56027bf0bf0a93a

What database are you using?

CosmosDB NoSQL

What hosting model are you using?

Static Web Apps (SWA)

Which API approach are you accessing DAB through?

GraphQL

Relevant log output

fail: Azure.DataApiBuilder.Service.Startup[0]
      A GraphQL request execution error occurred.
      System.Collections.Generic.KeyNotFoundException: The given key 'ReportAttachment' was not present in the dictionary.
         at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
         at Azure.DataApiBuilder.Core.Authorization.AuthorizationResolver.GetDBPolicyForRequest(String entityName, String roleName, EntityActionOperation operation) in /_/src/Core/Authorization/AuthorizationResolver.cs:line 201
         at Azure.DataApiBuilder.Core.Authorization.AuthorizationResolver.ProcessDBPolicy(String entityName, String roleName, EntityActionOperation operation, HttpContext httpContext) in /_/src/Core/Authorization/AuthorizationResolver.cs:line 188
         at Azure.DataApiBuilder.Core.Resolvers.AuthorizationPolicyHelpers.ProcessFilter(HttpContext context, IAuthorizationResolver authorizationResolver, ISqlMetadataProvider sqlMetadataProvider, String clientRoleHeader, List`1 elementalOperations, String entityName, DatabaseObject entityDBObject, Action`2 postProcessCallback) in /_/src/Core/Resolvers/AuthorizationPolicyHelpers.cs:line 161
         at Azure.DataApiBuilder.Core.Resolvers.AuthorizationPolicyHelpers.ProcessAuthorizationPolicies(EntityActionOperation operationType, BaseQueryStructure queryStructure, HttpContext context, IAuthorizationResolver authorizationResolver, ISqlMetadataProvider sqlMetadataProvider) in /_/src/Core/Resolvers/AuthorizationPolicyHelpers.cs:line 77
         at Azure.DataApiBuilder.Core.Resolvers.CosmosQueryStructure.Init(IDictionary`2 queryParams) in /_/src/Core/Resolvers/CosmosQueryStructure.cs:line 152
         at Azure.DataApiBuilder.Core.Resolvers.CosmosQueryEngine.ExecuteAsync(IMiddlewareContext context, IDictionary`2 parameters, String dataSourceName) in /_/src/Core/Resolvers/CosmosQueryEngine.cs:line 72
         at Azure.DataApiBuilder.Service.Services.ExecutionHelper.ExecuteQueryAsync(IMiddlewareContext context) in /_/src/Core/Services/ExecutionHelper.cs:line 79
         at ResolverTypeInterceptor.<>c__DisplayClass5_1.<<-ctor>b__5>d.MoveNext() in /_/src/Core/Services/ResolverTypeInterceptor.cs:line 23
      --- End of stack trace from previous location ---
         at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)
         at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)

Code of Conduct

MrMossevig commented 1 week ago

We have the exact same issue now and our application is completely broken because of it.

We were using GraphQL on top of CosmosDB and it was working fine until a couple of days ago (when SWA started using the new version of DAB).

Our configuration is as follows:

type InverterData {
  Name: String
  Value: String
  Unit: String
}

type ErrorBody {
  error_data: [InverterData]
  error_id: String
  error_number: Int
  collection_time: String
  stamp_time: String
}

type InformationBody {
  collection_data: [InverterData]
  sn: String
  stamp_time: Int
  collection_time: Int
  stamp_hash: String
}

type Telemetry @model {
  id: ID
  partitionKey: String
  Body: InformationBody
  _ts: Int
}

type Historical @model {
  id: ID
  partitionKey: String
  Body: InformationBody
  _ts: Int
}

type Error @model {
  id: ID
  partitionKey: String
  Body: ErrorBody
  _ts: Int
}

We have tried multiple solutions, but our application is still broken. I have checked for empty bodies, but in our case it looks like all documents are well-defined and that this is a more general problem.

The error reply returned for all queries returning are:

{
    "errors": [
        {
            "message": "The given key 'InformationBody' was not present in the dictionary.",
            "locations": [
                {
                    "line": 2,
                    "column": 5
                }
            ],
            "path": [
                "telemetries"
            ]
        }
    ]
}

@evacotelin Did you manage to find a workaround?

evacotelin commented 1 week ago

Hi @MrMossevig ,

We found the following workaround for this specific error: "The given key 'ReportAttachment' was not present in the dictionary."

Added the "@ model" annotation to the nested type aswell:

type AppReport @model {
  id: ID
  description: String
  reportType: String
  reportSubType: String
  status: String
  latitude: Float
  longitude: Float
  created: DateTime
  createdBy: String
  createdById: String
  approved: Boolean
  approvedBy: String
  approvedDate: DateTime
  attachments: [ReportAttachment]
  deleted: Boolean
  emailSent: Boolean
  caseResolutionText: String
}

type ReportAttachment @model {    // added annotation
  id: ID
  type: String
  url: String
  latitude: Float
  longitude: Float
}

And then added the nested type to the config file entities:

{
    // ... ,
    "entities": {
        // ... ,
        "ReportAttachment": {
              "source": "reports", // note that this is the "parent" entity
              "graphql": true,
              "permissions": [
                {
                  "role": "anonymous",
                  "actions": ["*"]
                }
              ]
            }
    }
}

Hope it helps!

MrMossevig commented 1 week ago

Thank you @evacotelin! It absolutely helped us.

After reading you reply and doing some testing I realized that the only thing it actually checks is whether the types defined in the graphql-schema are present in the entity dictionary.

So the only change I had to do was to add minimal entities for the types:

// ...
  "entities": {
    "InformationBody": {
      "source": "a",
      "graphql": true,
      "permissions": [
      ]
    },
    "ErrorBody": {
      "source": "b",
      "graphql": true,
      "permissions": [
      ]
    },
    "InverterData": {
      "source": "c",
      "graphql": true,
      "permissions": [
      ]
    },
    // ...
   }
// ...

I did not have to add the @model to the graphql-schema types.

sajeetharan commented 1 week ago

As @evacotelin suggested, this behavior is expected since all entities in the config should exist in the GraphQL schema. You can only query entities defined in the GraphQL schema.