mirror of https://gitee.com/bigwinds/arangodb
issue #110: add PATCH method for documents
This commit is contained in:
parent
4b16882b44
commit
b36776efb3
|
@ -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"
|
||||
}
|
|
@ -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;
|
||||
|
||||
// .............................................................................
|
||||
|
|
|
@ -145,7 +145,7 @@ namespace triagens {
|
|||
/// @brief updates a document
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual bool updateDocument ();
|
||||
virtual bool updateDocument (bool);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief deletes a document
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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, ©, replacement);
|
||||
TRI_SetVector(&object->_value._objects, i + 1, ©);
|
||||
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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue