How do i create an array pair for a given DynamoDB

2019-08-16 06:46发布

问题:

For an 'id' in my DynamoDB table (e.g. e5eb02ae-04d5-4331-91e6-11efaaf12ea5), i want to create a column called Pairs. In that one Pairs columns i would have

['a', 'b'], 
['c', 'd'],
['e', 'f'],
etc.... 

The also need to update Pairs if a new pair arrives, say ['g', 'h']. Right now, my update() below, replaces the pair every time.

    const newPairs = {
      number1: "g",
      number2: "h"
    }
    const updateinfo = {
       id: "e5eb02ae-04d5-4331-91e6-11efaaf12ea5",   
       Pairs: newPairs
    }
    try {
        await API.graphql(graphqlOperation (UpdateInfo, { input: updateinfo }))  //mutation
        console.log('success')
       }
       catch (err) {
         console.log(err)
       }

How do i do this, ensuring that my list is just appended to with the new pair, not replaced entirely with [g, h]?

回答1:

If you can post your schema / resolver mapping template I can offer more specific advice, but I'll do my best to answer this with what you've posted so far.

Simple Way

If you have the existing item already, one way to do this would be to update the existing Pairs and pass that to your existing mutation.

const existingItem = {
  id: "e5eb02ae-04d5-4331-91e6-11efaaf12ea5",
  Pairs: [['a', 'b'],['c', 'd'],['e', 'f']]
}

const newPairs = {
  number1: "g",
  number2: "h"
}

const updateinfo = {
  id: existingItem.id,
  // Note that if existingItem.Pairs is always defined this can be simplified to
  // Pairs: [...existingItem.Pairs, [newPairs.number1, newPairs.number2]]
  Pairs: existingItem.Pairs ?
      [...existingItem.Pairs, [newPairs.number1, newPairs.number2]] : 
      [[newPairs.number1, newPairs.number2]]
}

try {
  await API.graphql(graphqlOperation (UpdateInfo, { input: updateinfo }))  
  //mutation
  console.log('success')
} 
catch (err) {
  console.log(err)
}

Using DynamoDB functions

If you do not have the existing item or if Pairs can be pretty big, AWS DynamoDB's list_append function can be used instead.

list_append (operand, operand)

This function evaluates to a list with a new element added to it. You can append the new element to the start or the end of the list by reversing the order of the operands.

Here is an example with a specific mutation that uses it.

### SDL
type Item {
    id: ID!
    Pairs: [[String]]
}

input AddPairInput {
    id: ID!
    number1: String!
    number2: String!
}

type Mutation {
    addPairToItem(input: AddPairInput!): Item!
}

...rest of schema omitted for brevity 

### Resolver Request Mapping Template
{
    "version": "2017-02-28",
    "operation": "UpdateItem",
    "key": {
        "id": { "S": "$ctx.args.input.id"}
    },
    "update": {
        ### Note: we also use if_not_exists here so this works if Pairs is not yet defined on the item.
        "expression":"SET Pairs = list_append(if_not_exists(Pairs, :emptyList), :newPair)",
        "expressionValues": 
          { 
            ":newPair":{"L": [{"L":[{"S":"$ctx.args.input.number1"},
                                    {"S":"$ctx.args.input.number2"}]}]},
            ":emptyList":{"L": []}
          }
        }
}

### Resolver Response Mapping Template
$util.toJson($ctx.result)

This way is also nice because if someone else updates Pairs, you won't overwrite their update. You can also add the new Pair to the beginning of the list by inverting the order of your arguments to the list_append function.

DynamoDB Functions with AWS Amplify

If your project was generated by AWS Amplify, you will need to add a customer resolver.

Step 1: Add a new mutation to your schema

### ./amplify/backend/api/<api_name>/schema.graphql
type Item @model {
  id: ID!
  Pairs: [[String]]
}

type Mutation {
  addPairToItem(input: AddPairToItemInput!): Item!
}

input AddPairToItemInput {
  id: ID!
  number1: String!
  number2: String!
}

Step 2: Add a resolver request mapping template

### ./amplify/backend/api/<api_name>/resolvers/Mutation.addPairToItem.req.vtl
{
    "version": "2017-02-28",
    "operation": "UpdateItem",
    "key": {
        "id": { "S": "$ctx.args.input.id"}
    },
    "update": {
        "expression":"SET Pairs = list_append(if_not_exists(Pairs, :emptyList), :newPair)",
        "expressionValues":
          {
            ":newPair":{"L": [{"L":[{"S":"$ctx.args.input.number1"},{"S":"$ctx.args.input.number2"}]}]},
            ":emptyList":{"L": []}
          }
        }
}

Step 3: Add a resolver response mapping template

### ./amplify/backend/api/<api_name>/resolvers/Mutation.addPairToItem.res.vtl
$util.toJson($ctx.result)

Step 4: Add your custom resolver to your CustomResources stack

### ./amplify/backend/api/<api_name>/stacks/CustomResources.json
    "Resources": {
        // ...other resources may exist here
        "AddPairToItemResolver": {
            "Type": "AWS::AppSync::Resolver",
            "Properties": {
                "ApiId": {
                    "Ref": "AppSyncApiId"
                },
                "DataSourceName": "ItemTable",
                "TypeName": "Mutation",
                "FieldName": "addPairToItem",
                "RequestMappingTemplateS3Location": {
                    "Fn::Sub": [
                        "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.addPairToItem.req.vtl",
                        {
                            "S3DeploymentBucket": {
                                "Ref": "S3DeploymentBucket"
                            },
                            "S3DeploymentRootKey": {
                                "Ref": "S3DeploymentRootKey"
                            }
                        }
                    ]
                },
                "ResponseMappingTemplateS3Location": {
                    "Fn::Sub": [
                        "s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.addPairToItem.res.vtl",
                        {
                            "S3DeploymentBucket": {
                                "Ref": "S3DeploymentBucket"
                            },
                            "S3DeploymentRootKey": {
                                "Ref": "S3DeploymentRootKey"
                            }
                        }
                    ]
                }
            }
        }
    },

Step 5: Build and Deploy your new changes

  • Run amplify api gql-compile to see the new changes in your generated code (optional).
  • Run amplify push to deploy your changes.

Now you can either run amplify api console or use the new generated code to test the changes with your new mutation.

To generate the new code you can run amplify codegen. You should then be able to do something like this

import Amplify, { API, graphqlOperation } from "aws-amplify";
import * as mutations from './graphql/mutations';

// Mutation
const addPairToItem = {
    id: '1',
    number1: 'a',
    number2: 'b'
};

const newItem = await API.graphql(graphqlOperation(mutations.addPairToItem, {input: addPairToItem}));

Additional Examples

Remember to update your CustomResources.json file with any new resolvers you add.

Adding a single item to a list of scalar values

### ./amplify/backend/api/<api_name>/schema.graphql
type Item @model {
    id: ID!
    words: [String]
}

input AddWordInput {
    id: ID!
    word: String!
}

type Mutation {
    addWordToItem(input: AddWordInput!): Item!
}

### ./amplify/backend/api/<api_name>/resolvers/Mutation.addWordToItem.req.vtl
{
    "version": "2017-02-28",
    "operation": "UpdateItem",
    "key": {
        "id": { "S": "$ctx.args.input.id"}
    },
    "update": {
        "expression":"SET words = list_append(if_not_exists(words, :emptyList), :newWord)",
        "expressionValues":
          {
            ":newWord":{"L": [{"S":"$ctx.args.input.word"}]},
            ":emptyList":{"L": []}
          }
        }
}

### ./amplify/backend/api/<api_name>/resolvers/Mutation.addWordToItem.res.vtl
$util.toJson($ctx.result)


### Usage
import Amplify, { API, graphqlOperation } from "aws-amplify";
import * as mutations from './graphql/mutations';

// Mutation
const newWord = {
    id: '1',
    word: 'foo'
};

const newItem = await API.graphql(graphqlOperation(mutations.addWordToItem, {input: newWord}));

Adding multiple items to a list of scalar values

Note: I am introducing $util.dynamodb.toDynamoDBJson here to make constructing our VTL easier. I've been explicit thus far but this utility can simplify a lot of the work. More here

### ./amplify/backend/api/<api_name>/schema.graphql
type Item @model {
    id: ID!
    words: [String]
}

input AddWordsInput {
  id: ID!
  words: [String!]!
}

type Mutation {
  addWordsToItem(input: AddWordsInput!): Item!
}

### ./amplify/backend/api/<api_name>/resolvers/Mutation.addWordsToItem.req.vtl
{
    "version": "2017-02-28",
    "operation": "UpdateItem",
    "key": {
        "id": { "S": "$ctx.args.input.id"}
    },
    "update": {
        "expression":"SET words = list_append(if_not_exists(words, :emptyList), :newWords)",
        "expressionValues":
          {
            ":newWords": $util.dynamodb.toDynamoDBJson($ctx.args.input.words),
            ":emptyList": $util.dynamodb.toDynamoDBJson([])
          }
        }
}

### ./amplify/backend/api/<api_name>/resolvers/Mutation.addWordsToItem.res.vtl
$util.toJson($ctx.result)


### Usage
import Amplify, { API, graphqlOperation } from "aws-amplify";
import * as mutations from './graphql/mutations';

// Mutation
const newWords = {
    id: '1',
    words: ["bar","xyz","bar"]
};

const newItem = await API.graphql(graphqlOperation(mutations.addWordsToItem, {input: newWords}));

Remove an item from a list of scalar values

Removing elements from lists in DynamoDB is done using the REMOVE action. You must specify a non-negative index as part of the update expression. If the index does not exist on the item, your request will not fail (e.g. no index out of bounds exceptions).

type Item @model {
    id: ID!
    words: [String]
}

input RemoveWordInput {
  id: ID!
  wordIndex: Int!
}

type Mutation {
    removeWordFromItem(input: RemoveWordInput!): Item!
}

### ./amplify/backend/api/<api_name>/resolvers/Mutation.removeWordFromItem.req.vtl
{
    "version": "2017-02-28",
    "operation": "UpdateItem",
    "key": {
        "id": { "S": "$ctx.args.input.id"}
    },
    "update": {
        "expression":"REMOVE words[$ctx.args.input.wordIndex]"
    }
}

### ./amplify/backend/api/<api_name>/resolvers/Mutation.removeWordFromItem.res.vtl
$util.toJson($ctx.result)


### Usage
import Amplify, { API, graphqlOperation } from "aws-amplify";
import * as mutations from './graphql/mutations';

// Mutation
const removeWord = {
    id: '1',
    // The index is 0 based so wordIndex: 0
    // would delete the first item,
    // wordIndex: 1 deletes the second, etc.
    wordIndex: 1 
};

const newItem = await API.graphql(graphqlOperation(mutations.removeWordFromItem, {input: removeWord}));