DynamoDb delete non-existent item does not fail, why? - amazon-web-services

I am deleting a non-existing record from the dynamodb table using dynamoDbMapper.delete(object) which uses default DynamoDBDeleteExpression
I was expecting some sort of exception to arise since the record is absent from the DB but it does nothing. It does not even have a return type which could tell if the delete was successful or failure. Is there a way to add a delete expression or something which will make my delete throw an exception if the item is absent from the db?

It is by design:
Unless you specify conditions, the DeleteItem is an idempotent operation; running it multiple times on the same item or attribute does not result in an error response.
FROM: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html

It's possible to utilize ReturnValues to determine if delete did anything or not. If ReturnValues.Attributes is empty, it means delete didn't find a record to delete, and you can throw an error in this case. Example in JavaScript:
async function deleteWithThrowIfNotExists() {
const dynamo = new AWS.DynamoDB.DocumentClient();
const parameters = {
Key: {
user: 'john'
},
ReturnValues: 'ALL_OLD',
TableName: 'users'
};
const response = await dynamo.delete(parameters).promise();
if (!response.Attributes) {
throw new Error('Cannot delete item that does not exist')
}
}

You can first load the item, and if item doesn't exists you can throw exception otherwise delete it.

Related

Apollo duplicates first result to every node in array of edges

I am working on a react app with react-apollo
calling data through graphql when I check in browser network tab response it shows all elements of the array different
but what I get or console.log() in my app then all elements of array same as the first element.
I don't know how to fix please help
The reason this happens is because the items in your array get "normalized" to the same values in the Apollo cache. AKA, they look the same to Apollo. This usually happens because they share the same Symbol(id).
If you print out your Apollo response object, you'll notice that each of the objects have Symbol(id) which is used by Apollo cache. Your array items probably have the same Symbol(id) which causes them to repeat. Why does this happen?
By default, Apollo cache runs this function for normalization.
export function defaultDataIdFromObject(result: any): string | null {
if (result.__typename) {
if (result.id !== undefined) {
return `${result.__typename}:${result.id}`;
}
if (result._id !== undefined) {
return `${result.__typename}:${result._id}`;
}
}
return null;
}
Your array item properties cause multiple items to return the same data id. In my case, multiple items had _id = null which caused all of these items to be repeated. When this function returns null the docs say
InMemoryCache will fall back to the path to the object in the query,
such as ROOT_QUERY.allPeople.0 for the first record returned on the
allPeople root query.
This is the behavior we actually want when our array items don't work well with defaultDataIdFromObject.
Therefore the solution is to manually configure these unique identifiers with the dataIdFromObject option passed to the InMemoryCache constructor within your ApolloClient. The following worked for me as all my objects use _id and had __typename.
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
dataIdFromObject: o => (o._id ? `${o.__typename}:${o._id}`: null),
})
});
Put this in your App.js
cache: new InMemoryCache({
dataIdFromObject: o => o.id ? `${o.__typename}-${o.id}` : `${o.__typename}-${o.cursor}`,
})
I believe the approach in other two answers should be avoided in favor of following approach:
Actually it is quite simple. To understand how it works simply log obj as follows:
dataIdFromObject: (obj) => {
let id = defaultDataIdFromObject(obj);
console.log('defaultDataIdFromObject OBJ ID', obj, id);
}
You will see that id will be null in your logs if you have this problem.
Pay attention to logged 'obj'. It will be printed for every object returned.
These objects are the ones from which Apollo tries to get unique id ( you have to tell to Apollo which field in your object is unique for each object in your array of 'items' returned from GraphQL - the same way you pass unique value for 'key' in React when you use 'map' or other iterations when rendering DOM elements).
From Apollo dox:
By default, InMemoryCache will attempt to use the commonly found
primary keys of id and _id for the unique identifier if they exist
along with __typename on an object.
So look at logged 'obj' used by 'defaultDataIdFromObject ' - if you don't see 'id' or '_id' then you should provide the field in your object that is unique for each object.
I changed example from Apollo dox to cover three cases when you may have provided incorrect identifiers - it is for cases when you have more than one GraphQL types:
dataIdFromObject: (obj) => {
let id = defaultDataIdFromObject(obj);
console.log('defaultDataIdFromObject OBJ ID', obj, id);
if (!id) {
const { __typename: typename } = obj;
switch (typename) {
case 'Blog': {
// if you are using other than 'id' and '_id' - 'blogId' in this case
const undef = `${typename}:${obj.id}`;
const defined = `${typename}:${obj.blogId}`;
console.log('in Blogs -', undef, defined);
return `${typename}:${obj.blogId}`; // return 'blogId' as it is a unique
//identifier. Using any other identifier will lead to above defined problem.
}
case 'Post': {
// if you are using hash key and sort key then hash key is not unique.
// If you do query in DB it will always be the same.
// If you do scan in DB quite often it will be the same value.
// So use both hash key and sort key instead to avoid the problem.
// Using both ensures ID used by Apollo is always unique.
// If for post you are using hashKey of blogID and sortKey of postId
const notUniq = `${typename}:${obj.blogId}`;
const notUniq2 = `${typename}:${obj.postId}`;
const uniq = `${typename}:${obj.blogId}${obj.postId}`;
console.log('in Post -', notUniq, notUniq2, uniq);
return `${typename}:${obj.blogId}${obj.postId}`;
}
case 'Comment': {
// lets assume 'comment's identifier is 'id'
// but you dont use it in your app and do not fetch from GraphQl, that is
// you omitted 'id' in your GraphQL query definition.
const undefnd = `${typename}:${obj.id}`;
console.log('in Comment -', undefnd);
// log result - null
// to fix it simply add 'id' in your GraphQL definition.
return `${typename}:${obj.id}`;
}
default: {
console.log('one falling to default-not good-define this in separate Case', ${typename});
return id;
}
I hope now you see that the approach in other two answers are risky.
YOU ALWAYS HAVE UNIQUE IDENTIFIER. SIMPLY HELP APOLLO BY LETTING KNOW WHICH FIELD IN OBJECT IT IS. If it is not fetched by adding in query definition add it.
An alternative option to the accepted is to instead of dataIdFromObject, which appears to be for everything in the query, I was able to provide a keyFields function per type that required it.
const client = new ApolloClient({
cache: new InMemoryCache({
typePolicies: {
ItemType: {
keyFields: (obj) =>
obj.id + "-" + obj.language.id,
},
},
}),
});
In the above example ItemType can be whichever Type is specified in your schema. I happened to be joining a non-unique ID with a language to make a unique key but you can do it however you wish.

Dynamodb batch put if item does not exist

DynamoDB does not support batch update, it supports batch put only.
But is it possible to batchPut only of item with key does not exist?
In the batchWriteItem, there is the following note:
For example, you cannot specify conditions on individual put and delete requests, and BatchWriteItem does not return deleted items in the response.
Instead, I would recommend using putItem with a conditional expression. Towards the bottom of the putItem documentation there is the following note:
[...] To prevent a new item from replacing an existing item, use a
conditional expression that contains the attribute_not_exists function
with the name of the attribute being used as the partition key for the
table [...]
So make sure to add the following to ConditionExpression (using NodeJS syntax here)
const params = {
Item: {
userId: {
S: "Beyonce",
}
},
ConditionExpression: "attribute_not_exists(userId)"
};

Ember: Create a new record if none are found

I have a route that pulls data from a REST API. The first time a user enters, there won't be any data saved, so I need to create the record with some default values. I figure I need to do this here in the route so the user can see my default values (which in my actual app aren't just null), rather than creating during the save action.
If, however, there is data in the database, I need to return that record. Right now I'm stuck and must not be doing something right (probably has to do with promises, but I'm not sure).
The error I get is:
Error while processing route: project.dates Assertion Failed:
Expected an object as `data` in a call to `push` for star#model:project-date: ,
but was undefined
Here's my route code:
var project = this.modelFor('project');
var projectDates = this.store.find('project-date', project.id);
if (projectDates) {
return projectDates;
} else {
return this.store.createRecord('project-date', {
project: project.id,
start: null,
checkpointA: null,
finish: null
};
}
The puzzling thing is that if I just negate my if statement to get the other return value (like so: if (!projectDates)) then I still get the error above, but it also loads up the model! I have confirmed that the API (or my mock API) is returning data in the right format, as an object.
Okay, as I suspected, I could use promises stuff to solve this, just had to keep googling until I found something that jogged the right idea.
Here is how to set the model to the found record, or else create a new record:
model: function() {
var project = this.modelFor('project');
var projectDates = this.store.find('project-date', project.id).catch(function() {
return this.store.createRecord('project-date', {
project: project.id,
start: null,
checkpointA: null,
finish: null
};
});
return projectDates;
}

Increment Number Property in AWS DynamoDB

How do I increment a number in AWS Dynamodb?
The guide says when saving an item to simply resave it:
http://docs.aws.amazon.com/mobile/sdkforios/developerguide/dynamodb_om.html
However I am trying to use a counter where many users may be updating at the same time.
Other documentation has told me to use and UpdateItem operation but I cannot find a good example to do so.
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Modifying.html
However, I cannot find a method to implement the expression. In the future I will be adding values to arrays and maps. Will this be the same? My code is in Obj C
Currently my code looks like:
AWSDynamoDBUpdateItemInput *updateItemInput = [AWSDynamoDBUpdateItemInput new];
updateItemInput.tableName = #"TableName";
updateItemInput.key = #{
UniqueItemKey:#"KeyValue"
};
updateItemInput.updateExpression = #"SET counter = counter + :val";
updateItemInput.expressionAttributeValues =#{
#":val":#1
};
It looks like you're missing the last bit of code that actually makes the update item request:
AWSDynamoDB *dynamoDB = [AWSDynamoDB defaultDynamoDB];
[[dynamoDB updateItem:updateItemInput]
continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"The request failed. Error: [%#]", task.error);
}
if (task.exception) {
NSLog(#"The request failed. Exception: [%#]", task.exception);
}
if (task.result) {
//Do something with result.
}
return nil;
}];
In DynamoDB if you want to increment the value of the any propertie/field you can use the UpdateItemRequest with action option ADD. I used in android this method would update the existing value of the field. Let me share the code snippet. You can use any actions such like add,delete,put etc.
.....
AttributeValue viewcount = new AttributeValue().withS("100");
AttributeValueUpdate attributeValueUpdate = new AttributeValueUpdate().withAction(AttributeAction.ADD).withValue(viewcount);
updateItems.put(UploadVideoData.FIELD_VIEW_COUNT, attributeValueUpdate);
UpdateItemRequest updateItemRequest = new UpdateItemRequest().withTableName(UploadVideoData.TABLE_NAME)
.withKey(primaryKey).withAttributeUpdates(updateItems);
UpdateItemResult updateItemResult = amazonDynamoDBClient.updateItem(updateItemRequest);
....
You can see the above code will add 100 count into the existing value of that field.
This code is for android but the technique would remain the same.
Thank you.

Deleting a record with ember-model

I try to delete a record, a DELETE request is sent to the server but the request seems not correct:
What is done:
DELETE
/books
+ body json format
What I expect:
DELETE
/books/123
+ no body
What is really expected in ember-model ?
How can I achieve my expectation (DELETE books/123)
Looking at the source code, it seams clear how ember-model does the DELETE operation:
deleteRecord: function(record) {
var primaryKey = get(record.constructor, 'primaryKey'),
url = this.buildURL(record.constructor, get(record, primaryKey)),
self = this;
return this.ajax(url, record.toJSON(), "DELETE").then(function(data) {
self.didDeleteRecord(record, data);
});
}
basically the resulting format is: DELETE /books/123 + JSON body.
If your backend expects something else then the only way to change it would be to rewrite the deleteRecord for your custom needs. But IMO the simplest thing you could do is to just ignore the JSON body.
Hope it helps.