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

[Gen2] Add sort key to model identifier

Open ryanmalesic opened this issue 1 year ago • 5 comments

Is this related to a new or existing framework?

No response

Is this related to a new or existing API?

GraphQL API

Is this related to another service?

DynamoDB

Describe the feature you'd like to request

.identifier() on models should allow for sort keys.

Describe the solution you'd like

.identifier({ hashKey: [...], sortKey: [...] })

Describe alternatives you've considered

GSIs with sort keys don't fit my use-case as duplicates are allowed in GSIs

Additional context

No response

Is this something that you'd be interested in working on?

  • [x] 👋 I may be able to implement this feature request
  • [ ] ⚠️ This feature might incur a breaking change

ryanmalesic avatar Apr 01 '24 04:04 ryanmalesic

Hello, @ryanmalesic 👋 and thank you for opening this issue. It looks like you're referring to the Gen2 Developer Experience with this feature request. Just to confirm, have you reviewed the composite identifier section that shows how to use a sort key within the identifier() call?

The first string that's passed into the array is the partition key. The second (and any more) would be additional sort keys.

cwomack avatar Apr 01 '24 18:04 cwomack

Thanks. I guess my request then is updating the docs to explicitly call out that subsequent items are sort keys and NOT 1 PK with all the fields joined is needed then: https://docs.amplify.aws/gen2/build-a-backend/data/data-modeling/identifiers/#pageMain

ryanmalesic avatar Apr 01 '24 19:04 ryanmalesic

Also, it seems sort keys on the primary index don't support comparison operations in the autogenerated gets/queries? Is this correct

ryanmalesic avatar Apr 02 '24 09:04 ryanmalesic

Hi @ryanmalesic can you explain your use case a bit more? When performing a get request, the expected behavior is to return a single item so the arguments to the query are constrained as such, which means you can't use a filter expression. However, you can use a filter expression on a list query as it returns multiple items. So you can do something like this:

const schema = a.schema({
  Customer: a
    .model({
      name: a.string(),
      phoneNumber: a.phone().required(),
      accountRepresentativeId: a.id().required(),
    })
    .identifier(["accountRepresentativeId", "phoneNumber"])
    .authorization([a.allow.owner()]),
});
image

If this doesn't fit your use case either, then can you provide an example of your schema and the data access pattern you're trying to achieve?

chrisbonifacio avatar Apr 15 '24 18:04 chrisbonifacio

Understood on the get, that was a mistype.

Does list + filter convert to a Scan + Filter expression or a Query when calling DDB from the default generated app sync resolver?

If it's scan + filter, this is too unoptimized and comparatively expensive to a Query for my use-cases. Dynamo makes a point to discourage scan usage for things that could be queries

My use case would involve having a method called query (for example) on the generated model that accepts the pk and then optionally a query (beginsWith, lt, gt, etc) operation on the (composite) sort key. This would translate to a Query operation on the table in the resolver function

Also having a composite pk would be helpful. Instead of identifier having the first field of the array be the pk and the rest becoming a composite sk, it could be something like

.identifier({
    hashKey: [...],
    sortKey: [...],
})

ryanmalesic avatar Apr 15 '24 19:04 ryanmalesic

Understood on the get, that was a mistype.

Does list + filter convert to a Scan + Filter expression or a Query when calling DDB from the default generated app sync resolver?

If it's scan + filter, this is too unoptimized and comparatively expensive to a Query for my use-cases. Dynamo makes a point to discourage scan usage for things that could be queries

My use case would involve having a method called query (for example) on the generated model that accepts the pk and then optionally a query (beginsWith, lt, gt, etc) operation on the (composite) sort key. This would translate to a Query operation on the table in the resolver function

Also having a composite pk would be helpful. Instead of identifier having the first field of the array be the pk and the rest becoming a composite sk, it could be something like

.identifier({
    hashKey: [...],
    sortKey: [...],
})

That's correct, a List + Filter would result in a DynamoDB Scan. A Get would be a DynamoDB Query on the PK.

For a GraphQL query to trigger a Query instead you would have to setup a secondary index and use a query expression. https://docs.amplify.aws/react/build-a-backend/data/data-modeling/secondary-index/

Also, we do support composite PKs. @aws-amplify/backend-cli

We have launched and since made updates to the schema model. Please upgrade to the latest tag for both @aws-amplify/backend and @aws-amplify/backend-cli and let us know if you still have any issues or questions on the setup

npm i @aws-amplify/backend@latest @aws-amplify/backend-cli@latest 

chrisbonifacio avatar May 13 '24 16:05 chrisbonifacio

Closing this as fix has been releaesd

chrisbonifacio avatar May 14 '24 16:05 chrisbonifacio

How do we sort data without providing PK? I want to list Posts by createdAt for example.

Basovs avatar May 21 '24 13:05 Basovs

How do we sort data without providing PK? I want to list Posts by createdAt for example.

You can use a global secondary index!

https://docs.amplify.aws/react/build-a-backend/data/data-modeling/secondary-index/#add-sort-keys-to-secondary-indexes

For createdAt, we auto-populate that field but you can still add it to the schema as a string and then use it as the sort key to an index. You can then configure sortDirection on the query and set it to ASC or DESC.

chrisbonifacio avatar May 21 '24 14:05 chrisbonifacio

@chrisbonifacio Something is not as i want it to be. I have this code but the typescript complains:

const schema = a.schema({ Todo: a .model({ title: a.string(), content: a.string(), is_done: a.boolean(), created_at: a.datetime().required(), }) .secondaryIndexes(index => [index('created_at').sortKeys(['created_at'])]) .authorization(allow => [allow.guest()]), })

Can you please correct it? And maybe provide list query example it self as well. Cannot find any list query examples in the docs...

Basovs avatar May 21 '24 14:05 Basovs

@Basovs Were you able to implement this? I'm also looking to do the same

sid-js avatar May 25 '24 15:05 sid-js

@Basovs Were you able to implement this? I'm also looking to do the same

Yes @sid-js - here is how i solved it. Add these two/three lines to the Table you want to sort by created at:

a field named "type" which value will always be name of the table. type: a.string().required()

Add the createdAt field. So that backend schema and Typescript is happy. NOTE: im using different naming of createdAt. And even if you use createdAt which is the default field automatically provided by Amplify you still need to define it in the schema so that TypeScript and backend schema is happy. created_at: a.datetime().required()

and then create secondary index based on that field "type" .secondaryIndexes(index => [index('type').sortKeys(['created_at']).queryField('listByDate')])

Its kinda a small hack that is also used in Amplify Gen1 sorting. Now when you create new Todo for example you need to provide field "type": "Todo" in the payload.

And then when listing the todos you also need to provide type: amplifyClient.models.Todo.listByDate( { type: 'Todo', }, { sortDirection: 'DESC', }, )

Basovs avatar May 25 '24 17:05 Basovs

@Basovs Thanks, this worked. But is there any way to make this query paginated ? Can't find anything in the Docs either. @chrisbonifacio

sid-js avatar May 26 '24 13:05 sid-js