From 7c25902b27b64c7bd0a3efa59445261d07f58cf8 Mon Sep 17 00:00:00 2001 From: Vasiliy Date: Mon, 2 Apr 2018 15:27:48 +0300 Subject: [PATCH] issue 355.3: allow IResearchLink creation, as opposed to IResearch View update, to direct which CIDs get included in an IResearchView snapshot --- arangod/IResearch/IResearchLink.cpp | 10 +- arangod/IResearch/IResearchLink.h | 8 +- arangod/IResearch/IResearchView.cpp | 426 ++++--- arangod/IResearch/IResearchView.h | 67 +- arangod/IResearch/IResearchViewMeta.h | 2 +- arangod/MMFiles/MMFilesEngine.cpp | 2 +- arangod/RestHandler/RestViewHandler.cpp | 6 +- arangod/VocBase/LogicalView.cpp | 105 +- arangod/VocBase/LogicalView.h | 42 +- arangod/VocBase/vocbase.cpp | 27 +- tests/IResearch/IResearchLink-test.cpp | 269 ++++- tests/IResearch/IResearchView-test.cpp | 1286 ++++++++++++++++------ tests/IResearch/StorageEngineMock.cpp | 91 +- tests/IResearch/StorageEngineMock.h | 4 +- tests/VocBase/LogicalDataSource-test.cpp | 23 +- 15 files changed, 1735 insertions(+), 633 deletions(-) diff --git a/arangod/IResearch/IResearchLink.cpp b/arangod/IResearch/IResearchLink.cpp index 1ad55abf1e..0d856bc944 100644 --- a/arangod/IResearch/IResearchLink.cpp +++ b/arangod/IResearch/IResearchLink.cpp @@ -76,11 +76,16 @@ IResearchLink::IResearchLink( arangodb::LogicalCollection* collection ): _collection(collection), _defaultId(0), // 0 is never a valid id + _dropCollectionInDestructor(false), _id(iid), _view(nullptr) { } IResearchLink::~IResearchLink() { + if (_dropCollectionInDestructor) { + drop(); + } + unload(); // disassociate from view if it has not been done yet } @@ -181,7 +186,8 @@ int IResearchLink::drop() { } } - // FIXME TODO remove link via update properties on view + _dropCollectionInDestructor = false; // will do drop now + return _view->drop(_collection->id()); } @@ -260,6 +266,7 @@ bool IResearchLink::init(arangodb::velocypack::Slice const& definition) { return false; } + _dropCollectionInDestructor = view->emplace(collection()->id()); // track if this is the instance that called emplace _meta = std::move(meta); _view = std::move(view); @@ -541,6 +548,7 @@ int IResearchLink::unload() { } } + _dropCollectionInDestructor = false; // valid link (since unload(..) called), should not be dropped _view = nullptr; // mark as unassociated _viewLock.unlock(); // release read-lock on the IResearch View diff --git a/arangod/IResearch/IResearchLink.h b/arangod/IResearch/IResearchLink.h index 0333012eda..a8ee229dea 100644 --- a/arangod/IResearch/IResearchLink.h +++ b/arangod/IResearch/IResearchLink.h @@ -205,14 +205,12 @@ class IResearchLink { // FIXME TODO remove once View::updateProperties(...) will be fixed to write // the update delta into the WAL marker instead of the full persisted state // FIXME TODO remove #include "IResearchView.h" -// friend arangodb::Result IResearchView::updatePropertiesImpl( -// arangodb::velocypack::Slice const&, bool, bool -// ); + // friend arangodb::Result IResearchView::updateProperties(arangodb::velocypack::Slice const&, bool); friend class IResearchView; - LogicalCollection* _collection; // the linked collection TRI_voc_cid_t _defaultId; // the identifier of the desired view (iff _view == nullptr) + bool _dropCollectionInDestructor; // collection should be dropped from view in the destructor (for the case where init(..) is called folowed by distructor) TRI_idx_iid_t const _id; // the index identifier IResearchLinkMeta _meta; // how this collection should be indexed mutable irs::async_utils::read_write_mutex _mutex; // for use with _view to allow asynchronous disassociation @@ -232,4 +230,4 @@ int EnhanceJsonIResearchLink( NS_END // iresearch NS_END // arangodb -#endif +#endif \ No newline at end of file diff --git a/arangod/IResearch/IResearchView.cpp b/arangod/IResearch/IResearchView.cpp index e54801088f..3293579713 100644 --- a/arangod/IResearch/IResearchView.cpp +++ b/arangod/IResearch/IResearchView.cpp @@ -48,7 +48,6 @@ #include "Aql/SortCondition.h" #include "Basics/Result.h" #include "Basics/files.h" -#include "Basics/WriteLocker.h" #include "Logger/Logger.h" #include "Logger/LogMacros.h" #include "StorageEngine/EngineSelectorFeature.h" @@ -306,6 +305,27 @@ std::shared_ptr findFirstMatchingLink( return nullptr; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief compute the data path to user for iresearch persisted-store +/// get base path from DatabaseServerFeature (similar to MMFilesEngine) +/// the path is hardcoded to reside under: +/// /- +/// similar to the data path calculation for collections +//////////////////////////////////////////////////////////////////////////////// +irs::utf8_path getPersistedPath( + arangodb::DatabasePathFeature const& dbPathFeature, TRI_voc_cid_t id +) { + irs::utf8_path dataPath(dbPathFeature.directory()); + static const std::string subPath("databases"); + + dataPath /= subPath; + dataPath /= arangodb::iresearch::IResearchView::type().name(); + dataPath += "-"; + dataPath += std::to_string(id); + + return dataPath; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief inserts ArangoDB document into an IResearch data store //////////////////////////////////////////////////////////////////////////////// @@ -340,6 +360,100 @@ inline void insertDocument( doc.insert(irs::action::store, primaryKey); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief persist view definition to the storage engine +/// if in-recovery then register a post-recovery lambda for persistence +/// @return success +//////////////////////////////////////////////////////////////////////////////// +arangodb::Result persistProperties( + arangodb::LogicalView const& view, + arangodb::iresearch::IResearchView::AsyncSelf::ptr asyncSelf +) { + auto* engine = arangodb::EngineSelectorFeature::ENGINE; + + if (!engine) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("failure to get storage engine while persisting definition for LogicalView '") + view.name() + "'" + ); + } + + if (!engine->inRecovery()) { + // change view throws exception on error + try { + engine->changeView(view.vocbase(), view.id(), &view, true); + } catch (std::exception const& e) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("caught exception during persistance of properties for IResearch View '") + view.name() + "': " + e.what() + ); + } catch (...) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("caught exception during persistance of properties for IResearch View '") + view.name() + "'" + ); + } + + return arangodb::Result(); + } + + auto* feature = + arangodb::iresearch::getFeature("Database"); + + if (!feature) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("failure to get 'Database' feature while persisting definition for LogicalView '") + view.name() + "'" + ); + } + + return feature->registerPostRecoveryCallback( + [&view, asyncSelf]()->arangodb::Result { + auto* engine = arangodb::EngineSelectorFeature::ENGINE; + + if (!engine) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("failure to get storage engine while persisting definition for LogicalView") + ); + } + + if (!asyncSelf) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("invalid view instance passed while persisting definition for LogicalView") + ); + } + + SCOPED_LOCK(asyncSelf->mutex()); + + if (!asyncSelf->get()) { + LOG_TOPIC(INFO, arangodb::iresearch::IResearchFeature::IRESEARCH) + << "no view instance available while persisting definition for LogicalView"; + + return arangodb::Result(); // nothing to persist, view allready deallocated + } + + // change view throws exception on error + try { + engine->changeView(view.vocbase(), view.id(), &view, true); + } catch (std::exception const& e) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("caught exception during persistance of properties for IResearch View '") + view.name() + "': " + e.what() + ); + } catch (...) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("caught exception during persistance of properties for IResearch View '") + view.name() + "'" + ); + } + + return arangodb::Result(); + } + ); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief syncs an IResearch DataStore if required /// @return a sync was executed @@ -767,16 +881,16 @@ IResearchView::PersistedStore::PersistedStore(irs::utf8_path&& path) IResearchView::IResearchView( TRI_vocbase_t* vocbase, arangodb::velocypack::Slice const& info, - irs::utf8_path&& persistedPath, + arangodb::DatabasePathFeature const& dbPathFeature, bool isNew -) : DBServerLogicalView(vocbase, info, isNew), +): DBServerLogicalView(vocbase, info, isNew), FlushTransaction(toString(*this)), _asyncMetaRevision(1), _asyncSelf(irs::memory::make_unique(this)), _asyncTerminate(false), _memoryNode(&_memoryNodes[0]), // set current memory node (arbitrarily 0) _toFlush(&_memoryNodes[1]), // set flush-pending memory node (not same as _memoryNode) - _storePersisted(std::move(persistedPath)), + _storePersisted(getPersistedPath(dbPathFeature, id())), _threadPool(0, 0), // 0 == create pool with no threads, i.e. not running anything _inRecovery(false) { // set up in-recovery insertion hooks @@ -791,7 +905,7 @@ IResearchView::IResearchView( auto* viewPtr = view->get(); if (!viewPtr) { - LOG_TOPIC(WARN, arangodb::iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "Invalid call to post-recovery callback of iResearch view"; return arangodb::Result(); // view no longer in recovery state @@ -800,25 +914,25 @@ IResearchView::IResearchView( viewPtr->verifyKnownCollections(); if (viewPtr->_storePersisted) { - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "starting persisted-sync sync for iResearch view '" << viewPtr->id() << "'"; try { viewPtr->_storePersisted.sync(); } catch (std::exception const& e) { - LOG_TOPIC(ERR, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(ERR, IResearchFeature::IRESEARCH) << "caught exception while committing persisted store for iResearch view '" << viewPtr->id() << "': " << e.what(); return arangodb::Result(TRI_ERROR_INTERNAL, e.what()); } catch (...) { - LOG_TOPIC(ERR, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(ERR, IResearchFeature::IRESEARCH) << "caught exception while committing persisted store for iResearch view '" << viewPtr->id() << "'"; return arangodb::Result(TRI_ERROR_INTERNAL); } - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "finished persisted-sync sync for iResearch view '" << viewPtr->id() << "'"; } @@ -856,7 +970,7 @@ IResearchView::IResearchView( auto res = viewPtr->finish(state.id(), false); if (TRI_ERROR_NO_ERROR != res) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed to finish abort while processing write-transaction callback for IResearch view '" << viewPtr->name() << "'"; } @@ -866,10 +980,10 @@ IResearchView::IResearchView( auto res = viewPtr->finish(state.id(), true); if (TRI_ERROR_NO_ERROR != res) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed to finish commit while processing write-transaction callback for IResearch view '" << viewPtr->name() << "'"; } else if (state.waitForSync() && !viewPtr->sync()) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed to sync while processing write-transaction callback for IResearch view '" << viewPtr->name() << "'"; } @@ -1119,17 +1233,17 @@ void IResearchView::drop() { // remove persisted data store directory if present if (_storePersisted._path.exists_directory(exists) && (!exists || _storePersisted._path.remove())) { - DBServerLogicalView::drop(); deleted(true); + return; // success } } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while removing iResearch view '" << id() << "': " << e.what(); IR_LOG_EXCEPTION(); throw; } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while removing iResearch view '" << id() << "'"; IR_LOG_EXCEPTION(); throw; @@ -1140,8 +1254,25 @@ void IResearchView::drop() { int IResearchView::drop(TRI_voc_cid_t cid) { std::shared_ptr shared_filter(iresearch::FilterFactory::filter(cid)); - ReadMutex mutex(_mutex); // '_storeByTid' can be asynchronously updated + WriteMutex mutex(_mutex); // '_meta' and '_storeByTid' can be asynchronously updated SCOPED_LOCK(mutex); + auto cid_itr = _meta._collections.find(cid); + + if (cid_itr != _meta._collections.end()) { + auto result = persistProperties(*this, _asyncSelf); + + if (!result.ok()) { + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) + << "failed to persist logical view while dropping collection ' " << cid + << "' from IResearch View '" << name() << "': " << result.errorMessage(); + + return result.errorNumber(); + } + + _meta._collections.erase(cid_itr); + } + + mutex.unlock(true); // downgrade to a read-lock // ........................................................................... // if an exception occurs below than a drop retry would most likely happen @@ -1160,12 +1291,12 @@ int IResearchView::drop(TRI_voc_cid_t cid) { return TRI_ERROR_NO_ERROR; } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while removing from iResearch view '" << id() << "', collection '" << cid << "': " << e.what(); IR_LOG_EXCEPTION(); } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while removing from iResearch view '" << id() << "', collection '" << cid << "'"; IR_LOG_EXCEPTION(); @@ -1174,6 +1305,45 @@ int IResearchView::drop(TRI_voc_cid_t cid) { return TRI_ERROR_INTERNAL; } +bool IResearchView::emplace(TRI_voc_cid_t cid) { + WriteMutex mutex(_mutex); // '_meta' can be asynchronously updated + SCOPED_LOCK(mutex); + arangodb::Result result; + + if (!_meta._collections.emplace(cid).second) { + return false; + } + + try { + result = persistProperties(*this, _asyncSelf); + + if (result.ok()) { + return true; + } + } catch (std::exception const& e) { + _meta._collections.erase(cid); // undo meta modification + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) + << "caught exception during persisting of logical view while emplacing collection ' " << cid + << "' into IResearch View '" << name() << "': " << e.what(); + IR_LOG_EXCEPTION(); + throw; + } catch (...) { + _meta._collections.erase(cid); // undo meta modification + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) + << "caught exception during persisting of logical view while emplacing collection ' " << cid + << "' into IResearch View '" << name() << "'"; + IR_LOG_EXCEPTION(); + throw; + } + + _meta._collections.erase(cid); // undo meta modification + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) + << "failed to persist logical view while emplacing collection ' " << cid + << "' into IResearch View '" << name() << "': " << result.errorMessage(); + + return false; +} + int IResearchView::finish(TRI_voc_tid_t tid, bool commit) { std::vector> removals; DataStore trxStore; @@ -1230,12 +1400,12 @@ int IResearchView::finish(TRI_voc_tid_t tid, bool commit) { return TRI_ERROR_NO_ERROR; } catch (std::exception const& e) { - LOG_TOPIC(ERR, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(ERR, IResearchFeature::IRESEARCH) << "caught exception while committing transaction for iResearch view '" << id() << "', tid '" << tid << "': " << e.what(); IR_LOG_EXCEPTION(); } catch (...) { - LOG_TOPIC(ERR, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(ERR, IResearchFeature::IRESEARCH) << "caught exception while committing transaction for iResearch view '" << id() << "', tid '" << tid << "'"; IR_LOG_EXCEPTION(); @@ -1283,11 +1453,11 @@ arangodb::Result IResearchView::commit() { return TRI_ERROR_NO_ERROR; } catch (std::exception const& e) { - LOG_TOPIC(ERR, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(ERR, IResearchFeature::IRESEARCH) << "caught exception while committing memory store for iResearch view '" << id() << "': " << e.what(); IR_LOG_EXCEPTION(); } catch (...) { - LOG_TOPIC(ERR, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(ERR, IResearchFeature::IRESEARCH) << "caught exception while committing memory store for iResearch view '" << id(); IR_LOG_EXCEPTION(); } @@ -1363,7 +1533,7 @@ void IResearchView::getPropertiesVPack( linkBuilder.openObject(); if (!ptr->json(linkBuilder, false)) { - LOG_TOPIC(WARN, arangodb::iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed to generate json for IResearch link '" << ptr->id() << "' while generating json for IResearch view '" << id() << "'"; continue; // skip invalid link definitions @@ -1377,12 +1547,12 @@ void IResearchView::getPropertiesVPack( trx.commit(); } catch (std::exception const& e) { - LOG_TOPIC(WARN, arangodb::iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while generating json for IResearch view '" << id() << "': " << e.what(); IR_LOG_EXCEPTION(); return; // do not add 'links' section } catch (...) { - LOG_TOPIC(WARN, arangodb::iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while generating json for IResearch view '" << id() << "'"; IR_LOG_EXCEPTION(); return; // do not add 'links' section @@ -1440,16 +1610,16 @@ int IResearchView::insert( return TRI_ERROR_NO_ERROR; } - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed inserting into iResearch view '" << id() << "', collection '" << cid << "', revision '" << documentId.id() << "'"; } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while inserting into iResearch view '" << id() << "', collection '" << cid << "', revision '" << documentId.id() << "': " << e.what(); IR_LOG_EXCEPTION(); } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while inserting into iResearch view '" << id() << "', collection '" << cid << "', revision '" << documentId.id() << "'"; IR_LOG_EXCEPTION(); @@ -1526,18 +1696,18 @@ int IResearchView::insert( try { if (!store->_writer->insert(insert)) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed inserting batch into iResearch view '" << id() << "', collection '" << cid; return TRI_ERROR_INTERNAL; } store->_writer->commit(); // no need to consolidate if batch size is set correctly } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while inserting batch into iResearch view '" << id() << "', collection '" << cid << e.what(); IR_LOG_EXCEPTION(); } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while inserting batch into iResearch view '" << id() << "', collection '" << cid; IR_LOG_EXCEPTION(); } @@ -1573,18 +1743,8 @@ arangodb::Result IResearchView::link( builder.close(); std::unordered_set collections; - auto result = updateLinks(collections, *vocbase, *this, builder.slice()); - if (result.ok()) { - WriteMutex mutex(_mutex); // '_meta' can be asynchronously read - SCOPED_LOCK(mutex); - - collections.insert(_meta._collections.begin(), _meta._collections.end()); - validateLinks(collections, *vocbase, *this); // remove invalid cids (no such collection or no such link) - _meta._collections = std::move(collections); - } - - return result; + return updateLinks(collections, *vocbase, *this, builder.slice()); } /*static*/ std::shared_ptr IResearchView::make( @@ -1592,59 +1752,31 @@ arangodb::Result IResearchView::link( arangodb::velocypack::Slice const& info, bool isNew ) { - auto const id = readViewId(info); - - if (0 == id) { - // invalid ID - LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) - << "got invalid view identifier while constructing IResearch view"; - return nullptr; - } - auto* feature = arangodb::iresearch::getFeature("DatabasePath"); if (!feature) { LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) - << "failure to find feature 'DatabasePath' while constructing IResearch view '" - << id << "' in database '" << vocbase.id() << "'"; + << "failure to find feature 'DatabasePath' while constructing IResearch View in database '" << vocbase.id() << "'"; return nullptr; } - // get base path from DatabaseServerFeature (similar to MMFilesEngine) - // the path is hardcoded to reside under: - // /- - // similar to the data path calculation for collections - irs::utf8_path dataPath(feature->directory()); - static std::string subPath("databases"); - - dataPath /= subPath; - dataPath /= arangodb::iresearch::IResearchView::type().name(); - dataPath += "-"; - dataPath += std::to_string(id); - - auto view = std::shared_ptr( - new IResearchView(&vocbase, info, std::move(dataPath), isNew) - ); - - auto props = info.get("properties"); - - if (props.isNone()) { - // if no 'properties' then assume defaults - props = emptyObjectSlice(); - } - + PTR_NAMED(IResearchView, view, &vocbase, info, *feature, isNew); + auto& impl = reinterpret_cast(*view); + auto& json = info.isObject() ? info : emptyObjectSlice(); // if no 'info' then assume defaults + auto props = json.get("properties"); + auto& properties = props.isObject() ? props : emptyObjectSlice(); // if no 'info' then assume defaults std::string error; - if (!view->_meta.init(props, error)) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + if (!impl._meta.init(properties, error)) { + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed to initialize iResearch view from definition, error: " << error; return nullptr; } - return view; + return std::move(view); } size_t IResearchView::memory() const { @@ -1676,15 +1808,13 @@ size_t IResearchView::memory() const { } void IResearchView::open() { - DBServerLogicalView::open(); - auto* engine = arangodb::EngineSelectorFeature::ENGINE; if (engine) { _inRecovery = engine->inRecovery(); } else { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) - << "failure to get storage engine while starting feature 'IResearchAnalyzer'"; + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) + << "failure to get storage engine while opening IResearch View: " << name(); // assume not inRecovery() } @@ -1731,18 +1861,18 @@ void IResearchView::open() { } } } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while opening iResearch view '" << id() << "': " << e.what(); IR_LOG_EXCEPTION(); throw; } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while opening iResearch view '" << id() << "'"; IR_LOG_EXCEPTION(); throw; } - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed to open IResearch view '" << name() << "' at: " << _storePersisted._path.utf8(); throw std::runtime_error( @@ -1800,12 +1930,12 @@ int IResearchView::remove( return TRI_ERROR_NO_ERROR; } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while removing from iResearch view '" << id() << "', collection '" << cid << "', revision '" << documentId.id() << "': " << e.what(); IR_LOG_EXCEPTION(); } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while removing from iResearch view '" << id() << "', collection '" << cid << "', revision '" << documentId.id() << "'"; IR_LOG_EXCEPTION(); @@ -1834,7 +1964,7 @@ PrimaryKeyIndexReader* IResearchView::snapshot( } if (state.waitForSync() && !sync()) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "failed to sync while creating snapshot for IResearch view '" << name() << "', previous snapshot will be used instead"; } @@ -1857,14 +1987,14 @@ PrimaryKeyIndexReader* IResearchView::snapshot( reader.add(_storePersisted._reader); } } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while collecting readers for snapshot of IResearch view '" << id() << "': " << e.what(); IR_LOG_EXCEPTION(); return nullptr; } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception while collecting readers for snapshot of IResearch view '" << id() << "'"; IR_LOG_EXCEPTION(); @@ -1887,17 +2017,17 @@ bool IResearchView::sync(size_t maxMsec /*= 0*/) { try { SCOPED_LOCK(mutex); - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "starting active memory-store sync for iResearch view '" << id() << "'"; _memoryNode->_store.sync(); - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "finished memory-store sync for iResearch view '" << id() << "'"; if (maxMsec && TRI_microtime() >= thresholdSec) { return true; // skip if timout exceeded } - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "starting pending memory-store sync for iResearch view '" << id() << "'"; _toFlush->_store._segmentCount.store(0); // reset to zero to get count of new segments that appear during commit _toFlush->_store._writer->commit(); @@ -1908,7 +2038,7 @@ bool IResearchView::sync(size_t maxMsec /*= 0*/) { _toFlush->_store._segmentCount += _toFlush->_store._reader.size(); // add commited segments } - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "finished pending memory-store sync for iResearch view '" << id() << "'"; if (maxMsec && TRI_microtime() >= thresholdSec) { @@ -1917,7 +2047,7 @@ bool IResearchView::sync(size_t maxMsec /*= 0*/) { // must sync persisted store as well to ensure removals are applied if (_storePersisted) { - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "starting persisted-sync sync for iResearch view '" << id() << "'"; _storePersisted._segmentCount.store(0); // reset to zero to get count of new segments that appear during commit _storePersisted._writer->commit(); @@ -1928,17 +2058,17 @@ bool IResearchView::sync(size_t maxMsec /*= 0*/) { _storePersisted._segmentCount += _storePersisted._reader.size(); // add commited segments } - LOG_TOPIC(DEBUG, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(DEBUG, IResearchFeature::IRESEARCH) << "finished persisted-sync sync for iResearch view '" << id() << "'"; } return true; } catch (std::exception const& e) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception during sync of iResearch view '" << id() << "': " << e.what(); IR_LOG_EXCEPTION(); } catch (...) { - LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(WARN, IResearchFeature::IRESEARCH) << "caught exception during sync of iResearch view '" << id() << "'"; IR_LOG_EXCEPTION(); } @@ -1954,39 +2084,10 @@ bool IResearchView::sync(size_t maxMsec /*= 0*/) { return type; } -void IResearchView::toVelocyPack( - velocypack::Builder& result, - bool includeProperties, - bool includeSystem -) const { - // We write into an open object - TRI_ASSERT(result.isOpenObject()); - - DBServerLogicalView::toVelocyPack(result, includeProperties, includeSystem); - - // Object is still open - TRI_ASSERT(result.isOpenObject()); - - if (includeProperties) { - // implementation Information - result.add("properties", VPackValue(VPackValueType::Object)); - // note: includeSystem and forPersistence are not 100% synonymous, - // however, for our purposes this is an okay mapping; we only set - // includeSystem if we are persisting the properties - getPropertiesVPack(result, includeSystem); - result.close(); - } - - TRI_ASSERT(result.isOpenObject()); // We leave the object open -} - arangodb::Result IResearchView::updateProperties( - velocypack::Slice const& slice, - bool partialUpdate, - bool doSync + arangodb::velocypack::Slice const& slice, + bool partialUpdate ) { - WRITE_LOCKER(writeLocker, _infoLock); - auto* vocbase = this->vocbase(); if (!vocbase) { @@ -2103,52 +2204,37 @@ arangodb::Result IResearchView::updateProperties( _meta = std::move(meta); } - std::unordered_set collections; + if (!slice.hasKey(LINKS_FIELD)) { + return res; + } + // ........................................................................... // update links if requested (on a best-effort basis) // indexing of collections is done in different threads so no locks can be held and rollback is not possible // as a result it's also possible for links to be simultaneously modified via a different callflow (e.g. from collections) - if (slice.hasKey(LINKS_FIELD)) { - if (partialUpdate) { - res = updateLinks(collections, *vocbase, *this, slice.get(LINKS_FIELD)); - } else { - arangodb::velocypack::Builder builder; - - builder.openObject(); - - if (!appendLinkRemoval(builder, _meta) - || !mergeSlice(builder, slice.get(LINKS_FIELD))) { - return arangodb::Result( - TRI_ERROR_INTERNAL, - std::string("failed to construct link update directive while updating iResearch view '") + std::to_string(id()) + "'" - ); - } - - builder.close(); - res = updateLinks(collections, *vocbase, *this, builder.slice()); - } - } - // ........................................................................... - // if an exception occurs below then it would only affect collection linking - // consistency and an update retry would most likely happen - // always re-validate '_collections' because may have had externally triggered - // collection/link drops - // ........................................................................... - { - SCOPED_LOCK(mutex); // '_meta' can be asynchronously read - collections.insert(_meta._collections.begin(), _meta._collections.end()); - validateLinks(collections, *vocbase, *this); // remove invalid cids (no such collection or no such link) - _meta._collections = std::move(collections); + + std::unordered_set collections; + + if (partialUpdate) { + return updateLinks(collections, *vocbase, *this, slice.get(LINKS_FIELD)); } - // FIXME TODO to ensure valid recovery remove the original datapath only if the entire, but under lock to prevent double rename + arangodb::velocypack::Builder builder; - if (res.ok()) { - res = DBServerLogicalView::updateProperties(slice, partialUpdate, doSync); + builder.openObject(); + + if (!appendLinkRemoval(builder, _meta) + || !mergeSlice(builder, slice.get(LINKS_FIELD))) { + return arangodb::Result( + TRI_ERROR_INTERNAL, + std::string("failed to construct link update directive while updating IResearch View '") + name() + "'" + ); } - return res; + builder.close(); + + return updateLinks(collections, *vocbase, *this, builder.slice()); } void IResearchView::registerFlushCallback() { @@ -2234,7 +2320,7 @@ void IResearchView::verifyKnownCollections() { State state; if (!appendKnownCollections(cids, *snapshot(state, true))) { - LOG_TOPIC(ERR, iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(ERR, IResearchFeature::IRESEARCH) << "failed to collect collection IDs for IResearch view '" << id() << "'"; return; @@ -2246,7 +2332,7 @@ void IResearchView::verifyKnownCollections() { if (!collection) { // collection no longer exists, drop it and move on - LOG_TOPIC(TRACE, arangodb::iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(TRACE, IResearchFeature::IRESEARCH) << "collection '" << cid << "' no longer exists! removing from IResearch view '" << id() << "'"; @@ -2255,7 +2341,7 @@ void IResearchView::verifyKnownCollections() { // see if the link still exists, otherwise drop and move on auto link = findFirstMatchingLink(*collection, *this); if (!link) { - LOG_TOPIC(TRACE, arangodb::iresearch::IResearchFeature::IRESEARCH) + LOG_TOPIC(TRACE, IResearchFeature::IRESEARCH) << "collection '" << cid << "' no longer linked! removing from IResearch view '" << id() << "'"; @@ -2270,4 +2356,4 @@ NS_END // arangodb // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/arangod/IResearch/IResearchView.h b/arangod/IResearch/IResearchView.h index d37ce3c95c..d00937a53e 100644 --- a/arangod/IResearch/IResearchView.h +++ b/arangod/IResearch/IResearchView.h @@ -26,8 +26,6 @@ #include "Containers.h" #include "IResearchViewMeta.h" -#include "Basics/ReadWriteLock.h" -#include "Basics/WriteLocker.h" #include "VocBase/LogicalDataSource.h" #include "VocBase/LocalDocumentId.h" #include "VocBase/LogicalView.h" @@ -41,6 +39,7 @@ NS_BEGIN(arangodb) +class DatabasePathFeature; // forward declaration class TransactionState; // forward declaration class ViewIterator; // forward declaration @@ -112,6 +111,7 @@ class PrimaryKeyIndexReader: public irs::index_reader { class IResearchView final: public arangodb::DBServerLogicalView, public arangodb::FlushTransaction { public: + /////////////////////////////////////////////////////////////////////////////// /// @brief AsyncValue holding the view itself, modifiable by IResearchView /////////////////////////////////////////////////////////////////////////////// @@ -147,10 +147,19 @@ class IResearchView final: public arangodb::DBServerLogicalView, //////////////////////////////////////////////////////////////////////////////// /// @brief remove all documents matching collection 'cid' from this IResearch /// View and the underlying IResearch stores - /// also remove 'cid' from the runtime list of tracked collection IDs + /// also remove 'cid' from the persisted list of tracked collection IDs //////////////////////////////////////////////////////////////////////////////// int drop(TRI_voc_cid_t cid); + //////////////////////////////////////////////////////////////////////////////// + /// @brief acquire locks on the specified 'cid' during read-transactions + /// allowing retrieval of documents contained in the aforementioned + /// collection + /// also track 'cid' via the persisted list of tracked collection IDs + /// @return the 'cid' was newly added to the IResearch View + //////////////////////////////////////////////////////////////////////////////// + bool emplace(TRI_voc_cid_t cid); + //////////////////////////////////////////////////////////////////////////////// /// @brief insert a document into this IResearch View and the underlying /// IResearch stores @@ -246,20 +255,7 @@ class IResearchView final: public arangodb::DBServerLogicalView, //////////////////////////////////////////////////////////////////////////////// static arangodb::LogicalDataSource::Type const& type() noexcept; - void toVelocyPack( - velocypack::Builder& result, - bool includeProperties, - bool includeSystem - ) const override; - - /////////////////////////////////////////////////////////////////////////////// - /// @brief called when a view's properties are updated (i.e. delta-modified) - /////////////////////////////////////////////////////////////////////////////// - arangodb::Result updateProperties( - arangodb::velocypack::Slice const& slice, - bool partialUpdate, - bool doSync - ) override; + using LogicalView::updateProperties; /////////////////////////////////////////////////////////////////////////////// /// @brief visit all collection IDs that were added to the view @@ -267,7 +263,28 @@ class IResearchView final: public arangodb::DBServerLogicalView, /////////////////////////////////////////////////////////////////////////////// bool visitCollections(CollectionVisitor const& visitor) const override; + protected: + + ////////////////////////////////////////////////////////////////////////////// + /// @brief fill and return a JSON description of a IResearchView object + /// only fields describing the view itself, not 'link' descriptions + ////////////////////////////////////////////////////////////////////////////// + void getPropertiesVPack( + arangodb::velocypack::Builder& builder, + bool forPersistence + ) const override; + + ////////////////////////////////////////////////////////////////////////////// + /// @brief called when a view's properties are updated (i.e. delta-modified) + ////////////////////////////////////////////////////////////////////////////// + arangodb::Result updateProperties( + arangodb::velocypack::Slice const& slice, + bool partialUpdate + ) override; + private: + DECLARE_SPTR(LogicalView); + struct DataStore { irs::directory::ptr _directory; irs::directory_reader _reader; @@ -317,19 +334,10 @@ class IResearchView final: public arangodb::DBServerLogicalView, IResearchView( TRI_vocbase_t* vocbase, arangodb::velocypack::Slice const& info, - irs::utf8_path&& persistedPath, + arangodb::DatabasePathFeature const& dbPathFeature, bool isNew ); - /////////////////////////////////////////////////////////////////////////////// - /// @brief fill and return a JSON description of a IResearchView object - /// only fields describing the view itself, not 'link' descriptions - //////////////////////////////////////////////////////////////////////////////// - void getPropertiesVPack( - arangodb::velocypack::Builder& builder, - bool forPersistence - ) const; - ////////////////////////////////////////////////////////////////////////////// /// @brief Called in post-recovery to remove any dangling documents old links ////////////////////////////////////////////////////////////////////////////// @@ -365,11 +373,8 @@ class IResearchView final: public arangodb::DBServerLogicalView, std::function _trxReadCallback; // for snapshot(...) std::function _trxWriteCallback; // for insert(...)/remove(...) std::atomic _inRecovery; - - // FIXME came from "LogicalView", check whether it needs to be there - mutable basics::ReadWriteLock _infoLock; // lock protecting the properties }; NS_END // iresearch NS_END // arangodb -#endif +#endif \ No newline at end of file diff --git a/arangod/IResearch/IResearchViewMeta.h b/arangod/IResearch/IResearchViewMeta.h index 83f7ccbd20..b46c2f54f6 100644 --- a/arangod/IResearch/IResearchViewMeta.h +++ b/arangod/IResearch/IResearchViewMeta.h @@ -107,7 +107,7 @@ struct IResearchViewMeta { explicit Mask(bool mask = false) noexcept; }; - std::unordered_set _collections; // collection links added to this view via view property modification (may contain no-longer valid cids) + std::unordered_set _collections; // collection links added to this view via IResearchLink creation (may contain no-longer valid cids) CommitMeta _commit; std::locale _locale; // locale used for ordering processed attribute names size_t _threadsMaxIdle; // maximum idle number of threads for single-run tasks diff --git a/arangod/MMFiles/MMFilesEngine.cpp b/arangod/MMFiles/MMFilesEngine.cpp index 56851fa020..2d281d110b 100644 --- a/arangod/MMFiles/MMFilesEngine.cpp +++ b/arangod/MMFiles/MMFilesEngine.cpp @@ -2063,7 +2063,7 @@ TRI_vocbase_t* MMFilesEngine::openExistingDatabase(TRI_voc_tick_t id, auto const viewPath = readPath(it); - if (!viewPath.empty()) { + if (viewPath.empty()) { THROW_ARANGO_EXCEPTION_MESSAGE( TRI_ERROR_BAD_PARAMETER, "view path cannot be empty" diff --git a/arangod/RestHandler/RestViewHandler.cpp b/arangod/RestHandler/RestViewHandler.cpp index e35b5b9895..1e515f60e4 100644 --- a/arangod/RestHandler/RestViewHandler.cpp +++ b/arangod/RestHandler/RestViewHandler.cpp @@ -124,7 +124,7 @@ void RestViewHandler::createView() { if (view != nullptr) { VPackBuilder props; props.openObject(); - view->toVelocyPack(props, false, false); + view->toVelocyPack(props, true, false); props.close(); generateResult(rest::ResponseCode::CREATED, props.slice()); } else { @@ -314,9 +314,9 @@ void RestViewHandler::getViewProperties(std::string const& name) { if (view.get() != nullptr) { VPackBuilder props; props.openObject(); - view->toVelocyPack(props, false, false); + view->toVelocyPack(props, true, false); props.close(); - generateResult(rest::ResponseCode::OK, props.slice()); + generateResult(rest::ResponseCode::OK, props.slice().get("properties")); } else { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND); diff --git a/arangod/VocBase/LogicalView.cpp b/arangod/VocBase/LogicalView.cpp index dacf3646a6..c3fa6dab6b 100644 --- a/arangod/VocBase/LogicalView.cpp +++ b/arangod/VocBase/LogicalView.cpp @@ -60,9 +60,7 @@ TRI_voc_cid_t ReadPlanId(VPackSlice info, TRI_voc_cid_t vid) { return vid; } -} // namespace - -/*static*/ TRI_voc_cid_t LogicalView::readViewId(VPackSlice info) { +/*static*/ TRI_voc_cid_t ReadViewId(VPackSlice info) { if (!info.isObject()) { // ERROR CASE return 0; @@ -83,10 +81,42 @@ TRI_voc_cid_t ReadPlanId(VPackSlice info, TRI_voc_cid_t vid) { return id; } +} // namespace + // ----------------------------------------------------------------------------- // --SECTION-- LogicalView // ----------------------------------------------------------------------------- +// @brief Constructor used in coordinator case. +// The Slice contains the part of the plan that +// is relevant for this view +LogicalView::LogicalView(TRI_vocbase_t* vocbase, VPackSlice const& info) + : LogicalDataSource( + category(), + LogicalDataSource::Type::emplace( + arangodb::basics::VelocyPackHelper::getStringRef(info, "type", "") + ), + vocbase, + ReadViewId(info), + ReadPlanId(info, 0), + arangodb::basics::VelocyPackHelper::getStringValue(info, "name", ""), + Helper::readBooleanValue(info, "deleted", false) + ) { + if (!TRI_vocbase_t::IsAllowedName(info)) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_ILLEGAL_NAME); + } + + if (!id()) { + THROW_ARANGO_EXCEPTION_MESSAGE( + TRI_ERROR_BAD_PARAMETER, + "got invalid view identifier while constructing LogicalView" + ); + } + + // update server's tick value + TRI_UpdateTickServer(static_cast(id())); +} + /*static*/ std::shared_ptr LogicalView::create( TRI_vocbase_t& vocbase, velocypack::Slice definition, @@ -122,29 +152,6 @@ TRI_voc_cid_t ReadPlanId(VPackSlice info, TRI_voc_cid_t vid) { return category; } -// @brief Constructor used in coordinator case. -// The Slice contains the part of the plan that -// is relevant for this view -LogicalView::LogicalView(TRI_vocbase_t* vocbase, VPackSlice const& info) - : LogicalDataSource( - category(), - LogicalDataSource::Type::emplace( - arangodb::basics::VelocyPackHelper::getStringRef(info, "type", "") - ), - vocbase, - LogicalView::readViewId(info), - ReadPlanId(info, 0), - arangodb::basics::VelocyPackHelper::getStringValue(info, "name", ""), - Helper::readBooleanValue(info, "deleted", false) - ) { - if (!TRI_vocbase_t::IsAllowedName(info)) { - THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_ILLEGAL_NAME); - } - - // update server's tick value - TRI_UpdateTickServer(static_cast(id())); -} - // ----------------------------------------------------------------------------- // --SECTION-- DBServerLogicalView // ----------------------------------------------------------------------------- @@ -165,6 +172,13 @@ DBServerLogicalView::~DBServerLogicalView() { } } +void DBServerLogicalView::drop() { + TRI_ASSERT(!ServerState::instance()->isCoordinator()); + StorageEngine* engine = EngineSelectorFeature::ENGINE; + TRI_ASSERT(engine); + engine->dropView(vocbase(), this); +} + void DBServerLogicalView::open() { // Coordinators are not allowed to have local views! TRI_ASSERT(!ServerState::instance()->isCoordinator()); @@ -179,13 +193,6 @@ void DBServerLogicalView::open() { _isNew = false; } -void DBServerLogicalView::drop() { - TRI_ASSERT(!ServerState::instance()->isCoordinator()); - StorageEngine* engine = EngineSelectorFeature::ENGINE; - TRI_ASSERT(engine); - engine->dropView(vocbase(), this); -} - Result DBServerLogicalView::rename(std::string&& newName, bool doSync) { auto oldName = name(); @@ -213,7 +220,7 @@ Result DBServerLogicalView::rename(std::string&& newName, bool doSync) { void DBServerLogicalView::toVelocyPack( velocypack::Builder &result, - bool /*includeProperties*/, + bool includeProperties, bool includeSystem ) const { // We write into an open object @@ -233,20 +240,42 @@ void DBServerLogicalView::toVelocyPack( // storage engine related properties StorageEngine* engine = EngineSelectorFeature::ENGINE; - TRI_ASSERT(engine ); + TRI_ASSERT(engine); engine->getViewProperties(vocbase(), this, result); } + + if (includeProperties) { + // implementation Information + result.add("properties", VPackValue(VPackValueType::Object)); + // note: includeSystem and forPersistence are not 100% synonymous, + // however, for our purposes this is an okay mapping; we only set + // includeSystem if we are persisting the properties + getPropertiesVPack(result, includeSystem); + result.close(); + } + + TRI_ASSERT(result.isOpenObject()); // We leave the object open } arangodb::Result DBServerLogicalView::updateProperties( - VPackSlice const& /*slice*/, - bool /*partialUpdate*/, + VPackSlice const& slice, + bool partialUpdate, bool doSync ) { + auto res = updateProperties(slice, partialUpdate); + + if (!res.ok()) { + return res; + } + // after this call the properties are stored StorageEngine* engine = EngineSelectorFeature::ENGINE; TRI_ASSERT(engine); + if (engine->inRecovery()) { + return arangodb::Result(); // do not modify engine while in recovery + } + try { engine->changeView(vocbase(), id(), this, doSync); } catch (arangodb::basics::Exception const& e) { @@ -260,4 +289,4 @@ arangodb::Result DBServerLogicalView::updateProperties( // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/arangod/VocBase/LogicalView.h b/arangod/VocBase/LogicalView.h index 850eea0a78..7eddaa1552 100644 --- a/arangod/VocBase/LogicalView.h +++ b/arangod/VocBase/LogicalView.h @@ -60,12 +60,6 @@ class LogicalView : public LogicalDataSource { ////////////////////////////////////////////////////////////////////////////// static Category const& category() noexcept; - ////////////////////////////////////////////////////////////////////////////// - /// @brief invoke visitor on all collections that a view will return - /// @return visitation was successful - ////////////////////////////////////////////////////////////////////////////// - virtual bool visitCollections(CollectionVisitor const& visitor) const = 0; - ////////////////////////////////////////////////////////////////////////////// /// @brief opens an existing view when the server is restarted ////////////////////////////////////////////////////////////////////////////// @@ -102,14 +96,20 @@ class LogicalView : public LogicalDataSource { bool doSync ) = 0; - protected: - static TRI_voc_cid_t readViewId(velocypack::Slice slice); + ////////////////////////////////////////////////////////////////////////////// + /// @brief invoke visitor on all collections that a view will return + /// @return visitation was successful + ////////////////////////////////////////////////////////////////////////////// + virtual bool visitCollections(CollectionVisitor const& visitor) const = 0; + protected: LogicalView(TRI_vocbase_t* vocbase, velocypack::Slice const& definition); private: // FIXME seems to be ugly friend struct ::TRI_vocbase_t; + + // ensure LogicalDataSource members (e.g. _deleted/_name) are not modified asynchronously mutable basics::ReadWriteLock _lock; }; // LogicalView @@ -135,10 +135,10 @@ class DBServerLogicalView : public LogicalView { public: ~DBServerLogicalView() override; - void open() override; - void drop() override; + void open() override; + Result rename( std::string&& newName, bool doSync @@ -148,13 +148,13 @@ class DBServerLogicalView : public LogicalView { velocypack::Builder& result, bool includeProperties, bool includeSystem - ) const override; + ) const override final; arangodb::Result updateProperties( velocypack::Slice const& properties, bool partialUpdate, bool doSync - ) override; + ) override final; protected: DBServerLogicalView( @@ -163,10 +163,26 @@ class DBServerLogicalView : public LogicalView { bool isNew ); + ////////////////////////////////////////////////////////////////////////////// + /// @brief fill and return a jSON description of a View object implementation + ////////////////////////////////////////////////////////////////////////////// + virtual void getPropertiesVPack( + velocypack::Builder& builder, + bool forPersistence + ) const = 0; + + /////////////////////////////////////////////////////////////////////////////// + /// @brief called when a view's properties are updated (i.e. delta-modified) + /////////////////////////////////////////////////////////////////////////////// + virtual arangodb::Result updateProperties( + velocypack::Slice const& slice, + bool partialUpdate + ) = 0; + private: bool _isNew; }; // LogicalView } // namespace arangodb -#endif +#endif \ No newline at end of file diff --git a/arangod/VocBase/vocbase.cpp b/arangod/VocBase/vocbase.cpp index 5fd0f02f89..b3b580d9bc 100644 --- a/arangod/VocBase/vocbase.cpp +++ b/arangod/VocBase/vocbase.cpp @@ -1357,7 +1357,7 @@ int TRI_vocbase_t::renameView( auto itr1 = _dataSourceByName.find(oldName); if (itr1 == _dataSourceByName.end() - || arangodb::LogicalView::category() == itr1->second->category()) { + || arangodb::LogicalView::category() != itr1->second->category()) { return TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND; } @@ -1614,9 +1614,29 @@ std::shared_ptr TRI_vocbase_t::createViewWorker( THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DUPLICATE_NAME); } + // Coordinators are not allowed to have local views! + TRI_ASSERT(!ServerState::instance()->isCoordinator()); + + StorageEngine* engine = EngineSelectorFeature::ENGINE; + TRI_ASSERT(engine); + arangodb::velocypack::Builder builder; + auto res = engine->getViews(this, builder); + TRI_ASSERT(TRI_ERROR_NO_ERROR == res); + auto slice = builder.slice(); + TRI_ASSERT(slice.isArray()); + auto viewId = std::to_string(view->id()); + + // We have not yet persisted this view + for (auto entry: arangodb::velocypack::ArrayIterator(slice)) { + TRI_ASSERT(arangodb::basics::VelocyPackHelper::getStringRef(entry, "id", arangodb::velocypack::StringRef()).compare(viewId)); + } + registerView(basics::ConditionalLocking::DoNotLock, view); try { + // Let's try to persist it. + engine->createView(this, view->id(), view.get()); + // And lets open it. view->open(); @@ -1782,6 +1802,9 @@ int TRI_vocbase_t::dropView(std::shared_ptr view) { view->drop(); unregisterView(view); + StorageEngine* engine = EngineSelectorFeature::ENGINE; + engine->dropView(this, view.get()); + locker.unlock(); writeLocker.unlock(); @@ -2184,4 +2207,4 @@ TRI_voc_rid_t TRI_StringToRid(char const* p, size_t len, bool& isOld, // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/IResearch/IResearchLink-test.cpp b/tests/IResearch/IResearchLink-test.cpp index 80109b6e6e..7d5fc41da2 100644 --- a/tests/IResearch/IResearchLink-test.cpp +++ b/tests/IResearch/IResearchLink-test.cpp @@ -168,6 +168,7 @@ TEST_CASE("IResearchLinkTest", "[iresearch][iresearch-link]") { SECTION("test_defaults") { // no view specified { + s.engine.views.clear(); TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); @@ -179,6 +180,7 @@ SECTION("test_defaults") { // no view can be found { + s.engine.views.clear(); TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); @@ -190,6 +192,7 @@ SECTION("test_defaults") { // valid link creation { + s.engine.views.clear(); TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto linkJson = arangodb::velocypack::Parser::fromJson("{ \"type\": \"arangosearch\", \"view\": 42 }"); auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); @@ -242,6 +245,7 @@ SECTION("test_defaults") { // ensure jSON is still valid after unload() { + s.engine.views.clear(); TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto linkJson = arangodb::velocypack::Parser::fromJson("{ \"type\": \"arangosearch\", \"view\": 42 }"); auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); @@ -310,6 +314,269 @@ SECTION("test_defaults") { } } +SECTION("test_init") { + // collection registered with view (collection initially not in view) + { + auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\", \"id\": 100 }"); + auto linkJson = arangodb::velocypack::Parser::fromJson("{ \"type\": \"arangosearch\", \"view\": 42 }"); + auto viewJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"id\": 42, \"type\": \"arangosearch\" }"); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); + REQUIRE((nullptr != logicalCollection)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + + // no collections in view before + { + std::unordered_set expected; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + auto link = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == !link)); + + // collection in view after + { + std::unordered_set expected = { 100 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + link.reset(); + + // collection not in view on destruct + { + std::unordered_set expected; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } + + // collection registered with view (collection initially is in view) + { + auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\", \"id\": 101 }"); + auto linkJson = arangodb::velocypack::Parser::fromJson("{ \"type\": \"arangosearch\", \"view\": 43 }"); + auto viewJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"id\": 43, \"type\": \"arangosearch\", \"properties\": { \"collections\": [ 101 ] } }"); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); + REQUIRE((nullptr != logicalCollection)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + + // collection in view before + { + std::unordered_set expected = { 101 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + auto link = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == !link)); + + // collection in view after + { + std::unordered_set expected = { 101 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + link.reset(); + + // collection still in view on destruct + { + std::unordered_set expected = { 101 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } +} + +SECTION("test_drop") { + // collection drop (removes collection from view) subsequent destroy does not touch view + { + auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\", \"id\": 100 }"); + auto linkJson = arangodb::velocypack::Parser::fromJson("{ \"type\": \"arangosearch\", \"view\": 42 }"); + auto viewJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"id\": 42, \"type\": \"arangosearch\" }"); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); + REQUIRE((nullptr != logicalCollection)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + + auto link0 = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == !link0)); + + // collection in view before + { + std::unordered_set expected = { 100 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + CHECK((TRI_ERROR_NO_ERROR == link0->drop())); + + // collection not in view after + { + std::unordered_set expected; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + auto link1 = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == !link1)); + + // collection in view before (new link) + { + std::unordered_set expected = { 100 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + link0.reset(); + + // collection in view after (new link) subsequent destroy does not touch view + { + std::unordered_set expected = { 100 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } +} + +SECTION("test_unload") { + // index unload does not touch the view (subsequent destroy) + { + auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\", \"id\": 100 }"); + auto linkJson = arangodb::velocypack::Parser::fromJson("{ \"type\": \"arangosearch\", \"view\": 42 }"); + auto viewJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"id\": 42, \"type\": \"arangosearch\" }"); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); + REQUIRE((nullptr != logicalCollection)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + + auto link = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == !link)); + + // collection in view before + { + std::unordered_set expected = { 100 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + link->unload(); + + // collection in view after unload + { + std::unordered_set expected = { 100 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + link.reset(); + + // collection in view after destruct, subsequent destroy does not touch view + { + std::unordered_set expected = { 100 }; + std::set actual; + + CHECK((logicalView->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } +} + SECTION("test_write") { static std::vector const EMPTY; auto doc0 = arangodb::velocypack::Parser::fromJson("{ \"abc\": \"def\" }"); @@ -388,4 +655,4 @@ SECTION("test_write") { // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/IResearch/IResearchView-test.cpp b/tests/IResearch/IResearchView-test.cpp index 2c79d243ed..7c8e87ff0d 100644 --- a/tests/IResearch/IResearchView-test.cpp +++ b/tests/IResearch/IResearchView-test.cpp @@ -187,6 +187,7 @@ struct IResearchViewSetup { TRI_CreateDirectory(testFilesystemPath.c_str(), systemError, systemErrorStr); // suppress log messages since tests check error conditions + arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::ERR); // suppress ERROR recovery failure due to error from callback arangodb::LogTopic::setLogLevel(arangodb::iresearch::IResearchFeature::IRESEARCH.name(), arangodb::LogLevel::FATAL); irs::logger::output_le(iresearch::logger::IRL_FATAL, stderr); } @@ -195,6 +196,7 @@ struct IResearchViewSetup { system.reset(); // destroy before reseting the 'ENGINE' TRI_RemoveDirectory(testFilesystemPath.c_str()); arangodb::LogTopic::setLogLevel(arangodb::iresearch::IResearchFeature::IRESEARCH.name(), arangodb::LogLevel::DEFAULT); + arangodb::LogTopic::setLogLevel(arangodb::Logger::FIXME.name(), arangodb::LogLevel::DEFAULT); arangodb::application_features::ApplicationServer::server = nullptr; arangodb::EngineSelectorFeature::ENGINE = nullptr; @@ -249,16 +251,15 @@ SECTION("test_defaults") { arangodb::iresearch::IResearchViewMeta meta; std::string error; + CHECK(6 == slice.length()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(false == slice.get("deleted").getBool()); - CHECK(6 == slice.length()); - - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((5U == propSlice.length())); - CHECK((!propSlice.hasKey("links"))); // for persistence so no links - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((5U == slice.length())); + CHECK((!slice.hasKey("links"))); // for persistence so no links + CHECK((meta.init(slice, error) && expectedMeta == meta)); } // existing view definition with LogicalView @@ -278,16 +279,15 @@ SECTION("test_defaults") { arangodb::iresearch::IResearchViewMeta meta; std::string error; + CHECK(4 == slice.length()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); - CHECK(slice.get("deleted").isNone()); // no system properties - CHECK(4 == slice.length()); - - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((propSlice.hasKey("links"))); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); + CHECK((false == slice.hasKey("deleted"))); + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((slice.hasKey("links"))); + CHECK((meta.init(slice, error) && expectedMeta == meta)); } // new view definition with LogicalView (for persistence) @@ -300,20 +300,22 @@ SECTION("test_defaults") { arangodb::velocypack::Builder builder; builder.openObject(); - view->toVelocyPack(builder, false, true); + view->toVelocyPack(builder, true, true); builder.close(); auto slice = builder.slice(); arangodb::iresearch::IResearchViewMeta meta; std::string error; + CHECK(6 == slice.length()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(false == slice.get("deleted").getBool()); - CHECK(5 == slice.length()); - - auto propSlice = slice.get("properties"); - CHECK(propSlice.isNone()); + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((5U == slice.length())); + CHECK((!slice.hasKey("links"))); // for persistence so no links + CHECK((meta.init(slice, error) && expectedMeta == meta)); } // new view definition with LogicalView @@ -326,18 +328,24 @@ SECTION("test_defaults") { arangodb::velocypack::Builder builder; builder.openObject(); - view->toVelocyPack(builder, false, false); + view->toVelocyPack(builder, true, false); builder.close(); auto slice = builder.slice(); arangodb::iresearch::IResearchViewMeta meta; std::string error; + CHECK(4 == slice.length()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); - CHECK(slice.get("deleted").isNone()); - CHECK(slice.get("properties").isNone()); - CHECK(3 == slice.length()); + CHECK((false == slice.hasKey("deleted"))); + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); + + auto tmpSlice = slice.get("links"); + CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } // new view definition with links (not supported for link creation) @@ -376,9 +384,7 @@ SECTION("test_drop") { CHECK((true == !vocbase.lookupView("testView"))); CHECK((true == logicalCollection->getIndexes().empty())); CHECK((false == TRI_IsDirectory(dataPath.c_str()))); // createView(...) will call open() - auto logicalView = vocbase.createView(json->slice(), 0); - REQUIRE((false == !logicalView)); - auto view = &logicalView; + auto view = vocbase.createView(json->slice(), 0); REQUIRE((false == !view)); CHECK((true == logicalCollection->getIndexes().empty())); @@ -407,9 +413,7 @@ SECTION("test_drop_with_link") { CHECK((true == !vocbase.lookupView("testView"))); CHECK((true == logicalCollection->getIndexes().empty())); CHECK((false == TRI_IsDirectory(dataPath.c_str()))); // createView(...) will call open() - auto logicalView = vocbase.createView(json->slice(), 0); - REQUIRE((false == !logicalView)); - auto view = &logicalView; + auto view = vocbase.createView(json->slice(), 0); REQUIRE((false == !view)); CHECK((true == logicalCollection->getIndexes().empty())); @@ -420,7 +424,7 @@ SECTION("test_drop_with_link") { \"links\": { \"testCollection\": {} } \ }"); - arangodb::Result res = logicalView->updateProperties(links->slice(), true, false); + arangodb::Result res = view->updateProperties(links->slice(), true, false); CHECK(true == res.ok()); CHECK((false == logicalCollection->getIndexes().empty())); @@ -433,14 +437,14 @@ SECTION("test_drop_with_link") { SECTION("test_drop_cid") { static std::vector const EMPTY; - // cid not in list of fully indexed (view definition not updated, not persisted) + // cid not in list of collections for snapshot (view definition not updated, not persisted) { auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); // fill with test data { @@ -466,7 +470,13 @@ SECTION("test_drop_cid") { // drop cid 42 { - view->drop(42); + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + + CHECK((TRI_ERROR_NO_ERROR == view->drop(42))); + CHECK((!persisted)); // drop() does not modify view meta if cid did not exist previously view->sync(); } @@ -478,14 +488,15 @@ SECTION("test_drop_cid") { } } - // cid in list of fully indexed (view definition updated+persisted) + // cid in list of collections for snapshot (view definition updated+persisted) { - auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"collections\": [ 42 ] }"); + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"properties\": { \"collections\": [ 42 ] } }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + CHECK((s.engine.persistView(&vocbase, view).ok())); // register view with engine // fill with test data { @@ -511,7 +522,13 @@ SECTION("test_drop_cid") { // drop cid 42 { - view->drop(42); + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + + CHECK((TRI_ERROR_NO_ERROR == view->drop(42))); + CHECK((persisted)); // drop() modifies view meta if cid existed previously view->sync(); } @@ -522,11 +539,484 @@ SECTION("test_drop_cid") { CHECK(0 == snapshot->live_docs_count()); } } + + // cid in list of collections for snapshot (view definition updated, not persisted until recovery is complete) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"collections\": [ 42 ] }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + + // fill with test data + { + auto doc = arangodb::velocypack::Parser::fromJson("{ \"key\": 1 }"); + arangodb::iresearch::IResearchLinkMeta meta; + meta._includeAllFields = true; + arangodb::transaction::UserTransaction trx( + arangodb::transaction::StandaloneContext::Create(&vocbase), + EMPTY, EMPTY, EMPTY, arangodb::transaction::Options() + ); + CHECK((trx.begin().ok())); + view->insert(trx, 42, arangodb::LocalDocumentId(0), doc->slice(), meta); + CHECK((trx.commit().ok())); + view->sync(); + } + + // query + { + TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); + auto* snapshot = view->snapshot(*state, true); + CHECK(1 == snapshot->live_docs_count()); + } + + // drop cid 42 + { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + auto beforeRecovery = StorageEngineMock::inRecoveryResult; + StorageEngineMock::inRecoveryResult = true; + auto restoreRecovery = irs::make_finally([&beforeRecovery]()->void { StorageEngineMock::inRecoveryResult = beforeRecovery; }); + + CHECK((TRI_ERROR_NO_ERROR == view->drop(42))); + CHECK((!persisted)); // drop() modifies view meta if cid existed previously (but not persisted until after recovery) + view->sync(); + } + + // query + { + TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); + auto* snapshot = view->snapshot(*state, true); + CHECK(0 == snapshot->live_docs_count()); + } + + // collection not in view after drop (in recovery) + { + std::unordered_set expected; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } + + // cid in list of collections for snapshot (view definition persist failure) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"properties\": { \"collections\": [ 42 ] } }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + + // fill with test data + { + auto doc = arangodb::velocypack::Parser::fromJson("{ \"key\": 1 }"); + arangodb::iresearch::IResearchLinkMeta meta; + meta._includeAllFields = true; + arangodb::transaction::UserTransaction trx( + arangodb::transaction::StandaloneContext::Create(&vocbase), + EMPTY, EMPTY, EMPTY, arangodb::transaction::Options() + ); + CHECK((trx.begin().ok())); + view->insert(trx, 42, arangodb::LocalDocumentId(0), doc->slice(), meta); + CHECK((trx.commit().ok())); + view->sync(); + } + + // query + { + TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); + auto* snapshot = view->snapshot(*state, true); + CHECK(1 == snapshot->live_docs_count()); + } + + // drop cid 42 + { + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = []()->void { throw std::exception(); }; + + CHECK((TRI_ERROR_NO_ERROR != view->drop(42))); + view->sync(); + } + + // query + { + TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); + auto* snapshot = view->snapshot(*state, true); + CHECK(1 == snapshot->live_docs_count()); + } + + // collection in view after drop failure + { + std::unordered_set expected = { 42 }; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } + + // cid in list of collections for snapshot (view definition persist failure on recovery completion) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"properties\": { \"collections\": [ 42 ] } }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + + // fill with test data + { + auto doc = arangodb::velocypack::Parser::fromJson("{ \"key\": 1 }"); + arangodb::iresearch::IResearchLinkMeta meta; + meta._includeAllFields = true; + arangodb::transaction::UserTransaction trx( + arangodb::transaction::StandaloneContext::Create(&vocbase), + EMPTY, EMPTY, EMPTY, arangodb::transaction::Options() + ); + CHECK((trx.begin().ok())); + view->insert(trx, 42, arangodb::LocalDocumentId(0), doc->slice(), meta); + CHECK((trx.commit().ok())); + view->sync(); + } + + // query + { + TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); + auto* snapshot = view->snapshot(*state, true); + CHECK(1 == snapshot->live_docs_count()); + } + + // drop cid 42 + { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + auto beforeRecovery = StorageEngineMock::inRecoveryResult; + StorageEngineMock::inRecoveryResult = true; + auto restoreRecovery = irs::make_finally([&beforeRecovery]()->void { StorageEngineMock::inRecoveryResult = beforeRecovery; }); + + CHECK((TRI_ERROR_NO_ERROR == view->drop(42))); + CHECK((!persisted)); // drop() modifies view meta if cid existed previously (but not persisted until after recovery) + view->sync(); + } + + // query + { + TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); + auto* snapshot = view->snapshot(*state, true); + CHECK(0 == snapshot->live_docs_count()); + } + + // collection in view after drop failure + { + std::unordered_set expected; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + // persistence fails during execution of callback + { + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = []()->void { throw std::exception(); }; + auto* feature = + arangodb::iresearch::getFeature("Database"); + + CHECK_THROWS((feature->recoveryDone())); + } + } +} + +SECTION("test_emplace_cid") { + // emplace (already in list) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"properties\": { \"collections\": [ 42 ] } }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + + // collection in view before + { + std::unordered_set expected = { 42 }; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + // emplace cid 42 + { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + + CHECK((false == view->emplace(42))); + CHECK((!persisted)); // emplace() does not modify view meta if cid existed previously + } + + // collection in view after + { + std::unordered_set expected = { 42 }; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } + + // emplace (not in list) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + CHECK((s.engine.persistView(&vocbase, view).ok())); // register view with engine + + // collection in view before + { + std::unordered_set expected; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + // emplace cid 42 + { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + + CHECK((true == view->emplace(42))); + CHECK((persisted)); // emplace() modifies view meta if cid did not exist previously + } + + // collection in view after + { + std::unordered_set expected = { 42 }; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } + + // emplace (not in list, not persisted until recovery is complete) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + + // collection in view before + { + std::unordered_set expected; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + // emplace cid 42 + { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + auto beforeRecovery = StorageEngineMock::inRecoveryResult; + StorageEngineMock::inRecoveryResult = true; + auto restoreRecovery = irs::make_finally([&beforeRecovery]()->void { StorageEngineMock::inRecoveryResult = beforeRecovery; }); + + CHECK((true == view->emplace(42))); + CHECK((!persisted)); // emplace() modifies view meta if cid did not exist previously (but not persisted until after recovery) + } + + // collection in view after + { + std::unordered_set expected = { 42 }; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } + + // emplace (not in list, view definition persist failure) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + + // collection in view before + { + std::unordered_set expected; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + // emplace cid 42 + { + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = []()->void { throw std::exception(); }; + + CHECK((false == view->emplace(42))); + } + + // collection in view after + { + std::unordered_set expected; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + } + + // emplace (not in list, view definition persist failure on recovery completion) + { + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); + Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); + + // collection in view before + { + std::unordered_set expected; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + // emplace cid 42 + { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + auto beforeRecovery = StorageEngineMock::inRecoveryResult; + StorageEngineMock::inRecoveryResult = true; + auto restoreRecovery = irs::make_finally([&beforeRecovery]()->void { StorageEngineMock::inRecoveryResult = beforeRecovery; }); + + CHECK((true == view->emplace(42))); + CHECK((!persisted)); // emplace() modifies view meta if cid did not exist previously (but not persisted until after recovery) + } + + // collection in view after + { + std::unordered_set expected = { 42 }; + std::set actual; + + CHECK((view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }))); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + // persistence fails during execution of callback + { + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = []()->void { throw std::exception(); }; + auto* feature = + arangodb::iresearch::getFeature("Database"); + + CHECK_THROWS((feature->recoveryDone())); + } + } } SECTION("test_insert") { static std::vector const EMPTY; - auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\":\"arangosearch\" }"); + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); arangodb::aql::AstNode noop(arangodb::aql::AstNodeType::NODE_TYPE_FILTER); arangodb::aql::AstNode noopChild(true, arangodb::aql::AstNodeValueType::VALUE_TYPE_BOOL); // all @@ -538,10 +1028,10 @@ SECTION("test_insert") { StorageEngineMock::inRecoveryResult = true; auto restore = irs::make_finally([&before]()->void { StorageEngineMock::inRecoveryResult = before; }); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); view->open(); { @@ -573,10 +1063,10 @@ SECTION("test_insert") { StorageEngineMock::inRecoveryResult = true; auto restore = irs::make_finally([&before]()->void { StorageEngineMock::inRecoveryResult = before; }); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); view->open(); { @@ -608,10 +1098,10 @@ SECTION("test_insert") { { StorageEngineMock::inRecoveryResult = false; Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); // validate cid count { @@ -670,11 +1160,10 @@ SECTION("test_insert") { { StorageEngineMock::inRecoveryResult = false; Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); CHECK(view->category() == arangodb::LogicalView::category()); { @@ -705,10 +1194,10 @@ SECTION("test_insert") { { StorageEngineMock::inRecoveryResult = false; Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); { auto docJson = arangodb::velocypack::Parser::fromJson("{\"abc\": \"def\"}"); @@ -739,10 +1228,10 @@ SECTION("test_insert") { { StorageEngineMock::inRecoveryResult = false; Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); - CHECK((false == !view)); + auto viewImpl = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); + CHECK((false == !viewImpl)); + auto* view = dynamic_cast(viewImpl.get()); + CHECK((nullptr != view)); { auto docJson = arangodb::velocypack::Parser::fromJson("{\"abc\": \"def\"}"); @@ -778,10 +1267,10 @@ SECTION("test_link") { // drop invalid collection { TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto viewImpl = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); - REQUIRE((false == !viewImpl)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); { std::set cids; @@ -802,10 +1291,10 @@ SECTION("test_link") { TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); CHECK((nullptr != logicalCollection)); - auto viewImpl = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); - REQUIRE((false == !viewImpl)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); { std::set cids; @@ -826,15 +1315,16 @@ SECTION("test_link") { TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); CHECK((nullptr != logicalCollection)); - auto viewImpl = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); - REQUIRE((false == !viewImpl)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); auto links = arangodb::velocypack::Parser::fromJson("{ \ \"links\": { \"testCollection\": {} } \ }"); - CHECK((true == viewImpl->updateProperties(links->slice(), true, false).ok())); + CHECK((true == logicalView->updateProperties(links->slice(), true, false).ok())); { std::set cids; @@ -854,47 +1344,47 @@ SECTION("test_link") { // drop invalid collection + recreate { - Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto logicalView = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + auto logicalView = vocbase.createView(viewJson->slice(), 0); REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); { std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); CHECK((0 == cids.size())); } { - CHECK((false == logicalView->link(100, arangodb::iresearch::emptyObjectSlice()).ok())); + CHECK((false == viewImpl->link(100, arangodb::iresearch::emptyObjectSlice()).ok())); std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); CHECK((0 == cids.size())); } } // drop non-existing + recreate { - Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); CHECK((nullptr != logicalCollection)); - auto logicalView = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson->slice(), 0); REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); { std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); CHECK((0 == cids.size())); CHECK((true == logicalCollection->getIndexes().empty())); } { - CHECK((true == logicalView->link(logicalCollection->id(), arangodb::iresearch::emptyObjectSlice()).ok())); + CHECK((true == viewImpl->link(logicalCollection->id(), arangodb::iresearch::emptyObjectSlice()).ok())); std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); std::unordered_set expected = { 100 }; for (auto& cid: expected) { @@ -908,13 +1398,13 @@ SECTION("test_link") { // drop existing + recreate { - Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); CHECK((nullptr != logicalCollection)); - auto logicalView = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson->slice(), 0); REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); auto links = arangodb::velocypack::Parser::fromJson("{ \ \"links\": { \"testCollection\": { \"includeAllFields\": true } } \ @@ -923,7 +1413,7 @@ SECTION("test_link") { { std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); CHECK((1 == cids.size())); CHECK((1 == logicalCollection->getIndexes().size())); auto link = logicalCollection->getIndexes()[0]->toVelocyPack(true, false); @@ -933,9 +1423,9 @@ SECTION("test_link") { } { - CHECK((true == logicalView->link(logicalCollection->id(), arangodb::iresearch::emptyObjectSlice()).ok())); + CHECK((true == viewImpl->link(logicalCollection->id(), arangodb::iresearch::emptyObjectSlice()).ok())); std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); std::unordered_set expected = { 100 }; for (auto& cid: expected) { @@ -953,13 +1443,13 @@ SECTION("test_link") { // drop existing + recreate invalid { - Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); + TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); CHECK((nullptr != logicalCollection)); - auto logicalView = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson->slice(), 0); REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); auto links = arangodb::velocypack::Parser::fromJson("{ \ \"links\": { \"testCollection\": { \"includeAllFields\": true } } \ @@ -968,7 +1458,7 @@ SECTION("test_link") { { std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); CHECK((1 == cids.size())); CHECK((1 == logicalCollection->getIndexes().size())); auto link = logicalCollection->getIndexes()[0]->toVelocyPack(true, false); @@ -983,9 +1473,9 @@ SECTION("test_link") { builder.add("includeAllFields", arangodb::velocypack::Value("abc")); builder.close(); auto slice = builder.slice(); - CHECK((false == logicalView->link(logicalCollection->id(), slice).ok())); + CHECK((false == viewImpl->link(logicalCollection->id(), slice).ok())); std::set cids; - logicalView->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); std::unordered_set expected = { 100 }; for (auto& cid: expected) { @@ -1007,10 +1497,10 @@ SECTION("test_open") { { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); std::string dataPath = (((irs::utf8_path()/=s.testFilesystemPath)/=std::string("databases"))/=std::string("arangosearch-123")).utf8(); - auto namedJson = arangodb::velocypack::Parser::fromJson("{ \"id\": 123, \"name\": \"testView\", \"type\": \"testType\" }"); + auto json = arangodb::velocypack::Parser::fromJson("{ \"id\": 123, \"name\": \"testView\", \"type\": \"testType\" }"); CHECK((false == TRI_IsDirectory(dataPath.c_str()))); - auto view = arangodb::iresearch::IResearchView::make(vocbase, namedJson->slice(), false); + auto view = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); CHECK((false == !view)); CHECK((false == TRI_IsDirectory(dataPath.c_str()))); view->open(); @@ -1032,9 +1522,9 @@ SECTION("test_query") { // no filter/order provided, means "RETURN *" { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto logicalView = vocbase.createView(createJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); @@ -1045,9 +1535,9 @@ SECTION("test_query") { // ordered iterator { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto logicalView = vocbase.createView(createJson->slice(), 0); + CHECK((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); CHECK((false == !view)); // fill with test data @@ -1084,11 +1574,11 @@ SECTION("test_query") { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); std::vector collections{ logicalCollection->name() }; - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto logicalView = vocbase.createView(createJson->slice(), 0); + CHECK((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); CHECK((false == !view)); - arangodb::Result res = view->updateProperties(links->slice(), true, false); + arangodb::Result res = logicalView->updateProperties(links->slice(), true, false); CHECK(true == res.ok()); CHECK((false == logicalCollection->getIndexes().empty())); @@ -1153,11 +1643,11 @@ SECTION("test_query") { REQUIRE(feature); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); - auto view = std::dynamic_pointer_cast( - vocbase.createView(viewCreateJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewCreateJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); - arangodb::Result res = view->updateProperties(viewUpdateJson->slice(), true, false); + arangodb::Result res = logicalView->updateProperties(viewUpdateJson->slice(), true, false); REQUIRE(true == res.ok()); // start flush thread @@ -1205,6 +1695,11 @@ SECTION("test_query") { } SECTION("test_register_link") { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\", \"id\": 100 }"); auto viewJson0 = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\", \"id\": 101 }"); auto viewJson1 = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\", \"id\": 101, \"properties\": { \"collections\": [ 100 ] } }"); @@ -1212,12 +1707,12 @@ SECTION("test_register_link") { // new link in recovery { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); - auto view = std::dynamic_pointer_cast( - vocbase.createView(viewJson0->slice(), 0) - ); - + auto logicalView = vocbase.createView(viewJson0->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); { @@ -1244,20 +1739,33 @@ SECTION("test_register_link") { auto before = StorageEngineMock::inRecoveryResult; StorageEngineMock::inRecoveryResult = true; auto restore = irs::make_finally([&before]()->void { StorageEngineMock::inRecoveryResult = before; }); + persisted = false; auto link = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == persisted)); CHECK((false == !link)); - std::set cids; - view->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); - CHECK((0 == cids.size())); // link addition does not modify view meta + + // link addition does modify view meta + { + std::unordered_set expected = { 100 }; + std::set actual; + view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } } // new link { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); - auto view = std::dynamic_pointer_cast( - vocbase.createView(viewJson0->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson0->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); { @@ -1291,7 +1799,9 @@ SECTION("test_register_link") { CHECK((actual.empty())); } + persisted = false; auto link = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((true == persisted)); // link instantiation does modify and persist view meta CHECK((false == !link)); std::unordered_set cids; view->sync(); @@ -1300,20 +1810,28 @@ SECTION("test_register_link") { arangodb::iresearch::appendKnownCollections(cids, *snapshot); CHECK((0 == cids.size())); // link addition does trigger collection load + // link addition does modify view meta { + std::unordered_set expected = { 100 }; std::set actual; view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }); - CHECK((actual.empty())); // link addition does not modify view meta + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); } } // known link { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); - auto view = std::dynamic_pointer_cast( - vocbase.createView(viewJson1->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson1->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); { @@ -1337,7 +1855,35 @@ SECTION("test_register_link") { CHECK((actual.empty())); } + persisted = false; + auto link0 = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == persisted)); + CHECK((false == !link0)); + + { + std::unordered_set cids; + view->sync(); + TrxStatePtr state(s.engine.createTransactionState(nullptr, arangodb::transaction::Options())); + auto* snapshot = view->snapshot(*state, true); + arangodb::iresearch::appendKnownCollections(cids, *snapshot); + CHECK((0 == cids.size())); // link addition does trigger collection load + } + + { + std::unordered_set expected = { 100, 123 }; + std::set actual = { 123 }; + view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }); + + for (auto& cid: expected) { + CHECK((1 == actual.erase(cid))); + } + + CHECK((actual.empty())); + } + + persisted = false; auto link1 = arangodb::iresearch::IResearchMMFilesLink::make(1, logicalCollection, linkJson->slice()); + CHECK((false == persisted)); CHECK((false == !link1)); // duplicate link creation is allowed std::unordered_set cids; view->sync(); @@ -1361,16 +1907,22 @@ SECTION("test_register_link") { } SECTION("test_unregister_link") { + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; + auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\", \"id\": 100 }"); auto viewJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\", \"id\": 101, \"properties\": { } }"); // link removed before view (in recovery) { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); - auto view = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); // add a document to the view @@ -1392,7 +1944,7 @@ SECTION("test_unregister_link") { \"links\": { \"testCollection\": {} } \ }"); - arangodb::Result res = view->updateProperties(links->slice(), true, false); + arangodb::Result res = logicalView->updateProperties(links->slice(), true, false); CHECK(true == res.ok()); CHECK((false == logicalCollection->getIndexes().empty())); @@ -1422,7 +1974,9 @@ SECTION("test_unregister_link") { auto before = StorageEngineMock::inRecoveryResult; StorageEngineMock::inRecoveryResult = true; auto restore = irs::make_finally([&before]()->void { StorageEngineMock::inRecoveryResult = before; }); + persisted = false; CHECK((TRI_ERROR_NO_ERROR == vocbase.dropCollection(logicalCollection, true, -1))); + CHECK((false == persisted)); // link removal does not persist view meta CHECK((nullptr == vocbase.lookupCollection("testCollection"))); { @@ -1447,11 +2001,12 @@ SECTION("test_unregister_link") { // link removed before view { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); - auto view = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); // add a document to the view @@ -1473,7 +2028,7 @@ SECTION("test_unregister_link") { \"links\": { \"testCollection\": {} } \ }"); - arangodb::Result res = view->updateProperties(links->slice(), true, false); + arangodb::Result res = logicalView->updateProperties(links->slice(), true, false); CHECK(true == res.ok()); CHECK((false == logicalCollection->getIndexes().empty())); @@ -1499,7 +2054,9 @@ SECTION("test_unregister_link") { } CHECK((nullptr != vocbase.lookupCollection("testCollection"))); + persisted = false; CHECK((TRI_ERROR_NO_ERROR == vocbase.dropCollection(logicalCollection, true, -1))); + CHECK((true == persisted)); // collection removal persists view meta CHECK((nullptr == vocbase.lookupCollection("testCollection"))); { @@ -1524,18 +2081,19 @@ SECTION("test_unregister_link") { // view removed before link { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); - auto view = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* view = dynamic_cast(logicalView.get()); REQUIRE((false == !view)); auto links = arangodb::velocypack::Parser::fromJson("{ \ \"links\": { \"testCollection\": {} } \ }"); - arangodb::Result res = view->updateProperties(links->slice(), true, false); + arangodb::Result res = logicalView->updateProperties(links->slice(), true, false); CHECK(true == res.ok()); CHECK((false == logicalCollection->getIndexes().empty())); @@ -1552,37 +2110,42 @@ SECTION("test_unregister_link") { // view deallocated before link removed { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); { + auto createJson = arangodb::velocypack::Parser::fromJson("{}"); auto updateJson = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection\": {} } }"); - auto viewImpl = vocbase.createView(viewJson->slice(), 0); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get());auto x0 = logicalView.use_count(); REQUIRE((nullptr != viewImpl)); CHECK((viewImpl->updateProperties(updateJson->slice(), true, false).ok())); CHECK((false == logicalCollection->getIndexes().empty())); std::set cids; viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); CHECK((1 == cids.size())); - logicalCollection->getIndexes()[0]->unload(); // release view reference to prevent deadlock due to ~IResearchView() waiting for IResearchLink::unload() + CHECK((TRI_ERROR_NO_ERROR == vocbase.dropView(logicalView->name()))); + CHECK((1 == logicalView.use_count())); // ensure destructor for ViewImplementation is called CHECK((false == logicalCollection->getIndexes().empty())); } // create a new view with same ID to validate links { - auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\":\"arangosearch\"}"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), true) - ); + auto json = arangodb::velocypack::Parser::fromJson("{}"); + auto view = arangodb::iresearch::IResearchView::make(vocbase, viewJson->slice(), true); REQUIRE((false == !view)); + auto* viewImpl = dynamic_cast(view.get()); + REQUIRE((nullptr != viewImpl)); std::set cids; - view->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); + viewImpl->visitCollections([&cids](TRI_voc_cid_t cid)->bool { cids.emplace(cid); return true; }); CHECK((0 == cids.size())); for (auto& index: logicalCollection->getIndexes()) { auto* link = dynamic_cast(index.get()); - REQUIRE((*link != *view)); // check that link is unregistred from view + REQUIRE((*link != *viewImpl)); // check that link is unregistred from view } } } @@ -1600,13 +2163,13 @@ SECTION("test_self_token") { { auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false) - ); + auto view = arangodb::iresearch::IResearchView::make(vocbase, json->slice(), false); CHECK((false == !view)); - self = view->self(); + auto* viewImpl = dynamic_cast(view.get()); + REQUIRE((nullptr != viewImpl)); + self = viewImpl->self(); CHECK((false == !self)); - CHECK((view.get() == self->get())); + CHECK((viewImpl == self->get())); } CHECK((false == !self)); @@ -1619,30 +2182,37 @@ SECTION("test_tracked_cids") { // test empty before open (TRI_vocbase_t::createView(...) will call open()) { + s.engine.views.clear(); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto view = arangodb::iresearch::IResearchView::make(vocbase, viewJson->slice(), true); CHECK((nullptr != view)); + auto* viewImpl = dynamic_cast(view.get()); + REQUIRE((nullptr != viewImpl)); std::set actual; - view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }); + viewImpl->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }); CHECK((actual.empty())); } // test add via link before open (TRI_vocbase_t::createView(...) will call open()) { + s.engine.views.clear(); auto updateJson = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection\": { } } }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto view = arangodb::iresearch::IResearchView::make(vocbase, viewJson->slice(), true); - REQUIRE((nullptr != view)); - StorageEngineMock().registerView(&vocbase, std::shared_ptr(view.get(), [](arangodb::LogicalView*)->void{})); // ensure link can find view + auto logicalView = arangodb::iresearch::IResearchView::make(vocbase, viewJson->slice(), true); + REQUIRE((false == !logicalView)); + StorageEngineMock().registerView(&vocbase, logicalView); // ensure link can find view + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); + CHECK((s.engine.persistView(&vocbase, viewImpl).ok())); // register view with engine - CHECK((view->updateProperties(updateJson->slice(), false, false).ok())); + CHECK((viewImpl->updateProperties(updateJson->slice(), false, false).ok())); std::set actual; std::set expected = { 100 }; - view->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }); + viewImpl->visitCollections([&actual](TRI_voc_cid_t cid)->bool { actual.emplace(cid); return true; }); for (auto& cid: actual) { CHECK((1 == expected.erase(cid))); @@ -1654,16 +2224,18 @@ SECTION("test_tracked_cids") { // test drop via link before open (TRI_vocbase_t::createView(...) will call open()) { + s.engine.views.clear(); auto updateJson0 = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection\": { } } }"); auto updateJson1 = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection\": null } }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto viewImpl = std::dynamic_pointer_cast( - arangodb::iresearch::IResearchView::make(vocbase, viewJson->slice(), true) - ); + auto logicalView = arangodb::iresearch::IResearchView::make(vocbase, viewJson->slice(), true); + REQUIRE((false == !logicalView)); + StorageEngineMock().registerView(&vocbase, logicalView); // ensure link can find view + auto* viewImpl = dynamic_cast(logicalView.get()); REQUIRE((nullptr != viewImpl)); - StorageEngineMock().registerView(&vocbase, std::shared_ptr(viewImpl.get(), [](arangodb::LogicalView*)->void{})); // ensure link can find view + CHECK((s.engine.persistView(&vocbase, viewImpl).ok())); // register view with engine // create link { @@ -1695,13 +2267,14 @@ SECTION("test_tracked_cids") { { // initial populate persisted view { + s.engine.views.clear(); auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\", \"id\": 102, \"properties\": { } }"); auto* feature = arangodb::iresearch::getFeature("Flush"); REQUIRE(feature); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto viewImpl = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto logicalView = vocbase.createView(createJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); REQUIRE((nullptr != viewImpl)); static std::vector const EMPTY; @@ -1720,9 +2293,12 @@ SECTION("test_tracked_cids") { // test persisted CIDs on open { + s.engine.views.clear(); auto createJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\", \"id\": 102, \"properties\": { } }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto viewImpl = vocbase.createView(createJson->slice(), 0); + auto logicalView = vocbase.createView(createJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); REQUIRE((nullptr != viewImpl)); std::set actual; @@ -1733,11 +2309,14 @@ SECTION("test_tracked_cids") { // test add via link after open (TRI_vocbase_t::createView(...) will call open()) { + s.engine.views.clear(); auto updateJson = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection\": { } } }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto viewImpl = vocbase.createView(viewJson->slice(), 0); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); REQUIRE((nullptr != viewImpl)); CHECK((viewImpl->updateProperties(updateJson->slice(), false, false).ok())); @@ -1755,12 +2334,15 @@ SECTION("test_tracked_cids") { // test drop via link after open (TRI_vocbase_t::createView(...) will call open()) { + s.engine.views.clear(); auto updateJson0 = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection\": { } } }"); auto updateJson1 = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection\": null } }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto viewImpl = vocbase.createView(viewJson->slice(), 0); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); REQUIRE((nullptr != viewImpl)); // create link @@ -1798,15 +2380,15 @@ SECTION("test_transaction_registration") { REQUIRE((nullptr != logicalCollection0)); auto* logicalCollection1 = vocbase.createCollection(collectionJson1->slice()); REQUIRE((nullptr != logicalCollection1)); - auto logicalView = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); - REQUIRE((nullptr != logicalView)); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); + REQUIRE((nullptr != viewImpl)); // link collection to view { auto updateJson = arangodb::velocypack::Parser::fromJson("{ \"links\": { \"testCollection0\": {}, \"testCollection1\": {} } }"); - CHECK((logicalView->updateProperties(updateJson->slice(), false, false).ok())); + CHECK((viewImpl->updateProperties(updateJson->slice(), false, false).ok())); } // read transaction (by id) @@ -2075,9 +2657,9 @@ SECTION("test_transaction_snapshot") { static std::vector const EMPTY; auto viewJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\", \"type\": \"arangosearch\", \"commit\": { \"commitIntervalMsec\": 0 } }"); Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto viewImpl = std::dynamic_pointer_cast( - vocbase.createView(viewJson->slice(), 0) - ); + auto logicalView = vocbase.createView(viewJson->slice(), 0); + REQUIRE((false == !logicalView)); + auto* viewImpl = dynamic_cast(logicalView.get()); REQUIRE((nullptr != viewImpl)); // add a single document to view (do not sync) @@ -2214,9 +2796,7 @@ SECTION("test_update_overwrite") { // modify meta params { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); // initial update (overwrite) @@ -2240,20 +2820,20 @@ SECTION("test_update_overwrite") { builder.close(); auto slice = builder.slice(); - CHECK(slice.isObject()); - CHECK(slice.get("name").copyString() == "testView"); - CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); - CHECK(slice.get("deleted").isNone()); // no system properties - CHECK(4 == slice.length()); arangodb::iresearch::IResearchViewMeta meta; std::string error; - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); + CHECK(slice.isObject()); + CHECK(4 == slice.length()); + CHECK(slice.get("name").copyString() == "testView"); + CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); + CHECK(slice.get("deleted").isNone()); // no system properties + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } @@ -2274,19 +2854,19 @@ SECTION("test_update_overwrite") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } } @@ -2300,9 +2880,7 @@ SECTION("test_update_overwrite") { REQUIRE((nullptr != logicalCollection0)); auto* logicalCollection1 = vocbase.createCollection(collectionJson1->slice()); REQUIRE((nullptr != logicalCollection1)); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); CHECK((true == logicalCollection0->getIndexes().empty())); @@ -2325,19 +2903,19 @@ SECTION("test_update_overwrite") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); for (arangodb::velocypack::ObjectIterator itr(tmpSlice); itr.valid(); ++itr) { @@ -2378,19 +2956,19 @@ SECTION("test_update_overwrite") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); for (arangodb::velocypack::ObjectIterator itr(tmpSlice); itr.valid(); ++itr) { @@ -2421,9 +2999,7 @@ SECTION("test_update_overwrite") { auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); @@ -2445,10 +3021,10 @@ SECTION("test_update_overwrite") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - - auto tmpSlice = slice.get("properties").get("collections"); + slice = slice.get("properties"); + auto tmpSlice = slice.get("collections"); CHECK((true == tmpSlice.isArray() && 1 == tmpSlice.length())); - tmpSlice = slice.get("properties").get("links"); + tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); tmpSlice = tmpSlice.get("testCollection"); CHECK((true == tmpSlice.isObject())); @@ -2473,7 +3049,8 @@ SECTION("test_update_overwrite") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - auto tmpSlice = slice.get("properties").get("links"); + slice = slice.get("properties"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); tmpSlice = tmpSlice.get("testCollection"); CHECK((true == tmpSlice.isObject())); @@ -2488,13 +3065,15 @@ SECTION("test_update_partial") { \"name\": \"testView\", \ \"type\": \"arangosearch\" \ }"); + bool persisted = false; + auto before = StorageEngineMock::before; + auto restore = irs::make_finally([&before]()->void { StorageEngineMock::before = before; }); + StorageEngineMock::before = [&persisted]()->void { persisted = true; }; // modify meta params { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); @@ -2517,28 +3096,26 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } // test rollback on meta modification failure (as an example invalid value for 'locale') { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); @@ -2558,19 +3135,19 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } @@ -2580,9 +3157,7 @@ SECTION("test_update_partial") { auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); @@ -2593,7 +3168,9 @@ SECTION("test_update_partial") { auto before = StorageEngineMock::inRecoveryResult; StorageEngineMock::inRecoveryResult = true; auto restore = irs::make_finally([&before]()->void { StorageEngineMock::inRecoveryResult = before; }); + persisted = false; CHECK((view->updateProperties(updateJson->slice(), true, false).ok())); + CHECK((false == persisted)); arangodb::velocypack::Builder builder; @@ -2606,13 +3183,12 @@ SECTION("test_update_partial") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); + slice = slice.get("properties"); + CHECK(slice.isObject()); CHECK(( - true == propSlice.hasKey("links") - && propSlice.get("links").isObject() - && 1 == propSlice.get("links").length() + true == slice.hasKey("links") + && slice.get("links").isObject() + && 1 == slice.get("links").length() )); } @@ -2622,11 +3198,8 @@ SECTION("test_update_partial") { auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); - REQUIRE(view->category() == arangodb::LogicalView::category()); arangodb::iresearch::IResearchViewMeta expectedMeta; std::unordered_map expectedLinkMeta; @@ -2637,7 +3210,9 @@ SECTION("test_update_partial") { expectedMeta._collections.insert(logicalCollection->id()); expectedLinkMeta["testCollection"]; // use defaults + persisted = false; CHECK((view->updateProperties(updateJson->slice(), true, false).ok())); + CHECK((true == persisted)); // link addition does modify and persist view meta arangodb::velocypack::Builder builder; @@ -2646,19 +3221,19 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); for (arangodb::velocypack::ObjectIterator itr(tmpSlice); itr.valid(); ++itr) { @@ -2686,9 +3261,7 @@ SECTION("test_update_partial") { auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); @@ -2714,7 +3287,9 @@ SECTION("test_update_partial") { expectedMeta._collections.insert(logicalCollection->id()); expectedLinkMeta["testCollection"]; // use defaults + persisted = false; CHECK((view->updateProperties(updateJson->slice(), true, false).ok())); + CHECK((true == persisted)); // link addition does modify and persist view meta arangodb::velocypack::Builder builder; @@ -2723,18 +3298,18 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); for (arangodb::velocypack::ObjectIterator itr(tmpSlice); itr.valid(); ++itr) { @@ -2759,9 +3334,7 @@ SECTION("test_update_partial") { // add new link to non-existant collection { Vocbase vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); @@ -2780,18 +3353,18 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } @@ -2801,9 +3374,7 @@ SECTION("test_update_partial") { auto collectionJson = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testCollection\" }"); auto* logicalCollection = vocbase.createCollection(collectionJson->slice()); REQUIRE((nullptr != logicalCollection)); - auto view = std::dynamic_pointer_cast( - vocbase.createView(createJson->slice(), 0) - ); + auto view = vocbase.createView(createJson->slice(), 0); REQUIRE((false == !view)); REQUIRE(view->category() == arangodb::LogicalView::category()); @@ -2811,7 +3382,12 @@ SECTION("test_update_partial") { auto updateJson = arangodb::velocypack::Parser::fromJson( "{ \"links\": { \"testCollection\": {} } }" ); + persisted = false; + auto beforeRecovery = StorageEngineMock::inRecoveryResult; + StorageEngineMock::inRecoveryResult = true; + auto restoreRecovery = irs::make_finally([&beforeRecovery]()->void { StorageEngineMock::inRecoveryResult = beforeRecovery; }); CHECK((view->updateProperties(updateJson->slice(), true, false).ok())); + CHECK((false == persisted)); // link addition does not persist view meta arangodb::velocypack::Builder builder; @@ -2824,12 +3400,11 @@ SECTION("test_update_partial") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - - auto propSlice = slice.get("properties"); + slice = slice.get("properties"); CHECK(( - true == propSlice.hasKey("links") - && propSlice.get("links").isObject() - && 1 == propSlice.get("links").length() + true == slice.hasKey("links") + && slice.get("links").isObject() + && 1 == slice.get("links").length() )); } @@ -2841,7 +3416,9 @@ SECTION("test_update_partial") { auto before = StorageEngineMock::inRecoveryResult; StorageEngineMock::inRecoveryResult = true; auto restore = irs::make_finally([&before]()->void { StorageEngineMock::inRecoveryResult = before; }); + persisted = false; CHECK((view->updateProperties(updateJson->slice(), true, false).ok())); + CHECK((false == persisted)); arangodb::velocypack::Builder builder; @@ -2854,12 +3431,11 @@ SECTION("test_update_partial") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - - auto propSlice = slice.get("properties"); + slice = slice.get("properties"); CHECK(( - true == propSlice.hasKey("links") - && propSlice.get("links").isObject() - && 0 == propSlice.get("links").length() + true == slice.hasKey("links") + && slice.get("links").isObject() + && 0 == slice.get("links").length() )); } } @@ -2892,19 +3468,19 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); } @@ -2924,19 +3500,19 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } } @@ -2962,19 +3538,19 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } @@ -3002,19 +3578,19 @@ SECTION("test_update_partial") { builder.close(); auto slice = builder.slice(); + arangodb::iresearch::IResearchViewMeta meta; + std::string error; + CHECK(slice.isObject()); CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - arangodb::iresearch::IResearchViewMeta meta; - std::string error; + slice = slice.get("properties"); + CHECK(slice.isObject()); + CHECK((6U == slice.length())); + CHECK((meta.init(slice, error) && expectedMeta == meta)); - auto propSlice = slice.get("properties"); - CHECK(propSlice.isObject()); - CHECK((6U == propSlice.length())); - CHECK((meta.init(propSlice, error) && expectedMeta == meta)); - - auto tmpSlice = propSlice.get("links"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 0 == tmpSlice.length())); } @@ -3045,7 +3621,8 @@ SECTION("test_update_partial") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - auto tmpSlice = slice.get("properties").get("links"); + slice = slice.get("properties"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); } @@ -3073,7 +3650,8 @@ SECTION("test_update_partial") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - auto tmpSlice = slice.get("properties").get("links"); + slice = slice.get("properties"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); std::unordered_set actual; @@ -3113,9 +3691,10 @@ SECTION("test_update_partial") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - auto tmpSlice = slice.get("properties").get("collections"); + slice = slice.get("properties"); + auto tmpSlice = slice.get("collections"); CHECK((true == tmpSlice.isArray() && 1 == tmpSlice.length())); - tmpSlice = slice.get("properties").get("links"); + tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); tmpSlice = tmpSlice.get("testCollection"); CHECK((true == tmpSlice.isObject())); @@ -3141,7 +3720,8 @@ SECTION("test_update_partial") { CHECK(slice.get("name").copyString() == "testView"); CHECK(slice.get("type").copyString() == arangodb::iresearch::IResearchView::type().name()); CHECK(slice.get("deleted").isNone()); // no system properties - auto tmpSlice = slice.get("properties").get("links"); + slice = slice.get("properties"); + auto tmpSlice = slice.get("links"); CHECK((true == tmpSlice.isObject() && 1 == tmpSlice.length())); tmpSlice = tmpSlice.get("testCollection"); CHECK((true == tmpSlice.isObject())); @@ -3151,8 +3731,12 @@ SECTION("test_update_partial") { } } +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate tests +//////////////////////////////////////////////////////////////////////////////// + } // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/IResearch/StorageEngineMock.cpp b/tests/IResearch/StorageEngineMock.cpp index a0774d4f02..24704794b7 100644 --- a/tests/IResearch/StorageEngineMock.cpp +++ b/tests/IResearch/StorageEngineMock.cpp @@ -544,8 +544,6 @@ arangodb::PhysicalCollection* PhysicalCollectionMock::clone(arangodb::LogicalCol } int PhysicalCollectionMock::close() { - before(); - for (auto& index: _indexes) { index->unload(); } @@ -943,6 +941,7 @@ arangodb::Result PhysicalCollectionMock::updateProperties(arangodb::velocypack:: return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock collection updated OK } +std::function StorageEngineMock::before = []()->void {}; bool StorageEngineMock::inRecoveryResult = false; StorageEngineMock::StorageEngineMock() @@ -956,10 +955,12 @@ arangodb::WalAccess const* StorageEngineMock::walAccess() const { } void StorageEngineMock::addAqlFunctions() { + before(); // NOOP } void StorageEngineMock::addOptimizerRules() { + before(); // NOOP } @@ -975,8 +976,17 @@ void StorageEngineMock::changeCollection(TRI_vocbase_t* vocbase, TRI_voc_cid_t i // NOOP, assume physical collection changed OK } -void StorageEngineMock::changeView(TRI_vocbase_t* vocbase, TRI_voc_cid_t id, arangodb::LogicalView const*, bool doSync) { - // does nothing +void StorageEngineMock::changeView(TRI_vocbase_t* vocbase, TRI_voc_cid_t id, arangodb::LogicalView const* view, bool doSync) { + before(); + TRI_ASSERT(vocbase); + TRI_ASSERT(view); + TRI_ASSERT(views.find(std::make_pair(vocbase->id(), view->id())) != views.end()); + arangodb::velocypack::Builder builder; + + builder.openObject(); + view->toVelocyPack(builder, true, true); + builder.close(); + views[std::make_pair(vocbase->id(), view->id())] = std::move(builder); } std::string StorageEngineMock::collectionPath(TRI_vocbase_t const* vocbase, TRI_voc_cid_t id) const { @@ -1003,6 +1013,7 @@ arangodb::Result StorageEngineMock::createLoggerState(TRI_vocbase_t*, VPackBuild } arangodb::PhysicalCollection* StorageEngineMock::createPhysicalCollection(arangodb::LogicalCollection* collection, VPackSlice const& info) { + before(); return new PhysicalCollectionMock(collection, info); } @@ -1016,6 +1027,7 @@ arangodb::TransactionCollection* StorageEngineMock::createTransactionCollection( } arangodb::transaction::ContextData* StorageEngineMock::createTransactionContextData() { + before(); return new ContextDataMock(); } @@ -1029,18 +1041,22 @@ arangodb::TransactionState* StorageEngineMock::createTransactionState(TRI_vocbas } void StorageEngineMock::createView(TRI_vocbase_t* vocbase, TRI_voc_cid_t id, arangodb::LogicalView const*) { + before(); // NOOP, assume physical view created OK } void StorageEngineMock::getViewProperties(TRI_vocbase_t* vocbase, arangodb::LogicalView const* view, VPackBuilder& builder) { + before(); // NOOP } TRI_voc_tick_t StorageEngineMock::currentTick() const { + before(); return TRI_CurrentTickServer(); } std::string StorageEngineMock::databasePath(TRI_vocbase_t const* vocbase) const { + before(); return ""; // no valid path filesystem persisted, return empty string } @@ -1049,7 +1065,8 @@ void StorageEngineMock::destroyCollection(TRI_vocbase_t* vocbase, arangodb::Logi } void StorageEngineMock::destroyView(TRI_vocbase_t* vocbase, arangodb::LogicalView* view) noexcept { - // NOOP + before(); + // NOOP, assume physical view destroyed OK } arangodb::Result StorageEngineMock::dropCollection(TRI_vocbase_t* vocbase, arangodb::LogicalCollection* collection) { @@ -1061,12 +1078,13 @@ arangodb::Result StorageEngineMock::dropDatabase(TRI_vocbase_t*) { return arangodb::Result(); } -arangodb::Result StorageEngineMock::renameView(TRI_vocbase_t* vocbase, std::shared_ptr, - std::string const& newName) { - return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock view renames OK -} +arangodb::Result StorageEngineMock::dropView(TRI_vocbase_t* vocbase, arangodb::LogicalView* view) { + before(); + TRI_ASSERT(vocbase); + TRI_ASSERT(view); + TRI_ASSERT(views.find(std::make_pair(vocbase->id(), view->id())) != views.end()); + views.erase(std::make_pair(vocbase->id(), view->id())); -arangodb::Result StorageEngineMock::dropView(TRI_vocbase_t*, arangodb::LogicalView*) { return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock view dropped OK } @@ -1094,6 +1112,7 @@ int StorageEngineMock::getCollectionsAndIndexes(TRI_vocbase_t* vocbase, arangodb } void StorageEngineMock::getDatabases(arangodb::velocypack::Builder& result) { + before(); arangodb::velocypack::Builder system; system.openObject(); @@ -1107,19 +1126,29 @@ void StorageEngineMock::getDatabases(arangodb::velocypack::Builder& result) { } arangodb::velocypack::Builder StorageEngineMock::getReplicationApplierConfiguration(TRI_vocbase_t* vocbase, int& result) { + before(); result = TRI_ERROR_FILE_NOT_FOUND; // assume no ReplicationApplierConfiguration for vocbase return arangodb::velocypack::Builder(); } arangodb::velocypack::Builder StorageEngineMock::getReplicationApplierConfiguration(int& result) { + before(); result = TRI_ERROR_FILE_NOT_FOUND; + return arangodb::velocypack::Builder(); } int StorageEngineMock::getViews(TRI_vocbase_t* vocbase, arangodb::velocypack::Builder& result) { - TRI_ASSERT(false); - return TRI_ERROR_INTERNAL; + result.openArray(); + + for (auto& entry: views) { + result.add(entry.second.slice()); + } + + result.close(); + + return TRI_ERROR_NO_ERROR; } arangodb::Result StorageEngineMock::handleSyncKeys(arangodb::DatabaseInitialSyncer&, arangodb::LogicalCollection*, std::string const&) { @@ -1137,6 +1166,8 @@ arangodb::Result StorageEngineMock::lastLogger(TRI_vocbase_t*, std::shared_ptrid(), view->id())) == views.end()); // called after createView() + arangodb::velocypack::Builder builder; + + builder.openObject(); + view->toVelocyPack(builder, true, true); + builder.close(); + views[std::make_pair(vocbase->id(), view->id())] = std::move(builder); + return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock view persisted OK } @@ -1167,10 +1210,12 @@ void StorageEngineMock::prepareDropDatabase(TRI_vocbase_t* vocbase, bool useWrit } TRI_voc_tick_t StorageEngineMock::releasedTick() const { + before(); return _releasedTick; } void StorageEngineMock::releaseTick(TRI_voc_tick_t tick) { + before(); _releasedTick = tick; } @@ -1189,6 +1234,21 @@ arangodb::Result StorageEngineMock::renameCollection(TRI_vocbase_t* vocbase, ara return arangodb::Result(TRI_ERROR_INTERNAL); } +arangodb::Result StorageEngineMock::renameView(TRI_vocbase_t* vocbase, std::shared_ptr view, std::string const& newName) { + before(); + TRI_ASSERT(vocbase); + TRI_ASSERT(view); + TRI_ASSERT(views.find(std::make_pair(vocbase->id(), view->id())) != views.end()); + arangodb::velocypack::Builder builder; + + builder.openObject(); + view->toVelocyPack(builder, true, true); + builder.close(); + views[std::make_pair(vocbase->id(), view->id())] = std::move(builder); + + return arangodb::Result(TRI_ERROR_NO_ERROR); // assume mock view renames OK +} + int StorageEngineMock::saveReplicationApplierConfiguration(TRI_vocbase_t*, arangodb::velocypack::Slice, bool) { TRI_ASSERT(false); return TRI_ERROR_NO_ERROR; @@ -1200,10 +1260,12 @@ int StorageEngineMock::saveReplicationApplierConfiguration(arangodb::velocypack: } int StorageEngineMock::shutdownDatabase(TRI_vocbase_t* vocbase) { + before(); return TRI_ERROR_NO_ERROR; // assume shutdown successful } void StorageEngineMock::signalCleanup(TRI_vocbase_t* vocbase) { + before(); // NOOP, assume cleanup thread signaled OK } @@ -1213,6 +1275,7 @@ bool StorageEngineMock::supportsDfdb() const { } void StorageEngineMock::unloadCollection(TRI_vocbase_t* vocbase, arangodb::LogicalCollection* collection) { + before(); // NOOP assume collection unloaded OK } @@ -1357,4 +1420,4 @@ bool TransactionStateMock::hasFailedOperations() const { // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/IResearch/StorageEngineMock.h b/tests/IResearch/StorageEngineMock.h index acd3f732a0..f49e7289f0 100644 --- a/tests/IResearch/StorageEngineMock.h +++ b/tests/IResearch/StorageEngineMock.h @@ -134,7 +134,9 @@ class TransactionStateMock: public arangodb::TransactionState { class StorageEngineMock: public arangodb::StorageEngine { public: + static std::function before; static bool inRecoveryResult; + std::map, arangodb::velocypack::Builder> views; std::vector> vocbases; // must allocate on heap because TRI_vocbase_t does not have a 'noexcept' move constructor StorageEngineMock(); @@ -207,4 +209,4 @@ class StorageEngineMock: public arangodb::StorageEngine { TRI_voc_tick_t _releasedTick; }; -#endif +#endif \ No newline at end of file diff --git a/tests/VocBase/LogicalDataSource-test.cpp b/tests/VocBase/LogicalDataSource-test.cpp index 0c0a080345..3b69309d59 100644 --- a/tests/VocBase/LogicalDataSource-test.cpp +++ b/tests/VocBase/LogicalDataSource-test.cpp @@ -64,6 +64,27 @@ SECTION("test_category") { CHECK((arangodb::LogicalCollection::category() == instance.category())); } + + // LogicalView + { + class LogicalViewImpl: public arangodb::LogicalView { + public: + LogicalViewImpl(TRI_vocbase_t* vocbase, arangodb::velocypack::Slice const& definition) + : LogicalView(vocbase, definition) { + } + virtual void drop() override {} + virtual void open() override {} + virtual arangodb::Result rename(std::string&& newName, bool doSync) override { return arangodb::Result(); } + virtual void toVelocyPack(arangodb::velocypack::Builder& result, bool includeProperties, bool includeSystem) const override {} + virtual arangodb::Result updateProperties(arangodb::velocypack::Slice const& properties, bool partialUpdate, bool doSync) override { return arangodb::Result(); } + virtual bool visitCollections(CollectionVisitor const& visitor) const override { return true; } + }; + + auto json = arangodb::velocypack::Parser::fromJson("{ \"name\": \"testView\" }"); + LogicalViewImpl instance(nullptr, json->slice()); + + CHECK((arangodb::LogicalView::category() == instance.category())); + } } //////////////////////////////////////////////////////////////////////////////// @@ -74,4 +95,4 @@ SECTION("test_category") { // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- \ No newline at end of file