I have a dynamodb table, with columns as id (partition key) and date. I am trying to update the date where id=2, but I am getting below error as response:
message: "The provided key element does not match the schema"
__type: "com.amazon.coral.validate#ValidationException"
Below is my code:
import * as AWS from 'aws-sdk'
AWS.config.update({
region: 'us-east-1',
accessKeyId: 'MY_ACCESS_KEY',
secretAccessKey: 'MY_SECRET_KEY'
});
const docClient = new AWS.DynamoDB.DocumentClient()
export const updateData = (tableName : any, id : any, date : any) => {
let params = {
TableName: tableName,
Key:{
"id": id
},
UpdateExpression: `set date = :date`,
ExpressionAttributeValues: {":date": date},
};
docClient.update(params, function(err, data) {
if (!err) {
console.log(data);
}else{
console.log(err)
}
})
}
I am calling function like this:
updateData("mytable","2","2023-01-10")
I anyone confirm what I am doing wrong here?
date is a reserved keyword in DynamoDB
You need to use ExpressionAttributeNames param:
let params = {
TableName: tableName,
Key:{
"id": id
},
UpdateExpression: `set #date = :date`,
ExpressionAttributeValues: {":date": date},
ExpressionAttributeNames: {"#date":"date"}
};
As for the exception, ensure that your tables partition key is id and if type String, and that the table does not have a sort key, otherwise you need to add the sort key also.
Related
I have the following Lambda function to delete an item from my DynamoDB.
const AWS = require("aws-sdk");
AWS.config.update({ region: "us-west-2" });
var docClient = new AWS.DynamoDB.DocumentClient();
var deleteContact = function(event,callback) {
var params = {
TableName:"Contacts",
Key:{
id: event.id
},
ConditionExpression: "set event.id = :id",
ExpressionAttributeValues: {
":id": event.id
}
};
console.log("Attempting a conditional delete...");
docClient.delete(params, function(err, data) {
if (err) {
console.error("Unable to delete item. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));
}
});
};
deleteContact();
and here is the code in my react app which is requesting:
export const removeContact = createAsyncThunk(
'contactsApp/contacts/removeContact',
async (contactId, { dispatch, getState }) => {
await axios.post('https://API.amazonaws.com/prod', {
key1: `${contactId}`
});
console.log(contactId)
return contactId;
}
);
Currently, code works and deletes the parameters in the row. But the problem is it will not remove the id from the DynamoDB table. So everything will be deleted EXCEPT id.
As result, I would have GHOST items in my DynamoDB table:
DynamoDB screenshot
I am working on a blogging application (click the link to see the GitHub repo) with Express, EJS and MongoDB.
I have Posts that are grouped into Categories, each in its own collection.
I run into a problem trying to filter posts by category. To obtain the post by category url, I turn the category name into a slug and use it this way:
Posted in <%= post.category.cat_name %>
In the public routes file I have:
const express = require('express');
const postsController = require('../../controllers/front-end/posts');
// Express router
const router = express.Router();
// Get Posts
router.get('/', postsController.getPosts);
// Get Single Post
router.get('/:id', postsController.getSinglePost);
// Get Posts by Category
router.get('/:catname', postsController.getPostsByCategory);
module.exports = router;
The Post model:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
short_description: {
type: String,
required: true
},
full_text: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
post_image: {
type: String,
required: false
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Post', postSchema);
The Category model:
const mongoose = require('mongoose');
const categorySchema = new mongoose.Schema({
cat_name: {
type: String,
required: true
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Category', categorySchema);
In the Posts controller I turn the slug back into category name to filter posts by category name:
exports.getPostsByCategory = (req, res, next) => {
function titleize(slug) {
var words = slug.split("-");
return words.map(function(word) {
//return word;
return word.charAt(0).toUpperCase() + word.substring(1).toLowerCase();
}).join(' ');
}
const postCategory = titleize(req.params.catname);
const posts = Post.find({ cat_name: postCategory }, (err, posts) => {
console.log('Category: ', postCategory);
if(err){
console.log('Error: ', err);
} else {
res.render('default/index', {
moment: moment,
layout: 'default/layout',
website_name: 'MEAN Blog',
page_heading: 'XPress News',
page_subheading: 'A MEAN Stack Blogging Application',
posts: posts.reverse(),
});
}
}).populate('category');
};
The line console.log('Category: ', postCategory) outputs Category: Favicon.ico instead of the category name.
What am I doing wrong?
if I read this correctly, res is expect json pairs.
I am guessing your post.reverse() does not output in json format.
first of all - look at the moment where you ask DB - you need to await the answer, so you should use Promise.than() or async/await in your routes ...
Another one from request you get STRING as parameter - but in mongo schema you have Object...
So you should receive smth like "CastError: Cast to ObjectId failed...",
it depends on your vision you can: first select category from category.db => so you receive category Object after that you can search the posts using this object ..., or you can first populate results of posts by category (obtain plain category fields) and make further search...
category in your post-schema is a $ref to the category-schema, which is why it holds an objectId. In order to reference and actually query your category-schema while using .find(), you need to populate it first:
Post.
find({}).
populate({
path: 'category',
match: { cat_name: postCategory}
}).
exec((err, posts) => {
// ...
});
The mongoose documentation for $ref/populate() is a bit hidden here, in case you want know more about it.
Thanks to Lilian Baxan, here is the right getPostsByCategory method in controllers\front-end\posts.js:
const Category = require('../../models/categories');
//more code here
exports.getPostsByCategory = async (req, res, next) => {
function titleize(slug) {
var words = slug.split("-");
return words.map(function(word) {
//return word;
return word.charAt(0).toUpperCase() + word.substring(1).toLowerCase();
}).join(' ');
}
const postCategory = titleize(req.params.catname);
const singleCategory = await Category.findOne({cat_name:postCategory})
const posts = await Post.find({ category : singleCategory }, (err, posts) => {
if (err) {
console.log('Error: ', err);
} else {
res.render('default/index', {
moment: moment,
layout: 'default/layout',
website_name: 'MEAN Blog',
page_heading: 'XPress News',
page_subheading: 'A MEAN Stack Blogging Application',
posts: posts.reverse(),
});
}
}).populate('category');
};
I am trying to query Dynomo DB table and I want to go through over the resulting items in a function in my AWS Lambda. I am not able to extract result from Dynamo DB query. It is inside the closure, I am able to console log it, but I am not able to assign it for any variable in the scope of outer function.
What should I do to get it outside?
function check(id) {
//build params
let params = {
TableName: 'demo_table',
KeyConditionExpression: #key =: id,
Limit: 5,
ScanIndexForward: false,
ExpressionAttributeNames: {
#key: process.env.PRIMARYKEY
},
ExpressionAttributeValues: {
: id: id
}
};
//query ddb
let result = {};
ddb.query(params, function(err, data) {
if (err) {
console.log("AN ERROR OCCURED\n");
console.log(err);
} else {
//How to copy the data from here to outside??
//I can console log and see the data
result = data;
}
});
console.log(result); //returns {}
}
const check = async (id) => {
//build params
let params = {
TableName: 'demo_table',
KeyConditionExpression: #key =: id,
Limit: 5,
ScanIndexForward: false,
ExpressionAttributeNames: {
#
key: process.env.PRIMARYKEY
},
ExpressionAttributeValues: {
: id: id
}
};
let result = await new Promise((resolve, rejects) => {
ddb.query(params, function (err, data) {
if (err) rejects(err)
resolve(data)
});
})
console.log(result); //returns {}
}
By using promises you can get the data. database read is an asyncronous operation.
I need to filter data between two dates, a start and an end, to display in front only events that occurred between the selected dates ...
Product Controller method:
async show(req, res){
const { product_id, dt_ini, dt_fin } = req.params;
let dtIni = new Date(dt_ini);
let dtFin = new Date(dt_fin);
dtIni = dtIni.toISOString();
dtFin = dtFin.toISOString();
let product = await Product.findOne({ product_id });
if(product){
product = await product.populate('event').execPopulate();
await product.find({"event.timestamp": {'$gte': dtIni,'$lt': dtFin}});
}else{
return res.status(404).json({ error: `Product not found.`});
}
return res.json(product);
}
Model Event :
const mongoose = require('mongoose');
const EventSchema = new mongoose.Schema({
product_id: String,
timestamp: Date,
created_by: String,
description: String,
sale_price: Number,
list_price: Number,
has_discount: Boolean,
url: String,
warehouse: String,
page_type: String,
showcase:{
name: String,
position : Number
},
});
module.exports = mongoose.model('Event', EventSchema);
Model Product :
const mongoose = require('mongoose');
const ProductSchema = new mongoose.Schema({
product_id: Number,
product_sku: String,
product_name: String,
product_brand: String,
product_category: String,
event: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Event'
}
],
});
module.exports = mongoose.model('Product', ProductSchema);
After MongoDB populate() I am receiving this error:
(node:3496) UnhandledPromiseRejectionWarning: TypeError: product.find is not a function
What am I missing?
You can do a conditional populate:
async show(req, res){
try {
const { product_id, dt_ini, dt_fin } = req.params;
if(!product_id || !dt_ini || !dt_fin)
throw "You must supply a product id with start and end dates!";
const existingProduct = await Product
.findOne({ product_id })
.populate("event", null, { timestamp: { $gte: new Date(dt_ini), $lte: new Date(dt_fin) } })
if(!existingProduct)
throw "Unable to locate any product events in that date range.";
res.status(200).json({ product: existingProduct });
} catch(e) {
res.status(404).json({ error: e.toString() });
}
}
Or, you can do an aggregated query:
Here's a simplified working playground: https://mongoplayground.net/p/YGiywDCkR-2
async show(req, res){
try {
const { product_id, dt_ini, dt_fin } = req.params;
if(!product_id || !dt_ini || !dt_fin)
throw "You must supply a product id with start and end dates!";
const existingProduct = await Product.aggregate([
// match against the product id
{ $match: { product_id } },
// lookup (populate) the "event" field with event data as "events"
{
$lookup: {
from: "events",
localField: "event",
foreignField: "_id",
as: "events"
}
},
// spread out the populated "events" field
{ $unwind: "$events"},
// filter the "events.timestamp" with dates
{
$match: {
"events.timestamp": {
"$gte": new Date(dt_ini),
"$lte": new Date(dt_fin)
}
}
}
]);
if(existingProduct.length === 0)
throw "Unable to locate any product events in that date range.";
res.status(200).json({ product: existingProduct });
} catch(e) {
res.status(404).json({ error: e.toString() });
}
}
Although, be careful about using $lookup as it can be quite expensive.
I have done a lot of research and the topic does not have enough source for juniors like me. Everything I could find was case specific that was making it impossible to understand. Therefore for myself and for the people who will read this in the future I will not make my question too case specific.
I have created a table record on DynamoDB with the following lambda function:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({region: 'us-east-2', apiVersion: '2012-08-10'})
exports.handler = (event, context, callback) => {
console.log(event)
const params = {
Item: {
"UserId": {
S: "global"
},
"search": {
SS: [
"" + event.hashtag
]
}
},
TableName: "crypto-app"
};
dynamodb.putItem(params, function(err, data) {
if (err) {
callback(err)
} else {
callback(null, data)
}
});
};
this is creating a simple string set
{
"search": {
"SS": [
"london"
]
},
"UserId": {
"S": "global"
}
}
how can I add more strings to my string set with a lambda function to make it like this?
{
"search": {
"SS": [
"london", "tokyo", "moskow"
]
},
"UserId": {
"S": "global"
}
}
You can update the item and add additional string set values.
Here's how you would do it if you had named the attribute xxx rather than search, which is a reserved word.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({region: 'us-east-2'});
const params = {
Key: {
UserId: {
S: 'global',
},
},
UpdateExpression: 'ADD xxx :avals',
ExpressionAttributeValues: {
':avals': {
SS: ['tokyo', 'moskow'],
},
},
TableName: 'crypto-app',
};
dynamodb.updateItem(params, (err, data) => {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
However, because you named the attribute search, which is reserved, you need to essentially escape that reserved name using an expression attribute name, which is a placeholder that you use in an expression, as an alternative to an actual attribute name.
Here's an example of how you do that:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({region: 'us-east-2'});
const params = {
Key: {
UserId: {
S: 'global',
},
},
UpdateExpression: 'ADD #a :avals',
ExpressionAttributeValues: {
':avals': {
SS: ['tokyo', 'moskow'],
},
},
ExpressionAttributeNames: {
'#a': 'search',
},
TableName: 'crypto-app',
};
dynamodb.updateItem(paramse, (err, data) => {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
Another, probably better, way to do this is to use the DynamoDB DocumentClient. It's a higher level client interface and it simplifies working with items by abstracting away the notion of attribute values, and instead using native JavaScript types.
With the DocumentClient, rather than explicitly writing UserId: { 'S': 'global' }, you can simply use UserId: 'global' and the string type ('S') will be inferred.
Here's an example of the item update using DocumentClient:
const AWS = require('aws-sdk');
const dc = new AWS.DynamoDB.DocumentClient({region: 'us-east-2'});
const params = {
Key: {
UserId: 'global',
},
UpdateExpression: 'ADD #a :avals',
ExpressionAttributeValues: {
':avals': dc.createSet(['tokyo', 'moskow']),
},
ExpressionAttributeNames: {
'#a': 'search',
},
TableName: 'crypto-app',
};
dc.update(params, (err, data) => {
if (err) {
console.log(err);
} else {
console.log(data);
}
});