mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'devel' into schmutz-ng
This commit is contained in:
commit
427c8e4553
|
@ -142,6 +142,13 @@ class Buffer {
|
|||
initWithNone();
|
||||
}
|
||||
|
||||
void resetTo(ValueLength position) {
|
||||
if (position >= _alloc) {
|
||||
throw Exception(Exception::IndexOutOfBounds);
|
||||
}
|
||||
_pos = position;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
reset();
|
||||
if (_buffer != _local) {
|
||||
|
|
|
@ -51,19 +51,13 @@ class ArrayIterator {
|
|||
reset();
|
||||
}
|
||||
|
||||
ArrayIterator(ArrayIterator const& other)
|
||||
ArrayIterator(ArrayIterator const& other) noexcept
|
||||
: _slice(other._slice),
|
||||
_size(other._size),
|
||||
_position(other._position),
|
||||
_current(other._current) {}
|
||||
|
||||
ArrayIterator& operator=(ArrayIterator const& other) {
|
||||
_slice = other._slice;
|
||||
_size = other._size;
|
||||
_position = other._position;
|
||||
_current = other._current;
|
||||
return *this;
|
||||
}
|
||||
ArrayIterator& operator=(ArrayIterator const& other) = delete;
|
||||
|
||||
// prefix ++
|
||||
ArrayIterator& operator++() {
|
||||
|
@ -94,18 +88,26 @@ class ArrayIterator {
|
|||
return _slice.at(_position);
|
||||
}
|
||||
|
||||
ArrayIterator begin() { return ArrayIterator(_slice); }
|
||||
ArrayIterator begin() {
|
||||
auto it = ArrayIterator(*this);
|
||||
it._position = 0;
|
||||
return it;
|
||||
}
|
||||
|
||||
ArrayIterator begin() const { return ArrayIterator(_slice); }
|
||||
ArrayIterator begin() const {
|
||||
auto it = ArrayIterator(*this);
|
||||
it._position = 0;
|
||||
return it;
|
||||
}
|
||||
|
||||
ArrayIterator end() {
|
||||
auto it = ArrayIterator(_slice);
|
||||
auto it = ArrayIterator(*this);
|
||||
it._position = it._size;
|
||||
return it;
|
||||
}
|
||||
|
||||
ArrayIterator end() const {
|
||||
auto it = ArrayIterator(_slice);
|
||||
auto it = ArrayIterator(*this);
|
||||
it._position = it._size;
|
||||
return it;
|
||||
}
|
||||
|
@ -165,7 +167,7 @@ class ArrayIterator {
|
|||
|
||||
private:
|
||||
Slice _slice;
|
||||
ValueLength _size;
|
||||
ValueLength const _size;
|
||||
ValueLength _position;
|
||||
uint8_t const* _current;
|
||||
};
|
||||
|
@ -197,21 +199,14 @@ class ObjectIterator {
|
|||
}
|
||||
}
|
||||
|
||||
ObjectIterator(ObjectIterator const& other)
|
||||
ObjectIterator(ObjectIterator const& other) noexcept
|
||||
: _slice(other._slice),
|
||||
_size(other._size),
|
||||
_position(other._position),
|
||||
_current(other._current),
|
||||
_allowRandomIteration(other._allowRandomIteration) {}
|
||||
|
||||
ObjectIterator& operator=(ObjectIterator const& other) {
|
||||
_slice = other._slice;
|
||||
_size = other._size;
|
||||
_position = other._position;
|
||||
_current = other._current;
|
||||
_allowRandomIteration = other._allowRandomIteration;
|
||||
return *this;
|
||||
}
|
||||
ObjectIterator& operator=(ObjectIterator const& other) = delete;
|
||||
|
||||
// prefix ++
|
||||
ObjectIterator& operator++() {
|
||||
|
@ -246,18 +241,26 @@ class ObjectIterator {
|
|||
return ObjectPair(_slice.getNthKey(_position, true), _slice.getNthValue(_position));
|
||||
}
|
||||
|
||||
ObjectIterator begin() { return ObjectIterator(_slice, _allowRandomIteration); }
|
||||
ObjectIterator begin() {
|
||||
auto it = ObjectIterator(*this);
|
||||
it._position = 0;
|
||||
return it;
|
||||
}
|
||||
|
||||
ObjectIterator begin() const { return ObjectIterator(_slice, _allowRandomIteration); }
|
||||
ObjectIterator begin() const {
|
||||
auto it = ObjectIterator(*this);
|
||||
it._position = 0;
|
||||
return it;
|
||||
}
|
||||
|
||||
ObjectIterator end() {
|
||||
auto it = ObjectIterator(_slice, _allowRandomIteration);
|
||||
auto it = ObjectIterator(*this);
|
||||
it._position = it._size;
|
||||
return it;
|
||||
}
|
||||
|
||||
ObjectIterator end() const {
|
||||
auto it = ObjectIterator(_slice, _allowRandomIteration);
|
||||
auto it = ObjectIterator(*this);
|
||||
it._position = it._size;
|
||||
return it;
|
||||
}
|
||||
|
@ -300,10 +303,10 @@ class ObjectIterator {
|
|||
|
||||
private:
|
||||
Slice _slice;
|
||||
ValueLength _size;
|
||||
ValueLength const _size;
|
||||
ValueLength _position;
|
||||
uint8_t const* _current;
|
||||
bool _allowRandomIteration;
|
||||
bool const _allowRandomIteration;
|
||||
};
|
||||
|
||||
} // namespace arangodb::velocypack
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
|
||||
#include "AgencyComm.h"
|
||||
|
||||
#include <thread>
|
||||
#ifdef DEBUG_SYNC_REPLICATION
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
#include <velocypack/Iterator.h>
|
||||
#include <velocypack/Sink.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
|
@ -45,11 +50,6 @@
|
|||
#include "SimpleHttpClient/SimpleHttpClient.h"
|
||||
#include "SimpleHttpClient/SimpleHttpResult.h"
|
||||
|
||||
#include <thread>
|
||||
#ifdef DEBUG_SYNC_REPLICATION
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::application_features;
|
||||
using namespace arangodb::httpclient;
|
||||
|
@ -209,17 +209,22 @@ std::string AgencyWriteTransaction::toJson() const {
|
|||
void AgencyWriteTransaction::toVelocyPack(VPackBuilder& builder) const {
|
||||
VPackArrayBuilder guard(&builder);
|
||||
{
|
||||
VPackObjectBuilder guard2(&builder);
|
||||
VPackObjectBuilder guard2(&builder); // Writes
|
||||
for (AgencyOperation const& operation : operations) {
|
||||
operation.toVelocyPack(builder);
|
||||
}
|
||||
}
|
||||
if (preconditions.size() > 0) {
|
||||
VPackObjectBuilder guard3(&builder);
|
||||
|
||||
if (preconditions.size() > 0) {
|
||||
VPackObjectBuilder guard3(&builder); // Preconditions
|
||||
for (AgencyPrecondition const& precondition : preconditions) {
|
||||
precondition.toVelocyPack(builder);
|
||||
}
|
||||
} else {
|
||||
VPackObjectBuilder guard3(&builder);
|
||||
}
|
||||
|
||||
builder.add(VPackValue(clientId)); // Transactions
|
||||
}
|
||||
|
||||
bool AgencyWriteTransaction::validate(AgencyCommResult const& result) const {
|
||||
|
@ -278,6 +283,7 @@ void AgencyGeneralTransaction::toVelocyPack(VPackBuilder& builder) const {
|
|||
} else {
|
||||
std::get<0>(operation).toGeneralBuilder(builder);
|
||||
std::get<1>(operation).toGeneralBuilder(builder);
|
||||
builder.add(VPackValue(clientId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -322,18 +328,23 @@ AgencyCommResult::AgencyCommResult()
|
|||
_body(),
|
||||
_values(),
|
||||
_statusCode(0),
|
||||
_connected(false) {}
|
||||
_connected(false),
|
||||
_clientId("") {}
|
||||
|
||||
AgencyCommResult::AgencyCommResult(int code, std::string const& message)
|
||||
AgencyCommResult::AgencyCommResult(
|
||||
int code, std::string const& message, std::string const& clientId)
|
||||
: _location(),
|
||||
_message(message),
|
||||
_body(),
|
||||
_values(),
|
||||
_statusCode(code),
|
||||
_connected(false) {}
|
||||
_connected(false),
|
||||
_clientId(clientId) {}
|
||||
|
||||
bool AgencyCommResult::connected() const { return _connected; }
|
||||
|
||||
std::string AgencyCommResult::clientId() const { return _clientId; }
|
||||
|
||||
int AgencyCommResult::httpCode() const { return _statusCode; }
|
||||
|
||||
int AgencyCommResult::errorCode() const {
|
||||
|
@ -1064,11 +1075,12 @@ AgencyCommResult AgencyComm::sendTransactionWithFailover(
|
|||
|
||||
AgencyCommResult result = sendWithFailover(
|
||||
arangodb::rest::RequestType::POST,
|
||||
(timeout == 0.0 ? AgencyCommManager::CONNECTION_OPTIONS._requestTimeout
|
||||
: timeout),
|
||||
url, builder.slice().toJson());
|
||||
(timeout == 0.0) ?
|
||||
AgencyCommManager::CONNECTION_OPTIONS._requestTimeout : timeout,
|
||||
url, builder.slice().toJson(), transaction.getClientId());
|
||||
|
||||
if (!result.successful() && result.httpCode() != 412) {
|
||||
if (!result.successful() && result.httpCode() !=
|
||||
(int)arangodb::rest::ResponseCode::PRECONDITION_FAILED) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1278,13 +1290,13 @@ void AgencyComm::updateEndpoints(arangodb::velocypack::Slice const& current) {
|
|||
|
||||
AgencyCommResult AgencyComm::sendWithFailover(
|
||||
arangodb::rest::RequestType method, double const timeout,
|
||||
std::string const& initialUrl, std::string const& body) {
|
||||
std::string const& initialUrl, std::string const& body,
|
||||
std::string const& clientId) {
|
||||
|
||||
std::string endpoint;
|
||||
std::unique_ptr<GeneralClientConnection> connection =
|
||||
AgencyCommManager::MANAGER->acquire(endpoint);
|
||||
|
||||
|
||||
|
||||
AgencyCommResult result;
|
||||
std::string url = initialUrl;
|
||||
|
||||
|
@ -1320,7 +1332,7 @@ AgencyCommResult AgencyComm::sendWithFailover(
|
|||
++tries;
|
||||
|
||||
if (connection == nullptr) {
|
||||
AgencyCommResult result(400, "No endpoints for agency found.");
|
||||
AgencyCommResult result(400, "No endpoints for agency found.", clientId);
|
||||
LOG_TOPIC(ERR, Logger::AGENCYCOMM) << result._message;
|
||||
return result;
|
||||
}
|
||||
|
@ -1337,7 +1349,7 @@ AgencyCommResult AgencyComm::sendWithFailover(
|
|||
|
||||
// try to send; if we fail completely, do not retry
|
||||
try {
|
||||
result = send(connection.get(), method, conTimeout, url, body);
|
||||
result = send(connection.get(), method, conTimeout, url, body, clientId);
|
||||
} catch (...) {
|
||||
AgencyCommManager::MANAGER->failed(std::move(connection), endpoint);
|
||||
endpoint.clear();
|
||||
|
@ -1397,7 +1409,7 @@ AgencyCommResult AgencyComm::sendWithFailover(
|
|||
AgencyCommResult AgencyComm::send(
|
||||
arangodb::httpclient::GeneralClientConnection* connection,
|
||||
arangodb::rest::RequestType method, double timeout, std::string const& url,
|
||||
std::string const& body) {
|
||||
std::string const& body, std::string const& clientId) {
|
||||
TRI_ASSERT(connection != nullptr);
|
||||
|
||||
if (method == arangodb::rest::RequestType::GET ||
|
||||
|
@ -1411,6 +1423,9 @@ AgencyCommResult AgencyComm::send(
|
|||
AgencyCommResult result;
|
||||
result._connected = false;
|
||||
result._statusCode = 0;
|
||||
if (!clientId.empty()) {
|
||||
result._clientId = clientId;
|
||||
}
|
||||
|
||||
LOG_TOPIC(TRACE, Logger::AGENCYCOMM)
|
||||
<< "sending " << arangodb::HttpRequest::translateMethod(method)
|
||||
|
|
|
@ -27,12 +27,16 @@
|
|||
|
||||
#include "Basics/Common.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
#include <velocypack/Slice.h>
|
||||
#include <velocypack/velocypack-aliases.h>
|
||||
#include <type_traits>
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "Basics/Mutex.h"
|
||||
#include "Rest/HttpRequest.h"
|
||||
#include "SimpleHttpClient/GeneralClientConnection.h"
|
||||
|
@ -214,7 +218,9 @@ class AgencyOperation {
|
|||
class AgencyCommResult {
|
||||
public:
|
||||
AgencyCommResult();
|
||||
AgencyCommResult(int code, std::string const& message);
|
||||
AgencyCommResult(int code, std::string const& message,
|
||||
std::string const& transactionId = std::string());
|
||||
|
||||
~AgencyCommResult() = default;
|
||||
|
||||
public:
|
||||
|
@ -226,6 +232,8 @@ class AgencyCommResult {
|
|||
|
||||
int errorCode() const;
|
||||
|
||||
std::string clientId() const;
|
||||
|
||||
std::string errorMessage() const;
|
||||
|
||||
std::string errorDetails() const;
|
||||
|
@ -252,6 +260,9 @@ class AgencyCommResult {
|
|||
|
||||
private:
|
||||
std::shared_ptr<velocypack::Builder> _vpack;
|
||||
|
||||
public:
|
||||
std::string _clientId;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -268,7 +279,8 @@ public:
|
|||
virtual std::string toJson() const = 0;
|
||||
virtual void toVelocyPack(arangodb::velocypack::Builder&) const = 0;
|
||||
virtual std::string const& path() const = 0;
|
||||
|
||||
virtual std::string getClientId() const = 0;
|
||||
|
||||
virtual bool validate(AgencyCommResult const& result) const = 0;
|
||||
|
||||
};
|
||||
|
@ -280,13 +292,15 @@ public:
|
|||
struct AgencyGeneralTransaction : public AgencyTransaction {
|
||||
|
||||
explicit AgencyGeneralTransaction(
|
||||
std::pair<AgencyOperation,AgencyPrecondition> const& operation) {
|
||||
std::pair<AgencyOperation,AgencyPrecondition> const& operation) :
|
||||
clientId(to_string(boost::uuids::random_generator()())) {
|
||||
operations.push_back(operation);
|
||||
}
|
||||
|
||||
explicit AgencyGeneralTransaction(
|
||||
std::vector<std::pair<AgencyOperation,AgencyPrecondition>> const& _operations)
|
||||
: operations(_operations) {}
|
||||
std::vector<std::pair<AgencyOperation,AgencyPrecondition>> const& _opers) :
|
||||
operations(_opers),
|
||||
clientId(to_string(boost::uuids::random_generator()())) {}
|
||||
|
||||
AgencyGeneralTransaction() = default;
|
||||
|
||||
|
@ -299,12 +313,16 @@ struct AgencyGeneralTransaction : public AgencyTransaction {
|
|||
|
||||
void push_back(std::pair<AgencyOperation,AgencyPrecondition> const&);
|
||||
|
||||
inline std::string const& path() const override final {
|
||||
inline virtual std::string const& path() const override final {
|
||||
return AgencyTransaction::TypeUrl[2];
|
||||
}
|
||||
|
||||
virtual bool validate(AgencyCommResult const& result) const override final;
|
||||
inline virtual std::string getClientId() const override final {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
virtual bool validate(AgencyCommResult const& result) const override final;
|
||||
std::string clientId;
|
||||
|
||||
};
|
||||
|
||||
|
@ -315,23 +333,26 @@ struct AgencyGeneralTransaction : public AgencyTransaction {
|
|||
struct AgencyWriteTransaction : public AgencyTransaction {
|
||||
|
||||
public:
|
||||
|
||||
explicit AgencyWriteTransaction(AgencyOperation const& operation) {
|
||||
|
||||
explicit AgencyWriteTransaction(AgencyOperation const& operation) :
|
||||
clientId(to_string(boost::uuids::random_generator()())) {
|
||||
operations.push_back(operation);
|
||||
}
|
||||
|
||||
explicit AgencyWriteTransaction(
|
||||
std::vector<AgencyOperation> const& _operations)
|
||||
: operations(_operations) {}
|
||||
|
||||
|
||||
explicit AgencyWriteTransaction (std::vector<AgencyOperation> const& _opers) :
|
||||
operations(_opers),
|
||||
clientId(to_string(boost::uuids::random_generator()())) {}
|
||||
|
||||
AgencyWriteTransaction(AgencyOperation const& operation,
|
||||
AgencyPrecondition const& precondition) {
|
||||
AgencyPrecondition const& precondition) :
|
||||
clientId(to_string(boost::uuids::random_generator()())) {
|
||||
operations.push_back(operation);
|
||||
preconditions.push_back(precondition);
|
||||
}
|
||||
|
||||
|
||||
AgencyWriteTransaction(std::vector<AgencyOperation> const& _operations,
|
||||
AgencyPrecondition const& precondition) {
|
||||
AgencyPrecondition const& precondition) :
|
||||
clientId(to_string(boost::uuids::random_generator()())) {
|
||||
for (auto const& op : _operations) {
|
||||
operations.push_back(op);
|
||||
}
|
||||
|
@ -339,7 +360,8 @@ public:
|
|||
}
|
||||
|
||||
AgencyWriteTransaction(std::vector<AgencyOperation> const& opers,
|
||||
std::vector<AgencyPrecondition> const& precs) {
|
||||
std::vector<AgencyPrecondition> const& precs) :
|
||||
clientId(to_string(boost::uuids::random_generator()())) {
|
||||
for (auto const& op : opers) {
|
||||
operations.push_back(op);
|
||||
}
|
||||
|
@ -355,14 +377,19 @@ public:
|
|||
|
||||
std::string toJson() const override final;
|
||||
|
||||
inline std::string const& path() const override final {
|
||||
inline virtual std::string const& path() const override final {
|
||||
return AgencyTransaction::TypeUrl[1];
|
||||
}
|
||||
|
||||
inline virtual std::string getClientId() const override final {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
virtual bool validate(AgencyCommResult const& result) const override final;
|
||||
|
||||
std::vector<AgencyPrecondition> preconditions;
|
||||
std::vector<AgencyOperation> operations;
|
||||
std::string clientId;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -416,6 +443,10 @@ public:
|
|||
return AgencyTransaction::TypeUrl[3];
|
||||
}
|
||||
|
||||
inline virtual std::string getClientId() const override final {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
virtual bool validate(AgencyCommResult const& result) const override final;
|
||||
|
||||
std::vector<AgencyPrecondition> preconditions;
|
||||
|
@ -443,10 +474,14 @@ public:
|
|||
|
||||
std::string toJson() const override final;
|
||||
|
||||
inline std::string const& path() const override final {
|
||||
inline virtual std::string const& path() const override final {
|
||||
return AgencyTransaction::TypeUrl[0];
|
||||
}
|
||||
|
||||
inline virtual std::string getClientId() const override final {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
virtual bool validate(AgencyCommResult const& result) const override final;
|
||||
|
||||
std::vector<std::string> keys;
|
||||
|
@ -603,7 +638,8 @@ class AgencyComm {
|
|||
bool ensureStructureInitialized();
|
||||
|
||||
AgencyCommResult sendWithFailover(arangodb::rest::RequestType, double,
|
||||
std::string const&, std::string const&);
|
||||
std::string const&, std::string const&,
|
||||
std::string const& clientId = std::string());
|
||||
|
||||
private:
|
||||
bool lock(std::string const&, double, double,
|
||||
|
@ -612,7 +648,8 @@ class AgencyComm {
|
|||
bool unlock(std::string const&, arangodb::velocypack::Slice const&, double);
|
||||
|
||||
AgencyCommResult send(httpclient::GeneralClientConnection*, rest::RequestType,
|
||||
double, std::string const&, std::string const&);
|
||||
double, std::string const&, std::string const&,
|
||||
std::string const& clientId = std::string());
|
||||
|
||||
bool tryInitializeStructure(std::string const& jwtSecret);
|
||||
|
||||
|
|
|
@ -616,7 +616,9 @@ trans_ret_t Agent::transact(query_t const& queries) {
|
|||
for (const auto& query : VPackArrayIterator(qs)) {
|
||||
if (query[0].isObject()) {
|
||||
if(_spearhead.apply(query)) {
|
||||
maxind = _state.log(query[0], term());
|
||||
maxind = (query.length() == 3 && query[2].isString()) ?
|
||||
_state.log(query[0], term(), query[2].copyString()) :
|
||||
_state.log(query[0], term());
|
||||
ret->add(VPackValue(maxind));
|
||||
} else {
|
||||
ret->add(VPackValue(0));
|
||||
|
@ -651,30 +653,38 @@ trans_ret_t Agent::transact(query_t const& queries) {
|
|||
|
||||
|
||||
// Non-persistent write to non-persisted key-value store
|
||||
write_ret_t Agent::transient(query_t const& query) {
|
||||
std::vector<bool> applied;
|
||||
std::vector<index_t> indices;
|
||||
trans_ret_t Agent::transient(query_t const& queries) {
|
||||
|
||||
auto ret = std::make_shared<arangodb::velocypack::Builder>();
|
||||
auto leader = _constituent.leaderID();
|
||||
if (leader != id()) {
|
||||
return write_ret_t(false, leader);
|
||||
return trans_ret_t(false, leader);
|
||||
}
|
||||
|
||||
// Apply to spearhead and get indices for log entries
|
||||
{
|
||||
VPackArrayBuilder b(ret.get());
|
||||
|
||||
MUTEX_LOCKER(mutexLocker, _ioLock);
|
||||
|
||||
// Only leader else redirect
|
||||
if (challengeLeadership()) {
|
||||
_constituent.candidate();
|
||||
return write_ret_t(false, NO_LEADER);
|
||||
return trans_ret_t(false, NO_LEADER);
|
||||
}
|
||||
|
||||
applied = _transient.apply(query);
|
||||
|
||||
|
||||
// Read and writes
|
||||
for (const auto& query : VPackArrayIterator(queries->slice())) {
|
||||
if (query[0].isObject()) {
|
||||
_transient.apply(query);
|
||||
} else if (query[0].isString()) {
|
||||
_transient.read(query, *ret);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return write_ret_t(true, id());
|
||||
return trans_ret_t(true, id(), 0, 0, ret);
|
||||
|
||||
}
|
||||
|
||||
|
@ -695,14 +705,17 @@ inquire_ret_t Agent::inquire(query_t const& query) {
|
|||
{
|
||||
VPackArrayBuilder b(builder.get());
|
||||
for (auto const& i : si) {
|
||||
VPackObjectBuilder bb(builder.get());
|
||||
builder->add("index", VPackValue(i.index));
|
||||
builder->add("term", VPackValue(i.term));
|
||||
builder->add("query", VPackSlice(i.entry->data()));
|
||||
builder->add("index", VPackValue(i.index));
|
||||
VPackArrayBuilder bb(builder.get());
|
||||
for (auto const& j : i) {
|
||||
VPackObjectBuilder bbb(builder.get());
|
||||
builder->add("index", VPackValue(j.index));
|
||||
builder->add("term", VPackValue(j.term));
|
||||
builder->add("query", VPackSlice(j.entry->data()));
|
||||
builder->add("index", VPackValue(j.index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ret = inquire_ret_t(true, id(), builder);
|
||||
return ret;
|
||||
}
|
||||
|
@ -797,7 +810,6 @@ void Agent::run() {
|
|||
|
||||
} else {
|
||||
_appendCV.wait(1000000);
|
||||
updateConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -897,9 +909,9 @@ void Agent::detectActiveAgentFailures() {
|
|||
system_clock::now() - lastAcked.at(id)).count();
|
||||
if (ds > 180.0) {
|
||||
std::string repl = _config.nextAgentInLine();
|
||||
LOG_TOPIC(DEBUG, Logger::AGENCY) << "Active agent " << id << " has failed. << "
|
||||
<< repl << " will be promoted to active agency membership";
|
||||
// Guarded in ::
|
||||
LOG_TOPIC(DEBUG, Logger::AGENCY)
|
||||
<< "Active agent " << id << " has failed. << " << repl
|
||||
<< " will be promoted to active agency membership";
|
||||
_activator = std::make_unique<AgentActivator>(this, id, repl);
|
||||
_activator->start();
|
||||
return;
|
||||
|
@ -910,13 +922,6 @@ void Agent::detectActiveAgentFailures() {
|
|||
}
|
||||
|
||||
|
||||
void Agent::updateConfiguration() {
|
||||
|
||||
// First ask last know leader
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Orderly shutdown
|
||||
void Agent::beginShutdown() {
|
||||
Thread::beginShutdown();
|
||||
|
|
|
@ -94,7 +94,7 @@ class Agent : public arangodb::Thread {
|
|||
bool load();
|
||||
|
||||
/// @brief Unpersisted key-value-store
|
||||
write_ret_t transient(query_t const&);
|
||||
trans_ret_t transient(query_t const&);
|
||||
|
||||
/// @brief Attempt write
|
||||
write_ret_t write(query_t const&);
|
||||
|
@ -223,9 +223,6 @@ class Agent : public arangodb::Thread {
|
|||
/// @brief persist agency configuration in RAFT
|
||||
void persistConfiguration(term_t t);
|
||||
|
||||
/// @brief Update my configuration as passive agent
|
||||
void updateConfiguration();
|
||||
|
||||
/// @brief Find out, if we've had acknowledged RPCs recent enough
|
||||
bool challengeLeadership();
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ RestStatus RestAgencyHandler::handleTransient() {
|
|||
std::this_thread::sleep_for(duration_t(100));
|
||||
}
|
||||
|
||||
write_ret_t ret;
|
||||
trans_ret_t ret;
|
||||
|
||||
try {
|
||||
ret = _agent->transient(query);
|
||||
|
|
|
@ -154,9 +154,8 @@ std::vector<arangodb::consensus::index_t> State::log(
|
|||
|
||||
/// Log transaction (leader)
|
||||
arangodb::consensus::index_t State::log(
|
||||
velocypack::Slice const& slice, term_t term) {
|
||||
velocypack::Slice const& slice, term_t term, std::string const& clientId) {
|
||||
|
||||
std::string clientId;
|
||||
arangodb::consensus::index_t idx = 0;
|
||||
auto buf = std::make_shared<Buffer<uint8_t>>();
|
||||
|
||||
|
@ -165,6 +164,8 @@ arangodb::consensus::index_t State::log(
|
|||
TRI_ASSERT(!_log.empty()); // log must not ever be empty
|
||||
idx = _log.back().index + 1;
|
||||
_log.push_back(log_t(idx, term, buf, clientId)); // log to RAM
|
||||
_clientIdLookupTable.emplace(
|
||||
std::pair<std::string, arangodb::consensus::index_t>(clientId, idx));
|
||||
persist(idx, term, slice, clientId); // log to disk
|
||||
|
||||
return _log.back().index;
|
||||
|
@ -839,9 +840,9 @@ query_t State::allLogs() const {
|
|||
|
||||
}
|
||||
|
||||
std::vector<log_t> State::inquire(query_t const& query) const {
|
||||
std::vector<std::vector<log_t>> State::inquire(query_t const& query) const {
|
||||
|
||||
std::vector<log_t> result;
|
||||
std::vector<std::vector<log_t>> result;
|
||||
MUTEX_LOCKER(mutexLocker, _logLock); // Cannot be read lock (Compaction)
|
||||
|
||||
if (!query->slice().isArray()) {
|
||||
|
@ -861,13 +862,15 @@ std::vector<log_t> State::inquire(query_t const& query) const {
|
|||
+ std::to_string(pos) + " we got " + i.toJson());
|
||||
}
|
||||
|
||||
std::vector<log_t> transactions;
|
||||
auto ret = _clientIdLookupTable.equal_range(i.copyString());
|
||||
for (auto it = ret.first; it != ret.second; ++it) {
|
||||
if (it->second < _log[0].index) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(_log.at(it->second-_cur));
|
||||
transactions.push_back(_log.at(it->second-_cur));
|
||||
}
|
||||
result.push_back(transactions);
|
||||
|
||||
pos++;
|
||||
|
||||
|
|
|
@ -65,7 +65,9 @@ class State {
|
|||
std::vector<bool> const& indices, term_t term);
|
||||
|
||||
/// @brief Single log entry (leader)
|
||||
arangodb::consensus::index_t log(velocypack::Slice const& slice, term_t term);
|
||||
arangodb::consensus::index_t log(
|
||||
velocypack::Slice const& slice, term_t term,
|
||||
std::string const& clientId = std::string());
|
||||
|
||||
/// @brief Log entries (followers)
|
||||
arangodb::consensus::index_t log(query_t const& queries, size_t ndups = 0);
|
||||
|
@ -79,7 +81,7 @@ class State {
|
|||
index_t = 0, index_t = (std::numeric_limits<uint64_t>::max)()) const;
|
||||
|
||||
/// @brief Get log entries by client Id
|
||||
std::vector<log_t> inquire(query_t const&) const;
|
||||
std::vector<std::vector<log_t>> inquire(query_t const&) const;
|
||||
|
||||
/// @brief Get complete logged commands by lower and upper bounds.
|
||||
/// Default: [first, last]
|
||||
|
@ -182,6 +184,10 @@ class State {
|
|||
|
||||
/// @brief Operation options
|
||||
OperationOptions _options;
|
||||
|
||||
/// @brief Empty log entry;
|
||||
static log_t emptyLog;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,6 +205,7 @@ bool Store::apply(Slice const& query, bool verbose) {
|
|||
success = applies(query[0]);
|
||||
break;
|
||||
case 2: // precondition
|
||||
case 3: // precondition + clientId
|
||||
if (check(query[1])) {
|
||||
success = applies(query[0]);
|
||||
} else { // precondition failed
|
||||
|
|
|
@ -903,10 +903,11 @@ int ClusterInfo::createDatabaseCoordinator(std::string const& name,
|
|||
(int)arangodb::rest::ResponseCode::PRECONDITION_FAILED) {
|
||||
return setErrormsg(TRI_ERROR_ARANGO_DUPLICATE_NAME, errorMsg);
|
||||
}
|
||||
errorMsg = std::string("Failed to create database in ") + __FILE__ + ":" + std::to_string(__LINE__);
|
||||
errorMsg = std::string("Failed to create database with ") +
|
||||
res._clientId + " at " + __FILE__ + ":" + std::to_string(__LINE__);
|
||||
return setErrormsg(TRI_ERROR_CLUSTER_COULD_NOT_CREATE_DATABASE_IN_PLAN,
|
||||
errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// Now update our own cache of planned databases:
|
||||
loadPlan();
|
||||
|
@ -1171,6 +1172,7 @@ int ClusterInfo::createCollectionCoordinator(std::string const& databaseName,
|
|||
LOG_TOPIC(ERR, Logger::CLUSTER) << "Could not get agency dump!";
|
||||
}
|
||||
} else {
|
||||
errorMsg += std::string("\nClientId ") + res._clientId;
|
||||
errorMsg += std::string("\n") + __FILE__ + std::to_string(__LINE__);
|
||||
errorMsg += std::string("\n") + res.errorMessage();
|
||||
errorMsg += std::string("\n") + res.errorDetails();
|
||||
|
@ -1295,9 +1297,9 @@ int ClusterInfo::dropCollectionCoordinator(std::string const& databaseName,
|
|||
res = ac.sendTransactionWithFailover(trans);
|
||||
|
||||
if (!res.successful()) {
|
||||
LOG(ERR) << "###################### WAS ERLAUBE? ####################";
|
||||
AgencyCommResult ag = ac.getValues("");
|
||||
if (ag.successful()) {
|
||||
LOG_TOPIC(ERR, Logger::CLUSTER) << "ClientId: " << res._clientId;
|
||||
LOG_TOPIC(ERR, Logger::CLUSTER) << "Agency dump:\n"
|
||||
<< ag.slice().toJson();
|
||||
} else {
|
||||
|
@ -1756,6 +1758,7 @@ int ClusterInfo::ensureIndexCoordinator(
|
|||
AgencyCommResult result = ac.sendTransactionWithFailover(trx, 0.0);
|
||||
|
||||
if (!result.successful()) {
|
||||
errorMsg += "ClientId: " + result._clientId;
|
||||
errorMsg += std::string(" ") + __FILE__ + ":" + std::to_string(__LINE__);
|
||||
resultBuilder = *resBuilder;
|
||||
return TRI_ERROR_CLUSTER_COULD_NOT_CREATE_INDEX_IN_PLAN;
|
||||
|
@ -1976,6 +1979,7 @@ int ClusterInfo::dropIndexCoordinator(std::string const& databaseName,
|
|||
AgencyCommResult result = ac.sendTransactionWithFailover(trx, 0.0);
|
||||
|
||||
if (!result.successful()) {
|
||||
errorMsg += "ClientId: " + result._clientId;
|
||||
errorMsg += std::string(" ") + __FILE__ + ":" + std::to_string(__LINE__);
|
||||
events::DropIndex(collectionID, idString,
|
||||
TRI_ERROR_CLUSTER_COULD_NOT_DROP_INDEX_IN_PLAN);
|
||||
|
@ -2045,20 +2049,20 @@ void ClusterInfo::loadServers() {
|
|||
result.slice()[0].get(
|
||||
std::vector<std::string>(
|
||||
{AgencyCommManager::path(), "Current", "ServersRegistered"}));
|
||||
|
||||
|
||||
velocypack::Slice serversAliases =
|
||||
result.slice()[0].get(
|
||||
std::vector<std::string>(
|
||||
{AgencyCommManager::path(), "Target", "MapUniqueToShortID"}));
|
||||
|
||||
if (serversRegistered.isObject()) {
|
||||
|
||||
if (serversRegistered.isObject()) {
|
||||
decltype(_servers) newServers;
|
||||
decltype(_serverAliases) newAliases;
|
||||
|
||||
|
||||
size_t i = 0;
|
||||
for (auto const& res : VPackObjectIterator(serversRegistered)) {
|
||||
velocypack::Slice slice = res.value;
|
||||
|
||||
|
||||
if (slice.isObject() && slice.hasKey("endpoint")) {
|
||||
std::string server =
|
||||
arangodb::basics::VelocyPackHelper::getStringValue(
|
||||
|
@ -2075,7 +2079,7 @@ void ClusterInfo::loadServers() {
|
|||
newServers.emplace(std::make_pair(res.key.copyString(), server));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Now set the new value:
|
||||
{
|
||||
WRITE_LOCKER(writeLocker, _serversProt.lock);
|
||||
|
@ -2087,13 +2091,13 @@ void ClusterInfo::loadServers() {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LOG_TOPIC(DEBUG, Logger::CLUSTER)
|
||||
<< "Error while loading " << prefixServers
|
||||
<< " httpCode: " << result.httpCode()
|
||||
<< " errorCode: " << result.errorCode()
|
||||
<< " errorMessage: " << result.errorMessage()
|
||||
<< " body: " << result.body();
|
||||
<< "Error while loading " << prefixServers
|
||||
<< " httpCode: " << result.httpCode()
|
||||
<< " errorCode: " << result.errorCode()
|
||||
<< " errorMessage: " << result.errorMessage()
|
||||
<< " body: " << result.body();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -3593,7 +3593,7 @@ void LogicalCollection::mergeObjectsForUpdate(
|
|||
|
||||
std::unordered_map<std::string, VPackSlice> newValues;
|
||||
{
|
||||
VPackObjectIterator it(newValue, false);
|
||||
VPackObjectIterator it(newValue, true);
|
||||
while (it.valid()) {
|
||||
std::string key = it.key().copyString();
|
||||
if (!key.empty() && key[0] == '_' &&
|
||||
|
@ -3647,7 +3647,7 @@ void LogicalCollection::mergeObjectsForUpdate(
|
|||
|
||||
// add other attributes after the system attributes
|
||||
{
|
||||
VPackObjectIterator it(oldValue, false);
|
||||
VPackObjectIterator it(oldValue, true);
|
||||
while (it.valid()) {
|
||||
std::string key = it.key().copyString();
|
||||
// exclude system attributes in old value now
|
||||
|
@ -3690,15 +3690,15 @@ void LogicalCollection::mergeObjectsForUpdate(
|
|||
}
|
||||
|
||||
// add remaining values that were only in new object
|
||||
for (auto& it : newValues) {
|
||||
auto& s = it.second;
|
||||
for (auto const& it : newValues) {
|
||||
VPackSlice const& s = it.second;
|
||||
if (s.isNone()) {
|
||||
continue;
|
||||
}
|
||||
if (!keepNull && s.isNull()) {
|
||||
continue;
|
||||
}
|
||||
b.add(std::move(it.first), s);
|
||||
b.add(it.first, s);
|
||||
}
|
||||
|
||||
b.close();
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/* jshint strict: false, sub: true */
|
||||
/* global print, arango */
|
||||
'use strict';
|
||||
|
|
|
@ -296,90 +296,161 @@ function agencyTestSuite () {
|
|||
testClientIds : function () {
|
||||
var res;
|
||||
|
||||
var id = [guid(),guid(),guid(),guid(),guid(),
|
||||
guid(),guid(),guid(),guid(),guid()];
|
||||
var id = [guid(),guid(),guid(),guid(),guid(),guid(),
|
||||
guid(),guid(),guid(),guid(),guid(),guid(),
|
||||
guid(),guid(),guid()];
|
||||
var query = [{"a":12},{"a":13},{"a":13}];
|
||||
var pre = [{},{"a":12},{"a":12}];
|
||||
|
||||
writeAndCheck([[query[0], pre[0], id[0]]]);
|
||||
res = accessAgency("inquire",[id[0]]).bodyParsed;
|
||||
assertEqual(res.length, 1);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[0].length, 1);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
|
||||
writeAndCheck([[query[1], pre[1], id[0]]]);
|
||||
res = accessAgency("inquire",[id[0]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 1);
|
||||
assertEqual(res[0].length, 2);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[0][1].query, query[1]);
|
||||
|
||||
res = accessAgency("write",[[query[1], pre[1], id[2]]]);
|
||||
assertEqual(res.statusCode,412);
|
||||
res = accessAgency("inquire",[id[2]]).bodyParsed;
|
||||
assertEqual(res.length, 0);
|
||||
assertEqual(res[0].length, 0);
|
||||
|
||||
res = accessAgency("write",[[query[0], pre[0], id[3]],
|
||||
[query[1], pre[1], id[3]]]);
|
||||
assertEqual(res.statusCode,200);
|
||||
res = accessAgency("inquire",[id[3]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 1);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[0][1].query, query[1]);
|
||||
|
||||
res = accessAgency("write",[[query[0], pre[0], id[4]],
|
||||
[query[1], pre[1], id[4]],
|
||||
[query[2], pre[2], id[4]]]);
|
||||
assertEqual(res.statusCode,412);
|
||||
res = accessAgency("inquire",[id[4]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 1);
|
||||
assertEqual(res[0].length, 2);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[0][1].query, query[1]);
|
||||
|
||||
res = accessAgency("write",[[query[0], pre[0], id[5]],
|
||||
[query[2], pre[2], id[5]],
|
||||
[query[1], pre[1], id[5]]]);
|
||||
assertEqual(res.statusCode,412);
|
||||
|
||||
res = accessAgency("inquire",[id[5]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 1);
|
||||
assertEqual(res[0].length, 2);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[0][1].query, query[1]);
|
||||
|
||||
res = accessAgency("write",[[query[2], pre[2], id[6]],
|
||||
[query[0], pre[0], id[6]],
|
||||
[query[1], pre[1], id[6]]]);
|
||||
assertEqual(res.statusCode,412);
|
||||
res = accessAgency("inquire",[id[6]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 1);
|
||||
assertEqual(res[0].length, 2);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[0][1].query, query[1]);
|
||||
|
||||
res = accessAgency("write",[[query[2], pre[2], id[7]],
|
||||
[query[0], pre[0], id[8]],
|
||||
[query[1], pre[1], id[9]]]);
|
||||
assertEqual(res.statusCode,412);
|
||||
res = accessAgency("inquire",[id[7],id[8],id[9]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 0);
|
||||
assertEqual(res[1].length, 1);
|
||||
assertEqual(res[1][0].query, query[0]);
|
||||
assertEqual(res[2].length, 1);
|
||||
assertEqual(res[2][0].query, query[1]);
|
||||
|
||||
res = accessAgency("inquire",[id[9],id[7],id[8]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[1]);
|
||||
assertEqual(res[1].query, query[0]);
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 1);
|
||||
assertEqual(res[0][0].query, query[1]);
|
||||
assertEqual(res[1].length, 0);
|
||||
assertEqual(res[2].length, 1);
|
||||
assertEqual(res[2][0].query, query[0]);
|
||||
|
||||
res = accessAgency("inquire",[id[8],id[9],id[7]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 1);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[1].length, 1);
|
||||
assertEqual(res[1][0].query, query[1]);
|
||||
assertEqual(res[2].length, 0);
|
||||
|
||||
res = accessAgency("inquire",[id[7],id[9],id[8]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[1]);
|
||||
assertEqual(res[1].query, query[0]);
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 0);
|
||||
assertEqual(res[1].length, 1);
|
||||
assertEqual(res[1][0].query, query[1]);
|
||||
assertEqual(res[2].length, 1);
|
||||
assertEqual(res[2][0].query, query[0]);
|
||||
|
||||
res = accessAgency("inquire",[id[8],id[7],id[9]]).bodyParsed;
|
||||
assertEqual(res.length, 2);
|
||||
assertEqual(res[0].query, query[0]);
|
||||
assertEqual(res[1].query, query[1]);
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 1);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[1].length, 0);
|
||||
assertEqual(res[2].length, 1);
|
||||
assertEqual(res[2][0].query, query[1]);
|
||||
|
||||
res = accessAgency("inquire",[id[7],id[8],id[9]]).bodyParsed;
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 0);
|
||||
assertEqual(res[1].length, 1);
|
||||
assertEqual(res[1][0].query, query[0]);
|
||||
assertEqual(res[2].length, 1);
|
||||
assertEqual(res[2][0].query, query[1]);
|
||||
|
||||
res = accessAgency("inquire",[id[7],id[8],id[9]]).bodyParsed;
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 0);
|
||||
assertEqual(res[1].length, 1);
|
||||
assertEqual(res[1][0].query, query[0]);
|
||||
assertEqual(res[2].length, 1);
|
||||
assertEqual(res[2][0].query, query[1]);
|
||||
|
||||
res = accessAgency("write",[[query[2], pre[2], id[10]],
|
||||
[query[0], pre[0], id[11]],
|
||||
[query[1], pre[1], id[12]],
|
||||
[query[0], pre[0], id[12]]]);
|
||||
|
||||
res = accessAgency("inquire",[id[10],id[11],id[12]]).bodyParsed;
|
||||
assertEqual(res.length, 3);
|
||||
assertEqual(res[0].length, 0);
|
||||
assertEqual(res[1].length, 1);
|
||||
assertEqual(res[1][0].query, query[0]);
|
||||
assertEqual(res[2].length, 2);
|
||||
assertEqual(res[2][0].query, query[1]);
|
||||
assertEqual(res[2][1].query, query[0]);
|
||||
|
||||
res = accessAgency("transact",[[query[0], pre[0], id[13]],
|
||||
[query[2], pre[2], id[13]],
|
||||
[query[1], pre[1], id[13]],
|
||||
["a"]]);
|
||||
|
||||
|
||||
assertEqual(res.statusCode,412);
|
||||
assertEqual(res.bodyParsed.length, 4);
|
||||
assertEqual(res.bodyParsed[0] > 0, true);
|
||||
assertEqual(res.bodyParsed[1] > 0, true);
|
||||
assertEqual(res.bodyParsed[2], 0);
|
||||
assertEqual(res.bodyParsed[3], query[1]);
|
||||
|
||||
res = accessAgency("inquire",[id[13]]).bodyParsed;
|
||||
assertEqual(res.length, 1);
|
||||
assertEqual(res[0].length, 2);
|
||||
assertEqual(res[0][0].query, query[0]);
|
||||
assertEqual(res[0][1].query, query[2]);
|
||||
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue