How do you update a Set on DynamoDB using JavaScript document client? - amazon-dynamodb

I'm trying to add some items to a DynamoDB set. This worked fine with the original JavaScript SDK, but not with the new DocumentClient, using the createSet() function. Here's my code:
'use strict';
let docClient = new AWS.DynamoDB.DocumentClient({
region: 'us-east-2',
accessKeyId: 'AKIAJWIR35J4YZF4RQVQ',
secretAccessKey: 'xxxx'
});
var params = {
TableName : 'qa_Web_Application',
Key: {'Application_ID': '78f27a00-11f6-49cc-9adb-ae0795cf79d4'},
UpdateExpression : 'ADD #idList :newIds',
ExpressionAttributeNames : {
'#idList' : 'ids'
},
ExpressionAttributeValues : {
':newIds' : docClient.createSet([1,2])
}
};
console.log( params.ExpressionAttributeValues[":newIds"] );
docClient.update(params, function(err,data) {
if( err !== null ) {
console.log( err, err.stack );
} else {
console.log ( data );
}
});
Here's the output:
constructor {values: Array(2), type: "Number"}
Error: Invalid UpdateExpression: Incorrect operand type for operator or function; operator: ADD, operand type: MAP
I've seen this same question here (How do you update a Set on DynamoDB using JavaScript document client?), but that's what I'm basing this code example on, and it fails.

Related

How to dynamically update an attribute in a dynamodb item?

I created an item in dynamodb using Node js, the item has multiple attributes such as brand, category, discount, validity, etc. I am using uuid to generate ids for each item. Now let's say I want to update the validity attribute of the item, in which case I am currently sending the entire json object with the value of validity modified to the new value.
This is definitely not optimal, please help me find an optimal solution.
const params = {
TableName: process.env.PRODUCT_TABLE,
Key: {
id: event.pathParameters.id,
},
ExpressionAttributeNames: {
'#discount': 'discount',
},
ExpressionAttributeValues: {
':brand': data.brand,
':category': data.category,
':discount': data.discount,
':denominations': data.denominations,
":validity": data.validity,
":redemption": data.redemption
},
UpdateExpression: 'SET #discount = :discount, denominations = :denominations, brand = :brand, category = :category, validity = :validity, redemption = :redemption',
ReturnValues: 'ALL_NEW',
};
I want to send just the attribute I want to update with the new value, if I want to change the validity from 6 months to 8 months, I should just send something like:
{
"validity": "8 months"
}
And it should update the validity attribute of the item.
Same should apply to any other attribute of the item.
'use strict';
const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.update = (event, context, callback) => {
const data = JSON.parse(event.body);
let attr = {};
let nameobj = {};
let exp = 'SET #';
let arr = Object.keys(data);
let attrname = {};
arr.map((key) => {attr[`:${key}`]=data[key]});
arr.map((key) => {
exp += `${key} = :${key}, `
});
arr.map((key) => {nameobj[`#${key}`]=data[key]});
attrname = {
[Object.keys(nameobj)[0]] : nameobj[Object.keys(nameobj)[0]]
}
const params = {
TableName: process.env.PRODUCT_TABLE,
Key: {
id: event.pathParameters.id,
},
ExpressionAttributeNames: attrname,
ExpressionAttributeValues: attr,
UpdateExpression: exp,
ReturnValues: 'ALL_NEW',
};
// update the todo in the database
dynamoDb.update(params, (error, result) => {
// handle potential errors
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { 'Content-Type': 'text/plain' },
body: 'Couldn\'t update the card',
});
return;
}
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(result.Attributes),
};
callback(null, response);
});
};
Contrary to others comments, this is very possible, use the UpdateItem action.
Language agnostic API docs
JavaScript specific API docs
If you want to dynamically create the query, try something like this:
const generateUpdateQuery = (fields) => {
let exp = {
UpdateExpression: 'set',
ExpressionAttributeNames: {},
ExpressionAttributeValues: {}
}
Object.entries(fields).forEach(([key, item]) => {
exp.UpdateExpression += ` #${key} = :${key},`;
exp.ExpressionAttributeNames[`#${key}`] = key;
exp.ExpressionAttributeValues[`:${key}`] = item
})
exp.UpdateExpression = exp.UpdateExpression.slice(0, -1);
return exp
}
let data = {
'field' : { 'subfield': 123 },
'other': '456'
}
let expression = generateUpdateQuery(data)
let params = {
// Key, Table, etc..
...expression
}
console.log(params)
Output:
{
UpdateExpression: 'set #field = :field, #other = :other',
ExpressionAttributeNames: {
'#field': 'field',
'#other': 'other'
},
ExpressionAttributeValues: {
':field': {
'subfield': 123
},
':other': '456'
}
}
Using Javascript SDK V3:
Import from the right package:
import { DynamoDBClient PutItemCommandInput, UpdateItemCommandInput, UpdateItemCommand } from '#aws-sdk/client-dynamodb';
Function to dynamically do partial updates to the item:
(the code below is typescript can be easily converted to Javascript, just remove the types!)
function updateItem(id: string, item: any) {
const dbClient = new DynamoDBClient({region: 'your-region-here });
let exp = 'set ';
let attNames: any = { };
let attVal: any = { };
for(const attribute in item) {
const valKey = `:${attribute}`;
attNames[`#${attribute}`] = attribute;
exp += `#${attribute} = ${valKey}, `;
const val = item[attribute];
attVal[valKey] = { [getDynamoType(val)]: val };
}
exp = exp.substring(0, exp.length - 2);
const params: UpdateItemCommandInput = {
TableName: 'your-table-name-here',
Key: { id: { S: id } },
UpdateExpression: exp,
ExpressionAttributeValues: attVal,
ExpressionAttributeNames: attNames,
ReturnValues: 'ALL_NEW',
};
try {
console.debug('writing to db: ', params);
const command = new UpdateItemCommand(params);
const res = await dbClient.send(command);
console.debug('db res: ', res);
return true;
} catch (err) {
console.error('error writing to dynamoDB: ', err);
return false;
}
}
And to use it (we can do partial updates as well):
updateItem('some-unique-id', { name: 'some-attributes' });
What i did is create a helper class.
Here is a simple function : Add all the attribute and values that goes into, if the value is null or undefined it won't be in the expression.
I recommande to create a helper class with typescript and add more functions and other stuff like generator of expressionAttributeValues , expressionAttributeNames ... , Hope this help.
function updateExpression(attributes, values) {
const expression = attributes.reduce((res, attribute, index) => {
if (values[index]) {
res += ` #${attribute}=:${attribute},`;
}
return res;
}, "SET ");
return expression.slice(0, expression.length - 1)
}
console.log(
updateExpression(["id", "age", "power"], ["e8a8da9a-fab0-55ba-bae3-6392e1ebf624", 28, undefined])
);
You can use code and generate the params object based on the object you provide. It's just a JavaScript object, you walk through the items so that the update expression only contains the fields you have provided.
This is not really a DynamoDB question in that this is more a general JS coding question.
You can use UpdateItem; to familiarize yourself with DynamoDb queries I would suggest you DynamoDb NoSQL workbench:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.settingup.html
It can generate snippets for you based on your queries.
DynamoDb NoSQL workbench screenshot query

Wordpress Media Uploader query-attachment parameter

I'm trying to use the Media Uploader in a plugin.
I need to pass additional parameters to the query-attachment request.
I've tried to add a parameter to the Library like this:
file_frame = wp.media.frames.file_frame = wp.media({
title: "User's photo",
button: {
text: "Upload image",
},
library: {
type: ['image/jpeg','image/png'],
--> additional_param: 'a value'
},
multiple: false // Set to true to allow multiple files to be selected
});
But this additional parameter seems to screw up the new attachment upload process - the new uploaded attachment isn't display in the grid at the download end.
Is there a valid way to add a custom parameter to the query-attachment request?
Thank you for your answers
I dug a little bit un process and the best way to pass additional parameter I found is to add it during the query call.
To do it, several classes add to be extended.
Extending Query (wp.media.model.Query)
The sync Query's method is responsible to place the actual admin-ajax call to query the attachments. The sync method has 3 arguments, the last one - options - contains the parameters passed to the http request. The extended sync method adds the additional parameter before calling the parent sync request.
// Extending the wp.media.query to add a parameter
var Query1;
Query1=wp.media.model.Query1 = oldQuery.extend({
sync: function( method, model, options ) {
options = options || {};
options = _.extend(options, {'data':{'CadTest': '1'}});
return oldQuery.prototype.sync.apply(this, arguments);
}...
Now the must find a way to have this new class instancied. This done is the requery method of the Attachements class which call the 'static' ou 'class method' get of Query. In order to create a query based on Query1, the get method has had to be duplicated creating an Query1 object
get1: (function(){
/**
* #static
* #type Array
*/
var queries = [];
/**
* #returns {Query}
*/
return function( props, options ) {
var args = {},
orderby = Query1.orderby,
defaults = Query1.defaultProps,
query,
cache = !! props.cache || _.isUndefined( props.cache );
// Remove the `query` property. This isn't linked to a query,
// this *is* the query.
delete props.query;
delete props.cache;
// Fill default args.
_.defaults( props, defaults );
// Normalize the order.
props.order = props.order.toUpperCase();
if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
props.order = defaults.order.toUpperCase();
}
// Ensure we have a valid orderby value.
if ( ! _.contains( orderby.allowed, props.orderby ) ) {
props.orderby = defaults.orderby;
}
_.each( [ 'include', 'exclude' ], function( prop ) {
if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) {
props[ prop ] = [ props[ prop ] ];
}
} );
// Generate the query `args` object.
// Correct any differing property names.
_.each( props, function( value, prop ) {
if ( _.isNull( value ) ) {
return;
}
args[ Query1.propmap[ prop ] || prop ] = value;
});
// Fill any other default query args.
_.defaults( args, Query1.defaultArgs );
// `props.orderby` does not always map directly to `args.orderby`.
// Substitute exceptions specified in orderby.keymap.
args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
// Search the query cache for a matching query.
if ( cache ) {
query = _.find( queries, function( query ) {
return _.isEqual( query.args, args );
});
} else {
queries = [];
}
// Otherwise, create a new query and add it to the cache.
if ( ! query ) {
query = new wp.media.model.Query1( [], _.extend( options || {}, {
props: props,
args: args
} ) );
queries.push( query );
}
return query;
};
}()),
Note that get1 is a class method to create in the 2nd parameter of extend()
Extending Attachments - when the _requery method is called - in our case - the Attachments collection is replaced by a Query collection (which makes the actual admin-ajax call to query the attachments). In the extended _requery method, a Query1 query is created instead of a regular Query.
wp.media.model.Attachments1 = oldAttachments.extend({
_requery: function( refresh ) {
var props;
if ( this.props.get('query') ) {
props = this.props.toJSON();
props.cache = ( true !== refresh );
this.mirror( wp.media.model.Query1.get1( props ) );
}
},
});
Finally an object of type Attachment1 must be created during the library creation
wp.media.query1 = function( props ) {
return new wp.media.model.Attachments1( null, {
props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
});
};
and
// Extending the current media library frame to add a new tab
wp.media.view.MediaFrame.Post1 = oldMediaFrame.extend({
initialize: function(){
oldMediaFrame.prototype.initialize.apply(this, arguments);
var options = this.options;
this.states.add([
new Library({
id: 'inserts',
title: vja_params.title,
priority: 20,
toolbar: 'main-insert',
filterable: 'all',
multiple: false,
editable: false,
library: wp.media.query1( _.defaults({
type: 'image'
}, options.library ) ),
// Show the attachment display settings.
displaySettings: true,
// Update user settings when users adjust the
// attachment display settings.
displayUserSettings: true
}),
]);
},
The additional parameter is added in the _POST array and can be used in PHP code - ie in the query_attachments_args filter
public function query_attachments_args($args){
if (isset($_POST[CadTest])) {
do whatever you want...
}
return $args;
}
I solved it with:
var post_type = 'my-cpt';
var real_ajax_url = wp.ajax.settings.url;
// For uploading.
window.wp.Uploader.defaults.multipart_params.post_type = post_type;
// For querying.
wp.ajax.settings.url = real_ajax_url + '?post_type=' + post_type;
Then listen on the wp_ajax_query-attachments action for $_GET['post_type'] and listen on the admin_init hook for 'upload-attachment' !== $_POST['action'] and $_POST['post_type'] set to 'my-cpt'.
And unset/restore the values when finished with the dialog.

DynamoDB update error Invalid UpdateExpression: An expression attribute value used in expression is not defined

I am trying to perform an update on DynamoDB table.
The code is (Node.js):
let params = {
TableName: Organizations.Table,
Key: {
'ID': event.ID
},
UpdateExpression: 'SET #OrgName = :org, #Description = :desc',
ExpressionAttributeNames: {
'#OrgName': 'OrgName',
'#Description': 'Description'
},
ExpressionAtributeValues: {
':org': event.OrgName,
':desc': event.Description
},
ReturnValues: 'UPDATED_NEW'
};
this.docClient.update(params, (err, data) => {
if (err) {
return cb(err);
}
return cb(null, data);
});
The event object has all the properties needed.
After executing I get an error:
Invalid UpdateExpression: An expression attribute value used in
expression is not defined; attribute value: :desc
I just followed the examples from DynamoDB docs.
When I change the order of set values, e.g. to SET #Description = :desc, #OrgName = :org the error I get will be about attribute value :org. I also tried to specify expression attribute values explicitly, didn't help.
I can't get what is wrong.
Can someone help me?
There is a spelling mistake in ExpressionAtributeValues (i.e. 't' missing) which is causing the problem.
Please try the below. It should work.
UpdateExpression : "SET #OrgName = :org, #Description = :desc",
ExpressionAttributeNames: {
'#OrgName' : 'OrgName',
'#Description' : 'Description'
},
ExpressionAttributeValues: {':org' : 'new org value',
':desc' : 'new desc value'
},
ReturnValues: 'UPDATED_NEW'

How do I write an item to a DynamoDb with the AWS DynamoDB DocumentClient?

I'm having trouble with the AWS DynamoDb JS SDK v2.4.9. I want to use the DocumentClient class as opposed to the lower level DynamoDb class, but can't get it working.
This works:
function testPutItem( callback ) {
var tableName = 'todos';
var params = {
TableName: tableName,
Item: {
user_id: { S : userId },
id: { N : msFromEpoch }, // ms from epoch
title: { S : makeRandomStringWithLength(16) },
completed: { BOOL: false }
}
};
var dynamodb = new AWS.DynamoDB();
dynamodb.putItem(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
console.log(data); // successful response
if (callback) callback(data);
}
});
}
This does not work and gives the error InvalidParameterType: Expected params.Item[attribute] to be a structure for each attribute--as if DocumentClient is expecting the same input as DynamoDb:
function testPutItem( callback ) {
var tableName = 'todos';
var params = {
TableName: tableName,
Item: {
user_id: userId,
id: msFromEpoch,
title: makeRandomStringWithLength(16),
completed: false
}
};
console.log(params);
var docClient = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
docClient.put(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
console.log(data); // successful response
if (callback) callback(data);
}
});
}
Does anyone have any idea what I am doing wrong?
I used to have the same issue,
please try with a simple object first, cause it's due to some special characters in your attributes, see my example :
this generates the error
InvalidParameterType: Expected params.Item[attribute] to be a structure
var Item = {
domain: "knewtone.com",
categorie: "<some HTML Object stuff>",
title: "<some HTML stuff>",
html: "<some HTML stuff>""
};
but when i replace the HTML stuff with a formated Html, simple characters , it works
var Item = {
domain: "knewtone.com",
categorie: $(categorie).html(),
title: $(title).html(),
html: $(html).html()
};

Meteor collection2 - all validation messages

I am looking for a way to retrieve all validation errors. (I'm using Collection2 and SimpleSchema)
Consider this code:
Foo.insert({
title: '',
description: ''
}, function(error, result) {
console.error(error);
});
output:
{
message: 'Title may not be empty.',
invalidKeys: [
0: {
name: 'title',
type: 'required',
value: ''
},
1: {
name: 'description',
type: 'required',
value: ''
}
]
}
I would like to have all the error messages that are related to validation.
Unfortunately I couldn't find any solution for this.
SOLUTION:
I've found a satisfiable solution
Foo.simpleSchema().namedContext().keyErrorMessage('title');
I ran into the same problem and my solution was to insert said errors into a client mongo error collection which would then display the errors to the user. The following is what I came up with:
Schema
Schema.newUser = new SimpleSchema({....});
Client Side Validation
function tokenRegistration (newUser) {
var valContext = Schema.newUser.namedContext('tokenRegForm');
if (!valContext.validate(newUser)) {
var keys = valContext.invalidKeys();
_.each(keys, function (value) {
var error = value.name,
message = valContext.keyErrorMessage(error);
return ErrorMessage.insert({errormessage: message})
});
}
}

Resources