Amplify-cli: Error in GQL Compile re Many-to-Many Detection

Created on 12 Mar 2019  路  7Comments  路  Source: aws-amplify/amplify-cli

Describe the bug
When setting up a reflexive belongs-to connection, the order of the fields can trick the compiler into thinking that it's a many-to-many connection.

To Reproduce
If you create a model like so:

type Task @model @searchable @versioned {
  id: ID!
  blockedBy: Task @connection(name: "BlockedBy")
  blocks: [Task] @connection(name: "BlockedBy")

the compiler will work fine.

However, if you change the order of blocks and blockedBy the compiler will think that it's a many-to-many and throw an error.

This model, for example, will not compile:

type Task @model @searchable @versioned {
  id: ID!
  blocks: [Task] @connection(name: "BlockedBy")
  blockedBy: Task @connection(name: "BlockedBy")

Expected behavior
Distinguish between many-to-many connections and belongs-to connections properly regardless of field order.

Desktop (please complete the following information):

  • OS: MacOS 10.14.4
  • Amplify Version 1.1.4

Smartphone (please complete the following information):
n/a

enhancement graphql-transformer

Most helpful comment

I'm having an issue with a similar approach:

type Directory
  @model(queries: { get: "directory", list: "directories" })
  @versioned
  @auth(rules: [{ allow: owner }]) {
  id: ID!
  isRoot: Boolean!
  name: String!
  shared: Boolean!
  createdAt: String
  updatedAt: String
  parent: Directory @connection(name: "DirectoryParent", sortField: "createdAt")
  children: [Directory] @connection(name: "DirectoryParent", sortField: "createdAt")
}

This works just fine when I do amplify push. The thing is that it is not creating an index in the table. When I try to add a directory with a mutation I'm having the following error:

"The table does not have the specified index: gsi-DirectoryParent (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: ...)"
path: ["createDirectory", "children"]
0: "createDirectory"
1: "children"

It creates the object in the DB, but I receive that error nonetheless.

I created the gsi manually and no errors. But, shouldn't @connection directive do that for me?

All 7 comments

I don't think you can override named directives. If you change the names of one of your directives (e.g. for blocks make it name:"BlockedByList" does that fix the problem?

It never occurred to me to create connection relationships within a single table or recursive entries (!!!), is this an intended use case @kaustavghosh06 @yunt? (e.g. to create multiple GSIs on a single table on push?)

@nagey what is a reflexive (recursive?) belongs-to connection? (Recursive one-to-many?) Interested to learn, I might be doing my schemas all wrong.

@jkeys-ecg-nmsu name doesn't seem to matter 鈥斅燽ut not sure what you mean by overriding a named directive?

It's a method by which you can model parent/child (hierarchical) relationships (which is what a blocking task essentially is)

@nagey I meant like changing @connection(name:"NameOne") to @connection(name:"NameTwo").

I guess I'm a bit confused about the recursive relation of your structure. Does your application track a _root_ Task, such that your DynamoDB entries could be represented as an n-ary tree? (And getting the "current" blocking task by doing a depth-first search and terminating on first backtrack?) Or do you have a pool of Tasks, each of which can be blocked by other tasks in the pool?

Recursive database entries to show relations is a brand new concept to me, that's why I'm fascinated by this post. Do you have any reading you'd recommend about writing recursive tables? (Edit just to clarify, I've used mutually recursive relationships before, just never self-referential relationships. I'm really curious about how this data structure works, how you'd query against it, how you'd manage your references, etc.)

oh, yeah, that doesn't seem to work. I commented the connection out, deleted the gsi, pushed, then un-commented the new version and re-pushed.

there is no root task, just tasks that can be blocked by other tasks, or not. think of it like a gantt chart, some tasks are independent, but then some tasks have to have other tasks that must be completed before they can be completed.

it's pretty simple, but mostly a concept from RDBMSs 鈥斅爃ave a look at how rails does it if you want, though, not much to read there. it's usually implemented as a self join in the database itself. However, for what we're doing, we've got appsync doing the dirty work for us, so we can just recursively query to our heart's content.

Hey @nagey thanks for bringing this up. I believe this issue is on this line: https://github.com/aws-amplify/amplify-cli/blob/master/packages/graphql-connection-transformer/src/ModelConnectionTransformer.ts#L114.

The reason for the issue is that, when listed first, the @connection on the Task.blocks field thinks that itself is the associated connection field. This can be fixed by adding an additional check such that a connection field does not try to use itself as the other side of the connection. The correct rule would look like: connectionName && relatedDirectiveName && relatedDirectiveName === connectionName && !(f.name.value === field.name.value && relatedType.name.value === parent.name.value). I would be happy to review a PR and will otherwise add this to the backlog to get fixed.

I'm having an issue with a similar approach:

type Directory
  @model(queries: { get: "directory", list: "directories" })
  @versioned
  @auth(rules: [{ allow: owner }]) {
  id: ID!
  isRoot: Boolean!
  name: String!
  shared: Boolean!
  createdAt: String
  updatedAt: String
  parent: Directory @connection(name: "DirectoryParent", sortField: "createdAt")
  children: [Directory] @connection(name: "DirectoryParent", sortField: "createdAt")
}

This works just fine when I do amplify push. The thing is that it is not creating an index in the table. When I try to add a directory with a mutation I'm having the following error:

"The table does not have the specified index: gsi-DirectoryParent (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: ...)"
path: ["createDirectory", "children"]
0: "createDirectory"
1: "children"

It creates the object in the DB, but I receive that error nonetheless.

I created the gsi manually and no errors. But, shouldn't @connection directive do that for me?

It creates the object in the DB, but I receive that error nonetheless.

Yikes, I'm very disconcerted by the growing number of reports that seem to suggest that AppSync tells you it's doing one thing, and does another. Another thread has a comment that shows AppSync both returning a data field and an error field.

If AppSync (or Amplify GraphQL code generator) is doing implicit error handling and deploying fallback strategies we should know about it and it should be well documented. It's difficult to engineer around AppSync / Amplify API at the moment because of this.

Was this page helpful?
0 / 5 - 0 ratings