mirror of https://gitee.com/bigwinds/arangodb
995 lines
28 KiB
C++
995 lines
28 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// 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 "Basics/VelocyPackHelper.h"
|
|
#include "Utils/AqlTransaction.h"
|
|
#include "V8/v8-conv.h"
|
|
#include "V8/v8-vpack.h"
|
|
#include "VocBase/document-collection.h"
|
|
|
|
#include <velocypack/Buffer.h>
|
|
#include <velocypack/Iterator.h>
|
|
#include <velocypack/Slice.h>
|
|
#include <velocypack/velocypack-aliases.h>
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::aql;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief construct a document
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
AqlValue::AqlValue(TRI_doc_mptr_t const* mptr) {
|
|
_data.pointer = mptr->vpack();
|
|
setType(AqlValueType::VPACK_DOCUMENT);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief hashes the value
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint64_t AqlValue::hash(arangodb::AqlTransaction* trx) const {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
// we must use the slow hash function here, because a value may have
|
|
// different representations in case its an array/object/number
|
|
return slice().normalizedHash();
|
|
}
|
|
case DOCVEC:
|
|
case RANGE: {
|
|
VPackBuilder builder;
|
|
toVelocyPack(trx, builder);
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not the value contains a none value
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::isNone() const {
|
|
AqlValueType t = type();
|
|
if (t == DOCVEC || t == RANGE) {
|
|
return false;
|
|
}
|
|
|
|
return slice().isNone();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not the value is a null value
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::isNull(bool emptyIsNull) const {
|
|
AqlValueType t = type();
|
|
if (t == DOCVEC || t == RANGE) {
|
|
return false;
|
|
}
|
|
|
|
VPackSlice s(slice());
|
|
return (s.isNull() || (emptyIsNull && s.isNone()));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not the value is a boolean value
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::isBoolean() const {
|
|
AqlValueType t = type();
|
|
if (t == VPACK_DOCUMENT || t == DOCVEC || t == RANGE) {
|
|
return false;
|
|
}
|
|
return slice().isBoolean();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not the value is a number
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::isNumber() const {
|
|
AqlValueType t = type();
|
|
if (t == DOCVEC || t == RANGE) {
|
|
return false;
|
|
}
|
|
return slice().isNumber();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not the value is a string
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::isString() const {
|
|
AqlValueType t = type();
|
|
if (t == DOCVEC || t == RANGE) {
|
|
return false;
|
|
}
|
|
return slice().isString();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not the value is an object
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::isObject() const {
|
|
AqlValueType t = type();
|
|
if (t == RANGE || t == DOCVEC) {
|
|
return false;
|
|
}
|
|
return slice().isObject();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not the value is an array (note: this treats ranges
|
|
/// as arrays, too!)
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::isArray() const {
|
|
AqlValueType t = type();
|
|
if (t == RANGE || t == DOCVEC) {
|
|
return true;
|
|
}
|
|
return slice().isArray();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the (array) length (note: this treats ranges as arrays, too!)
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
size_t AqlValue::length() const {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
return 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_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
doCopy = false;
|
|
// fall-through intentional
|
|
case VPACK_EXTERNAL: {
|
|
VPackSlice s(slice());
|
|
if (s.isArray()) {
|
|
int64_t const n = static_cast<int64_t>(s.length());
|
|
if (position < 0) {
|
|
// a negative position is allowed
|
|
position = n + position;
|
|
}
|
|
if (position >= 0 && position < n) {
|
|
if (doCopy || s.byteSize() < sizeof(_data.internal)) {
|
|
mustDestroy = true;
|
|
return AqlValue(s.at(position));
|
|
}
|
|
// return a reference to an existing slice
|
|
return AqlValue(s.at(position).begin());
|
|
}
|
|
}
|
|
// fall-through intentional
|
|
break;
|
|
}
|
|
case DOCVEC: {
|
|
size_t const n = docvecSize();
|
|
if (position < 0) {
|
|
// a negative position is allowed
|
|
position = static_cast<int64_t>(n) + position;
|
|
}
|
|
if (position >= 0 && position < static_cast<int64_t>(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<int64_t>(total + it->size())) {
|
|
// found the correct vector
|
|
if (doCopy) {
|
|
mustDestroy = true;
|
|
return it->getValueReference(position - total, 0).clone();
|
|
}
|
|
return it->getValue(position - total, 0);
|
|
}
|
|
total += it->size();
|
|
}
|
|
}
|
|
// fall-through intentional
|
|
break;
|
|
}
|
|
case RANGE: {
|
|
size_t const n = range()->size();
|
|
if (position < 0) {
|
|
// a negative position is allowed
|
|
position = static_cast<int64_t>(n) + position;
|
|
}
|
|
|
|
if (position >= 0 && position < static_cast<int64_t>(n)) {
|
|
// only look up the value if it is within array bounds
|
|
VPackBuilder builder;
|
|
builder.add(VPackValue(_data.range->at(static_cast<size_t>(position))));
|
|
mustDestroy = true;
|
|
return AqlValue(builder);
|
|
}
|
|
// fall-through intentional
|
|
break;
|
|
}
|
|
}
|
|
|
|
// default is to return null
|
|
return AqlValue(arangodb::basics::VelocyPackHelper::NullValue());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the (object) element by name
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
AqlValue AqlValue::get(arangodb::AqlTransaction* trx,
|
|
std::string const& name, bool& mustDestroy,
|
|
bool doCopy) const {
|
|
mustDestroy = false;
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
doCopy = false;
|
|
// fall-through intentional
|
|
case VPACK_EXTERNAL: {
|
|
VPackSlice s(slice());
|
|
if (s.isObject()) {
|
|
VPackSlice found(s.get(name));
|
|
if (found.isCustom()) {
|
|
// _id needs special treatment
|
|
mustDestroy = true;
|
|
return AqlValue(trx->extractIdString(s));
|
|
}
|
|
if (!found.isNone()) {
|
|
if (doCopy || found.byteSize() < sizeof(_data.internal)) {
|
|
mustDestroy = true;
|
|
return AqlValue(found);
|
|
}
|
|
// return a reference to an existing slice
|
|
return AqlValue(found.begin());
|
|
}
|
|
}
|
|
// fall-through intentional
|
|
break;
|
|
}
|
|
case DOCVEC:
|
|
case RANGE: {
|
|
// will return null
|
|
break;
|
|
}
|
|
}
|
|
|
|
// default is to return null
|
|
return AqlValue(arangodb::basics::VelocyPackHelper::NullValue());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the (object) element(s) by name
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
AqlValue AqlValue::get(arangodb::AqlTransaction* trx,
|
|
std::vector<std::string> const& names,
|
|
bool& mustDestroy, bool doCopy) const {
|
|
mustDestroy = false;
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
doCopy = false;
|
|
// fall-through intentional
|
|
case VPACK_EXTERNAL: {
|
|
VPackSlice s(slice());
|
|
if (s.isObject()) {
|
|
VPackSlice found(s.get(names));
|
|
if (found.isCustom()) {
|
|
// _id needs special treatment
|
|
mustDestroy = true;
|
|
return AqlValue(trx->extractIdString(s));
|
|
}
|
|
if (!found.isNone()) {
|
|
if (doCopy || found.byteSize() < sizeof(_data.internal)) {
|
|
mustDestroy = true;
|
|
return AqlValue(found);
|
|
}
|
|
// return a reference to an existing slice
|
|
return AqlValue(found.begin());
|
|
}
|
|
}
|
|
// fall-through intentional
|
|
break;
|
|
}
|
|
case DOCVEC:
|
|
case RANGE: {
|
|
// will return null
|
|
break;
|
|
}
|
|
}
|
|
|
|
// default is to return null
|
|
return AqlValue(arangodb::basics::VelocyPackHelper::NullValue());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief check whether an object has a specific key
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool AqlValue::hasKey(arangodb::AqlTransaction* trx,
|
|
std::string const& name) const {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
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_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
VPackSlice s(slice());
|
|
if (s.isNull()) {
|
|
return 0.0;
|
|
}
|
|
if (s.isNumber()) {
|
|
return s.getNumber<double>();
|
|
}
|
|
if (s.isBoolean()) {
|
|
return s.getBoolean() ? 1.0 : 0.0;
|
|
}
|
|
if (s.isString()) {
|
|
std::string v(s.copyString());
|
|
try {
|
|
size_t behind = 0;
|
|
double value = std::stod(v, &behind);
|
|
while (behind < v.size()) {
|
|
char c = v[behind];
|
|
if (c != ' ' && c != '\t' && c != '\r' && c != '\n' && c != '\f') {
|
|
failed = true;
|
|
return 0.0;
|
|
}
|
|
++behind;
|
|
}
|
|
TRI_ASSERT(!failed);
|
|
return value;
|
|
} catch (...) {
|
|
if (v.empty()) {
|
|
return 0.0;
|
|
}
|
|
// conversion failed
|
|
break;
|
|
}
|
|
}
|
|
else 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);
|
|
}
|
|
}
|
|
// fall-through intentional
|
|
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_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
VPackSlice s(slice());
|
|
if (s.isNumber()) {
|
|
return s.getNumber<int64_t>();
|
|
}
|
|
if (s.isBoolean()) {
|
|
return s.getBoolean() ? 1 : 0;
|
|
}
|
|
if (s.isString()) {
|
|
std::string v(s.copyString());
|
|
try {
|
|
return static_cast<int64_t>(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();
|
|
}
|
|
}
|
|
// fall-through intentional
|
|
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_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
VPackSlice s(slice());
|
|
if (s.isBoolean()) {
|
|
return s.getBoolean();
|
|
}
|
|
if (s.isNumber()) {
|
|
return (s.getNumber<double>() != 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 construct a V8 value as input for the expression execution in V8
|
|
/// only construct those attributes that are needed in the expression
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
v8::Handle<v8::Value> AqlValue::toV8Partial(
|
|
v8::Isolate* isolate, arangodb::AqlTransaction* trx,
|
|
std::unordered_set<std::string> const& attributes) const {
|
|
AqlValueType t = type();
|
|
|
|
if (t == DOCVEC || t == RANGE) {
|
|
// cannot make use of these types
|
|
return v8::Null(isolate);
|
|
}
|
|
|
|
VPackOptions* options = trx->transactionContext()->getVPackOptions();
|
|
VPackSlice s(slice());
|
|
|
|
if (s.isObject()) {
|
|
v8::Handle<v8::Object> result = v8::Object::New(isolate);
|
|
|
|
// only construct those attributes needed
|
|
size_t left = attributes.size();
|
|
|
|
// we can only have got here if we had attributes
|
|
TRI_ASSERT(left > 0);
|
|
|
|
// iterate over all the object's attributes
|
|
for (auto const& it : VPackObjectIterator(s)) {
|
|
// check if we need to render this attribute
|
|
auto it2 = attributes.find(it.key.copyString());
|
|
|
|
if (it2 == attributes.end()) {
|
|
// we can skip the attribute
|
|
continue;
|
|
}
|
|
|
|
result->ForceSet(TRI_V8_STD_STRING((*it2)),
|
|
TRI_VPackToV8(isolate, it.value, options, &s));
|
|
|
|
if (--left == 0) {
|
|
// we have rendered all required attributes
|
|
break;
|
|
}
|
|
}
|
|
|
|
// return partial object
|
|
return result;
|
|
}
|
|
|
|
// fallback
|
|
return TRI_VPackToV8(isolate, s, options);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief construct a V8 value as input for the expression execution in V8
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
v8::Handle<v8::Value> AqlValue::toV8(
|
|
v8::Isolate* isolate, arangodb::AqlTransaction* trx) const {
|
|
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
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<v8::Array> result =
|
|
v8::Array::New(isolate, static_cast<int>(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));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
case RANGE: {
|
|
size_t const n = _data.range->size();
|
|
v8::Handle<v8::Array> result =
|
|
v8::Array::New(isolate, static_cast<int>(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<double>(_data.range->at(static_cast<size_t>(i)))));
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// we shouldn't get here
|
|
return v8::Null(isolate);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief materializes a value into the builder
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void AqlValue::toVelocyPack(AqlTransaction* trx,
|
|
arangodb::velocypack::Builder& builder) const {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
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(trx, builder);
|
|
}
|
|
}
|
|
builder.close();
|
|
break;
|
|
}
|
|
case RANGE: {
|
|
builder.openArray();
|
|
size_t const n = _data.range->size();
|
|
for (size_t i = 0; i < n; ++i) {
|
|
builder.add(VPackValue(_data.range->at(i)));
|
|
}
|
|
builder.close();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief materializes a value into the builder
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
AqlValue AqlValue::materialize(AqlTransaction* trx, bool& hasCopied) const {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
hasCopied = false;
|
|
return *this;
|
|
}
|
|
case DOCVEC: {
|
|
VPackBuilder builder;
|
|
toVelocyPack(trx, builder);
|
|
hasCopied = true;
|
|
return AqlValue(builder);
|
|
}
|
|
case RANGE: {
|
|
VPackBuilder builder;
|
|
toVelocyPack(trx, builder);
|
|
hasCopied = true;
|
|
return AqlValue(builder);
|
|
}
|
|
}
|
|
|
|
// we shouldn't get here
|
|
hasCopied = false;
|
|
return AqlValue();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief clone a value
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
AqlValue AqlValue::clone() const {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER: {
|
|
return AqlValue(_data.pointer);
|
|
}
|
|
case VPACK_INLINE: {
|
|
// copy internal data
|
|
return AqlValue(slice());
|
|
}
|
|
case VPACK_EXTERNAL: {
|
|
// copy buffer
|
|
VPackValueLength length = _data.buffer->size();
|
|
auto buffer = new VPackBuffer<uint8_t>(length);
|
|
buffer->append(reinterpret_cast<char const*>(_data.buffer->data()), length);
|
|
return AqlValue(buffer);
|
|
}
|
|
case DOCVEC: {
|
|
auto c = std::make_unique<std::vector<AqlItemBlock*>>();
|
|
c->reserve(docvecSize());
|
|
try {
|
|
for (auto const& it : *_data.docvec) {
|
|
c->emplace_back(it->slice(0, it->size()));
|
|
}
|
|
} catch (...) {
|
|
for (auto& it : *c) {
|
|
delete it;
|
|
}
|
|
throw;
|
|
}
|
|
return AqlValue(c.release());
|
|
}
|
|
case RANGE: {
|
|
// create a new value with a new range
|
|
return AqlValue(range()->_low, range()->_high);
|
|
}
|
|
}
|
|
|
|
TRI_ASSERT(false);
|
|
return AqlValue();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroy the value's internals
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void AqlValue::destroy() {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE: {
|
|
// nothing to do
|
|
break;
|
|
}
|
|
case VPACK_EXTERNAL: {
|
|
delete _data.buffer;
|
|
erase(); // to prevent duplicate deletion
|
|
break;
|
|
}
|
|
case DOCVEC: {
|
|
for (auto& it : *_data.docvec) {
|
|
delete it;
|
|
}
|
|
delete _data.docvec;
|
|
erase(); // to prevent duplicate deletion
|
|
break;
|
|
}
|
|
case RANGE: {
|
|
delete _data.range;
|
|
erase(); // to prevent duplicate deletion
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the slice from the value
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
VPackSlice AqlValue::slice() const {
|
|
switch (type()) {
|
|
case VPACK_DOCUMENT:
|
|
case VPACK_POINTER: {
|
|
return VPackSlice(_data.pointer);
|
|
}
|
|
case VPACK_INLINE: {
|
|
VPackSlice s(&_data.internal[0]);
|
|
if (s.isExternal()) {
|
|
s = VPackSlice(s.getExternal());
|
|
}
|
|
return s;
|
|
}
|
|
case VPACK_EXTERNAL: {
|
|
VPackSlice s(_data.buffer->data());
|
|
if (s.isExternal()) {
|
|
s = VPackSlice(s.getExternal());
|
|
}
|
|
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(
|
|
arangodb::AqlTransaction* trx, std::vector<AqlItemBlock*> const& src,
|
|
std::vector<std::string> const& variableNames) {
|
|
|
|
VPackBuilder builder;
|
|
builder.openArray();
|
|
|
|
for (auto const& current : src) {
|
|
RegisterId const n = current->getNrRegs();
|
|
|
|
std::vector<RegisterId> registers;
|
|
for (RegisterId j = 0; j < n; ++j) {
|
|
// temporaries don't have a name and won't be included
|
|
if (variableNames[j][0] != '\0') {
|
|
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);
|
|
}
|
|
|
|
builder.close();
|
|
}
|
|
}
|
|
|
|
builder.close();
|
|
return AqlValue(builder);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief create an AqlValue from a vector of AqlItemBlock*s
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
AqlValue AqlValue::CreateFromBlocks(
|
|
arangodb::AqlTransaction* trx, std::vector<AqlItemBlock*> const& src,
|
|
arangodb::aql::RegisterId expressionRegister) {
|
|
|
|
VPackBuilder builder;
|
|
builder.openArray();
|
|
|
|
for (auto const& current : src) {
|
|
for (size_t i = 0; i < current->size(); ++i) {
|
|
current->getValueReference(i, expressionRegister).toVelocyPack(trx, builder);
|
|
}
|
|
}
|
|
|
|
builder.close();
|
|
return AqlValue(builder);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief 3-way comparison for AqlValue objects
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int AqlValue::Compare(arangodb::AqlTransaction* trx, AqlValue const& left,
|
|
AqlValue const& right,
|
|
bool compareUtf8) {
|
|
VPackOptions* options = trx->transactionContext()->getVPackOptions();
|
|
|
|
AqlValue::AqlValueType const leftType = left.type();
|
|
AqlValue::AqlValueType const rightType = right.type();
|
|
|
|
if (leftType != rightType) {
|
|
if (leftType == RANGE || rightType == RANGE || leftType == DOCVEC || rightType == DOCVEC) {
|
|
// range|docvec against x
|
|
VPackBuilder leftBuilder;
|
|
left.toVelocyPack(trx, leftBuilder);
|
|
|
|
VPackBuilder rightBuilder;
|
|
right.toVelocyPack(trx, rightBuilder);
|
|
|
|
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_DOCUMENT:
|
|
case VPACK_POINTER:
|
|
case VPACK_INLINE:
|
|
case VPACK_EXTERNAL: {
|
|
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();
|
|
|
|
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(trx, lval, rval, compareUtf8);
|
|
|
|
if (cmp != 0) {
|
|
return cmp;
|
|
}
|
|
if (++litem == lsize) {
|
|
litem = 0;
|
|
lblock++;
|
|
}
|
|
if (++ritem == rsize) {
|
|
ritem = 0;
|
|
rblock++;
|
|
}
|
|
}
|
|
|
|
if (lblock == lsize && rblock == rsize) {
|
|
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;
|
|
}
|
|
|