Describe the bug
DataStore cannot delete hasOne relationships. It breaks at IndexedDBAdapter.deleteTraverse when trying to find the recordToDelete of the HAS_ONE relationType.
Unhandled Runtime Error
NotFoundError: Failed to execute 'index' on 'IDBObjectStore': The specified index was not found.
To Reproduce
Team then create Project with teamID.Project.Expected behavior
The DataStore should correctly traverse the hasOne relationship to delete the Team record before deleting the Project record.
Code Snippet
type Project @model {
id: ID!
name: String
teamID: ID!
team: Team @connection(fields: ["teamID"])
}
type Team @model {
id: ID!
name: String!
}
Team then create Project with teamID. DataStore.save(
new Team({ name: 'Team Name' })
)
.then(async ({ id: teamID }) => {
DataStore.save(
new Project({ teamID })
)
})
Project.DataStore.delete(Project, project.id)
What is Configured?
Amplify 4.27.1
Simple Auth with Cognito
GraphQL / Dynamo API
DataStore
NextJS app (irrelevant)
Additional context
A couple of notes.
hasOne relationship. case 'HAS_ONE':
for await (const model of models) {
const recordToDelete = <T>await this.db
.transaction(storeName, 'readwrite')
.objectStore(storeName)
.index(index) // <-- 🤔
.get(model.id);
@connection can only reference the primary index of a model (ie. it cannot specify a “keyName” as described below in the Has Many section).index is undefined.Here's a sample project to make it easier to reproduce.
Here's also something that's a bit confusing. The generated schema (src/models/schema.js) seems not to be properly generated.
The GQL schema definition looks like this:
type Project @model {
# ...
teamID: ID!
team: Team @connection(fields: ["teamID"])
}
# ...
And this is the generated DataStore schema.
export const schema = {
"models": {
"Project": {
// ...
"fields": {
// ...
"team": {
// ...
"association": {
"connectionType": "HAS_ONE",
"associatedWith": "id"
}
}
},
// ...
},
// ...
},
//...
};
My question is, why isn't the association including the targetName as it was defined (teamID)? This would be a problem when trying to look up the correct ID for the team relationship in deleteTraverse. Actually, when you debug the code, you see that it's specifying the Project.id but using the Team model. So, even if we specify the correct index (byId), the Team.id would not be used.
@manueliglesias Tagging you here as it seems you implemented the bulk of this code.
I can happily help with a PR but I would need some direction as I am still not fully familiar with the underlying algorithm and architecture you guys have implemented in the DataStore.
NOTE: I understand hasOne relationships might be smelly when used with DynamoDB, but my specific use-case requires a hasOne relationship with a @model. This is my use-case for reference. https://github.com/aws-amplify/amplify-cli/issues/4794#issuecomment-665420716
Ran into the same issue.
Any news on this @mauerbac ?
Hey @TheMoums - just spoke to the team. Not current ETA, but pushing the team to take a look.
@abdielou I was able to reproduce the issue. There are 2 sides to it, as you suggested. One is in the library (trying to access a nonexistent index) and also a bug in the CLI where codegen models is incorrectly setting the associatedWith field.
I'll open an issue in the CLI repo and reference this. I've written a bug fix for the library side already, so once the codegen bug fix gets merged, I'll open a PR for the library bug fix as well.
@abdielou @iartemiev I think it is not the modelgen bug but the amplify doc that misleads the understanding of the HAS_ONE relationship. Suppose from your case that
HAS_ONE _Team_.Before writing the schema I attach this post about the difference between HAS_ONE and BELONGS_TO FYI, which is not clarified in the original docs. The main difference is:
To determine who "has" the other object, look at where the foreign key is. We can say that a
User"has" aProfilebecause the profiles table has auser_idcolumn.
Based on the conclusion, it is obvious that it is not a good idea to add teamID in Project but to add projectID in Team. The following is the alternative schema and it works for me on datastore deleting.
type Project @model {
id: ID!
name: String!
team: Team @connection(fields: ["id"])
}
type Team @model
@key(fields: ["projectID"]) {
id: ID!
name: String
projectID: ID!
}
schema.js"Project": {
"name": "Project",
"fields": {
// ...
"team": {
"name": "team",
"isArray": false,
"type": {
"model": "Team"
},
"isRequired": false,
"attributes": [],
"association": {
"connectionType": "HAS_ONE",
"associatedWith": "projectID"
}
}
},
},
@AaronZyLee the Stack Overflow answer you're linking to is discussing this from an RDB design perspective. That's not necessarily relevant here, since we're dealing with NoSQL and GraphQL where different normalization rules and access patterns apply.
Even though your suggested schema might work, I think it's needlessly complex for a simple Has One relationship. Using @key will create a GSI on the Team table, which adds cost, but doesn't add value. I think what we currently have in the docs makes more sense for this use case. Feel free to reach out to me internally and we can discuss this in more detail.
@abdielou Hi! When you are using the same schema you wrote in the first comment, are you able to get the Team object back when querying Project?
type Project @model {
id: ID!
name: String
teamID: ID!
team: Team @connection(fields: ["teamID"])
}
type Team @model {
id: ID!
name: String!
}
Most helpful comment
Ran into the same issue.
Any news on this @mauerbac ?