How to check and then update AWS dynamoDB item in node.js? - node.js

Here is an example. I am using AWS Lambda function in Node.JS. This is the object in DynamoDB:
{
email: "johndoe#gmail.com",
name: "John Doe",
score: "12"
}
Now I want to first check what his saved score is. If his current score is more than the score already present in DB then update the DB else leave it as it is. Currently, my code is updating it every time. I'm not sure how to proceed and which function to use.
If his score is 15, then after checking the DB it should update it to:
{
email: "johndoe#gmail.com",
name: "John Doe",
score: "15"
}
If his score is 7, it should leave it as it is.
{
email: "johndoe#gmail.com",
name: "John Doe",
score: "12"
}
Edit - The following issue has also been solved. Found a workaround. Still looking for answers. So leaving it open.
Now my issue is that I am using the same function for both updating and creating records. Is that possible?
If a new user plays the game his email, name, and score are saved to the DB. And if an existing user plays, only his score is updated if it is greater than the one in DB.
This is where I'm currently stuck. Getting these 2 issues,
1. name is a reserved keyword. I wonder how was it allowed me to use name as an attribute when i was just using put.
2. email cannot be updated since it is part of key.
Here is my current code,
function addToLeaderBoard(currentScore, email, name){
var params = {
TableName: tablename,
Key: {
"email": email
},
UpdateExpression: "set score = :num, name = :player, email = :mail",
ConditionExpression: "score > :num",
ExpressionAttributeValues: {
":num": currentScore,
":player": name,
":mail": email
},
ReturnValues: "UPDATED_NEW"
};
docClient.update(params, function(err, data) {
if (err) console.log(err);
else console.log(data);
});
}

What you want to do is a conditional update. This allows you to only the update the item in DynamoDB if a condition is met. In your case that condition would be, that the new score has to be higher than the existing score.
Such a conditional update would look like the following example. Note the ConditionExpression in there, which is responsible for only updating the item when the condition is met.
'use strict';
const aws = require('aws-sdk');
var newScore = 123;
var docClient = new AWS.DynamoDB.DocumentClient()
var params = {
TableName: "players",
Key: {
"email": "johndoe#gmail.com"
},
UpdateExpression: "set score = :num",
ConditionExpression: "score > :num",
ExpressionAttributeValues: {
":num": newScore
},
ReturnValues: "UPDATED_NEW"
};
docClient.update(params, function(err, data) {
if (err) console.log(err);
else console.log(data);
}
});

Related

DynamoDB wants number if gets string and string if gets number

dynamodb is being very indecisive for me. I have a random id generator that includes both letters and numbers, but when I use putItem an error appears saying: ValidationException: The parameter cannot be converted to a numeric value: random3string8of5letters4and2numbers.
If I give in and decide to make my id generator number only, it appears with another error: ValidationException: One or more parameter values were invalid: Type mismatch for key user_id expected: S actual: N if I put user_id: "999999"
I am very confused right now. Any help would help :)
Function where weird stuff is happening:
const dynamo = new AWS.DynamoDB();
async function addUser(username, password, email, firstName) {
const items = {
"user_id": {
"N": 2 //or "2" or "j38873kjhad8123" or something like that
//normally r() - the random id generator that returns a random id
},
"username": {
"S": username
},
"password": {
"S": password
},
"email": {
"S": email
},
"firstName": {
"S": firstName
}
};
const message = new Promise((res, rej) => {
dynamo.putItem({
TableName: 'user_data',
Item: items
},
(err, data) => {
if (err) rej(err);
else res({ id: user_id, message: `User added to database with user_id ${user_id}` });
});
});
return message;
}
//example usage of function:
addUser("test", "test", "test", "test").then(...);
random id generator:
function r() { //get randomized string 30 chars long
let res = '';
const chars = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"];
for (let i = 0; i < 30; i++) {
res += chars[Math.floor(Math.random() * chars.length)];
}
return res;
}
There are two places that you need to indicate that user_id is a string:
in the attribute definitions when creating your table
in the item you are passing to the putItem call
It looks like your table definition was correctly configured with user_id as a string so the problem must be in the parameters to your putItem call. Use the following:
"user_id" : { "S": r() }

Add new array element to JSON object in Node-Postgres

I have a json field in a postgres database that looks something like this :
{
"first_name": "John",
"last_name": "Doe",
"email": "johndoe#example.org",
"reviews" :[
{
"comment": "John gave us great service! Love this guy!",
"last_name": "Mariana",
"first_name": "Brown"
}
]
}
Using node, I'm trying to add a new array element (a new review) to the reviews. I want to be able to do this dynamically. The query I'm using ads in a array at index 2. That is not dynamic. I did find helpful material here but all the answers don't tell you how to update(add) to second level keys like the one in my situation. How can I do this?
Query
var text ="UPDATE users SET info = JSONB_SET(info, '{reviews,2}', '"+review+"') WHERE id=$1 RETURNING*";//works but not dynamic
var values = [userid];
pool.query(text, values, (err, res) => {
if (err) {
//log errors
console.log(err.stack);
//return error
io.to(socketid).emit('review-sent',{valid:'false'});
} else {
//success
console.log(res.rows );
var json = res.rows;
//success
io.to(socketid).emit('review-sent',{valid:'true'});
}
});
You can use jsonb_insert() if that is given a negative index it counts from the end. Passing true as the third parameter will then insert the new value after that, so jsonb_insert(..., '{reviews, -1}', true) will append the new value at the end:
update users
set info = jsonb_insert(info,
'{reviews,-1}',
'{"comment": "...", "last_name": "...", "first_name": "..."}',
true)
where id = 1;

Remove JSON Element if it doesn't contain a specific value

I'm trying to return only values where john is found from a DynamoDB database.
I'm able to return values where it contains name: john from a mapped list, however the problem am having is that it appears to also be returning other values as well.
Running select: 'count' returns 1 match which is correct but it doesn't return anything when used.
I'm assuming that count just returns a number and not a specific select where john is matched.
I'm writing this in NodeJS; am hoping someone can help me figure this out.
I know that the value I only want shown are json elements where name: john, anything else I want omitted from being shown.
Here's my result as of right now:
{
"Department": [
{
"employees": [
{
"name": "john"
},
{
"name": "sally"
}
]
}
],
"Count": 1
}
My code:
const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies
const dc = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context, callback) => {
// Construct the params for filtering data through dynamodb
const params = {
FilterExpression: "contains(#department, :employees)",
ExpressionAttributeNames: {
"#department": "employees",
},
ExpressionAttributeValues: {
":employees": {
"name":"john"
}
},
ProjectionExpression: "#department",
TableName: 'mytable',
//Select: 'COUNT'
};
const resultDC = await dc.scan(params).promise();
var items = resultDC.Items;
var count = resultDC.Count;
var returned_list = {
'Department' : items,
'Count' : count,
};
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(returned_list),
};
callback(null, response);
};
I suggest you to use a Local Secondary Index.
Take a look here.

Modify value on indexed field in DynamoDB

I have a Users table with an index on the username field. This makes it possible for me to search for users by username.
When I update an object's username field, fx from "Alice" to "Bob", then no items are returned to me when I subsequently call:
// Fetch the user with username Bob
const params = {
TableName: 'Users',
IndexName: 'username-index',
KeyConditionExpression: 'username = :value',
ExpressionAttributeValues: {
':value': 'Bob'
}
}
const result = await dynamo.query(params).promise()
It seems to me that "Bob" is not added to the index. I am doing the update in a wrong way or is there a way for me to update the index?
******************************* UPDATE *******************************
This is code used to update the username:
const params = {
TableName: 'Users',
Key: { _id: USER_ID },
UpdateExpression: 'SET username = :value',
ExpressionAttributeValues: {
':value': username
}
}
const result = await dynamo.update(params).promise()
I have looked in the AWS Console and verified that the username has in fact been changed. Also, when I look under Indexes I can see that the Item count for the username-index is 18, while the number of users is 19.

DynamoDB ConditionalExpression not recognizing Attribute Names or Values

I'm using NodeJS's aws-sdk and am trying to do an update such that if the item doesn't exist it will throw an error. I'm using the Expression API instead of the legacy one. Here is my contrived example that isn't working for me.
client.update({
TableName: 'User',
Key: {'_id': '10'},
UpdateExpression: 'SET username = :user, password = :pword',
ConditionalExpression: 'attribute_exists(#idKey) AND #idKey = :idVal',
ExpressionAttributeNames: {
'#idKey': '_id'
},
ExpressionAttributeValues: {
':idVal': '10',
':user': 'user10',
':pword': 'password10'
}}, function(err, data){
if(err) console.log(err);
else console.log(data);
});
ValidationException: Value provided in ExpressionAttributeNames unused in expressions: keys: {#idKey}
I've tried various other ConditionalExpressions both using attribute names and inserting the actual value into the expression. I'm beginning to think this is a bug. Using the legacy Expected->Exists with the legacy AttributeUpdate works but I am unable to demonstrate this feature with Expressions.
You are already narrowing down to the specific item where _id=10 with the Key parameter of your UpdateItemRequest. If an item does not exist, you cannot condition the UpdateItem call on a specific value of a key. Therefore, only the attribute_exists(#idKey) in the ConditionExpression is necessary.
The following code elicits the behavior you desire (I had to change table name to Images and primary key to Id to match the contents of the DynamoDB Local Shell tutorial.
var params = {
TableName: 'Image',
Key: { // The primary key of the item (a map of attribute name to AttributeValue)
'_id': 'dynamodb.png'
},
UpdateExpression: 'SET username = :user, password = :pword',
ConditionExpression: 'attribute_exists(#id)',
ExpressionAttributeValues: {
':user': 'user10',
':pword': 'password10'
},
ExpressionAttributeNames: {
'#id': '_id'
},
ReturnValues: 'ALL_NEW'
};
docClient.update(params, function(err, data) {
if (err) ppJson(err); // an error occurred
else ppJson(data); // successful response
});
As a reminder, please do not post any real password data here :)

Resources