AWS.DynamoDB: KeyConditionExpression is missing - amazon-web-services

I wanted to make a search in DynamoDB equivalent to the following SQL query:
SELECT *
from db
where attr="abc"
Therefore, I wrote the following code:
function searchFile(carModel, busMapping, from_date, until_date, location, attackTraffic, isTagged) {
AWS.config = new AWS.Config({accessKeyId: '***', secretAccessKey: '***', region: 'us-west-2'});
var dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var params = {
"TableName" : 'db',
QueryFilter: {
"attr": {
ComparisonOperator: 'EQ',
AttributeValueList: [
{S: 'abc'}
]
}
}
}
dynamodb.query(params, function(err,data) {
if (err) {
alert(err);
}
})
}
I got the error message:
"Expected params.KeyConditionExpression to be a string"
I looked at the AWS documentation (http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#query-property) and saw that params.KeyConditionExpression must be defined. However, I still didn't understand what is expected to be inside this key. Can you propose something that can make my query work?

To perform a query against DynamoDB you must provide it with the primary/hash/partition key for the table.
KeyConditionExpression: The condition must perform an equality test on a single partition key value. The partition key equality test is required, and must be specified in the following format: partitionKeyName = :partitionkeyval
What you probably want is to use the scan method and provide it with a ScanFilter instead of a QueryFilter:
function searchFile(carModel, busMapping, from_date, until_date, location, attackTraffic, isTagged) {
AWS.config = new AWS.Config({accessKeyId: '***', secretAccessKey: '***', region: 'us-west-2'});
var dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var params = {
TableName : 'db',
ScanFilter: {
attr: {
ComparisonOperator: 'EQ',
AttributeValueList: [
{S: 'abc'}
]
}
}
}
dynamodb.scan(params, function(err,data) {
if (err) {
alert(err);
}
})
}

Related

How can I update a nested field in DynamoDB?

I defined a DynamoDB table and it has a nested field site. I'd like to update the field site.enable in below code. But when I run the update command I got this error:
ValidationException: The document path provided in the update expression is invalid for update`
What should I do in order to fix the issue?
{
TableName: 'MyTable',
Key: {
id: '4b7020d2-2d19-4aeb-7f27e49d5bec',
type: '80422149-c97d-4a1a-7bf20ef57056',
},
UpdateExpression: 'set #site.#siteenable= :siteenable',
ExpressionAttributeValues: {
':siteenable': true,
},
ExpressionAttributeNames: {
'#siteenable': 'enable',
'#site': 'site',
}
}
You don't mention a programming language, so I'm going to assume what I'm used to: Python.
In Python there are two ways you can do this:
The lower level client API, which requires you to format the data the way DynamoDB would
def enable_site_with_client():
ddb = boto3.client("dynamodb")
ddb.update_item(
TableName=TABLE_NAME,
Key={
"PK": {"S": "SITE_ENTRY"}
},
UpdateExpression="SET #site.#enabled = :update_value",
ExpressionAttributeNames={
"#site": "site",
"#enabled": "enabled"
},
ExpressionAttributeValues={
":update_value": {"BOOL": True}
}
)
The higher level resource API, which allows you to use the language native data structures
def enable_site_with_resource():
ddb = boto3.resource("dynamodb")
ddb.Table(TABLE_NAME).update_item(
Key={
"PK": "SITE_ENTRY"
},
UpdateExpression="SET #site.#enabled = :update_value",
ExpressionAttributeNames={
"#site": "site",
"#enabled": "enabled"
},
ExpressionAttributeValues={
":update_value": True
}
)
I have tested both of these and they work.
Given code works fine if the map site exists already, seeing the error message, it looks like the path site doesn't exist.
We could create empty map during the create of the document, then update it easily OR
We could create the map during the update "set #site = :siteValue"
Here is slightly modified query which creates the map.
const dynamodb = new AWS.DynamoDB();
let docClient = new AWS.DynamoDB.DocumentClient();
docClient.update(
{
TableName: "MyTable",
Key: {
id: "4b7020d2-2d19-4aeb-7f27e49d5bec",
type: "80422149-c97d-4a1a-7bf20ef57056",
},
UpdateExpression: "set #site = :siteValue",
ExpressionAttributeValues: {
":siteValue": { enable: true },
},
ExpressionAttributeNames: {
"#site": "site",
},
},
function (error, result) {
console.log("error", error, "result", result);
}
);
Here is an example for Java SDK V2 that will update root->nested->someValue
Map<String, AttributeValue> attributeValues = new HashMap<>();
attributeValues.put(":myValue", AttributeValue.builder().s(jsonString).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder()
.tableName("my_table_name")
.key(keyToUpdate)
.updateExpression("SET nested.someValue= :myValue")
.expressionAttributeValues(attributeValues)
.build();
client.updateItem(updateRequest);

DynamoDB returning null nextToken when there are still results in database

I have a GraphQL API (AppSync) backed by a DynamoDB table keyed a specific id with timestamp as the range key. I want to retrieve all possible history for that id so I wrote a query in my GraphQL schema that would allow me to do so. Here's the request vtl:
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression": "id = :id",
"expressionValues": {
":id": {
"S": "$context.args.id"
}
}
},
"nextToken": $util.toJson($util.defaultIfNullOrEmpty($context.args.nextToken, null))
}
There could be thousands of items in the ddb table for an id so I wrote a Lambda function to query for all of them and return the result in a list as such (I know the code can be simplified):
exports.handler = async function (event, context, callback) {
const graphqlClient = new appsync.AWSAppSyncClient({
url: process.env.APPSYNC_ENDPOINT,
region: process.env.AWS_REGION,
auth: {
type: 'AWS_IAM',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
sessionToken: process.env.AWS_SESSION_TOKEN
}
},
disableOffline: true
});
const query = gql`query GetAllItemsById(
$id: String!
$nextToken: String
) {
getAllItemsById(
id: $id
nextToken: $nextToken
) {
exampleField {
subField1
subField2
subField3
}
nextToken
}
}
`;
const initialResult = await graphqlClient.query({
query,
variables: {
id: event.id
}
});
var finalResult = initialResult.data.getAllItemsById.exampleField;
var nextToken = initialResult.data.getAllItemsById.nextToken;
while (nextToken !== null) {
const result = await graphqlClient.query({
query,
variables: {
id: event.id,
nextToken: nextToken
}
});
finalResult = finalResult.concat(result.data.getAllItemsById.exampleField);
nextToken = result.data.getAllItemsById.nextToken;
}
console.log("Total Results: " + finalResult.length);
return callback(null, finalResult);
};
For some reason, not all items are being returned. nextToken is null before all results are returned. I know DDB has a 1MB limit for query which is why I'm paginating using nextToken but why is it still not returning all the items in the table? Also, if there's a better way to implement this, I'm open to it.

DynamoDB - The provided key element does not match the schema

I have a dynamodb table with attributes: userId, propertyInfo, and propertyId. userId is primary index. When I use the following lambda code to update(PUT) the item in the table, I get "The provided key element does not match the schema".
const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.update = (event, context, callback) => {
const timestamp = new Date().getTime();
const data = JSON.parse(event.body);
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
propertyId: event.pathParameters.id,
},
ExpressionAttributeNames: {
'#new_propertyInfo': 'propertyInfo',
},
ExpressionAttributeValues: {
':propertyInfo': data.propertyInfo,
},
UpdateExpression: 'SET #new_propertyInfo = :propertyInfo',
ReturnValues: 'ALL_NEW',
};
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 fetch the item.',
});
return;
}
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(result.Attributes),
};
callback(null, response);
});
};
Body of my update request is:
{
"propertyInfo":
{
"houseNumber": 2000,
"street": "easy st"
}
}
The event.pathParameters.id is obtained from /property/{id}. I need this id to search DB's propertyId. The userId is needed for authorization purpose. Search and update I need to search by propertyId. Could someone help to explain to me what I need to do to set this up correctly please?

Searching DynamoDB for non primary keys and integrating into Alexa Skills

I am trying to search a non primary key using AWS Lambda and integrating it into the Alexa Skills Kit. I am very new to using DynamoDB and Alexa Skills Kit and I'm struggling to find any solutions to this online. The basic premise for what I am trying to do is querying the table yesno with two columns, id and message. Only looking through the message column to find a match with the text i specify in params.
Here is the Lambda code I am working with:
const AWSregion = 'eu-west-1';
const Alexa = require('alexa-sdk');
const AWS = require('aws-sdk');
//params for searching table
const params = {
TableName: 'yesno',
Key:{ "message": 'Ben Davies' }
};
AWS.config.update({
region: AWSregion
});
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
// alexa.appId = 'amzn1.echo-sdk-ams.app.1234';
// alexa.dynamoDBTableName = 'YourTableName'; // creates new table for session.attributes
alexa.registerHandlers(handlers);
alexa.execute();
};
const handlers = {
'LaunchRequest': function () {
this.response.speak('welcome to magic answers. ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'MyIntent': function () {
var MyQuestion = this.event.request.intent.slots.MyQuestion.value;
console.log('MyQuestion : ' + MyQuestion);
readDynamoItem(params, myResult=>{
var say = MyQuestion;
say = myResult;
say = 'you asked, ' + MyQuestion + '. I found a reckord for: ' + myResult;
this.response.speak(say).listen('try again');
this.emit(':responseReady');
});
},
'AMAZON.HelpIntent': function () {
this.response.speak('ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'AMAZON.CancelIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
}
};
// END of Intent Handlers {} ========================================================================================
// Helper Function =================================================================================================
function readDynamoItem(params, callback) {
var AWS = require('aws-sdk');
AWS.config.update({region: AWSregion});
var dynamodb = new AWS.DynamoDB();
console.log('reading item from DynamoDB table');
dynamodb.query(params, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else{
console.log(data); // successful response
callback(data.Item.message);
}
});
}
I know I am probably doing this completely wrong but there isn't much online for integrating DynamoDB with an Alexa Skill and the only thing i was able to find was searching by ID. This doesn't work for what i want to do without pulling all the items from the table into a map or a list, and seeing as I want to create a big database it seems quite inefficient.
On the Alexa side of things I am receiving the following service request when testing the code:
{
"session": {
"new": true,
"sessionId": "SessionId.f9558462-6db8-4bf5-84aa-22ee0920ae95",
"application": {
"applicationId": "amzn1.ask.skill.9f280bf7-d506-4d58-95e8-b9e93a66a420"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.AF5IJBMLKNE32GEFQ5VFGVK2P4YQOLVUSA5YPY7RNEMDPKSVCBRCPWC3OBHXEXAHROBTT7FGIYA7HJW2PMEGXWHF6SQHRX3VA372OHPZZJ33K7S4K7D6V3PXYB6I72YFIQBHMJ4QGJW3NS3E2ZFY5YFSBOEFW6V2E75YAZMRQCU7MNYPJUMJSUISSUA2WF2RA3CIIDCSEY35TWI"
}
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.7310073b-981a-41f8-9fa5-03d1b28c5aba",
"intent": {
"name": "MyIntent",
"slots": {
"MyQuestion": {
"name": "MyQuestion",
"value": "erere"
}
}
},
"locale": "en-US",
"timestamp": "2018-01-25T14:18:40Z"
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "amzn1.ask.skill.9f280bf7-d506-4d58-95e8-b9e93a66a420"
},
"user": {
"userId": "amzn1.ask.account.AF5IJBMLKNE32GEFQ5VFGVK2P4YQOLVUSA5YPY7RNEMDPKSVCBRCPWC3OBHXEXAHROBTT7FGIYA7HJW2PMEGXWHF6SQHRX3VA372OHPZZJ33K7S4K7D6V3PXYB6I72YFIQBHMJ4QGJW3NS3E2ZFY5YFSBOEFW6V2E75YAZMRQCU7MNYPJUMJSUISSUA2WF2RA3CIIDCSEY35TWI"
},
"device": {
"supportedInterfaces": {}
}
}
},
"version": "1.0"
}
And I am receiving a service response error simply saying 'The response is invalid'
Any help with this would be greatly appreciated
I would like to help you in dynamo db part.
In order to access non primary key columns in dynamodb you should perform scan operation.
For your table (yesno), id is a primary key and message is an additional column.
Snippet to access non primary key column [Message]
var dynamodb = new AWS.DynamoDB();
var params = {
TableName: 'yesno',
FilterExpression: 'message = :value',
ExpressionAttributeValues: {
':value': {"S": "Ben Davies"}
}
};
dynamodb.scan(params, function(err, data) {
if (err) // an error occurred
else console.log(data); // successful response
});
Snippet to access primary key column [Id]
var docClient = new AWS.DynamoDB.DocumentClient();
//Get item by key
var params = {
TableName: 'sis_org_template',
Key: { "id": "1"}
};
docClient.get(params, function(err, data) {
if (err) // an error occurred
else console.log(data); // successful response
});

AWS DynamoDB Attempting to ADD to a Set - Incorrect Operand

I am creating an API using Nodejs and DynamoDB as a back end. I am attempting to update an item to add to a set of "friends". When I update the user, I get the error, "Invalid UpdateExpression: Incorrect operand type for operator or function; operator: ADD, operand type: MAP". My understanding is that when adding to a set that does not exist, the set will be created. If it already exists, the new value should be added to the set. I do not understand why the set I attempt to ADD is being read as a map.
How users are created:
var params = {
TableName: "users",
Item:{
"id": Number(id),
"name": name,
"password": password
}
};
documentClient.put(params, function(err, data) {
if(err)
res.json(500, err);
else
res.json(200, data);
});
How friends are added:
var params = {
TableName: "users",
Key: {
"id": id
},
UpdateExpression: "ADD friends :friendId",
ExpressionAttributeValues: {
":friendId": { "NS": [friendId] }
},
ReturnValues: "UPDATED_NEW"
};
documentClient.update(params, function(err, data) {
if(err)
res.json(500, err);
else
res.json(200, data);
});
This question has an answer here
https://stackoverflow.com/a/38960676/4975772
Here's the relevant code formatted to fit your question
let AWS = require('aws-sdk');
let docClient = new AWS.DynamoDB.DocumentClient();
...
var params = {
TableName : 'users',
Key: {'id': id},
UpdateExpression : 'ADD #friends :friendId',
ExpressionAttributeNames : {
'#friends' : 'friends'
},
ExpressionAttributeValues : {
':friendId' : docClient.createSet([friendId])
},
ReturnValues: 'UPDATED_NEW'
};
docClient.update(params, callback);
If the set doesn't exist, then that code will create it for you. You can also run that code with a different set to update the set's elements. Super convenient.
Here is the working code. You don't need ADD here. Just use "set friends = :friendId" as friends attribute is not already present in the table (i.e. before the update you have only id, name and password in the table). The friend attribute is being added newly as part of the update.
var docClient = new AWS.DynamoDB.DocumentClient();
var table = "users";
var userid = 1;
var friendId = [123];
var params = {
TableName : table,
Key: {
"id" : userid
},
"UpdateExpression": "set friends = :friendId",
"ExpressionAttributeValues": {
":friendId": {"NS": friendId}
},
"ReturnValues" : "UPDATED_NEW"
};