aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.41k stars 2.12k forks source link

Nested Relational Data Not Returned in GraphQL API Queries on AWS Amplify #13126

Closed Glognus closed 5 months ago

Glognus commented 6 months ago

Before opening, please confirm:

JavaScript Framework

React, React Native

Amplify APIs

GraphQL API

Amplify Version

v6

Amplify Categories

api

Backend

Amplify CLI

Environment information

``` System: OS: macOS 13.6.5 CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz Memory: 2.91 GB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 21.7.1 - /usr/local/bin/node Yarn: 1.22.19 - /usr/local/bin/yarn npm: 10.5.0 - /usr/local/bin/npm npmPackages: @aws-amplify/datastore-storage-adapter: ^2.1.20 => 2.1.20 @aws-amplify/predictions: ^6.0.20 => 6.0.20 @aws-amplify/react-native: ^1.0.20 => 1.0.20 @aws-amplify/rtn-web-browser: ^1.0.20 => 1.0.20 @aws-amplify/ui-react-native: ^2.1.3 => 2.1.3 @babel/core: ^7.24.0 => 7.24.0 @babel/plugin-proposal-export-namespace-from: ^7.18.9 => 7.18.9 @babel/plugin-proposal-nullish-coalescing-operator: ^7.18.6 => 7.18.6 @babel/plugin-proposal-optional-chaining: ^7.21.0 => 7.21.0 @expo/vector-icons: ^13.0.0 => 13.0.0 (14.0.0) @faker-js/faker: ^8.4.1 => 8.4.1 @gorhom/bottom-sheet: ^4.6.1 => 4.6.1 @react-native-async-storage/async-storage: 1.22.3 => 1.22.3 @react-native-community/netinfo: 11.3.1 => 11.3.1 @react-native-masked-view/masked-view: 0.3.1 => 0.3.1 @react-native-picker/picker: 2.6.1 => 2.6.1 @react-navigation/native: ^6.1.16 => 6.1.16 @shopify/flash-list: 1.6.3 => 1.6.3 @shopify/restyle: ^2.4.2 => 2.4.2 @types/base-64: ^1.0.2 => 1.0.2 @types/react: ~18.2.65 => 18.2.65 (16.14.57) @types/react-native-base64: ^0.2.2 => 0.2.2 @typescript-eslint/eslint-plugin: ^7.2.0 => 7.2.0 @typescript-eslint/parser: ^7.2.0 => 7.2.0 HelloWorld: 0.0.1 aws-amplify: ^6.0.20 => 6.0.20 aws-amplify/adapter-core: undefined () aws-amplify/analytics: undefined () aws-amplify/analytics/kinesis: undefined () aws-amplify/analytics/kinesis-firehose: undefined () aws-amplify/analytics/personalize: undefined () aws-amplify/analytics/pinpoint: undefined () aws-amplify/api: undefined () aws-amplify/api/server: undefined () aws-amplify/auth: undefined () aws-amplify/auth/cognito: undefined () aws-amplify/auth/cognito/server: undefined () aws-amplify/auth/enable-oauth-listener: undefined () aws-amplify/auth/server: undefined () aws-amplify/datastore: undefined () aws-amplify/in-app-messaging: undefined () aws-amplify/in-app-messaging/pinpoint: undefined () aws-amplify/push-notifications: undefined () aws-amplify/push-notifications/pinpoint: undefined () aws-amplify/storage: undefined () aws-amplify/storage/s3: undefined () aws-amplify/storage/s3/server: undefined () aws-amplify/storage/server: undefined () aws-amplify/utils: undefined () babel-plugin-inline-import: ^3.0.0 => 3.0.0 base-64: ^1.0.0 => 1.0.0 core-js: ^3.36.0 => 3.36.0 (2.6.12, 1.2.7) eas-cli: ~7.5.0 => 7.5.0 eslint: ~8.57.0 => 8.57.0 expo: ~50.0.11 => 50.0.11 expo-auth-session: ~5.4.0 => 5.4.0 expo-av: ~13.10.5 => 13.10.5 expo-blur: ~12.9.2 => 12.9.2 expo-build-properties: ~0.11.1 => 0.11.1 expo-constants: ~15.4.5 => 15.4.5 expo-crypto: ~12.8.1 => 12.8.1 expo-dev-client: ~3.3.9 => 3.3.9 expo-file-system: ~16.0.8 => 16.0.8 expo-font: ~11.10.3 => 11.10.3 expo-image: ~1.10.6 => 1.10.6 expo-image-manipulator: ~11.8.0 => 11.8.0 expo-image-picker: ~14.7.1 => 14.7.1 expo-insights: ~0.6.1 => 0.6.1 expo-linear-gradient: ~12.7.2 => 12.7.2 expo-linking: ~6.2.2 => 6.2.2 expo-localization: ~14.8.3 => 14.8.3 expo-location: ~16.5.5 => 16.5.5 expo-permissions: ~14.4.0 => 14.4.0 expo-router: ~3.4.8 => 3.4.8 expo-splash-screen: ~0.26.4 => 0.26.4 expo-status-bar: ~1.11.1 => 1.11.1 expo-system-ui: ~2.9.3 => 2.9.3 expo-updates: ~0.24.11 => 0.24.11 expo-web-browser: ~12.8.2 => 12.8.2 lottie-react-native: 6.7.0 => 6.7.0 react: 18.2.0 => 18.2.0 (16.14.0) react-dom: 18.2.0 => 18.2.0 (16.14.0) react-intl: ^6.6.2 => 6.6.2 react-native: 0.73.6 => 0.73.6 react-native-autocomplete-dropdown: ^3.1.4 => 3.1.4 react-native-base64: ^0.2.1 => 0.2.1 react-native-blurhash: ^1.1.11 => 1.1.11 react-native-bouncy-checkbox: ^3.0.7 => 3.0.7 react-native-circular-progress: ^1.3.9 => 1.3.9 react-native-collapsible: ^1.6.1 => 1.6.1 react-native-figma-squircle: ^0.3.4 => 0.3.4 react-native-gesture-handler: ~2.15.0 => 2.15.0 react-native-get-random-values: ~1.11.0 => 1.11.0 react-native-iconly: git+https://github.com/Leeva-io/react-native-iconly.git => 1.0.11 react-native-keyboard-aware-scroll-view: ^0.9.5 => 0.9.5 react-native-maps: 1.11.3 => 1.11.3 react-native-modal: ^13.0.1 => 13.0.1 react-native-pager-view: 6.2.3 => 6.2.3 react-native-pinchable: ^0.2.1 => 0.2.1 react-native-reanimated: ~3.7.2 => 3.7.2 react-native-reanimated-carousel: ^3.5.1 => 3.5.1 react-native-redash: ^18.1.3 => 18.1.3 (12.6.1) react-native-safe-area-context: 4.9.0 => 4.9.0 react-native-screens: ~3.29.0 => 3.29.0 react-native-sqlite-storage: ^6.0.1 => 6.0.1 react-native-svg: 15.1.0 => 15.1.0 react-native-swiper: ^1.6.0 => 1.6.0 react-native-ui-lib: ^7.17.2 => 7.17.2 react-native-web: ~0.19.10 => 0.19.10 react-native-web-maps: ^0.3.0 => 0.3.0 react-test-renderer: 18.2.0 => 18.2.0 typescript: ^5.4.2 => 5.4.2 uilib-native: 4.1.2 zustand: ^4.5.2 => 4.5.2 npmGlobalPackages: @aws-amplify/cli: 10.5.1 corepack: 0.25.2 expo-cli: 6.0.0 npm: 10.5.0 ```

Describe the bug

I am experiencing an issue with a GraphQL API managed by AWS Amplify, where a @hasOne relationship fails to return the expected data. Despite correctly setting up my GraphQL schema with two types, User and BasicInfos, and establishing a @hasOne relationship between them, queries to fetch a User along with its related BasicInfos consistently return null for the basicInfos field. This occurs even though the userBasicInfosId correctly references an existing BasicInfos record. The issue persists despite confirming data integrity in the database and ensuring appropriate authentication and authorization rules are in place. This unexpected behavior suggests a potential bug in handling @hasOne relationships within AWS Amplify's GraphQL API.

Here's a brief overview of the relevant part of my GraphQL schema:

type User @model @auth(rules: [{ allow: private }, { allow: owner }]) {
  id: ID!
  name: String!
  basicInfos: BasicInfos @hasOne
  ...
}

type BasicInfos @model @auth(rules: [{ allow: private }, { allow: owner }]) {
  id: ID!
  gender: Gender
  ...
}

And the API response where the issue is observed:

{
  "__typename": "User",
  "_deleted": null,
  "_lastChangedAt": 1710362123611,
  "_version": 21,
  "basicInfos": null,
  "createdAt": "2024-03-13T04:57:29.625Z",
  "userBasicInfosId": "20fef648-3c77-4e62-a465-1bf2930b43c3",
  ...
}

The expected behavior is for the basicInfos field to return the associated BasicInfos object, not null. This issue hampers the application's functionality, as it relies on the complete data model for user profiles.

Expected behavior

Upon querying the User type with its associated BasicInfos through a @hasOne relationship, I expect the API response to include a populated basicInfos object corresponding to the userBasicInfosId. The response should detail the BasicInfos fields such as gender, etc., reflecting the existing relational data stored in the database. This behavior is anticipated based on the GraphQL schema definition and the established @hasOne relationship, ensuring a comprehensive retrieval of user profiles, including their basic information, in a single query. The expected response structure would resemble:

{
  "__typename": "User",
  "_deleted": null,
  "_lastChangedAt": 1710362123611,
  "_version": 21,
  "basicInfos": {
    "__typename": "BasicInfos",
    "id": "20fef648-3c77-4e62-a465-1bf2930b43c3",
    "gender": "GenderValue",
    ...
  },
  "createdAt": "2024-03-13T04:57:29.625Z",
  "userBasicInfosId": "20fef648-3c77-4e62-a465-1bf2930b43c3",
  ...
}

This outcome is crucial for the application's functionality, as it allows for efficient data retrieval and manipulation, providing a seamless user experience by fetching related data in a single request.

Reproduction steps

  1. Set up the Schema: Start by defining the GraphQL schema with the User and BasicInfos models, ensuring a @hasOne relationship from User to BasicInfos. Both models are configured with @auth directives for private access and ownership.

  2. Deploy the API: Deploy the GraphQL API using AWS Amplify. Ensure that the deployment completes successfully and that the database is initialized with the schema defined in step 1.

  3. Create Records: Using the AWS Amplify Console or a GraphQL client, create a User record along with a corresponding BasicInfos record. Ensure to link the BasicInfos record to the User by setting the userBasicInfosId field.

  4. Query the Data: Execute a GraphQL query to fetch a User along with its basicInfos. The query should look something like this:

    query GetUser {
     getUser(id: "user_id_here") {
       id
       name
       basicInfos {
         id
         gender
       }
     }
    }

    Replace "user_id_here" with the actual ID of the User record you created in step 3.

  5. Observe the Issue: Despite the correct setup and existence of the related BasicInfos record, the response for the basicInfos field returns null instead of the expected populated object.

Code Snippet

No response

Log output

No response

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

iPhone 15 Pro

Mobile Operating System

IOS 17.2

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

Glognus commented 6 months ago

I've discovered a potential lead on the issue, It appears to be related to conflict resolution settings. Disabling conflict resolution on my API allows the nested relational data to be correctly fetched.

This suggests a potential bug or interoperability issue between AWS Amplify DataStore and AWS Amplify API when conflict resolution is enabled. It seems to interfere with fetching and returning nested relational data.

Is there a workaround or a fix planned for this issue? Any guidance would be appreciated.

chrisbonifacio commented 6 months ago

Hi @Glognus thanks for raising this issue and providing detailed repro steps! Can you try running the same query in the AppSync console with conflict resolution enabled? This will help rule out whether the DataStore client is the source of the issue.

We'll try to reproduce this internally with the steps given and provide an update soon.

Also, inconsequential to the issue, but were you using conflict detection/resolution purposely? Or do you not need it in your application?

Glognus commented 6 months ago

Hi @chrisbonifacio, it return the same result with basicinfos null, i need to use it for my app at least optimistic or merge conflict resolution. Thanks !

chrisbonifacio commented 6 months ago

Hi @Glognus thank you for the update.

Unfortunately I haven't been able to reproduce this issue.

I tried with the following schema

# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules
input AMPLIFY {
  globalAuthRule: AuthRule = { allow: public }
} # FOR TESTING ONLY!
type User @model @auth(rules: [{ allow: private }, { allow: owner }]) {
  id: ID!
  name: String!
  basicInfos: BasicInfos @hasOne
}

enum Gender {
  MALE
  FEMALE
}

type BasicInfos @model @auth(rules: [{ allow: private }, { allow: owner }]) {
  id: ID!
  gender: Gender
}

I created a BasicInfo record, then a User with a basicInfoId to make the relationship. I received both the User record and BasicInfo nested data on mutation

Screenshot 2024-03-22 at 8 59 59 AM

I then tried the same GetUser query and received the same response with nested data.

Screenshot 2024-03-22 at 9 01 03 AM

Can you share how you are creating the records? Are you making sure to include a _version in the mutation arguments when creating a User? This is required for DataStore-enabled APIs (conflict detection enabled) and not including the version can result in no update to the record.

If you are creating a user first, then a BasicInfo, and finally updating a User with the basicInfoId, this might explain why disabling conflict detection results in updates seeming to work normally and able to retrieve the updated User record.

But to be sure, we'd have to see how you are trying to establish the connection between the records.

chrisbonifacio commented 5 months ago

Hi 👋 Closing this as we have not heard back from you. If you are still experiencing this issue and in need of assistance, please feel free to comment and provide us with any information previously requested by our team members so we can re-open this issue and be better able to assist you.

Thank you!