Skip to content

Commit

Permalink
Add SimpleDB support for deleting attributes (and hence items).
Browse files Browse the repository at this point in the history
  • Loading branch information
qris committed Jan 9, 2016
1 parent 38c47d5 commit c502333
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 37 deletions.
157 changes: 142 additions & 15 deletions lib/httpserver/S3Simulator.cpp
Expand Up @@ -551,11 +551,16 @@ void S3Simulator::HandleSimpleDBGet(HTTPRequest &rRequest, HTTPResponse &rRespon
false); // !throw_if_not_found

// Iterate over all parameters looking for "Attribute.X.Name" and
// "Attribute.X.Value" attributes, putting them into two maps.
// "Attribute.X.Value" attributes, putting them into the
// param_index_to_* maps. Note that we keep the "index" as a string
// here, even though it should be an integer, to avoid needlessly
// converting back and forth. Hence all these maps are keyed on
// strings.
std::map<std::string, std::string> param_index_to_name;
std::map<std::string, std::string> param_index_to_value;
std::map<std::string, bool> param_index_to_replace;
// Index to name and value maps for Expected (conditional Put) values.
// At the same time, add all "Expected.X.Name" and "Expected.X.Value"
// attributes to the expected_index_to_* maps.
std::map<std::string, std::string> expected_index_to_name;
std::map<std::string, std::string> expected_index_to_value;

Expand Down Expand Up @@ -627,7 +632,8 @@ void S3Simulator::HandleSimpleDBGet(HTTPRequest &rRequest, HTTPResponse &rRespon

std::map<std::string, std::string> expected_values;
// Iterate over the expected maps, matching up the names and values,
// putting them into the expected_values map.
// putting them into the expected_values map, which is easier to work
// with.
for(std::map<std::string, std::string>::iterator
i = expected_index_to_name.begin();
i != expected_index_to_name.end(); i++)
Expand All @@ -651,7 +657,8 @@ void S3Simulator::HandleSimpleDBGet(HTTPRequest &rRequest, HTTPResponse &rRespon
}

// Iterate over the attribute maps, matching up the names and values,
// putting them into the item data XML tree.
// putting them into the item data XML tree. Check the expected values
// at the same time, and stop immediately if they don't match.
for(std::map<std::string, std::string>::iterator
i = param_index_to_name.begin();
i != param_index_to_name.end(); i++)
Expand All @@ -660,8 +667,8 @@ void S3Simulator::HandleSimpleDBGet(HTTPRequest &rRequest, HTTPResponse &rRespon
std::string attr_name = i->second;

// If there is an Expected value for this attribute, and it
// doesn't match the actual current value, then don't change
// anything.
// doesn't match the actual current value, then throw an
// exception, which ensures that no data is changed.
std::map<std::string, std::string>::iterator pe =
expected_values.find(attr_name);
if(pe != expected_values.end())
Expand Down Expand Up @@ -720,6 +727,8 @@ void S3Simulator::HandleSimpleDBGet(HTTPRequest &rRequest, HTTPResponse &rRespon
attr_name, new_value));
}

// Write the new item data XML tree back to the database, overwriting the
// previous values of all attributes.
simpledb.PutAttributes(domain, rRequest.GetParameterString("ItemName"),
attributes);
response_tree.add("PutAttributesResponse", "");
Expand All @@ -730,6 +739,10 @@ void S3Simulator::HandleSimpleDBGet(HTTPRequest &rRequest, HTTPResponse &rRespon
simpledb.GetAttributes(domain,
rRequest.GetParameterString("ItemName"));

// Ensure that the root element is present, even if there are no
// individual attributes.
response_tree.add("GetAttributesResponse.GetAttributesResult", "");

// Add the attributes to the response tree.
for(std::multimap<std::string, std::string>::iterator
i = attributes.begin();
Expand All @@ -743,6 +756,111 @@ void S3Simulator::HandleSimpleDBGet(HTTPRequest &rRequest, HTTPResponse &rRespon
attribute);
}
}
else if(action == "DeleteAttributes")
{
// Get the existing attributes for this item, if it exists.
std::multimap<std::string, std::string> attributes =
simpledb.GetAttributes(domain,
rRequest.GetParameterString("ItemName"),
false); // !throw_if_not_found

// Iterate over all parameters looking for "Attribute.X.Name" and
// "Attribute.X.Value" attributes, putting them into the
// param_index_to_* maps. Note that we keep the "index" as a string
// here, even though it should be an integer, to avoid needlessly
// converting back and forth. Hence all these maps are keyed on
// strings.
std::map<std::string, std::string> param_index_to_name;
std::map<std::string, std::string> param_index_to_value;

for(HTTPRequest::Query_t::const_iterator i = params.begin();
i != params.end(); i++)
{
std::string param_name = i->first;
std::string param_value = i->second;
std::string param_number_type = RemovePrefix("Attribute.",
param_name);
if(!param_number_type.empty())
{
std::string param_index_name = RemoveSuffix(".Name",
param_number_type);
std::string param_index_value = RemoveSuffix(".Value",
param_number_type);
if(!param_index_name.empty())
{
param_index_to_name[param_index_name] =
param_value;
}
else if(!param_index_value.empty())
{
param_index_to_value[param_index_value] =
param_value;
}
else
{
THROW_EXCEPTION_MESSAGE(HTTPException,
S3SimulatorError, "DeleteAttributes: "
"Unparsed Attribute parameter: " <<
param_name);
}
}
}

// Iterate over the attribute maps, matching up the names and values,
// searching for and removing them from the item data XML tree.
for(std::map<std::string, std::string>::iterator
i = param_index_to_name.begin();
i != param_index_to_name.end(); i++)
{
std::string index = i->first;
std::string attr_name = i->second;

std::map<std::string, std::string>::iterator pv =
param_index_to_value.find(index);
if(pv == param_index_to_value.end())
{
THROW_EXCEPTION_MESSAGE(HTTPException,
S3SimulatorError, "DeleteAttributes: "
"Attribute Name without Value: " <<
index << ": " << attr_name);
}
std::string expected_value = pv->second;

bool deleted;
do
{
deleted = false;
typedef std::multimap<std::string, std::string>::iterator
iter_t;
std::pair<iter_t, iter_t> range =
attributes.equal_range(attr_name);

// Loop over all values for this attribute name (attr_name), which
// must all lie between range->first and range->second.
for(iter_t p_orig_attr = range.first; p_orig_attr != range.second;
p_orig_attr++)
{
if(p_orig_attr->second == expected_value)
{
attributes.erase(p_orig_attr);
deleted = true;
// The iterator is not valid any more, so
// break out and search again.
break;
}
}
}
while(deleted);
}

// Write the new item data XML tree back to the database, overwriting the
// previous values of all attributes, and in particular, removing any that
// are no longer present in the attributes map.
simpledb.PutAttributes(domain, rRequest.GetParameterString("ItemName"),
attributes);
response_tree.add("DeleteAttributesResponse", "");
}

else if(action == "Reset")
{
simpledb.Reset();
Expand Down Expand Up @@ -907,16 +1025,25 @@ std::multimap<std::string, std::string> SimpleDBSimulator::GetAttributes(
free(result);
ptree item_data = XmlStringToPtree(item_data_str);

// Iterate over the attributes in the item data tree, adding names and values
// to the attributes map.
BOOST_FOREACH(ptree::value_type &v,
item_data.get_child(PTREE_ITEM_ATTRIBUTES))
// There might not be any attributes, e.g. if they have all been deleted,
// so we need to check for and handle that situation, as it's not an error.
try
{
// Iterate over the attributes in the item data tree, adding names and values
// to the attributes map.
BOOST_FOREACH(ptree::value_type &v,
item_data.get_child(PTREE_ITEM_ATTRIBUTES))
{
std::string name = v.first;
std::string value = v.second.data();
attributes.insert(
std::multimap<std::string, std::string>::value_type(name,
value));
}
}
catch(boost::property_tree::ptree_bad_path &e)
{
std::string name = v.first;
std::string value = v.second.data();
attributes.insert(
std::multimap<std::string, std::string>::value_type(name,
value));
// Do nothing, just don't add any attributes to the list.
}

return attributes;
Expand Down
85 changes: 63 additions & 22 deletions lib/httpserver/SimpleDBClient.cpp
Expand Up @@ -398,6 +398,35 @@ SimpleDBClient::str_map_t SimpleDBClient::GetAttributes(const std::string& domai
return attributes;
}

void SimpleDBClient::AddPutAttributes(HTTPRequest& request, const str_map_t& attributes,
const str_map_t& expected, bool add_required)
{
int counter = 1;
for(str_map_t::const_iterator i = attributes.begin(); i != attributes.end(); i++)
{
std::ostringstream oss;
oss << "Attribute.";
oss << counter++;
request.AddParameter(oss.str() + ".Name", i->first);
request.AddParameter(oss.str() + ".Value", i->second);

if(add_required)
{
request.AddParameter(oss.str() + ".Replace", "true");
}
}

counter = 1;
for(str_map_t::const_iterator i = expected.begin(); i != expected.end(); i++)
{
std::ostringstream oss;
oss << "Expected.";
oss << counter++;
request.AddParameter(oss.str() + ".Name", i->first);
request.AddParameter(oss.str() + ".Value", i->second);
}
}

// --------------------------------------------------------------------------
//
// Function
Expand All @@ -421,37 +450,49 @@ SimpleDBClient::str_map_t SimpleDBClient::GetAttributes(const std::string& domai

void SimpleDBClient::PutAttributes(const std::string& domain_name,
const std::string& item_name, const SimpleDBClient::str_map_t& attributes,
const SimpleDBClient::str_map_t& expected)
const SimpleDBClient::str_map_t& expected)
{
HTTPRequest request = StartRequest(HTTPRequest::Method_GET, "PutAttributes");
request.AddParameter("DomainName", domain_name);
request.AddParameter("ItemName", item_name);

int counter = 1;
for(str_map_t::const_iterator i = attributes.begin(); i != attributes.end(); i++)
{
std::ostringstream oss;
oss << "Attribute.";
oss << counter++;
request.AddParameter(oss.str() + ".Name", i->first);
request.AddParameter(oss.str() + ".Value", i->second);
request.AddParameter(oss.str() + ".Replace", "true");
}

counter = 1;
for(str_map_t::const_iterator i = expected.begin(); i != expected.end(); i++)
{
std::ostringstream oss;
oss << "Expected.";
oss << counter++;
request.AddParameter(oss.str() + ".Name", i->first);
request.AddParameter(oss.str() + ".Value", i->second);
}

AddPutAttributes(request, attributes, expected, true); // add_required
request.AddParameter("Signature", CalculateSimpleDBSignature(request));

ptree response_tree;
SendAndReceiveXML(request, response_tree, "PutAttributesResponse");
}


// --------------------------------------------------------------------------
//
// Function
// Name: SimpleDBClient::DeleteAttributes(
// const std::string& domain_name,
// const std::string& item_name,
// const SimpleDBClient::str_map_t& attributes)
// Purpose: Deletes one or more attributes associated with the
// item. If all attributes of an item are deleted, the
// item is deleted. If you specify DeleteAttributes
// without attributes or values, all the attributes for
// the item are deleted (and hence the item itself).
// Created: 09/01/2016
//
// --------------------------------------------------------------------------
// http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_DeleteAttributes.html

void SimpleDBClient::DeleteAttributes(const std::string& domain_name,
const std::string& item_name, const SimpleDBClient::str_map_t& attributes,
const SimpleDBClient::str_map_t& expected)
{
HTTPRequest request = StartRequest(HTTPRequest::Method_GET, "DeleteAttributes");
request.AddParameter("DomainName", domain_name);
request.AddParameter("ItemName", item_name);

AddPutAttributes(request, attributes, expected, false); // add_required
request.AddParameter("Signature", CalculateSimpleDBSignature(request));

ptree response_tree;
SendAndReceiveXML(request, response_tree, "DeleteAttributesResponse");
}

5 changes: 5 additions & 0 deletions lib/httpserver/SimpleDBClient.h
Expand Up @@ -70,6 +70,9 @@ class SimpleDBClient
void PutAttributes(const std::string& domain_name,
const std::string& item_name, const str_map_t& attributes,
const str_map_t& expected = str_map_t());
void DeleteAttributes(const std::string& domain_name,
const std::string& item_name, const str_map_t& attributes,
const str_map_t& expected = str_map_t());

// These shouldn't really be APIs, but exposing them makes it easier to test
// this class.
Expand All @@ -87,6 +90,8 @@ class SimpleDBClient
void SendAndReceiveXML(HTTPRequest& request, ptree& response_tree,
const std::string& expected_root_element);
std::string CalculateSimpleDBSignature(const HTTPRequest& request);
void AddPutAttributes(HTTPRequest& request, const str_map_t& attributes,
const str_map_t& expected, bool add_required);
};


Expand Down
26 changes: 26 additions & 0 deletions test/httpserver/testhttpserver.cpp
Expand Up @@ -1020,6 +1020,22 @@ int test(int argc, const char *argv[])
expected_attrs.insert(attr_t("Size", "Large"));
TEST_THAT(simpledb_get_attributes(access_key, secret_key, expected_attrs));

// Test that we can delete values. We are supposed to pass some
// attribute values, but what happens if they don't match the current
// values is not specified.
request.SetParameter("Action", "DeleteAttributes");
request.RemoveParameter("Expected.1.Name");
request.RemoveParameter("Expected.1.Value");
request.RemoveParameter("Attribute.1.Replace");
request.RemoveParameter("Attribute.2.Replace");
TEST_THAT(add_simpledb_signature(request, secret_key));
TEST_THAT(send_and_receive_xml(request, response_tree,
"DeleteAttributesResponse"));

// Check that it has actually been removed.
expected_attrs.clear();
TEST_THAT(simpledb_get_attributes(access_key, secret_key, expected_attrs));

// Reset for the next test
request.SetParameter("Action", "Reset");
TEST_THAT(add_simpledb_signature(request, secret_key));
Expand Down Expand Up @@ -1105,6 +1121,16 @@ int test(int argc, const char *argv[])
// value.
actual_attrs = client.GetAttributes(domain, item);
TEST_THAT(compare_maps(new_attrs, actual_attrs));

// Test that we can delete values. We are supposed to pass some
// attribute values, but what happens if they don't match the current
// values is not specified.
client.DeleteAttributes(domain, item, new_attrs);

// Check that it has actually been removed.
expected_attrs.clear();
actual_attrs = client.GetAttributes(domain, item);
TEST_THAT(compare_maps(expected_attrs, actual_attrs));
}

// Kill it
Expand Down

0 comments on commit c502333

Please sign in to comment.