1
0
Fork 0

preparation for conditions

This commit is contained in:
Jan Steemann 2015-01-25 10:40:13 +01:00
parent 8580d49aec
commit c49986d0e8
11 changed files with 267 additions and 78 deletions

View File

@ -189,7 +189,7 @@ AstNode* Ast::createNodeFor (char const* variableName,
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST let node
/// @brief create an AST let node, without an IF condition
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeLet (char const* variableName,
@ -208,6 +208,27 @@ AstNode* Ast::createNodeLet (char const* variableName,
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST let node, with an IF condition
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeLet (char const* variableName,
AstNode const* expression,
AstNode const* condition) {
if (variableName == nullptr) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
AstNode* node = createNode(NODE_TYPE_LET);
AstNode* variable = createNodeVariable(variableName, true);
node->addMember(variable);
node->addMember(expression);
node->addMember(condition);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST filter node
////////////////////////////////////////////////////////////////////////////////
@ -1882,15 +1903,17 @@ AstNode* Ast::optimizeReference (AstNode* node) {
AstNode* Ast::optimizeLet (AstNode* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->type == NODE_TYPE_LET);
TRI_ASSERT(node->numMembers() == 2);
TRI_ASSERT(node->numMembers() >= 2);
AstNode* variable = node->getMember(0);
AstNode* expression = node->getMember(1);
bool const hasCondition = (node->numMembers() > 2);
auto v = static_cast<Variable*>(variable->getData());
TRI_ASSERT(v != nullptr);
if (expression->isConstant()) {
if (! hasCondition && expression->isConstant()) {
// if the expression assigned to the LET variable is constant, we'll store
// a pointer to the const value in the variable
// further optimizations can then use this pointer and optimize further, e.g.

View File

@ -213,13 +213,21 @@ namespace triagens {
AstNode const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST let node
/// @brief create an AST let node, without an IF condition
////////////////////////////////////////////////////////////////////////////////
AstNode* createNodeLet (char const*,
AstNode const*,
bool);
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST let node, with an IF condition
////////////////////////////////////////////////////////////////////////////////
AstNode* createNodeLet (char const*,
AstNode const*,
AstNode const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST filter node
////////////////////////////////////////////////////////////////////////////////

View File

@ -2124,7 +2124,7 @@ AqlItemBlock* EnumerateListBlock::getSome (size_t, size_t atMost) {
AqlItemBlock* cur = _buffer.front();
// get the thing we are looping over
AqlValue inVarReg = cur->getValue(_pos, _inVarRegId);
AqlValue inVarReg = cur->getValueReference(_pos, _inVarRegId);
size_t sizeInVar = 0; // to shut up compiler
// get the size of the thing we are looping over
@ -2132,7 +2132,7 @@ AqlItemBlock* EnumerateListBlock::getSome (size_t, size_t atMost) {
switch (inVarReg._type) {
case AqlValue::JSON: {
if (! inVarReg._json->isArray()) {
throwListExpectedException();
throwArrayExpectedException();
}
sizeInVar = inVarReg._json->size();
break;
@ -2160,11 +2160,11 @@ AqlItemBlock* EnumerateListBlock::getSome (size_t, size_t atMost) {
}
case AqlValue::SHAPED: {
throwListExpectedException();
throwArrayExpectedException();
}
case AqlValue::EMPTY: {
throwListExpectedException();
throwArrayExpectedException();
}
}
@ -2253,7 +2253,7 @@ size_t EnumerateListBlock::skipSome (size_t atLeast, size_t atMost) {
switch (inVarReg._type) {
case AqlValue::JSON: {
if (! inVarReg._json->isArray()) {
throwListExpectedException();
throwArrayExpectedException();
}
sizeInVar = inVarReg._json->size();
break;
@ -2278,7 +2278,7 @@ size_t EnumerateListBlock::skipSome (size_t atLeast, size_t atMost) {
case AqlValue::SHAPED:
case AqlValue::EMPTY: {
throwListExpectedException();
throwArrayExpectedException();
}
}
@ -2305,7 +2305,7 @@ size_t EnumerateListBlock::skipSome (size_t atLeast, size_t atMost) {
/// @brief create an AqlValue from the inVariable using the current _index
////////////////////////////////////////////////////////////////////////////////
AqlValue EnumerateListBlock::getAqlValue (AqlValue inVarReg) {
AqlValue EnumerateListBlock::getAqlValue (AqlValue const& inVarReg) {
switch (inVarReg._type) {
case AqlValue::JSON: {
// FIXME: is this correct? What if the copy works, but the
@ -2333,14 +2333,14 @@ AqlValue EnumerateListBlock::getAqlValue (AqlValue inVarReg) {
}
}
throwListExpectedException();
throwArrayExpectedException();
TRI_ASSERT(false);
// cannot be reached. function call above will always throw an exception
return AqlValue();
}
void EnumerateListBlock::throwListExpectedException () {
void EnumerateListBlock::throwArrayExpectedException () {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_QUERY_ARRAY_EXPECTED,
TRI_errno_string(TRI_ERROR_QUERY_ARRAY_EXPECTED) +
std::string(" as operand to FOR loop"));
@ -2359,6 +2359,8 @@ CalculationBlock::CalculationBlock (ExecutionEngine* engine,
_outReg(ExecutionNode::MaxRegisterId) {
std::unordered_set<Variable*> inVars = _expression->variables();
_inVars.reserve(inVars.size());
_inRegs.reserve(inVars.size());
for (auto it = inVars.begin(); it != inVars.end(); ++it) {
_inVars.push_back(*it);
@ -2381,6 +2383,13 @@ CalculationBlock::CalculationBlock (ExecutionEngine* engine,
TRI_ASSERT(it3 != en->getRegisterPlan()->varInfo.end());
_outReg = it3->second.registerId;
TRI_ASSERT(_outReg < ExecutionNode::MaxRegisterId);
if (en->_conditionVariable != nullptr) {
auto it = en->getRegisterPlan()->varInfo.find(en->_conditionVariable->id);
TRI_ASSERT(it != en->getRegisterPlan()->varInfo.end());
_conditionReg = it->second.registerId;
TRI_ASSERT(_conditionReg < ExecutionNode::MaxRegisterId);
}
}
CalculationBlock::~CalculationBlock () {
@ -2390,6 +2399,67 @@ int CalculationBlock::initialize () {
return ExecutionBlock::initialize();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief fill the target register in the item block with a reference to
/// another variable
////////////////////////////////////////////////////////////////////////////////
void CalculationBlock::fillBlockWithReference (AqlItemBlock* result) {
result->setDocumentCollection(_outReg, result->getDocumentCollection(_inRegs[0]));
size_t const n = result->size();
for (size_t i = 0; i < n; i++) {
// need not clone to avoid a copy, the AqlItemBlock's hash takes
// care of correct freeing:
AqlValue a = result->getValueReference(i, _inRegs[0]);
try {
result->setValue(i, _outReg, a);
}
catch (...) {
a.destroy();
throw;
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief shared code for executing a simple or a V8 expression
////////////////////////////////////////////////////////////////////////////////
void CalculationBlock::executeExpression (AqlItemBlock* result) {
std::vector<AqlValue>& data(result->getData());
std::vector<TRI_document_collection_t const*> docColls(result->getDocumentCollections());
RegisterId nrRegs = result->getNrRegs();
result->setDocumentCollection(_outReg, nullptr);
bool const hasCondition = (static_cast<CalculationNode const*>(_exeNode)->_conditionVariable != nullptr);
size_t const n = result->size();
for (size_t i = 0; i < n; i++) {
// check the condition variable (if any)
if (hasCondition) {
AqlValue conditionResult = result->getValueReference(i, _conditionReg);
if (! conditionResult.isTrue()) {
result->setValue(i, _outReg, AqlValue(new Json(Json::Null)));
continue;
}
}
// execute the expression
TRI_document_collection_t const* myCollection = nullptr;
AqlValue a = _expression->execute(_trx, docColls, data, nrRegs * i, _inVars, _inRegs, &myCollection);
try {
result->setValue(i, _outReg, a);
}
catch (...) {
a.destroy();
throw;
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief doEvaluation, private helper to do the work
////////////////////////////////////////////////////////////////////////////////
@ -2397,39 +2467,26 @@ int CalculationBlock::initialize () {
void CalculationBlock::doEvaluation (AqlItemBlock* result) {
TRI_ASSERT(result != nullptr);
size_t const n = result->size();
if (_isReference) {
// the expression is a reference to a variable only.
// the calculation is a reference to a variable only.
// no need to execute the expression at all
result->setDocumentCollection(_outReg, result->getDocumentCollection(_inRegs[0]));
fillBlockWithReference(result);
return;
}
for (size_t i = 0; i < n; i++) {
// need not clone to avoid a copy, the AqlItemBlock's hash takes
// care of correct freeing:
AqlValue a = result->getValue(i, _inRegs[0]);
try {
result->setValue(i, _outReg, a);
}
catch (...) {
a.destroy();
throw;
}
}
// non-reference expression
TRI_ASSERT(_expression != nullptr);
if (! _expression->isV8()) {
// an expression that does not require V8
executeExpression(result);
}
else {
vector<AqlValue>& data(result->getData());
vector<TRI_document_collection_t const*> docColls(result->getDocumentCollections());
RegisterId nrRegs = result->getNrRegs();
result->setDocumentCollection(_outReg, nullptr);
TRI_ASSERT(_expression != nullptr);
// must have a V8 context here to protect Expression::execute()
auto engine = _engine;
triagens::basics::ScopeGuard guard{
[&engine]() -> void {
engine->getQuery()->enterContext();
[&]() -> void {
_engine->getQuery()->enterContext();
},
[&]() -> void {
// must invalidate the expression now as we might be called from
@ -2437,24 +2494,17 @@ void CalculationBlock::doEvaluation (AqlItemBlock* result) {
if (ExecutionEngine::isRunningInCluster()) {
_expression->invalidate();
}
engine->getQuery()->exitContext();
_engine->getQuery()->exitContext();
}
};
ISOLATE;
v8::HandleScope scope(isolate); // do not delete this!
for (size_t i = 0; i < n; i++) {
// need to execute the expression
TRI_document_collection_t const* myCollection = nullptr;
AqlValue a = _expression->execute(_trx, docColls, data, nrRegs * i, _inVars, _inRegs, &myCollection);
try {
result->setValue(i, _outReg, a);
}
catch (...) {
a.destroy();
throw;
}
}
// do not merge the following function call with the same function call above!
// the V8 expression execution must happen in the scope that contains
// the V8 handle scope and the scope guard
executeExpression(result);
}
}

View File

@ -831,13 +831,13 @@ namespace triagens {
/// @brief create an AqlValue from the inVariable using the current _index
////////////////////////////////////////////////////////////////////////////////
AqlValue getAqlValue (AqlValue inVarReg);
AqlValue getAqlValue (AqlValue const&);
////////////////////////////////////////////////////////////////////////////////
/// @brief throws a "list expected" exception
/// @brief throws an "array expected" exception
////////////////////////////////////////////////////////////////////////////////
void throwListExpectedException ();
void throwArrayExpectedException ();
// -----------------------------------------------------------------------------
// --SECTION-- private variables
@ -898,13 +898,26 @@ namespace triagens {
int initialize () override;
private:
////////////////////////////////////////////////////////////////////////////////
/// @brief fill the target register in the item block with a reference to
/// another variable
////////////////////////////////////////////////////////////////////////////////
void fillBlockWithReference (AqlItemBlock*);
////////////////////////////////////////////////////////////////////////////////
/// @brief shared code for executing a simple or a V8 expression
////////////////////////////////////////////////////////////////////////////////
void executeExpression (AqlItemBlock*);
////////////////////////////////////////////////////////////////////////////////
/// @brief doEvaluation, private helper to do the work
////////////////////////////////////////////////////////////////////////////////
private:
void doEvaluation (AqlItemBlock* result);
void doEvaluation (AqlItemBlock*);
public:
@ -941,6 +954,12 @@ namespace triagens {
RegisterId _outReg;
////////////////////////////////////////////////////////////////////////////////
/// @brief condition variable register
////////////////////////////////////////////////////////////////////////////////
RegisterId _conditionReg;
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the expression is a simple variable reference
////////////////////////////////////////////////////////////////////////////////

View File

@ -1645,6 +1645,7 @@ double LimitNode::estimateCost (size_t& nrItems) const {
CalculationNode::CalculationNode (ExecutionPlan* plan,
triagens::basics::Json const& base)
: ExecutionNode(plan, base),
_conditionVariable(varFromJson(plan->getAst(), base, "conditionVariable")),
_outVariable(varFromJson(plan->getAst(), base, "outVariable")),
_expression(new Expression(plan->getAst(), base)) {
}
@ -1666,6 +1667,10 @@ void CalculationNode::toJsonHelper (triagens::basics::Json& nodes,
("outVariable", _outVariable->toJson())
("canThrow", triagens::basics::Json(_expression->canThrow()));
if (_conditionVariable != nullptr) {
json("conditionVariable", _conditionVariable->toJson());
}
// And add it:
nodes(json);
}
@ -1673,13 +1678,17 @@ void CalculationNode::toJsonHelper (triagens::basics::Json& nodes,
ExecutionNode* CalculationNode::clone (ExecutionPlan* plan,
bool withDependencies,
bool withProperties) const {
auto conditionVariable = _conditionVariable;
auto outVariable = _outVariable;
if (withProperties) {
if (_conditionVariable != nullptr) {
conditionVariable = plan->getAst()->variables()->createVariable(conditionVariable);
}
outVariable = plan->getAst()->variables()->createVariable(outVariable);
}
auto c = new CalculationNode(plan, _id, _expression->clone(),
outVariable);
auto c = new CalculationNode(plan, _id, _expression->clone(), conditionVariable, outVariable);
CloneHelper(c, plan, withDependencies, withProperties);

View File

@ -1398,8 +1398,10 @@ namespace triagens {
CalculationNode (ExecutionPlan* plan,
size_t id,
Expression* expr,
Variable const* conditionVariable,
Variable const* outVariable)
: ExecutionNode(plan, id),
_conditionVariable(conditionVariable),
_outVariable(outVariable),
_expression(expr) {
@ -1407,6 +1409,17 @@ namespace triagens {
TRI_ASSERT(_outVariable != nullptr);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief constructor
////////////////////////////////////////////////////////////////////////////////
CalculationNode (ExecutionPlan* plan,
size_t id,
Expression* expr,
Variable const* outVariable)
: CalculationNode(plan, id, expr, nullptr, outVariable) {
}
CalculationNode (ExecutionPlan*, triagens::basics::Json const& base);
////////////////////////////////////////////////////////////////////////////////
@ -1477,6 +1490,11 @@ namespace triagens {
for (auto vv : vars) {
v.emplace_back(vv);
}
if (_conditionVariable != nullptr) {
v.emplace_back(_conditionVariable);
}
return v;
}
@ -1504,6 +1522,12 @@ namespace triagens {
private:
////////////////////////////////////////////////////////////////////////////////
/// @brief an optional condition variable for the calculation
////////////////////////////////////////////////////////////////////////////////
Variable const* _conditionVariable;
////////////////////////////////////////////////////////////////////////////////
/// @brief output variable to write to
////////////////////////////////////////////////////////////////////////////////

View File

@ -419,11 +419,22 @@ ExecutionNode* ExecutionPlan::fromNodeFilter (ExecutionNode* previous,
ExecutionNode* ExecutionPlan::fromNodeLet (ExecutionNode* previous,
AstNode const* node) {
TRI_ASSERT(node != nullptr && node->type == NODE_TYPE_LET);
TRI_ASSERT(node->numMembers() == 2);
TRI_ASSERT(node->numMembers() >= 2);
AstNode const* variable = node->getMember(0);
AstNode const* expression = node->getMember(1);
Variable const* conditionVariable = nullptr;
if (node->numMembers() > 2) {
// a LET with an IF condition
auto condition = createTemporaryCalculation(node->getMember(2));
condition->addDependency(previous);
previous = condition;
conditionVariable = condition->outVariable();
}
auto v = static_cast<Variable*>(variable->getData());
ExecutionNode* en = nullptr;
@ -464,11 +475,11 @@ ExecutionNode* ExecutionPlan::fromNodeLet (ExecutionNode* previous,
// otherwise fall-through to normal behavior
}
// operand is some misc expression, including references to other variables
// operand is some misc expression, potentially including references to other variables
auto expr = new Expression(_ast, const_cast<AstNode*>(expression));
try {
en = registerNode(new CalculationNode(this, nextId(), expr, v));
en = registerNode(new CalculationNode(this, nextId(), expr, conditionVariable, v));
}
catch (...) {
// prevent memleak

View File

@ -142,7 +142,7 @@ AqlValue Expression::execute (triagens::arango::AqlTransaction* trx,
ISOLATE;
// Dump the expression in question
// std::cout << triagens::basics::Json(TRI_UNKNOWN_MEM_ZONE, _node->toJson(TRI_UNKNOWN_MEM_ZONE, true)).toString()<< "\n";
return _func->execute(isolate, _ast->query(), trx, _attributes, docColls, argv, startPos, vars, regs);
return _func->execute(isolate, _ast->query(), trx, docColls, argv, startPos, vars, regs);
}
catch (triagens::arango::Exception& ex) {
if (_ast->query()->verboseErrors()) {
@ -314,9 +314,10 @@ void Expression::analyzeExpression () {
_attributes = std::move(Ast::getReferencedAttributes(_node, isSafeForOptimization));
if (! isSafeForOptimization) {
// unfortunately there are not only top-level attribute accesses but
// also other accesses, e.g. the index values or the whole value
_attributes.clear();
// unfortunately there are not only top-level attribute accesses but
// also other accesses, e.g. the index values or accesses of the whole value.
// for example, we cannot optimize LET x = a +1 or LET x = a[0], but LET x = a._key
}
}
}
@ -345,6 +346,12 @@ void Expression::buildExpression () {
else if (_type == V8) {
// generate a V8 expression
_func = _executor->generateExpression(_node);
// optimizations for the generated function
if (_func != nullptr && ! _attributes.empty()) {
// pass which variables do not need to be fully constructed
_func->setAttributeRestrictions(_attributes);
}
}
_built = true;

View File

@ -59,7 +59,7 @@ namespace triagens {
class Expression {
enum ExpressionType {
enum ExpressionType : uint32_t {
UNPROCESSED,
JSON,
V8,
@ -173,13 +173,35 @@ namespace triagens {
/// @brief check whether this is a simple expression
////////////////////////////////////////////////////////////////////////////////
bool isSimple () {
inline bool isSimple () {
if (_type == UNPROCESSED) {
analyzeExpression();
}
return _type == SIMPLE;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether this is a JSON expression
////////////////////////////////////////////////////////////////////////////////
inline bool isJson () {
if (_type == UNPROCESSED) {
analyzeExpression();
}
return _type == JSON;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether this is a V8 expression
////////////////////////////////////////////////////////////////////////////////
inline bool isV8 () {
if (_type == UNPROCESSED) {
analyzeExpression();
}
return _type == V8;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether this is an attribute access of any degree (e.g. a.b,
/// a.b.c, ...)
@ -353,4 +375,3 @@ namespace triagens {
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)"
// End:

View File

@ -33,6 +33,7 @@
#include "Aql/Variable.h"
#include "Basics/json.h"
#include "V8/v8-conv.h"
#include "V8/v8-utils.h"
using namespace triagens::aql;
@ -71,7 +72,6 @@ V8Expression::~V8Expression () {
AqlValue V8Expression::execute (v8::Isolate* isolate,
Query* query,
triagens::arango::AqlTransaction* trx,
std::unordered_map<Variable const*, std::unordered_set<std::string>> const& attributes,
std::vector<TRI_document_collection_t const*>& docColls,
std::vector<AqlValue>& argv,
size_t startPos,
@ -80,7 +80,7 @@ AqlValue V8Expression::execute (v8::Isolate* isolate,
size_t const n = vars.size();
TRI_ASSERT_EXPENSIVE(regs.size() == n); // assert same vector length
bool const attributesPresent = ! attributes.empty();
bool const hasRestrictions = ! _attributeRestrictions.empty();
v8::Handle<v8::Object> values = v8::Object::New(isolate);
@ -90,11 +90,11 @@ AqlValue V8Expression::execute (v8::Isolate* isolate,
TRI_ASSERT_EXPENSIVE(! argv[reg].isEmpty());
if (attributesPresent && argv[startPos + reg]._type == AqlValue::JSON) {
if (hasRestrictions && argv[startPos + reg]._type == AqlValue::JSON) {
// check if we can get away with constructing a partial JSON object
auto it = attributes.find(vars[i]);
auto it = _attributeRestrictions.find(vars[i]);
if (it != attributes.end()) {
if (it != _attributeRestrictions.end()) {
// build a partial object
values->ForceSet(TRI_V8_STD_STRING(varname), argv[startPos + reg].toV8Partial(isolate, trx, (*it).second, docColls[reg]));
continue;
@ -121,11 +121,10 @@ AqlValue V8Expression::execute (v8::Isolate* isolate,
v8::TryCatch tryCatch;
auto func = v8::Local<v8::Function>::New(isolate, _func);
v8::Handle<v8::Value> result = func->Call(func, 1, args);
v8g->_query = old;
Executor::HandleV8Error(tryCatch, result);
// no exception was thrown if we get here
@ -138,6 +137,7 @@ AqlValue V8Expression::execute (v8::Isolate* isolate,
else {
// expression had a result. convert it to JSON
json = TRI_ObjectToJson(isolate, result);
// json = TRI_SimplifiedObjectToJson(isolate, result);
}
if (json == nullptr) {
@ -162,4 +162,4 @@ AqlValue V8Expression::execute (v8::Isolate* isolate,
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}"
// End:
// End

View File

@ -68,6 +68,18 @@ namespace triagens {
// --SECTION-- public methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief sets attribute restrictions. these prevent input variables to be
/// fully constructed as V8 objects (which can be very expensive), but limits
/// the objects to the actually used attributes only.
/// For example, the expression LET x = a.value + 1 will not build the full
/// object for "a", but only its "value" attribute
////////////////////////////////////////////////////////////////////////////////
void setAttributeRestrictions (std::unordered_map<Variable const*, std::unordered_set<std::string>> const& attributeRestrictions) {
_attributeRestrictions = attributeRestrictions;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief execute the expression
////////////////////////////////////////////////////////////////////////////////
@ -75,7 +87,6 @@ namespace triagens {
AqlValue execute (v8::Isolate* isolate,
Query* query,
triagens::arango::AqlTransaction*,
std::unordered_map<Variable const*, std::unordered_set<std::string>> const&,
std::vector<TRI_document_collection_t const*>&,
std::vector<AqlValue>&,
size_t,
@ -98,6 +109,12 @@ namespace triagens {
v8::Persistent<v8::Function> _func;
////////////////////////////////////////////////////////////////////////////////
/// @brief restrictions for creating the input values
////////////////////////////////////////////////////////////////////////////////
std::unordered_map<Variable const*, std::unordered_set<std::string>> _attributeRestrictions;
};
}