diff --git a/3rdParty/velocypack/src/Builder.cpp b/3rdParty/velocypack/src/Builder.cpp index 707aed2a60..77e2636fed 100644 --- a/3rdParty/velocypack/src/Builder.cpp +++ b/3rdParty/velocypack/src/Builder.cpp @@ -423,7 +423,7 @@ Slice Builder::getKey(std::string const& key) const { } uint8_t* Builder::set(Value const& item) { - auto const oldPos = _start + _pos; + auto const oldPos = _pos; auto ctype = item.cType(); checkKeyIsString(item.valueType() == ValueType::String); @@ -668,7 +668,7 @@ uint8_t* Builder::set(Value const& item) { "Cannot set a ValueType::Custom with this method"); } } - return oldPos; + return _start + oldPos; } uint8_t* Builder::set(Slice const& item) { @@ -687,6 +687,8 @@ uint8_t* Builder::set(ValuePair const& pair) { // ValueType::Binary, or ValueType::Custom, which can be built // with two pieces of information + auto const oldPos = _pos; + checkKeyIsString(pair.valueType() == ValueType::String); if (pair.valueType() == ValueType::Binary) { @@ -694,7 +696,7 @@ uint8_t* Builder::set(ValuePair const& pair) { appendUInt(v, 0xbf); memcpy(_start + _pos, pair.getStart(), checkOverflow(v)); _pos += v; - return nullptr; // unused here + return _start + oldPos; } else if (pair.valueType() == ValueType::String) { uint64_t size = pair.getSize(); if (size > 126) { @@ -802,12 +804,13 @@ uint8_t* Builder::add(ObjectIterator&& sub) { if (_keyWritten) { throw Exception(Exception::BuilderKeyAlreadyWritten); } - auto const oldPos = _start + _pos; + auto const oldPos = _pos; while (sub.valid()) { - add(sub.key().copyString(), sub.value()); + add(sub.key()); + add(sub.value()); sub.next(); } - return oldPos; + return _start + oldPos; } uint8_t* Builder::add(Value const& sub) { return addInternal(sub); } @@ -832,12 +835,12 @@ uint8_t* Builder::add(ArrayIterator&& sub) { if (_start[tos] != 0x06 && _start[tos] != 0x13) { throw Exception(Exception::BuilderNeedOpenArray); } - auto const oldPos = _start + _pos; + auto const oldPos = _pos; while (sub.valid()) { add(sub.value()); sub.next(); } - return oldPos; + return _start + oldPos; } static_assert(sizeof(double) == 8, "double is not 8 bytes"); diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index 3bd28d7b8c..15c6067f18 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -2232,7 +2232,7 @@ AqlValue Functions::Neighbors(arangodb::aql::Query* query, size_t split; char const* str = vertexId.c_str(); - if (!TRI_ValidateDocumentIdKeyGenerator(str, &split)) { + if (!TRI_ValidateDocumentIdKeyGenerator(str, vertexId.size(), &split)) { THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD); } diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.cpp b/arangod/RestHandler/RestVocbaseBaseHandler.cpp index 9f58b059d3..3e36ae58d8 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.cpp +++ b/arangod/RestHandler/RestVocbaseBaseHandler.cpp @@ -422,6 +422,10 @@ void RestVocbaseBaseHandler::generateTransactionError( generateError(HttpResponse::BAD, res, "invalid document key"); return; + case TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE: + generateError(HttpResponse::BAD, res, "invalid edge attribute"); + return; + case TRI_ERROR_ARANGO_OUT_OF_KEYS: generateError(HttpResponse::SERVER_ERROR, res, "out of keys"); return; @@ -505,6 +509,10 @@ void RestVocbaseBaseHandler::generateTransactionError( generateError(HttpResponse::BAD, result.code, "invalid document key"); return; + case TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE: + generateError(HttpResponse::BAD, result.code, "invalid edge attribute"); + return; + case TRI_ERROR_ARANGO_OUT_OF_KEYS: generateError(HttpResponse::SERVER_ERROR, result.code, "out of keys"); return; diff --git a/arangod/Utils/Transaction.cpp b/arangod/Utils/Transaction.cpp index 79c4e93f45..a0c284774c 100644 --- a/arangod/Utils/Transaction.cpp +++ b/arangod/Utils/Transaction.cpp @@ -686,44 +686,6 @@ OperationResult Transaction::insert(std::string const& collectionName, } // Validate Edges - if (isEdgeCollection(collectionName)) { - // Check _from - auto checkFrom = [&](VPackSlice const value) -> void { - size_t split; - VPackSlice from = value.get(TRI_VOC_ATTRIBUTE_FROM); - if (!from.isString()) { - THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE); - } - std::string docId = from.copyString(); - if (!TRI_ValidateDocumentIdKeyGenerator(docId.c_str(), &split)) { - THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE); - } - }; - - // Check _to - auto checkTo = [&](VPackSlice const value) -> void { - size_t split; - VPackSlice to = value.get(TRI_VOC_ATTRIBUTE_TO); - if (!to.isString()) { - THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE); - } - std::string docId = to.copyString(); - if (!TRI_ValidateDocumentIdKeyGenerator(docId.c_str(), &split)) { - THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE); - } - }; - - if (value.isArray()) { - for (auto s : VPackArrayIterator(value)) { - checkFrom(s); - checkTo(s); - } - } else { - checkFrom(value); - checkTo(value); - } - } - OperationOptions optionsCopy = options; if (ServerState::instance()->isCoordinator()) { @@ -811,47 +773,8 @@ OperationResult Transaction::insertLocal(std::string const& collectionName, if (!value.isObject()) { return TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID; } - // add missing attributes for document (_id, _rev, _key) - VPackBuilder merge; - merge.openObject(); - - // generate a new tick value - TRI_voc_tick_t const revisionId = TRI_NewTickServer(); - std::string keyString; - auto key = value.get(TRI_VOC_ATTRIBUTE_KEY); - - if (key.isNone()) { - // "_key" attribute not present in object - keyString = document->_keyGenerator->generate(revisionId); - merge.add(TRI_VOC_ATTRIBUTE_KEY, VPackValue(keyString)); - } else if (!key.isString()) { - // "_key" present but wrong type - return TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD; - } else { - keyString = key.copyString(); - int res = document->_keyGenerator->validate(keyString, false); - - if (res != TRI_ERROR_NO_ERROR) { - // invalid key value - return res; - } - } - - // add _rev attribute - merge.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(std::to_string(revisionId))); - - // add _id attribute - uint8_t* p = merge.add(TRI_VOC_ATTRIBUTE_ID, VPackValuePair(9ULL, VPackValueType::Custom)); - *p++ = 0xf3; // custom type for _id - DatafileHelper::StoreNumber(p, cid, sizeof(uint64_t)); - - merge.close(); - - VPackBuilder toInsert = VPackCollection::merge(value, merge.slice(), false, false); - VPackSlice insertSlice = toInsert.slice(); - TRI_doc_mptr_t mptr; - int res = document->insert(this, insertSlice, &mptr, options, + int res = document->insert(this, value, &mptr, options, !isLocked(document, TRI_TRANSACTION_WRITE)); if (res != TRI_ERROR_NO_ERROR) { @@ -865,6 +788,9 @@ OperationResult Transaction::insertLocal(std::string const& collectionName, TRI_ASSERT(mptr.getDataPtr() != nullptr); + std::string keyString + = VPackSlice(mptr.vpack()).get(TRI_VOC_ATTRIBUTE_KEY).copyString(); + buildDocumentIdentity(resultBuilder, cid, keyString, mptr.revisionIdAsSlice(), VPackSlice(), nullptr, options.returnNew ? &mptr : nullptr); diff --git a/arangod/V8Server/v8-util.cpp b/arangod/V8Server/v8-util.cpp index c1b604e5e6..cf158887fd 100644 --- a/arangod/V8Server/v8-util.cpp +++ b/arangod/V8Server/v8-util.cpp @@ -85,7 +85,7 @@ static bool ParseDocumentHandle(v8::Handle const arg, // collection name / document key size_t split; - if (TRI_ValidateDocumentIdKeyGenerator(*str, &split)) { + if (TRI_ValidateDocumentIdKeyGenerator(*str, str.length(), &split)) { collectionName = std::string(*str, split); auto const length = str.length() - split - 1; auto buffer = new char[length + 1]; @@ -96,7 +96,7 @@ static bool ParseDocumentHandle(v8::Handle const arg, } // document key only - if (TraditionalKeyGenerator::validateKey(*str)) { + if (TraditionalKeyGenerator::validateKey(*str, str.length())) { auto const length = str.length(); auto buffer = new char[length + 1]; memcpy(buffer, *str, length); diff --git a/arangod/VocBase/KeyGenerator.cpp b/arangod/VocBase/KeyGenerator.cpp index 433abecf68..ff1d46b933 100644 --- a/arangod/VocBase/KeyGenerator.cpp +++ b/arangod/VocBase/KeyGenerator.cpp @@ -231,23 +231,21 @@ TraditionalKeyGenerator::~TraditionalKeyGenerator() {} /// @brief validate a key //////////////////////////////////////////////////////////////////////////////// -bool TraditionalKeyGenerator::validateKey(char const* key) { +bool TraditionalKeyGenerator::validateKey(char const* key, size_t len) { unsigned char const* p = reinterpret_cast(key); - unsigned char const* s = p; + size_t pos = 0; while (true) { - unsigned char c = *p; - - if (c == '\0') { - return ((p - s) > 0) && ((p - s) <= TRI_VOC_KEY_MAX_LENGTH); + if (pos >= len || *p == '\0') { + return (pos > 0) && (pos <= TRI_VOC_KEY_MAX_LENGTH); } - if (LookupTable[c]) { - ++p; - continue; + if (!LookupTable[*p]) { + return false; } - return false; + ++p; + ++pos; } } @@ -271,7 +269,7 @@ int TraditionalKeyGenerator::validate(std::string const& key, bool isRestore) { } // validate user-supplied key - if (!validateKey(key.c_str())) { + if (!validateKey(key.c_str(), key.size())) { return TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD; } @@ -320,22 +318,21 @@ AutoIncrementKeyGenerator::~AutoIncrementKeyGenerator() {} /// @brief validate a numeric key //////////////////////////////////////////////////////////////////////////////// -bool AutoIncrementKeyGenerator::validateKey(char const* key) { +bool AutoIncrementKeyGenerator::validateKey(char const* key, size_t len) { char const* p = key; + size_t pos = 0; while (true) { - char c = *p; - - if (c == '\0') { - return ((p - key) > 0) && ((p - key) <= TRI_VOC_KEY_MAX_LENGTH); + if (pos >= len || *p == '\0') { + return (pos > 0) && (pos <= TRI_VOC_KEY_MAX_LENGTH); } - if (c >= '0' && c <= '9') { - ++p; - continue; + if (*p < '0' || *p > '9') { + return false; } - return false; + ++p; + ++pos; } } @@ -383,7 +380,7 @@ int AutoIncrementKeyGenerator::validate(std::string const& key, } // validate user-supplied key - if (!validateKey(key.c_str())) { + if (!validateKey(key.c_str(), key.size())) { return TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD; } @@ -428,9 +425,15 @@ void AutoIncrementKeyGenerator::toVelocyPack(VPackBuilder& builder) const { /// @brief validate a document id (collection name + / + document key) //////////////////////////////////////////////////////////////////////////////// -bool TRI_ValidateDocumentIdKeyGenerator(char const* key, size_t* split) { +bool TRI_ValidateDocumentIdKeyGenerator(char const* key, size_t len, + size_t* split) { + if (len == 0) { + return false; + } + char const* p = key; char c = *p; + size_t pos = 0; // extract collection name if (!(c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || @@ -439,13 +442,18 @@ bool TRI_ValidateDocumentIdKeyGenerator(char const* key, size_t* split) { } ++p; + ++pos; + + while (true) { + if (pos >= len) { + return false; + } - while (1) { c = *p; - if (c == '_' || c == '-' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { ++p; + ++pos; continue; } @@ -456,14 +464,15 @@ bool TRI_ValidateDocumentIdKeyGenerator(char const* key, size_t* split) { return false; } - if (static_cast(p - key) > TRI_COL_NAME_LENGTH) { + if (pos > TRI_COL_NAME_LENGTH) { return false; } // store split position - *split = p - key; + *split = pos; ++p; + ++pos; // validate document key - return TraditionalKeyGenerator::validateKey(p); + return TraditionalKeyGenerator::validateKey(p, len - pos); } diff --git a/arangod/VocBase/KeyGenerator.h b/arangod/VocBase/KeyGenerator.h index 2949ab8c98..1660092e32 100644 --- a/arangod/VocBase/KeyGenerator.h +++ b/arangod/VocBase/KeyGenerator.h @@ -157,7 +157,7 @@ class TraditionalKeyGenerator : public KeyGenerator { /// @brief validate a key ////////////////////////////////////////////////////////////////////////////// - static bool validateKey(char const* key); + static bool validateKey(char const* key, size_t len); public: ////////////////////////////////////////////////////////////////////////////// @@ -210,7 +210,7 @@ class AutoIncrementKeyGenerator : public KeyGenerator { /// @brief validate a key ////////////////////////////////////////////////////////////////////////////// - static bool validateKey(char const* key); + static bool validateKey(char const* key, size_t len); public: ////////////////////////////////////////////////////////////////////////////// @@ -259,6 +259,6 @@ class AutoIncrementKeyGenerator : public KeyGenerator { /// @brief validate a document id (collection name + / + document key) //////////////////////////////////////////////////////////////////////////////// -bool TRI_ValidateDocumentIdKeyGenerator(char const*, size_t*); +bool TRI_ValidateDocumentIdKeyGenerator(char const*, size_t, size_t*); #endif diff --git a/arangod/VocBase/Traverser.cpp b/arangod/VocBase/Traverser.cpp index 8d5d23485b..04bf1a9f32 100644 --- a/arangod/VocBase/Traverser.cpp +++ b/arangod/VocBase/Traverser.cpp @@ -39,7 +39,7 @@ arangodb::traverser::VertexId arangodb::traverser::IdStringToVertexId( size_t split; char const* str = vertex.c_str(); - if (!TRI_ValidateDocumentIdKeyGenerator(str, &split)) { + if (!TRI_ValidateDocumentIdKeyGenerator(str, vertex.size(), &split)) { THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_HANDLE_BAD); } diff --git a/arangod/VocBase/document-collection.cpp b/arangod/VocBase/document-collection.cpp index 3f19ae3c78..a66db790c7 100644 --- a/arangod/VocBase/document-collection.cpp +++ b/arangod/VocBase/document-collection.cpp @@ -3272,29 +3272,45 @@ int TRI_document_collection_t::insert(Transaction* trx, VPackSlice const slice, bool lock) { if (_info.type() == TRI_COL_TYPE_EDGE) { + // _from: VPackSlice s = slice.get(TRI_VOC_ATTRIBUTE_FROM); if (!s.isString()) { return TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE; } + VPackValueLength len; + char const* docId = s.getString(len); + size_t split; + if (!TRI_ValidateDocumentIdKeyGenerator(docId, len, &split)) { + return TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE; + } + // _to: s = slice.get(TRI_VOC_ATTRIBUTE_TO); if (!s.isString()) { return TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE; } + docId = s.getString(len); + if (!TRI_ValidateDocumentIdKeyGenerator(docId, len, &split)) { + return TRI_ERROR_ARANGO_INVALID_EDGE_ATTRIBUTE; + } } + uint64_t hash = 0; + VPackBuilder builder; + int res = newObjectForInsert(trx, slice, hash, builder); + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + VPackSlice newSlice = builder.slice(); + TRI_ASSERT(mptr != nullptr); mptr->setDataPtr(nullptr); - VPackSlice const key(slice.get(TRI_VOC_ATTRIBUTE_KEY)); - uint64_t const hash = key.hash(); - std::unique_ptr marker; if (options.recoveryMarker == nullptr) { - marker.reset(createVPackInsertMarker(trx, slice)); + marker.reset(createVPackInsertMarker(trx, newSlice)); } TRI_voc_tick_t markerTick = 0; - int res; // now insert into indexes { TRI_IF_FAILURE("InsertDocumentNoLock") { @@ -4034,6 +4050,47 @@ int TRI_document_collection_t::deleteSecondaryIndexes( return result; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief new object for insert, computes the hash of the key +//////////////////////////////////////////////////////////////////////////////// + +int TRI_document_collection_t::newObjectForInsert( + Transaction* trx, + VPackSlice const& value, + uint64_t& hash, + VPackBuilder& builder) { + // insert + { VPackObjectBuilder guard(&builder); + + TRI_SanitizeObject(value, builder); + uint8_t* p = builder.add(TRI_VOC_ATTRIBUTE_ID, + VPackValuePair(9ULL, VPackValueType::Custom)); + *p++ = 0xf3; // custom type for _id + DatafileHelper::StoreNumber(p, _info.id(), sizeof(uint64_t)); + VPackSlice s = value.get(TRI_VOC_ATTRIBUTE_KEY); + TRI_voc_tick_t const newRev = TRI_NewTickServer(); + if (s.isNone()) { + std::string keyString = _keyGenerator->generate(newRev); + uint8_t* where = builder.add(TRI_VOC_ATTRIBUTE_KEY, + VPackValue(keyString)); + s = VPackSlice(where); // point to newly built value, the string + } else if (!s.isString()) { + return TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD; + } else { + std::string keyString = s.copyString(); + int res = _keyGenerator->validate(keyString, false); + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + builder.add(TRI_VOC_ATTRIBUTE_KEY, s); + } + hash = s.hash(); + std::string rev = std::to_string(newRev); + builder.add(TRI_VOC_ATTRIBUTE_REV, VPackValue(rev)); + } + return TRI_ERROR_NO_ERROR; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief new object for replace, oldValue must have _key and _id correctly /// set. @@ -4044,7 +4101,6 @@ VPackBuilder TRI_document_collection_t::newObjectForReplace( VPackSlice const& oldValue, VPackSlice const& newValue, std::string const& rev) { - // replace VPackBuilder builder; { VPackObjectBuilder guard(&builder); diff --git a/arangod/VocBase/document-collection.h b/arangod/VocBase/document-collection.h index 97e9a7d24e..b4ab00dba3 100644 --- a/arangod/VocBase/document-collection.h +++ b/arangod/VocBase/document-collection.h @@ -253,6 +253,16 @@ struct TRI_document_collection_t : public TRI_collection_t { int deleteSecondaryIndexes(arangodb::Transaction*, TRI_doc_mptr_t const*, bool); +//////////////////////////////////////////////////////////////////////////////// +/// @brief new object for insert, value must have _key set correctly. +//////////////////////////////////////////////////////////////////////////////// + + int newObjectForInsert( + arangodb::Transaction* trx, + arangodb::velocypack::Slice const& value, + uint64_t& hash, + arangodb::velocypack::Builder& builder); + //////////////////////////////////////////////////////////////////////////////// /// @brief new object for Replace //////////////////////////////////////////////////////////////////////////////// diff --git a/js/common/tests/shell/shell-document.js b/js/common/tests/shell/shell-document.js index 42b5e78e18..95417f69be 100644 --- a/js/common/tests/shell/shell-document.js +++ b/js/common/tests/shell/shell-document.js @@ -2030,6 +2030,274 @@ function DatabaseDocumentSuite () { }; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite: returnNew and returnOld options +//////////////////////////////////////////////////////////////////////////////// + +function DatabaseDocumentSuiteReturnStuff () { + 'use strict'; + var cn = "UnitTestsCollectionBasics"; + var ERRORS = require("internal").errors; + var collection = null; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + db._drop(cn); + collection = db._create(cn, { waitForSync : false }); + + collection.load(); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + collection.drop(); + wait(0.0); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create with and without returnNew +//////////////////////////////////////////////////////////////////////////////// + + testCreateReturnNew : function () { + var res = collection.insert({"Hallo":12}); + assertTypeOf("object", res); + assertEqual(3, Object.keys(res).length); + assertTypeOf("string", res._id); + assertTypeOf("string", res._key); + assertTypeOf("string", res._rev); + + // Now with returnNew: true + res = collection.insert({"Hallo":12}, {returnNew: true}); + assertTypeOf("object", res); + assertEqual(4, Object.keys(res).length); + assertTypeOf("string", res._id); + assertTypeOf("string", res._key); + assertTypeOf("string", res._rev); + assertTypeOf("object", res["new"]); + assertEqual(12, res["new"].Hallo); + assertEqual(4, Object.keys(res["new"]).length); + + // Now with returnNew: false + res = collection.insert({"Hallo":12}, {returnNew: false}); + assertTypeOf("object", res); + assertEqual(3, Object.keys(res).length); + assertTypeOf("string", res._id); + assertTypeOf("string", res._key); + assertTypeOf("string", res._rev); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief remove with and without returnOld +//////////////////////////////////////////////////////////////////////////////// + + testRemoveReturnOld : function () { + var res = collection.insert({"Hallo":12}); + var res2 = collection.remove(res._key); + + assertTypeOf("object", res2); + assertEqual(3, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + + // Now with returnOld: true + res = collection.insert({"Hallo":12}); + res2 = collection.remove(res._key, {returnOld: true}); + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + assertTypeOf("object", res2.old); + assertEqual(12, res2.old.Hallo); + assertEqual(4, Object.keys(res2.old).length); + + // Now with returnOld: false + res = collection.insert({"Hallo":12}); + res2 = collection.remove(res._key, {returnOld: false}); + assertTypeOf("object", res2); + assertEqual(3, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief replace with and without returnOld and returnNew +//////////////////////////////////////////////////////////////////////////////// + + testReplaceReturnOldNew : function () { + var res = collection.insert({"Hallo":12}); + var res2 = collection.replace(res._key,{"Hallo":13}); + + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + + // Now with returnOld: true + res = collection.insert({"Hallo":12}); + res2 = collection.replace(res._key, {"Hallo":13}, {returnOld: true}); + assertTypeOf("object", res2); + assertEqual(5, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + assertTypeOf("object", res2.old); + assertEqual(12, res2.old.Hallo); + assertEqual(4, Object.keys(res2.old).length); + + // Now with returnOld: false + res = collection.insert({"Hallo":12}); + res2 = collection.replace(res._key, {"Hallo":14}, {returnOld: false}); + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + + // Now with returnNew: true + res = collection.insert({"Hallo":12}); + res2 = collection.replace(res._key, {"Hallo":14}, {returnNew: true}); + assertTypeOf("object", res2); + assertEqual(5, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + assertTypeOf("object", res2["new"]); + assertEqual(14, res2["new"].Hallo); + assertEqual(4, Object.keys(res2["new"]).length); + + // Now with returnOld: false + res = collection.insert({"Hallo":12}); + res2 = collection.replace(res._key, {"Hallo":15}, {returnNew: false}); + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + + // Now with returnNew: true and returnOld:true + res = collection.insert({"Hallo":12}); + res2 = collection.replace(res._key, {"Hallo":16}, + {returnNew: true, returnOld: true}); + assertTypeOf("object", res2); + assertEqual(6, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + assertTypeOf("object", res2.old); + assertTypeOf("object", res2["new"]); + assertEqual(16, res2["new"].Hallo); + assertEqual(12, res2.old.Hallo); + assertEqual(4, Object.keys(res2["new"]).length); + assertEqual(4, Object.keys(res2.old).length); + + // Now with returnOld: false and returnNew: false + res = collection.insert({"Hallo":12}); + res2 = collection.replace(res._key, {"Hallo":15}, + {returnNew: false, returnOld: false}); + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief update with and without returnOld and returnNew +//////////////////////////////////////////////////////////////////////////////// + + testUpdateReturnOldNew : function () { + var res = collection.insert({"Hallo":12}); + var res2 = collection.update(res._key,{"Hallo":13}); + + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + + // Now with returnOld: true + res = collection.insert({"Hallo":12}); + res2 = collection.update(res._key, {"Hallo":13}, {returnOld: true}); + assertTypeOf("object", res2); + assertEqual(5, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + assertTypeOf("object", res2.old); + assertEqual(12, res2.old.Hallo); + assertEqual(4, Object.keys(res2.old).length); + + // Now with returnOld: false + res = collection.insert({"Hallo":12}); + res2 = collection.update(res._key, {"Hallo":14}, {returnOld: false}); + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + + // Now with returnNew: true + res = collection.insert({"Hallo":12}); + res2 = collection.update(res._key, {"Hallo":14}, {returnNew: true}); + assertTypeOf("object", res2); + assertEqual(5, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + assertTypeOf("object", res2["new"]); + assertEqual(14, res2["new"].Hallo); + assertEqual(4, Object.keys(res2["new"]).length); + + // Now with returnOld: false + res = collection.insert({"Hallo":12}); + res2 = collection.update(res._key, {"Hallo":15}, {returnNew: false}); + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + + // Now with returnNew: true and returnOld:true + res = collection.insert({"Hallo":12}); + res2 = collection.update(res._key, {"Hallo":16}, + {returnNew: true, returnOld: true}); + assertTypeOf("object", res2); + assertEqual(6, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + assertTypeOf("object", res2.old); + assertTypeOf("object", res2["new"]); + assertEqual(16, res2["new"].Hallo); + assertEqual(12, res2.old.Hallo); + assertEqual(4, Object.keys(res2["new"]).length); + assertEqual(4, Object.keys(res2.old).length); + + // Now with returnOld: false and returnNew: false + res = collection.insert({"Hallo":12}); + res2 = collection.update(res._key, {"Hallo":15}, + {returnNew: false, returnOld: false}); + assertTypeOf("object", res2); + assertEqual(4, Object.keys(res2).length); + assertTypeOf("string", res2._id); + assertTypeOf("string", res2._key); + assertTypeOf("string", res2._rev); + } + + }; +} //////////////////////////////////////////////////////////////////////////////// /// @brief executes the test suites @@ -2041,5 +2309,7 @@ jsunity.run(CollectionDocumentSuite); jsunity.run(DatabaseDocumentSuiteErrorHandling); jsunity.run(DatabaseDocumentSuite); +jsunity.run(DatabaseDocumentSuiteReturnStuff); + return jsunity.done();