1
0
Fork 0

issue #110: add PATCH method for documents

This commit is contained in:
Jan Steemann 2012-08-20 19:16:45 +02:00
parent 4b16882b44
commit b36776efb3
8 changed files with 337 additions and 20 deletions

View File

@ -0,0 +1,67 @@
> curl --data @- -X PATCH --dump - http://localhost:8529/_api/document/73482/7229681
{ "hello": "world" }
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
{
"_rev": 11916057,
"_id": "73482/7229681",
"error": false
}
> curl --data @- -X PATCH --dump - http://localhost:8529/_api/document/73482/7229681
{ "numbers": { "one": 1, "two": 2, "three": 3, "empty": null } }
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
{
"_rev": 12309273,
"_id": "73482/7229681",
"error": false
}
> curl -X GET --dump - http://localhost:8529/_api/document/73482/7229681
{ "numbers": { "one": 1, "two": 2, "three": 3, "empty": null } }
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
{
"hello": "world",
"numbers": {
"empty": null,
"one": 1,
"two": 2,
"three": 3
},
"_rev": 12309273,
"_id": "73482/7229681"
}
> curl --data @- -X PATCH --dump - http://localhost:8529/_api/document/73482/7229681?keepNull=false
{ "hello": null, "numbers": { "four": 4 } }
{
"_rev": 12571417,
"_id": "73482/7229681",
"error": false
}
> curl -X GET --dump - http://localhost:8529/_api/document/73482/7229681
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
{
"numbers": {
"empty": null,
"one": 1,
"two": 2,
"three": 3,
"four": 4,
},
"_rev": 12571417,
"_id": "73482/7229681"
}

View File

@ -29,6 +29,8 @@
#include "Basics/StringUtils.h"
#include "BasicsC/conversions.h"
#include "BasicsC/json.h"
#include "BasicsC/json-utilities.h"
#include "BasicsC/string-buffer.h"
#include "Rest/HttpRequest.h"
#include "Rest/JsonContainer.h"
@ -129,8 +131,8 @@ HttpHandler::status_e RestDocumentHandler::execute () {
case HttpRequest::HTTP_REQUEST_GET: res = readDocument(); break;
case HttpRequest::HTTP_REQUEST_HEAD: res = checkDocument(); break;
case HttpRequest::HTTP_REQUEST_POST: res = createDocument(); break;
case HttpRequest::HTTP_REQUEST_PUT: res = updateDocument(); break;
case HttpRequest::HTTP_REQUEST_PATCH: res = updateDocument(); break;
case HttpRequest::HTTP_REQUEST_PUT: res = updateDocument(false); break;
case HttpRequest::HTTP_REQUEST_PATCH: res = updateDocument(true); break;
case HttpRequest::HTTP_REQUEST_ILLEGAL:
res = false;
@ -619,15 +621,14 @@ bool RestDocumentHandler::checkDocument () {
///
/// @REST{PUT /_api/document/@FA{document-handle}}
///
/// Updates the document identified by @FA{document-handle}. If the document exists
/// and can be updated, then a @LIT{HTTP 201} is returned and the "ETag" header
/// field contains the new revision of the document.
/// Completely updates (i.e. replaces) the document identified by @FA{document-handle}.
/// If the document exists and can be updated, then a @LIT{HTTP 201} is returned
/// and the "ETag" header field contains the new revision of the document.
///
/// Note the updated document passed in the body of the request normally also
/// contains the @FA{document-handle} in the attribute @LIT{_id} and the
/// revision in @LIT{_rev}. These attributes, however, are ignored. Only the URI
/// and the "ETag" header are relevant in order to avoid confusion when using
/// proxies.
/// If the new document passed in the body of the request contains the
/// @FA{document-handle} in the attribute @LIT{_id} and the revision in @LIT{_rev},
/// these attributes will be ignored. Only the URI and the "ETag" header are
/// relevant in order to avoid confusion when using proxies.
///
/// The body of the response contains a JSON object with the information about
/// the handle and the revision. The attribute @LIT{_id} contains the known
@ -674,9 +675,39 @@ bool RestDocumentHandler::checkDocument () {
/// Alternative to header field:
///
/// @verbinclude rest-update-document-rev-other
///
/// @RESTHEADER{PATCH /_api/document,patches a document}
///
/// @REST{PATCH /_api/document/@FA{document-handle}}
///
/// Partially updates the document identified by @FA{document-handle}.
/// The body of the request must contain a JSON document with the attributes
/// to patch (the patch document). All attributes from the patch document will
/// be added to the existing document if they do not yet exist, and overwritten
/// in the existing document if they do exist there.
///
/// Setting an attribute value to @LIT{null} in the patch document will cause a
/// value of @LIT{null} be saved for the attribute by default. If the intention
/// is to delete existing attributes with the patch command, the URL parameter
/// @LIT{keepNull} can be used with a value of @LIT{false}.
/// This will modify the behavior of the patch command to remove any attributes
/// from the existing document that are contained in the patch document with an
/// attribute value of @LIT{null}.
///
/// The body of the response contains a JSON object with the information about
/// the handle and the revision. The attribute @LIT{_id} contains the known
/// @FA{document-handle} of the updated document, the attribute @LIT{_rev}
/// contains the new document revision.
///
/// If the document does not exist, then a @LIT{HTTP 404} is returned and the
/// body of the response contains an error document.
///
/// @EXAMPLES
///
/// @verbinclude rest-patch-document
////////////////////////////////////////////////////////////////////////////////
bool RestDocumentHandler::updateDocument () {
bool RestDocumentHandler::updateDocument (bool isPatch) {
vector<string> const& suffix = _request->suffix();
if (suffix.size() != 2) {
@ -724,7 +755,41 @@ bool RestDocumentHandler::updateDocument () {
TRI_voc_rid_t rid = 0;
TRI_voc_cid_t cid = _documentCollection->base._cid;
TRI_doc_mptr_t const mptr = _documentCollection->updateJson(_documentCollection, json, did, revision, &rid, policy, true);
// init target document
TRI_doc_mptr_t mptr;
if (isPatch) {
bool nullMeansRemove;
bool found;
char const* valueStr = _request->value("keepNull", found);
if (!found || StringUtils::boolean(valueStr)) {
// keep null values
nullMeansRemove = false;
}
else {
// delete null attributes
nullMeansRemove = true;
}
// read the existing document
TRI_doc_mptr_t document = _documentCollection->read(_documentCollection, did);
TRI_json_t* old = TRI_JsonShapedJson(_documentCollection->_shaper, &document._document);
if (old != 0) {
TRI_json_t* patchedJson = TRI_MergeJson(TRI_UNKNOWN_MEM_ZONE, old, json, nullMeansRemove);
TRI_FreeJson(_documentCollection->_shaper->_memoryZone, old);
if (patchedJson != 0) {
mptr = _documentCollection->updateJson(_documentCollection, patchedJson, did, revision, &rid, policy, true);
TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, patchedJson);
}
}
}
else {
mptr = _documentCollection->updateJson(_documentCollection, json, did, revision, &rid, policy, true);
}
res = mptr._did == 0 ? TRI_errno() : 0;
// .............................................................................

View File

@ -145,7 +145,7 @@ namespace triagens {
/// @brief updates a document
////////////////////////////////////////////////////////////////////////////////
virtual bool updateDocument ();
virtual bool updateDocument (bool);
////////////////////////////////////////////////////////////////////////////////
/// @brief deletes a document

View File

@ -37,6 +37,63 @@
/// @{
////////////////////////////////////////////////////////////////////////////////
static TRI_json_t* MergeRecursive (TRI_memory_zone_t* zone,
const TRI_json_t* const lhs,
const TRI_json_t* const rhs,
const bool nullMeansRemove) {
size_t i, n;
TRI_json_t* result = TRI_CopyJson(zone, lhs);
if (result == NULL) {
return NULL;
}
n = rhs->_value._objects._length;
for (i = 0; i < n; i += 2) {
// enumerate all the replacement values
TRI_json_t* key = TRI_AtVector(&rhs->_value._objects, i);
TRI_json_t* value = TRI_AtVector(&rhs->_value._objects, i + 1);
if (value->_type == TRI_JSON_NULL && nullMeansRemove) {
// replacement value is a null and we don't want to store nulls => delete attribute from the result
TRI_DeleteArrayJson(zone, result, key->_value._string.data);
}
else {
// replacement value is not a null or we want to store nulls
TRI_json_t* lhsValue = TRI_LookupArrayJson(lhs, key->_value._string.data);
if (lhsValue == NULL) {
// existing array does not have the attribute => append new attribute
if (value->_type == TRI_JSON_ARRAY) {
TRI_json_t* empty = TRI_CreateArrayJson(zone);
TRI_json_t* merged = MergeRecursive(zone, empty, value, nullMeansRemove);
TRI_Insert3ArrayJson(zone, result, key->_value._string.data, merged);
TRI_FreeJson(zone, empty);
}
else {
TRI_Insert3ArrayJson(zone, result, key->_value._string.data, TRI_CopyJson(zone, value));
}
}
else {
// existing array already has the attribute => replace attribute
if (lhsValue->_type == TRI_JSON_ARRAY && value->_type == TRI_JSON_ARRAY) {
TRI_json_t* merged = MergeRecursive(zone, lhsValue, value, nullMeansRemove);
TRI_ReplaceArrayJson(zone, result, key->_value._string.data, merged);
TRI_FreeJson(zone, merged);
}
else {
TRI_ReplaceArrayJson(zone, result, key->_value._string.data, value);
}
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief fruitsort initialisation parameters
///
@ -663,6 +720,24 @@ bool TRI_HasDuplicateKeyJson (const TRI_json_t* const object) {
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief merge two JSON documents into one
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* TRI_MergeJson (TRI_memory_zone_t* zone,
const TRI_json_t* const lhs,
const TRI_json_t* const rhs,
const bool nullMeansRemove) {
TRI_json_t* result;
assert(lhs->_type == TRI_JSON_ARRAY);
assert(rhs->_type == TRI_JSON_ARRAY);
result = MergeRecursive(zone, lhs, rhs, nullMeansRemove);
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

View File

@ -120,7 +120,7 @@ TRI_json_t* TRI_IntersectListsJson (const TRI_json_t* const,
/// @brief sorts a json list in place
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* TRI_SortListJson (TRI_json_t* const list);
TRI_json_t* TRI_SortListJson (TRI_json_t* const);
////////////////////////////////////////////////////////////////////////////////
/// @brief checks if a JSON struct has duplicate attribute names
@ -128,6 +128,15 @@ TRI_json_t* TRI_SortListJson (TRI_json_t* const list);
bool TRI_HasDuplicateKeyJson (const TRI_json_t* const);
////////////////////////////////////////////////////////////////////////////////
/// @brief merge two JSON documents into one
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* TRI_MergeJson (TRI_memory_zone_t*,
const TRI_json_t* const,
const TRI_json_t* const,
const bool);
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

View File

@ -31,7 +31,6 @@
#include "BasicsC/logging.h"
#include "BasicsC/string-buffer.h"
#include "BasicsC/strings.h"
#include "BasicsC/strings.h"
// -----------------------------------------------------------------------------
// --SECTION-- JSON
@ -651,7 +650,7 @@ void TRI_Insert4ArrayJson (TRI_memory_zone_t* zone, TRI_json_t* object, char* na
/// @brief looks up an attribute in an json array
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* TRI_LookupArrayJson (TRI_json_t* object, char const* name) {
TRI_json_t* TRI_LookupArrayJson (const TRI_json_t* const object, char const* name) {
size_t n;
size_t i;
@ -677,6 +676,95 @@ TRI_json_t* TRI_LookupArrayJson (TRI_json_t* object, char const* name) {
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief deletes an element from a json array
////////////////////////////////////////////////////////////////////////////////
bool TRI_DeleteArrayJson (TRI_memory_zone_t* zone, TRI_json_t* object, char const* name) {
size_t n;
size_t i;
assert(object->_type == TRI_JSON_ARRAY);
assert(name);
n = object->_value._objects._length;
for (i = 0; i < n; i += 2) {
TRI_json_t* key;
key = TRI_AtVector(&object->_value._objects, i);
if (key->_type != TRI_JSON_STRING) {
continue;
}
if (TRI_EqualString(key->_value._string.data, name)) {
TRI_json_t* old;
// remove key
old = TRI_AtVector(&object->_value._objects, i);
if (old != NULL) {
TRI_DestroyJson(zone, old);
}
TRI_RemoveVector(&object->_value._objects, i);
// remove value
old = TRI_AtVector(&object->_value._objects, i);
if (old != NULL) {
TRI_DestroyJson(zone, old);
}
TRI_RemoveVector(&object->_value._objects, i);
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief replaces an element in a json array
////////////////////////////////////////////////////////////////////////////////
bool TRI_ReplaceArrayJson (TRI_memory_zone_t* zone, TRI_json_t* object, char const* name, TRI_json_t* replacement) {
size_t n;
size_t i;
assert(object->_type == TRI_JSON_ARRAY);
assert(name);
n = object->_value._objects._length;
for (i = 0; i < n; i += 2) {
TRI_json_t* key;
key = TRI_AtVector(&object->_value._objects, i);
if (key->_type != TRI_JSON_STRING) {
continue;
}
if (TRI_EqualString(key->_value._string.data, name)) {
TRI_json_t copy;
// retrieve the old element
TRI_json_t* old = TRI_AtVector(&object->_value._objects, i + 1);
if (old != NULL) {
TRI_DestroyJson(zone, old);
}
TRI_CopyToJson(zone, &copy, replacement);
TRI_SetVector(&object->_value._objects, i + 1, &copy);
return true;
}
}
// object not found in array, now simply add it
TRI_Insert2ArrayJson(zone, object, name, replacement);
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief stringifies a json object
////////////////////////////////////////////////////////////////////////////////
@ -865,7 +953,7 @@ int TRI_CopyToJson (TRI_memory_zone_t* zone,
/// @brief copies a json object
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* TRI_CopyJson (TRI_memory_zone_t* zone, TRI_json_t* src) {
TRI_json_t* TRI_CopyJson (TRI_memory_zone_t* zone, const TRI_json_t* const src) {
TRI_json_t* dst;
int res;

View File

@ -249,10 +249,22 @@ void TRI_Insert3ArrayJson (TRI_memory_zone_t*, TRI_json_t* object, char const* n
void TRI_Insert4ArrayJson (TRI_memory_zone_t* zone, TRI_json_t* object, char* name, size_t nameLength, TRI_json_t* subobject);
////////////////////////////////////////////////////////////////////////////////
/// @brief looks up an attribute in an json array
/// @brief looks up an attribute in a json array
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* TRI_LookupArrayJson (TRI_json_t* object, char const* name);
TRI_json_t* TRI_LookupArrayJson (const TRI_json_t* const object, char const* name);
////////////////////////////////////////////////////////////////////////////////
/// @brief deletes an element from a json array
////////////////////////////////////////////////////////////////////////////////
bool TRI_DeleteArrayJson (TRI_memory_zone_t* zone, TRI_json_t* object, char const* name);
////////////////////////////////////////////////////////////////////////////////
/// @brief replaces an element in a json array
////////////////////////////////////////////////////////////////////////////////
bool TRI_ReplaceArrayJson (TRI_memory_zone_t* zone, TRI_json_t* object, char const* name, TRI_json_t* replacement);
////////////////////////////////////////////////////////////////////////////////
/// @brief stringifies a json object
@ -288,7 +300,7 @@ int TRI_CopyToJson (TRI_memory_zone_t*, TRI_json_t* dst, TRI_json_t const* src);
/// @brief copies a json object
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* TRI_CopyJson (TRI_memory_zone_t*, TRI_json_t*);
TRI_json_t* TRI_CopyJson (TRI_memory_zone_t*, const TRI_json_t* const);
////////////////////////////////////////////////////////////////////////////////
/// @brief parses a json string

View File

@ -198,6 +198,7 @@ namespace triagens {
case HttpRequest::HTTP_REQUEST_POST:
case HttpRequest::HTTP_REQUEST_PUT:
case HttpRequest::HTTP_REQUEST_PATCH:
this->_bodyLength = this->_request->contentLength();
if (this->_bodyLength > 0) {