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
|