//////////////////////////////////////////////////////////////////////////////// /// 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(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 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 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( // 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 arangodb::Result modifyLinks( // modify links std::unordered_set& modified, // modified collection ids ViewType& view, // modified view arangodb::velocypack::Slice const& links, // modified link definitions std::unordered_set 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 _collection; size_t _collectionsToLockOffset; // std::numeric_limits::max() == removal only std::shared_ptr _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::max()) {} State(size_t collectionsToLockOffset, size_t linkDefinitionsOffset) : _collectionsToLockOffset(collectionsToLockOffset), _linkDefinitionsOffset(linkDefinitionsOffset) {} }; std::vector collectionsToLock; std::vector> linkDefinitions; std::vector 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 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 collectionsToRemove; // track removal for potential reindex std::unordered_set 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(*(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 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(index); if (link && link->id() == id) { return link; // found required link } return nullptr; } /*static*/ std::shared_ptr 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(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, true, 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 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(index); if (link && !visitor(*link)) { return false; // abort requested } } return true; } /*static*/ arangodb::Result IResearchLinkHelper::updateLinks( std::unordered_set& modified, arangodb::LogicalView& view, arangodb::velocypack::Slice const& links, std::unordered_set const& stale /*= {}*/ ) { LOG_TOPIC("00bf9", TRACE, arangodb::iresearch::TOPIC) << "beginning IResearchLinkHelper::updateLinks"; try { if (arangodb::ServerState::instance()->isCoordinator()) { return modifyLinks( // modify modified, // modified cids LogicalView::cast(view), // modified view links, // link modifications stale // stale links ); } return modifyLinks( // modify modified, // modified cids LogicalView::cast(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 // -----------------------------------------------------------------------------