Add Bolt v5 support (#938)
This commit is contained in:
parent
d917c3f0fd
commit
30ec570bb9
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -20,6 +20,8 @@ inline constexpr uint8_t kPreamble[4] = {0x60, 0x60, 0xB0, 0x17};
|
|||||||
enum class Signature : uint8_t {
|
enum class Signature : uint8_t {
|
||||||
Noop = 0x00,
|
Noop = 0x00,
|
||||||
Init = 0x01,
|
Init = 0x01,
|
||||||
|
LogOn = 0x6A,
|
||||||
|
LogOff = 0x6B,
|
||||||
AckFailure = 0x0E, // only v1
|
AckFailure = 0x0E, // only v1
|
||||||
Reset = 0x0F,
|
Reset = 0x0F,
|
||||||
Goodbye = 0x02,
|
Goodbye = 0x02,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -28,7 +28,7 @@ inline constexpr size_t kChunkWholeSize = kChunkHeaderSize + kChunkMaxDataSize;
|
|||||||
*/
|
*/
|
||||||
inline constexpr size_t kHandshakeSize = 20;
|
inline constexpr size_t kHandshakeSize = 20;
|
||||||
|
|
||||||
inline constexpr uint16_t kSupportedVersions[] = {0x0100, 0x0400, 0x0401, 0x0403};
|
inline constexpr uint16_t kSupportedVersions[] = {0x0100, 0x0400, 0x0401, 0x0403, 0x0502};
|
||||||
|
|
||||||
inline constexpr int kPullAll = -1;
|
inline constexpr int kPullAll = -1;
|
||||||
inline constexpr int kPullLast = -1;
|
inline constexpr int kPullLast = -1;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -33,7 +33,14 @@ namespace memgraph::communication::bolt {
|
|||||||
template <typename Buffer>
|
template <typename Buffer>
|
||||||
class Decoder {
|
class Decoder {
|
||||||
public:
|
public:
|
||||||
explicit Decoder(Buffer &buffer) : buffer_(buffer) {}
|
explicit Decoder(Buffer &buffer) : buffer_(buffer), major_v_(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets the user update the version.
|
||||||
|
* This is all single thread for now. TODO: Update if ever multithreaded.
|
||||||
|
* @param major_v the major version of the Bolt protocol used.
|
||||||
|
*/
|
||||||
|
void UpdateVersion(int major_v) { major_v_ = major_v; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a Value from the available data in the buffer.
|
* Reads a Value from the available data in the buffer.
|
||||||
@ -208,6 +215,10 @@ class Decoder {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
Buffer &buffer_;
|
Buffer &buffer_;
|
||||||
|
int major_v_; //!< Major version of the underlying Bolt protocol
|
||||||
|
// TODO: when refactoring
|
||||||
|
// Ideally the major_v would be a compile time constant. If the higher level (Bolt driver) ends up being separate
|
||||||
|
// classes, this could be just a template and each version of the driver would use the appropriate decoder.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool ReadNull(const Marker &marker, Value *data) {
|
bool ReadNull(const Marker &marker, Value *data) {
|
||||||
@ -370,11 +381,7 @@ class Decoder {
|
|||||||
}
|
}
|
||||||
ret.emplace(std::move(dv_key.ValueString()), std::move(dv_val));
|
ret.emplace(std::move(dv_key.ValueString()), std::move(dv_val));
|
||||||
}
|
}
|
||||||
if (ret.size() != size) {
|
return ret.size() == size;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadVertex(Value *data) {
|
bool ReadVertex(Value *data) {
|
||||||
@ -407,6 +414,14 @@ class Decoder {
|
|||||||
}
|
}
|
||||||
vertex.properties = std::move(dv.ValueMap());
|
vertex.properties = std::move(dv.ValueMap());
|
||||||
|
|
||||||
|
if (major_v_ > 4) {
|
||||||
|
// element_id introduced in v5.0
|
||||||
|
if (!ReadValue(&dv, Value::Type::String)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vertex.element_id = std::move(dv.ValueString());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,6 +460,23 @@ class Decoder {
|
|||||||
}
|
}
|
||||||
edge.properties = std::move(dv.ValueMap());
|
edge.properties = std::move(dv.ValueMap());
|
||||||
|
|
||||||
|
if (major_v_ > 4) {
|
||||||
|
// element_id introduced in v5.0
|
||||||
|
if (!ReadValue(&dv, Value::Type::String)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
edge.element_id = std::move(dv.ValueString());
|
||||||
|
// from_element_id introduced in v5.0
|
||||||
|
if (!ReadValue(&dv, Value::Type::String)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
edge.from_element_id = std::move(dv.ValueString());
|
||||||
|
// to_element_id introduced in v5.0
|
||||||
|
if (!ReadValue(&dv, Value::Type::String)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
edge.to_element_id = std::move(dv.ValueString());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,6 +503,14 @@ class Decoder {
|
|||||||
}
|
}
|
||||||
edge.properties = std::move(dv.ValueMap());
|
edge.properties = std::move(dv.ValueMap());
|
||||||
|
|
||||||
|
if (major_v_ > 4) {
|
||||||
|
// element_id introduced in v5.0
|
||||||
|
if (!ReadValue(&dv, Value::Type::String)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
edge.element_id = std::move(dv.ValueString());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -36,7 +36,14 @@ namespace memgraph::communication::bolt {
|
|||||||
template <typename Buffer>
|
template <typename Buffer>
|
||||||
class BaseEncoder {
|
class BaseEncoder {
|
||||||
public:
|
public:
|
||||||
explicit BaseEncoder(Buffer &buffer) : buffer_(buffer) {}
|
explicit BaseEncoder(Buffer &buffer) : buffer_(buffer), major_v_(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets the user update the version.
|
||||||
|
* This is all single thread for now. TODO: Update if ever multithreaded.
|
||||||
|
* @param major_v the major version of the Bolt protocol used.
|
||||||
|
*/
|
||||||
|
void UpdateVersion(int major_v) { major_v_ = major_v; }
|
||||||
|
|
||||||
void WriteRAW(const uint8_t *data, uint64_t len) { buffer_.Write(data, len); }
|
void WriteRAW(const uint8_t *data, uint64_t len) { buffer_.Write(data, len); }
|
||||||
|
|
||||||
@ -116,7 +123,8 @@ class BaseEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WriteVertex(const Vertex &vertex) {
|
void WriteVertex(const Vertex &vertex) {
|
||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + 3);
|
int struct_n = 3 + 1 * int(major_v_ > 4); // element_id introduced from v5
|
||||||
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + struct_n);
|
||||||
WriteRAW(utils::UnderlyingCast(Signature::Node));
|
WriteRAW(utils::UnderlyingCast(Signature::Node));
|
||||||
WriteInt(vertex.id.AsInt());
|
WriteInt(vertex.id.AsInt());
|
||||||
|
|
||||||
@ -132,10 +140,16 @@ class BaseEncoder {
|
|||||||
WriteString(prop.first);
|
WriteString(prop.first);
|
||||||
WriteValue(prop.second);
|
WriteValue(prop.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (major_v_ > 4) {
|
||||||
|
// element_id introduced in v5.0
|
||||||
|
WriteString(vertex.element_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteEdge(const Edge &edge, bool unbound = false) {
|
void WriteEdge(const Edge &edge, bool unbound = false) {
|
||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + (unbound ? 3 : 5));
|
int struct_n = (unbound ? 3 + 1 * int(major_v_ > 4) : 5 + 3 * int(major_v_ > 4)); // element_id introduced from v5
|
||||||
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + struct_n);
|
||||||
WriteRAW(utils::UnderlyingCast(unbound ? Signature::UnboundRelationship : Signature::Relationship));
|
WriteRAW(utils::UnderlyingCast(unbound ? Signature::UnboundRelationship : Signature::Relationship));
|
||||||
|
|
||||||
WriteInt(edge.id.AsInt());
|
WriteInt(edge.id.AsInt());
|
||||||
@ -152,10 +166,22 @@ class BaseEncoder {
|
|||||||
WriteString(prop.first);
|
WriteString(prop.first);
|
||||||
WriteValue(prop.second);
|
WriteValue(prop.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (major_v_ > 4) {
|
||||||
|
// element_id introduced in v5.0
|
||||||
|
WriteString(edge.element_id);
|
||||||
|
if (!unbound) {
|
||||||
|
// from_element_id introduced in v5.0
|
||||||
|
WriteString(edge.from_element_id);
|
||||||
|
// to_element_id introduced in v5.0
|
||||||
|
WriteString(edge.to_element_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteEdge(const UnboundedEdge &edge) {
|
void WriteEdge(const UnboundedEdge &edge) {
|
||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + 3);
|
const int struct_n = 3 + 1 * int(major_v_ > 4); // element_id introduced from v5
|
||||||
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + struct_n);
|
||||||
WriteRAW(utils::UnderlyingCast(Signature::UnboundRelationship));
|
WriteRAW(utils::UnderlyingCast(Signature::UnboundRelationship));
|
||||||
|
|
||||||
WriteInt(edge.id.AsInt());
|
WriteInt(edge.id.AsInt());
|
||||||
@ -168,6 +194,11 @@ class BaseEncoder {
|
|||||||
WriteString(prop.first);
|
WriteString(prop.first);
|
||||||
WriteValue(prop.second);
|
WriteValue(prop.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (major_v_ > 4) {
|
||||||
|
// element_id introduced in v5.0
|
||||||
|
WriteString(edge.element_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WritePath(const Path &path) {
|
void WritePath(const Path &path) {
|
||||||
@ -264,6 +295,7 @@ class BaseEncoder {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
Buffer &buffer_;
|
Buffer &buffer_;
|
||||||
|
int major_v_; //!< Major version of the underlying Bolt protocol (TODO: Think about reimplementing the versioning)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class T>
|
template <class T>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -41,6 +41,8 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
|||||||
public:
|
public:
|
||||||
ClientEncoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
ClientEncoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
||||||
|
|
||||||
|
using BaseEncoder<Buffer>::UpdateVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a Init message.
|
* Writes a Init message.
|
||||||
*
|
*
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -34,6 +34,8 @@ class Encoder : private BaseEncoder<Buffer> {
|
|||||||
public:
|
public:
|
||||||
Encoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
Encoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
||||||
|
|
||||||
|
using BaseEncoder<Buffer>::UpdateVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a Record message.
|
* Sends a Record message.
|
||||||
*
|
*
|
||||||
|
@ -121,6 +121,9 @@ class Session {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handshake_done_ = true;
|
handshake_done_ = true;
|
||||||
|
// Update the decoder's Bolt version (v5 has changed the undelying structure)
|
||||||
|
decoder_.UpdateVersion(version_.major);
|
||||||
|
encoder_.UpdateVersion(version_.major);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkState chunk_state;
|
ChunkState chunk_state;
|
||||||
|
@ -91,6 +91,37 @@ State RunHandlerV4(Signature signature, TSession &session, State state, Marker m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
State RunHandlerV5(Signature signature, TSession &session, State state, Marker marker) {
|
||||||
|
switch (signature) {
|
||||||
|
case Signature::Run:
|
||||||
|
return HandleRunV5<TSession>(session, state, marker);
|
||||||
|
case Signature::Pull:
|
||||||
|
return HandlePullV5<TSession>(session, state, marker);
|
||||||
|
case Signature::Discard:
|
||||||
|
return HandleDiscardV5<TSession>(session, state, marker);
|
||||||
|
case Signature::Reset:
|
||||||
|
return HandleReset<TSession>(session, marker);
|
||||||
|
case Signature::Begin:
|
||||||
|
return HandleBegin<TSession>(session, state, marker);
|
||||||
|
case Signature::Commit:
|
||||||
|
return HandleCommit<TSession>(session, state, marker);
|
||||||
|
case Signature::Goodbye:
|
||||||
|
return HandleGoodbye<TSession>();
|
||||||
|
case Signature::Rollback:
|
||||||
|
return HandleRollback<TSession>(session, state, marker);
|
||||||
|
case Signature::Noop:
|
||||||
|
return HandleNoop<TSession>(state);
|
||||||
|
case Signature::Route:
|
||||||
|
return HandleRoute<TSession>(session, marker);
|
||||||
|
case Signature::LogOff:
|
||||||
|
return HandleLogOff<TSession>();
|
||||||
|
default:
|
||||||
|
spdlog::trace("Unrecognized signature received (0x{:02X})!", utils::UnderlyingCast(signature));
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executor state run function
|
* Executor state run function
|
||||||
* This function executes an initialized Bolt session.
|
* This function executes an initialized Bolt session.
|
||||||
@ -120,6 +151,8 @@ State StateExecutingRun(TSession &session, State state) {
|
|||||||
}
|
}
|
||||||
return RunHandlerV4<TSession>(signature, session, state, marker);
|
return RunHandlerV4<TSession>(signature, session, state, marker);
|
||||||
}
|
}
|
||||||
|
case 5:
|
||||||
|
return RunHandlerV5<TSession>(signature, session, state, marker);
|
||||||
default:
|
default:
|
||||||
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
|
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
|
||||||
return State::Close;
|
return State::Close;
|
||||||
|
@ -309,6 +309,12 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
State HandleRunV5(TSession &session, const State state, const Marker marker) {
|
||||||
|
// Using V4 on purpose
|
||||||
|
return HandleRunV4<TSession>(session, state, marker);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
State HandlePullV1(TSession &session, const State state, const Marker marker) {
|
State HandlePullV1(TSession &session, const State state, const Marker marker) {
|
||||||
return details::HandlePullDiscardV1<true>(session, state, marker);
|
return details::HandlePullDiscardV1<true>(session, state, marker);
|
||||||
@ -319,6 +325,12 @@ State HandlePullV4(TSession &session, const State state, const Marker marker) {
|
|||||||
return details::HandlePullDiscardV4<true>(session, state, marker);
|
return details::HandlePullDiscardV4<true>(session, state, marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
State HandlePullV5(TSession &session, const State state, const Marker marker) {
|
||||||
|
// Using V4 on purpose
|
||||||
|
return HandlePullV4<TSession>(session, state, marker);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
State HandleDiscardV1(TSession &session, const State state, const Marker marker) {
|
State HandleDiscardV1(TSession &session, const State state, const Marker marker) {
|
||||||
return details::HandlePullDiscardV1<false>(session, state, marker);
|
return details::HandlePullDiscardV1<false>(session, state, marker);
|
||||||
@ -329,6 +341,12 @@ State HandleDiscardV4(TSession &session, const State state, const Marker marker)
|
|||||||
return details::HandlePullDiscardV4<false>(session, state, marker);
|
return details::HandlePullDiscardV4<false>(session, state, marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
State HandleDiscardV5(TSession &session, const State state, const Marker marker) {
|
||||||
|
// Using V4 on purpose
|
||||||
|
return HandleDiscardV4<TSession>(session, state, marker);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
State HandleReset(TSession &session, const Marker marker) {
|
State HandleReset(TSession &session, const Marker marker) {
|
||||||
// IMPORTANT: This implementation of the Bolt RESET command isn't fully
|
// IMPORTANT: This implementation of the Bolt RESET command isn't fully
|
||||||
@ -486,4 +504,10 @@ State HandleRoute(TSession &session, const Marker marker) {
|
|||||||
}
|
}
|
||||||
return State::Error;
|
return State::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
State HandleLogOff() {
|
||||||
|
// Not arguments sent, the user just needs to reauthenticate
|
||||||
|
return State::Init;
|
||||||
|
}
|
||||||
} // namespace memgraph::communication::bolt
|
} // namespace memgraph::communication::bolt
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -27,17 +27,25 @@ namespace details {
|
|||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
std::optional<State> AuthenticateUser(TSession &session, Value &metadata) {
|
std::optional<State> AuthenticateUser(TSession &session, Value &metadata) {
|
||||||
// Get authentication data.
|
// Get authentication data.
|
||||||
|
// From neo4j driver v4.4, fields that have a default value are not sent.
|
||||||
|
// In order to have back-compatibility, the missing fields will be added.
|
||||||
|
|
||||||
auto &data = metadata.ValueMap();
|
auto &data = metadata.ValueMap();
|
||||||
if (!data.count("scheme")) {
|
if (data.empty()) { // Special case auth=None
|
||||||
spdlog::warn("The client didn't supply authentication information!");
|
spdlog::warn("The client didn't supply the authentication scheme! Trying with \"none\"...");
|
||||||
return State::Close;
|
data["scheme"] = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string password;
|
std::string password;
|
||||||
if (data["scheme"].ValueString() == "basic") {
|
if (data["scheme"].ValueString() == "basic") {
|
||||||
if (!data.count("principal") || !data.count("credentials")) {
|
if (!data.count("principal")) { // Special case principal = ""
|
||||||
spdlog::warn("The client didn't supply authentication information!");
|
spdlog::warn("The client didn't supply the principal field! Trying with \"\"...");
|
||||||
return State::Close;
|
data["principal"] = "";
|
||||||
|
}
|
||||||
|
if (!data.count("credentials")) { // Special case credentials = ""
|
||||||
|
spdlog::warn("The client didn't supply the credentials field! Trying with \"\"...");
|
||||||
|
data["credentials"] = "";
|
||||||
}
|
}
|
||||||
username = data["principal"].ValueString();
|
username = data["principal"].ValueString();
|
||||||
password = data["credentials"].ValueString();
|
password = data["credentials"].ValueString();
|
||||||
@ -106,6 +114,30 @@ std::optional<Value> GetMetadataV4(TSession &session, const Marker marker) {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &data = metadata.ValueMap();
|
||||||
|
if (!data.count("user_agent")) {
|
||||||
|
spdlog::warn("The client didn't supply the user agent!");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::info("Client connected '{}'", data.at("user_agent").ValueString());
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
std::optional<Value> GetInitDataV5(TSession &session, const Marker marker) {
|
||||||
|
if (marker != Marker::TinyStruct1) [[unlikely]] {
|
||||||
|
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value metadata;
|
||||||
|
if (!session.decoder_.ReadValue(&metadata, Value::Type::Map)) {
|
||||||
|
spdlog::trace("Couldn't read metadata!");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
const auto &data = metadata.ValueMap();
|
const auto &data = metadata.ValueMap();
|
||||||
if (!data.count("user_agent")) {
|
if (!data.count("user_agent")) {
|
||||||
spdlog::warn("The client didn't supply the user agent!");
|
spdlog::warn("The client didn't supply the user agent!");
|
||||||
@ -117,6 +149,22 @@ std::optional<Value> GetMetadataV4(TSession &session, const Marker marker) {
|
|||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
std::optional<Value> GetAuthDataV5(TSession &session, const Marker marker) {
|
||||||
|
if (marker != Marker::TinyStruct1) [[unlikely]] {
|
||||||
|
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value metadata;
|
||||||
|
if (!session.decoder_.ReadValue(&metadata, Value::Type::Map)) {
|
||||||
|
spdlog::trace("Couldn't read metadata!");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
State SendSuccessMessage(TSession &session) {
|
State SendSuccessMessage(TSession &session) {
|
||||||
// Neo4j's Java driver 4.1.1+ requires connection_id.
|
// Neo4j's Java driver 4.1.1+ requires connection_id.
|
||||||
@ -180,6 +228,57 @@ State StateInitRunV4(TSession &session, Marker marker, Signature signature) {
|
|||||||
|
|
||||||
return SendSuccessMessage(session);
|
return SendSuccessMessage(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TSession>
|
||||||
|
State StateInitRunV5(TSession &session, Marker marker, Signature signature) {
|
||||||
|
if (signature == Signature::Noop) [[unlikely]] {
|
||||||
|
SPDLOG_DEBUG("Received NOOP message");
|
||||||
|
return State::Init;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signature == Signature::Init) {
|
||||||
|
auto maybeMetadata = GetInitDataV5(session, marker);
|
||||||
|
|
||||||
|
if (!maybeMetadata) {
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SendSuccessMessage(session) == State::Close) {
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
// Stay in Init
|
||||||
|
return State::Init;
|
||||||
|
|
||||||
|
} else if (signature == Signature::LogOn) {
|
||||||
|
if (marker != Marker::TinyStruct1) [[unlikely]] {
|
||||||
|
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||||
|
spdlog::trace(
|
||||||
|
"The client sent malformed data, but we are continuing "
|
||||||
|
"because the official Neo4j Java driver sends malformed "
|
||||||
|
"data. D'oh!");
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybeMetadata = GetAuthDataV5(session, marker);
|
||||||
|
if (!maybeMetadata) {
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
auto result = AuthenticateUser(session, *maybeMetadata);
|
||||||
|
if (result) {
|
||||||
|
spdlog::trace("Failed to authenticate, closing connection...");
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SendSuccessMessage(session) == State::Close) {
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
return State::Idle;
|
||||||
|
|
||||||
|
} else [[unlikely]] {
|
||||||
|
spdlog::trace("Expected Init signature, but received 0x{:02X}!", utils::UnderlyingCast(signature));
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace details
|
} // namespace details
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,6 +307,9 @@ State StateInitRun(TSession &session) {
|
|||||||
}
|
}
|
||||||
return details::StateInitRunV4<TSession>(session, marker, signature);
|
return details::StateInitRunV4<TSession>(session, marker, signature);
|
||||||
}
|
}
|
||||||
|
case 5: {
|
||||||
|
return details::StateInitRunV5<TSession>(session, marker, signature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
|
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
|
||||||
return State::Close;
|
return State::Close;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -342,6 +342,9 @@ std::ostream &operator<<(std::ostream &os, const Vertex &vertex) {
|
|||||||
[&](auto &stream, const auto &pair) { stream << pair.first << ": " << pair.second; });
|
[&](auto &stream, const auto &pair) { stream << pair.first << ": " << pair.second; });
|
||||||
os << "}";
|
os << "}";
|
||||||
}
|
}
|
||||||
|
if (!vertex.element_id.empty()) {
|
||||||
|
os << " element_id: " << vertex.element_id;
|
||||||
|
}
|
||||||
return os << ")";
|
return os << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -57,6 +57,7 @@ struct Vertex {
|
|||||||
Id id;
|
Id id;
|
||||||
std::vector<std::string> labels;
|
std::vector<std::string> labels;
|
||||||
std::map<std::string, Value> properties;
|
std::map<std::string, Value> properties;
|
||||||
|
std::string element_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,6 +70,9 @@ struct Edge {
|
|||||||
Id to;
|
Id to;
|
||||||
std::string type;
|
std::string type;
|
||||||
std::map<std::string, Value> properties;
|
std::map<std::string, Value> properties;
|
||||||
|
std::string element_id;
|
||||||
|
std::string from_element_id;
|
||||||
|
std::string to_element_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +83,7 @@ struct UnboundedEdge {
|
|||||||
Id id;
|
Id id;
|
||||||
std::string type;
|
std::string type;
|
||||||
std::map<std::string, Value> properties;
|
std::map<std::string, Value> properties;
|
||||||
|
std::string element_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -150,7 +150,9 @@ storage::Result<communication::bolt::Vertex> ToBoltVertex(const storage::VertexA
|
|||||||
for (const auto &prop : *maybe_properties) {
|
for (const auto &prop : *maybe_properties) {
|
||||||
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
||||||
}
|
}
|
||||||
return communication::bolt::Vertex{id, labels, properties};
|
// Introduced in Bolt v5 (for now just send the ID)
|
||||||
|
const auto element_id = std::to_string(id.AsInt());
|
||||||
|
return communication::bolt::Vertex{id, labels, properties, element_id};
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccessor &edge, const storage::Storage &db,
|
storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccessor &edge, const storage::Storage &db,
|
||||||
@ -165,7 +167,11 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccesso
|
|||||||
for (const auto &prop : *maybe_properties) {
|
for (const auto &prop : *maybe_properties) {
|
||||||
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
||||||
}
|
}
|
||||||
return communication::bolt::Edge{id, from, to, type, properties};
|
// Introduced in Bolt v5 (for now just send the ID)
|
||||||
|
const auto element_id = std::to_string(id.AsInt());
|
||||||
|
const auto from_element_id = std::to_string(from.AsInt());
|
||||||
|
const auto to_element_id = std::to_string(to.AsInt());
|
||||||
|
return communication::bolt::Edge{id, from, to, type, properties, element_id, from_element_id, to_element_id};
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db,
|
storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db,
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Neo4j.Driver.Simple" Version="5.8.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
35
tests/drivers/csharp/v5_8/DocsHowToQuery/Program.cs
Normal file
35
tests/drivers/csharp/v5_8/DocsHowToQuery/Program.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Neo4j.Driver;
|
||||||
|
|
||||||
|
public class Basic {
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
using (var driver = GraphDatabase.Driver(
|
||||||
|
"bolt://localhost:7687", AuthTokens.None,
|
||||||
|
(ConfigBuilder builder) => builder.WithEncryptionLevel(
|
||||||
|
EncryptionLevel.None))) using (var session = driver.Session()) {
|
||||||
|
session.Run("MATCH (n) DETACH DELETE n;").Consume();
|
||||||
|
Console.WriteLine("Database cleared.");
|
||||||
|
|
||||||
|
session.Run("CREATE (alice:Person {name: \"Alice\", age: 22});").Consume();
|
||||||
|
Console.WriteLine("Record created.");
|
||||||
|
|
||||||
|
var node = (INode)session.Run("MATCH (n) RETURN n;").First()["n"];
|
||||||
|
Console.WriteLine("Record matched.");
|
||||||
|
|
||||||
|
var label = string.Join("", node.Labels);
|
||||||
|
var name = node["name"];
|
||||||
|
var age = (long)node["age"];
|
||||||
|
|
||||||
|
if (!label.Equals("Person") || !name.Equals("Alice") || !age.Equals(22)) {
|
||||||
|
Console.WriteLine("Data doesn't match!");
|
||||||
|
System.Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Label: " + label);
|
||||||
|
Console.WriteLine("name: " + name);
|
||||||
|
Console.WriteLine("age: " + age);
|
||||||
|
}
|
||||||
|
Console.WriteLine("All ok!");
|
||||||
|
}
|
||||||
|
}
|
64
tests/drivers/csharp/v5_8/Transactions/Program.cs
Normal file
64
tests/drivers/csharp/v5_8/Transactions/Program.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Neo4j.Driver;
|
||||||
|
|
||||||
|
public class Transactions {
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
using (var driver = GraphDatabase.Driver(
|
||||||
|
"bolt://localhost:7687", AuthTokens.None,
|
||||||
|
(builder) => builder.WithEncryptionLevel(EncryptionLevel.None))) {
|
||||||
|
ClearDatabase(driver);
|
||||||
|
// Wrong query.
|
||||||
|
try {
|
||||||
|
using (var session = driver.Session()) using (var tx = session.BeginTransaction()) {
|
||||||
|
CreatePerson(tx, "mirko");
|
||||||
|
// Incorrectly start CREATE
|
||||||
|
tx.Run("CREATE (").Consume();
|
||||||
|
CreatePerson(tx, "slavko");
|
||||||
|
tx.Commit();
|
||||||
|
}
|
||||||
|
} catch (ClientException) {
|
||||||
|
Console.WriteLine("Rolled back transaction");
|
||||||
|
}
|
||||||
|
Trace.Assert(CountNodes(driver) == 0, "Expected transaction was rolled back.");
|
||||||
|
// Correct query.
|
||||||
|
using (var session = driver.Session()) using (var tx = session.BeginTransaction()) {
|
||||||
|
CreatePerson(tx, "mirka");
|
||||||
|
CreatePerson(tx, "slavka");
|
||||||
|
tx.Commit();
|
||||||
|
}
|
||||||
|
Trace.Assert(CountNodes(driver) == 2, "Expected 2 created nodes.");
|
||||||
|
ClearDatabase(driver);
|
||||||
|
using (var session = driver.Session()) {
|
||||||
|
// Create a lot of nodes so that the next read takes a long time.
|
||||||
|
session.Run("UNWIND range(1, 100000) AS i CREATE ()").Consume();
|
||||||
|
try {
|
||||||
|
Console.WriteLine("Running a long read...");
|
||||||
|
session.Run("MATCH (a), (b), (c), (d), (e), (f) RETURN COUNT(*) AS cnt").Consume();
|
||||||
|
} catch (TransientException) {
|
||||||
|
Console.WriteLine("Transaction timed out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Console.WriteLine("All ok!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreatePerson(ITransaction tx, string name) {
|
||||||
|
var parameters = new Dictionary<string, Object> { { "name", name } };
|
||||||
|
var result = tx.Run("CREATE (person:Person {name: $name}) RETURN person", parameters);
|
||||||
|
Console.WriteLine("Created: " + ((INode)result.First()["person"])["name"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ClearDatabase(IDriver driver) {
|
||||||
|
using (var session = driver.Session()) session.Run("MATCH (n) DETACH DELETE n").Consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CountNodes(IDriver driver) {
|
||||||
|
using (var session = driver.Session()) {
|
||||||
|
var result = session.Run("MATCH (n) RETURN COUNT(*) AS cnt");
|
||||||
|
return Convert.ToInt32(result.First()["cnt"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
tests/drivers/csharp/v5_8/Transactions/Transactions.csproj
Normal file
12
tests/drivers/csharp/v5_8/Transactions/Transactions.csproj
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Neo4j.Driver.Simple" Version="5.8.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
22
tests/drivers/csharp/v5_8/run.sh
Executable file
22
tests/drivers/csharp/v5_8/run.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
# check if dotnet-sdk-2.1 is installed
|
||||||
|
for i in dotnet; do
|
||||||
|
if ! which $i >/dev/null; then
|
||||||
|
echo "Please install $i!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for i in *; do
|
||||||
|
if [ ! -d $i ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
pushd $i
|
||||||
|
dotnet publish -c release --self-contained --runtime linux-x64 --framework netcoreapp2.1 -o build/
|
||||||
|
./build/$i
|
||||||
|
popd
|
||||||
|
done;
|
89
tests/drivers/go/v5/docs_quick_start.go
Normal file
89
tests/drivers/go/v5/docs_quick_start.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"fmt"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handle_if_error(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error occured: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dbUri := "bolt://localhost:7687"
|
||||||
|
driver, err := neo4j.NewDriver(dbUri, neo4j.BasicAuth("", "", ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("An error occurred opening conn: %s", err)
|
||||||
|
}
|
||||||
|
defer driver.Close()
|
||||||
|
|
||||||
|
session := driver.NewSession(neo4j.SessionConfig{})
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
_, err = session.WriteTransaction(clearDatabase)
|
||||||
|
handle_if_error(err)
|
||||||
|
fmt.Println("Database cleared.")
|
||||||
|
|
||||||
|
_, err = session.WriteTransaction(createItemFn)
|
||||||
|
handle_if_error(err)
|
||||||
|
fmt.Println("Record created.")
|
||||||
|
|
||||||
|
_,err = session.WriteTransaction(testAll)
|
||||||
|
handle_if_error(err)
|
||||||
|
fmt.Println("All ok!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearDatabase(tx neo4j.Transaction) (interface{}, error) {
|
||||||
|
result, err := tx.Run(
|
||||||
|
"MATCH (n) DETACH DELETE n;",
|
||||||
|
map[string]interface{}{})
|
||||||
|
handle_if_error(err)
|
||||||
|
return result.Consume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createItemFn(tx neo4j.Transaction) (interface{}, error) {
|
||||||
|
result, err := tx.Run(
|
||||||
|
`CREATE (alice:Person {name: "Alice", age: 22});`,
|
||||||
|
map[string]interface{}{})
|
||||||
|
handle_if_error(err)
|
||||||
|
return result.Consume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAll(tx neo4j.Transaction) (interface{}, error) {
|
||||||
|
result, err := tx.Run(
|
||||||
|
"MATCH (n) RETURN n;",
|
||||||
|
map[string]interface{}{})
|
||||||
|
handle_if_error(err)
|
||||||
|
|
||||||
|
if !result.Next() {
|
||||||
|
log.Fatal("Missing result.")
|
||||||
|
}
|
||||||
|
|
||||||
|
node_record, found := result.Record().Get("n")
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("Wrong result returned.")
|
||||||
|
}
|
||||||
|
|
||||||
|
node_value := node_record.(neo4j.Node)
|
||||||
|
fmt.Println("Record matched.")
|
||||||
|
|
||||||
|
label := node_value.Labels[0]
|
||||||
|
name, err := neo4j.GetProperty[string](node_value, "name")
|
||||||
|
handle_if_error(err)
|
||||||
|
age, err := neo4j.GetProperty[int64](node_value, "age")
|
||||||
|
handle_if_error(err)
|
||||||
|
|
||||||
|
if label != "Person" && name != "Alice" && age != 22 {
|
||||||
|
return nil, fmt.Errorf("Data doesn't match.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Label", label)
|
||||||
|
fmt.Println("name", name)
|
||||||
|
fmt.Println("age", age)
|
||||||
|
|
||||||
|
return result.Consume()
|
||||||
|
}
|
||||||
|
|
8
tests/drivers/go/v5/go.mod
Normal file
8
tests/drivers/go/v5/go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module bolt-test
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.9.0 // indirect
|
||||||
|
golang.org/dl v0.0.0-20230502172222-5216546bad51 // indirect
|
||||||
|
)
|
10
tests/drivers/go/v5/go.sum
Normal file
10
tests/drivers/go/v5/go.sum
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.5.0 h1:KxufacDV+IqkzbzvjIAIGkBsa2i0lEB8/MhCgOQxrQo=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.5.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.6.0 h1:+LxOHCyDWGjtD8qHhb20GUpvwCFcJm1wqSEyo2MiehE=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.6.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.8.1 h1:IysKg6KJIUgyItmnHRRrt2N8srbd6znMslRW3qQErTQ=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.8.1/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.9.0 h1:TYxT0RSiwnvVFia90V7TLnRXv8HkdQQ6rTUaPVoyZ+w=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.9.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
|
golang.org/dl v0.0.0-20230502172222-5216546bad51 h1:Bmo/kmR2hzyhGt3jjtl1ghkCqa5LINbB9D3QTkiLJIY=
|
||||||
|
golang.org/dl v0.0.0-20230502172222-5216546bad51/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
20
tests/drivers/go/v5/run.sh
Executable file
20
tests/drivers/go/v5/run.sh
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
GO_VERSION="1.18.9"
|
||||||
|
GO_VERSION_DIR="/opt/go$GO_VERSION"
|
||||||
|
if [ -f "$GO_VERSION_DIR/go/bin/go" ]; then
|
||||||
|
export GOROOT="$GO_VERSION_DIR/go"
|
||||||
|
export GOPATH="$HOME/go$GO_VERSION"
|
||||||
|
export PATH="$GO_VERSION_DIR/go/bin:$PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if go is installed
|
||||||
|
for i in go; do
|
||||||
|
if ! which $i >/dev/null; then
|
||||||
|
echo "Please install $i!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
go get github.com/neo4j/neo4j-go-driver/v5
|
||||||
|
go run docs_quick_start.go
|
1
tests/drivers/java/v5_8/.gitignore
vendored
Normal file
1
tests/drivers/java/v5_8/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
target/
|
93
tests/drivers/java/v5_8/pom.xml
Normal file
93
tests/drivers/java/v5_8/pom.xml
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>memgraph</groupId>
|
||||||
|
<artifactId>java-driver-tests</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.neo4j.driver</groupId>
|
||||||
|
<artifactId>neo4j-java-driver</artifactId>
|
||||||
|
<version>5.8.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>build-a</id>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>memgraph.DocsHowToQuery</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<finalName>DocsHowToQuery</finalName>
|
||||||
|
</configuration>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>build-b</id>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>memgraph.MaxQueryLength</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<finalName>MaxQueryLength</finalName>
|
||||||
|
</configuration>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>build-c</id>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>memgraph.Transactions</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<finalName>Transactions</finalName>
|
||||||
|
</configuration>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
38
tests/drivers/java/v5_8/run.sh
Executable file
38
tests/drivers/java/v5_8/run.sh
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
if [ -d "/usr/lib/jvm/java-17-oracle" ]; then
|
||||||
|
export JAVA_HOME="/usr/lib/jvm/java-17-oracle"
|
||||||
|
fi
|
||||||
|
if [ -d "/usr/lib/jvm/java-17-openjdk-amd64" ]; then
|
||||||
|
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
|
||||||
|
fi
|
||||||
|
if [ -d "/opt/apache-maven-3.9.2" ]; then
|
||||||
|
export M2_HOME="/opt/apache-maven-3.9.2"
|
||||||
|
fi
|
||||||
|
export PATH="$JAVA_HOME/bin:$M2_HOME/bin:$PATH"
|
||||||
|
|
||||||
|
for i in java mvn; do
|
||||||
|
if ! which $i >/dev/null; then
|
||||||
|
echo "Please install $i!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
JAVA_VER=$(java -version 2>&1 >/dev/null | grep 'version' | cut -d "\"" -f2 | cut -d "." -f1)
|
||||||
|
if [ $JAVA_VER -ne 17 ]
|
||||||
|
then
|
||||||
|
echo "neo4j-java-driver v5.8 requires Java 17. Please install it!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CentOS 7 doesn't have Java version that supports var keyword
|
||||||
|
source ../../../../environment/util.sh
|
||||||
|
|
||||||
|
mvn clean package
|
||||||
|
|
||||||
|
java -jar target/DocsHowToQuery.jar
|
||||||
|
java -jar target/MaxQueryLength.jar
|
||||||
|
java -jar target/Transactions.jar
|
@ -0,0 +1,48 @@
|
|||||||
|
package memgraph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import org.neo4j.driver.AuthTokens;
|
||||||
|
import org.neo4j.driver.Driver;
|
||||||
|
import org.neo4j.driver.GraphDatabase;
|
||||||
|
import org.neo4j.driver.Query;
|
||||||
|
import org.neo4j.driver.Config;
|
||||||
|
|
||||||
|
import static org.neo4j.driver.Values.parameters;
|
||||||
|
|
||||||
|
public class DocsHowToQuery {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var config = Config.builder().withoutEncryption().build();
|
||||||
|
var driver = GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("", ""), config);
|
||||||
|
|
||||||
|
try (var session = driver.session()) {
|
||||||
|
session.run("MATCH (n) DETACH DELETE n;");
|
||||||
|
System.out.println("Database cleared.");
|
||||||
|
|
||||||
|
session.run("CREATE (alice:Person {name: 'Alice', age: 22});");
|
||||||
|
System.out.println("Record created.");
|
||||||
|
|
||||||
|
var node = session.run("MATCH (n) RETURN n;").list().get(0).get("n").asNode();
|
||||||
|
System.out.println("Record matched.");
|
||||||
|
|
||||||
|
var label = node.labels().iterator().next();
|
||||||
|
var name = node.get("name").asString();
|
||||||
|
var age = node.get("age").asInt();
|
||||||
|
|
||||||
|
if (!label.equals("Person") || !name.equals("Alice") || age != 22) {
|
||||||
|
System.out.println("Data doesn't match!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Label: " + label);
|
||||||
|
System.out.println("name: " + name);
|
||||||
|
System.out.println("age: " + age);
|
||||||
|
|
||||||
|
System.out.println("All ok!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Determines how long could be a query executed
|
||||||
|
* from Java driver.
|
||||||
|
*
|
||||||
|
* Performs binary search until the maximum possible
|
||||||
|
* query size has found.
|
||||||
|
*/
|
||||||
|
package memgraph;
|
||||||
|
|
||||||
|
import static org.neo4j.driver.Values.parameters;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import org.neo4j.driver.*;
|
||||||
|
import org.neo4j.driver.types.*;
|
||||||
|
|
||||||
|
public class MaxQueryLength {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// init driver
|
||||||
|
Config config = Config.builder().withoutEncryption().build();
|
||||||
|
Driver driver = GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("", ""), config);
|
||||||
|
// init query
|
||||||
|
int property_size = 0;
|
||||||
|
int min_len = 1;
|
||||||
|
int max_len = 100000;
|
||||||
|
String query_template = "CREATE (n {name:\"%s\"})";
|
||||||
|
int template_size = query_template.length() - 2; // because of %s
|
||||||
|
|
||||||
|
// binary search
|
||||||
|
while (true) {
|
||||||
|
property_size = (max_len + min_len) / 2;
|
||||||
|
try (Session session = driver.session()) {
|
||||||
|
String property_value = new String(new char[property_size]).replace('\0', 'a');
|
||||||
|
String query = String.format(query_template, property_value);
|
||||||
|
session.run(query).consume();
|
||||||
|
if (min_len == max_len || property_size + 1 > max_len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
min_len = property_size + 1;
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(
|
||||||
|
String.format("Query length: %d; Error: %s", property_size + template_size, e));
|
||||||
|
max_len = property_size - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// final result
|
||||||
|
System.out.println(String.format("\nThe max length of a query executed from "
|
||||||
|
+ "Java driver is: %s\n",
|
||||||
|
property_size + template_size));
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
driver.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package memgraph;
|
||||||
|
|
||||||
|
import static org.neo4j.driver.Values.parameters;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.neo4j.driver.AuthTokens;
|
||||||
|
import org.neo4j.driver.Config;
|
||||||
|
import org.neo4j.driver.Driver;
|
||||||
|
import org.neo4j.driver.GraphDatabase;
|
||||||
|
import org.neo4j.driver.Result;
|
||||||
|
import org.neo4j.driver.Session;
|
||||||
|
import org.neo4j.driver.Transaction;
|
||||||
|
import org.neo4j.driver.TransactionWork;
|
||||||
|
import org.neo4j.driver.exceptions.ClientException;
|
||||||
|
import org.neo4j.driver.exceptions.TransientException;
|
||||||
|
|
||||||
|
public class Transactions {
|
||||||
|
public static String createPerson(Transaction tx, String name) {
|
||||||
|
Result result =
|
||||||
|
tx.run("CREATE (a:Person {name: $name}) RETURN a.name", parameters("name", name));
|
||||||
|
return result.single().get(0).asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Config config = Config.builder()
|
||||||
|
.withoutEncryption()
|
||||||
|
.withMaxTransactionRetryTime(0, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
Driver driver =
|
||||||
|
GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "1234"), config);
|
||||||
|
|
||||||
|
try (Session session = driver.session()) {
|
||||||
|
try {
|
||||||
|
session.writeTransaction(new TransactionWork<String>() {
|
||||||
|
@Override
|
||||||
|
public String execute(Transaction tx) {
|
||||||
|
createPerson(tx, "mirko");
|
||||||
|
Result result = tx.run("CREATE (");
|
||||||
|
return result.single().get(0).asString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ClientException e) {
|
||||||
|
System.out.println(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.writeTransaction(new TransactionWork<String>() {
|
||||||
|
@Override
|
||||||
|
public String execute(Transaction tx) {
|
||||||
|
System.out.println(createPerson(tx, "mirko"));
|
||||||
|
System.out.println(createPerson(tx, "slavko"));
|
||||||
|
return "Done";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("All ok!");
|
||||||
|
|
||||||
|
boolean timed_out = false;
|
||||||
|
try {
|
||||||
|
session.writeTransaction(new TransactionWork<String>() {
|
||||||
|
@Override
|
||||||
|
public String execute(Transaction tx) {
|
||||||
|
Result result = tx.run("MATCH (a), (b), (c), (d), (e), (f) RETURN COUNT(*) AS cnt");
|
||||||
|
return result.single().get(0).asString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (TransientException e) {
|
||||||
|
timed_out = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timed_out) {
|
||||||
|
System.out.println("The query timed out as was expected.");
|
||||||
|
} else {
|
||||||
|
throw new Exception("The query should have timed out, but it didn't!");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.close();
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ fi
|
|||||||
|
|
||||||
if [ ! -d node_modules ]; then
|
if [ ! -d node_modules ]; then
|
||||||
# Driver generated with: `npm install neo4j-driver`
|
# Driver generated with: `npm install neo4j-driver`
|
||||||
npm install --no-package-lock --no-save neo4j-driver @babel/runtime
|
npm install --no-package-lock --no-save neo4j-driver@4.1.1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
node docs_how_to_query.js
|
node docs_how_to_query.js
|
||||||
|
42
tests/drivers/node/v5_8/docs_how_to_query.js
Normal file
42
tests/drivers/node/v5_8/docs_how_to_query.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
var neo4j = require('neo4j-driver');
|
||||||
|
var driver = neo4j.driver("bolt://localhost:7687",
|
||||||
|
neo4j.auth.basic("", ""),
|
||||||
|
{ encrypted: 'ENCRYPTION_OFF' });
|
||||||
|
var session = driver.session();
|
||||||
|
|
||||||
|
function die() {
|
||||||
|
session.close();
|
||||||
|
driver.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_query(query, callback) {
|
||||||
|
var run = session.run(query, {});
|
||||||
|
run.then(callback).catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
die();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run_query("MATCH (n) DETACH DELETE n;", function (result) {
|
||||||
|
console.log("Database cleared.");
|
||||||
|
run_query("CREATE (alice:Person {name: 'Alice', age: 22});", function (result) {
|
||||||
|
console.log("Record created.");
|
||||||
|
run_query("MATCH (n) RETURN n", function (result) {
|
||||||
|
console.log("Record matched.");
|
||||||
|
const alice = result.records[0].get("n");
|
||||||
|
const label = alice.labels[0];
|
||||||
|
const name = alice.properties["name"];
|
||||||
|
const age = alice.properties["age"];
|
||||||
|
if(label != "Person" || name != "Alice" || age != 22){
|
||||||
|
console.log("Data doesn't match!");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
console.log("Label: " + label);
|
||||||
|
console.log("name: " + name);
|
||||||
|
console.log("age: " + age);
|
||||||
|
console.log("All ok!");
|
||||||
|
driver.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
51
tests/drivers/node/v5_8/max_query_length.js
Normal file
51
tests/drivers/node/v5_8/max_query_length.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Determines how long could be a query executed
|
||||||
|
// from JavaScript driver.
|
||||||
|
//
|
||||||
|
// Performs binary search until the maximum possible
|
||||||
|
// query size has found.
|
||||||
|
|
||||||
|
// init driver
|
||||||
|
var neo4j = require('neo4j-driver');
|
||||||
|
var driver = neo4j.driver("bolt://localhost:7687",
|
||||||
|
neo4j.auth.basic("", ""),
|
||||||
|
{ encrypted: 'ENCRYPTION_OFF' });
|
||||||
|
|
||||||
|
// init state
|
||||||
|
var property_size = 0;
|
||||||
|
var min_len = 1;
|
||||||
|
var max_len = 1000000;
|
||||||
|
|
||||||
|
// hacking with JS and callbacks concept
|
||||||
|
function serial_execution() {
|
||||||
|
var next_size = [Math.floor((min_len + max_len) / 2)];
|
||||||
|
setInterval(function() {
|
||||||
|
if (next_size.length > 0) {
|
||||||
|
property_size = next_size.pop();
|
||||||
|
var query = "CREATE (n {name:\"" +
|
||||||
|
(new Array(property_size)).join("a")+ "\"})";
|
||||||
|
var session = driver.session();
|
||||||
|
session.run(query, {}).then(function (result) {
|
||||||
|
console.log("Success with the query length " + query.length);
|
||||||
|
if (min_len == max_len || property_size + 1 > max_len) {
|
||||||
|
console.log("\nThe max length of a query from JS driver is: " +
|
||||||
|
query.length + "\n");
|
||||||
|
session.close();
|
||||||
|
driver.close();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
min_len = property_size + 1;
|
||||||
|
next_size.push(Math.floor((min_len + max_len) / 2));
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.log("Failure with the query length " + query.length);
|
||||||
|
max_len = property_size - 1;
|
||||||
|
next_size.push(Math.floor((min_len + max_len) / 2));
|
||||||
|
}).then(function(){
|
||||||
|
session.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// execution
|
||||||
|
console.log("\nDetermine how long can be a query sent from JavaScript driver.");
|
||||||
|
serial_execution(); // I don't like JavaScript
|
17
tests/drivers/node/v5_8/run.sh
Executable file
17
tests/drivers/node/v5_8/run.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
if ! which node >/dev/null; then
|
||||||
|
echo "Please install nodejs!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d node_modules ]; then
|
||||||
|
# Driver generated with: `npm install neo4j-driver`
|
||||||
|
npm install --no-package-lock --no-save neo4j-driver@5.8.0
|
||||||
|
fi
|
||||||
|
|
||||||
|
node docs_how_to_query.js
|
||||||
|
node max_query_length.js
|
46
tests/drivers/python/v5_8/docs_how_to_query.py
Normal file
46
tests/drivers/python/v5_8/docs_how_to_query.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2021 Memgraph Ltd.
|
||||||
|
#
|
||||||
|
# Use of this software is governed by the Business Source License
|
||||||
|
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
# License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
#
|
||||||
|
# As of the Change Date specified in that file, in accordance with
|
||||||
|
# the Business Source License, use of this software will be governed
|
||||||
|
# by the Apache License, Version 2.0, included in the file
|
||||||
|
# licenses/APL.txt.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from neo4j import GraphDatabase, basic_auth
|
||||||
|
|
||||||
|
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("", ""), encrypted=False)
|
||||||
|
session = driver.session()
|
||||||
|
|
||||||
|
session.run("MATCH (n) DETACH DELETE n").consume()
|
||||||
|
print("Database cleared.")
|
||||||
|
|
||||||
|
session.run('CREATE (alice:Person {name: "Alice", age: 22})').consume()
|
||||||
|
print("Record created.")
|
||||||
|
|
||||||
|
node = session.run("MATCH (n) RETURN n").single()["n"]
|
||||||
|
print("Record matched.")
|
||||||
|
|
||||||
|
label = list(node.labels)[0]
|
||||||
|
name = node["name"]
|
||||||
|
age = node["age"]
|
||||||
|
|
||||||
|
if label != "Person" or name != "Alice" or age != 22:
|
||||||
|
print("Data does not match")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("Label: %s" % label)
|
||||||
|
print("name: %s" % name)
|
||||||
|
print("age: %s" % age)
|
||||||
|
|
||||||
|
session.close()
|
||||||
|
driver.close()
|
||||||
|
|
||||||
|
print("All ok!")
|
48
tests/drivers/python/v5_8/max_query_length.py
Normal file
48
tests/drivers/python/v5_8/max_query_length.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2021 Memgraph Ltd.
|
||||||
|
#
|
||||||
|
# Use of this software is governed by the Business Source License
|
||||||
|
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
# License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
#
|
||||||
|
# As of the Change Date specified in that file, in accordance with
|
||||||
|
# the Business Source License, use of this software will be governed
|
||||||
|
# by the Apache License, Version 2.0, included in the file
|
||||||
|
# licenses/APL.txt.
|
||||||
|
|
||||||
|
from neo4j import GraphDatabase, basic_auth
|
||||||
|
|
||||||
|
driver = GraphDatabase.driver("bolt://localhost:7687", auth=None, encrypted=False)
|
||||||
|
|
||||||
|
query_template = 'CREATE (n {name:"%s"})'
|
||||||
|
template_size = len(query_template) - 2 # because of %s
|
||||||
|
min_len = 1
|
||||||
|
max_len = 1000000
|
||||||
|
|
||||||
|
# binary search because we have to find the maximum size (in number of chars)
|
||||||
|
# of a query that can be executed via driver
|
||||||
|
while True:
|
||||||
|
assert min_len > 0 and max_len > 0, (
|
||||||
|
"The lengths have to be positive values! If this happens something"
|
||||||
|
" is terrible wrong with min & max lengths OR the database"
|
||||||
|
" isn't available."
|
||||||
|
)
|
||||||
|
property_size = (max_len + min_len) // 2
|
||||||
|
try:
|
||||||
|
driver.session().run(query_template % ("a" * property_size)).consume()
|
||||||
|
if min_len == max_len or property_size + 1 > max_len:
|
||||||
|
break
|
||||||
|
min_len = property_size + 1
|
||||||
|
except Exception as e:
|
||||||
|
print("Query size %s is too big!" % (template_size + property_size))
|
||||||
|
max_len = property_size - 1
|
||||||
|
|
||||||
|
assert property_size == max_len, "max_len probably has to be increased!"
|
||||||
|
|
||||||
|
print("\nThe max length of a query from Python driver is: %s\n" % (template_size + property_size))
|
||||||
|
|
||||||
|
# sessions are not closed bacause all sessions that are
|
||||||
|
# executed with wrong query size might be broken
|
||||||
|
driver.close()
|
26
tests/drivers/python/v5_8/run.sh
Executable file
26
tests/drivers/python/v5_8/run.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
# system check
|
||||||
|
if ! which virtualenv >/dev/null; then
|
||||||
|
echo "Please install virtualenv!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# setup virtual environment
|
||||||
|
if [ ! -d "ve3" ]; then
|
||||||
|
virtualenv -p python3 ve3 || exit 1
|
||||||
|
source ve3/bin/activate
|
||||||
|
python3 -m pip install neo4j==5.8.0 || exit 1
|
||||||
|
deactivate
|
||||||
|
fi
|
||||||
|
|
||||||
|
# activate virtualenv
|
||||||
|
source ve3/bin/activate
|
||||||
|
|
||||||
|
# execute test
|
||||||
|
python3 docs_how_to_query.py || exit 1
|
||||||
|
python3 max_query_length.py || exit 1
|
||||||
|
python3 transactions.py || exit 1
|
69
tests/drivers/python/v5_8/transactions.py
Normal file
69
tests/drivers/python/v5_8/transactions.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2021 Memgraph Ltd.
|
||||||
|
#
|
||||||
|
# Use of this software is governed by the Business Source License
|
||||||
|
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
# License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
#
|
||||||
|
# As of the Change Date specified in that file, in accordance with
|
||||||
|
# the Business Source License, use of this software will be governed
|
||||||
|
# by the Apache License, Version 2.0, included in the file
|
||||||
|
# licenses/APL.txt.
|
||||||
|
|
||||||
|
from neo4j import GraphDatabase, basic_auth
|
||||||
|
from neo4j.exceptions import ClientError, TransientError
|
||||||
|
|
||||||
|
|
||||||
|
def tx_error(tx, name, name2):
|
||||||
|
a = tx.run("CREATE (a:Person {name: $name}) RETURN a", name=name).value()
|
||||||
|
print(a[0])
|
||||||
|
tx.run("CREATE (").consume()
|
||||||
|
a = tx.run("CREATE (a:Person {name: $name}) RETURN a", name=name2).value()
|
||||||
|
print(a[0])
|
||||||
|
|
||||||
|
|
||||||
|
def tx_good(tx, name, name2):
|
||||||
|
a = tx.run("CREATE (a:Person {name: $name}) RETURN a", name=name).value()
|
||||||
|
print(a[0])
|
||||||
|
a = tx.run("CREATE (a:Person {name: $name}) RETURN a", name=name2).value()
|
||||||
|
print(a[0])
|
||||||
|
|
||||||
|
|
||||||
|
def tx_too_long(tx):
|
||||||
|
tx.run("MATCH (a), (b), (c), (d), (e), (f) RETURN COUNT(*) AS cnt")
|
||||||
|
|
||||||
|
|
||||||
|
with GraphDatabase.driver("bolt://localhost:7687", auth=None, encrypted=False) as driver:
|
||||||
|
|
||||||
|
def add_person(f, name, name2):
|
||||||
|
with driver.session() as session:
|
||||||
|
session.write_transaction(f, name, name2)
|
||||||
|
|
||||||
|
# Wrong query.
|
||||||
|
try:
|
||||||
|
add_person(tx_error, "mirko", "slavko")
|
||||||
|
except ClientError:
|
||||||
|
pass
|
||||||
|
# Correct query.
|
||||||
|
add_person(tx_good, "mirka", "slavka")
|
||||||
|
|
||||||
|
# Setup for next query.
|
||||||
|
with driver.session() as session:
|
||||||
|
session.run("UNWIND range(1, 100000) AS x CREATE ()").consume()
|
||||||
|
|
||||||
|
# Query that will run for a very long time, transient error expected.
|
||||||
|
timed_out = False
|
||||||
|
try:
|
||||||
|
with driver.session() as session:
|
||||||
|
session.run("MATCH (a), (b), (c), (d), (e), (f) RETURN COUNT(*) AS cnt").consume()
|
||||||
|
except TransientError:
|
||||||
|
timed_out = True
|
||||||
|
|
||||||
|
if timed_out:
|
||||||
|
print("The query timed out as was expected.")
|
||||||
|
else:
|
||||||
|
raise Exception("The query should have timed out, but it didn't!")
|
||||||
|
|
||||||
|
print("All ok!")
|
@ -372,15 +372,15 @@ TEST(BoltSession, HandshakeWithVersionOffset) {
|
|||||||
ASSERT_EQ(session.version_.minor, 3);
|
ASSERT_EQ(session.version_.minor, 3);
|
||||||
ASSERT_EQ(session.version_.major, 4);
|
ASSERT_EQ(session.version_.major, 4);
|
||||||
}
|
}
|
||||||
// With multiple offsets
|
// With multiple offsets (added v5.2)
|
||||||
{
|
{
|
||||||
INIT_VARS;
|
INIT_VARS;
|
||||||
const uint8_t priority_request[] = {0x60, 0x60, 0xb0, 0x17, 0x00, 0x03, 0x03, 0x07, 0x00, 0x03,
|
const uint8_t priority_request[] = {0x60, 0x60, 0xb0, 0x17, 0x00, 0x03, 0x03, 0x07, 0x00, 0x03,
|
||||||
0x03, 0x06, 0x00, 0x03, 0x03, 0x05, 0x00, 0x03, 0x03, 0x04};
|
0x03, 0x06, 0x00, 0x03, 0x03, 0x05, 0x00, 0x03, 0x03, 0x04};
|
||||||
const uint8_t priority_response[] = {0x00, 0x00, 0x03, 0x04};
|
const uint8_t priority_response[] = {0x00, 0x00, 0x02, 0x05};
|
||||||
ExecuteHandshake(input_stream, session, output, priority_request, priority_response);
|
ExecuteHandshake(input_stream, session, output, priority_request, priority_response);
|
||||||
ASSERT_EQ(session.version_.minor, 3);
|
ASSERT_EQ(session.version_.minor, 2);
|
||||||
ASSERT_EQ(session.version_.major, 4);
|
ASSERT_EQ(session.version_.major, 5);
|
||||||
}
|
}
|
||||||
// Offset overflows
|
// Offset overflows
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user