aboutsummaryrefslogtreecommitdiffstats
path: root/storage/src/vespa/storage/persistence/testandsethelper.cpp
blob: 019327e7aede2c88f6714b9780fd3756b3b50eb6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// @author Vegard Sjonfjell

#include "testandsethelper.h"
#include "persistenceutil.h"
#include "fieldvisitor.h"
#include <vespa/persistence/spi/persistenceprovider.h>
#include <vespa/document/base/exceptions.h>
#include <vespa/document/select/parser.h>
#include <vespa/document/repo/documenttyperepo.h>
#include <vespa/vespalib/util/stringfmt.h>

using namespace std::string_literals;

namespace storage {

void TestAndSetHelper::resolveDocumentType(const document::DocumentTypeRepo & documentTypeRepo) {
    if (_docTypePtr != nullptr) return;
    if (!_docId.hasDocType()) {
        throw TestAndSetException(api::ReturnCode(api::ReturnCode::ILLEGAL_PARAMETERS, "Document id has no doctype"));
    }

    _docTypePtr = documentTypeRepo.getDocumentType(_docId.getDocType());
    if (_docTypePtr == nullptr) {
        throw TestAndSetException(api::ReturnCode(api::ReturnCode::ILLEGAL_PARAMETERS, "Document type does not exist"));
    }
}

void TestAndSetHelper::parseDocumentSelection(const document::DocumentTypeRepo & documentTypeRepo,
                                              const document::BucketIdFactory & bucketIdFactory) {
    document::select::Parser parser(documentTypeRepo, bucketIdFactory);

    try {
        _docSelectionUp = parser.parse(_condition.getSelection());
    } catch (const document::select::ParsingFailedException & e) {
        throw TestAndSetException(api::ReturnCode(api::ReturnCode::ILLEGAL_PARAMETERS, "Failed to parse test and set condition: "s + e.getMessage()));
    }
}

spi::GetResult TestAndSetHelper::retrieveDocument(const document::FieldSet & fieldSet, spi::Context & context) {
    return _spi.get(_env.getBucket(_docId, _bucket), fieldSet, _docId, context);
}

TestAndSetHelper::TestAndSetHelper(const PersistenceUtil& env,
                                   const spi::PersistenceProvider& spi,
                                   const document::BucketIdFactory& bucket_id_factory,
                                   const documentapi::TestAndSetCondition& condition,
                                   document::Bucket bucket,
                                   document::DocumentId doc_id,
                                   const document::DocumentType* doc_type_ptr,
                                   bool missingDocumentImpliesMatch)
    : _env(env),
      _spi(spi),
      _condition(condition),
      _bucket(bucket),
      _docId(std::move(doc_id)),
      _docTypePtr(doc_type_ptr),
      _missingDocumentImpliesMatch(missingDocumentImpliesMatch)
{
    const auto & repo = _env.getDocumentTypeRepo();
    resolveDocumentType(repo);
    parseDocumentSelection(repo, bucket_id_factory);
}

TestAndSetHelper::~TestAndSetHelper() = default;

TestAndSetHelper::Result
TestAndSetHelper::fetch_and_match_raw(spi::Context& context) {
    // Walk document selection tree to build a minimal field set
    FieldVisitor fieldVisitor(*_docTypePtr);
    try {
        _docSelectionUp->visit(fieldVisitor);
    } catch (const document::FieldNotFoundException& e) {
        throw TestAndSetException(api::ReturnCode(
                api::ReturnCode::ILLEGAL_PARAMETERS,
                vespalib::make_string("Condition field '%s' could not be found, or is an imported field. "
                                      "Imported fields are not supported in conditional mutations.",
                                      e.getFieldName().c_str())));
    }
    auto result = retrieveDocument(fieldVisitor.getFieldSet(), context);
    // If document exists, match it with selection
    if (result.hasDocument()) {
        auto docPtr = result.getDocumentPtr();
        if (_docSelectionUp->contains(*docPtr) != document::select::Result::True) {
            return {result.getTimestamp(), Result::ConditionOutcome::IsNotMatch};
        }
        // Document matches
        return {result.getTimestamp(), Result::ConditionOutcome::IsMatch};
    }
    return {result.getTimestamp(), result.is_tombstone() ? Result::ConditionOutcome::IsTombstone
                                                         : Result::ConditionOutcome::DocNotFound};
}

api::ReturnCode
TestAndSetHelper::to_api_return_code(const Result& result) const {
    switch (result.condition_outcome) {
    case Result::ConditionOutcome::IsNotMatch:
        return {api::ReturnCode::TEST_AND_SET_CONDITION_FAILED,
                vespalib::make_string("Condition did not match document nodeIndex=%d bucket=%" PRIx64,
                                      _env._nodeIndex, _bucket.getBucketId().getRawId())};
    case Result::ConditionOutcome::IsTombstone:
    case Result::ConditionOutcome::DocNotFound:
        if (!_missingDocumentImpliesMatch) {
            return {api::ReturnCode::TEST_AND_SET_CONDITION_FAILED,
                    vespalib::make_string("Document does not exist nodeIndex=%d bucket=%" PRIx64,
                                          _env._nodeIndex, _bucket.getBucketId().getRawId())};
        }
        [[fallthrough]]; // as match
    case Result::ConditionOutcome::IsMatch:
        return {}; // OK
    }
    abort();
}

api::ReturnCode
TestAndSetHelper::retrieveAndMatch(spi::Context & context) {
    auto result = fetch_and_match_raw(context);
    return to_api_return_code(result);
}

} // storage