amplify-swift icon indicating copy to clipboard operation
amplify-swift copied to clipboard

Datastore HasOne connection never populated

Open ForeandAft opened this issue 3 years ago • 7 comments

Describe the bug When specifying a HasOne connection as described in the documentation (https://docs.amplify.aws/cli/graphql-transformer/connection#has-one), the linked child object is never populated when querying from AppSync.

To Reproduce Defined as model as shown:

type NomadUser @model @auth(rules: [{ allow: owner, ownerField: "cognitoId", operations: [create, update, delete] },
{ allow: private, provider: iam, operations: [create, update, delete] }])
@key(fields: ["cognitoId", "id"], name: "NomadUsersByCognitoId") {
	id: ID!
	screenName: String
	fullName: String!
	aboutMe: String
	website: AWSURL
	facebookLink: AWSURL
	twitterLink: String
	instagramLink: String
	youTubeLink: AWSURL
	childrenAges: [String]
	interests: [String]
}
type NomadBlockedUser @model @auth(rules: [{ allow: owner, ownerField: "owner", operations: [read, create, delete] },
	{ allow: private, provider: iam, operations: [create, update, delete] }])
    @key(fields: ["owner", "id"], name: "blockedUsersbyOwner")
		@key(fields: ["blockedProfileID", "id"], name: "blocksByBlockedProfileID")
{
    id: ID!
    owner: String
    blockedProfileID: ID!
    blockedProfile: NomadUser @connection(fields: ["blockedProfileID"])
		blockedCognitoId: String
}

After codgen completed I am able to insert objects into the datastore and DynamoDB successfully. I can query the child object from the AppSync Query tool as well

However, when I query the DataStore as shown:

Amplify.DataStore.query(NomadBlockedUser.self, where: keys.owner == SecurityManager.shared.currentUserSub, sort: nil, paginate: nil) { (result) in
            switch result{
            case.success(let data):
                onCompletion(data, nil)
                if let profile = data[0].blockedProfile
                {
                    print(profile.aboutMe)
                }
            case .failure(let error):
                print("Error")
            }
        }
    }

the child profile object is always nil. I have confirmed that even if the data is being lazy-loaded from the local store, this should still work because the NomadUser that I have blocked is in the local data store.

Expected behavior I expect the BlockedProfile object to be populated when querying the datastore.

Environment(please complete the following information): Amplify Framework 1.6.1 Dependency Manager Cocoapods Swift 5.0 Amplify version 4.45.2

Device Information (please complete the following information):

  • Device: iPhone 12pro Simulator
  • iOS Version: iOS 14

Additional context Add any other context about the problem here.

ForeandAft avatar Mar 16 '21 17:03 ForeandAft

Experiencing this as well :(

kneekey23 avatar Mar 22 '21 03:03 kneekey23

Summary from @diegocstn

  • couldn't repro with the simplest scheme possible (no auth rules)
  • couldn't repro with following schema
type Team @model @auth(rules: [{ allow: owner, operations: [create, update, delete] }]) {
  id: ID!
  name: String!
}

type Project @model @auth(rules: [{ allow: owner, operations: [read, create, delete] }]) {
  id: ID!
  name: String
  team: Team @connection
}

Next Steps

  • Check status of sqlite: Is the data even there?
  • Assuming it's not (which seems like a safe assumption), progressively add rules using @kneekey23's schema & auth config as a guide, until we find a config that reproduces the issue

palpatim avatar Apr 09 '21 18:04 palpatim

This issue is stale because it has been open for 14 days with no activity. Please, provide an update or it will be automatically closed in 7 days.

github-actions[bot] avatar Apr 30 '21 00:04 github-actions[bot]

Also experienced this! The connection works with

type Project @model @auth(rules: [{ allow: owner, operations: [read, create, delete] }]) {
  id: ID!
  name: String
  team: Team @connection
}

but fails with

type Project @model @auth(rules: [{ allow: owner, operations: [read, create, delete] }]) {
  id: ID!
  name: String
  teamID: String!
  team: Team @connection(fields: ["teamID"]
}

is this being fixed?

kwomackcodes avatar May 07 '21 01:05 kwomackcodes

Thanks for taking the time to comment. We’re working on ways to make DataStore relationships simpler. Once we get that done, we may need to adjust code in the DataStore plugin to properly handle the situation above. We’ll update this issue when we have more information.

palpatim avatar Jun 18 '21 16:06 palpatim

Also experienced this! The connection works with

type Project @model @auth(rules: [{ allow: owner, operations: [read, create, delete] }]) {
  id: ID!
  name: String
  team: Team @connection
}

but fails with

type Project @model @auth(rules: [{ allow: owner, operations: [read, create, delete] }]) {
  id: ID!
  name: String
  teamID: String!
  team: Team @connection(fields: ["teamID"]
}

is this being fixed?

@palpatim @diegocstn - I think it is worth noting that the second schema creates a .hasOne ModelAssociation, whereas the first schema actually creates a .belongsTo ModelAssociation (see screenshot below that shows this). It is not always transparent to the consumer what type of ModelAssociation is being used. But the fact that one schema works and the other does not is consistent with the original issue. The data only seems to return for .belongsTo associations, and not .hasOne associations.

Probably adding to the confusion/issue is that both of these schemas are called out as examples of hasOne relations in the CLI docs. It seems like either the docs need to be updated or the ModelAssociation used should be updated.

Note: the first schema being represented with .belongsTo and the second with .hasOne is consistent with amplify-android from what I have observed.

Screenshot of first schema being represented with a .belongsTo ModelAssociation:

Screen Shot 2021-06-24 at 5 24 28 PM

Jordan-Nelson avatar Jun 25 '21 14:06 Jordan-Nelson

Is there any update on this issue?

anoop4real avatar Feb 08 '22 17:02 anoop4real

Hello, Thank you for your patience in this and we apologize for our delay in response. We have made several improvements in Datastore and API in recent releases, mainly the feature called Lazy Loading. Please upgrade your CLI version to >10.8.0 and Amplify version >2.4.0 to use this feature. Follow the steps here : https://docs.amplify.aws/cli/migration/lazy-load-custom-selection-set/#lazy-loading-connected-models

For reference, I used the following schema and code snippet. I was able to fetch the blockedProfile inside NomadBlockedUser. I have attached a screenshot of the console log as well.

type NomadUser @model 
@auth(rules: [{ allow: public }]){
	id: ID!
	screenName: String
	fullName: String!
	aboutMe: String
}

type NomadBlockedUser @model 
@auth(rules: [{ allow: public }])
{
    id: ID!
    owner: String
    blockedProfileID: ID!
    blockedProfile: NomadUser @hasOne(fields: ["blockedProfileID"])
		blockedCognitoId: String
}

func createUserAndQuery() async {
        do {
            let nomadUser = NomadUser(fullName: "dummyUser")
            let savedNomadUser = try await Amplify.DataStore.save(nomadUser)
            print("[GH1107]Saved user: \(savedNomadUser)")
            let nomadBlockedUser = NomadBlockedUser(blockedProfileID: nomadUser.id)
            let savedNomadBlockedUser = try await Amplify.DataStore.save(nomadBlockedUser)
            print("[GH1107]Saved blocked user: \(savedNomadBlockedUser)")
            let queriedBlockedUsers = try await Amplify.DataStore.query(NomadBlockedUser.self)
            print("[GH1107]Queried blocked user list count: \(queriedBlockedUsers.count)")
            let blockedUser = queriedBlockedUsers[0]
            let containedUser = try await blockedUser.blockedProfile
            print("[GH1107]Contained user: \(String(describing: containedUser))")
        } catch {
            print("[GH1107]Failed with error : \(error)")
        }
    }

GH1107

thisisabhash avatar May 11 '23 02:05 thisisabhash