multimeric / AmplifyCountDirective

Count the number of items in your DynamoDB tables using Amplify
12 stars 1 forks source link

Count field definition #11

Open Tedsterh opened 2 years ago

Tedsterh commented 2 years ago

I have added a new direct called fieldCount that uses an hasMany or manyToMany directive to access the other table instead, it also has a field argument like these connections so you can pass a field for it to return on, if none are provided it generates one similar to the hasOne directive.

I am not too sure how the resolvers work so not sure if it maps to the field properly or not just yet, any help and tips would be amazing!

multimeric commented 2 years ago

The zip file is still in the pull request.

multimeric commented 2 years ago

Also, I believe more changes will be needed in the lambda resolver, to handle an index. We will want to Query it, for one.

multimeric commented 2 years ago

Oh, I just realised what you're doing. You're not trying to add a count for secondary indexes like I thought (?). So the query stuff is not relevant.

Tedsterh commented 2 years ago

Yes sorry, I'm just creating a count field for list connections, I thought I had remove the .zip so i'll go back and do that now.

Tedsterh commented 2 years ago

I think we can get the name of the index generated by @hasMany so that we can scan the index rather than filter it, not sure which would be more efficient?

Or we could force an index field so that we don't have to try and find it ourselves, like they do on the @hasMany if you want to use your own index.

multimeric commented 2 years ago

Hmm I have to admit I've never actually used @hasMany. Can you show me an example schema for one? We should use the index if it generates one.

Tedsterh commented 2 years ago

You can use it with a custom index or let it generate one, both do the same thing effectively I'm just not sure how we could easy access the generated one from our directive.

type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @hasMany
}

type Comment @model {
  id: ID!
  content: String!
}

Or with a custom index:

type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"])
}

type Comment @model {
  id: ID!
  postID: ID! @index(name: "byPost", sortKeyFields: ["content"])
  content: String!
}

We could then require them to pass the index name which in this case is byPost

multimeric commented 2 years ago

So at a high level, you want to be able to getPost(id: 35){ commentCount }, which will give you the number of associated comments?

For @hasMany, I think we will want to always use a Dynamo Query for these kinds of requests, since they are always associated with an index. It will certainly be much faster. A good way to check what kind of Dynamo call we should use it to look at the resolvers generated by Amplify for Post{ comments }. We will broadly want to replicate this, only inside the lambda, and using the COUNT attribute.

Tedsterh commented 2 years ago

Yes that's what I'm thinking, the hope is because we are querying it directly too I can use a filter on it, to filter out certain comments from a total in this case.

I'll look at using an index then and checkout their resolvers for the query, I've not used dynamo queries before.

Should I make a new handler or just edit the current one to accept an optional index name?

multimeric commented 2 years ago

Definitely use the same handler. Most of the code will remain the same and we don't want to duplicate everything. You can pass the info like the index name in through a new key in the event (alongside context, tableName etc).

multimeric commented 2 years ago

Yeah so dynamo's Query can do everything Scan can do, including applying arbitrary filters. But there's the restriction that you can only Query one partition, not the entire table. But we can just copy what Amplify does, like I said.

Tedsterh commented 2 years ago

This is how they do it for the posts one they have a postID passed into the query that they can access

#if( !$util.isNull($ctx.args. postID) )
  #set( $modelQueryExpression.expression = "#postID = : postID" )
  #set( $modelQueryExpression.expressionNames = {
  "#postID": "postID"
} )
  #set( $modelQueryExpression.expressionValues = {
  ": postID": {
      "S": "$ctx.args. postID"
  }
} )

do you know what's in the context, is there a way to access the primary key of the model that the field is in?

I have found out how to use the indexes we simply pass in the index name IndexName: event.indexName, and then it uses the expressionNames and expressionValues to do the actual filtering.

multimeric commented 2 years ago

Okay, so you'll have to write a resolver for the @hasMany fields that passes this info into the lambda. Check out the AppSync docs for resolvers, they're quite helpful: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html. But if there's anything you aren't able to do with them, we can push it to the lambda.

Tedsterh commented 2 years ago

According to the docs we should be able to do something like ctx.source.id so we could either figure out the name of the primary key in the generate stuff or we could just have an argument passed into the directive again, I think the primary key would be better?

multimeric commented 2 years ago

What I would suggest is actually checking for the existence of the @hasMany directive, and if it's present, extracting out the index information from that, possibly using some functions from the relational transformer module. I don't think AppSync inherently knows what the primary key is for a table, and even if it did, it may not be the index the user selected.