diff --git a/src/communication/bolt/v1/decoder/decoded_value.cpp b/src/communication/bolt/v1/decoder/decoded_value.cpp index 170e14e4d..12a0e298e 100644 --- a/src/communication/bolt/v1/decoder/decoded_value.cpp +++ b/src/communication/bolt/v1/decoder/decoded_value.cpp @@ -2,117 +2,38 @@ namespace communication::bolt { -bool DecodedValue::ValueBool() const { - if (type_ != Type::Bool) { - throw DecodedValueException(); +#define DEF_GETTER_BY_VAL(type, value_type, field) \ + value_type DecodedValue::Value##type() const { \ + if (type_ != Type::type) throw DecodedValueException(); \ + return field; \ } - return bool_v; -} -int64_t DecodedValue::ValueInt() const { - if (type_ != Type::Int) { - throw DecodedValueException(); - } - return int_v; -} +DEF_GETTER_BY_VAL(Bool, bool, bool_v) +DEF_GETTER_BY_VAL(Int, int64_t, int_v) +DEF_GETTER_BY_VAL(Double, double, double_v) -double DecodedValue::ValueDouble() const { - if (type_ != Type::Double) { - throw DecodedValueException(); - } - return double_v; -} +#undef DEF_GETTER_BY_VAL -const std::string &DecodedValue::ValueString() const { - if (type_ != Type::String) { - throw DecodedValueException(); +#define DEF_GETTER_BY_REF(type, value_type, field) \ + value_type &DecodedValue::Value##type() { \ + if (type_ != Type::type) throw DecodedValueException(); \ + return field; \ + } \ + const value_type &DecodedValue::Value##type() const { \ + if (type_ != Type::type) throw DecodedValueException(); \ + return field; \ } - return string_v; -} -const std::vector<DecodedValue> &DecodedValue::ValueList() const { - if (type_ != Type::List) { - throw DecodedValueException(); - } - return list_v; -} +DEF_GETTER_BY_REF(String, std::string, string_v) +DEF_GETTER_BY_REF(List, std::vector<DecodedValue>, list_v) +using map_t = std::map<std::string, DecodedValue>; +DEF_GETTER_BY_REF(Map, map_t, map_v) +DEF_GETTER_BY_REF(Vertex, DecodedVertex, vertex_v) +DEF_GETTER_BY_REF(Edge, DecodedEdge, edge_v) +DEF_GETTER_BY_REF(UnboundedEdge, DecodedUnboundedEdge, unbounded_edge_v) +DEF_GETTER_BY_REF(Path, DecodedPath, path_v) -const std::map<std::string, DecodedValue> &DecodedValue::ValueMap() const { - if (type_ != Type::Map) { - throw DecodedValueException(); - } - return map_v; -} - -const DecodedVertex &DecodedValue::ValueVertex() const { - if (type_ != Type::Vertex) { - throw DecodedValueException(); - } - return vertex_v; -} - -const DecodedEdge &DecodedValue::ValueEdge() const { - if (type_ != Type::Edge) { - throw DecodedValueException(); - } - return edge_v; -} - -bool &DecodedValue::ValueBool() { - if (type_ != Type::Bool) { - throw DecodedValueException(); - } - return bool_v; -} - -int64_t &DecodedValue::ValueInt() { - if (type_ != Type::Int) { - throw DecodedValueException(); - } - return int_v; -} - -double &DecodedValue::ValueDouble() { - if (type_ != Type::Double) { - throw DecodedValueException(); - } - return double_v; -} - -std::string &DecodedValue::ValueString() { - if (type_ != Type::String) { - throw DecodedValueException(); - } - return string_v; -} - -std::vector<DecodedValue> &DecodedValue::ValueList() { - if (type_ != Type::List) { - throw DecodedValueException(); - } - return list_v; -} - -std::map<std::string, DecodedValue> &DecodedValue::ValueMap() { - if (type_ != Type::Map) { - throw DecodedValueException(); - } - return map_v; -} - -DecodedVertex &DecodedValue::ValueVertex() { - if (type_ != Type::Vertex) { - throw DecodedValueException(); - } - return vertex_v; -} - -DecodedEdge &DecodedValue::ValueEdge() { - if (type_ != Type::Edge) { - throw DecodedValueException(); - } - return edge_v; -} +#undef DEF_GETTER_BY_REF DecodedValue::DecodedValue(const DecodedValue &other) : type_(other.type_) { switch (other.type_) { @@ -142,6 +63,12 @@ DecodedValue::DecodedValue(const DecodedValue &other) : type_(other.type_) { case Type::Edge: new (&edge_v) DecodedEdge(other.edge_v); return; + case Type::UnboundedEdge: + new (&unbounded_edge_v) DecodedUnboundedEdge(other.unbounded_edge_v); + return; + case Type::Path: + new (&path_v) DecodedPath(other.path_v); + return; } permanent_fail("Unsupported DecodedValue::Type"); } @@ -179,6 +106,12 @@ DecodedValue &DecodedValue::operator=(const DecodedValue &other) { case Type::Edge: new (&edge_v) DecodedEdge(other.edge_v); return *this; + case Type::UnboundedEdge: + new (&unbounded_edge_v) DecodedUnboundedEdge(other.unbounded_edge_v); + return *this; + case Type::Path: + new (&path_v) DecodedPath(other.path_v); + return *this; } permanent_fail("Unsupported DecodedValue::Type"); } @@ -216,6 +149,12 @@ DecodedValue::~DecodedValue() { case Type::Edge: edge_v.~DecodedEdge(); return; + case Type::UnboundedEdge: + unbounded_edge_v.~DecodedUnboundedEdge(); + return; + case Type::Path: + path_v.~DecodedPath(); + return; } permanent_fail("Unsupported DecodedValue::Type"); } @@ -238,7 +177,10 @@ DecodedValue::operator query::TypedValue() const { case Type::Map: return query::TypedValue( std::map<std::string, query::TypedValue>(map_v.begin(), map_v.end())); - default: + case Type::Vertex: + case Type::Edge: + case Type::UnboundedEdge: + case Type::Path: throw DecodedValueException( "Unsupported conversion from DecodedValue to TypedValue"); } @@ -265,6 +207,37 @@ std::ostream &operator<<(std::ostream &os, const DecodedEdge &edge) { return os << "}]"; } +std::ostream &operator<<(std::ostream &os, const DecodedUnboundedEdge &edge) { + os << "E[" << edge.type; + os << " {"; + PrintIterable(os, edge.properties, ", ", [&](auto &stream, const auto &pair) { + stream << pair.first << ": " << pair.second; + }); + return os << "}]"; +} + +std::ostream &operator<<(std::ostream &os, const DecodedPath &path) { + os << path.vertices[0]; + debug_assert(path.indices.size() % 2 == 0, + "Must have even number of indices"); + for (auto it = path.indices.begin(); it != path.indices.end();) { + auto edge_ind = *it++; + auto vertex_ind = *it++; + bool arrow_to_right = true; + if (edge_ind < 0) { + arrow_to_right = false; + edge_ind = -edge_ind; + } + + if (!arrow_to_right) os << "<"; + os << "-" << path.edges[edge_ind - 1] << "-"; + if (arrow_to_right) os << ">"; + os << path.vertices[vertex_ind]; + } + + return os; +} + std::ostream &operator<<(std::ostream &os, const DecodedValue &value) { switch (value.type_) { case DecodedValue::Type::Null: @@ -292,6 +265,38 @@ std::ostream &operator<<(std::ostream &os, const DecodedValue &value) { return os << value.ValueVertex(); case DecodedValue::Type::Edge: return os << value.ValueEdge(); + case DecodedValue::Type::UnboundedEdge: + return os << value.ValueUnboundedEdge(); + case DecodedValue::Type::Path: + return os << value.ValuePath(); + } + permanent_fail("Unsupported DecodedValue::Type"); +} + +std::ostream &operator<<(std::ostream &os, const DecodedValue::Type type) { + switch (type) { + case DecodedValue::Type::Null: + return os << "null"; + case DecodedValue::Type::Bool: + return os << "bool"; + case DecodedValue::Type::Int: + return os << "int"; + case DecodedValue::Type::Double: + return os << "double"; + case DecodedValue::Type::String: + return os << "string"; + case DecodedValue::Type::List: + return os << "list"; + case DecodedValue::Type::Map: + return os << "map"; + case DecodedValue::Type::Vertex: + return os << "vertex"; + case DecodedValue::Type::Edge: + return os << "edge"; + case DecodedValue::Type::UnboundedEdge: + return os << "unbounded_edge"; + case DecodedValue::Type::Path: + return os << "path"; } permanent_fail("Unsupported DecodedValue::Type"); } diff --git a/src/communication/bolt/v1/decoder/decoded_value.hpp b/src/communication/bolt/v1/decoder/decoded_value.hpp index b456286ba..3edef6b19 100644 --- a/src/communication/bolt/v1/decoder/decoded_value.hpp +++ b/src/communication/bolt/v1/decoder/decoded_value.hpp @@ -37,6 +37,26 @@ struct DecodedEdge { std::map<std::string, DecodedValue> properties; }; +/** + * Structure used when reading an UnboundEdge with the decoder. + * The decoder writes data into this structure. + */ +struct DecodedUnboundedEdge { + int64_t id; + std::string type; + std::map<std::string, DecodedValue> properties; +}; + +/** + * Structure used when reading a Path with the decoder. + * The decoder writes data into this structure. + */ +struct DecodedPath { + std::vector<DecodedVertex> vertices; + std::vector<DecodedUnboundedEdge> edges; + std::vector<int64_t> indices; +}; + /** * DecodedValue provides an encapsulation arround TypedValue, DecodedVertex * and DecodedEdge. This is necessary because TypedValue stores vertices and @@ -60,7 +80,9 @@ class DecodedValue { List, Map, Vertex, - Edge + Edge, + UnboundedEdge, + Path }; // constructors for primitive types @@ -86,6 +108,12 @@ class DecodedValue { DecodedValue(const DecodedEdge &value) : type_(Type::Edge) { new (&edge_v) DecodedEdge(value); } + DecodedValue(const DecodedUnboundedEdge &value) : type_(Type::UnboundedEdge) { + new (&unbounded_edge_v) DecodedUnboundedEdge(value); + } + DecodedValue(const DecodedPath &value) : type_(Type::Path) { + new (&path_v) DecodedPath(value); + } DecodedValue &operator=(const DecodedValue &other); DecodedValue(const DecodedValue &other); @@ -93,25 +121,29 @@ class DecodedValue { Type type() const { return type_; } - // constant getters - bool ValueBool() const; - int64_t ValueInt() const; - double ValueDouble() const; - const std::string &ValueString() const; - const std::vector<DecodedValue> &ValueList() const; - const std::map<std::string, DecodedValue> &ValueMap() const; - const DecodedVertex &ValueVertex() const; - const DecodedEdge &ValueEdge() const; +#define DECL_GETTER_BY_VALUE(type, value_type) \ + value_type Value##type() const; - // getters - bool &ValueBool(); - int64_t &ValueInt(); - double &ValueDouble(); - std::string &ValueString(); - std::vector<DecodedValue> &ValueList(); - std::map<std::string, DecodedValue> &ValueMap(); - DecodedVertex &ValueVertex(); - DecodedEdge &ValueEdge(); + DECL_GETTER_BY_VALUE(Bool, bool) + DECL_GETTER_BY_VALUE(Int, int64_t) + DECL_GETTER_BY_VALUE(Double, double) + +#undef DECL_GETTER_BY_VALUE + +#define DECL_GETTER_BY_REFERENCE(type, value_type) \ + value_type &Value##type(); \ + const value_type &Value##type() const; + + DECL_GETTER_BY_REFERENCE(String, std::string) + DECL_GETTER_BY_REFERENCE(List, std::vector<DecodedValue>) + using map_t = std::map<std::string, DecodedValue>; + DECL_GETTER_BY_REFERENCE(Map, map_t) + DECL_GETTER_BY_REFERENCE(Vertex, DecodedVertex) + DECL_GETTER_BY_REFERENCE(Edge, DecodedEdge) + DECL_GETTER_BY_REFERENCE(UnboundedEdge, DecodedUnboundedEdge) + DECL_GETTER_BY_REFERENCE(Path, DecodedPath) + +#undef DECL_GETTER_BY_REFERNCE // conversion function to TypedValue operator query::TypedValue() const; @@ -131,6 +163,8 @@ class DecodedValue { std::map<std::string, DecodedValue> map_v; DecodedVertex vertex_v; DecodedEdge edge_v; + DecodedUnboundedEdge unbounded_edge_v; + DecodedPath path_v; }; }; @@ -149,5 +183,8 @@ class DecodedValueException : public utils::BasicException { */ std::ostream &operator<<(std::ostream &os, const DecodedVertex &vertex); std::ostream &operator<<(std::ostream &os, const DecodedEdge &edge); +std::ostream &operator<<(std::ostream &os, const DecodedUnboundedEdge &edge); +std::ostream &operator<<(std::ostream &os, const DecodedPath &path); std::ostream &operator<<(std::ostream &os, const DecodedValue &value); +std::ostream &operator<<(std::ostream &os, const DecodedValue::Type type); } diff --git a/src/communication/bolt/v1/decoder/decoder.hpp b/src/communication/bolt/v1/decoder/decoder.hpp index db4dca3e9..6170c95a2 100644 --- a/src/communication/bolt/v1/decoder/decoder.hpp +++ b/src/communication/bolt/v1/decoder/decoder.hpp @@ -75,8 +75,29 @@ class Decoder { case Marker::Map32: return ReadMap(marker, data); - case Marker::TinyStruct3: - return ReadVertex(marker, data); + case Marker::TinyStruct3: { + // For tiny struct 3 we will also read the Signature to switch between + // vertex, unbounded_edge and path. Note that in those functions we + // won't perform an additional signature read. + uint8_t signature; + if (!buffer_.Read(&signature, 1)) { + DLOG(WARNING) << "[ReadVertex] Missing marker and/or signature data!"; + return false; + } + switch (static_cast<Signature>(signature)) { + case Signature::Node: + return ReadVertex(data); + case Signature::UnboundRelationship: + return ReadUnboundedEdge(data); + case Signature::Path: + return ReadPath(data); + default: + DLOG(WARNING) << "[ReadValue] Expected [node | unbounded_ege | " + "path] signature, received " + << signature; + return false; + } + } case Marker::TinyStruct5: return ReadEdge(marker, data); @@ -348,29 +369,12 @@ class Decoder { return true; } - bool ReadVertex(const Marker &marker, DecodedValue *data) { - uint8_t value; + bool ReadVertex(DecodedValue *data) { DecodedValue dv; DecodedVertex vertex; DLOG(INFO) << "[ReadVertex] Start"; - if (!buffer_.Read(&value, 1)) { - DLOG(WARNING) << "[ReadVertex] Missing marker and/or signature data!"; - return false; - } - - // check header - if (marker != Marker::TinyStruct3) { - DLOG(WARNING) << "[ReadVertex] Received invalid marker " - << (uint64_t)underlying_cast(marker); - return false; - } - if (value != underlying_cast(Signature::Node)) { - DLOG(WARNING) << "[ReadVertex] Received invalid signature " << value; - return false; - } - // read ID if (!ReadValue(&dv, DecodedValue::Type::Int)) { DLOG(WARNING) << "[ReadVertex] Couldn't read ID!"; @@ -432,7 +436,7 @@ class Decoder { // read ID if (!ReadValue(&dv, DecodedValue::Type::Int)) { - DLOG(WARNING) << "[ReadEdge] couldn't read ID!"; + DLOG(WARNING) << "[ReadEdge] Couldn't read ID!"; return false; } edge.id = dv.ValueInt(); @@ -471,5 +475,91 @@ class Decoder { return true; } + + bool ReadUnboundedEdge(DecodedValue *data) { + DecodedValue dv; + DecodedUnboundedEdge edge; + + DLOG(INFO) << "[ReadUnboundedEdge] Start"; + + // read ID + if (!ReadValue(&dv, DecodedValue::Type::Int)) { + DLOG(WARNING) << "[ReadUnboundedEdge] Couldn't read ID!"; + return false; + } + edge.id = dv.ValueInt(); + + // read type + if (!ReadValue(&dv, DecodedValue::Type::String)) { + DLOG(WARNING) << "[ReadUnboundedEdge] Couldn't read type!"; + return false; + } + edge.type = dv.ValueString(); + + // read properties + if (!ReadValue(&dv, DecodedValue::Type::Map)) { + DLOG(WARNING) << "[ReadUnboundedEdge] Couldn't read properties!"; + return false; + } + edge.properties = dv.ValueMap(); + + *data = DecodedValue(edge); + + DLOG(INFO) << "[ReadUnboundedEdge] Success"; + + return true; + } + + bool ReadPath(DecodedValue *data) { + DecodedValue dv; + DecodedPath path; + + DLOG(INFO) << "[ReadPath] Start"; + + // vertices + if (!ReadValue(&dv, DecodedValue::Type::List)) { + DLOG(WARNING) << "[ReadPath] Couldn't read vertices!"; + return false; + } + for (const auto &vertex : dv.ValueList()) { + if (vertex.type() != DecodedValue::Type::Vertex) { + DLOG(WARNING) << "[ReadPath] Received a '" << vertex.type() << "' element in the vertices list!"; + return false; + } + path.vertices.emplace_back(vertex.ValueVertex()); + } + + // edges + if (!ReadValue(&dv, DecodedValue::Type::List)) { + DLOG(WARNING) << "[ReadPath] Couldn't read edges!"; + return false; + } + for (const auto &edge : dv.ValueList()) { + if (edge.type() != DecodedValue::Type::UnboundedEdge) { + DLOG(WARNING) << "[ReadPath] Received a '" << edge.type() << "' element in the edges list!"; + return false; + } + path.edges.emplace_back(edge.ValueUnboundedEdge()); + } + + // indices + if (!ReadValue(&dv, DecodedValue::Type::List)) { + DLOG(WARNING) << "[ReadPath] Couldn't read indices!"; + return false; + } + for (const auto &index : dv.ValueList()) { + if (index.type() != DecodedValue::Type::Int) { + DLOG(WARNING) << "[ReadPath] Received a '" << index.type() << "' element in the indices list (expected an int)!"; + return false; + } + path.indices.emplace_back(index.ValueInt()); + } + + *data = DecodedValue(path); + + DLOG(INFO) << "[ReadPath] Success"; + + return true; + } }; } diff --git a/src/communication/bolt/v1/encoder/base_encoder.hpp b/src/communication/bolt/v1/encoder/base_encoder.hpp index dee9663fd..081163083 100644 --- a/src/communication/bolt/v1/encoder/base_encoder.hpp +++ b/src/communication/bolt/v1/encoder/base_encoder.hpp @@ -127,13 +127,16 @@ class BaseEncoder { } } - void WriteEdge(const EdgeAccessor &edge) { - WriteRAW(underlying_cast(Marker::TinyStruct) + 5); - WriteRAW(underlying_cast(Signature::Relationship)); + void WriteEdge(const EdgeAccessor &edge, bool unbound = false) { + WriteRAW(underlying_cast(Marker::TinyStruct) + (unbound ? 3 : 5)); + WriteRAW(underlying_cast(unbound ? Signature::UnboundRelationship + : Signature::Relationship)); WriteUInt(edge.temporary_id()); - WriteUInt(edge.from().temporary_id()); - WriteUInt(edge.to().temporary_id()); + if (!unbound) { + WriteUInt(edge.from().temporary_id()); + WriteUInt(edge.to().temporary_id()); + } // write type WriteString(edge.db_accessor().EdgeTypeName(edge.EdgeType())); @@ -147,8 +150,47 @@ class BaseEncoder { } } - void WritePath() { - // TODO: this isn't implemented in the backend! + void WritePath(const query::Path &path) { + // Prepare the data structures to be written. + // + // Unique vertices in the path. + std::vector<VertexAccessor> vertices; + // Unique edges in the path. + std::vector<EdgeAccessor> edges; + // Indices that map path positions to vertices/edges elements. Positive + // indices for left-to-right directionality and negative for right-to-left. + std::vector<int> indices; + + // Helper function. Looks for the given element in the collection. If found + // it puts it's index into `indices`. Otherwise emplaces the given element + // into the collection and puts that index into `indices`. A multiplier is + // added to switch between positive and negative indices (that define edge + // direction). + auto add_element = [&indices](auto &collection, const auto &element, + int multiplier, int offset) { + auto found = std::find(collection.begin(), collection.end(), element); + indices.emplace_back( + multiplier * (std::distance(collection.begin(), found) + offset)); + if (found == collection.end()) collection.emplace_back(element); + }; + + vertices.emplace_back(path.vertices()[0]); + for (uint i = 0; i < path.size(); i++) { + const auto &e = path.edges()[i]; + const auto &v = path.vertices()[i + 1]; + add_element(edges, e, e.to_is(v) ? 1 : -1, 1); + add_element(vertices, v, 1, 0); + } + + // Write data. + WriteRAW(underlying_cast(Marker::TinyStruct) + 3); + WriteRAW(underlying_cast(Signature::Path)); + WriteTypeSize(vertices.size(), MarkerList); + for (auto &v : vertices) WriteVertex(v); + WriteTypeSize(edges.size(), MarkerList); + for (auto &e : edges) WriteEdge(e, true); + WriteTypeSize(indices.size(), MarkerList); + for (auto &i : indices) WriteInt(i); } void WriteTypedValue(const query::TypedValue &value) { @@ -181,8 +223,7 @@ class BaseEncoder { WriteEdge(value.Value<EdgeAccessor>()); break; case query::TypedValue::Type::Path: - // TODO: this is not implemeted yet! - WritePath(); + WritePath(value.ValuePath()); break; } } diff --git a/src/query/path.hpp b/src/query/path.hpp index ca925e310..48214eaf6 100644 --- a/src/query/path.hpp +++ b/src/query/path.hpp @@ -48,6 +48,9 @@ class Path { Expand(others...); } + /** Returns the number of expansions (edges) in this path. */ + auto size() const { return edges_.size(); } + auto &vertices() { return vertices_; } auto &edges() { return edges_; } const auto &vertices() const { return vertices_; } diff --git a/src/storage/edge_accessor.cpp b/src/storage/edge_accessor.cpp index 227b06de2..86cf33a49 100644 --- a/src/storage/edge_accessor.cpp +++ b/src/storage/edge_accessor.cpp @@ -12,9 +12,18 @@ VertexAccessor EdgeAccessor::from() const { return VertexAccessor(current().from_, db_accessor()); } +bool EdgeAccessor::from_is(const VertexAccessor &v) const { + return v.operator==(¤t().from_); +} + VertexAccessor EdgeAccessor::to() const { return VertexAccessor(current().to_, db_accessor()); } + +bool EdgeAccessor::to_is(const VertexAccessor &v) const { + return v.operator==(¤t().to_); +} + bool EdgeAccessor::is_cycle() const { return ¤t().to_ == ¤t().from_; } diff --git a/src/storage/edge_accessor.hpp b/src/storage/edge_accessor.hpp index 948c52633..3580cf412 100644 --- a/src/storage/edge_accessor.hpp +++ b/src/storage/edge_accessor.hpp @@ -32,11 +32,19 @@ class EdgeAccessor : public RecordAccessor<Edge> { */ VertexAccessor from() const; + /** Checks if the given vertex is the source of this edge, without + * creating an additional accessor to perform the check. */ + bool from_is(const VertexAccessor &v) const; + /** * Returns an accessor to the destination Vertex of this edge. */ VertexAccessor to() const; + /** Checks ig the given vertex is the destination of this edge, without + * creating an additional accessor to perform the check. */ + bool to_is(const VertexAccessor &v) const; + /** Returns true if this edge is a cycle (start and end node are * the same. */ bool is_cycle() const; diff --git a/src/storage/record_accessor.hpp b/src/storage/record_accessor.hpp index 8d1105dbd..15857919c 100644 --- a/src/storage/record_accessor.hpp +++ b/src/storage/record_accessor.hpp @@ -100,6 +100,13 @@ class RecordAccessor : public TotalOrdering<RecordAccessor<TRecord>> { return vlist_ == other.vlist_; } + /** Enables equality check against a version list pointer. This makes it + * possible to check if an accessor and a vlist ptr represent the same graph + * element without creating an accessor (not very cheap). */ + bool operator==(const mvcc::VersionList<TRecord> *other_vlist) const { + return vlist_ == other_vlist; + } + /** * Returns a GraphDB accessor of this record accessor. *