1
0
Fork 0
arangodb/arangod/Aql/Scopes.cpp

282 lines
7.7 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 Jan Steemann
////////////////////////////////////////////////////////////////////////////////
#include "Aql/Scopes.h"
#include "Basics/Exceptions.h"
using namespace arangodb::aql;
/// @brief create the scope
Scope::Scope(ScopeType type) : _type(type), _variables() {}
/// @brief destroy the scope
Scope::~Scope() {}
/// @brief return the name of a scope type
std::string Scope::typeName() const { return typeName(_type); }
/// @brief return the name of a scope type
std::string Scope::typeName(ScopeType type) {
switch (type) {
case AQL_SCOPE_MAIN:
return "main";
case AQL_SCOPE_SUBQUERY:
return "subquery";
case AQL_SCOPE_FOR:
return "for";
case AQL_SCOPE_COLLECT:
return "collection";
}
TRI_ASSERT(false);
return "unknown";
}
/// @brief adds a variable to the scope
void Scope::addVariable(Variable* variable) {
// intentionally like this... must always overwrite the value
// if the key already exists
_variables[variable->name] = variable;
}
/// @brief checks if a variable exists in the scope
bool Scope::existsVariable(char const* name, size_t nameLength) const {
return (getVariable(name, nameLength) != nullptr);
}
/// @brief checks if a variable exists in the scope
bool Scope::existsVariable(std::string const& name) const {
return (getVariable(name) != nullptr);
}
/// @brief returns a variable
Variable const* Scope::getVariable(char const* name, size_t nameLength) const {
std::string const varname(name, nameLength);
auto it = _variables.find(varname);
if (it == _variables.end()) {
return nullptr;
}
return (*it).second;
}
/// @brief returns a variable
Variable const* Scope::getVariable(std::string const& name) const {
auto it = _variables.find(name);
if (it == _variables.end()) {
return nullptr;
}
return (*it).second;
}
/// @brief return a variable, allowing usage of special pseudo vars such
/// as OLD and NEW
Variable const* Scope::getVariable(char const* name, size_t nameLength,
bool allowSpecial) const {
auto variable = getVariable(name, nameLength);
if (variable == nullptr && allowSpecial) {
// variable does not exist
// now try variable aliases OLD (= $OLD) and NEW (= $NEW)
if (strcmp(name, "OLD") == 0) {
variable = getVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_OLD));
} else if (strcmp(name, "NEW") == 0) {
variable = getVariable(TRI_CHAR_LENGTH_PAIR(Variable::NAME_NEW));
}
}
return variable;
}
/// @brief create the scopes
Scopes::Scopes() : _activeScopes(), _currentVariables() {
_activeScopes.reserve(4);
}
/// @brief destroy the scopes
Scopes::~Scopes() {
for (auto& it : _activeScopes) {
delete it;
}
}
/// @brief start a new scope
void Scopes::start(ScopeType type) {
auto scope = std::make_unique<Scope>(type);
_activeScopes.emplace_back(scope.get());
scope.release();
}
/// @brief end the current scope
void Scopes::endCurrent() {
TRI_ASSERT(!_activeScopes.empty());
Scope* scope = _activeScopes.back();
TRI_ASSERT(scope != nullptr);
_activeScopes.pop_back();
delete scope;
}
/// @brief end the current scope plus any FOR scopes it is nested in
void Scopes::endNested() {
TRI_ASSERT(!_activeScopes.empty());
int iterations = 0;
while (!_activeScopes.empty()) {
++iterations;
auto scope = _activeScopes.back();
TRI_ASSERT(scope != nullptr);
ScopeType type = scope->type();
if (type == AQL_SCOPE_MAIN || type == AQL_SCOPE_SUBQUERY) {
// main and subquery scopes cannot be closed here
break;
}
if (type != AQL_SCOPE_FOR && iterations >= 2) {
// if nested, do not close anything but for scopes
break;
}
endCurrent();
}
}
/// @brief adds a variable to the current scope
void Scopes::addVariable(Variable* variable) {
TRI_ASSERT(!_activeScopes.empty());
TRI_ASSERT(variable != nullptr);
for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) {
auto scope = (*it);
if (scope->existsVariable(variable->name)) {
// duplicate variable name
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_VARIABLE_REDECLARED,
variable->name.c_str());
}
}
// if this fails, there won't be a memleak
_activeScopes.back()->addVariable(variable);
}
/// @brief replaces an existing variable in the current scope
void Scopes::replaceVariable(Variable* variable) {
TRI_ASSERT(!_activeScopes.empty());
TRI_ASSERT(variable != nullptr);
for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) {
auto scope = (*it);
if (scope->existsVariable(variable->name)) {
// replace existing variable
scope->addVariable(variable);
return;
}
}
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unable to find AQL variable in scopes");
}
/// @brief checks whether a variable exists in any scope
bool Scopes::existsVariable(char const* name, size_t nameLength) const {
return (getVariable(name, nameLength) != nullptr);
}
/// @brief return a variable by name - this respects the current scopes
Variable const* Scopes::getVariable(char const* name, size_t nameLength) const {
TRI_ASSERT(!_activeScopes.empty());
for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) {
auto variable = (*it)->getVariable(name, nameLength);
if (variable != nullptr) {
return variable;
}
}
return nullptr;
}
/// @brief return a variable by name - this respects the current scopes
Variable const* Scopes::getVariable(std::string const& name) const {
TRI_ASSERT(!_activeScopes.empty());
for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) {
auto variable = (*it)->getVariable(name);
if (variable != nullptr) {
return variable;
}
}
return nullptr;
}
/// @brief return a variable by name - this respects the current scopes
Variable const* Scopes::getVariable(char const* name, size_t nameLength,
bool allowSpecial) const {
TRI_ASSERT(!_activeScopes.empty());
for (auto it = _activeScopes.rbegin(); it != _activeScopes.rend(); ++it) {
auto variable = (*it)->getVariable(name, nameLength, allowSpecial);
if (variable != nullptr) {
return variable;
}
}
return nullptr;
}
/// @brief get the $CURRENT variable
Variable const* Scopes::getCurrentVariable() const {
if (_currentVariables.empty()) {
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN,
Variable::NAME_CURRENT);
}
auto result = _currentVariables.back();
TRI_ASSERT(result != nullptr);
return result;
}
/// @brief stack a $CURRENT variable from the stack
void Scopes::stackCurrentVariable(Variable const* variable) {
_currentVariables.emplace_back(variable);
}
/// @brief unregister the $CURRENT variable from the stack
void Scopes::unstackCurrentVariable() {
TRI_ASSERT(!_currentVariables.empty());
_currentVariables.pop_back();
}