Add Bolt v5 support (#938)

This commit is contained in:
andrejtonev 2023-06-12 18:55:15 +02:00 committed by GitHub
parent d917c3f0fd
commit 30ec570bb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1176 additions and 34 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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;
} }

View File

@ -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>

View File

@ -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.
* *

View File

@ -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.
* *

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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 << ")";
} }

View File

@ -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;
}; };
/** /**

View File

@ -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,

View 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>

View 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!");
}
}

View 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"]);
}
}
}

View 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>

View 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;

View 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()
}

View 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
)

View 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
View 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
View File

@ -0,0 +1 @@
target/

View 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
View 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

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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

View 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();
});
});
});

View 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
View 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

View 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!")

View 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()

View 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

View 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!")

View File

@ -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
{ {