diff --git a/src/communication/bolt/v1/codes.hpp b/src/communication/bolt/v1/codes.hpp index 91c30f831..4e37e94aa 100644 --- a/src/communication/bolt/v1/codes.hpp +++ b/src/communication/bolt/v1/codes.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -20,6 +20,8 @@ inline constexpr uint8_t kPreamble[4] = {0x60, 0x60, 0xB0, 0x17}; enum class Signature : uint8_t { Noop = 0x00, Init = 0x01, + LogOn = 0x6A, + LogOff = 0x6B, AckFailure = 0x0E, // only v1 Reset = 0x0F, Goodbye = 0x02, diff --git a/src/communication/bolt/v1/constants.hpp b/src/communication/bolt/v1/constants.hpp index 365fa0d57..44d9f4b7a 100644 --- a/src/communication/bolt/v1/constants.hpp +++ b/src/communication/bolt/v1/constants.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -28,7 +28,7 @@ inline constexpr size_t kChunkWholeSize = kChunkHeaderSize + kChunkMaxDataSize; */ 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 kPullLast = -1; diff --git a/src/communication/bolt/v1/decoder/decoder.hpp b/src/communication/bolt/v1/decoder/decoder.hpp index 7c157d187..593f514f5 100644 --- a/src/communication/bolt/v1/decoder/decoder.hpp +++ b/src/communication/bolt/v1/decoder/decoder.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -33,7 +33,14 @@ namespace memgraph::communication::bolt { template class Decoder { 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. @@ -208,6 +215,10 @@ class Decoder { protected: 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: bool ReadNull(const Marker &marker, Value *data) { @@ -370,11 +381,7 @@ class Decoder { } ret.emplace(std::move(dv_key.ValueString()), std::move(dv_val)); } - if (ret.size() != size) { - return false; - } - - return true; + return ret.size() == size; } bool ReadVertex(Value *data) { @@ -407,6 +414,14 @@ class Decoder { } 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; } @@ -445,6 +460,23 @@ class Decoder { } 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; } @@ -471,6 +503,14 @@ class Decoder { } 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; } diff --git a/src/communication/bolt/v1/encoder/base_encoder.hpp b/src/communication/bolt/v1/encoder/base_encoder.hpp index 064cd0f95..ee34ec085 100644 --- a/src/communication/bolt/v1/encoder/base_encoder.hpp +++ b/src/communication/bolt/v1/encoder/base_encoder.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -36,7 +36,14 @@ namespace memgraph::communication::bolt { template class BaseEncoder { 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); } @@ -116,7 +123,8 @@ class BaseEncoder { } 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)); WriteInt(vertex.id.AsInt()); @@ -132,10 +140,16 @@ class BaseEncoder { WriteString(prop.first); 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) { - 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)); WriteInt(edge.id.AsInt()); @@ -152,10 +166,22 @@ class BaseEncoder { WriteString(prop.first); 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) { - 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)); WriteInt(edge.id.AsInt()); @@ -168,6 +194,11 @@ class BaseEncoder { WriteString(prop.first); WriteValue(prop.second); } + + if (major_v_ > 4) { + // element_id introduced in v5.0 + WriteString(edge.element_id); + } } void WritePath(const Path &path) { @@ -264,6 +295,7 @@ class BaseEncoder { protected: Buffer &buffer_; + int major_v_; //!< Major version of the underlying Bolt protocol (TODO: Think about reimplementing the versioning) private: template diff --git a/src/communication/bolt/v1/encoder/client_encoder.hpp b/src/communication/bolt/v1/encoder/client_encoder.hpp index 3ccf0c910..49f3f0c16 100644 --- a/src/communication/bolt/v1/encoder/client_encoder.hpp +++ b/src/communication/bolt/v1/encoder/client_encoder.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -41,6 +41,8 @@ class ClientEncoder : private BaseEncoder { public: ClientEncoder(Buffer &buffer) : BaseEncoder(buffer) {} + using BaseEncoder::UpdateVersion; + /** * Writes a Init message. * diff --git a/src/communication/bolt/v1/encoder/encoder.hpp b/src/communication/bolt/v1/encoder/encoder.hpp index 6ec84051a..bc3203d90 100644 --- a/src/communication/bolt/v1/encoder/encoder.hpp +++ b/src/communication/bolt/v1/encoder/encoder.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -34,6 +34,8 @@ class Encoder : private BaseEncoder { public: Encoder(Buffer &buffer) : BaseEncoder(buffer) {} + using BaseEncoder::UpdateVersion; + /** * Sends a Record message. * diff --git a/src/communication/bolt/v1/session.hpp b/src/communication/bolt/v1/session.hpp index da94ad118..cf7717fe3 100644 --- a/src/communication/bolt/v1/session.hpp +++ b/src/communication/bolt/v1/session.hpp @@ -121,6 +121,9 @@ class Session { return; } 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; diff --git a/src/communication/bolt/v1/states/executing.hpp b/src/communication/bolt/v1/states/executing.hpp index ddc460bda..518e976c9 100644 --- a/src/communication/bolt/v1/states/executing.hpp +++ b/src/communication/bolt/v1/states/executing.hpp @@ -91,6 +91,37 @@ State RunHandlerV4(Signature signature, TSession &session, State state, Marker m } } +template +State RunHandlerV5(Signature signature, TSession &session, State state, Marker marker) { + switch (signature) { + case Signature::Run: + return HandleRunV5(session, state, marker); + case Signature::Pull: + return HandlePullV5(session, state, marker); + case Signature::Discard: + return HandleDiscardV5(session, state, marker); + case Signature::Reset: + return HandleReset(session, marker); + case Signature::Begin: + return HandleBegin(session, state, marker); + case Signature::Commit: + return HandleCommit(session, state, marker); + case Signature::Goodbye: + return HandleGoodbye(); + case Signature::Rollback: + return HandleRollback(session, state, marker); + case Signature::Noop: + return HandleNoop(state); + case Signature::Route: + return HandleRoute(session, marker); + case Signature::LogOff: + return HandleLogOff(); + default: + spdlog::trace("Unrecognized signature received (0x{:02X})!", utils::UnderlyingCast(signature)); + return State::Close; + } +} + /** * Executor state run function * This function executes an initialized Bolt session. @@ -120,6 +151,8 @@ State StateExecutingRun(TSession &session, State state) { } return RunHandlerV4(signature, session, state, marker); } + case 5: + return RunHandlerV5(signature, session, state, marker); default: spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor); return State::Close; diff --git a/src/communication/bolt/v1/states/handlers.hpp b/src/communication/bolt/v1/states/handlers.hpp index ffd998f19..03524a490 100644 --- a/src/communication/bolt/v1/states/handlers.hpp +++ b/src/communication/bolt/v1/states/handlers.hpp @@ -309,6 +309,12 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) { } } +template +State HandleRunV5(TSession &session, const State state, const Marker marker) { + // Using V4 on purpose + return HandleRunV4(session, state, marker); +} + template State HandlePullV1(TSession &session, const State state, const Marker marker) { return details::HandlePullDiscardV1(session, state, marker); @@ -319,6 +325,12 @@ State HandlePullV4(TSession &session, const State state, const Marker marker) { return details::HandlePullDiscardV4(session, state, marker); } +template +State HandlePullV5(TSession &session, const State state, const Marker marker) { + // Using V4 on purpose + return HandlePullV4(session, state, marker); +} + template State HandleDiscardV1(TSession &session, const State state, const Marker marker) { return details::HandlePullDiscardV1(session, state, marker); @@ -329,6 +341,12 @@ State HandleDiscardV4(TSession &session, const State state, const Marker marker) return details::HandlePullDiscardV4(session, state, marker); } +template +State HandleDiscardV5(TSession &session, const State state, const Marker marker) { + // Using V4 on purpose + return HandleDiscardV4(session, state, marker); +} + template State HandleReset(TSession &session, const Marker marker) { // 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; } + +template +State HandleLogOff() { + // Not arguments sent, the user just needs to reauthenticate + return State::Init; +} } // namespace memgraph::communication::bolt diff --git a/src/communication/bolt/v1/states/init.hpp b/src/communication/bolt/v1/states/init.hpp index adab15d82..3e77b632c 100644 --- a/src/communication/bolt/v1/states/init.hpp +++ b/src/communication/bolt/v1/states/init.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -27,17 +27,25 @@ namespace details { template std::optional AuthenticateUser(TSession &session, Value &metadata) { // 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(); - if (!data.count("scheme")) { - spdlog::warn("The client didn't supply authentication information!"); - return State::Close; + if (data.empty()) { // Special case auth=None + spdlog::warn("The client didn't supply the authentication scheme! Trying with \"none\"..."); + data["scheme"] = "none"; } + std::string username; std::string password; if (data["scheme"].ValueString() == "basic") { - if (!data.count("principal") || !data.count("credentials")) { - spdlog::warn("The client didn't supply authentication information!"); - return State::Close; + if (!data.count("principal")) { // Special case principal = "" + spdlog::warn("The client didn't supply the principal field! Trying with \"\"..."); + 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(); password = data["credentials"].ValueString(); @@ -106,6 +114,30 @@ std::optional GetMetadataV4(TSession &session, const Marker marker) { 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 +std::optional 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(); if (!data.count("user_agent")) { spdlog::warn("The client didn't supply the user agent!"); @@ -117,6 +149,22 @@ std::optional GetMetadataV4(TSession &session, const Marker marker) { return metadata; } +template +std::optional 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 State SendSuccessMessage(TSession &session) { // 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); } + +template +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 /** @@ -208,6 +307,9 @@ State StateInitRun(TSession &session) { } return details::StateInitRunV4(session, marker, signature); } + case 5: { + return details::StateInitRunV5(session, marker, signature); + } } spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor); return State::Close; diff --git a/src/communication/bolt/v1/value.cpp b/src/communication/bolt/v1/value.cpp index a370f78d5..92abdf87d 100644 --- a/src/communication/bolt/v1/value.cpp +++ b/src/communication/bolt/v1/value.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -342,6 +342,9 @@ std::ostream &operator<<(std::ostream &os, const Vertex &vertex) { [&](auto &stream, const auto &pair) { stream << pair.first << ": " << pair.second; }); os << "}"; } + if (!vertex.element_id.empty()) { + os << " element_id: " << vertex.element_id; + } return os << ")"; } diff --git a/src/communication/bolt/v1/value.hpp b/src/communication/bolt/v1/value.hpp index 442e8060d..0ac5dfabf 100644 --- a/src/communication/bolt/v1/value.hpp +++ b/src/communication/bolt/v1/value.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -57,6 +57,7 @@ struct Vertex { Id id; std::vector labels; std::map properties; + std::string element_id; }; /** @@ -69,6 +70,9 @@ struct Edge { Id to; std::string type; std::map properties; + std::string element_id; + std::string from_element_id; + std::string to_element_id; }; /** @@ -79,6 +83,7 @@ struct UnboundedEdge { Id id; std::string type; std::map properties; + std::string element_id; }; /** diff --git a/src/glue/communication.cpp b/src/glue/communication.cpp index c8f7c1879..f1a785888 100644 --- a/src/glue/communication.cpp +++ b/src/glue/communication.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 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 @@ -150,7 +150,9 @@ storage::Result ToBoltVertex(const storage::VertexA for (const auto &prop : *maybe_properties) { 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 ToBoltEdge(const storage::EdgeAccessor &edge, const storage::Storage &db, @@ -165,7 +167,11 @@ storage::Result ToBoltEdge(const storage::EdgeAccesso for (const auto &prop : *maybe_properties) { 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 ToBoltPath(const query::Path &path, const storage::Storage &db, diff --git a/tests/drivers/csharp/v5_8/DocsHowToQuery/DocsHowToQuery.csproj b/tests/drivers/csharp/v5_8/DocsHowToQuery/DocsHowToQuery.csproj new file mode 100644 index 000000000..f5bdfa763 --- /dev/null +++ b/tests/drivers/csharp/v5_8/DocsHowToQuery/DocsHowToQuery.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.1 + + + + + + + diff --git a/tests/drivers/csharp/v5_8/DocsHowToQuery/Program.cs b/tests/drivers/csharp/v5_8/DocsHowToQuery/Program.cs new file mode 100644 index 000000000..95289f22c --- /dev/null +++ b/tests/drivers/csharp/v5_8/DocsHowToQuery/Program.cs @@ -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!"); + } +} diff --git a/tests/drivers/csharp/v5_8/Transactions/Program.cs b/tests/drivers/csharp/v5_8/Transactions/Program.cs new file mode 100644 index 000000000..9c2f5f414 --- /dev/null +++ b/tests/drivers/csharp/v5_8/Transactions/Program.cs @@ -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 { { "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"]); + } + } +} diff --git a/tests/drivers/csharp/v5_8/Transactions/Transactions.csproj b/tests/drivers/csharp/v5_8/Transactions/Transactions.csproj new file mode 100644 index 000000000..f5bdfa763 --- /dev/null +++ b/tests/drivers/csharp/v5_8/Transactions/Transactions.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.1 + + + + + + + diff --git a/tests/drivers/csharp/v5_8/run.sh b/tests/drivers/csharp/v5_8/run.sh new file mode 100755 index 000000000..1d8fa6f76 --- /dev/null +++ b/tests/drivers/csharp/v5_8/run.sh @@ -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; diff --git a/tests/drivers/go/v5/docs_quick_start.go b/tests/drivers/go/v5/docs_quick_start.go new file mode 100644 index 000000000..69805acc1 --- /dev/null +++ b/tests/drivers/go/v5/docs_quick_start.go @@ -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() +} + diff --git a/tests/drivers/go/v5/go.mod b/tests/drivers/go/v5/go.mod new file mode 100644 index 000000000..ed02d3b35 --- /dev/null +++ b/tests/drivers/go/v5/go.mod @@ -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 +) diff --git a/tests/drivers/go/v5/go.sum b/tests/drivers/go/v5/go.sum new file mode 100644 index 000000000..29a1ab00e --- /dev/null +++ b/tests/drivers/go/v5/go.sum @@ -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= diff --git a/tests/drivers/go/v5/run.sh b/tests/drivers/go/v5/run.sh new file mode 100755 index 000000000..344495f15 --- /dev/null +++ b/tests/drivers/go/v5/run.sh @@ -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 diff --git a/tests/drivers/java/v5_8/.gitignore b/tests/drivers/java/v5_8/.gitignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/tests/drivers/java/v5_8/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/tests/drivers/java/v5_8/pom.xml b/tests/drivers/java/v5_8/pom.xml new file mode 100644 index 000000000..c0a64e8c0 --- /dev/null +++ b/tests/drivers/java/v5_8/pom.xml @@ -0,0 +1,93 @@ + + 4.0.0 + + memgraph + java-driver-tests + 1.0 + + + 17 + 17 + + + + + org.neo4j.driver + neo4j-java-driver + 5.8.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-assembly-plugin + + + build-a + + + + memgraph.DocsHowToQuery + + + + jar-with-dependencies + + false + DocsHowToQuery + + package + + single + + + + build-b + + + + memgraph.MaxQueryLength + + + + jar-with-dependencies + + false + MaxQueryLength + + package + + single + + + + build-c + + + + memgraph.Transactions + + + + jar-with-dependencies + + false + Transactions + + package + + single + + + + + + + + diff --git a/tests/drivers/java/v5_8/run.sh b/tests/drivers/java/v5_8/run.sh new file mode 100755 index 000000000..6ae33ec4d --- /dev/null +++ b/tests/drivers/java/v5_8/run.sh @@ -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 diff --git a/tests/drivers/java/v5_8/src/main/java/memgraph/DocsHowToQuery.java b/tests/drivers/java/v5_8/src/main/java/memgraph/DocsHowToQuery.java new file mode 100644 index 000000000..27e6aaa47 --- /dev/null +++ b/tests/drivers/java/v5_8/src/main/java/memgraph/DocsHowToQuery.java @@ -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(); + } +} diff --git a/tests/drivers/java/v5_8/src/main/java/memgraph/MaxQueryLength.java b/tests/drivers/java/v5_8/src/main/java/memgraph/MaxQueryLength.java new file mode 100644 index 000000000..40d563ac9 --- /dev/null +++ b/tests/drivers/java/v5_8/src/main/java/memgraph/MaxQueryLength.java @@ -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(); + } +} diff --git a/tests/drivers/java/v5_8/src/main/java/memgraph/Transactions.java b/tests/drivers/java/v5_8/src/main/java/memgraph/Transactions.java new file mode 100644 index 000000000..430eccafb --- /dev/null +++ b/tests/drivers/java/v5_8/src/main/java/memgraph/Transactions.java @@ -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() { + @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() { + @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() { + @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(); + } +} diff --git a/tests/drivers/node/v4_1/run.sh b/tests/drivers/node/v4_1/run.sh index 8f9083434..2649527ab 100755 --- a/tests/drivers/node/v4_1/run.sh +++ b/tests/drivers/node/v4_1/run.sh @@ -10,7 +10,7 @@ fi if [ ! -d node_modules ]; then # 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 node docs_how_to_query.js diff --git a/tests/drivers/node/v5_8/docs_how_to_query.js b/tests/drivers/node/v5_8/docs_how_to_query.js new file mode 100644 index 000000000..0e9f5f7e7 --- /dev/null +++ b/tests/drivers/node/v5_8/docs_how_to_query.js @@ -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(); + }); + }); +}); diff --git a/tests/drivers/node/v5_8/max_query_length.js b/tests/drivers/node/v5_8/max_query_length.js new file mode 100644 index 000000000..bd923c226 --- /dev/null +++ b/tests/drivers/node/v5_8/max_query_length.js @@ -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 diff --git a/tests/drivers/node/v5_8/run.sh b/tests/drivers/node/v5_8/run.sh new file mode 100755 index 000000000..a24c5110c --- /dev/null +++ b/tests/drivers/node/v5_8/run.sh @@ -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 diff --git a/tests/drivers/python/v5_8/docs_how_to_query.py b/tests/drivers/python/v5_8/docs_how_to_query.py new file mode 100644 index 000000000..1701ccbde --- /dev/null +++ b/tests/drivers/python/v5_8/docs_how_to_query.py @@ -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!") diff --git a/tests/drivers/python/v5_8/max_query_length.py b/tests/drivers/python/v5_8/max_query_length.py new file mode 100644 index 000000000..41ee532be --- /dev/null +++ b/tests/drivers/python/v5_8/max_query_length.py @@ -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() diff --git a/tests/drivers/python/v5_8/run.sh b/tests/drivers/python/v5_8/run.sh new file mode 100755 index 000000000..48ac619bd --- /dev/null +++ b/tests/drivers/python/v5_8/run.sh @@ -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 diff --git a/tests/drivers/python/v5_8/transactions.py b/tests/drivers/python/v5_8/transactions.py new file mode 100644 index 000000000..73f1d9d75 --- /dev/null +++ b/tests/drivers/python/v5_8/transactions.py @@ -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!") diff --git a/tests/unit/bolt_session.cpp b/tests/unit/bolt_session.cpp index 0c1d7fdfc..2ab896296 100644 --- a/tests/unit/bolt_session.cpp +++ b/tests/unit/bolt_session.cpp @@ -372,15 +372,15 @@ TEST(BoltSession, HandshakeWithVersionOffset) { ASSERT_EQ(session.version_.minor, 3); ASSERT_EQ(session.version_.major, 4); } - // With multiple offsets + // With multiple offsets (added v5.2) { INIT_VARS; 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}; - 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); - ASSERT_EQ(session.version_.minor, 3); - ASSERT_EQ(session.version_.major, 4); + ASSERT_EQ(session.version_.minor, 2); + ASSERT_EQ(session.version_.major, 5); } // Offset overflows {