Query key condition not supported in DynamoDB - amazon-web-services

I'm attempting to set up my DynamoDB table so I can query the data via a rest API. My table has a partition key (id) which is a randomly generated ID, a sort key (name) and a List of strings (domain).
I have also set up a global secondary index for the "name" field.
I'm attempting to write a lambda that will search for items using both the name and possible search the domain array also. Is this possible? If so how do I set up the table as I'm currently getting this error:
Query key condition not supported
Here is my Lambda query code:
async function query(tableName, searchTerm) {
return new Promise((resolve, reject) => {
const params = {
IndexName: "nameIndex",
ExpressionAttributeNames: {
"#name": "name",
},
ExpressionAttributeValues: {
":topic": { S: searchTerm },
},
KeyConditionExpression: "begins_with(#name, :topic)",
TableName: tableName,
};
ddb.query(params, (err, data) => {
if (err) {
reject(err);
} else {
console.log("Success", data.Items);
resolve(data.Items);
}
});
});
}

A PK must be provided as a value. You can't do expressions like begins with on a PK.
Here are a lot of example programs doing queries with Node:
https://github.com/aws-samples/aws-dynamodb-examples/tree/master/DynamoDB-SDK-Examples/node.js/WorkingWithQueries

Related

Querying with FilterExpression CONTAINS method

I want to query a specific row from a table based on a track's genre and release year.
Task: Query rows with release_year=2018 AND genre="pop"
Tracks Table:
trackId----------------release_year----------------genres----------------count
trackId: "7sT7kZEYd1MrmzLLIRVZas", release_year: 2018, genres: ["pop","rap", "hip-hop"], count: 7
Below code is how I am attempting to make this query as shown in FilterExpression. Currently I'm checking if the Table's attribute: genres (Array), contains :genre, which is a String sent from the client in the request. How would one set up a GSI in a way to make this query?
let queryParams = {
TableName: tableName,
IndexName: req.query.index,
KeyConditionExpression: 'release_year = :release_year and genres = :genre',
ExpressionAttributeValues: { ':release_year': parseInt(req.query.year), ':genre': req.query.genre},
FilterExpression : "contains (genres, :genre)",
ScanIndexForward: false,
Limit: 50
}
dynamodb.query(queryParams, (err, data) => {
if (err) {
res.statusCode = 500;
res.json({error: 'Could not load items: ' + err});
} else {
res.json(data.Items);
}
});
GSI should be created with release_year as the PK. Generes is part of the FilterExpression only.
let queryParams = {
TableName: tableName,
IndexName: req.query.index,
KeyConditionExpression: 'release_year = :release_year',
ExpressionAttributeValues: { ':release_year': parseInt(req.query.year), ':genre': req.query.genre},
FilterExpression : "contains (genres, :genre)",
ScanIndexForward: false,
Limit: 50
}
dynamodb.query(queryParams, (err, data) => {
if (err) {
res.statusCode = 500;
res.json({error: 'Could not load items: ' + err});
} else {
res.json(data.Items);
}
});

createTable first, then putItem into table that just created

With using the API version of '2012-08-10', I'm trying to create a table on DynamoDB. My Lambda code acquiring groupID via API. Once the table is created, as a follow up I'm trying to add the first item to the table as follows:
const AWS = require('aws-sdk');
AWS.config.update({ region: 'eu-central-1' });
const dynamodb = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
exports.handler = (event, context, callback) => {
var itemParams = {
...some itemParams
});
var tableParams = {
...some tableParams
};
dynamodb.createTable(tableParams, function (err, data) {
if (err) { console.log(err, err.stack) }
else { console.log("Table created", data); }
});
dynamodb.putItem(itemParams, function (err, data) {
if (err) callback(null, err);
else callback(null, data);
});
};
Unfortunately I'm receiving a "ResourceNotFoundException" error. Basically putItem doesn't recognize the table just created. Any suggestions on how to create a trigger here? Is there a way to putItem to the table that is just created? Thank you!
Also in case you are wondering the details of params:
var tableParams = {
AttributeDefinitions: [
{
AttributeName: "memberID",
AttributeType: "S"
}
],
KeySchema: [
{
AttributeName: "memberID",
KeyType: "HASH"
}
],
ProvisionedThroughput: {
ReadCapacityUnits: 2,
WriteCapacityUnits: 2
},
TableName: "plexiGr_" + event.groupID
};
var itemParams = {
TableName: "plexiGr_" + event.groupID,
Item: {
"memberID": { S: event.groupAdmin },
"memberName": { S: "Julius" },
"memberAge": { N: "32" }
},
ConditionExpression: "attribute_not_exists(groupID)"
};
The DynamoDB CreateTable operation is asynchronous - it just starts to create a table, but returns before the table's creation is finished. The CreateTable document explains that:
CreateTable is an asynchronous operation. Upon receiving a CreateTable request, DynamoDB immediately returns a response with a TableStatus of CREATING. After the table is created, DynamoDB sets the TableStatus to ACTIVE. You can perform read and write operations only on an ACTIVE table.
In other words, when CreateTable completes, the table is not yet usable: You now need to do a loop of DescribeTable, waiting until the table's status has become ACTIVE. Only then you can use the table.
Because this is a useful loop, most AWS libraries have a function to automate it. I'm familar with the Python one in the boto3 library, but I assume nodejs also has a similar one. Or you can just call DescribeTable yourself.
To reiterate, despite what other responses said, it is not enough to wait for CreateTable to complete. It will complete much earlier than you can really use the table. You must also wait, separately, for the new table's status to change to ACTIVE.
You are not waiting until your table is created. The commands for creating the table and putting an item in it are run in sequence but without waiting for the callbacks.
So either you put the code to write the item into the callback of the create table action or you use promises to wait until the table is created.
dynamodb.createTable(tableParams, function(err, data) {
if (err) {
console.log(err, err.stack);
return;
}
dynamodb.putItem(itemParams, function(putItemErr, putItemData) {
if (putItemErr) callback(null, putItemErr);
else callback(null, putItemData);
});
});
Using promises:
await dynamodb.createTable(tableParams).promise();
await dynamodb.putItem(itemParams).promise();

DynamoDB Scan for a nested attribute values with NodeJS

I have a DynamoDB with nested values inside.
An Entry looks like the following:
Now I would like to scan all entries in the database to find all entries with a specific episodeGuid.
I tried this code (and some variants), but always with 0 results.
var params = {
TableName: "myTableName",
FilterExpression: "#episodeGuid = :myEpisode",
ExpressionAttributeNames: {
'#episodeGuid': 'attributes.playbackInfo.episodeGuid',
},
// ExpressionAttributeValues: { ":myEpisode": { "S": "podlove-2018-12-06t13:07:10+00:00-f8a9b2963f313e5" } }
ExpressionAttributeValues: { ":myEpisode": "podlove-2018-12-06t13:07:10+00:00-f8a9b2963f313e5" }
};
oDynamoDBClient.scan(params, async function (err, data) {
console.log('read return');
if (err) console.log(err, err.stack); // an error occurred
else {
console.log(data);
}
});
Can someone give me a hint how can I find my entries?
The filter is looking for an attribute named attributes.playbackInfo.episodeGuid as opposed to a nested attribute.
To look for a nested attribute, the expression needs to contain to ..
FilterExpression: "#attributes.#playbackInfo.#episodeGuid = :myEpisode",
ExpressionAttributeNames: {
'#attributes': 'attributes',
'#playbackInfo': 'playbackInfo',
'#episodeGuid': 'episodeGuid',
},

Querying many-to-many in DynamoDB with Node.js

I was reading about how to model many-to-many relationships in DynamoDB from this article: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html
Let's say that the requirement is to display a list of all Bills for a given Invoice. But you need to display all attributes for each bill (red circles in the image).
I could query all the Bills for Invoice-92551 as follow:
var params = {
TableName: "some-table",
KeyConditionExpression: "#pk = :pk",
ExpressionAttributeNames: {
"#pk":"pk",
},
ExpressionAttributeValues: {
":pk": "Invoice-92551"
},
ProjectionExpression: "pk, sk, isPaid, Amount, created...etc"
};
docClient.query(params, function(error, billsForGivenInvoice) {...});
Ok, I have now the bills but I need more attributes for each of them. I could do the following:
var params = {
RequestItems: {
"some-table": {
Keys: ["Bill-4224663", "Bill-4224687"],
ProjectionExpression: "...other attributes needed..."
}
}
};
docClient.batchGet(params, function(error, bills) {});
Is it possible to query both results in one go?. Instead of calling first to query() and them a batchGet().

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"
};