//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Max Neunhoeffer //////////////////////////////////////////////////////////////////////////////// #include "AqlValue.h" #include "Aql/AqlItemBlock.h" #include "Aql/Arithmetic.h" #include "Aql/Range.h" #include "Aql/SharedAqlItemBlockPtr.h" #include "Basics/ConditionalDeleter.h" #include "Basics/VelocyPackHelper.h" #include "Transaction/Context.h" #include "Transaction/Helpers.h" #include "Transaction/Methods.h" #include "V8/v8-vpack.h" #include #include #include #include #include using namespace arangodb; using namespace arangodb::aql; // some functionality borrowed from 3rdParty/velocypack/include/velocypack // this is a copy of that functionality, because the functions in velocypack // are not accessible from here namespace { static inline uint64_t toUInt64(int64_t v) noexcept { // If v is negative, we need to add 2^63 to make it positive, // before we can cast it to an uint64_t: uint64_t shift2 = 1ULL << 63; int64_t shift = static_cast(shift2 - 1); return v >= 0 ? static_cast(v) : static_cast((v + shift) + 1) + shift2; // Note that g++ and clang++ with -O3 compile this away to // nothing. Further note that a plain cast from int64_t to // uint64_t is not guaranteed to work for negative values! } // returns number of bytes required to store the value in 2s-complement static inline uint8_t intLength(int64_t value) noexcept { if (value >= -0x80 && value <= 0x7f) { // shortcut for the common case return 1; } uint64_t x = value >= 0 ? static_cast(value) : static_cast(-(value + 1)); uint8_t xSize = 0; do { xSize++; x >>= 8; } while (x >= 0x80); return xSize + 1; } } // namespace /// @brief hashes the value uint64_t AqlValue::hash(transaction::Methods* trx, uint64_t seed) const { switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { // we must use the slow hash function here, because a value may have // different representations in case it's an array/object/number return slice().normalizedHash(seed); } case DOCVEC: case RANGE: { transaction::BuilderLeaser builder(trx); toVelocyPack(trx, *builder.get(), false); // we must use the slow hash function here, because a value may have // different representations in case its an array/object/number return builder->slice().normalizedHash(seed); } } return 0; } /// @brief whether or not the value contains a none value bool AqlValue::isNone() const noexcept { AqlValueType t = type(); if (t == DOCVEC || t == RANGE) { return false; } try { return slice().isNone(); } catch (...) { return false; } } /// @brief whether or not the value is a null value bool AqlValue::isNull(bool emptyIsNull) const noexcept { AqlValueType t = type(); if (t == DOCVEC || t == RANGE) { return false; } try { VPackSlice s(slice()); return (s.isNull() || (emptyIsNull && s.isNone())); } catch (...) { return false; } } /// @brief whether or not the value is a boolean value bool AqlValue::isBoolean() const noexcept { AqlValueType t = type(); if (t == DOCVEC || t == RANGE) { return false; } try { return slice().isBoolean(); } catch (...) { return false; } } /// @brief whether or not the value is a number bool AqlValue::isNumber() const noexcept { AqlValueType t = type(); if (t == DOCVEC || t == RANGE) { return false; } try { return slice().isNumber(); } catch (...) { return false; } } /// @brief whether or not the value is a string bool AqlValue::isString() const noexcept { AqlValueType t = type(); if (t == DOCVEC || t == RANGE) { return false; } try { return slice().isString(); } catch (...) { return false; } } /// @brief whether or not the value is an object bool AqlValue::isObject() const noexcept { AqlValueType t = type(); if (t == RANGE || t == DOCVEC) { return false; } try { return slice().isObject(); } catch (...) { return false; } } /// @brief whether or not the value is an array (note: this treats ranges /// as arrays, too!) bool AqlValue::isArray() const noexcept { AqlValueType t = type(); if (t == RANGE || t == DOCVEC) { return true; } try { return slice().isArray(); } catch (...) { return false; } } char const* AqlValue::getTypeString() const noexcept { if (isNone()) { return "none"; } else if (isNull(true)) { return "null"; } else if (isBoolean()) { return "bool"; } else if (isNumber()) { return "number"; } else if (isString()) { return "string"; } else if (isObject()) { return "object"; } else if (isArray()) { return "array"; } return "none"; } /// @brief get the (array) length (note: this treats ranges as arrays, too!) size_t AqlValue::length() const { switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { return static_cast(slice().length()); } case DOCVEC: { return docvecSize(); } case RANGE: { return range()->size(); } } TRI_ASSERT(false); return 0; } /// @brief get the (array) element at position AqlValue AqlValue::at(int64_t position, bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isArray()) { int64_t const n = static_cast(s.length()); if (position < 0) { // a negative position is allowed position = n + position; } if (position >= 0 && position < n) { if (doCopy) { mustDestroy = true; return AqlValue(s.at(position)); } // return a reference to an existing slice return AqlValue(s.at(position).begin()); } } // intentionally falls through break; } case DOCVEC: { size_t const n = docvecSize(); if (position < 0) { // a negative position is allowed position = static_cast(n) + position; } if (position >= 0 && position < static_cast(n)) { // only look up the value if it is within array bounds size_t total = 0; for (auto const& it : *_data.docvec) { if (position < static_cast(total + it->size())) { // found the correct vector if (doCopy) { mustDestroy = true; return it ->getValueReference(static_cast(position - total), 0) .clone(); } return it->getValue(static_cast(position - total), 0); } total += it->size(); } } // intentionally falls through break; } case RANGE: { size_t const n = range()->size(); if (position < 0) { // a negative position is allowed position = static_cast(n) + position; } if (position >= 0 && position < static_cast(n)) { // only look up the value if it is within array bounds return AqlValue(AqlValueHintInt(_data.range->at(static_cast(position)))); } // intentionally falls through break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the (array) element at position AqlValue AqlValue::at(int64_t position, size_t n, bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isArray()) { if (position < 0) { // a negative position is allowed position = static_cast(n) + position; } if (position >= 0 && position < static_cast(n)) { if (doCopy) { mustDestroy = true; return AqlValue(s.at(position)); } // return a reference to an existing slice return AqlValue(s.at(position).begin()); } } // intentionally falls through break; } case DOCVEC: { if (position < 0) { // a negative position is allowed position = static_cast(n) + position; } if (position >= 0 && position < static_cast(n)) { // only look up the value if it is within array bounds size_t total = 0; for (auto const& it : *_data.docvec) { if (position < static_cast(total + it->size())) { // found the correct vector if (doCopy) { mustDestroy = true; return it ->getValueReference(static_cast(position - total), 0) .clone(); } return it->getValue(static_cast(position - total), 0); } total += it->size(); } } // intentionally falls through break; } case RANGE: { if (position < 0) { // a negative position is allowed position = static_cast(n) + position; } if (position >= 0 && position < static_cast(n)) { // only look up the value if it is within array bounds return AqlValue(AqlValueHintInt(_data.range->at(static_cast(position)))); } // intentionally falls through break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the _key attribute from an object/document AqlValue AqlValue::getKeyAttribute(bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isObject()) { VPackSlice found = transaction::helpers::extractKeyFromDocument(s); if (!found.isNone()) { if (doCopy) { mustDestroy = true; return AqlValue(found); } // return a reference to an existing slice return AqlValue(found.begin()); } } // intentionally falls through break; } case DOCVEC: case RANGE: { // will return null break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the _id attribute from an object/document AqlValue AqlValue::getIdAttribute(CollectionNameResolver const& resolver, bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isObject()) { VPackSlice found = transaction::helpers::extractIdFromDocument(s); if (found.isCustom()) { // _id as a custom type needs special treatment mustDestroy = true; return AqlValue(transaction::helpers::extractIdString(&resolver, found, s)); } if (!found.isNone()) { if (doCopy) { mustDestroy = true; return AqlValue(found); } // return a reference to an existing slice return AqlValue(found.begin()); } } // intentionally falls through break; } case DOCVEC: case RANGE: { // will return null break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the _from attribute from an object/document AqlValue AqlValue::getFromAttribute(bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isObject()) { VPackSlice found = transaction::helpers::extractFromFromDocument(s); if (!found.isNone()) { if (doCopy) { mustDestroy = true; return AqlValue(found); } // return a reference to an existing slice return AqlValue(found.begin()); } } // intentionally falls through break; } case DOCVEC: case RANGE: { // will return null break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the _to attribute from an object/document AqlValue AqlValue::getToAttribute(bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isObject()) { VPackSlice found = transaction::helpers::extractToFromDocument(s); if (!found.isNone()) { if (doCopy) { mustDestroy = true; return AqlValue(found); } // return a reference to an existing slice return AqlValue(found.begin()); } } // intentionally falls through break; } case DOCVEC: case RANGE: { // will return null break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the (object) element by name AqlValue AqlValue::get(CollectionNameResolver const& resolver, std::string const& name, bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isObject()) { VPackSlice found(s.get(name)); if (found.isCustom()) { // _id needs special treatment mustDestroy = true; return AqlValue(transaction::helpers::extractIdString(&resolver, s, VPackSlice())); } if (!found.isNone()) { if (doCopy) { mustDestroy = true; return AqlValue(found); } // return a reference to an existing slice return AqlValue(found.begin()); } } // intentionally falls through break; } case DOCVEC: case RANGE: { // will return null break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the (object) element by name AqlValue AqlValue::get(CollectionNameResolver const& resolver, arangodb::velocypack::StringRef const& name, bool& mustDestroy, bool doCopy) const { mustDestroy = false; switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isObject()) { VPackSlice found(s.get(name)); if (found.isCustom()) { // _id needs special treatment mustDestroy = true; return AqlValue(transaction::helpers::extractIdString(&resolver, s, VPackSlice())); } if (!found.isNone()) { if (doCopy) { mustDestroy = true; return AqlValue(found); } // return a reference to an existing slice return AqlValue(found.begin()); } } // intentionally falls through break; } case DOCVEC: case RANGE: { // will return null break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief get the (object) element(s) by name AqlValue AqlValue::get(CollectionNameResolver const& resolver, std::vector const& names, bool& mustDestroy, bool doCopy) const { mustDestroy = false; if (names.empty()) { return AqlValue(AqlValueHintNull()); } switch (type()) { case VPACK_SLICE_POINTER: doCopy = false; [[fallthrough]]; case VPACK_INLINE: [[fallthrough]]; case VPACK_MANAGED_SLICE: [[fallthrough]]; case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isObject()) { if (s.isExternal()) { s = s.resolveExternal(); } VPackSlice prev; size_t const n = names.size(); for (size_t i = 0; i < n; ++i) { // fetch subattribute prev = s; s = s.get(names[i]); if (s.isExternal()) { s = s.resolveExternal(); } if (s.isNone()) { // not found return AqlValue(AqlValueHintNull()); } else if (s.isCustom()) { // _id needs special treatment if (i + 1 == n) { // x.y._id mustDestroy = true; return AqlValue(transaction::helpers::extractIdString(&resolver, s, prev)); } // x._id.y return AqlValue(AqlValueHintNull()); } else if (i + 1 < n && !s.isObject()) { return AqlValue(AqlValueHintNull()); } } if (!s.isNone()) { if (doCopy) { mustDestroy = true; return AqlValue(s); } // return a reference to an existing slice return AqlValue(s.begin()); } } // intentionally falls through break; } case DOCVEC: case RANGE: { // will return null break; } } // default is to return null return AqlValue(AqlValueHintNull()); } /// @brief check whether an object has a specific key bool AqlValue::hasKey(std::string const& name) const { switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); return (s.isObject() && s.hasKey(name)); } case DOCVEC: case RANGE: { break; } } // default is to return false return false; } /// @brief get the numeric value of an AqlValue double AqlValue::toDouble() const { bool failed; // will be ignored return toDouble(failed); } double AqlValue::toDouble(bool& failed) const { failed = false; switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isNull()) { return 0.0; } if (s.isNumber()) { return s.getNumber(); } if (s.isBoolean()) { return s.getBoolean() ? 1.0 : 0.0; } if (s.isString()) { return arangodb::aql::stringToNumber(s.copyString(), failed); } if (s.isArray()) { auto length = s.length(); if (length == 0) { return 0.0; } if (length == 1) { bool mustDestroy; // we can ignore destruction here return at(0, mustDestroy, false).toDouble(failed); } } // intentionally falls through break; } case DOCVEC: case RANGE: { if (length() == 1) { bool mustDestroy; // we can ignore destruction here return at(0, mustDestroy, false).toDouble(failed); } // will return 0 return 0.0; } } failed = true; return 0.0; } /// @brief get the numeric value of an AqlValue int64_t AqlValue::toInt64() const { switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isNumber()) { return s.getNumber(); } if (s.isBoolean()) { return s.getBoolean() ? 1 : 0; } if (s.isString()) { std::string v(s.copyString()); try { return static_cast(std::stoll(v)); } catch (...) { if (v.empty()) { return 0; } // conversion failed } } else if (s.isArray()) { auto length = s.length(); if (length == 0) { return 0; } if (length == 1) { // we can ignore destruction here bool mustDestroy; return at(0, mustDestroy, false).toInt64(); } } // intentionally falls through break; } case DOCVEC: case RANGE: { if (length() == 1) { bool mustDestroy; return at(0, mustDestroy, false).toInt64(); } // will return 0 break; } } return 0; } /// @brief whether or not the contained value evaluates to true bool AqlValue::toBoolean() const { switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { VPackSlice s(slice()); if (s.isBoolean()) { return s.getBoolean(); } if (s.isNumber()) { return (s.getNumber() != 0.0); } if (s.isString()) { return (s.getStringLength() > 0); } if (s.isArray() || s.isObject() || s.isCustom()) { // custom _id type is also true return true; } // all other cases, including Null and None return false; } case DOCVEC: case RANGE: { return true; } } return false; } /// @brief return the total size of the docvecs size_t AqlValue::docvecSize() const { TRI_ASSERT(type() == DOCVEC); size_t s = 0; for (auto const& it : *_data.docvec) { s += it->size(); } return s; } /// @brief return the size of the docvec array size_t AqlValue::sizeofDocvec() const { TRI_ASSERT(type() == DOCVEC); return sizeof(_data.docvec[0]) * _data.docvec->size(); } /// @brief construct a V8 value as input for the expression execution in V8 v8::Handle AqlValue::toV8(v8::Isolate* isolate, transaction::Methods* trx) const { switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { VPackOptions* options = trx->transactionContext()->getVPackOptions(); return TRI_VPackToV8(isolate, slice(), options); } case DOCVEC: { // calculate the result array length size_t const s = docvecSize(); // allocate the result array v8::Handle result = v8::Array::New(isolate, static_cast(s)); uint32_t j = 0; // output row count for (auto const& it : *_data.docvec) { size_t const n = it->size(); for (size_t i = 0; i < n; ++i) { result->Set(j++, it->getValueReference(i, 0).toV8(isolate, trx)); if (V8PlatformFeature::isOutOfMemory(isolate)) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } } } return result; } case RANGE: { size_t const n = _data.range->size(); Range::throwIfTooBigForMaterialization(n); v8::Handle result = v8::Array::New(isolate, static_cast(n)); for (uint32_t i = 0; i < n; ++i) { // is it safe to use a double here (precision loss)? result->Set(i, v8::Number::New(isolate, static_cast(_data.range->at( static_cast(i))))); if (i % 1000 == 0) { if (V8PlatformFeature::isOutOfMemory(isolate)) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } } } return result; } } // we shouldn't get here return v8::Null(isolate); } /// @brief materializes a value into the builder void AqlValue::toVelocyPack(VPackOptions const* options, arangodb::velocypack::Builder& builder, bool resolveExternals) const { switch (type()) { case VPACK_SLICE_POINTER: if (!resolveExternals && isManagedDocument()) { builder.addExternal(_data.pointer); break; } [[fallthrough]]; case VPACK_INLINE: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { if (resolveExternals) { bool const sanitizeExternals = true; bool const sanitizeCustom = true; arangodb::basics::VelocyPackHelper::sanitizeNonClientTypes( slice(), VPackSlice::noneSlice(), builder, options, sanitizeExternals, sanitizeCustom); } else { builder.add(slice()); } break; } case DOCVEC: { builder.openArray(); for (auto const& it : *_data.docvec) { size_t const n = it->size(); for (size_t i = 0; i < n; ++i) { it->getValueReference(i, 0).toVelocyPack(options, builder, resolveExternals); } } builder.close(); break; } case RANGE: { builder.openArray(true); size_t const n = _data.range->size(); Range::throwIfTooBigForMaterialization(n); for (size_t i = 0; i < n; ++i) { builder.add(VPackValue(_data.range->at(i))); } builder.close(); break; } } } void AqlValue::toVelocyPack(transaction::Methods* trx, arangodb::velocypack::Builder& builder, bool resolveExternals) const { toVelocyPack(trx->transactionContextPtr()->getVPackOptions(), builder, resolveExternals); } /// @brief materializes a value into the builder AqlValue AqlValue::materialize(VPackOptions const* options, bool& hasCopied, bool resolveExternals) const { switch (type()) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { hasCopied = false; return *this; } case DOCVEC: case RANGE: { bool shouldDelete = true; ConditionalDeleter> deleter(shouldDelete); std::shared_ptr> buffer(new VPackBuffer, deleter); VPackBuilder builder(buffer); toVelocyPack(options, builder, resolveExternals); hasCopied = true; return AqlValue(buffer.get(), shouldDelete); } } // we shouldn't get here hasCopied = false; return AqlValue(); } AqlValue AqlValue::materialize(transaction::Methods* trx, bool& hasCopied, bool resolveExternals) const { return materialize(trx->transactionContextPtr()->getVPackOptions(), hasCopied, resolveExternals); } /// @brief clone a value AqlValue AqlValue::clone() const { switch (type()) { case VPACK_INLINE: { // copy internal data return AqlValue(slice()); } case VPACK_SLICE_POINTER: { if (isManagedDocument()) { // copy from externally managed document. this will not copy the data return AqlValue(AqlValueHintDocumentNoCopy(_data.pointer)); } // copy from regular pointer. this may copy the data return AqlValue(_data.pointer); } case VPACK_MANAGED_SLICE: { return AqlValue(AqlValueHintCopy(_data.slice)); } case VPACK_MANAGED_BUFFER: { // copy buffer return AqlValue(VPackSlice(_data.buffer->data())); } case DOCVEC: { auto c = std::make_unique>(); c->reserve(docvecSize()); for (auto const& it : *_data.docvec) { c->emplace_back(it->slice(0, it->size())); } return AqlValue(c.release()); } case RANGE: { // create a new value with a new range return AqlValue(range()->_low, range()->_high); } } TRI_ASSERT(false); return {}; } /// @brief destroy the value's internals void AqlValue::destroy() noexcept { switch (type()) { case VPACK_INLINE: { case VPACK_SLICE_POINTER: // nothing to do return; } case VPACK_MANAGED_SLICE: { delete[] _data.slice; break; } case VPACK_MANAGED_BUFFER: { delete _data.buffer; break; } case DOCVEC: { // Will delete all ItemBlocks delete _data.docvec; break; } case RANGE: { delete _data.range; break; } } erase(); // to prevent duplicate deletion } /// @brief return the slice from the value VPackSlice AqlValue::slice() const { switch (type()) { case VPACK_INLINE: { VPackSlice s(&_data.internal[0]); if (s.isExternal()) { s = s.resolveExternal(); } return s; } case VPACK_SLICE_POINTER: { return VPackSlice(_data.pointer); } case VPACK_MANAGED_SLICE: { VPackSlice s(_data.slice); if (s.isExternal()) { s = s.resolveExternal(); } return s; } case VPACK_MANAGED_BUFFER: { VPackSlice s(_data.buffer->data()); if (s.isExternal()) { s = s.resolveExternal(); } return s; } case DOCVEC: case RANGE: { } } THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID); } /// @brief create an AqlValue from a vector of AqlItemBlock*s AqlValue AqlValue::CreateFromBlocks(transaction::Methods* trx, std::vector const& src, std::vector const& variableNames) { bool shouldDelete = true; ConditionalDeleter> deleter(shouldDelete); std::shared_ptr> buffer(new VPackBuffer, deleter); VPackBuilder builder(buffer); builder.openArray(); for (auto const& current : src) { RegisterId const n = current->getNrRegs(); std::vector registers; for (RegisterId j = 0; j < n; ++j) { // temporaries don't have a name and won't be included if (!variableNames[j].empty()) { registers.emplace_back(j); } } for (size_t i = 0; i < current->size(); ++i) { builder.openObject(); // only enumerate the registers that are left for (auto const& reg : registers) { builder.add(VPackValue(variableNames[reg])); current->getValueReference(i, reg).toVelocyPack(trx, builder, false); } builder.close(); } } builder.close(); return AqlValue(buffer.get(), shouldDelete); } /// @brief create an AqlValue from a vector of AqlItemBlock*s AqlValue AqlValue::CreateFromBlocks(transaction::Methods* trx, std::vector const& src, arangodb::aql::RegisterId expressionRegister) { bool shouldDelete = true; ConditionalDeleter> deleter(shouldDelete); std::shared_ptr> buffer(new VPackBuffer, deleter); VPackBuilder builder(buffer); builder.openArray(); for (auto const& current : src) { for (size_t i = 0; i < current->size(); ++i) { current->getValueReference(i, expressionRegister).toVelocyPack(trx, builder, false); } } builder.close(); return AqlValue(buffer.get(), shouldDelete); } /// @brief comparison for AqlValue objects int AqlValue::Compare(velocypack::Options const* options, AqlValue const& left, AqlValue const& right, bool compareUtf8) { AqlValue::AqlValueType const leftType = left.type(); AqlValue::AqlValueType const rightType = right.type(); if (leftType != rightType) { // TODO implement this case more efficiently if (leftType == RANGE || rightType == RANGE || leftType == DOCVEC || rightType == DOCVEC) { // range|docvec against x VPackBuilder leftBuilder; left.toVelocyPack(options, leftBuilder, false); VPackBuilder rightBuilder; right.toVelocyPack(options, rightBuilder, false); return arangodb::basics::VelocyPackHelper::compare(leftBuilder.slice(), rightBuilder.slice(), compareUtf8, options); } // fall-through to other types intentional } // if we get here, types are equal or can be treated as being equal switch (leftType) { case VPACK_INLINE: case VPACK_SLICE_POINTER: case VPACK_MANAGED_SLICE: case VPACK_MANAGED_BUFFER: { return arangodb::basics::VelocyPackHelper::compare(left.slice(), right.slice(), compareUtf8, options); } case DOCVEC: { // use lexicographic ordering of AqlValues regardless of block, // DOCVECs have a single register coming from ReturnNode. size_t lblock = 0; size_t litem = 0; size_t rblock = 0; size_t ritem = 0; size_t const lsize = left._data.docvec->size(); size_t const rsize = right._data.docvec->size(); if (lsize == 0 || rsize == 0) { if (lsize == rsize) { // both empty return 0; } return (lsize < rsize ? -1 : 1); } size_t lrows = left._data.docvec->at(0)->size(); size_t rrows = right._data.docvec->at(0)->size(); while (lblock < lsize && rblock < rsize) { AqlValue const& lval = left._data.docvec->at(lblock)->getValueReference(litem, 0); AqlValue const& rval = right._data.docvec->at(rblock)->getValueReference(ritem, 0); int cmp = Compare(options, lval, rval, compareUtf8); if (cmp != 0) { return cmp; } if (++litem == lrows) { litem = 0; lblock++; if (lblock < lsize) { lrows = left._data.docvec->at(lblock)->size(); } } if (++ritem == rrows) { ritem = 0; rblock++; if (rblock < rsize) { rrows = right._data.docvec->at(rblock)->size(); } } } if (lblock == lsize && rblock == rsize) { // both blocks exhausted return 0; } return (lblock < lsize ? -1 : 1); } case RANGE: { if (left.range()->_low < right.range()->_low) { return -1; } if (left.range()->_low > right.range()->_low) { return 1; } if (left.range()->_high < right.range()->_high) { return -1; } if (left.range()->_high > right.range()->_high) { return 1; } return 0; } } return 0; } int AqlValue::Compare(transaction::Methods* trx, AqlValue const& left, AqlValue const& right, bool compareUtf8) { return Compare(trx->transactionContextPtr()->getVPackOptions(), left, right, compareUtf8); } AqlValue::AqlValue(std::vector* docvec) noexcept { TRI_ASSERT(docvec != nullptr); _data.docvec = docvec; setType(AqlValueType::DOCVEC); } /// @brief return the item block at position AqlItemBlock* AqlValue::docvecAt(size_t position) const { TRI_ASSERT(isDocvec()); return _data.docvec->at(position).get(); } AqlValue::AqlValue() noexcept { // construct a slice of type None // we will simply zero-initialize the two 64 bit words _data.words[0] = 0; _data.words[1] = 0; // VPACK_INLINE must have a value of 0, and VPackSlice::None must be equal // to a NUL byte too static_assert(AqlValueType::VPACK_INLINE == 0, "invalid value for VPACK_INLINE"); } AqlValue::AqlValue(uint8_t const* pointer) { // we must get rid of Externals first here, because all // methods that use VPACK_SLICE_POINTER expect its contents // to be non-Externals if (*pointer == '\x1d') { // an external setPointer(VPackSlice(pointer).resolveExternals().begin()); } else { setPointer(pointer); } TRI_ASSERT(!VPackSlice(_data.pointer).isExternal()); } AqlValue::AqlValue(AqlValueHintNull const&) noexcept { _data.internal[0] = 0x18; // null in VPack setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(AqlValueHintBool const& v) noexcept { _data.internal[0] = v.value ? 0x1a : 0x19; // true/false in VPack setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(AqlValueHintZero const&) noexcept { _data.internal[0] = 0x30; // 0 in VPack setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(AqlValueHintDouble const& v) noexcept { double value = v.value; if (std::isnan(value) || !std::isfinite(value) || value == HUGE_VAL || value == -HUGE_VAL) { // null _data.internal[0] = 0x18; } else { // a "real" double _data.internal[0] = 0x1b; uint64_t dv; memcpy(&dv, &value, sizeof(double)); VPackValueLength vSize = sizeof(double); int i = 1; for (uint64_t x = dv; vSize > 0; vSize--) { _data.internal[i] = x & 0xff; x >>= 8; ++i; } } setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(AqlValueHintInt const& v) noexcept { int64_t value = v.value; if (value >= 0 && value <= 9) { // a smallint _data.internal[0] = static_cast(0x30U + value); } else if (value < 0 && value >= -6) { // a negative smallint _data.internal[0] = static_cast(0x40U + value); } else { uint8_t vSize = intLength(value); uint64_t x; if (vSize == 8) { x = toUInt64(value); } else { int64_t shift = 1LL << (vSize * 8 - 1); // will never overflow! x = value >= 0 ? static_cast(value) : static_cast(value + shift) + shift; } _data.internal[0] = 0x1fU + vSize; int i = 1; while (vSize-- > 0) { _data.internal[i] = x & 0xffU; ++i; x >>= 8; } } setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(AqlValueHintUInt const& v) noexcept { uint64_t value = v.value; if (value <= 9) { // a smallint _data.internal[0] = static_cast(0x30U + value); } else { int i = 1; uint8_t vSize = 0; do { vSize++; _data.internal[i] = static_cast(value & 0xffU); ++i; value >>= 8; } while (value != 0); _data.internal[0] = 0x27U + vSize; } setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(char const* value, size_t length) { TRI_ASSERT(value != nullptr); if (length == 0) { // empty string _data.internal[0] = 0x40; setType(AqlValueType::VPACK_INLINE); return; } if (length < sizeof(_data.internal) - 1) { // short string... can store it inline _data.internal[0] = static_cast(0x40 + length); memcpy(_data.internal + 1, value, length); setType(AqlValueType::VPACK_INLINE); } else if (length <= 126) { // short string... cannot store inline, but we don't need to // create a full-featured Builder object here _data.slice = new uint8_t[length + 1]; _data.slice[0] = static_cast(0x40U + length); memcpy(&_data.slice[1], value, length); setType(AqlValueType::VPACK_MANAGED_SLICE); } else { // long string // create a big enough uint8_t buffer _data.slice = new uint8_t[length + 9]; _data.slice[0] = static_cast(0xbfU); uint64_t v = length; for (uint64_t i = 0; i < 8; ++i) { _data.slice[i + 1] = v & 0xffU; v >>= 8; } memcpy(&_data.slice[9], value, length); setType(AqlValueType::VPACK_MANAGED_SLICE); } } AqlValue::AqlValue(std::string const& value) : AqlValue(value.c_str(), value.size()) {} AqlValue::AqlValue(AqlValueHintEmptyArray const&) noexcept { _data.internal[0] = 0x01; // empty array in VPack setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(AqlValueHintEmptyObject const&) noexcept { _data.internal[0] = 0x0a; // empty object in VPack setType(AqlValueType::VPACK_INLINE); } AqlValue::AqlValue(arangodb::velocypack::Buffer* buffer, bool& shouldDelete) { TRI_ASSERT(buffer != nullptr); TRI_ASSERT(shouldDelete); // here, the Buffer is still owned by the caller // intentionally do not resolve externals here // if (slice.isExternal()) { // // recursively resolve externals // slice = slice.resolveExternals(); // } if (buffer->length() < sizeof(_data.internal)) { // Use inline value memcpy(_data.internal, buffer->data(), static_cast(buffer->length())); setType(AqlValueType::VPACK_INLINE); } else { // Use managed buffer, simply reuse the pointer and adjust the original // Buffer's deleter _data.buffer = buffer; setType(AqlValueType::VPACK_MANAGED_BUFFER); shouldDelete = false; // adjust deletion control variable } } AqlValue::AqlValue(AqlValueHintDocumentNoCopy const& v) noexcept { setPointer(v.ptr); TRI_ASSERT(!VPackSlice(_data.pointer).isExternal()); } AqlValue::AqlValue(AqlValueHintCopy const& v) { TRI_ASSERT(v.ptr != nullptr); initFromSlice(VPackSlice(v.ptr)); } AqlValue::AqlValue(arangodb::velocypack::Builder const& builder) { TRI_ASSERT(builder.isClosed()); initFromSlice(builder.slice()); } AqlValue::AqlValue(arangodb::velocypack::Builder const* builder) { TRI_ASSERT(builder->isClosed()); initFromSlice(builder->slice()); } AqlValue::AqlValue(arangodb::velocypack::Slice const& slice) { initFromSlice(slice); } AqlValue::AqlValue(int64_t low, int64_t high) { _data.range = new Range(low, high); setType(AqlValueType::RANGE); } bool AqlValue::requiresDestruction() const noexcept { auto t = type(); return (t != VPACK_SLICE_POINTER && t != VPACK_INLINE); } bool AqlValue::isEmpty() const noexcept { return (_data.internal[0] == '\x00' && _data.internal[sizeof(_data.internal) - 1] == VPACK_INLINE); } bool AqlValue::isPointer() const noexcept { return type() == VPACK_SLICE_POINTER; } bool AqlValue::isManagedDocument() const noexcept { return isPointer() && (_data.internal[sizeof(_data.internal) - 2] == 1); } bool AqlValue::isRange() const noexcept { return type() == RANGE; } bool AqlValue::isDocvec() const noexcept { return type() == DOCVEC; } Range const* AqlValue::range() const { TRI_ASSERT(isRange()); return _data.range; } void AqlValue::erase() noexcept { _data.internal[0] = '\x00'; setType(AqlValueType::VPACK_INLINE); } size_t AqlValue::memoryUsage() const noexcept { auto const t = type(); switch (t) { case VPACK_INLINE: case VPACK_SLICE_POINTER: return 0; case VPACK_MANAGED_SLICE: try { return VPackSlice(_data.slice).byteSize(); } catch (...) { return 0; } case VPACK_MANAGED_BUFFER: return _data.buffer->size(); case DOCVEC: // no need to count the memory usage for the item blocks in docvec. // these have already been counted elsewhere (in ctors of AqlItemBlock // and AqlItemBlock::setValue) return sizeofDocvec(); case RANGE: return sizeof(Range); } return 0; } AqlValue::AqlValueType AqlValue::type() const noexcept { return static_cast(_data.internal[sizeof(_data.internal) - 1]); } void AqlValue::initFromSlice(arangodb::velocypack::Slice const& slice) { // intentionally do not resolve externals here // if (slice.isExternal()) { // // recursively resolve externals // slice = slice.resolveExternals(); // } arangodb::velocypack::ValueLength length = slice.byteSize(); if (length < sizeof(_data.internal)) { // Use inline value memcpy(_data.internal, slice.begin(), static_cast(length)); setType(AqlValueType::VPACK_INLINE); } else { // Use managed slice _data.slice = new uint8_t[length]; memcpy(&_data.slice[0], slice.begin(), length); setType(AqlValueType::VPACK_MANAGED_SLICE); } } void AqlValue::setType(AqlValue::AqlValueType type) noexcept { _data.internal[sizeof(_data.internal) - 1] = type; } template void AqlValue::setPointer(uint8_t const* pointer) noexcept { _data.pointer = pointer; // we use the byte at (size - 2) to distinguish between data pointing to // database documents (size[-2] == 1) and other data(size[-2] == 0) _data.internal[sizeof(_data.internal) - 2] = isManagedDoc ? 1 : 0; _data.internal[sizeof(_data.internal) - 1] = AqlValueType::VPACK_SLICE_POINTER; } template void AqlValue::setPointer(uint8_t const* pointer) noexcept; template void AqlValue::setPointer(uint8_t const* pointer) noexcept; AqlValueHintCopy::AqlValueHintCopy(uint8_t const* ptr) : ptr(ptr) {} AqlValueHintDocumentNoCopy::AqlValueHintDocumentNoCopy(uint8_t const* v) : ptr(v) {} AqlValueHintBool::AqlValueHintBool(bool v) noexcept : value(v) {} AqlValueHintDouble::AqlValueHintDouble(double v) noexcept : value(v) {} AqlValueHintInt::AqlValueHintInt(int64_t v) noexcept : value(v) {} AqlValueHintInt::AqlValueHintInt(int v) noexcept : value(int64_t(v)) {} AqlValueHintUInt::AqlValueHintUInt(uint64_t v) noexcept : value(v) {} AqlValueGuard::AqlValueGuard(AqlValue& value, bool destroy) : _value(value), _destroy(destroy) {} AqlValueGuard::~AqlValueGuard() { if (_destroy) { _value.destroy(); } } void AqlValueGuard::steal() { _destroy = false; } AqlValue& AqlValueGuard::value() { return _value; } size_t std::hash::operator()(arangodb::aql::AqlValue const& x) const noexcept { arangodb::aql::AqlValue::AqlValueType type = x.type(); size_t res = std::hash()(type); if (type == arangodb::aql::AqlValue::VPACK_INLINE) { try { return res ^ static_cast( arangodb::velocypack::Slice(&x._data.internal[0]).hash()); } catch (...) { TRI_ASSERT(false); } // fallthrough to default hashing } // treat all other pointer types the same, because they will // have the same bit representations return res ^ std::hash()(x._data.pointer); } bool std::equal_to::operator()(arangodb::aql::AqlValue const& a, arangodb::aql::AqlValue const& b) const noexcept { arangodb::aql::AqlValue::AqlValueType type = a.type(); if (type != b.type()) { return false; } if (type == arangodb::aql::AqlValue::VPACK_INLINE) { try { return arangodb::velocypack::Slice(&a._data.internal[0]) .binaryEquals(arangodb::velocypack::Slice(&b._data.internal[0])); } catch (...) { TRI_ASSERT(false); } // fallthrough to default comparison } // treat all other pointer types the same, because they will // have the same bit representations return a._data.pointer == b._data.pointer; }