1
0
Fork 0
arangodb/arangod/IResearch/IResearchLink.cpp

584 lines
17 KiB
C++

//////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2017 EMC Corporation
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is EMC Corporation
///
/// @author Andrey Abramov
/// @author Vasiliy Nabatchikov
////////////////////////////////////////////////////////////////////////////////
#include "ApplicationServerHelper.h"
#include "IResearchFeature.h"
#include "VelocyPackHelper.h"
#include "Basics/LocalTaskQueue.h"
#include "Logger/Logger.h"
#include "Logger/LogMacros.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "StorageEngine/StorageEngine.h"
#include "StorageEngine/TransactionState.h"
#include "Transaction/Methods.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/LogicalView.h"
#include "IResearchLink.h"
NS_LOCAL
////////////////////////////////////////////////////////////////////////////////
/// @brief the string representing the link type
////////////////////////////////////////////////////////////////////////////////
static const std::string& LINK_TYPE = arangodb::iresearch::IResearchFeature::type();
////////////////////////////////////////////////////////////////////////////////
/// @brief the name of the field in the iResearch Link definition denoting the
/// iResearch Link type
////////////////////////////////////////////////////////////////////////////////
static const std::string LINK_TYPE_FIELD("type");
////////////////////////////////////////////////////////////////////////////////
/// @brief the id of the field in the iResearch Link definition denoting the
/// corresponding iResearch View
////////////////////////////////////////////////////////////////////////////////
static const std::string VIEW_ID_FIELD("view");
////////////////////////////////////////////////////////////////////////////////
/// @brief an IResearchView token not associated with any view
/// used to reduce null checks by always having '_view' initialized
////////////////////////////////////////////////////////////////////////////////
static const arangodb::iresearch::IResearchView::AsyncSelf::ptr NO_VIEW =
irs::memory::make_unique<arangodb::iresearch::IResearchView::AsyncSelf>(nullptr);
typedef irs::async_utils::read_write_mutex::read_mutex ReadMutex;
typedef irs::async_utils::read_write_mutex::write_mutex WriteMutex;
NS_END
NS_BEGIN(arangodb)
NS_BEGIN(iresearch)
IResearchLink::IResearchLink(
TRI_idx_iid_t iid,
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
}
bool IResearchLink::operator==(IResearchView const& view) const noexcept {
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
return _view && _view->id() == view.id();
}
bool IResearchLink::operator!=(IResearchView const& view) const noexcept {
return !(*this == view);
}
bool IResearchLink::operator==(IResearchLinkMeta const& meta) const noexcept {
return _meta == meta;
}
bool IResearchLink::operator!=(IResearchLinkMeta const& meta) const noexcept {
return !(*this == meta);
}
bool IResearchLink::allowExpansion() const {
return true; // maps to multivalued
}
void IResearchLink::batchInsert(
transaction::Methods* trx,
std::vector<std::pair<arangodb::LocalDocumentId, arangodb::velocypack::Slice>> const& batch,
std::shared_ptr<arangodb::basics::LocalTaskQueue> queue /*= nullptr*/
) {
if (batch.empty()) {
return; // nothing to do
}
if (!queue) {
throw std::runtime_error(std::string("failed to report status during batch insert for iResearch link '") + arangodb::basics::StringUtils::itoa(_id) + "'");
}
if (!_collection) {
queue->setStatus(TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED); // '_collection' required
return;
}
if (!trx) {
queue->setStatus(TRI_ERROR_BAD_PARAMETER); // 'trx' required
return;
}
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (!_view) {
queue->setStatus(TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED); // IResearchView required
return;
}
auto res = _view->insert(*trx, _collection->id(), batch, _meta);
if (TRI_ERROR_NO_ERROR != res) {
queue->setStatus(res);
}
}
bool IResearchLink::canBeDropped() const {
return true; // valid for a link to be dropped from an iResearch view
}
LogicalCollection* IResearchLink::collection() const noexcept {
return _collection;
}
int IResearchLink::drop() {
if (!_collection) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // '_collection' required
}
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (!_view) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // IResearchView required
}
// if the collection is in the process of being removed then drop it from the view
if (_collection->deleted()) {
auto result = _view->updateProperties(emptyObjectSlice(), true, false); // revalidate all links
if (!result.ok()) {
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH)
<< "failed to force view link revalidation while unloading dropped IResearch link '" << _id
<< "' for IResearch view '" << _view->id() << "'";
return result.errorNumber();
}
}
_dropCollectionInDestructor = false; // will do drop now
return _view->drop(_collection->id());
}
bool IResearchLink::hasBatchInsert() const {
return true;
}
bool IResearchLink::hasSelectivityEstimate() const {
return false; // selectivity can only be determined per query since multiple fields are indexed
}
TRI_idx_iid_t IResearchLink::id() const noexcept {
return _id;
}
bool IResearchLink::init(arangodb::velocypack::Slice const& definition) {
// disassociate from view if it has not been done yet
if (TRI_ERROR_NO_ERROR != unload()) {
return false;
}
std::string error;
IResearchLinkMeta meta;
if (!meta.init(definition, error)) {
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) << "error parsing view link parameters from json: " << error;
TRI_set_errno(TRI_ERROR_BAD_PARAMETER);
return false; // failed to parse metadata
}
if (collection() && definition.hasKey(VIEW_ID_FIELD)) {
auto identifier = definition.get(VIEW_ID_FIELD);
auto vocbase = collection()->vocbase();
if (vocbase && identifier.isNumber() && uint64_t(identifier.getInt()) == identifier.getUInt()) {
auto viewId = identifier.getUInt();
// NOTE: this will cause a deadlock if registering a link while view is being created
auto logicalView = vocbase->lookupView(viewId);
if (!logicalView || IResearchView::type() != logicalView->type()) {
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) << "error looking up view '" << viewId << "': no such view";
return false; // no such view
}
// TODO FIXME find a better way to look up an iResearch View
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
auto* view = dynamic_cast<IResearchView*>(logicalView.get());
#else
auto* view = static_cast<IResearchView*>(logicalView.get());
#endif
if (!view) {
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) << "error finding view: '" << viewId << "' for link '" << _id << "'";
return false;
}
auto viewSelf = view->self();
if (!viewSelf) {
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH)
<< "error read-locking view: '" << viewId
<< "' for link '" << _id << "'";
return false;
}
_viewLock = std::unique_lock<ReadMutex>(viewSelf->mutex()); // aquire read-lock before checking view
if (!viewSelf->get()) {
_viewLock.unlock();
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) << "error getting view: '" << viewId << "' for link '" << _id << "'";
return false;
}
_dropCollectionInDestructor = view->emplace(collection()->id()); // track if this is the instance that called emplace
_meta = std::move(meta);
_view = std::move(view);
// FIXME TODO remove once View::updateProperties(...) will be fixed to write
// the update delta into the WAL marker instead of the full persisted state
{
auto* engine = arangodb::EngineSelectorFeature::ENGINE;
if (engine && engine->inRecovery()) {
_defaultId = view->id();
}
}
return true;
}
}
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) << "error finding view for link '" << _id << "'";
TRI_set_errno(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
return false;
}
Result IResearchLink::insert(
transaction::Methods* trx,
arangodb::LocalDocumentId const& documentId,
VPackSlice const& doc,
Index::OperationMode mode
) {
if (!_collection) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // '_collection' required
}
if (!trx) {
return TRI_ERROR_BAD_PARAMETER; // 'trx' required
}
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (!_view) {
return TRI_ERROR_ARANGO_INDEX_HANDLE_BAD; // IResearchView required
}
return _view->insert(*trx, _collection->id(), documentId, doc, _meta);
}
bool IResearchLink::isPersistent() const {
auto* engine = arangodb::EngineSelectorFeature::ENGINE;
// FIXME TODO remove once MMFilesEngine will fillIndex(...) during recovery
// currently the index is created but fill is deffered untill the end of recovery
// at the end of recovery only non-persistent indexes are filled
if (engine && engine->inRecovery()) {
return false;
}
return true; // records persisted into the iResearch view
}
bool IResearchLink::isSorted() const {
return false; // iResearch does not provide a fixed default sort order
}
bool IResearchLink::json(
arangodb::velocypack::Builder& builder,
bool forPersistence
) const {
if (!builder.isOpenObject() || !_meta.json(builder)) {
return false;
}
builder.add("id", VPackValue(std::to_string(_id)));
builder.add(LINK_TYPE_FIELD, VPackValue(typeName()));
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (_view) {
builder.add(VIEW_ID_FIELD, VPackValue(_view->id()));
} else if (_defaultId) { // '0' _defaultId == no view name in source jSON
//if (_defaultId && forPersistence) { // MMFilesCollection::saveIndex(...) does not set 'forPersistence'
builder.add(VIEW_ID_FIELD, VPackValue(_defaultId));
}
return true;
}
void IResearchLink::load() {
// Note: this function is only used by RocksDB
}
bool IResearchLink::matchesDefinition(VPackSlice const& slice) const {
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (slice.hasKey(VIEW_ID_FIELD)) {
if (!_view) {
return false; // slice has identifier but the current object does not
}
auto identifier = slice.get(VIEW_ID_FIELD);
if (!identifier.isNumber()
|| uint64_t(identifier.getInt()) != identifier.getUInt()
|| identifier.getUInt() != _view->id()) {
return false; // iResearch View names of current object and slice do not match
}
} else if (_view) {
return false; // slice has no 'name' but the current object does
}
IResearchLinkMeta other;
std::string errorField;
return other.init(slice, errorField) && _meta == other;
}
size_t IResearchLink::memory() const {
auto size = sizeof(IResearchLink); // includes empty members from parent
size += _meta.memory();
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (_view) {
size_t count = 0;
// get a count of collections currently defined in the view
_view->visitCollections([&count](TRI_voc_cid_t)->bool {
++count;
return true;
});
// <iResearch View size> / <number of tracked collection IDs>
// a rough approximation of how much memory is used by each collection ID
size += _view->memory() / std::max(size_t(1), count);
}
return size;
}
/*static*/ arangodb::Result IResearchLink::normalize(
arangodb::velocypack::Builder& normalized,
velocypack::Slice definition,
bool // isCreation
) {
if (!normalized.isOpenObject()) {
return arangodb::Result(
TRI_ERROR_BAD_PARAMETER,
std::string("invalid output buffer provided for IResearch link normalized definition generation")
);
}
std::string error;
IResearchLinkMeta meta;
if (!meta.init(definition, error)) {
return arangodb::Result(
TRI_ERROR_BAD_PARAMETER,
std::string("error parsing IResearch link parameters from json: ") + error
);
}
normalized.add(LINK_TYPE_FIELD, arangodb::velocypack::Value(LINK_TYPE));
// copy over IResearch View identifier
if (definition.hasKey(VIEW_ID_FIELD)) {
normalized.add(VIEW_ID_FIELD, definition.get(VIEW_ID_FIELD));
}
return meta.json(normalized)
? arangodb::Result()
: arangodb::Result(
TRI_ERROR_BAD_PARAMETER,
std::string("error generating IResearch link normalized definition")
)
;
}
Result IResearchLink::remove(
transaction::Methods* trx,
LocalDocumentId const& documentId,
VPackSlice const& doc,
Index::OperationMode mode
) {
if (!_collection) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // '_collection' required
}
if (!trx) {
return TRI_ERROR_BAD_PARAMETER; // 'trx' required
}
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (!_view) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // IResearchView required
}
// remove documents matching on cid and rid
return _view->remove(*trx, _collection->id(), documentId);
}
Result IResearchLink::remove(
transaction::Methods* trx,
arangodb::LocalDocumentId const& documentId,
Index::OperationMode mode
) {
if (!_collection) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // '_collection' required
}
if (!trx) {
return TRI_ERROR_BAD_PARAMETER; // 'trx' required
}
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
if (!_view) {
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // IResearchView required
}
// remove documents matching on cid and documentId
return _view->remove(*trx, _collection->id(), documentId);
}
/*static*/ bool IResearchLink::setType(arangodb::velocypack::Builder& builder) {
if (!builder.isOpenObject()) {
return false;
}
builder.add(LINK_TYPE_FIELD, arangodb::velocypack::Value(LINK_TYPE));
return true;
}
/*static*/ bool IResearchLink::setView(
arangodb::velocypack::Builder& builder,
TRI_voc_cid_t value
) {
if (!builder.isOpenObject()) {
return false;
}
builder.add(VIEW_ID_FIELD, arangodb::velocypack::Value(value));
return true;
}
Index::IndexType IResearchLink::type() const {
// TODO: don't use enum
return Index::TRI_IDX_TYPE_IRESEARCH_LINK;
}
char const* IResearchLink::typeName() const {
return LINK_TYPE.c_str();
}
int IResearchLink::unload() {
WriteMutex mutex(_mutex); // '_view' can be asynchronously read
SCOPED_LOCK(mutex);
if (!_view) {
return TRI_ERROR_NO_ERROR;
}
_defaultId = _view->id(); // remember view ID just in case (e.g. call to toVelocyPack(...) after unload())
auto* col = collection();
if (!col) {
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH) << "failed finding collection while unloading IResearch link '" << _id << "'";
return TRI_ERROR_ARANGO_COLLECTION_NOT_LOADED; // '_collection' required
}
// this code is used by the MMFilesEngine
// if the collection is in the process of being removed then drop it from the view
// FIXME TODO remove once LogicalCollection::drop(...) will drop its indexes explicitly
if (col->deleted()) {
auto res = drop();
if (TRI_ERROR_NO_ERROR != res) {
LOG_TOPIC(WARN, iresearch::IResearchFeature::IRESEARCH)
<< "failed to drop collection from view while unloading dropped IResearch link '" << _id
<< "' for IResearch view '" << _view->id() << "'";
return res;
}
}
_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
return TRI_ERROR_NO_ERROR;
}
const IResearchView* IResearchLink::view() const {
ReadMutex mutex(_mutex); // '_view' can be asynchronously modified
SCOPED_LOCK(mutex);
return _view;
}
NS_END // iresearch
NS_END // arangodb
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------