AWS DynamoDB Transact Write using IN operator on Array - node.js

I am working on one query where I need to use TrasactWrite of dynamoDB and perform update query on one of my table.
Scenario is I am having array of unique IDs and I need to perform same update operation on each ID in array and change one flag in table without using Loop or map.
But I am having difficulty in finding example or docs related to how do I use IN operator on IDs which are primary keys in a Key parameter of transactWrite.
Here is my sample code:
let IDs = await find('table', { type: 'some_type' }, 'type-index}
let params = {
TransactItems: [{
Update: {
TableName: 'table',
Key: '#_id IN :IDs,
UpdateExpression: 'set #flag = :flag',
ExpressionAttributeNames: {
'#flag': 'flag',
'#_id': '_id'
},
ExpressionAttributeValues: {
':flag': false,
":IDs": IDs
}
}
}]
}
Already tried: Answer Its not same question as mine
I will answer if anyone has any question. Any help will be really helpful. Thank you

You can't use IN operator in this case - Update. With update operator you have to put key value to Update object. A key includes partitionKey and sortKey, I think in your case, you just set partitionKey.
To update more than one item by ID, you can put many Update object to TransactItems.
let IDs = await find('table', { type: 'some_type' }, 'type-index');
let params = {
TransactItems: [], // init empty array
}
IDs.forEach((ID) => {// loop though IDs array
params.TransactItems.push({
Update: {
TableName: 'table',
Key: {
'_id': ID, // ID value
},
UpdateExpression: 'set #flag = :flag',
ExpressionAttributeNames: {
'#flag': 'flag',
// '#_id': '_id' // remove this line
},
ExpressionAttributeValues: {
':flag': false,
// ":IDs": IDs // remove this line
}
}
})
});
// await client.transactWrite(params).promise()

Related

Using updateOne method to update an object field inside array - throws error "Cannot create field 'url' in element"

I have MongoDB database (with Mongoose) containing a collection of Products (among others), which looks like this:
[
{
name: 'Product A',
url: 'product-a',
category: 'accesory',
price: 12,
shortDescription: ['example description'],
technicalSpecs: [{ speed: 10, weight: 20 }],
images: [],
reviews: [],
relatedProducts: [
{
url: 'product-b',
name: 'Product B',
// to be added in Update query
//id: id_of_related_product
}
]
} /* other Product objects */
]
As every MongoDB document is provided with _id property by default, but within the relatedProducts array i only have url and name properties, i want to add the id property (associated with corresponding Product) for each object in the relatedProducts array, so i will be able to conveniently query and process those related products.
I came up with an idea to query all Products to get only those, which have non-empty relatedProducts array. Then i loop them and i search for Product model, which has specific url and name properties - this let's me get it's true (added by MongoDB) _id. At the end i want to add this _id to matching object inside relatedProducts array.
My code:
async function assignIDsToRelatedProducts(/* Model constructor */ Product) {
const productsWithRelatedOnes = await Product.find(
{ relatedProducts: { $ne: [] }}, ['relatedProducts', 'name', 'url']
);
for (const productItem of productsWithRelatedOnes) {
for (const relatedProduct of productItem.relatedProducts) {
const product = await Product.findOne(
{ url: relatedProduct.url, name: relatedProduct.name },
'_id'
);
// throws error
await productItem.updateOne(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': product._id } }
);
}
}
}
However it throws the following error:
MongoError: Cannot create field 'url' in element {relatedProducts: [ /* array's objects here */ ]}
I don't know why MongoDB tries to create field 'url', as i use it to project/query url field (not create it) in updateOne method. How to fix this?
And - as i am newbie to MongoDB - is there a simpler way of achieving my goal? I feel that those two nested for..of loops are unnecessary, or even preceding creation of productsWithRelatedOnes variable is.
Is it possible to do with Mongoose Virtuals? I have tried it, but i couldn't match virtual property within the same Product Model - attach it to each object in relatedProducts array - after calling .execPopulate i received either an empty array or undefined (i am aware i should post at-the-time code of using Virtual, but for now i switched to above solution).
Although i didn't find solution or even reason of my problem, i solved it with a slightly other approach:
async function assignIDsToRelatedProducts(Product) {
const productsHavingRelatedProducts = Product.find({ relatedProducts: { $ne: [] }});
for await (const withRelated of productsHavingRelatedProducts) {
for (const relatedProductToUpdate of withRelated.relatedProducts) {
const relatedProduct = await Product
.findOne(
{ url: relatedProductToUpdate.url, name: relatedProductToUpdate.name },
['url', '_id']
);
await Product.updateMany(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': relatedProduct._id } }
);
}
}
const amountOfAllProducts = await Product.find({}).countDocuments();
const amountOfRelatedProductsWithID = await Product
.find({ 'relatedProducts.id': { $exists: true } }).countDocuments();
console.log('All done?', amountOfAllProducts === amountOfRelatedProductsWithID);
}
Yet, i still suppose it can be done more concisely, without the initial looping. Hopefully somebody will suggest better solution. :)

How to delete property from object received in sequelize?

I have a model which requires a date input in the where clause for a query.
const Model = sequelizeTwo.define('model', {
A: Sequelize.BIGINT,
B: Sequelize.DOUBLE,
C: Sequelize.DOUBLE,
D: Sequelize.DOUBLE,
E: Sequelize.DOUBLE,
F: Sequelize.DOUBLE,
DATE: Sequelize.DATEONLY,
newCol: Sequelize.VIRTUAL
},{
tableName: "Model",
timestamps: false,
freezeTableName: true
})
DATE here is used as a params for the query to display information on the client. But that is the end of it's use. I don't want to send DATE to the client back but I see no way to remove it.
If i remove it from the model, it gives a timezone error which is another problem as well.
app.get('/api/:date', (req, res) => {
var date = req.params.date
Model.findAll({
where: {
DATE: {
[Op.eq]: date
}
},
order: [
['A', 'ASC']
]
}).then(result => {
...
for (i; i < result.length; i++) {
...
delete result[i].DATE
console.log(result[i].DATE)
result[i]["newCol"] = values;
}
res.json(result);
})
})
I have tried using the delete operator inside and outside the loop, but it's of no use. It still retains the property and sends it back to the client
delete result[i].DATE //Inside loop
delete result.DATE //before loop
The values do they get updated when assignment is done but the property/key can't be modified.
result[i].DATE = null or undefined
What i want to achieve here is that I just want to send an object back which has all the properties in the sequelize model except DATE
I think you should use excludes feature for this case:
Model.findAll({
where: {
DATE: {
[Op.eq]: date
}
},
order: [
['A', 'ASC']
],
attributes: { exclude: ['DATE'] }
})
More information here: sequelize documentation
I think that the best practice is to create another model for the view as a data transfer object (DTO), and to map only the desired properties to it (without the Date).
Further read: DTO.
Pseudocode example:
Model.findAll({
//...
}).then(result => {
...
let resultDTO = mapToDTO(result);
res.json(resultDTO);
})

How to query a table by using IN conditions in DynamoDB

Struggling to find an example of how to query a table to return rows with ids from a given list.
The query below throws on the inclusion of IN
var params = {
id: '7deb3df9-552b-47a4-aef3-ad601f141d50'
};
var p = {
TableName: 'players',
KeyConditionExpression: 'id IN (:id)',
ExpressionAttributeValues: buildQuery(params)
};
You can't use "IN" operator with KeyConditionExpression, please see details in this SO question
You may want to use batchGetItem instead of query, which is not so efficient though.
Here is how your params could look like:
var params = {
RequestItems: {
'players': {
Keys: [{
id: "7deb3df9-552b-47a4-aef3-ad601f141d50",
rangeKey: "<range key 1>" // <--- if your table has a range key, you must specify its value here
}, {
id: "<ANOTHER ID 2>",
rangeKey: "<range key 2>"
}, {
id: "<ANOTHER ID 3>",
rangeKey: "<range key 3>"
}]
}
}
};
dynamodbDoc.batchGet(params, function(err, data) {
});

MongoDB and dynamic query building with $push statement with node.js

I'm creating an application where I need to keep version of each field in database when update is performed. So I figured that I would use statement like this to accomplish it.
collection.update({ _id: id },
{
$push: { placeholder : { $each: [{ data: keyValue, timeStamp: date, editor: editorID}] } }
},
The placeholder is a field which might be different each time depending which field I'm updating in the table.
var key = req.body.key;
var keyValue = req.body.keyValue;
var editorID = req.body.editor;
var date = new Date();
The above variables are initialized based on the form submitted and the key is the name of the database filed which I would like to use instead of placeholder.
How can I make the placeholder to represent database field name passed from the form?
I found the solution.
var query = {};
query[key] = { data: keyValue, timeStamp: date, editor: editorID };
and then
collection.update({ _id: id },
{
$push:query
}
},

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