Appsync Resolver UpdateItem ignore null args? - amazon-web-services

I am doing this in my Appsync Resolver:
{
"version" : "2017-02-28",
"operation" : "UpdateItem",
"key" : {
"pk" : { "S" : "Container" },
"id" : { "S" : "${ctx.args.id}" }
},
"update" : {
"expression" : "SET #name = :name, description = :description",
"expressionNames": {
"#name" : "name"
},
"expressionValues": {
":name" : { "S": "${context.arguments.name}" },
":description" : { "S": "${context.arguments.description}" },
}
}
}
But sometimes I may not pass in both name and description. How would I make it not SET those columns when those args are null?

All you need to do is to create your own SET expression with condition checked based on your need. Below expression check if any argument is null or empty, I don't want to update it.
#set( $expression = "SET" )
#set( $expValues = {} )
## NAME
#if( !$util.isNullOrEmpty(${context.arguments.name}) )
#set( $expression = "${expression} name = :name" )
$!{expValues.put(":name", { "S" : "${context.arguments.name}" })}
#end
## DESCRIPTION
#if( !$util.isNullOrEmpty(${context.arguments.description}) )
#if( ${expression} != "SET" )
#set( $expression = "${expression}," )
#end
#set( $expression = "${expression} description = :description" )
$!{expValues.put(":description", { "S" : "${context.arguments.description}" })}
#end
{
"version" : "2017-02-28",
"operation" : "UpdateItem",
"key" : {
"pk" : { "S" : "Container" }
"id" : { "S" : "${context.arguments.id}" }
},
"update" : {
"expression" : "${expression}",
"expressionValues": $util.toJson($expValues)
}
}
Hope it is useful!

This is very much possible. You just have to add a simple if statement to check if the value is there. A parallel example can be seen in the docs here: https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-resolvers.html
Specifically, that example (below) uses the application of optional arguments into a list operation.
{
"version" : "2017-02-28",
"operation" : "Scan"
#if( ${context.arguments.count} )
,"limit": ${context.arguments.count}
#end
#if( ${context.arguments.nextToken} )
,"nextToken": "${context.arguments.nextToken}"
#end
}
Just applying that if's null check should work for you.

Related

How to use query operation with app sync auto generated resolver?

Below is the autogenerated resolver of appsync with DynamoDB. I am unable to understand what object I have to pass to use the Query operation.
#set( $limit = $util.defaultIfNull($context.args.limit, 100) )
#set( $ListRequest = {
"version": "2018-05-29",
"limit": $limit
} )
#if( $context.args.nextToken )
#set( $ListRequest.nextToken = $context.args.nextToken )
#end
#if( $context.args.filter )
#set( $ListRequest.filter = $util.parseJson("$util.transform.toDynamoDBFilterExpression($ctx.args.filter)") )
#end
#if( !$util.isNull($modelQueryExpression)
&& !$util.isNullOrEmpty($modelQueryExpression.expression) )
$util.qr($ListRequest.put("operation", "Query"))
$util.qr($ListRequest.put("query", $modelQueryExpression))
#if( !$util.isNull($ctx.args.sortDirection) && $ctx.args.sortDirection == "DESC" )
#set( $ListRequest.scanIndexForward = false )
#else
#set( $ListRequest.scanIndexForward = true )
#end
#else
$util.qr($ListRequest.put("operation", "Scan"))
#end
$util.toJson($ListRequest)
I have tried to pass the below query but got the all data of table. I have also tried with simple filter scan it's working and returning the data but I want to use index.
let modelQueryExpression = {
"expression": "#bookingtype = :bookingtype AND #bookinguserid = :bookinguserid",
"expressionNames": {
"#bookinguserid": "bookinguserid",
"#bookingtype": "bookingtype",
},
"expressionValues": {
":bookingtype": {
"S": JSON.parse(user).id,
},
":parent": {
"S": 'Trip',
},
}
}
const bookingdata = await API.graphql({ query: listBookings, variables: { modelQueryExpression } });

How to get the last inserted item from dynamodb, after that create new item using Aws appsync

I am using aws appsync and dynomoDB. I have a table called Payment and I want to create each item with an unique sequential number that is PaymentNo.
If we want to achive this, we have to get last inserted item in order to update the PaymentNO count. Actually, I don't know, "how to get the last inserted item from table in the same resolver?
could anyone help me to write a resolver for this ?
schema
type Payment #model(subscriptions: null) {
id: ID!
paid_amount: Float
paid_date: AWSDate
description: String
PaymentNO: Int
}
My request resolver
## [Start] Set default values. **
$util.qr($context.args.input.put("id", $util.defaultIfNull($ctx.args.input.id, $util.autoId())))
#set( $createdAt = $util.time.nowISO8601() )
## Automatically set the createdAt timestamp. **
$util.qr($context.args.input.put("createdAt", $util.defaultIfNull($ctx.args.input.createdAt, $createdAt)))
## Automatically set the updatedAt timestamp. **
$util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $createdAt)))
## [End] Set default values. **
## [Start] Prepare DynamoDB PutItem Request. **
$util.qr($context.args.input.put("__typename", "FeePayment"))
#if( $modelObjectKey )
#set( $condition = {
"expression": "",
"expressionNames": {},
"expressionValues": {}
} )
#foreach( $entry in $modelObjectKey.entrySet() )
#if( $velocityCount == 1 )
$util.qr($condition.put("expression", "attribute_not_exists(#keyCondition$velocityCount)"))
#else
$util.qr($condition.put("expression", "$condition.expression AND attribute_not_exists(#keyCondition$velocityCount)"))
#end
$util.qr($condition.expressionNames.put("#keyCondition$velocityCount", "$entry.key"))
#end
#else
#set( $condition = {
"expression": "attribute_not_exists(#id)",
"expressionNames": {
"#id": "id"
},
"expressionValues": {}
} )
#end
#if( $context.args.condition )
#set( $condition.expressionValues = {} )
#set( $conditionFilterExpressions = $util.parseJson($util.transform.toDynamoDBConditionExpression($context.args.condition)) )
$util.qr($condition.put("expression", "($condition.expression) AND $conditionFilterExpressions.expression"))
$util.qr($condition.expressionNames.putAll($conditionFilterExpressions.expressionNames))
$util.qr($condition.expressionValues.putAll($conditionFilterExpressions.expressionValues))
#end
#if( $condition.expressionValues && $condition.expressionValues.size() == 0 )
#set( $condition = {
"expression": $condition.expression,
"expressionNames": $condition.expressionNames
} )
#end
{
"version": "2018-05-29",
"operation": "PutItem",
"key": #if( $modelObjectKey ) $util.toJson($modelObjectKey) #else {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id)
} #end,
"attributeValues": $util.dynamodb.toMapValuesJson($context.args.input),
"condition": $util.toJson($condition)
}
## [End] Prepare DynamoDB PutItem Request. **
response resolver
#if( $ctx.error )
$util.error($ctx.error.message, $ctx.error.type)
#else
$util.toJson($ctx.result)
#end

AWS AppSync vtl make set null if argument is empty list

I want to update a 'person' item in my table.
I want to update the persons name and his set of skills.
It's also possible that we just use the updatePerson mutation to update the name. And we will update the skills later.
At that point the argument 'skills' is an empty list. However DynamoDB does not allow for empty sets.
Currently I am trying to work around this by first checking if the skills argument is an empty list. But it is still telling me "An string set may not be empty for key :skills".
This is my current request mapping template, but atm the isNullOrDefault check does not work.
#if ($util.isNullOrEmpty($context.arguments.skills))
#set ($skills = $utils.dynamodb.toNullJson())
#else
#set ($skills = $utils.dynamodb.toStringSetJson($context.arguments.skills))
#end
{
"version" : "2018-05-29",
"operation" : "UpdateItem",
"key": {
"id" : $utils.dynamodb.toDynamoDBJson($context.arguments.id)
},
"update" : {
"expression" : "set #name = :name, #skills= :skills,
"expressionNames" : {
"#name": "name",
"#skills": "skills",
},
"expressionValues" : {
":name" : $utils.dynamodb.toDynamoDBJson($context.arguments.name),
":skills" : $skills,
}
}
}
Do you know how I can set the set of skills if the skills argument is not an empty array and not set it if the skills argument is an empty array?
Instead of setting null into a string-set attribute, I think you just remove the attribute from the item item.skills = undefined.
You can use SET, and REMOVE actions to achieve that. The update is dynamically generated based on the input of skills. Sample code (I haven't tested it myself)
#set ($update = {
"expression" : "set #name = :name remove #skills",
"expressionNames" : {
"#name": "name",
"#skills": "skills"
},
"expressionValues" : {
":name" : $utils.dynamodb.toDynamoDBJson($context.arguments.name)
}
})
#if (!$util.isNullOrEmpty($context.arguments.skills))
#set ($update = {
"expression" : "set #name = :name set #skills = $skill",
"expressionNames" : {
"#name": "name",
"#skills": "skills"
},
"expressionValues" : {
":name" : $utils.dynamodb.toDynamoDBJson($context.arguments.name),
":skills" :$utils.dynamodb.toStringSetJson($context.arguments.skills),
}
})
#end
{
"version" : "2018-05-29",
"operation" : "UpdateItem",
"key": {
"id" : $utils.dynamodb.toDynamoDBJson($context.arguments.id)
},
"update" : $update // or maybe $util.toJson($update)
}

How to avoid implicit 'null' substitution in GraphQL (AWS App Sync)?

Consider below query:
const fetchItemsQuery = `query(
$paginationPageSize: Int!
$paginationPageNumber: Int!
$searchByNameRequest: String
$categoryID: String
) {
getItems(
paginationPageSize: $paginationPageSize
paginationPageNumber: $paginationPageNumber
searchByNameRequest: $searchByNameRequest
categoryID: $categoryID
) {
// ...
}
};
If to submit it from client side with variables:
{
paginationPageSize: 20,
paginationPageNumber: 1
}
in AWS App Sync logs, it reads:
{
paginationPageSize: 20,
paginationPageNumber: 1,
searchByNameRequest: null,
categoryID: null
}
I checked the request payload from browser - no nulls, just
{
paginationPageSize: 20,
paginationPageNumber: 1
}
So the cause is not in frontend side.
I found out that the null substitution occurring in request resolving template:
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": {
"field": "listProduct",
"arguments": $util.toJson($context.args) // <- here
}
}
$util.toJson uses Apache Velocity.
Can I avoid $util.toJson($context.args) will substitute the nulls in my request variables?
Important: the solution should not touch the explicitly submitted nulls .
You could try to filter out null arguments from the $context.args map.
#set( $args = {} )
#foreach( $entry in $context.args.entrySet())
#if( $entry.value != $null )
#set( $junk = $args.put($entry.key, $entry.value) )
#end
#end
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": {
"field": "listProduct",
"arguments": $util.toJson($args)
}
}

Appsync dynamodb request mapping - default variables

I'm looking a way for setting default variables for appsync dynamodb resolvers, if one of variables from query is not provided.
Let's say we have simple db request mapping
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
## If your table's hash key is not named 'id', update it here. **
"id" : { "S" : "$utils.autoId()" }
},
"attributeValues" : {
## Add an item for each field you would like to store to Amazon DynamoDB. **
"key" : { "S" : "${context.arguments.id}" },
"name" : { "S" : "${context.arguments.name}" }
}
}
I found a way to do this using conditional statements
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
## If your table's hash key is not named 'id', update it here. **
"id" : { "S" : "$utils.autoId()" }
},
"attributeValues" : {
## Add an item for each field you would like to store to Amazon DynamoDB. **
"key" : { "S" : "${context.arguments.id}" },
#if( ($context.arguments.name))
"name" : { "S" : "${context.arguments.name}" }
#else
"name" : { "S" : "default" }
#end
}
}
Above way we can also exclude one of attributes if it is not provided
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
## If your table's hash key is not named 'id', update it here. **
"id" : { "S" : "$utils.autoId()" }
},
"attributeValues" : {
## Add an item for each field you would like to store to Amazon DynamoDB. **
"key" : { "S" : "${context.arguments.id}" },
#if( ($context.arguments.name))
"name" : { "S" : "${context.arguments.name}" }
#end
}
}
If $context.arguments.name is not provided then name attribute isn't inserted into dynamodb.
But is there any smarter way to do above?
If you don't want to specify keys explicitly, you can do something like:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
## If your table's hash key is not named 'id', update it here. **
"id" : { "S" : "$utils.autoId()" }
},
#set( $expValues = {} )
#foreach( $entry in $context.arguments.entrySet() )
$!{expValues.put("${entry.key}", { "S" : "${entry.value}" })}
#end
#if( !${expValues.isEmpty()} )
"attributeValues" : $utils.toJson($expValues)
#end
}