Creating a Request Params for batchWriteItem(params,function(){}) in javaScript - node.js

I have been trying to create params for batchWriteItem(), when I create them like this it perfactly works.
var params = {
RequestItems: {
'tableNameXYZ' : [ //hardcoded
{
PutRequest: {
"Item" : {
"indexNumber":{
"N": xyz
},...
}
},
PutRequest: {
"Item" : {
"indexNumber":{
"N": xyz1
},...
}
}
}
]
};
But I am unable to work on it when I have table name in a variable:
var tableName = 'tableNameXYZ';
var params = {
RequestItems: {
tableName : [
{
PutRequest: {
"Item" : {
"indexNumber":{
"N": xyz
},...
}
},
PutRequest: {
"Item" : {
"indexNumber":{
"N": xyz1
},...
}
}
}
]
};
Obviously this wouldn't have worked since I played rough with JSON object here.
Then I tried this:
var params = {};
params.RequestItems[tableName] = [
{
PutRequest: {
"Item" : {
"indexNumber":{
"N": xyz
},...
}
},
PutRequest: {
"Item" : {
"indexNumber":{
"N": xyz1
},...
}
}
}
];
Now its throwing me an error like:
TypeError: Cannot set property 'tableNameXYZ' of undefined
at exports.handler (/var/task/index.js:181:32)
Can someone Pointout my mistake?
Thanks.

You've got this error because:
var params = {};
Creates an empty object (called params) with no properties, but:
params.RequestItems[tableName] = [ // ...rest of code
Tries to assign params.RequestItems.the-value-of-tableName.
But params has no property called RequestItems and so, params.RequestItems is undefined. And it is a TypeError to try and set the value of a property (in this case the-value-of-tableName) on undefined.
To fix this, use:
var params = {};
params.RequestItems = {}; // assign RequestItems an empty object.
params.RequestItems[tableName] = [ // ...rest of code.

Related

AWS Update item with list on DynamoDB using lambda Nodejs

I have an item in my DynamoDB table that looks like this:
{
"qid": {
"S": "0"
},
"options": {
"L": [
{
"S": "Summer"
},
{
"S": "Winter"
}
]
},
"votes": {
"L": [
{
"N": "11"
},
{
"N": "13"
}
]
}
}
With a lambda function, I want to update one of the elements in the votes list. I want to update the index that matches the index of the option I get from the event (the event has qid, option and number of votes). So here is what I tried:
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context) => {
const qid = event.qid;
const option = event.option;
const vote = event.vote;
const params = {
TableName : 'QnV',
Key: {
qid: qid
}
}
try {
const data = await docClient.get(params).promise()
const options = data.Item.options;
const index = options.findIndex(option)
const updateParams = {
TableName: 'QnV',
Key: {
'qid': qid
},
UpdateExpression: 'set votes[:index] = :vote',
ExpressionAttributeValues: {
':index': index,
':vote': vote
},
ReturnValues: 'UPDATED_NEW'
};
const updateData = await docClient.update(updateParams).promise();
return {
statusCode: 200,
body: JSON.stringify(updateData)
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify(err)
};
}
};
I tried to test it with this:
{
"qid": "0",
"option": "Winter",
"vote": 14
}
But I get an error of 500 and an empty body. It looks like the update params variable is empty, but I don't understand why.
I tried to search online but it looks like I'm doing everything correctly (but if that was the case I didn't have a problem). I also didn't forget to grant permission to the function.
Edit: As suggested, I change the body of the error to err.message, and got this:
{
"statusCode": 500,
"body": "\"Winter is not a function\""
}
Please help me find the bug, I would really appreciate it!
Thank you
After more debugging I found the problems. There were two.
Incorrect index method.
const index = options.findIndex(option)
Changed to:
const index = options.indexOf(option)
The way I tried to find the index in updateParams was incorrect. Instead of:
UpdateExpression: 'set votes[:index] = :vote',
ExpressionAttributeValues: {
':index': index,
':vote': vote
},
It should be:
UpdateExpression: `set votes[${index}] = :vote`,
ExpressionAttributeValues: {
':vote': {N: vote},
},

Querying nested object using $find by applying conditions in Mongodb

This is my json object
{
"account_id" : "1",
"sections" : [
"name" : "sec1",
"label" : {
"label1" : "text1",
"label2" : "text2"
}
},
"name" : "sec2",
"label" : {
"label3" : "text3",
"label4" : "text4",
"label5" : "text5"
}
},
]
}
So in this json I wanted to query the label object where sector= sec1. I have used the below code but it didn't work.
var getData = (db, query) => {
return db
.collection(TABLE_NAME)
.find(query, { account_id: { sections: { label: 1 } } })
.toArrayAsync();
};
var dataList = (db, event) => {
let dataQuery = {
account_id: id,
'sections.name': event.params.section
};
return getData(db, dataQuery);
};
module.exports.getData = (event, cb) => {
return using(connectDatabase(), db => {
return dataList (db, event);
}).then(data => cb(null, responseObj(data, 200)), err =>
cb(responseObj(err, 500)));
};
Could someone kindly help me? Thanks inadvance.
Try something like this. use $project, we can selectively remove or retain field and we can reassign existing field values and derive entirely new values. after projecting the labels and name do a $match to extract the document by name. One thing to notice is that by using $project,it will automatically assign the document's _id.
var dataList = (db, event) => {
return db
.collection(TABLE_NAME)
.aggregate([
{
$match: { account_id: your_id }
},
{
$unwind: '$sections'
},
{
$project:{labels:'$sections.label',name:'$sections.name'}
},
{
$match:{name:section_name}
}]).toArray();
};
You have to use aggregate method with $unwind syntax to find item in array of object.
var dataList = (db, event) => {
return db
.collection(TABLE_NAME)
.aggregate([
{
$match: {
account_id: id,
}
},
{ $unwind: "$comments" },
{
$match: {
'name': event.params.section
}
}
])
.toArrayAsync();
};
Result:
[{
"account_id": "1",
"sections": {
"name": "sec2",
"label": {
"label3": "text3",
"label4": "text4",
"label5": "text5"
}
}
}]

Use variable in mongodb object dot notation

I want to increment a property value of an object if it does exist inside an array.
Mongo record:
{
"_id" : ObjectId("5b7bdd9f0465e8345ba83aad"),
"userID" : "400",
"userName" : "Jon Snow",
"pageName" : "1",
"courseName" : "Maths",
"socketID" : [
"aswKWYyE1euk2GNIAAAD"
],
"online" : true,
"userHistory" : {
"pagesVisited" : [
{
"page" : "1",
"timesVisited" : 1
}
],
"coursesVisited" : [
"Maths"
]
},
"date" : ISODate("2018-08-21T09:38:39.281Z")
}
Here on userHistory.pagesVisited on the page property if I get the value 1 again then I want to increment the timesVisited property like so:
"pagesVisited" : [
{
"page" : "1",
"timesVisited" : 2
}
],
Here's what I have tried with no luck:
let userDetails = {
userID: queryUser.userID,
userName: queryUser.username,
pageName: queryUser.pageName,
courseName: queryUser.courseName,
socketID: [socket.id],
online: true,
userHistory: {
pagesVisited: [
{
"page" : queryUser.pageName,
"timesVisited" : 1
}
],
coursesVisited: [queryUser.courseName]
},
date: new Date()
};
onlineUsersDB.findOne({'userID': userDetails.userID}).then(async (user) => {
if (user) {
let page = {"page": queryUser.pageName, "timesVisited": 1};
let course = queryUser.courseName;
let updatedUser = await onlineUsersDB.findOneAndUpdate(
{'userID': user.userID},
{
$set: {'online': true},
$push: { 'socketID': socket.id },
},
{ $addToSet: { 'userHistory.coursesVisited': course } },
{ returnOriginal: false }
);
let updatedUserPageRef = updatedUser.value.userHistory.pagesVisited;
if (updatedUserPageRef) {
let pageFound = await updatedUserPageRef.findIndex(item => item.page === page);
if (pageFound >= 0) {
let updatedUserPage = await onlineUsersDB.findOneAndUpdate(
{'userID': updatedUser.value.userID},
// Here I want to reference the variable pageFound
{$inc: { 'userHistory.pagesVisited.[pageFound].timesVisited': 1 }},
{ returnOriginal: false }
);
console.log(JSON.stringify(updatedUserPage,null, 2));
}
}
let users = await onlineUsersDB.find({'online': true}).toArray();
io.to(room).emit('online-users', users);
io.to(room).emit('user-back-online', updatedUser);
} else {
if (userDetails.userID !== '100') {
await onlineUsersDB.insert(userDetails);
}
let users = await onlineUsersDB.find({'online': true}).toArray();
io.to(room).emit('online-users', users);
}
}).catch((e) => console.log(e));
In the above code where my comment is I want to reference the variable pageFound in my object dot notation like so:
{$inc: { 'userHistory.pagesVisited.[pageFound].timesVisited': 1 }}
It works when I give it a hardcoded value like:
{$inc: { 'userHistory.pagesVisited.0.timesVisited': 1 }}
After experimenting a little bit I made it to work like this:
I broke out my query string into another variable using template literals.
let pageInc = `userHistory.pagesVisited.${pageFound}.timesVisited`;
And then referenced the variable in my query like so:
{$inc: { [pageInc]: 1 }}
And it works now.

Mongo + check multiple fields existing

I am working mongo with nodejs.
I have array list:
var checkFields = ["field1","field2","field3"];
I try to get the count of records having the array list fields and user field is equal to admin.
Sample data:
[
{
"checkFields": {
"field1": "00124b3a5c31",
"user": "admin"
}
},
{
"checkFields": {
"field2": "00124b3a5c31",
"user": "admin"
}
},
{
"checkFields": {
"field1": "00124b3a5c31",
"user": "regular"
}
}
]
Query:
db.collection_name.find(
{"checkFields.user" : "admin"}
{ "checkFields.field1": { $exists: true} }
)
Expected Result:
Result is to get rows of count of matching the field in array list(checkFields).
Building up an $or array for the list of field existence checks is the right approach, but assuming you're on a current node.js build you can simplify the query creation to:
var checkFieldsLists = checkFields.map(field => ({
['checkFields.' + field]: {$exists: true}
}));
var query = {
$or: checkFieldsLists,
'checkFields.user': 'admin'
}
This removes the superfluous $or for the "user is admin" check which lets you also remove the outer $and, so that the generated query is:
{ '$or':
[ { 'checkFields.field1': { '$exists': true } },
{ 'checkFields.field2': { '$exists': true } },
{ 'checkFields.field3': { '$exists': true } } ],
'checkFields.user': 'admin' }
I tried the following code. Its working but don't know its good solution and perfomance. Please anyone have better answer means please post it.
var checkFields = ["field1", "field2", "field3"];
var checkFieldsLists = [];
for ( i = 0; i < checkFields.length; i++) {
var jsObj = {};
jsObj['checkFields.' + checkFields[i]] = {};
jsObj['checkFields.' + checkFields[i]].$exists = true;
checkFieldsLists.push(jsObj);
}
var query = {
"$and" : [{
"$or" : checkFieldsLists
}, {
"$or" : [{
"checkFields.user" : "admin"
}]
}]
};
console.log(JSON.stringify(query));
//console log will return
/*
{"$and":[{
"$or" : [{
"checkFields.field1" : {
"$exists" : true
}
}, {
"checkFields.field2" : {
"$exists" : true
}
}, {
"checkFields.field3" : {
"$exists" : true
}
}]
}, {
"$or" : [{
"checkFields.user" : "admin"
}]
}]
}
*/
collection.find(query);
Here is the solution using aggregate query.
var Db = require('mongodb').Db, Server = require('mongodb').Server, assert = require('assert');
var db = new Db('localhost', new Server('localhost', 27017));
var checkFields = ["field1", "field2", "field3"];
var checkFieldsLists = [];
for (var i = 0; i < checkFields.length; i++) {
var jsObj = {};
jsObj['checkFields.' + checkFields[i]] = {};
jsObj['checkFields.' + checkFields[i]].$exists = true;
checkFieldsLists.push(jsObj);
}
var query = {
"$and" : [{
"$or" : checkFieldsLists
}, {
"$or" : [{
"checkFields.user" : "admin"
}]
}]
};
var matchQuery = {
"$match" : {
"checkFields.user" : "admin",
"$or" : checkFieldsLists
}
};
var groupQuery = {
$group : {
_id : null,
count : {
$sum : 1
}
}
};
var aggregateCheckFields = function(db, callback) {
console.log("Match query is ====>" + JSON.stringify(matchQuery));
console.log("Group query is ====>" + JSON.stringify(matchQuery));
db.collection('checkfields').aggregate([ matchQuery, groupQuery ]).toArray(
function(err, result) {
assert.equal(err, null);
console.log("Result is ===>" + JSON.stringify(result));
if (result.length > 0) {
console.log("Count is ===>" + result[0].count);
}
callback(result);
});
};
db.open(function(err, db) {
aggregateCheckFields(db, function() {
db.close();
});
});
Output:-
Result is ===>[{"_id":null,"count":3}]
Count is ===>3

$addToSet Based on Object key exists

I have array 'pets': [{'fido': ['abc']} that is a embeded document. When I add a pet to the array, how can I check to see if that pet already exists? For instance, if I added fido again... how can I check if only fido exists and not add it? I was hoping I could use $addToSet but I only want to check part of the set(the pets name).
User.prototype.updatePetArray = function(userId, petName) {
userId = { _id: ObjectId(userId) };
return this.collection.findOneAndUpdate(userId,
{ $addToSet: { pets: { [petName]: [] } } },
{ returnOriginal: false,
maxTimeMS: QUERY_TIME });
Result of adding fido twice:
{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': []}]}}
{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': [u'abc']}, {u'fido': []}]}}
If there is always going to be "variable" content within each member of the "pets" array ( i.e petName as the key ) then $addToSet is not for you. At least not not at the array level where you are looking to apply it.
Instead you basically need an $exists test on the "key" of the document being contained in the array, then either $addToSet to the "contained" array of that matched key with the positional $ operator, or where the "key" was not matched then $push directly to the "pets" array, with the new inner content directly as the sole array member.
So if you can live with not returning the modified document, then "Bulk" operations are for you. In modern drivers with bulkWrite():
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that yields the BulkWriteResult of both calls
return this.collection.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]);
};
If you must return the modified document, then you are going to need to resolve each call and return the one that actually matched something:
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that returns the result that matched and modified
return new Promise(function(resolve,reject) {
var operations = [
this.collection.findOneAndUpdate(filter1,update1,{ "returnOriginal": false}),
this.collection.findOneAndUpdate(filter2,update2,{ "returnOriginal": false})
];
// Promise.all runs both, and discard the null document
Promise.all(operations).then(function(result) {
resolve(result.filter(function(el) { return el.value != null } )[0].value);
},reject);
});
};
In either case this requires "two" update attempts where only "one" will actually succeed and modify the document, since only one of the $exists tests is going to be true.
So as an example of that first case, the "query" and "update" are resolving after interpolation as:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": true }
},
{ "$addToSet": { "pets.$.fido": "ccc" } }
And the second update as:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": false }
},
{ "$push": { "pets": { "fido": ["ccc"] } } }
Given varibles of:
userId = "56d7b759e955e2812c6c8c1b",
petName = "fido",
content = "ccc";
Personally I would not be naming keys like this, but rather change the structure to:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets": [{ "name": "fido", "data": ["abc"] }]
}
That makes the update statements easier, and without the need for variable interpolation into the key names. For example:
{
"_id": ObjectId(userId),
"pets.name": petName
},
{ "$addToSet": { "pets.$.data": content } }
and:
{
"_id": ObjectId(userId),
"pets.name": { "$ne": petName }
},
{ "$push": { "pets": { "name": petName, "data": [content] } } }
Which feels a whole lot cleaner and can actually use an "index" for matching, which of course $exists simply cannot.
There is of course more overhead if using .findOneAndUpdate(), since this is afterall "two" actual calls to the server for which you need to await a response as opposed to the Bulk method which is just "one".
But if you need the returned document ( option is the default in the driver anyway ) then either do that or similarly await the Promise resolve from the .bulkWrite() and then fetch the document via .findOne() after completion. Albeit that doing it via .findOne() after the modification would not truly be "atomic" and could possibly return the document "after" another similar modification was made, and not only in the state of that particular change.
N.B Also assuming that apart from the keys of the subdocuments in "pets" as a "set" that your other intention for the array contained was adding to that "set" as well via the additional content supplied to the function. If you just wanted to overwrite a value, then just apply $set instead of $addToSet and similarly wrap as an array.
But it sounds reasonable that the former was what you were asking.
BTW. Please clean up by horrible setup code in this example for the query and update objects in your actual code :)
As a self contained listing to demonstrate:
var async = require('async'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var coll = db.collection('pettest');
var petName = "fido",
content = "bbb";
var filter1 = { "_id": 1 },
filter2 = { "_id": 1 },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
console.log(JSON.stringify(update1,undefined,2));
console.log(JSON.stringify(update2,undefined,2));
function CleanInsert(callback) {
async.series(
[
// Clean data
function(callback) {
coll.deleteMany({},callback);
},
// Insert sample
function(callback) {
coll.insert({ "_id": 1, "pets": [{ "fido": ["abc"] }] },callback);
}
],
callback
);
}
async.series(
[
CleanInsert,
// Modify Bulk
function(callback) {
coll.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
coll.findOne({ "_id": 1 }).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
callback();
});
},callback);
},
CleanInsert,
// Modify Promise all
function(callback) {
var operations = [
coll.findOneAndUpdate(filter1,update1,{ "returnOriginal": false }),
coll.findOneAndUpdate(filter2,update2,{ "returnOriginal": false })
];
Promise.all(operations).then(function(res) {
//console.log(JSON.stringify(res,undefined,2));
console.log(
JSON.stringify(
res.filter(function(el) { return el.value != null })[0].value
)
);
callback();
},callback);
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
And the output:
{
"$addToSet": {
"pets.$.fido": "bbb"
}
}
{
"$push": {
"pets": {
"fido": [
"bbb"
]
}
}
}
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{
"_id": 1,
"pets": [
{
"fido": [
"abc",
"bbb"
]
}
]
}
{"_id":1,"pets":[{"fido":["abc","bbb"]}]}
Feel free to change to different values to see how different "sets" are applied.
Please try this one with string template, here is one example running under mongo shell
> var name = 'fido';
> var t = `pets.${name}`; \\ string temple, could parse name variable
> db.pets.find()
{ "_id" : ObjectId("56d7b5019ed174b9eae2b9c5"), "pets" : [ { "fido" : [ "abc" ]} ] }
With the following update command, it will not update it if the same pet name exists.
> db.pets.update({[t]: {$exists: false}}, {$addToSet: {pets: {[name]: []}}})
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
If the pets document is
> db.pets.find()
{ "_id" : ObjectId("56d7b7149ed174b9eae2b9c6"), "pets" : [ { "fi" : [ "abc" ] } ] }
After update with
> db.pets.update({[t]: {$exists: false}}, {$addToSet: {pets: {[name]: []}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
The result shows add the pet name if it does Not exist.
> db.pets.find()
{ "_id" : ObjectId("56d7b7149ed174b9eae2b9c6"), "pets" : [ { "fi" : [ "abc" ] }, { "fido" : [ ] } ] }

Resources