mirror of https://gitee.com/bigwinds/arangodb
905 lines
33 KiB
C++
905 lines
33 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2018 ArangoDB 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 Andrey Abramov
|
|
/// @author Vasiliy Nabatchikov
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Basics/Common.h"
|
|
|
|
#include "IResearchLinkHelper.h"
|
|
#include "IResearchCommon.h"
|
|
#include "IResearchFeature.h"
|
|
#include "IResearchLink.h"
|
|
#include "IResearchLinkMeta.h"
|
|
#include "IResearchView.h"
|
|
#include "IResearchViewCoordinator.h"
|
|
#include "VelocyPackHelper.h"
|
|
#include "Basics/StaticStrings.h"
|
|
#include "Basics/StringUtils.h"
|
|
#include "Logger/Logger.h"
|
|
#include "Logger/LogMacros.h"
|
|
#include "RestServer/SystemDatabaseFeature.h"
|
|
#include "RestServer/DatabaseFeature.h"
|
|
#include "StorageEngine/EngineSelectorFeature.h"
|
|
#include "StorageEngine/StorageEngine.h"
|
|
#include "Transaction/Methods.h"
|
|
#include "Transaction/StandaloneContext.h"
|
|
#include "Utils/CollectionNameResolver.h"
|
|
#include "Utils/ExecContext.h"
|
|
#include "velocypack/Iterator.h"
|
|
#include "VocBase/LogicalCollection.h"
|
|
#include "VocBase/Methods/Indexes.h"
|
|
|
|
namespace {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief the string representing the link type
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
std::string const& LINK_TYPE = arangodb::iresearch::DATA_SOURCE_TYPE.name();
|
|
|
|
arangodb::Result canUseAnalyzers( // validate
|
|
arangodb::iresearch::IResearchLinkMeta const& meta, // metadata
|
|
TRI_vocbase_t const& defaultVocbase // default vocbase
|
|
) {
|
|
auto* sysDatabase = arangodb::application_features::ApplicationServer::lookupFeature< // find feature
|
|
arangodb::SystemDatabaseFeature // featue type
|
|
>();
|
|
auto sysVocbase = sysDatabase ? sysDatabase->use() : nullptr;
|
|
|
|
for (auto& pool: meta._analyzerDefinitions) {
|
|
if (!pool) {
|
|
continue; // skip invalid entries
|
|
}
|
|
|
|
bool result;
|
|
|
|
if (sysVocbase) {
|
|
result = arangodb::iresearch::IResearchAnalyzerFeature::canUse( // validate
|
|
arangodb::iresearch::IResearchAnalyzerFeature::normalize( // normalize
|
|
pool->name(), defaultVocbase, *sysVocbase // args
|
|
), // analyzer
|
|
arangodb::auth::Level::RO // auth level
|
|
);
|
|
} else {
|
|
result = arangodb::iresearch::IResearchAnalyzerFeature::canUse( // validate
|
|
pool->name(), arangodb::auth::Level::RO // args
|
|
);
|
|
}
|
|
|
|
if (!result) {
|
|
return {
|
|
TRI_ERROR_FORBIDDEN,
|
|
std::string("read access is forbidden to arangosearch analyzer '") + pool->name() + "'"
|
|
};
|
|
}
|
|
}
|
|
|
|
// for (auto& field: meta._fields) {
|
|
// TRI_ASSERT(field.value().get()); // ensured by UniqueHeapInstance constructor
|
|
// auto& entry = field.value();
|
|
// auto res = canUseAnalyzers(*entry, defaultVocbase);
|
|
//
|
|
// if (!res.ok()) {
|
|
// return res;
|
|
// }
|
|
// }
|
|
|
|
return {};
|
|
}
|
|
|
|
arangodb::Result createLink( // create link
|
|
arangodb::LogicalCollection& collection, // link collection
|
|
arangodb::LogicalView const& view, // link view
|
|
arangodb::velocypack::Slice definition // link definition
|
|
) {
|
|
try {
|
|
bool isNew = false;
|
|
auto link = collection.createIndex(definition, isNew);
|
|
|
|
if (!(link && isNew)) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_INTERNAL, // code
|
|
std::string("failed to create link between arangosearch view '") + view.name() + "' and collection '" + collection.name() + "'"
|
|
);
|
|
}
|
|
|
|
// ensure link is synchronized after upgrade in single-server
|
|
if (arangodb::ServerState::instance()->isSingleServer()) {
|
|
auto* db = arangodb::DatabaseFeature::DATABASE;
|
|
|
|
if (db && (db->checkVersion() || db->upgrade())) {
|
|
// FIXME find a better way to retrieve an IResearch Link
|
|
// cannot use static_cast/reinterpret_cast since Index is not related to
|
|
// IResearchLink
|
|
auto impl = std::dynamic_pointer_cast<arangodb::iresearch::IResearchLink>(link);
|
|
|
|
if (impl) {
|
|
return impl->commit();
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (arangodb::basics::Exception const& e) {
|
|
return arangodb::Result(e.code(), e.what());
|
|
}
|
|
|
|
return arangodb::Result();
|
|
}
|
|
|
|
arangodb::Result createLink( // create link
|
|
arangodb::LogicalCollection& collection, // link collection
|
|
arangodb::iresearch::IResearchViewCoordinator const& view, // link view
|
|
arangodb::velocypack::Slice definition // link definition
|
|
) {
|
|
static const std::function<bool(irs::string_ref const& key)> acceptor = [](
|
|
irs::string_ref const& key // json key
|
|
)->bool {
|
|
// ignored fields
|
|
return key != arangodb::StaticStrings::IndexType // type field
|
|
&& key != arangodb::iresearch::StaticStrings::ViewIdField; // view id field
|
|
};
|
|
arangodb::velocypack::Builder builder;
|
|
|
|
builder.openObject();
|
|
builder.add( // add
|
|
arangodb::StaticStrings::IndexType, // key
|
|
arangodb::velocypack::Value(LINK_TYPE) // value
|
|
);
|
|
builder.add( // add
|
|
arangodb::iresearch::StaticStrings::ViewIdField, // key
|
|
arangodb::velocypack::Value(view.guid()) // value
|
|
);
|
|
|
|
if (!arangodb::iresearch::mergeSliceSkipKeys(builder, definition, acceptor)) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_INTERNAL, // code
|
|
std::string("failed to generate definition while creating link between arangosearch view '") + view.name() + "' and collection '" + collection.name() + "'"
|
|
);
|
|
}
|
|
|
|
builder.close();
|
|
|
|
arangodb::velocypack::Builder tmp;
|
|
|
|
return arangodb::methods::Indexes::ensureIndex( // ensure index
|
|
&collection, builder.slice(), true, tmp // args
|
|
);
|
|
}
|
|
|
|
template<typename ViewType>
|
|
arangodb::Result dropLink( // drop link
|
|
arangodb::LogicalCollection& collection, // link collection
|
|
arangodb::iresearch::IResearchLink const& link // link to drop
|
|
) {
|
|
// don't need to create an extra transaction inside arangodb::methods::Indexes::drop(...)
|
|
if (!collection.dropIndex(link.id())) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_INTERNAL, // code
|
|
std::string("failed to drop link '") + std::to_string(link.id()) + "' from collection '" + collection.name() + "'"
|
|
);
|
|
}
|
|
|
|
return arangodb::Result();
|
|
}
|
|
|
|
template<>
|
|
arangodb::Result dropLink<arangodb::iresearch::IResearchViewCoordinator>( // drop link
|
|
arangodb::LogicalCollection& collection, // link collection
|
|
arangodb::iresearch::IResearchLink const& link // link to drop
|
|
) {
|
|
arangodb::velocypack::Builder builder;
|
|
|
|
builder.openObject();
|
|
builder.add( // add
|
|
arangodb::StaticStrings::IndexId, // key
|
|
arangodb::velocypack::Value(link.id()) // value
|
|
);
|
|
builder.close();
|
|
|
|
return arangodb::methods::Indexes::drop(&collection, builder.slice());
|
|
}
|
|
|
|
template <typename ViewType>
|
|
arangodb::Result modifyLinks( // modify links
|
|
std::unordered_set<TRI_voc_cid_t>& modified, // modified collection ids
|
|
ViewType& view, // modified view
|
|
arangodb::velocypack::Slice const& links, // modified link definitions
|
|
std::unordered_set<TRI_voc_cid_t> const& stale = {} // stale links
|
|
) {
|
|
LOG_TOPIC("4bdd2", DEBUG, arangodb::iresearch::TOPIC)
|
|
<< "link modification request for view '" << view.name() << "', original definition:" << links.toString();
|
|
|
|
if (!links.isObject()) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_BAD_PARAMETER, // code
|
|
std::string("error parsing link parameters from json for arangosearch view '") + view.name() + "'"
|
|
);
|
|
}
|
|
|
|
struct State {
|
|
std::shared_ptr<arangodb::LogicalCollection> _collection;
|
|
size_t _collectionsToLockOffset; // std::numeric_limits<size_t>::max() == removal only
|
|
std::shared_ptr<arangodb::iresearch::IResearchLink> _link;
|
|
size_t _linkDefinitionsOffset;
|
|
arangodb::Result _result; // operation result
|
|
bool _stale = false; // request came from the stale list
|
|
explicit State(size_t collectionsToLockOffset)
|
|
: State(collectionsToLockOffset, std::numeric_limits<size_t>::max()) {}
|
|
State(size_t collectionsToLockOffset, size_t linkDefinitionsOffset)
|
|
: _collectionsToLockOffset(collectionsToLockOffset), _linkDefinitionsOffset(linkDefinitionsOffset) {}
|
|
};
|
|
std::vector<std::string> collectionsToLock;
|
|
std::vector<std::pair<arangodb::velocypack::Builder, arangodb::iresearch::IResearchLinkMeta>> linkDefinitions;
|
|
std::vector<State> linkModifications;
|
|
|
|
for (arangodb::velocypack::ObjectIterator linksItr(links); linksItr.valid(); ++linksItr) {
|
|
auto collection = linksItr.key();
|
|
|
|
if (!collection.isString()) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_BAD_PARAMETER, // code
|
|
std::string("error parsing link parameters from json for arangosearch view '") + view.name() + "' offset '" + arangodb::basics::StringUtils::itoa(linksItr.index()) + '"'
|
|
);
|
|
}
|
|
|
|
auto link = linksItr.value();
|
|
auto collectionName = collection.copyString();
|
|
|
|
if (link.isNull()) {
|
|
linkModifications.emplace_back(collectionsToLock.size());
|
|
collectionsToLock.emplace_back(collectionName);
|
|
|
|
continue; // only removal requested
|
|
}
|
|
|
|
arangodb::velocypack::Builder normalized;
|
|
|
|
normalized.openObject();
|
|
|
|
// @note: DBServerAgencySync::getLocalCollections(...) generates
|
|
// 'forPersistence' definitions that are then compared in
|
|
// Maintenance.cpp:compareIndexes(...) via
|
|
// arangodb::Index::Compare(...)
|
|
// hence must use 'isCreation=true' for normalize(...) to match
|
|
auto res = arangodb::iresearch::IResearchLinkHelper::normalize( // normalize to validate analyzer definitions
|
|
normalized, link, true, view.vocbase(), &view.primarySort() // args
|
|
);
|
|
|
|
if (!res.ok()) {
|
|
return res;
|
|
}
|
|
|
|
normalized.close();
|
|
link = normalized.slice(); // use normalized definition for index creation
|
|
|
|
LOG_TOPIC("4bdd1", DEBUG, arangodb::iresearch::TOPIC)
|
|
<< "link modification request for view '" << view.name() << "', normalized definition:" << link.toString();
|
|
|
|
static const std::function<bool(irs::string_ref const& key)> acceptor = [](
|
|
irs::string_ref const& key // json key
|
|
)->bool {
|
|
// ignored fields
|
|
return key != arangodb::StaticStrings::IndexType // type field
|
|
&& key != arangodb::iresearch::StaticStrings::ViewIdField; // view id field
|
|
};
|
|
arangodb::velocypack::Builder namedJson;
|
|
|
|
namedJson.openObject();
|
|
namedJson.add( // add
|
|
arangodb::StaticStrings::IndexType, // key
|
|
arangodb::velocypack::Value(LINK_TYPE) // value
|
|
);
|
|
namedJson.add( // add
|
|
arangodb::iresearch::StaticStrings::ViewIdField, // key
|
|
arangodb::velocypack::Value(view.guid()) // value
|
|
);
|
|
|
|
if (!arangodb::iresearch::mergeSliceSkipKeys(namedJson, link, acceptor)) {
|
|
return arangodb::Result(
|
|
TRI_ERROR_INTERNAL,
|
|
std::string("failed to update link definition with the view name while updating arangosearch view '") + view.name() + "' collection '" + collectionName + "'"
|
|
);
|
|
}
|
|
|
|
namedJson.close();
|
|
|
|
std::string error;
|
|
arangodb::iresearch::IResearchLinkMeta linkMeta;
|
|
|
|
if (!linkMeta.init(namedJson.slice(), true, error, &view.vocbase())) { // validated and normalized with 'isCreation=true' above via normalize(...)
|
|
return arangodb::Result(
|
|
TRI_ERROR_BAD_PARAMETER,
|
|
std::string("error parsing link parameters from json for arangosearch view '") + view.name() + "' collection '" + collectionName + "' error '" + error + "'"
|
|
);
|
|
}
|
|
|
|
linkModifications.emplace_back(collectionsToLock.size(), linkDefinitions.size());
|
|
collectionsToLock.emplace_back(collectionName);
|
|
linkDefinitions.emplace_back(std::move(namedJson), std::move(linkMeta));
|
|
}
|
|
|
|
auto trxCtx = // transaction context
|
|
arangodb::transaction::StandaloneContext::Create(view.vocbase());
|
|
|
|
// add removals for any 'stale' links not found in the 'links' definition
|
|
for (auto& id: stale) {
|
|
if (!trxCtx->resolver().getCollection(id)) {
|
|
LOG_TOPIC("4bdd7", WARN, arangodb::iresearch::TOPIC)
|
|
<< "request for removal of a stale link to a missing collection '" << id << "', ignoring";
|
|
|
|
continue; // skip adding removal requests to stale links to non-existent collections (already dropped)
|
|
}
|
|
|
|
linkModifications.emplace_back(collectionsToLock.size());
|
|
linkModifications.back()._stale = true;
|
|
collectionsToLock.emplace_back(std::to_string(id));
|
|
}
|
|
|
|
if (collectionsToLock.empty()) {
|
|
return arangodb::Result(); // nothing to update
|
|
}
|
|
|
|
arangodb::ExecContextScope scope(arangodb::ExecContext::superuser()); // required to remove links from non-RW collections
|
|
|
|
{
|
|
std::unordered_set<TRI_voc_cid_t> collectionsToRemove; // track removal for potential reindex
|
|
std::unordered_set<TRI_voc_cid_t> collectionsToUpdate; // track reindex requests
|
|
|
|
// resolve corresponding collection and link
|
|
for (auto itr = linkModifications.begin(); itr != linkModifications.end();) {
|
|
auto& state = *itr;
|
|
auto& collectionName = collectionsToLock[state._collectionsToLockOffset];
|
|
|
|
state._collection = trxCtx->resolver().getCollection(collectionName);
|
|
|
|
if (!state._collection) {
|
|
// remove modification state if removal of non-existant link on
|
|
// non-existant collection
|
|
if (state._linkDefinitionsOffset >= linkDefinitions.size()) { // link removal request
|
|
itr = linkModifications.erase(itr);
|
|
|
|
continue;
|
|
}
|
|
|
|
return arangodb::Result(
|
|
TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND,
|
|
std::string(
|
|
"failed to get collection while updating arangosearch view '") +
|
|
view.name() + "' collection '" + collectionName + "'");
|
|
}
|
|
|
|
state._link =
|
|
arangodb::iresearch::IResearchLinkHelper::find(*(state._collection), view);
|
|
|
|
// remove modification state if removal of non-existant link
|
|
if (!state._link // links currently does not exist
|
|
&& state._linkDefinitionsOffset >= linkDefinitions.size()) { // link removal request
|
|
|
|
LOG_TOPIC("c7111", TRACE, arangodb::iresearch::TOPIC)
|
|
<< "found link for collection '" << state._collection->name()
|
|
<< "' - slated for removal";
|
|
|
|
view.unlink(state._collection->id()); // drop any stale data for the specified collection
|
|
itr = linkModifications.erase(itr);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (state._link // links currently exists
|
|
&& !state._stale // did not originate from the stale list (remove
|
|
// stale links lower)
|
|
&& state._linkDefinitionsOffset >= linkDefinitions.size()) { // link removal request
|
|
LOG_TOPIC("a58da", TRACE, arangodb::iresearch::TOPIC)
|
|
<< "found link '" << state._link->id() << "' for collection '"
|
|
<< state._collection->name() << "' - slated for removal";
|
|
auto cid = state._collection->id();
|
|
|
|
// remove duplicate removal requests (e.g. by name + by CID)
|
|
if (collectionsToRemove.find(cid) != collectionsToRemove.end()) { // removal previously requested
|
|
itr = linkModifications.erase(itr);
|
|
|
|
continue;
|
|
}
|
|
|
|
collectionsToRemove.emplace(cid);
|
|
}
|
|
|
|
if (state._link // links currently exists
|
|
&& state._linkDefinitionsOffset < linkDefinitions.size()) { // link update request
|
|
LOG_TOPIC("8419d", TRACE, arangodb::iresearch::TOPIC)
|
|
<< "found link '" << state._link->id() << "' for collection '"
|
|
<< state._collection->name() << "' - slated for update";
|
|
collectionsToUpdate.emplace(state._collection->id());
|
|
}
|
|
|
|
LOG_TOPIC_IF("e9a8c", TRACE, arangodb::iresearch::TOPIC, state._link)
|
|
<< "found link '" << state._link->id() << "' for collection '"
|
|
<< state._collection->name() << "' - unsure what to do";
|
|
|
|
LOG_TOPIC_IF("b01be", TRACE, arangodb::iresearch::TOPIC, !state._link)
|
|
<< "no link found for collection '" << state._collection->name() << "'";
|
|
|
|
++itr;
|
|
}
|
|
|
|
// remove modification state if it came from the stale list and a separate
|
|
// removal or reindex also present
|
|
for (auto itr = linkModifications.begin(); itr != linkModifications.end();) {
|
|
auto& state = *itr;
|
|
auto cid = state._collection->id();
|
|
|
|
// remove modification if came from the stale list and a separate reindex
|
|
// or removal request also present otherwise consider 'stale list
|
|
// requests' as valid removal requests
|
|
if (state._stale // originated from the stale list
|
|
&& (collectionsToRemove.find(cid) !=
|
|
collectionsToRemove.end() // also has a removal request
|
|
// (duplicate removal request)
|
|
|| collectionsToUpdate.find(cid) != collectionsToUpdate.end())) { // also has a reindex request
|
|
itr = linkModifications.erase(itr);
|
|
LOG_TOPIC("5c99e", TRACE, arangodb::iresearch::TOPIC)
|
|
<< "modification unnecessary, came from stale list, for link '"
|
|
<< state._link->id() << "'";
|
|
|
|
continue;
|
|
}
|
|
|
|
++itr;
|
|
}
|
|
|
|
// remove modification state if no change on existing link and reindex not
|
|
// requested
|
|
for (auto itr = linkModifications.begin(); itr != linkModifications.end();) {
|
|
auto& state = *itr;
|
|
|
|
// remove modification if removal request with an update request also
|
|
// present
|
|
if (state._link // links currently exists
|
|
&& state._linkDefinitionsOffset >= linkDefinitions.size() // link removal request
|
|
&& collectionsToUpdate.find(state._collection->id()) !=
|
|
collectionsToUpdate.end()) { // also has a reindex request
|
|
itr = linkModifications.erase(itr);
|
|
LOG_TOPIC("1d095", TRACE, arangodb::iresearch::TOPIC)
|
|
<< "modification unnecessary, remove+update, for link '"
|
|
<< state._link->id() << "'";
|
|
|
|
continue;
|
|
}
|
|
|
|
// remove modification state if no change on existing link or
|
|
if (state._link // links currently exists
|
|
&& state._linkDefinitionsOffset < linkDefinitions.size() // link creation request
|
|
&& collectionsToRemove.find(state._collection->id()) ==
|
|
collectionsToRemove.end() // not a reindex request
|
|
&& *(state._link) == linkDefinitions[state._linkDefinitionsOffset].second) { // link meta not modified
|
|
itr = linkModifications.erase(itr);
|
|
LOG_TOPIC("4c196", TRACE, arangodb::iresearch::TOPIC)
|
|
<< "modification unnecessary, no change, for link '"
|
|
<< state._link->id() << "'";
|
|
|
|
continue;
|
|
}
|
|
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
// execute removals
|
|
for (auto& state : linkModifications) {
|
|
if (state._link) { // link removal or recreate request
|
|
state._result = dropLink<ViewType>(*(state._collection), *(state._link));
|
|
modified.emplace(state._collection->id());
|
|
}
|
|
}
|
|
|
|
// execute additions
|
|
for (auto& state: linkModifications) {
|
|
if (state._result.ok() // valid state (unmodified or after removal)
|
|
&& state._linkDefinitionsOffset < linkDefinitions.size()) {
|
|
state._result = createLink( // create link
|
|
*(state._collection), // collection
|
|
view, // view
|
|
linkDefinitions[state._linkDefinitionsOffset].first.slice() // definition
|
|
);
|
|
modified.emplace(state._collection->id());
|
|
}
|
|
}
|
|
|
|
std::string error;
|
|
|
|
// validate success
|
|
for (auto& state: linkModifications) {
|
|
if (!state._result.ok()) {
|
|
error.append(error.empty() ? "" : ", ") // separator
|
|
.append(collectionsToLock[state._collectionsToLockOffset]) // collection name
|
|
.append(": ").append(std::to_string(state._result.errorNumber())) // error code
|
|
.append(" ").append(state._result.errorMessage()); // error message
|
|
}
|
|
}
|
|
|
|
if (error.empty()) {
|
|
return arangodb::Result();
|
|
}
|
|
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_ARANGO_ILLEGAL_STATE, // code
|
|
std::string("failed to update links while updating arangosearch view '") + view.name() + "', retry same request or examine errors for collections: " + error
|
|
);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace arangodb {
|
|
namespace iresearch {
|
|
|
|
/*static*/ VPackSlice const& IResearchLinkHelper::emptyIndexSlice() {
|
|
static const struct EmptySlice {
|
|
VPackBuilder _builder;
|
|
VPackSlice _slice;
|
|
EmptySlice() {
|
|
VPackBuilder fieldsBuilder;
|
|
|
|
fieldsBuilder.openArray();
|
|
fieldsBuilder.close(); // empty array
|
|
_builder.openObject();
|
|
_builder.add(arangodb::StaticStrings::IndexFields,
|
|
fieldsBuilder.slice()); // empty array
|
|
_builder.add(arangodb::StaticStrings::IndexType,
|
|
arangodb::velocypack::Value(LINK_TYPE)); // the index type required by Index
|
|
_builder.close(); // object with just one field required by the Index
|
|
// constructor
|
|
_slice = _builder.slice();
|
|
}
|
|
} emptySlice;
|
|
|
|
return emptySlice._slice;
|
|
}
|
|
|
|
/*static*/ bool IResearchLinkHelper::equal( // are link definitions equal
|
|
arangodb::velocypack::Slice const& lhs, // left hand side
|
|
arangodb::velocypack::Slice const& rhs // right hand side
|
|
) {
|
|
if (!lhs.isObject() || !rhs.isObject()) {
|
|
return false;
|
|
}
|
|
|
|
auto lhsViewSlice = lhs.get(StaticStrings::ViewIdField);
|
|
auto rhsViewSlice = rhs.get(StaticStrings::ViewIdField);
|
|
|
|
if (!lhsViewSlice.binaryEquals(rhsViewSlice)) {
|
|
if (!lhsViewSlice.isString() || !rhsViewSlice.isString()) {
|
|
return false;
|
|
}
|
|
|
|
auto ls = lhsViewSlice.copyString();
|
|
auto rs = rhsViewSlice.copyString();
|
|
|
|
if (ls.size() > rs.size()) {
|
|
std::swap(ls, rs);
|
|
}
|
|
|
|
// in the cluster, we may have identifiers of the form
|
|
// `cxxx/` and `cxxx/yyy` which should be considered equal if the
|
|
// one is a prefix of the other up to the `/`
|
|
if (ls.empty() || ls.back() != '/' || ls.compare(rs.substr(0, ls.size()))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::string errorField;
|
|
IResearchLinkMeta lhsMeta;
|
|
IResearchLinkMeta rhsMeta;
|
|
|
|
return lhsMeta.init(lhs, true, errorField) // left side meta valid (for db-server analyzer validation should have already apssed on coordinator)
|
|
&& rhsMeta.init(rhs, true, errorField) // right side meta valid (for db-server analyzer validation should have already apssed on coordinator)
|
|
&& lhsMeta == rhsMeta; // left meta equal right meta
|
|
}
|
|
|
|
/*static*/ std::shared_ptr<IResearchLink> IResearchLinkHelper::find( // find link
|
|
LogicalCollection const& collection, // collection to search
|
|
TRI_idx_iid_t id // index id to find
|
|
) {
|
|
auto index = collection.lookupIndex(id);
|
|
|
|
if (!index || arangodb::Index::TRI_IDX_TYPE_IRESEARCH_LINK != index->type()) {
|
|
return nullptr; // not an IResearchLink
|
|
}
|
|
|
|
// TODO FIXME find a better way to retrieve an IResearch Link
|
|
// cannot use static_cast/reinterpret_cast since Index is not related to
|
|
// IResearchLink
|
|
auto link = std::dynamic_pointer_cast<IResearchLink>(index);
|
|
|
|
if (link && link->id() == id) {
|
|
return link; // found required link
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/*static*/ std::shared_ptr<IResearchLink> IResearchLinkHelper::find(
|
|
LogicalCollection const& collection, LogicalView const& view) {
|
|
for (auto& index : collection.getIndexes()) {
|
|
if (!index || arangodb::Index::TRI_IDX_TYPE_IRESEARCH_LINK != index->type()) {
|
|
continue; // not an IResearchLink
|
|
}
|
|
|
|
// TODO FIXME find a better way to retrieve an iResearch Link
|
|
// cannot use static_cast/reinterpret_cast since Index is not related to
|
|
// IResearchLink
|
|
auto link = std::dynamic_pointer_cast<IResearchLink>(index);
|
|
|
|
if (link && *link == view) {
|
|
return link; // found required link
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/*static*/ arangodb::Result IResearchLinkHelper::normalize( // normalize definition
|
|
arangodb::velocypack::Builder& normalized, // normalized definition (out-param)
|
|
arangodb::velocypack::Slice definition, // source definition
|
|
bool isCreation, // definition for index creation
|
|
TRI_vocbase_t const& vocbase, // index vocbase
|
|
IResearchViewSort const* primarySort /* = nullptr */
|
|
) {
|
|
if (!normalized.isOpenObject()) {
|
|
return arangodb::Result(
|
|
TRI_ERROR_BAD_PARAMETER,
|
|
std::string("invalid output buffer provided for arangosearch link normalized definition generation")
|
|
);
|
|
}
|
|
|
|
std::string error;
|
|
IResearchLinkMeta meta;
|
|
|
|
// @note: implicit analyzer validation via IResearchLinkMeta done in 2 places:
|
|
// IResearchLinkHelper::normalize(...) if creating via collection API
|
|
// ::modifyLinks(...) (via call to normalize(...) prior to getting
|
|
// superuser) if creating via IResearchLinkHelper API
|
|
if (!meta.init(definition, true, error, &vocbase)) {
|
|
return arangodb::Result(
|
|
TRI_ERROR_BAD_PARAMETER,
|
|
std::string("error parsing arangosearch link parameters from json: ") + error
|
|
);
|
|
}
|
|
|
|
auto res = canUseAnalyzers(meta, vocbase); // same validation as in modifyLinks(...) for Views API
|
|
|
|
if (!res.ok()) {
|
|
return res;
|
|
}
|
|
|
|
normalized.add(
|
|
arangodb::StaticStrings::IndexType, arangodb::velocypack::Value(LINK_TYPE)
|
|
);
|
|
|
|
// copy over IResearch Link identifier
|
|
if (definition.hasKey(arangodb::StaticStrings::IndexId)) {
|
|
normalized.add( // preserve field
|
|
arangodb::StaticStrings::IndexId, // key
|
|
definition.get(arangodb::StaticStrings::IndexId) // value
|
|
);
|
|
}
|
|
|
|
// copy over IResearch View identifier
|
|
if (definition.hasKey(StaticStrings::ViewIdField)) {
|
|
normalized.add(
|
|
StaticStrings::ViewIdField, definition.get(StaticStrings::ViewIdField)
|
|
);
|
|
}
|
|
|
|
if (definition.hasKey(arangodb::StaticStrings::IndexInBackground)) {
|
|
normalized.add( // preserve field
|
|
arangodb::StaticStrings::IndexInBackground, // key
|
|
definition.get(arangodb::StaticStrings::IndexInBackground) // value
|
|
);
|
|
}
|
|
|
|
if (primarySort) {
|
|
// normalize sort if specified
|
|
meta._sort = *primarySort;
|
|
}
|
|
|
|
if (!meta.json(normalized, isCreation, nullptr, &vocbase)) { // 'isCreation' is set when forPersistence
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_BAD_PARAMETER, // code
|
|
"error generating arangosearch link normalized definition" // message
|
|
);
|
|
}
|
|
|
|
return arangodb::Result();
|
|
}
|
|
|
|
/*static*/ std::string const& IResearchLinkHelper::type() noexcept {
|
|
return LINK_TYPE;
|
|
}
|
|
|
|
/*static*/ arangodb::Result IResearchLinkHelper::validateLinks( // validate
|
|
TRI_vocbase_t& vocbase, // vocbase
|
|
arangodb::velocypack::Slice const& links // link definitions
|
|
) {
|
|
if (!links.isObject()) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_BAD_PARAMETER, // code
|
|
std::string("while validating arangosearch link definition, error: definition is not an object")
|
|
);
|
|
}
|
|
|
|
size_t offset = 0;
|
|
arangodb::CollectionNameResolver resolver(vocbase);
|
|
|
|
for (arangodb::velocypack::ObjectIterator itr(links); // setup
|
|
itr.valid(); // condition
|
|
++itr, ++offset // step
|
|
) {
|
|
auto collectionName = itr.key();
|
|
auto linkDefinition = itr.value();
|
|
|
|
if (!collectionName.isString()) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_BAD_PARAMETER, // code
|
|
std::string("while validating arangosearch link definition, error: collection at offset ") + std::to_string(offset) + " is not a string"
|
|
);
|
|
}
|
|
|
|
auto collection = resolver.getCollection(collectionName.copyString());
|
|
|
|
if (!collection) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND, // code
|
|
std::string("while validating arangosearch link definition, error: collection '") + collectionName.copyString() + "' not found"
|
|
);
|
|
}
|
|
|
|
// check link auth as per https://github.com/arangodb/backlog/issues/459
|
|
if (arangodb::ExecContext::CURRENT // have context
|
|
&& !arangodb::ExecContext::CURRENT->canUseCollection(vocbase.name(), collection->name(), arangodb::auth::Level::RO)) {
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_FORBIDDEN, // code
|
|
std::string("while validating arangosearch link definition, error: collection '") + collectionName.copyString() + "' not authorized for read access"
|
|
);
|
|
}
|
|
|
|
IResearchLinkMeta meta;
|
|
std::string errorField;
|
|
|
|
if (!linkDefinition.isNull()) { // have link definition
|
|
if (!meta.init(linkDefinition, false, errorField, &vocbase)) { // for db-server analyzer validation should have already applied on coordinator
|
|
return arangodb::Result( // result
|
|
TRI_ERROR_BAD_PARAMETER, // code
|
|
errorField.empty()
|
|
? (std::string("while validating arangosearch link definition, error: invalid link definition for collection '") + collectionName.copyString() + "': " + linkDefinition.toString())
|
|
: (std::string("while validating arangosearch link definition, error: invalid link definition for collection '") + collectionName.copyString() + "' error in attribute: " + errorField)
|
|
);
|
|
}
|
|
// validate analyzers origin
|
|
// analyzer should be either from same database as view (and collection) or from system database
|
|
{
|
|
const auto& currentVocbase = vocbase.name();
|
|
for (const auto& analyzer : meta._analyzerDefinitions) {
|
|
TRI_ASSERT(analyzer); // should be checked in meta init
|
|
if (ADB_UNLIKELY(!analyzer)) {
|
|
continue;
|
|
}
|
|
auto* pool = analyzer.get();
|
|
auto analyzerVocbase = IResearchAnalyzerFeature::extractVocbaseName(pool->name());
|
|
|
|
if (!IResearchAnalyzerFeature::analyzerReachableFromDb(analyzerVocbase, currentVocbase, true)) {
|
|
return {
|
|
TRI_ERROR_BAD_PARAMETER,
|
|
std::string("Analyzer '")
|
|
.append(pool->name())
|
|
.append("' is not accessible from database '")
|
|
.append(currentVocbase).append("'")
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return arangodb::Result();
|
|
}
|
|
|
|
/*static*/ bool IResearchLinkHelper::visit( // visit
|
|
arangodb::LogicalCollection const& collection, // collection to visit
|
|
std::function<bool(IResearchLink& link)> const& visitor // visitor to apply
|
|
) {
|
|
for (auto& index: collection.getIndexes()) {
|
|
if (!index // not a valid index
|
|
|| arangodb::Index::TRI_IDX_TYPE_IRESEARCH_LINK != index->type()) {
|
|
continue; // not an IResearchLink
|
|
}
|
|
|
|
// TODO FIXME find a better way to retrieve an iResearch Link
|
|
// cannot use static_cast/reinterpret_cast since Index is not related to IResearchLink
|
|
auto link = std::dynamic_pointer_cast<IResearchLink>(index);
|
|
|
|
if (link && !visitor(*link)) {
|
|
return false; // abort requested
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*static*/ arangodb::Result IResearchLinkHelper::updateLinks(
|
|
std::unordered_set<TRI_voc_cid_t>& modified,
|
|
arangodb::LogicalView& view,
|
|
arangodb::velocypack::Slice const& links,
|
|
std::unordered_set<TRI_voc_cid_t> const& stale /*= {}*/
|
|
) {
|
|
LOG_TOPIC("00bf9", TRACE, arangodb::iresearch::TOPIC)
|
|
<< "beginning IResearchLinkHelper::updateLinks";
|
|
try {
|
|
if (arangodb::ServerState::instance()->isCoordinator()) {
|
|
return modifyLinks<IResearchViewCoordinator>( // modify
|
|
modified, // modified cids
|
|
LogicalView::cast<IResearchViewCoordinator>(view), // modified view
|
|
links, // link modifications
|
|
stale // stale links
|
|
);
|
|
}
|
|
|
|
return modifyLinks<IResearchView>( // modify
|
|
modified, // modified cids
|
|
LogicalView::cast<IResearchView>(view), // modified view
|
|
links, // link modifications
|
|
stale // stale links
|
|
);
|
|
} catch (arangodb::basics::Exception& e) {
|
|
LOG_TOPIC("72dde", WARN, arangodb::iresearch::TOPIC)
|
|
<< "caught exception while updating links for arangosearch view '" << view.name() << "': " << e.code() << " " << e.what();
|
|
IR_LOG_EXCEPTION();
|
|
|
|
return arangodb::Result(
|
|
e.code(),
|
|
std::string("error updating links for arangosearch view '") + view.name() + "'"
|
|
);
|
|
} catch (std::exception const& e) {
|
|
LOG_TOPIC("9d5f8", WARN, arangodb::iresearch::TOPIC)
|
|
<< "caught exception while updating links for arangosearch view '" << view.name() << "': " << e.what();
|
|
IR_LOG_EXCEPTION();
|
|
|
|
return arangodb::Result(
|
|
TRI_ERROR_BAD_PARAMETER,
|
|
std::string("error updating links for arangosearch view '") + view.name() + "'"
|
|
);
|
|
} catch (...) {
|
|
LOG_TOPIC("ff0b6", WARN, arangodb::iresearch::TOPIC)
|
|
<< "caught exception while updating links for arangosearch view '" << view.name() << "'";
|
|
IR_LOG_EXCEPTION();
|
|
|
|
return arangodb::Result(
|
|
TRI_ERROR_BAD_PARAMETER,
|
|
std::string("error updating links for arangosearch view '") + view.name() + "'"
|
|
);
|
|
}
|
|
}
|
|
|
|
} // iresearch
|
|
} // arangodb
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- END-OF-FILE
|
|
// -----------------------------------------------------------------------------
|