|
| 1 | +//////////////////////////////////////////////////////////////////////////////// |
| 2 | +/// DISCLAIMER |
| 3 | +/// |
| 4 | +/// Copyright 2019 ArangoDB GmbH, Cologne, Germany |
| 5 | +/// |
| 6 | +/// Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | +/// you may not use this file except in compliance with the License. |
| 8 | +/// You may obtain a copy of the License at |
| 9 | +/// |
| 10 | +/// http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +/// |
| 12 | +/// Unless required by applicable law or agreed to in writing, software |
| 13 | +/// distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | +/// See the License for the specific language governing permissions and |
| 16 | +/// limitations under the License. |
| 17 | +/// |
| 18 | +/// Copyright holder is ArangoDB GmbH, Cologne, Germany |
| 19 | +/// |
| 20 | +/// @author Michael Hackstein |
| 21 | +//////////////////////////////////////////////////////////////////////////////// |
| 22 | + |
| 23 | +#include "AqlCallList.h" |
| 24 | + |
| 25 | +#include "Basics/StaticStrings.h" |
| 26 | +#include "Basics/voc-errors.h" |
| 27 | +#include "Containers/Enumerate.h" |
| 28 | +#include "Logger/LogMacros.h" |
| 29 | +#include "Logger/Logger.h" |
| 30 | + |
| 31 | +#include <velocypack/Builder.h> |
| 32 | +#include <velocypack/Collection.h> |
| 33 | +#include <velocypack/Slice.h> |
| 34 | +#include <velocypack/velocypack-aliases.h> |
| 35 | + |
| 36 | +#include <iostream> |
| 37 | +#include <map> |
| 38 | +#include <strin
F987
g_view> |
| 39 | + |
| 40 | +using namespace arangodb; |
| 41 | +using namespace arangodb::aql; |
| 42 | + |
| 43 | +namespace { |
| 44 | +// hack for MSVC |
| 45 | +auto getStringView(VPackSlice slice) -> std::string_view { |
| 46 | + velocypack::StringRef ref = slice.stringRef(); |
| 47 | + return std::string_view(ref.data(), ref.size()); |
| 48 | +} |
| 49 | +} // namespace |
| 50 | + |
| 51 | +AqlCallList::AqlCallList(AqlCall const& call) : _specificCalls{call} {} |
| 52 | + |
| 53 | +AqlCallList::AqlCallList(AqlCall const& specificCall, AqlCall const& defaultCall) |
| 54 | + : _specificCalls{specificCall}, _defaultCall{defaultCall} {} |
| 55 | + |
| 56 | +[[nodiscard]] auto AqlCallList::popNextCall() -> AqlCall { |
| 57 | + TRI_ASSERT(hasMoreCalls()); |
| 58 | + if (!_specificCalls.empty()) { |
| 59 | + // We only implemented for a single given call. |
| 60 | + TRI_ASSERT(_specificCalls.size() == 1); |
| 61 | + auto res = _specificCalls.back(); |
| 62 | + _specificCalls.pop_back(); |
| 63 | + return res; |
| 64 | + } |
| 65 | + TRI_ASSERT(_defaultCall.has_value()); |
| 66 | + return _defaultCall.value(); |
| 67 | +} |
| 68 | + |
| 69 | +[[nodiscard]] auto AqlCallList::peekNextCall() const -> AqlCall const& { |
| 70 | + TRI_ASSERT(hasMoreCalls()); |
| 71 | + if (!_specificCalls.empty()) { |
| 72 | + // We only implemented for a single given call. |
| 73 | + TRI_ASSERT(_specificCalls.size() == 1); |
| 74 | + return _specificCalls.back(); |
| 75 | + } |
| 76 | + TRI_ASSERT(_defaultCall.has_value()); |
| 77 | + return _defaultCall.value(); |
| 78 | +} |
| 79 | + |
| 80 | +[[nodiscard]] auto AqlCallList::hasMoreCalls() const noexcept -> bool { |
| 81 | + return !_specificCalls.empty() || _defaultCall.has_value(); |
| 82 | +} |
| 83 | + |
| 84 | +[[nodiscard]] auto AqlCallList::modifyNextCall() -> AqlCall& { |
| 85 | + TRI_ASSERT(hasMoreCalls()); |
| 86 | + if (_specificCalls.empty()) { |
| 87 | + TRI_ASSERT(_defaultCall.has_value()); |
| 88 | + // We need to emplace a copy of defaultCall into the specific calls |
| 89 | + // This can then be modified and eventually be consumed |
| 90 | + _specificCalls.emplace_back(_defaultCall.value()); |
| 91 | + } |
| 92 | + return _specificCalls.back(); |
| 93 | +} |
| 94 | + |
| 95 | +auto AqlCallList::fromVelocyPack(VPackSlice slice) -> ResultT<AqlCallList> { |
| 96 | + if (ADB_UNLIKELY(!slice.isObject())) { |
| 97 | + using namespace std::string_literals; |
| 98 | + return Result(TRI_ERROR_TYPE_ERROR, |
| 99 | + "When deserializating AqlCallList: Expected object, got "s + |
| 100 | + slice.typeName()); |
| 101 | + } |
| 102 | + |
| 103 | + auto expectedPropertiesFound = std::map<std::string_view, bool>{}; |
| 104 | + expectedPropertiesFound.emplace(StaticStrings::AqlCallListSpecific, false); |
| 105 | + expectedPropertiesFound.emplace(StaticStrings::AqlCallListDefault, false); |
| 106 | + |
| 107 | + auto const readSpecific = [](velocypack::Slice slice) -> ResultT<std::vector<AqlCall>> { |
| 108 | + if (ADB_UNLIKELY(!slice.isArray())) { |
| 109 | + auto message = std::string{"When deserializating AqlCall: When reading " + |
| 110 | + StaticStrings::AqlCallListSpecific + |
| 111 | + ": " |
| 112 | + "Unexpected type "}; |
| 113 | + message += slice.typeName(); |
| 114 | + return Result(TRI_ERROR_TYPE_ERROR, std::move(message)); |
| 115 | + } |
| 116 | + std::vector<AqlCall> res; |
| 117 | + res.reserve(slice.length()); |
| 118 | + for (auto const& c : VPackArrayIterator(slice)) { |
| 119 | + auto maybeAqlCall = AqlCall::fromVelocyPack(c); |
| 120 | + if (ADB_UNLIKELY(maybeAqlCall.fail())) { |
| 121 | + auto message = std::string{"When deserializing AqlCallList: entry "}; |
| 122 | + message += std::to_string(res.size()); |
| 123 | + message += ": "; |
| 124 | + message += std::move(maybeAqlCall).errorMessage(); |
| 125 | + return Result(TRI_ERROR_TYPE_ERROR, std::move(message)); |
| 126 | + } |
| 127 | + res.emplace_back(maybeAqlCall.get()); |
| 128 | + } |
| 129 | + return res; |
| 130 | + }; |
| 131 | + |
| 132 | + auto const readDefault = [](velocypack::Slice slice) -> ResultT<std::optional<AqlCall>> { |
| 133 | + if (ADB_UNLIKELY(!slice.isObject() && !slice.isNull())) { |
| 134 | + auto message = |
| 135 | + std::string{"When deserializating AqlCallList: When reading " + |
| 136 | + StaticStrings::AqlCallListDefault + |
| 137 | + ": " |
| 138 | + "Unexpected type "}; |
| 139 | + message += slice.typeName(); |
| 140 | + return Result(TRI_ERROR_TYPE_ERROR, std::move(message)); |
| 141 | + } |
| 142 | + if (slice.isNull()) { |
| 143 | + return {std::nullopt}; |
| 144 | + } |
| 145 | + auto maybeAqlCall = AqlCall::fromVelocyPack(slice); |
| 146 | + if (ADB_UNLIKELY(maybeAqlCall.fail())) { |
| 147 | + auto message = std::string{"When deserializing AqlCallList: default "}; |
| 148 | + message += std::move(maybeAqlCall).errorMessage(); |
| 149 | + return Result(TRI_ERROR_TYPE_ERROR, std::move(message)); |
| 150 | + } |
| 151 | + return {std::move(maybeAqlCall.get())}; |
| 152 | + }; |
| 153 | + |
| 154 | + AqlCallList result{AqlCall{}}; |
| 155 | + |
| 156 | + for (auto const it : velocypack::ObjectIterator(slice)) { |
| 157 | + auto const keySlice = it.key; |
| 158 | + if (ADB_UNLIKELY(!keySlice.isString())) { |
| 159 | + return Result(TRI_ERROR_TYPE_ERROR, |
| 160 | + "When deserializating AqlCallList: Key is not a string"); |
| 161 | + } |
| 162 | + auto const key = getStringView(keySlice); |
| 163 | + |
| 164 | + if (auto propIt = expectedPropertiesFound.find(key); |
| 165 | + ADB_LIKELY(propIt != expectedPropertiesFound.end())) { |
| 166 | + if (ADB_UNLIKELY(propIt->second)) { |
| 167 | + return Result( |
| 168 | + TRI_ERROR_TYPE_ERROR, |
| 169 | + "When deserializating AqlCallList: Encountered duplicate key"); |
| 170 | + } |
| 171 | + propIt->second = true; |
| 172 | + } |
| 173 | + |
| 174 | + if (key == StaticStrings::AqlCallListSpecific) { |
| 175 | + auto maybeCalls = readSpecific(it.value); |
| 176 | + if (maybeCalls.fail()) { |
| 177 | + return std::move(maybeCalls).result(); |
| 178 | + } |
| 179 | + result._specificCalls = maybeCalls.get(); |
| 180 | + } else if (key == StaticStrings::AqlCallListDefault) { |
| 181 | + auto maybeCall = readDefault(it.value); |
| 182 | + if (maybeCall.fail()) { |
| 183 | + return std::move(maybeCall).result(); |
| 184 | + } |
| 185 | + result._defaultCall = maybeCall.get(); |
| 186 | + } else { |
| 187 | + LOG_TOPIC("c30c1", WARN, Logger::AQL) |
| 188 | + << "When deserializating AqlCallList: Encountered unexpected key " << key; |
| 189 | + // If you run into this assertion during rolling upgrades after adding a |
| 190 | + // new attribute, remove it in the older version. |
| 191 | + TRI_ASSERT(false); |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + for (auto const& it : expectedPropertiesFound) { |
| 196 | + if (ADB_UNLIKELY(!it.second)) { |
| 197 | + auto message = std::string{"When deserializating AqlCall: missing key "}; |
| 198 | + message += it.first; |
| 199 | + return Result(TRI_ERROR_TYPE_ERROR, std::move(message)); |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + return result; |
| 204 | +} |
| 205 | + |
| 206 | +auto AqlCallList::toVelocyPack(VPackBuilder& builder) const -> void { |
| 207 | + // We need to have something that is serializable |
| 208 | + TRI_ASSERT(hasMoreCalls()); |
| 209 | + builder.openObject(); |
| 210 | + builder.add(VPackValue(StaticStrings::AqlCallListSpecific)); |
| 211 | + |
| 212 | + { |
| 213 | + builder.openArray(); |
| 214 | + for (auto const& call : _specificCalls) { |
| 215 | + call.toVelocyPack(builder); |
| 216 | + } |
| 217 | + builder.close(); |
| 218 | + } |
| 219 | + builder.add(VPackValue(StaticStrings::AqlCallListDefault)); |
| 220 | + if (_defaultCall.has_value()) { |
| 221 | + _defaultCall.value().toVelocyPack(builder); |
| 222 | + } else { |
| 223 | + builder.add(VPackSlice::nullSlice()); |
| 224 | + } |
| 225 | + |
| 226 | + builder.close(); |
| 227 | +} |
| 228 | + |
| 229 | +auto AqlCallList::toString() const -> std::string { |
| 230 | + auto stream = std::stringstream{}; |
| 231 | + stream << *this; |
| 232 | + return stream.str(); |
| 233 | +} |
| 234 | + |
| 235 | +bool arangodb::aql::operator==(AqlCallList const& left, AqlCallList const& right) { |
| 236 | + if (left._specificCalls.size() != right._specificCalls.size()) { |
| 237 | + return false; |
| 238 | + } |
| 239 | + // Sorry call does not implement operator!= |
| 240 | + if (!(left._defaultCall == right._defaultCall)) { |
| 241 | + return false; |
| 242 | + } |
| 243 | + for (auto const& [index, call] : enumerate(left._specificCalls)) { |
| 244 | + if (!(call == right._specificCalls[index])) { |
| 245 | + return false; |
| 246 | + } |
| 247 | + } |
| 248 | + return true; |
| 249 | +} |
| 250 | + |
| 251 | +auto arangodb::aql::operator<<(std::ostream& out, AqlCallList const& list) -> std::ostream& { |
| 252 | + out << "specific: [ "; |
| 253 | + for (auto const& [index, call] : enumerate(list._specificCalls)) { |
| 254 | + if (index > 0) { |
| 255 | + out << ", "; |
| 256 | + } |
| 257 | + out << call; |
| 258 | + } |
| 259 | + out << " ]"; |
| 260 | + if (list._defaultCall.has_value()) { |
| 261 | + out << " default: " << list._defaultCall.value(); |
| 262 | + } |
| 263 | + return out; |
| 264 | +} |
0 commit comments